/******************************************************
Termostato con sensore DHT22 v3.2
Massimo Gostinelli - 28/12/2023
Programma liberamente modificabile e distribuibile
Tutti i collegamenti sono relativi ad Arduino Nano v3.0
Encoder management thanks to MoThunderz:
https://youtu.be/fgOfSHTYeio?si=Hno76wld1ioIjZJm
https://github.com/mo-thunderz/RotaryEncoder
*** Pinout DHT22 ***
Pin 1: Vcc (+5V)
Pin 2: Dati
Pin 3: NC
Pin 4: GND
******************************************************/
#include <EEPROM.h>
#include <ezButton.h>
#include <SimpleDHT.h>
#include <LiquidCrystal.h>
// inizializza la libreria associando i pin alla relativa connessione nel display LCD 16x2
const int RS = 8, EN = 10, D4 = 4, D5 = 5, D6 = 6, D7 = 7;
LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);
// numero di righe e colonne del display LCD
const int numRows = 2;
const int numCols = 16;
SimpleDHT22 dht22; // tipo di sensore utilizzato (DTH11 o DTH22)
int err = SimpleDHTErrSuccess;
#define pinDHT22 16 // pin A2 dati provenienti dal sensore [Ta-Hr] (pin fisico 21)
#define pinOUT 9 // pin D9 uscita per il relay (pin fisico 12)
#define pinLED 13 // LED integrato nella scheda Arduino
#define pinA_LED 11 // uscita PWM per controllo retro-illuminazione display LCD (pin fisico 14)
float Ta = 99.9; // temperatura ambiente rilevata dal sensore, partendo da 99.9 al riavvio l'uscita sarà OFF
float Hr = 0; // umidità relativa rilevata dal sensore
volatile float Tc; // temperatura impostata con l'encoder
const float Th = 0.5; // temperatura di isteresi
unsigned long previousMillis = 0; // variabile per creare un ritardo senza la funzione "delay"
const long interval = 2500; // ritardo in ms usato per la lettura del sensore che avviene ogni 2s
//*** DEFINIZIONE ENCODER ***
#define EncCLK 2 // definizione pin CLK dell'encoder
#define EncDT 3 // definizione pin DT dell'encoder
unsigned long lastIncReadTime = micros();
unsigned long lastDecReadTime = micros();
int PauseLength = 25000;
int FastIncrement = 1; // rotazioni veloci: 1 => non impostato; 10 => 10 step con rotazione veloce
volatile float Counter; // contatore degli step dell'encoder
float Mem; // valore nella EEPROM
//*** DEFINIZIONE PULSANTE ***
const int PressTime = 1000; // tempo in ms che definisce la pressione lunga
ezButton button(14); // pulsante SW dell'encoder su A0
unsigned long pressedTime = 0;
unsigned long releasedTime = 0;
bool isPressing = false;
bool isLongDetected = false;
unsigned long pMillis = 0;
// mappa del simbolo del grado °
byte Grado[8] = {
0b01110,
0b01010,
0b01110,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000
};
void setup() {
//Serial.begin(9600);
pinMode(pinLED, OUTPUT);
pinMode(pinOUT, OUTPUT);
pinMode(pinA_LED, OUTPUT);
analogWrite(pinA_LED, 255); // retro-illuminazione LCD al 100%
lcd.begin(numCols, numRows);
lcd.createChar(0, Grado); // crea il simbolo del grado °
lcd.setCursor(0, 0); // posiziona il cursore (colonna, riga), partendo da 0 (zero)
lcd.print("Termostato DHT22"); // stampa lo splash-screen
lcd.setCursor(6, 1);
lcd.print("v3.2");
delay(3000); // ritardo di 3 sec per lo splash-screen
lcd.clear(); // pulisce il display
// Imposta i pin dell'encoder e li connette agli interrupts
pinMode(EncCLK, INPUT_PULLUP);
pinMode(EncDT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(EncCLK), ReadEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(EncDT), ReadEncoder, CHANGE);
button.setDebounceTime(50); // anti-rimbalzo a 50 millisecondi
Mem = EEPROM.read(0);
Counter = Mem; // valore temperatura "Tc" iniziale preso dal valore in memoria
}
void loop() {
// lettura DHT22 con ritardo senza la funzione "delay" ***
if (millis() - previousMillis >= interval) {
previousMillis = millis(); // salva il valore aggiornato dei millisecondi correnti
// legge i dati e segnala un possibile errore nella lettura del flusso provenente dal sensore
if ((err = dht22.read2(pinDHT22, &Ta, &Hr, NULL)) != SimpleDHTErrSuccess) {
//Serial.print("Lettura dati DHT22 fallita, err=");
//Serial.println(err);
}
}
// se la temperatura ambiente scende sotto la temperatura impostata meno l'isteresi, accende la caldaia
if (Ta <= Tc - Th) {
digitalWrite(pinLED, HIGH); // accende il LED integrato
digitalWrite(pinOUT, HIGH); // accende la caldaia
lcd.setCursor(13, 1);
lcd.print("ON "); // scrive ON sul display
}
// se la temperatura ambiente sale sopra la temperatura impostata, spegne la caldaia
if (Ta >= Tc) {
digitalWrite(pinLED, LOW); // spegne il LED integrato
digitalWrite(pinOUT, LOW); // spegne la caldaia
lcd.setCursor(13, 1);
lcd.print("OFF"); // scrive OFF sul display
}
lcd.setCursor(0, 0);
lcd.print("Ta:");
lcd.print(String(Ta, 1)); // temperatura dell'ambiente con 1 cifra decimale
lcd.print((char)0); // carattere speciale, simbolo del grado °
lcd.print("C");
lcd.setCursor(10, 0);
lcd.print("Hr:");
lcd.print(String(Hr, 0)); // umidità relativa rilevata dal sensore con 0 cifre decimali
lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("Tc:");
lcd.print(String(Tc, 1)); // temperatura di commutazione con 1 cifra decimale
lcd.print((char)0); // carattere speciale, simbolo del grado °
lcd.print("C");
button.loop();
//*** LOOP PULSANTE ********************************************************************
if (button.isPressed()) {
pressedTime = millis();
isPressing = true;
isLongDetected = false;
}
if (button.isReleased()) {
isPressing = false;
releasedTime = millis();
long pressDuration = releasedTime - pressedTime;
if (pressDuration < PressTime) {
digitalWrite(13, !digitalRead(13)); // Short press SW button
//Serial.println("EEPROM READ");
Counter = Mem;
pMillis = millis();
lcd.setCursor(10, 1);
lcd.write(127); // carattere freccia sinistra
lcd.print("M"); // scrive <-M sul display
}
}
if (isPressing == true && isLongDetected == false) {
long pressDuration = millis() - pressedTime;
if (pressDuration > PressTime) {
EEPROM.write(0, int(Counter));
//Serial.println("EEPROM WRITE"); // Long press SW button
Mem = EEPROM.read(0);
isLongDetected = true;
if ((millis() - pMillis >= PressTime)) {
pMillis = millis();
lcd.setCursor(10, 1);
lcd.write(126); // carattere freccia destra
lcd.print("M"); // scrive ->M sul display
}
}
}
if ((millis() - pMillis >= 800)) {
lcd.setCursor(10, 1);
lcd.print(" "); // cancella ->M o <-M dal display dopo 800 ms
}
//**************************************************************************************
static float LastCounter = 0;
if (Counter != LastCounter) {
Counter = constrain(Counter, 120, 250); // Limita Counter tra 120 e 250
Tc = Counter / 10; // divisione per 10, la temperatura impostabile sarà compresa tra 12.0 e 25.0 °C
//Serial.print(String(Tc, 1)); // Formatto con una cifra decimale
//Serial.print(" - ");
//Serial.println(String(Mem / 10, 1));
LastCounter = Counter;
}
}
//*** Funzione per la lettura dell'encoder, aggiorna se fatto uno step completo ***
void ReadEncoder() {
static int Step = 3; // Encoder con 2 step = 3; Encoder con 1 step = 1
static int Increment = 5; // Controlla l'incremento nella variabile Counter
static uint8_t Old_AB = 3; // Lookup table index
static int8_t EncVal = 0; // Encoder value
static const int8_t EncStates[] = { 0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0 }; // Lookup table
Old_AB <<= 2; // Remember previous state
if (digitalRead(EncCLK)) Old_AB |= 0x02; // Add current state of pin A
if (digitalRead(EncDT)) Old_AB |= 0x01; // Add current state of pin B
EncVal += EncStates[(Old_AB & 0x0f)];
// Update Counter if encoder has rotated a full indent, that is at least 4 steps
if (EncVal > Step) {
int ChangeValue = Increment;
if ((micros() - lastIncReadTime) < PauseLength) {
ChangeValue = FastIncrement * ChangeValue;
}
lastIncReadTime = micros();
Counter = Counter + ChangeValue;
EncVal = 0;
} else if (EncVal < -Step) {
int ChangeValue = -Increment;
if ((micros() - lastDecReadTime) < PauseLength) {
ChangeValue = FastIncrement * ChangeValue;
}
lastDecReadTime = micros();
Counter = Counter + ChangeValue;
EncVal = 0;
}
}