/* =============================================================== 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