Files
MicrOBU/obu-firmware/FLASHING.md
T
Ashin WalpolaandClaude Opus 5 528637dab6 Fix UPER encoding of CurvatureCalculationMode; verified on hardware
CurvatureCalculationMode is the one extensible ENUMERATED in CAM:
  ENUMERATED {yawRateUsed(0), yawRateNotUsed(1), unavailable(2), ...}
UPER encodes an extensible ENUMERATED as an extension bit followed by the root
index - 1 + 2 = 3 bits. All three of our encoders wrote only the 2-bit index,
shifting yawRate and the entire low-frequency container one bit early for any
standards-compliant receiver.

It went unnoticed because every end of this project shared the mistake: the
Kotlin codec was ported bit-for-bit from cam.c, so phone and ESP32 agreed
perfectly with each other and with nothing else. Confirmed against the ETSI
ASN.1 in the C-ITS-Parser checkout, where rasn marks this type - and only this
type - #[non_exhaustive].

Fixed in all three copies of the encoder (app CamUperCodec.kt,
obu-firmware/main/cam.c, obu-cam-transmistter/main/cam.c) plus the decoder,
which now rejects rather than misreads a set extension bit. Frame size is
unchanged at 43 bytes. Transmitter reflashed and the phone decodes its CAMs.

Also in this change:

- serial_link: skip send_frame entirely when no USB host is attached, and raise
  the tx mutex timeout above the worst-case hold. With the phone unplugged every
  write blocked its full timeout while holding the lock, so forwarded CAM_RX
  traffic starved the 1 Hz heartbeat - observed as "tx mutex timeout, dropping
  frame" on the console, and it would have tripped the phone's link watchdog.
  Verified gone on hardware.
- Log decoded and failed CAMs in CamUseCaseRepository. "The app shows nothing"
  had two indistinguishable causes; a silent `?: return` made this bug much
  harder to find than it needed to be.
- Remove the ESP32 send-only/send-and-receive toggle. Reception can't be
  disabled in firmware (raw TX only works while promiscuous), so it was an
  app-side filter pretending to be a radio control.
- V2X monitor follows the serial link state on the ESP32 path instead of MQTT,
  which is permanently disconnected there; CAM intake is gated on the link being
  up, and engine state is cleared when it drops.
- About screen: 0.5.0, Phase 03.
- Track obu-cam-transmistter, the bench CAM transmitter. Its cam.c is compiled
  (unlike obu-firmware's reference copy) and must stay bit-identical to the other
  two - this commit is what that coupling costs when it's broken.
- Document the two-toolchain split: this project builds on IDF 5.5.4, obu-firmware
  on the pinned 6.1. Exporting both in one shell fails confusingly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 14:50:35 +02:00

5.1 KiB

obu-firmware — setup & flashing notes

Two toolchains - use a dedicated terminal for each

This project builds against the receiver-firmware's pinned ESP-IDF 6.1. The separate obu-cam-transmistter project builds against the global ESP-IDF 5.5.4. Exporting both 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.

Every new PowerShell session

Activate the toolchain (obu-firmware has no esp-idf of its own — reuse the receiver firmware's already-installed checkout):

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
C:\Users\Ashin\Documents\micrOBU_workspace\its-g5-receiver-firmware\esp-idf\export.ps1
idf.py --version

Build & flash

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 COM5 -b 921600 flash monitor

Swap COM5 for whatever port the ESP32-C5 enumerates as (Device Manager → Ports). monitor opens the serial console after flashing — Ctrl+] to exit.

If the build fails

  • "includes X.h, provided by Y component(s)... not in the requirements list" — IDF 5.x split the old monolithic driver component apart (esp_driver_gpio, esp_driver_uart, etc.). Add the named component to REQUIRES in main/CMakeLists.txt and rebuild. Already fixed once for esp_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 CAM serial link (serial_link.c) 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 monitor from your PC. Leave the phone unplugged from this one; it only carries idf.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.

Work down this list — each step isolates the layer below it.

  1. Flash and install together. SERIAL_LINK_MAX_PAYLOAD is 512 on both sides. A phone at 512 talking to firmware still at 160 (or vice versa) silently rejects every large frame at the length exceeds max, resync branch. Never update one side alone.
  2. 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.
  3. 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 the matched device line.
  4. Is the link alive? The firmware sends a STATUS heartbeat 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.
  5. If it connects but no CAM_RX ever arrives — suspect DTR. The app now asserts DTR/RTS on open (openDevice() in UsbSerialTransport.kt), because CdcAcmSerialDriver doesn'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 the setDTR(true) call and with it commented out) and record the answer in serial_link.h next to the VID/PID note, so nobody has to guess again.
  6. Watch the counters, not just "Sent: N". The CAM Pinger card shows consecutive write failures (phone side) and the firmware's tx-failure / oversize-drop / CRC-error totals from the heartbeat. A rising tx fail means CAMs reach the ESP32 but esp_wifi_80211_tx rejects them — a radio problem, not a link problem.

Notes

  • No git submodule update needed 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 — always export from the receiver-firmware's pinned checkout, since this firmware's undocumented PHY/driver internals were verified against that specific build.