#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <TM1637Display.h>
#include <math.h>
// ==========================================
// 1. CONFIGURACIÓN DE PANTALLA (comentar o descomentar solo una para habilitar la configuracion del hardware)
// ==========================================
#define MODO_PANTALLA_LCD
//#define MODO_PANTALLA_TM1637
// ==========================================
// 2. INTERFAZ DE CONTROL (comentar o descomentar solo una para habilitar la configuracion del hardware)
// ==========================================
#define CONTROL_POR_POTENCIOMETROS
//#define CONTROL_POR_PULSADORES
// ==========================================
// 3. SELECCIÓN DE SENSOR NTC (comentar o descomentar solo una para habilitar la configuracion del hardware)
// ==========================================
//#define SENSOR_NTC_ANALOGICO
#define SENSOR_MAX6682
// ==========================================
// ASIGNACIÓN ESTRICTA DE PINES I/O
// ==========================================
const int PIN_RELAY = 10;
const int PIN_PWM_BRILLO = 6; // Salida PWM física para controlar el brllo del display LCD
#ifdef CONTROL_POR_POTENCIOMETROS
const int PIN_POT_SETPOINT = A1;
const int PIN_POT_BRILLO = A2;
#endif
#ifdef CONTROL_POR_PULSADORES
const int PIN_BTN_SET_UP = 2;
const int PIN_BTN_SET_DOWN = 3;
const int PIN_BTN_BRI_UP = 4;
const int PIN_BTN_BRI_DOWN = 5;
#endif
#ifdef SENSOR_NTC_ANALOGICO
const int pinNTC = A0;
const float R_AUX = 3000.0;
const float R_NOMINAL = 3000.0;
const float T_NOMINAL = 25.0;
const float BETA = 3950.0;
#endif
#ifdef SENSOR_MAX6682
const int MAX6682_CS = 7;
const int MAX6682_MISO = 12;
const int MAX6682_SCK = 13;
#endif
// ==========================================
// PARAMETROS POR SI SE USA EL MOD TERMOSTATO
// ==========================================
const float HISTERESIS = 1.0;
// el agua tiene inercia térmica, asi que sumamos3 minutos en milisegundos para proteger el equipo para no ciclar rápido... 3 minutos es una delta considerable
const unsigned long TIEMPO_MIN_APAGADO = 180000;
// Variables de control interno
int nivelBrillo = 4;
int setpointDecimas = 300;
bool relayActivo = false;
unsigned long tUltimoApagado = 0; // registro para el anti-cicladorapido
// Variables para la lógica del TM1637 (Titileo y timeout de 2 segundos)
unsigned long tUltimoCambioSet = 0;
bool mostrandoSetpoint = false;
bool alternarTitileo = false;
unsigned long tUltimoTitileo = 0;
// Instanciación de pantallas (A4 y A5 se manejan nativos por Wire en I2C)
#ifdef MODO_PANTALLA_LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);
#endif
#ifdef MODO_PANTALLA_TM1637
#define CLK_TM1637 8
#define DIO_TM1637 9
TM1637Display tmDisplay(CLK_TM1637, DIO_TM1637);
#endif
// Función de lectura MAX6682
#ifdef SENSOR_MAX6682
float leerMAX6682()
{
unsigned int datos = 0;
digitalWrite(MAX6682_CS, LOW);
delayMicroseconds(1);
for (int i = 15; i >= 0; i--)
{
digitalWrite(MAX6682_SCK, HIGH);
delayMicroseconds(1);
if (digitalRead(MAX6682_MISO)) datos |= (1 << i);
digitalWrite(MAX6682_SCK, LOW);
delayMicroseconds(1);
}
digitalWrite(MAX6682_CS, HIGH);
if (datos == 0xFFFF || datos == 0) return -999.0;
return ((datos >> 5) & 0x03FF) * 0.25;
}
#endif
void setup()
{
pinMode(PIN_RELAY, OUTPUT);
digitalWrite(PIN_RELAY, LOW);
pinMode(PIN_PWM_BRILLO, OUTPUT);
#ifdef CONTROL_POR_PULSADORES
pinMode(PIN_BTN_SET_UP, INPUT_PULLUP);
pinMode(PIN_BTN_SET_DOWN, INPUT_PULLUP);
pinMode(PIN_BTN_BRI_UP, INPUT_PULLUP);
pinMode(PIN_BTN_BRI_DOWN, INPUT_PULLUP);
#endif
#ifdef SENSOR_MAX6682
pinMode(MAX6682_CS, OUTPUT);
pinMode(MAX6682_SCK, OUTPUT);
pinMode(MAX6682_MISO, INPUT);
digitalWrite(MAX6682_CS, HIGH);
digitalWrite(MAX6682_SCK, LOW);
#endif
#ifdef MODO_PANTALLA_LCD
lcd.init();
lcd.clear();
#endif
#ifdef MODO_PANTALLA_TM1637
tmDisplay.clear();
#endif
}
void loop()
{
int anteriorSetpoint = setpointDecimas;
/////////////////////////////////////////
// 1_INTERFAZ DE USUARIO
/////////////////////////////////////////
#ifdef CONTROL_POR_POTENCIOMETROS
int lecturaBrillo = analogRead(PIN_POT_BRILLO);
#ifdef MODO_PANTALLA_LCD
nivelBrillo = map(lecturaBrillo, 0, 1023, 0, 255);
#endif
#ifdef MODO_PANTALLA_TM1637
nivelBrillo = map(lecturaBrillo, 0, 1023, 0, 7);
#endif
int lecturaSet = analogRead(PIN_POT_SETPOINT);
setpointDecimas = map(lecturaSet, 0, 1023, 150, 450); // 15.0°C a 45.0°C
#endif
#ifdef CONTROL_POR_PULSADORES
if (digitalRead(PIN_BTN_BRI_UP) == LOW)
{
#ifdef MODO_PANTALLA_LCD
nivelBrillo = min(nivelBrillo + 15, 255);
#endif
#ifdef MODO_PANTALLA_TM1637
nivelBrillo = min(nivelBrillo + 1, 7);
#endif
delay(150);
}
if (digitalRead(PIN_BTN_BRI_DOWN) == LOW)
{
#ifdef MODO_PANTALLA_LCD
nivelBrillo = max(nivelBrillo - 15, 0);
#endif
#ifdef MODO_PANTALLA_TM1637
nivelBrillo = max(nivelBrillo - 1, 0);
#endif
delay(150);
}
if (digitalRead(PIN_BTN_SET_UP) == LOW)
{
setpointDecimas = min(setpointDecimas + 1, 450);
delay(150);
}
if (digitalRead(PIN_BTN_SET_DOWN) == LOW)
{
setpointDecimas = max(setpointDecimas - 1, 150);
delay(150);
}
#endif
// Detectar si se modifico el setpoint
if (setpointDecimas != anteriorSetpoint)
{
tUltimoCambioSet = millis();
mostrandoSetpoint = true;
}
// aplicar brillo por hardware en D6 (para el LCD se usa el pin PWM)
#ifdef MODO_PANTALLA_LCD
analogWrite(PIN_PWM_BRILLO, nivelBrillo);
#endif
#ifdef MODO_PANTALLA_TM1637
tmDisplay.setBrightness(nivelBrillo);
#endif
float tempSetpointReal = setpointDecimas / 10.0;
/////////////////////////////
// 2_ADQUISICION DEL SENSOR
///////////////////////////
float temperaturaC = 0.0;
bool errorSensor = false;
#ifdef SENSOR_NTC_ANALOGICO
int valorADC = analogRead(pinNTC);
if (valorADC <= 0 || valorADC >= 1023)
{
errorSensor = true;
}
else
{
float resistenciaNTC = R_AUX * ((1023.0 / (float)valorADC) - 1.0);
float temperaturaK = resistenciaNTC / R_NOMINAL;
temperaturaK = log(temperaturaK);
temperaturaK /= BETA;
temperaturaK += 1.0 / (T_NOMINAL + 273.15);
temperaturaK = 1.0 / temperaturaK;
temperaturaC = temperaturaK - 273.15;
}
#endif
#ifdef SENSOR_MAX6682
temperaturaC = leerMAX6682();
if (temperaturaC == -999.0) errorSensor = true;
#endif
if (errorSensor)
{
digitalWrite(PIN_RELAY, LOW);
#ifdef MODO_PANTALLA_LCD
lcd.setCursor(0, 0); lcd.print("ERROR SENSOR ");
lcd.setCursor(0, 1); lcd.print("SISTEMA PARADO ");
#endif
#ifdef MODO_PANTALLA_TM1637
uint8_t dataErr[] = { 0x79, 0x50, 0x50, 0x00 };
tmDisplay.setSegments(dataErr);
#endif
delay(500);
return;
}
////////////////////////////////
// 3_LÓGICA DE CONTROL DE RELAY
/////////////////////////////////
unsigned long tiempoActual = millis();
if (temperaturaC <= (tempSetpointReal - HISTERESIS))
{
// Intenta encender la caldera porque hace frío
// Evaluamos si ya pasó el tiempo prudencial de protección desde el último apagado
if (!relayActivo && (tiempoActual - tUltimoApagado >= TIEMPO_MIN_APAGADO || tUltimoApagado == 0))
{
relayActivo = true;
}
}
else if (temperaturaC >= (tempSetpointReal + HISTERESIS))
{
// Apaga la caldera porque ya llegó a la temperatura objetivo
if (relayActivo)
{
relayActivo = false;
tUltimoApagado = tiempoActual; // Guardamos la hora exacta del apagado
}
}
digitalWrite(PIN_RELAY, relayActivo ? HIGH : LOW);
///////////////////////////
// 4. CONTROL DE PANTALLAS
///////////////////////////
#ifdef MODO_PANTALLA_LCD
// linea 1: temperatura actual y estado del relay
lcd.setCursor(0, 0);
lcd.print("TEMP: "); lcd.print(temperaturaC, 1); lcd.write(0xDF); lcd.print("C ");
lcd.setCursor(12, 0);
lcd.print(relayActivo ? "[ON]" : "[OFF]");
// Línea 2: temperatura seteada
lcd.setCursor(0, 1);
lcd.print("SET: "); lcd.print(tempSetpointReal, 1); lcd.write(0xDF); lcd.print("C ");
#endif
#ifdef MODO_PANTALLA_TM1637
// Evaluar la ventana de tiempo de 2 segundos (2000 ms) tras tocar el setpoint
if (mostrandoSetpoint && (tiempoActual - tUltimoCambioSet < 2000))
{
// Controlar el ritmo del titileo (cada 200 ms vacía/muestra)
if (tiempoActual - tUltimoTitileo > 200)
{
alternarTitileo = !alternarTitileo;
tUltimoTitileo = tiempoActual;
}
if (alternarTitileo)
{
tmDisplay.clear();
}
else
{
tmDisplay.showNumberDec((int)tempSetpointReal, false);
}
}
else
{
// Pasaron los 2 segundos o no se tocó nada: vuelve a temperatura actual fija
mostrandoSetpoint = false;
tmDisplay.showNumberDec((int)temperaturaC, false);
}
#endif
delay(20);
}