2937 lines
74 KiB
C++
2937 lines
74 KiB
C++
/*
|
||
===============================================================
|
||
REPROMOD TESTER – v1.4 (04-11-2025 18: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).
|
||
|
||
===============================================================
|
||
*/
|
||
/* Notas para operador (resumen práctico)
|
||
- SAFE / SAFE+: en la gráfica de Canal 1, pulsa B2 para rotar: SAFE → NO SAFE → SAFE+.
|
||
SAFE corta si V < 0.30 V tras el arm (protege por corto). SAFE+ añade corte por pico de I.
|
||
- Voltímetro: B2 para salir (aviso pinzas). Estado: ESTABLE / FLUCTUANTE / PULSANTE.
|
||
- Tester Chip: B2 rota la categoría; B1 inicia la medición. Si el ajuste no es 1.0 V (1.2 V VRAM), avisa.
|
||
- Portátil: B1 valida 18–21 V y entra en análisis. B1 conmuta relé; B2 vuelve atrás.
|
||
- OLED Sleep: solo en HOME+IDLE. Cualquier tecla despierta (se ignora el primer click tras despertar).
|
||
*/
|
||
/*───────────────────────────────────────────────────────────────
|
||
🧾 CHANGELOG – REPROMOD TESTER FIRMWARE
|
||
───────────────────────────────────────────────────────────────
|
||
Base oficial: v1.0 (31-10-2025 21:47)
|
||
Hardware: Arduino MEGA 2560 Pro, INA226, OLED SSD1306 128×64
|
||
───────────────────────────────────────────────────────────────
|
||
v1.4
|
||
• Añadido mensaje “RETIRE LA BATERÍA” en modo Portátil (fase de ajuste).
|
||
• Corrección visual: flecha “<B2” (antes “B2<”) en todas las pantallas.
|
||
• Beep corto cada 5 s cuando el análisis Portátil está en ON.
|
||
• Parpadeo visual del texto “ANÁLISIS” (blanco/negro) según estado ON/OFF.
|
||
• Mantenimiento de relé activo en análisis, visualización estable.
|
||
• Implementado modo CALIBRACIÓN:
|
||
- Nueva opción en menú principal.
|
||
- Paso 1: comprobación 10 V con resistencia R100 5 W.
|
||
- Paso 2: ajuste de corriente (0.100 A ±0.005 A).
|
||
- Paso 3: ajuste de voltaje (10.00 V ±0.05 V).
|
||
- Guardado automático en EEPROM (factores calI_k / calV_k).
|
||
- Relé activo durante calibración para lectura continua.
|
||
- Mensajes de error y confirmación “CALIBRACIÓN COMPLETADA”.
|
||
• Refinado muestreo de lectura INA226 durante ajustes.
|
||
• Ajustes menores de sincronización y presentación OLED.
|
||
|
||
───────────────────────────────────────────────────────────────*/
|
||
|
||
#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// =====================================================
|
||
// PANTALLA OLED *No altere la resolución
|
||
#define OLED_ADDR 0x3C
|
||
#define OLED_W 128
|
||
#define OLED_H 64
|
||
Adafruit_SSD1306 display(OLED_W, OLED_H, &Wire);
|
||
|
||
// INA226 *Cálculo de medición en referencia al valor de la fuente
|
||
#define INA_ADDR 0x40
|
||
INA226 ina(INA_ADDR);
|
||
// Calibración campo
|
||
const float SHUNT_OHMS = 0.005f; // 5 mΩ, si tienes otra resistencia cámbiala en tu placa a esta
|
||
// Si metiste divisor de tensión de 100k/100k a VBUS multiplicamos x2:
|
||
const float V_CAL = 1.0501f * 2.0f; // Valor de fuente ejem. 10.5v / Valor de Ina226 ejem. 5v = V_CAL (2.10f)
|
||
// Ajuste el fino de amperajes
|
||
const float I_CAL = 0.810f; // Este ajuste es para (2.47A → 2.00A) relación 1:!
|
||
|
||
// CONFIGURACIÓN DE PINES
|
||
#define RELAY1 7 // canal 1 con SAFE / Led indicador
|
||
#define RELAY2 6 // canal 2 libre / Led indicador
|
||
#define BTN1 9 // Botón 1
|
||
#define BTN2 10 // Botón 2
|
||
#define BEEPER 5 // Beeper
|
||
|
||
// =============================FIN DE CONFIGURACIÓN// ================================================
|
||
|
||
enum Ch1State {
|
||
CH1_IDLE = 0,
|
||
CH1_ACTIVE,
|
||
CH1_TRIPPED
|
||
};
|
||
Ch1State ch1State = CH1_IDLE;
|
||
|
||
bool wakeSwallowInputs = false;
|
||
bool safeModeEnabled = true;
|
||
float tc_lastAdjV = NAN;
|
||
float tc_lastAdjA = NAN;
|
||
const float SAFE_SHORT_VOLTAGE = 0.40f;
|
||
const unsigned long SAFE_ARM_DELAY_MS = 200;
|
||
|
||
enum SafeMode : uint8_t { SAFE_V = 0, SAFE_OFF = 1, SAFE_PLUS = 2 };
|
||
SafeMode safeMode = SAFE_V;
|
||
|
||
bool lastTripWasPlus = false;
|
||
unsigned long ch1OnMs = 0;
|
||
|
||
float tc_aPrev = NAN;
|
||
float tc_aP2P = 0.0f;
|
||
float tc_aStepMax = 0.0f;
|
||
uint16_t tc_runMs = 4000;
|
||
uint16_t tc_settleMs = 200;
|
||
bool lastShortTrip = false;
|
||
bool tripScreenDrawn = false;
|
||
|
||
enum TestChipSel_TC : uint8_t {
|
||
TCSEL_CPU = 0,
|
||
TCSEL_GPU = 1,
|
||
TCSEL_VRAM = 2,
|
||
TCSEL_BUCK = 3,
|
||
TCSEL_COUNT = 4
|
||
};
|
||
uint8_t testChipSel_TC = TCSEL_CPU;
|
||
|
||
enum TestChipPhase_TC : uint8_t {
|
||
TCPH_READY = 0,
|
||
TCPH_ARMED,
|
||
TCPH_MEASURE,
|
||
TCPH_RESULT
|
||
};
|
||
TestChipPhase_TC testPhase_TC = TCPH_READY;
|
||
|
||
enum LaptopPhase { LAPH_ADJ = 0, LAPH_ANALYSIS = 1 };
|
||
LaptopPhase laptopPhase = LAPH_ADJ;
|
||
|
||
enum TestChipResult_TC : uint8_t {
|
||
TCR_NONE = 0,
|
||
TCR_OK,
|
||
TCR_VLOW,
|
||
TCR_VHIGH,
|
||
TCR_SHORT,
|
||
TCR_FAIL,
|
||
TCR_NOCON
|
||
};
|
||
TestChipResult_TC lastResult_TC = TCR_NONE;
|
||
|
||
bool RELAY_ACTIVE_LOW = false;
|
||
inline void setRelay(uint8_t pin, bool on){
|
||
digitalWrite(pin,
|
||
RELAY_ACTIVE_LOW
|
||
? (on ? LOW : HIGH)
|
||
: (on ? HIGH : LOW)
|
||
);
|
||
}
|
||
|
||
bool relay1=false;
|
||
bool relay2=false;
|
||
|
||
const char *FW_VERSION = "Tester v1.4";
|
||
|
||
const uint16_t TC_TRACE_MAX = 120;
|
||
float tc_trace[TC_TRACE_MAX];
|
||
uint16_t tc_traceCount = 0;
|
||
|
||
uint8_t tc_finalCode = 4;
|
||
|
||
float tc_vMin = 99.0f;
|
||
float tc_vMax = 0.0f;
|
||
float tc_vSum = 0.0f;
|
||
uint16_t tc_cnt = 0;
|
||
float tc_lastCheckVolt = 0.0f;
|
||
|
||
float tc_aMin = 99.0f;
|
||
float tc_aMax = 0.0f;
|
||
float tc_aSum = 0.0f;
|
||
|
||
float tc_vMinStable = 99.0f;
|
||
float tc_vMaxStable = 0.0f;
|
||
float tc_aSumStable = 0.0f;
|
||
uint16_t tc_cntStable = 0;
|
||
float tc_aMinStable = 99.0f;
|
||
float tc_aMaxStable = 0.0f;
|
||
|
||
unsigned long tc_stableStartMs = 0;
|
||
float tc_aSumEarly = 0.0f; uint16_t tc_aCntEarly = 0;
|
||
float tc_aSumLate = 0.0f; uint16_t tc_aCntLate = 0;
|
||
|
||
unsigned long tcMeasureStartMs = 0;
|
||
unsigned long TC_MEASURE_WINDOW_MS = 2000;
|
||
|
||
const float TC_OK_MIN_V = 0.90f;
|
||
const float TC_OK_MAX_V = 1.10f;
|
||
const float TC_SHORT_V = 0.30f;
|
||
const float TC_HIGH_V = 1.40f;
|
||
|
||
bool laptopNeedsAdj = true;
|
||
|
||
const float LAPTOP_VMIN = 18.0f;
|
||
const float LAPTOP_VMAX = 21.0f;
|
||
|
||
const float L_NO_POWER_A = 0.010f;
|
||
const float L_STBY_MIN_A = 0.015f;
|
||
const float L_STBY_MAX_A = 0.050f;
|
||
const float L_COMM_SWING_A = 0.020f;
|
||
const float L_BOOT_A = 0.50f;
|
||
const float L_SHORT_I = 3.0f;
|
||
const float L_SHORT_DROP_P = 0.10f;
|
||
|
||
static float lap_aFast = 0.0f, lap_aSlow = 0.0f, lap_aSwingPeak = 0.0f;
|
||
static float lap_vBase = 0.0f;
|
||
static bool lap_lastRelayOn = false;
|
||
static bool lap_wasAboveStandby = false;
|
||
static unsigned long lap_lastAboveTs = 0;
|
||
static unsigned long lap_lastSampleMs = 0;
|
||
static float lap_aMax = 0.0f, lap_aMin = 99.0f;
|
||
#include <avr/wdt.h>
|
||
void delaySafe(unsigned long ms){
|
||
unsigned long t0 = millis();
|
||
while (millis() - t0 < ms){
|
||
wdt_reset();
|
||
delay(10);
|
||
}
|
||
}
|
||
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){
|
||
t0 = millis();
|
||
state = 1;
|
||
}
|
||
break;
|
||
|
||
case 1:
|
||
if (!(b1 && b2)){
|
||
state = 0;
|
||
break;
|
||
}
|
||
if (millis() - t0 >= 250){
|
||
|
||
for (int i = 0; i < 3; i++){
|
||
setRelay(RELAY1, true);
|
||
setRelay(RELAY2, true);
|
||
delay(120);
|
||
|
||
setRelay(RELAY1, false);
|
||
setRelay(RELAY2, false);
|
||
delay(200);
|
||
}
|
||
|
||
setRelay(RELAY1, false);
|
||
setRelay(RELAY2, false);
|
||
|
||
wdt_enable(WDTO_15MS);
|
||
while (true) { }
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
void beepOnce(uint16_t ms){
|
||
tone(BEEPER, 3000);
|
||
delay(ms);
|
||
noTone(BEEPER);
|
||
}
|
||
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);
|
||
}
|
||
}
|
||
|
||
void beepLongWarning(){
|
||
tone(BEEPER, 2500);
|
||
delay(3000);
|
||
noTone(BEEPER);
|
||
}
|
||
|
||
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;
|
||
bool consumeClick(uint8_t pin){
|
||
|
||
if (wakeSwallowInputs){
|
||
|
||
if (digitalRead(BTN1)==HIGH && digitalRead(BTN2)==HIGH){
|
||
wakeSwallowInputs = false;
|
||
|
||
lastBtn1 = HIGH; lastBtn2 = HIGH;
|
||
}
|
||
return false;
|
||
}
|
||
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){
|
||
clicked = true;
|
||
}
|
||
*lastPtr = reading;
|
||
}
|
||
return clicked;
|
||
}
|
||
|
||
bool checkLongPressB2(){
|
||
bool b2Now = (digitalRead(BTN2)==LOW);
|
||
unsigned long now = millis();
|
||
|
||
if(!b2Held){
|
||
if(b2Now){
|
||
b2Held=true;
|
||
b2EscapeDone=false;
|
||
b2PressStart=now;
|
||
}
|
||
} else {
|
||
if(!b2Now){
|
||
b2Held=false;
|
||
b2EscapeDone=false;
|
||
} else {
|
||
if(!b2EscapeDone && (now - b2PressStart)>=B2_LONG_MS){
|
||
b2EscapeDone=true;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool voltCheckExitHold(){
|
||
static bool vHeld=false;
|
||
static unsigned long vStart=0;
|
||
static bool fired=false;
|
||
|
||
bool nowPressed = (digitalRead(BTN2)==LOW);
|
||
unsigned long nowMs = millis();
|
||
|
||
if(!vHeld){
|
||
if(nowPressed){
|
||
vHeld=true;
|
||
vStart=nowMs;
|
||
fired=false;
|
||
}
|
||
} else {
|
||
if(!nowPressed){
|
||
vHeld=false;
|
||
} else {
|
||
if(!fired && (nowMs - vStart)>=B2_LONG_MS){
|
||
fired=true;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
#ifndef REPROMOD_COMPAT_FWD_INCLUDED
|
||
#define REPROMOD_COMPAT_FWD_INCLUDED
|
||
|
||
#include <stdint.h>
|
||
|
||
enum TestChipResult_TC : uint8_t;
|
||
|
||
void beepOnce(uint16_t ms);
|
||
void beepCount(uint8_t n, uint16_t onMs = 80, uint16_t offMs = 80);
|
||
|
||
extern const unsigned long SAFE_ARM_DELAY_MS;
|
||
|
||
class Adafruit_SSD1306;
|
||
extern Adafruit_SSD1306 display;
|
||
|
||
void setRelay(uint8_t pin, bool on);
|
||
void readVA(float &voltsOut, float &sOut);
|
||
void registerActivity(void);
|
||
|
||
extern bool relay1;
|
||
extern unsigned long ch1OnMs;
|
||
extern bool lastShortTrip;
|
||
|
||
#endif
|
||
|
||
bool displaySleeping=false;
|
||
unsigned long lastActivityMs=0;
|
||
const unsigned long OLED_SLEEP_MS = 20000;
|
||
|
||
void registerActivity(){
|
||
lastActivityMs = millis();
|
||
}
|
||
|
||
void oledOff(){
|
||
if(!displaySleeping){
|
||
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
||
displaySleeping=true;
|
||
}
|
||
}
|
||
void oledOn(){
|
||
if(displaySleeping){
|
||
display.ssd1306_command(SSD1306_DISPLAYON);
|
||
displaySleeping=false;
|
||
}
|
||
}
|
||
|
||
bool rawBtnPressed(){
|
||
return (digitalRead(BTN1)==LOW) || (digitalRead(BTN2)==LOW);
|
||
}
|
||
|
||
enum UiMode {
|
||
MODE_HOME = 0,
|
||
MODE_MENU,
|
||
MODE_TEST_CHIP,
|
||
MODE_VOLT,
|
||
MODE_LAPTOP,
|
||
MODE_CALIB
|
||
};
|
||
UiMode uiMode = MODE_HOME;
|
||
|
||
enum MainMenuItem {
|
||
MM_CANAL2 = 0,
|
||
MM_TESTCHIP,
|
||
MM_MEDICION,
|
||
MM_LAPTOP,
|
||
MM_CALIBRACION,
|
||
MM_COUNT
|
||
};
|
||
uint8_t mainSel = 0;
|
||
|
||
enum TestChipItem {
|
||
TC_CPU = 0,
|
||
TC_GPU,
|
||
TC_VRAM,
|
||
TC_BUCK,
|
||
TC_COUNT
|
||
};
|
||
uint8_t testChipSel = 0;
|
||
|
||
const uint8_t PW=118;
|
||
const uint8_t PH=24;
|
||
const uint8_t GX=5;
|
||
const uint8_t GY=20;
|
||
uint8_t gbuf[PW];
|
||
uint8_t head=0;
|
||
|
||
float aHist[4] = {0,0,0,0};
|
||
unsigned long tHist[4] = {0,0,0,0};
|
||
uint8_t aHidx = 0;
|
||
bool aHistPrimed = false;
|
||
const float GRAPH_MAX_DROP = 1.0f;
|
||
|
||
float vref = 0.0f;
|
||
const float VREF_HYST = 0.40f;
|
||
const unsigned long VREF_REARM_HOLD_MS=1000;
|
||
|
||
#define INA_SHUNT_REG 0x01
|
||
|
||
float readVoltageCal(){
|
||
return ina.getBusVoltage() * V_CAL;
|
||
}
|
||
|
||
int16_t inaReadRegister16(uint8_t reg){
|
||
Wire.beginTransmission(INA_ADDR);
|
||
Wire.write(reg);
|
||
Wire.endTransmission(false);
|
||
Wire.requestFrom(INA_ADDR,(uint8_t)2);
|
||
if(Wire.available()<2) return 0;
|
||
uint16_t msb=Wire.read();
|
||
uint16_t lsb=Wire.read();
|
||
uint16_t raw=(msb<<8)|lsb;
|
||
return (int16_t)raw;
|
||
}
|
||
|
||
float readShuntVoltage_mV(){
|
||
int16_t raw=inaReadRegister16(INA_SHUNT_REG);
|
||
return (float)raw * 0.0025f;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
float aSlow = 0.0f;
|
||
float aPeakSwing = 0.0f;
|
||
unsigned long lastVoltSampleMs=0;
|
||
|
||
const float FLUCT_THRESH = 0.01f;
|
||
const float PULSE_THRESH = 0.05f;
|
||
|
||
void drawLaptopAnalysisSimple(const char* msg, float ampsNow, bool relayOn) {
|
||
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
static bool blink = false;
|
||
static unsigned long blinkTs = 0;
|
||
if (millis() - blinkTs >= 500) {
|
||
blink = !blink;
|
||
blinkTs = millis();
|
||
}
|
||
|
||
display.setTextSize(2);
|
||
if (relayOn && blink) {
|
||
display.fillRect(0, 0, 128, 20, SSD1306_WHITE);
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setCursor(18, 2);
|
||
display.print("ANALISIS");
|
||
} else {
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(18, 0);
|
||
display.print("ANALISIS");
|
||
}
|
||
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.fillRect(0, 16, 160, 14, SSD1306_WHITE);
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setTextSize(1);
|
||
display.setCursor(2, 20);
|
||
display.print(msg);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
display.setTextSize(2);
|
||
display.setCursor(10, 34);
|
||
display.print(fabs(ampsNow), 3);
|
||
display.print("A");
|
||
|
||
float vNow = 0.0f, aNow = 0.0f;
|
||
readVA(vNow, aNow);
|
||
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(48, 52);
|
||
int vDisplay = (int)vNow;
|
||
int vDec = (int)((vNow - vDisplay) * 10);
|
||
if (vDisplay < 10) display.print('0');
|
||
display.print(vDisplay);
|
||
display.print('.');
|
||
display.print(vDec);
|
||
display.print('V');
|
||
extern float lap_aMax, lap_aMin;
|
||
display.setCursor(88, 35);
|
||
display.print("A Max");
|
||
display.setCursor(88, 43);
|
||
display.print(lap_aMax, 2);
|
||
display.fillRect(2, 52, 40, 14, SSD1306_WHITE);
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setTextSize(1);
|
||
display.setCursor(5, 54);
|
||
display.print("B1 ");
|
||
display.print(relayOn ? "ON" : "OFF");
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
display.setCursor(98, 54);
|
||
display.print("< B2");
|
||
|
||
display.display();
|
||
}
|
||
|
||
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);
|
||
|
||
display.fillRect(bx-2, by-1, bw+4, bh+2, SSD1306_WHITE);
|
||
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setCursor(x,y);
|
||
display.print(txt);
|
||
|
||
display.setTextColor(SSD1306_WHITE);
|
||
}
|
||
|
||
void drawHomeIdle(){
|
||
display.clearDisplay();
|
||
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
display.setCursor(3, 54);
|
||
display.print("B1 canal1");
|
||
|
||
display.setCursor(79, 54);
|
||
display.print("B2 menu");
|
||
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
{
|
||
const char *txt="REPROMOD";
|
||
int16_t x1,y1; uint16_t w1,h1;
|
||
display.setTextSize(2);
|
||
display.getTextBounds(txt,0,0,&x1,&y1,&w1,&h1);
|
||
|
||
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);
|
||
|
||
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();
|
||
}
|
||
|
||
void drawTripScreen() {
|
||
display.clearDisplay();
|
||
|
||
display.setTextSize(2);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(2, 2);
|
||
display.println(F("PROTECCION"));
|
||
|
||
display.fillRect(0, 28, 128, 14, SSD1306_WHITE);
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setTextSize(1);
|
||
display.setCursor(4, 32);
|
||
if (lastTripWasPlus) {
|
||
display.println(F(" Pico de corriente"));
|
||
} else {
|
||
display.println(F(" Caida de voltaje"));
|
||
}
|
||
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(1);
|
||
display.setCursor(2, 54);
|
||
display.println(F("B1> Rearmar"));
|
||
display.setCursor(94, 54);
|
||
display.println(F("< B2"));
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawChannel1Frame(float v, float a, float dropV, float pct, bool safeOn){
|
||
display.clearDisplay();
|
||
|
||
display.fillRect(0,0,128,12,SSD1306_BLACK);
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(0,0);
|
||
display.print(v,2); display.print("v ");
|
||
display.print(a,2); display.print("A");
|
||
|
||
const int16_t STATUS_X = 76;
|
||
const int16_t STATUS_Y = 0;
|
||
const int16_t STATUS_W = 52;
|
||
const int16_t STATUS_H = 12;
|
||
|
||
display.fillRect(STATUS_X, STATUS_Y, STATUS_W, STATUS_H, SSD1306_BLACK);
|
||
|
||
switch (safeMode) {
|
||
case SAFE_V: {
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(STATUS_X + 12, STATUS_Y);
|
||
display.print("SAFE");
|
||
} break;
|
||
|
||
case SAFE_PLUS: {
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(STATUS_X + 6, STATUS_Y);
|
||
display.print("SAFE+");
|
||
} break;
|
||
|
||
case SAFE_OFF: {
|
||
|
||
static bool blink=false;
|
||
static unsigned long t0=0;
|
||
unsigned long now=millis();
|
||
if (now - t0 > 500) { t0 = now; blink = !blink; }
|
||
|
||
if (blink) {
|
||
display.fillRect(STATUS_X, STATUS_Y, STATUS_W, STATUS_H, SSD1306_WHITE);
|
||
display.drawRect(STATUS_X, STATUS_Y, STATUS_W, STATUS_H, SSD1306_BLACK);
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setCursor(STATUS_X + 6, STATUS_Y + 1);
|
||
display.print("NO SAFE");
|
||
display.setTextColor(SSD1306_WHITE);
|
||
} else {
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(STATUS_X + 6, STATUS_Y);
|
||
display.print("NO SAFE");
|
||
}
|
||
} break;
|
||
}
|
||
|
||
const uint8_t PLOT_TOP = 14;
|
||
const uint8_t PLOT_BOTTOM = OLED_H - 18;
|
||
const uint8_t plotH = (PLOT_BOTTOM > PLOT_TOP) ? (PLOT_BOTTOM - PLOT_TOP) : 1;
|
||
|
||
for(uint8_t band=1; band<=4; band++){
|
||
uint8_t yGuide = PLOT_TOP + (uint16_t)plotH * band / 5;
|
||
for(uint8_t x=GX; x < (uint8_t)(GX+PW); x++){
|
||
if((x % 4) < 2){
|
||
if(yGuide < OLED_H) display.drawPixel(x, yGuide, SSD1306_WHITE);
|
||
}
|
||
}
|
||
}
|
||
|
||
{
|
||
uint8_t x = GX;
|
||
uint8_t idx = head;
|
||
for(uint8_t i=0; i<PW; i++){
|
||
|
||
uint8_t oldY = gbuf[idx];
|
||
uint8_t yScaled = (uint8_t)((uint32_t)oldY * (uint32_t)(plotH-1) / (uint32_t)((PH>1)?(PH-1):1));
|
||
uint8_t baseY = PLOT_TOP + yScaled;
|
||
|
||
for(int8_t dy=-1; dy<=1; dy++){
|
||
int16_t yy = (int16_t)baseY + dy;
|
||
if(yy>=0 && yy<OLED_H) display.drawPixel(x, yy, SSD1306_WHITE);
|
||
}
|
||
|
||
x++;
|
||
idx = (idx+1) % PW;
|
||
}
|
||
}
|
||
|
||
int mvInt = (int)(dropV*1000.0f+0.5f); if(mvInt>999) mvInt=999;
|
||
int pctInt=(int)(pct+0.5f);
|
||
if(pctInt<0) pctInt=0; if(pctInt>99) pctInt=99;
|
||
|
||
display.setTextSize(2);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(2, OLED_H-18);
|
||
|
||
if(mvInt<100) display.print('0');
|
||
if(mvInt<10) display.print('0');
|
||
display.print(mvInt);
|
||
display.print("mV ");
|
||
|
||
if(pctInt<10) display.print('0');
|
||
display.print(pctInt);
|
||
display.print("%");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawTestChipMenu_TC(uint8_t sel, TestChipResult_TC pendingRes){
|
||
|
||
display.clearDisplay();
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
display.setCursor(30,0);
|
||
display.print("TESTER CHIP");
|
||
|
||
if(sel==TCSEL_CPU){
|
||
drawInvertedLabel(10,14,"CPU");
|
||
} else {
|
||
display.setCursor(10,14); display.print("CPU");
|
||
}
|
||
|
||
if(sel==TCSEL_GPU){
|
||
drawInvertedLabel(90,14,"GPU");
|
||
} else {
|
||
display.setCursor(90,14); display.print("GPU");
|
||
}
|
||
|
||
if(sel==TCSEL_VRAM){
|
||
drawInvertedLabel(10,30,"VRAM");
|
||
} else {
|
||
display.setCursor(10,30); display.print("VRAM");
|
||
}
|
||
|
||
if(sel==TCSEL_BUCK){
|
||
drawInvertedLabel(90,30,"BUCK");
|
||
} else {
|
||
display.setCursor(90,30); display.print("BUCK");
|
||
}
|
||
|
||
display.setCursor(4,42);
|
||
display.print("Ajuste 1v a 4A B1>");
|
||
drawInvertedLabel(6, 54, " DESCONECTE PINZAS!");
|
||
|
||
}
|
||
void drawTestArmed_TC(uint8_t sel){
|
||
|
||
display.clearDisplay();
|
||
display.setTextSize(2);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
switch(sel){
|
||
case TCSEL_CPU: display.setCursor(0,0); display.print("CPU"); break;
|
||
case TCSEL_GPU: display.setCursor(0,0); display.print("GPU"); break;
|
||
case TCSEL_VRAM: display.setCursor(0,0); display.print("VRAM"); break;
|
||
case TCSEL_BUCK: display.setCursor(0,0); display.print("BUCK"); break;
|
||
}
|
||
|
||
display.setTextSize(1);
|
||
display.setCursor(0,24);
|
||
display.print("Pinza + en placa");
|
||
|
||
display.setCursor(0,34);
|
||
display.print("Pinza - en GND");
|
||
|
||
drawInvertedLabel(0,50,"B1 TEST");
|
||
display.setCursor(60,50);
|
||
display.print("< B2");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawTestMeasuring_TC(uint8_t sel){
|
||
|
||
display.clearDisplay();
|
||
display.setTextSize(2);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
switch(sel){
|
||
case TCSEL_CPU: display.setCursor(0,0); display.print("CPU"); break;
|
||
case TCSEL_GPU: display.setCursor(0,0); display.print("GPU"); break;
|
||
case TCSEL_VRAM: display.setCursor(0,0); display.print("VRAM"); break;
|
||
case TCSEL_BUCK: display.setCursor(0,0); display.print("BUCK"); break;
|
||
}
|
||
|
||
display.setTextSize(1);
|
||
display.setCursor(0,28);
|
||
display.print("Comprobando V...");
|
||
|
||
display.setCursor(0,40);
|
||
display.print("Mantenga 1v estable");
|
||
|
||
unsigned long elapsed = millis() - tcMeasureStartMs;
|
||
uint8_t barW = (elapsed >= TC_MEASURE_WINDOW_MS)
|
||
? 100
|
||
: (uint8_t)( (elapsed*100UL)/TC_MEASURE_WINDOW_MS );
|
||
display.drawRect(0,56,104,6,SSD1306_WHITE);
|
||
display.fillRect(1,57,barW,4,SSD1306_WHITE);
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawTestResult_TC(uint8_t sel,
|
||
TestChipResult_TC res,
|
||
float vAvg,float vMin,float vMax)
|
||
{
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
display.setTextSize(2);
|
||
switch(sel){
|
||
case TCSEL_CPU: display.setCursor(0,0); display.print("CPU"); break;
|
||
case TCSEL_GPU: display.setCursor(0,0); display.print("GPU"); break;
|
||
case TCSEL_VRAM: display.setCursor(0,0); display.print("VRAM"); break;
|
||
case TCSEL_BUCK: display.setCursor(0,0); display.print("BUCK"); break;
|
||
}
|
||
|
||
display.setTextSize(1);
|
||
|
||
const char *msgMain = "";
|
||
bool invertMsg=false;
|
||
|
||
switch(res){
|
||
case TCR_OK: msgMain="CHIP OK!"; invertMsg=false; break;
|
||
case TCR_VLOW: msgMain="V BAJO"; invertMsg=true; break;
|
||
case TCR_VHIGH: msgMain="V ALTO"; invertMsg=true; break;
|
||
case TCR_SHORT: msgMain="CORTO!"; invertMsg=true; break;
|
||
case TCR_NOCON: msgMain="SIN CARGA";invertMsg=true; break;
|
||
case TCR_FAIL:
|
||
default: msgMain="CHIP FALLA"; invertMsg=true; break;
|
||
}
|
||
|
||
if(invertMsg){
|
||
drawInvertedLabel(0,24,msgMain);
|
||
}else{
|
||
display.setCursor(0,24);
|
||
display.print(msgMain);
|
||
}
|
||
|
||
float useMin = (tc_cntStable>0) ? tc_vMinStable : vMin;
|
||
float useMax = (tc_cntStable>0) ? tc_vMaxStable : vMax;
|
||
|
||
float dropPct = 0.0f;
|
||
if(useMax > 0.001f){
|
||
dropPct = (useMax - useMin)/useMax * 100.0f;
|
||
if(dropPct < 0) dropPct = 0;
|
||
if(dropPct > 99.0f) dropPct = 99.0f;
|
||
}
|
||
|
||
display.setCursor(0,36);
|
||
display.print("Media ");
|
||
display.print(vAvg,2);
|
||
display.print("v");
|
||
|
||
display.setCursor(0,46);
|
||
display.print("MIN ");
|
||
display.print(vMin,2);
|
||
display.print("v");
|
||
|
||
display.setCursor(64,46);
|
||
display.print("MAX ");
|
||
display.print(vMax,2);
|
||
display.print("v");
|
||
|
||
display.setCursor(0,56);
|
||
display.print("Caida ");
|
||
display.print(dropPct,0);
|
||
display.print("%");
|
||
|
||
drawInvertedLabel(95,56,"B1>");
|
||
|
||
display.setCursor(70,56);
|
||
display.print("< B2");
|
||
|
||
display.display();
|
||
}
|
||
void drawTestSummaryScreen_WithGraph_Compact(
|
||
uint8_t sel,
|
||
uint8_t finalCode,
|
||
float vAvg,
|
||
float vMin,
|
||
float vMax,
|
||
uint8_t dropPct
|
||
){
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
const char *chipTxt = "CPU";
|
||
switch(sel){
|
||
case TCSEL_CPU: chipTxt="CPU"; break;
|
||
case TCSEL_GPU: chipTxt="GPU"; break;
|
||
case TCSEL_VRAM: chipTxt="VRAM"; break;
|
||
case TCSEL_BUCK: chipTxt="BUCK"; break;
|
||
}
|
||
|
||
const char *resTxt = "OK";
|
||
bool badRes = false;
|
||
switch(finalCode){
|
||
case 0: resTxt="OK"; badRes=false; break;
|
||
case 1: resTxt="V BAJO"; badRes=true; break;
|
||
case 2: resTxt="V ALTO"; badRes=true; break;
|
||
case 3: resTxt="CORTO"; badRes=true; break;
|
||
case 5: resTxt="SIN CARGA"; badRes=true; break;
|
||
default:
|
||
case 4: resTxt="INESTABLE"; badRes=true; break;
|
||
}
|
||
|
||
char headerBuf[20];
|
||
snprintf(headerBuf,sizeof(headerBuf),"%s %s", chipTxt, resTxt);
|
||
|
||
display.setTextSize(1);
|
||
int16_t hbX, hbY; uint16_t hbW, hbH;
|
||
display.getTextBounds(headerBuf, 0,0, &hbX,&hbY,&hbW,&hbH);
|
||
|
||
int16_t boxX = (OLED_W - (int16_t)hbW - 6) / 2;
|
||
if(boxX < 0) boxX = 0;
|
||
int16_t boxY = 0;
|
||
uint16_t boxW = hbW + 6;
|
||
uint16_t boxH = hbH + 4;
|
||
|
||
display.fillRect(boxX, boxY, boxW, boxH, SSD1306_WHITE);
|
||
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setCursor(boxX+3, boxY+2);
|
||
display.print(headerBuf);
|
||
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
const int GXg = 0;
|
||
const int GW = 128;
|
||
const int yTop = 16;
|
||
const int y1V = 24;
|
||
const int y05V = 36;
|
||
const int y0V = 52;
|
||
const int yText0 = 10;
|
||
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(0, yText0);
|
||
display.print("1v");
|
||
|
||
display.setCursor(0, y05V-3);
|
||
display.print("0.5");
|
||
|
||
display.setCursor(0, y0V-3);
|
||
display.print("0v");
|
||
|
||
for (int x = 12; x < GW; x++) {
|
||
if ((x % 4) < 2) {
|
||
display.drawPixel(GXg + x, y1V, SSD1306_WHITE);
|
||
}
|
||
}
|
||
|
||
for (int x = 12; x < GW; x++) {
|
||
if ((x % 4) < 2) {
|
||
display.drawPixel(GXg + x, y05V, SSD1306_WHITE);
|
||
}
|
||
}
|
||
|
||
for (int x = 12; x < GW; x++) {
|
||
display.drawPixel(GXg + x, y0V, SSD1306_WHITE);
|
||
}
|
||
|
||
if (tc_traceCount > 1) {
|
||
|
||
uint16_t startIdx = (uint16_t)(tc_traceCount * 20UL / 100UL);
|
||
if (startIdx >= tc_traceCount - 1) startIdx = 0;
|
||
|
||
uint16_t usedCount = tc_traceCount - startIdx;
|
||
if (usedCount < 2) usedCount = 2;
|
||
|
||
const float V_TOP = 1.00f;
|
||
const float V_MID = 0.50f;
|
||
|
||
const int yTopTrace = y1V;
|
||
const int yBottomTrace = y05V;
|
||
|
||
int prevX = -1;
|
||
int prevY = -1;
|
||
|
||
for (int x = 12; x < GW; x++) {
|
||
|
||
uint16_t idx = startIdx + (uint16_t)(
|
||
((uint32_t)(x-12) * (uint32_t)usedCount) / (uint32_t)(GW-12)
|
||
);
|
||
if (idx >= tc_traceCount) idx = tc_traceCount - 1;
|
||
|
||
float vNow = tc_trace[idx];
|
||
|
||
if (vNow > V_TOP) vNow = V_TOP;
|
||
if (vNow < V_MID) vNow = V_MID;
|
||
|
||
float norm = (vNow - V_MID) / (V_TOP - V_MID);
|
||
|
||
int yPix = yBottomTrace
|
||
- (int)( norm * (float)(yBottomTrace - yTopTrace) + 0.5f );
|
||
|
||
if (yPix < yTopTrace) yPix = yTopTrace;
|
||
if (yPix > yBottomTrace) yPix = yBottomTrace;
|
||
|
||
int Xpix = GXg + x;
|
||
display.drawPixel(Xpix, yPix, SSD1306_WHITE);
|
||
|
||
if (prevX >= 0) {
|
||
if (abs(yPix - prevY) > 1) {
|
||
int yA = (yPix < prevY) ? yPix : prevY;
|
||
int yB = (yPix < prevY) ? prevY : yPix;
|
||
for (int yy = yA; yy <= yB; yy++) {
|
||
display.drawPixel(Xpix, yy, SSD1306_WHITE);
|
||
}
|
||
}
|
||
}
|
||
|
||
prevX = Xpix;
|
||
prevY = yPix;
|
||
}
|
||
|
||
} else {
|
||
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(32, (y1V + y05V)/2 - 3);
|
||
display.print("SIN DATOS");
|
||
}
|
||
|
||
char footerBuf[48];
|
||
|
||
int vMax_i = (int)floor(fabs(vMax));
|
||
int vMax_d = (int)round((fabs(vMax) - (float)vMax_i) * 100.0f);
|
||
if (vMax_d > 99) vMax_d = 99;
|
||
|
||
int vMin_i = (int)floor(fabs(vMin));
|
||
int vMin_d = (int)round((fabs(vMin) - (float)vMin_i) * 100.0f);
|
||
if (vMin_d > 99) vMin_d = 99;
|
||
|
||
int vAvg_i = (int)floor(fabs(vAvg));
|
||
int vAvg_d = (int)round((fabs(vAvg) - (float)vAvg_i) * 100.0f);
|
||
if (vAvg_d > 99) vAvg_d = 99;
|
||
|
||
snprintf(
|
||
footerBuf,
|
||
sizeof(footerBuf),
|
||
"%02u%% +%d.%02d -%d.%02d =%d.%02d",
|
||
(unsigned int)dropPct,
|
||
vMax_i, vMax_d,
|
||
vMin_i, vMin_d,
|
||
vAvg_i, vAvg_d
|
||
);
|
||
|
||
int16_t fx, fy;
|
||
uint16_t fW, fH;
|
||
display.getTextBounds(footerBuf, 0, 0, &fx, &fy, &fW, &fH);
|
||
|
||
int16_t footerY = OLED_H - fH;
|
||
int16_t footerX = (OLED_W - fW) / 2;
|
||
if (footerX < 0) footerX = 0;
|
||
|
||
display.setCursor(footerX, footerY);
|
||
display.print(footerBuf);
|
||
|
||
display.display();
|
||
}
|
||
|
||
TestChipResult_TC classifyResult_TC(){
|
||
|
||
uint16_t useCnt = (tc_cntStable > 0) ? tc_cntStable : tc_cnt;
|
||
float useVmin = (tc_cntStable > 0) ? tc_vMinStable : tc_vMin;
|
||
float useVmax = (tc_cntStable > 0) ? tc_vMaxStable : tc_vMax;
|
||
|
||
if(useCnt == 0){
|
||
return TCR_FAIL;
|
||
}
|
||
|
||
float vAvg = tc_vSum / (float)tc_cnt;
|
||
float aAvg = tc_aSum / (float)tc_cnt;
|
||
|
||
if (testChipSel_TC == TCSEL_GPU) {
|
||
if (aAvg < 0.02f) return TCR_NOCON;
|
||
} else {
|
||
if (aAvg < 0.05f) return TCR_NOCON;
|
||
}
|
||
|
||
if ((useVmin < 0.60f && aAvg > 0.5f) || (useVmin < 0.35f)) {
|
||
return TCR_SHORT;
|
||
}
|
||
|
||
if(useVmax > 1.40f){
|
||
return TCR_VHIGH;
|
||
}
|
||
|
||
bool vOk = (vAvg >= 0.80f && vAvg <= 1.20f);
|
||
bool aOk = (aAvg >= 0.20f && aAvg <= 3.50f);
|
||
if(vOk && aOk){
|
||
return TCR_OK;
|
||
}
|
||
|
||
if(vAvg < 0.80f){
|
||
return TCR_VLOW;
|
||
}
|
||
|
||
if(vAvg > 1.20f){
|
||
return TCR_VHIGH;
|
||
}
|
||
|
||
return TCR_FAIL;
|
||
}
|
||
|
||
const float CPU_SHORT_V = 0.30f;
|
||
const float CPU_MIN_AVG_OK_V = 0.75f;
|
||
const float CPU_MAX_EXPECTED_V = 1.20f;
|
||
const float CPU_DROP_BAD_PCT = 30.0f;
|
||
|
||
TestChipResult_TC classifyCPU_TC(){
|
||
if(tc_cnt == 0){
|
||
|
||
return TCR_FAIL;
|
||
}
|
||
|
||
float vAvg = tc_vSum / (float)tc_cnt;
|
||
|
||
float dropPct = 0.0f;
|
||
if (tc_vMax > 0.001f) {
|
||
dropPct = (tc_vMax - tc_vMin) / tc_vMax * 100.0f;
|
||
if(dropPct < 0.0f) dropPct = 0.0f;
|
||
}
|
||
|
||
if (tc_vMin < CPU_SHORT_V){
|
||
return TCR_SHORT;
|
||
}
|
||
|
||
if (tc_vMax > CPU_MAX_EXPECTED_V){
|
||
return TCR_VHIGH;
|
||
}
|
||
|
||
if (vAvg < CPU_MIN_AVG_OK_V){
|
||
return TCR_VLOW;
|
||
}
|
||
|
||
if (dropPct > CPU_DROP_BAD_PCT){
|
||
return TCR_FAIL;
|
||
}
|
||
|
||
return TCR_OK;
|
||
}
|
||
void drawMainMenu(uint8_t sel){
|
||
display.clearDisplay();
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
if(sel==MM_CANAL2){
|
||
drawInvertedLabel(4,6,"CANAL 2");
|
||
} else {
|
||
display.setCursor(4,6);
|
||
display.print("CANAL 2");
|
||
}
|
||
|
||
if(sel==MM_TESTCHIP){
|
||
drawInvertedLabel(74,6,"TEST CHIP");
|
||
} else {
|
||
display.setCursor(74,6);
|
||
display.print("TEST CHIP");
|
||
}
|
||
|
||
if(sel==MM_MEDICION){
|
||
drawInvertedLabel(4,21,"VOLTIMETRO");
|
||
} else {
|
||
display.setCursor(4,21);
|
||
display.print("VOLTIMETRO");
|
||
}
|
||
|
||
if(sel==MM_LAPTOP){
|
||
drawInvertedLabel(74,21,"PORTATIL");
|
||
} else {
|
||
display.setCursor(74,21);
|
||
display.print("PORTATIL");
|
||
}
|
||
|
||
if(sel==MM_CALIBRACION){
|
||
drawInvertedLabel(4,36,"CALIBRAR");
|
||
} else {
|
||
display.setCursor(4,36);
|
||
display.print("CALIBRAR");
|
||
}
|
||
|
||
drawInvertedLabel(4,52,"B1>");
|
||
display.setCursor(95,52);
|
||
display.print("< B2");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawTestChipMenu(uint8_t sel){
|
||
display.clearDisplay();
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
if(sel==TC_CPU){
|
||
drawInvertedLabel(4,8,"CPU");
|
||
} else {
|
||
display.setCursor(4,8); display.print("CPU");
|
||
}
|
||
|
||
if(sel==TC_GPU){
|
||
drawInvertedLabel(74,8,"GPU");
|
||
} else {
|
||
display.setCursor(74,8); display.print("GPU");
|
||
}
|
||
|
||
if(sel==TC_VRAM){
|
||
drawInvertedLabel(4,24,"VRAM");
|
||
} else {
|
||
display.setCursor(4,24); display.print("VRAM");
|
||
}
|
||
|
||
if(sel==TC_BUCK){
|
||
drawInvertedLabel(74,24,"BUCK");
|
||
} else {
|
||
display.setCursor(74,24); display.print("BUCK");
|
||
}
|
||
|
||
display.setCursor(4,44);
|
||
display.print("Ajuste 1v Pulse B1");
|
||
|
||
display.setCursor(4,56);
|
||
display.print("Volver B2 largo");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawVoltimetro(float vShow, const char *estadoTxt){
|
||
display.clearDisplay();
|
||
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2);
|
||
display.setCursor(2,0);
|
||
display.print("VOLTIMETRO");
|
||
|
||
{
|
||
char buf[16];
|
||
dtostrf(vShow,0,3,buf);
|
||
int16_t x1,y1; uint16_t w1,h1;
|
||
display.setTextSize(2);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.getTextBounds(buf,0,0,&x1,&y1,&w1,&h1);
|
||
int16_t xx=(OLED_W - (w1+12))/2;
|
||
if(xx<0)xx=0;
|
||
int16_t yy=24;
|
||
display.setCursor(xx,yy);
|
||
display.print(buf);
|
||
display.print("V");
|
||
}
|
||
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(10,50);
|
||
display.print("Senal ");
|
||
display.print(estadoTxt);
|
||
|
||
drawInvertedLabel(90,50,"< B2");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawVoltimetroExitWarning(){
|
||
display.clearDisplay();
|
||
display.fillRect(0,0,OLED_W,OLED_H,SSD1306_WHITE);
|
||
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setTextSize(2);
|
||
display.setCursor(16,12);
|
||
display.print("RECUERDE");
|
||
|
||
display.setTextSize(1);
|
||
display.setCursor(28,36);
|
||
display.print("¡DESCONECTE");
|
||
display.setCursor(28,48);
|
||
display.print("LAS PINZAS!");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawLaptopScreen(bool needAdj19v){
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2);
|
||
display.setCursor(20,0);
|
||
display.print("PORTATIL");
|
||
display.setTextSize(1);
|
||
|
||
if (needAdj19v) {
|
||
|
||
display.setCursor(15, 20);
|
||
display.print("AJUSTE EL VOLTAJE");
|
||
drawInvertedLabel(22, 30, "RANGO 18 A 21V");
|
||
display.setTextSize(1);
|
||
display.setCursor(22,42);
|
||
display.print("RETIRE BATERIA");
|
||
display.setCursor(4, 54);
|
||
drawInvertedLabel(4,54, "B1>");
|
||
display.setCursor(100, 54);
|
||
display.print("< B2");
|
||
} else {
|
||
|
||
display.setCursor(4, 16);
|
||
display.print("ANALISIS");
|
||
|
||
display.setTextSize(2);
|
||
display.setCursor(0, 30);
|
||
display.print("0.000A");
|
||
|
||
display.setTextSize(1);
|
||
display.setCursor(4, 54);
|
||
display.print("B1 on/off");
|
||
display.setCursor(90, 54);
|
||
display.print("< B2");
|
||
}
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawLaptopAdjustWarning(float vAvg){
|
||
display.clearDisplay();
|
||
|
||
display.setTextSize(2);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(0, 0);
|
||
display.print("AVISO");
|
||
|
||
display.setTextSize(1);
|
||
display.setCursor(0, 22);
|
||
display.print("Medido: ");
|
||
display.print(vAvg, 2);
|
||
display.print("V");
|
||
|
||
display.fillRect(0, 36, 128, 14, SSD1306_WHITE);
|
||
display.setTextColor(SSD1306_BLACK);
|
||
display.setCursor(8, 38);
|
||
display.print("Ajuste 18 a 21V primero");
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(88, 54);
|
||
display.print("< B2");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void drawCalibracionScreen(){
|
||
float vNow=0.0f, aNow=0.0f;
|
||
readVA(vNow, aNow);
|
||
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
|
||
display.setTextSize(2);
|
||
display.setCursor(17,0);
|
||
display.print("CALIBRAR");
|
||
|
||
display.setTextSize(1);
|
||
display.setCursor(4,20);
|
||
display.print("COLOQUE UNA R100 5W");
|
||
display.setTextSize(1);
|
||
display.setCursor(22,30);
|
||
display.print("EN LAS PINZAS");
|
||
display.setTextSize(1);
|
||
display.setCursor(10,40);
|
||
display.print("AJUSTE EN 10.000v");
|
||
|
||
drawInvertedLabel(4,52,"B1 Siguiente");
|
||
display.setCursor(94,54);
|
||
display.print("< B2");
|
||
|
||
display.display();
|
||
}
|
||
|
||
void armCh1(){
|
||
relay1=true;
|
||
setRelay(RELAY1,true);
|
||
|
||
safeModeEnabled=true;
|
||
ch1OnMs=millis();
|
||
vref = readVoltageCal();
|
||
|
||
lastShortTrip=false;
|
||
tripScreenDrawn=false;
|
||
|
||
ch1State=CH1_ACTIVE;
|
||
}
|
||
|
||
void forceGoHome(){
|
||
relay1=false;
|
||
setRelay(RELAY1,false);
|
||
ch1State = CH1_IDLE;
|
||
lastShortTrip=false;
|
||
tripScreenDrawn=false;
|
||
uiMode = MODE_HOME;
|
||
}
|
||
|
||
void redrawCurrentScreenAfterWake() {
|
||
uiMode = MODE_HOME;
|
||
ch1State = CH1_IDLE;
|
||
relay1 = false;
|
||
setRelay(RELAY1,false);
|
||
drawHomeIdle();
|
||
}
|
||
|
||
bool tcCheckOneVolt_OK() {
|
||
|
||
relay1 = true;
|
||
setRelay(RELAY1, true);
|
||
safeModeEnabled = true;
|
||
ch1OnMs = millis();
|
||
|
||
delay(50);
|
||
|
||
float vNow = 0.0f, aNow = 0.0f;
|
||
readVA(vNow, aNow);
|
||
|
||
if (aNow >= 4.0f) {
|
||
relay1 = false;
|
||
setRelay(RELAY1, false);
|
||
beepCount(3);
|
||
lastResult_TC = TCR_SHORT;
|
||
testPhase_TC = TCPH_RESULT;
|
||
return false;
|
||
}
|
||
|
||
tc_lastCheckVolt = vNow;
|
||
|
||
relay1 = false;
|
||
setRelay(RELAY1, false);
|
||
|
||
if (vNow >= TC_OK_MIN_V && vNow <= TC_OK_MAX_V) {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
struct TCProfile {
|
||
float minV;
|
||
float maxV;
|
||
float shortV;
|
||
float highV;
|
||
float dropBadPct;
|
||
unsigned long windowMs;
|
||
};
|
||
|
||
TCProfile tcProfile;
|
||
|
||
void configureProfileForCurrentSelection(uint8_t sel) {
|
||
switch (sel) {
|
||
case TCSEL_CPU:
|
||
tcProfile = {0.80f, 1.20f, 0.30f, 1.40f, 30.0f, 5500};
|
||
break;
|
||
|
||
case TCSEL_GPU:
|
||
tcProfile = {0.80f, 1.20f, 0.30f, 1.50f, 25.0f, 5500};
|
||
break;
|
||
|
||
case TCSEL_VRAM:
|
||
tcProfile = {1.00f, 1.40f, 0.50f, 1.60f, 20.0f, 3000};
|
||
break;
|
||
|
||
case TCSEL_BUCK:
|
||
tcProfile = {0.60f, 2.00f, 0.20f, 2.50f, 50.0f, 500};
|
||
break;
|
||
|
||
default:
|
||
tcProfile = {0.80f, 1.20f, 0.30f, 1.40f, 30.0f, 5500};
|
||
break;
|
||
}
|
||
|
||
TC_MEASURE_WINDOW_MS = tcProfile.windowMs;
|
||
}
|
||
|
||
void handleTestChip_TC(){
|
||
|
||
if(checkLongPressB2()){
|
||
beepOnce(40);
|
||
|
||
setRelay(RELAY1,false);
|
||
relay1=false;
|
||
|
||
ch1State = CH1_IDLE;
|
||
uiMode = MODE_HOME;
|
||
return;
|
||
}
|
||
|
||
switch(testPhase_TC){
|
||
|
||
case TCPH_READY: {
|
||
|
||
drawTestChipMenu_TC(testChipSel_TC, lastResult_TC);
|
||
display.display();
|
||
|
||
if (consumeClick(BTN2)) {
|
||
beepOnce(40);
|
||
testChipSel_TC = (testChipSel_TC + 1) % TCSEL_COUNT;
|
||
drawTestChipMenu_TC(testChipSel_TC, lastResult_TC);
|
||
display.display();
|
||
registerActivity();
|
||
break;
|
||
}
|
||
|
||
if (consumeClick(BTN1)) {
|
||
|
||
relay1 = true;
|
||
setRelay(RELAY1, true);
|
||
delay(100);
|
||
|
||
float vNow = 0.0f, aNow = 0.0f;
|
||
readVA(vNow, aNow);
|
||
tc_lastAdjV = vNow;
|
||
tc_lastAdjA = aNow;
|
||
delay(200);
|
||
relay1 = false;
|
||
setRelay(RELAY1, false);
|
||
|
||
float vTarget = 1.0f;
|
||
if (testChipSel_TC == TCSEL_VRAM) {
|
||
vTarget = 1.20f;
|
||
}
|
||
|
||
float vMinOk = vTarget * 0.90f;
|
||
float vMaxOk = vTarget * 1.10f;
|
||
|
||
if (vNow >= vMinOk && vNow <= vMaxOk) {
|
||
|
||
switch (testChipSel_TC) {
|
||
case TCSEL_GPU: tc_runMs = 6000; tc_settleMs = 300; break;
|
||
case TCSEL_VRAM: tc_runMs = 4000; tc_settleMs = 200; break;
|
||
case TCSEL_BUCK: tc_runMs = 300; tc_settleMs = 100; break;
|
||
default: tc_runMs = 4000; tc_settleMs = 200; break;
|
||
}
|
||
beepOnce(80);
|
||
testPhase_TC = TCPH_ARMED;
|
||
break;
|
||
}
|
||
|
||
beepCount(2, 60, 80);
|
||
display.clearDisplay();
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(30,0);
|
||
display.print("TESTER CHIP");
|
||
|
||
if (testChipSel_TC == TCSEL_VRAM) {
|
||
drawInvertedLabel(4,48,"AJUSTE 1.2V PRIMERO");
|
||
display.setCursor(4,28);
|
||
display.print("Medido: ");
|
||
display.print(tc_lastAdjV, 3);
|
||
display.print("V");
|
||
} else {
|
||
drawInvertedLabel(4,48,"AJUSTE 1V PRIMERO");
|
||
display.setCursor(4,28);
|
||
display.print("Medido: ");
|
||
display.print(tc_lastAdjV, 3);
|
||
display.print("V");
|
||
}
|
||
|
||
display.display();
|
||
display.display();
|
||
delaySafe(6000);
|
||
break;
|
||
break;
|
||
}
|
||
|
||
} break;
|
||
|
||
case TCPH_ARMED: {
|
||
|
||
drawTestArmed_TC(testChipSel_TC);
|
||
|
||
if (consumeClick(BTN2)) {
|
||
beepOnce(40);
|
||
testPhase_TC = TCPH_READY;
|
||
return;
|
||
}
|
||
|
||
if(consumeClick(BTN1)){
|
||
beepOnce(80);
|
||
|
||
tc_vMin = 99.0f;
|
||
tc_vMax = 0.0f;
|
||
tc_vSum = 0.0f;
|
||
tc_cnt = 0;
|
||
tc_aMinStable = 99.0f;
|
||
tc_aMaxStable = 0.0f;
|
||
|
||
tc_vMinStable = 99.0f;
|
||
tc_vMaxStable = 0.0f;
|
||
tc_aSumStable = 0.0f;
|
||
tc_cntStable = 0;
|
||
tc_aPrev = NAN;
|
||
tc_aP2P = 0.0f;
|
||
tc_aStepMax = 0.0f;
|
||
|
||
for (uint16_t i = 0; i < TC_TRACE_MAX; i++) {
|
||
tc_trace[i] = 0.0f;
|
||
}
|
||
tc_traceCount = 0;
|
||
|
||
relay1=true;
|
||
setRelay(RELAY1,true);
|
||
safeModeEnabled=true;
|
||
ch1OnMs=millis();
|
||
|
||
tcMeasureStartMs=millis();
|
||
|
||
testPhase_TC = TCPH_MEASURE;
|
||
return;
|
||
}
|
||
configureProfileForCurrentSelection(testChipSel_TC);
|
||
|
||
} break;
|
||
case TCPH_MEASURE: {
|
||
|
||
const unsigned long WARMUP_IGNORE_MS = 50;
|
||
const unsigned long STABLE_START_MS = 200;
|
||
|
||
unsigned long now = millis();
|
||
unsigned long elapsed = now - tcMeasureStartMs;
|
||
|
||
float vNow = 0.0f;
|
||
float aNow = 0.0f;
|
||
readVA(vNow, aNow);
|
||
|
||
if (elapsed < (tcMeasureStartMs + (TC_MEASURE_WINDOW_MS / 2))) {
|
||
tc_aSumEarly += aNow;
|
||
tc_aCntEarly++;
|
||
} else {
|
||
tc_aSumLate += aNow;
|
||
tc_aCntLate++;
|
||
}
|
||
|
||
if (aNow > tc_aMax) tc_aMax = aNow;
|
||
if (aNow < tc_aMin) tc_aMin = aNow;
|
||
|
||
if (!isnan(tc_aPrev)) {
|
||
float step = fabs(aNow - tc_aPrev);
|
||
if (step > tc_aStepMax) tc_aStepMax = step;
|
||
}
|
||
tc_aPrev = aNow;
|
||
|
||
if (tc_traceCount < TC_TRACE_MAX) {
|
||
tc_trace[tc_traceCount] = vNow;
|
||
tc_traceCount++;
|
||
}
|
||
|
||
if (elapsed >= WARMUP_IGNORE_MS){
|
||
|
||
if (vNow < tc_vMin) tc_vMin = vNow;
|
||
if (vNow > tc_vMax) tc_vMax = vNow;
|
||
tc_vSum += vNow;
|
||
|
||
if (aNow < tc_aMin) tc_aMin = aNow;
|
||
if (aNow > tc_aMax) tc_aMax = aNow;
|
||
tc_aSum += aNow;
|
||
if (aNow < tc_aMinStable) tc_aMinStable = aNow;
|
||
if (aNow > tc_aMaxStable) tc_aMaxStable = aNow;
|
||
tc_cnt++;
|
||
}
|
||
|
||
if (elapsed >= STABLE_START_MS){
|
||
if (vNow < tc_vMinStable) tc_vMinStable = vNow;
|
||
if (vNow > tc_vMaxStable) tc_vMaxStable = vNow;
|
||
tc_aSumStable += aNow;
|
||
tc_cntStable++;
|
||
|
||
if (tc_stableStartMs == 0) tc_stableStartMs = millis();
|
||
|
||
unsigned long stableMs = millis() - tc_stableStartMs;
|
||
const unsigned long STABLE_SPLIT_MS = 400;
|
||
|
||
if (stableMs < STABLE_SPLIT_MS) {
|
||
tc_aSumEarly += aNow; tc_aCntEarly++;
|
||
} else {
|
||
tc_aSumLate += aNow; tc_aCntLate++;
|
||
}
|
||
}
|
||
|
||
if (safeModeEnabled){
|
||
if ((now - ch1OnMs) >= SAFE_ARM_DELAY_MS){
|
||
if (vNow < TC_SHORT_V){
|
||
relay1 = false;
|
||
setRelay(RELAY1,false);
|
||
beepCount(3);
|
||
|
||
lastResult_TC = TCR_SHORT;
|
||
tc_finalCode = 3;
|
||
|
||
testPhase_TC = TCPH_RESULT;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
drawTestMeasuring_TC(testChipSel_TC);
|
||
|
||
if (elapsed >= TC_MEASURE_WINDOW_MS){
|
||
|
||
relay1 = false;
|
||
setRelay(RELAY1,false);
|
||
|
||
if (testChipSel_TC == TCSEL_CPU || testChipSel_TC == TCSEL_GPU) {
|
||
|
||
uint16_t useCnt = (tc_cntStable > 0) ? tc_cntStable : tc_cnt;
|
||
float useVmin = (tc_cntStable > 0) ? tc_vMinStable : tc_vMin;
|
||
float useVmax = (tc_cntStable > 0) ? tc_vMaxStable : tc_vMax;
|
||
|
||
float vAvg_all = (tc_cnt>0) ? (tc_vSum / (float)tc_cnt) : 0.0f;
|
||
float aAvg_all = (tc_cnt>0) ? (tc_aSum / (float)tc_cnt) : 0.0f;
|
||
|
||
float aEarly = (tc_aCntEarly>0) ? (tc_aSumEarly/(float)tc_aCntEarly) : 0.0f;
|
||
float aLate = (tc_aCntLate >0) ? (tc_aSumLate /(float)tc_aCntLate ) : aEarly;
|
||
|
||
if (aEarly >= 0.30f && aLate <= 0.05f && useVmin >= tcProfile.minV*0.95f) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
|
||
else if (tc_aStepMax >= 0.50f) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
|
||
float dropPct = 0.0f;
|
||
if (useVmax > 0.001f) {
|
||
dropPct = (useVmax - useVmin) / useVmax * 100.0f;
|
||
if (dropPct < 0.0f) dropPct = 0.0f;
|
||
if (dropPct > 99.0f) dropPct = 99.0f;
|
||
}
|
||
|
||
float aEarlyCPU = (tc_aCntEarly > 0) ? (tc_aSumEarly / (float)tc_aCntEarly) : 0.0f;
|
||
float aLateCPU = (tc_aCntLate > 0) ? (tc_aSumLate / (float)tc_aCntLate ) : 0.0f;
|
||
float aDropCPU = aEarlyCPU - aLateCPU;
|
||
float aP2P_CPU = tc_aMax - tc_aMin;
|
||
|
||
const float EARLY_MIN_A = 0.20f;
|
||
const float LATE_NEAR_ZERO = 0.05f;
|
||
const float DROP_MIN_A = 0.15f;
|
||
const float STEP_BAD_A = 0.35f;
|
||
const float P2P_BAD_A = 0.45f;
|
||
const float DROP_V_HELP = 10.0f;
|
||
|
||
if ( (aEarlyCPU >= EARLY_MIN_A && aLateCPU <= LATE_NEAR_ZERO) ||
|
||
(aDropCPU >= DROP_MIN_A) ||
|
||
(tc_aStepMax >= STEP_BAD_A) ||
|
||
(aP2P_CPU >= P2P_BAD_A && dropPct >= DROP_V_HELP) ) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
|
||
const float NO_LOAD_A = 0.02f;
|
||
const float MIN_A_CPU = 0.30f;
|
||
const float MIN_A_GPU = 0.50f;
|
||
const float MIN_A_OK = (testChipSel_TC==TCSEL_GPU) ? MIN_A_GPU : MIN_A_CPU;
|
||
|
||
if (aAvg_all <= 0.005f) {
|
||
lastResult_TC = TCR_NOCON;
|
||
}
|
||
else if ((useVmin < 0.60f && aAvg_all > 0.5f) || (useVmin < 0.35f)) {
|
||
lastResult_TC = TCR_SHORT;
|
||
}
|
||
else if (useVmax > tcProfile.highV) {
|
||
lastResult_TC = TCR_VHIGH;
|
||
}
|
||
else if (vAvg_all < tcProfile.minV) {
|
||
lastResult_TC = TCR_VLOW;
|
||
}
|
||
else if (vAvg_all > tcProfile.maxV) {
|
||
lastResult_TC = TCR_VHIGH;
|
||
}
|
||
else if (aAvg_all < MIN_A_OK) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
else if (dropPct > tcProfile.dropBadPct) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
else {
|
||
lastResult_TC = TCR_OK;
|
||
}
|
||
|
||
{
|
||
float aP2P = tc_aMax - tc_aMin;
|
||
float rel = (aAvg_all > 0.0f) ? (aP2P / aAvg_all) : 0.0f;
|
||
if (rel > 0.55f && dropPct > 6.0f) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
}
|
||
}
|
||
else if (testChipSel_TC == TCSEL_VRAM) {
|
||
|
||
float vAvg_all = (tc_cnt>0) ? (tc_vSum/(float)tc_cnt) : 0.0f;
|
||
float aAvg_all = (tc_cnt>0) ? (tc_aSum/(float)tc_cnt) : 0.0f;
|
||
|
||
if (aAvg_all < 0.02f) lastResult_TC = TCR_NOCON;
|
||
else if (tc_vMin < 0.50f) lastResult_TC = TCR_SHORT;
|
||
else if (tc_vMax > 1.60f) lastResult_TC = TCR_VHIGH;
|
||
else if (vAvg_all < 1.10f) lastResult_TC = TCR_VLOW;
|
||
else if (vAvg_all > 1.30f) lastResult_TC = TCR_VHIGH;
|
||
else lastResult_TC = TCR_OK;
|
||
}
|
||
|
||
else {
|
||
uint16_t useCnt = (tc_cntStable > 0) ? tc_cntStable : tc_cnt;
|
||
float useVmin = (tc_cntStable > 0) ? tc_vMinStable : tc_vMin;
|
||
float useVmax = (tc_cntStable > 0) ? tc_vMaxStable : tc_vMax;
|
||
|
||
float vAvg_all = (tc_cnt > 0) ? (tc_vSum / (float)tc_cnt) : 0.0f;
|
||
float aAvg_all = (tc_cnt > 0) ? (tc_aSum / (float)tc_cnt) : 0.0f;
|
||
float aP2P = tc_aMax - tc_aMin;
|
||
|
||
float dropPct = 0.0f;
|
||
if (useVmax > 0.001f) {
|
||
dropPct = (useVmax - useVmin) / useVmax * 100.0f;
|
||
if (dropPct < 0.0f) dropPct = 0.0f;
|
||
if (dropPct > 99.0f) dropPct = 99.0f;
|
||
}
|
||
|
||
const float NOLOAD_A = 0.02f;
|
||
const float OK_V_MIN = 0.85f;
|
||
const float OK_V_MAX = 1.20f;
|
||
const float OK_DROP_MAX = 20.0f;
|
||
const float FLUCT_A_STRONG = 0.40f;
|
||
const float FLUCT_A_MED = 0.25f;
|
||
const float UNSTABLE_DROP = 25.0f;
|
||
const float SHORT_A_SPIKE = 3.0f;
|
||
const float SHORT_V_ABS = 0.60f;
|
||
|
||
if (aAvg_all < NOLOAD_A && dropPct < 3.0f) {
|
||
lastResult_TC = TCR_NOCON;
|
||
}
|
||
|
||
else if ( (tc_aMax >= SHORT_A_SPIKE && useVmin < SHORT_V_ABS) ||
|
||
(dropPct >= 55.0f && aAvg_all > 0.5f) ) {
|
||
lastResult_TC = TCR_SHORT;
|
||
}
|
||
|
||
else if (aP2P >= FLUCT_A_STRONG) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
|
||
else if (aP2P >= FLUCT_A_MED && dropPct >= UNSTABLE_DROP) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
|
||
else if (vAvg_all >= OK_V_MIN && vAvg_all <= OK_V_MAX && dropPct <= OK_DROP_MAX) {
|
||
|
||
if (aAvg_all >= 0.04f && aAvg_all <= 3.0f) {
|
||
lastResult_TC = TCR_OK;
|
||
} else {
|
||
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
}
|
||
|
||
else if (vAvg_all < OK_V_MIN) {
|
||
lastResult_TC = TCR_VLOW;
|
||
}
|
||
else if (vAvg_all > OK_V_MAX) {
|
||
lastResult_TC = TCR_VHIGH;
|
||
}
|
||
|
||
else {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
}
|
||
|
||
if (tc_cntStable >= 12 && lastResult_TC != TCR_SHORT && lastResult_TC != TCR_NOCON) {
|
||
float aP2P_stable = tc_aMaxStable - tc_aMinStable;
|
||
|
||
const float TH_CPU_GPU = 0.45f;
|
||
const float TH_VRAM_BUCK = 0.30f;
|
||
|
||
bool isCpuGpu = (testChipSel_TC == TCSEL_CPU || testChipSel_TC == TCSEL_GPU);
|
||
float th = isCpuGpu ? TH_CPU_GPU : TH_VRAM_BUCK;
|
||
|
||
float vAvg_all = (tc_cnt>0) ? (tc_vSum / (float)tc_cnt) : 0.0f;
|
||
if (vAvg_all >= 0.75f && vAvg_all <= 1.25f) {
|
||
if (aP2P_stable >= th) {
|
||
lastResult_TC = TCR_FAIL;
|
||
}
|
||
}
|
||
}
|
||
|
||
switch(lastResult_TC){
|
||
case TCR_OK: tc_finalCode = 0; break;
|
||
case TCR_VLOW: tc_finalCode = 1; break;
|
||
case TCR_VHIGH: tc_finalCode = 2; break;
|
||
case TCR_SHORT: tc_finalCode = 3; break;
|
||
case TCR_NOCON: tc_finalCode = 5; break;
|
||
default: tc_finalCode = 4; break;
|
||
}
|
||
|
||
testPhase_TC = TCPH_RESULT;
|
||
return;
|
||
}
|
||
|
||
} break;
|
||
|
||
case TCPH_RESULT: {
|
||
|
||
float vAvg = (tc_cnt>0) ? (tc_vSum/(float)tc_cnt) : 0.0f;
|
||
|
||
float dropPctF = 0.0f;
|
||
if(tc_vMax>0.001f){
|
||
dropPctF = (tc_vMax - tc_vMin)/tc_vMax * 100.0f;
|
||
if(dropPctF<0) dropPctF=0;
|
||
}
|
||
uint8_t dropPct = (dropPctF>99.0f)?99:(uint8_t)(dropPctF+0.5f);
|
||
|
||
drawTestSummaryScreen_WithGraph_Compact(
|
||
testChipSel_TC,
|
||
tc_finalCode,
|
||
vAvg,
|
||
tc_vMin,
|
||
tc_vMax,
|
||
dropPct
|
||
);
|
||
|
||
if (consumeClick(BTN1)) {
|
||
beepOnce(80);
|
||
|
||
tc_vMin = 99.0f;
|
||
tc_vMax = 0.0f;
|
||
tc_vSum = 0.0f;
|
||
tc_cnt = 0;
|
||
|
||
tc_aMin = 99.0f;
|
||
tc_aMax = 0.0f;
|
||
tc_aSum = 0.0f;
|
||
tc_aMinStable = 99.0f;
|
||
tc_aMaxStable = 0.0f;
|
||
tc_vMinStable = 99.0f;
|
||
tc_vMaxStable = 0.0f;
|
||
tc_aSumStable = 0.0f;
|
||
tc_cntStable = 0;
|
||
tc_aPrev = NAN;
|
||
tc_aP2P = 0.0f;
|
||
tc_aStepMax = 0.0f;
|
||
for (uint16_t i = 0; i < TC_TRACE_MAX; i++) {
|
||
tc_trace[i] = 0.0f;
|
||
}
|
||
tc_traceCount = 0;
|
||
|
||
lastResult_TC = TCR_NONE;
|
||
tc_finalCode = 4;
|
||
|
||
relay1 = true;
|
||
setRelay(RELAY1, true);
|
||
safeModeEnabled = true;
|
||
ch1OnMs = millis();
|
||
|
||
tcMeasureStartMs = millis();
|
||
|
||
testPhase_TC = TCPH_MEASURE;
|
||
return;
|
||
}
|
||
|
||
if (consumeClick(BTN2)) {
|
||
beepOnce(40);
|
||
|
||
relay1 = false;
|
||
setRelay(RELAY1,false);
|
||
|
||
tc_vMin = 99.0f;
|
||
tc_vMax = 0.0f;
|
||
tc_vSum = 0.0f;
|
||
tc_cnt = 0;
|
||
tc_aMinStable = 99.0f;
|
||
tc_aMaxStable = 0.0f;
|
||
tc_aMin = 99.0f;
|
||
tc_aMax = 0.0f;
|
||
tc_aSum = 0.0f;
|
||
|
||
tc_vMinStable = 99.0f;
|
||
tc_vMaxStable = 0.0f;
|
||
tc_aSumStable = 0.0f;
|
||
tc_cntStable = 0;
|
||
tc_aPrev = NAN;
|
||
tc_aP2P = 0.0f;
|
||
tc_aStepMax = 0.0f;
|
||
for (uint16_t i = 0; i < TC_TRACE_MAX; i++) {
|
||
tc_trace[i] = 0.0f;
|
||
}
|
||
tc_traceCount = 0;
|
||
|
||
lastResult_TC = TCR_NONE;
|
||
tc_finalCode = 4;
|
||
|
||
testPhase_TC = TCPH_READY;
|
||
return;
|
||
}
|
||
|
||
} break;
|
||
|
||
}
|
||
}
|
||
|
||
void handleLaptop_TC() {
|
||
if (laptopPhase == LAPH_ADJ) {
|
||
if (consumeClick(BTN2)) {
|
||
beepOnce(40);
|
||
relay1 = false; setRelay(RELAY1, false);
|
||
ch1State = CH1_IDLE;
|
||
uiMode = MODE_MENU;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
if (consumeClick(BTN1)) {
|
||
beepOnce(80);
|
||
relay1 = true; setRelay(RELAY1, true);
|
||
safeModeEnabled = true;
|
||
ch1OnMs = millis();
|
||
unsigned long t0 = millis();
|
||
float vSum = 0.0f;
|
||
uint16_t vCnt = 0;
|
||
float vNow = 0.0f, aNow = 0.0f;
|
||
while (millis() - t0 < 1000) {
|
||
readVA(vNow, aNow);
|
||
vSum += vNow;
|
||
vCnt++;
|
||
delay(10);
|
||
}
|
||
relay1 = false; setRelay(RELAY1, false);
|
||
float vAvg = (vCnt ? (vSum / (float)vCnt) : 0.0f);
|
||
if (vAvg < LAPTOP_VMIN || vAvg > LAPTOP_VMAX) {
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2); display.setCursor(15, 0); display.print("ATENCION");
|
||
display.setTextSize(1); display.setCursor(18, 24);
|
||
display.print("Medido: "); display.print(vAvg, 2); display.print("V");
|
||
drawInvertedLabel(17, 40, "AJUSTE 18 a 21V");
|
||
display.display();
|
||
delaySafe(3500);
|
||
return;
|
||
}
|
||
laptopPhase = LAPH_ANALYSIS;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
drawLaptopScreen(true);
|
||
return;
|
||
}
|
||
|
||
{
|
||
if (consumeClick(BTN2)) {
|
||
beepOnce(40);
|
||
relay1 = false; setRelay(RELAY1, false);
|
||
ch1State = CH1_IDLE;
|
||
uiMode = MODE_MENU;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
if (consumeClick(BTN1)) {
|
||
beepOnce(60);
|
||
relay1 = !relay1;
|
||
setRelay(RELAY1, relay1);
|
||
}
|
||
|
||
float vNow = 0.0f, aNow = 0.0f;
|
||
readVA(vNow, aNow);
|
||
|
||
if (relay1 && !lap_lastRelayOn) {
|
||
lap_vBase = vNow;
|
||
lap_aFast = aNow;
|
||
lap_aSlow = aNow;
|
||
lap_aSwingPeak = 0.0f;
|
||
lap_aMax = 0.0f;
|
||
lap_aMin = 99.0f;
|
||
}
|
||
|
||
if (relay1) {
|
||
if (aNow > lap_aMax) lap_aMax = aNow;
|
||
if (aNow < lap_aMin) lap_aMin = aNow;
|
||
}
|
||
|
||
lap_lastRelayOn = relay1;
|
||
|
||
static unsigned long lap_beepTs = 0;
|
||
if (relay1) {
|
||
if (lap_beepTs == 0) lap_beepTs = millis();
|
||
if (millis() - lap_beepTs >= 5000UL) {
|
||
beepOnce(40);
|
||
lap_beepTs = millis();
|
||
}
|
||
} else {
|
||
lap_beepTs = 0;
|
||
}
|
||
|
||
unsigned long nowMs = millis();
|
||
if (nowMs - lap_lastSampleMs >= 30) {
|
||
lap_lastSampleMs = nowMs;
|
||
lap_aFast += (aNow - lap_aFast) * 0.40f;
|
||
lap_aSlow += (aNow - lap_aSlow) * 0.05f;
|
||
float swing = fabs(lap_aFast - lap_aSlow);
|
||
if (swing > lap_aSwingPeak) lap_aSwingPeak = swing;
|
||
else lap_aSwingPeak *= 0.92f;
|
||
if (aNow >= L_STBY_MIN_A) {
|
||
lap_wasAboveStandby = true;
|
||
lap_lastAboveTs = nowMs;
|
||
}
|
||
}
|
||
|
||
const char* diagMsg = "Analisis activo";
|
||
if (!relay1) {
|
||
diagMsg = "Analisis pausado";
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
|
||
if (vNow > LAPTOP_VMAX || vNow < LAPTOP_VMIN) {
|
||
diagMsg = "V FUERA RANGO";
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
|
||
if ((aNow >= L_SHORT_I) ||
|
||
(lap_vBase > 0.1f && vNow < (lap_vBase * (1.0f - L_SHORT_DROP_P)) && aNow > 0.10f)) {
|
||
diagMsg = "CORTO!";
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
|
||
if (aNow < L_NO_POWER_A) {
|
||
if (lap_wasAboveStandby && (nowMs - lap_lastAboveTs) < 1500) {
|
||
diagMsg = "SE APAGA";
|
||
} else {
|
||
diagMsg = "SIN ALIMENTACION";
|
||
}
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
|
||
if (aNow >= L_STBY_MIN_A && aNow <= L_STBY_MAX_A && lap_aSwingPeak < L_COMM_SWING_A) {
|
||
diagMsg = "STANDBY OK";
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
|
||
if (lap_aSwingPeak >= L_COMM_SWING_A && aNow < L_BOOT_A) {
|
||
diagMsg = "COMUNICACION OK";
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
|
||
if (aNow >= L_BOOT_A) {
|
||
diagMsg = "INTENTO ARRANQUE";
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
|
||
diagMsg = "ALIMENTANDO...";
|
||
drawLaptopAnalysisSimple(diagMsg, aNow, relay1);
|
||
return;
|
||
}
|
||
}
|
||
|
||
void handleHomeMode(){
|
||
if(checkLongPressB2()){
|
||
beepOnce(40);
|
||
forceGoHome();
|
||
registerActivity();
|
||
return;
|
||
}
|
||
|
||
switch(ch1State){
|
||
|
||
case CH1_IDLE:{
|
||
if(consumeClick(BTN1)){
|
||
if(!displaySleeping){
|
||
beepOnce(50);
|
||
armCh1();
|
||
registerActivity();
|
||
return;
|
||
}
|
||
|
||
if (consumeClick(BTN2)) {
|
||
beepOnce(40);
|
||
setRelay(RELAY1, false);
|
||
relay1 = false;
|
||
ch1State = CH1_IDLE;
|
||
uiMode = MODE_HOME;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
if(consumeClick(BTN2)){
|
||
if(!displaySleeping){
|
||
beepOnce(40);
|
||
uiMode=MODE_MENU;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
if(!displaySleeping){
|
||
drawHomeIdle();
|
||
}
|
||
delay(40);
|
||
} break;
|
||
|
||
case CH1_ACTIVE:{
|
||
if(consumeClick(BTN1)){
|
||
if(!displaySleeping){
|
||
beepOnce(50);
|
||
relay1=false;
|
||
setRelay(RELAY1,false);
|
||
ch1State=CH1_IDLE;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (consumeClick(BTN2)){
|
||
if(!displaySleeping){
|
||
|
||
safeMode = (SafeMode)((((int)safeMode) + 1) % 3);
|
||
|
||
switch(safeMode){
|
||
case SAFE_V: beepOnce(80); break;
|
||
case SAFE_OFF: beepOnce(20); break;
|
||
case SAFE_PLUS: beepCount(2,40,60); break;
|
||
}
|
||
|
||
ch1OnMs = millis();
|
||
registerActivity();
|
||
}
|
||
}
|
||
|
||
float vNow=0.0f, aNow=0.0f;
|
||
readVA(vNow,aNow);
|
||
|
||
unsigned long nowMs = millis();
|
||
|
||
if (safeMode != SAFE_OFF){
|
||
if ((nowMs - ch1OnMs) >= SAFE_ARM_DELAY_MS) {
|
||
if (vNow < SAFE_SHORT_VOLTAGE) {
|
||
relay1=false; setRelay(RELAY1,false);
|
||
lastTripWasPlus = false;
|
||
beepCount(3);
|
||
lastShortTrip=true;
|
||
ch1State=CH1_TRIPPED;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
const float I_BASE = 0.06f;
|
||
const float I_SPIKE = 1.10f;
|
||
const float I_ABS_HARD = 1.50f;
|
||
const unsigned long I_ABS_CONFIRM_MS = 4;
|
||
const unsigned long I_IGN_START_MS = 6;
|
||
const float DI_DT = 0.80f;
|
||
|
||
if (safeMode == SAFE_PLUS){
|
||
if ((nowMs - ch1OnMs) >= (SAFE_ARM_DELAY_MS + I_IGN_START_MS)) {
|
||
aHist[aHidx] = aNow;
|
||
tHist[aHidx] = nowMs;
|
||
aHidx = (aHidx + 1) & 0x03;
|
||
if (!aHistPrimed && aHidx==0) aHistPrimed = true;
|
||
if (aHistPrimed){
|
||
int i0 = (aHidx + 3) & 0x03;
|
||
int i1 = (aHidx + 2) & 0x03;
|
||
float a_now = aHist[i0];
|
||
float a_prev = aHist[i1];
|
||
unsigned long dt = tHist[i0] - tHist[i1];
|
||
float di = a_now - a_prev;
|
||
bool jumpFast = ((a_now >= I_SPIKE) && (dt <= 40)) || ((di >= DI_DT) && (dt <= 40));
|
||
bool absHigh = (a_now >= I_ABS_HARD);
|
||
static unsigned long absStartMs = 0;
|
||
if (absHigh){
|
||
if (absStartMs == 0) absStartMs = nowMs;
|
||
} else {
|
||
absStartMs = 0;
|
||
}
|
||
bool absConfirmed = (absStartMs && (nowMs - absStartMs) >= I_ABS_CONFIRM_MS);
|
||
if (jumpFast || absConfirmed){
|
||
relay1=false; setRelay(RELAY1,false);
|
||
lastTripWasPlus = true;
|
||
beepCount(3);
|
||
lastShortTrip=true;
|
||
ch1State=CH1_TRIPPED;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
static unsigned long diffStart=0;
|
||
unsigned long now=millis();
|
||
float dvfabs=fabs(vNow - vref);
|
||
if(dvfabs > VREF_HYST){
|
||
if(diffStart==0){
|
||
diffStart=now;
|
||
} else if(now-diffStart>=VREF_REARM_HOLD_MS){
|
||
vref=vNow;
|
||
diffStart=0;
|
||
}
|
||
} else {
|
||
diffStart=0;
|
||
}
|
||
|
||
float dropV = vref - vNow;
|
||
if(dropV<0) dropV=0;
|
||
float pctDrop=(vref>0.001f)?(dropV/vref*100.0f):0.0f;
|
||
|
||
if(pctDrop>=99.0f){
|
||
beepOnce(60);
|
||
}
|
||
|
||
float zoomFactor;
|
||
if (dropV < 0.10f) {
|
||
zoomFactor = 4.0f;
|
||
} else if (dropV < 0.20f) {
|
||
zoomFactor = 2.5f;
|
||
} else if (dropV < 0.40f) {
|
||
zoomFactor = 1.5f;
|
||
} else {
|
||
zoomFactor = 1.0f;
|
||
}
|
||
|
||
float scaledDrop = dropV * zoomFactor;
|
||
|
||
if(scaledDrop > GRAPH_MAX_DROP) scaledDrop = GRAPH_MAX_DROP;
|
||
if(scaledDrop < 0) scaledDrop = 0;
|
||
|
||
float norm = scaledDrop / GRAPH_MAX_DROP;
|
||
uint8_t yVal = (uint8_t)(norm * (PH-1) + 0.5f);
|
||
|
||
gbuf[head] = yVal;
|
||
head = (head+1) % PW;
|
||
|
||
if(!displaySleeping){
|
||
drawChannel1Frame(vNow,aNow,dropV,pctDrop,safeModeEnabled);
|
||
}
|
||
|
||
delay(80);
|
||
} break;
|
||
|
||
case CH1_TRIPPED: {
|
||
|
||
if (consumeClick(BTN1)) {
|
||
if (!displaySleeping) {
|
||
beepOnce(50);
|
||
armCh1();
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (consumeClick(BTN2)) {
|
||
if (!displaySleeping) {
|
||
beepOnce(40);
|
||
setRelay(RELAY1, false);
|
||
relay1 = false;
|
||
ch1State = CH1_IDLE;
|
||
uiMode = MODE_HOME;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (!displaySleeping) {
|
||
drawTripScreen();
|
||
}
|
||
|
||
delay(40);
|
||
} break;
|
||
}
|
||
}
|
||
|
||
void handleMainMenu(){
|
||
if(checkLongPressB2()){
|
||
beepOnce(40);
|
||
forceGoHome();
|
||
registerActivity();
|
||
return;
|
||
}
|
||
|
||
if(consumeClick(BTN2)){
|
||
if(!displaySleeping){
|
||
beepOnce(40);
|
||
mainSel = (mainSel+1)%MM_COUNT;
|
||
registerActivity();
|
||
}
|
||
}
|
||
|
||
if(consumeClick(BTN1)){
|
||
if(!displaySleeping){
|
||
beepOnce(80);
|
||
registerActivity();
|
||
|
||
if(mainSel==MM_CANAL2){
|
||
relay2 = !relay2;
|
||
setRelay(RELAY2, relay2);
|
||
forceGoHome();
|
||
return;
|
||
} else if (mainSel == MM_TESTCHIP) {
|
||
|
||
relay1 = false;
|
||
setRelay(RELAY1, false);
|
||
ch1State = CH1_IDLE;
|
||
|
||
testChipSel = 0;
|
||
testChipSel_TC = TCSEL_CPU;
|
||
lastResult_TC = TCR_NONE;
|
||
testPhase_TC = TCPH_READY;
|
||
tc_vMin = 99.0f;
|
||
tc_vMax = 0.0f;
|
||
tc_vSum = 0.0f;
|
||
tc_cnt = 0;
|
||
tcMeasureStartMs = 0;
|
||
|
||
safeModeEnabled = true;
|
||
ch1OnMs = millis();
|
||
|
||
uiMode = MODE_TEST_CHIP;
|
||
|
||
registerActivity();
|
||
drawTestChipMenu_TC(testChipSel_TC, lastResult_TC);
|
||
display.display();
|
||
|
||
return;
|
||
}
|
||
else if(mainSel==MM_LAPTOP){
|
||
relay1=false; setRelay(RELAY1,false);
|
||
ch1State=CH1_IDLE;
|
||
laptopPhase = LAPH_ADJ;
|
||
uiMode=MODE_LAPTOP;
|
||
return;
|
||
}
|
||
|
||
else if(mainSel==MM_MEDICION){
|
||
relay1=false; setRelay(RELAY1,false);
|
||
ch1State=CH1_IDLE;
|
||
uiMode=MODE_VOLT;
|
||
return;
|
||
} else if(mainSel==MM_LAPTOP){
|
||
relay1=false; setRelay(RELAY1,false);
|
||
ch1State=CH1_IDLE;
|
||
uiMode=MODE_LAPTOP;
|
||
return;
|
||
}
|
||
else if(mainSel==MM_CALIBRACION){
|
||
relay1=false; setRelay(RELAY1,false);
|
||
ch1State=CH1_IDLE;
|
||
uiMode=MODE_CALIB;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(!displaySleeping){
|
||
drawMainMenu(mainSel);
|
||
}
|
||
delay(60);
|
||
}
|
||
|
||
void handleTestChipMenu(){
|
||
if(checkLongPressB2()){
|
||
beepOnce(40);
|
||
forceGoHome();
|
||
registerActivity();
|
||
return;
|
||
}
|
||
|
||
if(consumeClick(BTN2)){
|
||
if(!displaySleeping){
|
||
beepOnce(40);
|
||
testChipSel = (testChipSel+1)%TC_COUNT;
|
||
registerActivity();
|
||
}
|
||
}
|
||
|
||
if(consumeClick(BTN1)){
|
||
if(!displaySleeping){
|
||
|
||
beepOnce(80);
|
||
registerActivity();
|
||
}
|
||
}
|
||
|
||
if(!displaySleeping){
|
||
drawTestChipMenu(testChipSel);
|
||
}
|
||
delay(60);
|
||
}
|
||
|
||
void handleVoltimetro(){
|
||
if (consumeClick(BTN2)){
|
||
beepOnce(60);
|
||
drawVoltimetroExitWarning();
|
||
beepLongWarning();
|
||
registerActivity();
|
||
forceGoHome();
|
||
return;
|
||
}
|
||
static float vSlow=0.0f;
|
||
static float vFast=0.0f;
|
||
static float vPeakSwing=0.0f;
|
||
static unsigned long winStart=0;
|
||
static uint8_t phase=0;
|
||
static uint8_t lastState=255;
|
||
static float vShow=0.0f;
|
||
static uint8_t pendingState=255;
|
||
static unsigned long stateStart=0;
|
||
const float FLUCT_THRESH_V=0.020f;
|
||
const float PULSE_THRESH_V=0.250f;
|
||
const float DV_STEP=0.120f;
|
||
const float PULSE_RATE=0.25f;
|
||
const unsigned long STABLE_TIME_MS=1000UL;
|
||
if (phase==0){
|
||
if (winStart==0){
|
||
winStart=micros();
|
||
vPeakSwing=0.0f;
|
||
vFast=0.0f;
|
||
vSlow=0.0f;
|
||
}
|
||
float vPrev=0.0f;
|
||
bool havePrev=false;
|
||
unsigned long n=0, spikes=0;
|
||
float vMin=999.0f, vMax=0.0f, vSum=0.0f;
|
||
while ((unsigned long)(micros()-winStart) < 30000UL){
|
||
float vNow=0.0f, aDummy=0.0f;
|
||
readVA(vNow,aDummy);
|
||
if (vFast==0.0f && vSlow==0.0f){ vFast=vNow; vSlow=vNow; }
|
||
vFast += (vNow - vFast)*0.5f;
|
||
vSlow += (vNow - vSlow)*0.05f;
|
||
float swing=fabs(vFast - vSlow);
|
||
if (swing>vPeakSwing) vPeakSwing=swing;
|
||
if (!havePrev){ vPrev=vNow; havePrev=true; }
|
||
else { if (fabs(vNow - vPrev)>DV_STEP) spikes++; vPrev=vNow; }
|
||
if (vNow<vMin) vMin=vNow;
|
||
if (vNow>vMax) vMax=vNow;
|
||
vSum+=vNow;
|
||
n++;
|
||
}
|
||
float p2p=(vMax - vMin);
|
||
float rate=(n>0)?(float)spikes/(float)n:0.0f;
|
||
uint8_t state;
|
||
if ((p2p>PULSE_THRESH_V) && (rate>PULSE_RATE)) state=2;
|
||
else if (p2p>FLUCT_THRESH_V || rate>(PULSE_RATE*0.5f)) state=1;
|
||
else state=0;
|
||
unsigned long now=millis();
|
||
if (state!=pendingState){
|
||
pendingState=state;
|
||
stateStart=now;
|
||
}
|
||
if ((now - stateStart)>=STABLE_TIME_MS && state!=lastState){
|
||
if (state==2) beepCount(3);
|
||
else if (state==1) beepCount(2);
|
||
else beepOnce(60);
|
||
lastState=state;
|
||
}
|
||
vShow=(n>0)?(vSum/(float)n):vSlow;
|
||
phase=1;
|
||
} else {
|
||
if(!displaySleeping){
|
||
const char *estadoTxt;
|
||
if (lastState==2) estadoTxt="PULSANTE";
|
||
else if (lastState==1) estadoTxt="FLUCTUANTE";
|
||
else estadoTxt="ESTABLE";
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2);
|
||
display.setCursor(8,0);
|
||
display.print("VOLTIMETRO");
|
||
char buf[16];
|
||
dtostrf(vShow,0,3,buf);
|
||
int16_t x1,y1; uint16_t w1,h1;
|
||
display.setTextSize(2);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.getTextBounds(buf,0,0,&x1,&y1,&w1,&h1);
|
||
int16_t xx=(OLED_W - (w1+12))/2; if(xx<0)xx=0;
|
||
int16_t yy=24;
|
||
display.setCursor(xx,yy);
|
||
display.print(buf);
|
||
display.print("V");
|
||
display.setTextSize(1);
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setCursor(10,50);
|
||
display.print("V ");
|
||
display.print(estadoTxt);
|
||
drawInvertedLabel(88,50,"< B2");
|
||
display.display();
|
||
}
|
||
phase=0;
|
||
winStart=0;
|
||
}
|
||
}
|
||
|
||
|
||
void handleLaptop(){
|
||
if(checkLongPressB2()){
|
||
beepOnce(40);
|
||
forceGoHome();
|
||
registerActivity();
|
||
return;
|
||
}
|
||
|
||
bool needAdj19v = true;
|
||
|
||
if(consumeClick(BTN1)){
|
||
if(!displaySleeping){
|
||
beepOnce(80);
|
||
registerActivity();
|
||
|
||
}
|
||
}
|
||
|
||
if(!displaySleeping){
|
||
drawLaptopScreen(needAdj19v);
|
||
}
|
||
|
||
delay(60);
|
||
}
|
||
|
||
void handleOledSleep(){
|
||
unsigned long now = millis();
|
||
|
||
if(displaySleeping){
|
||
if(rawBtnPressed()){
|
||
oledOn();
|
||
beepOnce(30);
|
||
redrawCurrentScreenAfterWake();
|
||
wakeSwallowInputs = true;
|
||
registerActivity();
|
||
}
|
||
return;
|
||
}
|
||
|
||
if(uiMode == MODE_HOME && ch1State == CH1_IDLE){
|
||
if((now - lastActivityMs) >= OLED_SLEEP_MS){
|
||
oledOff();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
void setup(){
|
||
ina.begin();
|
||
ina.setMaxCurrentShunt(16.0f, 0.005f);
|
||
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);
|
||
|
||
Wire.begin();
|
||
Wire.setClock(100000);
|
||
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
|
||
display.setRotation(2);
|
||
display.clearDisplay();
|
||
display.display();
|
||
|
||
ina.begin();
|
||
wdt_enable(WDTO_2S);
|
||
vref = readVoltageCal();
|
||
for(uint8_t i=0;i<PW;i++) gbuf[i]=0;
|
||
head=0;
|
||
|
||
uiMode = MODE_HOME;
|
||
ch1State = CH1_IDLE;
|
||
mainSel = 0;
|
||
testChipSel = 0;
|
||
safeModeEnabled = true;
|
||
lastShortTrip=false;
|
||
tripScreenDrawn=false;
|
||
ch1OnMs=0;
|
||
aSlow=0.0f;
|
||
aPeakSwing=0.0f;
|
||
lastVoltSampleMs=millis();
|
||
|
||
displaySleeping=false;
|
||
lastActivityMs=millis();
|
||
|
||
drawHomeIdle();
|
||
}
|
||
|
||
static uint8_t calPhase = 0;
|
||
|
||
void drawCalibStep1(){
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2); display.setCursor(17,0); display.print("CALIBRAR");
|
||
display.setTextSize(1); display.setCursor(6,18); display.print("COLOQUE UNA R100 5W");
|
||
display.setCursor(22,28); display.print("EN LAS PINZAS");
|
||
display.setCursor(17,38); display.print("AJUSTE A 10.00v");
|
||
drawInvertedLabel(6,48,"B1> Siguiente");
|
||
display.setCursor(98,52); display.print("< B2");
|
||
display.display();
|
||
}
|
||
|
||
#include <EEPROM.h>
|
||
|
||
float calI_k = 1.000f;
|
||
float calV_k = 1.000f;
|
||
const uint16_t EE_CAL_I_ADDR = 96;
|
||
const uint16_t EE_CAL_V_ADDR = 100;
|
||
|
||
void saveCalNV(){ EEPROM.put(EE_CAL_I_ADDR, calI_k); EEPROM.put(EE_CAL_V_ADDR, calV_k); }
|
||
void loadCalNV(){ EEPROM.get(EE_CAL_I_ADDR, calI_k); EEPROM.get(EE_CAL_V_ADDR, calV_k); if(!(calI_k>0.5f && calI_k<1.5f)) calI_k=1.0f;
|
||
if(!(calV_k>0.5f && calV_k<1.5f)) calV_k=1.0f; }
|
||
|
||
void drawCalibStep2(float iDisp, float kI){
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2); display.setCursor(17,0); display.print("CALIBRAR");
|
||
display.setTextSize(1); display.setCursor(10,18); display.print("AJUSTE A 0.100 AMP");
|
||
display.setTextSize(2);display.setCursor(28,28); display.print(iDisp,3); display.print("A");
|
||
drawInvertedLabel(35,48,"B1+ B2-");
|
||
display.display();
|
||
}
|
||
|
||
void drawCalibStep3(float vDisp, float kV){
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2); display.setCursor(17,0); display.print("CALIBRAR");
|
||
display.setTextSize(1); display.setCursor(10,18); display.print("AJUSTE A 10.00 VOL");
|
||
display.setTextSize(2);display.setCursor(28,28); display.print(vDisp,2); display.print("V");
|
||
drawInvertedLabel(35,48,"B1+ B2-");
|
||
display.display();
|
||
}
|
||
|
||
void drawCalibDone(){
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2); display.setCursor(6,0); display.print("COMPLETADO");
|
||
display.setTextSize(1); display.setCursor(12,24); display.print("CAL_I "); display.print(calI_k,3);
|
||
display.setCursor(12,36); display.print("CAL_V "); display.print(calV_k,3);
|
||
display.display();
|
||
}
|
||
|
||
void handleCalibracion(){
|
||
static uint8_t calPhase = 0;
|
||
static unsigned long calI_okStart = 0, calV_okStart = 0;
|
||
static unsigned long calI_outTs = 0, calV_outTs = 0;
|
||
static float iFilt = 0.0f, vFilt = 0.0f;
|
||
if (calPhase==0 && consumeClick(BTN2)) {
|
||
beepOnce(40);
|
||
relay1 = false; setRelay(RELAY1, false);
|
||
uiMode = MODE_MENU;
|
||
registerActivity();
|
||
return;
|
||
}
|
||
if(calPhase==0){
|
||
if(consumeClick(BTN1)){
|
||
beepOnce(80);
|
||
relay1=true; setRelay(RELAY1,true);
|
||
unsigned long t0 = millis();
|
||
float vSum=0.0f; uint16_t vCnt=0; float vNow=0.0f, aNow=0.0f;
|
||
while(millis()-t0<1000){ readVA(vNow,aNow); vSum+=vNow; vCnt++; delay(10); }
|
||
relay1=false; setRelay(RELAY1,false);
|
||
float vAvg = vCnt ? (vSum/(float)vCnt) : 0.0f;
|
||
if(vAvg>=9.5f && vAvg<=10.5f){ calPhase=1; registerActivity(); }
|
||
else{
|
||
display.clearDisplay();
|
||
display.setTextColor(SSD1306_WHITE);
|
||
display.setTextSize(2); display.setCursor(18,0); display.print("ATENCION");
|
||
display.setTextSize(1); display.setCursor(8,24); display.print("Medido: "); display.print(vAvg,2); display.print("V");
|
||
drawInvertedLabel(6,40,"DESAJUSTE EXCESIVO");
|
||
drawInvertedLabel(6,50,"AJUSTE V_CAL ANTES");
|
||
display.display();
|
||
delaySafe(10000);
|
||
}
|
||
}
|
||
drawCalibStep1();
|
||
return;
|
||
}
|
||
|
||
if(calPhase==1){
|
||
if(!relay1){ relay1=true; setRelay(RELAY1,true); }
|
||
float vNow=0.0f, aNow=0.0f; readVA(vNow,aNow);
|
||
iFilt += (aNow - iFilt) * 0.30f;
|
||
if(consumeClick(BTN1)){ float v=calI_k+0.01f; if(v>1.20f) v=1.20f; calI_k=v; beepOnce(40); registerActivity(); }
|
||
if(consumeClick(BTN2)){ float v=calI_k-0.01f; if(v<0.80f) v=0.80f; calI_k=v; beepOnce(40); registerActivity(); }
|
||
float iDisp = iFilt*calI_k;
|
||
bool inRange = (iDisp>=0.095f && iDisp<=0.105f);
|
||
unsigned long now = millis();
|
||
if(inRange){
|
||
if(calI_okStart==0) calI_okStart = now;
|
||
calI_outTs = 0;
|
||
}else{
|
||
if(calI_outTs==0) calI_outTs = now;
|
||
if(now - calI_outTs >= 150UL){ calI_okStart = 0; }
|
||
}
|
||
drawCalibStep2(iDisp, calI_k);
|
||
if(calI_okStart){
|
||
unsigned long dt = now - calI_okStart; if(dt>5000UL) dt=5000UL;
|
||
uint16_t w = (uint16_t)(dt*128UL/5000UL);
|
||
display.fillRect(0,62,w,2,SSD1306_WHITE);
|
||
display.display();
|
||
if(dt>=5000UL){ calPhase=2; registerActivity(); }
|
||
}else{
|
||
display.drawRect(0,62,128,2,SSD1306_WHITE);
|
||
display.display();
|
||
}
|
||
return;
|
||
}
|
||
|
||
if(calPhase==2){
|
||
float vNow=0.0f, aNow=0.0f; readVA(vNow,aNow);
|
||
if(consumeClick(BTN1)){ float v=calV_k+0.01f; if(v>1.20f) v=1.20f; calV_k=v; beepOnce(40); registerActivity(); }
|
||
if(consumeClick(BTN2)){ float v=calV_k-0.01f; if(v<0.80f) v=0.80f; calV_k=v; beepOnce(40); registerActivity(); }
|
||
float vDisp = vNow*calV_k;
|
||
if(vDisp>=9.90f && vDisp<=10.10f){ if(calV_okStart==0) calV_okStart=millis(); }
|
||
else { calV_okStart=0; }
|
||
drawCalibStep3(vDisp, calV_k);
|
||
if(calV_okStart){
|
||
unsigned long dt = millis()-calV_okStart; if(dt>5000UL) dt=5000UL;
|
||
uint16_t w = (uint16_t)(dt*128UL/5000UL);
|
||
display.fillRect(0,62,w,2,SSD1306_WHITE);
|
||
display.display();
|
||
if(dt>=5000UL){ saveCalNV(); calPhase=3; calV_okStart=0; registerActivity(); }
|
||
} else {
|
||
display.drawRect(0,62,128,2,SSD1306_WHITE);
|
||
display.display();
|
||
}
|
||
return;
|
||
}
|
||
|
||
if(calPhase>=3){
|
||
relay1=false; setRelay(RELAY1,false);
|
||
drawCalibDone();
|
||
delaySafe(1200);
|
||
uiMode = MODE_MENU;
|
||
calPhase = 0;
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
void loop(){
|
||
wdt_reset();
|
||
handleOledSleep();
|
||
repromod_dualButtonResetHandler();
|
||
|
||
if(displaySleeping){
|
||
delay(10);
|
||
return;
|
||
}
|
||
|
||
switch(uiMode){
|
||
case MODE_HOME:
|
||
handleHomeMode();
|
||
break;
|
||
|
||
case MODE_MENU:
|
||
handleMainMenu();
|
||
break;
|
||
|
||
case MODE_TEST_CHIP:
|
||
handleTestChip_TC();
|
||
break;
|
||
|
||
case MODE_VOLT:
|
||
handleVoltimetro();
|
||
break;
|
||
|
||
case MODE_LAPTOP:
|
||
handleLaptop_TC();
|
||
break;
|
||
|
||
case MODE_CALIB:
|
||
handleCalibracion();
|
||
break;
|
||
}
|
||
}
|
||
|