Drogheda/03-Architecture-Hardware/watchdog-pcb-v3-design.md

485 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
tags: [project-drogheda, hardware, watchdog, pcb, thermal, fans, schematic, v3]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-21
status: Active — design for review; bench-validate before fab
supersedes_section: hardware-respec-and-pcb-brief.md §4 (watchdog carrier detail)
---
# HBOX Watchdog Carrier PCB — v3.0 Design (Watchdog + Smart-Fan Thermal)
> ⚠️ **Current electrical state (2026-06-24): see
> [[03-Architecture-Hardware/watchdog-electrical-design-state|watchdog-electrical-design-state.md]]
> (CANONICAL).** Since this doc, the DFR1015 folded **on-board** (GX16 pin 3 = raw ignition), the
> **battery was re-added** (LTC4040 + NTC temp-qualified charging on the 1S4P pack), and **reset +
> 4-LED gauge + power-cutoff** were added. Where this doc disagrees with the canonical state, the
> canonical state wins. (Also: 1 fan, not "two" — see the 1-fan update.)
> The production PCB for the STM32F411 watchdog, **expanded** to drive two
> extraction fans and monitor internal temperature/humidity. This is the
> schematic-capture spec: every sub-circuit, every value, every part, the pin
> map, the firmware deltas, and the layout/fab path. Pairs with:
> [[03-Architecture-Hardware/watchdog-pcb-v3-bom|v3 BOM (xlsx)]] ·
> [[03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide|KiCad + JLCPCB guide]] ·
> [[04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2|firmware v2.1]] ·
> [[03-Architecture-Hardware/watchdog-schematic|as-built watchdog]].
**Decisions driving this revision (2026-06-21, Leo):**
| # | Decision | Consequence |
|---|---|---|
| 1 | **Scope = watchdog + thermal *carrier*** | Blackpill plugs on via 2×20 headers (reuse v2.1 firmware, no MCU bring-up). Mean Well SD-15B-05 and the battery stay **external modules**. Lowest-risk first board. |
| 2 | **5 V fan ×1** | One extraction fan, low-side (MOSFET PWM) + tach. ⚠ Loads the same 5 V the modem bursts on — mitigated with a **dedicated fan rail** (§0a). Needs a **passive filtered inlet** to pull air through, and has **no redundancy** — a fan failure falls back to conduction + thermal derate (tach-fault flags it). |
| 3 | **I²C temp + humidity** | SHT40 (board ambient + RH) + TMP102 probe (battery/modem hot spot) on one I²C bus. |
| 4 | **JLCPCB with assembly** | Parts chosen from JLC/LCSC stock; we output Gerbers + BOM + CPL. Footprints biased to JLC-placeable + hand-reworkable. |
| 5 | **Live telemetry ($PHLG): bench-test first, NOT built into v3** (2026-06-22) | The 4G modem keeps its proven home on the F9P **XBee socket = UART2**; there is **no spare UART** to tap (see §2a). v3 ships health on the **USB STREAM log** (already works). PA9/PA10 header is **reserved/DNP** only. The forwarding question is settled by an isolated **bench experiment** (§7a), not by a board change. |
---
## 0. Read this first — two engineering caveats
### 0a. 5 V fans must NOT share the RTK stack rail
The Mean Well **SD-15B-05** gives **5 V @ 3 A**. The RTK+4G stack already peaks
**~2–2.5 A on LTE-TX bursts** — the brownout the whole redesign is fixing. One
5 V fan adds **~0.15–0.25 A plus inrush**. Coincident worst case ≈ 2.7 A still
nears the supply limit, and **fan inrush + a modem burst at the same instant
is exactly the sag that drops the link.** So the board gives the fan its **own
5 V** from a small **24→5 V buck** tapped off the cabinet 24 V (which is already
inside the HBOX). A **0 Ω jumper (JP1)** lets you instead bridge the fan to the main
5 V — with a single ~0.2 A fan that's more defensible than with two, but the
dedicated rail stays the safe default. **Do not power the fan from the *switched*
(load-switch) stack 5 V** — it must run when hot even while the stack is power-cycling.
### 0b. Fans + IP67 is a real tradeoff — own it
**Moving air in/out of the box breaks the IP67 seal.** A fan only helps if it
*exchanges* HBOX/cabinet air with outside air; that path is an opening. The honest
position for the v3 mechanical:
- Ventilation drops the rating from **IP67 → ~IP54/IP55** at best, *with* filtered,
hooded, downward-facing louvers. In a **salty, humid, dusty port** that is a
maintenance item (filters clog/corrode).
- Keep every electronic mitigation we already have: **conformal-coat the PCB**
(acrylic/urethane), keep a **Gore pressure-vent** for condensation, and treat the
fans as **assist**, not the whole thermal solution. **Conduction to the metal
plate (hardware brief §2b) still does the heavy lifting**; fans buy the last few °C.
- The firmware runs the fans **only when temperature demands it** (closed loop, §7),
minimising the hours the vents are actively pulling dirty air.
> If ICAVE can accept IP54 with filter maintenance, fans are fine. If they require
> IP67, the answer is conduction + sunshade, not fans. **This board supports both** —
> populate or depopulate the fan section. Confirm the rating target with ICAVE.
---
## 1. What the close look at the watchdog found
The as-built watchdog (`watchdog-schematic.md`) + v2.1 firmware is **sound logic on
fragile hardware**. Keep the brains, fix the body.
**Keep (proven):**
- The **high-side power gate** concept (PA1 → stack 5 V), **fail-safe OFF** at boot
via the gate pull-up. This is the heart of 4G/NTRIP recovery — don't change its
behaviour, only its build quality.
- Three **sense dividers** (battery PA0, 5 V PA4, ignition PA5) and the v2.1
recovery state machine (backoff + LIMP + NMEA-silence + IWDG + MCU-thermal).
- USART2 (PA2/PA3) to the F9P UART1 for NMEA read + UBX rate.
**Fix (the liabilities):**
| Liability | Root issue | v3 board fix |
|---|---|---|
| 2N3904 + IRF9540N + pull-up, hand-wired | vibration intermittents; IRF9540N is **not** logic-level (marginal gate drive from a 5 V swing) | SMD discrete high-side switch (**AO3401A** logic-level P-FET + **MMBT3904**) + **PPTC** overcurrent; optional eFuse upgrade |
| LED + dividers on Dupont | mechanical | all on-board, 0603, conformal-coatable |
| `1.5 kΩ "????"` mystery R | undocumented | resolved on this schematic (it was the **LED series R**; dividers are 10k/10k — see §6) |
| MCU temp only | heat casualties are the **18650/modem**, not the die | **external** I²C sensors at the hot spot (§5) |
| No thermal *actuation* | firmware can derate but can't cool | **1 fan driver + closed-loop control** (§4, §7) |
| No SWD/header bring-out | can't debug a sealed unit | **SWD + USB + reserve-USART1** headers (§6) |
---
## 2. Expanded STM32F411 pin map (v2.1 pins kept; 6 added)
```
STM32F411 "Blackpill" (mounted on 2×20 headers)
── existing (firmware v2.1, unchanged) ───────────────────────────────────
PA0 BAT_SENSE ADC1_IN0 18650 ÷2 divider
PA1 PWR_CTRL GPIO out → RTK-stack load switch enable (HIGH = ON)
PA2 GNSS_TX USART2_TX → simpleRTK2B UART1 RX (UBX rate cmds)
PA3 GNSS_RX USART2_RX ← simpleRTK2B UART1 TX (NMEA read)
PA4 5V_SENSE ADC1_IN4 stack 5 V ÷2 divider (vehicle-power present)
PA5 IGN_SENSE GPIO in ignition via DFR1015 (INPUT_PULLDOWN)
PB0 EXT_LED GPIO out external status LED (low-side MOSFET)
PC13 LED GPIO out onboard LED (active-LOW)
── NEW for v3 (fan + temperature) ────────────────────────────────────────
PA6 FAN_PWM TIM3_CH1 25 kHz PWM → fan low-side gate
PB10 FAN_TACH EXTI fan tach (open-collector, 10k pull-up to 3V3)
PA7 (spare) TIM3_CH2 reserved — 2nd fan PWM if ever needed
PB12 (spare) EXTI reserved — 2nd fan tach if ever needed
PB6 I2C1_SCL I2C1 SHT40 + TMP102 (4.7k pull-ups to 3V3)
PB7 I2C1_SDA I2C1 SHT40 + TMP102
── reserved (do not populate now; future in-band $PHLG telemetry relay) ───
PA9 USART1_TX header → future: inject $PHLG into modem uplink
PA10 USART1_RX header ← future: F9P UART2 NMEA tap
PA13 SWDIO / PA14 SWCLK SWD debug header
PA11 USB_DM / PA12 USB_DP Blackpill micro-USB = STREAM log capture
```
No conflicts: TIM3_CH1 (PA6) does fan PWM; PB0 (TIM3_CH3) stays plain-GPIO
LED. The reserved USART1 pair (H3) **future-proofs** the board for the
[[03-Architecture-Hardware/watchdog-health-telemetry|`$PHLG` health-in-server]] upgrade
without a respin — but in v3 it is **left unpopulated; no relay is wired** (see §2a + the
decision row above). **PA10 caveat:** PA10 is USART1-RX *and* a known Blackpill DFU-entry
pin — a load/float here can block flash mode. Keep H3 unpopulated, or fit a **~22 kΩ
pull-up on PA10 to 3V3**, so this board never inherits the flaky-flashing gremlin.
### 2a. WeAct Black Pill header pinout — what lands on J1/J2
The module mounts on two 1×20 headers. From the WeAct **MiniF4x1Cx V3.0** schematic
(`MiniF4x1Cx_V30 SchDoc.pdf`, headers P1/P2), the physical pin order is (pin 1 = VBAT end):
| Pin | J1 = WeAct **P1** | carrier net | J2 = WeAct **P2** | carrier net |
|--:|---|---|---|---|
| 1 | VBAT | — | PB12 | — (spare) |
| 2 | PC13 | — (onboard LED) | PB13 | — |
| 3 | PC14 | — | PB14 | — |
| 4 | PC15 | — | PB15 | — |
| 5 | NRST | — | PA8 | — |
| 6 | PA0 | **BAT_SENSE** | PA9 | **USART1_TX** (reserve) |
| 7 | PA1 | **PWR_CTRL** | PA10 | **USART1_RX** (reserve, +Rdfu 22k) |
| 8 | PA2 | **GNSS_TX** | PA11 | — (USB) |
| 9 | PA3 | **GNSS_RX** | PA12 | — (USB) |
| 10 | PA4 | **5V_SENSE** | PA15 | — |
| 11 | PA5 | **IGN_SENSE** | PB3 | — |
| 12 | PA6 | **FAN_PWM** | PB4 | — |
| 13 | PA7 | — (spare) | PB5 | — |
| 14 | PB0 | **EXT_LED** | PB6 | **I2C_SCL** |
| 15 | PB1 | — | PB7 | **I2C_SDA** |
| 16 | PB2 | — (BOOT1, pulled low on-module) | PB8 | — |
| 17 | PB10 | **FAN_TACH** | PB9 | — |
| 18 | 3V3 | **3V3** (LDO out) | 5V | **MAIN5V** (in) |
| 19 | GND | **GND** | GND | **GND** |
| 20 | 5V | **MAIN5V** (in) | 3V3 | **3V3** (LDO out) |
**Consequences (caught from the real pinout):**
- **SWD (PA13/PA14) is NOT on the 2×20** — it's the module's separate 4-pin SWD header.
→ **H1 dropped**; flash/debug via the Blackpill **USB-C**.
- **5V pin = power IN, 3V3 pin = LDO OUT.** `MAIN5V` feeds the 5V pin; the module's 3V3
LDO output *is* the carrier `3V3` rail (sensors + I²C/tach pull-ups).
- **BOOT1 (PB2)** is pulled low on the module — no carrier action needed.
- USB (PA11/PA12) is on the header but unused (we flash via the USB-C connector).
### 2b. Why there is no spare UART (the telemetry reality)
The simpleRTK2B **Pro** breaks out **both** F9P UARTs, but only one is free:
| F9P port | Header pins | Used by | Free? |
|---|---|---|---|
| **UART1** | TX1 / RX1 | **watchdog** (NMEA read + UBX rate) — PA2/PA3 | taken |
| **UART2** | TX2 / RX2 **and the XBee socket** | **4G NTRIP Master** (server uplink) | **no — shared bus** |
`TX2` = F9P-UART2-TX = the **modem's DIN** (the wire the modem reads and forwards to the
server). The F9P already drives it, so the watchdog can't simply "connect a second wire" to
send `$PHLG` — that's two transmitters on one line. Getting health to the server therefore
needs a **relay** (interpose the MCU between F9P-UART2 and the modem), which is a deliberate
architecture change **deferred out of v3**. To *learn whether it's even possible*, run the
isolated bench experiment in **§7a** — it touches no PCB.
---
## 3. Block diagram
```text
Cabinet 24 V ──GX16-5(pin1/2)──► P1 [24V_IN]
│ ├─► reverse-pol P-FET (Q10) + TVS (D10, SMBJ33A) ─► 24V rail
│ │ ├──────────────────────────────► FAN buck in
│ (ignition 24V ─GX16 pin3─► DFR1015 ext module ─► P1[IGN] ─► PA5 divider)
│
Mean Well SD-15B-05 (EXTERNAL, 24→5 V 3 A) ──► P2 [5V_IN] ─► MAIN 5V rail
MAIN 5V ─► Blackpill 5V pin (onboard LDO → 3V3)
MAIN 5V ─► high-side load switch (Q1 AO3401A / U1 eFuse opt) ─EN◄PA1─► SW_5V ─► P3 simpleRTK2B + 4G
MAIN 5V ─► PA4 sense divider
18650 / PowerBoost (EXTERNAL) ──► P4 [BAT+ , BAT_SENSE] ─► PA0 divider
FAN rail: 24V ─► U2 buck (MP1584EN) ─► 5V_FAN (JP1: alt-bridge to MAIN 5V)
5V_FAN ───► Fan (+) ┌─── Fan tach ─10k─► PB10
Fan (−) ─ Q2 (AO3400) ◄PWM PA6 ┘ (D2 SS34 flyback ∥ fan)
I²C1 (PB6/7) ─┬─ SHT40 (U3, 0x44) board ambient temp + RH
└─ TMP102 (U4, 0x48) on P5 pigtail → battery/modem hot spot
PB0 ─► Q4 (AO3400) ─► status LED (D1) SWD/USB/USART1 headers (H1/H2/H3)
```
---
## 4. Sub-circuit detail (capture this exactly)
### 4.1 24 V input protection (light — the cabinet does the heavy lifting)
The cabinet already has the Phoenix SPD + breaker, so the board needs only:
- **Q10** reverse-polarity P-MOSFET in the 24 V high side. **⚠ Corrected (review Finding 1):
AO3401A (±12 V Vgs) and SI2305 (−20 V Vds) are NOT rated for a 24 V rail and would fail.**
Use **Vds ≥ −40 V, Vgs ≥ ±20 V** — e.g. **IRFR5305** (−55 V/±20 V, DPAK; verify LCSC stock);
the proto can use a TO-220 **IRF9540** (−100 V/±20 V). Add a **gate→source Zener clamp
DzG ≈ 15 V** (A→Q10.G, K→Q10.S) **with Rrp 10 k gate→GND**: the gate sits ~9 V, Vgs ≈ −15 V
(in rating, fully on). Buck input current is ~0.2 A — the part is sized by *rating*, not current.
- **D10** TVS **SMBJ33A** (33 V standoff) 24 V→GND, plus **C10** 100 µF/50 V
electrolytic + **C11** 100 nF bulk/decoupling.
### 4.2 Dedicated fan 5 V buck (U2) — see caveat §0a
- **U2 = MP1584EN** (SOP-8, 4.5–28 V in, up to 3 A) set to **5.0 V** out.
- FB divider for 5 V: **R20 = 100 kΩ top**, **R21 = 19.1 kΩ bottom** (Vout =
0.8 V·(1+R20/R21) ≈ 5.0 V — verify against the MP1584 datasheet FB = 0.8 V).
- **L2 = 10 µH** (≥1 A sat), **C22 = 22 µF** in, **C23 = 22 µF** out, freewheel
Schottky per datasheet (SS34), EN tied to 24V-rail-derived enable.
- **JP1**: 0 Ω / solder-jumper. **Open** = fans on U2 (recommended). **Closed** =
fans bridged to MAIN 5 V (only if SD-15B-05 is upsized; never both).
- Keep U2's switching node small; **keep this buck away from the GNSS antenna feed**
(RF cleanliness — see §8).
> Simpler alternative if you'd rather not lay out a buck on the first spin: drop U2,
> close **JP1**, and **upsize the external 5 V supply to a 5 V/5 A** unit so the rail
> has true headroom. The board supports it; it's a BOM/jumper choice, not a respin.
### 4.3 RTK-stack high-side load switch (Q1 trio — proven, SMD)
Keeps the as-built topology (so **firmware PA1 logic is unchanged**) but in
solid, sourceable SMD, plus a resettable fuse for the missing current limit:
```
MAIN 5V ──[F1 PPTC 3A]──┬───────────────[R2 10k]──┐
│ │
Q1 AO3401A Source Gate ── Q5 MMBT3904 Collector
(P-MOS) │ │
Drain ──► SW_5V ─► P3 stack Q5 Emitter ── GND
│
PA1 ──[R1 1k]── Q5 Base ───────────────┘
```
- PA1 **HIGH** → MMBT3904 on → P-FET gate low → **SW_5V ON**. PA1 LOW → R2 holds
gate high → **OFF** (fail-safe at boot). Identical to as-built.
- **AO3401A** is logic-level and switches fully from the 5 V gate swing (the old
**IRF9540N was not** — a real latent bug we're removing).
- **F1 PPTC** (3 A hold / ~6 A trip, e.g., **MF-MSMF300**) gives overcurrent
protection without a hard-to-source eFuse, and **cannot permanently latch a healthy
unit out** (it self-resets) — satisfies the "never black-hole" invariant.
- **C3 = 47 µF** bulk on SW_5V (modem-burst reservoir).
- **Optional upgrade footprint U1**: pin-compatible-ish current-limit load switch /
eFuse (e.g., **AP2553W6-7** SOT-23-6, EN active-high, FLAGn). Lay the footprint
*and* the discrete; populate one. Verify LCSC stock at order; **default = discrete**.
### 4.4 Fan driver ×1 (low-side PWM + tach) — Q2
Single extraction fan:
```
5V_FAN ──► Fan(+) Fan tach ──[R_t 1k]──┬──► PB10 (EXTI)
Fan(−) ──┬──► Q2 Drain │
D2 SS34 ∥ fan │ (AO3400A, N-MOS) [R 10k pull-up → 3V3]
(cathode→5V_FAN) │ Source ── GND
PWM PAx ─[Rg 100Ω]─ Gate ──[Rgs 100k → GND]
```
- **Q2 = AO3400A** logic-level N-MOSFET (SOT-23): full-on from 3.3 V PWM, 5.7 A /
30 V — vast margin over a <0.5 A fan.
- **D2 = SS34** Schottky **flyback** across the fan (motor is inductive — protects
the FET at PWM turn-off). Cathode to 5V_FAN, anode to drain.
- **Rg 100 Ω** gate series (tames edges/EMI), **Rgs 100 kΩ** gate→GND pull-down =
**fans OFF until the MCU drives PWM** (fail-safe; consistent with the ethos — if the
MCU is dead the stack is also unpowered, so there's nothing to cool).
- **Tach**: 3-wire fan's open-collector tach → **10 kΩ pull-up to 3.3 V** + **1 kΩ**
series into the EXTI pin. 2 pulses/rev → MCU computes RPM and flags a stalled/failed
fan. **Use 3-wire (or 4-wire) fans so tach exists.** PWM is on the **ground leg**, so
on a 3-wire fan the tach is valid only while PWM is high — firmware samples RPM during
a brief 100 % window (§7). *(4-wire fans avoid this entirely; if you can source 5 V
4-wire, drive their PWM pin from PAx via a 2N7002 open-drain and leave power
always-on — cleaner tach. 5 V 4-wire fans are rarer, hence 3-wire default.)*
### 4.5 Status LED (unchanged topology, SMD)
`3V3 ─[R3 1k]─►|D1 LED|─► Q4 (AO3400) drain; source→GND; PB0 ─ gate, [10k → GND]
pull-down.` (Resolves the "1.5 kΩ ????" — it was this series resistor; 1 kΩ is the
clean value for a 3.3 V drive.)
### 4.6 Sense conditioning (filtered; protect the ignition input)
| Net | Divider | RC filter | Protection |
|---|---|---|---|
| PA0 BAT (3.0–4.2 V) | R5 10k / R6 10k (÷2) | C5 100 nF | — |
| PA4 5V (≈5 V) | R7 10k / R8 10k (÷2) | C7 100 nF | — |
| PA5 IGN (from DFR1015) | **none — direct** | C9 100 nF | **Corrected (Finding 2): Rig 100 Ω series + D5 BAT54 clamp to 3V3, NO ÷2.** PA5 is a *digital* input (`INPUT_PULLDOWN`); the DFR1015 logic level reaches PA5 undivided (a ÷2 would drop it below VIH). Internal pulldown = LOW when ignition off; clamp caps ≤3.3 V. |
ADC stays ≤3.3 V with margin (verified in firmware-review). Firmware
`DIVIDER_RATIO = 2.0` matches the 10k/10k legs.
---
## 5. Temperature + humidity sensing (I²C1)
| Ref | Part | Addr | Where | Why |
|---|---|---|---|---|
| U3 | **SHT40-AD1B-R2** (DFN-4) | 0x44 | on-board, near an air-path edge | board-ambient **temp + RH** (RH flags condensation risk — IP/coastal) |
| U4 | **TMP102AIDRLR** (SOT-563) | 0x48 (ADD0→GND) | on **P5 pigtail** to the 18650/modem | the **actual heat casualty** spot — the data the thermal RCA needs |
- One bus, **4.7 kΩ pull-ups** (R30/R31) on SCL/SDA to 3.3 V. Both 3.3 V parts.
- 100 nF decoupling each. SHT40 wants a small keep-out/slot so it reads air, not board.
- Firmware reads all three temps (SHT40, TMP102, **plus** the existing MCU-internal)
→ triangulated thermal picture and the fan-control input (§7).
---
## 6. Connectors & headers (all keyed/locking)
| Ref | Connector | Pins | Net |
|---|---|---|---|
| P1 | GX16-5 (HBOX↔cabinet, **keep**) | 5 | 24V_IN, GND, IGN, (spare ×2) |
| P2 | Molex Micro-Fit 3.0, 2-pos | 2 | 5V_IN from SD-15B-05, GND |
| P3 | JST-XH 4-pos | 4 | SW_5V, GND, GNSS_TX(PA2), GNSS_RX(PA3) → simpleRTK2B |
| P4 | JST-XH 2-pos | 2 | BAT+ (to PA0 divider), GND (18650/PowerBoost) |
| P5 | JST-XH 4-pos | 4 | 3V3, GND, SDA, SCL → TMP102 probe pigtail |
| P6 | JST-XH 4-pos | 4 | 5V_FAN, Fan−(PWM), GND, Tach → fan |
| P8 | JST-XH 2-pos | 2 | EXT_LED+, EXT_LED− (panel LED) |
| H1 | 1×4 0.1″ | 4 | SWD: 3V3, SWDIO, SWCLK, GND |
| H2 | (Blackpill micro-USB) | — | STREAM log capture / flashing |
| H3 | 1×3 0.1″ **DNP** | 3 | reserve USART1: PA9, PA10, GND (future $PHLG) |
| — | 2× 2×20 female headers | 40 | Blackpill seats here |
> **Pin-1 / keying:** silkscreen pin-1 arrows + the GX16 keyway; JST-XH is already
> keyed. Label every connector net on silk (DFM/serviceability requirement).
---
## 7. Firmware deltas (spec only — bench-test before any field flash)
The v2.1 `.ino` changes are **small and additive** (the ethos: bench first; the
sandbox can't build STM32). New globals + functions, no state-machine surgery.
1. **Pins**: add `PIN_FAN_PWM PA6`, `PIN_FAN_TACH PB10` (PA7/PB12 stay spare);
init TIM3 CH1 PWM at **25 kHz**; attach an EXTI counter on the tach pin.
2. **I²C temps**: `Wire.begin()` on PB6/PB7; read SHT40 (0x44, temp+RH) and TMP102
(0x48). Add `tAirC`, `rhPct`, `tBatC` to telemetry. Keep MCU-internal as cross-check.
3. **Closed-loop fan curve** (input = `max(tAirC, tBatC)`):
- `< 45 °C` → fans **off** (0 % — minimise vent hours / dust ingress).
- `45–70 °C` → **linear 30 → 100 %** PWM.
- `≥ 70 °C` → **100 %**.
- **Hysteresis 3 °C** on the off→on edge (no chatter). This *replaces nothing* —
the existing `THERM_WARN_C 75` derate-to-30 s still stands as the last resort.
4. **Tach fault**: once/30 s, command a fan to **100 % for ~300 ms**, count tach edges,
compute RPM. `0 RPM while commanded > 0` → latch `FAN_FAULT`, log `EVT`, raise it in
telemetry. A dead fan in a hot box must be **visible**, not silent.
5. **Telemetry (on-unit only in v3)**: extend the v2.1 `TLM` record with
`tair,rh,tbat,fan_rpm,fan_pwm,fan_fault`, captured to the **USB STREAM log**.
These are the same fields a future `$PHLG`-to-server sentence would carry — but **v3 does
NOT push them live to the server** (no UART relay; see §2a + decision row 5). Live
telemetry is gated on the bench experiment below.
6. **IWDG-safe**: fan/tach/I²C work is non-blocking; keep petting the dog. I²C read uses
a short timeout so a stuck sensor can't hang the loop.
Pin map and control law live here; **implemented in v2.3** (`blackpillrtkcode_v2.ino`) —
closed-loop fan + I²C SHT40/TMP102 + tach fault, with MCU-temp fallback. **Bench-test before
any field flash** (the sandbox can't build STM32; the logic was host-verified).
### 7a. The one bench experiment that decides live telemetry (no PCB change)
> **✅ RESULT — 2026-06-22: PASS (no extra hardware needed).** Enabled `$PUBX` (a u-blox
> **proprietary** `$P…` talker — same class + checksum format as `$PHLG`) on the F9P
> **UART2** and watched the server `udp/5010` capture: `$PUBX,00,…` arrives **end-to-end,
> intact**, alongside GGA/RMC. → **The 4G NTRIP Master forwards proprietary `$P…`
> sentences, so `$PHLG` health-in-server is confirmed viable.** No XBee-breakout test
> needed. Remaining work = the **UART2 relay** (interpose the watchdog to inject `$PHLG`)
> \+ backend parser — a deliberate later board, still out of v3 scope.
>
> *Incidental (flagged for GNSS follow-up):* the same capture caught two live units —
> bench **H0014 (Querétaro) at SPS (GGA quality 1), RSSI −93**, and field
> **H0017 (Veracruz/ICAVE) at DGPS (quality 2, RMC mode `D`, diff base id 0133), RSSI −51**.
> Per Leo the bench is in a **basement** (poor sky + weak cellular) — which alone explains
> the bench SPS, since **RTK fix needs open sky (carrier-phase) regardless of
> corrections/config**. But note H0017 has *good* sky+signal and still only reaches **DGPS,
> not RTK (4/5)** → the **corrections pipeline** (free rtk2go caster + RAM-only F9P config)
> is a *separate* ceiling from antenna placement. One **open-sky test with a proper caster +
> FLASH-saved config** separates the two. (Reinforces the v2.1 SPS-thrash guard: a healthy
> basement/field unit may legitimately sit at SPS/DGPS — do **not** power-cycle it.)
Settles "does the 4G NTRIP Master forward a proprietary `$P…` sentence?" by **isolating the
modem** — the F9P is not involved, so there's no bus collision:
1. Remove the **4G NTRIP Master** from the F9P XBee socket; mount it on a bench/XBee adapter
with **3.3 V power, the SIM, and the LTE antenna**.
2. Wire **Blackpill PA9 (USART1 TX) → modem DIN** (the `TX2`/F9P-UART2-TX net in the socket)
and **modem DOUT → Blackpill PA10 (RX)** *or* a USB-UART so a PC can watch it.
3. Match the modem's UART baud (ArduSimple default **115200**, 3.3 V). Let it register.
4. Emit `$PHLG,2.1,RUN,LIMP,1,12,34192,4.01,5.02,63,71,2,1,420,IWDG*CS\r\n` and watch the
**server `tcpdump`**.
- **Appears →** modem forwards proprietary sentences → `$PHLG` is viable → *then* plan a
relay-capable board.
- **Doesn't →** modem whitelists talkers → fall back to a `$GPTXT,HLT,…` subtype
(special-cased by **field 1** so it never collides with the cell-id parse).
Either way, **v3 ships unchanged** — this experiment only informs a *later* decision.
---
## 8. Layout, stackup & DFM (for a relative beginner — the rules that matter)
- **Stackup**: **2-layer is acceptable** for this mostly-DC/low-speed board and is
cheaper; **4-layer** (GND + power inner planes) is better for the GNSS RF and the
buck noise — recommended if budget allows. Either way: **solid ground pour** on a
full layer.
- **Star-ground the power**: fan return currents (PWM, pulsed) and the modem-burst
return must **not** share a thin trace with the ADC/sensor grounds. Bring power
grounds to the pour at one stitching region; keep sensor/MCU ground quiet.
- **Buck (U2) discipline**: tiny switch-node copper, input cap **right at** the IC,
ground the cap to the pour with vias. **Keep U2 and the fan-PWM traces away from the
GNSS antenna pigtail / P3** — switcher harmonics degrade the ZED-F9P front-end (= the
watchdog's own "link health" signal).
- **Trace widths** (1 oz Cu): 24 V_IN and 5 V rails ≥ **0.5 mm** (fan + stack current);
SW_5V to P3 ≥ **0.8 mm** (modem bursts); signals 0.25 mm fine.
- **Thermal**: copper pour + vias under Q1/Q10/U2 tabs. Mounting holes positioned to
let the board **bolt to the cabinet metal plate** (conduction path, hardware brief §2b).
- **Creepage**: nothing >24 V here, so standard clearances are ample; still keep the
24 V input section tidy.
- **Conformal coat** after assembly (mask connectors/headers/SHT40 slot) — humidity +
vibration joint stability.
- **JLC specifics**: 0603 passives (hand-reworkable *and* machine-placeable),
SOT-23 actives, 1.6 mm FR-4, HASL or ENIG. Add **fiducials** if you want tight
placement. Put the JLC order number on silk (free, helps reorders).
---
## 9. Bring-up & test plan (prototype first)
1. **Bare-board power**: 24 V in, confirm **5V_FAN = 5.0 V**, reverse-polarity FET
blocks a flipped input, TVS not hot.
2. **Load switch**: toggle PA1 → SW_5V on/off; confirm OFF at reset; PPTC holds the
measured stack current, trips on a deliberate short.
3. **Fan**: ramp PA6 PWM 0→100 %, scope the gate + fan node, confirm flyback,
read **tach RPM** vs PWM; pull the fan → confirm `FAN_FAULT`.
4. **Sensors**: I²C scan finds 0x44 + 0x48; heat the TMP102 probe → fan ramps per curve.
5. **Power budget (the §0a check)**: with **JP1 open**, scope MAIN 5 V during an LTE
burst **and** a fan ramp simultaneously — confirm the stack rail no longer sags.
Repeat with JP1 closed to *demonstrate* the coupling you're avoiding.
6. **Full stack**: mount Blackpill + F9P + 4G, run v2.1+fan firmware, watch the STREAM
log for temps/RPM, confirm RTK recovery behaviour is unchanged.
7. Only then: pilot 2–3 boards in real cabinets before the fleet (mirrors the
August-30-unit ramp).
---
## 10. Open questions / decisions
- [ ] **IP rating target** with ICAVE: IP67 (no vents → conduction only) vs IP54/55
(filtered louvers + fans). Determines whether the fan section is populated.
- [ ] **Fan choice (×1)**: 5 V **3-wire** (tach), size (40/50/60 mm), + a **filtered passive inlet** so the single extraction fan can pull air through. Measured
static-pressure need through the chosen duct/filter. Source 2–3 candidates.
- [ ] **U2 buck vs upsize supply** (§4.2): lay the buck (default) or go 5 V/5 A external
+ JP1-closed. Recommend laying the buck for rail isolation.
- [x] **Battery — RESOLVED (Finding 3): default = dropped (vehicle-powered only)** — removes
the worst thermal/safety liability and makes the connection list correct as-is (MAIN5V =
SD-15B-05 powers the MCU; **PA4 senses the vehicle-side 5 V**; P4 = sense-only, battery
DNP). *If ICAVE needs parked tracking*, fit LiFePO4/supercap on P4 **and move the PA4
sense tap to the SD-15B-05/P2 (vehicle) node**, not the battery-backed rail.
- [x] ~~Confirm the **DFR1015** stays external or folds on-board~~ → **RESOLVED 2026-06-24: folds ON-BOARD.** GX16 pin 3 now carries raw machine ignition (12–24 V) into the on-board DFR1015 → Rig 100 Ω → D5 BAT54 clamp 3V3 → PA5 (digital, no divider).
- [ ] **Load switch**: discrete (default, sourceable) vs eFuse U1 — verify AP2553 LCSC
stock at order; otherwise ship discrete.
## Related
- [[CLAUDE|Master Index]] · [[03-Architecture-Hardware/watchdog-schematic|as-built watchdog]] ·
[[03-Architecture-Hardware/hardware-respec-and-pcb-brief|hardware re-spec + PCB brief]] ·
[[03-Architecture-Hardware/watchdog-health-telemetry|health telemetry ($PHLG)]] ·
[[03-Architecture-Hardware/BOM-master|BOM master]] ·
[[03-Architecture-Hardware/watchdog-pcb-v3-bom|v3 PCB BOM (xlsx)]] ·
[[03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide|KiCad + JLCPCB guide]]