Core
Loading...
Searching...
No Matches
boot.c File Reference

Core bootloader. More...

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "usart.h"
#include "boot.h"
#include "gpio.h"
#include "clock.h"
#include "can.h"
#include "error_handler.h"
#include "core_config.h"
#include "stm32g4xx_hal.h"

Macros

#define BOOTSTATE   __attribute__ ((section (".bootstate")))
 
#define BOOTPROGNAME   __attribute__ ((section (".progname"))) __attribute__ ((__used__))
 
#define ALTBANK_BASE   0x08040000
 
#define BOOT_STATE_KEY   0xABCDEF00
 
#define BOOT_STATE_NORMAL   0x00
 
#define BOOT_STATE_VERIFY   0x01
 
#define BOOT_STATE_VERIFY_SOFT_SWITCH   0x02
 
#define BOOT_STATE_SOFT_SWITCHED   0x04
 
#define BOOT_STATE_VERIFIED   0x08
 
#define BOOT_STATE_ENTER   0x10
 
#define BOOT_STATE_ERROR   0x80
 
#define BOOT_STATE_NB_ERROR   0x40
 
#define BOOT_STATUS_OK   0x00
 
#define BOOT_STATUS_INVALID_ADDRESS   0x01
 
#define BOOT_STATUS_ERASE_ERROR   0x02
 
#define BOOT_STATUS_PROG_ERROR   0x03
 
#define BOOT_STATUS_STATE_ERROR   0x04
 
#define BOOT_STATUS_NB_ERROR   0x05
 
#define BOOT_STATUS_ALREADY_BOOTED   0x06
 
#define BOOT_STATUS_NO_BSM   0x07
 
#define BOOT_STATUS_SOFTSWAP_SUCCESS   0x08
 
#define BOOT_STATUS_MAINBANK   0x09
 
#define BOOT_OPCODE_RESET   0x00
 
#define BOOT_OPCODE_SOFTSWAP   0x01
 
#define BOOT_OPCODE_VERIFY   0x02
 
#define BOOT_OPCODE_HARDSWAP   0x03
 
#define BOOT_OPCODE_EXTERNAL   0x04
 
#define BOOT_OPCODE_INTERNAL   0x05
 

Functions

void boot_soft_toggle ()
 Soft bank swap.
 
void boot_reset ()
 Reset the chip.
 
uint32_t check_nonbooting ()
 Check if the program currently running is running from the non-booting bank.
 
void boot_state_machine ()
 Process the current boot state and swap banks if needed.
 
void core_boot_reset_and_enter ()
 Reset the chip and enter the bootloader.
 
void core_boot_init ()
 Initialize the FDCAN filters, check the boot state, and enter the bootloader if necessary. If the state is not valid, an error message will be transmitted.
 

Variables

const char BOOTPROGNAME progname [32] = PROGRAM_NAME_STRING
 
uint32_t BOOTSTATE boot_state
 
uint32_t boot_toggle [8]
 
uint32_t boot_reset_state
 

Detailed Description

Core bootloader.

This core library component implements a bootloader that allows boards to be programmed over CAN.

Theory of operation

The STM32G473 has 512k of FLASH, which is split into two banks of 256k each. The chip can be configured to boot either from the first or the second bank by configuring the non-volatile BFB2 bit in the option byte registers. When the BFB2 bit is set in the FLASH option byte register, the boots from the second bank, otherwise, it boots from the first bank. However, there is also an option to temporarily swap the banks and run the code in the second bank even when booting from the first bank. This allows the code in the non-booting bank to be verified before finalizing the swap. If the verification fails, then the chip will fall back to the working code in the booting bank.

Programming a board takes place according to the following process:

  1. The programmer sends a command to the target board to enter the bootloader
  2. The programmer sends program data to the target board. The target board writes the program data to the non-booting bank
  3. After each block of data is written, the target board reads the block back so the programmer can verify it
  4. Once all of the data has been written, the programmer commands the target board to switch to the non-booting bank
  5. The target board board resets and performs a soft bank swap
  6. The programmer commands the target board to enter the bootloader in the non-booting bank
  7. The programmer sends a command to the bootloader in the non-booting bank to verify that FDCAN communication is working
  8. The programmer commands the target board to binalize the bank swap
  9. The target board updates the option byte, resets, and runs the new code

The bootloader keeps track of its state across resets and between banks using the boot_state variable, which is stored at the highest RAM address (above the stack). This variable is not initialized when the chip is reset, so its value is always preserved unless the chip is power cycled. The highest 24 bits of boot_state are known as the boot key and must always be set to 0xABCDEF. If the boot key is incorrect, an error is raised. This might occur if the boot_state variable is not correctly configured.

FDCAN format

The bootloader communicates with the programmer board using FDCAN with extended IDs. The extra bits in the ID are used to communicate they type of message and the address to be programmed (if required), so all 64 bytes in the body of the message can be used for data.

Communication from the programmer to the target boards

Each board has a unique board ID and master ID, so the master will respond to several IDs, one for each device that can be programmed. The 29-bit extended board IDs have the following format:

3130292827262524 2322212019181716
ID[10:0] CTRL RD/WR
15141312111098 76543210
PAD ADDR[14:0]
  • ID[10:0]: slave ID, used for arbitration
  • CTRL: 0 for a control frame, 1 for a data frame
  • RD/WR: 1 to read from the slave, 0 to write to the slave
  • PAD: Set if the last doubleword in the frame is a padding doubleword
  • ADDR[14:0]: Doubleword address

To decrease the number of bits required to transmit the address, the address is left-shifted by 3 bits. This means that the address will be aligned on an 8-byte boundary. This is required for writing, since all writes to the FLASH memory must be doubleword-aligned.

Messages sent with CTRL set to 0 can have the following contents:

Data Operation
55 55 55 55 55 55 55 55 Enter bootloader
00 Reset chip
01 Soft swap (jump to non-booting bank)
02 Verify
03 Hard swap (only possible after verification with 02)
04 Switch to external programming mode (only possible in booting bank)
05 Leave external programming mode (only possible in booting bank)

Note that all commands except "enter bootloader" require the chip to have been booted.

Messages sent with CTRL set to 1 can either be read or write requests. For a read request, the first data byte is the length and the second byte indicates the bank. Note that while the address must be 8-byte aligned, the length of the read can have any value. If the bank byte is non-zero, then the target board will read from the bank that is not currently running.

For a write request, all of the data bytes contain data to be written. The number of data bytes must be a multiple of 8. Since FDCAN only supports certain packet lengths, if the packet contains fewer doublewords than would fit in the packet, the PAD byte is set. For example, to transmit 56 bytes of data (7 doublewords), one would need a packet length of 64 (DLC set to 15) and set the PAD bit to indicate that the last doubleword should be ignored.

Communication from the target boards to the programmer

The master IDs have the following format:

3130292827262524 2322212019181716
ID[10:0] 1 DATA/STAT
15141312111098 76543210
PAD ADDR[14:0]
  • ID[10:0]: master ID, used for arbitration
  • DATA/STAT: 1 if the frame contains data (echo or read), 0 if the frame contains a status message
  • PAD: Set if the last doubleword in the frame is a padding doubleword
  • ADDR[15:0]: Doubleword address

Status messages transmitted from a board to the programmer are 64 bits long and have the following format:

01234567 89101112131415
STATUS[7:0] MEMRMP BFB2
1617181920212223 2425262728293031
EOP OPERR PROGERR WRPERR PGAERR SIZERR PGSERR MISSERR FASTERR RDERR OPTVERR
3233343536373839 4041424344454647
VERIFY VERIFY_SOFT_SWITCH SOFT_SWITCHED VERIFIED ENTER NB_ERROR ERROR BOOT_STATE_KEY[7:0]
4849505152535455 5657585960616263
BOOT_STATE_KEY[23:8]
  • STATUS[7:0]: Status code
    0StatusNo error
    1ErrorAddress out of range
    2ErrorError while erasing
    3ErrorError while programming
    4ErrorBoot state error
    5ErrorWrite from non-booting bank
    6StatusBoard is already booted (sent when bootloader received opcode 0x55)
    7StatusBSM did not run
    8StatusSoft swap successful (sent when core_boot_init() runs in the non-booting bank)
    9StatusSent when core_boot_init() runs in the booting bank
  • BFB2: BFB2 bit from the option byte register. Indicates which bank the chip will boot from
  • MEMRMP: MEMRMP bit from the memory remap register. Indicates which bank is currently running
  • Next two bytes contain the lowest two bytes of FLASH_SR
  • Next three bytes contain the boot key, which should be 0xABCDEF
  • Bits 7 through 0 contain the boot state.
  • ERROR: Indicates a state error occurred in the booting bank
  • NB_ERROR: Indicates a state error occurred in the non-booting bank
  • ENTER: Indicates that the program should enter the bootloader after the next reset
  • VERIFIED: Indicates that the program in the non-booting bank has been verified. If this bit is set, the banks can be swapped
  • SOFT_SWITCHED: Indicates that the soft switching succeeded
  • VERIFY_SOFT_SWITCH: Indicates that the signal to soft switch has been processed by the boot state machine in the booting bank
  • VERIFY: Indicates that the chip should soft switch after the next reset

Components

The bootloader consists of four main components: the startup script, the boot state machine, the core_boot_init() function, and an entry point.

Startup script

For the bootloader to work, the default startup script must be replaced by the startup script startup_stm32g473xx.s. The startup script defines the interrupt handlers for the program, including the reset handler. By default, the reset handler initializes the stack and jumps to the application code. The modified startup script required for the bootloader also runs the boot state machine before performing any additional initialization.

Boot state machine

The boot state machine runs before any other code and updates the boot state or soft swaps as necessary. The BSM first checks the nature of the reset. If the reset was caused externally (by pulling the nRST pin low), then the state is reset to NORMAL and the BSM continues to the application code.

The behavior of the BSM depends on whether it is running in the booting or non-booting banks. In the booting bank, the BSM will change the state to VERIFY_SOFT_SWAP and soft-swap if the state is VERIFY and continue to application code otherwise.

In the non-booting bank, the BSM will change the state to SOFT_SWITCHED if the state is VERIFY_SOFT_SWAP. Otherwise, the BSM will set the NB_ERROR bit and reset. This causes the chip to return to the booting bank.

core_boot_init()

The core_boot_init() function is called by application code after the clocks, GPIO, and FDCAN modules needed for the bootloader have been initialized. This function initializes the RX filter for the board's bootloader ID. The function also checks the boot state and enters the bootloader if necessary. See the state diagram above for details.

Entry point

The software enters the bootloader by calling core_boot_reset_and_enter(). The FDCAN RX interrupt hander in can.c will automatically call this function if it receives a packet addressed to its bootloader FDCAN ID containing the command to enter the bootloader

Function Documentation

◆ boot_soft_toggle()

void boot_soft_toggle ( )
extern

Soft bank swap.

This function is defined in startup_stm32g473xx.s

◆ boot_state_machine()

void boot_state_machine ( )

Process the current boot state and swap banks if needed.

This function is called from startup_stm32g473xx.s and is called before the HAL is initialized and before the RAM is initialized. The boot state is preserved in a special section (.bootstate) at the end of the RAM that is not initialized, so its contents are preserved between resets.

◆ check_nonbooting()

uint32_t check_nonbooting ( )

Check if the program currently running is running from the non-booting bank.

Return values
0The program currently running is in the booting bank
1The program currently running is in the non-booting bank

◆ core_boot_init()

void core_boot_init ( )

Initialize the FDCAN filters, check the boot state, and enter the bootloader if necessary. If the state is not valid, an error message will be transmitted.

Note
This function should be called after the FDCAN module is initialized.