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, whileremove_api()
just remove the API assigned to a pin. - Inside each function, add
get_api()
andremove_api()
of the APIs that are supported. For example, if STM32F4 BSP supports ADC and DAC APIs, theget_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()
andstm32f4_dac_get()
functions are from BSP’s implementation of functional APIs. After defining them, you need to put them here inget_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()
andremove_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_adc
objects. 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 astm32f4_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 thertems_gpio
object.
rtems_periph_api *stm32f4_adc_destroy(
rtems_gpio *gpio
)
{
// free the memory of an API object
}
- Remember to edit
get_api()
andremove_api
according to the steps above (in section “Adding Peripherals API support for BSP/drivers”)