System Overview¶
Block Diagram¶
graph TB
subgraph HOST["Host PC"]
TOOL["Update Tool<br/>(Python / custom)"]
end
subgraph BL["Bootloader — src/main_app.c"]
RX["UART RX ISR<br/>byte_rx_cb()"]
FSM["Protocol FSM<br/>update_com_fsm()"]
AES["AES-CBC<br/>tiny-AES-c"]
CRC["CRC-32<br/>crc_api.h"]
FLASH_W["Flash Writer<br/>write_page_to_flash()"]
LED["Status LED<br/>update_bl_status_led()"]
KEY["Button Poll<br/>bl_key_check()"]
EXIT["Exit Check<br/>check_bl_exit_condition()"]
end
subgraph DRV["Hardware Driver Layer — hw/<TARGET>/Core/Bl_drv_interface/"]
USART["bl_usart_drv"]
FLASH["bl_flash_drv"]
KEY_D["bl_key_drv"]
LED_D["bl_status_led_drv"]
CRC_D["bl_crc_drv"]
CORE["core_drv"]
end
subgraph HW["Hardware"]
UART_HW["UART2"]
FLASH_HW["Internal Flash"]
BTN["USER Button"]
LED_HW["Status LED"]
CRC_HW["CRC Peripheral<br/>(optional)"]
NVIC["NVIC / SysTick"]
end
APP["Application<br/>@ APP_START"]
TOOL -- "UART 115200 8N1" --> UART_HW
UART_HW --> USART --> RX --> FSM
FSM --> AES --> FLASH_W
FSM --> CRC
FLASH_W --> FLASH --> FLASH_HW
CRC --> CRC_D --> CRC_HW
LED_D --> LED_HW
KEY_D --> BTN
CORE --> NVIC
EXIT -- "jump_to_app()" --> APP
EXIT -- "system_reset()" --> CORE
Module Responsibilities¶
src/main_app.c — Bootloader Core¶
The only platform-independent source file. Contains:
| Function | Role |
|---|---|
main_app() |
Entry point; initialises hardware, runs main loop |
byte_rx_cb() |
UART ISR callback; feeds bytes into the protocol FSM |
handle_new_cmd() |
Arms the receive buffer for multi-byte commands |
update_com_fsm() |
Dispatches fully received commands |
do_get_version() |
Replies with protocol version, device ID, flash page size |
do_start() |
Validates firmware header; erases app flash; initialises AES and CRC |
do_next_page() |
Decrypts one page; accumulates CRC; writes to flash |
write_page_to_flash() |
Issues the actual flash write; defers the first 8 bytes |
check_bl_exit_condition() |
Jumps to app or resets when the timeout expires |
update_bl_status_led() |
Toggles the status LED every 500 ms |
bl_key_check() |
Debounces the push-button; extends stay-alive timeout |
update_SysTick_tim() |
SysTick ISR callback; increments the 100 ms tick counter |
hw/<TARGET>/Core/Bl_drv_interface/ — Hardware Drivers¶
Six driver modules, each implementing a fixed interface. The core never accesses registers directly.
| Driver | Header | Responsibility |
|---|---|---|
bl_usart_drv |
bl_usart_drv.h |
UART init/deinit, byte TX, RX ISR, callback registration |
bl_flash_drv |
bl_flash_drv.h |
Flash unlock/lock, page erase, double-word write |
bl_key_drv |
bl_key_drv.h |
GPIO input for push-button (active-low polling) |
bl_status_led_drv |
bl_status_led_drv.h |
GPIO output for status LED |
bl_crc_drv |
bl_crc_drv.h |
Hardware CRC peripheral (only compiled when CRC_MODULE=HW) |
core_drv |
core_drv.h |
SysTick, NVIC, MSP setup, app jump, system reset, app validity |
src/CRC/crc.c — Software CRC¶
Bit-by-bit software implementation of CRC-32/IEEE 802.3 (poly 0x04C11DB7, refin/refout, xorout 0xFFFFFFFF). Selected when CRC_MODULE=SW (the default).
lib/tiny-AES-c/ — AES Encryption¶
Third-party AES-128-CBC implementation by kokke. Used for decrypting the incoming firmware stream. The AES key is injected at compile time via the ENCR_KEY CMake option.
Boot Flow¶
sequenceDiagram
participant HW as Reset
participant BL as Bootloader
participant HOST as Host PC
participant APP as Application
HW->>BL: Reset (POR / WDT / software)
BL->>BL: init_hardware()<br/>GPIO, UART, SysTick, CRC
BL->>BL: Check USER button<br/>(bl_key_pressed())
alt Button held
BL->>BL: timeout = PUSH_BUTTON_DET_TIMEOUT (60 s)
else Button released
BL->>BL: timeout = START_TIMEOUT (500 ms)
end
loop Main loop
BL->>BL: bl_key_check()
BL->>BL: update_bl_status_led()
BL->>BL: check_bl_exit_condition()
BL->>BL: update_com_fsm()
end
HOST->>BL: CMD_GET_VERSION (0x01)
BL->>HOST: version + device ID + page size
HOST->>BL: CMD_START + header<br/>(version, ID, IV, page_count, CRC)
BL->>BL: Validate header
BL->>BL: Erase application flash
BL->>BL: AES_CBC_init(KEY, IV)
BL->>HOST: ACK
loop For each firmware page
HOST->>BL: CMD_NEXT_PAGE + encrypted page data
BL->>BL: AES_CBC_decrypt_buffer()
BL->>BL: CRC_add_byte_tab()
BL->>BL: FLASH_write() (skip first 8 bytes on page 1)
BL->>HOST: ACK
end
BL->>BL: CRC_result() == header.crc ?
alt CRC matches
BL->>BL: FLASH_write(APP_START, first 8 bytes)
BL->>BL: timeout expires → check_bl_exit_condition()
BL->>BL: deinit_hardware()
BL->>APP: jump_to_app()
else CRC mismatch
BL->>HOST: CMD_ERR | CMD_NEXT_PAGE
end
Memory Map (STM32G070RB example)¶
0x08000000 ┌──────────────────────────┐
│ Bootloader (4 KB) │ FLASH (rx) in linker.ld
│ ISR vectors + code │
0x08001000 ├──────────────────────────┤ ← APP_START
│ Application (≤124 KB) │ Written by do_next_page()
│ │
│ First 8 bytes written │ ← deferred until CRC passes
│ last (atomic guarantee) │
0x08020000 └──────────────────────────┘ ← APP_END
0x20000000 ┌──────────────────────────┐
│ SRAM (36 KB) │
│ Stack, page_buf, │
│ volatile state │
0x20009000 └──────────────────────────┘
Page buffer alignment
page_buf is declared with __attribute__((aligned(4))) so the uint32_t * cast used during flash writes is always correctly aligned on Cortex-M0+ targets, which do not support unaligned word accesses.