The BeagleBone runs an Angstrom linux distro, which includes the special /dev/mem file, which provides access to the processor’s entire physical memory. More on /dev/mem in the linux man pages here. The BeagleBone is built around the TI AM3359 ARM Cortex-A8 microprocessor, and breaks out many of its I/O pins to two 2×23 pin female headers, including 4 UART ports, 7 ADC inputs, 4 PWM outputs, a whole bunch of GPIO pins and more. The use of these peripherals is very well documented in the AM335x Technical Reference Manual. The use of each module is done through reading and writing to different 16- and 32-bit memory registers, whose addresses are given in the manual. A Unix mmap can be used to create a memory map of the /dev/mem file, which allows read and write access to it through byte addressing. This way the module registers may be accessed by their addresses in section 2.1 of the reference manual.

The most basic module to use is the GPIO module. Other modules get more complicated, but they are all very well documented in the Technical Reference Manual. The biggest difference is that many of the other modules have clocks which must be enabled before they can be used. This is done using the clock module registers, and is described in detail in chapter 8.

I’ll walk through blinking one of the on-board LEDs using Python’s mmap module. Before starting, we need to know which pin to use. To do so, we want a copy of the BeagleBone schematic handy, which can be found in the links here or on the uSD card supplied with the BeagleBone. According to page 3 of the schematics, the USR1 LED is connected to the GPIO1_22 pin (easily found by searching ‘USR1‘ in the pdf), which means pin 22 in the GPIO1 module. Now it’s time for a little Python. First we need to import the mmap module, which is not on the BeagleBone by default, but can be installed by running ‘# opkg install python-mmap’, and defining a few addresses:

1
2
3
4
5
6
7
8
9
from mmap import mmap
import time, struct

GPIO1_offset = 0x4804c000
GPIO1_size = 0x4804cfff-GPIO1_offset
GPIO_OE = 0x134
GPIO_SETDATAOUT = 0x194
GPIO_CLEARDATAOUT = 0x190
USR1 = 1<<22

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 22 we need bit 22, or 1 shifted left 22 places.

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

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

Now we need to set the pin as an output. I’ll do this line by line:

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:

1
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:

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

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.

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

1
reg_status &= ~(USR1)

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

1
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:

1
2
3
4
5
6
7
8
9
for i in range(5):
  # Set it high:
  mem[GPIO_SETDATAOUT:GPIO_SETDATAOUT+4] = struct.pack("<L", USR1)
  time.sleep(0.5)
  # Set it low:
  mem[GPIO_CLEARDATAOUT:GPIO_CLEARDATAOUT+4] = struct.pack("<L", USR1)
  time.sleep(0.5)

mem.close()

Which will blink the LED 5 times then close the mmap and exit.

There is an issue here which is not at all obvious, which is that setting a pin as an output in the output enable register in no way guarantees that the corresponding physical pin will be connected to it. In fact, there is no guarantee that any pin will be as it is labeled in the schematics! This is due to the fact that the AM335x processors have a whole lot more module inputs and outputs than there are physical pins, so every external pin is actually the output of a multiplexer with up to 7 different possible modes. All this pinmuxing is handled by the AM335x control module. Of course there’s a catch, which is hiding in section 9.1:

Note: For writing to the control module registers, the Cortex A8 MPU will need to be in privileged mode of operation and writes will not work from user mode.

Luckily, thanks to the friendly BeableBone developers, there is a user-level workaround. There is a file for each external pin found in /sys/kernel/debug/omap_mux/. Writing to these files tells a driver to configure the pin multiplexers as desired. To find the proper file names is a bit of a pain, and requires one more document; the AM3359 datasheet, found here. The first step is to find the pin of interest in the Beaglebone schematics. The USRx LED outputs actually are muxed correctly when the board boots, so for this example I’ll choose a different GPIO pin. Lets say we want to use GPIO2_7, which is pin 46 on the P8 header, as found on page 11 of the schematics. We need to find where the pin is connected to the AM3359, which is easily done by searching ‘GPIO2_7‘. What we’re after is the long list of modes for the pin, which in this case is:

1
LCD_DATA1/GPMC_A1//EHRPWM2B//PR1_PRU1_PRU_R30_1/PR1_PRU1_PRU_R31_1/GPIO2_7

Now we open up the AM3359 datasheet and go to Table 2-7. What we’re looking for is the pin for which the first item in this list, in this case LCD_DATA1, is the first item in the ‘signal name’ column. Notice if you use the search function to find it, it shows up a few times in the ‘signal name’ column. These are different pins which can also be set to this function, but they are not the actual physical pin we are looking for. Once found, look down the ‘signal name’ column within the LCD_DAT1 row until you find GPIO2_7, and take note of the mode number just to the right of it (7 in this case). Also take note of the ‘pin name’ for mode 0, as this will be the name of the file we need to write to, in this case ‘lcd_data1′ (these are all upper-case in the schematics, but the file names are all lower-case). While here, take a look at the ‘buffer strength’ column. This is the maximum current the pins can source; only 6mA for the GPIO pins! This is usable for low-power LEDs and the such, but external transistors are generally a must for any sort of power supply switching.

Now it’s back to the reference manual for a minute, to Table 9-58 in section 9.3.1.50. This shows us everything we can configure with the control module files, including pullup and pulldown resistors, and ‘input enable’. The input enable has no effect on output behavior, so it can just be left set for the GPIO pins. So, what we want is to set the LCD_DAT1 multiplexer to mode 7 with rx_enabled. The pinmux files expect the desired value to be written in hexadecimal, so the binary value we want of 100111 (1<<5 for receiver enable | 111 for mode 7) must be written as its hex value of 27. This can be done at the command line:

1
# echo 27 > /sys/kernel/debug/omap_mux/lcd_data1

Or in Python:

1
2
with open("/sys/kernel/debug/omap_mux/lcd_data1", 'wb') as f:
  f.write('27')

And we can easily confirm the change with:

1
# cat /sys/kernel/debug/omap_mux/lcd_data1

So to show the whole process, here’s a script that will blink an LED connected to GPIO2_7 until ctrl-c is pressed (notice that the mmap offset has changed, as the GPIO pin used here is in the GPIO2 module, where as we were using GPIO1 before):

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
from mmap import mmap
import time, struct

GPIO2_offset = 0x481ac000
GPIO2_size = 0x481acfff-GPIO2_offset
GPIO_OE = 0x134
GPIO_SETDATAOUT = 0x194
GPIO_CLEARDATAOUT = 0x190
LED = 1<<7

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

with open("/sys/kernel/debug/omap_mux/lcd_data1", 'wb') as f:
  f.write('27')

reg = struct.unpack("<L", mem[GPIO_OE:GPIO_OE+4])[0]
mem[GPIO_OE:GPIO_OE+4] = struct.pack("<L", reg & ~LED)

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

except KeyboardInterrupt:
  mem.close()

I’ve been working on a Python library to make the tedious process above as painless as possible, which can be found at github.com/alexanderhiam/PyBBIO. As of writing this, it includes a simple API for the GPIO modules, the ADC, and the UART serial ports, as well as a few built-in libraries, like one for building simple web interfaces to control hardware IO. To show the difference, here’s the same script above, but this time using PyBBIO:

1
2
3
4
5
6
7
8
9
10
11
12
from bbio import *

LED = GPIO2_7

def setup():
  pinMode(LED, OUTPUT) # This does the pinmuxing automatically

def loop():
  toggle(LED)
  delay(500) # In milliseconds

run(setup, loop)
Share on Google+Tweet about this on TwitterShare on Facebook