RA Flexible Software Package Documentation  Release v5.4.0

 
Using Registers Directly

Overview

In some cases users may wish to utilize chip functionality that is not supported yet by FSP. While we encourage contacting us when new feature requests arise it takes time before any updates are made. In the meantime, it is recommended to use the register definition files accessed via renesas.h to add custom functionality as needed. Official ARM CMSIS documentation refers to this format of related register definition files as device header files. Within Renesas, these files are often referred to as an IO define or more commonly iodefine.

What's in an "iodefine"?

Iodefine files contain definitions for all the I/O registers on a device. For RA FSP, one iodefine header is provided per device group (RA6M3, RA2A1 etc.) that contains all the register definitions provided in the hardware manual for that group. These headers are accessed via renesas.h, which selects the appropriate file based on the MCU configured in the project.

Note
Prior to FSP 4.0.0 renesas.h contained a superset of all register definitions for RA MCUs. This became unsustainable as new device families were added and was confusing for users, so headers for each device were created and renesas.h was updated to serve as a selector.

Where are iodefine files located?

In RA FSP, iodefine files are stored in ra/fsp/src/bsp/cmsis/Device/RENESAS/Include. This includes both renesas.h as well as the device group specific iodefine headers.

Using the register definitions

Each peripheral register set is provided as a struct. In general, the template to follow is:

R_ + peripheral abbreviation + channel number + -> + register name [ + _b. + bitfield name]

Basic register access

Registers are most commonly accessed whole. For example, say we want to read the counter on GPT channel 3:

uint32_t count = R_GPT3->GTCNT;

As shown in the template, the iodefines can also be used to access bitfields. For example, to start GPT channel 3:

R_GPT3->GTCR_b.CST = 1;

Bitfield macros

It is worth noting that each bitfield access will cause a full read-modify-write which cannot be combined by the compiler because the register definitions are by necessity volatile. This can be very size and speed inefficient, particularly when the peripheral is on a very slow clock. It is recommended to write whole registers wherever possible. This is made easier by macros that are provided alongside the register definitions.

Every bitfield has two associated macros: _Msk (bitfield mask) and _Pos (bit position). These macros can be used to manipulate whole registers instead of using bitfield access when multiple bitfields are modified simultaneously. For example, setting both GPT3 output pins to 100% duty cycle:

uint32_t gtuddtyc = R_GPT3->GTUDDTYC
gtuddtyc &= ~(R_GPT0_GTUDDTYC_OADTY_Msk | R_GPT0_GTUDDTYC_OBDTY_MSK) // Mask off bits to clear
gtuddtyc |= (3 << R_GPT0_GTUDDTYC_OADTY_Pos) | (3 << R_GPT0_GTUDDTYC_OBDTY_Pos); // Shift values to bitfield positions
R_GPT3->GTUDDTYC = gtuddtyc;
Note
The macros are all named with channel 0 due to internal process requirements; they can be used with all channels.

Arrays and clusters

Some registers are part of an array. These are typically listed in the manual using indexes like "n" or "m", but may occasionally be listed with individual numbers or letters. Accessing register arrays is just like accessing a regular array. For example, the GPT duty cycle registers are GTCCRA through GTCCRF. In the iodefine, they are accessed via the GTCCR array:

/* Set GTCCRB */
R_GPT3->GTCCR[1] = duty_cycle_b;
Note
Not all indices may be available in every array. Be sure to check the hardware manual to verify which registers are provided on the device in use.

Sometimes there are groups of registers that are repeated multiple times. These groups are called a cluster. Clusters are accessed similarly to arrays, except the actual registers are elements of the array values instead. For example, mailboxes in the CAN peripheral have several registers each:

/* Get data from mailbox 3 */
uint32_t mailbox = 3;
/* Get the frame data length code */
uint32_t data_length = R_CAN0->MB[mailbox].DL_b.DLC;
/* Get message data */
uint8_t data[8];
for (uint32_t i = 0U; i < data_length; i++)
{
data[i] = p_reg->MB[mailbox].D[i]; // Copy receive data to buffer
}

Tips for writing hardware drivers

Channel register access

FSP drivers often calculate a register offset in the Open function. This is typically done by multiplying the channel by the offset between channels 0 and 1. For example, here is the calculation for GPT:

    /* Save register base address. */
    uint32_t base_address = (uint32_t) R_GPT0 + (p_cfg->channel * ((uint32_t) R_GPT1 - (uint32_t) R_GPT0));
    p_instance_ctrl->p_reg = (R_GPT0_Type *) base_address;

When drivers need to support multiple channels of a peripheral it can be helpful to save the offset in a persistent variable or structure so that it only needs to be calculated once.