// elenco librerie richieste per il progetto
#include <Adafruit_NeoPixel.h> //libreria led addressable
#include <Wire.h> //libreria i2c
#include <LiquidCrystal_I2C.h> //libreria lcd
// --- CONFIGURAZIONE PIN ---
#define PIN_LED 4 //uscita per comandare led addressable
#define NUMPIXELS 12 //numero led da comandare
#define SWITCH_PRESENZA 2 //Ingresso di lettura presenza macchina
#define SWITCH_COPERCHIO 3 //Ingresso di lettura chiusura coperchio
#define IN_VENTOLA A0 //Ingresso di lettura controllo ventola
#define RELE_LAMPADA 5 //Uscita per attivazione lampada simulazione giorno-notte
#define RELE_ALIMENTAZIONE 6 //Uscita per attivazione alimentazione
#define ON HIGH //Definisce la logica dei relè
#define OFF LOW //Definisce la logica dei relè
// #define PASS pixels.Color(0, 200, 0); //definisce colore led per test ok
// --- CONFIGURAZIONE LCD ---
// Imposta l'indirizzo a 0x27 o 0x3F a seconda del tuo modulo, secondo numero: colonne, terzo numero: righe
// LiquidCrystal_I2C lcd(0x27, 16, 2);
LiquidCrystal_I2C lcd(0x27, 20, 4);
// ==========================================
// CONFIGURAZIONE FLESSIBILE (Modifica qui)
// ==========================================
const int TOLLERANZA = 50; // Margine di errore sulle letture analogiche
// Definiamo i tempi di attesa per ogni fase (in millisecondi)
const unsigned long T_RISCALDAMENTO = 10000; // 70 secondi iniziali
const unsigned long T_ATTESA_1_2 = 10000; // 10 secondi tra fase 1 e 2
const unsigned long T_ATTESA_2_3 = 10000; // Esempio: 50 secondi tra fase 2 e 3
// Definiamo le soglie analogiche (in bit 0-1023)
const int TARGET_FASE1 = 500; //soglia per fase "notte", la prima eseguita
const int TARGET_FASE2 = 500; //soglia per fase giorno immissione
const int TARGET_FASE3 = 500; //soglia per fase giorno espulsione
// ==========================================
enum Stato { IDLE, RISCALDAMENTO, FASE1, ATTESA_1_2, FASE2, ATTESA_2_3, FASE3, OK_FINALE, KO_FINALE };
Stato statoAttuale = IDLE;
unsigned long timerInizio = 0;
Adafruit_NeoPixel pixels(NUMPIXELS, PIN_LED, NEO_GRB + NEO_KHZ800);
// --- TAVOLOZZA COLORI ---
uint32_t COLORE_RISCALDAMENTO = pixels.Color(0, 120, 150); // ciano
uint32_t COLORE_FASE1 = pixels.Color(150, 150, 0); // giallo
uint32_t COLORE_FASE2 = pixels.Color(150, 150, 0); // giallo
uint32_t PASS = pixels.Color(0, 200, 0); // Verde
uint32_t FAIL = pixels.Color(200, 0, 0); // Rosso
uint32_t SPENTO = pixels.Color(0, 0, 0); // Nero/Spento
void setup() {
// Inizializzazione LCD 20x4
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Sistema Pronto");
// Inizializzazione LED neopixel
pixels.begin();
pixels.show(); // Spegne tutti i LED all'avvio
pixels.setBrightness(200); // Valore da 0 a 255
// Inizializzazione ingressi digitali
pinMode(SWITCH_PRESENZA, INPUT_PULLUP);
pinMode(SWITCH_COPERCHIO, INPUT_PULLUP);
// inizializzazione relè per alimentazione e lampada
// Nota: Alcuni moduli relè si accendono con LOW. Se è il tuo caso, usa HIGH.
pinMode(RELE_ALIMENTAZIONE, OUTPUT);
digitalWrite(RELE_ALIMENTAZIONE, OFF); // Assicuriamoci che l'alimentazione sia spenta
pinMode(RELE_LAMPADA, OUTPUT);
digitalWrite(RELE_LAMPADA, OFF); // Partiamo con luce spenta
//inizializzazione seriale
Serial.begin(9600);
Serial.println("Sistema Pronto. In attesa di segnali...");
}
void loop() {
// Lettura ingressi (LOW = Attivo perché in Pullup)
bool ingressiAttivi = (digitalRead(SWITCH_PRESENZA) == LOW && digitalRead(SWITCH_COPERCHIO) == LOW);
// Gestione Reset
if (!ingressiAttivi) {
if (statoAttuale != IDLE) {
Serial.println("Reset: Segnale interrotto!");
resetSistema();
}
return;
}
switch (statoAttuale) {
case IDLE:
aggiornaLCD(" SISTEMA AVVIATO ",
"--------------------",
"STATO: Riscaldamento",
"Attendere prego...");
digitalWrite(RELE_ALIMENTAZIONE, ON); // ACCENDE alimentazione (se attivo alto)
timerInizio = millis();
statoAttuale = RISCALDAMENTO;
break;
case RISCALDAMENTO: //fase di accensione, dove si attende l'attivazione dell'attuatore ceroso prima di attivare la ventola
gestisciAttesa(T_RISCALDAMENTO, FASE1, COLORE_RISCALDAMENTO);
break;
case FASE1: //fase notturna, la prima eseguita perché si parte con coperchio chiuso e luce spenta
eseguiTest(TARGET_FASE1, ATTESA_1_2, "Fase 1");
break;
case ATTESA_1_2: //attesa tra fase notte e fase giorno immissione
gestisciAttesa(T_ATTESA_1_2, FASE2, COLORE_FASE1);
digitalWrite(RELE_LAMPADA, ON); // Attiva l'accensione della lampada per simulare il giorno
break;
case FASE2: //fase giorno immissione
eseguiTest(TARGET_FASE2, ATTESA_2_3, "Fase 2");
break;
case ATTESA_2_3: //attesa tra fase immisssione e fase espulsione
gestisciAttesa(T_ATTESA_2_3, FASE3, COLORE_FASE2);
break;
case FASE3: //fase giorno espulsione
eseguiTest(TARGET_FASE3, OK_FINALE, "Fase 3");
break;
case OK_FINALE: //se fasi passate con successo, scrive su display, spegne luce, alimentazione e accende led verde
aggiornaLCD(" ESITO FINALE: ",
"********************",
" TEST SUPERATO ",
"********************");
digitalWrite(RELE_ALIMENTAZIONE, OFF); // Spegne relè di alimentazione
digitalWrite(RELE_LAMPADA, OFF); // Spegne relè della lampada
coloraTutto(PASS);
break;
case KO_FINALE: //se fasi non superate, scrive su display, spegne luce, alimentazione e accende led verde
aggiornaLCD(" ESITO FINALE: ",
"--------------------",
" TEST FALLITO ",
" RIPROVARE ");
digitalWrite(RELE_ALIMENTAZIONE, OFF); // Spegne relè di alimentazione
digitalWrite(RELE_LAMPADA, OFF); // Spegne relè della lampada
coloraTutto(FAIL);
break;
}
}
// --- FUNZIONI DI SUPPORTO ---
void resetSistema() {
digitalWrite(RELE_ALIMENTAZIONE, OFF); // Spegne relè di alimentazione
digitalWrite(RELE_LAMPADA, OFF); // Spegne relè della lampada
statoAttuale = IDLE;
pixels.clear();
pixels.show();
aggiornaLCD(" PRONTO AL TEST ",
"Inserire macchina e ",
"chiudere coperchio ",
"per iniziare...");
}
void gestisciAttesa(unsigned long durata, Stato prossimoStato, uint32_t colore) {
unsigned long trascorso = millis() - timerInizio;
// Visualizziamo il progresso sull'LCD
String tempoRimanente = "Attesa: " + String((durata - trascorso) / 1000) + " secondi";
aggiornaLCD(" IN ELABORAZIONE ",
"--------------------",
tempoRimanente,
"Operazione in corso ");
// --- ANIMAZIONE LAMPEGGIANTE ---
static unsigned long ultimoUpdateLED = 0;
static bool acceso = false; // Questa variabile fa da "interruttore"
if (millis() - ultimoUpdateLED > 500) { // 500ms acceso, 500ms spento
ultimoUpdateLED = millis();
acceso = !acceso; // Inverte lo stato: se era vero diventa falso, se era falso diventa vero
if (acceso) {
coloraTutto(colore); // Accende tutti i LED con il colore della fase
} else {
pixels.clear(); // Spegne tutti i LED
pixels.show();
}
}
// -------------------------------
// // Animazione LED rotante
// static unsigned long ultimoUpdateLED = 0;
// if (millis() - ultimoUpdateLED > 150) {
// ultimoUpdateLED = millis();
// pixels.clear();
// int led = (millis() / 150) % NUMPIXELS;
// pixels.setPixelColor(led, colore);
// pixels.show();
// }
if (trascorso >= durata) {
statoAttuale = prossimoStato;
// Se passiamo a un test, resettiamo il timer non serve ma lo facciamo per pulizia
timerInizio = millis();
}
}
void eseguiTest(int target, Stato statoSeOk, String nomeFase) {
// Facciamo una media di 10 letture per maggiore precisione
long somma = 0;
int lettureValide = 0; // Contatore per la media corretta
int campioni = 50; // numero di campioni da considerare
int scarto = 10; // numero di campioni da scartare
for(int i=0; i<campioni; i++) {
int valoreIstantaneo = analogRead(IN_VENTOLA);
// Scartiamo le prime 10 (0-9) e le ultime 10 (90-99)
if (i >= scarto && i < campioni-scarto) {
somma += valoreIstantaneo;
lettureValide++; // Sarà 80 alla fine del ciclo
}
delay(10);
// Serial.print("Letture:");Serial.print(i);Serial.print("Campioni:");Serial.println(lettureValide);
}
int letturaMedia = somma / lettureValide;
// Mostriamo i dettagli tecnici sulle 4 righe
aggiornaLCD(nomeFase,
"Valore: " + String(letturaMedia),
"Target: " + String(target),
"Range: +/- " + String(TOLLERANZA));
delay(1000); // Piccola pausa per far leggere i dati all'utente
Serial.print(nomeFase + " Lettura: "); Serial.println(letturaMedia);
if (letturaMedia >= (target - TOLLERANZA) && letturaMedia <= (target + TOLLERANZA)) {
aggiornaLCD(nomeFase,
"RISULTATO: OK",
"",
"");
if (statoAttuale == FASE1) {
digitalWrite(RELE_LAMPADA, ON); // ACCENDE Lampada
}
timerInizio = millis();
statoAttuale = statoSeOk;
} else {
statoAttuale = KO_FINALE;
}
}
void aggiornaLCD(String riga1, String riga2, String riga3, String riga4) {
static String ultimaR1, ultimaR2, ultimaR3, ultimaR4;
if (riga1 != ultimaR1 || riga2 != ultimaR2 || riga3 != ultimaR3 || riga4 != ultimaR4) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(riga1);
lcd.setCursor(0, 1);
lcd.print(riga2);
lcd.setCursor(0, 2);
lcd.print(riga3);
lcd.setCursor(0, 3);
lcd.print(riga4);
ultimaR1 = riga1;
ultimaR2 = riga2;
ultimaR3 = riga3;
ultimaR4 = riga4;
}
}
void coloraTutto(uint32_t c) {
for(int i=0; i<NUMPIXELS; i++) pixels.setPixelColor(i, c);
pixels.show();
}Presenza macchina tasto blu
Coperchio chiuso tasto verde
Relè alimentazione
Relè lampada
Lampeggiante:
VERDE = OK
ROSSO = KO
GIALLO = TEST