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

Table 11.1 C Part (On-board code)

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.

Table 11.2 Python Part (PC-side code)

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:

  1. Check the status of the bootloader:

    .\fox.ps1 bootloader check -vv
    
  2. Load the foxBMS 2 binary into the flash memory of BMS-Master:

    .\fox.ps1 bootloader load-app -vv
    
  3. Run the foxBMS 2 application on BMS-Master:

    .\fox.ps1 bootloader run-app -vv
    
  4. Reset the boot process:

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

Table 11.3 Memory Configuration

NAME

ORIGIN

LENGTH

ATTR

FILL

VECTORS_DIRECT

0x00000000

0x00000020

X

0xFFFFFFFF

BOOTLOADER

0x00000020

0x00017FE0

RX

0xFFFFFFFF

PROGRAM_INFO_AREA

0x00018000

0x00007FE0

RX

0xFFFFFFFF

VECTORS_INDIRECT

0x0001FFE0

0x00000020

X

0xFFFFFFFF

APP

0x00020000

0x003E0000

RX

0xFFFFFFFF

STACK

0x08000000

0x00010000

RW

RAM

0x08010000

0x00020000

RWX

RAM_FLASH

0x08030000

0x00040000

RWX

RAM_FLASH_API

0x08070000

0x00010000

RWX

ECC_VECTORS_DIRECT

0xF0400000

0x00000004

R

ECC_BOOTLOADER

0xF0400004

0x00002FFC

R

ECC_PROGRAM_INFO_AREA

0xF0403000

0x00000FFC

R

ECC_VECTORS_INDIRECT

0xF0403FFC

0x00000004

R

ECC_APP

0xF0404000

0x0007C000

R

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:

Communication between PC and bootloader

Fig. 11.1 Communication between PC and bootloader

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.

Boot FSM state

Fig. 11.2 Boot 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.

Interaction between boot and CAN FSM in the case of a run command

Fig. 11.3 Interaction between boot and CAN FSM in the case of a run command

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.

Interaction between boot and CAN FSM in the case of a reset command

Fig. 11.4 Interaction between boot and CAN FSM in the case of a reset command

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.

Interaction between boot and CAN FSM during program loading

Fig. 11.5 Interaction between boot and CAN FSM during program loading

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.

Partial CAN FSM state changes

Fig. 11.6 Partial CAN FSM state changes

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:

Partial CAN FSM state changes

Fig. 11.7 Partial CAN FSM state changes

The entire state diagram of the CAN FSM state machine is shown as follows:

CAN FSM state

Fig. 11.8 CAN FSM state changes

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:

  1. The linker script of the application (foxBMS 2) app.cmd and app_hex.cmd.

  2. The linker script of the bootloader bootloader.cmd.

  3. The address jump between the first vector table and the second vector table, which is defined in boot_intvecs.asm.

  4. The corresponding macros defined in one bootloader header file : boot_cfg.h.

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

  1. Change the MPU configuration for the region from 0x08000000 to 0x0807FFFF to PRIV_RW_USER_RW_EXEC to enable calling the functions in this area without triggering any error. More details can be found in here

  2. Configure 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 :

  1. (void (*)(void))boot_jumpAddress casts the boot_jumpAddress variable to a function pointer. This cast assumes that the address stored in boot_jumpAddress points to a function with no arguments and no return value (i.e., a function that takes void as both its argument and return types).

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