Friday, February 19, 2016

Hands on GPIOs

Now that we know what a GPIO is, let's try to write a driver that we will use further to play with our Pi.

The reference document for this work is the BCM2835-ARM-Peripherals.pdf that you can easily find on the web.

GPIO Registers

GPFSELx : GPIO Function Select with x in [0;5]
Everyone of these registers configures up to 10 pins (except for GPFSEL5 which configures only 3).
Each pin can be configured as an input, output or alternate.
Here is the table with the definition of every alternate configuration for every pi.

GPSETx : GPIO Set function with x in [0;1]
Every pin is represented by a bit in these registers. If the pin is configured as an output, setting its bit in  GPSET will drive it to HIGH level.
GPCLRx : GPIO Clear function with x in [0;1]
Every pin is represented by a bit in these registers. If the pin is configured as an output, setting its bit in  GPSET will drive it to LOW level.
GPLEVx : GPIO Level wih x in [0;1]
Every pin is represented by a bit in these registers. You can get the current level of a pin by reading its pin in these registers.
GPEDSx : GPIO pin event status with x in [0;1]
Every pin is represented by a bit in these registers. A bit is set by the micro-controller whenever an event occurs on a pin and matches the event we are waiting for (configurable edge or level).
You can also configure the interrupt controller to be alerted with an interrupt for any pin.

GPIO pin configuration registers

GPRENx : GPIO Rising edge detect enable with x in [0;1]
GPFENx : GPIO  Falling edge detect enable with x in [0;1]
GPHENx: GPIO High detect enable with x in [0;1]
GPLENx : GPIO Low detect enable with x in [0;1]
GPARENx : GPIO Asynchronous rising edge  detect enable with x in [0;1]
GPAFENx: GPIO Asynchronous falling edge  detect enable with x in [0;1]

Asynchronous means the incoming signal is not sampled by the system clock. As such
rising edges of very short duration can be detected.
GPPUD : GPIO Pull up/down register
GPPUDCLKx : GPIO Pull up/down clock register with x in [0;1]
Use GPPUD to configure a pull up, pull down or unused mode. Then, use GPPUDCLKx to apply this mode to the desired pins.

Writing the driver step by step

Now that we know the registers to modify to handle raspberry GPIOs, we can go further and define our design. Here is a list of what we need :

A. GPIO registers memory mapping : Every register is located at a specific address in the microcontroller.  The first step would be to define them.
B. GPIO registers accessors: typically macros to avoid using masks in our code.
C. a GPIO static configuration : we will define the pins default configuration at build time. This is usually done to limit the number of function calls at boot to configure the device.
D. an interface : how will we use this driver ? We won't discuss about the integration of this driver into linux here.

And of course, everything we need to test our work !

A. GPIO Registers memory mapping

Given the BCM2835 document, the addresses start from 0x7E200000 and go to 0x7E2000B0, with 4 bytes per register (32 bits platform). My choice here is to create a structure where all of the registers are listed in the same order as the reference manual rather than defining every one of the registers with a #define statement. It is more elegant and a lot more handy when it comes to use them in the code. The final step will be to define the structure as volatile and to assign to it the GPIO base address (0x7E200000).

typedef struct
      uint32_t GPFSEL[6];
      uint32_t Reserved0; // Reserved byte
uint32_t GPSET[2]
      uint32_t Reserved1;
uint32_t GPCLR[2];
      uint32_t Reserved2;
uint32_t GPLEV[2];
      uint32_t Reserved3;
uint32_t GPEDS[2];
      uint32_t Reserved4;
uint32_t GPREN[2];
      uint32_t Reserved5;
uint32_t GPFEN[2];
      uint32_t Reserved6;
uint32_t GPHEN[2];
      uint32_t Reserved7;
uint32_t GPLEN[2];
      uint32_t Reserved8;
uint32_t GPAREN[2];
      uint32_t Reserved9;
uint32_t GPAFEN[2];
      uint32_t Reserved10;
uint32_t GPPUD;
uint32_t GPPUDCLK[2];
      uint32_t Reserved11[4];
      uint8 Test;
}volatile* GPIO_MemMap_ts;

The structures declared in GPIO_MemMap_ts should be defined too.
 When you are done, all you need is to declare a global variable for the GPIO_MemMap_ts:

const GPIO_MemMap_ts GPIO_BASE = 0x7E200000;

B. GPIO Registers Accessors

What we need here is to have an access to the bit of our choice in one of the GPIO registers. There is of course many ways to do that, here is a possible solution:

/* GPFSEL accessing macros */
#define GPFSEL_MODE_INPUT   ((uint32_t)0x000)
#define GPFSEL_MODE_OUTPUT  ((uint32_t)0x001)
#define GPFSEL_MODE_ALT0    ((uint32_t)0x100)
#define GPFSEL_MODE_ALT1    ((uint32_t)0x101)
#define GPFSEL_MODE_ALT2    ((uint32_t)0x110)
#define GPFSEL_MODE_ALT3    ((uint32_t)0x111)
#define GPFSEL_MODE_ALT4    ((uint32_t)0x011)
#define GPFSEL_MODE_ALT5    ((uint32_t)0x010)

#define GPFSEL_SET_FSELy(base, y, mode) (((volatile* GPIO_MemMap_ts)base).GPFSEL[y/10]) |= (mode << (3* (y%10)))

C. GPIO Static configuration

Work in progress...


Post a Comment