Exploring Edison - Fast Memory Mapped I/O
Written by Harry Fairhead   
Wednesday, 23 September 2015
Article Index
Exploring Edison - Fast Memory Mapped I/O
Fast GIPO access
Fast Input
Complete program; Summary

Fast Mapped I/O

The most recent versions of mraa support another way to access the GPIO and it is much faster. 

Most of the delay in setting the GPIO line is due to mraa using the Linux SYSFS subsystem. SYSFS is a file system that can be used for all sorts of interfacing tasks. In this case SYSFS is being used to map the GPIO pins as if they were files in a file system. This has the advantage of making the GPIO available to almost anything that runs under Linux, but has the disadvantage of a high overhead. 

A faster way to work with the GPIO lines is to allow the software to write directly to the memory locations where the GPIO port registers live. This is a standard part of the SYSFS facility and there is a special file that memory maps the driver so writing to a particular address sets a given line high and to another address sets it low. 

Notice that using memory mapped I/O only changes the way the line is read or written. For all other operations such as setting the line's direction a SYSFS call is used. 

The installation of the memory map is something mraa can do and it will substitute memory mapped read and write function for any given pin.

The addresses and data masks are computed from scratch each time and there is a slight speed up to be gained by precomputing them and providing your own read/write functions for each pin. However the gains are hardly worth it - see later. 

You can set how any pin is accessed using the function:

mraa_gpio_use_mmaped(pin,1/0).

If the second parameter is 1 then the pin is accessed directly i.e. a fast memory mapped access. If the parameter is 0 the slower SYSFS interface is used.

Changing the program to use fast I/O on pin 31:

#include "mraa.h"
#include <stdio.h>
#include <unistd.h>


int main()
{
 mraa_gpio_context pin31=mraa_gpio_init(31);
 mraa_gpio_dir(pin31, MRAA_GPIO_OUT);
 mraa_gpio_use_mmaped(pin31,1);
 for (;;) {
  mraa_gpio_write(pin31, 0);
  mraa_gpio_write(pin31, 1);
 }
 return MRAA_SUCCESS;
}


With this change the line is toggled for approximately 0.25 microsecond high and 0.3 microseconds low which is around 60 times faster. The reason for the difference in the high and low times is that at this frequency capacitive effects become important and the wave form isn't a perfect square wave. The exact timing figures you get will depend on the logic thresholds used by the measuring device. 

Putting this another way the SYSFS approach can produce a 0.03 Mhz pulse train but memory mapping can produce a (close to) 2Mhz pulse train.

Of course we still have the problem that the program is running under a non- realtime operating system and therefore it will be interrupted and there will be jitter in the faster pulse train as well. 

The next step is to create pulses longer than 0.25 microseconds. 

There isn't much point in trying to use usleep because the overhead in yielding to the operating system is such that usleep(1) produces 98 microsecond pulse. In other words using usleep with fast memory map access produces pulses in the same sort of region as you can create using slow SYSFS.

If you want to use usleep with fast memory mapped mraa you can use the following formula to work the delay. If you want a pulse of width t microseconds delay for :

t' = 0.999*t - 92.5

microseconds in usleep. This is accurate from 100  to  800 microseconds.

If you want to generate pulses in the range 0.25 to 100 microsecond range then you have little option but to busy wait.  


#include "mraa.h"
#include <stdio.h>
#include <unistd.h>


int main()
{
 mraa_gpio_context pin31=mraa_gpio_init(31);
 mraa_gpio_dir(pin31, MRAA_GPIO_OUT);
 mraa_gpio_use_mmaped(pin31,1);
 int i;
 for (;;) {
  mraa_gpio_write(pin31, 0);
  for(i=1;i<7500;i++){};
  mraa_gpio_write(pin31, 1);
  for(i=1;i<7500;i++){};
 }
 return MRAA_SUCCESS;
}

 

The relationship between loop counter and pulse length is linear up to at least 100 microseconds. 

timingchart3

 

The formula for the number of loops needed to create a pulse of length t is:

n = 71.36*t - 21.276

So for a 10 microsecond pulse you need 692 loops. Not perfect, but a good start for manual trimming.

In short using fast memory mapped output and busy wait you can generate reasonably accurate 1 to 10 microsecond pulses. 

Notice that this doesn't mean we are home and dry when it comes to fast output. If you try to change multiple lines within a loop then the time for each loop increases and there will be phase shifts between the pulses generated. For example if you try:

#include <stdio.h>
#include <unistd.h>
int main(){ 
 mraa_init();
 
 mraa_gpio_context pin15 = mraa_gpio_init(15);
 mraa_gpio_dir(pin15, MRAA_GPIO_OUT_HIGH);
 mraa_gpio_use_mmaped(pin15,1);
 mraa_gpio_context pin31 = mraa_gpio_init(31);
 mraa_gpio_dir(pin31, MRAA_GPIO_OUT_LOW);
 mraa_gpio_use_mmaped(pin31,1);
 for (;;) {
  mraa_gpio_write(pin15, 0);
  mraa_gpio_write(pin31, 1);
  mraa_gpio_write(pin15, 1);
  mraa_gpio_write(pin31, 0);
 }
 return MRAA_SUCCESS;
}


Which is just the two pulse train generator given in the previous chapter but speeded up using memory mapped output you will discover that the pulse length increases to 0.5 microseconds i.e. double and you still have the same order or phase shift between the two pulse trains:

 

twopulse

 

Notice however that each pulse is 0.5 microseconds and the overlap is for a much shorter time - approximately 0.25 microsecond. 

However the comparison isn't completely fair. If you generate memory mapped pulses of the same sort of length as the SYSFS approach works with then things look a lot better:

 

pulsepahse2

Now it does look as if the pulse trains are out of phase and the overlap is smaller. The point is that memory mapping is not just for short pulse durations but for more accurate pulse generation. 

Without a register based access to the GPIO which would allow you to set multiple pin outs in one operation this is about as good as it gets. 

 

ExploringEdison

 

<ASIN:B00ND1KH42@COM>

<ASIN:B00ND1KNXM>

<ASIN:B00ND1KH10>



Last Updated ( Tuesday, 10 May 2016 )