/**
* @file Inversor_Maestro_ESP32.ino
* @brief Firmware de Inversor DC-AC (Onda Senoidal Pura 12V a 120V 60Hz) con Sincronismo
* @author Experto Firmware ESP32
* @note COMPILACIÓN CORREGIDA: Compatible con ESP32 Arduino Core v2.x y v3.x (ESP-IDF v5)
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <esp_task_wdt.h>
#include <driver/mcpwm.h>
// ------------------------------------------------------------------
// CONFIGURACIÓN DE HARDWARE Y PINES (MAYÚSCULAS_SNAKE_CASE)
// ------------------------------------------------------------------
#define PIN_PWM_DC_DC_1 18
#define PIN_PWM_DC_DC_2 19
#define PIN_SPWM_1 25
#define PIN_SPWM_2 26
// Sensores Analógicos (ADC1 preferido por WiFi activado)
#define PIN_V_BAT 34
#define PIN_I_OUT 35
#define PIN_TEMP 32
#define PIN_ZERO_CROSS 33
// Controles y Relés
#define PIN_RELE_SYNC 27
#define PIN_FAN 14
#define PIN_LED_ESTADO 2
#define PIN_LED_FALLA 4
#define PIN_SWITCH_MANUAL 13
// ------------------------------------------------------------------
// CONSTANTES Y UMBRALES DE PROTECCIÓN
// ------------------------------------------------------------------
const float BAT_MIN_VOLTS = 10.5;
const float BAT_MAX_VOLTS = 15.0;
const float MAX_CURRENT_A = 40.0;
const float MAX_TEMP_C = 70.0;
const float FAN_ON_TEMP = 45.0;
// Tabla de 64 pasos de Onda Senoidal Pura almacenada en memoria Flash (PROGMEM)
// Ahorra SRAM vital. Se generan valores PWM para un ciclo positivo de 60 Hz.
const uint8_t SINE_TABLE[64] PROGMEM = {
0, 12, 25, 37, 50, 62, 74, 86, 97, 108, 119, 130, 140, 150, 159, 168,
176, 184, 191, 198, 203, 209, 213, 217, 221, 223, 225, 226, 226, 225, 223, 221,
217, 213, 209, 203, 198, 191, 184, 176, 168, 159, 150, 140, 130, 119, 108, 97,
86, 74, 62, 50, 37, 25, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
// ------------------------------------------------------------------
// VARIABLES GLOBALES (Uso de memoria optimizado)
// ------------------------------------------------------------------
volatile uint8_t sine_index = 0;
volatile bool sync_locked = false;
volatile bool inverter_running = false;
volatile bool ups_mode = false;
volatile bool grid_detected = false;
volatile float telemetry_v_bat = 12.6;
volatile float telemetry_i_out = 0.0;
volatile float telemetry_temp = 30.0;
WebServer server(80);
TaskHandle_t ControlTaskHandle;
// ------------------------------------------------------------------
// FUNCIONES DE INTERRUPCIÓN (Caché rápida IRAM)
// ------------------------------------------------------------------
/**
* Interrupción por hardware para detección de cruce por cero de la red.
* Sirve de referencia para ajustar la fase (Sincronismo PLL).
*/
void IRAM_ATTR onZeroCross() {
grid_detected = true;
// Si estamos en modo UPS, el cruce por cero activa la rampa
if (ups_mode && !inverter_running) {
// En un UPS real, activaríamos el inversor inmediatamente aquí
}
// Lógica rápida de sincronización PLL
if (sync_locked) {
sine_index = 0; // Forzamos el reset de la tabla para amarrar la fase
}
}
/**
* Timer de Interrupción para actualizar el duty cycle del SPWM a 60Hz.
*/
void IRAM_ATTR onSPWM_Timer() {
if (!inverter_running) return;
// Leer valor de la tabla Senoidal
uint8_t duty = pgm_read_byte(&SINE_TABLE[sine_index]);
// Aquí se escribiría al hardware PWM (ej. ledcWrite en ESP32)
// ledcWrite(0, duty); // Canal 0
// ledcWrite(1, 255 - duty); // Canal 1 invertido si corresponde
sine_index++;
if (sine_index >= 64) sine_index = 0;
}
// ------------------------------------------------------------------
// INICIALIZACIÓN DE TAREAS Y HARDWARE
// ------------------------------------------------------------------
/**
* Inicialización segura del WDT compatible con ESP-IDF v4 y v5.
* SOLUCIÓN AL ERROR DE COMPILACIÓN REPORTADO.
*/
void initWDT() {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// ESP32 Arduino Core v3.x
esp_task_wdt_config_t twdt_config = {
.timeout_ms = 2000,
.idle_core_mask = (1 << portNUM_PROCESSORS) - 1,
.trigger_panic = true,
};
esp_task_wdt_init(&twdt_config);
#else
// ESP32 Arduino Core v2.x
esp_task_wdt_init(2, true);
#endif
esp_task_wdt_add(NULL); // Suscribe el hilo actual
}
void initDCDC_Boost() {
// Configuración del módulo MCPWM (Motor Control) para alta frecuencia a 40kHz
// Genera señales Push-Pull para el transformador de Tap Central.
mcpwm_config_t pwm_config;
pwm_config.frequency = 40000; // 40kHz
pwm_config.cmpr_a = 48.0; // 48% duty cycle (Dead-time de seguridad)
pwm_config.cmpr_b = 48.0;
pwm_config.counter_mode = MCPWM_UP_COUNTER;
pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, PIN_PWM_DC_DC_1);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, PIN_PWM_DC_DC_2);
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
}
// ------------------------------------------------------------------
// RUTINAS DE TAREAS (RTOS)
// ------------------------------------------------------------------
/**
* Tarea Crítica (Core 1): Protecciones, ADC y Control Térmico.
*/
void coreControlTask(void *pvParameters) {
initWDT();
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(10); // Ciclo de 10ms
while (true) {
esp_task_wdt_reset(); // Alimentar WDT local
// 1. Lectura ADC rápida
uint16_t adc_vbat = analogRead(PIN_V_BAT);
uint16_t adc_iout = analogRead(PIN_I_OUT);
uint16_t adc_temp = analogRead(PIN_TEMP);
// Conversión a valores reales (Ejemplo básico, requiere calibración)
telemetry_v_bat = (adc_vbat * 3.3 / 4095.0) * 5.0; // Divisor de tensión
telemetry_i_out = (adc_iout * 3.3 / 4095.0) * 20.0; // Sensor ACS712/ACS758
telemetry_temp = (adc_temp * 3.3 / 4095.0) * 100.0; // Sensor LM35 / Termistor
// 2. Control del Ventilador (Histéresis básica)
if (telemetry_temp > FAN_ON_TEMP) {
digitalWrite(PIN_FAN, HIGH);
} else if (telemetry_temp < (FAN_ON_TEMP - 5.0)) {
digitalWrite(PIN_FAN, LOW);
}
// 3. Protecciones de Estado Sólido
bool trigger_fault = false;
if (telemetry_v_bat > BAT_MAX_VOLTS || telemetry_v_bat < BAT_MIN_VOLTS) trigger_fault = true;
if (telemetry_i_out > MAX_CURRENT_A) trigger_fault = true;
if (telemetry_temp > MAX_TEMP_C) trigger_fault = true;
if (trigger_fault) {
inverter_running = false;
digitalWrite(PIN_LED_FALLA, HIGH);
digitalWrite(PIN_LED_ESTADO, LOW);
// APAGADO RÁPIDO DCDC
mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0);
mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0);
} else {
digitalWrite(PIN_LED_FALLA, LOW);
}
vTaskDelayUntil(&xLastWakeTime, xFrequency); // Timer estricto no bloqueante
}
}
// ------------------------------------------------------------------
// WEB SERVER (Evitar objetos String pesados)
// ------------------------------------------------------------------
void handleApiStatus() {
// Uso de buffer estático para evitar fragmentación de memoria (Heap)
char json_buffer[256];
snprintf(json_buffer, sizeof(json_buffer),
"{\\"vBat\\":%.2f,\\"iOut\\":%.2f,\\"temp\\":%.2f,\\"running\\":%d,\\"sync\\":%d,\\"ups\\":%d}",
telemetry_v_bat, telemetry_i_out, telemetry_temp,
inverter_running, sync_locked, ups_mode);
server.send(200, "application/json", json_buffer);
}
void setup() {
Serial.begin(115200);
// Pines de salida y LEDs
pinMode(PIN_LED_ESTADO, OUTPUT);
pinMode(PIN_LED_FALLA, OUTPUT);
pinMode(PIN_FAN, OUTPUT);
pinMode(PIN_RELE_SYNC, OUTPUT);
// Cruce por Cero con Interrupción
pinMode(PIN_ZERO_CROSS, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_ZERO_CROSS), onZeroCross, FALLING);
// Módulo DC-DC
initDCDC_Boost();
// Tarea crítica atada al Core 1 (El WiFi corre en Core 0 por defecto)
xTaskCreatePinnedToCore(
coreControlTask,
"Control_Task",
4096, // Stack size optimizado
NULL,
configMAX_PRIORITIES - 1, // Alta prioridad
&ControlTaskHandle,
1 // Core 1
);
// Inicialización Web
WiFi.softAP("Inversor_ESP32", "admin1234");
server.on("/api/status", HTTP_GET, handleApiStatus);
server.begin();
}
void loop() {
// El Core 0 maneja el WiFi y tareas del servidor HTTP de baja prioridad
server.handleClient();
// Alimentar WDT en loop principal (Core 0)
delay(10);
}
`;