804 lines
No EOL
22 KiB
C++
804 lines
No EOL
22 KiB
C++
/*
|
||
===============================================================
|
||
REPROMOD TESTER – v1.0 (2025-10-31 21:47)
|
||
© 2025 Repromod® – Marca registrada
|
||
Licencia: Creative Commons BY-NC-SA 4.0 International
|
||
https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||
|
||
Descripción:
|
||
Analizador/inyector basado en Arduino Mega + INA226 + OLED SSD1306.
|
||
Modos: HOME/Gráfica SAFE (canal 1), Voltímetro, Tester Chip (CPU/GPU/VRAM/BUCK),
|
||
y Portátil (18–21V). SAFE protege por caída brusca de V; SAFE+ añade pico de corriente.
|
||
|
||
Dónde editar configuración en el código:
|
||
- Direcciones/Dimensiones OLED e I2C: buscar definiciones de OLED_* e INA_ADDR
|
||
- Calibraciones: SHUNT_OHMS, V_CAL, I_CAL
|
||
- Pines: RELAY1/RELAY2/BTN1/BTN2/BEEPER
|
||
|
||
Botonera:
|
||
- B1: Canal1 / acciones contextuales (armar, rearmar, medir)
|
||
- B2: Menú / salir / cambiar SAFE (según modo)
|
||
- B1+B2 (hold): Bloque de emergencia → ciclo relés y WDT reset
|
||
|
||
Bloque de emergencia:
|
||
Doble pulsación mantenida BTN1+BTN2 ejecuta la rutina de seguridad:
|
||
conmuta relés 3 veces y reinicia por watchdog (WDT).
|
||
|
||
===============================================================
|
||
*/
|
||
|
||
/*
|
||
===========================================================================
|
||
ESTRUCTURA PRINCIPAL DEL PROGRAMA:
|
||
|
||
1. CONFIGURACIÓN Y DEFINICIONES
|
||
- Bibliotecas incluidas
|
||
- Configuración hardware (OLED, INA226, pines)
|
||
- Constantes de calibración
|
||
- Enumeraciones y variables globales
|
||
|
||
2. FUNCIONES DE HARDWARE
|
||
- Control de relés
|
||
- Lectura de voltaje/corriente
|
||
- Manejo de botones
|
||
- Sistema beeper/audible
|
||
- Watchdog y seguridad
|
||
|
||
3. FUNCIONES DE VISUALIZACIÓN (OLED)
|
||
- Pantallas principales (HOME, MENÚ, etc.)
|
||
- Gráficas y representación de datos
|
||
- Gestión de sleep/awake display
|
||
|
||
4. MÁQUINAS DE ESTADO PRINCIPALES
|
||
- Modo HOME (Canal 1 con SAFE)
|
||
- Modo TEST CHIP (CPU/GPU/VRAM/BUCK)
|
||
- Modo VOLTIMETRO
|
||
- Modo PORTÁTIL (18-21V)
|
||
- Modo MENÚ
|
||
|
||
5. FUNCIONES AUXILIARES Y LÓGICA DE NEGOCIO
|
||
- Clasificación de resultados
|
||
- Protecciones SAFE/SAFE+
|
||
- Análisis de señales
|
||
===========================================================================
|
||
*/
|
||
|
||
#include <avr/wdt.h>
|
||
#include <Wire.h>
|
||
#include <Adafruit_GFX.h> //V.1.11.9
|
||
#include <Adafruit_SSD1306.h> //V.2.5.7
|
||
#include <INA226.h> // ROB TILLAART V.0.4.4
|
||
#include <math.h>
|
||
|
||
// ======================== CONFIGURACIÓN BÁSICA =================================
|
||
// ================================================================================
|
||
|
||
/*
|
||
CONFIGURACIÓN OLED - NO MODIFICAR RESOLUCIÓN
|
||
Display SSD1306 de 128x64 pixels conectado via I2C
|
||
*/
|
||
#define OLED_ADDR 0x3C
|
||
#define OLED_W 128
|
||
#define OLED_H 64
|
||
Adafruit_SSD1306 display(OLED_W, OLED_H, &Wire);
|
||
|
||
/*
|
||
CONFIGURACIÓN INA226 - SENSOR DE CORRIENTE/VOLTAJE
|
||
Dirección I2C: 0x40
|
||
*/
|
||
#define INA_ADDR 0x40
|
||
INA226 ina(INA_ADDR);
|
||
|
||
/*
|
||
CALIBRACIÓN DE CAMPO - AJUSTAR SEGÚN COMPONENTES REALES
|
||
- SHUNT_OHMS: Resistencia shunt (5mΩ = 0.005Ω)
|
||
- V_CAL: Factor de calibración voltaje (incluye divisor de tensión 100k/100k)
|
||
- I_CAL: Factor de calibración corriente (relación 2.47A → 2.00A = 0.81)
|
||
*/
|
||
const float SHUNT_OHMS = 0.005f; // 5 mΩ
|
||
const float V_CAL = 1.0501f * 2.0f; // Ajuste fino de voltaje (divisor x2)
|
||
const float I_CAL = 0.810f; // Ajuste fino de corriente
|
||
|
||
/*
|
||
CONFIGURACIÓN DE PINES ARDUINO MEGA
|
||
- RELAY1: Canal 1 con protección SAFE + LED indicador
|
||
- RELAY2: Canal 2 libre + LED indicador
|
||
- BTN1/BTN2: Botones de control con pull-up
|
||
- BEEPER: Salida para buzzer/altavoz
|
||
*/
|
||
#define RELAY1 7 // canal 1 con SAFE / Led indicador
|
||
#define RELAY2 6 // canal 2 libre / Led indicador
|
||
#define BTN1 9 // Botón 1 - Acciones principales
|
||
#define BTN2 10 // Botón 2 - Menú/SAFE
|
||
#define BEEPER 5 // Beeper - Feedback audible
|
||
|
||
// ======================= FIN CONFIGURACIÓN =====================================
|
||
// ================================================================================
|
||
|
||
/*
|
||
ESTADOS DEL CANAL 1 (SISTEMA SAFE)
|
||
- IDLE: Relé apagado, esperando activación
|
||
- ACTIVE: Relé activado, monitoreando parámetros
|
||
- TRIPPED: Protección activada, requiere rearme
|
||
*/
|
||
enum Ch1State {
|
||
CH1_IDLE = 0, // Esperando activación
|
||
CH1_ACTIVE, // Relé activado, monitoreando
|
||
CH1_TRIPPED // Protección disparada
|
||
};
|
||
Ch1State ch1State = CH1_IDLE;
|
||
|
||
/*
|
||
VARIABLES DE CONTROL DE INTERFAZ
|
||
- wakeSwallowInputs: Ignora primera pulsación tras despertar
|
||
- safeModeEnabled: Habilita/deshabilita protecciones
|
||
- Modos SAFE: V (solo voltaje), OFF (sin protección), PLUS (voltaje + corriente)
|
||
*/
|
||
bool wakeSwallowInputs = false;
|
||
bool safeModeEnabled = true;
|
||
|
||
enum SafeMode : uint8_t {
|
||
SAFE_V = 0, // Protección solo por caída de voltaje
|
||
SAFE_OFF = 1, // Sin protección
|
||
SAFE_PLUS = 2 // Protección voltaje + picos corriente
|
||
};
|
||
SafeMode safeMode = SAFE_V;
|
||
|
||
/*
|
||
PARÁMETROS DE PROTECCIÓN SAFE
|
||
- SAFE_SHORT_VOLTAGE: Umbral de detección de corto (0.4V)
|
||
- SAFE_ARM_DELAY_MS: Tiempo de estabilización post-activación
|
||
*/
|
||
const float SAFE_SHORT_VOLTAGE = 0.40f;
|
||
const unsigned long SAFE_ARM_DELAY_MS = 200;
|
||
|
||
/*
|
||
VARIABLES DE ESTADO GLOBALES
|
||
- lastTripWasPlus: Indica si último disparo fue por corriente
|
||
- ch1OnMs: Timestamp de activación del canal 1
|
||
- relay1/relay2: Estado actual de los relés
|
||
*/
|
||
bool lastTripWasPlus = false;
|
||
unsigned long ch1OnMs = 0;
|
||
bool relay1 = false;
|
||
bool relay2 = false;
|
||
|
||
/*
|
||
===========================================================================
|
||
SISTEMA TESTER CHIP (CPU/GPU/VRAM/BUCK)
|
||
===========================================================================
|
||
*/
|
||
|
||
// Selección de tipo de chip a testear
|
||
enum TestChipSel_TC : uint8_t {
|
||
TCSEL_CPU = 0, // Procesador principal
|
||
TCSEL_GPU = 1, // Unidad de procesamiento gráfico
|
||
TCSEL_VRAM = 2, // Memoria de video
|
||
TCSEL_BUCK = 3, // Convertidor buck/regulador
|
||
TCSEL_COUNT = 4 // Total de opciones
|
||
};
|
||
uint8_t testChipSel_TC = TCSEL_CPU;
|
||
|
||
// Fases del proceso de testeo
|
||
enum TestChipPhase_TC : uint8_t {
|
||
TCPH_READY = 0, // Esperando selección/configuración
|
||
TCPH_ARMED, // Preparado para medición
|
||
TCPH_MEASURE, // Realizando medición
|
||
TCPH_RESULT // Mostrando resultados
|
||
};
|
||
TestChipPhase_TC testPhase_TC = TCPH_READY;
|
||
|
||
// Resultados posibles del testeo
|
||
enum TestChipResult_TC : uint8_t {
|
||
TCR_NONE = 0, // Sin resultado
|
||
TCR_OK, // Chip funcionando correctamente
|
||
TCR_VLOW, // Voltaje demasiado bajo
|
||
TCR_VHIGH, // Voltaje demasiado alto
|
||
TCR_SHORT, // Cortocircuito detectado
|
||
TCR_FAIL, // Fallo general del chip
|
||
TCR_NOCON // Sin conexión/sin carga
|
||
};
|
||
TestChipResult_TC lastResult_TC = TCR_NONE;
|
||
|
||
/*
|
||
VARIABLES DE MEDICIÓN TEST CHIP
|
||
- Arrays para almacenamiento de muestras
|
||
- Valores mínimos/máximos/promedio
|
||
- Temporizadores de medición
|
||
*/
|
||
const uint16_t TC_TRACE_MAX = 120; // Máximo de muestras en gráfica
|
||
float tc_trace[TC_TRACE_MAX]; // Buffer de trazado de voltaje
|
||
uint16_t tc_traceCount = 0; // Contador de muestras actuales
|
||
|
||
float tc_vMin = 99.0f, tc_vMax = 0.0f, tc_vSum = 0.0f; // Estadísticas voltaje
|
||
float tc_aMin = 99.0f, tc_aMax = 0.0f, tc_aSum = 0.0f; // Estadísticas corriente
|
||
uint16_t tc_cnt = 0; // Contador total de muestras
|
||
|
||
// Umbrales de decisión para test chip
|
||
const float TC_OK_MIN_V = 0.90f; // Voltaje mínimo aceptable
|
||
const float TC_OK_MAX_V = 1.10f; // Voltaje máximo aceptable
|
||
const float TC_SHORT_V = 0.30f; // Umbral de cortocircuito
|
||
const float TC_HIGH_V = 1.40f; // Umbral de sobrevoltaje
|
||
|
||
/*
|
||
===========================================================================
|
||
SISTEMA PORTÁTIL (18-21V)
|
||
===========================================================================
|
||
*/
|
||
|
||
enum LaptopPhase {
|
||
LAPH_ADJ = 0, // Ajuste inicial de voltaje
|
||
LAPH_ANALYSIS = 1 // Análisis en curso
|
||
};
|
||
LaptopPhase laptopPhase = LAPH_ADJ;
|
||
|
||
// Parámetros de análisis portátil
|
||
const float LAPTOP_VMIN = 18.0f; // Voltaje mínimo aceptable
|
||
const float LAPTOP_VMAX = 21.0f; // Voltaje máximo aceptable
|
||
|
||
const float L_NO_POWER_A = 0.010f; // Corriente sin energía
|
||
const float L_STBY_MIN_A = 0.015f; // Mínimo corriente standby
|
||
const float L_STBY_MAX_A = 0.050f; // Máximo corriente standby
|
||
const float L_COMM_SWING_A = 0.020f; // Umbral comunicación
|
||
const float L_BOOT_A = 0.50f; // Umbral arranque
|
||
const float L_SHORT_I = 3.0f; // Corriente de cortocircuito
|
||
|
||
/*
|
||
===========================================================================
|
||
SISTEMA DE INTERFAZ Y MODOS DE OPERACIÓN
|
||
===========================================================================
|
||
*/
|
||
|
||
enum UiMode {
|
||
MODE_HOME = 0, // Pantalla principal + Canal 1 SAFE
|
||
MODE_MENU, // Menú de selección de funciones
|
||
MODE_TEST_CHIP, // Tester de chips (CPU/GPU/VRAM/BUCK)
|
||
MODE_VOLT, // Voltímetro de precisión
|
||
MODE_LAPTOP // Análisis de portátiles
|
||
};
|
||
UiMode uiMode = MODE_HOME;
|
||
|
||
/*
|
||
===========================================================================
|
||
FUNCIONES DE CONTROL DE HARDWARE
|
||
===========================================================================
|
||
*/
|
||
|
||
/**
|
||
* Control de relés con soporte para configuración activa baja/alta
|
||
* @param pin Pin del relé a controlar
|
||
* @param on Estado deseado (true=activado)
|
||
*/
|
||
inline void setRelay(uint8_t pin, bool on){
|
||
digitalWrite(pin, RELAY_ACTIVE_LOW ? (on ? LOW : HIGH) : (on ? HIGH : LOW));
|
||
}
|
||
|
||
/**
|
||
* Lectura calibrada de voltaje y corriente
|
||
* @param voltsOut Voltaje medido (salida)
|
||
* @param ampsOut Corriente medida (salida)
|
||
*/
|
||
void readVA(float &voltsOut, float &sOut){
|
||
float vOut = readVoltageCal();
|
||
float shunt_mV = readShuntVoltage_mV();
|
||
float shunt_V = shunt_mV / 1000.0f;
|
||
float amps = (shunt_V / SHUNT_OHMS) * I_CAL;
|
||
|
||
voltsOut = vOut;
|
||
ampsOut = amps;
|
||
}
|
||
|
||
/**
|
||
* Sistema de delay seguro con reset de watchdog
|
||
* @param ms Milisegundos a esperar
|
||
*/
|
||
void delaySafe(unsigned long ms){
|
||
unsigned long t0 = millis();
|
||
while (millis() - t0 < ms){
|
||
wdt_reset();
|
||
delay(10);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* BLOQUE DE EMERGENCIA - Doble pulsación prolongada BTN1+BTN2
|
||
* Realiza ciclo de relés y reinicio por watchdog
|
||
*/
|
||
void repromod_dualButtonResetHandler(){
|
||
static uint8_t state = 0;
|
||
static unsigned long t0 = 0;
|
||
const bool b1 = (digitalRead(BTN1) == LOW);
|
||
const bool b2 = (digitalRead(BTN2) == LOW);
|
||
|
||
switch(state){
|
||
case 0:
|
||
if (b1 && b2){ // Ambos botones presionados
|
||
t0 = millis();
|
||
state = 1; // Pasar a estado de verificación
|
||
}
|
||
break;
|
||
|
||
case 1:
|
||
if (!(b1 && b2)){ // Botones liberados prematuramente
|
||
state = 0;
|
||
break;
|
||
}
|
||
if (millis() - t0 >= 250){ // Pulsación mantenida >250ms
|
||
|
||
// Ciclo de seguridad de relés
|
||
for (int i = 0; i < 3; i++){
|
||
setRelay(RELAY1, true);
|
||
setRelay(RELAY2, true);
|
||
delay(120);
|
||
setRelay(RELAY1, false);
|
||
setRelay(RELAY2, false);
|
||
delay(200);
|
||
}
|
||
|
||
// Asegurar relés apagados
|
||
setRelay(RELAY1, false);
|
||
setRelay(RELAY2, false);
|
||
|
||
// Reinicio forzado por watchdog
|
||
wdt_enable(WDTO_15MS);
|
||
while (true) { }
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
===========================================================================
|
||
SISTEMA AUDIBLE (BEEPER)
|
||
===========================================================================
|
||
*/
|
||
|
||
/** Beep simple */
|
||
void beepOnce(uint16_t ms){
|
||
tone(BEEPER, 3000);
|
||
delay(ms);
|
||
noTone(BEEPER);
|
||
}
|
||
|
||
/** Beep múltiple con control de timing */
|
||
void beepCount(uint8_t n, uint16_t onMs=80, uint16_t offMs=80){
|
||
for(uint8_t i=0;i<n;i++){
|
||
beepOnce(onMs);
|
||
if(i+1<n) delay(offMs);
|
||
}
|
||
}
|
||
|
||
/** Alerta larga para advertencias importantes */
|
||
void beepLongWarning(){
|
||
tone(BEEPER, 2500);
|
||
delay(3000);
|
||
noTone(BEEPER);
|
||
}
|
||
|
||
/*
|
||
===========================================================================
|
||
SISTEMA DE DETECCIÓN DE BOTONES
|
||
===========================================================================
|
||
*/
|
||
|
||
// Variables para debounce y detección de pulsaciones largas
|
||
bool lastBtn1=HIGH, lastBtn2=HIGH;
|
||
unsigned long lastDeb1=0,lastDeb2=0;
|
||
const unsigned long DEBOUNCE_MS=50;
|
||
|
||
bool b2Held=false;
|
||
bool b2EscapeDone=false;
|
||
unsigned long b2PressStart=0;
|
||
const unsigned long B2_LONG_MS = 600;
|
||
|
||
/**
|
||
* Detección de clicks con debounce
|
||
* @param pin Pin del botón a verificar
|
||
* @return true si se detectó un click válido
|
||
*/
|
||
bool consumeClick(uint8_t pin){
|
||
// Ignorar primera pulsación tras despertar
|
||
if (wakeSwallowInputs){
|
||
if (digitalRead(BTN1)==HIGH && digitalRead(BTN2)==HIGH){
|
||
wakeSwallowInputs = false;
|
||
lastBtn1 = HIGH; lastBtn2 = HIGH;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Lógica de debounce
|
||
bool *lastPtr=(pin==BTN1)? &lastBtn1 : &lastBtn2;
|
||
unsigned long *debPtr=(pin==BTN1)? &lastDeb1 : &lastDeb2;
|
||
bool reading = digitalRead(pin);
|
||
unsigned long now = millis();
|
||
bool clicked = false;
|
||
|
||
if(reading != *lastPtr && (now - *debPtr) > DEBOUNCE_MS){
|
||
*debPtr = now;
|
||
if(reading == LOW){ // Detección de flanco descendente
|
||
clicked = true;
|
||
}
|
||
*lastPtr = reading;
|
||
}
|
||
return clicked;
|
||
}
|
||
|
||
/**
|
||
* Detección de pulsación larga en BTN2
|
||
* @return true si se detectó pulsación larga
|
||
*/
|
||
bool checkLongPressB2(){
|
||
bool b2Now = (digitalRead(BTN2)==LOW);
|
||
unsigned long now = millis();
|
||
|
||
if(!b2Held){
|
||
if(b2Now){ // Inicio de pulsación
|
||
b2Held=true;
|
||
b2EscapeDone=false;
|
||
b2PressStart=now;
|
||
}
|
||
} else {
|
||
if(!b2Now){ // Botón liberado
|
||
b2Held=false;
|
||
b2EscapeDone=false;
|
||
} else {
|
||
// Verificar si se alcanzó el tiempo de pulsación larga
|
||
if(!b2EscapeDone && (now - b2PressStart)>=B2_LONG_MS){
|
||
b2EscapeDone=true;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
===========================================================================
|
||
SISTEMA DE GESTIÓN DE PANTALLA OLED
|
||
===========================================================================
|
||
*/
|
||
|
||
bool displaySleeping=false;
|
||
unsigned long lastActivityMs=0;
|
||
const unsigned long OLED_SLEEP_MS = 20000; // 20 segundos hasta sleep
|
||
|
||
/** Registro de actividad para prevenir sleep */
|
||
void registerActivity(){
|
||
lastActivityMs = millis();
|
||
}
|
||
|
||
/** Apagar pantalla OLED */
|
||
void oledOff(){
|
||
if(!displaySleeping){
|
||
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
||
displaySleeping=true;
|
||
}
|
||
}
|
||
|
||
/** Encender pantalla OLED */
|
||
void oledOn(){
|
||
if(displaySleeping){
|
||
display.ssd1306_command(SSD1306_DISPLAYON);
|
||
displaySleeping=false;
|
||
}
|
||
}
|
||
|
||
/** Verificar si algún botón está presionado (sin debounce) */
|
||
bool rawBtnPressed(){
|
||
return (digitalRead(BTN1)==LOW) || (digitalRead(BTN2)==LOW);
|
||
}
|
||
|
||
/*
|
||
===========================================================================
|
||
FUNCIONES DE DIBUJADO EN OLED
|
||
===========================================================================
|
||
*/
|
||
|
||
/**
|
||
* Dibujar etiqueta invertida (texto negro sobre fondo blanco)
|
||
* @param x Coordenada X
|
||
* @param y Coordenada Y
|
||
* @param txt Texto a mostrar
|
||
*/
|
||
void drawInvertedLabel(int16_t x, int16_t y, const char *txt){
|
||
int16_t bx,by; uint16_t bw,bh;
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.getTextBounds(txt, x, y, &bx,&by,&bw,&bh);
|
||
|
||
// Fondo blanco
|
||
display.fillRect(bx-2, by-1, bw+4, bh+2, SSD1306_WHITE);
|
||
|
||
// Texto negro
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setCursor(x,y);
|
||
display.print(txt);
|
||
|
||
display.setTextColor(SSD1306_WHITE);
|
||
}
|
||
|
||
/**
|
||
* Pantalla de inicio en estado IDLE
|
||
*/
|
||
void drawHomeIdle(){
|
||
display.clearDisplay();
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
// Instrucciones de botones
|
||
display.setCursor(3, 54);
|
||
display.print("B1 canal1");
|
||
display.setCursor(79, 54);
|
||
display.print("B2 menu");
|
||
|
||
// Logo y versión
|
||
{
|
||
const char *txt="REPROMOD";
|
||
int16_t x1,y1; uint16_t w1,h1;
|
||
display.setTextSize(2);
|
||
display.getTextBounds(txt,0,0,&x1,&y1,&w1,&h1);
|
||
|
||
// Marco alrededor del logo
|
||
int16_t boxX = (OLED_W - (w1+6)) / 2;
|
||
if(boxX<0) boxX=0;
|
||
int16_t boxY = 8;
|
||
uint16_t boxW = w1+6;
|
||
uint16_t boxH = h1+4;
|
||
|
||
display.fillRect(boxX,boxY,boxW,boxH,SSD1306_WHITE);
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setCursor(boxX+3, boxY+2);
|
||
display.print(txt);
|
||
|
||
// Versión del firmware
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
int16_t vx,vy; uint16_t vw,vh;
|
||
display.getTextBounds(FW_VERSION,0,0,&vx,&vy,&vw,&vh);
|
||
display.setCursor((OLED_W - vw)/2, boxY+boxH+6);
|
||
display.print(FW_VERSION);
|
||
}
|
||
|
||
display.display();
|
||
}
|
||
|
||
// ... (continuaría con el resto de funciones de dibujo con comentarios similares)
|
||
|
||
/*
|
||
===========================================================================
|
||
MÁQUINAS DE ESTADO PRINCIPALES
|
||
===========================================================================
|
||
*/
|
||
|
||
/**
|
||
* Manejo del modo HOME (Canal 1 con protección SAFE)
|
||
*/
|
||
void handleHomeMode(){
|
||
if(checkLongPressB2()){
|
||
beepOnce(40);
|
||
forceGoHome();
|
||
registerActivity();
|
||
return;
|
||
}
|
||
|
||
switch(ch1State){
|
||
case CH1_IDLE:
|
||
// Esperando activación del canal 1
|
||
if(consumeClick(BTN1)){
|
||
if(!displaySleeping){
|
||
beepOnce(50);
|
||
armCh1(); // Activar canal 1
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if(consumeClick(BTN2)){
|
||
if(!displaySleeping){
|
||
beepOnce(40);
|
||
uiMode=MODE_MENU; // Ir al menú principal
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if(!displaySleeping){
|
||
drawHomeIdle();
|
||
}
|
||
delay(40);
|
||
break;
|
||
|
||
case CH1_ACTIVE:
|
||
// Canal 1 activo - monitoreando parámetros
|
||
if(consumeClick(BTN1)){
|
||
if(!displaySleeping){
|
||
beepOnce(50);
|
||
// Desactivar canal 1
|
||
relay1=false;
|
||
setRelay(RELAY1,false);
|
||
ch1State=CH1_IDLE;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Cambio de modo SAFE con BTN2
|
||
if (consumeClick(BTN2)){
|
||
if(!displaySleeping){
|
||
safeMode = (SafeMode)((((int)safeMode) + 1) % 3);
|
||
|
||
// Feedback audible según modo
|
||
switch(safeMode){
|
||
case SAFE_V: beepOnce(80); break;
|
||
case SAFE_OFF: beepOnce(20); break;
|
||
case SAFE_PLUS: beepCount(2,40,60); break;
|
||
}
|
||
|
||
ch1OnMs = millis(); // Reset timer de activación
|
||
registerActivity();
|
||
}
|
||
}
|
||
|
||
// Lectura de parámetros actuales
|
||
float vNow=0.0f, aNow=0.0f;
|
||
readVA(vNow,aNow);
|
||
unsigned long nowMs = millis();
|
||
|
||
// Verificación de protecciones SAFE
|
||
if (safeMode != SAFE_OFF){
|
||
if ((nowMs - ch1OnMs) >= SAFE_ARM_DELAY_MS) {
|
||
if (vNow < SAFE_SHORT_VOLTAGE) {
|
||
// Disparo por bajo voltaje
|
||
relay1=false; setRelay(RELAY1,false);
|
||
lastTripWasPlus = false;
|
||
beepCount(3);
|
||
lastShortTrip=true;
|
||
ch1State=CH1_TRIPPED;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Protección SAFE+ (detección de picos de corriente)
|
||
if (safeMode == SAFE_PLUS){
|
||
// ... (lógica de detección de picos de corriente)
|
||
}
|
||
|
||
// Actualización de gráfica y visualización
|
||
if(!displaySleeping){
|
||
// ... (cálculos para gráfica y dibujado)
|
||
}
|
||
|
||
delay(80);
|
||
break;
|
||
|
||
case CH1_TRIPPED:
|
||
// Estado de protección disparada - requiere rearme
|
||
if (consumeClick(BTN1)) {
|
||
if (!displaySleeping) {
|
||
beepOnce(50);
|
||
armCh1(); // Re-armar canal 1
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (consumeClick(BTN2)) {
|
||
if (!displaySleeping) {
|
||
beepOnce(40);
|
||
// Volver a estado IDLE
|
||
setRelay(RELAY1, false);
|
||
relay1 = false;
|
||
ch1State = CH1_IDLE;
|
||
uiMode = MODE_HOME;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (!displaySleeping) {
|
||
drawTripScreen(); // Mostrar pantalla de protección
|
||
}
|
||
|
||
delay(40);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
===========================================================================
|
||
CONFIGURACIÓN INICIAL (SETUP)
|
||
===========================================================================
|
||
*/
|
||
|
||
void setup(){
|
||
// Inicialización INA226
|
||
ina.begin();
|
||
ina.setMaxCurrentShunt(16.0f, 0.005f);
|
||
|
||
// Configuración de pines
|
||
pinMode(RELAY1,OUTPUT);
|
||
pinMode(RELAY2,OUTPUT);
|
||
setRelay(RELAY1,false); relay1=false;
|
||
setRelay(RELAY2,false); relay2=false;
|
||
|
||
pinMode(BTN1,INPUT_PULLUP);
|
||
pinMode(BTN2,INPUT_PULLUP);
|
||
pinMode(BEEPER,OUTPUT);
|
||
noTone(BEEPER);
|
||
|
||
// Inicialización I2C y OLED
|
||
Wire.begin();
|
||
Wire.setClock(100000);
|
||
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
|
||
display.setRotation(2);
|
||
display.clearDisplay();
|
||
display.display();
|
||
|
||
// Configuración watchdog
|
||
wdt_enable(WDTO_2S);
|
||
|
||
// Inicialización de variables
|
||
vref = readVoltageCal();
|
||
for(uint8_t i=0;i<PW;i++) gbuf[i]=0;
|
||
head=0;
|
||
|
||
// Estado inicial
|
||
uiMode = MODE_HOME;
|
||
ch1State = CH1_IDLE;
|
||
mainSel = 0;
|
||
testChipSel = 0;
|
||
safeModeEnabled = true;
|
||
lastShortTrip=false;
|
||
tripScreenDrawn=false;
|
||
ch1OnMs=0;
|
||
|
||
displaySleeping=false;
|
||
lastActivityMs=millis();
|
||
|
||
// Mostrar pantalla de inicio
|
||
drawHomeIdle();
|
||
}
|
||
|
||
/*
|
||
===========================================================================
|
||
BUCLE PRINCIPAL (LOOP)
|
||
===========================================================================
|
||
*/
|
||
|
||
void loop(){
|
||
// Reset del watchdog en cada iteración
|
||
wdt_reset();
|
||
|
||
// Gestión de sleep de pantalla
|
||
handleOledSleep();
|
||
|
||
// Verificación de bloque de emergencia
|
||
repromod_dualButtonResetHandler();
|
||
|
||
// Si la pantalla está en sleep, minimizar procesamiento
|
||
if(displaySleeping){
|
||
delay(10);
|
||
return;
|
||
}
|
||
|
||
// Ejecutar máquina de estado según modo actual
|
||
switch(uiMode){
|
||
case MODE_HOME:
|
||
handleHomeMode(); // Canal 1 + Gráfica SAFE
|
||
break;
|
||
case MODE_MENU:
|
||
handleMainMenu(); // Menú principal
|
||
break;
|
||
case MODE_TEST_CHIP:
|
||
handleTestChip_TC(); // Tester de chips
|
||
break;
|
||
case MODE_VOLT:
|
||
handleVoltimetro(); // Voltímetro
|
||
break;
|
||
case MODE_LAPTOP:
|
||
handleLaptop_TC(); // Análisis de portátiles
|
||
break;
|
||
}
|
||
} |