STM32G431RB firmware for the SDV diagnostic gateway project. Acts as the ECU node: responds to OBD-II / UDS diagnostics over CAN-FD, and bridges CAN ↔ RS485 internally under FreeRTOS.
Application + UDS flash bootloader (FBL), hardware-validated on the bench: 95 UDS / 30 FBL / 22 OBD-II SocketCAN checks green, plus host unit tests (no-HW) for the DTC engine.
- OBD-II simulator (SAE J1979 / ISO 15031-5): Mode 01 PIDs — RPM (0x0C), speed (0x0D), coolant temp (0x05), supported-PID bitmap (0x00) — plus Mode 03 (stored DTC), 04 (clear DTC), 07 (pending DTC), 09 (vehicle info / VIN) — with simulated values.
- UDS server (ISO 14229): 15 services in the app — 0x10 session control (P2/P2* advertised), 0x11 reset, 0x14 clear DTC, 0x19 DTC information (20 subfunctions: status bits, snapshot, extended data, mirror memory), 0x22/0x23 read DID / read memory, 0x27 security access, 0x28/0x85 communication & DTC-setting control, 0x2A periodic read + 0x2C dynamic DID, 0x2E write DID, 0x2F IO control, 0x31 routines, 0x3E tester present — plus 0x34/0x36/0x37 served by the bootloader (below). NRCs per ISO 14229-1: 0x7F wrong-session vs 0x33 locked split, 0x78 responsePending before the slot erase.
- UDS flash bootloader (FBL): 28 KB bare-metal BL at 0x08000000 owns reprogramming with a CRC32 boot gate, anti-rollback counter and a brick-safe update marker — details in the FBL section below.
- ISO-TP (ISO 15765-2): single/multi-frame assembly, flow control; RX/TX state machines split for full-duplex (§9.8.3).
- CAN-FD (BRS): 500 kbps arbitration / 2 Mbps data.
- CAN addressing (ISO 15765-2 / 14229-1): physical (0x7E0↔0x7E8) and functional (0x7DF broadcast) — functional requests map to this node's physical response ID; UDS services suppress responses on functional requests.
- FreeRTOS gateway: separate tasks for CAN-Rx, RS485, debug UART; CAN↔RS485 bidirectional message routing; queues + mutex.
- Safety: IWDG refreshed only when all tasks (main / CAN-Rx / RS485) set their alive bits — any stalled task resets the board within ~2 s. The watchdog keeps running in the bootloader (only POR stops it), so the BL refreshes it in its superloop and between flash-erase pages. CAN bus-off recovery runs in task context (flag from ISR), and the error handler drops to a safe state on CAN/UART faults.
obd-simulator/
├── Core/
│ ├── Inc/ main.h, obd2_simulator.h, fdcan_config.h, can_addressing.h,
│ │ diag_session.h, uds_service.h, iso_tp.h, rs485.h, uart_debug.h, ...
│ └── Src/ main.c (FreeRTOS tasks), fdcan_config.c, obd2_simulator.c,
│ iso_tp.c, diag_session.c, uds_service.c, rs485.c, uart_debug.c,
│ bl_main.c / bl_boot.c / bl_uds.c / ota_flash.c (bootloader, -DBL_BUILD),
│ fdcan_loopback_test.c (diagnostic, gated by RUN_FDCAN_LOOPBACK_TEST)
├── Middlewares/FreeRTOS-Kernel/
├── docs/ # setup notes, development history (docs/history/), blog draft
├── tests/ # Python bench suites (UDS / FBL / OBD-II, SocketCAN)
│ # + host unit tests (host_dtc_test.c — no hardware)
├── Makefile # arm-none-eabi-gcc
├── MCP2562FD_PINOUT.md # CAN-FD transceiver pinout
├── STM32G431RBTX_FLASH.ld # 128KB Flash / 32KB RAM linker script
└── startup_stm32g431xx.s
┌─────────────┐ CAN-FD (BRS 500k/2M) ┌──────────────────────────┐
│ Tester │ 0x7E0↔0x7E8 · 0x7DF func │ STM32G431RB │
│ PC / RPi │ ◄─────────────────────────────► │ (ECU node) │
│ SocketCAN / │ via MCP2562FD (PA11/PA12) │ │
│ DoIP gw │ │ FDCAN1 → ISO-TP → UDS │
└─────────────┘ │ │ │
│ FreeRTOS gateway │
┌─────────────┐ SWD (flash/debug) + │ │ │
│ ST-LINK │ UART VCP (PA2/PA3, 115200) │ ▼ │
│ VCP → │ ◄─────────────────────────────► │ USART1 (PA9/PA10) → │
│ /dev/ttyACM0│ │ MAX485 (DE/RE=PA8) ─────┼──► RS485 bus
└─────────────┘ └──────────────────────────┘
| Part | Use |
|---|---|
| STM32 Nucleo-G431RB | ECU (Cortex-M4 @ 170 MHz, 128 KB Flash, 32 KB RAM) |
| MCP2562FD | CAN-FD transceiver (PA11 RX, PA12 TX) |
| MAX485 | RS485 transceiver (PA9 TX, PA10 RX, PA8 DE/RE) |
| 120 Ω × 2 | CAN / RS485 termination |
| Pin | Function |
|---|---|
| PA11 / PA12 | FDCAN1_RX / TX |
| PA9 / PA10 | USART1 TX/RX (RS485) |
| PA8 | MAX485 DE/RE direction |
| PA2 / PA3 | USART2 TX/RX — ST-LINK debug VCP |
| PA5 | LD4 LED |
# toolchain
sudo apt install gcc-arm-none-eabi stlink-tools # Fedora: dnf install arm-none-eabi-gcc stlink (Atomic: rpm-ostree)
# HAL drivers (first time)
git clone --depth 1 https://github.com/STMicroelectronics/stm32g4xx_hal_driver.git Drivers/STM32G4xx_HAL_Driver
mkdir -p Drivers/CMSIS/Device/ST
git clone --depth 1 https://github.com/STMicroelectronics/cmsis_device_g4.git Drivers/CMSIS/Device/ST/STM32G4xx
# build & flash
make -j$(nproc) # application
make bl # bootloader (28 KB slot at 0x08000000)
make flash # mass erase + BL + app via ST-LINK
# debug console (115200 8N1, ST-LINK VCP)
screen /dev/ttyACM0 115200- Mode: CAN-FD with BRS — arbitration 500 kbps, data 2 Mbps
- Physical: request 0x7E0 · response 0x7E8 (1:1 OBD-II/UDS diagnostic)
- Functional: request 0x7DF (broadcast) → this node responds on its physical response ID 0x7E8 (not 0x7E7); UDS services suppress functional responses.
- Filter: accepts standard frames 0x000–0x7FF on RX FIFO0
- Firmware is passive: waits for requests, sends responses (no periodic TX).
- USART1 @ 115200 8N1, half-duplex (DE/RE on PA8)
- Frame format:
[ID_H][ID_L][DLC][DATA 0..N]— RX assembly completes on 3 + DLC bytes (variable length), DLC > 64 resyncs - Bidirectional CAN↔RS485 routing in the FreeRTOS gateway task
128 KB flash cannot hold A/B partitions (app ≈ 66 KB ×2 + BL + meta), so a
classic FBL layout is used (Core/Inc/bl_handoff.h):
| Region | Range | Size | Contents |
|---|---|---|---|
| BL | 0x08000000 – 0x08006FFF | 28 KB | bare-metal UDS flash server (make bl) |
| App | 0x08007000 – 0x0801DFFF | 92 KB | FreeRTOS application |
| Meta | 0x0801E000 – 0x0801FFFF | 8 KB | magic / state / version / size / CRC32 |
- Boot gate: meta VALID + CRC32 match → jump to app; meta absent with plausible vectors → jump (dev / SWD flash path); anything else → stay in the BL waiting for UDS (brick-safe).
- Update flow:
0x10 02in the app sets a no-init RAM handoff flag (0x20007F00 — top 256 B reserved in both linker scripts) and resets; the BL then serves the programming sequence0x27 → 0x34 → 0x36 → 0x37 → 0x31 CheckMemory(CRC32 + anti-rollback). Meta is invalidated to UPDATING before the slot erase, so power loss mid-flash leaves the board in the BL, never bricked. - 0x34 timing: NRC 0x78 (responsePending) goes out before the page-wise erase (~1 s for a full 92 KB slot) so the tester switches to its P2* budget; the erase refreshes the IWDG between pages.
Bench suites run over SocketCAN against the board (python-can required):
sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on && sudo ip link set can0 up
sudo python3 tests/uds_test.py can0 # UDS: 95 checks (sessions, security, NRC,
# DTC info, 0x2A/0x2C, OBD-II modes)
sudo python3 tests/fbl_test.py can0 # FBL: 30 checks (full flash cycle, brick
# recovery, anti-rollback, 0x78)
sudo python3 tests/obd2_test.py can0 # OBD-II modes 01/03/04/07/09: 22 checksHost unit test — no hardware needed (DTC fault manager):
gcc -Wall -I tests/host -I Core/Inc Core/Src/obd2_simulator.c \
tests/host/host_dtc_test.c -o /tmp/host_dtc_test && /tmp/host_dtc_testQuick manual check:
cansend can0 7E0#02010C0000000000 # Mode 01 PID 0x0C (RPM)
candump can0 # → 7E8 responseLatest full-bench run: UDS 95 + FBL 30 + OBD-II 22 all green on the committed IWDG-enabled build (2026-10, FBL suite entered via
0x10 02handoff from the running app).
Measured via arm-none-eabi-size:
| Image | Flash (text+data) | Slot | RAM (data+bss) |
|---|---|---|---|
| Application | ~65.8 KB | 92 KB app slot (70%) | ~21.8 KB / 32 KB (68%) |
| Bootloader | ~23.1 KB | 28 KB BL slot (81%) | ~5.6 KB |
App RAM includes the 16 KB FreeRTOS heap; the top 256 B of RAM is reserved for the BL handoff flag. ISO-TP RX/TX buffers are 512 B each (
ISO_TP_MAX_MESSAGE_SIZE) — larger receptions (OTA bulk) stream through the sink callback instead of full buffering.
Each hand-written module fills a standard AUTOSAR BSW role:
| Module | Role | AUTOSAR BSW |
|---|---|---|
fdcan_config.c |
FDCAN init, filters, RX/TX FIFO, ISR | Can / CanIf |
iso_tp.c |
SF/FF/CF/FC segmentation, STmin/BS pacing, stream sink | CanTp |
uds_service.c + diag_session.c |
service dispatch, sessions, S3 timer, SecurityAccess | Dcm (DSL/DSD/DSP) |
obd2_simulator.c |
fault debounce, DTC status machine, freeze frame, mirror memory | Dem |
bl_*.c + ota_flash.c |
boot gate (CRC32 / anti-rollback), 0x34/36/37/31 reprogramming | FBL / Fls |
main.c (alive bits + IWDG) |
watchdog refresh only when all tasks alive | WdgM |
- Bare-metal HAL (no CubeMX code generation); FreeRTOS added by hand.
- Toolchain: arm-none-eabi-gcc 15.x (tested 15.2.0, Fedora).
- Static analysis: cppcheck MISRA-C:2012 spot checks (e.g., Rule 10.4 explicit casts).