Safety & Monitoring Systems¶
Overview¶
The regulator implements multiple independent safety systems to protect the alternator, battery, electrical system, and controller hardware. These systems operate at different response times and protection levels, from immediate hardware shutdowns to adaptive learning algorithms.
Protection Layer Architecture¶
Layer 1: Hardware Protection (< 1ms response)¶
- INA228 overvoltage detection: Independent hardware threshold monitoring
- GPIO emergency shutdown: Software-triggered immediate MOSFET disable
- Flyback diode protection: Field coil inductive spike suppression
Layer 2: Real-time Software Protection (50ms response)¶
- Emergency field collapse: Voltage spike detection and rapid shutdown
- Temperature limits: Immediate field reduction on overheating
- Current limits: Battery and alternator current protection
Layer 3: Adaptive Protection (seconds to minutes)¶
- Learning mode penalties: Thermal history-based current reduction
- Charging stage management: Bulk/float transitions for battery protection
- Dynamic sensor validation: Cross-checking redundant measurements
Layer 4: System Health Monitoring (continuous)¶
- Watchdog protection: 15-second hang detection with restart
- Memory monitoring: Heap and stack overflow detection
- Performance tracking: Loop time and CPU utilization analysis
Hardware Protection Systems¶
INA228 Overvoltage Protection¶
The INA228 provides independent hardware monitoring with configurable voltage thresholds:
void updateINA228OvervoltageThreshold() {
// Update the hardware limit based on current BulkVoltage setting
VoltageHardwareLimit = BulkVoltage + 0.1;
// Calculate threshold in LSB units for INA228 with proper rounding
const double LSB = 0.003125; // 3.125 mV/LSB
uint16_t thresholdLSB = (uint16_t)(VoltageHardwareLimit / LSB + 0.5);
// Program overvoltage threshold and clear under-voltage
INA.setBusOvervoltageTH(thresholdLSB);
INA.setBusUndervoltageTH(0x0000);
// Configure DIAG_ALRT behavior explicitly for predictable operation
INA.clearDiagnoseAlertBit(INA228_DIAG_SLOW_ALERT); // Compare on instantaneous readings
INA.clearDiagnoseAlertBit(INA228_DIAG_ALERT_LATCH); // Transparent mode
INA.clearDiagnoseAlertBit(INA228_DIAG_ALERT_POLARITY); // Active-low open-drain
INA.setDiagnoseAlertBit(INA228_DIAG_BUS_OVER_LIMIT); // Enable BUSOL reporting
// Verify what was actually written to the chip
uint16_t readback_BOVL = INA.getBusOvervoltageTH();
uint16_t readback_BUVL = INA.getBusUndervoltageTH();
queueConsoleMessage("INA228 readback: BOVL=0x" + String(readback_BOVL, HEX) +
" (" + String(readback_BOVL * LSB, 3) + "V), BUVL=0x" + String(readback_BUVL, HEX));
}
Operation: - Threshold: BulkVoltage + 0.1V (typically 14.0V for 12V systems) - Response time: <1ms hardware detection - Action: Sets status bit in DIAG_ALRT register - Recovery: Automatic when voltage drops below threshold
Monitoring Loop:
// Check for hardware overvoltage alert
uint16_t alertStatus = readINA228AlertRegister(INA.getAddress());
if (alertStatus & 0x0010) { // Check BUSOL bit (bit 4)
queueConsoleMessage("INA228 DIAG_ALRT: Overvoltage detected at " + String(IBV, 2) + "V");
}
Field Coil Protection¶
Inductive kickback protection prevents MOSFET damage during field switching:
Circuit Design:
Battery 12V → Field Coil (2-6Ω, ~50mH) → MOSFET → Ground
↘
Flyback Diode (1N4007 or equivalent)
Protection Mechanisms: - Flyback diode: Provides current path during MOSFET turn-off - MOSFET ratings: Selected for 2x maximum field current and voltage - PWM frequency: 15kHz above audible range, below EMI concerns
Real-Time Software Protection¶
Emergency Field Collapse¶
Immediate field shutdown on dangerous voltage spikes:
void emergencyFieldCollapse() {
if (currentBatteryVoltage > (ChargingVoltageTarget + 0.2)) {
digitalWrite(4, 0); // Immediate MOSFET disable
dutyCycle = MinDuty;
setDutyPercent((int)dutyCycle);
fieldCollapseTime = currentTime;
queueConsoleMessage("EMERGENCY: Field collapsed - voltage spike (" +
String(currentBatteryVoltage, 2) + "V)");
return; // Exit field control immediately
}
}
Recovery Logic:
// Maintain shutdown for 10 seconds
if (fieldCollapseTime > 0 && (currentTime - fieldCollapseTime) < FIELD_COLLAPSE_DELAY) {
digitalWrite(4, 0);
dutyCycle = MinDuty;
setDutyPercent((int)dutyCycle);
return;
}
// Clear flag and resume normal operation
if (fieldCollapseTime > 0 && (currentTime - fieldCollapseTime) >= FIELD_COLLAPSE_DELAY) {
fieldCollapseTime = 0;
queueConsoleMessage("Field collapse delay expired - normal operation resumed");
}
Parameters: - Trigger threshold: ChargingVoltageTarget + 0.2V - Lockout time: 10 seconds (FIELD_COLLAPSE_DELAY) - Response time: <50ms (next control cycle)
Temperature Protection¶
Hierarchical temperature protection with increasing severity:
void temperatureProtection() {
// Determine active temperature source
if (TempSource == 0) {
TempToUse = AlternatorTemperatureF; // OneWire DS18B20
} else {
TempToUse = temperatureThermistor; // ADS1115 thermistor
}
// Progressive protection levels
if (!IgnoreTemperature && TempToUse > TemperatureLimitF) {
if (dutyCycle > (MinDuty + 2 * dutyStep)) {
dutyCycle -= 2 * dutyStep; // Aggressive 1.6% reduction
queueConsoleMessage("Temperature limit reached, backing off...");
}
}
// Emergency temperature shutdown
if (TempToUse > (TemperatureLimitF + 20)) {
dutyCycle = MinDuty;
digitalWrite(4, 0);
queueConsoleMessage("EMERGENCY: Temperature excessive - field shutdown");
}
}
Temperature Sources: - OneWire DS18B20: Digital sensor, ±0.5°C accuracy, noise immune - Thermistor: Analog sensor via ADS1115, faster response - Source selection: User configurable via TempSource setting
Current Limiting¶
Multiple current protection systems prevent equipment damage:
void currentProtection() {
// Battery current protection
if (Bcur > MaximumAllowedBatteryAmps && dutyCycle > (MinDuty + dutyStep)) {
dutyCycle -= dutyStep;
queueConsoleMessage("Battery current limit reached, backing off...");
}
// Alternator current protection (implicit via voltage regulation)
// High alternator current → voltage drop → increased field → voltage recovery
// BMS current limit integration
if (bmsLogic == 1) {
bmsSignalActive = !digitalRead(36);
if ((bmsLogicLevelOff == 0 && !bmsSignalActive) ||
(bmsLogicLevelOff == 1 && bmsSignalActive)) {
// BMS requesting charge stop
chargingEnabled = false;
queueConsoleMessage("BMS requesting charge stop");
}
}
}
Voltage Protection¶
Multi-level voltage protection prevents overcharging:
void voltageProtection() {
float currentBatteryVoltage = getBatteryVoltage();
// Standard voltage regulation
if (currentBatteryVoltage > ChargingVoltageTarget &&
dutyCycle > (MinDuty + 3 * dutyStep)) {
dutyCycle -= 3 * dutyStep; // Most aggressive: 2.4% reduction
queueConsoleMessage("Voltage limit reached, backing off...");
}
// Cross-validation between voltage sensors
if (abs(BatteryV - IBV) > 0.1) {
queueConsoleMessage("Voltage sensor disagreement - Field shut off for safety!");
digitalWrite(33, HIGH); // Alarm
digitalWrite(4, 0); // Field disable
dutyCycle = MinDuty;
return;
}
}
Voltage Sources Compared:
- BatteryV: ADS1115 via voltage divider (1MΩ/75kΩ)
- IBV: INA228 high-precision measurement
- Cross-validation: 0.1V maximum disagreement allowed
Alarm System¶
Alarm Conditions¶
The system monitors multiple parameters and triggers alarms when limits are exceeded:
void CheckAlarms() {
static unsigned long lastRunTime = 0;
if (millis() - lastRunTime < 250) return; // 250ms update rate
lastRunTime = millis();
bool currentAlarmCondition = false;
String alarmReason = "";
if (AlarmActivate == 1) {
// Temperature alarm
if (TempAlarm > 0 && TempToUse > TempAlarm) {
currentAlarmCondition = true;
alarmReason = "High alternator temperature: " + String(TempToUse) +
"°F (limit: " + String(TempAlarm) + "°F)";
}
// Voltage alarms
float currentVoltage = getBatteryVoltage();
if (VoltageAlarmHigh > 0 && currentVoltage > VoltageAlarmHigh) {
currentAlarmCondition = true;
alarmReason = "High battery voltage: " + String(currentVoltage, 2) +
"V (limit: " + String(VoltageAlarmHigh) + "V)";
}
if (VoltageAlarmLow > 0 && currentVoltage < VoltageAlarmLow && currentVoltage > 8.0) {
currentAlarmCondition = true;
alarmReason = "Low battery voltage: " + String(currentVoltage, 2) +
"V (limit: " + String(VoltageAlarmLow) + "V)";
}
// Current alarms
if (CurrentAlarmHigh > 0 && MeasuredAmps > CurrentAlarmHigh) {
currentAlarmCondition = true;
alarmReason = "High alternator current: " + String(MeasuredAmps, 1) +
"A (limit: " + String(CurrentAlarmHigh) + "A)";
}
}
// Apply alarm output with latching logic
processAlarmOutput(currentAlarmCondition, alarmReason);
}
Alarm Processing and Latching¶
void processAlarmOutput(bool currentCondition, String reason) {
static bool previousAlarmState = false;
bool outputAlarmState = false;
// Handle alarm test (always works regardless of AlarmActivate)
if (AlarmTest == 1) {
if (alarmTestStartTime == 0) {
alarmTestStartTime = millis();
queueConsoleMessage("ALARM TEST: Testing buzzer for 2 seconds");
}
if (millis() - alarmTestStartTime < ALARM_TEST_DURATION) {
currentCondition = true;
} else {
AlarmTest = 0;
alarmTestStartTime = 0;
}
}
// Handle manual latch reset
if (ResetAlarmLatch == 1) {
alarmLatch = false;
ResetAlarmLatch = 0;
queueConsoleMessage("ALARM LATCH: Manually reset");
}
// Latching logic
if (AlarmLatchEnabled == 1) {
if (currentCondition) alarmLatch = true;
outputAlarmState = alarmLatch;
} else {
outputAlarmState = currentCondition;
}
// Final output control
bool finalOutput = false;
if (AlarmTest == 1 || (AlarmActivate == 1 && outputAlarmState)) {
finalOutput = true;
}
digitalWrite(33, finalOutput ? HIGH : LOW);
// Console messaging
if (currentCondition != previousAlarmState) {
if (currentCondition) {
queueConsoleMessage("ALARM ACTIVATED: " + reason);
} else if (AlarmLatchEnabled == 0) {
queueConsoleMessage("ALARM CLEARED");
}
previousAlarmState = currentCondition;
}
}
Alarm Features: - Configurable thresholds: User-adjustable limits for all parameters - Latching mode: Alarm stays on until manually reset - Test function: 2-second buzzer test - Manual reset: Web interface reset capability
System Health Monitoring¶
Watchdog Protection¶
The ESP32 dual-core architecture uses a 15-second watchdog timer to prevent system hangs and ensure safe operation:
What is a Watchdog? - Purpose: Detects when code gets stuck in infinite loops or hangs - Timeout: If the main task doesn't "feed" the watchdog within 15 seconds, the ESP32 automatically reboots - Safety benefit: During reboot, all GPIO pins reset to LOW, which turns OFF the alternator field - Hardware reset: Complete system restart ensures recovery from any software failure
Watchdog Setup:
void setupWatchdog() {
// Try to add main task to existing watchdog (Arduino framework pre-initializes)
esp_err_t add_result = esp_task_wdt_add(NULL);
if (add_result == ESP_OK) {
queueConsoleMessage("Watchdog already active - main task added");
} else {
// Create new watchdog if none exists
esp_task_wdt_config_t wdt_config = {
.timeout_ms = 15000, // 15 seconds
.idle_core_mask = 0, // Don't monitor idle cores
.trigger_panic = true // Reboot on timeout
};
esp_task_wdt_init(&wdt_config);
esp_task_wdt_add(NULL);
queueConsoleMessage("Watchdog created and main task added");
}
}
Core Architecture and Task Coverage: - Core 1: Main loop with field control - monitored by watchdog - Core 0: WiFi, system tasks, TempTask - separate monitoring - TempTask: Independent temperature reading task with separate health monitoring
Main Loop Watchdog Feeding:
void loop() {
esp_task_wdt_reset(); // Feed watchdog every loop iteration
// ... main loop code ...
}
TempTask Health Monitoring¶
The temperature reading task runs independently on Core 0 and has its own health monitoring system:
// TempTask health monitoring globals
unsigned long lastTempTaskHeartbeat = 0;
bool tempTaskHealthy = true;
const unsigned long TEMP_TASK_TIMEOUT = 20000; // 20 seconds
void TempTask(void *parameter) {
for (;;) {
// Update heartbeat to show task is alive
lastTempTaskHeartbeat = millis();
tempTaskHealthy = true;
// Temperature reading operations...
// Update heartbeat during long conversion waits
for (int i = 0; i < 25; i++) {
vTaskDelay(pdMS_TO_TICKS(200));
lastTempTaskHeartbeat = millis(); // Show task isn't hung
}
}
}
void checkTempTaskHealth() {
unsigned long now = millis();
// Check if TempTask is responding
if (now - lastTempTaskHeartbeat > TEMP_TASK_TIMEOUT) {
if (tempTaskHealthy) {
tempTaskHealthy = false;
queueConsoleMessage("CRITICAL: TempTask hung - reducing field for safety");
// Safety response: reduce field output
if (!IgnoreTemperature && dutyCycle > MinDuty + 20) {
dutyCycle = MinDuty + 10; // Conservative field setting
}
digitalWrite(33, HIGH); // Sound alarm
}
} else {
// TempTask recovered
if (!tempTaskHealthy) {
tempTaskHealthy = true;
queueConsoleMessage("TempTask: Recovered and responding normally");
}
}
}
Benefits: - Independent monitoring: Separate from main watchdog system - Safety response: Reduces field output when temperature monitoring fails - Automatic recovery: Detects when TempTask resumes normal operation - User notification: Console messages about temperature monitoring status
Memory Monitoring¶
Continuous heap and stack monitoring with early warning:
void updateSystemHealthMetrics() {
// Heap monitoring
rawFreeHeap = esp_get_free_heap_size();
FreeHeap = rawFreeHeap / 1024; // Convert to KB
MinFreeHeap = esp_get_minimum_free_heap_size() / 1024;
FreeInternalRam = heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024;
// Fragmentation calculation
if (rawFreeHeap == 0) {
Heapfrag = 100;
} else {
Heapfrag = 100 - ((heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) * 100) / rawFreeHeap);
}
// Critical heap warnings (throttled)
unsigned long now = millis();
if (FreeHeap < 20 && (now - lastHeapWarningTime > WARNING_THROTTLE_INTERVAL)) {
queueConsoleMessage("CRITICAL: Heap dangerously low (" + String(FreeHeap) + "KB)");
lastHeapWarningTime = now;
}
}
Stack Monitoring:
void printBasicTaskStackInfo() {
numTasks = uxTaskGetNumberOfTasks();
if (numTasks > MAX_TASKS) numTasks = MAX_TASKS;
tasksCaptured = uxTaskGetSystemState(taskArray, numTasks, NULL);
for (int i = 0; i < tasksCaptured; i++) {
stackBytes = taskArray[i].usStackHighWaterMark * sizeof(StackType_t);
const char* taskName = taskArray[i].pcTaskName;
if (stackBytes < 256) {
queueConsoleMessage("CRITICAL: " + String(taskName) + " stack very low (" +
String(stackBytes) + "B)");
}
}
}
Performance Monitoring¶
Loop timing and CPU utilization tracking:
void loop() {
starttime = esp_timer_get_time(); // Start timing
// ... main loop execution ...
endtime = esp_timer_get_time();
LoopTime = (endtime - starttime); // Microseconds
if (LoopTime > 5000000) { // 5 seconds
queueConsoleMessage("WARNING: Loop took " + String(LoopTime / 1000) +
"ms - potential watchdog risk");
}
// Track maximum times
if (LoopTime > MaximumLoopTime) MaximumLoopTime = LoopTime;
if (LoopTime > MaxLoopTime) MaxLoopTime = LoopTime; // Persistent across sessions
}
CPU Load Tracking:
void updateCpuLoad() {
// Get IDLE task runtime counters
for (int i = 0; i < taskCount; i++) {
if (strcmp(taskSnapshot[i].pcTaskName, "IDLE0") == 0) {
idle0Time = taskSnapshot[i].ulRunTimeCounter;
} else if (strcmp(taskSnapshot[i].pcTaskName, "IDLE1") == 0) {
idle1Time = taskSnapshot[i].ulRunTimeCounter;
}
}
// Calculate CPU load as percentage
unsigned long deltaIdle0 = idle0Time - lastIdle0Time;
unsigned long deltaIdle1 = idle1Time - lastIdle1Time;
unsigned long timeDiff = millis() - lastCheckTime;
cpuLoadCore0 = 100 - ((deltaIdle0 * 100) / (timeDiff * 100));
cpuLoadCore1 = 100 - ((deltaIdle1 * 100) / (timeDiff * 100));
cpuLoadCore0 = constrain(cpuLoadCore0, 0, 100);
cpuLoadCore1 = constrain(cpuLoadCore1, 0, 100);
}
Data Validation and Sensor Safety¶
Sensor Freshness Tracking¶
Data age monitoring prevents control decisions based on stale readings:
// Global data freshness system
enum DataIndex {
IDX_ALTERNATOR_TEMP = 0,
IDX_BATTERY_V,
IDX_MEASURED_AMPS,
// ... 17 total sensor data indices
MAX_DATA_INDICES = 17
};
unsigned long dataTimestamps[MAX_DATA_INDICES];
const unsigned long DATA_TIMEOUT = 10000; // 10 seconds
// Mark data fresh when successfully read
#define MARK_FRESH(index) dataTimestamps[index] = millis()
// Check if data is stale
#define IS_STALE(index) (millis() - dataTimestamps[index] > DATA_TIMEOUT)
// Conditional assignment for stale data
#define SET_IF_STALE(index, variable, staleValue) \
if (IS_STALE(index)) { variable = staleValue; }
Usage in Control Systems:
// Check temperature data freshness for safety
unsigned long tempAge = currentTime - dataTimestamps[IDX_ALTERNATOR_TEMP];
bool tempDataVeryStale = (tempAge > 30000); // 30 seconds
if (tempDataVeryStale) {
queueConsoleMessage("OneWire sensor stale - sensor dead or disconnected");
digitalWrite(33, HIGH); // Sound alarm
}
Sensor Cross-Validation¶
Multiple sensors for critical measurements with disagreement detection:
float getBatteryVoltage() {
float selectedVoltage = 0;
static unsigned long lastWarningTime = 0;
switch (BatteryVoltageSource) {
case 0: // INA228 (preferred)
if (!IS_STALE(IDX_IBV) && IBV > 8.0 && IBV < 70.0) {
selectedVoltage = IBV;
} else {
if (millis() - lastWarningTime > 10000) {
queueConsoleMessage("INA228 unavailable, falling back to ADS1115");
lastWarningTime = millis();
}
selectedVoltage = BatteryV; // Automatic fallback
}
break;
case 1: // ADS1115 (backup)
if (!IS_STALE(IDX_BATTERY_V) && BatteryV > 8.0 && BatteryV < 70.0) {
selectedVoltage = BatteryV;
} else {
queueConsoleMessage("ADS1115 unavailable, falling back to INA228");
selectedVoltage = IBV;
}
break;
}
// Final validation
if (selectedVoltage < 8.0 || selectedVoltage > 70.0 || isnan(selectedVoltage)) {
queueConsoleMessage("CRITICAL: No valid battery voltage found!");
selectedVoltage = 999; // Error indication
}
return selectedVoltage;
}
Thermal Stress Monitoring¶
Alternator Lifetime Modeling¶
Continuous thermal stress calculation based on Arrhenius equation:
void calculateThermalStress() {
unsigned long now = millis();
if (now - lastThermalUpdateTime < THERMAL_UPDATE_INTERVAL) return;
float elapsedSeconds = (now - lastThermalUpdateTime) / 1000.0f;
lastThermalUpdateTime = now;
// Calculate component temperatures
float T_winding_F = TempToUse + WindingTempOffset; // User-configurable offset
float T_bearing_F = TempToUse + 40.0f; // Fixed offset
float T_brush_F = TempToUse + 50.0f; // Fixed offset
// Calculate alternator RPM
float Alt_RPM = RPM * PulleyRatio;
// Calculate component life expectancies (hours)
float T_winding_K = (T_winding_F - 32.0f) * 5.0f / 9.0f + 273.15f;
float L_insul = L_REF_INSUL * exp(EA_INSULATION / BOLTZMANN_K *
(1.0f / T_winding_K - 1.0f / T_REF_K));
float L_grease = L_REF_GREASE * pow(0.5f, (T_bearing_F - 158.0f) / 18.0f) *
(6000.0f / max(Alt_RPM, 100.0f));
float temp_factor = 1.0f + 0.0025f * (T_brush_F - 150.0f);
float L_brush = (L_REF_BRUSH * 6000.0f / max(Alt_RPM, 100.0f)) / max(temp_factor, 0.1f);
// Accumulate damage over time
float hours_elapsed = elapsedSeconds / 3600.0f;
CumulativeInsulationDamage += hours_elapsed / L_insul;
CumulativeGreaseDamage += hours_elapsed / L_grease;
CumulativeBrushDamage += hours_elapsed / L_brush;
// Calculate remaining life percentages
InsulationLifePercent = (1.0f - CumulativeInsulationDamage) * 100.0f;
GreaseLifePercent = (1.0f - CumulativeGreaseDamage) * 100.0f;
BrushLifePercent = (1.0f - CumulativeBrushDamage) * 100.0f;
// Constrain to 0-100% range
InsulationLifePercent = constrain(InsulationLifePercent, 0.0f, 100.0f);
GreaseLifePercent = constrain(GreaseLifePercent, 0.0f, 100.0f);
BrushLifePercent = constrain(BrushLifePercent, 0.0f, 100.0f);
}
Thermal Model Constants:
- L_REF_INSUL: 100,000 hours at 100°C reference
- L_REF_GREASE: 40,000 hours at 158°F reference
- L_REF_BRUSH: 5,000 hours at 6000 RPM, 150°F
- EA_INSULATION: 1.0 eV activation energy
- BOLTZMANN_K: 8.617×10⁻⁵ eV/K
Console Logging and Debugging¶
Message Queue System¶
Thread-safe circular buffer for system messages:
// Fixed-size circular buffer for thread safety
struct ConsoleMessage {
char message[128]; // Fixed size prevents dynamic allocation
unsigned long timestamp;
};
#define CONSOLE_QUEUE_SIZE 10
ConsoleMessage consoleQueue[CONSOLE_QUEUE_SIZE];
volatile int consoleHead = 0;
volatile int consoleTail = 0;
volatile int consoleCount = 0;
void queueConsoleMessage(String message) {
// Prevent stack overflow from oversized messages
if (message.length() > 120) {
message = message.substring(0, 120) + "...";
}
// Thread-safe circular buffer operation
int nextHead = (consoleHead + 1) % CONSOLE_QUEUE_SIZE;
int localTail = consoleTail;
if (nextHead != localTail) { // Not full
strncpy(consoleQueue[consoleHead].message, message.c_str(), 127);
consoleQueue[consoleHead].message[127] = '\0';
consoleQueue[consoleHead].timestamp = millis();
consoleHead = nextHead;
if (consoleCount < CONSOLE_QUEUE_SIZE) {
consoleCount++;
}
}
// If full, oldest message is automatically overwritten
}
Warning Throttling¶
Prevents console spam from repeated conditions:
void updateSystemHealthMetrics() {
unsigned long now = millis();
// Critical heap warnings (throttled to 30 seconds)
if (FreeHeap < 20 && (now - lastHeapWarningTime > WARNING_THROTTLE_INTERVAL)) {
queueConsoleMessage("CRITICAL: Heap dangerously low (" + String(FreeHeap) + "KB)");
lastHeapWarningTime = now;
}
// Stack warnings (throttled)
if (stackBytes < 256 && (now - lastStackWarningTime > WARNING_THROTTLE_INTERVAL)) {
queueConsoleMessage("CRITICAL: " + String(taskName) + " stack very low");
lastStackWarningTime = now;
}
}
Configuration Parameters¶
Safety Thresholds¶
| Parameter | Default | Range | Description |
|---|---|---|---|
| TemperatureLimitF | 150 | 100-250 | Alternator temperature limit (°F) |
| VoltageAlarmHigh | 15 | 12-18 | High voltage alarm |