© Stephen Smith 2020
S. SmithProgramming with 64-Bit ARM Assembly Languagehttps://doi.org/10.1007/978-1-4842-5881-1_8

8. Programming GPIO Pins

Stephen Smith1 
(1)
Gibsons, BC, Canada
 

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

The original Raspberry Pi 1 has 26 GPIO pins, but the new Raspberry Pi’s expanded this to 40 pins. In this section, we will limit our discussion to the original 26 pins. They either provide power or are generally programmable:
  • 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.

Suppose we want to programmatically control pin 17; the first thing we do is tell the driver we want to do this. We write the string “17” to /sys/class/gpio/export. If this succeeds, then we now control the pin. The driver then creates the following files in a gpio17 folder:
  • /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).

Warning

LEDs have a positive and negative side. The positive side needs to connect to the GPIO pin; reversing it could damage the LED.

Figure 8-1 shows how the LEDs and resistors are wired up on a breadboard.
../images/494415_1_En_8_Chapter/494415_1_En_8_Fig1_HTML.jpg
Figure 8-1

Breadboard with LEDs and resistors installed

Initially, we’ll define a set of macros in gpiomacros.S. containing Listing 8-1, which uses the macros in fileio.S to perform the various GPIO functions.
// Various macros to access the GPIO pins
// on the Raspberry Pi.
//
// X9 - file descriptor.
//
#include "fileio.S"
// Macro nanoSleep to sleep .1 second
// Calls Linux nanosleep entry point.
// Pass a reference to a timespec in both X0 and X1
// First is input time to sleep in seconds and nanoseconds.
// Second is time left to sleep if interrupted (which we ignore)
.macro  nanoSleep
        ldr         X0, =timespecsec
        ldr         X1, =timespecsec
        mov         x8, #__NR_nanosleep
        svc         0
.endm
.macro  GPIOExport  pin
        openFile    gpioexp, O_WRONLY
        mov         X9, X0      // save the file descriptor
        writeFile   X9, pin, #2
        flushClose  X9
.endm
.macro  GPIODirectionOut   pin
        // copy pin into filename pattern
        ldr         X1, =pin
        ldr         X2, =gpiopinfile
        add         X2, X2, #20
        ldrb        W3, [X1], #1 // load pin and post increment
        strb        W3, [X2], #1 // store to filename and post increment
        ldrb        W3, [X1]
        strb        W3, [X2]
        openFile    gpiopinfile, O_WRONLY
        mov         X9, X0      // save the file descriptor
        writeFile   X9, outstr, #3
        flushClose  X9
.endm
.macro  GPIOWrite   pin, value
        // copy pin into filename pattern
        ldr         X1, =pin
        ldr         X2, =gpiovaluefile
        add         X2, X2, #20
        ldrb        W3, [X1], #1 // load pin and post incr
        strb        W3, [X2], #1 // store to file and post incr
        ldrb        W3, [X1]
        strb        W3, [X2]
        openFile    gpiovaluefile, O_WRONLY
        mov         X9, X0      // save the file descriptor
        writeFile   X9, value, #1
        flushClose  X9
.endm
.data
timespecsec:   .dword   0
timespecnano:  .dword   100000000
gpioexp:    .asciz  "/sys/class/gpio/export"
gpiopinfile: .asciz "/sys/class/gpio/gpioxx/direction"
gpiovaluefile: .asciz "/sys/class/gpio/gpioxx/value"
outstr:     .asciz  "out"
            .align  4    // save users having to do this.
.text
Listing 8-1

Macros to control the GPIO pins

Now we need a controlling program, main.S containing Listing 8-2, to orchestrate the process.
//
// Assembler program to flash three LEDs connected to the
// Raspberry Pi GPIO port.
//
// W6 - loop variable to flash lights 10 times
//
#include "gpiomacros.S"
.global _start       // Provide program starting address
_start: GPIOExport  pin17
        GPIOExport  pin27
        GPIOExport  pin22
        nanoSleep
        GPIODirectionOut pin17
        GPIODirectionOut pin27
        GPIODirectionOut pin22
        // setup a loop counter for 10 iterations
        mov         W6, #10
loop:   GPIOWrite   pin17, high
        nanoSleep
        GPIOWrite   pin17, low
        GPIOWrite   pin27, high
        nanoSleep
        GPIOWrite   pin27, low
        GPIOWrite   pin22, high
        nanoSleep
        GPIOWrite   pin22, low
        // decrement loop counter and see if we loop
        // Subtract 1 from loop register
        // setting status register
        subs    W6, W6, #1
        // If we haven't counted down to 0 then loop
        b.ne     loop
_end:   mov     X0, #0      // Use 0 return code
        mov     X8, #__NR_exit
        svc     0           // Linux command to terminate
pin17:      .asciz  "17"
pin27:      .asciz  "27"
pin22:      .asciz  "22"
low:        .asciz  "0"
high:       .asciz  "1"
Listing 8-2

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.”

Note

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.

Warning

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 memory address for the GPIO registers under 64-bit Kali Linux is 0xFE200000. This address is configurable by the operating system, so you need to check what it is for what you are doing. The easiest way to confirm the true value is to use the command
dmesg
In its output you will find something like
[  +0.000669] gpiomem-bcm2835 fe200000.gpiomem: Initialised: Registers at 0xfe200000
Note

The output of dmesg could be quite long. Use

    dmesg | grep gpio

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.

This leads us back to everything being a file in Linux. There is a file that will give us a pointer, which we can use to access these memory locations, as follows:
  1. 1.

    Open the file /dev/mem.

     
  2. 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.

The GPIO registers are 32 bits in size. We can only transfer data between these registers and a 32-bit W version of a CPU register. For instance, if X2 contains the address to a GPIO address and we try to read it with
LDR   X1, [X2]
we will get a bus error when we run our program, because the GPIO controller can’t provide 64 bits of data. We must use
LDR   W1, [X2]

GPIO Function Select Registers

The first thing we need to do is configure the pins we are using for output. There is a bank of six registers to configure all the GPIO pins for input or output. These GPIO function select registers are named GPSEL0-GPSEL5. Each pin gets 3 bits in one of these registers to configure it. These are read-write registers. Since each register is 32 bits, each one can control ten pins, with 2 bits left unused (GPSEL5 only controls four pins). Table 8-1 shows the details of each select register.
Table 8-1

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

To use these registers, the protocol is to
  1. 1.

    Read the register

     
  2. 2.

    Set the bits for what we want to do

     
  3. 3.

    Write the value back

     
Note

We must be careful not to affect other bits in the register.

Table 8-2 shows the bits corresponding to each pin in the GPSEL1 register.
Table 8-2

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

There are two registers for setting pins, then two registers to clear them. The first register controls the first 32 pins; then the second controls the remaining 22 pins. Table 8-3 shows the details of these registers.
Table 8-3

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

We’ll now repeat our flashing LEDs program, but this time we’ll use mapped memory and access the GPIO’s registers directly. First of all, the macros that do the nitty-gritty work from Listing 8-3 go in gpiomem.S.
// Various macros to access the GPIO pins
// on the Raspberry Pi.
//
// X9 - memory map address.
//
#include "fileio.S"
.equ   pagelen, 4096
.equ   setregoffset, 28
.equ   clrregoffset, 40
.equ   PROT_READ, 1
.equ   PROT_WRITE, 2
.equ   MAP_SHARED, 1
// Macro to map memory for GPIO Registers
.macro mapMem
       openFile     devmem, O_O_RDWR+O_EXCL // open /dev/mem
       ADDS         X4, XZR, X0  // fd for memmap
       // check for error and print error msg if necessary
       B.PL         1f  // pos number file opened ok
       MOV          X1, #1  // stdout
       LDR          X2, =memOpnsz     // Error msg
       LDR          W2, [X2]
       writeFile    X1, memOpnErr, X2 // print the error
       B            _end
// Setup can call the mmap2 Linux service
1:     ldr          X5, =gpioaddr      // address we want / 4096
       ldr          X5, [X5]           // load the address
       mov          X1, #pagelen       // size of mem we want
       // mem protection options
       mov          X2, #(PROT_READ + PROT_WRITE)
       mov          X3, #MAP_SHARED    // mem share options
       // let linux choose a virtual address
       mov          X0, #0
       mov          X8, #__NR_mmap     // mmap service num
       svc          0                  // call service
       // keep the returned virtual address
       ADDS         X9, XZR, X0
       // check for error and print error msg if necessary
       B.PL         2f  // pos number file opened ok
       MOV          X1, #1  // stdout
       LDR          X2, =memMapsz      // Error msg
       LDR          W2, [X2]
       writeFile    X1, memMapErr, X2  // print the error
       B            _end
2:
.endm
// Macro nanoSleep to sleep .1 second
// Calls Linux nanosleep entry point which is function 162.
// Pass a reference to a timespec in both X0 and X1
// First is input time to sleep in seconds and nanoseconds.
// Second is time left to sleep if interrupted (which we ignore)
.macro  nanoSleep
        ldr         X0, =timespecsec
        ldr         X1, =timespecsec
        mov         X8, #__NR_nanosleep
        svc         0
.endm
.macro  GPIODirectionOut   pin
      ldr    X2, =pin     // offset of select register
      ldr    W2, [X2]      // load the value
      ldr    W1, [X9, X2]  // address of register
      ldr    X3, =pin     // address of pin table
      add    X3, X3, #4    // load amount to shift from table
      ldr    W3, [X3]      // load value of shift amt
      mov    X0, #0b111    // mask to clear 3 bits
      lsl    X0, X0, X3    // shift into position
      bic    X1, X1, X0    // clear the three bits
      mov    X0, #1        // 1 bit to shift into pos
      lsl    X0, X0, X3    // shift by amount from table
      orr    X1, X1, X0    // set the bit
      str    W1, [X9, X2]  // save it to register to do work
.endm
.macro  GPIOTurnOn   pin, value
      mov    X2, X9        // address of gpio regs
      add    X2, X2, #setregoffset // off to set reg
      mov    X0, #1        // 1 bit to shift into pos
      ldr    X3, =pin     // base of pin info table
      add    X3, X3, #8    // add offset for shift amt
      ldr    W3, [X3]      // load shift from table
      lsl    X0, X0, X3    // do the shift
      str    W0, [X2]      // write to the register
.endm
.macro  GPIOTurnOff   pin, value
      mov    X2, X9        // address of gpio regs
      add    X2, X2, #clrregoffset // off set of clr reg
      mov    X0, #1        // 1 bit to shift into pos
      ldr    X3, =pin     // base of pin info table
      add    X3, X3, #8    // add offset for shift amt
      ldr    W3, [X3]      // load shift from table
      lsl    X0, X0, X3    // do the shift
      str    W0, [X2]      // write to the register
.endm
.data
timespecsec:   .dword   0
timespecnano:  .dword   100000000
//devmem:       .asciz  "/dev/gpiomem"
devmem:         .asciz  "/dev/mem"
memOpnErr:     .asciz  "Failed to open /dev/mem "
memOpnsz:      .word  .-memOpnErr
memMapErr:     .asciz  "Failed to map memory "
memMapsz:      .word  .-memMapErr
               .align  4 // relign after strings
//gpioaddr:      .dword   0x0      // mem address for gpiomem
gpioaddr:      .dword   0xFE200000        // mem address of gpio registers
pin17:         .word   4   // offset to select register
               .word   21  // bit offset in select register
               .word   17  // bit offset in set & clr register
pin22:         .word   8   // offset to select register
               .word   6  // bit offset in select register
               .word   22  // bit offset in set & clr register
pin27:         .word   8   // offset to select register
               .word   21  // bit offset in select register
               .word   27  // bit offset in set & clr register
.text
Listing 8-3

GPIO support macros using mapped memory

Now the driving program mainmem.S containing Listing 8-4, which is quite similar to the last one. The main differences are in the macros.
//
// Assembler program to flash three LEDs connected to the
// Raspberry Pi GPIO port using direct memory access.
//
// W6 - loop variable to flash lights 10 times
//
#include "gpiomem.S"
.global _start      // Provide program starting address
_start: mapMem
        nanoSleep
        GPIODirectionOut pin17
        GPIODirectionOut pin27
        GPIODirectionOut pin22
        // setup a loop counter for 10 iterations
        mov         W6, #10
loop:   GPIOTurnOn   pin17
        nanoSleep
        GPIOTurnOff   pin17
        GPIOTurnOn    pin27
        nanoSleep
        GPIOTurnOff   pin27
        GPIOTurnOn    pin22
        nanoSleep
brk1:
        GPIOTurnOff   pin22
        //decrement loop counter and see if we loop
        // Subtract 1 from loop register setting status register
        subs    W6, W6, #1
        // If we haven't counted down to 0 then loop
        b.ne     loop
_end:   mov     X0, #0      // Use 0 return code
        mov     X8, #__NR_exit
        svc     0           // Linus command to terminate
Listing 8-4

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

To access /dev/mem, you need root access, so run this program with root access via
sudo ./flashmem

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.”

For each pin, we provide three values in the .data section:
  1. 1.

    The offset to the select register (from the base memory address)

     
  2. 2.

    The bit offset in select register for this pin

     
  3. 3.

    The bit offset in set & clr register

     

With these in hand, accessing and manipulating the GPIO control registers is a snap.

Note

We only populate these tables for the three pins we use.

Setting Pin Direction

Start with loading the offset of the selection register for our pin—for pin17, this is 4:
ldr   X2, =pin    // offset of select register
ldr   W2, [X2]     // load the value
Our table consists of 32-bit words, so we load it into the lower 32 bits of register 2, namely, W2. Now use pre-index addressing to load the current contents of the selection register. X9 is the address, plus the offset we just loaded into W2/X2.
ldr   W1, [X9, X2]    // address of register
Remember we must access the GPIO registers as 32 bits, so we must load them into a W register. We now load the second item in the table, the shift into the control register for our 3 bits.
ldr   X3, =pin     // address of pin table
add   X3, X3, #4    // load amount to shift from table
ldr   W3, [X3]      // load value of shift amt
Clear the 3 bits with a mask of binary 111 that we shift into position, then call bit clear (bic) to clear:
mov   X0, #0b111    // mask to clear 3 bits
lsl   X0, X0, X3    // shift into position
bic   X1, X1, X0    // clear the three bits
We move one into position, so we can set the lower of the 3 bits to 1 using a logical or instruction (orr):
mov   X0, #1        // 1 bit to shift into pos
lsl   X0, X0, X3    // shift by amount from table
orr   X1, X1, X0    // set the bit
Finally, now that we’ve set our 3 bits, we write the value back to the GPIO control register to execute our command:
str   W1, [X9, X2]    // save it to register to do work

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.

Since all our pins are controlled by one register, we just have its offset defined in a .EQU directive. We take the base virtual address and add that offset.
mov   X2, X9                // address of gpio regs
add   X2, X2, #setregoffset // off to set reg
Next, we want to have a register with just a 1 in the correct position. We start with 1 and shift it into position. We look up that shift value as the third item in our pin lookup table.
mov   X0, #1        // 1 bit to shift into pos
ldr   X3, =pin     // base of pin info table
add   X3, X3, #8    // add offset for shift amt
ldr   W3, [X3]      // load shift from table
lsl   X0, X0, X3    // do the shift
Now we have X0 containing a 1 in the correct bit; we write it back to the GPIO set register to turn on the LED, again writing it using the 32-bit version of register 0:
str   W0, [X2]    // write to the register

Clearing the pin is the same, except that we use the clear register rather than the set register.

Summary

In this chapter, we built on everything we’ve learned so far, to write a program to flash a series of LEDs attached to the GPIO ports on our Raspberry Pi. We did this in two ways:
  1. 1.

    Using the GPIO device driver by accessing the files under /sys/class/gpio

     
  2. 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. 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. 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. 3.

    Why does Kali Linux consider access to the GPIO controller dangerous and restrict usage to root?

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.81.58