System Overview¶
Purpose & Design Philosophy¶
The Xengineering Alternator Regulator controls alternator field output using ESP32-based PWM control with multi-source sensor inputs. The system provides battery monitoring, web-based configuration, and adaptive learning algorithms for thermal management.
Core Design Principles¶
- Safety: Multiple protection layers prevent alternator, battery, and electrical system damage
- Learning Mode: Adaptive alternator control based on temperature feedback and overheat history
- Real-time Interface: Web-based monitoring and control accessible from any device
- Reliability: Watchdog protection, error handling, and automatic recovery systems
- Open Source: Full transparency for modification and community development
System Architecture¶
Hardware Foundation¶
ESP32-S3 (16MB Flash) ← Main Controller
├── ADS1115 ← 4-channel analog input (battery V, alt current, RPM, temp)
├── INA228 ← High-precision battery monitor with hardware overvoltage protection
├── OneWire ← DS18B20 temperature sensors
├── Field MOSFET Driver ← PWM control of alternator field
├── Digital I/O ← Ignition, alarms, switches, BMS signals
├── Communication Interfaces ← NMEA2K, NMEA0183, Victron VE.Direct
└── WiFi ← Web interface and OTA updates
Dual-Core Software Architecture¶
The ESP32-S3 dual-core architecture separates critical functions for optimal performance and reliability:
Core 1 (Application Core): - Main control loop with field control algorithms - Sensor data acquisition (ADS1115, INA228) - Safety monitoring and alarm systems - Web interface data transmission - Watchdog monitoring (15-second timeout)
Core 0 (Protocol Core): - WiFi and network communication - TempTask for DS18B20 temperature reading - System background tasks - Independent health monitoring
TempTask Architecture¶
Temperature reading requires special handling due to DS18B20 timing constraints:
void TempTask(void *parameter) {
// Runs independently on Core 0
for (;;) {
// Update heartbeat to show task is alive
lastTempTaskHeartbeat = millis();
// Only read temperature every 10 seconds
if (millis() - lastTempRead < 10000) {
vTaskDelay(pdMS_TO_TICKS(1000));
continue;
}
// Trigger conversion
sensors.requestTemperaturesByAddress(tempDeviceAddress);
// Wait 5 seconds for conversion (cannot be interrupted)
for (int i = 0; i < 25; i++) {
vTaskDelay(pdMS_TO_TICKS(200));
lastTempTaskHeartbeat = millis(); // Update heartbeat during wait
}
// Read and validate result
if (sensors.readScratchPad(tempDeviceAddress, scratchPad)) {
// Process temperature data...
MARK_FRESH(IDX_ALTERNATOR_TEMP);
}
lastTempRead = millis();
}
}
Why Separate Task? - 5-second blocking operation: DS18B20 conversion time would freeze main loop - Core isolation: Temperature reading doesn't interfere with field control - Independent monitoring: Separate health checking system - Continuous operation: Runs while main loop handles critical control functions
Main Loop Structure¶
Setup Phase (setup())¶
The initialization sequence establishes all system components:
void setup() {
Serial.begin(115200);
// System initialization
initializeNVS(); // Non-volatile storage for persistent data
loadNVSData(); // Restore previous session data
ensureLittleFS(); // File system for settings and web files
InitSystemSettings(); // Load all configuration from files
// Hardware initialization
if (hardwarePresent == 1) {
initializeHardware(); // Sensors, communication interfaces
}
// Create TempTask on Core 0
xTaskCreatePinnedToCore(TempTask, "TempTask", 4096, NULL, 0, &tempTaskHandle, 0);
// Network and security
loadPasswordHash(); // Web interface authentication
setupWiFi(); // Network connectivity
// Safety systems
setupWatchdog(); // 15-second hang detection
Serial.println("=== SETUP COMPLETE ===");
}
Key Setup Functions:
- initializeNVS(): Persistent data storage initialization
- initializeHardware(): Sensor and communication interfaces
- setupWiFi(): Network configuration and connection
- InitSystemSettings(): Load settings from LittleFS files
- setupWatchdog(): Configure system hang protection
Watchdog Protection System¶
The watchdog system provides critical safety protection against software hangs:
What is a Watchdog? A watchdog timer is a hardware-based safety mechanism that monitors software execution: - 15-second timeout: If the main loop doesn't "feed" the watchdog, the ESP32 automatically reboots - Safety purpose: During reboot, all GPIO pins reset to LOW, which immediately turns OFF the alternator field - Hang detection: Prevents infinite loops, memory corruption, or other failures from leaving the field energized - Hardware independence: Operates independently of software, providing ultimate fail-safe protection
Watchdog Setup:
void setupWatchdog() {
// Try to add main task to existing watchdog (Arduino framework often 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");
}
}
Task Coverage: - Main loop (Core 1): Monitored by ESP32 watchdog timer - TempTask (Core 0): Independent heartbeat monitoring system - System tasks: Background tasks monitored by FreeRTOS
Main Loop Operation (loop())¶
The main control loop executes every 50-100ms with strict timing control:
void loop() {
esp_task_wdt_reset(); // Feed 15-second watchdog
starttime = esp_timer_get_time(); // Performance timing
// Core sensor reading
ReadAnalogInputs(); // ADS1115 state machine
if (VeData == 1) ReadVEData(); // Victron VE.Direct
if (NMEA2KData == 1) NMEA2000.ParseMessages();
// Battery and engine monitoring
UpdateEngineRuntime(elapsedMillis);
UpdateBatterySOC(elapsedMillis);
// Field control (main function)
if (LearningMode == 1) {
AdjustFieldLearnMode(); // Adaptive control algorithm
} else {
AdjustField(); // Standard control algorithm
}
// Safety and health monitoring
CheckAlarms(); // Temperature, voltage, current limits
checkTempTaskHealth(); // TempTask monitoring
calculateThermalStress(); // Alternator lifetime modeling
updateSystemHealthMetrics(); // CPU, memory, stack monitoring
// User interface and data logging
SendWifiData(); // Real-time web interface updates
UpdateDisplay(); // Local OLED display
// Periodic maintenance
checkAndRestart(); // Scheduled system restart (2 hours)
// Performance monitoring
endtime = esp_timer_get_time();
LoopTime = (endtime - starttime);
if (LoopTime > MaxLoopTime) MaxLoopTime = LoopTime;
}
Loop Timing: - Target: 50-100ms per iteration - Watchdog timeout: 15 seconds - Performance monitoring: Track maximum loop time - Automatic restart: Every 2 hours for maintenance
Data Flow Architecture¶
Sensor Data Pipeline¶
Hardware Sensors → ESP32 GPIO/I2C → Data Validation → Global Variables → Web Interface
↓ ↓ ↓ ↓ ↓
ADS1115/INA228 → ReadAnalogInputs() → Sanity Checks → MeasuredAmps → SendWifiData()
OneWire Temp → TempTask() → Range Limits → AlternatorTemperatureF
NMEA2K/VE.Direct → Parse Functions → Protocol Valid → VictronVoltage
Control Flow¶
Sensor Inputs → Target Calculation → Field Control → PWM Output → Alternator Response
↓ ↓ ↓ ↓ ↓
Battery V,I → uTargetAmps (Hi/Low/RPM) → dutyCycle → setDutyPercent() → Field Current
Temperature → Safety Overrides → Constraints → MOSFET Driver → Heat Generation
Data Freshness System¶
The system tracks sensor data age to detect failed sensors:
// Data timestamp tracking
enum DataIndex {
IDX_ALTERNATOR_TEMP = 0,
IDX_BATTERY_V,
IDX_MEASURED_AMPS,
// ... 17 total indices
};
unsigned long dataTimestamps[MAX_DATA_INDICES];
// Mark data fresh when successfully read
#define MARK_FRESH(index) dataTimestamps[index] = millis()
// Check if data is stale (>10 seconds old)
#define IS_STALE(index) (millis() - dataTimestamps[index] > DATA_TIMEOUT)
Benefits: - Web interface shows red indicators for stale data - Prevents control decisions based on old sensor readings - Enables automatic sensor fallback strategies
Memory Management¶
Partition Scheme (16MB ESP32-S3)¶
nvs : 20KB - Non-volatile storage (persistent variables)
otadata : 8KB - OTA boot selection
factory : 4MB - Factory firmware (GPIO15 recovery)
ota_0 : 4MB - Production firmware (OTA updates)
factory_fs : 2MB - Factory web files
prod_fs : 2MB - Production web files (OTA updated)
userdata : 3.9MB - Data logs and configuration
coredump : 64KB - Debug crash dumps
Runtime Memory Management¶
- Heap monitoring: Continuous tracking with console warnings
- Stack monitoring: FreeRTOS task stack usage analysis for both cores
- Fragmentation tracking: Heap fragmentation percentage
- Console message queue: Fixed-size circular buffer (10 messages)
- Task isolation: TempTask has independent 4KB stack allocation
Memory Safety: - Main loop watchdog protection prevents hung tasks - TempTask heartbeat monitoring prevents temperature reading failures - Stack overflow detection for critical tasks - Automatic restart if heap falls below 20KB - Fixed-size buffers to prevent dynamic allocation issues
Safety Systems¶
Hardware Protection¶
- INA228 overvoltage protection: Hardware-level field shutdown at BulkVoltage + 0.1V
- Emergency field collapse: Immediate MOSFET disable on voltage spikes
- Watchdog timer: 15-second timeout with automatic restart and GPIO reset
Dual-Layer Monitoring¶
// Main loop monitoring (Core 1)
void loop() {
esp_task_wdt_reset(); // Feed main watchdog every 50-100ms
// ... field control and safety systems ...
}
// TempTask monitoring (Core 0)
void checkTempTaskHealth() {
if (millis() - lastTempTaskHeartbeat > TEMP_TASK_TIMEOUT) {
// TempTask appears hung - take safety action
if (dutyCycle > MinDuty + 20) {
dutyCycle = MinDuty + 10; // Reduce field for safety
}
queueConsoleMessage("CRITICAL: TempTask hung - reducing field for safety");
}
}
Software Protection Layers¶
// Multiple safety checks in field control
if (currentBatteryVoltage > (ChargingVoltageTarget + 0.2)) {
digitalWrite(4, 0); // Emergency field shutdown
dutyCycle = MinDuty;
fieldCollapseTime = millis(); // 10-second lockout
return;
}
// Temperature protection with task health validation
if (!tempTaskHealthy || TempToUse > TemperatureLimitF) {
dutyCycle -= 2 * dutyStep; // Aggressive reduction
}
// Current limits
if (Bcur > MaximumAllowedBatteryAmps) {
dutyCycle -= dutyStep; // Battery protection
}
Alarm Conditions¶
- High alternator temperature
- Battery voltage too high/low
- Excessive alternator or battery current
- Sensor failures (stale data detection)
- System health issues (low memory, stack overflow)
- TempTask monitoring failures
Configuration Management¶
Settings Storage¶
- LittleFS files: Human-readable text files for each setting
- NVS storage: Binary data for runtime variables and statistics
- Persistent variables: Battery SOC, energy totals, thermal damage accumulation
- Session variables: Reset on each boot (current maximums, loop times)
Default Value System¶
// Example: Target current setting
if (!LittleFS.exists("/TargetAmps.txt")) {
writeFile(LittleFS, "/TargetAmps.txt", String(TargetAmps).c_str());
} else {
TargetAmps = readFile(LittleFS, "/TargetAmps.txt").toInt();
}
Benefits: - Settings survive firmware updates - Easy troubleshooting (readable files) - Factory reset capability - Individual setting modification without recompilation
Performance Characteristics¶
Timing Requirements¶
- Main loop (Core 1): 50-100ms typical, 15000ms maximum (watchdog limit)
- TempTask (Core 0): 10-second cycle with 5-second blocking conversion
- Sensor reading: ADS1115 ~20ms per channel, INA228 ~5ms
- Field adjustment: Every 50ms (configurable)
- Web updates: Every 50ms (real-time data), 2-3 seconds (status data)
Core Utilization¶
- Core 1 CPU load: 15-25% average (field control, sensors, safety)
- Core 0 CPU load: 5-15% average (WiFi, TempTask, system tasks)
- Total system load: 20-40% peak during web interface updates
- Memory usage: ~200KB typical (320KB available)
- Network bandwidth: ~2KB/second for real-time web interface
Resource Usage¶
- Flash memory: ~1.4MB firmware (4MB available)
- Task stacks: 4KB TempTask, 8KB main loop, 4KB WiFi tasks
- Heap allocation: Fixed-size buffers, minimal dynamic allocation
- I2C bandwidth: ~2kHz for continuous sensor operation
Scalability Limits¶
- Maximum sensor channels: 4 analog (ADS1115), expandable via I2C
- Web clients: 5-10 simultaneous connections tested
- Data logging: 180 days local storage in userdata partition
- Settings: 100+ individual parameters in LittleFS files
- Task capacity: Limited by ESP32 FreeRTOS task scheduler
Development Environment¶
Required Tools¶
- Arduino IDE 2.x with ESP32 board package
- Custom partition scheme:
partitions.csvin sketch folder - Libraries: 20+ external libraries for sensors and communication
- Build configuration: ESP32-S3, 16MB flash, 240MHz CPU, PSRAM enabled
Build Process¶
# Board configuration
FQBN="esp32:esp32:esp32s3:FlashSize=16M,PartitionScheme=custom,CPUFreq=240,PSRAMMode=enabled"
# Compilation
arduino-cli compile --fqbn $FQBN --output-dir ./build .
# OTA package creation
tar --format=ustar -cf firmware.tar -C build firmware.bin data/
Core Assignment Verification¶
// Verify task core assignments during development
void printTaskInfo() {
TaskHandle_t tempTaskHandle = xTaskGetHandle("TempTask");
TaskHandle_t loopTaskHandle = xTaskGetCurrentTaskHandle();
Serial.printf("TempTask running on core: %d\n", xTaskGetAffinity(tempTaskHandle));
Serial.printf("Main loop running on core: %d\n", xTaskGetAffinity(loopTaskHandle));
}
This system overview provides the foundation for understanding the detailed subsystem documentation that follows. The dual-core architecture with independent safety monitoring ensures reliable alternator control while maintaining responsive user interfaces and comprehensive system protection.