Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QCy5VHMVLkAmftYM6swWmx
19 KiB
| title | tags | project | author | date | status | authority | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| EinarBoard v6 — Phase 1 — NTRIP Client in the STM32 — Bench Spec & Execution Prompt |
|
Project Drogheda | Helgen CTO + Claude | 2026-08-24 | Active — THE spec for the Phase 1 bench spike. Execute before any v6 layout exists. | implements Phase 1 of `2026-08-24_…EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md`. Firmware baseline = `04-Source-Code/firmware/blackpillrtkcode_v3` (v3.0.3). Where this conflicts with the v6 brief on Phase 1 mechanics, THIS file wins. |
Phase 1 — NTRIP client in the STM32 (bench spike)
!!! warning "Designators from before 22-Oct (T43)" This document keeps the designators the board had on its date. Since 22-Oct (T43) the V5.6, V5.7 and v6 carry new ones, and several old names now belong to another part: on the v6, U6 is now the A7670SA modem (was U19) and the LTC4040 is U17 (was U6). To read it against today's board, use the old → new table (in Spanish); parts already gone by 22-Oct are not in it.
Why this exists. The Ardusimple 4G NTRIP Master runs the NTRIP client on the module. A bare A7670SA does not. v6 therefore requires a piece of firmware Helgen has never shipped, sitting directly on the correction path — the exact path the June RCA already proved you cannot see into. This spec retires that risk on a $40 bench rig, before a single v6 trace is drawn.
0. ⚠ Correction to the v6 brief §10
The brief said to run this spike on a v5.5 board through its XBee socket. After extracting the actual v5 netlist, that is wrong on two counts and the plan is changed:
- The socket cannot power the modem.
J7.1 = /3V3_MODEM, generated by U7 (MP1584) → L7 (10 µH/2 A) → 3V3_MODEM, C41 22 µF + C43 100 µF. The A7670SA needs VBAT 3.4–4.2 V at up to 2.8 A. Wrong voltage, wrong current, wrong rail. Feeding it from there would brown outSW_5Vand reproduce the 10-Ago boot loop on the bench. - The UART nets collide.
/USART1_RX(PA10) carries three loads:U16.43,K1.1(relay NC) andMOD1.23(the F9P's TX2). There is no free RX pin on that net for the modem, and with K1 de-energised the NC contact ties/USART1_RXto/DIN_4G(modem RX) — driving the modem's TX into that net creates a self-loop.
Phase 1 therefore runs on a standalone bench rig (§1) that mirrors the v6 topology exactly, using pins that are free in v6. Nothing is soldered to the 15 boards in fab. An optional confirmation run on a real v5.5 board is described in §9 — after G1 passes.
1. Bench rig
1.1 Bill of materials (~US$45, all obtainable in MX in days)
| Item | Note |
|---|---|
| STM32F411CEU6 "Blackpill" | already in stock. Same family as the F411RET6 — USART6 lives on PB6/PB7 in the 48-pin package (see §1.3) |
| A7670SA core board with USB, nano-SIM holder, UART header, u.FL/SMA | ~US$12–18 (MercadoLibre / AliExpress). ⚠ Confirm the variant is -SA. An -E/-C board has no B4/B66 and will never attach on Telcel |
| (fallback) LILYGO T-A7670SA | ~US$33; has its own LiPo + 5 V regulation. Requires flashing the on-board ESP32 with a transparent UART bridge, or tapping the modem pins directly |
| simpleRTK2B Pro + multiband antenna | already owned |
| LTE antenna + pigtail | ~US$5 |
| M2M SIM, Telcel or AT&T MX, real data plan | ⚠ not a flat-rate sensor SIM — see v6 brief §6.4 |
| Bench PSU capable of 4.0 V / 3 A, or a 1S LiPo | powers the modem only |
| SN74LVC2T45DCUR ×2 (or a TXS0108E breakout) | only if the core board exposes raw 1.8 V I/O |
1.2 Power — the rule that keeps this honest
The modem gets its own supply. Never off the Blackpill's 3V3 or a USB 5 V pin. 4.0 V, ≥3 A, with ≥470 µF of low-ESR bulk within 20 mm of the module's VBAT pins, and all three VBAT pins tied. Grounds common, single star point at the modem.
Instrument this: put a scope on VBAT during the first LTE attach and record the droop. >300 mV means the v6 rail is undersized and you have learned it for $0. This measurement is a Phase 1 deliverable, not an optional extra — it is the empirical check on the
AP64350-class buck chosen in v6 Block P.
1.3 Wiring — mirrors v6 exactly
| Function | v6 net | v6 MCU pin | Bench (Blackpill F411CE) |
|---|---|---|---|
MCU → modem UART (AT + GGA + $PHLG) |
/USART6_TX |
PC6 | PB6 (USART1_TX, AF7) |
| modem → MCU UART (responses + RTCM tap) | /USART6_RX |
PC7 | PB7 (USART1_RX, AF7) |
| modem UART TX → GNSS UART2 RX (RTCM, hardware path) | /DOUT_4G |
— | direct wire to the Pro's RX2 |
| GNSS UART1 ↔ MCU | /GNSS_TX, /GNSS_RX |
PA2 / PA3 | PA2 / PA3 (unchanged, HardwareSerial GNSS(PA3, PA2)) |
| GNSS UART2 TX (uplink NMEA tap) | /USART1_RX |
PA10 | not wired on the bench — see the note below |
| modem PWRKEY | /MODEM_PWRKEY |
PC8 | PB8, via 2N7002 open-drain |
| modem STATUS | /MODEM_STATUS |
PC9 | PB9, level-shifted |
| modem RESET | /MODEM_RST |
PC11 | PB5, via 2N7002 open-drain |
| modem rail enable | /MODEM_PWR_EN |
PC10 | PB4 → bench PSU inhibit / load-switch |
⚠ The 48-pin Blackpill has only two usable UARTs, and this matters. On the F411CE (UFQFPN48) there is no PC6/PC7, so USART6 is only available on PA11/PA12 — the USB pins. That leaves USART2 (PA2/PA3, the receiver) and USART1 (PA9/PA10 or PB6/PB7, the modem). Remapping USART1 to PB6/PB7 for the modem means PA10 cannot simultaneously be USART1_RX, so the UART2 uplink tap is not available on the bench rig.
This costs nothing. The MCU already receives the full NMEA stream — GGA included — from the receiver's UART1 on USART2. Phase 1 sources the caster GGA from there. The PA10 tap exists on v6 only to relay the receiver's UART2 output byte-for-byte for Modo B, which is a v6 concern, not a Phase 1 one. The F411RET6 on v6 has PC6/PC7 and keeps all three.
Level shifting. A7670SA I/O is 1.8 V.
MODEM_TXneeds 3.3 → 1.8;MODEM_RXandMODEM_STATUSneed 1.8 → 3.3 (the STM32's V_IH ≈ 2.31 V — 1.8 V will not read as HIGH).PWRKEYandRESEThave internal 50 kΩ pull-ups to VBAT: drive them open-drain with a 2N7002, never push-pull, so an absent MCU leaves them unasserted. Many A7670SA core boards already carry a shifter — verify with a meter before connecting anything, and never drive 3.3 V into a raw 1.8 V pin.
1.4 Topology note — the fail-safe survives
On v6 the modem's UART TX fans out to both the GNSS UART2 RX (hardware RTCM path) and
the MCU. K1 keeps its role: NC = F9P TX2 → modem RX (MCU dead ⇒ the receiver's own GGA
still reaches the caster ⇒ corrections keep flowing), NO = PC6 (MCU in line, forwarding GGA
and injecting $PHLG). The only schematic change is that K1 pin 10 moves from PA9 to PC6
(v6 Block T). Replicate this on the bench with a jumper: for Phase 1, run permanently in the
"MCU in line" position.
Consequence, stated plainly: v6 makes Modo B the normal operating mode, not a bench special.
That is consistent with the EPCT decision already on record ($PHLG in-band via Modo B).
2. Firmware architecture
2.1 Placement and guards
New block in blackpillrtkcode_v3.ino (Phase 1) → promoted to blackpillrtkcode_v4/ for v6.
Follow the file's existing idiom exactly:
#define ENABLE_NTRIP_CLIENT 0 // 0 = fleet (modem owns NTRIP) · 1 = MCU owns NTRIP [v6]
#define ENABLE_NTRIP_DEBUG 1 // NTRIP/AT trace lines on the USB STREAM log
#ifndef-guarded so -DENABLE_NTRIP_CLIENT=1 works as an arduino-cli build flag without
editing the file — same pattern as ENABLE_MODOB_BENCH. FW version string becomes
"3.1.0-ntrip" so a stray flash is detectable in $PHLG field 1.
2.2 Hard rules
- No
delay(). No blocking reads. Everything is a millis()-timed state machine serviced fromloop(), likechargeGovernor()andmodoBService(). The IWDG is on (ENABLE_IWDG 1) — a blocking AT wait will reset the board. - No
String, no heap. Fixed buffers,snprintf, exactly asbuildPHLG()does. - The NTRIP client never gates the safety loop. Power, charge governor, thermal and fan services keep running at their existing cadence regardless of modem state.
- Secrets never enter git (invariant #6). APN and caster credentials come from
config/ntrip.local.h(git-ignored) or-Dbuild flags. Shipconfig/ntrip.example.halongside the existing4G_NTRIP_HelgenSRV-NMEA.example.txt.
2.3 State machine
┌──────────────────────────── beginBackoff(reason) ────────────────────┐
│ │
MODEM_OFF ─► RAIL_UP ─► PWRKEY_PULSE ─► WAIT_READY ─► CONFIG ─► ATTACH ─► │
▲ │ │
│ ▼ │
│ SOCKET_OPEN ─► NTRIP_GET ──────┤
│ │ │
│ ▼ │
└──────────────── HARD_CYCLE ◄── BACKOFF ◄──────────────── STREAMING ──┘
| State | Action | Timeout → next |
|---|---|---|
MODEM_OFF |
MODEM_PWR_EN low; all modem GPIO released |
— |
RAIL_UP |
MODEM_PWR_EN high |
100 ms → PWRKEY_PULSE |
PWRKEY_PULSE |
PWRKEY low ≥ 50 ms (SIMCom; never copy Quectel's ≥2 s), release | → WAIT_READY |
WAIT_READY |
wait for RDY/+CPIN: READY; UART ready ≈ 8 s, STATUS asserts ≈ 11.2 s |
20 s → BACKOFF |
CONFIG |
ATE0 · AT+CMEE=2 · AT+CPIN? · AT+CGDCONT=1,"IP","<APN>" · AT+CSQ · AT+CREG? |
10 s → BACKOFF |
ATTACH |
AT+CGATT? poll until 1; then AT+NETOPEN |
90 s → BACKOFF |
SOCKET_OPEN |
AT+CIPOPEN=0,"TCP","<caster>",<port> |
30 s → BACKOFF |
NTRIP_GET |
send the §3 request; await ICY 200 OK / HTTP/1.x 200 |
15 s → BACKOFF |
STREAMING |
transparent RTCM in; GGA + $PHLG out; watchdogs of §5 |
see §5 |
BACKOFF |
rail stays up, socket closed, exponential wait | → PWRKEY_PULSE |
HARD_CYCLE |
MODEM_PWR_EN low ≥ 2 s, then MODEM_OFF |
invariant #3's recovery path |
Backoff ladder (mirror chargeGovernor()'s cooldown idiom):
5 s → 10 → 20 → 40 → 80 → 160 → 300 s cap. Reset to 5 s after 10 minutes of healthy
STREAMING. After 3 consecutive BACKOFF entries without ever reaching STREAMING,
escalate to HARD_CYCLE.
Invariant #2 — never vanish, never loop. The backoff ladder plus the 3-strike escalation is what stops this becoming a fast-reboot loop. It is not decoration; it is the invariant.
3. NTRIP v1 handshake — exact bytes
GET /<MOUNTPOINT> HTTP/1.0\r\n
User-Agent: NTRIP HelgenHBOX/3.1\r\n
Authorization: Basic <base64(user:pass)>\r\n
Accept: */*\r\n
Connection: close\r\n
\r\n
- Accept either
ICY 200 OK(NTRIP v1) orHTTP/1.0 200 OK/HTTP/1.1 200 OK. 401→ do not retry blind. LatchNTRIP_AUTH_FAIL, emit anEVT, and back off to the 300 s cap. Hammering a caster on bad credentials gets you IP-banned — and rtk2go does ban.SOURCETABLE 200 OKmeans the mountpoint does not exist. Distinct latch, distinct EVT. This is a real failure mode and the June RCA would have caught the caster problem months earlier if the firmware had reported it.- Base64 is computed at build time from the credentials header — no runtime encoder, no credentials in RAM as plaintext longer than necessary.
GGA uplink. Send one $GPGGA/$GNGGA to the caster every 10 s while STREAMING,
byte-exact, never rebuilt — copy the sentence verbatim from the receiver's own output
(bench: the USART2 NMEA stream; v6: the PA10 UART2 tap). VRS mountpoints require it;
single-base mountpoints ignore it harmlessly. A rebuilt GGA with a recomputed checksum is the
classic way to get silently dropped by a caster — nmeaChecksumOk() already exists in the
firmware for validating it, but the bytes sent must be the originals.
4. RTCM handling and the telemetry that closes the June RCA
RTCM3 bytes flow modem → GNSS UART2 in hardware. The MCU sees the same bytes on its RX and parses them for telemetry without being in the data path's critical timing.
Frame walker (no buffering of payloads — count only):
- Preamble
0xD3, then 6 reserved bits + 10-bit big-endian length, then payload, then 3-byte CRC-24Q. - Message type = first 12 bits of the payload.
- Maintain:
rtcmBytes(u32),rtcmMsgs(u32),lastRtcmMs,rtcmCrcBad(u16), and a small bitmap of seen message types (1005/1006 station, 1074–1077 GPS MSM, 1084–1087 GLO, 1094–1097 GAL, 1124–1127 BDS).
This is the single most valuable thing Phase 1 produces. On 10-Ago the fleet showed zero corrections applied anywhere and there was no way to tell whether the caster, the modem, the wiring or the receiver was at fault. After this,
$PHLGanswers it directly: RTCM arriving but no fix ⇒ receiver/config or base-band mismatch. No RTCM ⇒ caster or link. Build the observability before you build the board.
STREAMING health watchdogs:
| Condition | Action |
|---|---|
| no RTCM byte for 30 s | → BACKOFF ("rtcm stall") |
| no TCP data at all for 60 s | → BACKOFF |
+CIPCLOSE/+IPCLOSE URC |
→ BACKOFF immediately |
CSQ = 99 for 60 s |
→ BACKOFF |
| 3 backoffs, never streamed | → HARD_CYCLE |
5. $PHLG extension — append-only
buildPHLG() is documented append-only and the backend decoder is already deployed. v3.0.3
emits 25 fields. Append six, in this order, and change nothing before them:
| # | Field | Type | Meaning |
|---|---|---|---|
| 26 | ntrip |
u8 | modem state enum (0=OFF … 8=STREAMING, 9=AUTH_FAIL, 10=NO_MOUNT) |
| 27 | rtcm_age |
u32 | ms since last RTCM byte (0xFFFFFFFF = never) |
| 28 | rtcm_msgs |
u32 | RTCM3 frames accepted this power session |
| 29 | rtcm_bad |
u16 | CRC-24Q failures |
| 30 | csq |
u8 | last AT+CSQ RSSI index (99 = unknown) |
| 31 | nret |
u16 | backoff entries this power session |
Also extend the COLS, header in logBoot() and the TLM, record in logTelemetry() with the
same six, same order. Backend: retranslator's $PHLG decoder must tolerate both 25- and
31-field sentences — mixed-firmware fleets are guaranteed during rollout.
Update docs/LOGGING-SPEC.md to v2.2 in the same commit.
6. hostsim
tools/hostsim already carries sim, sim_mb, rollover and a run.sh driver. Add:
- scenario
ntrip— a fake modem behind a pipe that can be scripted to: attach normally · never attach · return401· returnSOURCETABLE· accept then go silent mid-stream · drop TCP · emit malformed RTCM (bad CRC). - Assertions: the ladder is exponential and capped; it resets after 10 min healthy;
HARD_CYCLEfires on the 3rd fruitless backoff and not before;$PHLGstays ≤ 160 bytes with all 31 fields; the XOR checksum is right; no path ever callsdelay(). run.shbuilds and runs bothENABLE_NTRIP_CLIENTvariants, as it already does formodob.
Green hostsim is a precondition for touching the bench rig, not a post-hoc check.
7. G1 — acceptance gate
G1 passes only when all of these hold on the bench rig, in one unbroken run:
- 24 h continuous,
fixQuality ≥ 4(RTK-fixed or float) for ≥ 95 % of samples. - ≥ 1 forced reconnect survived — pull the LTE antenna for 60 s, restore. RTK must return
without human intervention and
nretmust increment. - ≥ 1
HARD_CYCLEexercised — hold the modem in a fruitless state 3 backoffs running; confirm the rail drops ≥2 s and the unit recovers. rtcm_msgsmonotonically increasing and the message-type bitmap shows the expected MSM sets for the base in use.- VBAT droop < 300 mV at LTE attach, scope-captured (§1.2), with the capture archived to
06-Testing-Validation/. - No IWDG reset across the whole run (
g_resetCausestays as booted). - Auth/mountpoint failures produce the correct distinct latches, verified by deliberately using bad credentials and a bad mountpoint.
Deliverables: the 24 h TLM log, the scope capture, the hostsim run output, and a one-page
result note in 06-Testing-Validation/.
8. ⚠ Phase 1 depends on Phase −1
Gate G0 first. The 10-Ago fleet truth is zero corrections applied anywhere → check
HelgenTestStation @ rtk2go:2101. If the caster or base is broken, this bench rig will
faithfully report STREAMING with rtcm_msgs = 0 and you will spend a week debugging
firmware that is working correctly. Prove the correction source on existing hardware
before starting.
9. Optional confirmation on a real v5.5 board (after G1)
Only once G1 has passed, and only if you want the extra confidence:
- Remove the simpleRTK2B Pro shield from MOD1 — otherwise the F9P's TX2 contends with the
modem on
/USART1_RX. - Energise K1 (
PIN_BYP_CTRLHIGH) so COM–NO is closed and/DIN_4Gis isolated from/USART1_RX. - Solder to the THT relay pins (accessible, no fine-pitch work): K1 pin 10 = PA9,
K1 pin 1 = PA10, K1 pins 5/6 =
/DIN_4G./DOUT_4Gis available at J7 pin 2. - The modem still runs from its own 4.0 V supply.
3V3_MODEMstays unloaded.
This confirms the connector and relay mechanics only. It does not validate the v6 pin map (PC6/PC7 have no copper on v5.5) — that is what Phase 6 first-article bring-up is for.
10. Cross-references
- v6 brief:
2026-08-24_Drogheda_EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md - Schematic delta:
2026-08-24_Drogheda_EinarBoard-v6_Phase-2_Schematic-Delta_v1.0.md - Firmware baseline:
04-Source-Code/firmware/blackpillrtkcode_v3/(v3.0.3),docs/LOGGING-SPEC.md - Modo B /
$PHLGdecision:2026-08-08_Drogheda_Firmware-v3.1_ModoB-PHLG_Design-Brief_v1.0.md - Ardusimple config being replaced:
04-Source-Code/firmware/config/4G_NTRIP_HelgenSRV-NMEA.example.txt - Invariants: #1 power-off safe · #2 never vanish, never loop (§2.3 ladder) ·
#3 corrections ride the 4G, recover by power-cycling (
HARD_CYCLE) · #6 no secrets (§2.2)