Chapter 4_Renesas MCU Zero-Based Introductory Tutorial Series: Renesas MCU Source Code Design Specifications

This tutorial is written based on the DShanMCU-RA6M5 development board published by Weidongshan Baiwen . Students who need it can get it here: https://item.taobao.com/item.htm?id=728461040949

Obtain supporting information: https://renesas-docs.100ask.net

Summary of Renesas MCU zero-based entry series tutorials : https://blog.csdn.net/qq_35181236/article/details/132779862


Chapter 4 Renesas MCU Source Code Design Specifications

Objectives of this chapter

  • Understand the FSP source code structure and design specifications
  • Understand module design ideas and calling methods

4.1 Overall framework

4.1.1 Source code hierarchy and directory

Renesas provides developers with a "Flexible Software Package" (FSP, Flexible Software Package), which provides multi-level software from the bottom up, as shown in the following figure:

image1

It can be divided into these layers from bottom to top:

  1. Board Support Package (BSP, Board Support Package): Simply put, from the first instruction executed at power-on until the main function, the code for this process is provided by the BSP. Its main task is to ensure that the MCU transitions from the reset state to the user application state. In the process, it sets up the clock, interrupts, stack, heap, and C language runtime environment. It also configures the port's I/O pins and performs any specific board initialization. Functions start with "R_BSP_", macros start with "BSP_", and data types start with "_bsp".
  2. Hardware Abstraction Layer Drivers (HAL, Hardware Abstraction Layer Drivers): Simply put, using the BSP code allows the program to run to the main function, but how to access GPIO, I2C, SPI and other devices in the main function requires the use of HAL code . HAL is the encapsulation of MCU register operations. Through the HAL function, you don't need to pay attention to the underlying specific hardware operations when writing programs, but focus on higher-level operations, so that the code written in this way is easier to port to other MCUs. Function names start with "R_".
  3. Middleware (Middleware): The middleware layer is located above the HAL layer and below the user application, providing a functional stack and protocol for the application. For example, when wanting to simulate a USB serial port, the HAL layer has realized USB transmission, and the USB serial port protocol is a set of mechanisms implemented on top of USB transmission. The USB serial port protocol is a set of pure software protocols, which can be classified as middleware.
  4. Real Time Operating System (RTOS, Real Time Operating System): It only relies on the underlying BSP to provide functions such as multitasking, synchronization and mutual exclusion.
  5. Application (Application): At the top layer, it can use HAL functions to access hardware, and can also use middleware to complete complex functions.

Taking the first program as an example, the project directory is as follows:

  1. BSP source code: From the file name, you can know that the functions are startup, system, clock/interrupt related operations
  1. HAL source code: This program only involves GPIO operations, so there is only ioport-related HAL source code
  1. BSP configuration files: These files are generated by the FSP configuration tool and contain BSP-related parameters.
  1. User data: For example, the user chooses which GPIOs and SPI controllers to use in the FSP configuration interface.

  1. User code (Application): You can add your own code in hal_entry.c

image2.4

  1. Link script: When using e2 studio, it uses the GNU GCC tool chain to compile the program, and a link script is required

4.1.2 Example of calling process

Taking the project "MyBlinkyProject" as an example, in hal_entry.c, the code for operating LED is as follows:

void hal_entry(void)
{
    
    
    /* TODO: add your own code here */
    extern bsp_leds_t g_bsp_leds;
    bsp_leds_t Leds = g_bsp_leds;

    while (1)
    {
    
    
        g_ioport.p_api->pinWrite(&g_ioport.p_ctrl, Leds.p_leds[BSP_LED_LED1],         			BSP_IO_LEVEL_LOW);

        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);

        g_ioport.p_api->pinWrite(&g_ioport.p_ctrl, Leds.p_leds[BSP_LED_LED1],        			BSP_IO_LEVEL_HIGH);

        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
    }
#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}
  • "g_ioport.p_api->pinWrite" in line 9 is to call the "R_IOPORT_PinWrite" function in r_ioport.c, which is the application's call to the HAL library function.

4.2 Module design ideas

There are four levels when using FSP to write programs: Application is written by users, Middleware is third-party code, and the amount of code in BSP is very small, so the HAL layer is the core of FSP. The HAL layer is the driver of each module. These drivers are called Modules. A Module provides an interface for people to call upwards, and other Modules may be used downwards, as follows:

How to use the interface provided by a Module? Taking the project "MyBlinkyProject" as an example, there are the following two methods to call r_ioport.c to provide the interface:

g_ioport.p_api->pinWrite(&g_ioport.p_ctrl, Leds.p_leds[BSP_LED_LED1], BSP_IO_LEVEL_LOW);
R_IOPORT_PinWrite(&g_ioport_ctrl, Leds.p_leds[BSP_LED_LED1], BSP_IO_LEVEL_LOW);

How are they different? This involves the concept of FSP source code design:

  1. Separation of configuration and interface
  2. Separation of interfaces and instances

4.2.1 Separation of configuration and interface

Taking GPIO as an example, there is 1 LED and 1 button as shown below:

For the same MCU, the operations of PIN1 and PIN2 are similar, and only one set of their interface functions can be written. But PIN1 needs to be set as output function, PIN2 needs to be set as input function and its internal pull-up resistor is enabled. That is: the interface functions of PIN1 and PIN2 can be the same set, but their configurations are different.

For ioport, use the ioport_pin_cfg_t structure to describe the configuration of a pin:

typedef struct st_ioport_pin_cfg
{
    
    
    uint32_t pin_cfg; ///< Pin PFS configuration - Use ioport_cfg_options_t parameters
  						  to configure
    bsp_io_port_pin_t pin; ///< Pin identifier
} ioport_pin_cfg_t;

For example, for PIN1, configure it as an output in the FSP configuration tool; for PIN2, configure it as an input and pull-up in the FSP configuration tool, and you can get the following two items:

const ioport_pin_cfg_t g_bsp_pin_cfg_data[] ={
    
    
……
{
    
     .pin = BSP_IO_PORT_00_PIN_05, 
 .pin_cfg = ((uint32_t) IOPORT_CFG_IRQ_ENABLE
 | (uint32_t) IOPORT_CFG_PORT_DIRECTION_INPUT 
 | (uint32_t) IOPORT_CFG_PULLUP_ENABLE) },
{
    
     .pin = BSP_IO_PORT_00_PIN_06, 
 .pin_cfg = ((uint32_t) IOPORT_CFG_PORT_DIRECTION_OUTPUT
 | (uint32_t) IOPORT_CFG_PORT_OUTPUT_LOW) },
……
};

Before using the hardware, you need to use the interface function to configure the hardware according to the configuration information provided by the user. For ioport, use the ioport_api_t structure to describe the pin interface function. You can see the following structure in r_ioport.c:

/* IOPort Implementation of IOPort Driver */
const ioport_api_t g_ioport_on_ioport =
{
    
    
    .open = R_IOPORT_Open,
    .close = R_IOPORT_Close,
    .pinsCfg = R_IOPORT_PinsCfg,
    .pinCfg = R_IOPORT_PinCfg,
    .pinEventInputRead = R_IOPORT_PinEventInputRead,
    .pinEventOutputWrite = R_IOPORT_PinEventOutputWrite,
    .pinRead = R_IOPORT_PinRead,
    .pinWrite = R_IOPORT_PinWrite,
    .portDirectionSet = R_IOPORT_PortDirectionSet,
    .portEventInputRead = R_IOPORT_PortEventInputRead,
    .portEventOutputWrite = R_IOPORT_PortEventOutputWrite,
    .portRead = R_IOPORT_PortRead,
    .portWrite = R_IOPORT_PortWrite,
};

For ioport, the configuration is separated from the interface: specify the pin in the ioport_cfg_t parameter, specify the configuration value, and then call the "pinCfg" function pointer to configure the pin. When using the FSP configuration tool, select a pin and set its parameters, and the corresponding ioport_cfg_t structure will be generated. When we write a program to call the "pinCfg" function pointer in r_ioport.c, pass in this ioport_cfg_t structure.

4.2.2 Separation of interfaces and instances

Suppose there are two generations of products as shown in the figure below, and their LED connections are different:

image2.7

For the first generation products, the following structure has been implemented in r_ioport.c:

/* IOPort Implementation of IOPort Driver */
const ioport_api_t g_ioport_on_ioport =
{
    
    
     .open = R_IOPORT_Open,
     .close = R_IOPORT_Close,
     .pinsCfg = R_IOPORT_PinsCfg,
     .pinCfg = R_IOPORT_PinCfg,
     .pinEventInputRead = R_IOPORT_PinEventInputRead,
     .pinEventOutputWrite = R_IOPORT_PinEventOutputWrite,
     .pinRead = R_IOPORT_PinRead,
     .pinWrite = R_IOPORT_PinWrite,
     .portDirectionSet = R_IOPORT_PortDirectionSet,
     .portEventInputRead = R_IOPORT_PortEventInputRead,
     .portEventOutputWrite = R_IOPORT_PortEventOutputWrite,
     .portRead = R_IOPORT_PortRead,
     .portWrite = R_IOPORT_PortWrite,
};

For the second generation products, we can implement the following structure in r_spiioport.c:

/* IOPort Implementation of SPIIOPort Driver */
const ioport_api_t g_spiioport_on_ioport =
{
    
    
     .open = R_SPIIOPORT_Open,
     .close = R_SPIIOPORT_Close,
     .pinsCfg = R_SPIIOPORT_PinsCfg,
     .pinCfg = R_SPIIOPORT_PinCfg,
     .pinEventInputRead = R_SPIIOPORT_PinEventInputRead,
     .pinEventOutputWrite = R_SPIIOPORT_PinEventOutputWrite,
     .pinRead = R_SPIIOPORT_PinRead,
     .pinWrite = R_SPIIOPORT_PinWrite,
     .portDirectionSet = R_SPIIOPORT_PortDirectionSet,
     .portEventInputRead = R_SPIIOPORT_PortEventInputRead,
     .portEventOutputWrite = R_SPIIOPORT_PortEventOutputWrite,
     .portRead = R_SPIIOPORT_PortRead,
     .portWrite = R_SPIIOPORT_PortWrite,
};

There are now two interface structures: g_ioport_on_ioport and g_spiioport_on_ioport. Which one should be used? Where to specify? Another concept needs to be introduced: instances. Taking ioport as an example, there are the following structure types:

/** This structure encompasses everything that is needed to use an instance of this 
interface.
*/
typedef struct st_ioport_instance
{
    
    
 ioport_ctrl_t * p_ctrl; ///< Pointer to the control structure for this instance
 ioport_cfg_t const * p_cfg; ///< Pointer to the configuration structure for this instance
 ioport_api_t const * p_api; ///< Pointer to the API structure for this instance
} ioport_instance_t;

ioport_instance_tThere are 3 members in the structure:

  1. p_cfg pointer: When using different pins and different configurations, let it point to a corresponding configuration structure;
  2. p_api pointer: When using different hardware interfaces, let it point to the corresponding interface function structure;
  3. p_ctrl pointer: plays an auxiliary role, such as marking whether the module is enabled and recording its register base address

Taking the project "MyBlinkyProject" as an example, an instantiation object is defined in ra_gen\common_data.c:

const ioport_instance_t g_ioport =
{
    
     .p_api = &g_ioport_on_ioport, .p_ctrl = &g_ioport_ctrl, .p_cfg = &g_bsp_pin_cfg, };

In g_ioport:

  • p_cfg points to g_bsp_pin_cfg, which is configuration information;
  • p_api points to g_ioport_on_ioport, which is interface information;
  • p_ctrl points to g_ioport_ctrl, it is only used to record whether the driver is opened.

For 1st generation products, the p_api of g_ioport points to g_ioport_on_ioport; for 2nd generation products, let it point to g_spiioport_on_ioport. When using the instantiated structure g_ioport to operate the LED, even if the underlying operation interface is replaced, the user code still does not need to be changed:

g_ioport.p_api->pinWrite(&g_ioport.p_ctrl, Leds.p_leds[BSP_LED_LED1], BSP_IO_LEVEL_LOW);

If you directly use the interface function to operate the LED, it is as follows:

R_IOPORT_PinWrite(&g_ioport_ctrl, Leds.p_leds[BSP_LED_LED1], BSP_IO_LEVEL_LOW);

For second generation products, it needs to be modified to another interface, as follows:

R_SPIIOPORT_PinWrite(&g_ioport_ctrl, Leds.p_leds[BSP_LED_LED1], BSP_IO_LEVEL_LOW);

Using instantiated structures to operate hardware has great advantages in code uniformity, readability, and portability. It allows further abstraction between applications and hardware. When changing the underlying peripheral device, you only need to modify the instantiation structure and do not need to change the application layer code. In the actual development process, you can also directly call the underlying API function (such as R_IOPORT_PinWrite) for two reasons:

  1. Considerations based on compiler optimization: Assume that 10 API interface functions are defined, but only 1 is used in the application layer code, then the other 9 functions can be "optimized" and they do not need to be compiled into the executable program. inside. If you use an instantiated structure, because these 10 functions are referenced in p_api, they will not be optimized.
  2. Some customers may only want to call the lowest level API (to avoid overly cumbersome function pointers).

4.3 Code specifications

4.3.1 Terminology

  • **Module: **Modules can be peripheral drivers, pure software, or something in between, and are the building blocks of FSP. Modules are usually independent units, but they may depend on other modules. Applications can be built by combining multiple modules to provide users with the functionality they need.
  • Module Instance: A single, independent instantiation (configuration) module. For example, r_ioport.c implements GPIO operations and is a Module. When you want to operate a certain pin, you need a "module instance", that is, the "ioport_instance_t structure", which contains configuration information and interface information.
  • Interfaces : Interfaces contain API definitions that can be shared by modules with similar functions. Modules provide common functionality through these definitions. Through these API definitions, modules using the same interface can be used interchangeably. Think of an interface as a contract between two modules, where both modules agree to collaborate using the information agreed upon in the contract. Interfaces are just definitions and do not increase the code size. For example, the ioport API is defined in r_ioport_api.h.
  • Instances: The interface specifies the functions provided, and the instances actually implement these functions. For example, API interfaces are defined in r_ioport.h, and these interfaces are implemented in r_ioport.c. r_ioport.c is the "instance".
  • Drivers : Drivers are a specific type of module that can directly modify the registers on the RA product family MCU.
  • **Stacks:** This word is easily confused with heap and stack in C language, but it does not mean stack here. The FSP architecture is designed in such a way that modules can work together to form a stack. A stack consists of a top-level module and all its dependencies, simply put, multiple modules with dependencies.
  • Application : Code owned and maintained by the user.
  • Callback Functions: These functions will be called when an event occurs (for example, when the USB receives some data). They are part of the application and if used for interrupts, they should be kept short as they will run inside the interrupt service routine, preventing other interrupts from executing.

4.3.2 API naming rules

In general, internal functions follow the "NounNounVerb" (noun noun verb) naming convention, such as CommunicationAbort(). The return value of the function indicates whether it is successful or not. When the function wants to output results to the outside, these results are only returned in the output parameters, and the first parameter is always a pointer to its control structure. The following are commonly used prefixes in FSP:

  1. R_BSP_xxx: Prefix for BSP functions, such as R_BSP_VersionGet().
  2. BSP_xxx: Prefix of BSP macro, such as BSP_IO_LEVEL_LOW.
  3. FSP_xxx: Commonly used FSP prefixes, mainly defining error codes (such as FSP_ERR_INVALID_ARGUMENT) and version information (such as FSP_VERSION_BUILD).
  4. g_<interface> on <instance>: the name of the constant global structure of the instance, use this structure to manage each implementation function of the API, for example, the g_ioport_on_ioport structure is the various API functions implemented by r_ioport.c.
  5. r_<interface>_api.h: The name of the interface module header file, such as r_spi_api.h.
  6. R_<MODULE>_<Function>: The name of the FSP driver API, such as R_SPI_WriteRead().
  7. RM_<MODULE>_<Function>: The name of the middleware function, such as RM_BLE_ABS_Open().

End of this chapter

Guess you like

Origin blog.csdn.net/qq_35181236/article/details/132780213