Most single board computers based on an ARM CPU have a set of general-purpose I/O (GPIO) pins that you can use to control homemade electronics projects. In this chapter, we look at the GPIO ports on a Raspberry Pi. We will run the 64-bit version of Kali Linux. Most of the Raspberry Pi starter kits include a breadboard and a few electronic components to play with. In this chapter, we will look at programming GPIO pins from Assembly Language.
We will experiment with a breadboard containing several LEDs and resistors, so we can write some real code. We will program the GPIO pins in two ways: first, by using the Linux device driver and, second, by accessing the GPIO controller’s registers directly.
GPIO Overview
Pins 1 and 17: Provide +3.3V DC power
Pins 2 and 4: Provide +5V DC power
Pins 6, 9, 14, 20, and 25: Provide electrical ground
Pins 3, 5, 7–8, 10–13, 15, 16, 18, 19, 21–24, and 26: Are programmable general purpose
For the programmable pins, we can use them for output, where we control whether they output power or not (binary 1 or 0). We can read them to see if power is provided, for instance, if it is connected to a switch.
However, this isn’t all there is to GPIO; besides the functions we’ve talked about so far, a number of the pins have alternate functions that you can select programmatically. For instance, pins 3 and 5 can support the I2C standard that allows two microchips to talk to each other.
There are pins that can support two serial ports which are handy for connecting to radios or printers. There are pins that support pulse width modulation (PWM) and pulse-position modulation (PPM), which convert digital to analog and are handy for controlling electric motors.
For our first program, we’re going to let Linux do the heavy lifting for us. This will be typical for how to control hardware when there is a device driver available.
In Linux, Everything Is a File
The model for controlling devices in Linux is to map each device to a file. The file appears under either /dev or /sys and can be manipulated with the same Linux service calls that operate on regular files. The GPIO pins are no different. There is a Linux device driver for them that controls the pin’s operations via application programs opening files, then reads and writes data to them.
The files to control the GPIO pins all appear under the /sys/class/gpio folder. By writing short text strings to these files, we control the operation of the pins.
/sys/class/gpio/gpio17/direction: Used to specify whether the pin is for input or output
/sys/class/gpio/gpio17/value: Used to set or read the value of the pin
/sys/class/gpio/gpio17/edge: Used to set an interrupt to detect value changes
/sys/class/gpio/gpio17/active_low: Used to invert the meaning of 0 and 1
The next thing we do is set the direction for the pin, either use it for input or for output. We either write “in” or “out” to the direction file to do this.
Now we can write to the value file for an output pin or read the value file for an input pin. To turn on a pin, we write “1” to value, and to turn it off, we write “0.” When activated, the GPIO pin provides +3.3V.
When we are done with a pin, we should write its pin number to /sys/class/gpio/unexport. However, this will be done automatically when our program terminates.
We can do all this with the macros we created in Chapter 7, “Linux Operating System Services,” in fileio.S. In fact, by providing this interface, you can control the GPIO pins via any programming language capable of reading and writing files, which is pretty much every single one.
Flashing LEDs
To demonstrate programming the GPIO, we will connect some LEDs to a breadboard and then make them flash in sequence.
We will connect each of three LEDs to a GPIO pin (in this case 17, 27, and 22), then to ground through a resistor. We need the resistor because the GPIO is specified to keep the current under 16mA, or you can damage the circuits.
Most of the kits come with several 220 Ohm resistors. By Ohm’s law, I = V / R, these would cause the current to be 3.3V/220Ω = 15mA, so just right. You need to have a resistor in series with the LED since the LED’s resistance is quite low (typically around 13 Ohms and variable).
LEDs have a positive and negative side. The positive side needs to connect to the GPIO pin; reversing it could damage the LED.
Macros to control the GPIO pins
Main program to flash the LEDs
This program is a straightforward application of the Linux system service calls we learned in Chapter 7, “Linux Operating System Services.”
Under Kali Linux, the /sys/class/gpio files have restricted access, so you either need to run your program using sudo.
Moving Closer to the Metal
For Assembly Language programmers, the previous example is not satisfying. When we program in Assembly Language, we are usually directly manipulating devices for performance reasons, or to perform operations that simply can’t be done in high-level programming languages. In this section, we will interact with the GPIO controller directly.
Make sure you back up your work before running your program, since you may need to power off and power back on again. The GPIO controller controls 54 pins, the Raspberry Pi only exposes either 26 or 40 of them, depending on the Pi model, and for external use, many of the others are used by the Raspberry Pi for other important tasks. In the previous section, the device driver provided a level of protection, so we couldn’t easily do any damage. Now that we are writing directly to the GPIO controller, we have no such protection; if we make a mistake and manipulate the wrong pins, we may interfere with the Raspberry Pi’s operation and cause it to crash or lock up.
Virtual Memory
We looked at how to access memory in Chapter 5, “Thanks for the Memories,” and the memory addresses our instructions are stored at in gdb. These memory addresses aren’t physical memory addresses; rather they’re virtual memory addresses. As a Linux process, our program is given a large virtual address space that we can expand well beyond the amount of physical memory. Within this address space, some of it is mapped to physical memory to store our Assembly instructions, our .data sections, and our 8MB stack. Furthermore, Linux may swap some of this memory to secondary storage like the SD Card as it needs more physical memory for other processes. There is a lot of complexity in the memory management process to allow dozens of processes to run independently of each other, each thinking it has the whole system to itself.
In the next section, we want access to specific physical memory addresses, but when we request that access, Linux returns a virtual memory pointer that is different than the physical address we asked for. This is okay, as behind the scenes the memory management hardware in the Raspberry Pi will be doing the memory translations between virtual and physical memory for us.
In Devices, Everything Is Memory
The GPIO controller has 41 registers; however, we can’t read or write these like the ARM CPU’s registers. The ARM instruction set doesn’t know anything about the GPIO controller and there are no special instructions to support it. The way we access these registers is by reading and writing to specific memory locations. There is circuitry in the Raspberry Pi’s system on a chip (SoC) that will see these memory reads and writes and redirect them to the GPIO’s registers. This is how most hardware communicates.
The output of dmesg could be quite long. Use
or something similar to scan for this entry.
This is a kernel message from initializing the Broadcom bcm2835 GPIO controller chip, which gives the useful information of where the registers are.
Sounds easy—we know how to load addresses into registers, then reference the memory stored there. Not so fast, if we tried this, our program would just crash with a memory access error. This is because these memory addresses are outside those assigned to our program, and we are not allowed to use them. Our first job then is to get access.
- 1.
Open the file /dev/mem.
- 2.Then we ask /dev/mem to map the registers for GPIO into our memory space. We do this with the Linux mmap service. Mmap takes the following parameters:
X0: Hint for the virtual address we would like. We don’t really care and will use NULL, which gives Linux complete freedom to choose.
X1: Length of region. Should be a multiple of 4096, the memory page size.
X2: Memory protection required.
X3: File descriptor to access /dev/mem.
X4: Offset into physical memory. In our case 0xFE200000.
This call will return a virtual address in X0 that maps to the physical address we asked for. This function returns a small negative number if it fails, which we can look up in errno.h.
Registers in Bits
We will cover just those registers we need to configure our pins for output, then to set the bits to flash the LEDs. If you are interested in the full functionality, then check the Broadcom data sheet for the GPIO controller.
Although we’ve mapped these registers to memory locations, they don’t always act like memory. Some of the registers are write-only and if we read them, we won’t crash, but we’ll just read some random bits. Broadcom defines the protocol for interacting with the registers; it’s a good idea to follow their documentation exactly. These aren’t like CPU registers or real memory. The circuitry is intercepting our memory reads and writes to these locations, but only acting on things that it understands. In the previous sections, the Linux device driver for GPIO hid all these details from us.
GPIO Function Select Registers
GPIO function select registers
No. | Address | Name | Pins |
---|---|---|---|
0 | 0xFE200000 | GPSEL0 | 0–9 |
1 | 0xFE200004 | GPSEL1 | 10–19 |
2 | 0xFE200008 | GPSEL2 | 20–29 |
3 | 0xFE20000C | GPSEL3 | 30–39 |
4 | 0xFE200010 | GPSEL4 | 40–49 |
5 | 0xFE200014 | GPSEL5 | 50–53 |
- 1.
Read the register
- 2.
Set the bits for what we want to do
- 3.
Write the value back
We must be careful not to affect other bits in the register.
Pin number and corresponding bits for the GPSEL1 register
Pin No. | GPSEL1 Bits |
---|---|
10 | 0–2 |
11 | 3–5 |
12 | 6–8 |
13 | 9–11 |
14 | 12–14 |
15 | 15–17 |
16 | 18–20 |
17 | 21–23 |
18 | 24–26 |
19 | 27–29 |
We store 000 in the 3 bits if we want to input from the pin, and we store 001 in the bits if we want to write to the pin.
GPIO Output Set and Clear Registers
The GP set and clear pin registers
No. | Address | Name | Pins |
---|---|---|---|
0 | 0xFE20001C | GPSET0 | 0–31 |
1 | 0xFE200020 | GPSET1 | 32–53 |
2 | 0xFE200028 | GPCLR0 | 0–31 |
3 | 0xFE20002C | GPCLR1 | 32–53 |
These registers are write-only. You should set the bit for the register you want (with all the other bits 0) and write that bit. Reading these registers is meaningless.
The Broadcom datasheet states this as a feature, in that they save you reading the register first, then it’s easier to just set a single bit, than edit a bit in a sequence of bits. However, it could also be that this saved them some circuitry and reduced the cost of the controller chip.
More Flashing LEDs
GPIO support macros using mapped memory
Main program for the memory mapped flashing lights
The main program is the same as the first example, except that it includes a different set of macros.
The first thing we need to do is call the mapMem macro. This opens /dev/mem and sets up and calls the mmap service as we described in the section “In Devices, Everything Is Memory.” We store the returned address into X9, so that it is easily accessible from the rest of the macros. There is error checking on the file open and mmap calls since these can fail.
Root Access
If you don’t, then the file open will fail. Accessing /dev/mem is very powerful and gives you access to all memory and all hardware devices.
This is a restricted operation, so we need to be root. Programs that directly access memory are usually implemented as Linux device drivers or kernel loadable modules, but then installing these also requires root access. A virus or other malware would love to have access to all physical memory.
There is a more restricted version, /dev/gpiomem. This is a safer file to use, since it will only return the mapping for the GPIO addresses. It has the additional benefit that you don’t need to know the physical address of the GPIO registers. If you use this file instead of /dev/mem, then the only other change you need to make is to set gpioaddr to 0, since this file knows the address. Kali Linux still requires root access for this file, but some other Linux distributions allow user programs to access it. The code for this is provided in the listing but commented out.
Table Driven
We won’t cover multiplication or division until Chapter 11, “Multiply, Divide, and Accumulate”; without these, it’s hard to compute the pin offsets inside these registers. Division is a slow operation and Assembly Language programmers tend to avoid it. The common workaround is to use a table of precomputed values, rather than calculating the values as we need them. A table lookup is very fast, and we examined all the features in the ARM instruction set to help us do this in Chapter 5, “Thanks for the Memories.”
- 1.
The offset to the select register (from the base memory address)
- 2.
The bit offset in select register for this pin
- 3.
The bit offset in set & clr register
With these in hand, accessing and manipulating the GPIO control registers is a snap.
We only populate these tables for the three pins we use.
Setting Pin Direction
Setting and Clearing Pins
Setting and clearing pins is easier, since we don’t need to read the register first. We just need to construct the value to write and execute it.
Clearing the pin is the same, except that we use the clear register rather than the set register.
Summary
- 1.
Using the GPIO device driver by accessing the files under /sys/class/gpio
- 2.
Using direct memory access by asking the device driver for /dev/mem to give us a virtual block of memory corresponding to the GPIO’s control registers
Controlling devices are a key use case for Assembly Language programming. Hopefully, this chapter gave you a flavor for what is involved.
In Chapter 9, “Interacting with C and Python,” we will learn how to interact with high-level programming languages like C and Python.
Exercises
- 1.
Not all device interactions can be abstracted by reading or writing files. Linux allows a general function, ioctl, to define special operations. Consider a network interface; what are some functions you would need to control with ioctl?
- 2.
Why does the GPIO controller pack so much functionality into each register? Why not have a separate register for each pin? What are the pros and cons of each approach?
- 3.
Why does Kali Linux consider access to the GPIO controller dangerous and restrict usage to root?