From 1044e706e5af4c421c99397ef07754167d6d8157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leopoldo=20Rodr=C3=ADguez?= Date: Tue, 1 Sep 2026 18:00:17 -0600 Subject: [PATCH] auto-sync 2026-09-01 18:00 --- ...2_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md | 63 ++ ...6_GNSS-4G-Integration_Design-Brief_v1.0.md | 657 ++++++++++++++++++ ...ard-v6_Phase-1_NTRIP-Firmware-Spec_v1.0.md | 337 +++++++++ ...arBoard-v6_Phase-2_Schematic-Delta_v1.0.md | 477 +++++++++++++ ...Drogheda_KiCad-Submodule-Migration_v1.0.md | 188 +++++ ...-27_Drogheda_v6-Antenna-Chain_Diagram.html | 175 +++++ 03-Architecture-Hardware/kicad | 2 +- scripts/2026-08-25_v6_T6_rf_block.py | 234 +++++++ 8 files changed, 2132 insertions(+), 1 deletion(-) create mode 100644 03-Architecture-Hardware/2026-08-22_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md create mode 100644 03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md create mode 100644 03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-1_NTRIP-Firmware-Spec_v1.0.md create mode 100644 03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-2_Schematic-Delta_v1.0.md create mode 100644 03-Architecture-Hardware/2026-08-27_Drogheda_KiCad-Submodule-Migration_v1.0.md create mode 100644 03-Architecture-Hardware/2026-08-27_Drogheda_v6-Antenna-Chain_Diagram.html create mode 100644 scripts/2026-08-25_v6_T6_rf_block.py diff --git a/03-Architecture-Hardware/2026-08-22_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md b/03-Architecture-Hardware/2026-08-22_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md new file mode 100644 index 0000000..c511575 --- /dev/null +++ b/03-Architecture-Hardware/2026-08-22_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md @@ -0,0 +1,63 @@ +# EinarBoard v5.5 — Auditoría EMC y fundamentos de layout (2026-08-22) + +Contra el `.kicad_pcb` enviado a fabricar (15 pzas). Objetivo: decidir qué se corrige **antes de +la segunda corrida** y qué pasa al diseño v6 (ZED-F9P + 4G on-board). Stackup: F.Cu señal+pour +GND · In1 plano GND · In2 plano 3V3 · B.Cu señal+pour GND — topología correcta de 4 capas. ✔ + +## 🔴 Crítico — corregir ANTES de la segunda corrida + +**1. `/SW_BAT` (nodo de conmutación del LTC4040) es una sola pista de 0.3 mm × ~15 mm.** +U6 pines SW → L6 (inductor 2.2 µH/8 A) va todo en 0.3 mm por F.Cu. En modo respaldo (boost desde +batería) ese nodo lleva la corriente completa del inductor: con 5 V/2.5 A de carga desde 3.7 V son +**~4 A promedio** — una pista de 0.3 mm/1 oz se calienta decenas de °C y tira tensión. El +datasheet del LTC4040 pide SW ancho y corto. *En la corrida actual:* funcionará en respaldo ligero; +vigilar en banco el caso "apagón + ráfaga TX del 4G". *Corrección:* polígono o pista ≥2 mm. + +**2. `/BAT_PLUS` — los últimos ~20 mm hacia U6/C35 quedan en 0.3–0.5 mm.** +El tramo largo P4→(columna x≈193) va bien (1.0 mm en B.Cu + paralelo In2), pero la aproximación +final a los pads BAT es una cadena de 0.3 mm. Misma corriente que SW_BAT (2.4 A carga / ~4 A +descarga). *Corrección:* ensanchar a ≥1.5 mm + vías múltiples en el último tramo. + +## 🟠 Importante — corregir en la segunda corrida si se puede, obligatorio en v6 + +**3. Lazo de retroalimentación del buck del ventilador (U2):** el divisor R20/R21 está a ~25 mm +del pin FB y la pista `/FB_FAN` recorre **60.7 mm**, con un punto a ~0 mm del cobre de `/SW_FAN` +(no es corto — DRC limpio — pero es acoplamiento directo del nodo ruidoso al nodo de alta +impedancia). Riesgo: jitter/rizado en 5V_FAN y EMI radiada. *Corrección:* R20/R21 pegados al pin 4, +FB corto y lejos de SW. (U7 y U12 están decentes: FB 8–9 mm, separación ≥2 mm.) + +**4. Lazos calientes de entrada de los bucks:** Cin más cercano a 5–8.5 mm del pin VIN +(U2→C11 6.3 mm · U7→C40 5.1 mm · U12→C101 8.5 mm). Regla: <3 mm. *Corrección:* un 1 µF+10 µF +pegados a VIN-GND de cada MP1584. + +**5. ESD del puerto DFU lejos del conector:** D14/D15/D16 (PESD) están a ~41 mm de J11, junto al +MCU. Clampean, pero el tramo conector→clamp radia durante la descarga. *v6:* moverlos a <10 mm +de J11. **J9 (USB del módem) no tiene ESD** — es arnés interno, aceptable, pero añadirlo en v6. + +## 🟡 Menor — anotar para v6 + +- **Cristales lejos de sus chips:** Y3 HSE a 11.9 mm de U16, Y4 LSE a 15.2 mm, Y1 a 12.3 mm de + U11. El plano GND de In1 blinda lo que cruza por capas inferiores (verificado: los cruces de + /KCOIL, /I2C, /MAIN5V van bajo el plano), así que funciona — pero en v6 pegarlos a <5 mm y en + paquetes 3225 en vez de HC-49. +- **Par ETH RX desbalanceado:** RXP 16.1 mm vs RXN 23.6 mm (Δ7.5 mm). 100BASE-TX lo tolera de + sobra funcionalmente; en v6 rutear como par acoplado y igualar. +- Celdas de 20 mm sin vía de costura: 8 (mayoría bajo el zócalo del Ardusimple). Densidad global + buena: 165 vías GND, mediana vecino-más-cercano 4.6 mm. ✔ +- Stub único: cola de 5.6 mm de /3V3 en In2 — es el propio plano de 3V3, inofensivo. + +## ✔ Verificado y sano + +Desacoplos: 12 caps ≤4 mm de su pin; los "lejanos" (C78/C30/C10) son bulk de entrada junto a los +conectores — colocación correcta. Pares USB FS: J9 Δ1.5 mm, J11 ok — Full-Speed lo permite. +Protección 24 V: D10 (SMBJ33A) a 9.9 mm de P1 y Q10 a 11.7 mm — bien. Anchos de potencia +restantes: /SW_5V 1.0 mm continuo, /24V_IN 1.0 mm, /FAN_RET 1.0 mm ✔ (los mínimos de 0.25 en +VEH5V/MAIN5V/3V3 son taps de sensado, no portadores). Zona keepout bajo el magjack presente ✔. +Costura GND y planos: correctos. + +## Para el arranque de v6 (ZED-F9P + 4G on-board) + +Heredar de esta lista: SW/BAT anchos desde el día uno, Cin en el pin, FB corto, cristales 3225 +pegados, ESD en cada conector externo, pares diferenciales acoplados (el F9P añade USB y el 4G +añade USB/UART de alta velocidad + **RF**: la antena LTE exige keepout de cobre, 50 Ω controlada +y tierra sólida — eso define el stackup antes de colocar nada). diff --git a/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md b/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md new file mode 100644 index 0000000..2077cd7 --- /dev/null +++ b/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md @@ -0,0 +1,657 @@ +--- +title: EinarBoard v6 — On-Board GNSS + 4G Integration — Design Brief & Execution Prompt +tags: [project-drogheda, hardware, einarboard, v6, gnss, rtk, 4g, lte, rf, design-brief, prompt] +project: Project Drogheda +author: Helgen CTO + Claude (decisions opened 2026-08-24) +date: 2026-08-24 +status: Draft v1.0 — planning. §0 decisions locked by Leo 24-Ago; §1 GNSS choice is the one open gate. +authority: supplements [[watchdog-electrical-design-state]] (canonical) and inherits the fix list of + `2026-08-22_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md` in full. Where this conflicts with the 3.0 + brief on v6 scope, THIS file wins. +--- + +# EinarBoard v6 — Design Brief ("the prompt") + +> **What this is.** The instruction set for turning `hbox-watchdog-v5` (EinarBoard 3.0 / v5.5, +> 15 pcs in fab since 22-Ago) into **EinarBoard v6** — the first HBOX board with the **GNSS +> receiver and the 4G modem soldered on**, retiring the simpleRTK2B Pro shield (MOD1) and the +> XBee-socket 4G NTRIP Master (J7/J8). +> +> Standing rules unchanged: **file-level KiCad edits only**; git checkpoint after every phase; +> every structural edit verified by an independent parse; bench gates before fab. + +--- + +## 0. Decisions locked 2026-08-24 (Leo) + +| Topic | Decision | +|---|---| +| Scope | **Full integration** — GNSS + 4G both on-board; MOD1 and J7/J8 deleted | +| Board outline | **Keep 140.000 × 100.000 mm** (x ∈ [60.19, 200.19], y ∈ [40.32, 140.32]) — no enclosure rework | +| SIM | **MFF2 soldered SIM** (DFN 5×6). No tray, no spring contacts, no opening a sealed IP67 box in Veracruz | +| **GNSS receiver** | 🔒 **LOCKED — ZED-F9P-04B, LCSC `C5120257`** (Leo, 24-Ago). Rationale §1. LG290P stays as the documented v7 cost-down | +| **4G module** | 🔒 **LOCKED — SIMCom A7670SA-LASC, LCSC `C18548276`** (Leo, 24-Ago). LTE Cat-1 + **2G GSM** fallback; 3G answered in §2.3 | +| Inherited | The **entire v5.5 EMC fix list** is day-one scope, not a wish list (§7) | + +**Still open — need Leo:** antenna interface (§4.4), modem VBAT source sign-off (§3.2), +homologación path (§9.4), and gate **T1** (§2.4) — which does **not** block the schematic, only +the decision to stay on the A7670SA if the cabinet runs hot. + +--- + +## 1. GNSS receiver — "cheapest" vs "best value" are not the same part + +### 1.1 The hard constraint that eliminates the truly cheap tier + +Your base is the Ardusimple **AS-PRO-CORS = ZED-F9P = L1/L2**, and Mexican public CORS (INEGI +RGNA) are overwhelmingly **GPS L1/L2 + GLONASS**. RTK requires the rover to track the same +frequencies the base broadcasts. + +That **disqualifies the entire cheap L1/L5 tier**: Quectel LC29H (all variants, $22–33), +ZED-F9P-**15B**, NEO-F9P, LG69T. They are L1 + L5, **no L2**. Fed from an L1/L2 base they +degrade to effectively single-frequency — you would be paying for RTK silicon and getting +float. Do not let the $22 LC29H price tempt anyone: it is the right part for a product where +you also own the base, and the wrong part for a fleet you diagnose remotely from Querétaro. + +Also permanently out: ATGM332D / ATGM336H / AT6668 ($1.44–4.75, huge LCSC stock) — **single-band +L1, no carrier-phase RTK at all.** + +### 1.2 The three that survive + +| | **ZED-F9P-04B** | **LG290P03AAMD** | **UM980** | +|---|---|---|---| +| Vendor | u-blox (CH) | Quectel (CN) | Unicore (CN) | +| Bands | L1/L2 | **L1/L2/L5/E6** | all incl. E6/B2b | +| RTK rate / convergence | 7–24 Hz / <12 s | 20 Hz / **5 s** | 50 Hz / <5 s | +| Package | 54-LGA 22×17×2.4 | **12.2×16×2.6** | 54-LGA 22×17×2.6 | +| Supply / current | 2.7–3.6 V, 85 mA trk (130 pk) | 3.15–3.45 V, **91 mA** | 3.0–3.6 V, **145–180 mA / 480 mW** | +| Internal LNA | **No — active antenna mandatory** | ambiguous (see §1.5) | **No** | +| USB | **Yes** | **No** | No | +| qty 1 | **$84.82 LCSC** (C5120257) / $131.74 Mouser | $79.89 | ~$94.88 | +| qty 100 | $116.56 Mouser | **$59.54** | ~$80.65 | +| JLCPCB | C5120257, Extended, 104–129 stk | **not listed** — consign | C9900015354, Extended | + +### 1.3 The money, honestly + +| Volume | ZED-F9P (LCSC) | LG290P | Saving by switching | +|---|---|---|---| +| 30 units | $2,545 | ~$2,100 | **~$450** | +| 100 units | $8,482 (if stock holds) | $5,954 | ~$2,500 | +| 300 units | ~$34,968 (Mouser $116.56) | $17,862 | **~$17,100** | + +Against that saving, switching away from u-blox costs you, **once**: + +- **The entire config toolchain.** u-center, UBX-CFG-VALSET, your saved receiver configs, your + RAM-vs-BBR/Flash lesson from the June RCA — all of it is u-blox-specific. LG290P configures + with `$PQTM…` sentences. +- **The backend decoder.** htracker already ingests `$PUBX,00` (retranslator +138, delivered + 10-Ago). LG290P does not emit PUBX. That's a protocol change on the server too. +- **A fresh RTCM-compatibility unknown**, in a program whose *actual* June root cause (free + rtk2go caster, RAM-only config, unverified UART2 path) is still not fully closed. + +**Read: at 30 units the saving is $450 — less than a week of firmware and backend work. At +300 units it is $17k — real money.** + +### 1.4 🔒 DECIDED 24-Ago — ZED-F9P-04B + +> **v6 = ZED-F9P-04B, sourced from LCSC (C5120257) at $84.82.** +> It is $32/unit cheaper than Mouser with *zero* engineering change, it is the receiver your +> base station is paired to, and it keeps u-center as your field-diagnosis tool while you are +> simultaneously taking on an RF front end, a cellular radio, and an NTRIP client you've never +> shipped. **One new unknown at a time.** +> +> **Documented cost-down for the 300-unit phase = LG290P.** Scope it as a bounded respin of the +> GNSS island only (§5 puts the receiver in its own corner precisely so this stays cheap), plus +> the firmware/backend work, and pull the trigger when volume pays for it. + +⚠ **Sourcing caveat that applies either way:** LCSC shows ~104–129 pcs of ZED-F9P-04B. A +100-unit run eats the entire bin. Mouser has 573 + 834 arriving, 18-week factory line. **For a +30–100 unit run, buy the receivers yourself and consign them to JLCPCB** — you control the most +expensive, longest-lead line item instead of being hostage to an LCSC bin. + +*(If you want the LG290P now anyway, say so and I'll re-baseline §5/§6 — but then Phase 1 in +§10 grows a receiver-bring-up spike and the backend decoder becomes v6 scope.)* + +### 1.5 Open verification if LG290P is ever chosen + +Its datasheet says "external active antenna"; Mouser's part description says "**w/ LNA +(KT5030)**". These cannot both drive the RF section. **Resolve against the LG290P Hardware +Design doc before laying out one millimetre of RF.** + +--- + +## 2. 4G modem + +### 2.1 Bands decide the variant before anything else + +Telcel = **B4** (bulk) + B7 + B66. AT&T MX = B2/B4/B5/B7. Altán "Red Compartida" = **B28** +(700 MHz — the best-propagating band in the country). Movistar rides AT&T's RAN. + +→ You need **B2/B4/B5/B7/B28/B66**. That is exactly the **`-SA`** (SIMCom), **`-LA`** (Quectel +EG915x) and **`-AU`** (Quectel EC200x) suffixes. **Any `-EU` / `-E` / `-CN` / `-C` part is +useless here** — no B4, no B66. This is the single most common way this decision goes wrong. + +### 2.2 🔒 DECIDED 24-Ago — SIMCom A7670SA-LASC + +> **SIMCom A7670SA-LASC — LCSC C18548276, $7.58 @1, $7.39 @10, ~$6.3–6.9 @1k, 220 in stock, +> already in the JLCPCB parts library** (Extended, X-ray inspection required, MSL3). + +Why it wins on your criteria: + +- **Cheapest thing that is actually assemblable at JLCPCB.** The only real alternative in the + JLC library is Quectel EG915U-LA (C19185632) at ~$9–13. +- **Correct LatAm bands**: LTE-FDD B1/2/3/4/5/7/8/**28**/**66**, plus GSM 900/1800. +- **Single antenna.** Cat-1bis-class: one `RF_ANT` pin, no RX-diversity port. One less 50 Ω + path, one less antenna, one less thing to get wrong. (This is also why **Cat-4 is the wrong + choice** — EC200A/SIM7600 add a second antenna port, a 29×32 mm footprint and 2–4× the price + for throughput you will never use on a 50–200 MB/month NTRIP + NMEA stream.) +- **Public docs, no NDA**, and the A76xx AT set is SIM7600-compatible → **TinyGSM works**, which + is what makes the §2.5 firmware estimate credible. +- **Feature-variant upgrade path with no respin**: `-LASC` (bare) → `-LASE` (+BLE) → `-FASE` + (+GNSS +BLE) are the same 88-LGA footprint. + +**Not interchangeable, do not confuse:** A7670 = 88-LGA · A7672 = 124 hybrid LCC+LGA · +SIM7070 = 68-LCC. All 24×24 mm, all different footprints. Migration = respin. + +### 2.3 On your 3G question — the honest answer + +**3G is not the lever you want, and it is on its way out.** Mexico's 2G/3G shutdown is still +under negotiation rather than executed (operators were still discussing it in July 2026 — +millions of handsets block it), so 3G *exists* today. But designing a 2026 board with a 5–10 +year fleet life around a network the regulator and all three carriers are actively trying to +switch off is buying a fallback with an expiry date. + +What actually fixes poor coverage, in order of effect: + +1. **Band coverage at low frequency.** B28 (700 MHz) and B5 (850 MHz) penetrate buildings and + container stacks far better than B4/B66 (1700/2100 MHz). The A7670SA has all of them. +2. **Cat-1bis link budget.** A narrower, lower-order category has a better link budget than + Cat-4 at the cell edge. The cheap choice is also the more robust one here. +3. **A proper external antenna on a bulkhead SMA**, not a chip antenna inside a metal-adjacent + cabinet. §4.4. +4. **2G/GSM fallback** — which the A7670SA already has (GSM 900/1800). At 20–40 kbps GPRS you + will not hold RTCM at full rate, but you keep telemetry and remote command alive, which is + what "never vanish" (invariant #2) actually requires. + +**If you still want WCDMA/3G**, the cheap route is **SIM7600SA-H** (Cat-4 + WCDMA + GSM, LatAm +bands) at ~$25–40 — but you pay ~4× the module cost, a second antenna port, a 29×32 mm +footprint, and more heat, for a network that is being switched off. **My recommendation is no.** +Take the A7670SA's 2G fallback and spend the difference on the antenna. + +### 2.4 Temperature — the one place I'd push back on "cheapest" + +Your own RCA names the 4G modem as a heat casualty in the nested cabinet under Veracruz sun. + +| Part | Operating range | +|---|---| +| SIMCom A7670SA | **−30…+80 °C** (−40…+85 degraded) | +| Quectel EG915U-LA | **−35…+75 °C** (−40…+85 extended) | +| Telit LE910C1-LA (~$48) | **−40…+85 °C full** | +| u-blox LARA-R6001 (~$43–55) | **−40…+85 °C full** | + +> 🔴 **GATE T1 — cabinet temperature. YOU ALREADY HAVE THIS DATA. DO NOT INSTRUMENT ANYTHING.** +> +> Fielded V3 units already report **P12 = 10 k NTC → PB1 → `tntc` ("Cámara" = cabinet +> interior)** and **P5 = SHT40 → `tair`**, inside `$PHLG` every 30 s, and the htracker +> retranslator has been ingesting `$PHLG` in production since 10-Ago. **Query the existing +> telemetry history for the max `tntc` per unit over the hottest days.** +> +> That query — free, today, no hardware, no truck roll — settles the single largest cost +> decision in v6: an **$8** A7670SA versus a **$48** Telit LE910C1-LA. Across 300 units that +> is a **$12,000** decision sitting in a database you already own. +> +> Decision rule: **max `tntc` < 70 °C → A7670SA.** 70–75 °C → A7670SA **plus** the M2.5 +> heatsink clamp (§5) and re-measure on the first article. **> 75 °C → escalate the tier**; +> you already know what a modem failure costs at ICAVE. + +### 2.5 What the modem costs you in firmware + +**Neither the A7670SA nor any Quectel module has a built-in NTRIP client.** That was the +Ardusimple 4G NTRIP Master's entire value, and you are giving it up. You implement NTRIP v1 +yourself: `GET /MOUNTPOINT HTTP/1.0` + `Authorization: Basic …`, then read raw RTCM3 off a +transparent TCP socket and shovel it into the receiver. + +Realistic budget: **1–2 weeks**, not 1 day — the AT/socket layer is a day; reconnect/backoff +logic, watchdog integration, TLS-on/TLS-off caster handling, signal-quality telemetry into +`$PHLG`, and field failure modes are the rest. + +> ✅ **This risk can be retired for free, right now.** See §10 Phase 1: the **XBee socket on the +> v5.5 boards currently in fab** will host an A7670SA breakout. You can prove the entire +> NTRIP-in-STM32 stack on hardware you already paid for, while the boards are in transit, +> before a single v6 trace exists. + +--- + +## 3. Power architecture — the biggest new electrical work + +### 3.1 What the modem demands + +| Parameter | A7670SA | Consequence | +|---|---|---| +| VBAT | 3.4–4.2 V (typ 3.8) | **Not the 3V3 rail. Ever.** | +| Peak current | **2.8 A** (GSM TX burst) | | +| Droop limit | **≤300 mV** at the burst | | +| Bulk | **≥300 µF** if the source truly delivers 2.8 A; **≥1000 µF** if not | | +| VBAT trace | **≥3 mm** wide or equivalent pour | | +| VBAT pins | **All three (55/56/57) must be connected** | RF performance depends on it | +| I/O rail | **1.8 V** | **Level shifting to the 3.3 V STM32 is mandatory** | +| VDD_1V8 | output, 50 mA max | level-shift reference only, not a system rail | + +### 3.2 🔴 The 10-Ago fleet boot-loop makes this decision for us + +**Read the 10/11-Ago incident before designing this rail.** Root cause on record: a flat 12 Ah +pack demanding 2.42 A **plus the LTE attach burst** exceeded the **SD-15B-05's 3 A**, while the +**LTC4040 input limit (3.12 A) is set ABOVE the supply's rating** → rail sagged to the PFI +4.60 V trip → failover to a flat pack → brownout → reboot → loop. The modem stayed "invisible" +because it never finished its 30–60 s LTE attach inside the loop. Standing HW errata: *"input-limit +resistor must come DOWN below PSU rating (or bigger PSU)."* + +**v6 adds ~1.6 A of on-board modem to that same 5 V rail.** Naively bucking 3.8 V off MAIN5V +would aim the new radio straight at the failure mode that already took the fleet down. + +| | (a) Direct from the 1S pack | (b) Buck from MAIN5V | (c) **Buck from OR(24V_IN, MAIN5V)** ← recommended | +|---|---|---|---| +| Load on the SD-15B 3 A budget, normal op | none | 🔴 **+1.6 A — the 10-Ago failure mode** | **none — runs off 24 V** | +| Alive during blackout | ✔ | ✔ | ✔ (falls back to MAIN5V ← pack ← LTC4040) | +| Bench without a pack | ✘ | ✔ | ✔ | +| Under-voltage risk | 🔴 pack sags below the 3.4 V modem floor | ✔ | ✔ | +| Couples TX bursts onto | 🔴 the LTC4040 BAT node — flagged 🔴 in the EMC audit | the 5 V rail | 24 V (normal) / 5 V (blackout only) | + +**Recommendation (c) — one buck, two inputs.** A **4.5–40 V input, 3 A synchronous buck** (e.g. +MP4560 / AP64350 / TPS54331 class — *not* a fourth MP1584, which is already the source of EMC +findings 3 and 4) whose input is an **ideal-diode / PMOS OR of `24V_IN` and `MAIN5V`**, output +3.8 V, feeding the modem through a **P-FET load switch under MCU control (`MODEM_PWR_EN`)**. + +Why this is the robust *and* cheap answer, for roughly **$1.50 more** than option (b): +- With the cabinet live, the modem's ~9.5 W comes off **24 V at ~0.45 A** — the SD-15B never + sees it, and the 10-Ago brownout path is structurally removed rather than merely re-tuned. +- In blackout the same buck accepts MAIN5V and the radio keeps reporting — invariant #2 holds. +- One converter, one inductor, one BOM line. D10 (SMBJ33A) already clamps the 24 V input. +- `MODEM_PWR_EN` gives the watchdog authority to hard power-cycle a wedged radio — invariant #3. + +> ⚠ **Still fix the input-limit errata.** Even with the modem off the 5 V rail, the LTC4040's +> input limit must come below the PSU rating (or the PSU must grow). v6 is the board where that +> gets corrected, not deferred again. + +> ⚠ **And this makes the v5.5 🔴 EMC findings load-bearing.** In blackout MAIN5V does feed the +> modem, so the LTC4040 boost hits the ~4 A `/SW_BAT` case the EMC audit flagged. **`/SW_BAT` +> ≥2 mm polygon and `/BAT_PLUS` ≥1.5 mm + via farm are v6 hard requirements, not second-run +> nice-to-haves.** + +### 3.3 GNSS rail — low-noise, non-negotiable + +The F9P and UM980 have **no internal SAW**. This board carries **three MP1584 switchers**. +They will happily lock onto your buck's harmonics. + +→ **Dedicated low-noise LDO** off 3V3 for the GNSS island (not a buck, not a shared tap), +placed on the GNSS island itself. Verify the **ZED-F9P VCC ramp rate ≤ 20 µs/V** — that is a +*damage* limit, not a guideline; check it against the LDO's soft-start. + +### 3.4 Budget delta (worst case, backup mode) + +| Load | Was (v5.5) | v6 | +|---|---|---| +| 4G | via 3V3_MODEM to XBee socket, ~1.5 A pk @3.3 V | **2.0 A pk @3.8 V ≈ 7.6 W → ~1.6 A from MAIN5V** | +| GNSS | via MOD1 shield off 5 V | 85–130 mA @3.3 V (F9P) | +| Everything else | unchanged | unchanged | + +Recompute the LTC4040 backup runtime against the 1S4P ~12 Ah pack once (b) is locked. + +--- + +## 4. RF — the part that decides the stackup before anything is placed + +### 4.1 Stackup + +Current topology is already correct and stays: **F.Cu signal+GND pour · In1 solid GND · +In2 3V3 plane · B.Cu signal+GND pour.** + +New requirement: **order the board as controlled impedance** (JLCPCB 4-layer, JLC04161H-7628; +F.Cu→In1 prepreg ≈ 0.2104 mm). Target **50 Ω CPWG on F.Cu referenced to In1** — roughly +0.35 mm trace / 0.25 mm gap at εr ≈ 4.4, **to be confirmed against JLCPCB's own impedance +calculator before layout**, not after. + +Absolute rules for both RF traces: +- Referenced to the **In1 GND plane with no split, ever** — no plane cut, no via field gap, no + In2 3V3 island underneath. +- As short and straight as possible; ground-stitched along **both** sides at ≤ λ/20. +- No other signal crosses them on any layer. + +### 4.2 GNSS RF chain + +Active antenna (mandatory for F9P/UM980) → bias tee: **≥68 nH 0603 inductor** (self-resonance +above 1.6 GHz) + DC block on the RF path + 100 nF ∥ 100 pF on the bias node + **ESD diode rated +>2 GHz** at the connector. + +Wire **`ANT_DETECT` and `ANT_SHORT_N`** to the MCU (or a spare PCF8574 pin). Free antenna-fault +telemetry — and you have already lost two units (H0005, H0014) to 0-sats antenna faults you +could only diagnose by reading logs. + +⚠ **Do not use `VCC_RF` as the antenna bias on UM980** — Unicore explicitly says its protection +is inadequate. Feed the antenna from a separate current-limited rail. + +### 4.3 🔴 The #1 technical risk in this whole change: GNSS desense + +A 23 dBm LTE transmitter drawing 2–3 A bursts, 3 cm from a GNSS front end, is a classic +RTK-killer — LNA compression and broadband noise will cost you exactly the fix you are paying +for. **The Ardusimple shield has been hiding this problem from you. A bare module hands it to +you.** + +Mitigations, in order of effect: + +1. **Maximum board separation** — §5 puts GNSS in the bottom-left corner and LTE at the right + edge: **~149 mm apart, essentially the full board diagonal.** This is the whole reason the + floorplan is shaped the way it is. +2. **Antenna separation ≥ 20 cm** on the enclosure. **Treat this as a design rule, not an + optimization.** Two separate bulkheads, as far apart as the cabinet allows. +3. Route the modem's return path away from the GNSS island; no shared reference between the + two RF regions except the solid In1 plane. +4. **Front-end filtering.** Straightforward for an L1-only receiver; **for a multi-band receiver + a single 1575 SAW would kill L2/L5**, so rely on an active antenna with good out-of-band + rejection + separation. Specify the antenna accordingly. +5. **Measure it.** Gate G3 in §10: fix quality with the modem idle vs. transmitting at full + power. This is a pass/fail bench test, not an opinion. + +### 4.4 Antenna interface — OPEN, needs Leo + +Recommendation: **2× U.FL on board → pigtails → 2× SMA bulkhead on the enclosure**, placed as +far apart as the cabinet allows. This is what makes mitigation #2 possible; an on-board or +short-pigtail arrangement forfeits it. + +Needs from you: does the current enclosure have room for a second bulkhead, and where? That +answer changes where the U.FL connectors sit, which changes the floorplan. + +### 4.5 LTE link budget — the module is not the variable + +**Every LTE module here transmits at the same power.** 3GPP Power Class 3 is **+23 dBm ±2 dB**, +and it is a conformance requirement, not a feature — the $8 A7670SA and the $48 Telit put out +the same watt. Receive sensitivity is likewise floored by 3GPP; premium parts buy perhaps +**1–3 dB** of margin, not a different class of performance. + +What actually sets the link budget on this product, in descending order of effect: + +| Factor | Typical swing | Notes | +|---|---|---| +| **Antenna type and placement** | **10–20 dB** | An antenna inside or flush against a metal cabinet on a metal crane is the whole ballgame | +| **Band in use** | 6–10 dB | **B28 (700 MHz)** and B5 (850 MHz) penetrate structure far better than B4/B66 at 1700/2100 MHz. The A7670SA has all of them | +| **Cable / pigtail loss** | 1–3 dB | Keep the LTE pigtail short; every extra 300 mm of thin coax at 1.9 GHz is real | +| **Cat-1bis vs Cat-4** | 1–2 dB in our favour | Lower-order category = better cell-edge behaviour. The cheap choice is also the more robust one | +| **Module vendor** | **1–3 dB** | The only line the extra $40 buys | + +> **You already have the field measurement.** The `$GPTXT,NET,H00xx,TELCEL,-87,334 020` line +> the fleet emits carries the modem's **RSSI in dBm** and the operator's MCC/MNC (334/020 = +> Telcel MX). The example on record shows **−87 dBm**, which is a comfortable working level. +> **Query the fleet for the per-unit distribution over the last month, the same way as gate T1.** +> If the worst unit sits near −105 dBm you have an antenna/siting problem to solve — and no +> module in this survey would have solved it. Phase 1 also adds `csq` to `$PHLG` (field 30), +> which makes this permanently visible instead of a one-off query. + +**Conclusion: spend on the antenna and its mounting, not on the modem.** A better bulkhead +antenna, a shorter pigtail and a clear mount are worth 10 dB; a more expensive module is worth 2. + +### 4.6 🔴 GNSS multipath — the environment problem, and what actually fixes it + +A container terminal is close to a worst case: stacked steel boxes, RTG and STS cranes, +reach stackers, all of it moving. **Multipath is a receiver-and-antenna problem. No modem +choice affects it, and no cabling change fixes it.** + +**First, make sure it is actually the diagnosis.** As of 10-Ago the fleet showed **zero +corrections applied anywhere** — that produces SPS, full stop, and it looks nothing like +multipath. **Multipath presents as float-instead-of-fixed, or a fix that wanders**, and it can +only be diagnosed *after* RTCM is confirmed arriving. Phase 1's new telemetry separates the +two for the first time: + +| `rtcm_msgs` | fix quality | Diagnosis | +|---|---|---| +| 0 / not rising | 1 (SPS) | **link or caster** — not multipath | +| rising | 1 (SPS) | receiver config, or base/rover **band mismatch** | +| rising | 5 (float), never 4 | **multipath / sky obstruction** — this is the real thing | +| rising | 4 but `hAcc` noisy | multipath, partially mitigated | + +**Mitigations, cheapest and most effective first:** + +1. **A ground plane under the patch antenna.** A metal disc **≥ 100 mm diameter** beneath a + patch kills ground-bounce and is the single best return per peso in this list. Often free. +2. **Mount height and clear sky.** Highest point of the machine, away from vertical steel. + On an RTG that is the top of the gantry, not the cabinet lid. +3. **A better antenna.** A multiband antenna with good **axial ratio** and suppressed gain at + low elevation rejects reflections that arrive from below the horizon. Budget US$60–120 — + still cheaper than a receiver upgrade and more effective for this failure mode. +4. **Raise the elevation mask** in the receiver (10–15°, `CFG-NAVSPG-INFIL_MINELEV`) to discard + low-elevation reflected signals, and set the correct **dynamic model** for the machine. +5. **Keep L2.** Dual-frequency is materially better at ambiguity resolution under multipath — + another reason the L1/L5-only cheap tier was the wrong answer (§1.1). +6. **Log C/N₀ per satellite** at a problem site. A healthy sky and a reflective sky look + completely different, and it turns an argument into a measurement. + +**If — after 1–6 — multipath is still the binding constraint**, then and only then does silicon +help, and the honest options are: **ZED-X20P (+$31/unit**, same 54-LGA 22×17 footprint, so +Block N accepts it with a pin-map check, plus better interference mitigation and 55 mA instead +of 85 mA) or **Septentrio mosaic-X5** (genuinely best-in-class multipath rejection, but 3–5× +the F9P and no LCSC/JLC presence). **Do not buy either on suspicion — buy it on a C/N₀ log.** + +--- + +## 5. Floorplan on 140 × 100 — proposed + +Verified from `hbox-watchdog-v5.kicad_pcb` (228 footprints, board x ∈ [60.19, 200.19], +y ∈ [40.32, 140.32]). + +**Freed by deletion:** + +| Deleted | Ref | Position | Area recovered | +|---|---|---|---| +| simpleRTK2B Pro UNO socket | **MOD1** (32 pads) | (90.6, 129.9) | **bottom-left, ~68 × 53 mm** | +| XBee sockets, 2.0 mm | **J7, J8** (10 pads each) | (174.2, 74.8) / (196.5, 74.8) | **right edge, ~27 × 20 mm** | +| Shield / heatsink bolts | MH7, MH9, MH10, MH11 | various | reclaim as GND stitching (the EMC audit found 8 empty 20 mm stitching cells, **mostly under MOD1**) | + +**Proposed placement:** + +``` + x=60 ┌────────────────────────────────────────────────────┐ x=200 + y=40 │ [J10 RJ45] connector row: P5 P10 P2 P11 P6 P8 P12 P4 │ + │ │ + │ [U11 W5500] [U16 STM32F411] [U2/L2 fan] │ + y=70 │ [SW1 SW2 J11] Y3 Y4 [U10 SC16IS] ┌──────────┤ + │ [U14][U15] │ ★ LTE │ ← freed XBee area + │ [U13 PCF8574] │ U19 A7670SA + y=100│ │ MFF2 SIM │ + │ ┌──────────────────┐ [U7] [U6 LTC4040+L6] │ 3.8V buck + │ │ ★ GNSS ISLAND │ [U12] [P14]│ + y=140│ │ U18 + LDO + bias │ [P15][P16] [J9] [P1] │ + └──┴──[U.FL GNSS]─────┴───────────────────[U.FL LTE]┘ +``` + +- **GNSS island → the freed MOD1 area, bottom-left corner.** It is the furthest point on this + board from all four switchers (nearest: U7 at 140.0, 112.9 — ~60 mm; L6/U6 LTC4040 at + ~173, 110 — ~95 mm), and it puts the U.FL at a board edge. The island holds only the + receiver, its LDO, its decoupling and its bias tee — **nothing else**, so a future LG290P + swap (§1.4) is a contained respin. +- **LTE island → the freed XBee area at the right edge**, next to the existing power cluster + where the current already lives. The 27 × 20 mm freed is **too tight for a 24 × 24 module + + ≥300 µF bulk + buck + MFF2** — reclaim adjacent area by relocating **P14 (RGB_LED_EXT, at + 195.1, 101.4)** and shifting the **U2/L2 fan buck** north. Budget ~35 × 30 mm. +- **Resulting GNSS ↔ LTE separation ≈ 149 mm** — the full diagonal. That is the point. +- Keep **2× M2.5 holes** near the modem for a heatsink clamp (§2.4). + +--- + +## 6. Signal & connectivity delta + +### 6.1 The good news: the MCU side barely changes + +These nets already exist in `hbox-watchdog-v5.kicad_sch` and simply terminate on solder-down +silicon instead of a socket: + +`GNSS_RX` · `GNSS_TX` · `DIN_4G` · `DOUT_4G` · `MODEM_RST` · `MODEM_LED` · `3V3_MODEM` + +**No MCU pin reallocation is required for the basic paths.** USART1/2/6 are all on the F411RET6; +RS-485/RS-232 stay on the SC16IS752 over SPI. + +### 6.2 New signals needed + +| Signal | Where it goes | Note | +|---|---|---| +| `MODEM_PWRKEY` | spare **PCF8574 (U13)** pin | ⚠ **timing is vendor-specific**: SIMCom = low ≥50 ms to power on, ≥2.5 s to power off. Quectel = low ≥2 s on, ≥3 s off. **Never copy timing between vendors.** | +| `MODEM_STATUS` | spare PCF8574 pin | asserts ≈11.2 s after power-on (A7670); UART ready ≈8 s | +| `MODEM_PWR_EN` | GPIO → P-FET load switch | the hard power-cycle path (§3.2) | +| `GNSS_RESET_N` | GPIO | F9P needs a 7–13 kΩ pull-up | +| `GNSS_ANT_DETECT`, `GNSS_ANT_SHORT_N` | PCF8574 | free antenna-fault telemetry (§4.2) | +| `GNSS_TIMEPULSE` | GPIO / timer input | optional but nearly free | +| **RTCM path** | **modem UART → GNSS UART2, direct in hardware** | this is what the Ardusimple shield did internally; replicate it. MCU does the NTRIP handshake, then the RTCM3 stream flows without touching the MCU — exactly the Modo B architecture already engineered | + +> **Action:** confirm free PCF8574 pins against the v5 netlist before Phase 2. `F0_SEL`, +> `F1_SEL`, `F2_SEL`, `EXT_LED`, `MODEM_LED`, `CHRG_STAT`, `CHRG_FAULT` are already on it. + +### 6.3 Level shifting — mandatory, do not skip + +Modem UART is **1.8 V**; the STM32 is 3.3 V. Use **TXS0108E** (what Quectel recommends) or the +discrete MMBT3904 network SIMCom documents. This is not a "3.3 V is close enough" situation. + +### 6.4 MFF2 SIM + +- DFN 5×6, on the LTE island, close to the modem. +- **ESD array at the SIM lines** (ESDA6V1W5 / SMF15C class). +- A7670SA has an internal 4.7 kΩ pull-up on USIM1_DATA; **USIM2_DATA would need an external + one** — you only need SIM1. +- 1.8 V and 3.0 V auto-detect, no strapping needed. +- ⚠ **Sourcing:** you need an actual MFF2 SIM from Telcel/AT&T MX or an M2M MVNO with a Mexican + profile. **Flat-rate IoT SIMs sized for sensors (500 MB / 10 years) are an order of magnitude + too small** for 50–200 MB/month. Start this conversation early — it has a lead time and it is + not an engineering task. + +### 6.5 USB + +**J9 (USB-C) is kept** and becomes the modem's USB — recovery path if a firmware update bricks +it, plus PPP/RNDIS debugging. **Reserve VBUS/D+/D−/GND and a `USB_BOOT` test point**; USB_BOOT +must not be pulled low at power-up. Add the ESD array that v5.5 does not have (**<2 pF** on the +data lines). 90 Ω differential. + +--- + +## 7. Inherited EMC fix list — v6 hard scope + +Straight from `2026-08-22_…EMC-Audit_v1.0.md`. **None of these is optional in v6:** + +| # | Fix | v6 status | +|---|---|---| +| 🔴 1 | `/SW_BAT` → **≥2 mm polygon** (was 0.3 mm × 15 mm carrying ~4 A) | **Now load-bearing** — the modem adds ~1.6 A to MAIN5V in backup (§3.2) | +| 🔴 2 | `/BAT_PLUS` final approach → **≥1.5 mm + via farm** | same reason | +| 🟠 3 | Fan buck FB: **R20/R21 at pin 4**, `/FB_FAN` short and away from `/SW_FAN` | mandatory | +| 🟠 4 | **1 µF + 10 µF within 3 mm** of every buck VIN-GND (was 5–8.5 mm) | mandatory — and applies to the new 3.8 V buck | +| 🟠 5 | **ESD at every external connector, <10 mm** (D14/D15/D16 were 41 mm from J11) | mandatory + J9 now gets ESD it never had | +| 🟡 6 | Crystals **3225 package, <5 mm** from their ICs (Y1/Y3/Y4 are 12–15 mm away in HC-49) | do it | +| 🟡 7 | ETH RX pair **coupled and length-matched** (Δ7.5 mm today) | do it | +| 🟡 8 | Fill the 8 empty 20 mm stitching cells | free — they were mostly under MOD1, which is being deleted | + +--- + +## 8. BOM & cost delta + +**Removed per unit:** Ardusimple **4G NTRIP Master** (€112 @≥50 ≈ **$121**) + Ardusimple +**simpleRTK2B Pro** shield *(price to verify)* + MOD1 headers + J7/J8 XBee sockets. + +**Added per unit (indicative):** + +| Item | Cost | +|---|---| +| ZED-F9P-04B (LCSC C5120257) | $84.82 | +| A7670SA-LASC (LCSC C18548276) | $7.58 | +| MFF2 SIM | carrier-dependent | +| 3.8 V/3 A buck + inductor + ≥300 µF bulk | ~$2.50 | +| Low-noise LDO + GNSS decoupling | ~$0.80 | +| TXS0108E level shifter | ~$0.50 | +| 2× U.FL + 2 pigtails + 2 SMA bulkheads | ~$6 | +| RF passives, bias tee, ESD arrays | ~$2 | +| Controlled-impedance fab option | per-order, small | +| **Subtotal added** | **~$104 + SIM** | + +**The headline: the 4G side alone saves ~$113/unit** ($121 Ardusimple → $7.58 module). Add the +simpleRTK2B Pro's margin over a bare F9P on top. At 30 units that is meaningful; at 300 it is +the difference between a viable and a non-viable unit price. + +⚠ JLCPCB notes: both modules are **Extended** ($3 one-time setup each), both flagged **X-ray +inspection required**, both **Standard PCBA tier only** — no Economic assembly. A7670SA is +**MSL3** (168 h floor life ≤30 °C/60 %RH) — matters for JLC and for your own rework. + +--- + +## 9. Risk register + +| # | Risk | Severity | Mitigation | +|---|---|---|---| +| R1 | **GNSS desense from LTE TX** — you buy RTK silicon and get float | 🔴 | §4.3: 149 mm board separation, ≥20 cm antenna separation, measured at gate G3 | +| R2 | **Modem thermal** — cheap tier is −30…+80 °C, cabinet may exceed it | 🔴 | **Gate T1 (§2.4): soak a fielded unit now.** Escalate to Telit/u-blox if >75 °C | +| R3 | **NTRIP client in STM32** — new firmware on the critical path | 🟠 | **Phase 1: prove it on v5.5's XBee socket while the boards are in fab** | +| R4 | **Homologación** — the host now contains a radio (§9.4) | 🟠 | Start the lab conversation in Phase 0; it is calendar time, not engineering time | +| R5 | **Pack sag below the 3.4 V modem floor** | 🟠 | Resolved by §3.2 option (b); if (a) is ever chosen, add UV inhibit | +| R6 | **v5.5 EMC findings carried forward** | 🟠 | §7 — day-one scope | +| R7 | **F9P LCSC stock (~104–129 pcs)** | 🟡 | Consign from Mouser for anything ≥100 units | +| R8 | **First RF design in this program** | 🟡 | Controlled impedance ordered, In1 unbroken, external review of the RF section before fab | + +### 9.4 Homologación — start this early + +The regulator changed: **IFT was dissolved 17 Oct 2025 and replaced by the Comisión Reguladora +de Telecomunicaciones (CRT)** from 21 Oct 2025. Existing IFT certificates remain valid and +labels still carry the IFT number; procedures continue under the same technical norms. + +The certificate is issued to **your product** (applicant + brand + model + year), not to the +module. Modular relief exists in practice — the host is labelled *"Este producto contiene el +módulo aprobado modelo XXXX, IFT No. …"* — but there is **no blanket exemption**. Applicable: +**IFT-008** (cellular) and **NOM-208 / NOM-221**. Testing in an accredited Mexican lab or a US +MRA-recognized lab, ~3–5 weeks. + +Two consequences that bite: +1. **Freeze on one module before you file.** Switching modules later = new or amended certificate. +2. Sources **disagree** on whether the certificate holder must be the importer. **Get this pinned + down by the lab, in writing, in Phase 0.** + +None of the cheap LatAm modules advertises an IFT/CRT certificate. **Do not assume one exists +because the datasheet says "Latin America."** A7670SA does carry FCC + Anatel, which shortens +the RF test scope without eliminating the filing. + +Treat it as one fixed cost amortized over hundreds of units — which is an argument *for* the +cheap module, not against it. + +--- + +## 10. Phases & gates + +| Phase | Work | Gate | +|---|---|---| +| **−1** — 🔴 **before any of this** | **Fix the base / caster.** 10-Ago fleet truth: *zero corrections applied anywhere* → check `HelgenTestStation` @ rtk2go:2101. **No silicon in this brief produces RTK if the correction source is broken.** v6 would ship five units of new hardware into the same SPS-not-RTK result | **G0: at least one fielded unit holding GGA quality 4/5 for 1 h on the existing hardware** | +| **0** — now, parallel | **Run the T1 telemetry query (§2.4) — it is free and it decides the modem tier** · lock §1.4 GNSS · lock §3.2 rail · answer §4.4 antenna · **call a homologación lab** · order A7670SA breakout + M2M SIM (~$40 total) | Decisions in this doc, v1.1 | +| **1** — while v5.5 is in fab | **NTRIP-in-STM32 spike on a v5.5 board via the XBee socket** + A7670SA breakout: AT driver, TCP, NTRIP v1 GET, RTCM3 → GNSS UART2, reconnect/backoff, `$PHLG` signal telemetry | **G1: 24 h continuous RTK fix through the on-board modem path, ≥1 reconnect survived** | +| **2** — schematic | New blocks (GNSS island, LTE island, 3.8 V rail, MFF2, level shift, RF chains) + **all of §7** | **G2: ERC 0** + external review of the RF and power sections | +| **3** — stackup & floorplan | Confirm 50 Ω geometry against JLCPCB's calculator · place per §5 · verify GNSS↔LTE separation · confirm the enclosure takes a second bulkhead | **G3 (design): RF traces referenced to unbroken In1, no exceptions** | +| **4** — route | RF first, then power (wide from day one), then signal · zones · DRC | DRC 0 · independent netlist parse | +| **5** — DFM | BOM/CPL, LCSC pass, consignment plan, controlled-impedance order option | fab release | +| **6** — first article | Bring-up, DFU, **then G3 measured**: fix quality with modem idle vs. TX at full power | **G3 (bench): no measurable degradation in fix quality or C/N₀ under LTE TX** | + +**Phase 1 is the whole trick.** It converts the largest unknown in v6 — firmware you have never +shipped talking to a radio you have never soldered — into a bench result you obtain on hardware +already paid for, weeks before committing a layout. + +--- + +## 11. What I'd argue for, in one paragraph + +Take the **A7670SA** — it is the cheapest part that is simultaneously band-correct, JLC-assemblable, +2G-capable and well-documented, and the money you save there is real ($113/unit against the +Ardusimple). But **keep the ZED-F9P for v6**. The $450 you would save at 30 units by switching +to an LG290P does not pay for a new configuration protocol, a new backend decoder, and a fresh +RTCM-compatibility unknown — in the same cycle where you are already taking on your first RF +front end, your first cellular radio and your first NTRIP client. Do the receiver cost-down as +a contained v7 respin of one corner of the board, once volume makes it worth $17k. **One new +unknown at a time** is the rule that gets this board to work on the first article. + +--- + +## 12. Cross-references + +- v5.5 EMC fix list: `2026-08-22_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md` +- Prior modem survey (bench-era): `2026-07-08_Drogheda_4G-Modem-Alternatives_v1.0.md` +- 3.0 brief & method rules: `2026-08-12_Drogheda_EinarBoard-3.0_MCU-Integration_Design-Brief_v1.0.md` +- Canonical electrical state: `watchdog-electrical-design-state.md` +- Invariants touched: **#1** power-off safe · **#2** never vanish (§2.3, §3.2) · **#3** + corrections ride the 4G, recover by power-cycling (§3.2 `MODEM_PWR_EN`) diff --git a/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-1_NTRIP-Firmware-Spec_v1.0.md b/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-1_NTRIP-Firmware-Spec_v1.0.md new file mode 100644 index 0000000..447b8c0 --- /dev/null +++ b/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-1_NTRIP-Firmware-Spec_v1.0.md @@ -0,0 +1,337 @@ +--- +title: EinarBoard v6 — Phase 1 — NTRIP Client in the STM32 — Bench Spec & Execution Prompt +tags: [project-drogheda, firmware, v6, ntrip, a7670sa, 4g, rtcm, bench, design-brief, prompt] +project: Project Drogheda +author: Helgen CTO + Claude +date: 2026-08-24 +status: Active — THE spec for the Phase 1 bench spike. Execute before any v6 layout exists. +authority: 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) + +> **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: + +1. **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 out `SW_5V` and reproduce the 10-Ago boot loop on the bench. +2. **The UART nets collide.** `/USART1_RX` (PA10) carries **three** loads: `U16.43`, + `K1.1` (relay NC) and `MOD1.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_RX` to `/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 F411**CE** +> (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_TX` needs 3.3 → 1.8; `MODEM_RX` and +> `MODEM_STATUS` need 1.8 → 3.3 (the STM32's V_IH ≈ 2.31 V — **1.8 V will not read as HIGH**). +> `PWRKEY` and `RESET` have 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: + +```c +#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 + +1. **No `delay()`. No blocking reads.** Everything is a millis()-timed state machine serviced + from `loop()`, like `chargeGovernor()` and `modoBService()`. The IWDG is on + (`ENABLE_IWDG 1`) — a blocking AT wait will reset the board. +2. **No `String`, no heap.** Fixed buffers, `snprintf`, exactly as `buildPHLG()` does. +3. **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. +4. **Secrets never enter git** (invariant #6). APN and caster credentials come from + `config/ntrip.local.h` (git-ignored) or `-D` build flags. Ship + `config/ntrip.example.h` alongside the existing `4G_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",""` · `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","",` | 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 / HTTP/1.0\r\n +User-Agent: NTRIP HelgenHBOX/3.1\r\n +Authorization: Basic \r\n +Accept: */*\r\n +Connection: close\r\n +\r\n +``` + +- Accept **either** `ICY 200 OK` (NTRIP v1) or `HTTP/1.0 200 OK` / `HTTP/1.1 200 OK`. +- `401` → **do not retry blind.** Latch `NTRIP_AUTH_FAIL`, emit an `EVT`, and back off to the + 300 s cap. Hammering a caster on bad credentials gets you IP-banned — and rtk2go does ban. +- `SOURCETABLE 200 OK` means **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, `$PHLG` answers 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 · return `401` · return `SOURCETABLE` · 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_CYCLE` fires on the 3rd fruitless backoff and **not** before; `$PHLG` stays ≤ 160 bytes + with all 31 fields; the XOR checksum is right; **no path ever calls `delay()`**. +- `run.sh` builds and runs both `ENABLE_NTRIP_CLIENT` variants, as it already does for `modob`. + +> 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:** + +1. **24 h continuous**, `fixQuality ≥ 4` (RTK-fixed or float) for **≥ 95 %** of samples. +2. **≥ 1 forced reconnect survived** — pull the LTE antenna for 60 s, restore. RTK must return + **without human intervention** and `nret` must increment. +3. **≥ 1 `HARD_CYCLE` exercised** — hold the modem in a fruitless state 3 backoffs running; + confirm the rail drops ≥2 s and the unit recovers. +4. **`rtcm_msgs` monotonically increasing** and the message-type bitmap shows the expected + MSM sets for the base in use. +5. **VBAT droop < 300 mV** at LTE attach, scope-captured (§1.2), with the capture archived to + `06-Testing-Validation/`. +6. **No IWDG reset** across the whole run (`g_resetCause` stays as booted). +7. 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: + +1. **Remove the simpleRTK2B Pro shield** from MOD1 — otherwise the F9P's TX2 contends with the + modem on `/USART1_RX`. +2. **Energise K1** (`PIN_BYP_CTRL` HIGH) so COM–NO is closed and `/DIN_4G` is isolated from + `/USART1_RX`. +3. 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_4G` is available at **J7 pin 2**. +4. The modem still runs from its **own** 4.0 V supply. `3V3_MODEM` stays 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 / `$PHLG` decision: `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) diff --git a/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-2_Schematic-Delta_v1.0.md b/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-2_Schematic-Delta_v1.0.md new file mode 100644 index 0000000..8af2596 --- /dev/null +++ b/03-Architecture-Hardware/2026-08-24_Drogheda_EinarBoard-v6_Phase-2_Schematic-Delta_v1.0.md @@ -0,0 +1,477 @@ +--- +title: EinarBoard v6 — Phase 2 — Schematic Delta (v5 → v6) — Build Spec +tags: [project-drogheda, hardware, einarboard, v6, schematic, kicad, gnss, 4g, rf, delta] +project: Project Drogheda +author: Helgen CTO + Claude +date: 2026-08-24 +status: Active — THE schematic spec for v6. Every net/pin below is verified against the + as-drawn `hbox-watchdog-v5.kicad_pcb` netlist (extracted 2026-08-24), not assumed. +authority: implements Phase 2 of `2026-08-24_…EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md`. + Supplements `watchdog-electrical-design-state.md` (canonical) and + `watchdog-production-schematic-delta.md` (the v3 precedent for this document's format). +--- + +# EinarBoard v6 — Schematic Delta + +> **Base:** `kicad/hbox-watchdog-v5` (EinarBoard 3.0 / v5.5, 15 pcs in fab since 22-Ago, +> git checkpoint `c3e8e48`). **Target:** `kicad/hbox-watchdog-v6`. +> +> **Method rules, unchanged and non-negotiable:** file-level KiCad edits only (the MCP backend +> clobbered disk state on 24-Jul — read-only MCP queries are fine); git checkpoint after every +> block; every structural edit verified by an independent parse; `%.10g` coordinates; footprint +> rotation = R(−θ); zone refills in the GUI only. +> +> ⚠ **Before starting:** clear the stale `.git/*.lock` files and confirm the v5.5 fix commit +> actually landed. This has bitten two sessions running. + +--- + +## 0. ⚠ Two corrections to the v6 brief, from the extracted netlist + +The brief was written before the netlist was pulled. Both of these were wrong: + +1. **"Spare PCF8574 pins" — there is only ONE.** `U13` (PCF8574T, SOIC-16 **WIDE**) has + P0–P3 on `/BATLED1–4`, P4–P6 on `/RGB_R/G/B`. **Only `P7` (pin 12) and `~INT` (pin 13) + are free.** All new control signals therefore go on **MCU pins**, of which there are + plenty (§3). +2. **`/MODEM_RST` is not MCU-controlled today.** It connects only `J7.5` and `SW4G1.1` + (a pushbutton). The firmware has **never** been able to reset the modem in software — its + only tool is killing `SW_5V` via `PWR_CTRL`. **v6 fixes this** (Block T). + +A third finding worth recording: **`/3V3_MODEM` is made by `U7` (MP1584) → `L7` → `3V3_MODEM`**, +input `/SW_5V`. `U7`'s job changes completely in v6 (§Block P). + +--- + +## 1. Verified v5 baseline (extracted 2026-08-24) + +### 1.1 STM32F411RET6 (`U16`) — as drawn + +| Pin | Port | Net | Pin | Port | Net | +|---|---|---|---|---|---| +| 2 | PC13 | `/LED_C13` | 41 | PA8 | `/CHRG_EN` | +| 8 | PC0 | `/ETH_INT` | 42 | PA9 | `/USART1_TX` | +| 14 | PA0 | `/BAT_SENSE` | 43 | PA10 | `/USART1_RX` | +| 15 | PA1 | `/PWR_CTRL` | 44/45 | PA11/12 | `/DFU_DM` `/DFU_DP` | +| 16 | PA2 | `/GNSS_TX` | 46/49 | PA13/14 | `/SWDIO` `/SWCLK` | +| 17 | PA3 | `/GNSS_RX` | 50 | PA15 | `/ETH_RST` | +| 20 | PA4 | `/5V_SENSE` | 55 | PB3 | `/CHRG_STAT` | +| 21 | PA5 | `/IGN_SENSE` | 56 | PB4 | `/CHRG_FAULT` | +| 22 | PA6 | `/FAN_PWM` | 57 | PB5 | `/BYP_CTRL` | +| 26 | PB0 | `/EXT_LED` | 58/59 | PB6/PB7 | `/I2C_SCL` `/I2C_SDA` | +| 27 | PB1 | `/NTC_TEMP` | 61 | PB8 | `/SER_IRQ` | +| 28 | PB2 | `/BOOT1_STRAP` | 33–36 | PB12–15 | `/ETH_CS/SCK/MISO/MOSI` | + +**Free (unconnected in v5): PC1, PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9, PC10, PC11, PC12, +PD2, PA7, PB9, PB10** — 16 pins. **USART6 = PC6/PC7 is free**, which is what makes v6 possible +without disturbing anything. + +### 1.2 The three nets that carry the whole GNSS/4G architecture + +| Net | v5 members | Meaning | +|---|---|---| +| `/USART1_RX` | `U16.43` (PA10) · `Rdfu1.1` (22k) · **`K1.1` (NC)** · **`MOD1.23`** (F9P TX2) | receiver's UART2 uplink; MCU taps it passively | +| `/DIN_4G` | **`K1.5/6` (COM)** · `J7.3` | modem's data-**in**; fed from the relay | +| `/DOUT_4G` | `J7.2` · **`MOD1.17`** (F9P RX2) | **RTCM downlink, hardwired, never switched** | + +`K1` (G5V-1 DC5): `1`=NC=`/USART1_RX` · `5/6`=COM=`/DIN_4G` · `10`=NO=`/USART1_TX` (PA9) · +`2`=`/SYS5V` · `9`=`/KCOIL` ← `BYP_CTRL` (PB5) via `R40`→`Q6`. + +### 1.3 Power tree (verified) + +``` +P1.1 ─/24V_IN─┬─ D10 (SMBJ33A) + └─ Q10 (IRFR5305, rev-pol) ──/24V── U2.7 (MP1584 fan buck) ──/SW_FAN─► 5V_FAN +P2.1 ─/VEH5V── RS1 (8 mΩ) ──/VIN_SW── U6.7 (LTC4040 VIN) +U6 (LTC4040) ──/MAIN5V── Q7 (AO4406A cutoff, gate /IGATE) ──/SYS5V── F1 (PPTC 3A) + ├─ U12 (MP1584) ─► /3V3_ETH + └─ K1.2 (relay coil supply) +/SW_IN ── Q1 (AO3401A, gate /GATE_LS ← PWR_CTRL) ──/SW_5V──┬─ MOD1.5 (shield 5 V) + └─ U7.2/7 (MP1584) ─L7─► /3V3_MODEM +U17 (AMS1117-3.3) ──/3V3 +``` + +--- + +## 2. Deletions + +| Ref | What | Action | +|---|---|---| +| `MOD1` | simpleRTK2B Pro, Arduino UNO R3 socket, 32 pads @ (90.6, 129.9) | **DELETE** — nets `/GNSS_RX` `/GNSS_TX` `/DOUT_4G` `/USART1_RX` `/SW_5V` re-terminate on `U18` | +| `J7`, `J8` | XBee 2.0 mm sockets, 10 pads each @ (174.2 / 196.5, 74.8) | **DELETE** — nets re-terminate on `U19` | +| `SW4G1` | modem RESET pushbutton | **DELETE** — replaced by MCU control (Block T) | +| `Rmled1`, `MODEM_LED_A` | modem LED + resistor | **KEEP**, re-source from the A7670SA `NETLIGHT` pin | +| `MH7`, `MH9`, `MH10`, `MH11` | shield / heatsink M2.5 bolt holes | **DELETE MH7/MH9/MH10**; **KEEP MH11** and add one partner near `U19` for the modem heatsink clamp | +| `U7`, `L7`, `C41`, `C43` | the 3V3 modem buck | **REPURPOSE** — see Block P | + +**Do not delete `K1`.** It is rewired (Block T), not removed — it is the fail-safe that keeps +corrections flowing with a dead MCU. + +--- + +## 3. MCU pin allocation — v6 additions + +| New net | Pin | Dir | Function | +|---|---|---|---| +| `/USART6_TX` | **PC6** | OUT | MCU → modem (AT, GGA uplink, `$PHLG`) — **via `K1` NO** | +| `/USART6_RX` | **PC7** | IN | modem → MCU (AT responses, RTCM tap) | +| `/MODEM_PWRKEY` | **PC8** | OUT | open-drain via `Q20` (2N7002) | +| `/MODEM_STATUS` | **PC9** | IN | level-shifted 1.8 → 3.3 V | +| `/MODEM_PWR_EN` | **PC10** | OUT | enables `U20` buck — the `HARD_CYCLE` path | +| `/MODEM_RST` | **PC11** | OUT | open-drain via `Q21` (2N7002) — **new MCU control** | +| `/GNSS_RESET_N` | **PC12** | OUT | open-drain; `U18` needs a 7–13 kΩ pull-up | +| `/GNSS_ANT_DETECT` | **PC1** | IN | antenna-open telemetry | +| `/GNSS_ANT_SHORT_N` | **PC2** | IN | antenna-short telemetry | +| `/GNSS_TIMEPULSE` | **PA7** | IN | TIM3_CH2 input capture | + +**Unchanged:** `/GNSS_TX` PA2 · `/GNSS_RX` PA3 (USART2, `HardwareSerial GNSS(PA3, PA2)`) · +`/USART1_RX` PA10 (receiver UART2 tap) · `/BYP_CTRL` PB5 (K1 coil). +**Freed:** **PA9** — `K1.10` moves to PC6. Leave PA9 as a test point. +**Still spare after v6:** PC3, PC4, PC5, PB9, PB10, PD2, PA9 — **7 pins.** + +> ⚠ **Verify no double-allocation** with an independent netlist parse before ERC. That check +> is the reason this table exists. + +--- + +## Block N — GNSS island (`U18`) + +**Part:** `ZED-F9P-04B`, 54-pin LGA 22.0 × 17.0 × 2.4 mm — **LCSC `C5120257`, Extended.** +*(Alternative under evaluation for v7: LG290P, 12.2 × 16 mm — see brief §1.4. **Do not** +attempt a dual footprint; the pad rings overlap and the pin functions do not align.)* + +| Signal | Connect to | Note | +|---|---|---| +| `VCC` | `/3V3_GNSS` | **from `U21` LDO only** — never the shared `/3V3` | +| `V_BCKP` | `/3V3` via 1 kΩ + Schottky | 45 µA hot-start; keeps ephemeris across a power-cycle | +| `GND` | GND | full pad-under-module via field | +| `UART1 TX/RX` | `/GNSS_RX` / `/GNSS_TX` | ⚠ net names are **MCU-relative** in this design: `/GNSS_TX` = MCU PA2 out → receiver RX | +| `UART2 RX` | `/DOUT_4G` | **RTCM in — hardware path, unswitched** | +| `UART2 TX` | `/USART1_RX` | uplink NMEA → `K1.1` (NC) and PA10 | +| `RESET_N` | `/GNSS_RESET_N` | **7–13 kΩ pull-up to `/3V3_GNSS` required** | +| `TIMEPULSE` | `/GNSS_TIMEPULSE` | 4 mA drive | +| `ANT_DETECT` / `ANT_SHORT_N` | `/GNSS_ANT_DETECT` / `/GNSS_ANT_SHORT_N` | free antenna-fault telemetry — you have already lost H0005 and H0014 to antenna faults | +| `VCC_RF` | Block S bias tee | 50 mA typ / **300 mA abs max** | +| `D_SEL` | leave open (UART mode) | open/high = UART; low = SPI | +| `SAFEBOOT_N` | leave open | | +| `USB` (`V_USB`, D+/D−) | **test pads only**, not a connector | u-center access at first article; 90 Ω pair, ESD if ever fitted | + +**Decoupling:** 100 nF per supply pin **≤ 2 mm**, 10 µF + 1 µF bulk ≤ 10 mm, per the +Capacitor-Placement-Map rules. + +### `U21` — GNSS LDO + +**3.3 V, ≥ 300 mA, low noise.** Candidates: `TPS7A2033PDBVR` (low-noise, preferred) or +`AP2112K-3.3TRG1` (cheap, LCSC `C51118`). + +**Input = `/SW_5V`, output = `/3V3_GNSS`** — deliberately *not* the shared `/3V3`. Two reasons: +the receiver stays on the MCU-gated stack rail, so `PWR_CTRL` (PA1 → `Q1`) still power-cycles +it exactly as it does today; and an LDO fed from 5 V has ~1.7 V of headroom, which is what +gives it the PSRR to reject the three MP1584 switchers this board carries. The ZED-F9P has +**no internal SAW** — it will lock onto buck harmonics given the chance. + +> 🔴 **`ZED-F9P` VCC ramp rate ≤ 20 µs/V is a DAMAGE limit, not a guideline** — 0 → 3.3 V in +> **≤ 66 µs**. Most LDOs with soft-start are far slower. **Measure the chosen LDO's rise time +> on the bench before fab.** If it cannot meet it, add a fast load switch or a supervisor. +> This is a fab gate, not a nice-to-have. + +--- + +## Block O — LTE island (`U19`) + +**Part:** `A7670SA-LASC`, 88-pin LGA 24 × 24 × 2.5 mm — **LCSC `C18548276`**, in the JLCPCB +library, Extended, **X-ray inspection required, MSL 3**. +⚠ **`-SA` only.** `-E`/`-C` variants have no B4/B66 and will never attach on Telcel. +⚠ A7670 (88-LGA) ≠ A7672 (124 LCC+LGA) ≠ SIM7070 (68-LCC). Not interchangeable. + +| Signal | Connect to | Note | +|---|---|---| +| `VBAT` **pins 55, 56, 57** | `/VBAT_MODEM` | **all three, mandatory** for RF performance. Trace/pour **≥ 3 mm** | +| `GND` | GND | full via field | +| `UART_TXD` | `/DOUT_4G` (level-shifted, Block R) | → `U18` UART2 RX **and** → `/USART6_RX` | +| `UART_RXD` | `/DIN_4G` (level-shifted) | ← `K1` COM | +| `PWRKEY` | `/MODEM_PWRKEY_M` | via `Q20` open-drain. **Low ≥ 50 ms = on; ≥ 2.5 s = off.** Internal 50 kΩ to VBAT. **≤ 100 nF total capacitance** or you get spurious auto-power-on | +| `RESET` | `/MODEM_RST_M` | via `Q21` open-drain, low 2–2.5 s. Same ≤ 100 nF rule | +| `STATUS` | `/MODEM_STATUS_M` | 1.8 V out; asserts ≈ 11.2 s after power-on | +| `NETLIGHT` | `Rmled1` → `/MODEM_LED_A` | reuse the existing LED | +| `VDD_1V8` | `/1V8_MODEM` + 2.2 µF | **50 mA max — level-shift reference ONLY, never a system rail** | +| `USIM1_*` | Block Q | internal 4.7 kΩ pull-up on `USIM1_DATA` — no external needed | +| `USB` D+/D−, VBUS | `J9` (kept) | 90 Ω pair. **Reserve a `USB_BOOT` test pad** — the only recovery if a firmware update bricks it. `USB_BOOT` must **not** be pulled low at power-up | +| `RF_ANT` (pin 60) | Block S | **single antenna** — Cat-1bis, no diversity port | + +**Decoupling at the VBAT pins:** 33 pF ∥ 10 pF ∥ 100 nF ∥ 1 µF ceramics, plus the Block P bulk. + +--- + +## Block P — modem 3.8 V rail (`U20`) — replaces `U7`'s modem duty + +> **Why this block exists, in one sentence:** the 10-Ago fleet boot-loop was caused by +> exceeding the SD-15B's 3 A, and putting a 2 A LTE modem back on that same rail would +> recreate it. + +``` +/24V ──────────► D20 (SS34) ──┐ + ├──/VIN_MODEM──► U20 (buck, 3.8 V) ──L20──► /VBAT_MODEM ──► U19 +/MAIN5V ──► D21 (PMEG3050) ───┘ ▲ + │ EN + /MODEM_PWR_EN (PC10) +``` + +| Item | Spec | +|---|---| +| `U20` | **4.5–40 V in, ≥ 3 A, synchronous buck.** Candidate: `AP64350` class. ⚠ **40 V rating required** — `D10` (SMBJ33A) clamps at ~53 V, a 28 V part such as TPS54331 is **not** acceptable. **NOT a fourth MP1584** — the existing three are the source of EMC findings 3 and 4 | +| Output | **3.8 V** (mid-window of the A7670SA's 3.4–4.2 V) | +| `D20` | SS34 — already in the BOM (`D2`, `D12`). Drop irrelevant on the 24 V leg | +| `D21` | **low-Vf Schottky** (PMEG3050 class, Vf ≈ 0.35 V @ 2 A). In blackout `MAIN5V` ≈ 5 V → `VIN_MODEM` ≈ 4.65 V → duty ≈ 0.82. **Verify `U20`'s max duty covers it**; if marginal, use a P-FET ideal diode (`AO3401A`, already in the BOM as `Q1`) | +| Bulk | **≥ 470 µF low-ESR** within 20 mm of the VBAT pins (spec floor is 300 µF; the extra is cheap insurance against the 2.8 A GSM burst) | +| `Cin` | **1 µF + 10 µF within 3 mm** of `U20`'s VIN-GND — EMC finding 4, mandatory | +| Feedback | divider **at the FB pin**, `/FB_MODEM` short and routed away from `/SW_MODEM` — EMC finding 3, mandatory | +| Enable | `/MODEM_PWR_EN` (PC10). **Power-cycle = EN low ≥ 2 s.** Add a 1 kΩ bleed across the bulk so it actually collapses | + +**Behaviour:** cabinet live → modem's ~9.5 W comes from **24 V at ~0.45 A** and the SD-15B +never sees it. Blackout → same buck accepts `MAIN5V` (pack via LTC4040), unit keeps reporting +(**invariant #2**). + +### `U7` / `L7` / `C41` / `C43` — disposition + +`/3V3_MODEM` no longer feeds a modem. **Repurpose `U7` as the 3.3 V supply for the level +shifters and the LTE-island logic**, renaming the net `/3V3_LTE`, **or** delete `U7`/`L7`/ +`C41`/`C43` entirely and take that logic from `/3V3`. **Decide before routing the 3V3 tree** — +this is the same open decision that was left dangling on `U12`/`L8` in the 3.0 brief, and it +should not be left dangling twice. Recommendation: **delete**, feed from `/3V3`; the shifters +draw milliamps. + +### 🔴 Also fix in this block + +**The LTC4040 input-limit errata.** `RS1` (8 mΩ) sets a 3.12 A input limit **above** the +SD-15B-05's 3 A rating. Bring the limit **below** the PSU rating, or specify a larger PSU. +This has been a standing errata since 10-Ago. **v6 is where it gets fixed.** + +--- + +## Block Q — MFF2 SIM (`U22`) + +| Item | Spec | +|---|---| +| Package | **MFF2 / DFN-8 5 × 6 mm (SON-8)** | +| Signals | `USIM1_VDD` · `USIM1_CLK` · `USIM1_DATA` · `USIM1_RST` · GND | +| Pull-ups | none — `U19` has an internal 4.7 kΩ on `USIM1_DATA` | +| Voltage | 1.8 V / 3.0 V auto-detect, no strapping | +| ESD | array at the SIM lines (`ESDA6V1W5` / `SMF15C` class) | +| Placement | **≤ 20 mm from `U19`**, short matched-ish traces, GND guard | + +⚠ **Sourcing is a lead-time item, not an engineering task.** You need a real Telcel/AT&T MX or +Mexican-profile MVNO MFF2. Flat-rate sensor IoT SIMs (500 MB / 10 years) are **an order of +magnitude too small** for 50–200 MB/month. Start this in Phase 0. + +--- + +## Block R — level translation (`U23`, `U24`) + +**A7670SA I/O is 1.8 V. The STM32's V_IH ≈ 2.31 V — 1.8 V will not read as HIGH.** + +| Line | Direction | Method | +|---|---|---| +| `/DIN_4G` (MCU/K1 → modem RX) | 3.3 → 1.8 | `SN74LVC2T45DCUR`, DIR fixed | +| `/DOUT_4G` (modem TX → MCU + receiver) | 1.8 → 3.3 | `SN74LVC2T45DCUR`, DIR fixed | +| `/MODEM_STATUS` | 1.8 → 3.3 | same device, 2nd channel | +| `/MODEM_PWRKEY`, `/MODEM_RST` | 3.3 → open-drain | `Q20`, `Q21` = 2N7002. **Not** through a shifter — MCU absent must leave them unasserted | + +- `VccA` = `/1V8_MODEM` (from `U19`'s `VDD_1V8`, 2.2 µF); `VccB` = `/3V3`. +- **Prefer direction-fixed `74LVC2T45` over auto-direction `TXS0108E`** — auto-direction parts + misbehave against strong drivers and multi-load nets, and `/DOUT_4G` drives **two** loads. +- ⚠ **The RTCM path passes through `U24`.** Add DNP 0 Ω bypass footprints across it so a + suspect shifter can be jumpered out at bring-up. +- `/DOUT_4G` fan-out on the 3.3 V side: `U18` UART2 RX **and** `/USART6_RX` (PC7) through a + **100 Ω series** resistor for isolation. + +--- + +## Block S — RF front ends + +### Stackup (unchanged topology, new requirement) + +`F.Cu` signal + GND pour · `In1` **solid GND** · `In2` 3V3 plane · `B.Cu` signal + GND pour. ✔ + +**New: order as controlled impedance.** JLCPCB 4-layer `JLC04161H-7628`, F.Cu→In1 prepreg +≈ 0.2104 mm. Target **50 Ω CPWG on F.Cu referenced to In1** — roughly 0.35 mm trace / 0.25 mm +gap at εr ≈ 4.4. **Confirm with JLCPCB's own impedance calculator before layout, not after.** + +**Rules for both RF traces, no exceptions:** referenced to an unbroken `In1` (no plane split, +no `In2` island underneath), as short and straight as possible, ground-stitched both sides at +≤ λ/20, nothing crossing on any layer. + +### S1 — GNSS (`J12`) + +``` +J12 (U.FL) ─╥─ C_DC (100 nF) ─── U18.RF_IN + ║ + └─ L_BIAS (≥68 nH 0603, SRF > 1.6 GHz) ─┬─ 100 nF ∥ 100 pF ─ GND + └─ VCC_RF (U18) + D_ESD (>2 GHz rated, <0.5 pF) at J12 +``` + +Active antenna is **mandatory** — the ZED-F9P has **no internal LNA**. + +### S2 — LTE (`J13`) + +``` +U19.RF_ANT ─── π-match pads (series 0402 + 2× shunt 0402, series = 0 Ω default) + ─── J13 (U.FL) ─── D_ESD (low-cap <0.5 pF) +``` + +### S3 — 🔴 GNSS desense — the #1 technical risk + +A 23 dBm transmitter drawing 2–3 A bursts next to a GNSS front end is a classic RTK-killer. +**The Ardusimple shield has been hiding this. A bare module hands it to you.** + +1. **Board separation:** `U18` in the freed `MOD1` area (bottom-left), `U19` in the freed + XBee area (right edge) → **≈ 149 mm apart, the full diagonal.** The GNSS corner is also the + furthest point from all four switchers (nearest, `U7` at 140.0/112.9, ~60 mm). +2. **Antenna separation ≥ 20 cm** on the enclosure — two bulkheads. **Design rule, not an + optimization.** +3. Modem return path routed away from the GNSS island; no shared reference but `In1`. +4. **No 1575 SAW** — it would kill L2 on a dual-band receiver. Specify an active antenna with + good out-of-band rejection instead. +5. **Measured at gate G3** (Phase 6): fix quality and C/N₀ with the modem idle vs. transmitting + at full power. Pass/fail, not opinion. + +### S4 — ESD everywhere (EMC finding 5) + +**Every external connector gets a clamp within 10 mm.** `D14/D15/D16` move from the MCU to +within 10 mm of `J11`. `J9` gets the ESD array it never had (**< 2 pF** on the data lines). +`J12`, `J13`, `P1`, `P4`, `P15`, `P16` all get appropriate clamps. + +--- + +## Block T — `K1` rewiring (one net change, big consequence) + +| `K1` pin | v5 | **v6** | +|---|---|---| +| 1 (NC) | `/USART1_RX` | **unchanged** — receiver UART2 TX | +| 5/6 (COM) | `/DIN_4G` | **unchanged** — modem RX | +| 10 (NO) | `/USART1_TX` (PA9) | **`/USART6_TX` (PC6)** ← the only change | +| 2 | `/SYS5V` | unchanged | +| 9 | `/KCOIL` ← `BYP_CTRL` (PB5) | unchanged | + +**What this buys.** Energised (normal operation) the MCU owns the modem's RX line: it can send +AT commands, forward the receiver's GGA byte-exact, and inject `$PHLG`. **De-energised — MCU +dead or absent — the relay relaxes and the receiver's own GGA goes straight to the modem, so +corrections keep flowing.** Invariants #2 and #3 both hold, with hardware, for the price of one +moved net. + +**Consequence to state plainly: v6 makes Modo B the normal operating mode.** Consistent with +the EPCT decision already on record. + +--- + +## 4. New net list + +| Net | Members | +|---|---| +| `/3V3_GNSS` | `U21.out` · `U18.VCC` · decoupling | +| `/VBAT_MODEM` | `L20.2` · `U19.55/56/57` · bulk + ceramics | +| `/VIN_MODEM` | `D20.k` · `D21.k` · `U20.VIN` · Cin | +| `/1V8_MODEM` | `U19.VDD_1V8` · `U23.VccA` · `U24.VccA` · 2.2 µF | +| `/USART6_TX` | `U16.37` (PC6) · `K1.10` | +| `/USART6_RX` | `U16.38` (PC7) · 100 Ω → `/DOUT_4G` (3.3 V side) | +| `/MODEM_PWRKEY` / `_M` | `U16.39` (PC8) → `Q20` → `U19.PWRKEY` | +| `/MODEM_STATUS` / `_M` | `U19.STATUS` → `U24` → `U16.40` (PC9) | +| `/MODEM_PWR_EN` | `U16.51` (PC10) · `U20.EN` | +| `/MODEM_RST` / `_M` | `U16.52` (PC11) → `Q21` → `U19.RESET` | +| `/GNSS_RESET_N` | `U16.53` (PC12) · `U18.RESET_N` · 10 kΩ pull-up | +| `/GNSS_ANT_DETECT` | `U16.9` (PC1) · `U18.ANT_DETECT` | +| `/GNSS_ANT_SHORT_N` | `U16.10` (PC2) · `U18.ANT_SHORT_N` | +| `/GNSS_TIMEPULSE` | `U16.23` (PA7) · `U18.TIMEPULSE` | +| `/SIM_VDD` `/SIM_CLK` `/SIM_DATA` `/SIM_RST` | `U19` ↔ `U22` | +| `/RF_GNSS` `/RF_LTE` | 50 Ω, own netclass, **no autorouting** | + +**Changed:** `/DOUT_4G` gains `U18`.UART2_RX, `U24` and the PC7 tap; loses `J7.2`, `MOD1.17`. +`/DIN_4G` gains `U23`/`U19`; loses `J7.3`. `/USART1_RX` loses `MOD1.23`, gains `U18`.UART2_TX. +`/USART1_TX` loses `K1.10` → PA9 becomes a test point. + +**Netclasses:** keep `HV24`; add **`RF`** (50 Ω, no auto-route) and **`PWR_MODEM`** +(`/VBAT_MODEM`, `/VIN_MODEM` — min width 1.5 mm, target ≥ 3 mm). + +--- + +## 5. Inherited EMC fixes — schematic-level, day-one + +| # | Fix | Where it lands in the schematic | +|---|---|---| +| 🔴 1 | `/SW_BAT` ≥ 2 mm polygon | netclass + layout; **load-bearing** — blackout now feeds the modem too | +| 🔴 2 | `/BAT_PLUS` ≥ 1.5 mm + via farm | same | +| 🟠 3 | `R20`/`R21` at `U2` pin 4, `/FB_FAN` short | schematic grouping so layout can honour it | +| 🟠 4 | 1 µF + 10 µF ≤ 3 mm of every buck VIN — **including `U20`** | add the parts now | +| 🟠 5 | ESD < 10 mm of every external connector | Block S4 | +| 🟡 6 | Crystals → **3225** package, < 5 mm (`Y1`, `Y3`, `Y4` are HC-49 at 12–15 mm) | change footprints in the schematic pass | +| 🟡 7 | ETH RX pair coupled and length-matched | layout, flagged here | +| 🟡 8 | Fill the 8 empty 20 mm stitching cells | free — most were under `MOD1` | + +--- + +## 6. Part additions + +| Ref | Part | LCSC | Note | +|---|---|---|---| +| `U18` | ZED-F9P-04B | **C5120257** | Extended, X-ray, ~104–129 in stock — **consign from Mouser for ≥ 100 units** | +| `U19` | A7670SA-LASC | **C18548276** | Extended, X-ray, MSL 3, 220 in stock | +| `U20` | 40 V / 3 A sync buck (AP64350 class) | verify | **40 V minimum** | +| `U21` | TPS7A2033 or AP2112K-3.3 | C51118 (AP2112K) | ⚠ verify the ≤ 66 µs ramp | +| `U22` | MFF2 SIM | carrier | lead-time item | +| `U23`, `U24` | SN74LVC2T45DCUR | verify | direction-fixed | +| `Q20`, `Q21` | 2N7002 | verify | open-drain PWRKEY / RESET | +| `D20` | SS34 | in BOM (`D2`) | 24 V leg | +| `D21` | PMEG3050 class | verify | low-Vf, 5 V leg | +| `J12`, `J13` | U.FL / IPEX MHF1 SMT receptacle | verify | + 2 pigtails, 2 SMA bulkheads | +| `L20` | buck inductor per `U20`'s datasheet | verify | ≥ 4 A saturation | +| — | 2× 220 µF low-ESR + ceramics | verify | Block P bulk | +| — | ESD arrays, RF ESD, bias-tee L/C, π-match 0402s | verify | Blocks S1–S4 | + +> **All "verify" rows must be confirmed against the live JLCPCB library at upload**, not from +> a cached DB. The `v4-bom.xls` labels are on record as unreliable; check pin-counts vs +> pad-counts every time. + +--- + +## 7. Execution order & verification + +| Step | Work | Check | +|---|---|---| +| T0 | Clear stale `.git/*.lock`; confirm the v5.5 fix commit landed; branch `v6` from `c3e8e48`; copy v5 → `hbox-watchdog-v6` | parse-verify the copy opens clean | +| T1 | Deletions (§2) | netlist parse: `MOD1`/`J7`/`J8`/`SW4G1` gone, no orphan nets | +| T2 | Block T (`K1.10` → PC6) | 1-net diff, nothing else moved | +| T3 | Block N + `U21` | ERC on the GNSS island alone | +| T4 | Block O + Block Q + Block R | ERC; **confirm every 1.8 V line crosses a shifter** | +| T5 | Block P + LTC4040 input-limit fix | recompute the backup power budget vs the 1S4P ~12 Ah pack | +| T6 | Block S (RF + ESD) + netclasses | `RF` netclass exists and is excluded from autorouting | +| T7 | EMC list §5 | each row ticked individually | +| T8 | Full ERC + independent pin-map parse | **ERC 0**; no double-allocated MCU pin | + +**Gate G2:** ERC 0 **plus** an external review of the RF and power sections. This is Helgen's +first RF design — do not let ERC-clean stand in for reviewed. + +--- + +## 8. What is deliberately NOT in v6 + +- **A second GNSS footprint.** LG290P's pads overlap the F9P ring and the functions don't align. + The cost-down is a v7 respin of one corner — which is exactly why Block N is an island. +- **A modem mezzanine daughtercard.** Rejected with "full integration" (brief §0); revisit only + if gate T1's cabinet temperature forces the industrial tier. +- **eUICC.** MFF2 only, per the 24-Ago decision. +- **Cat-4 / WCDMA.** See brief §2.3. + +--- + +## 9. Cross-references + +- v6 brief: `2026-08-24_Drogheda_EinarBoard-v6_GNSS-4G-Integration_Design-Brief_v1.0.md` +- Phase 1 firmware: `2026-08-24_Drogheda_EinarBoard-v6_Phase-1_NTRIP-Firmware-Spec_v1.0.md` +- EMC fix list: `2026-08-22_Drogheda_EinarBoard-v5_EMC-Audit_v1.0.md` +- Canonical electrical state: `watchdog-electrical-design-state.md` +- Format precedent: `watchdog-production-schematic-delta.md` +- Cap placement rules: `2026-08-14_Drogheda_EinarBoard-3.0_Capacitor-Placement-Map_v1.0.md` +- Invariants: **#1** power-off safe (`Q20`/`Q21` open-drain, buck EN default off) · **#2** + never vanish (Block P dual input, Block T fail-safe) · **#3** power-cycle recovery + (`MODEM_PWR_EN`) · **#5** no head-of-line blocking · **#6** no secrets diff --git a/03-Architecture-Hardware/2026-08-27_Drogheda_KiCad-Submodule-Migration_v1.0.md b/03-Architecture-Hardware/2026-08-27_Drogheda_KiCad-Submodule-Migration_v1.0.md new file mode 100644 index 0000000..4ec8ab1 --- /dev/null +++ b/03-Architecture-Hardware/2026-08-27_Drogheda_KiCad-Submodule-Migration_v1.0.md @@ -0,0 +1,188 @@ +# KiCad → git submodule + Gitea mirror — migration record & runbook + +**Date:** 2026-08-27 · **Status:** local side DONE, pushes pending (need network + credentials) + +--- + +## 1. What was done + +`03-Architecture-Hardware/kicad` is no longer tracked file-by-file in +`project-drogheda`. It is now a **standalone git repository**, referenced from the +parent as a submodule. + +| Item | Value | +|---|---| +| Submodule path | `03-Architecture-Hardware/kicad` | +| Submodule name | `kicad` | +| Canonical remote (`origin`) | `git@github.com:lrodriguez15/hbox-kicad.git` | +| Mirror remote (`helgen`) | `ssh://git@git.helgen.online:222/Helgen-Services/HBox_PCB.git` | +| Submodule branch | `main`, **72 commits**, HEAD `4bf41c6` | +| Parent commit | `5bb2e12` — "kicad → submodule (hbox-kicad)" | +| Rollback tag (parent) | `pre-kicad-submodule-2026-08-26` → `daafd5c` | + +**History was preserved.** The 71 board commits were extracted with +`git subtree split --prefix=03-Architecture-Hardware/kicad`, so blame and bisect +still work on the schematic and PCB. Commit **hashes differ** from the parent's +(subtree split rewrites them); messages, authors and dates are identical. + +Verified: every file that existed under `kicad/` at `daafd5c` is byte-identical +in the submodule (blob-hash comparison, 309/309 files). `git fsck +--connectivity-only` on the new repo is clean. + +### Deliberate content changes (1 commit on top of the split) + +- **added** `.gitignore` — KiCad `.kicad_prl`, `*.lck`, `*.T*bak` agent scratch + backups, `.history/`, `.mcp-backups/`, `_to_delete/`, `*_drc_violations.json`, + `*.log`, `snapshots/`, Office lock files +- **added** `README.md` — layout, remotes, history note +- **untracked** `hbox-watchdog-v3.kicad_prl` (per-user KiCad UI state) +- **untracked** `~hbox-watchdog-v5.kicad_pro.lck` (KiCad session lock, already + deleted on disk) + +### Not done, on purpose + +- **Parent history was not rewritten.** Its `.git` stays ~239 MB because the old + kicad blobs are still in it. Nothing force-pushed, no clone broken. The parent + simply stops growing from here on. If you ever want it slim, that is a separate + `git filter-repo` + force-push job. +- Branch `kicad-split` still exists in the parent as the split's output. Delete it + once the push succeeds: `git branch -D kicad-split`. + +--- + +## 2. What you still have to do + +### Step 1 — create the GitHub repo and push the submodule + +```bash +cd ~/Documents/Claude/Projects/Drogheda/03-Architecture-Hardware/kicad + +# create it (private) — or make it in the web UI as lrodriguez15/hbox-kicad +gh repo create lrodriguez15/hbox-kicad --private + +git push -u origin main +``` + +~240 MB on the first push, mostly the STL/STEP mechanical exports. Expect a few +minutes. + +### Step 2 — push the parent + +```bash +cd ~/Documents/Claude/Projects/Drogheda +git push origin main +git push origin pre-kicad-submodule-2026-08-26 # rollback tag +git branch -D kicad-split # split scaffolding, no longer needed +``` + +Push the submodule **first**. A parent commit whose gitlink points at a commit +nobody can fetch will break every clone. + +### Step 3 — set up the Gitea pull mirror + +Gitea can only make a repo a *pull* mirror **at migration time** — it cannot be +switched on for a repo that already exists. `Helgen-Services/HBox_PCB` is +currently empty, so: + +1. Gitea → `Helgen-Services/HBox_PCB` → **Settings → Danger Zone → Delete**. +2. **+ → New Migration → GitHub**. + - Clone URL: `https://github.com/lrodriguez15/hbox-kicad.git` + - Auth: your GitHub username + a **classic PAT with `repo` scope** + (required — the GitHub repo is private) + - Owner: `Helgen-Services` · Repository name: `HBox_PCB` + - ✅ **This repository will be a mirror** +3. After migration: **Settings → Mirror Settings** → set the interval (default is + `8h`; `1h` is reasonable) and use **Synchronize Now** to test. + +You push only to GitHub; Gitea follows on its own. **This needs no SSH key** — +the mirror pulls over HTTPS with the PAT, which is why it is the low-friction +option while your SSH credentials are still pending. + +If you would rather keep the existing `HBox_PCB` repo as-is, the fallback is a +local dual push instead of a server-side mirror: + +```bash +cd .../kicad +git remote set-url --add --push origin git@github.com:lrodriguez15/hbox-kicad.git +git remote set-url --add --push origin ssh://git@git.helgen.online:222/Helgen-Services/HBox_PCB.git +# now `git push origin main` hits both — but both must be reachable, every time +``` + +### Step 4 — SSH to Gitea (only if you want to push there directly) + +```bash +ssh-keygen -t ed25519 -C "leo@helgen.tech" -f ~/.ssh/id_ed25519_helgen +pbcopy < ~/.ssh/id_ed25519_helgen.pub # paste into Gitea → Settings → SSH/GPG Keys + +cat >> ~/.ssh/config <<'EOF' +Host git.helgen.online + HostName git.helgen.online + Port 222 + User git + IdentityFile ~/.ssh/id_ed25519_helgen +EOF + +ssh -T git@git.helgen.online -p 222 # expect a Gitea greeting +``` + +The `helgen` remote is already configured in the submodule, so after this +`git push helgen main` works. **Do not** push to Gitea if you set it up as a pull +mirror in Step 3 — a mirror is read-only and force-overwrites local pushes on the +next sync. + +--- + +## 3. Day-to-day workflow from now on + +Board work is committed **inside** the submodule, then the parent is told which +commit to point at: + +```bash +cd .../Drogheda/03-Architecture-Hardware/kicad +git add hbox-watchdog-v6.kicad_sch hbox-watchdog-v6.kicad_pcb +git commit -m "v6 T5: LTC4040 errata + U7 delete" +git push # → GitHub, Gitea follows + +cd ../.. # parent repo +git add 03-Architecture-Hardware/kicad # stages the new gitlink +git commit -m "kicad: v6 T5" +git push +``` + +Forgetting the second half is the classic submodule trap: the board work is safe +on GitHub, but the parent still points at the old commit. + +### Cloning fresh + +```bash +git clone --recurse-submodules git@github.com:lrodriguez15/project-drogheda.git +# already cloned without it: +git submodule update --init --recursive +``` + +Anyone who only has Gitea access can redirect the submodule locally without +editing `.gitmodules`: + +```bash +git config --global url."ssh://git@git.helgen.online:222/Helgen-Services/HBox_PCB.git".insteadOf \ + "git@github.com:lrodriguez15/hbox-kicad.git" +``` + +For a fast, board-files-only checkout: `git clone --filter=blob:none` skips the +historical STL/STEP blobs until something actually needs them. + +--- + +## 4. Open items / observations + +- **`~hbox-watchdog-v6.kicad_pro` was modified while this ran** — KiCad is (or + was) open on the v6 project. `hbox-watchdog-v6.kicad_pro` shows as modified in + the submodule; commit or discard it as you see fit. +- **Your other three submodules point at `ssh://git.helgen.dev:6611/…`** + (`hbox-firmware`, `htracker`, `hbox-log-tools`) and are currently uninitialised. + If `.dev:6611` has been superseded by `git.helgen.online:222`, those URLs need + updating too — say the word and I'll do it in one pass. +- **This VM has no network access**, which is why every push above is left for + you to run. Everything that could be done offline is done. +- `_to_delete/` folders under `03-Architecture-Hardware/` and `kicad/` hold git + lock/temp files that the sandbox was not permitted to unlink. Safe to delete. diff --git a/03-Architecture-Hardware/2026-08-27_Drogheda_v6-Antenna-Chain_Diagram.html b/03-Architecture-Hardware/2026-08-27_Drogheda_v6-Antenna-Chain_Diagram.html new file mode 100644 index 0000000..fc92289 --- /dev/null +++ b/03-Architecture-Hardware/2026-08-27_Drogheda_v6-Antenna-Chain_Diagram.html @@ -0,0 +1,175 @@ + + + + +HBOX v6 — Antenna Chain + + + +

How the GNSS antenna connection works

+

One coax does two jobs at once: RF comes down from the antenna to the receiver, and 3.3 V goes up the same cable to power the amplifier inside the antenna.

+ +
+ + + + + + + + + + + + + EinarBoard v6 (PCB) + + + + ZED-F9P + RF_IN + VCC_RF 3.3 V + + + + + + C143 100 nF + DC block: RF passes, DC stops + + + + + + + + L21 68 nH + bias tee: DC passes, + RF blocked + + + + 3.3 V, ≤50 mA + + + + + + + + D27 ESD 0.5 pF + + + + + J12 + U.FL + + + + + pigtail 10–15 cm + + + + enclosure wall + + + + + SMA bulkhead + nut + gasket seal (IP) + takes the torque, not the PCB + + + + + antenna cable (SMA macho) + + + + Active GNSS antenna (outside, clear sky) + + patch L1/L2 + + LNA + + + + the LNA runs on the 3.3 V that arrives up the coax + ground plane / metal surface under the antenna kills reflections + + + + L1 / L2 from satellites + + + + RF signal → down the coax → into RF_IN + + 3.3 V DC → up the same coax → powers the antenna's LNA + +
Fig. 1 — the same piece of coax carries both directions. On the board, L21 (the bias tee) injects 3.3 V onto the antenna side of the line — an inductor passes DC but looks like a wall to 1.5 GHz RF, so the injection doesn't disturb the signal. C143 (the DC block) does the mirror job: RF passes into the receiver, DC cannot. Result: the antenna's built-in amplifier gets its power with zero extra wires, which is why the F9P requires an active antenna and why J12's little circuit exists.
+
+ +
+ + + + HBOX cabinet (top view) + + + + EinarBoard v6 — 140 × 100 + + J12 GNSS + + J13 LTE + + ≈149 mm apart on the board + + + + + + + + + SMA bulkhead + SMA bulkhead + + + + + + + + + antennas ≥ 20 cm apart — the #1 defence against the modem deafening the GNSS + + + + + + GNSS antenna + highest point, + clear sky + + + LTE whip + +
Fig. 2 — where each piece lives. The board carries only the two U.FL receptacles, placed at opposite corners. Short pigtails jump to SMA bulkheads that clamp through the enclosure wall — the nut and gasket keep the seal, and any torque from screwing antennas on lands in the wall, never in the PCB's solder joints. Outside, the antennas separate further: GNSS up high with open sky, the LTE whip away from it.
+
+ + diff --git a/03-Architecture-Hardware/kicad b/03-Architecture-Hardware/kicad index 23ed361..cc1c4c1 160000 --- a/03-Architecture-Hardware/kicad +++ b/03-Architecture-Hardware/kicad @@ -1 +1 @@ -Subproject commit 23ed361f0d1bbb72e6ba2978d9f0834dae5c9e79 +Subproject commit cc1c4c12a4055f703b8862fb5b14ea2876601dff diff --git a/scripts/2026-08-25_v6_T6_rf_block.py b/scripts/2026-08-25_v6_T6_rf_block.py new file mode 100644 index 0000000..ec652bf --- /dev/null +++ b/scripts/2026-08-25_v6_T6_rf_block.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +"""v6 T6 - Block S: RF front ends (J12 GNSS + bias tee, J13 LTE + pi-pad), RF ESD, +P17 u-center USB port, and the RF / PWR_MODEM netclasses. + +Parts: U.FL = C88374 (fp RF-SMD_FRF05002-JSS103M, 1=SIG 2/3=GND shield); +RF ESD = ESD9L5.0ST5G C82326 (0.5 pF - the PESD used elsewhere is ~70 pF, NOT RF-safe). + +Usage: python3 2026-08-25_v6_T6_rf_block.py [--apply] +""" +import os, sys, shutil, uuid, re, json, glob + +KI = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '03-Architecture-Hardware', 'kicad') +SCH = os.path.join(KI, 'hbox-watchdog-v6.kicad_sch') +VSYM= os.path.join(KI, 'Drogheda-v6.kicad_sym') +PRO = os.path.join(KI, 'hbox-watchdog-v6.kicad_pro') +APPLY = '--apply' in sys.argv +ROOT = '/561403b3-895e-4153-a7be-3876ae930d43' + +def f(v): + v = 0.0 if abs(v) < 1e-9 else v + return '%.10g' % v +def bal(t): + d=0;i=False;e=False + for ch in t: + if e: e=False; continue + if ch=='\\': e=True; continue + if ch=='"': i=not i; continue + if i: continue + if ch=='(': d+=1 + elif ch==')': d-=1 + return d +def span_from(text,i): + d=0;instr=False;esc=False;j=i + while j0 else len(libsrc)] + pins=PIN_RE2.findall(body) or PIN_RE.findall(body) + return [(num,nm,float(x),float(y)) for x,y,rot,nm,num in pins] + +sch=open(SCH,encoding='utf-8').read() +vsym=open(VSYM,encoding='utf-8').read() + +# --- prerequisites --- +esd_syms=[n for n in re.findall(r'\n \(symbol "([^"]+)"',vsym) if 'ESD9L' in n] +if not esd_syms: sys.exit('ESD9L5.0ST5G not in Drogheda-v6.kicad_sym - run easyeda2kicad C82326 first') +ESDN=esd_syms[0] +esd_fp_files=[os.path.basename(p)[:-10] for p in glob.glob(os.path.join(KI,'Drogheda-v6.pretty','*.kicad_mod')) + if 'SOD' in os.path.basename(p).upper() and '128' not in os.path.basename(p)] +if not esd_fp_files: sys.exit('no SOD-9xx footprint found for the ESD9L part') +ESDFP='Drogheda-v6:'+esd_fp_files[0] +esdp=libpins(vsym,ESDN) +print('ESD symbol %s pins: %s fp=%s'%(ESDN,[(n,nm) for n,nm,_,_ in esdp],ESDFP)) +# cathode pad = the one NOT named A/anode; EasyEDA usually 1=A? assert explicit naming +kpin=[n for n,nm,_,_ in esdp if nm.upper() in ('K','C','CATHODE','2','1') and nm.upper() in ('K','C','CATHODE')] +apin=[n for n,nm,_,_ in esdp if nm.upper() in ('A','ANODE')] +if not (kpin and apin): + # fall back: unidirectional SOD-923 convention on EasyEDA symbols: pin named by number + sys.exit('ESD9L pin names ambiguous: %s - inspect and hardcode before applying'%[(n,nm) for n,nm,_,_ in esdp]) +KP,AP=kpin[0],apin[0] + +UFL=libpins(vsym,'U.FL-R-SMT-1') +if not UFL: sys.exit('U.FL-R-SMT-1 symbol missing') + +lib_cached=sch.split('(lib_symbols',1)[1] +def cachedpins(name): + pp=libpins(lib_cached,name) + return pp + +# --- cache new symbols in sch --- +def grab(libsrc,name,prefix): + i=libsrc.find(' (symbol "%s"\n'%name) + if i<0: sys.exit('symbol %s not found'%name) + j=libsrc.index('(',i); k=span_from(libsrc,j) + return libsrc[j:k].replace('(symbol "%s"'%name,'(symbol "%s:%s"'%(prefix,name),1) +cache='' +for nm in ('U.FL-R-SMT-1',ESDN): + if '(symbol "Drogheda-v6:%s"'%nm not in sch: + cache+='\n\t\t'+grab(vsym,nm,'Drogheda-v6') +m=re.search(r'\n\t\(lib_symbols\n',sch) +k=span_from(sch,sch.index('(',m.start())) +sch=sch[:k-1]+cache+'\n\t'+sch[k-1:] + +# --- helpers --- +add=''; nlab=0; newlabels=[] +def add_lab(net,x,y,rot,just): + global add,nlab + add+=('\n\t(label "%s"\n\t\t(at %s %s %d)\n\t\t(effects\n\t\t\t(font\n\t\t\t\t(size 1.27 1.27)\n' + '\t\t\t)\n\t\t\t(justify %s)\n\t\t)\n\t\t(uuid "%s")\n\t)')%(net,f(x),f(y),rot,just,uuid.uuid4()) + nlab+=1; newlabels.append((net,round(x,3),round(y,3))) +def orient(px,py): + if abs(px)<0.01: return 90,'left bottom' + return (0,'left bottom') if px>0 else (180,'right bottom') +def mksym(lib_id,ref,val,x,y,fp,npins,lcsc='',dnp=False): + props=[('Reference',ref,x-3,y-3,False),('Value',val,x+3,y-3,False),('Footprint',fp,x,y,True), + ('Datasheet','~',x,y,True),('Description','',x,y,True)] + if lcsc: props.append(('LCSC',lcsc,x,y,True)) + p='' + for nm,v,px_,py_,hide in props: + h='\n\t\t\t(hide yes)' if hide else '' + p+=('\t\t(property "%s" "%s"\n\t\t\t(at %s %s 0)%s\n\t\t\t(show_name no)\n\t\t\t(do_not_autoplace no)\n' + '\t\t\t(effects\n\t\t\t\t(font\n\t\t\t\t\t(size 1.27 1.27)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n')%(nm,v,f(px_),f(py_),h) + pn=''.join('\t\t(pin "%s"\n\t\t\t(uuid "%s")\n\t\t)\n'%(n,uuid.uuid4()) for n in npins) + return ('\n\t(symbol\n\t\t(lib_id "%s")\n\t\t(at %s %s 0)\n\t\t(unit 1)\n\t\t(body_style 1)\n' + '\t\t(exclude_from_sim no)\n\t\t(in_bom yes)\n\t\t(on_board yes)\n\t\t(in_pos_files yes)\n' + '\t\t(dnp %s)\n\t\t(uuid "%s")\n%s%s\t\t(instances\n\t\t\t(project "hbox-watchdog-v6"\n' + '\t\t\t\t(path "%s"\n\t\t\t\t\t(reference "%s")\n\t\t\t\t\t(unit 1)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n\t)' + )%(lib_id,f(x),f(y),'yes' if dnp else 'no',uuid.uuid4(),p,pn,ROOT,ref) +def place(lib_id,ref,val,x,y,fp,netmap,lcsc='',dnp=False,src=None): + global add + pp=libpins(src,lib_id.split(':',1)[1]) if src is not None else cachedpins(lib_id) + if not pp: sys.exit('no pins for %s'%lib_id) + add+=mksym(lib_id,ref,val,x,y,fp,[n for n,_,_,_ in pp],lcsc,dnp) + for num,_,px,py in pp: + rot,just=orient(px,py) + add_lab(netmap[num],x+px,y-py,rot,just) + +UFLFP='Drogheda-v6:RF-SMD_FRF05002-JSS103M' +# ----- S1: GNSS chain ----- +place('Drogheda-v6:U.FL-R-SMT-1','J12','UFL-GNSS',400.0,155.0,UFLFP,{'1':'RF_ANT_GNSS','2':'GND','3':'GND'},'C88374',src=vsym) +place('Device:C','C143','100nF-RF',418.0,150.0,'Capacitor_SMD:C_0402_1005Metric',{'1':'RF_ANT_GNSS','2':'RF_GNSS'}) +place('Device:L','L21','68nH',424.0,160.0,'Inductor_SMD:L_0603_1608Metric',{'1':'RF_ANT_GNSS','2':'VCC_RF_GNSS'}) +place('Device:C','C144','100nF',432.0,160.0,'Capacitor_SMD:C_0402_1005Metric',{'1':'VCC_RF_GNSS','2':'GND'}) +place('Device:C','C145','100pF',438.0,160.0,'Capacitor_SMD:C_0402_1005Metric',{'1':'VCC_RF_GNSS','2':'GND'}) +place('Drogheda-v6:'+ESDN,'D27','ESD9L5.0ST5G',406.0,165.0,ESDFP,{KP:'RF_ANT_GNSS',AP:'GND'},'C82326',src=vsym) +# ----- S2: LTE chain ----- +place('Device:C','C146','DNP-pi',296.0,262.0,'Capacitor_SMD:C_0402_1005Metric',{'1':'RF_LTE','2':'GND'},dnp=True) +place('Device:R','R118','0R',302.0,262.0,'Resistor_SMD:R_0402_1005Metric',{'1':'RF_LTE','2':'RF_ANT_LTE'}) +place('Device:C','C147','DNP-pi',308.0,262.0,'Capacitor_SMD:C_0402_1005Metric',{'1':'RF_ANT_LTE','2':'GND'},dnp=True) +place('Drogheda-v6:U.FL-R-SMT-1','J13','UFL-LTE',330.0,262.0,UFLFP,{'1':'RF_ANT_LTE','2':'GND','3':'GND'},'C88374',src=vsym) +place('Drogheda-v6:'+ESDN,'D28','ESD9L5.0ST5G',344.0,262.0,ESDFP,{KP:'RF_ANT_LTE',AP:'GND'},'C82326',src=vsym) +# ----- P17: u-center USB port ----- +place('Connector_Generic:Conn_01x04','P17','GNSS-USB-UCENTER',410.0,185.0, + 'Connector_JST:JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical', + {'1':'GNSS_VBUS','2':'GNSS_USB_DM','3':'GNSS_USB_DP','4':'GND'}) +place('Device:C','C148','1uF',428.0,185.0,'Capacitor_SMD:C_0603_1608Metric',{'1':'GNSS_VBUS','2':'GND'}) +d14lib=None +for b in re.finditer(r'\n\t\(symbol\n(?:(?!\n\t\(symbol\n).)*',sch,re.S): + blk=b.group(0) + if re.search(r'\(property "Reference" "D14"',blk): + d14lib=re.search(r'\(lib_id "([^"]+)"',blk).group(1); break +for ref,sig,x in [('D29','GNSS_USB_DM',436.0),('D30','GNSS_USB_DP',442.0),('D31','GNSS_VBUS',448.0)]: + place(d14lib,ref,'PESD5V0S1BA',x,185.0,'Diode_SMD:D_SOD-323',{'1':sig,'2':'GND'},'C2827694') +# labels on U18 USB pins (replacing the no_connects removed below) +add_lab('GNSS_VBUS',436.98,107.3,180,'right bottom') +add_lab('GNSS_USB_DM',503.02,116.19,0,'left bottom') +add_lab('GNSS_USB_DP',503.02,118.73,0,'left bottom') + +# --- remove the 3 no_connects on U18 V_USB / USB_DM / USB_DP --- +STALE={(436.98,107.3),(503.02,116.19),(503.02,118.73)} +removed=0; out=[]; last=0 +for m in re.finditer(r'\n\t\(no_connect\s*\n\s*\(at ([-\d.]+) ([-\d.]+)\)(?:(?!\n\t\().)*?\n\t\)',sch,re.S): + c=(round(float(m.group(1)),3),round(float(m.group(2)),3)) + if c in STALE: + out.append(sch[last:m.start()]); last=m.end(); removed+=1 +out.append(sch[last:]); sch=''.join(out) + +# --- collision guard --- +labs=set() +for m in re.finditer(r'\((?:label|global_label) "[^"]+"\s*\n\s*\(at ([-\d.]+) ([-\d.]+)',sch): + labs.add((round(float(m.group(1)),3),round(float(m.group(2)),3))) +wire_vts=set() +for m in re.finditer(r'\(wire\s*\n\s*\(pts\s*\n((?:\s*\(xy [-\d.]+ [-\d.]+\)\s*\n?)+)',sch): + for a,b in re.findall(r'\(xy ([-\d.]+) ([-\d.]+)\)',m.group(1)): + wire_vts.add((round(float(a),3),round(float(b),3))) +pins=set() +for b in re.finditer(r'\n\t\(symbol\n(?:(?!\n\t\(symbol\n).)*',sch,re.S): + blk=b.group(0) + l=re.search(r'\(lib_id "([^"]+)"',blk); a=re.search(r'\(at ([-\d.]+) ([-\d.]+) (\d+)\)',blk) + if not(l and a): continue + for pp in (cachedpins(l.group(1)) or []): + pins.add((round(float(a.group(1))+pp[2],3),round(float(a.group(2))-pp[3],3))) +INTENTIONAL={(436.98,107.3),(503.02,116.19),(503.02,118.73)} +existing=(labs|wire_vts|pins)-INTENTIONAL +coll=[(n,x,y) for n,x,y in newlabels if (x,y) in existing] +if coll: sys.exit('COLLISION: %s'%coll[:6]) + +end=sch.rstrip(); assert end.endswith(')') +sch=end[:-1].rstrip('\n')+add+'\n)\n' + +# --- netclasses in the .kicad_pro (JSON) --- +pro=json.load(open(PRO,encoding='utf-8')) +ns=pro.setdefault('net_settings',{}) +classes=ns.setdefault('classes',[]) +names=[c.get('name') for c in classes] +proto=None +for c in classes: + if c.get('name') not in ('Default',None): proto=c; break +if proto is None and classes: proto=classes[0] +def mkclass(name,width,clearance): + base=dict(proto) if proto else {} + base.update({'name':name,'track_width':width,'clearance':clearance}) + base.pop('nets',None) + return base +added_classes=[] +if 'RF' not in names: + classes.append(mkclass('RF',0.35,0.25)); added_classes.append('RF') +if 'PWR_MODEM' not in names: + classes.append(mkclass('PWR_MODEM',1.5,0.2)); added_classes.append('PWR_MODEM') +ncp=ns.setdefault('netclass_patterns',[]) +pats={p.get('pattern') for p in ncp} +for net,cls in [('/RF_GNSS','RF'),('/RF_ANT_GNSS','RF'),('/RF_LTE','RF'),('/RF_ANT_LTE','RF'), + ('/VBAT_MODEM','PWR_MODEM'),('/VIN_MODEM','PWR_MODEM'),('/SW_MODEM','PWR_MODEM')]: + if net not in pats: + ncp.append({'netclass':cls,'pattern':net}) + +print('added: J12+bias tee (C143 DC block, L21 68nH, C144/C145), D27 | J13+pi (C146/C147 DNP, R118 0R), D28') +print('P17 u-center port + C148 + D29/D30/D31 PESD | U18 USB labels on, %d no_connects removed'%removed) +print('netclasses added: %s + 7 net patterns'%added_classes) +print('labels added: %d | paren: %d'%(nlab,bal(sch))) +if not APPLY: + print('\nDRY RUN - nothing written. Re-run with --apply.'); sys.exit(0) +if bal(sch)!=0: sys.exit('REFUSING: paren imbalance') +shutil.copy2(SCH,SCH+'.T6bak'); shutil.copy2(PRO,PRO+'.T6bak') +open(SCH,'w',encoding='utf-8').write(sch) +json.dump(pro,open(PRO,'w',encoding='utf-8'),indent=2) +print('\nAPPLIED. Backups: *.T6bak')