![]() |
RA Flexible Software Package Documentation
Release v5.8.0
|
|
This guide describes the Renesas Flexible Software Package (FSP) architecture and how to use the FSP Application Programming Interface (API).
FSP uses the ISO/IEC 9899:1999 (C99) C programming language standard. Specific features introduced in C99 that are used include standard integer types (stdint.h), booleans (stdbool.h), designated initializers, and the ability to intermingle declarations and code.
Doxygen is the default documentation tool used by FSP. You can find Doxygen comments throughout the FSP source.
Weak symbols are used occasionally in FSP. They are used to ensure that a project builds even when the user has not defined an optional function.
Dynamic memory allocation through use of the malloc() and free() functions are not used in FSP modules; all memory required by FSP modules is allocated in the application and passed to the module in a pointer. Exceptions are considered only for ports of 3rd party code that require dynamic memory.
Term | Description | Reference |
---|---|---|
BSP | Short for Board Support Package. In FSP, the BSP provides just enough foundation to allow other FSP modules to work together without issue. | MCU Board Support Package |
Module | Modules can be peripheral drivers, purely software, or anything in between. Each module consists of a folder with source code, documentation, and anything else that the customer needs to use the code effectively. Modules are independent units, but they may depend on other modules. Applications can be built by combining multiple modules to provide the user with the features they need. | FSP Modules |
Driver | A driver is a specific kind of module that directly modifies registers on the MCU. | - |
Interface | An interface contains API definitions that can be shared by modules with similar features. Interfaces are definitions only and do not add to code size. | FSP Interfaces |
Stacks | The FSP architecture is designed such that modules work together to form a stack. A stack consists of a top level module and all its dependencies. | FSP Stacks |
Module Instance | Single and independent instantiation of a module. An application may require two GPT timers. Each of these timers is a module instance of the r_gpt module. | - |
Application | Code that is owned and maintained by the user. Application code may be based on sample application code provided by Renesas, but it is the responsibility of the user to maintain as necessary. | - |
Callback Function | This term refers to a function that is called when an event occurs. As an example, suppose the user would like to be notified every second based on the RTC. As part of the RTC configuration, a callback function can be supplied that will be jumped to during each RTC interrupt. When a single callback services multiple events, the arguments contain the triggering event. Callback functions for interrupts should be kept short and handled carefully because when they are called the MCU is still inside of an interrupt, delaying any pending interrupts. | - |
Modules are the core building block of FSP. Modules can do many different things, but all modules share the basic concept of providing functionality upwards and requiring functionality from below.
The amount of functionality provided by a module is determined based on functional use cases. Common functionality required by multiple modules is often placed into a self-contained submodule so it can be reused. Code size, speed and complexity are also considered when defining a module.
The simplest FSP application consists of one module with the Board Support Package (BSP) and the user application on top.
The Board Support Package (BSP) is the foundation for FSP modules, providing functionality to determine the MCU used as well as configuring clocks, interrupts and pins. For the sake of clarity, the BSP will be omitted from further diagrams.
Some modules distributed alongside FSP originate from outside sources. A full list of sources for FSP modules, including versions and hyperlinks, can be found in the Third Party Software section of the release notes for each release.
All modules distributed with FSP are packaged as CMSIS components in CMSIS packs. Each module consists of source files and a tooling support file used to integrate the module with e² studio or RASC. The tooling support file defines the configurations used to generate code in the ra_gen and ra_cfg folders.
Module versions can be seen on the Components tab of the FSP Configuration editor. The FSP Configuration editor automatically selects compatible components.
All third party modules have a semantic version are versioned with their original semantic version plus added metadata fsp.<fsp_semantic_version>
. The metadata is added to reflect the tooling support file added for the FSP configuration tool.
Third party modules versioned with +renesas.<counter>
in the metadata have been forked and updated for FSP. If +renesas.<counter>
is not in the metadata, the third party code is unchanged from its original source.
If changes are made to third party module source code to support FSP, the changes are pushed to a public Renesas GitHub fork of the original source. Links to Renesas forks are provided in the Third Party Software section of the release notes for each release.
Modules that originate from outside sources that do not have a semantic version are versioned with the FSP version.
All modules that are part of FSP or integrated with FSP are tested as a package. Mixing versions is not encouraged and may lead to support issues.
When modules are layered atop one another, an FSP stack is formed. The stacking process is performed by matching what one module provides with what another module requires. For example, the SPI module (SPI (r_spi)) requires a module that provides the transfer interface (Transfer Interface) to send or receive data without a CPU interrupt. The transfer interface requirement can be fulfilled by the DTC driver module (Transfer (r_dtc)).
Through this methodology the same code can be shared by several modules simultaneously. The example below illustrates how the same DTC module can be used with SPI (SPI (r_spi)), UART (UART (r_sci_uart)) and SDHI (SD/MMC (r_sdhi)).
The ability to stack modules ensures the flexibility of the architecture as a whole. If multiple modules include the same functionality issues arise when application features must work across different user designs. To ensure that modules are reusable, any dependent modules must be capable of being swapped out for other modules that provide the same features. The FSP architecture provides this flexibility to swap modules in and out through the use of FSP interfaces.
At the architecture level, interfaces are the way that modules provide common features. This commonality allows modules that adhere to the same interface to be used interchangeably. Interfaces can be thought of as a contract between two modules - the modules agree to work together using the information that was established in the contract.
On RA hardware there is occasionally an overlap of features between different peripherals. For example, I2C communications can be achieved through use of the IIC peripheral or the SCI peripheral. However, there is a difference in the level of features provided by both peripherals; in I2C mode the SCI peripheral will only support a subset of the capabilities of the fully-featured IIC.
Interfaces aim to provide support for the common features that most users would expect. This means that some of the advanced features of a peripheral (such as IIC) might not be available in the interface. In most cases these features are still available through interface extensions.
In FSP design, interfaces are defined in header files. All interface header files are located in the folder ra/fsp/inc/api
and end with *_api.h
. Interface extensions are defined in header files in the folder ra/fsp/inc/instances
. The following sections detail what makes up an interface.
Whenever possible, interfaces use typed enumerations for function parameters and structure members.
Enumerations remove uncertainty when deciding what values are available for a parameter. FSP enumeration options follow a strict naming convention where the name of the type is prefixed on the available options. Combining the naming convention with the autocomplete feature available in e² studio (Ctrl + Space) provides the benefits of rapid coding while maintaining high readability.
Callback functions allow modules to asynchronously alert the user application when an event has occurred, such as when a byte has been received over a UART channel or an IRQ pin is toggled. FSP driver modules define and handle the interrupt service routines for RA MCU peripherals to ensure any required hardware procedures are implemented. The interrupt service routines in FSP modules then call the user-defined callbacks to allow the application to respond.
Callback functions must be defined in the user application. They always return void
and take a structure for their one parameter. The structure is defined in the interface for the module and is named <interface>_callback_args_t
. The contents of the structure may vary depending on the interface, but two members are common: event
and p_context
.
The event
member is an enumeration defined in the interface used by the application to determine why the callback was called. Using the UART example, the callback could be triggered for many different reasons, including when a byte is received, all bytes have been transmitted, or a framing error has occurred. The event
member allows the application to determine which of these three events has occurred and handle it appropriately.
The p_context
member is used for providing user-specified data to the callback function. In many cases a callback function is shared between multiple channels or module instances; when the callback occurs, the code handling the callback needs context information so that it can determine which module instance the callback is for. For example, if the callback wanted to make an FSP API call in the callback, then at a minimum the callback will need a reference to the relevant control structure. To make this easy, the user can provide a pointer to the control structure as the p_context
. When the callback occurs, the control structure is passed in the p_context
element of the callback structure.
Callback functions are called from within an interrupt service routine. For this reason callback functions should be kept as short as possible so they do not affect the real time performance of the user's system. An example skeleton function for the flash interface callback is shown below.
When a module is not directly used in the user application (that is, it is not the top layer of the stack), its callback function will be handled by the module above. For example, if a module requires a UART interface module the upper layer module will control and use the UART's callback function. In this case the user would not need to create a callback function for the UART module in their application code.
At a minimum, all FSP interfaces include three data structures: a configuration structure, an API structure, and an instance structure.
The configuration structure is used for the initial configuration of a module during the <MODULE>_Open()
call. The structure consists of members such as channel number, bitrate, and operating mode.
The configuration structure is used purely as an input into the module. It may be stored and referenced by the module, so the configuration structure and anything it references must persist as long as the module is open.
The configuration structure is allocated for each module instance in files generated by the RA Configuration editor.
When FSP stacks are used, it is also important to understand that configuration structures only have members that apply to the current interface. If multiple layers in the same stack define the same configuration parameters then it becomes difficult to know where to modify the option. For example, the baud rate for a UART is only defined in the UART module instance. Any modules that use the UART interface rely on the baud rate being provided in the UART module instance and do not offer it in their own configuration structures.
All interfaces include an API structure which contains function pointers for all the supported interface functions. An example structure for the DAC is shown below.
The API structure is what allows for modules to easily be swapped in and out for other modules that are instances of the same interface. Let's look at an example application using the DAC interface above.
RA MCUs have an internal DAC peripheral. If the DAC API structure in the DAC interface is not used the application can make calls directly into the module. In the example below the application is making calls to the R_DAC_Write() function which is provided in the r_dac module.
Now let's assume that the user needs more DAC channels than are available on the MCU and decides to add an external DAC module named dac_external using I2C for communications. The application must now distinguish between the two modules, adding complexity and further dependencies to the application.
The use of interfaces and the API structure allows for the use of an abstracted DAC. This means that no extra logic is needed if the user's dac_external module implements the FSP DAC interface, so the application no longer depends upon hard-coded module function names. Instead the application now depends on the DAC interface API which can be implemented by any number of modules.
Every FSP interface also has an instance structure. The instance structure encapsulates everything required to use the module:
The instance structure is not required at the application layer. It is used to connect modules to their dependencies (other than the BSP).
Instance structures have a standardized name of <interface>_instance_t
. An example from the Transfer Interface is shown below.
Note that when an instance structure variable is declared, the API is the only thing that is instance specific, not module instance specific. This is because all module instances of the same module share the same underlying module source code. If SPI is being used on SCI channels 0 and 2 then both module instances use the same API while the configuration and control structures are typically different.
While interfaces dictate the features that are provided, instances actually implement those features. Each instance is tied to a specific interface. Instances use the enumerations, data structures, and API prototypes from the interface. This allows an application that uses an interface to swap out the instance when needed.
On RA MCUs some peripherals are used to implement multiple interfaces. In the example below the IIC and SPI peripherals map to only one interface each while the SCI peripheral implements three interfaces.
In FSP design, instances consist of the interface extension and API defined in the instance header file located in the folder ra/fsp/inc/instances
and the module source ra/fsp/src/<module>
.
The control structure is used as a unique identifier for the module instance and contains memory required by the module. Elements in the control structure are owned by the module and must not be modified by the application. The user allocates storage for a control structure, often as a global variable, then sends a pointer to it into the <MODULE>_Open()
call for a module. At this point, the module initializes the structure as needed. The user must then send in a pointer to the control structure for all subsequent module calls.
In some cases, instances require more information than is provided in the interface. This situation can occur in the following cases:
The p_extend
member provides this extension function.
Use of interface extensions is not always necessary. Some instances do not offer an extension since all functionality is provided in the interface. In these cases the p_extend
member can be set to NULL. The documentation for each instance indicates whether an interface extension is available and whether it is mandatory or optional.
When extended configuration is required it can be supplied through the p_extend
parameter of the interface configuration structure.
The extended configuration structure is part of the instance, but it is also still considered to be part of the configuration structure. All usage notes about the configuration structure described in FSP Interface Configuration Structure apply to the extended configuration structure as well.
The extended configuration structure and all typed structures and enumerations required to define it make up the interface extension.
Each instance includes a constant global variable tying the interface API functions to the functions provided by the module. The name of this structure is standardized as g_<interface>_on_<instance>
. Examples include g_spi_on_spi, g_transfer_on_dtc, and g_adc_on_adc. This structure is available to be used through an extern in the instance header file (r_spi.h, r_dtc.h, and r_adc.h respectively).
FSP functions start with the uppercase module name (<MODULE>
). All modules have <MODULE>_Open()
and <MODULE>_Close()
functions. The <MODULE>_Open()
function must be called before any of the other functions.
Other functions that will commonly be found are <MODULE>_Read()
, <MODULE>_Write()
, <MODULE>_InfoGet()
, and <MODULE>_StatusGet()
. The <MODULE>_StatusGet()
function provides a status that could change asynchronously, while <MODULE>_InfoGet()
provides information that cannot change after open or can only be updated by API calls. Example function names include:
The const
qualifier is used with API parameters whenever possible. An example case is shown below.
In this example, flash_cfg_t
is a structure of configuration parameters for the r_flash_hp module. The parameter p_cfg
is a pointer to this structure. The first const
qualifier on p_cfg
ensures the flash_cfg_t
structure cannot be modified by R_FLASH_HP_Open(). This allows the structure to be allocated as a const variable and stored in ROM instead of RAM.
The const
qualifier after the pointer star for both p_ctrl
and p_cfg
ensures the FSP function does not modify the input pointer addresses. While not fool-proof by any means this does provide some extra checking inside the FSP code to ensure that arguments that should not be altered are treated as such.
The BSP provides a function R_FSP_VersionGet()
which fills in a structure of type fsp_pack_version_t
. This can be used to determine the FSP version at runtime.
There are also FSP_VERSION_*
macros in fsp_version.h that can be used to determine the FSP version at build time.
All modules have a build-time configuration header file. Most configuration options are supplied at run time, though options that are rarely used or apply to all instances of a module may be moved to build time. The advantage of using a build-time configuration option is to potentially reduce code size reduction by removing an unused feature.
All modules have a build time option to enable or disable parameter checking for the module. FSP modules check function arguments for validity when possible, though this feature is disabled by default to reduce code size. Enabling it can help catch parameter errors during development and debugging. By default, each module's parameter checking configuration inherits the BSP parameter checking setting (set on the BSP tab of the RA Configuration editor). Leaving each module's parameter checking configuration set to Default (BSP) allows parameter checking to be enabled or disabled globally in all FSP code through the parameter checking setting on the BSP tab.
If an error condition can reasonably be avoided it is only checked in a section of code that can be disabled by disabling parameter checking. Most FSP APIs can only return FSP_SUCCESS if parameter checking is disabled. An example of an error that cannot be reasonably avoided is the "bus busy" error that occurs when another master is using an I2C bus. This type of error can be returned even if parameter checking is disabled.
The high-level file structure of an FSP project is shown below.
Directly underneath the base ra
folder the folders are split into the source and include folders. Include folders are kept separate from the source for easy browsing and easy setup of include paths.
The ra_gen
folder contains code generated by the RA Configuration editor. This includes global variables for the control structure and configuration structure for each module.
The ra_cfg
folder is where configuration header files are stored for each module. See FSP Build Time Configurations for information on what is provided in these header files.
TrustZone support for FSP is primarily handled in the RA Configuration Tool.
During development of a TrustZone project, users create an RA TrustZone Secure Project first, followed by an RA TrustZone Non-secure Project that is linked to the RA TrustZone Secure Project. Allocation of secure memory is handled automatically within the tooling. The non-secure project starts at the required alignment boundary beyond the memory taken by the secure project.
The tooling generates guard functions for any module marked as Non-secure Callable. These guard functions are owned by the application once generated, so they can be modified as necessary by the secure application developer.
The default non-secure callable guard functions limit the configuration and control structure to the structures generated in the secure project. They also check any input pointers to ensure the caller does not overwrite secure memory.
If the non-secure project needs a callback function from a non-secure callable module, the callback can be registered after the module is opened using the callback_set() guard function.
The TrustZone projects can be migrated to newer FSP version as mentioned in the following resource.
Additional steps are required if newer FSP version introduces new guard function. In such case, simply migrating the project would result in the build failure for non secure project. Following extra steps are required:
The following resources provide technical background, application notes and example projects that demonstrate key TrustZone concepts and implementation procedures.
FSP modules are meant to be both reusable and stackable. It is important to remember that modules are not dependent upon other modules, but upon other interfaces. The user is then free to fulfill the interface using the instance that best fits their needs.
In the image above interface Y is a dependency of interface X and has its own dependency on interface Z. Interface X only has a dependency on interface Y. Interface X has no knowledge of interface Z. This is a requirement for ensuring that layers can easily be swapped out.
The typical use of an FSP module involves generating required module data then using the API in the application.
The RA Configuration editor (available both in the Renesas e² studio IDE as well as through the standalone RA Smart Configurator) provides a graphical user interface for setting the parameters of the interface and instance configuration structures. It also automatically includes those structures (once they are configured in the GUI) in application-specific header files that can be included in application code.
The RA Configuration editor allocates storage for the control structures, all required configuration structures, and the instance structure in generated files in the ra_gen
folder. Use the Properties window to set the values for the members of the configuration structures as needed. Refer to the Configuration section of the module usage notes for documentation about the configuration options.
If the interface has a callback function option then the application must declare and define the function. The return value is always of type void
and the parameter to the function is a typed structure of name <interface>_callback_args_t
. Once the function has been defined, assign its name to the p_callback
member of the configuration structure. Callback function names can be assigned through the Properties window for the selected module.
Call the module's <MODULE>_Open()
function. Pass pointers to the generated control structure and configuration structure. The names of these structures are based on the 'Name' field provided in the configuration editor. The control structure is <Name>_ctrl
and the configuration structure is <Name>_cfg
. An example <MODULE>_Open()
call for an r_rtc module instance named g_clock
is:
Refer to the module usage notes for example code to help get started with any particular module.