wiki:GSoC/2013/Raspberry_Pi_BSP_Peripherals

Raspberry Pi BSP Peripherals

Mentors: Alan Cudmore, Muhammad Adnan, Jennifer Averett and Amar Takhar.

Students: André Marques.

Introduction: This project has provided the Raspberry Pi BSP with support for more peripherals, namely: GPIO driver, I2C and SPI Bus drivers. Unfortunately the Frame Buffer graphics driver support was left out.

Requirements: This project requires some familiarity with the RTEMS codebase, low level programming in C and some hardware knowledge.

Project Contributions

GPIO

The access to the GPIO hardware is done through an API. It operates the total of 54 GPIOs, which include pins reserved for Raspberry Pi own peripherals (such as the EMMC interface for the SD card) hence not all of these 54 GPIOs are usable to connect to external peripheral devices. Currently only GPIOs 0-31 of these 54 pins can be directly controlled by an API user, which is enough to control all the external GPIO pins on all Raspberry Pi models:

  • Models A and B P1 GPIO header. Note: the P5 GPIO header should also work with the API but it was not tested mainly because the header is unpopulated by default on the Raspberry Pi hardware [6];
  • Model B+ J8 GPIO header. It was not confirmed yet but the raspberrypi BSP was already tested on the B+ by Alan Cudmore, and since it just expands the model A/B P1 header the GPIO API should work as well.

Through the API an application may:

  • Set or clear a digital output pin level;
  • Select a GPIO pin function, such as digital input or output or one of the 5 possible alternative functions (each pin has a different set of possible alternate functions);
  • Setup a certain GPIO based interface such as SPI or I2C data buses or JTAG;
  • Configure a digital input GPIO pin (or several at the same time) pull-up resistor as a pull-up, pull-down or disabled;
  • Debounce a switch connected to a gpio input pin. This ignores interrupts generated on a digital input GPIO caused by a bouncing switch through software by requiring a certain number of clock ticks (defined by the user) to pass between interrupts to guarantee that the actual source of an interrupt comes from a real user button press.
  • Enable/disable interrupts on digital input pins, that can call an user defined function every time that the interrupt is detected

Although the GPIO API tracks every GPIO pin, it only directly controls GPIO pins configured as digital inputs or outputs. Any pin configured to perform any other function should be operated by another API or driver.

The GPIO pins to use with the API are the GPIO* labels as shown in [7].

The GPIO API keeps track of which GPIO pins are being used and their current function. Trying to select a pin which the API knows is being used somewhere else will result on an error, which may also happen if one of the GPIO based interfaces (JTAG or the SPI or I2C buses) is enabled and an application uses one of the pins reserved for those interfaces. This feature can help prevent some bugs such as unknowingly setting the same pin for different functions or several times in a row, and also prevents setting up GPIO based interfaces when the pins they require are being used as digital inputs or outputs (in the case of the SPI/I2C buses the error will come from the digital I/O pins setup, since the buses are enabled before the application starts to execute).

Warning: When using the GPIO pins care should be taken on the current drawn by the devices. Each pin can safely provide 16mA of current, and the total current drawn from all GPIO pins on a GPIO header should not surpass the 50mA mark (the maximum current the header 3v3 pin can provide). Since the GPIOs are directly connected to the arm, too much current can permanently damage the Raspberry Pi as it also does not feature any current protection mechanisms.

Using the GPIO API with an application

The GPIO API functions are to be used by both end users (to use GPIO pins as digital inputs or outputs) and device drivers (using the available GPIO based interfaces).

To use the GPIO API you must include its header

#include <bsp/gpio.h>

and initialize it by calling

gpio_initialize()

This will initialize the pin's data structure and set all pins as unused, the exception being the pins used for the SPI or I2C buses if any of them are enabled. If you want to use pins 2 and/or 3 as digital I/O pins you must first disable the I2C bus on the raspberrypi BSP configure.ac script. The same applies to pins 7, 8, 9, 10 and 11, which will be reserved to the SPI bus it it is enabled.

Any further gpio_initialize() call will be silently ignored.

With the GPIO API initialized, a pin function can be selected with:

gpio_select_pin(int pin, rpi_pin type)

where pin is one of the GPIO labels [7] and type must one of the following:

  • DIGITAL_INPUT
  • DIGITAL_OUTPUT
  • ALT_FUNC_0
  • ALT_FUNC_1
  • ALT_FUNC_2
  • ALT_FUNC_3
  • ALT_FUNC_4
  • ALT_FUNC_5
  • NOT_USED

In the case of an end user he will want to use the DIGITAL_* types to perform digital I/O.

If for some reason a user wants to change a pin's function at run time it must call

gpio_disable_pin(int dev_pin)

with the pin's GPIO label.

Using digital inputs

When a pin is functioning as a digital input an user may get its level - or value - either by polling the pin with

gpio_get_val(int pin)

which will return 0 or 1 depending on the pin's current logical level, or enable interrupts on the pin with:

gpio_enable_interrupt(int dev_pin, gpio_interrupt interrupt, void (*handler)(void))

where dev_pin is the pin's GPIO lavel and the handler may be a user defined function (must return void and receive no parameters and perform no blocking or wait calls. Print calls will slow the function execution). The interrupt parameter can be one of the following:

  • FALLING_EDGE
  • RISING_EDGE
  • BOTH_EDGES
  • LOW_LEVEL
  • HIGH_LEVEL
  • BOTH_LEVELS
  • NONE

This will cause the user defined function to be called when the pin's edge or level state matches the selected interrupt type.

Enabled interrupts may be disabled with:

gpio_disable_interrupt(int dev_pin)

Pull-up resistors

Each GPIO pin has a pull-up resistor, which can be configured with

gpio_setup_input_mode(int *pins, int pin_count, rpi_gpio_input_mode mode)

to set a number of pins to the same resistor configuration or

gpio_input_mode(int pin, rpi_gpio_input_mode mode)

to setup only one pin.

mode must be one of the following:

  • PULL_UP
  • PULL_DOWN
  • NO_PULL_RESISTOR

Deboucing switch

Since a digital input pin is usually connected to switches and buttons problems may arise when the pin is interrupt-driven, as a button may bounce when released and generate a number of unintended interrupts. To solve this the GPIO API provides a software solution by allowing to attach a deboucing function to an input pin with:

gpio_debounce_switch(int dev_pin, int ticks)

This functions receives a number of clock ticks which the API will wait to pass between interrupts before processing a new one. Any interrupt generated less than ticks clock ticks have passed since the last processed interrupt will be ignored.

Using digital outputs

A digital output pin can have a high (1) level by calling

gpio_set(int pin)

or a low (0) level with

gpio_clear(int pin)

Sample code and test cases

Sample code and device configurations can be seen on my development blog and on github.

Future work

The GPIO API does not know which Raspberry Pi model it is running on, so when setting up the I2C bus on the GPIO header it assumes to be running on a rev 2 board. This means that currently it is not possible for the API to setup the I2C bus on a rev 1 board. This can be either solved by a configure.ac option or by probing the hardware through the Mailbox interface.

The maximum current that guarantees the desired logic level of a GPIO pin can be configured by changing a pin's drive strength (http://pt.scribd.com/doc/101830961/GPIO-Pads-Control2). Warning: this configuration does not limit the amount of current drawn from a GPIO pin'''

SPI/I2C

The access to the two buses is done through the cpukit/libi2c API, which uses the Raspberry Pi buses through a set of low level functions which this GSOC project provides (refer to cpukit_libi2c/README_libi2c for documentation on the libi2c API).

To start using any of the buses first they must be enabled on the configure.ac script (at the root of the raspberrypi BSP directory).

This will cause the BSP_spi_init (for SPI) or BSP_i2c_init (for I2C) function to be called, which setups the bus interface on the GPIO header, registers the bus on "/dev/spi" or "/dev/i2c" and registers any needed device driver which requires the bus.

To use a device that requires one of the buses its device driver must be registered on the function BSP_spi_register_drivers or BSP_i2c_register_drivers using the libi2c function rtems_libi2c_register_drv.

Creating a new device driver

Because the SPI and I2C buses uses the libi2c API, device drivers will use libi2c functions to operate the bus so any existing libi2c device driver should also work with the raspberrypi BSP. This also means that a device driver may be located either on the raspberrypi BSP directory or in libchip.

Configuring the bus

The libi2c API provides a struct which should be used by a device driver to configure the bus to a certain device:

typedef struct {
  uint32_t baudrate;       
                          
  uint8_t  bits_per_char; 
  bool     lsb_first;      
  bool     clock_inv;     
  bool     clock_phs;     
  uint32_t  idle_char;     
} rtems_libi2c_tfr_mode_t;

A SPI device driver should fill the whole structure, but an I2C device driver only need to fill the baudrate field.

The baudrate field must contain the bus clock frequency in Hz.

When using the bus, the device driver (either for SPI or I2C) should perform the following steps:

# rtems_libi2c_send_start() # rtems_libi2c_ioctl() with the RTEMS_LIBI2C_IOCTL_SET_TFRMODE request and the rtems_libi2c_tfr_mode_t structure # rtems_libi2c_send_addr (rtems_device_minor_number minor, int rw). For SPI the rw parameter is not used, so any value is valid. For I2C rw should be FALSE when going to write to the bus and TRUE when reading from the bus. Doing a write followed by a read (such as sending a request command to the device and read its response) on the I2C bus both should perform this entire sequence (step 2 only needs to be performed once) and change the rw parameter accordingly. # A 1 millisecond wait can be made here, just to ensure that the device has time setup its registers (the raspberry pi SPI and I2C buses implementation both wait for the transfer on the bus to complete before returning)

# rtems_libi2c_write_bytes() or rtems_libi2c_read_bytes() to write or read data to/from the bus

# rtems_libi2c_send_stop

# Wait another 1 millisecond to ensure the device understood the stop

To use the device an application must open its device file, which will be in the format "/dev/spi.<device_name>" or "/dev/i2c.<device_name>", and operate it through the device file.

Sample code and test cases

Sample code and device configurations can be seen on my development blog and on github.

SPI future work

  • Test bidirectional mode;
  • Add lossi mode support;
  • Add DMA access (requires DMA support on the raspberrypi BSP).

I2C future work

  • Clock stretching (although there is a known bug on the raspberry pi itself with this feature);
  • 10 bit addressing;
  • Clock edge delays.

Miscellaneous Sections

Acronyms

RPi: Raspberry Pi

GPIO: General Purpose Input/Output?

I2C: Inter-Integrated Circuit

SPI: Serial Peripheral Interface

SOC: System-on-a-chip

References

[1] - Current GPIO work for RPi by Pierre Ficheux

[2] - Broadcom BCM2835 (the RPi SOC) Peripherals Guide

[3] - GSOC RTEMS repository

[4] - RTEMS_rpi_testing github repository

[5] - Development blog

[6] - http://elinux.org/RPi_Low-level_peripherals#P5_header

[7] - http://elinux.org/RPi_Low-level_peripherals#General_Purpose_Input.2FOutput_.28GPIO.29

Last modified on Feb 16, 2015 at 9:50:31 PM Last modified on Feb 16, 2015, 9:50:31 PM