Safeties and Protections¶
What the firmware does when voltage, current, temperature, or engine speed leaves the safe range. Each protection below lists its trigger, the action taken, and the variable names to grep for; user-adjustable thresholds persist in non-volatile storage (NVS, via settingWrite() / settingRead()), while a few are deliberately hardcoded.
The protections share a small set of mechanisms (ramp-down paths, immediate cuts, cooldown lockout). Those are defined once below, then referenced by name.
Notation and Shared Mechanisms¶
Two voltage sensors¶
BatteryV— measured by the ADS1115 analog converter. Used only for the cross-sensor disagreement check.IBV— measured by the INA228 battery monitor. The signal used by all software protections;getBatteryVoltage()returns it.
The INA228 also has a hardware alert pin that operates on the chip's own internally averaged voltage, independent of firmware.
Three shutdown paths¶
| Path | Behavior | Used for |
|---|---|---|
| Immediate cut | applyImmediateCut() — field enable line LOW within one control tick, PWM zeroed, control loop reset, system drops to fault state. No cooldown. |
Hardware overvoltage latch, hard overcurrent, critical temperature, engine speed below minimum |
Warning ramp (MODE_WARNING_RAMP_AND_LOCKOUT) |
Staged ramp-down: slew duty to the tach-keepalive floor at the normal ramp rate, optional hold, then a slow ramp to zero, then a settle period before the enable line is cut. Followed by the cooldown lockout. | Software hard overvoltage shutdown, sensor disagreement warning, temperature warning |
Critical ramp (MODE_CRITICAL_RAMP) |
Same machinery but skips the gradual phases — jumps straight to the final settle-and-cut. Slew limiting forced off. Same cooldown after. | Critical sensor disagreement, implausible voltage, stale temperature or current data |
The ramp shape is user-tunable (DutyRampRate, DutySlowRampRate, ShutdownPhase2HoldMs, SettleTimeBeforeCut). Search 6_functions.ino for runShutdownPath.
Cooldown lockout¶
After any warning- or critical-ramp shutdown, charging is locked out for a cooldown period (FIELD_COLLAPSE_DELAY, user-settable, default tens of seconds). When it elapses the system retries; if the fault persists, a new lockout begins. Immediate-cut faults do not use the cooldown — they latch or stay cut.
Arming scope of the throttling protections¶
The fast overvoltage trims (Groups 1–3) and load-dump detection arm only while the voltage-holding loop is engaged (voltageControlActive — absorption, float, and the voltage-holding override modes). Group 1 additionally waits until measured voltage is within a small guard band of the target (PRED_GUARD, hardcoded) so it cannot fire during normal ramp-up far below target. A dashboard test switch (testProtectionsEnabled) can disarm the trim layers for plant-characterization tests; the hard shutdowns are not affected by it.
Overvoltage Protections¶
Several distinct mechanisms, from gentle to absolute. Groups 1–3 are throttling protections: they keep charging but trim the current setpoint cap (fastOvCurrentCap) so voltage stops rising. Load dump and the hard shutdown ramp the field down. The INA228 hardware alert is the final backup that works even if firmware hangs.
Group 1 — prediction-based trim¶
Projects where voltage will be a fraction of a second from now using the smoothed rate of rise, and trims the current cap if the projection overshoots.
- Trigger: projected voltage (
IBVplus lookahead horizonTdPredtimes the smoothed rate-of-rise) exceeds the active target by a margin (OvPredMarginV). - Action: lowers
fastOvCurrentCapproportionally to the projected excess (slopeKHard, amps per volt) and setsfastOvClampActive, which also removes the duty slew limit so the current loop can collapse the field at full speed. - Knobs:
OvGroup1Enable,TdPred,OvPredMarginV,DvdtAlpha(rate-of-rise smoothing),KHard(shared with Group 2).
Group 2 — measured-voltage trim¶
Same trim action, but fires on present measured voltage with no prediction: IBV above target by OvMeasMarginV. Knobs: OvGroup2Enable, OvMeasMarginV, KHard.
Group 3 — current-excess supervisor (iExcess)¶
Catches overshoot before it shows in voltage, by noticing that measured alternator current is running well above the commanded setpoint at a moment when the voltage loop should be trimming back.
- Trigger: measured current exceeds the setpoint by
IExcessKamps forIExcessNconsecutive control ticks, while voltage is already near target. - Release hysteresis: stays latched until current falls a couple of amps back under the arming threshold (
IEXCESS_HYST, hardcoded) — prevents chatter. - Action: snaps the voltage-loop integrator (
cv_I) to zero (or bleeds it proportionally,IExcessKBleed), lowersfastOvCurrentCap, setsfastOvClampActive. On release, the integrator is re-seeded to a fraction of its pre-event value (IExcessReseedFrac) so it does not bounce straight back through the setpoint. - Signal source selectable (
IExcessSigSrc): moving average, exponential smoothing, or raw.
Load-dump detection¶
A three-tier rate-of-change detector on battery current — catches the upward current-slope spike when a big load is suddenly switched off. Each tier pairs a threshold with a persistence count: one very fast sample (hard-switched electronic disconnects), two consecutive fast samples (relay openings), or three consecutive moderate samples (slow contact separations). Two-in-a-row requirements reject the alternating-sign measurement noise of the current sensor.
- Gate: voltage loop engaged and the battery monitor in its fast sampling mode (field on).
- Action: integrator, setpoint, and current cap all collapse to zero at once; recovery is driven by the normal anti-windup bleed.
- Knobs:
LoadDumpDtThresh1/LoadDumpDtThresh/LoadDumpDtThresh3(amps per second, all user-settable). Search 6_functions.ino forLoadDumpDtThresh1.
Software hard shutdown (absolute trip)¶
The only software-layer absolute overvoltage shutdown: IBV above AlternatorHardShutdownV triggers the warning ramp plus cooldown. If voltage is well beyond the trip point (a hardcoded extra margin), the slew limit is bypassed and the first ramp phase becomes instant. The threshold default scales with the configured system voltage on first boot; once saved it is absolute — re-set it manually if you change system class. Active in automatic mode only.
INA228 hardware alert (hardware backup)¶
The fastest and most independent protection — it operates with no firmware involvement.
- Trigger: the chip's internally averaged bus voltage exceeds a programmed limit (
VoltageHardwareLimit, automatically maintained a fixed offset aboveBulkVoltage— seeupdateINA228OvervoltageThreshold()in 5_functions.ino). - Action: the chip's alert pin (active low, open drain) physically pulls the field enable line down — the gate driver loses power and the field collapses regardless of firmware state.
- Software latch:
CheckAlarms()polls the chip's alert register and setsinaOvervoltageLatched. While latched, the event selector reports the overvoltage reason at top priority andapplyImmediateCut()runs every tick. The latch is re-checked on a slow cadence and releases automatically once the chip reports clear. - Post-event suppression: for a short window after the latch clears (
INA_OV_DISAGREE_SUPPRESS_MS), the sensor-disagreement check is suppressed — the two sensors legitimately diverge while the field collapses. - Active in all modes, including manual and Limp Home.
Co-firing behavior¶
The software hard shutdown and the hardware alert sit at the same nominal threshold by design. The software path reads raw IBV and wins on fast transients; the hardware's averaged value catches slow ramps and firmware hangs. When both fire, applyImmediateCut() notices the enable line is already low and records state without redundant action.
Voltage Sensor Failure Protections¶
These do not detect a battery problem — they detect that the voltage sensors themselves disagree or read impossible values.
- Disagreement warning — the two sensors differ by more than
VoltageDisagreeThresholdcontinuously forVoltageDisagreeTimeout. Warning ramp + lockout. - Disagreement critical — a much larger difference (auto-scaled with system voltage class) sustained for a short hardcoded debounce, or either sensor returning not-a-number or near zero (those fire with no debounce). Critical ramp + lockout. Search 6_functions.ino for
isVoltageDisagreementCritical. - Implausibility — both sensors outside the plausible range for the system class (one bad sensor alone does not trip this). Critical ramp + lockout, no debounce. Search 6_functions.ino for
isVoltageSensorPlausible.
All three are active in automatic mode only.
Overvoltage Cascade — Illustrative 12 V Defaults¶
With factory defaults on a 12 V system, in order of escalation:
| Condition | Protection | Action |
|---|---|---|
| Voltage ~0.1 V over target | Group 2 | Trim current cap |
| Predicted voltage ~0.15 V over target | Group 1 | Trim current cap |
| Current excess while near target | Group 3 (iExcess) | Snap integrator, trim cap |
Voltage over the absolute trip (AlternatorHardShutdownV) |
Software hard shutdown | Warning ramp + lockout |
Chip-averaged voltage over VoltageHardwareLimit |
Hardware alert | Physical field cut |
| Well beyond the absolute trip | Slew-bypass accelerator | Ramp becomes instant |
| Both sensors outside plausible range | Implausibility | Critical ramp + lockout |
Note the reference difference: the Group 1–3 margins are relative to the active charging target (bulk, absorption, or float as appropriate), while the hard-shutdown thresholds are absolute.
Temperature Protections¶
All temperature protections act on TempToUse — the alternator temperature probe by default, or the thermistor input if selected (TempSource). Setting IgnoreTemperature disables T0–T3 below; the staleness and task-health checks (T4, T5) remain.
T0 — continuous thermal derating (the thermal PID)¶
Not a fault response — normal steady-state thermal management, described in full on Charging Control. It subtracts a current penalty (thermalPenaltyAmps) from the ceiling before the limit is ever reached, working from a predicted temperature (present value plus rate-of-rise times a lookahead horizon). The setpoint is the actual damage limit (TemperatureLimitF).
T1 — warning ramp¶
Temperature exceeds the limit by a small margin (TempWarnExcess): warning ramp + cooldown lockout. Automatic mode only.
T2 — sustained warning¶
The T1 condition held continuously for TempSustainedTimeout: the field is cut and stays off — no automatic restart, manual re-enable or reboot required. The escalation exists because repeatedly bouncing off the warning ramp means derating has failed.
T3 — critical immediate cut¶
Temperature exceeds the limit by a larger margin (TempCritExcess): applyImmediateCut(), no ramp. Active in all modes including manual — it is evaluated at top priority in the event selector (selectFieldEventReason()), ahead of the manual-mode branch. Manual does not bypass it.
T4 — temperature task hang¶
The temperature probe runs in its own task on the second CPU core; a heartbeat monitor (checkTempTaskHealth(), deadline TEMP_TASK_TIMEOUT) raises the alarm condition if the task goes silent. This raises the buzzer/latch condition; the actual field cut comes from T5, which fires on the same silence.
T5 — temperature data stale¶
The primary field-cut for any temperature monitoring failure: no validated reading for the staleness window (about twenty seconds), or readings that are not-a-number or wildly out of range. Critical ramp + lockout; recovers automatically on the first valid reading after the lockout clears. Covers sensor disconnects, CRC failures, and hung tasks alike.
Overcurrent Protections¶
Hard overcurrent (electronic fuse)¶
Measured alternator current above HardOCTripAmps for a short debounce (HardOCDebounceMs): applyImmediateCut(). The trip point is auto-derived a fixed margin above the user's maximum table current (MaxTableValue) and recomputed whenever that changes — search 3_functions.ino for HardOCTripAmps. Active in all modes including manual, at top priority.
Current alarms (buzzer only)¶
Two thresholds drive the buzzer but never touch the field: alternator current above CurrentAlarmHigh, and net battery current magnitude above MaximumAllowedBatteryAmps. Both via CheckAlarms() when alarms are armed.
Engine Speed Gate (RPM gate)¶
Engine speed below MinRPMForField (with IgnoreRPM off) cuts the field immediately — there is no reason to excite the field on a stopped or stalling engine. Active in all modes; no cooldown, recovers as soon as RPM returns.
System-Level Safeties¶
Hardware watchdog¶
A hardware watchdog timer (initialized via esp_task_wdt_init in Xregulator.ino) reboots the processor if the main loop stops feeding it for sixteen seconds. On reboot all GPIO pins reset low, which itself cuts the field driver — a hang fails safe.
Alarm Buzzer (GPIO21)¶
Driven by CheckAlarms() on a fixed cadence, aggregating every alarm condition into one output on GPIO21. The buzzer is informational only — it has no authority over the field.
Electrical output¶
The alarm output is a switched 5 V supply driven through a current-limited high-side switch (folds back at roughly a quarter amp, so shorts and oversized loads cannot damage the board) with a built-in flyback clamp diode, so relay coils and magnetic buzzers connect directly. Full circuit details and supported-load table: Warning Buzzer.
Alarm conditions¶
| Condition | Threshold setting |
|---|---|
| High alternator temperature | TempAlarm |
| Low alternator temperature | TempAlarmLow (zero disables) |
| High battery voltage | VoltageAlarmHigh |
| Low battery voltage | VoltageAlarmLow |
| High alternator current | CurrentAlarmHigh |
| High battery current | MaximumAllowedBatteryAmps |
| Hardware overvoltage latch | any INA228 alert event |
| Temperature sensor not responding | staleness window |
| Temperature task hung | heartbeat deadline |
The temperature-task-hang condition is raised regardless of the arm switch (console message and latch), but the physical output stays silent unless alarms are armed (AlarmActivate).
Alarm logic¶
How the three user toggles, the live condition, and the latch combine:
AlarmTest |
AlarmActivate |
AlarmLatchEnabled |
Condition | Latch | Output |
|---|---|---|---|---|---|
| on | any | any | any | — | ON (auto-clears after the test period) |
| off | off | any | any | any | OFF |
| off | on | off | none | — | OFF |
| off | on | off | tripped | — | ON |
| off | on | on | tripped | set | ON |
| off | on | on | cleared after a trip | still set | ON until latch reset |
| off | on | on | never tripped / latch reset | clear | OFF |
A latched alarm can be silenced by disarming, but returns on re-arm unless the latch is reset (ResetAlarmLatch). New conditions arriving while silenced still set the latch.
Dashboard controls¶
The Alarms panel exposes all of the above:

| Dashboard control | Firmware toggle |
|---|---|
| Alarm Enable (Off / Armed) | AlarmActivate |
| Alarm Status indicator | Alarm_Status |
| Temperature / voltage / current thresholds | the settings in the conditions table |
| Alarm Latch Mode (Momentary / Latched) | AlarmLatchEnabled |
| Test Buzzer | AlarmTest |
| Reset Latch | ResetAlarmLatch |
Mode-by-Mode Active Protections¶
| Protection | AUTO | MANUAL | LIMP HOME |
|---|---|---|---|
| Groups 1 / 2 / 3 (overvoltage trims) | Active (voltage loop engaged) | — | — |
| Load dump | Active | — | — |
| Software hard shutdown | Active | — | — |
| INA228 hardware alert (physical) | Active | Active | Active |
| INA228 software latch | Active | Active | Active |
| Sensor disagreement (warning + critical) | Active | — | — |
| Voltage implausible | Active | — | — |
| T0 thermal derating | Active | — | — |
| T1 / T2 temperature warning | Active | — | — |
| T3 critical temperature cut | Active | Active | — |
| T4 temperature task hang (alarm) | Active | Active | Active |
| T5 temperature data stale | Active | — | — |
| Hard overcurrent | Active | Active | — |
| RPM gate | Active | Active | — |
| Watchdog reboot | Active | Active | Active |
| Buzzer | When armed | Same | Same |
"—" means the priority chain in selectFieldControlMode() resolves the mode before that protection is evaluated, or Limp Home deliberately bypasses it. Limp Home keeps only the hardware-level protections — it exists for getting home on failed sensors.