// Costanti
// Configurazione pin display
#define RS 12
#define E 10
#define D4 5
#define D5 4
#define D6 9
#define D7 13
#define NUM_SCREENS 4; // Numero totale di schermate infoStatus()
#define DHT_PIN_EXTERNAL 2 // DHT esterno pin 2
#define DHT_PIN_INTERNAL 3 // DHT interno pin 3
#define POTENZIOMETROPIN 0 // Potenziometro pin 0
#define FLUSSO_DIRETTO 6 // Flusso diretto simulato LED rosso pin 6
#define CIRCUITO_RECUPERO_CALORE 8 // Circuito a recupero di calore simulato LED blu pin 8
#define BUTTONPIN1 1 // Pin del primo pulsante
#define BUTTONPIN2 2 // Pin del secondo pulsante
#define BUTTONPIN3 3 // Pin del terzo pulsante
#define BUTTONPIN4 4 // Pin del quarto pulsante
// Variabili globali
// Tempo di pressione per i pulsanti
unsigned long pressStartTime1 = 0;
unsigned long pressStartTime2 = 0;
unsigned long pressStartTime3 = 0;
unsigned long pressStartTime4 = 0;
float sogliaTemperaturaBenessere = 2.0; // Valore (default) Differenza minima per attivare il flusso diretto
int currentScreen = 0; // Indice della schermata attuale (per infoStatus())
int CO2Value = 0; // Livello di CO2 presente nell'aria
float temperature_internal = 0; // Temperatura interna rilevata dal DHT22 (pin 3)
float temperature_external = 0; // Temperatura esterna rilevata dal DHT22 (pin 2)
bool FourBitMode = false; // Variabile per tenere traccia della modalità a 4 bit o 8 bit
char ReadSendState = -1; // Variabile per tenere traccia dello stato di invio o lettura
/*
* Invia dati al display LCD.
* Questa funzione invia un byte di dati al display LCD. Se la modalità a 4 bit è attiva,
* invia prima i primi 4 bit di dati e poi i successivi 4 bit. Ogni trasmissione di 4 bit
* coinvolge un set di pin dati (D4-D7) e il pin di controllo E (Enable).
*/
void LcdSend(unsigned char Data)
{
if (FourBitMode)
{
// Invia i primi 4 bit dei dati ai pin D4, D5, D6 e D7
customDigitalWrite(D4, (Data >> 4) & 0x01);
customDigitalWrite(D5, (Data >> 5) & 0x01);
customDigitalWrite(D6, (Data >> 6) & 0x01);
customDigitalWrite(D7, (Data >> 7) & 0x01);
// Impulso di attivazione per il display LCD
delayMicroseconds(10);
customDigitalWrite(E, HIGH); // Attiva il pin E
delayMicroseconds(10);
customDigitalWrite(E, LOW); // Disattiva il pin E
delayMicroseconds(100); // Attende
}
// Invia i secondi 4 bit dei dati ai pin D4, D5, D6 e D7
customDigitalWrite(D4, (Data >> 0) & 0x01);
customDigitalWrite(D5, (Data >> 1) & 0x01);
customDigitalWrite(D6, (Data >> 2) & 0x01);
customDigitalWrite(D7, (Data >> 3) & 0x01);
// Impulso di attivazione per il display LCD
delayMicroseconds(10);
customDigitalWrite(E, HIGH); // Attiva il pin E
delayMicroseconds(10);
customDigitalWrite(E, LOW); // Disattiva il pin E
delayMicroseconds(100); // Attende
}
/*
* Invia un comando al display LCD.
* Questa funzione imposta il pin RS (Register Select) a LOW per indicare
* che sta inviando un comando al display LCD e quindi chiama LcdSend() per inviare
* effettivamente il comando.
*/
void LcdCommand(unsigned char Command)
{
// Verifica se il display LCD è nello stato di invio dati (RS HIGH), se lo è, passa a inviare comandi (RS LOW).
if (ReadSendState != LOW)
{
// Imposta RS (Register Select) a LOW per indicare un comando
ReadSendState = LOW;
customDigitalWrite(RS, LOW);
}
// Invia il comando specificato al display LCD
LcdSend(Command);
// Se il comando è il comando di cancellazione (0x01), attendi un periodo di tempo maggiore
if (Command == 0x01) delayMicroseconds(2000); // Il comando Clear richiede più tempo
}
/*
* Scrive un singolo carattere sul display LCD.
* Questa funzione scrive un carattere specificato sul display LCD. Imposta il pin RS (Register Select)
* a HIGH per indicare la scrittura di dati al display. Il carattere specificato viene quindi inviato alla
* funzione LcdSend() per la trasmissione effettiva.
*/
void LcdWrite(int Letter)
{
if (ReadSendState != HIGH)
{
// Imposta RS (Register Select) a HIGH per indicare la scrittura di dati
ReadSendState = HIGH;
customDigitalWrite(RS, HIGH);
}
LcdSend(Letter);
}
/**
* Scrive una stringa di testo sul display LCD.
* Questa funzione scrive una stringa di testo specificata sul display LCD. Imposta il pin RS (Register Select)
* a HIGH per indicare la scrittura di dati al display. La funzione iterativamente invia ciascun carattere della
* stringa al display utilizzando la funzione LcdSend().
*/
void LcdWrite(const char* Text)
{
if (ReadSendState != HIGH)
{
// Imposta RS (Register Select) a HIGH per indicare la scrittura di dati
ReadSendState = HIGH;
customDigitalWrite(RS, HIGH);
}
for (; *Text != 0; Text++)
{
// Invia lettera per lettera
char Letter = *Text;
LcdSend(Letter);
}
}
/**
* Inizializza il display LCD.
* Questa funzione inizializza il display LCD. Se è la prima inizializzazione (bFirstInit è true),
* attende un periodo di accensione, imposta le modalità di funzionamento dei pin e passa dalla modalità a 8 bit
* alla modalità a 4 bit. Successivamente, invia una serie di comandi di inizializzazione al display LCD
* per configurarlo secondo le specifiche desiderate.
* bFirstInit Indica se questa è la prima inizializzazione del display LCD.
*/
void LcdInit(bool bFirstInit)
{
if (bFirstInit)
{
// Attendi un periodo di accensione
delayMicroseconds(15000);
// Imposta i pin come OUTPUT
customPinMode(RS, OUTPUT);
customPinMode(E, OUTPUT);
customPinMode(D4, OUTPUT);
customPinMode(D5, OUTPUT);
customPinMode(D6, OUTPUT);
customPinMode(D7, OUTPUT);
}
// Inizia con la modalità a 8 bit
FourBitMode = false;
// Invia i primi comandi di inizializzazione
LcdCommand(0x03);
delayMicroseconds(4000);
LcdCommand(0x03);
LcdCommand(0x03);
LcdCommand(0x02);
// Passa alla modalità a 4 bit
FourBitMode = true;
// Invia ulteriori comandi di inizializzazione
LcdCommand(0x28);
LcdCommand(0x0C);
LcdCommand(0x01); // Comando di cancellazione
LcdCommand(0x06);
}
/**
* Imposta la posizione del cursore sul display LCD.
* Questa funzione calcola e invia il comando appropriato per posizionare il cursore
* sulla riga e sulla colonna specificate del display LCD. La riga deve essere 0 o 1,
* dove 0 rappresenta la prima riga e 1 rappresenta la seconda riga.
* Column La colonna su cui posizionare il cursore (da 0 a 15).
* Row La riga su cui posizionare il cursore (0 per la prima riga, 1 per la seconda).
*/
void LcdSetCursor(unsigned char Column, unsigned char Row)
{
// Calcola e invia il comando per posizionare il cursore
LcdCommand(0x80 | (Column + (Row != 0 ? 0x40 : 0x00)));
}
// Cancella il contenuto del display
void LcdClear() {
LcdCommand(0x01); // Invia il comando per cancellare il display
delayMicroseconds(2000); // Aspetta il tempo necessario per il comando di cancellazione
}
// Lettura del potenziometro
int readPotentiometer(int pin) {
ADMUX = 0x40 | (pin & 0x07); // Imposta il pin da leggere
ADCSRA |= (1 << ADSC); // Avvia la conversione
// Attendi la fine della conversione
while (ADCSRA & (1 << ADSC)) {
// Attendi
}
return ADC; // Restituisci il valore letto
}
// Funzione personalizzata per impostare la modalità dei pin
void customPinMode(int pin, int mode) {
if (mode == INPUT) {
// Imposta il pin come input (lettura).
if (pin < 8) {
DDRD &= ~(1 << pin); // Imposta il bit corrispondente a 0 nel registro DDRD.
} else if (pin < 14) {
DDRB &= ~(1 << (pin - 8)); // Imposta il bit corrispondente a 0 nel registro DDRB.
}
} else if (mode == OUTPUT) {
// Imposta il pin come output (scrittura).
if (pin < 8) {
DDRD |= (1 << pin); // Imposta il bit corrispondente a 1 nel registro DDRD.
} else if (pin < 14) {
DDRB |= (1 << (pin - 8)); // Imposta il bit corrispondente a 1 nel registro DDRB.
}
}
}
// Funzione personalizzata per leggere lo stato dei pin
int customDigitalRead(int pin) {
if (pin < 8) {
// Se il pin è nell'intervallo 0-7, leggi dal registro PIND.
return (PIND & (1 << pin)) ? HIGH : LOW; // Restituisci HIGH se il bit è 1, altrimenti LOW.
} else if (pin < 14) {
// Se il pin è nell'intervallo 8-13, leggi dal registro PINB.
return (PINB & (1 << (pin - 8))) ? HIGH : LOW; // Restituisci HIGH se il bit è 1, altrimenti LOW.
}
// Se il pin è fuori dall'intervallo specificato, restituisci LOW (stato di default).
return LOW;
}
// Funzione personalizzata per scrivere lo stato dei pin
void customDigitalWrite(int pin, int value) {
if (value == LOW) {
if (pin < 8) {
// Se il pin è nell'intervallo 0-7, impostalo a LOW utilizzando il registro PORTD.
PORTD &= ~(1 << pin); // Azzera il bit corrispondente per impostare il pin a LOW.
} else if (pin < 14) {
// Se il pin è nell'intervallo 8-13, impostalo a LOW utilizzando il registro PORTB.
PORTB &= ~(1 << (pin - 8)); // Azzera il bit corrispondente per impostare il pin a LOW.
}
} else if (value == HIGH) {
if (pin < 8) {
// Se il pin è nell'intervallo 0-7, impostalo a HIGH utilizzando il registro PORTD.
PORTD |= (1 << pin); // Imposta il bit corrispondente per impostare il pin a HIGH.
} else if (pin < 14) {
// Se il pin è nell'intervallo 8-13, impostalo a HIGH utilizzando il registro PORTB.
PORTB |= (1 << (pin - 8)); // Imposta il bit corrispondente per impostare il pin a HIGH.
}
}
}
// ISR per il gruppo PCINT1 (pin A0-A5)
/*
* Interrupt Service Routine (ISR) chiamata quando si verifica un cambiamento su uno dei pin del gruppo PCINT1 (pin A0-A5).
* Questa ISR rileva il momento in cui uno dei pulsanti collegati ai pin PC1, PC2, PC3 o PC4 viene premuto.
* Quando un pulsante viene premuto, viene registrato il timestamp corrente in una variabile globale associata a quel pulsante.
* Questa ISR è utilizzata per rilevare il momento in cui i pulsanti vengono premuti.
*/
ISR(PCINT1_vect) {
if (((PINC & (1 << PC1))) == 0) {
// Se il pin PC1 è a LOW (pulsante premuto), registra il timestamp corrente per il pulsante 1.
pressStartTime1 = millis();
}
if (((PINC & (1 << PC2))) == 0) {
// Se il pin PC2 è a LOW (pulsante premuto), registra il timestamp corrente per il pulsante 2.
pressStartTime2 = millis();
}
if (((PINC & (1 << PC3))) == 0) {
// Se il pin PC3 è a LOW (pulsante premuto), registra il timestamp corrente per il pulsante 3.
pressStartTime3 = millis();
}
if (((PINC & (1 << PC4))) == 0) {
// Se il pin PC4 è a LOW (pulsante premuto), registra il timestamp corrente per il pulsante 4.
pressStartTime4 = millis();
}
}
// Lettura del DHT22
float customReadDHT22(int DHTPIN) {
// Inizializzazione dei dati del sensore
int data[5];
// Imposto il pin del sensore DHT22 come OUTPUT, pin LOW per avviare la comunicazione
customPinMode(DHTPIN, OUTPUT);
customDigitalWrite(DHTPIN, LOW);
// Attendo per almeno 1 millisecondo
delayMicroseconds(1000);
// Pin HIGH per iniziare la comunicazione con il sensore DHT22
customDigitalWrite(DHTPIN, HIGH);
// Attendi per almeno 40 microsecondi come richiesto dalla specifica del DHT22
delayMicroseconds(40);
// Cambio il pin in modalità INPUT per ascoltare la risposta del sensore
customPinMode(DHTPIN, INPUT);
// Attendo che il sensore risponda abbassando il pin (LOW)
while (customDigitalRead(DHTPIN) == HIGH);
// Attendo che il sensore inizi a trasmettere dati alzando il pin (HIGH)
while (customDigitalRead(DHTPIN) == LOW);
// Attendo che il sensore finisca di trasmettere dati abbassando il pin (LOW)
while (customDigitalRead(DHTPIN) == HIGH);
// Disabilito gli interrupt globali per evitare interruzioni durante la lettura dei dati
cli();
// Leggo i dati dal sensore
for (int i = 0; i < 5; i++) {
int value = 0;
for (int j = 0; j < 8; j++) {
// Attendo che il sensore alzi il pin per iniziare la trasmissione di un bit
while (customDigitalRead(DHTPIN) == LOW);
// Attendo per 50 microsecondi per leggere il valore del bit
delayMicroseconds(50);
// Se il pin è HIGH, imposta il bit corrispondente in value
if (customDigitalRead(DHTPIN) == HIGH) {
value |= (1 << (7 - j));
}
// Attendo che il sensore abbassi nuovamente il pin
while (customDigitalRead(DHTPIN) == HIGH);
}
// Memorizzo il valore letto nei dati
data[i] = value;
}
// Calcolo la temperatura dai dati letti
float temperature = ((data[2] & 0x7F) << 8 | data[3]) * 0.1;
// Verifico il bit più significativo per la temperatura e imposto la temperatura come negativa se necessario
if (data[2] & 0x80) {
temperature = -temperature;
}
// Verifico il pin del sensore DHT22 (3 per interno, 2 per esterno) e aggiorno le variabili globali corrispondenti
if (DHTPIN == 3) {
temperature_internal = temperature;
} else {
temperature_external = temperature;
}
// Riabilito gli interrupt globali
sei();
}
// Funzione per stampare la temperatura di benessere
void displaySogliaTemperaturaBenessere(float soglia) {
LcdClear();
LcdSetCursor(0, 0);
LcdWrite("Temp. Benessere");
delay(1000);
LcdSetCursor(0, 1);
char temperatureBuffer[10];
dtostrf(soglia, 4, 1, temperatureBuffer);
char lcdText[20];
sprintf(lcdText, "%s C", temperatureBuffer);
LcdWrite(lcdText);
delay(1500);
LcdClear();
}
// Funzione per stampare una variabile generica sul display
void stampaVar(float var) {
LcdSetCursor(0, 1);
char temperatureBuffer[10];
dtostrf(var, 4, 1, temperatureBuffer);
char lcdText[20];
sprintf(lcdText, "%s C", temperatureBuffer);
LcdWrite(lcdText);
delay(1500);
LcdClear();
}
// Funzione per la visualizzazione delle informazioni
void infoStatus(int screenIndex) {
LcdClear(); // Pulisce il display
LcdSetCursor(0, 0);
switch (screenIndex) {
case 0:
LcdWrite("Temp. Interna:");
LcdSetCursor(0, 1);
stampaVar(temperature_internal); // Mostra la temperatura interna
break;
case 1:
LcdWrite("Temp. Esterna:");
LcdSetCursor(0, 1);
stampaVar(temperature_external); // Mostra la temperatura esterna
break;
case 2:
LcdWrite("Livello CO2:");
LcdSetCursor(0, 1);
stampaVar(CO2Value); // Mostra il livello di CO2
break;
case 3:
LcdWrite("Stato Ventilaz.:");
LcdSetCursor(0, 1);
// Verifico lo stato della ventilazione (acceso/spento) leggendo lo stato dei pin
if (((PIND & (1 << PD6)) || (PINB & (1 << PB0)))) LcdWrite("ON");
else LcdWrite("OFF");
delay(1500);
break;
}
}
// Funzione che simula lo spegnimento del sistema
void spegniSistema() {
// Spengo i sistemi di areazione
/* Blocca l'esecuzione in un ciclo infinito, il sistema è stato spento
è necessario il riavvio manuale della simulazione su Wokwi */
while (true) {
customDigitalWrite(FLUSSO_DIRETTO, LOW);
customDigitalWrite(CIRCUITO_RECUPERO_CALORE, LOW);
}
}
// Funzione per stampare nel display lo stato dell'aria attuale
void statoAria(const char* stato) {
LcdClear(); // Pulisce il display
LcdSetCursor(0, 0);
LcdWrite("Qualita' aria");
//delay(1000);
LcdSetCursor(0, 1);
LcdWrite(stato);
delay(1500);
LcdClear(); // Pulisce nuovamente il display
}
void setup() {
// Inizializzazione display
LcdInit(true);
// setup sistemi di ventilazione
customPinMode(FLUSSO_DIRETTO, OUTPUT);
customPinMode(CIRCUITO_RECUPERO_CALORE, OUTPUT);
// setup pulsanti
customPinMode(BUTTONPIN1, INPUT);
customPinMode(BUTTONPIN2, INPUT);
customPinMode(BUTTONPIN3, INPUT);
customPinMode(BUTTONPIN4, INPUT);
// Change (button)
EICRA |= (1 << ISC00);
// Abilita l'interrupt per il gruppo PCINT1 (pin 8-13)
PCICR |= (1 << PCIE1);
// Abilita il maschera interrupt per il gruppo PCINT1 (pin A0-A5)
PCMSK1 |= (1 << PC0) | (1 << PC1) | (1 << PC2) | (1 << PC3) | (1 << PC4);
//PCICR |= B00000010; // We activate the interrupts of the PC port
// Abilita gli interrupt globali
sei();
// Configura l'ADC
ADMUX = 0; // Usa il riferimento AREF, bit di sinistra a 0
ADMUX |= (1 << REFS0); // Riferimento esterno a 5V
ADCSRA |= (1 << ADEN); // Abilita l'ADC
}
void loop() {
// TEMP +1
// non so perchè la temperatura continua a salire finche non ci premo un altra volta
if (!(((PINC & (1 << PC1))) == 0)) {
unsigned long pressDuration1 = millis() - pressStartTime1;
pressDuration1 = (float)pressDuration1 / 1000.0; // Converte in secondi la pressione del pulsante
if (pressDuration1 >= 4.0) {
sogliaTemperaturaBenessere += 1.0; // Incrementa la soglia di 1.0 gradi
displaySogliaTemperaturaBenessere(sogliaTemperaturaBenessere); // Stampa nel display
pressStartTime1 = 0;
}
}
// TEMP -1
// non so perchè la temperatura continua a diminuire finche non ci premo un altra volta
if (!(((PINC & (1 << PC2))) == 0)) {
unsigned long pressDuration2 = millis() - pressStartTime2;
// Usa pressDuration2 per decidere quale azione intraprendere
pressDuration2 = (float)pressDuration2 / 1000.0; // Converte in secondi la pressione del pulsante
if (pressDuration2 >= 4.0) {
sogliaTemperaturaBenessere -= 1.0; // Decrementa la soglia di 1.0 gradi
displaySogliaTemperaturaBenessere(sogliaTemperaturaBenessere); // Stampa nel display
pressStartTime2 = 0;
}
}
// INFO SISTEMA eq. (buttonState3 == HIGH)
if (!(((PINC & (1 << PC3))) == 0)) {
unsigned long pressDuration3 = millis() - pressStartTime3;
pressDuration3 = (float)pressDuration3 / 1000.0; // Converte in secondi la pressione del pulsante
currentScreen = (currentScreen + 1) % NUM_SCREENS; // Aumenta l'indice e torna a 0 se necessario
infoStatus(currentScreen); // Mostra la nuova schermata
delay(500); // Piccolo ritardo per evitare letture multiple
LcdClear(); // Pulisce il display
}
// SPEGNIMENTO
if (!(((PINC & (1 << PC4))) == 0)) {
unsigned long pressDuration4 = millis() - pressStartTime4;
pressDuration4 = (float)pressDuration4 / 1000.0; // Converte in secondi la pressione del pulsante
if (pressDuration4 >= 10) {
// Spegni il sistema
spegniSistema();
}
}
// Lettura del potenziometro
int potenziometroValue = readPotentiometer(POTENZIOMETROPIN);
// Aumento il valore letto per avere livelli compresi nella scala di riferimento per il i CO2 Value
CO2Value = potenziometroValue * 4;
// Lettura delle temperature da DHT interno ed esterno
customReadDHT22(DHT_PIN_INTERNAL);
customReadDHT22(DHT_PIN_EXTERNAL);
/*
Meno di 800 ppm: Valore ottimale per ambienti interni ben ventilati con buona qualità dell'aria.
Fino a 1000 ppm: Accettabile per ambienti interni, ma la qualità dell'aria può iniziare a diminuire e alcuni individui possono percepire una sensazione di disagio.
1000 - 2000 ppm: Valore accettabile per brevi periodi, ma la qualità dell'aria può essere compromessa, causando possibili effetti sulla concentrazione e il comfort delle persone.
Oltre 2000 ppm: Livelli elevati che indicano una ventilazione insufficiente e una scarsa qualità dell'aria. Possono causare affaticamento, mal di testa e disagio per le persone.
*/
// Decisioni rispetto al livello di CO2 rilevato
if (CO2Value < 800) {
// Livello di CO2 accettabile, spengo entrambi i sistemi di ventilazione
customDigitalWrite(FLUSSO_DIRETTO, LOW);
customDigitalWrite(CIRCUITO_RECUPERO_CALORE, LOW);
// Sistemi di ventilazione OFF
// Verifico che il terzo pulsante (infoStatus) sia LOW per evitare di sovrapporre le stampe su display
if (((PINC & (1 << PC3))) == 0) {
statoAria("buona");
}
} else if (CO2Value > 800 && CO2Value < 2000) {
// Livello di CO2 accettabile, spengo entrambi i sistemi di ventilazione
customDigitalWrite(FLUSSO_DIRETTO, LOW);
customDigitalWrite(CIRCUITO_RECUPERO_CALORE, LOW);
// Sistemi di ventilazione OFF
// Verifico che il terzo pulsante (infoStatus) sia LOW per evitare di sovrapporre le stampe su display
if (((PINC & (1 << PC3))) == 0) {
statoAria("accettabile");
}
} else if (CO2Value > 2000) {
// Livello di CO2 elevato, deve essere attivata ventilazione
/* Differenza di temperatura tra temperatura esterna e interna al di sotto della quale
scelgo di attivare il flusso diretto o circuito a recupero di calore */
if (temperature_internal - temperature_external >= sogliaTemperaturaBenessere) {
// Attiva il relay per il flusso diretto
customDigitalWrite(FLUSSO_DIRETTO, HIGH);
customDigitalWrite(CIRCUITO_RECUPERO_CALORE, LOW);
// Flusso diretto ON
} else {
// Attiva il relay per il circuito di recupero del calore
customDigitalWrite(FLUSSO_DIRETTO, LOW);
customDigitalWrite(CIRCUITO_RECUPERO_CALORE, HIGH);
// Circuito a recupero di calore ON
// Verifico che il terzo pulsante (infoStatus) sia LOW per evitare di sovrapporre le stampe su display
} if (((PINC & (1 << PC3))) == 0) {
statoAria("cattiva");
}
}
}