repromod-tester/Repromod_Tester_v1_comentado.ino
2025-11-03 19:16:11 +01:00

804 lines
No EOL
22 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
===============================================================
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 (1821V). 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 &ampsOut){
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;
}
}