What changed, and why
The original design [1] used two independent half-bridges — each sensor measured against its own local fixed-resistor reference, each with its own ADS1115 and LM4040. "Is there a difference between the two sensors" was answered by subtracting two independently-calibrated absolute readings in software.
The actual question this circuit needs to answer is narrower than two absolute temperatures: is there a difference, and which direction. Putting both thermistors as the two active arms of one bridge answers that directly, in hardware, instead of indirectly via software subtraction of two separately-erred measurements.
Why this topology
- Direction is unambiguous everywhere in range. NTC resistance and bridge midpoint voltage both move monotonically, so the sign of the differential output reliably tells you which sensor is colder — no sign reversal anywhere from -30°C to 45°C.
- Common-mode rejection is structural, not statistical. Excitation drift, ADC gain error, and the huge seasonal/diurnal swing itself are shared by both arms and cancel in the bridge output, instead of needing two independently-built boards to happen to agree.
- "Dimensionless signal detector" — the bridge's differential output is fundamentally a ratio (the relative mismatch between the two thermistor legs), not an absolute quantity. That's the core of the BBDSD framing.
- Resolution is much better on the signal that matters — both thermistors track the same ambient swing together, so only their small difference shows up in the reading, letting a much tighter ADC gain setting be used than a design where one leg is frozen at a fixed reference.
Zeroing: digital offset, not a hardware trim
An earlier revision of this design included a physical trim potentiometer to null the bridge at build time. That hardware has been removed — zeroing is done entirely digitally instead, using the same mechanism moon_temp_ads1115 already has proven in the field: an MQTT-settable per-channel offset, persisted to flash, added to the raw reading before publishing. No reflash needed to adjust it.
Calibration procedure: with both thermistors sitting together at the same known temperature, the digital offset is set so the bridge reading is zero. After that, the calibrated signal should read as pure noise around zero — that's the validation criterion. A real signal during an actual moonlight event is then judged against that established noise floor, not against zero directly.
Split power domains
Two separate rails, on purpose:
- +3V3 — always-on, same rail the ESP32 and I²C pull-ups already use. The ADS1115's own supply lives here — the ADC is never power-cycled.
- SWITCHED_VEXC — gated by a P-MOSFET, feeds only the bridge excitation path (bias resistor → LM4040 reference → the bridge itself).
With the ADC continuously powered, there's no ADC power-on/reference-startup uncertainty mixed into the read timing. The only settling time left to account for is the bridge's own RC time constant — a single, precisely-known, purely-bridge-limited quantity. Firmware can control exactly how long the bridge has been energized before triggering a conversion, without that number being confounded by ADC startup time — directly serving the accuracy goal for this measurement.
Worked numbers
MF58-family NTC (B57861S0103F045, B=3988K), -30°C to 45°C range. Bridge sensitivity peaks at balance (~25°C, where both arms are equal) and tapers toward both extremes:
| Ambient baseline | Sensitivity (dV/dΔT) |
|---|---|
| -30°C | ~6.1 mV/°C (worst case) |
| 25°C | ~23.0 mV/°C (peak, balanced) |
| 45°C | ~17.0 mV/°C |
Even at the worst-case cold end, the chosen ADC gain setting (±0.512V) covers a differential well beyond any plausible real effect without clipping, giving roughly ~780-2900 counts/°C depending on ambient baseline.
Full schematic & source
The complete KiCad schematic, BOM rationale, and design notes are in the project repository — more detail than fits cleanly in a web summary, and kept in sync with the actual buildable files.
github.com/rooster-ninja/moon-temp-tracer →