meteo-carrier — PCB design brief

One board that connects an ESP32 module to its sensors, its satellite modem, and an off-board solar + battery power system.

Prepared 2026-08-17, revised 2026-11-04 · written for an external PCB designer, which is why it still says “you”; since 2026-10-02 the board is laid out in the project itself (KiCad 10, device/hardware/kicad/meteo-carrier/, ADR 0017) and fabbed — rev. 4, five boards assembled, JLCPCB W2026100309193023, in hand since 2026-10-13, boards 1–3 built and level since 2026-10-28 — and this page is its plain-language statement · condensed from the project's carrier specs (2026-08-09-meteo-carrier-pcb-design.md = the electrical contract, 2026-08-15-carrier-pcb-order-and-proto-design.md = the realization). Where this page and those specs disagree, the specs win — except the netlist, whose source is connection-table.csv and the KiCad scripts (ADR 0017); the spec's own table is stale (DOC-11). Numbers are tagged [M] measured, [D] datasheet, [C] computed.

1What this device is, in 60 seconds

An autonomous weather probe sealed inside a Ø200 mm floating sphere. It sleeps almost all the time on a battery, wakes hourly to read sensors, and powers a satellite modem for ~48.5 s [C] a window to transmit: once a day on LAND, every 3 h on SEA. Because it sleeps for months, the electrical design is dominated by one number: the sleeping floor, ~648 µA as built [C] (board 1 read 0.73 mA at JP1 [M]; boards 2–3 are still owed, FW-30, FW-31). Two LEDs left in on purpose are nine-tenths of it: the FireBeetle's at ~500 µA [D] and the gauge breakout's at ~100 µA [C] (decided 2026-11-03). The carrier's own share is a few microamps: the LDO's 1.6 µA [D] and 0.5 µA [D] a switch. Everything hungry (GPS, modem) sits behind its own power switch.

Three copies of the board fly in probes that can never be serviced again; two more are spares, untouched since stage 0. So: five identical boards, boring and reworkable beats clever and dense.

2The key idea: power generation lives OFF the board

This is the part that most needs saying up front. The solar panels, the battery pack, and the charger are three separate physical assemblies that never touch the PCB you are designing:

  1. Solar array — 3 small panels (Voltaic P123, 6 V class, 0.64 W each [D]) mounted in the hull's crown. They are OR-ed through one Schottky diode each on a small combiner board next to the panels. One two-wire cable comes down from it.
  2. Battery pack — 6 × 18650 Li-ion cells (20.7 Ah nominal [C], from 3450 mAh [D] each) lying in bays printed straight into the battery platform, low in the hull, with two 10 kΩ NTC thermistors at the pack: one meant for the charger's THERM pad, one for your board. They are separate parts on purpose — see the note in §5.
  3. Charger board — an off-the-shelf Adafruit bq24074 breakout (#4755, since 15 October — ADR 0018), mounted on its own pillars next to the carrier. The solar cable and the battery leads both land here. It charges the pack and provides the power path. Its THERM pad carries a 10 kΩ NTC in parallel with 200 kΩ and blocks charging in the cold in hardware. Ice-tested on 2026-11-01/02: charger 1 stops at about −2.3 °C, charger 2 at about −2.0 °C, charger 3 between −0.4 and −1.0 °C, and all three resume just above 0 °C [C]; accepted as built on 2026-11-03 (ADR 0018, decision 3). The gate is proven by ice and the ISET pad falling to zero, never by the charge LED: charger 1's went dark in a stop and stayed dark while charging [M]. The pad sits behind a printed solder link that is cut with a knife, which is the whole reason this board replaced the bq25185, whose TS pin has no pad. It has no boost, so there is nothing to tie off.
The whole power interface to your board is battery voltage and ground — two conductors: BATT+ · GND. That is all. The 5 V and EN conductors this brief used to specify were deleted in September when the modem moved onto the battery rail. No solar wiring, no cell wiring, no charge current ever crosses the PCB. (Corollary: do not add solar terminals or OR-ing diodes to the board — an earlier revision had them and they were deliberately deleted; a second series diode would cost the array real voltage.)
The charger is the bq24074 (#4755), chosen 15 October (ADR 0018), reversing the 8 September pick of the bq25185, whose thermistor pin turned out to have no pad. Both boards present the same two conductors, which is why the charger connects by cable to a connector rather than by a footprint. Keep the board charger-agnostic; nothing about the layout turns on which one is fitted.

3The system at a glance

OFF-BOARD (NOT YOUR PCB) 3× solar panels P123 · 6 V · 0.64 W in the hull crown combiner 3× Schottky OR its own small board battery pack 6× 18650 · 20.7 Ah [C] + 2× 10 kΩ NTC (THERM pad + carrier) charger board bq24074 (#4755) charge + power path THERM cold gate no boost THM link cut 0.5 A setting SOLAR IN (2-wire) BAT NTC → carrier the second thermistor leak pads 2 screws on hull wall DS18B20 sea-temp probe (1-Wire) THE CARRIER BOARD meteo-carrier 2-layer · 90 × 70 mm · 4× M3 charger port — 2 conductors BATT+ · GND (size: see §5) Q1 → F1 polyfuse → MCP1700 LDO always-on 3V3, 1.6 µA Iq JP1 / JP2 metering jumpers 2× MCU pin rows (male) the FireBeetle plugs on, can side down 2× TPS22918 load switch GPS 3V3 (IO25) · modem VBAT (IO26) QW1 Qwiic — I²C0 always-on QW2 Qwiic — I²C1 switched SOCK_BARO, SOCK_BME — I²C0 XH-5 RockBLOCK port VBAT · GND · RX · TX · On/Off XH-3 sensor ports ×3 DS18B20 · NTC · leak (keyed) 0805 passives (fab-placed) pull-downs, pull-up, dividers, caps PLUG-IN MODULES ESP32 module FireBeetle 2 ESP32-E (can side down) I²C0 sensors BME280 (0x77) MAX17048 gauge (0x36) BMP581 baro (0x47) SAM-M8Q GPS I²C1, on the switched rail + 2nd BMP581 (0x47) RockBLOCK 9603 Iridium modem · UART 3.3 V VBAT pin 8 · 470 mA max
Amber = off-board assemblies (exist already; connect by cable). Blue = the carrier board. Green = modules that plug into it. The arrows into the board are its complete external interface: seven cable ports, as the harness map counts them (J5 the charger, J6 the modem, J7–J9 the three sensors, QW1 and QW2 the Qwiic cables), plus the module sockets and J10, which takes no cable. Figures in the diagram: 0.64 W a panel [D], 20.7 Ah [C], 1.6 µA [D], 470 mA [D].
off-board assembly the carrier PCB plug-in module

4What the board actually does

Functionally the carrier is four things:

  1. Makes the always-on 3.3 V rail. BATT+ → Q1 (a P-channel MOSFET that blocks a reversed pack, added 2026-10-02) → F1 polyfuse (1.1 A hold) → MCP1700-3302E LDO (TO-92, 1.6 µA [D] quiescent — this exact part: its Iq is the carrier's share of the sleep budget) → JP1 jumper → 3V3 to the MCU pin rows, QW1, SOCK_BARO and SOCK_BME.
  2. Switches the two hungry rails, with a load switch each. GPS: 3V3 → U2 TPS22918 → QW2, gated by IO25. Modem: VBAT → U3 TPS22918 → JP2 → the RockBLOCK port, gated by IO26. The modem rail is tapped ahead of F1, because a 1.1 A polyfuse carries real resistance and a few hundred mV at 500 mA [C] would eat the margin against the modem's 3.0 V minimum. The pack lead has already spent some of it before J5: the pack's T1.6A fuse is soldered inline in its + lead, the gauge sits inline, and two JST-PH plugs join them, and a resistive fuse dropped 0.5–1.1 V under load [M] (pack 3, 2026-11-02; five of the twenty fuses are bad and the part is in question, FW-28). So a fuse goes in with a heat sink on each leg, metered 0.000 on the 2k range first, and is read for 0.00 V across it with the board drawing before the hull closes. The gauge sits on the charger's side of the fuse, so a resistive fuse makes it read the charger, not the cell (4.23 V over a 3.54 V cell on pack 3 [M]). Both gate lines carry 100 kΩ pull-downs so neither rail can come up during boot, reset, or with no MCU fitted.
  3. Routes the buses. I²C0 (IO21/22) to the always-on sensors; I²C1 (IO18/19) to the GPS only (QW2, and on its second Qwiic port the second BMP581); UART (IO16/17) to the modem port; 1-Wire (IO4) to the DS18B20 port; two gated analog dividers (NTC, leak) into input-only ADC pins.
  4. Seats the modules. Two rows of male 2.54 mm pin headers for the DFRobot FireBeetle 2 ESP32-E — its own strips are female, so it plugs on can side down (the DevKit seat was dropped 2026-10-02) — plus a 1×7 female socket for the BMP581 breakout, a 1×4 + 1×6 seat SOCK_BME for the BME280 breakout (decided 2026-10-02, see §10), and two Qwiic (JST-SH) connectors.

5Connectors — the board's entire edge

All wire ports are JST-XH, top-entry, through-hole. The connector sizes double as keying; the two 3-pin sensor ports are additionally keyed by a vacant centre pin.

PortTypePin order (1→n)Goes to
ChargerXH-5, three positions vacantBATT+ · — · — · — · GNDbq24074 board (cable)
RockBLOCKXH-5VBAT · GND · RX · TX · On/Offmodem (3.3 V LVTTL UART — direct, no shifter). The sleep pin is now wired: the owned units are v3.D, which takes ordinary 3.3 V logic
DS18B20XH-3DATA · GND · 3V3 — this order is deliberate keying, keep itwaterproof temp probe
NTCXH-3, centre pin vacantSENSE_EXC (IO13) · — · NTC_SENSE (IO39)10 kΩ NTC at battery pack
LeakXH-3, centre pin vacantsense · — · GNDtwo pads on the hull wall
QW1 / QW2JST-SH 4-pin (Qwiic), SMDstandard QwiicQW1 → MAX17048 fuel gauge only; QW2 → the GPS, and the second BMP581 (SparkFun SEN-20171) on the GPS's second Qwiic port: I²C1, 0x47, powered only while the GPS rail is up (decided 2026-11-03; whether it stays on that rail is FW-29). The BME280 is a header board, not Qwiic — see §10
JP1 / JP22-pin 2.54 header + shuntin-line in 3V3 / in the switched modem railmetering + interlock (below)
J10 service1×5 male 2.54 headerVBAT · GND · 3V3 · IO0 · RSTnot a cable port: meter points, and boot/reset for a seated module whose own buttons face the board (added 2026-10-02). With the BMP581 seated it cannot be reached: the breakout overhangs it by ~2.5 mm [C] to its pin tips, so rev. 4 pulls the sensor to use it; a rev. 5 would move J10 clear (FW-18, parked)
Why the odd keying: connector sizes are the keying. On a plain 2-pin NTC port a mis-plugged 2-pin cable would put up to ≈ 8.5 V [C] on an ESP32 pin rated 3.6 V absolute max. With the vacant centre, any offset-inserted smaller housing lands one contact on a dead pin — open circuit, nothing happens. The DS18B20 pin order makes every remaining sensor-harness cross-plug electrically passive. Please don't "clean this up".
The charger port is XH-5 with three dead pins — decided 2026-09-09: this argument used to rest on the only 2-pin XH in the system being the solar cable. The charger port dropped from five conductors to two in September, so a plain XH-2 would have let a solar cable fit a port that feeds F1, the LDO (6 V absolute max) and the modem rail (5.4 V max). It is now an XH-5 body with BATT+ on position 1, GND on position 5 and 2–4 vacant: a 2-position housing spans only adjacent positions, so it can never bridge them, and the charger harness becomes a 5-position body that cannot enter any 3-position sensor port. Positions 2–4 are keying, not spare capacity — do not route to them, and the harness is crimped with its two contacts at the ends, not side by side.

Every cross-plug, and what it does

The keying above is only as good as the table that checks it. Re-derived 2026-09-09 for the port sizes as they stand after ADR 0014; it supersedes the residual cross-plug matrix in the rev. 3 order spec, which was computed when J5 was a populated XH-5 and J6 was XH-4. Both moved.

The whole table rests on one untested assumption: that a smaller JST-XH housing can be pushed into a larger XH shroud. Rev. 3 assumes it for the 3-position ports — that is the entire argument for the vacant centre pin — but never checked it for the 4- and 5-position ports, where it argued only “no same-size partner”. If the assumption is false, every smaller→larger cell below becomes CAN'T FIT and both surviving hazards disappear. Two minutes with connectors already in the drawer, and nothing in the tree records the answer either way.

✕ can’t fit — bigger housing, smaller shroud ○ open — a contact lands on nothing ◆ passive — connects, nothing damaged ▲ damage — a part is over its rating

Harness ↓ into port → J5 chargerXH-5 · live 1, 5 J6 modemXH-5 · all live J7 NTCXH-3 · centre vacant J8 DS18B20XH-3 · all live J9 leakXH-3 · centre vacant
SolarXH-2 · up to ≈ 8.5 V [C]○ Open1 and 5 never adjacent▲ Damage≈ 8.5 V [C] on the modem rail, or IO16/IO17○ Openone contact on the dead centre▲ Damage≈ 8.5 V [C] on IO4 or the 3V3 rail○ Openone contact on the dead centre
ChargerXH-5 · 3.0–4.2 V—its own port◆ Passivepack onto the modem rail, which normally carries it; GND onto IO14. Unverified: the audit of 2026-10-15 asks whether U3 conducts backwards when off, returning through IO14 (FW-25, finding 16)✕ Can't fit5-position body✕ Can't fit5-position body✕ Can't fit5-position body
ModemXH-5 · 5 wires◆ Passivesupply wire to BATT+, On/Off to GND, the rest on vacant pins—its own port✕ Can't fit5-position body✕ Can't fit5-position body✕ Can't fit5-position body
NTCXH-3 · contacts 1, 3○ Openat most one live pin reached◆ Passivea 10 kΩ thermistor bridging two pins; microamps—its own port◆ Passive3V3 at both ends through R3 — no static current◆ Passivea misread; both nets are gated dividers
DS18B20XH-3 · all three○ Openno return path through the vacant pins◆ Passivesensor rated to 5.5 V; the rail is 4.2 V◆ Passivesensor ground on the vacant pin — unpowered—its own port◆ Passivesensor ground on the vacant pin — unpowered
LeakXH-3 · contacts 1, 3○ Openat most one live pin reached◆ Passivetwo hull pads with no source behind them◆ Passivea misread; both nets are gated dividers◆ Passive~0 V across the pads — no electrolysis—its own port
Two hazards survive the keying, and neither is about J5. Sizing the charger port closes every cross that involves it and stops the charger harness reaching any sensor port. What it does not touch is the solar line. Both were to be closed by how the solar cable is built — decided 2026-10-03 — and one half of that is not as built. The solar line is not a carrier port — it runs combiner → charger. Its charger end is soldered into the charger’s two Opt. Cap holes, VBUS and GND, and its one XH-2 plug mates at the combiner board. The plan soldered the panels to the combiner, so that a plug pulled off it carried no panel voltage and the loose housing the Solar row of the table assumes did not exist. As built, every panel lead ends in an XH plug into a socket on the combiner’s pad pair, on all three combiner boards — a set of three crimped red-to-minus was caught by the sign check — so a parted panel plug is a live XH-2 at up to ≈ 8.5 V [C]. Whether the plugs stay or are soldered is open (FW-29). The thermistor lead also has a 2-pin XH break near the charger, labelled, not keyed, since the solar lead’s XHP-2 fits it too. Either way, do not put a connector at the charger end.

6Net list (the contract)

NetFromToNotes
VBATQ1 (from charger port pin 1, see the VBAT_IN row)F1 and U3 VIN, C7, J10 pin 1battery voltage behind the reverse-pack MOSFET, 3.0–4.2 V. The modem rail taps here, before F1 — see §4
VBAT_FF1MCP1700 VIN, C1 (1 µF)fused battery rail
3V3_RAWMCP1700 VOUTC2 (1 µF), C3 (10 µF), JP1.1
3V3JP1.2FireBeetle 3V3 (both pins) · QW1 VCC · SOCK_BARO, SOCK_BME · R6 → DS18B20 port · TPS22918 VIN, C10 (1 µF) · J10 pin 3the board rail
3V3_GPSTPS22918 VOUTQW2 VCCon ~18 min/day on SEA (a fix at every hourly wake) [C], one fix a day on LAND
GPS_ENMCU IO25TPS22918 ON + R1 100k→GNDrail off at boot / MCU absent
RB_ENMCU IO26U3 ON + R2 100k→GNDmodem rail must not rise in reset
SDA0/SCL0MCU IO21/IO22QW1, SOCK_BARO, SOCK_BMEalways-on I²C: BME280 0x77, MAX17048 0x36, BMP581 0x47
SDA1/SCL1MCU IO18/IO19QW2 only — the GPS and, on its second Qwiic port, the second BMP581 (0x47)switched GPS bus — no series parts
OW_DATAMCU IO4R7 → DS18B20 port DATA; R3 4.7k→3V3 on the MCU side1-Wire
SENSE_EXCMCU IO13NTC port pin 1 · R5 100koutput; high only during reads
NTC_SENSEMCU IO39NTC port pin 3 / R4 10k→GND junctioninput-only ADC1. Moved from IO34 on 2026-10-02: the FireBeetle ties IO34 to its own battery tap through a 1 MΩ divider
LEAK_SENSEMCU IO35R5 / leak port pin 1 junction + C6 100 nF→GNDinput-only ADC1; C6 fixes source impedance (τ = 10 ms)
VBAT_RBU3 VOUTJP2 pin 1switched modem rail; on ~48.5 s a window [C], eight a day on SEA (every 3 h), one on LAND
VBAT_RB_SWJP2 pin 2RockBLOCK port pin 1to the modem's pin 8: 3.0–5.4 V straight from the pack. No bulk capacitor — the module's own supercapacitors buffer the transmit burst and must never be removed
RB_ONOFFMCU IO14RockBLOCK port pin 5the modem's sleep input; v3.D takes ordinary 3.3 V logic, so the GPIO drives it directly
RB_RX / RB_TXMCU IO17 / IO16RockBLOCK port 3 / 4ESP32 transmits on IO17
VBAT → C7U3 VINC7 1 µF → GNDadded 2026-10-02: the load switch's input capacitor
VBAT_IN → Q1 → VBATcharger port pin 1Q1 drain → source; gate on GNDadded 2026-10-02 after the reviews: reverse-battery protection. A swapped crimp no longer puts the pack backwards across the board — the modem's feed bypasses F1, so the fuse never covered that
GPS_CT, RB_CTU2 pin 4, U3 pin 4C8, C9 4.7 nF → GNDadded 2026-10-02: soft start, a few ms instead of ~0.1 ms, so a breakout's capacitors cannot drag the 3V3 rail toward the ESP32's brown-out when a rail switches on
OW_3V3, OW_DATA_JR6, R7 (100 Ω)DS18B20 port pins 3 and 1added 2026-10-02: the one cable that carries the always-on rail into sea water; a flooded probe costs a thermometer, not the station
FB_VCCJP3 (solder jumper, open)MCU VCC pinadded 2026-10-02: the fallback feed through the module's own regulator, if the undocumented back-feed into its 3V3 pins proves costly. Measured since: the module's VCC node reads 3.29 / 3.14 / 3.24 → 3.02 V on three modules [M], following the module, not the board; whether to close JP3 is open, and energy does not decide it (FW-19)
VBAT, GND, 3V3, FB_IO0, FB_RSTThe rails, then the MCU's IO0 and RST pinsJ10, 1×5 service headeradded 2026-10-02: the seated module's own buttons face the carrier; each rail sits beside GND so a slipped probe never bridges the pack onto 3V3 (second review round, same day)
GNDsingle net, poured on both layersthe charger port's pin 5, the Qwiic shells, every ground pin

Spare MCU pins after this map: IO23 (bidirectional), IO36 (input-only ADC1), and IO34 only with its on-board 1 MΩ divider in mind — IO14 left the spare list when the modem's sleep pin was wired. Nothing uses ADC2 — that's deliberate (ADC2 dies when the radio is on).

7Bill of materials on the board

Fab-placed SMD (0805 unless noted)

  • U2, U3 — TPS22918DBVR, SOT-23-6 (two: GPS rail and modem rail)
  • QW1, QW2 — JST-SH 4-pin (SM04B-SRSS-TB)
  • R1, R2, R5 — 100 kΩ · R3 — 4.7 kΩ · R4 — 10 kΩ · R6, R7 — 100 Ω
  • C1, C2, C7, C10 — 1 µF · C3 — 10 µF
  • C6 — 100 nF · C8, C9 — 4.7 nF (0603)
  • Q1 — AO3401A P-channel MOSFET, SOT-23
  • JP3 — two bare pads, no part: a solder jumper left open
  • F1 — polyfuse, 1.1 A hold, 1812 (Bourns MF-MSMF110/16-2) — fab-placed since 2026-10-02

Hand-soldered through-hole (co-build)

  • U1 — MCP1700-3302E/TO, TO-92 (owned)
  • JP1, JP2 — 2-pin headers + shunts
  • J10 — 1×5 male service header
  • 5× wire ports: XH-5 for the charger (2 conductors), XH-5 for the modem, 3× XH-3 for the sensors. The XH-4 this brief used to specify is gone — check any header order placed against the older list
  • 2× male pin header rows for the FireBeetle (1×14, 1×18) + SOCK_BARO (1×7) and SOCK_BME (1×4 + 1×6) female sockets, 2.54 mm

8Layout rules that actually matter

9Mechanical + fab

10What was open, and how each was settled

11Bring-up sequence the design should support

This is how every board gets verified — worth knowing while placing parts, because it defines what must be probeable:

  1. Continuity: 3V3↔GND, VBAT↔GND not shorted.
  2. JP1/JP2 out, battery on charger port → F1 passes, 3.30 V ± 0.1 [D] at JP1 pin 1 (board 1 read 3.34 V [M]). Fit JP1. A plug is metered for sign before it mates: red probe on position 1, black on position 5, a plus sign expected, on the unmated pack plug. One pack plugged into the gauge reversed opened its fuse and killed the gauge and both bq24074 chargers; only Q1 saved the carrier, and the bench cable's XH end has no key against J5 (FW-27).
  3. No MCU: GPS_EN and RB_EN read 0 V (pull-downs working).
  4. MCU in → 3V3 at socket → flash.
  5. I²C0 scan finds 0x77 / 0x36 / 0x47; assert IO25 → I²C1 finds the GPS (0x42) and the second BMP581 (0x47); de-assert → rail collapses. A gauge's ID is not proof it works: two answered 0x36 with the right ID and a fixed, wrong millivolt reading [M]. Read it against a meter, and before and after the modem rail comes on, where a live gauge falls 0.1–0.19 V [M] within a second; a reading that does not move is no reading (FW-26).
  6. DS18B20 answers on 1-Wire; NTC reads mid-scale; damp finger across leak pads drops IO35.
  7. RockBLOCK last, meter in series at JP2. Cold, the first OK came at 7.0–12.0 s [M] (12.0 s, 7.0 s, then 9.0 s on both modems outdoors) and the meter read 0.45 A for ~13 s, then 0.13 A for ~10 s [M] — the module charging its supercapacitors before the Iridium side will do anything. The pack's low end is still unmeasured (FW-02).

Questions → back to me; the full specs (with every rationale) live in the project repo under docs/superpowers/specs/. This page prints cleanly — File → Print → Save as PDF.

Part of SouthPing — southping.org. This page is licensed CC BY 4.0: reuse it, adapt it, publish it, with credit to SouthPing and a link back. Source code in the project is Apache-2.0. Manufacturers' datasheets and product photographs remain theirs and are not covered by this grant.