Fundamentos de Comparadores Analógicos
Los comparadores analógicos son circuitos electrónicos fundamentales que realizan la comparación entre dos señales analógicas y generan una salida digital binaria. Son componentes esenciales en sistemas mecatrónicos donde se requiere tomar decisiones lógicas basadas en niveles de señales analógicas.
Principio de Funcionamiento
Un comparador analógico evalúa dos voltajes de entrada:
- V+ (Entrada no inversora): Señal de entrada principal
- V- (Entrada inversora): Voltaje de referencia (threshold)
- Vout: Salida digital (HIGH/LOW)
Función de transferencia:
Vout = HIGH si V+ > V-
Vout = LOW si V+ ≤ V-
Comparación Analógica
Aplicaciones en Mecatrónica Industrial
- Sistemas de protección por sobre/sub voltaje
- Detectores de umbral para sensores
- Control ON/OFF por temperatura
- Sistemas de emergencia por presión
- Conversores PWM a señales digitales
- Sistemas de ventanas de comparación
- Detectores de cruce por cero
- Interfaces de sensores industriales
Comparadores Analógicos en ESP32
Nota Importante
El ESP32 no tiene comparadores analógicos dedicados en hardware. Sin embargo, podemos implementar la funcionalidad de comparación utilizando el ADC y lógica de software para crear comparadores digitales eficientes.
Métodos de Implementación
Método | Descripción | Precisión |
---|---|---|
Software Simple | Comparación directa ADC | 12 bits |
Con Histéresis | Ventanas de comparación | 12 bits + filtrado |
Filtrado Digital | Anti-ruido avanzado | Alta estabilidad |
Hardware Externo | Comparadores dedicados | Muy alta velocidad |
Consideraciones de Diseño
- Ruido: Implementar filtrado para señales estables
- Histéresis: Evitar oscilaciones en el umbral
- Velocidad: Balance entre precisión y rapidez
- Referencias: Estabilidad del voltaje de referencia
Tipos de Comparadores Implementables
Comparador Simple
V+ > V-
Ventana de Comparación
V_min < V+ < V_max
Con Histéresis
Umbrales diferenciados
Implementación Práctica
Comparador Simple por Software
/**
* Comparador Analógico Simple en ESP32
* Autor: Curso ESP32 Mecatrónica UNAM
* Implementación por software de comparador analógico
*/
#include "driver/adc.h"
#include "esp_adc_cal.h"
// Configuración del ADC
esp_adc_cal_characteristics_t adc_chars;
const adc_channel_t INPUT_CHANNEL = ADC_CHANNEL_0; // GPIO36 - Señal de entrada
const adc_channel_t REFERENCE_CHANNEL = ADC_CHANNEL_3; // GPIO39 - Referencia
// Pin de salida digital
const int OUTPUT_PIN = 2;
// Variables de comparación
struct ComparatorState {
uint32_t input_value;
uint32_t reference_value;
float input_voltage;
float reference_voltage;
bool output_state;
unsigned long last_update;
};
ComparatorState comparator;
void setup() {
Serial.begin(115200);
// Configurar ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(INPUT_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(REFERENCE_CHANNEL, ADC_ATTEN_DB_11);
// Calibrar ADC
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
// Configurar pin de salida
pinMode(OUTPUT_PIN, OUTPUT);
digitalWrite(OUTPUT_PIN, LOW);
Serial.println("=== COMPARADOR ANALÓGICO ESP32 ===");
Serial.println("Entrada: GPIO36 (ADC1_CH0)");
Serial.println("Referencia: GPIO39 (ADC1_CH3)");
Serial.println("Salida: GPIO2");
Serial.println("Lógica: OUT = HIGH si INPUT > REFERENCE");
Serial.println("=====================================");
}
void loop() {
updateComparator();
printComparatorStatus();
delay(100);
}
void updateComparator() {
// Leer valores ADC con promediado para reducir ruido
comparator.input_value = readADCAverage(INPUT_CHANNEL, 10);
comparator.reference_value = readADCAverage(REFERENCE_CHANNEL, 10);
// Convertir a voltajes
comparator.input_voltage = esp_adc_cal_raw_to_voltage(
comparator.input_value, &adc_chars) / 1000.0;
comparator.reference_voltage = esp_adc_cal_raw_to_voltage(
comparator.reference_value, &adc_chars) / 1000.0;
// Lógica del comparador
comparator.output_state = comparator.input_voltage > comparator.reference_voltage;
// Actualizar salida digital
digitalWrite(OUTPUT_PIN, comparator.output_state ? HIGH : LOW);
comparator.last_update = millis();
}
uint32_t readADCAverage(adc_channel_t channel, int samples) {
uint32_t sum = 0;
for (int i = 0; i < samples; i++) {
sum += adc1_get_raw(channel);
delayMicroseconds(100);
}
return sum / samples;
}
void printComparatorStatus() {
Serial.printf("Input: %.3fV (%d) | Ref: %.3fV (%d) | Output: %s\n",
comparator.input_voltage, comparator.input_value,
comparator.reference_voltage, comparator.reference_value,
comparator.output_state ? "HIGH" : "LOW");
}
Comparador con Histéresis
/**
* Comparador con Histéresis para ESP32
* Evita oscilaciones en el punto de comparación
* Ideal para aplicaciones industriales con ruido
*/
class HysteresisComparator {
private:
adc_channel_t input_channel;
float reference_voltage;
float hysteresis_voltage;
bool current_state;
bool initialized;
esp_adc_cal_characteristics_t* adc_chars;
// Umbrales calculados
float upper_threshold;
float lower_threshold;
public:
HysteresisComparator(adc_channel_t input_ch, float ref_v, float hyst_v,
esp_adc_cal_characteristics_t* adc_cal) {
input_channel = input_ch;
reference_voltage = ref_v;
hysteresis_voltage = hyst_v;
adc_chars = adc_cal;
current_state = false;
initialized = false;
// Calcular umbrales
upper_threshold = reference_voltage + (hysteresis_voltage / 2.0);
lower_threshold = reference_voltage - (hysteresis_voltage / 2.0);
// Configurar canal ADC
adc1_config_channel_atten(input_channel, ADC_ATTEN_DB_11);
}
bool update() {
// Leer y convertir valor de entrada
uint32_t adc_raw = readFilteredADC();
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(adc_raw, adc_chars);
float input_voltage = voltage_mv / 1000.0;
// Lógica de histéresis
if (!initialized) {
// Primera lectura - establecer estado inicial
current_state = input_voltage > reference_voltage;
initialized = true;
} else {
// Aplicar histéresis
if (current_state) {
// Estado HIGH - cambiar a LOW solo si baja del umbral inferior
if (input_voltage < lower_threshold) {
current_state = false;
}
} else {
// Estado LOW - cambiar a HIGH solo si supera el umbral superior
if (input_voltage > upper_threshold) {
current_state = true;
}
}
}
return current_state;
}
float getInputVoltage() {
uint32_t adc_raw = readFilteredADC();
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(adc_raw, adc_chars);
return voltage_mv / 1000.0;
}
void printStatus() {
float input_v = getInputVoltage();
Serial.printf("Input: %.3fV | Ref: %.3fV ± %.3fV | State: %s\n",
input_v, reference_voltage, hysteresis_voltage/2,
current_state ? "HIGH" : "LOW");
Serial.printf("Thresholds: LOW<%.3fV | HIGH>%.3fV\n",
lower_threshold, upper_threshold);
}
private:
uint32_t readFilteredADC() {
// Filtro de media móvil
const int samples = 5;
uint32_t sum = 0;
for (int i = 0; i < samples; i++) {
sum += adc1_get_raw(input_channel);
delayMicroseconds(200);
}
return sum / samples;
}
};
// Ejemplo de uso
esp_adc_cal_characteristics_t adc_chars;
HysteresisComparator tempComparator(ADC_CHANNEL_0, 1.5, 0.2, &adc_chars);
void setup() {
Serial.begin(115200);
// Configurar ADC
adc1_config_width(ADC_WIDTH_BIT_12);
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
Serial.println("=== COMPARADOR CON HISTÉRESIS ===");
Serial.println("Referencia: 1.5V ± 0.1V");
Serial.println("=================================");
}
void loop() {
bool output = tempComparator.update();
tempComparator.printStatus();
// Usar resultado para control
digitalWrite(LED_BUILTIN, output);
delay(200);
}
Comparador de Ventana
/**
* Comparador de Ventana para ESP32
* Detecta si la señal está dentro de un rango específico
* Aplicación: Control de calidad, sistemas de protección
*/
enum WindowComparatorState {
BELOW_WINDOW, // Señal por debajo del rango
IN_WINDOW, // Señal dentro del rango
ABOVE_WINDOW // Señal por encima del rango
};
class WindowComparator {
private:
adc_channel_t input_channel;
float lower_limit;
float upper_limit;
WindowComparatorState current_state;
esp_adc_cal_characteristics_t* adc_chars;
// Estadísticas
unsigned long state_change_count;
unsigned long last_state_change;
public:
WindowComparator(adc_channel_t input_ch, float low_limit, float high_limit,
esp_adc_cal_characteristics_t* adc_cal) {
input_channel = input_ch;
lower_limit = low_limit;
upper_limit = high_limit;
adc_chars = adc_cal;
current_state = IN_WINDOW;
state_change_count = 0;
last_state_change = 0;
// Configurar canal ADC
adc1_config_channel_atten(input_channel, ADC_ATTEN_DB_11);
}
WindowComparatorState update() {
float input_voltage = getInputVoltage();
WindowComparatorState new_state;
// Determinar estado basado en la ventana
if (input_voltage < lower_limit) {
new_state = BELOW_WINDOW;
} else if (input_voltage > upper_limit) {
new_state = ABOVE_WINDOW;
} else {
new_state = IN_WINDOW;
}
// Detectar cambio de estado
if (new_state != current_state) {
current_state = new_state;
state_change_count++;
last_state_change = millis();
}
return current_state;
}
bool isInWindow() {
return current_state == IN_WINDOW;
}
bool isBelowWindow() {
return current_state == BELOW_WINDOW;
}
bool isAboveWindow() {
return current_state == ABOVE_WINDOW;
}
float getInputVoltage() {
uint32_t adc_raw = readStableADC();
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(adc_raw, adc_chars);
return voltage_mv / 1000.0;
}
String getStateString() {
switch (current_state) {
case BELOW_WINDOW: return "BELOW";
case IN_WINDOW: return "IN_RANGE";
case ABOVE_WINDOW: return "ABOVE";
default: return "UNKNOWN";
}
}
void printDetailedStatus() {
float input_v = getInputVoltage();
float window_center = (upper_limit + lower_limit) / 2.0;
float window_width = upper_limit - lower_limit;
Serial.println("========== WINDOW COMPARATOR STATUS ==========");
Serial.printf("Input Voltage: %.3fV\n", input_v);
Serial.printf("Window: %.3fV - %.3fV (Center: %.3fV, Width: %.3fV)\n",
lower_limit, upper_limit, window_center, window_width);
Serial.printf("Current State: %s\n", getStateString().c_str());
Serial.printf("State Changes: %lu (Last: %lu ms ago)\n",
state_change_count, millis() - last_state_change);
// Indicadores visuales
if (isInWindow()) {
Serial.println("Status: ✅ WITHIN ACCEPTABLE RANGE");
} else if (isBelowWindow()) {
Serial.printf("Status: ⬇️ BELOW RANGE (%.3fV under)\n",
lower_limit - input_v);
} else {
Serial.printf("Status: ⬆️ ABOVE RANGE (%.3fV over)\n",
input_v - upper_limit);
}
Serial.println("=============================================\n");
}
private:
uint32_t readStableADC() {
// Lectura con filtrado avanzado
const int samples = 15;
uint32_t readings[samples];
// Tomar múltiples muestras
for (int i = 0; i < samples; i++) {
readings[i] = adc1_get_raw(input_channel);
delayMicroseconds(100);
}
// Ordenamiento burbuja simple para filtro de mediana
for (int i = 0; i < samples - 1; i++) {
for (int j = 0; j < samples - i - 1; j++) {
if (readings[j] > readings[j + 1]) {
uint32_t temp = readings[j];
readings[j] = readings[j + 1];
readings[j + 1] = temp;
}
}
}
// Retornar mediana
return readings[samples / 2];
}
};
// Ejemplo de aplicación: Control de calidad de voltaje
esp_adc_cal_characteristics_t adc_chars;
WindowComparator qualityControl(ADC_CHANNEL_0, 2.8, 3.2, &adc_chars);
const int ALARM_PIN = 2;
const int OK_LED_PIN = 4;
void setup() {
Serial.begin(115200);
// Configurar ADC
adc1_config_width(ADC_WIDTH_BIT_12);
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
// Configurar pines de salida
pinMode(ALARM_PIN, OUTPUT);
pinMode(OK_LED_PIN, OUTPUT);
Serial.println("=== SISTEMA DE CONTROL DE CALIDAD ===");
Serial.println("Ventana aceptable: 2.8V - 3.2V");
Serial.println("Alarma activa fuera del rango");
Serial.println("====================================");
}
void loop() {
WindowComparatorState state = qualityControl.update();
// Control de indicadores
if (qualityControl.isInWindow()) {
digitalWrite(OK_LED_PIN, HIGH);
digitalWrite(ALARM_PIN, LOW);
} else {
digitalWrite(OK_LED_PIN, LOW);
digitalWrite(ALARM_PIN, HIGH); // Alarma activa
}
// Status detallado cada 2 segundos
static unsigned long last_detailed_print = 0;
if (millis() - last_detailed_print > 2000) {
qualityControl.printDetailedStatus();
last_detailed_print = millis();
}
delay(100);
}
Ejercicios Prácticos Interactivos
Objetivo: Crear un sistema de iluminación automática que encienda LEDs cuando la luz ambiente sea inferior a un umbral configurable.
Aplicación: Sistemas de iluminación inteligente en edificios industriales.
Materiales Necesarios
- ESP32 DevKit
- Fotoresistor LDR (5-20kΩ)
- Resistor de referencia 10kΩ
- LEDs de alta luminosidad (x3)
- Resistores 220Ω para LEDs
- Potenciómetro 10kΩ (ajuste umbral)
/**
* Ejercicio 1: Control Automático de Iluminación
* Sistema que activa iluminación basado en nivel de luz ambiente
*/
#include "driver/adc.h"
#include "esp_adc_cal.h"
esp_adc_cal_characteristics_t adc_chars;
// Configuración de pines
const adc_channel_t LDR_CHANNEL = ADC_CHANNEL_0; // GPIO36
const adc_channel_t THRESHOLD_CHANNEL = ADC_CHANNEL_3; // GPIO39
const int LED_PINS[] = {2, 4, 16}; // 3 LEDs de salida
const int NUM_LEDS = 3;
// Sistema de comparación con histéresis
class LightComparator {
private:
float hysteresis_margin = 0.1; // 100mV de histéresis
bool lights_on = false;
public:
bool shouldTurnOn(float ambient, float threshold) {
if (!lights_on) {
// Luces apagadas - encender si está muy oscuro
return ambient < (threshold - hysteresis_margin);
} else {
// Luces encendidas - apagar solo si hay suficiente luz
return ambient < (threshold + hysteresis_margin);
}
}
void setState(bool state) { lights_on = state; }
bool getState() { return lights_on; }
};
LightComparator lightController;
void setup() {
Serial.begin(115200);
// Configurar ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(LDR_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(THRESHOLD_CHANNEL, ADC_ATTEN_DB_11);
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
// Configurar LEDs
for (int i = 0; i < NUM_LEDS; i++) {
pinMode(LED_PINS[i], OUTPUT);
digitalWrite(LED_PINS[i], LOW);
}
Serial.println("=== SISTEMA DE ILUMINACIÓN AUTOMÁTICA ===");
Serial.println("LDR: GPIO36 | Umbral: GPIO39");
Serial.println("LEDs: GPIO2, GPIO4, GPIO16");
Serial.println("========================================");
}
void loop() {
// Leer sensores con filtrado
float ambient_light = readVoltageFiltered(LDR_CHANNEL);
float threshold_voltage = readVoltageFiltered(THRESHOLD_CHANNEL);
// Determinar estado de iluminación
bool should_light = lightController.shouldTurnOn(ambient_light, threshold_voltage);
lightController.setState(should_light);
// Controlar LEDs con efecto gradual
controlLEDs(should_light);
// Mostrar estado
printSystemStatus(ambient_light, threshold_voltage, should_light);
delay(200);
}
float readVoltageFiltered(adc_channel_t channel) {
uint32_t adc_sum = 0;
const int samples = 8;
for (int i = 0; i < samples; i++) {
adc_sum += adc1_get_raw(channel);
delayMicroseconds(500);
}
uint32_t adc_avg = adc_sum / samples;
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(adc_avg, &adc_chars);
return voltage_mv / 1000.0;
}
void controlLEDs(bool enable) {
static unsigned long last_update = 0;
static int led_index = 0;
static bool transitioning = false;
if (enable) {
// Encender LEDs secuencialmente para efecto visual
if (millis() - last_update > 100) {
digitalWrite(LED_PINS[led_index], HIGH);
led_index = (led_index + 1) % NUM_LEDS;
if (led_index == 0 && !transitioning) {
// Todos encendidos
for (int i = 0; i < NUM_LEDS; i++) {
digitalWrite(LED_PINS[i], HIGH);
}
}
last_update = millis();
}
} else {
// Apagar todos los LEDs
for (int i = 0; i < NUM_LEDS; i++) {
digitalWrite(LED_PINS[i], LOW);
}
led_index = 0;
}
}
void printSystemStatus(float ambient, float threshold, bool lights_on) {
// Convertir voltajes a porcentajes de luz
float ambient_percent = (ambient / 3.3) * 100;
float threshold_percent = (threshold / 3.3) * 100;
Serial.printf("Luz ambiente: %.2fV (%.1f%%) | ", ambient, ambient_percent);
Serial.printf("Umbral: %.2fV (%.1f%%) | ", threshold, threshold_percent);
Serial.printf("Iluminación: %s", lights_on ? "🔆 ON" : "🌙 OFF");
if (lights_on && ambient < threshold) {
Serial.println(" [MODO NOCTURNO]");
} else if (!lights_on && ambient >= threshold) {
Serial.println(" [LUZ SUFICIENTE]");
} else {
Serial.println(" [TRANSICIÓN]");
}
}
Objetivo: Implementar un sistema de protección que active ventilación de emergencia cuando la temperatura supere límites críticos.
Aplicación: Sistemas de seguridad térmica en equipos industriales.
Materiales Necesarios
- ESP32 DevKit
- Sensor temperatura TMP36
- Potenciómetro 10kΩ (setpoint)
- Relé 5V (control ventilador)
- LED rojo (alarma) + resistor
- Buzzer 5V (opcional)
- Transistor 2N2222 para relé
/**
* Ejercicio 2: Sistema de Protección por Temperatura
* Control de seguridad térmica con múltiples niveles de alarma
*/
#include "driver/adc.h"
#include "esp_adc_cal.h"
esp_adc_cal_characteristics_t adc_chars;
// Configuración de pines
const adc_channel_t TEMP_CHANNEL = ADC_CHANNEL_0; // GPIO36 - TMP36
const adc_channel_t SETPOINT_CHANNEL = ADC_CHANNEL_3; // GPIO39 - Setpoint
const int FAN_RELAY_PIN = 2; // Control de ventilador
const int ALARM_LED_PIN = 4; // LED de alarma
const int BUZZER_PIN = 16; // Buzzer de alarma
const int STATUS_LED_PIN = 17; // LED de estado normal
// Estados del sistema de protección
enum ThermalProtectionState {
NORMAL, // Temperatura normal
WARNING, // Temperatura elevada
CRITICAL, // Temperatura crítica
EMERGENCY // Emergencia térmica
};
class ThermalProtectionSystem {
private:
ThermalProtectionState current_state;
float warning_offset = 5.0; // °C sobre setpoint
float critical_offset = 10.0; // °C sobre setpoint
float emergency_offset = 15.0; // °C sobre setpoint
// Histéresis para cada nivel
float hysteresis = 2.0; // °C
unsigned long state_change_time;
unsigned long alarm_start_time;
bool alarm_active;
public:
ThermalProtectionSystem() {
current_state = NORMAL;
state_change_time = 0;
alarm_start_time = 0;
alarm_active = false;
}
ThermalProtectionState evaluateTemperature(float temp, float setpoint) {
ThermalProtectionState new_state = current_state;
// Calcular umbrales con histéresis
float warning_temp = setpoint + warning_offset;
float critical_temp = setpoint + critical_offset;
float emergency_temp = setpoint + emergency_offset;
// Lógica de transición de estados con histéresis
switch (current_state) {
case NORMAL:
if (temp > warning_temp) new_state = WARNING;
break;
case WARNING:
if (temp < warning_temp - hysteresis) {
new_state = NORMAL;
} else if (temp > critical_temp) {
new_state = CRITICAL;
}
break;
case CRITICAL:
if (temp < critical_temp - hysteresis) {
new_state = WARNING;
} else if (temp > emergency_temp) {
new_state = EMERGENCY;
}
break;
case EMERGENCY:
if (temp < emergency_temp - hysteresis) {
new_state = CRITICAL;
}
break;
}
// Actualizar estado si cambió
if (new_state != current_state) {
current_state = new_state;
state_change_time = millis();
if (current_state >= WARNING && !alarm_active) {
alarm_start_time = millis();
alarm_active = true;
} else if (current_state == NORMAL) {
alarm_active = false;
}
}
return current_state;
}
String getStateString() {
switch (current_state) {
case NORMAL: return "NORMAL";
case WARNING: return "WARNING";
case CRITICAL: return "CRITICAL";
case EMERGENCY: return "EMERGENCY";
default: return "UNKNOWN";
}
}
bool shouldActivateFan() {
return current_state >= WARNING;
}
bool shouldActivateAlarm() {
return current_state >= CRITICAL;
}
bool isEmergency() {
return current_state == EMERGENCY;
}
unsigned long getTimeSinceStateChange() {
return millis() - state_change_time;
}
unsigned long getAlarmDuration() {
return alarm_active ? millis() - alarm_start_time : 0;
}
};
ThermalProtectionSystem thermalSystem;
void setup() {
Serial.begin(115200);
// Configurar ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(TEMP_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(SETPOINT_CHANNEL, ADC_ATTEN_DB_11);
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
// Configurar pines de salida
pinMode(FAN_RELAY_PIN, OUTPUT);
pinMode(ALARM_LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(STATUS_LED_PIN, OUTPUT);
// Estado inicial
digitalWrite(FAN_RELAY_PIN, LOW);
digitalWrite(ALARM_LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(STATUS_LED_PIN, HIGH);
Serial.println("=== SISTEMA DE PROTECCIÓN TÉRMICA ===");
Serial.println("TMP36: GPIO36 | Setpoint: GPIO39");
Serial.println("Ventilador: GPIO2 | Alarma: GPIO4");
Serial.println("Niveles: +5°C Warning, +10°C Critical, +15°C Emergency");
Serial.println("====================================");
}
void loop() {
// Leer temperatura y setpoint
float temperature = readTemperatureTMP36();
float setpoint = readSetpoint();
// Evaluar estado térmico
ThermalProtectionState state = thermalSystem.evaluateTemperature(temperature, setpoint);
// Controlar actuadores
controlOutputs(state);
// Mostrar estado detallado
printThermalStatus(temperature, setpoint, state);
delay(500);
}
float readTemperatureTMP36() {
// Leer TMP36 con filtrado
uint32_t adc_sum = 0;
const int samples = 10;
for (int i = 0; i < samples; i++) {
adc_sum += adc1_get_raw(TEMP_CHANNEL);
delayMicroseconds(1000);
}
uint32_t adc_avg = adc_sum / samples;
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(adc_avg, &adc_chars);
// Conversión TMP36: 10mV/°C, 500mV offset a 0°C
float temperature = (voltage_mv - 500.0) / 10.0;
return temperature;
}
float readSetpoint() {
// Convertir potenciómetro a rango de temperatura (20-60°C)
uint32_t adc_raw = adc1_get_raw(SETPOINT_CHANNEL);
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(adc_raw, &adc_chars);
// Mapear 0-3.3V a 20-60°C
float setpoint = 20.0 + (voltage_mv / 3300.0) * 40.0;
return setpoint;
}
void controlOutputs(ThermalProtectionState state) {
static unsigned long last_blink = 0;
static bool blink_state = false;
// Control del ventilador
digitalWrite(FAN_RELAY_PIN, thermalSystem.shouldActivateFan() ? HIGH : LOW);
// Control del LED de estado
digitalWrite(STATUS_LED_PIN, (state == NORMAL) ? HIGH : LOW);
// Control de alarmas con patrones de parpadeo
if (thermalSystem.shouldActivateAlarm()) {
// Parpadeo rápido para critical, muy rápido para emergency
int blink_interval = (state == EMERGENCY) ? 100 : 300;
if (millis() - last_blink > blink_interval) {
blink_state = !blink_state;
digitalWrite(ALARM_LED_PIN, blink_state ? HIGH : LOW);
// Buzzer solo en emergency
if (state == EMERGENCY) {
digitalWrite(BUZZER_PIN, blink_state ? HIGH : LOW);
}
last_blink = millis();
}
} else {
// Apagar alarmas
digitalWrite(ALARM_LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
}
}
void printThermalStatus(float temp, float setpoint, ThermalProtectionState state) {
Serial.println("========== ESTADO TÉRMICO ==========");
Serial.printf("Temperatura: %.2f°C\n", temp);
Serial.printf("Setpoint: %.2f°C\n", setpoint);
Serial.printf("Diferencia: %+.2f°C\n", temp - setpoint);
Serial.printf("Estado: %s\n", thermalSystem.getStateString().c_str());
// Tiempo en estado actual
unsigned long time_in_state = thermalSystem.getTimeSinceStateChange();
Serial.printf("Tiempo en estado: %lu seg\n", time_in_state / 1000);
// Estado de actuadores
Serial.printf("Ventilador: %s\n", thermalSystem.shouldActivateFan() ? "🌀 ACTIVO" : "⭕ OFF");
if (thermalSystem.shouldActivateAlarm()) {
unsigned long alarm_time = thermalSystem.getAlarmDuration();
Serial.printf("🚨 ALARMA ACTIVA (%lu seg)\n", alarm_time / 1000);
}
// Indicadores de estado
switch (state) {
case NORMAL:
Serial.println("Status: ✅ Temperatura normal");
break;
case WARNING:
Serial.println("Status: ⚠️ Temperatura elevada - Ventilación activada");
break;
case CRITICAL:
Serial.println("Status: 🔥 Temperatura crítica - Alarma activa");
break;
case EMERGENCY:
Serial.println("Status: 🆘 EMERGENCIA TÉRMICA - Protocolo de seguridad");
break;
}
Serial.println("==================================\n");
}
Objetivo: Crear un sistema de monitoreo que detecte variaciones de voltaje fuera de rangos aceptables y registre eventos de calidad de energía.
Aplicación: Sistemas de monitoreo de calidad eléctrica en instalaciones industriales.
Materiales Necesarios
- ESP32 DevKit
- Divisores de voltaje precision (resistors 0.1%)
- Transformador de voltaje (para medición AC)
- Capacitores de filtrado
- Display LCD 16x2 I2C
- LEDs indicadores (Verde/Amarillo/Rojo)
- Tarjeta microSD para logging
/**
* Ejercicio 3: Monitor de Calidad de Energía
* Sistema avanzado de monitoreo con múltiples comparadores
* y registro de eventos
*/
#include "driver/adc.h"
#include "esp_adc_cal.h"
#include
#include
esp_adc_cal_characteristics_t adc_chars;
// Canales de medición
const adc_channel_t VOLTAGE_CHANNEL = ADC_CHANNEL_0; // GPIO36
const adc_channel_t CURRENT_CHANNEL = ADC_CHANNEL_3; // GPIO39
const adc_channel_t REFERENCE_CHANNEL = ADC_CHANNEL_6; // GPIO34
// Pines de indicadores
const int GREEN_LED = 2; // Operación normal
const int YELLOW_LED = 4; // Advertencia
const int RED_LED = 16; // Falla crítica
const int BUZZER_PIN = 17; // Alarma sonora
// Estándares de calidad de energía (IEEE 519)
struct PowerQualityLimits {
float nominal_voltage = 120.0; // Voltaje nominal (V)
float voltage_tolerance = 0.05; // ±5%
float voltage_warning = 0.08; // ±8%
float voltage_critical = 0.10; // ±10%
float nominal_frequency = 60.0; // Frecuencia nominal (Hz)
float frequency_tolerance = 0.1; // ±0.1 Hz
float thd_warning = 0.05; // 5% THD
float thd_critical = 0.08; // 8% THD
};
enum PowerQualityEvent {
NORMAL_OPERATION,
VOLTAGE_SAG, // Subtensión
VOLTAGE_SWELL, // Sobretensión
VOLTAGE_INTERRUPTION, // Interrupción
FREQUENCY_DEVIATION, // Desviación de frecuencia
HARMONIC_DISTORTION // Distorsión armónica
};
struct PowerQualityData {
float voltage_rms;
float current_rms;
float frequency;
float thd_voltage;
float power_factor;
PowerQualityEvent active_event;
unsigned long event_start_time;
unsigned long event_duration;
};
class PowerQualityMonitor {
private:
PowerQualityLimits limits;
PowerQualityData current_data;
// Buffers para análisis
static const int SAMPLE_BUFFER_SIZE = 128;
float voltage_buffer[SAMPLE_BUFFER_SIZE];
float current_buffer[SAMPLE_BUFFER_SIZE];
int buffer_index;
// Estadísticas de eventos
struct EventStatistics {
unsigned long total_events;
unsigned long sag_events;
unsigned long swell_events;
unsigned long interruption_events;
unsigned long frequency_events;
unsigned long harmonic_events;
unsigned long last_event_time;
} event_stats;
// Estado del monitor
bool monitoring_active;
unsigned long last_analysis_time;
unsigned long analysis_interval = 1000; // 1 segundo
public:
PowerQualityMonitor() {
buffer_index = 0;
monitoring_active = false;
last_analysis_time = 0;
memset(&event_stats, 0, sizeof(event_stats));
current_data.active_event = NORMAL_OPERATION;
current_data.event_start_time = 0;
}
void initialize() {
// Configurar ADC con alta precisión
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(VOLTAGE_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(CURRENT_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(REFERENCE_CHANNEL, ADC_ATTEN_DB_11);
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
monitoring_active = true;
Serial.println("Monitor de Calidad de Energía inicializado");
}
void update() {
if (!monitoring_active) return;
// Adquisición continua de muestras
acquireSamples();
// Análisis periódico
if (millis() - last_analysis_time >= analysis_interval) {
performPowerQualityAnalysis();
detectPowerQualityEvents();
last_analysis_time = millis();
}
}
private:
void acquireSamples() {
// Muestreo de voltaje y corriente
uint32_t voltage_adc = adc1_get_raw(VOLTAGE_CHANNEL);
uint32_t current_adc = adc1_get_raw(CURRENT_CHANNEL);
// Convertir a valores reales
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(voltage_adc, &adc_chars);
uint32_t current_mv = esp_adc_cal_raw_to_voltage(current_adc, &adc_chars);
// Escalado para voltaje y corriente reales
voltage_buffer[buffer_index] = (voltage_mv / 1000.0) * 50.0; // Factor de escala del divisor
current_buffer[buffer_index] = (current_mv / 1000.0) * 10.0; // Factor de escala del sensor
buffer_index = (buffer_index + 1) % SAMPLE_BUFFER_SIZE;
}
void performPowerQualityAnalysis() {
// Calcular RMS de voltaje
current_data.voltage_rms = calculateRMS(voltage_buffer, SAMPLE_BUFFER_SIZE);
current_data.current_rms = calculateRMS(current_buffer, SAMPLE_BUFFER_SIZE);
// Calcular frecuencia (simulada - requiere análisis de cruces por cero)
current_data.frequency = estimateFrequency();
// Calcular THD (Total Harmonic Distortion) simplificado
current_data.thd_voltage = calculateTHD(voltage_buffer, SAMPLE_BUFFER_SIZE);
// Calcular factor de potencia (simulado)
current_data.power_factor = calculatePowerFactor();
}
float calculateRMS(float* buffer, int size) {
float sum_squares = 0;
for (int i = 0; i < size; i++) {
sum_squares += buffer[i] * buffer[i];
}
return sqrt(sum_squares / size);
}
float estimateFrequency() {
// Implementación simplificada - detecta cruces por cero
int zero_crossings = 0;
float last_sample = voltage_buffer[0];
for (int i = 1; i < SAMPLE_BUFFER_SIZE; i++) {
if ((last_sample < 0 && voltage_buffer[i] >= 0) ||
(last_sample >= 0 && voltage_buffer[i] < 0)) {
zero_crossings++;
}
last_sample = voltage_buffer[i];
}
// Frecuencia estimada basada en cruces por cero
float sample_rate = 1000.0; // Hz (asumiendo 1ms entre muestras)
float estimated_freq = (zero_crossings * sample_rate) / (2 * SAMPLE_BUFFER_SIZE);
return estimated_freq > 0 ? estimated_freq : limits.nominal_frequency;
}
float calculateTHD(float* buffer, int size) {
// THD simplificado - mide distorsión respecto a senoidal ideal
float fundamental_power = 0;
float total_power = 0;
// Análisis simplificado de armónicos
for (int i = 0; i < size; i++) {
float sample = buffer[i];
total_power += sample * sample;
// Aproximación del fundamental (muy simplificada)
float ideal_sine = limits.nominal_voltage * sin(2 * M_PI * limits.nominal_frequency * i / 1000.0);
fundamental_power += ideal_sine * ideal_sine;
}
if (fundamental_power > 0) {
return sqrt((total_power - fundamental_power) / fundamental_power);
}
return 0;
}
float calculatePowerFactor() {
// Factor de potencia simplificado (requiere análisis de fase)
// Por simplicidad, retornamos un valor basado en la calidad de la forma de onda
float distortion_factor = 1.0 - current_data.thd_voltage;
return constrain(distortion_factor, 0.7, 1.0);
}
void detectPowerQualityEvents() {
PowerQualityEvent new_event = NORMAL_OPERATION;
// Análisis de voltaje
float voltage_deviation = abs(current_data.voltage_rms - limits.nominal_voltage) / limits.nominal_voltage;
if (current_data.voltage_rms < limits.nominal_voltage * 0.1) {
new_event = VOLTAGE_INTERRUPTION;
} else if (voltage_deviation > limits.voltage_critical) {
if (current_data.voltage_rms > limits.nominal_voltage) {
new_event = VOLTAGE_SWELL;
} else {
new_event = VOLTAGE_SAG;
}
}
// Análisis de frecuencia
float freq_deviation = abs(current_data.frequency - limits.nominal_frequency);
if (freq_deviation > limits.frequency_tolerance) {
new_event = FREQUENCY_DEVIATION;
}
// Análisis de THD
if (current_data.thd_voltage > limits.thd_critical) {
new_event = HARMONIC_DISTORTION;
}
// Gestión de eventos
if (new_event != current_data.active_event) {
if (current_data.active_event != NORMAL_OPERATION) {
// Finalizar evento anterior
current_data.event_duration = millis() - current_data.event_start_time;
logPowerQualityEvent(current_data.active_event, current_data.event_duration);
}
// Iniciar nuevo evento
current_data.active_event = new_event;
current_data.event_start_time = millis();
if (new_event != NORMAL_OPERATION) {
event_stats.total_events++;
event_stats.last_event_time = millis();
updateEventStatistics(new_event);
}
}
}
void updateEventStatistics(PowerQualityEvent event) {
switch (event) {
case VOLTAGE_SAG: event_stats.sag_events++; break;
case VOLTAGE_SWELL: event_stats.swell_events++; break;
case VOLTAGE_INTERRUPTION: event_stats.interruption_events++; break;
case FREQUENCY_DEVIATION: event_stats.frequency_events++; break;
case HARMONIC_DISTORTION: event_stats.harmonic_events++; break;
default: break;
}
}
void logPowerQualityEvent(PowerQualityEvent event, unsigned long duration) {
Serial.println("========== EVENTO DE CALIDAD DE ENERGÍA ==========");
Serial.printf("Tipo: %s\n", getEventString(event).c_str());
Serial.printf("Duración: %lu ms\n", duration);
Serial.printf("Voltaje RMS: %.2f V\n", current_data.voltage_rms);
Serial.printf("Frecuencia: %.2f Hz\n", current_data.frequency);
Serial.printf("THD: %.2f%%\n", current_data.thd_voltage * 100);
Serial.println("===============================================");
}
String getEventString(PowerQualityEvent event) {
switch (event) {
case NORMAL_OPERATION: return "Operación Normal";
case VOLTAGE_SAG: return "Subtensión (SAG)";
case VOLTAGE_SWELL: return "Sobretensión (SWELL)";
case VOLTAGE_INTERRUPTION: return "Interrupción de Voltaje";
case FREQUENCY_DEVIATION: return "Desviación de Frecuencia";
case HARMONIC_DISTORTION: return "Distorsión Armónica";
default: return "Evento Desconocido";
}
}
public:
void printDetailedStatus() {
Serial.println("========== ESTADO DE CALIDAD DE ENERGÍA ==========");
Serial.printf("Voltaje RMS: %.2f V (Nom: %.2f V)\n",
current_data.voltage_rms, limits.nominal_voltage);
Serial.printf("Corriente RMS: %.2f A\n", current_data.current_rms);
Serial.printf("Frecuencia: %.2f Hz (Nom: %.2f Hz)\n",
current_data.frequency, limits.nominal_frequency);
Serial.printf("THD Voltaje: %.2f%% (Límite: %.2f%%)\n",
current_data.thd_voltage * 100, limits.thd_critical * 100);
Serial.printf("Factor de Potencia: %.3f\n", current_data.power_factor);
Serial.printf("Evento Actual: %s\n", getEventString(current_data.active_event).c_str());
if (current_data.active_event != NORMAL_OPERATION) {
unsigned long current_duration = millis() - current_data.event_start_time;
Serial.printf("Duración del Evento: %lu ms\n", current_duration);
}
Serial.println("\n--- ESTADÍSTICAS DE EVENTOS ---");
Serial.printf("Total de Eventos: %lu\n", event_stats.total_events);
Serial.printf("Subtensiones: %lu\n", event_stats.sag_events);
Serial.printf("Sobretensiones: %lu\n", event_stats.swell_events);
Serial.printf("Interrupciones: %lu\n", event_stats.interruption_events);
Serial.printf("Desviaciones de Frecuencia: %lu\n", event_stats.frequency_events);
Serial.printf("Distorsiones Armónicas: %lu\n", event_stats.harmonic_events);
Serial.println("============================================\n");
}
PowerQualityEvent getCurrentEvent() {
return current_data.active_event;
}
PowerQualityData getCurrentData() {
return current_data;
}
};
PowerQualityMonitor powerMonitor;
void setup() {
Serial.begin(115200);
// Configurar pines de indicadores
pinMode(GREEN_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
// Estado inicial
digitalWrite(GREEN_LED, HIGH);
digitalWrite(YELLOW_LED, LOW);
digitalWrite(RED_LED, LOW);
digitalWrite(BUZZER_PIN, LOW);
// Inicializar monitor
powerMonitor.initialize();
Serial.println("=== SISTEMA DE MONITOREO DE CALIDAD DE ENERGÍA ===");
Serial.println("Estándares IEEE 519 implementados");
Serial.println("Monitoreo continuo de voltaje, frecuencia y THD");
Serial.println("================================================");
}
void loop() {
// Actualizar monitor de calidad
powerMonitor.update();
// Controlar indicadores
controlIndicators();
// Status detallado cada 5 segundos
static unsigned long last_detailed_status = 0;
if (millis() - last_detailed_status > 5000) {
powerMonitor.printDetailedStatus();
last_detailed_status = millis();
}
delay(50); // 20 Hz de actualización
}
void controlIndicators() {
PowerQualityEvent current_event = powerMonitor.getCurrentEvent();
PowerQualityData data = powerMonitor.getCurrentData();
// Resetear todos los indicadores
digitalWrite(GREEN_LED, LOW);
digitalWrite(YELLOW_LED, LOW);
digitalWrite(RED_LED, LOW);
digitalWrite(BUZZER_PIN, LOW);
// Control basado en el tipo de evento
switch (current_event) {
case NORMAL_OPERATION:
digitalWrite(GREEN_LED, HIGH);
break;
case VOLTAGE_SAG:
case VOLTAGE_SWELL:
case FREQUENCY_DEVIATION:
// Eventos de advertencia - LED amarillo parpadeante
digitalWrite(YELLOW_LED, (millis() % 1000) < 500);
break;
case VOLTAGE_INTERRUPTION:
case HARMONIC_DISTORTION:
// Eventos críticos - LED rojo y alarma
digitalWrite(RED_LED, (millis() % 500) < 250);
digitalWrite(BUZZER_PIN, (millis() % 1000) < 100); // Beep corto
break;
}
}
Referencias Técnicas Especializadas
Documentación Técnica
Estándares Industriales
- IEEE 519: Harmonic Control in Electrical Power Systems
- IEC 61000-4-30: Power Quality Measurement Methods
- ANSI C84.1: Voltage Ratings for Electric Power Systems