EBC Exercise 11b gpio via mmap

From eLinux.org
Jump to: navigation, search

thumb‎ Embedded Linux Class by Mark A. Yoder


In previous exercises (EBC Exercise 10 Flashing an LED and EBC Exercise 11 gpio Polling and Interrupts) we saw how to interact with the general purpose I/O pins via sysfs files. These are rather easy ways to work with gpio; however they tend to be slow and require a lot of the CPU. In this exercise we explore accessing gpio directly via mmap. First we'll do it via devmem2 and the command line and later via a C program.

Memory Map

One of the nice things about using gpio via sysfs is you don't need to know little details like what gpio pin is mapped to what memory address. Now you need to know those details. Let's flash the USR3 LED by directly writing to memory. First turn off the trigger.

bone$ cd /sys/class/leds/beaglebone\:green\:usr3
bone$ echo none > trigger
bone$ echo 1 > brightness

The USR3 LED should be on. Now find which gpio it is attached to.

bone$ gpioinfo | less

Search for usr

gpiochip1 - 32 lines:
        line   0:   "GPMC_AD0"      "P8_25"   input  active-high [used]
        line   1:   "GPMC_AD1"      "P8_24"   input  active-high [used]
...
        line  16:    "GPMC_A0"      "P9_15"   input  active-high [used]
        line  17:    "GPMC_A1"      "P9_23"   input  active-high [used]
        line  18:    "GPMC_A2"      "P9_14"   input  active-high [used]
        line  19:    "GPMC_A3"      "P9_16"   input  active-high [used]
        line  20:    "GPMC_A4"       unused   input  active-high 
        line  21:    "GPMC_A5" "beaglebone:green:usr0" output active-high [used]
        line  22:    "GPMC_A6" "beaglebone:green:usr1" output active-high [used]
        line  23:    "GPMC_A7" "beaglebone:green:usr2" output active-high [used]
        line  24:    "GPMC_A8" "beaglebone:green:usr3" output active-high [used]
        line  25:    "GPMC_A9"       unused   input  active-high 
        line  26:   "GPMC_A10"       unused   input  active-high 

It's attached to gpio port (chip) 1, bit (line) 24. To find the address of this register, look up the am335x Technical Reference Manual (Google it). Look for GPIO1 in the Memory Map table. You'll see its base address is 0x4804_C000. Click on the GPIO1 link and you'll see Table 25-5. GPIO REGISTERS. This shows you what to add to the base address to get the various registers for GPIO1. For example, to see how the 32 GPIO1 pins are set you read the GPIO_DATAOUT register who's offset is 0x13Ch. Therefore you want to access 0x4804c000 + 0x13c = 0x4804c13c.

devmem2

(See if memtool https://manpages.ubuntu.com/manpages/xenial/man8/memtool.8.html will work instead of devmem2.)

An easy way to read the contents of a memory location is with devmem2. First install it with:

bone$ sudo apt install ti-devmem2

If that doesn't work, try:

bone$ wget https://bootlin.com/pub/mirror/devmem2.c
bone$ gcc devmem2.c –o devmem2
bone$ sudo mv devmem2 /usr/bin

Now use it

bone$ sudo devmem2 0x4804c13c
/dev/mem opened.
Memory mapped at address 0xb6f99000.
Read at address  0x4804C13C (0xb6f9913c): 0x01800000

The address passed is the physical address. devmem2 returns the virtual address along with the contents of the memory location. USR3 is gpio1_24, which is the 24th bit of this value, which is 1 if the LED is on.

The GPIO_CLEARDATAOUT (0x190) register is used to clear given bits in a register.

bone$ sudo devmem2 0x4804c190 w 0x01000000
/dev/mem opened.
Memory mapped at address 0xb6f53000.
Read at address  0x4804C190 (0xb6f53190): 0x01800000
Write at address 0x4804C190 (0xb6f53190): 0x01000000, readback 0x01000000

The LED should be off now. Turn it on using the GPIO_SETDATAOUT (0x194) register.

bone$ sudo devmem2 0x4804c194 w 0x01000000
/dev/mem opened.
Memory mapped at address 0xb6f9f000.
Read at address  0x4804C194 (0xb6f9f194): 0x00800000
Write at address 0x4804C194 (0xb6f9f194): 0x01800000, readback 0x01800000

The USR3 LED should be back on again.

mmap via c

devmem2 is an easy way to access memory mapped devices from the shell prompt, but if you need speed, you need access from a C program. mmap is a way of mapping an address space into a user-space program. For example, the following code uses mmap to point gpio_addr to the base address for GPIO1. It then computes the addresses for the SETDATAOUT and CLEARDATAOUT registers.

Note: This file can be found in exercises/sensors/mmap/gpioToggle.c

#define GPIO1_START_ADDR 0x4804C000
#define GPIO1_END_ADDR   0x4804e000
#define GPIO1_SIZE (GPIO1_END_ADDR - GPIO1_START_ADDR)

#define GPIO_SETDATAOUT 0x194
#define GPIO_CLEARDATAOUT 0x190
#define USR3 (1<<24)

volatile void *gpio_addr;
volatile unsigned int *gpio_setdataout_addr;
volatile unsigned int *gpio_cleardataout_addr;
int fd = open("/dev/mem", O_RDWR);
gpio_addr = mmap(0, GPIO1_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_START_ADDR);

gpio_setdataout_addr   = gpio_addr + GPIO_SETDATAOUT;
gpio_cleardataout_addr = gpio_addr + GPIO_CLEARDATAOUT;

gpio_setdataout_addr points to the register that sets the GPIO1 pins, therefore USR3 LED can be turned on with

*gpio_setdataout_addr = USR3;

and turned off with

*gpio_cleardataout_addr = USR3;

mmap via python

Here's a python version of mmap().

From: https://graycat.io/tutorials/beaglebone-io-using-python-mmap/

Note: This file can be found in exercises/sensors/mmap/gpioToggle.py

from mmap import mmap
import time, struct 

Mapping the entire /dev/mem file would require that over a gigabyte be allocated in Python's heap, so the offset address and size variables are used to keep the mmap as small as possible, in this case just the GPIO1 register. These values are straight out of the memory map in section 2.1 of the Technical Reference Manual. the GPIO_OE, GPIO_SETDATAOUT and GPIO_CLEARDATAOUT addresses are found in section 25.4, which shows the address offsets of each register within the GPIO modules, starting from the base module address. Chapter 25 explains how to use the GPIO registers. All we need to do is set a pin as an output, then set and clear its output state. To do the first, we need the 'output enable' register (GPIO_OE above). Then the GPIO_SETDATAOUT and GPIO_CLEARDATAOUT registers will do the rest. Each one of these registers is 32 bits long, each bit of which corresponding to one of 32 GPIO pins, so for pin 24 we need bit 24, or 1 shifted left 24 places.

GPIO1_offset = 0x4804c000
GPIO1_size = 0x4804cfff-GPIO1_offset
GPIO_OE = 0x134
GPIO_SETDATAOUT = 0x194
GPIO_CLEARDATAOUT = 0x190
USR3 = 1<<24

Next we need to make the mmap, using the desired size and offset:

with open("/dev/mem", "r+b" ) as f:
  mem = mmap(f.fileno(), GPIO1_size, offset=GPIO1_offset)

The mmap is addressed byte by byte, so we can't just set a single bit. The easiest thing to do is grab the whole 4-byte register:

packed_reg = mem[GPIO_OE:GPIO_OE+4]

We now have 32 bits packed into a string, so to do any sort of bitwise operations with it we must unpack it: The 'L' tells struct.unpack() to unpack the string into an unsigned long, which will give us the full 32-bit register. The '<' tells it that the string is packed little-endian, or least-significant byte first. The BeagleBone's memory is little-endian, so if we tell this to struct.unpack() it will return the 32 bits in the order they are shown in the reference manual register maps.

reg_status = struct.unpack("<L", packed_reg)[0]

We now have the 32-bit integer value of the register, so we can configure the LED as an output by clearing its bit:

reg_status &= ~(USR3)

Now all that's left to do is to pack it little-endian back into a string and update the mmap:

mem[GPIO_OE:GPIO_OE+4] = struct.pack("<L", reg_status)

Now that we know the pin is configured as an output, it's time to get blinking. We could use the GPIO_DATAOUT register to do this, but we would want to preserve the state of all the other bits in it, so we would need to do the same process of unpacking, manipulating then repacking. That's what the SETDATAOUT and CLEARDATAOUT registers are for. Writes to them affect only the pins whose bits are set to 1, making the next step much easier:

try:
  while(True):
    mem[GPIO_SETDATAOUT:GPIO_SETDATAOUT+4] = struct.pack("<L", USR3)
    time.sleep(0.5)
    mem[GPIO_CLEARDATAOUT:GPIO_CLEARDATAOUT+4] = struct.pack("<L", USR3)
    time.sleep(0.5)

Then run with:

sudo ./LEDtoggle.py

Hit ^C to stop.

gpioToggle.c

gpioToggle.c is a complete example using mmap. Run it with

beagle$ cd ~/exercises/sensors/mmap
beagle$ make
cc -O3 -g  -o gpioThru gpioThru.c
cc -O3 -g  -o gpioToggle gpioToggle.c
beagle$ sudo ./gpioToggle
Mapping 4804C000 - 4804E000 (size: 2000)
GPIO mapped to 0xb6f40000
GPIO OE mapped to 0xb6f40134
GPIO SETDATAOUTADDR mapped to 0xb6f40194
GPIO CLEARDATAOUT mapped to 0xb6f40190
GPIO1 configuration: F60FFFFF
GPIO1 configuration: F60FFFFF
Start blinking LED USR3
^C
Ctrl-C pressed, cleaning up and exiting...

Notice it reports addresses 0x4804C000 and 0xb6f40000. The first is the physical address of the GPIO1 base register. 0xb6f40000 is the virtual address of the same register. Running gpioToggle again will have the same physical address, but the virtual address might change.

gpioThru

gpioThru using mmap to read an input pin and write it to an output pin. You can use this program to see how quickly the Beagle can respond to an input. Be sure to run setup.gpioThru.sh before the first time so the pin directions will be set.

MmapGPIOdelay.png
beagle$ ./setup.gpioThru.sh
beagle$ ./gpioThru
Mapping 44E07000 - 44E09000 (size: 2000)
GPIO mapped to 0xb6f36000
GPIO OE mapped to 0xb6f36134
GPIO SETDATAOUTADDR mapped to 0xb6f36194
GPIO CLEARDATAOUT mapped to 0xb6f36190
Start copying GPIO_07 to GPIO_03
^C
Ctrl-C pressed, cleaning up and exiting...

I'm seeing delays between 220ns and 600ns. The gpio subsystem runs at 100MHz, so I would expect delays in the 10's of ns, not 100's.




thumb‎ Embedded Linux Class by Mark A. Yoder