Low Level C Programming with the Raspberry Pi 2 - GPIO Driver

Home / Low Level C Programming with the Raspberry Pi 2 - GPIO Driver

This article discusses programming a GPIO Driver on a Raspberry Pi 2 using C. If you haven't already, check out the introductory post. You will also need the Broadcom BCM2835 datasheet. The Raspberry Pi 2 uses a BCM2836 chip, but at the time of writing this post the datasheet for the BCM2836 has not been released. However, the BCM2835 and BCM2836 have the same underlying architecture. For the scope of this article, the only difference between the two is that the addresses of the registers are different.

The full source code for the GPIO Driver can be found near the bottom of this post, or by clicking here

Accessing the Pi's memory

As I mentioned in the introductory post, we need to write to the Pi's registers. So the first thing we need to do is access the memory of the BCM2836 chip. To do this, we first need to know the physical address at which the peripheral registers start (AKA the base peripheral address) for the Pi 2. This address is different than it is for the Pi 1, so looking through the BCM2835 (the chip in Pi 1) datasheet won't be much help. Luckily, the helpful Pi community on the internet told me that it could be found at 0x3F000000. We'll define this using a macro in the code.

Note that this value 0x3F000000 is just the address at which all of the peripheral registers start. There are many different peripherals, and so we need to find the one specifically for GPIO. If you don't care about how to find this value, I'll just let you know its 0x3F200000 and you can skip the next paragraph.

The GPIO registers are located at some offset of the base peripheral address. To find this offset, we consult the BCM2835 datasheet. First, we must find the virtual base peripheral address. This is because the datasheet lists information using virtual addresses rather than physical ones. Page 6 of the datasheet tells us that the virtual base peripheral address is 0x7E000000. Page 90 of the datasheet then tells us that the virtual address of the GPIO peripheral is 0x7E200000. This means that the offset from the peripheral base address to the GPIO peripheral address is 0x00200000. This offset is the same for both the physical and the virtual addresses. We'll define the address for the GPIO peripheral base with a macro as well:

Finally, to access the BCM2836's memory, we must access /dev/mem. /dev/mem is a pseudo-driver which provides access to the system's physical memory. Then, we can map /dev/mem into the virtual address space of our program using the mmap() command.

Assuming that mmap() is successful, the gpio variable is now a pointer to the RPi 2's GPIO peripheral registers.

Working with GPIO Registers

Information about the RPi 2's GPIO peripheral can be found starting at page 89 of the datasheet. On page 90, the datasheet has a layout of all of the GPIO registers (Section 6.1 - Register View). Note that there is a typo on this layout. The first register, GPFSEL0, is listed twice. We'll define some macros to make the code easy to read and provide easy access to these registers. (Remember that the gpio variable points to the start of the GPIO registers)

Setting the function of a GPIO pin

Before we can use a GPIO pin, we must set its functionality in the GPIO Function Select (GPFSEL) Registers. Information about these can be found on pages 91-94 of the datasheet. The GPFSEL registers are used to define the operation of a GPIO pin. There are six GPFSEL registers in total, named GPFSEL0 through GPFSEL5. The layout of the first one GPFSEL0 is shown below. All of the GPFSEL registers have pretty much the same layout.

Layout and functionality of the GPIO GPFSEL registers from the BCM2835 datasheet.

Layout and functionality of the GPFSEL registers from the BCM2835 datasheet.

Each GPIO pin has three corresponding bits in one of the GPSEL registers. Writing a value to these bits will configure that GPIO pin to function in a certain way, as specified in the datasheet (000 for input, 001 for output, etc). Let's define some macros for these different functionalities:

Then, we can write a function that sets the operation mode of a GPIO pin, as shown below. Here, pin is the GPIO pin number and function should be one of the macros defined above that defines the pin's operation mode.

How exactly does this work? First, we need to find the three bits that will configure the GPIO pin. Note that the registers are 32-bit. This means that each one of the GPFSEL registers contains the function select bits for 10 GPIO pins (each GPFSEL register has 2 bits left over that are not used for anything). The bits go in order, so GPFSEL0 has the bits for GPIO pins 0 through 9, GPFSEL1 has the bits for GPIO pins 10 through 19, and so on. So, if we simply divide the pin number by 10, we get the GPFSEL register number that we need to write to. This is what the code offset = pin / 10 is doing.

Then, we can access the correct GPFSEL register using the macro we defined before: GPFSEL[offset] (Remember that we defined GPFSEL to be a pointer to the GPFSEL0 register using a macro)

shift = (pin % 10) * 3 gives the offset of the three selection bits inside the GPFSEL register.

Then, the selection bits for the GPIO Pin are cleared and afterwards set accordingly using bit manipulation.

Reading and Writing to a GPIO pin

Reading and writing to a GPIO pin is probably the simplest part of the code. Setting a GPIO Pin high is done through the GPIO Pin output Set (GPSET) registers. Clearing a GPIO Pin (AKA setting it low) is done through the GPIO Pin Output Clear (GPCLR) registers. The info on these registers can be found on page 95-96 of the datasheet. Essentially, each GPIO pin has a corresponding bit in one of these registers. Writing a 1 to that bit will set or clear the GPIO pin.

Similarly, the GPIO Pin Level (GPLEV) registers can be read to see the actual value of a pin. Page 96 on the datasheet has the info for these registers.

Full Source Code

gpio_driver.h
gpio_driver.c

6 thoughts on “Low Level C Programming with the Raspberry Pi 2 - GPIO Driver”

  1. Thanks for the tutorial.
    I didn't quite understand why you used 0b111 in GPFSEL[offset] &= ~(0b111 << shift) to clear the pin ! would be very thankful for your explanation.

    1. Remember that each GPIO pin has three corresponding bits in the Function Selection registers. These bits are being set using an OR function:

      Before we execute this OR we have to clear the bits, or else we would end up with the wrong value because the bits that are originally 1 will stay 1 after the OR, even if they aren't supposed to. (For example, if the three bits are originally 100 and you want to set it to 011, executing the OR function without clearing first would result in 100 | 011 = 111, which is wrong)

      If you're asking about how that code works to clear the pin:

      ~(0b111 < < shift) Generates a bitmask with all 1's except for the three bits starting at the bit designated by the offset shiftshift had a value of 6 then ~(0b111 < < shift) would come out to be 0b11111111111111111111111000111111. This bitmask allows us to clear those three bits using the AND function. Click here for a little more explanation

  2. Thank you for the excellent tutorial!

    I have 2 questions regarding peripherals and 1 for knowledge:
    1) in the BCM2835 one on page 96 it says:
    0 = GPIO pin n is low
    0 = GPIO pin n is high
    so both are 0 ? is this a typo ? if not please clarify this point

    2) in the new BCM2836 peripheral https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf
    it is not clear as the one you refered to, is it me or they did not clarify the same info as in the old one

    3) Can I use this approach to communicate via I2C to the Sense-hat ? if yes, do you have good guides, books, videos etc.. ?

    1. Sorry for late response, busy time in my life

      1. Yes I'd imagine thats a typo. I have seen several typos on that spec so I wouldn't be surprised

      2. That BCM2836 document you linked is not a spec for the peripherals, which are what is relevant for working with GPIO/I2C/whatever. When I wrote this I hadn't seen a peripheral datasheet released for the BCM2836, so I just used the one for the BCM2835. They have the same architecture (just different addresses)

      3. I will release the I2C driver too and yes its a generic I2C driver so it should be usable with Sense-hat. (I've never worked with Sense-hat before though so not entirely sure)

  3. This is a great tutorial. I am trying to learn embedded programming in C and I keep getting hung up on how to start? How do you know the sequence of events and how do you figure out what register to write to?). How do you know where to look for things like the peripheral base address because if I use some other chipset it all falls apart. Any advice or things I should look into? I keep getting confused and lost. I am not a pro at programming in C but I am trying to learn.

Leave a Reply

Your email address will not be published. Required fields are marked *