© Stephen Smith 2019
S. SmithRaspberry Pi Assembly Language Programminghttps://doi.org/10.1007/978-1-4842-5287-1_8

8. Programming GPIO Pins

Stephen Smith1 
(1)
Gibsons, BC, Canada
 

The Raspberry Pi has a set of General Purpose I/O (GPIO) pins that you can use to control homemade electronic projects. 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 a number of LEDs and resistors, so we can write some real code. We will program the GPIO pins two ways, firstly by using the included Linux device driver and secondly by accessing the GPIO controller’s registers directly.

GPIO Overview

The original Raspberry Pi 1 has 26 GPIO pins; newer Raspberry Pi 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 (are 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) that convert digital to analog and are handy for controlling electric motors.

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 then controls the pin’s operations via application programs opening files then reading and writing data to them.

The files to controlling the GPIO pin all appear under the /sys/class/gpio folder. By writing short text strings to the files here, 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, that is pretty much every single one. Raspbian includes some special libraries to control the GPIO pins for Python and Scratch to make it easier, but behind the scenes they are just making the file I/O calls we are describing.

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/486919_1_En_8_Chapter/486919_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 that use the macros in fileio.s to perform the various GPIO functions.
@ Various macros to access the GPIO pins
@ on the Raspberry Pi.
@
@ R8 - file descriptor.
@
.include "fileio.s"
@ Macro nanoSleep to sleep .1 second
@ Calls Linux nanosleep service which is funct 162.
@ Pass a reference to a timespec in both r0 and r1
@ First is input time to sleep in secs and nanosecs.
@ Second is time left to sleep if interrupted
.macro  nanoSleep
        ldr         r0, =timespecsec
        ldr         r1, =timespecsec
        mov         r7, #sys_nanosleep
        svc         0
.endm
.macro  GPIOExport  pin
        openFile    gpioexp, O_WRONLY
        mov         r8, r0      @ save the file desc
        writeFile   r8, pin, #2
        flushClose  r8
.endm
.macro  GPIODirectionOut   pin
        @ copy pin into filename pattern
        ldr         r1, =pin
        ldr         r2, =gpiopinfile
        add         r2, #20
        ldrb        r3, [r1], #1 @ load pin and post incr
        strb        r3, [r2], #1 @ store to filename and post incr
        ldrb        r3, [r1]
        strb        r3, [r2]
        openFile    gpiopinfile, O_WRONLY
        mov         r8, r0      @ save the file descriptor
        writeFile   r8, outstr, #3
        flushClose  r8
.endm
.macro  GPIOWrite   pin, value
        @ copy pin into filename pattern
        ldr         r1, =pin
        ldr         r2, =gpiovaluefile
        add         r2, #20
        ldrb        r3, [r1], #1    @ load pin and post increment
        strb        r3, [r2], #1    @ store to filename and post increment
        ldrb        r3, [r1]
        strb        r3, [r2]
        openFile    gpiovaluefile, O_WRONLY
        mov         r8, r0      @ save the file descriptor
        writeFile   r8, value, #1
        flushClose  r8
.endm
.data
timespecsec:   .word   0
timespecnano:  .word   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  2          @ save users of this file having to do this.
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.
@
@ r6 - loop variable to flash lights 10 times
@
.include "gpiomacros.s"
.global _start                @ Provide program starting address to linker
_start: GPIOExport  pin17
        GPIOExport  pin27
        GPIOExport  pin22
        nanoSleep
        GPIODirectionOut pin17
        GPIODirectionOut pin27
        GPIODirectionOut pin22
        @ set up a loop counter for 10 iterations
        mov         r6, #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    r6, #1
   @ If we haven't counted down to 0 then loop
        bne     loop
_end:   mov     R0, #0  @ Use 0 return code
        mov     R7, #1  @ Command code 1 terminates
        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.”

Moving Closer to the Metal

For Assembly language programmers, the previous example is not satisfying. When we program in Assembly, 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, 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 we looked at the memory addresses our instructions are stored at in gdb. These memory addresses aren’t physical memory addresses; rather, they are virtual memory addresses. As a Linux process, our program is given a 4 GB virtual address space. 3 GB of this is for us and 1 GB is for system things. Within this address space, some of it is mapped to physical memory to store our Assembly instructions, our .data sections, and our 8 MB 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 we know that behind the scenes the memory management hardware in the Raspberry Pi will be doing the memory translations between virtual and physical memory for us.

About Raspberry Pi 4 RAM

You might wonder why the Raspberry Pi 4 comes with up to 4 GB of RAM, but our process can only access 3 GB of it? However, all this RAM will be used, since each process and the kernel can have up to 3 GB of RAM. In fact, the memory controller in the Raspberry Pi has 40 address pins, so it can address more than 4 GB of physical memory.

In the future, if there is a 16 GB version of the Pi, that memory can be used, even if Raspbian is still 32 bits. Every 32-bit process could map different sections of memory, so even though a 32-bit process can only access 3 GB of memory at a time, it can use more by swapping parts of its virtual address space to different physical regions.

In Devices, Everything Is Memory

The GPIO controller has 41 registers. We can’t read or write these like the ARM CPU’s registers. The ARM32 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. This is the job of the Linux device drivers, to translate these memory register accesses into a standard set of file I/O calls.

The memory address for the GPIO registers on the Raspberry Pi 2, 3, and 4 is 0x3F200000 (for the Raspberry Pi 0 and 1, it is 0x20200000). 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 that 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 mmap2 service. Mmap2 takes the following parameters:
    • R0: Hint for the virtual address we would like. We don’t really care and will use NULL, which gives Linux complete freedom to choose.

    • R1: Length of region. Should be a multiple of 4096, the memory page size.

    • R2: Memory protection required.

    • R3: File descriptor to open /dev/mem.

    • R4: Offset into physical memory in 4096-byte pages (we’ll use 0x3f200000/4096).

      This call will return a virtual address in R0 that maps to the physical address we asked for. The original mmap took an offset in bytes for the physical address; this restricted the call to mapping the first 4 GB of memory. The newer mmap2 call takes the address in pages allowing a greater range of physical addresses without the need to go to full 64 bits. This function returns a small negative number if it fails.

     

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 datasheet 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 the GPIO hid all these details from us.

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

0x3f200000

GPSEL0

0–9

1

0x3f200004

GPSEL1

10–19

2

0x3f200008

GPSEL2

20–29

3

0x3f20000c

GPSEL3

30–39

4

0x3f200010

GPSEL4

40–49

5

0x3f200014

GPSEL5

50–53

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

    Read the register.

     
  2. 2.

    Set the bits for our register.

     
  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 and then two registers to clear them. The first register controls the first 32 pins and then the second controls the remaining 22 pins that aren’t accessible to us. Table 8-3 shows the details of these registers.
Table 8-3

The GP set and clear pin registers

No.

Address

Name

Pins

0

0x3f20001c

GPSET0

0–31

1

0x3f200020

GPSET1

32–53

2

0x3f200028

GPCLR0

0–31

3

0x3f20002c

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.
@
@ R8 - 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, S_RDWR    @ open /dev/mem
      movs        r4, r0      @ fd for memmap
      @ check for error and print error msg if necessary
      BPL         1f  @ pos number file opened ok
      MOV         R1, #1  @ stdout
      LDR         R2, =memOpnsz    @ Error msg
      LDR         R2, [R2]
      writeFile   R1, memOpnErr, R2 @ print the error
      B           _end
@ Set up can call the mmap2 Linux service
1:    ldr         r5, =gpioaddr     @ address we want / 4096
      ldr         r5, [r5]    @ load the address
      mov         r1, #pagelen @ size of mem we want
@ mem protection options
      mov         r2, #(PROT_READ + PROT_WRITE)
      mov         r3, #MAP_SHARED   @ mem share options
      mov         r0, #0            @ let linux choose a virtual address
      mov         r7, #sys_mmap2    @ mmap2 service num
      svc         0       @ call service
      movs        r8, r0 @ keep the returned virt addr
      @ check for error and print error msg
      @ if necessary.
      BPL         2f  @ pos number file opened ok
      MOV         R1, #1  @ stdout
      LDR         R2, =memMapsz     @ Error msg
      LDR         R2, [R2]
      writeFile   R1, memMapErr, R2 @ 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 r0 and r1
@ First is input time to sleep in seconds and nanoseconds.
@ Second is time left to sleep if interrupted (which we ignore)
.macro  nanoSleep
        ldr         r0, =timespecsec
        ldr         r1, =timespecsec
        mov         r7, #sys_nanosleep
        svc         0
.endm
.macro  GPIODirectionOut   pin
      ldr   r2, =pin   @ offset of select register
      ldr   r2, [r2]    @ load the value
      ldr   r1, [r8, r2]  @ address of register
      ldr   r3, =pin   @ address of pin table
      add   r3, #4 @ load amount to shift from table
      ldr   r3, [r3]       @ load value of shift amt
      mov   r0, #0b111     @ mask to clear 3 bits
      lsl   r0, r3         @ shift into position
      bic   r1, r0         @ clear the three bits
      mov   r0, #1         @ 1 bit to shift into pos
      lsl   r0, r3         @ shift by amount from table
      orr   r1, r0         @ set the bit
      str   r1, [r8, r2]  @ save it to reg to do work
.endm
.macro  GPIOTurnOn   pin, value
      mov   r2, r8      @ address of gpio regs
      add   r2, #setregoffset @ off to set reg
      mov   r0, #1      @ 1 bit to shift into pos
      ldr   r3, =pin   @ base of pin info table
      add   r3, #8      @ add offset for shift amt
      ldr   r3, [r3]   @ load shift from table
      lsl   r0, r3      @ do the shift
      str   r0, [r2]    @ write to the register
.endm
.macro  GPIOTurnOff   pin, value
      mov   r2, r8      @ address of gpio regs
      add   r2, #clrregoffset @ off set of clr reg
      mov   r0, #1      @ 1 bit to shift into pos
      ldr   r3, =pin   @ base of pin info table
      add   r3, #8      @ add offset for shift amt
      ldr   r3, [r3]   @ load shift from table
      lsl   r0, r3      @ do the shift
      str   r0, [r2]    @ write to the register
.endm
.data
timespecsec:   .word   0
timespecnano:  .word   100000000
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 @ realign after strings
@ mem address of gpio register / 4096
gpioaddr: .word   0x3F200
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 contains Listing 8-4 that 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.
@
@ r6 - loop variable to flash lights 10 times
@
.include "gpiomem.s"
.global _start                  @ Provide program starting address to linker
_start: mapMem
        nanoSleep
        GPIODirectionOut pin17
        GPIODirectionOut pin27
        GPIODirectionOut pin22
        @ set up a loop counter for 10 iterations
        mov         r6, #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
        subs    r6, #1
@ If we haven't counted down to 0 then loop
        bne     loop
_end:   mov     R0, #0      @ Use 0 return code
        mov     R7, #1      @ Command code 1 terms
        svc     0           @ Linux 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 mmap2 service as we described in the section “In Devices, Everything Is Memory.” We store the returned address into R8, so that it is easily accessible from the rest of the macros. There is error checking on the file open and mmap2 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. We didn’t have to do this with the last program, because the GPIO device driver keeps everything safe. 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.

Table Driven

We won’t cover multiplication or division until Chapter 10, “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 our .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 and 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 pin 17, this is 4.
      ldr   r2, =pin     @ offset of select register
      ldr   r2, [r2]      @ load the value
Now use pre-indexed addressing to load the current contents of the selection register. r8 is the address, plus the offset we just loaded into r2.
      ldr   r1, [r8, r2]  @ address of register
We now load the second item in the table, the shift into the control register for our 3 bits.
      ldr   r3, =pin     @ address of pin table
      add   r3, #4 @ load amount to shift from table
      ldr   r3, [r3]      @ 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   r0, #0b111     @ mask to clear 3 bits
      lsl   r0, r3         @ shift into position
      bic   r1, r0         @ 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   r0, #1         @ 1 bit to shift into pos
      lsl   r0, r3         @ shift by amount from table
      orr   r1, r0         @ 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   r1, [r8, r2]  @ save it to reg 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 to it 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   r2, r8      @ address of gpio regs
      add   r2, #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   r0, #1      @ 1 bit to shift into pos
      ldr   r3, =pin   @ base of pin info table
      add   r3, #8      @ add offset for shift amt
      ldr   r3, [r3]   @ load shift from table
      lsl   r0, r3      @ do the shift
Now we have r0 containing a 1 in the correct bit; we write it back to the GPIO set register to turn on the LED.
      str   r0, [r2]    @ 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.

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

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