RA Flexible Software Package Documentation  Release v5.2.0

 
FSP Architecture

FSP Architecture Overview

This guide describes the Renesas Flexible Software Package (FSP) architecture and how to use the FSP Application Programming Interface (API).

C99 Use

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

Doxygen is the default documentation tool used by FSP. You can find Doxygen comments throughout the FSP source.

Weak Symbols

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.

Memory Allocation

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.

FSP Terms

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. -

FSP Modules

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.

figure-fsp-module.svg
Modules

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.

figure-fsp-application-single-module.svg
Module with application

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.

Module Sources

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.

Module Distribution

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 Versioning

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.

FSP Stacks

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)).

figure-fsp-multiple-drivers-use-dtc.svg
Stacks -- Shared DTC Module

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.

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.

FSP Interface Enumerations

Whenever possible, interfaces use typed enumerations for function parameters and structure members.

typedef enum e_i2c_master_addr_mode
{
I2C_MASTER_ADDR_MODE_7BIT = 1, ///< Use 7-bit addressing mode
I2C_MASTER_ADDR_MODE_10BIT = 2, ///< Use 10-bit addressing mode
} i2c_master_addr_mode_t;

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.

FSP Interface Callback Functions

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.

void flash_callback (flash_callback_args_t * p_args)
{
/* See what event caused this callback. */
switch (p_args->event)
{
{
/* Handle event. */
break;
}
{
/* Handle event. */
break;
}
{
/* Handle event. */
break;
}
{
/* Handle event. */
break;
}
{
/* Handle error. */
break;
}
{
/* Handle error. */
break;
}
{
/* Handle error. */
break;
}
{
/* Handle error. */
break;
}
{
/* Handle error. */
break;
}
}
}

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.

FSP Interface Data Structures

At a minimum, all FSP interfaces include three data structures: a configuration structure, an API structure, and an instance structure.

FSP Interface Configuration 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.

FSP Interface API Structure

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.

typedef struct st_dac_api
{
/** Initial configuration.
*
* @param[in] p_ctrl Pointer to control block. Must be declared by user. Elements set here.
* @param[in] p_cfg Pointer to configuration structure. All elements of this structure must be set by user.
*/
fsp_err_t (* open)(dac_ctrl_t * const p_ctrl, dac_cfg_t const * const p_cfg);
/** Close the D/A Converter.
*
* @param[in] p_ctrl Control block set in @ref dac_api_t::open call for this timer.
*/
fsp_err_t (* close)(dac_ctrl_t * const p_ctrl);
/** Write sample value to the D/A Converter.
*
* @param[in] p_ctrl Control block set in @ref dac_api_t::open call for this timer.
* @param[in] value Sample value to be written to the D/A Converter.
*/
fsp_err_t (* write)(dac_ctrl_t * const p_ctrl, uint16_t value);
/** Start the D/A Converter if it has not been started yet.
*
* @param[in] p_ctrl Control block set in @ref dac_api_t::open call for this timer.
*/
fsp_err_t (* start)(dac_ctrl_t * const p_ctrl);
/** Stop the D/A Converter if the converter is running.
*
* @param[in] p_ctrl Control block set in @ref dac_api_t::open call for this timer.
*/
fsp_err_t (* stop)(dac_ctrl_t * const p_ctrl);
} dac_api_t;

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.

figure-fsp-dac-write.svg
DAC Write example

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.

figure-fsp-external-dac.svg
DAC Write with two write modules

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.

figure-fsp-dac-interface.svg
DAC Interface

FSP Interface Instance Structure

Every FSP interface also has an instance structure. The instance structure encapsulates everything required to use the module:

  • A pointer to the instance API structure (FSP Instance API)
  • A pointer to the configuration structure
  • A pointer to the control structure

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.

typedef struct st_transfer_instance
{
transfer_ctrl_t * p_ctrl; ///< Pointer to the control structure for this instance
transfer_cfg_t const * p_cfg; ///< Pointer to the configuration structure for this instance
transfer_api_t const * p_api; ///< Pointer to the API structure for this instance
} transfer_instance_t;

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.

FSP Instances

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.

figure-fsp-interface-implementation-example.svg
Instances

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>.

FSP Instance Control Structure

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.

FSP Interface Extensions

In some cases, instances require more information than is provided in the interface. This situation can occur in the following cases:

  • An instance offers extra features that are not common to most instances of the interface. An example of this is the start source selection of the GPT (Timer, General PWM (r_gpt)). The GPT can be configured to start based on hardware events such as a falling edge on a trigger pin. This feature is not common to all timers, so it is included in the GPT instance.
  • An interface must be very generic out of necessity. As an interface becomes more generic, the number of possible instances increases. An example of an interface that must be generic is a block media interface that abstracts functions required by a file system. Possible instances include SD card, SPI Flash, SDRAM, USB, and many more.

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.

FSP Extended Configuration Structure

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.

FSP Instance API

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 API Standards

FSP Function Names

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:

Use of const in API parameters

The const qualifier is used with API parameters whenever possible. An example case is shown below.

fsp_err_t R_FLASH_HP_Open(flash_ctrl_t * const p_api_ctrl, flash_cfg_t const * const p_cfg);

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.

FSP Version Information

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.

FSP Build Time Configurations

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.

FSP File Structure

The high-level file structure of an FSP project is shown below.

ra_gen
ra
+---fsp
+---inc
| +---api
| \---instances
\---src
+---bsp
\---r_module
ra_cfg
+---fsp_cfg
+---bsp
+---driver

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.

FSP TrustZone Support

TrustZone support for FSP is primarily handled in the RA Configuration Tool.

FSP TrustZone Projects

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.

Non-Secure Callable Guard Functions

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.

Callbacks in Non-Secure from Non-Secure Callable Modules

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.

Migrating TrustZone Project to newer FSP Version

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:

  • If xxx_guard.c file in src folder of secure project was not modified earlier
    1. Delete the xxx_guard.c file in secure project before generating the Project Files.
    2. Generate Project contents and build the secure project.
    3. Follow the steps in Application Note to migrate the Non-Secure project.
  • If xxx_guard.c file in src folder of secure project was modified earlier
    1. Take the backup of existing xxx_guard.c file and delete it from src folder of secure project before generating the Project Files.
    2. Generate Project contents. It creates a new guard.c file. Compare the contents of the older file and newly generated guard.c file.
    3. Copy the modified code from the older file (i.e Security checks added by user) and add it to newly generated file.
    4. Follow the steps in Application Note to migrate the Non-Secure project.

Additional TrustZone Information

The following resources provide technical background, application notes and example projects that demonstrate key TrustZone concepts and implementation procedures.

FSP Architecture in Practice

FSP Connecting Layers

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.

figure-fsp-interface-dependency-connection.svg
Connecting layers

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.

Using FSP Modules in an Application

The typical use of an FSP module involves generating required module data then using the API in the application.

Create a Module Instance in the RA Configuration Editor

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.

Use the Instance API in the Application

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:

R_RTC_Open(&g_clock_ctrl, &g_clock_cfg);
Note
Each layer in the FSP Stack is responsible for calling the API functions of its dependencies. This means that users are only responsible for calling the API functions at the layer at which they are interfacing. Using the example above of a SPI module with a DTC dependency, the application uses only SPI APIs. The application starts by calling R_SPI_Open(). Internally, the SPI module opens the DTC. It locates R_DTC_Open() by accessing the dependent transfer interface function pointers from the pointers DTC instances (spi_cfg_t::p_transfer_tx and spi_cfg_t::p_transfer_rx) to open the DTC.

Refer to the module usage notes for example code to help get started with any particular module.