FLASHING.md still described flashing as for the previous firmware. The port
changed the partition table (NVS 24 KB -> 80 KB, app 0x10000 -> 0x20000), so a
board coming from the previous firmware needs its NVS range erased once and a
full flash, not app-flash; without the erase the BLE bond cannot be stored and
the phone pairs on every connection. Documented that, what the boot log and
the app show afterwards (credentials provisioned on first Connect, stale phone
pairings to forget), both ways back to the previous firmware (the backup image,
or commit 7285fa1 built with IDF 6.1), the production board's port (COM3,
UART bridge), the station-link names in the phone and bring-up sections, and
that the build is self-contained with obu-firmware/external/vanetza-idf.
9.1 KiB
obu-firmware — setup & flashing notes
Two toolchains - use a dedicated terminal for each
Since 2026-09-23 this project builds against ESP-IDF 6.0.2 exactly
(C:\Espressif\frameworks\esp-idf-v6.0.2): it is the vanetza-idf port (see
NOTES.md), and vanetza-idf's radio_c5.cmake refuses any other version because
the raw TX path pokes private Wi-Fi driver structures only validated there. The
previous C firmware used the receiver firmware's IDF 6.1; the separate
obu-cam-transmistter project builds against the global ESP-IDF 5.5.4.
Exporting two of them in one PowerShell window fails: the second export
inherits the first's IDF_PYTHON_ENV_PATH and reports every Python dependency
as unmet. Don't run install.bat to "fix" that - open a fresh terminal, or
clear the state with $env:IDF_PYTHON_ENV_PATH = $null; $env:IDF_PATH = $null.
The build is self-contained: the C-ITS library comes from
obu-firmware/external/vanetza-idf, a copy of external/vanetza-idf from the
colleague's microbu-esp32c5 repository (their commit cf4b99f, unchanged). Pass
-DVANETZA_IDF_DIR=<path> to idf.py to build against another checkout. The
first build downloads espressif/esp-boost into managed_components/.
Nothing is fetched from or pushed to the colleague's repository (HAW GitLab,
urban-mobility-lab/microbu/microbu-esp32c5). To take a newer vanetza-idf from
it, copy their external/vanetza-idf over this folder, rebuild and test, and
commit it here. The rest of their tree (their own VAM firmware, PKI tooling,
station-link Python tools, the V2X2MAP bridge) is not part of this repository.
Every new PowerShell session
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
$env:IDF_TOOLS_PATH = "C:\Espressif"
C:\Espressif\frameworks\esp-idf-v6.0.2\export.ps1
idf.py --version # v6.0.2
Build & flash
Flash over the board's UART-bridge port (CH343; COM3 for the production OBU on the bench), not the native port the phone uses.
cd C:\Users\Ashin\AndroidStudioProjects\MicrOBU\obu-firmware
idf.py set-target esp32c5 # only needed once per clean build folder
idf.py build
idf.py -p COM3 -b 921600 flash monitor
Swap COM3 for whatever port the ESP32-C5's bridge enumerates as (Device Manager →
Ports). monitor opens the serial console after flashing — Ctrl+] to exit.
Use flash (bootloader + partition table + app), not app-flash, whenever the
partition table may differ from what the board has.
First flash onto a board with the previous firmware
The partition table changed with the port (NVS 24 KB → 80 KB, app 0x10000 → 0x20000), and the old NVS is full of Wi-Fi settings the previous firmware left behind. Erase the whole new NVS range once, then do a full flash:
python -m esptool --chip esp32c5 -p COM3 -b 921600 erase-region 0x9000 0x15000
idf.py -p COM3 -b 921600 flash
Without the erase, the board cannot store the BLE bond and the phone has to pair on every connection (2026-09-23). After it:
- the boot log shows
0 bonded phone(s) in NVS,BLE advertising started as 'micrOBU-XXXX'andstation task ready; the radio stays off until the app connects; - on the first Connect the app provisions the demo credentials once (the card says "Provisioning the demo credentials"); they stay in NVS from then on;
- a phone that was paired with the board before must forget
micrOBU-XXXXin Android's Bluetooth settings and pair again (passkey 123456).
Erasing NVS later (e.g. after a partition change) has the same effects.
Going back to the previous firmware
The previous, unsigned C firmware (frame types 0x01-0x05, CAM over USB only) can come back two ways. The app detects it and falls back to its protocol.
- Image:
firmware-backups/in the repository root (gitignored, on the lab laptop only) holds a full-flash image of the COM3 board as it was before the port, with the esptool command in its README.txt. It restores the old partition table and NVS too. - Source: commit
7285fa1is the last one before the port. Check it out in a separate worktree and build it with the receiver firmware's IDF 6.1 (its-g5-receiver-firmware\esp-idf\export.ps1). Erase the NVS range as above first, thenflash(full, since the partition table differs); erasing after flashing would wipe the start of the old app, which sits at 0x10000.
If the build fails
- "includes X.h, provided by Y component(s)... not in the requirements
list" — IDF 5.x split the old monolithic
drivercomponent apart (esp_driver_gpio,esp_driver_uart, etc.). Add the named component toREQUIRESinmain/CMakeLists.txtand rebuild. Already fixed once foresp_driver_gpio+esp_driver_uart— if a new header comes up, same fix. - Otherwise, start clean before re-building:
idf.py fullclean idf.py build
Connecting the phone (ESP32-C5-WIFI6-KIT)
The board has two USB-C ports — use the right one:
- Native USB-C port (labeled for JTAG/native USB, up to 12 Mbps) — this
is where the phone plugs in via USB-OTG. The station link
(
serial_link.cpp: station-link messages as frame type 0x10 in the 0xAA55 framing) runs over the ESP32-C5's native USB Serial/JTAG peripheral on this port, enumerating as a CDC-ACM device under Espressif's VID/PID (0x303A/0x1001). - UART-bridge port (labeled for flashing) — this is what you use for
idf.py flash monitorfrom your PC. Leave the phone unplugged from this one; it only carriesidf.py's flashing protocol and the ESP_LOG console.
The app recognizes the ESP32-C5's VID/PID via a custom probe table in
UsbSerialTransport.kt (the default usb-serial-for-android prober doesn't
know Espressif's device IDs). If the phone doesn't detect anything when
plugged into the native port, first confirm with a tool like "USB Device
Info" (or adb shell dumpsys usb from a PC) that Android sees a USB device
at all — that isolates a bad/charge-only OTG cable from an app-side issue.
Bring-up checklist (phone <-> ESP32-C5 link)
Work down this list — each step isolates the layer below it.
- Flash and install together. The app detects the firmware generation by its heartbeat and speaks either protocol, but only an app from 2026-09-23 on knows the station-link protocol; messages are at most 512 octets on both sides.
- Does Android see the device at all? Plug the phone into the native
USB-C port, hit Connect, and read logcat for
UsbSerialTransport. It logs every attached device and each device's interfaces. Empty list = cable / OTG / wrong port, below the app entirely. - Did the right interface get claimed? The C5's USB Serial/JTAG is a
composite device — expect CDC control (class 2) + CDC data (class 10) +
vendor-specific JTAG (class 255) in that dump. Compare against the
ports=count on thematched deviceline. - Is the link alive? The firmware sends a STATUS at 1 Hz regardless of radio traffic, and the app marks the link ERROR after ~3.5 s of silence. Connected-and-staying-connected means device→host actually works. The board starts its radio only after the app's STATION_CONFIGURE, so nothing is received before Connect.
- If it connects but no V2X_RX ever arrives — suspect DTR. The app now
asserts DTR/RTS on open (
openDevice()inUsbSerialTransport.kt), becauseCdcAcmSerialDriverdoesn't do it by default and the ESP32's USB Serial/JTAG endpoint may gate TX on the host opening the CDC line. This is still unverified on real hardware — test it both ways (with thesetDTR(true)call and with it commented out) and record the answer inserial_link.hnext to the VID/PID note, so nobody has to guess again. - Watch the counters, not just "Sent: N". The CAM Pinger card shows
consecutive write failures (phone side) and the firmware's tx-failure /
RX-queue-drop / CRC-error totals from the heartbeat; the connection card
shows tickets, signed and refused counts. A rising
tx failmeans messages reach the ESP32 but the radio refuses them — a radio problem, not a link problem. A risingrefusedwith signing on usually means no ticket (NVS erased: reconnect so the app provisions again).
Connecting over Bluetooth instead
Settings > Connection > ESP32-C5 > link: Bluetooth, then Connect. The board
advertises as micrOBU-XXXX (last two bytes of its BT MAC; micrOBU-4AFA on
COM3), but only while nothing uses its native USB port. Android asks to pair
the first time: passkey 123456 (fixed in simple_ble.cpp). The bond is kept
on both sides; the board keeps one bond, so pairing a second phone or a PC
replaces the first. Log lines on the console start with cits_ble:.
Notes
- No
git submodule updateneeded here — obu-firmware has no pinned submodule of its own, unlike its-g5-receiver-firmware. - Don't use the global "ESP-IDF 5.5 PowerShell" shortcut or the receiver
firmware's 6.1 checkout — export from
esp-idf-v6.0.2, the version the vanetza-idf radio's undocumented driver internals were verified against. - The console (ESP_LOG, boot messages, panics) stays on the UART-bridge port. Opening it resets the board; do that only while nothing else depends on the session.