iTranslated by AI
Reading the Arduino UNO R4 Minima Bootloader Source Code (Application Startup)
Revision History
- rev.1 2025/11/18 Corrected typos in section and array names (vector → vectors)
- rev.0 2024/12/19 Newly created
1. Introduction
In the previous article, I described an example of the build procedure for the Arduino UNO R4 Minima (hereafter, Minima) bootloader. This article is a memo of what I found when checking the flow from this bootloader until it starts the application (sketch).
- Previous article
Building a Bootloader for Arduino UNO R4 Minima
2. Before Reading the Source Code
2.1. Creating a Make Log
This article will be easier to read if you have the build environment created in the previous article on hand. Also, to make it easier to understand which files were compiled, you might want to rebuild the bootloader using the following commands and keep a log of the make process.
cd ~/arduino-r4-bootloader/arduino-renesas-bootloader/
rm -rf ./_build
TINYUSB_ROOT=$PWD/../tinyusb make -f Makefile.minima --debug=v --print-data-base >& build.log
2.2. Startup of the Bootloader Itself
Before reading the bootloader source code, let's confirm the flow of how the microcontroller starts a program (in this case, the bootloader) written in the internal flash.
Arduino UNO R4 is equipped with the "R7FA4M1AB3CFM#AA0" microcontroller, which belongs to Renesas Electronics' RA4M1 MCU group. This microcontroller uses a Cortex-M4 core and features 256 kB of internal flash memory[1].
Broadly speaking, this microcontroller has two types of operating modes. One is "Single-chip mode," which starts the program written in internal flash, and the other is "SCI/USB boot mode," used for rewriting the internal flash program[2]. "Single-chip mode" is used to start the bootloader. On the other hand, "SCI/USB boot mode" was used when we flashed the bootloader in the previous article.
A program that starts in single-chip mode must be structured so that the Cortex-M4 core can boot it. The Cortex-M4 uses the Armv7E-M architecture; in this case, the program image must place a vector table at its starting address (in this instance, 0000 0000h, the beginning of the internal flash[3]).
The vector table stores the following information[4]:
- First 4 bytes: Initial Stack Pointer
- Next 4 bytes : Initial Program Counter (Reset Vector)
In the Armv7E-M architecture, the following operations are performed during the reset process[5]:
- Read the initial stack pointer from the vector table and set that value to the Main Stack Pointer (MSP).
- Read the reset vector from the vector table and set that value to the Program Counter (PC).
3. Linker Script
As mentioned above, in this case, a program that starts in single-chip mode must place the vector table at the beginning of internal flash. The process of placing not only the vector table but also variables and functions at specific locations (addresses) is performed during the linking stage, typically using a linker script. Therefore, we will first examine the linker script to investigate how the vector table is arranged.
Before that, let's briefly describe linker scripts. When C source code is compiled, the program is categorized into sections such as:
-
.textsection: Function and program execution code. -
.datasection: Global and static variables with initial values. -
.bsssection: Global and static variables without initial values. -
.rodatasection: Constants and read-only data.
It is also possible to categorize specific data into unique sections. For example, if the following code is compiled with GCC, the constant app_ver is categorized into the .app_ver section:
const uint32_t app_ver __attribute__((section(".app_ver"))) = 0x00010000;
The linker determines the location of these sections in memory during the linking process. The linker script is used for this purpose. It defines specifically where each section should be placed in memory.
In the build environment created in the previous article, the linker scripts are located at the following paths:
tinyusb/hw/bsp/ra/linker/gcc/fsp.ld
tinyusb/hw/bsp/ra/linker/gcc/ra4m1.ld
(The prefix "~/arduino-r4-bootloader/" is omitted hereafter)
In fsp.ld, memory region layouts (such as FLASH or RAM) are abstractly specified using constants like FLASH_START and FLASH_LENGTH. On the other hand, in ra4m1.ld, concrete values are assigned to these constants—for instance, FLASH_START = 0x00000000 and FLASH_LENGTH = 0x40000.
The MEMORY block in the linker script defines the memory regions used by the program.
The internal flash is defined as FLASH. ORIGIN is the start address, and LENGTH is the memory size (in bytes here). While the definitions are ORIGIN = FLASH_ORIGIN + NS_IMAGE_OFFSET and LENGTH = LIMITED_FLASH_LENGTH, for the bootloader built this time, they resolve to ORIGIN = 0 and LENGTH = 0x40000.
Continuing further down, you will find the SECTIONS block. Within this block, there is a description to place the .text section at the beginning of FLASH.
Furthermore, at the start of this .text section, there are entries like KEEP(*(.fixed_vectors*)) and KEEP(*(.application_vectors*)). Here, * is a wildcard; the first * represents any filename (e.g., startup.o from startup.c), and the second represents any character string. If startup.o contains data categorized into a section called .fixed_vectors123, that data will be held at the KEEP(*(.fixed_vectors*)) position.
Let's check the structure of the vector table. The vector table is documented in the Renesas manual[4:1]. In the current source code, the vector table for exception numbers 0–15 (Arm-origin) and the vector table for number 16 onwards are defined separately. These are placed in .fixed_vectors and .application_vectors, respectively. The reset vector, which is the initial program counter, is registered as the second entry (exception number 1) in the vector table. Next, we will search for the definition of the vector table placed in the .fixed_vectors section.
4. Vector Table
The vector table placed in .fixed_vectors is defined in the following location:
tinyusb/hw/mcu/renesas/fsp/ra/fsp/src/bsp/cmsis/Device/RENESAS/Source/startup.c
__Vectors is placed in .fixed_vectors using BSP_PLACE_IN_SECTION(BSP_SECTION_FIXED_VECTORS). The Reset_Handler, registered as the second entry in this array, is the reset vector. Note that the definitions for these macros can be found here:
tinyusb/hw/mcu/renesas/fsp/ra/fsp/src/bsp/mcu/all/bsp_compiler_support.h
5. Reset_Handler
Reset_Handler is also defined within startup.c.
6. SystemInit
The SystemInit function performs initial settings for the system to operate correctly. Specifically, this includes initializing variables in the .bss and .data sections, clock settings, MPU (Memory Protection Unit) settings, and enabling the FPU (Floating Point Unit). However, I believe it is fine to check these details after confirming the flow of the bootloader starting the application. Therefore, the processing within the SystemInit function will be described in a separate article and omitted here.
7. main
The main function is defined in the source code of arduino-renesas-bootloader.
arduino-renesas-bootloader/src/main.c
7.1. board_init
First, regarding the board_init function on line 179, its definition is back in tinyusb at the following location.
tinyusb/hw/bsp/ra/family.c
It enables interrupts using __enable_irq() and configures pin functions with R_IOPORT_Open(&port_ctrl, &family_pin_cfg);. The family_pin_cfg is defined in board.h.
tinyusb/hw/bsp/ra/linker/gcc/board.h
Line 41 is the setting for the LED, which is marked as L on the Minima's silkscreen. Lines 43–45 are the USB pin settings. Line 42 sets D12 as an input pin, but it doesn't seem to be used in this bootloader.
Returning to the board_init function, the constant TRACE_ETM is undefined, so lines 108–116 are excluded from the code.
Additionally, the constant CFG_TUSB_OS is defined as OPT_OS_NONE in arduino-renesas-bootloader/src/tusb_config.h.
Therefore, the remaining code in the board_init function is the execution of the SysTick_Config function on line 127 and the board_led_write function on line 130. The latter is for turning off the LED. The SysTick_Config function is located in tinyusb/lib/CMSIS_5/CMSIS/Core/Include/core_cm4.h.
SysTick is described in the manuals from Renesas and Arm[6][7].
Since the CLKSOURCE bit is set to 1, the processor clock (ICLK) is specified as the clock source. The operating frequency value of ICLK is stored in the SystemCoreClock variable within the SystemInit function.
By setting (SystemCoreClock / 1000) - 1 to SysTick->LOAD (the RELOAD field of the SYST_RVR register), the SysTick interrupt interval is set to 1 millisecond[8]. The SysTick interrupt handler is defined at the following location.
tinyusb/hw/bsp/ra/family.c
The system_ticks variable is incremented every millisecond. This value can be retrieved using the board_millis function. This function is used later in a process that waits for 500 milliseconds.
7.2. About Bootloader Mode
Returning to the main function. After the board_init function executes, a branching process occurs to choose whether to enter bootloader mode or start the application. Bootloader mode is useful for recovering the system if an application that prevents USB communication is written. In the case of the Minima, this mode can be activated by double-tapping the reset button after power-on[9]. This mode allows for rewriting the program without starting the application. In this article, I will broaden the scope slightly to touch upon the activation procedure for this bootloader mode.
First, let's check the following if statement.
The definition of BOOT_DOUBLE_TAP_DATA is as follows.
This VBTBKR register is a backup register, and as long as power is supplied from the VCC or VBATT pins, the data is retained even if the reset button is pressed[10][11][12].
In this bootloader, it judges whether to enter bootloader mode by reading the value of the backup register.
The processing for bootloader mode is implemented after the bootloader label in the main function. If this if statement is entered, it jumps to the bootloader label to enter bootloader mode. However, at power-on, the magic value is not written in the backup register, so this condition is not met.
Next, let's check the following if statement.
The R_SYSTEM->RSTSR0_b.PORF (Power-On Reset detection Flag) written in the if condition is set during a power-on reset (when power is applied without pressing the reset button)[13].
This flag is cleared by a RES pin reset (when the reset button is pressed)[14][15][16].
Since the condition of the if statement is !R_SYSTEM->RSTSR0_b.PORF, it is satisfied when the Power-On Reset detection flag is cleared (i.e., when the bootloader is started by pressing the reset button). When entering this if block, a magic value is written to the backup register. Note that since writing to the backup register can be enabled or disabled via the protect register[17], the protect register is manipulated before and after the backup register write process.
The subsequent code in lines 190–195 of main.c is excluded in the case of the Minima[18]. Therefore, let's examine line 196 onwards.
There is a process to clear the backup register value to 0 after waiting for 500 milliseconds.
If the reset button is pressed during this wait, the bootloader restarts while the data in the backup register is retained. At this time, if the magic value is written in the backup register, the process enters the if statement on line 182 and shifts into bootloader mode.
The behavior when double-tapping the reset button after power-on is summarized below:
- When power is applied and the bootloader starts, it does not enter the
ifstatement on line 182 because the magic value is not written in the backup register. Also, since the power-on reset detection flag is set, it does not enter theifstatement on line 187. - When the bootloader is restarted by pressing the reset button, the power-on reset detection flag is cleared, so it enters the
ifstatement on line 187 and writes the magic value to the backup register. - If the reset button is pressed again during the subsequent 500ms wait process, the bootloader restarts with the magic value written in the backup register, causing it to enter the
ifstatement on line 182 and enter bootloader mode.
If the reset button is not pressed during the 500ms wait, the backup register value is cleared to zero, and the process moves to the application startup phase.
Note that this bootloader mode can also be activated by methods other than double-tapping the reset button. This mode is also used when rewriting a sketch (application) from the Arduino IDE. Specifically, when a sketch upload is executed in the Arduino IDE, the application receives the request via USB communication, writes the magic value to the backup register, and then restarts to launch the bootloader mode.
7.3. Checking the Application Image
Before proceeding with the application startup, there is a process to check the structure of the application image.
The application program is written starting from a position SKETCH_FLASH_OFFSET bytes away from the beginning of the internal flash. This value is defined in the following location:
arduino-renesas-bootloader/src/flash.h
Since BSP_FEATURE_FLASH_LP_VERSION is 3 (not 0) in this case[19], SKETCH_FLASH_OFFSET will be the value from either line 7 or line 9. Also, while DFU_LOADER is added to CFLAGS as -DDFU_LOADER in Makefile.minima, BOSSA_LOADER is undefined. Therefore, the value of SKETCH_FLASH_OFFSET becomes (16 * 1024) = 0x4000 as per line 9.
Application program images are also created to have a vector table at the beginning of the image, similar to the bootloader. Indeed, when checking the map file generated when building Blink for the Minima, it was as shown in the figure below.

Therefore, the first 4 bytes of the image store the initial stack pointer of the application program. Line 201 of main.c is likely a simple check of this initial stack pointer value. Specifically, it seems to verify that the stack pointer is located within the internal SRAM. In the RA4M1 group, the internal SRAM is assigned to the address range 0x20000000 to 0x20008000[3:1]. Therefore, if you take the bitwise AND of an address on the SRAM with 0xFF000000, the result will be 0x20000000.
If this condition is met, it is judged that the vector table is correctly placed at the beginning of the application image, and the process proceeds to the execution of the boot5 function for starting the application. If it is not met, it assumes that a valid application has not been written and is likely designed to transition to bootloader mode.
7.4. Starting the Application
Finally, let's check the contents of the boot5 function.
First, it uses the R_BSP_MODULE_STOP macro to perform a module stop for USBFS.
tinyusb/hw/mcu/renesas/fsp/ra/fsp/src/bsp/mcu/all/bsp_module_stop.h
However, USBFS is in a module stop state at reset[20], and I have not seen any process to release the module state in the flow so far. This process might be more of a precautionary measure[21]. Also, module stop processing is executed for USBHS in addition to USBFS, but the RA4M1 series microcontrollers do not have USBHS. Therefore, the corresponding field in the MSTPCRB register is treated as reserved. However, it seems there is no problem with writing 1 to this reserved field[22].
Next, let's check the subsequent process. Here, the stack pointer monitoring by the MPU, which was configured in the SystemInit function, is disabled.
Since the microcontroller used in the Minima does not have TrustZone, the disabling process is performed on line 96. R_MPU_SPMON is defined in the following header.
tinyusb/hw/mcu/renesas/fsp/ra/fsp/src/bsp/cmsis/Device/RENESAS/Include/R7FA4M1AB.h
Finally, let's look at the last part of the process.
After disabling interrupts with __disable_irq(), __DSB() and __ISB() are executed in this order. __DSB() is a process that waits until memory and register write operations performed before this instruction are completed[23]. On the other hand, __ISB() is an instruction to flush the processor's pipeline[24].
Since disabling interrupts is followed by disabling the SysTick interrupt, changing the vector table, and the application startup process, __DSB() and __ISB() might be included to perform the application startup process while minimizing the influence of processes on the bootloader side.
After disabling the SysTick interrupt, the process continues with changing the reference to the vector table to the one on the application side. Changing the referenced vector table can be done by setting it in the VTOR register[25].
Since the application's vector table is located at the image's start address (SKETCH_FLASH_OFFSET), this address is registered in VTOR. Then, it loads the initial value of the stack pointer from the application-side vector table and sets it to the Main Stack Pointer. Finally, it loads the reset vector address stored 4 bytes from the beginning of the vector table and starts the application by executing it as a function with no return value and no arguments.
Note that although the architecture is different, according to documents for Armv8-M equipped with TrustZone, it is recommended to execute __DSB() and __ISB() after changing the value of VTOR (Vector Table Offset Register)[26]. If you want to ensure that the application starts with the new vector table written in VTOR, you might also write __DSB() and __ISB() after the VTOR change.
-
Arduino "Arduino® UNO R4 Minima Product Reference Manual SKU: ABX00080", p1, "Description" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.92, "3. Operating Modes" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.94, "Figure 4.1 Memory Map" ↩︎ ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.280, "Table 13.3 Interrupt Vector Table" ↩︎ ↩︎
-
Arm "ARMv7-M Architecture Reference Manual Version: E.e", p.B1-530, "B1.5.5 Reset behavior" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.87, "2.9 SysTick System Timer" ↩︎
-
Arm "ARMv7-M Architecture Reference Manual Version: E.e", p.B3-621, "B3.3.3 SysTick Control and Status Register, SYST_CSR",
SysTick->CTRLin the source code corresponds toSYST_CSR. ↩︎ -
Since
SystemCoreClockcycles occur per second, the time required to count downSystemCoreClock / 1000times is 1 millisecond. ↩︎ -
Arduino "Arduino® UNO R4 Minima Product Reference Manual SKU: ABX00080", p.17, "12.5 Board Recovery" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.237, "11.1.5 Backup Registers" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.243, "11.2.6 VBATT Backup Register (VBTBKRn) (n = 0 to 511)" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.256, "11.3.4 Usage of VBATT Backup Registers" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.105, "5.3.2 Power-On Reset" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.95, "Table 5.1 Reset Names and Sources" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.96, "Table 5.2 Reset Detection Flags to be Initialized by Each Reset Source" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.99, "5.2.1 Reset Status Register 0 (RSTSR0)" ↩︎
-
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.262, "12.2.1 Protect Register (PRCR)" ↩︎
-
The constant
RENESAS_CORTEX_M23is undefined. Also,TURN_OFF_CHARGER_LEDis defined inarduino-renesas-bootloader/src/flash.honly whenBSP_FEATURE_FLASH_HP_VERSIONis non-zero, but this constant is 0 intinyusb/hw/mcu/renesas/fsp/ra/fsp/src/bsp/mcu/ra4m1/bsp_feature.h. ↩︎ -
The definition of
BSP_FEATURE_FLASH_LP_VERSIONcan be found at:tinyusb/hw/mcu/renesas/fsp/ra/fsp/src/bsp/mcu/ra4m1/bsp_feature.h↩︎ -
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.821, "27.4.1 Setting the Module-Stop Function" ↩︎
-
Looking at the source code, the module stop state of USBFS is released by the
rusb2_module_startfunction via thedcd_initfunction inside thetud_initfunction executed in bootloader mode (tud_init: tinyusb/src/device/usbd.c,dcd_init: tinyusb/src/portable/renesas/rusb2/dcd_rusb2.c,rusb2_module_start: tinyusb/src/portable/renesas/rusb2/rusb2_ra.h). Therefore, ifboot5is called after entering bootloader mode, this module stop process would be necessary. In fact, looking atmain.c, there are traces indicating that it was previously considered to execute theboot5function during bootloader mode, though it is currently commented out. ↩︎ -
Renesas Electronics Corporation, "Renesas RA4M1 Group User's Manual: Hardware Rev.1.10", 2023.09, p.201, "10.2.3 Module Stop Control Register B (MSTPCRB)" ↩︎
-
Arm "ARMv7-M Architecture Reference Manual Version: E.e", p.A3-94, "Data Synchronization Barrier (DSB)" ↩︎
-
Arm "ARMv7-M Architecture Reference Manual Version: E.e", p.A3-95, "Instruction Synchronization Barrier (ISB)" ↩︎
-
Arm "ARMv7-M Architecture Reference Manual Version: E.e", p.B1-525, "B1.5.3 The vector table" ↩︎
-
Arm "Armv8-M Memory Model and Memory Protection User Guide Version 1.1", p.17, "2.3.4 When do you need a DSB followed by an ISB?" ↩︎
Discussion
情報ありがとうございます。
3.リンカスクリプトにあります
KEEP(*(.fixed_vector*))KEEP(*(.application_vector*))は
KEEP(*(.fixed_vectors*))KEEP(*(.application_vectors*))のようですね。
ご指摘ありがとうございます!