The Ararext Bootloader is a modernized bootloader for STM32F407xx microcontrollers, implemented in Rust for memory safety and maintainability while maintaining comparable performance to traditional C implementations.
- Memory Safety First: Leverages Rust's type system to eliminate entire classes of bugs
- Clear Separation of Concerns: Modular design with single-responsibility modules
- Type-Driven Development: Types encode constraints and guarantee correctness
- Minimal Unsafe Code: Unsafe blocks are isolated, documented, and minimized
main()
├── Hardware Initialization
│ ├── Clock configuration (84 MHz)
│ ├── GPIO setup (button, LED)
│ ├── UART configuration (USART2, USART3)
│ └── CRC peripheral setup
├── Startup Decision Logic
│ ├── Read button state
│ ├── If pressed: bootloader_loop()
│ └── If not: jump_to_user_app()
└── bootloader_loop()
├── Read command length
├── Read command packet
├── Parse packet into CommandPacket
└── Dispatch to handlers
Key Decisions:
- Uses
nb::block!()for non-blocking UART reads (better than polling) - Separate loop functions for clarity
- Fast startup sequence (LED blink pattern)
All magic numbers and configuration in one place:
- Command codes (0x51-0x5C)
- Response codes (0xA5, 0x7F)
- Memory addresses and sizes
- Bootloader version and limits
Advantages:
- Easy to modify configuration
- Type-safe constants
- Single source of truth
UartComm {
rx_buffer: [u8; 200],
rx_count: usize,
}
├── read_byte() - Read from UART
├── write_byte() - Write to UART
├── write_buffer() - Write multiple bytes
├── send_ack() - Send ACK response
├── send_nack() - Send NACK response
└── rx_buffer() - Access received data
CommandPacket {
length: u8,
command: u8,
payload: [u8; 197],
payload_len: usize,
crc: u32,
}
└── parse() - Parse buffer into structured packet
Protocol Frame:
[Length: 1] [Command: 1] [Payload: N] [CRC: 4]
↓ ↓
Length of command + payload + CRC Verified by handler
Design Choices:
- Fixed-size buffer prevents overflow
CommandPackettype ensures structure- CRC extracted during parsing
For each command (12 total):
├── Validate input
├── Check address/parameters
├── Perform operation
├── Send ACK with response or NACK
└── Return to bootloader loop
Key functions:
├── handle_getver_cmd() - Return version
├── handle_getcid_cmd() - Return chip ID
├── handle_go_cmd() - Jump with address validation
├── handle_flash_erase_cmd() - Erase sectors
├── handle_mem_write_cmd() - Write to memory
├── handle_mem_read_cmd() - Read from memory
└── ... (6 more command handlers)
Error Handling Pattern:
if packet.len() < required_len {
UartComm::send_nack(tx);
return;
}
if memory::verify_address(address) == ADDR_INVALID {
UartComm::send_nack(tx);
return;
}
// Success
UartComm::send_ack(command, response_len, tx);verify_address(u32) → ADDR_VALID | ADDR_INVALID
├── Checks SRAM1 range
├── Checks SRAM2 range
├── Checks FLASH range
└── Checks Backup SRAM range
get_mcu_chip_id() → u16
├── Reads DBGMCU->IDCODE
└── Extracts bits [11:0]
get_flash_rdp_level() → u8
├── Reads option bytes (0x1FFFC000)
└── Extracts RDP bits [15:8]
MemoryRegion enum
├── SRAM1
├── SRAM2
├── Flash
├── BackupSram
└── Unknown
Design:
- Type-safe region identification
- Validates all addresses before operations
- Prevents jumping to peripheral memory
FlashSector {
number: u8,
base_address: u32,
size: u32,
}
└── get_sector_info(n) → Option<FlashSector>
execute_flash_erase(sector, count) → Result<(), &str>
├── Validates sector count
├── Supports mass erase (sector = 0xFF)
├── Clamps number_of_sectors
└── Returns errors as strings
execute_mem_write(address, data) → Result<(), &str>
├── Byte-by-byte programming
├── Maintains flash safety
└── Error reporting
configure_flash_sector_rw_protection(...)
├── Mode 1: Write protection
├── Mode 2: Read/write protection
└── Can disable all protection
read_ob_rw_protection_status() → u16
└── Queries current protection
Safe Operations:
- Returns
Result<T, E>for errors - Validates inputs before hardware access
- Uses volatile reads/writes for hardware access
- Includes operation completion checks
verify_crc(crc, data, crc_host) → u8
├── Accumulates CRC over data
├── Compares with host CRC
└── Returns SUCCESS or FAIL
calculate_crc(crc, data) → u32
└── Calculates CRC for data
Implementation:
- Uses STM32F4 hardware CRC peripheral
- CRC-32 polynomial
- Resets after each calculation
UART interrupt
↓
Read byte in bootloader_loop
↓
Parse CommandPacket from buffer
↓
Match on packet.command
↓
Dispatch to handler (12 possible paths)
↓
Handler validates & executes
↓
Send ACK/NACK response
↓
Loop back to receive next command
Application → Host PC (Serial)
↓
[Command]
↓
Bootloader parses
↓
Validates address range
↓
Reads/writes hardware
↓
Returns response
BL_MEM_WRITE command arrives
↓
Parse address, length, data
↓
Verify address is in FLASH
↓
Unlock flash module
↓
For each byte: program byte
↓
Lock flash module
↓
Send ACK response
- All jumps validated against safe regions
- Peripheral memory explicitly blocked
- Memory regions type-safe via enum
- Hardware CRC peripheral accelerates calculation
- All commands include CRC
- Detects transmission errors
- Flash sectors can be write/read protected
- Protection bits in option bytes
- Can be disabled via command
- Fixed-size buffers with size checking
- Packet length validation
- Bounds checking on payload access
CommandPacketenforces structure- Enum variants for memory regions
- Result types for error handling
0x08000000 ┌─────────────────────┐
│ Bootloader Sector 0 │
│ (16 KB) │
0x08004000 ├─────────────────────┤
│ Bootloader Sector 1 │
│ (16 KB) │
0x08008000 ├─────────────────────┐◄── FLASH_SECTOR2_BASE_ADDRESS
│ User Application │
│ (Sectors 2-7) │
│ (~480 KB) │
0x08080000 └─────────────────────┘
0x20000000 ┌─────────────────────┐
│ SRAM1 │
│ (112 KB) │
0x2001C000 ├─────────────────────┤
│ SRAM2 │
│ (16 KB) │
0x20020000 └─────────────────────┘
0x40024000 ┌─────────────────────┐
│ Backup SRAM │
│ (4 KB) │
0x40025000 └─────────────────────┘
Currently uses:
- SysTick: Via
cortex_m::asm::delay() - UART: Blocking reads (can be upgraded to interrupt-driven)
- No custom ISRs: Uses default cortex-m-rt handlers
Future improvement: Interrupt-driven UART for better responsiveness
-
Reset Handler (cortex-m-rt)
- Initialize
.datasection - Zero
.bsssection - Call
main()
- Initialize
-
Clock Configuration
- Enable HSE (8 MHz external crystal)
- Configure PLL (M=8, N=84, P=2, Q=7)
- Result: 84 MHz system clock
-
GPIO Initialization
- PA0 (B1) as pull-up input
- PA5 (LD2) as push-pull output
- UART pins as alternates
-
Peripheral Setup
- USART2 at 115200 bps
- USART3 at 115200 bps (for debug)
- CRC peripheral
-
LED Blink Sequence
- 3 blinks (0.1s each) indicate bootloader starting
- LED stays on during bootloader operation
- LED turns off when jumping to app
-
Mode Decision
- Read button (PA0)
- If LOW: Enter bootloader_loop()
- If HIGH: jump_to_user_app()
- Startup: ~10ms to mode decision
- Command processing: <1ms for most commands
- Flash erase: ~100ms per sector
- Flash write: ~1ms per byte (byte-by-byte programming)
- Binary: ~25 KB (release build)
- Code: ~20 KB
- Data: ~4 KB (stack + statics)
- Stack: ~2 KB
- Static data: ~1 KB
- Heap: 0 KB (no dynamic allocation)
-
Byte-by-Byte Flash Writes
- Current: Slow (~1ms per byte)
- Could optimize to word-level writes
-
Blocking UART
- Current: Blocking reads
- Could upgrade to interrupt-driven
-
No Dynamic Allocation
- No heap usage
- Could add for flexibility
-
OTP Reading
- Currently stubbed
- Needs implementation
- Word-level flash programming
- Interrupt-driven UART
- OTP read support
- Hardware CRC optimization
- Over-The-Air (OTA) firmware updates
- Secure boot support
- Rollback protection
- Bootloader update capability
- Encrypted firmware support
- Code signing verification
- Failsafe recovery mechanism
- Multi-bank firmware support
| Feature | Rust | C |
|---|---|---|
| Memory Safety | Compile-time guaranteed | Runtime checks needed |
| Binary Size | 25 KB | 28 KB |
| Build Time | ~90s | ~30s |
| Code Organization | Excellent | Good |
| Type Safety | Excellent | Basic |
| Performance | Comparable | Slightly faster |
| Maintainability | Better | Good |
| Learning Curve | Steep | Moderate |
- CRC calculation verification
- Address validation exhaustive tests
- Memory region identification tests
- Command protocol compliance
- Flash operation verification
- Address jumping safety
- Real STM32F407xx device
- UART protocol stress testing
- Flash endurance validation
This design prioritizes safety, maintainability, and clarity over micro-optimizations, making it suitable for production bootloader usage.