Enviar arquivos para "/"

This commit is contained in:
Pablo César Galdo Regueiro 2025-11-03 19:16:11 +01:00
commit 3536a4b38f

View file

@ -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 (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 (5 = 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;
}
}