.. include:: ./../../macros.txt .. _BOOTLOADER: ########## Bootloader ########## Description ----------- A `bootloader <https://de.wikipedia.org/wiki/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| 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 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. 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. 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. 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|. 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| to |bms-master|). .. table:: C Part (On-board code) :name: c-part-on-board-code :widths: grid +-----------------------------+--------------------------------------------+ | 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:: Python Part (PC-side code) :name: python-part-pc-side-code :widths: grid +-----------------------------+--------------------------------------------+ | 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. | +-----------------------------+--------------------------------------------+ 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: .. tabs:: .. group-tab:: Win32/PowerShell .. code-block:: powershell .\fox.ps1 bootloader check -vv .. group-tab:: Win32/cmd.exe .. code-block:: bat fox.bat bootloader check -vv .. group-tab:: Win32/Git bash .. code-block:: shell ./fox.ps1 bootloader check -vv .. group-tab:: Linux .. code-block:: shell ./fox.ps1 bootloader check -vv #. Load the |foxbms| binary into the flash memory of |bms-master|: .. tabs:: .. group-tab:: Win32/PowerShell .. code-block:: powershell .\fox.ps1 bootloader load-app -vv .. group-tab:: Win32/cmd.exe .. code-block:: bat fox.bat bootloader load-app -vv .. group-tab:: Win32/Git bash .. code-block:: shell ./fox.ps1 bootloader load-app -vv .. group-tab:: Linux .. code-block:: shell ./fox.ps1 bootloader load-app -vv #. Run the |foxbms| application on |bms-master|: .. tabs:: .. group-tab:: Win32/PowerShell .. code-block:: powershell .\fox.ps1 bootloader run-app -vv .. group-tab:: Win32/cmd.exe .. code-block:: bat fox.bat bootloader run-app -vv .. group-tab:: Win32/Git bash .. code-block:: shell ./fox.ps1 bootloader run-app -vv .. group-tab:: Linux .. code-block:: shell ./fox.ps1 bootloader run-app -vv #. Reset the boot process: .. tabs:: .. group-tab:: Win32/PowerShell .. code-block:: powershell .\fox.ps1 bootloader reset -vv .. group-tab:: Win32/cmd.exe .. code-block:: bat fox.bat bootloader reset -vv .. group-tab:: Win32/Git bash .. code-block:: shell ./fox.ps1 bootloader reset -vv .. group-tab:: Linux .. code-block:: shell ./fox.ps1 bootloader reset -vv In the case of an error status, a reset command or a power-on restart should resolve the problem. 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 <https://www.ti.com/lit/ug/spnu563a/spnu563a.pdf?ts=1725549128780&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FTMS570LC4357%253FHQS%253Dti-null-null-verifimanuf_manuf-manu-pf-octopart-wwe>`__ . In this project, the flash memory (from ``0x0x00000000`` to ``0x00400000``) has been divided into 5 regions, as shown in :numref:`memory-configuration`. 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 :ref:`about_vector_table`. The program of bootloader (except for its vector table) has been saved in the memory labeled ``BOOTLOADER``. The binary of |foxbms| 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 :numref:`memory-configuration`. 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 :ref:`how_to_load_flash_api_to_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 <https://www.ti.com/lit/ug/spnu563a/spnu563a.pdf?ts=1725549128780&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FTMS570LC4357%253FHQS%253Dti-null-null-verifimanuf_manuf-manu-pf-octopart-wwe>`__ . .. table:: Memory Configuration :name: memory-configuration :widths: grid :align: center +--------------------------+----------------+-------------------+--------+----------------+ | 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`` | | +--------------------------+----------------+-------------------+--------+----------------+ 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 :numref:`communication_between_pc_and_bootloader`: .. figure:: img/communication_between_pc_and_bootloader.png :alt: Communication between PC and bootloader :name: communication_between_pc_and_bootloader :width: 80 % :align: center Communication between PC and bootloader 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. 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. .. figure:: img/boot_fsm_state.png :name: boot_fsm_state :align: center :alt: Boot FSM state 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. .. figure:: img/can_boot_run.png :name: can_boot_run :align: center :alt: Interaction between boot and CAN FSM in the case of a run command 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. .. figure:: img/can_boot_reset.png :name: can_boot_reset :align: center :alt: Interaction between boot and CAN FSM in the case of a reset command 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``. .. figure:: img/can_boot_load.png :name: can_boot_load :align: center :alt: Interaction between boot and CAN FSM during program loading 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``. .. figure:: img/can_fsm_state_partial_1.png :name: can_fsm_state_partial_1 :align: center :alt: Partial CAN FSM state changes 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: .. figure:: img/can_fsm_state_partial_2.png :name: can_fsm_state_partial_2 :align: center :alt: Partial CAN FSM state changes Partial CAN FSM state changes The entire state diagram of the CAN FSM state machine is shown as follows: .. figure:: img/can_fsm_state.png :name: can_fsm_state :align: center :alt: CAN FSM state 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. Developer Note -------------- 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|) ``app.cmd`` and ``app_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. .. _about_vector_table: About Vector Table ^^^^^^^^^^^^^^^^^^ The `vector table <https://www.ti.com/lit/an/spna236/spna236.pdf?ts=1724301653744>`__ 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 <https://developer.arm.com/documentation/den0042/a/Exceptions-and-Interrupts/Exception-priorities/The-Vector-table>`__. 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] <https://developer.arm.com/documentation/den0042/a/Exceptions-and-Interrupts/Exception-priorities/The-Vector-table>`__ 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. 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 <https://developer.arm.com/documentation/ddi0460/d/Programmers-Model/Program-status-registers?lang=en>`__. .. _how_to_load_flash_api_to_sram: 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 <https://www.ti.com/lit/ug/spnu501h/spnu501h.pdf?ts=1725300688204>`__, which is also called `run-time relocation <https://software-dl.ti.com/codegen/docs/tiarmclang/compiler_tools_user_guide/compiler_manual/program_loading_and_running/run-time-relocation-stdz0694629.html#stdz0694629>`__. To run program from SRAM, the following steps must be done: #. 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 <https://www.ti.com/lit/an/spna238/spna238.pdf?ts=1718810487618&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FTMS570LC4357%253Fbm-verify%253DAAQAAAAJ_____32ivajprZvDL3z6rbQIE5s075poxTO-MCK52TSVps_v6YYqaPSWjYQ8ZJlLdypjTy-Tqv1nypZNJa1qxYZE777ZbDRShl_L8Q1uxIgT9PjWdBBcqMH_O4ldH-G8PpDeULXMzVm45DMrLCW8TZVoHk_o-JO4xL6_YoJ_V544VamYSzvz1R-2oBwOWyiZuirQHIT5IREJYlRnmJ7kRaSYlO-hR7Az-4W2mZbI37U0s3Gsu6HSHoGDeozZKUhPR2Nmjswq-T-k5BuK7K-96wpqYDwRG1TQDbKaZYJ1WRYw-Dk-Of3GwVAufbiX23E>`__ #. 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 <https://software-dl.ti.com/codegen/docs/tiarmclang/compiler_tools_user_guide/compiler_manual/linker_description/08_using_linker_generated_copy_tables/using-built-in-link-operators-in-copy-tables-stdz0757585.html>`__. 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 <https://www.ti.com/lit/an/spna235/spna235.pdf>`__. 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 : .. code:: c uint32_t boot_jumpAddress; boot_jumpAddress = (uint32_t)APP_START_ADDRESS; ((void (*)(void))boot_jumpAddress)(); This code performs the following actions: .. code:: c 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. .. code:: c ((void (*)(void))boot_jumpAddress)(); is a function pointer cast and call operation. Here's what it does : #. ``(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). #. ``()`` 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. Some Tips for debugging ----------------------- 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: .. code:: text 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: .. code:: text FLASHFILE.SAVE <path_of_the_binary_file> <start_address>--<end_address>