Category Archives: Raspberry Pi

Home / Raspberry Pi
2 Posts

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

A lot of the work being done on the Raspberry Pi involves high-level programming using languages such as Python. However, the Raspberry Pi is also well-equipped for lower-level "embedded-style" programming (like programming a microcontroller). In this article I'll be going through how to create drivers that implement GPIO and I2C functionality on a Raspberry Pi. These drivers will be written in C, a lower-level programming language. The description and source code of these drivers are on their own pages. The links to these can be found at the bottom of this article.

Why would you want to program the Raspberry Pi like a microcontroller? To learn, mainly. After all, the Pi was originally developed for educational purposes. Another advantage is that programs written in C are capable of running much faster than programs written in high-level languages such as Python. There are many articles online which compare the speed of different languages on a Raspberry Pi. Here is one example that shows how much faster the C language can be.

To follow the code, you should be familiar with C, have a decent understanding of pointers/pointer arithmetic, and understand how bit manipulation works.

Introduction

Low-level programming of microcontrollers is different because it involves writing to registers. A register is a memory location that is frequently used by the CPU. The size of a register can vary and is usually measured in the number of bits (e.g 16-bit register). Writing certain values to these registers allows us to program the CPU.

But how do we know what we need to write to the registers? The datasheets will tell us. The Raspberry Pi Model A, B, B+, and Zero use the Broadcom BCM2835 chip. The Raspberry Pi 2 uses the Broadcom BCM2836 chip. At the time of writing this, the datasheet for the BCM2836 has not been released. However, the underlying architectures of the BCM2835 and BCM2836 are the same. For the scope of this article, the only difference between the two is that the addresses of the registers are different.

Implementing the Drivers!

The implementation and source code of the drivers are in separate articles. The links can be found below.

GPIO Driver
I2C Driver