Skip to content

Instantly share code, notes, and snippets.

@wware
Last active February 3, 2023 08:26
Show Gist options
  • Save wware/8150366 to your computer and use it in GitHub Desktop.
Save wware/8150366 to your computer and use it in GitHub Desktop.
# Taking this from http://www.alexanderhiam.com/tutorials/beaglebone-io-using-python-mmap/
# I haven't done any work on this yet.
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()

Why not just use sysfs?

Because it's slow. Using mmap with /dev/mem is way way faster, even in Python, and zippity-quick in C.

Python-mmap on the Raspberry Pi

Based on C code by Dom and Gert at http://elinux.org/RPi_Low-level_peripherals. Using mmap is potentially much faster than http://en.wikipedia.org/wiki/Sysfs, which is what almost all RPi tutorials recommend.

So where do these addresses and constants come from? See http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf, page 90. You'll want to read everything from page 90 to page 104 if you want to know what's going on. And take a look at the table of contents (pages 2 and 3) to see the chip's capabilities.

The ARM architecture is used for many different chips made by different manufacturers, and you can find open-source Verilog and VHDL implementations. How the manufacturers differentiate their products is by offering different sets of peripherals. Knowledge gained with one ARM implementation is largely transferable to another. The first ARM family I got acquainted with was the AT91SAM7 family, and the GPIO there isn't so different from Broadcom's BCM2835 used in the Raspberry Pi. The Broadcom chip has a hardware memory mapper which the SAM7 family lacked, so one couldn't run Linux on the SAM7 chips.

Also see my RPi hacking repository on Github, where I did some mmap hacking in a C module of the xxmodule.c variety.

The Beaglebone Black version

My example for the Beaglebone Black is taken from an excellent blog post by Alexander Hiam. The BBB uses a different ARM chip, the Sitara AM335x from TI. The Technical Reference Manual contains the information we need to talk to the GPIO.

Section 2 contains the memory map for the chip, and on page 181 we see that 0x481AC000 is where to find the registers to control GPIO2, the third GPIO block. Those registers are listed in section 25.4.1 on page 4871. The following pages, all the way out to 4897, detail the different bits and fields contained in those registers.

#!/usr/bin/env python
# How to access GPIO registers via Python & mmap on the Raspberry-Pi
# Adapted from C program by Dom and Gert, 15-Feb-2013
# See http://elinux.org/RPi_Low-level_peripherals
# You'll also need
# http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
# to follow what's going on. Page numbers refer to this PDF.
import mmap
import struct
import time
BCM2708_PERI_BASE = 0x20000000
GPIO_BASE = (BCM2708_PERI_BASE + 0x200000) # GPIO controller
PAGE_SIZE = (4*1024)
BLOCK_SIZE = (4*1024)
# Set up gpio pointer for direct register access. When we perform the mmap.mmap() call below,
# this will become an array of bytes which is memory-mapped into the physical address space of
# the processor. Normal Python array operations (e.g. slices) are available.
gpio = []
def read_from_ptr(ofs):
# Read a 32-bit word (four bytes)
return struct.unpack("<L", gpio[4*ofs:4*ofs+4])[0]
def write_to_ptr(ofs, data):
# Write a 32-bit word (four bytes)
gpio[4*ofs:4*ofs+4] = struct.pack("<L", data)
# GPIO setup macros. The function select registers shown on pages 92 thru 94 assign 3 bits
# to control each GPIO pin, and have 10 pins per register. That's why you see "g / 10" and
# "((g % 10) * 3)" so frequently in the following functions. The three bit codes are 000
# for input, 001 for output, and 010-111 map to alternate functions 0 thru 5, described on
# pages 102 and 103. In Dom and Gert's code, it was necessary to call INP_GPIO on any pin
# before using OUT_GPIO or SET_GPIO_ALT in order to set the three bits to a known value (000)
# but I've rewritten those functions to clear the bits before ORing in the new value.
def INP_GPIO(g):
shift = ((g % 10) * 3)
x = read_from_ptr(g / 10)
x &= ~(7 << shift)
write_to_ptr(g / 10, x)
def OUT_GPIO(g):
shift = ((g % 10) * 3)
x = read_from_ptr(g / 10)
x = (x & ~(7 << shift)) | (1 << shift)
write_to_ptr(g / 10, x)
def SET_GPIO_ALT(g, a, m={0:4, 1:5, 2:6, 3:7, 4:3, 5:2}):
# See three bit codes at the top of page 92
shift = ((g % 10) * 3)
x = read_from_ptr(g / 10)
x = (x & ~(7 << shift)) | (m[a] << shift)
write_to_ptr(g / 10, x)
def GPIO_SET(data):
# Set bits which are 0, ignore bits which are 1
write_to_ptr(7, data) # See GPSET0 register on page 90
def GPIO_CLR(data):
# Clears bits which are 1, ignore bits which are 0
write_to_ptr(10, data) # See GPCLR0 register on page 90
if __name__ == '__main__':
with open('/dev/mem', 'r+b') as f:
gpio = mmap.mmap(f.fileno(), BLOCK_SIZE, offset=GPIO_BASE)
# Switch GPIO 7..11 to output mode
for g in range(7, 12):
OUT_GPIO(g)
# flash some LEDs
for rep in range(5):
for g in range(7, 12):
GPIO_SET(1 << g)
time.sleep(0.25)
for g in range(7, 12):
GPIO_CLR(1 << g)
time.sleep(0.25)
@stogoh
Copy link

stogoh commented Dec 6, 2018

I can't get it working on Tinkerboard. In the manual of the chip it says, that the offset for the GPIO5 is 0xFF7C0000. But it's outputting following:

Traceback (most recent call last):
  File "test-mmap-gpio.py", line 47, in <module>
    OUT_GPIO(g)
  File "test-mmap-gpio.py", line 28, in OUT_GPIO
    x = read_from_ptr(g / 10)
  File "test-mmap-gpio.py", line 14, in read_from_ptr
    return struct.unpack("<L", gpio[4 * ofs:4 * ofs + 4])[0]
TypeError: slice indices must be integers or None or have an __index__ method

Can someone help me?

http://opensource.rock-chips.com/images/8/8f/Rockchip_RK3288_TRM_V1.2_Part1-20170321.pdf

@jk987
Copy link

jk987 commented Feb 3, 2023

Couple notes:

For Raspbbery Pi 3 the peripheral base is 0x3f000000 instead of 0x20000000.

And all those shl -> GPIO_SET -> write_to_ptr -> struct.pack make it slower then using default RPi.GPIO.output().
But if you prepare struct in advance and do just gpio[x:x+4] = s then you are faster.

Anyway great example and thank you very much for it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment