GSoC 2022 RTEMS —Peripherals API

Duc Doan
5 min readJul 25, 2022

--

This article describes the Peripherals API system, application usage, and how to create new APIs based on this.

Repository URL: https://github.com/dtbpkmte/GSoC-2022-RTEMS

Description

This API aims to provide a way to easily create more APIs that provide functionalities for a GPIO pin. For example, a pin can have ADC functionality if it is given ADC API.

The design of this API system follows an object-oriented way. The peripherals API rtems_periph_api is itself a “base class” for all APIs that has operations on a single GPIO pin (like ADC, DAC, PWM, etc.). rtems_periph_api contains a field that tells the type of this API. A functional API will inherit this rtems_periph_api structure while having its own methods and attributes. Then, BSP or drivers can inherit those functional APIs to contain even more specific fields and methods.

Combining with the GPIO API, specifically the rtems_gpio structure, they create a means to assign an extra functionality to a GPIO pin with unified API calls. rtems_gpio has a member of type rtems_periph_api *, which points to an API object. Because any API type is also rtems_periph_api, this design allows for flexibly changing APIs to GPIO pins (which is done just by using rtems_periph_api_set_api()).

The API type is defined in the enumeration rtems_periph_api_type.

An API object might need some initialization steps when it is created. Therefore, the init member is a pointer to an initialization function.

The design of this system and its relationship with GPIO API can be viewed by this diagram:

Usage

Users need to first get a rtems_gpio pointer as usual:

rtems_gpio *pin;
rtems_gpio_get(PIN_NUMBER, &pin);

When API change is needed, users only need to call one function when they want to set an API to a pin before using that API’s features:

rtems_periph_api_set_api(rtems_gpio *pin, rtems_periph_api_type type);

Adding Peripherals API support for BSP/drivers

There are two important functions that a BSP or driver needs to implement and register with the Peripherals API: get_api(), which returns a functional API object, and remove_api(), which destroy and free the memory of an API object (naming is not important here, but one function creates an object and the other frees it). There are 2 main steps for this process.

Creating BSP-specific get_api() and remove_api()

Here are the steps to be done for a BSP:

  • Create the two required functions with the following prototype (example of STM32F4 BSP):
rtems_periph_api *stm32f4_periph_get_api(
rtems_gpio *pin,
rtems_periph_api_type type
);
rtems_status_code stm32f4_periph_remove_api(
rtems_gpio *pin
);
  • The get_api() function takes an argument of API type and returns the corresponding API object, while remove_api() just remove the API assigned to a pin.
  • Inside each function, add get_api() and remove_api() of the APIs that are supported. For example, if STM32F4 BSP supports ADC and DAC APIs, the get_api() function will be:
rtems_periph_api *stm32f4_periph_get_api(
rtems_gpio *pin,
rtems_periph_api_type type
)
{
switch (type) {
case RTEMS_PERIPH_API_TYPE_ADC:
return stm32f4_adc_get(pin);
case RTEMS_PERIPH_API_TYPE_DAC:
return stm32f4_dac_get(pin);
default:
return NULL;
}
}

Note: The stm32f4_adc_get() and stm32f4_dac_get() functions are from BSP’s implementation of functional APIs. After defining them, you need to put them here in get_api(). As you add more API supports, you need to add more cases in the switch.

Enable Peripherals API in GPIO

The Peripherals API is dependent on GPIO. Therefore, we need to configure GPIO to register the get_api() and remove_api() functions we defined above.

  • Inside the source code of BSP GPIO, include the header that contains 2 functions above
  • In bsp_gpio_register_controllers(), register those 2 functions:
rtems_status_code bsp_gpio_register_controllers(
void
)
{
return rtems_gpio_register(
stm32f4_gpio_get,
stm32f4_gpio_destroy,
stm32f4_periph_get_api, // get_api
stm32f4_periph_remove_api, // remove_api
n // number of pins
);
}
  • The Peripherals API support should be ready by now. As you add a new API implementation, you only need to modify get_api() and remove_api() functions.

Creating new APIs

This section documents how to create new APIs in this system. The general idea is to “inherit” rtems_periph_api, but here are detailed steps:

  • Create a C header file
  • Include necessary headers: bsp/periph_api.h, bsp/gpio2.h
  • Declare necessary functions and variables according to the functionality of the API
  • Create a structure with the first member being rtems_periph_api type. Example:
struct rtems_adc_api {
rtems_periph_api base;
// ADC API-specific members
uint32_t read_raw(rtems_gpio *);
void read_raw_nb(rtems_gpio *, uint32_t *result);
...
}
  • This is optional, but might make it easier for BSP/drivers to inherit this API: define a macro that helps building an object of this API structure
#define RTEMS_ADC_BUILD_API(                        \
_init, \
_read_raw, \
_read_raw_nb) \
{ \
.base = { \
.api_type = RTEMS_PERIPH_API_TYPE_ADC, \
.init = _init \
}, \
.read_raw = _read_raw, \
.read_raw_nb = _read_raw_nb \
}
  • Define API functions: you may want to check for the type of API before executing functions. That will decrease speed though, so one option is to only check for type if RTEMS_DEBUG is defined. An example function would be:
uint32_t rtems_adc_read_raw(rtems_gpio *pin) {
#ifdef RTEMS_DEBUG
while (*(rtems_periph_type *)pin != RTEMS_PERIPH_TYPE_ADC);
#endif
return ((rtems_adc_api *) (pin->api))->read_raw(pin);
}

Note: Remember to cast the API pointer into your API.

Implementation of API for BSP/drivers

When you are done with creating the API, the next step would be implementing it for a BSP or device driver. The general idea is to create a structure that “inherits” an API structure. Below are the detailed steps, taking the ADC API and STM32F4 BSP as an example (note that this is not real code of ADC API).

Create BSP-specific implementation

  • Include the functional API header. Here, it’s bsp/adc.h, the RTEMS ADC API.
  • Create a structure with the first member being the API object:
struct stm32f4_adc {
rtems_adc_api base_api;

// STM32F4-specific ADC members
ADC_TypeDef *ADCx;
uint32_t channel;
uint32_t resolution;
}
  • Create a base API object that will be assigned to new stm32f4_adcobjects. This object must contain pointers to this BSP’s handlers:
static const rtems_adc_api stm32f4_base_api = RTEMS_ADC_BUILD_API(
stm32f4_init,
stm32f4_read_raw,
stm32f4_read_raw_nb
);
  • Create an init() function that will initialize the ADC when a stm32f4_adc object is created. You don’t have to call it; the peripherals API will call it automatically.
/**
* @brief Performs initialization for an ADC pin
* @note This function needs to be registered with the
* peripherals API.
*/
void stm32f4_adc_init(
rtems_gpio *base
)
{
// initialize hardware here
}
  • Create a function that will create a new object of this BSP-specific structure and return a pointer to it. This is where the BUILD_BASE macro can be used to create the base API object.
rtems_periph_api *stm32f4_adc_get(
rtems_gpio *gpio
)
{
// create new object and return pointer to it
}
  • Similarly, create a function that will free the memory of an API object:

Note: this only frees the stm32f4_adc object (and all other objects inside it), but it must NOT free the rtems_gpio object.

rtems_periph_api *stm32f4_adc_destroy(
rtems_gpio *gpio
)
{
// free the memory of an API object
}
  • Remember to edit get_api() and remove_api according to the steps above (in section “Adding Peripherals API support for BSP/drivers”)

--

--

Duc Doan

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