October 29, 2013

Register access to the GPIOs of the Beaglebone via memory mapping

On the Beaglebone Black, Linux abstracts the GPIO pins as files: To control a given pin, you can write to certain files. For example, if you want to turn off the blue LED on the far side of the board, navigate via command line to /sys/class/leds/beaglebone:green:usr0. The trigger file in this directory controls the behaviour of the LED. By default, it is set to heartbeat.The command

echo none > trigger

turns the LED off, whereas

echo default-on > trigger

turns it on. You can find more information at many places, for example here.
However, this method is fairly slow and -at least for me - somewhat unsatisfying since you do not see at all what is going on at the hardware level. The achievable toggle speed of a GPIO pin via the Linux file method is something like 5 kHZ, which is painfully slow. To achieve higher speeds and work closer to the hardware, we can use memory mapping and directly access the GPIO registers. For the following sample program, you should connect a LED to Pin 28 on GPIO board 1, which is pin 12 on Header 9. Make sure not to connect the LED directly - use a transistor instead; the GPIO pins can only supply 4 mA. Also make sure that the usr0-LED is off as explained above. The program will first blink the LED at pin 28 and the usr0 LED 5 times; then it turns pin 28 into an input which is polled once per second for 20 seconds, and the usr0-LED is switched on and off accordingly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <iostream>
#define OE_ADDR 0x134
#define GPIO_DATAOUT 0x13C
#define GPIO_DATAIN 0x138
#define GPIO0_ADDR 0x44E07000
#define GPIO1_ADDR 0x4804C000
#define GPIO2_ADDR 0x481AC000
#define GPIO3_ADDR 0x481AF000
using namespace std;

int main(){
    int fd = open("/dev/mem",O_RDWR | O_SYNC);
    ulong* pinconf1 =  (ulong*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_ADDR);
    pinconf1[OE_ADDR/4] &= (0xFFFFFFFF ^ (1 << 28));
    for(int i = 0 ; i < 5; i++){
     pinconf1[GPIO_DATAOUT/4]  |= (1 << 28);
     pinconf1[GPIO_DATAOUT/4]  ^= (1 << 21);
     sleep(1);
     pinconf1[GPIO_DATAOUT/4]  ^= (1 << 28);
     pinconf1[GPIO_DATAOUT/4]  |= (1 << 21);
     sleep(1);
    }
    pinconf1[GPIO_DATAOUT/4]  &= (0xFFFFFFFF ^ ((1 << 21) | (1 << 28)));
    pinconf1[OE_ADDR/4] |= ( 1 << 28);
    for(int i = 0; i < 20; i++){
     cout << pinconf1[GPIO_DATAIN/4]  << endl;
     if(pinconf1[GPIO_DATAIN/4] & (1  << 28)){
      cout << "on" <<endl;
      pinconf1[GPIO_DATAOUT/4]  |= (1 << 21);
     }
     else{
      cout << "off" <<endl;
      pinconf1[GPIO_DATAOUT/4] &= (0xFFFFFFFF ^ (1 << 21));
     }
     sleep(1);
    }
    pinconf1[GPIO_DATAOUT/4]  ^= (1 << 21);
    return 0;
}
Now, what precisely does this program do? The magic numbers appearing in the defines are the memory addresses of the registers controlling the GPIO pins, so we have a look into the technical reference manual of the AM335x-processor used in the Beaglebone Black, which happens to be a 4600 pages document. In Chapter 2, Memory Map, you find a long list with the memory addresses of the various registers controlling the behaviour of the processor. Scrolling down a few pages, you find the register address of GPIO1 is 0x4804C000, which we defined to be GPIO1_ADDR in oyr program. In my version of the reference manual, you find GPIO1 on page 211, but that may change in future versions of the reference manual.

So the registers at and directly after the address 0x4804C000 control the behaviour of the pins of GPIO1. This alone is not yet helpful, so we turn to chapter 25, General Purpose Input/Output, for more details. In the GPIO registers subsection, we find the addresses and descriptions of the various registers. The addresses given there are relative to the beginning of the GPIO1 registers, respectively the beginning of the GPIO0 and GPIO2 registers. For example, the GPIO_OE register has a relative address of 0x134, so the GPIO_OE register for GPIO1 will be at 0x4804C000+0x134 = 0x4804C134. Going back to the memory map in chapter 2 of the reference manual, we find that the GPIO0 registers start at 0x44E07000, so the GPIO_OE for GPIO0 will be at 0x44E07134, and similarly for GPIO2.

The GPIO_OE register is the register controlling whether a pin is an input or output. It is a 32bit register whose k-th bit corresponds to pin k of GPIO1. If this bit is 1 (as it is by default), the pin is an input; if it is zero, the pin is an output. The GPIO_DATAIN register is for reading the value of an input pin: If pin k is an input, its k-th bit is 0 if pin k is low and 1 if it is high. If pin k is an output, GPIO_DATAOUT is for setting the pin: Writing a 0 to bit k sets pin k low, writing 1 sets it high. Alternatively, you can use the GPIO_CLEARDATAOUT and GPIO_SETDATAOUT registers: writing a 1 to bit k clears respectively sets pin k.

Now we know which registers we have to control, but how can we actually control them? We use memory mapping to get a pointer which points to the beginning of GPIO1. This is achieved in the lines

int fd = open("/dev/mem",O_RDWR | O_SYNC);
ulong* pinconf1 = (ulong*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_ADDR);

This "maps the memory" at the physical location GPIO1_ADDR to the pointer pinconf1; we make it a pointer to ulong since a long happens to be 32bit on the Beaglebone and the registers are 32 bit. Note that the offset addresses of the various registers are in bytes, not in registers: This is why we have to divide by 4 later on in the code  since an ulong is 4 byte. For all practical purposes, pinconf1 is now a pointer pointing to the address GPIO1_ADDR; however, a "normal" pointer can only point to the memory allocated to the process, so we have to use memory mapping. The NULL argument to mmap specifies that Linux is free to put the memory map wherver it wants in the memory of the process; PROT_READ | PROT_WRITE specify that we can both read and write to the memoryand map_shared says that we actually access the underlying memory and not only the map in the memory of the process when we write to the memory map. The file /dev/mem we pass via the variable fd happens to be the place where Linux abstracts the phzsical memory as a file. The GPIO1_ADDR specifies where the memory map starts and the number 0x1000 = 4096 specifies its length - in this case, we map the 4 kB following 0x4804c000. If you consult the technical reference manual again, you will see that the GPIO registers for each GPIO board total 4kB, so we have precisely mapped all the registers for GPIO1.

The program is hopefully fairly self-explanatory now: pinconf1[OE_ADDR/4] is the memory at address 0x4804c000 (where our mmap begins) + 0x134 (the offset of OE). Remember that we have to divide by 4 since pinconf1 is an ulong pointer and the offsets are in bytes. Now pinconf1[OE_ADDR/4] &= (0xFFFFFFFF ^ (1 << 28)) sets pin 28 of GPIO1 to be an output and all other to inputs. Then pinconf1[GPIO_DATAOUT/4] |= (1 << 28) sets pin 28 to high. The register pinconf1[GPIO_DATAIN/4] stores the input values of the various pins; we read bit 28 from this register, which means reading the input of pin 28, and process the result accordingly.
All in all, this is a much neater and faster way to access the GPIO pins of the Beaglebone. Be aware, however, that not all pins can be accessed directly; some of the pins are by default reserved for non-GPIO stuff and have to be enabled via the device tree, which is a story for another day.

6 comments:

  1. Thanks for posting this - I've been searching a while for a clear explanation of mmap and how to speed up the IO on beaglebone black... This is a great explanation!

    ReplyDelete
  2. Thanks for this, very helpful. However please note that GPIO3_ADDR should be 0x481AE000

    ReplyDelete
  3. Many many thanks for this great contribution. I like it very much and mainly the way it is presented, i.e. succinct, concise and clear.
    Awaiting other input form you on BeagleBone Black...

    ReplyDelete
  4. Thanks a lot! I use the same principles for writing to the RTC registers.

    ReplyDelete
  5. Thanks a lot.. Finally found in detail documentation...

    ReplyDelete