diff --git a/Repromod_Tester_v1_comentado.ino b/Repromod_Tester_v1_comentado.ino new file mode 100644 index 0000000..9c1952f --- /dev/null +++ b/Repromod_Tester_v1_comentado.ino @@ -0,0 +1,804 @@ +/* +=============================================================== +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 +#include +#include //V.1.11.9 +#include //V.2.5.7 +#include // ROB TILLAART V.0.4.4 +#include + +// ======================== 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 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