121 lines
6.2 KiB
Markdown
121 lines
6.2 KiB
Markdown
# Firmware Review — `blackpillrtkcode.ino` (v1 → v2)
|
||
|
||
Review of the as-deployed watchdog firmware, with the fixes shipped in
|
||
`04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2.ino`. Severity: 🔴 likely field-impacting ·
|
||
🟠 robustness · 🟡 cleanup.
|
||
|
||
UBX command checksums and ADC divider math were **verified programmatically**
|
||
(see end of this doc) — those parts of v1 are correct.
|
||
|
||
---
|
||
|
||
## Issues found
|
||
|
||
### 🔴 1. Loss-of-corrections recovery targets the wrong subsystem
|
||
**v1:** after `SOFT_RESET_DELAY` (5 min) of `fixQuality < 4`, it sends
|
||
`UBX_SOFT_RESET` (a GNSS hot-reset) to the ZED-F9P.
|
||
**Problem:** corrections arrive over the **4G NTRIP modem**, not from the GNSS.
|
||
Hot-resetting the receiver does nothing for a hung modem — the dominant field
|
||
failure per the client. Five minutes are spent on a no-op before the only
|
||
effective action (a full power-cycle at 10 min).
|
||
**v2:** drops the GNSS hot-reset; recovery = **power-cycle the whole stack**
|
||
(which restarts the 4G module), gated by a grace window and backoff.
|
||
|
||
### 🔴 2. Hard-reset has no backoff → reboot-loop / Wialon flapping
|
||
**v1:** after `HARD_RESET_DELAY` (10 min) it power-cycles, returns to BROWNOUT,
|
||
immediately powers back on, and — if the fault persists — does it **again every
|
||
~10 min indefinitely**. Each cycle is a multi-minute disconnect.
|
||
**Problem:** a genuinely dead 4G card, or a unit parked without sky view, makes
|
||
the unit **flap off Wialon forever**. This is a prime suspect for "desconexiones
|
||
recurrentes."
|
||
**v2:** **exponential backoff** (1→2→4→8→16 min, capped 20 min) then a **latched
|
||
LIMP mode** that keeps the unit **reporting standalone GPS** and only retries
|
||
recovery every 30 min. The unit never silently vanishes.
|
||
|
||
### 🔴 3. No detection of a fully hung stack (NMEA silence)
|
||
**v1:** only reacts via `fixQuality`. If the GNSS/4G stack hangs and stops
|
||
emitting NMEA entirely, `fixQuality` simply freezes at its last value — the
|
||
watchdog may not act at all, or acts on stale data.
|
||
**v2:** tracks `lastNmeaMs`; **no NMEA for 60 s while powered ⇒ power-cycle now**.
|
||
Catches hung hardware far faster than the fix-quality ladder.
|
||
|
||
### 🟠 4. No hysteresis / debounce on vehicle-power & ignition
|
||
**v1:** `pa4_on = (v5vF > 4.0)` is a single hard threshold; ignition is a raw
|
||
`digitalRead`. Around the threshold (alternator noise, ignition flicker), the
|
||
rate mode (1 Hz↔30 s) and state can chatter.
|
||
**v2:** hysteresis band (ON >4.2 V, OFF <3.8 V) + 750 ms debounce on both
|
||
vehicle-power and ignition.
|
||
|
||
### 🟠 5. Brownout exit has no debounce (oscillation risk)
|
||
**v1:** entry to BROWNOUT is debounced 3 s, but the **exit** powers on the instant
|
||
`pa4_on || pa5_on || vBat>3.7`. A brief blip near the threshold can start a
|
||
power-on it can't sustain → drain → repeat.
|
||
**v2:** wake is driven by the same debounced/hysteretic signals; brownout entry
|
||
uses a 5 s confirmation.
|
||
|
||
### 🟠 6. No thermal awareness (the actual field failure mode)
|
||
**v1:** nothing measures temperature, despite "died from heat."
|
||
**v2:** reads the STM32 internal temp sensor (F411 factory cal), logs session
|
||
**min/max**, **derates to 30 s** above 75 °C to cut self-heat/power, and shows it
|
||
on the dashboard. Firmware can't cool the box, but a pulled unit's `tMax` now
|
||
**proves or kills the thermal hypothesis**. (Disable with `ENABLE_MCU_THERMAL 0`
|
||
if your core lacks `ATEMP`/`AVREF`.)
|
||
|
||
### 🟡 7. `String` heap churn on an ARM MCU
|
||
**v1:** comments claim `const char*` avoids fragmentation, but `nmeaBuffer` is a
|
||
`String`, and `drawDashboard` builds a `String paddedMsg` every refresh.
|
||
**v2:** fixed `char nmeaBuf[110]` parser (no heap) and `snprintf` padding.
|
||
|
||
### 🟡 8. Doc/firmware mismatch: boot delay
|
||
The LED table says "waiting 25 s"; v1 `BOOT_STABILIZE_TIME` is **15 s**.
|
||
**v2:** set to **25 s** to match the documented SOP (gives the 4G modem more time
|
||
to attach before the supervisor starts judging the link). Tune on the bench.
|
||
|
||
### 🟡 9. Minor: redundant `pinMode` before `GNSS.begin()`
|
||
Harmless (the serial peripheral reconfigures the pins); left as explicit intent
|
||
in v2.
|
||
|
||
---
|
||
|
||
## What v1 got *right* (kept in v2)
|
||
|
||
- **Float is not treated as failure.** `fixQuality >= 4` correctly counts both
|
||
RTK **fixed (4)** and **float (5)** as healthy. (Earlier verbal claim that
|
||
FLOAT triggered resets was wrong — corrected here.)
|
||
- Floating the TX pin on power-off to avoid back-powering the rail through a GPIO.
|
||
- Low-pass filtering the ADC readings.
|
||
- Hysteresis between `BAT_CRITICAL` (3.4 V) and `BAT_RECOVERY` (3.7 V).
|
||
- The UBX rate commands and their checksums (verified below).
|
||
|
||
---
|
||
|
||
## v2 recovery state machine (summary)
|
||
|
||
```
|
||
SLEEP ──(vehicle pwr | ignition | Vbat>3.7)──► BOOTING ──(25 s)──► RUNNING
|
||
▲ │
|
||
│ Vbat<3.4 & no pwr & no ign (5 s) │
|
||
└───────────────────────────────────────────────────────── RUNNING supervises:
|
||
• no NMEA 60 s ........................► POWER_CYCLE (gated by backoff)
|
||
• RTK lost > 120 s grace .............► POWER_CYCLE (gated by backoff)
|
||
• RTK healthy 5 min .................► clear backoff + LIMP latch
|
||
POWER_CYCLE (6 s rail off) ──► BOOTING ; after 5 fails ──► LIMP (keep reporting)
|
||
```
|
||
|
||
---
|
||
|
||
## Verification performed (2026-06-19)
|
||
|
||
- **UBX checksums** recomputed (8-bit Fletcher over class…payload): `UBX_1HZ`
|
||
→ `01 39` ✓, `UBX_30S` → `1E AD` ✓, `UBX_SOFT_RESET` → `0F 66` ✓.
|
||
- **CFG-RATE decode:** 1 Hz = measRate 1000 ms × navRate 1; 30 s = 1000 ms × 30 ✓.
|
||
- **ADC dividers (12-bit, 3.3 V, ÷2):** LiPo 3.4–4.2 V → 1.70–2.10 V at pin
|
||
(2110–2606 counts); 5 V rail → 2.50 V (3102 counts). No ADC clipping ✓.
|
||
- **Backoff ladder:** 1, 2, 4, 8, 16 min → LIMP @ 30 min, as designed ✓.
|
||
- **Compile:** v2 builds clean under `g++ -std=c++14 -Wall -Wextra` against a
|
||
hardware stub (logic/types/flow only).
|
||
|
||
> ⚠️ The stub compile validates **logic**, not the STM32 target. **Bench-flash and
|
||
> test v2 on real hardware** (Arduino IDE / PlatformIO, STM32duino core) before
|
||
> any field deployment — especially the thermal read and the power-cycle timing.
|