11. Bootloader
11.1. Description
A bootloader is a program to load initialization code. In our case, it is the initial segment of the program located at the start of the flash of the microcontroller (TI TMS570LC4357), and its primary purpose is to update the foxBMS 2 application via interfaces like CAN on the fly without the use of a debugger.
This bootloader provides four basic functions:
transferring the application
running the application
resetting the boot process
checking the status of the onboard bootloader
11.1.1. Transferring the Application
To transfer the application binary to BMS-Master, the binary file first needs to be parsed into small sections, and a CRC signature will be calculated for each section. (‘section’ in this context refers to a data block that can be stored in a single flash sector of the physical flash memory.) During transferring, the data sections will be sent one by one. Once a section transfer is completed, the program section will be written into its relevant flash space, and an onboard CRC signature will be calculated.
Next, the CRC signature for this program section will be sent and compared with the onboard calculated CRC signature to ensure the flashed section data is correct. If the result is true, the next section will be transferred. Otherwise, the onboard bootloader will wait for the same section data again.
Once all section data has been sent successfully, the CRC signature of the last section, which is also the CRC signature of the entire application binary, will be saved into the flash memory where the program information is stored. After that, the vector table will be sent, and a CRC signature for this vector table will also be sent to verify its validity. The availability flag of the program will be set only if the CRC signatures of the vector table from both the PC side and the BMS-Master side match.
11.1.2. Running the Application
Once the bootloader receives the run command via a communication interface like CAN, it will load the program information from the last loading session into a global structure variable and check its availability flag to see if a program exists.
If a program exists, an onboard CRC calculation will be performed based on the start address and length of the program contained in the variable that carries the program information. The obtained CRC signature will then be compared with the one saved during the previous loading session to verify the program’s validity.
If the onboard program is valid, the bootloader will jump to the application’s init function. Otherwise, it will inform the PC that there is no valid program available onboard.
11.1.3. Resetting the Boot Process
If a reset boot process command is received by the bootloader via CAN, the global structure variable that carries the program information will first be reset and updated to flash. After that, the CAN communication-related variables will be reset, and the RAM will be cleaned up. Additionally, the flash banks where the application data is supposed to be written will be erased, and a software reset will be performed to reset the MCU.
11.1.4. Checking the Status of the Onboard Bootloader
The command to check the status of the bootloader will trigger the bootloader’s callback functions to retrieve its status, including the boot FSM status and the CAN FSM status, which represent the status of the boot process and the CAN module, respectively. This can be used to verify if the connection (e.g., CAN) between the PC and the bootloader is error-free and to check if the bootloader program is successfully running on the BMS-Master.
11.2. Project Structure
This bootloader project contains two parts: the bootloader itself and the PC program, which communicates with the bootloader (e.g., send the newest binary file of foxBMS 2 to BMS-Master).
Module |
Description |
---|---|
driver |
Contains low level driver modules to control the on-board resources. |
engine |
Contains mid-level engine modules to control the overall program flow. |
hal |
Contains the build script and the hash code for HALCoGen. |
main |
Contains the files where the ‘main’ function and ‘_c_int00’ function are located. In addition, it also contains the linker script for configuring the memory distribution and the files supported at the system level. |
Module |
Description |
---|---|
bootloader.py |
Contains the Bootloader class, which serves as the main entry point for sending application data or requests to the bootloader. |
bootloader_can.py |
Contains the BootloaderInterfaceCan class, which enables high-level communication with the bootloader via CAN. |
bootloader_can_basics.py |
Contains the BootloaderCanBasics class, where the basic CAN communication functions, including sending and receiving specified messages, are implemented. |
bootloader_binary_file.py |
Contains the BootloaderBinaryFile class, responsible for managing the application binary file and providing functions to perform operations on it, such as calculating CRC, extracting data, and more. |
bootloader_can_messages.py |
Contains all enums of CAN messages and functions to get specified CAN messages in a dictionary. |
11.3. How to use it
To build the bootloader binary, use the command variant
build_bootloader_embedded
.
After the binary is successfully built, you can flash it onto the BMS-Master
board using a debugger.
Once the binary is flashed, you can control it using commands available in the
fox tool’s CLI.
There are four commands can be used to communicate the bootloader:
Check the status of the bootloader:
.\fox.ps1 bootloader check -vv
fox.bat bootloader check -vv
./fox.ps1 bootloader check -vv
./fox.ps1 bootloader check -vv
Load the foxBMS 2 binary into the flash memory of BMS-Master:
.\fox.ps1 bootloader load-app -vv
fox.bat bootloader load-app -vv
./fox.ps1 bootloader load-app -vv
./fox.ps1 bootloader load-app -vv
Run the foxBMS 2 application on BMS-Master:
.\fox.ps1 bootloader run-app -vv
fox.bat bootloader run-app -vv
./fox.ps1 bootloader run-app -vv
./fox.ps1 bootloader run-app -vv
Reset the boot process:
.\fox.ps1 bootloader reset -vv
fox.bat bootloader reset -vv
./fox.ps1 bootloader reset -vv
./fox.ps1 bootloader reset -vv
In the case of an error status, a reset command or a power-on restart should resolve the problem.
11.4. Memory Configuration
The microcontroller (TI TMS570LC4357) has two independent flash banks. Each of them consists of 16 sectors and has a storage area of 2MB. While the sizes of the sectors in the second flash bank are uniform, the sizes of the sectors in the first flash bank are not identical. More details can be found in Technical Reference Manual of TMS570LC43 .
In this project, the flash memory (from 0x0x00000000
to 0x00400000
)
has been divided into 5 regions, as shown in Table 11.3.
The initial vector table is saved in the memory labeled
VECTORS_DIRECT
, and the second vector table, where the actual exception
entries are implemented, is saved in the memory labeled
VECTORS_INDIRECT
.
More details about the vector tables can be found in About Vector Table.
The program of bootloader (except for its vector table) has been saved in the
memory labeled BOOTLOADER
.
The binary of foxBMS 2 is supposed to be put in the memory labeled APP
.
The information of the program will be saved to memory labeled
PROGRAM_INFO_AREA
.
In this project, the RAM of the microcontroller is configured as shown in
Table 11.3.
As the names indicate, the memory labeled STACK
refers to the space
allocated for stack usage, while the memory labeled RAM
serves as the
general RAM space.
The memory labeled RAM_FLASH
serves as the section buffer to temporarily
store transferred data before it is written to flash sector.
The flash-related functions and libraries will run from the memory labeled
RAM_FLASH_API
.
More details can be found in How to load the “Flash API” into the SRAM?.
The memory labeled with the ‘ECC’ prefix is where the calculated error correction codes (ECC) are saved. More details about the ECC can be found in Technical Reference Manual of TMS570LC43 .
NAME |
ORIGIN |
LENGTH |
ATTR |
FILL |
---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11.5. Communication Between PC and Bootloader
The communication between the PC and CAN has been realized via pre-defined CAN messages. The complete data transfer process is as shown in Fig. 11.1:
11.6. Workflow
The workflow of the bootloader is achieved through the implementation of a boot state machine and a CAN state machine. While the CAN state machine controls the CAN communication and the correct sequence of the data transfer process, the boot state machine controls the main workflow of the bootloader.
11.6.1. Boot FSM State Machine
As shown in the following figure, at the start of the program, the boot FSM
state will be initialized to BOOT_FSM_STATE_WAIT
.
From this state, the boot FSM state can change to BOOT_FSM_STATE_LOAD
,
BOOT_FSM_STATE_RUN
, or BOOT_FSM_STATE_RESET
, in response to changes in
the CAN FSM state.
The following state diagram represents the change of states in the CAN module.
At the beginning, the CAN FSM state will be loaded with
CAN_FSM_STATE_NO_COMMUNICATION
.
To run the program, a corresponding request will be sent to the bootloader via
the CAN bus.
Once the CAN module has received this CAN message, it will change its state to
CAN_FSM_STATE_RUN_PROGRAM
.
If the boot state machine is in the expected state (BOOT_FSM_STATE_WAIT
) at
this moment, a validation process will be started to check if the onboard
program is available and validated.
The program will start if everything is correct.
If a reset request has been sent via CAN bus, the CAN FSM state will change to
CAN_FSM_STATE_RESET_BOOT
from any state.
If the incoming CAN message is to start a data transfer process, the CAN FSM
state will then change to CAN_FSM_STATE_WAIT_FOR_INFO
, and the boot FSM
state will change to BOOT_FSM_STATE_LOAD
.
Once the information of the program has been successfully transferred, the
state will change to CAN_FSM_STATE_WAIT_FOR_DATA_LOOPS
.
From this state, the data will be transferred.
To ensure the transferring data (8 bytes every time) is correct, a
corresponding loop number needs to be sent before the 8 data bytes in each
loop.
Only if the loop number is the one the CAN module expects, the data bytes will
be considered correct and will be written into the sector buffer.
To transmit the data efficiently and precisely, the whole program will be divided into sectors. Each sector contains the data that can be filled into the sector buffer. One sector contains 16 sub-sectors. Each sub-sector contains 1024 * 8 bytes and can be transferred by 1024 data loops.
Every time a sub-sector has been transferred, an acknowledgment (ACK) message
will be sent to inform the data sender that the whole sub-sector has been
successfully received.
If a whole sector has been transferred and written into the sector buffer, the
corresponding flash sector will be written using the data that is temporarily
saved in the sector buffer.
Immediately, the written flash sector will be validated by comparing the
received CRC signature (8 bytes) with the onboard calculated CRC signature
(8 bytes).
If the result is invalid, the variables involved with data transfer and the
sector buffer will be reset to the original state at the start of the data
transfer for this sector.
The CAN FSM state will reset to either CAN_FSM_STATE_WAIT_FOR_DATA_LOOPS
or
CAN_FSM_STATE_RECEIVED_8_BYTES_CRC
.
Once all data has been received, written, and validated, the CAN FSM state will
be set to CAN_FSM_STATE_FINISHED_FINAL_VALIDATION
.
After the whole program has been received and validated, the vector table for this program will be transferred and validated as well, as shown in the following diagram:
The entire state diagram of the CAN FSM state machine is shown as follows:
The functionality of this state machine has been achieved through the calling of functions inside the boot module, which basically checks the state of the CAN module.
11.7. Developer Note
11.7.1. What should be considered/modified while configuring the memory?
To configure the memory of the microcontroller (TI TMS570LC4357), several parts need to be considered/configured:
The linker script of the application (foxBMS 2)
app.cmd
andapp_hex.cmd
.The linker script of the bootloader
bootloader.cmd
.The address jump between the first vector table and the second vector table, which is defined in
boot_intvecs.asm
.The corresponding macros defined in one bootloader header file :
boot_cfg.h
.The app size defined in
cli/cmd_bootloader/app_constants.py
inside the pc-side code of bootloader.
11.7.2. About Vector Table
The
vector table
is usually placed at the start address (0x00
) of the flash and has a length
of 0x20
.
It contains 8 8 32-bit ARM instructions in our case.
In the bootloader, there are two vector tables (VECTORS_DIRECT
and
VECTORS_INDIRECT
)
located at 0x00
and 0x0001FFE0
of the flash memory.
While the first vector table only reroutes to the undefined entry, the SVC
(supervisor call) entry, the prefetch entry, the data abort entry, and the
phantom interrupt entry, to these inside the second vector table
(VECTORS_INDIRECT
), the real function entries to handle these entries are
implemented in the second vector table using
b xxx.
Different from these entries, the reset entry points always to the _c_int00
function which will also be called first before any other functions.
The IRQ and FIQ interrupt table will be loaded by
ldr pc,[pc,#-0x1b0]
inside the first vector table.
The configuration and initialization of the vectored interrupt manager is done
in _c_int00
by vimInit()
.
During booting, the _c_int00
function is first called, but the actual
working exception entries (except for IRQ and FIQ) will be these inside the
second vector table.
After the application is loaded, the vector table of the application will
replace the second vector table.
This means that by jumping into the second vector table, the _c_int00
function of the application will be called, where the configuration for, e.g.,
VIM will be reset for the application.
Meanwhile, the real entries for the exceptions will be replaced by the entries
shipped with the application.
11.7.3. Changing operation mode
Since some functions inside flash and CRC modules change values in the protected flash area, such as register values, certain privileges need to be claimed before calling these functions.
To raise the privilege, SVC handlers are implemented to change the value of the program status register.
11.7.4. How to load the “Flash API” into the SRAM?
To erase and write the flash bank where the bootloader is located, the relevant flash API and the functions that use the flash API need to be run from SRAM rather than flash. More details can be found in here, which is also called run-time relocation. To run program from SRAM, the following steps must be done:
Change the MPU configuration for the region from
0x08000000
to0x0807FFFF
toPRIV_RW_USER_RW_EXEC
to enable calling the functions in this area without triggering any error. More details can be found in hereConfigure loading the flash API and flash relevant functions to flash memory and run them from SRAM by using build-in link operators in linker script. More details can be found in here.
11.7.5. How to use the onboard CRC module in Semi-AUTO mode?
The detailed information regarding CRC onboard module and the CRC algorithm used in the CRC onboard module can be found in here.
11.7.6. How to jump to a certain address?
When you run the code, the bootloader will start first because this is the
bootloader’s reset vector that is on the first address 0x0
.
The bootloader’s main will jump into the application like this :
uint32_t boot_jumpAddress;
boot_jumpAddress = (uint32_t)APP_START_ADDRESS;
((void (*)(void))boot_jumpAddress)();
This code performs the following actions:
boot_jumpAddress = (uint32_t)APP_START_ADDRESS;
assigns the value of APP_START_ADDRESS
(0x00020020
) to the
boot_jumpAddress
variable after casting it to an unsigned 32-bit
integer.
((void (*)(void))boot_jumpAddress)();
is a function pointer cast and call operation. Here’s what it does :
(void (*)(void))boot_jumpAddress
casts theboot_jumpAddress
variable to a function pointer. This cast assumes that the address stored inboot_jumpAddress
points to a function with no arguments and no return value (i.e., a function that takesvoid
as both its argument and return types).()
immediately invokes (calls) the function pointed to by the casted function pointer.
In summary, this code converts an address (boot_jumpAddress
) into
a function pointer and then calls the function at that address.
11.8. Some Tips for debugging
11.8.1. How to flash the bootloader and the application together using trace32?
Open Trace32* and flash the bootloader as usual. Access the command bar in Trace32 and type the following commands one after the other:
flash.erase 0x00020000--0x00400000
flash.program 0x00020000--0x00400000
Data.LOAD.Binary <path_of_the_binary_file> 0x00020000--0x00400000
flash.program off
Save the loaded binary via debugger to local:
FLASHFILE.SAVE <path_of_the_binary_file> <start_address>--<end_address>