Duc Doan
5 min readJul 12, 2022
mkdir -p ~/ros2_humble/src
cd ~/ros2_humble
wget https://raw.githubusercontent.com/ros2/ros2/humble/ros2.repos
vcs import src < ros2.reposGSoC 2022 RTEMS — Coding Period Week 4 — GPIO Wiki

The GPIO API aims to provide portability among all boards supported by RTEMS.

***Updates***

  • v1: 7/12/2022
  • v2: 7/25/2022
- Add support of peripherals API
- Removal of rtems_gpio_init() and rtems_gpio_deinit()
- bsp_gpio_register_controllers() is now called by RTEMS GPIO API, no need to be called in bsp_start_hook

Features

  • One single application code compiles for all BSPs.
  • Flat pin numbering system, mapping from 0.
  • Supports adding GPIO expanders with same API as integrated GPIO controllers.
  • Configurable pin mode, pull resistor mode.
  • Supports external interrupt.
  • Operations: read, write, toggle pin.

Description

This GPIO API maps pins from all controllers, including integrated and GPIO expanders, into a flat numbering system from 0 to (pin count — 1). The first indices, counting from 0, are mapped to integrated GPIO pins. An example of pin mapping: on STM32F407x, there are 9 GPIO ports (GPIOA to GPIOI) with 16 pins per port; this is mapped from pin 0 (pin A0) to pin 143 (pin I15).

Pin mapping is done using some internal tables that store pin counts and _get() functions of each GPIO controller. Given a virtual pin number, a _get() function returns a correct rtems_gpio object. Those functions are driver-specific and need to be registered to RTEMS GPIO using rtems_gpio_register.

The core of this API is the struct rtems_gpio, which holds information about virtual pin number and pointer to a rtems_gpio_handlers object, which contains pointers to BSP/driver-specific handlers. rtems_gpio struct is similar to a base class; all drivers need to create structs that “inherit” it (putting a rtems_gpio as the first member). Each rtems_gpio object can be considered a pin, and it is passed to functions to operate on a pin. User needs to get an object by passing a virtual pin number, then after finishing everything with that pin, they should destroy that object to prevent memory leak.

GPIO API now supports the Peripherals API system. rtems_gpio structure contains a rtems_periph_api * member that points to an API object assigned to the pin. GPIO has to call the Peripherals API’s initialization function and pass to it some handlers (get_api() and remove_api()). More details about the relationship of the GPIO API and the Peripherals API are discussed in the Peripherals API wiki.

Usage

General initialization procedure:

  • Create a variable of type rtems_gpio *, you need one for each pin.
  • Use rtems_gpio_get() to store the pointer to a rtems_gpio object into the created variable; this is the representation of a pin.
  • Configure pin mode or pull resistor mode using rtems_gpio_set_pin_mode() and rtems_gpio_set_pull().
  • Once the user has done with the pin, they can destroy the object using rtems_gpio_destroy() to prevent memory leak.

To use the pin with interrupt (after initialized):

  • Use rtems_gpio_configure_interrupt() to register ISR, ISR argument, trigger mode and pull resistor mode.
  • Use rtems_gpio_enable_interrupt() to actually enable interrupt.
  • Use rtems_gpio_disable_interrupt() to disable interrupt.
  • Use rtems_gpio_remove_interrupt() to unregister ISR and do other deinitializations.

Below is an example application to turn on an LED at virtual pin 60:

// Create pointer to a rtems_gpio object
rtems_gpio *led;
rtems_gpio_get(60, &led);
// Initialize the pin as output push-pull, no pull resistor
rtems_gpio_init(led);
rtems_gpio_set_pin_mode(led, RTEMS_GPIO_PINMODE_OUTPUT_PP);
rtems_gpio_set_pull(led, RTEMS_GPIO_NOPULL);
// Write logical HIGH to the pin
rtems_gpio_write(led, RTEMS_GPIO_PIN_SET);

Example to configure interrupt for a push button on pin 0:

...
// Create pointer to a rtems_gpio object
rtems_gpio *button;
rtems_gpio_get(0, &button);
// Set the pin as input
rtems_gpio_set_pin_mode(button, RTEMS_GPIO_PINMODE_INPUT);
// Configure interrupt in falling edge mode, no pull resistor
rtems_gpio_configure_interrupt(
button,
button_isr,
null,
RTEMS_GPIO_INT_TRIG_FALLING,
RTEMS_GPIO_NOPULL
);
// Enable interrupt
rtems_gpio_enable_interrupt(button);
...
// button ISR
void button_isr(void* arg) {
// do something
}

BSP/driver implementation guide

BSPs and GPIO expander drivers need to implement some specific functions and register to RTEMS GPIO to use this API. The steps are as follow, with example of STM32F4 BSP:

  • Include GPIO API header file: gpio2.h.
  • Create a struct that has a rtems_gpio as the first member. You can add more members into the struct for driver-specific operations.
typedef struct {
/**
* @brief This member is a rtems_gpio object.
*/
rtems_gpio base;
/**
*@brief This member is the pin number from 0 to 15.
*/
uint32_t pin;
/**
* @brief This member is HAL GPIOx pointer.
*/
GPIO_TypeDef *port;
} stm32f4_gpio;
  • Create a get_gpio_from_base() macro to get the driver-specific GPIO object from a rtems_gpio pointer.
/**
* @brief Macro to get stm32f4_gpio object from a base rtems_gpio
* object.
*
* This is a wrapper of RTEMS_CONTAINER_OF macro
*
* @param base The pointer to a rtems_gpio object
* @retval The pointer to the stm32f4_gpio object owning
* the specified rtems_gpio object
*/
#define get_gpio_from_base(base) \
RTEMS_CONTAINER_OF(base, stm32f4_gpio, base)
  • Create driver-specific handlers that adhere to the function types defined by the API (in rtems_gpio_handlers) and implement them. Create a rtems_gpio_handlers object with all required function pointers; this will be necessary to create a GPIO object.
static const rtems_gpio_handlers stm32f4_gpio_handlers = {
.set_pin_mode = stm32f4_gpio_set_pin_mode,
.set_pull = stm32f4_gpio_set_pull,
.configure_interrupt = stm32f4_gpio_configure_interrupt,
.remove_interrupt = stm32f4_gpio_remove_interrupt,
.enable_interrupt = stm32f4_gpio_enable_interrupt,
.disable_interrupt = stm32f4_gpio_disable_interrupt,
.read = stm32f4_gpio_read,
.write = stm32f4_gpio_write,
.toggle = stm32f4_gpio_toggle
};
  • Define 2 driver-specific functions, get() and destroy(), that creates a GPIO object from intermediate pin number and destroy an existing object. They are required to be passed into rtems_gpio_register().

Note: an intermediate pin number is counted from 0 inside each driver. This is calculated by virtual_pin — pin_offset. A pin offset is the number of pins mapped before the first pin of a specific controller. For example, a GPIO expander is registered after 144 pins of STM32F4. Virtual pin 150 then corresponds to intermediate pin 6 of the GPIO expander. Intermediate pin number is used because drivers do not now the pin maps of RTEMS GPIO manager. Drivers need to convert intermediate pin number to their specific physical pin.

Note: Use RTEMS_GPIO_BUILD_BASE macro to initialize the base rtems_gpio member.

  • Implement bsp_gpio_register_controllers(); this function needs to call rtems_gpio_register() once for each integrated GPIO controller to register it with RTEMS GPIO. This will map the physical pins into virtual pin numbering system.

Important: This source file must include the BSP-specific header for the peripherals API. In the rtems_gpio_register() call, the handlers in that header _get_api() and _remove_api() must be passed as arguments. Please refer to my Peripherals API article for this step.

  • Inside spec/build folder of the BSP, add the newly added headers and source files into obj.yml. Option files need to be added to grp.yml.
  • Go to root of RTEMS source, build and install the BSP.
Duc Doan

Robotics Engineering, Electrical and Computer Engineering, WPI 2024. Contributor at RTEMS, Google Summer of Code 2022.