Guide · 26 min di lettura

Serra intelligente con Arduino: sensori, irrigazione e codice

Serra intelligente con Arduino: DHT22, umidità del terreno e LDR; ventola, riscaldatore, pompa e LED con relè e MOSFET, isteresi, LCD e codice ESP32.

Una serra intelligente con Arduino è un sistema di automazione in cui un Arduino o un ESP32 misura temperatura, umidità dell'aria, umidità del terreno e luce nella serra e aziona da solo la ventola, il riscaldatore, la pompa dell'acqua e le luci di coltivazione. È uno dei temi preferiti per le fiere della scienza, i concorsi scolastici e le lezioni di tecnologia, perché il risultato si vede: quando la terra si asciuga parte la pompa, quando dentro fa caldo gira la ventola, la sera si accendono i LED. In più, tra un modellino ricavato da un contenitore portaoggetti e una vera mini serra sul balcone non c'è quasi nessuna differenza di circuito o di codice.

In questa guida partiamo da ciò di cui le piante hanno davvero bisogno e poi costruiamo il modellino passo dopo passo: la lista dei componenti e il costo indicativo, il collegamento dei sensori (DHT22, sensore capacitivo di umidità del terreno, LDR) e degli attuatori (ventola, riscaldatore, pompa e LED tramite relè e un MOSFET), il piano di alimentazione e la tabella dei pin. Poi spieghiamo la logica di soglia e isteresi che impedisce ai relè di scattare in continuazione, il codice Arduino completo senza delay() e il display LCD 16x2. Nelle ultime sezioni trovi come controllare la serra dallo smartphone con un ESP32, una versione MicroPython per il Raspberry Pi Pico, consigli per presentazioni scolastiche e fiere della scienza e gli errori più comuni.

DHT22 (AM2302)
DHT22 (AM2302)
LDR
LDR
I2C LCD 16x2
I2C LCD 16x2
LM2596
LM2596
Arduino Uno
Arduino Uno

Come funziona una serra intelligente?

Ogni serra intelligente segue lo stesso ciclo in quattro passi: misura, decidi, agisci, mostra. I sensori misurano l'ambiente: quanti gradi ci sono dentro, quanto è umida l'aria, la terra si è asciugata, c'è abbastanza luce? Arduino confronta questi valori con le regole che hai stabilito: «se la temperatura supera i 30 °C, accendi la ventola», «se l'umidità del terreno scende sotto il 35%, irriga». Gli attuatori eseguono la decisione: i relè accendono e spengono ventola, riscaldatore e pompa, mentre un MOSFET regola la luminosità della striscia LED. Infine il display LCD e lo smartphone ti mostrano lo stato attuale della serra. I computer di controllo climatico delle serre professionali funzionano esattamente con questo ciclo; usano solo più sensori e motori più grandi.

Cosa si misura?SensoreAttuatoreRegola (esempio)
Temperatura dell'ariaDHT22Ventola e riscaldatoreLa ventola parte a 30 °C, il riscaldatore si accende a 16 °C
Umidità relativaDHT22VentolaLa ventola parte all'85% e si ferma al 75%
Umidità del terrenoSensore capacitivo di umidità del terrenoMini pompa a immersioneSotto il 35% irriga a impulsi di 3 secondi
LuceLDRStriscia LED (MOSFET)Quando fa buio, i LED restano accesi al massimo 4 ore
Acqua nel serbatoioInterruttore a galleggianteBlocco della pompaSe il serbatoio è vuoto, la pompa non parte mai

Di cosa hanno bisogno le piante? Temperatura, umidità e luce ideali

Prima di scrivere le regole devi sapere a cosa punti. La maggior parte delle piante vuole temperature diverse di giorno e di notte; un leggero raffreddamento notturno aiuta la pianta a riposare. Se l'umidità dell'aria resta a lungo sopra l'85%, l'acqua condensa sulle foglie e le malattie fungine come muffe e peronospora si diffondono in fretta; per questo in serra la ventola non serve solo a rinfrescare, ma anche a eliminare l'umidità. Per la luce, la maggior parte delle piantine di ortaggi vuole 14–16 ore al giorno, ma ha bisogno anche di un periodo di buio di almeno 6–8 ore; lasciare i LED accesi tutta la notte fa più male che bene. I valori qui sotto sono una buona guida per iniziare; adattali a ciò che coltivi.

PiantaGiornoNotteUmidità relativaNota
Pomodoro21–27 °C15–18 °C60–75%Sopra i 32 °C i fiori possono cadere
Peperone21–29 °C16–20 °C60–75%Più sensibile al freddo del pomodoro
Lattuga e spinaci15–20 °C10–15 °C60–70%Con caldo e giornate lunghe va presto a seme
Fragola18–24 °C10–13 °C60–75%Con umidità alta aumenta il rischio di muffa grigia
Piantine e germinazione20–25 °C18–20 °C70–85%La temperatura del terriccio conta più di quella dell'aria
Se il giorno della dimostrazione in aula ci sono 22 °C, una ventola impostata a 30 °C non girerà mai. Per la presentazione porta le soglie 2–3 gradi sopra la temperatura ambiente, così quando scaldi il DHT22 nel palmo della mano la giuria vede partire la ventola in pochi secondi. Per l'uso reale torna ai valori della tabella.

Costruire il modellino della serra

Il contenitore più semplice ed economico è una scatola portaoggetti di plastica trasparente con coperchio: una da 40–60 litri basta per due o tre vasi, è impermeabile e si fora facilmente con un taglierino o con una punta riscaldata. Se vuoi un aspetto più realistico, costruisci un telaio con listelli di legno e rivestilo con lastre trasparenti di plexiglas o PETG da 2–3 mm; una serra con il tetto a due falde fa molto più effetto in una presentazione. Circa 50×35×40 cm è una buona misura per iniziare. Qualunque contenitore scegli, il coperchio o una parete laterale deve potersi aprire: dovrai metterci le mani per innaffiare, spostare il sensore e sistemare i cavi.

  • Elettronica fuori: Dentro la serra c'è molta umidità e al mattino l'acqua condensa sulle pareti. Metti Arduino, modulo relè e alimentatore in una scatola separata fuori dalla serra; dentro restano solo sensori, ventola, riscaldatore, tubo della pompa e LED.
  • Ventilazione: L'aria calda sale, quindi monta la ventola nella parte alta di una delle pareti corte, in modo che spinga l'aria fuori. Nella parte bassa della parete opposta apri una presa d'aria grande quanto la ventola e coprila con una zanzariera; così l'aria fresca entra dal basso e quella calda e umida esce dall'alto.
  • Riscaldatore: Monta il riscaldatore vicino al fondo, su una lastra di alluminio o una piastrella di ceramica, ad almeno 5 cm dalle pareti di plastica e dalle piante. Salendo, l'aria calda circola in tutta la serra.
  • Illuminazione LED: Incolla la striscia LED sotto il coperchio, 15–30 cm sopra le piante. Se la incolli su un sottile profilo di alluminio, dissipa meglio il calore e l'adesivo non si stacca col tempo.
  • Sensori: Metti il DHT22 all'altezza delle piante, dove non lo colpiscano il sole diretto né la luce diretta dei LED. Interra il sensore di umidità nella zona delle radici, lontano dal bordo del vaso. La LDR non deve vedere i LED: sistemala fuori dalla serra, sul tetto, rivolta verso il cielo.
  • Serbatoio dell'acqua: Un contenitore da 1–2 litri basta per la pompa e il galleggiante. Metti dei sottovasi sotto i vasi; l'acqua in eccesso si raccoglie lì invece di allagare il fondo della scatola.
Se l'estremità del tubo si trova più in basso del livello dell'acqua nel serbatoio, l'acqua continua a scorrere anche dopo lo stop della pompa per effetto sifone, e il vaso trabocca. Tieni il serbatoio più in basso dei vasi oppure pratica con un ago un piccolo foro d'aria nel punto più alto del tubo: quando la pompa si ferma, l'aria entra dal foro e il flusso si interrompe subito.

Lista dei componenti e costo

ComponenteQ.tàFunzioneFascia di prezzo
Arduino Uno R3 o ESP32 DevKit1Il cervello della serraMedia
Modulo temperatura e umidità DHT22 (AM2302)1Temperatura e umidità dell'ariaBassa
Sensore capacitivo di umidità del terreno (v1.2)1 (uno per vaso)Umidità del terrenoBassa
LDR (GL5528) e resistenza da 10 kΩ1 + 1Luce ambienteMolto bassa
Interruttore a galleggiante per il livello dell'acqua1Blocca la pompa a serbatoio vuotoBassa
Modulo 4 relè 5 V (con fotoaccoppiatore e JD-VCC)1Ventola, riscaldatore e pompaBassa
MOSFET a canale N logic level (IRLZ44N), resistenze da 220 Ω e 100 kΩ1 setLuminosità della striscia LED (PWM)Molto bassa
Mini pompa a immersione 3–6 V e 1 m di tubo in silicone1IrrigazioneBassa
Ventola da PC 12 V 80 mm1–2Ventilazione ed eliminazione dell'umiditàBassa
Riscaldatore PTC 12 V (10–30 W) e fusibile termico1 + 1RiscaldamentoBassa
Striscia LED 12 V (per coltivazione o bianco freddo), circa 1 m1Illuminazione supplementareBassa
Display LCD I2C 16x21Display di statoBassa – media
Alimentatore 12 V 5 A e convertitore step-down LM25961 + 1AlimentazioneMedia
Diodo 1N5819 o 1N4007, condensatore da 470 µF, morsetti, cavetti jumper–Protezione e collegamentiMolto bassa
Scatola trasparente con coperchio o lastre di plexiglas, vasi, sottovasi–Struttura della serraMedia

I prezzi cambiano molto da un venditore all'altro, quindi meglio un budget indicativo che cifre esatte. Esclusa la scheda, la versione base (DHT22, sensore di umidità del terreno, LDR, relè a 2 canali, pompa e ventola da 5 V) costa di solito circa 25–40 €. La versione completa, con riscaldatore, striscia LED, LCD, alimentatore 12 V, LM2596, galleggiante e contenitore, si aggira sui 70–120 €; la maggior parte del budget va in scheda, alimentatore, display e contenitore. Se hai uno starter kit Arduino, scheda, breadboard, cavetti, LDR, resistenze e spesso anche l'LCD ce li hai già. Con un budget ridotto, rimanda riscaldatore e LED a una seconda fase: irrigazione e ventilazione da sole sono già un progetto utile e funzionante.

Sensori: DHT22, sensore di umidità del terreno e LDR

Per la serra ti consigliamo il DHT22 (AM2302). Costa un po' più del DHT11, ma misura la temperatura da −40 a 80 °C con ±0,5 °C e l'umidità su tutto l'intervallo 0–100% con circa ±2%; il DHT11 è preciso solo a ±2 °C tra 0 e 50 °C e non vede l'umidità sopra il 90%. Dato che una serra può superare il 90% di umidità al mattino e scendere sotto zero d'inverno, il DHT22 è molto più adatto. Il sensore fornisce al massimo una lettura ogni 2 secondi, per questo il codice lo legge ogni 2,5 secondi. Se il sole o i LED lo colpiscono direttamente, il suo involucro si scalda e segna qualche grado più dell'aria; un cappuccio bianco forato che gli faccia ombra evita il problema. Collegamenti e libreria sono spiegati nell'articolo sensore di temperatura e umidità DHT11 e DHT22.

Modulo sensore di umidità del terreno
Sensore di umidità del terreno: la sonda interrata nella zona delle radici indica con una tensione analogica quanto è bagnata la terra; per una lunga durata in serra scegli il modello capacitivo.

Per l'umidità del terreno scegli un sensore capacitivo. I sensori resistivi economici (YL-69, FC-28) hanno due punte metalliche a contatto diretto con la terra e, se alimentati di continuo, si corrodono in poche settimane per elettrolisi; non vanno bene per un sistema che deve funzionare per mesi. Nel sensore capacitivo l'elettrodo è rivestito e nessun metallo tocca la terra. L'uscita è analogica e scende man mano che la terra si bagna: su un Uno, un sensore tenuto in aria legge circa 500–600, immerso in acqua circa 250–300. Questi numeri cambiano da sensore a sensore; prima di caricare il programma principale, leggi quindi nel monitor seriale i valori del tuo sensore in aria e in acqua e scrivili nelle costanti SOIL_DRY_RAW e SOIL_WET_RAW. Il codice converte questi due valori in 0–100% con map(). Trovi altri dettagli nell'articolo sensore di umidità del terreno.

Per la luce basta la LDR (GL5528) del tuo kit. Collegala come partitore di tensione con una resistenza da 10 kΩ: 5V → LDR → A1 → 10 kΩ → GND. Quando la luce aumenta, la resistenza della LDR scende e il valore su A1 sale; anche questo il codice lo converte in 0–100% partendo da due numeri che misuri al buio e in pieno sole. Una LDR non è un luxmetro preciso, ma risponde in modo affidabile alla domanda «è buio, è nuvoloso, c'è il sole?». Se vuoi misurare la luce con più precisione, puoi passare a un sensore di luce I2C come il BH1750. Usa lo strumento Partitore di tensione per il calcolo e l'articolo sensore di luce LDR per i dettagli.

Se la LDR vede la luce dei LED, il sistema entra in un ciclo in cui si accende e si spegne da solo: fa buio, i LED si accendono, la LDR crede che sia giorno e li spegne, poi vede di nuovo il buio. Per evitarlo, sistema la LDR fuori dalla serra, lontano dai LED, e nel codice usa l'isteresi invece di una soglia unica. Anche l'elettronica nella parte alta del sensore capacitivo non è impermeabile: non interrarlo oltre la linea segnata e sigilla i bordi della scheda con colla a caldo o smalto per unghie trasparente.

Attuatori: relè o MOSFET?

Un pin di Arduino può fornire solo circa 20 mA, mentre ventola, riscaldatore, pompa e striscia LED assorbono centinaia di milliampere o addirittura diversi ampere. Per questo ogni carico si comanda tramite un relè o un MOSFET. Il relè è un interruttore meccanico: accende e spegne un carico in un circuito del tutto separato, in alternata o in continua, ma sa solo accendere o spegnere, fa clic a ogni commutazione e i suoi contatti hanno una vita limitata. Il MOSFET è un interruttore elettronico silenzioso; funziona solo con carichi in continua, ma può commutare centinaia di volte al secondo e quindi regolare luminosità e velocità con il PWM. In questo progetto usiamo un modulo a 4 relè per riscaldatore, ventola e pompa e un MOSFET per la luminosità della striscia LED.

CaricoValore tipicoComandoPerché?
Riscaldatore PTC 12 V10–30 W, circa 1–2,5 ARelè (IN2)Corrente alta, basta acceso/spento
Ventola 12 V 80 mm0,1–0,2 ARelè (IN1) o MOSFETIl relè è semplice; con un MOSFET si può anche regolare la velocità in PWM
Mini pompa a immersione 3–6 V0,1–0,25 A, di più all'avvioRelè (IN3) o MOSFET con diodoLavora a impulsi brevi; i disturbi del motore vanno isolati
Striscia LED 12 V (5050, 60 LED/m)Circa 1,2 A al metroMOSFET logic levelRegolazione della luminosità in PWM e funzionamento silenzioso
Mini pompa dell'acqua a immersione
Mini pompa a immersione 3–6 V: va immersa nel serbatoio, assorbe circa 100–200 mA e spinge l'acqua nel vaso attraverso un tubo; non farla mai girare a secco.

A seconda dell'altezza del tubo, una mini pompa a immersione eroga circa 1–1,5 litri al minuto, quindi un impulso di 3 secondi porta nel vaso 50–75 mL d'acqua. Per questo non facciamo girare la pompa di continuo «finché la terra non è bagnata», ma a impulsi brevi: irriga 3 secondi, aspetta 60 secondi che l'acqua si distribuisca nella terra, poi misura di nuovo. Se il sensore venisse riletto prima che l'acqua lo raggiunga, la terra sembrerebbe ancora asciutta e la pompa andrebbe avanti finché il vaso non trabocca. Una pompa che gira a secco si scalda e si guasta in pochi minuti; il galleggiante nel serbatoio la blocca quando l'acqua finisce. Ogni volta che il motore si ferma, la sua bobina genera un picco di tensione inversa; un diodo collegato in inversa ai capi della pompa (diodo di ricircolo 1N5819 o 1N4007, con la fascetta verso il +) protegge da questi disturbi sia il contatto del relè sia Arduino. Scelta della pompa e modi per comandarla sono spiegati nell'articolo mini pompa dell'acqua.

Per riscaldare usa un riscaldatore PTC da 12 V. Negli elementi ceramici PTC (coefficiente di temperatura positivo) la resistenza aumenta con il calore, per cui la loro temperatura superficiale si limita da sola a un valore fisso; è molto più sicuro di un filo resistivo nudo che diventa incandescente. In un contenitore di plastica scegli un modello a bassa temperatura, con la superficie limitata a circa 80 °C. Un riscaldatore da 24 W assorbe 2 A a 12 V secondo P = V × I; nei primi secondi, da freddo, la corrente è un po' più alta. Per l'illuminazione puoi usare una striscia LED per coltivazione rosso-blu o una striscia LED bianco freddo; per la fotosintesi le piante usano soprattutto la luce rossa (circa 660 nm) e blu (circa 450 nm). Comanda la striscia con un MOSFET logic level come l'IRLZ44N: il gate va a D9 tramite una resistenza da 220 Ω e a GND tramite una da 100 kΩ; il + della striscia va ai 12 V, il − al drain del MOSFET e il source a GND. I moduli MOSFET standard come l'IRF520 non conducono del tutto con 5 V sul gate e si scaldano con carichi di diversi ampere; i dettagli sono nell'articolo usare un MOSFET con Arduino. Per calcolare corrente e potenza puoi usare lo strumento Legge di Ohm.

Il riscaldatore è l'unico vero rischio d'incendio della serra. Montalo su una base metallica dove non possa toccare le pareti di plastica, aggiungi sulla linea 12 V un fusibile termico in serie al riscaldatore e a contatto con la lastra su cui è montato, e prova il sistema per ore prima di lasciarlo senza sorveglianza. Il fusibile deve aprirsi poco sopra la temperatura limite del riscaldatore (per esempio 92 °C per un PTC da 80 °C); un valore più basso salterebbe senza motivo durante il normale funzionamento. I tappetini riscaldanti per semenzai e i riscaldatori più grandi funzionano di solito con la tensione di rete, e la tensione di rete è letale. Non usare la tensione di rete in una serra modellino: basta un riscaldatore PTC da 12 V. Se ti serve davvero un apparecchio collegato alla rete, il collegamento deve farlo un elettricista o un adulto competente, e il modulo relè deve stare dentro una scatola chiusa e isolata. Le regole per un collegamento sicuro sono nell'articolo Cos'è un modulo relè?.

Piano di alimentazione: linee a 12 V e 5 V

I problemi più frequenti nelle serre intelligenti («Arduino si riavvia quando parte la pompa», «sull'LCD compaiono caratteri strani») dipendono dall'alimentazione. La soluzione più pulita è ricavare due linee da un unico alimentatore da 12 V 5 A. La linea a 12 V alimenta direttamente riscaldatore, ventole e striscia LED e va anche al jack DC dell'Arduino Uno. Gli stessi 12 V entrano in un convertitore step-down LM2596 regolato a 5 V; bobine dei relè, pompa e LCD si alimentano da questa linea a 5 V. Se togli il ponticello JD-VCC dal modulo relè, la corrente delle bobine arriva tutta dall'LM2596 e il lato Arduino accende solo i LED dei fotoaccoppiatori. I sensori (DHT22, sensore di umidità del terreno e partitore della LDR) alimentali invece dal pin 5V dello stesso Arduino: analogRead() misura rispetto ai 5 V della scheda, quindi alimentare i sensori analogici con la stessa tensione mantiene stabili le letture. Collega sempre tra loro i GND dell'alimentatore, dell'LM2596 e di Arduino.

CaricoLineaCorrente indicativa
Riscaldatore PTC (24 W)12 V2 A (di più nel primo istante, da freddo)
Striscia LED (1 m)12 V1–1,2 A
Due ventole da 80 mm12 V0,2–0,4 A
Arduino Uno e sensori (dal jack DC)12 VCirca 0,06–0,08 A
Ingresso dell'LM2596 (con 1 A sul lato 5 V)12 VCirca 0,5 A
Modulo relè (tre relè eccitati)5 V0,2–0,25 A
Mini pompa a immersione5 V0,1–0,25 A (di più all'avvio)
LCD I2C (retroilluminazione accesa)5 V20–30 mA

Anche se tutto funziona insieme, la corrente totale si aggira sui 4–4,5 A, quindi un alimentatore da 12 V 5 A (60 W) lascia abbastanza margine. In pratica riscaldatore e ventola della temperatura non funzionano mai insieme, perché tra le loro soglie c'è un ampio intervallo. Prima di collegare l'LM2596 al circuito, misura la sua uscita con il multimetro e regolala a 5,0 V con il trimmer; appena tolto dalla confezione può lasciar passare quasi tutti i 12 V. Puoi alimentare Arduino via USB mentre carichi il codice e dal jack DC una volta installato; averli collegati entrambi insieme non è un problema per l'Uno. Altri dettagli nella guida alimentazione di Arduino.

Collegamenti e tabella dei pin

La tabella qui sotto mostra i pin usati dai tre programmi di questa guida. Sull'Uno abbiamo collegato il MOSFET che comanda la striscia LED a D9, che supporta il PWM; sull'Uno il PWM c'è solo su D3, D5, D6, D9, D10 e D11. Galleggiante e relè non hanno bisogno del PWM. Dato che l'LCD usa solo il bus I2C (A4 e A5), occupa pochissimi pin; sull'Uno restano liberi A2, A3 e alcuni pin digitali per un secondo sensore del terreno o un pulsante. Sull'ESP32 abbiamo collegato i sensori analogici ai pin ADC1 (GPIO 34 e 35), che funzionano anche con il Wi-Fi acceso. Puoi vedere la piedinatura della tua scheda nello strumento Pinout.

CollegamentoArduino UnoESP32Raspberry Pi Pico
DHT22 DATAD2GPIO 4GP15
Galleggiante (l'altro capo a GND)D3GPIO 13GP14
Relè IN3: pompaD4GPIO 25GP18
Relè IN1: ventolaD7GPIO 26GP16
Relè IN2: riscaldatoreD8GPIO 27GP17
Gate del MOSFET (tramite 220 Ω): LEDD9GPIO 18GP19
AOUT del sensore di umidità del terrenoA0GPIO 34GP26
Punto centrale del partitore della LDRA1GPIO 35 (partitore da 3V3)GP27 (partitore da 3V3)
LCD I2C SDA / SCLA4 / A5GPIO 21 / 22GP4 / GP5
Alimentazione dei sensori5V3V33V3
I pin dell'ESP32 e del Pico lavorano a 3,3 V e non tollerano i 5 V. Su queste schede alimenta DHT22, sensore di umidità del terreno e partitore della LDR dal pin 3V3, così al pin analogico non arrivano mai più di 3,3 V. Un modulo relè da 5 V attivo basso (active LOW) a volte non si diseccita del tutto con un segnale HIGH da 3,3 V; se un relè resta eccitato, su un modulo con JD-VCC alimenta VCC a 3,3 V e JD-VCC a 5 V, oppure compra un modulo comandabile a 3,3 V. Per la striscia LED scegli un MOSFET che conduca completamente già con 3,3 V sul gate (per esempio l'IRLB8721).

Soglia e isteresi: perché i relè sbattono?

Immagina di comandare la ventola con una soglia unica: «accendila se la temperatura supera i 30 °C, altrimenti spegnila». Quando la temperatura oscilla tra 29,9 e 30,1 °C (cosa molto frequente per il rumore del sensore), il relè si accende e si spegne a ogni lettura. Si consumano i contatti del relè e il motore della ventola, e con il riscaldatore cavi e alimentatore vengono sollecitati inutilmente a ogni accensione. La soluzione è l'isteresi: usi due soglie diverse per accendere e per spegnere. La ventola parte a 30 °C ma si ferma solo quando la temperatura è scesa a 27 °C. In questa banda morta di 3 gradi il sistema mantiene il suo ultimo stato. I termostati ambiente delle caldaie e i frigoriferi funzionano con la stessa logica.

ControlloSi accendeSi spegneBanda morta
Ventola (temperatura)≥ 30 °C≤ 27 °C3 °C
Ventola (umidità)≥ 85%≤ 75%10%
Riscaldatore≤ 16 °C≥ 19 °C3 °C
IrrigazioneTerreno ≤ 35%Terreno ≥ 50%15%
Illuminazione LEDLuce ≤ 25%Luce ≥ 35% o dopo 4 ore10%

L'ampiezza della banda morta è un compromesso: se è troppo stretta il relè commuta ancora spesso, se è troppo larga la temperatura si allontana troppo dall'obiettivo. Circa 2–3 °C per la temperatura, 8–10% per l'umidità e 10–15% per l'umidità del terreno sono un buon punto di partenza. Abbiamo anche lasciato un ampio intervallo tra i 19 °C a cui si spegne il riscaldatore e i 30 °C a cui parte la ventola, così riscaldatore e ventola della temperatura non lavorano mai l'uno contro l'altra. Nel codice l'isteresi è raccolta in una piccola funzione hysteresis(), per non doverla riscrivere a ogni controllo: se la soglia di accensione è maggiore di quella di spegnimento significa «accendi quando sale» (ventola); se è minore, «accendi quando scende» (riscaldatore, irrigazione, LED). Se vuoi anche smussare i picchi nei dati del sensore, dai un'occhiata all'articolo filtrare i dati dei sensori.

Struttura del codice: misura, decidi, agisci, mostra

La regola più importante nel codice della serra è che nessun compito aspetti un altro. Se dopo la pompa aspettassi un minuto intero con delay(60000), in quel tempo non girerebbe la ventola e il display non si aggiornerebbe. Per questo il codice è fatto di piccole funzioni che misurano il tempo con millis(), ognuna responsabile di un solo compito; loop() le chiama a turno migliaia di volte al secondo e ogni funzione fa qualcosa solo quando è il suo momento. L'irrigazione è una piccola macchina a stati con tre stati: attendi (IDLE), irriga (PUMPING) e lascia assorbire l'acqua (SOAKING). Tutte le impostazioni sono costanti all'inizio del codice, quindi per cambiare una soglia non devi cercare in tutto il programma. La logica di questo approccio è spiegata negli articoli millis() e delay() e macchina a stati con Arduino. Il codice include anche queste misure di sicurezza:

  • Avvio sicuro: Il valore di «spento» viene scritto sui pin dei relè prima di chiamare pinMode(); così all'accensione di Arduino i relè non scattano nemmeno per un istante.
  • Guasto del sensore: Se il DHT22 non si riesce a leggere per tre volte di fila, il codice passa a uno stato sicuro: spegne il riscaldatore, accende la ventola e mostra «Sensor hatasi» (errore sensore) sull'LCD. Un sistema che non conosce la temperatura non deve mai lasciare acceso il riscaldatore.
  • Protezione del serbatoio: Quando il galleggiante segnala il serbatoio vuoto, la pompa non parte affatto e, se stava girando, si ferma subito.
  • Protezione contro il traboccamento: Se in un ciclo di irrigazione l'umidità del terreno non arriva al 50% dopo sei impulsi, forse il sensore è uscito dalla terra o il tubo si è staccato; il codice ferma l'irrigazione e mostra «Sulama hatasi» (guasto irrigazione). L'avviso sparisce da solo quando la terra torna bagnata.
  • Registrazione dei dati: Ogni 10 secondi il codice scrive le letture nel monitor seriale come riga separata da virgole (CSV); puoi copiare queste righe e creare grafici in Excel.

Montaggio e collaudo passo dopo passo

  1. Prepara il contenitore: apri i fori per ventola, presa d'aria, tubo e cavi, e sistema la scatola dell'elettronica fuori dalla serra.
  2. Prepara le linee di alimentazione: collega l'alimentatore 12 V a un morsetto, regola l'LM2596 a 5,0 V con il multimetro e unisci tutti i GND.
  3. Collega l'LCD I2C e scrivi un testo sul display. Se non mostra nulla, prova lo sketch scanner e il trimmer del contrasto dell'articolo LCD I2C.
  4. Aggiungi i sensori uno alla volta e provali nel monitor seriale: annota i valori del DHT22, il valore grezzo del sensore del terreno in aria e in acqua e quello della LDR al buio e al sole.
  5. Prova prima i relè senza carico, guardando i loro LED e ascoltando il clic. Poi collega la ventola, la pompa (con il serbatoio pieno) e per ultimo il riscaldatore.
  6. Collega la striscia LED tramite il MOSFET e cambia il valore di analogWrite() per vedere variare la luminosità.
  7. Carica il programma principale, inserisci i tuoi valori di taratura nelle costanti e fai funzionare il sistema per qualche ora sotto controllo: irriga quando non deve, c'è un relè che scatta di continuo o un componente che si scalda troppo?
Non cercare di collegare tutto in una volta e farlo funzionare al primo colpo. Quando sei moduli non funzionano insieme, trovare il guasto può richiedere ore; i moduli provati uno alla volta, una volta uniti, di solito funzionano al primo tentativo.

Codice completo per Arduino Uno

Per compilare il codice installa dal Gestore librerie la DHT sensor library di Adafruit (insieme alla sua dipendenza Adafruit Unified Sensor) e la libreria LiquidCrystal I2C di Frank de Brabander; Wire è già inclusa nell'IDE di Arduino. L'indirizzo dell'LCD di solito è 0x27, su alcuni moduli 0x3F. Prima di caricare il codice, scrivi i valori di taratura che hai misurato nelle costanti SOIL_DRY_RAW, SOIL_WET_RAW, LDR_DARK_RAW e LDR_BRIGHT_RAW. Se il tuo galleggiante si apre con l'acqua invece di chiudersi, imposta TANK_OK su HIGH; se il tuo modulo relè è attivo alto, scambia i valori di RELAY_ON e RELAY_OFF.

C++ (Arduino IDE)
1#include <Wire.h>
2#include <LiquidCrystal_I2C.h>
3#include <DHT.h>
4
5// Pinler / Pins
6const byte DHT_PIN = 2, FLOAT_PIN = 3, PUMP_RELAY = 4, FAN_RELAY = 7, HEATER_RELAY = 8;
7const byte LED_PIN = 9;                          // MOSFET gate (PWM)
8const byte SOIL_PIN = A0, LDR_PIN = A1;
9
10// Kalibrasyon: kendi ölçümlerini yaz / Calibration: write your own readings
11const int SOIL_DRY_RAW = 520, SOIL_WET_RAW = 260;   // havada ve suda / in air and in water
12const int LDR_DARK_RAW = 60, LDR_BRIGHT_RAW = 900;  // karanlık ve güneş / dark and sunlight
13
14// Eşikler (açma, kapama) / Thresholds (on, off)
15const float FAN_ON_C = 30, FAN_OFF_C = 27;       // sıcaklıkta fan / fan on heat
16const float HUM_ON = 85, HUM_OFF = 75;           // nemde fan / fan on humidity
17const float HEAT_ON_C = 16, HEAT_OFF_C = 19;     // ısıtıcı / heater
18const float SOIL_DRY = 35, SOIL_OK = 50;         // sulama başla, dur / start, stop watering
19const float DARK_ON = 25, DARK_OFF = 35;         // ışık % / light %
20
21// Zamanlar / Timing
22const unsigned long READ_MS = 2500;              // DHT22: en az 2 sn / at least 2 s
23const unsigned long PUMP_MS = 3000;              // tek sulama darbesi / one watering pulse
24const unsigned long SOAK_MS = 60000;             // suyun yayılması / let the water soak in
25const byte MAX_PULSES = 6;                       // fazlası arıza / more means a fault
26const unsigned long EXTRA_LIGHT_MS = 4UL * 3600000UL;  // günü 4 saat uzat / extend the day by 4 h
27const byte LED_LEVEL = 200;                      // LED parlaklığı 0-255 / LED brightness 0-255
28const byte RELAY_ON = LOW, RELAY_OFF = HIGH;     // aktif LOW röle / active-LOW relay
29const byte TANK_OK = LOW;                        // şamandıra suyla kapanır / float closes with water
30
31DHT dht(DHT_PIN, DHT22);
32LiquidCrystal_I2C lcd(0x27, 16, 2);
33
34float tempC = NAN, hum = NAN, soil = 0, light = 0;
35byte dhtErrors = 0, pulses = 0, page = 0;
36bool fanByTemp = false, fanByHum = false, fanOn = false, heaterOn = false;
37bool needWater = false, pumpFault = false, isDark = false, ledOn = false, tankOk = true;
38unsigned long lastRead = 0, lastLcd = 0, lastLog = 0, pumpAt = 0, darkSince = 0;
39
40enum PumpState { IDLE, PUMPING, SOAKING };
41PumpState pumpState = IDLE;
42
43// onAt > offAt: yükselince aç (fan) / turn on when rising (fan)
44// onAt < offAt: düşünce aç (ısıtıcı) / turn on when falling (heater)
45bool hysteresis(bool state, float value, float onAt, float offAt) {
46  if (onAt > offAt) {
47    if (value >= onAt) return true;
48    if (value <= offAt) return false;
49  } else {
50    if (value <= onAt) return true;
51    if (value >= offAt) return false;
52  }
53  return state;                                  // ölü bölgede durumu koru / keep state in the dead band
54}
55
56void setRelay(byte pin, bool on) {
57  digitalWrite(pin, on ? RELAY_ON : RELAY_OFF);
58}
59
60float readPercent(byte pin, int raw0, int raw100) {
61  long sum = 0;
62  for (byte i = 0; i < 10; i++) sum += analogRead(pin);   // 10 ölçüm ortalaması / average of 10
63  long pct = map(sum / 10, raw0, raw100, 0, 100);
64  return constrain(pct, 0, 100);
65}
66
67void setup() {
68  Serial.begin(9600);
69  setRelay(PUMP_RELAY, false);   pinMode(PUMP_RELAY, OUTPUT);    // açılışta kapalı / off at boot
70  setRelay(FAN_RELAY, false);    pinMode(FAN_RELAY, OUTPUT);
71  setRelay(HEATER_RELAY, false); pinMode(HEATER_RELAY, OUTPUT);
72  pinMode(LED_PIN, OUTPUT);
73  pinMode(FLOAT_PIN, INPUT_PULLUP);
74  dht.begin();
75  lcd.init();
76  lcd.backlight();
77  lcd.print(F("BlueGrays Sera"));
78  Serial.println(F("sn,sicaklik,nem,toprak,isik,fan,isitici,pompa,led"));  // CSV başlığı / CSV header
79  delay(2000);                                   // DHT22 açılış süresi / DHT22 start-up time
80  readSensors();
81  lastRead = millis();
82}
83
84void loop() {
85  unsigned long now = millis();
86  if (now - lastRead >= READ_MS) {
87    lastRead = now;
88    readSensors();
89  }
90  controlClimate();
91  controlLight(now);
92  controlPump(now);
93  updateLcd(now);
94  logCsv(now);
95}
96
97// 1) Ölç / Measure
98void readSensors() {
99  float t = dht.readTemperature();
100  float h = dht.readHumidity();
101  if (isnan(t) || isnan(h)) {
102    if (dhtErrors < 255) dhtErrors++;            // okuma hatası / read error
103  } else {
104    tempC = t;
105    hum = h;
106    dhtErrors = 0;
107  }
108  soil = readPercent(SOIL_PIN, SOIL_DRY_RAW, SOIL_WET_RAW);
109  light = readPercent(LDR_PIN, LDR_DARK_RAW, LDR_BRIGHT_RAW);
110  tankOk = digitalRead(FLOAT_PIN) == TANK_OK;
111}
112
113// 2) Fan ve ısıtıcı / Fan and heater
114void controlClimate() {
115  if (dhtErrors >= 3) {                          // sensör yok: güvenli durum / no sensor: safe state
116    heaterOn = false;
117    fanByTemp = true;
118  } else {
119    fanByTemp = hysteresis(fanByTemp, tempC, FAN_ON_C, FAN_OFF_C);
120    fanByHum = hysteresis(fanByHum, hum, HUM_ON, HUM_OFF);
121    heaterOn = hysteresis(heaterOn, tempC, HEAT_ON_C, HEAT_OFF_C);
122  }
123  fanOn = fanByTemp || fanByHum;
124  setRelay(FAN_RELAY, fanOn);
125  setRelay(HEATER_RELAY, heaterOn);
126}
127
128// 3) LED aydınlatma / LED grow light
129void controlLight(unsigned long now) {
130  bool wasDark = isDark;
131  isDark = hysteresis(isDark, light, DARK_ON, DARK_OFF);
132  if (isDark && !wasDark) darkSince = now;      // akşam oldu / dusk detected
133  ledOn = isDark && (now - darkSince < EXTRA_LIGHT_MS);
134  analogWrite(LED_PIN, ledOn ? LED_LEVEL : 0);
135}
136
137// 4) Sulama: darbe + bekleme / Watering: pulse + soak
138void controlPump(unsigned long now) {
139  needWater = hysteresis(needWater, soil, SOIL_DRY, SOIL_OK);
140  if (!needWater) {
141    pulses = 0;
142    pumpFault = false;                           // toprak ıslandı / soil is wet again
143  }
144  switch (pumpState) {
145    case IDLE:
146      if (needWater && tankOk && !pumpFault) {
147        if (pulses >= MAX_PULSES) {
148          pumpFault = true;                      // nem yükselmiyor / moisture is not rising
149        } else {
150          pumpState = PUMPING;
151          pumpAt = now;
152          pulses++;
153        }
154      }
155      break;
156    case PUMPING:
157      if (now - pumpAt >= PUMP_MS || !tankOk) {
158        pumpState = SOAKING;
159        pumpAt = now;
160      }
161      break;
162    case SOAKING:
163      if (now - pumpAt >= SOAK_MS) pumpState = IDLE;
164      break;
165  }
166  setRelay(PUMP_RELAY, pumpState == PUMPING);
167}
168
169// 5) LCD: üç sayfa / three pages
170void updateLcd(unsigned long now) {
171  if (now - lastLcd < 3000) return;
172  lastLcd = now;
173  page = (page + 1) % 3;
174  lcd.clear();
175  if (page == 0) {
176    lcd.print(F("Sicaklik: "));
177    if (isnan(tempC)) lcd.print(F("--")); else lcd.print(tempC, 1);
178    lcd.print('C');
179    lcd.setCursor(0, 1);
180    lcd.print(F("Hava nemi: %"));
181    if (isnan(hum)) lcd.print(F("--")); else lcd.print(hum, 0);
182  } else if (page == 1) {
183    lcd.print(F("Toprak: %"));
184    lcd.print(soil, 0);
185    lcd.setCursor(0, 1);
186    lcd.print(F("Isik: %"));
187    lcd.print(light, 0);
188  } else {
189    lcd.print(F("F:"));  lcd.print(fanOn);
190    lcd.print(F(" I:")); lcd.print(heaterOn);
191    lcd.print(F(" P:")); lcd.print(pumpState == PUMPING);
192    lcd.print(F(" L:")); lcd.print(ledOn);
193    lcd.setCursor(0, 1);
194    if (dhtErrors >= 3) lcd.print(F("Sensor hatasi"));
195    else if (!tankOk) lcd.print(F("Depo bos!"));
196    else if (pumpFault) lcd.print(F("Sulama hatasi"));
197    else lcd.print(F("Sistem normal"));
198  }
199}
200
201// 6) Seri port CSV kaydı / Serial CSV log
202void logCsv(unsigned long now) {
203  if (now - lastLog < 10000) return;
204  lastLog = now;
205  Serial.print(now / 1000);  Serial.print(',');
206  Serial.print(tempC, 1);    Serial.print(',');
207  Serial.print(hum, 0);      Serial.print(',');
208  Serial.print(soil, 0);     Serial.print(',');
209  Serial.print(light, 0);    Serial.print(',');
210  Serial.print(fanOn);       Serial.print(',');
211  Serial.print(heaterOn);    Serial.print(',');
212  Serial.print(pumpState == PUMPING); Serial.print(',');
213  Serial.println(ledOn);
214}
FunzioneCompitoOgni quanto?
readSensors()Legge DHT22, umidità del terreno, LDR e galleggiante; converte i valori analogici in 0–100% con la media di 10 lettureOgni 2,5 secondi
controlClimate()Comanda la ventola con isteresi di temperatura o di umidità e il riscaldatore con isteresi di temperatura; passa a uno stato sicuro se il sensore si guastaA ogni ciclo
controlLight()Accende i LED in PWM quando fa buio e li spegne al massimo dopo 4 oreA ogni ciclo
controlPump()Macchina a stati dell'irrigazione: irriga 3 s, attendi 60 s, misura di nuovo; con protezione del serbatoio e contro il traboccamentoA ogni ciclo
updateLcd()Alterna tre pagine: valori dell'aria, umidità del terreno e luce, stato dei dispositiviOgni 3 secondi
logCsv()Scrive letture e stati nel monitor seriale come riga CSVOgni 10 secondi

La prima pagina dell'LCD mostra temperatura e umidità dell'aria, la seconda umidità del terreno e livello di luce, la terza lo stato dei dispositivi: «F:1 I:0 P:0 L:1» significa che la ventola gira, che riscaldatore (I = ısıtıcı) e pompa sono spenti e che i LED sono accesi. La riga in basso mostra uno dei messaggi «Sistem normal» (tutto regolare), «Depo bos!» (serbatoio vuoto), «Sulama hatasi» (guasto irrigazione) o «Sensor hatasi» (errore sensore); puoi tradurre facilmente questi testi nel codice. Compilato, il codice occupa circa il 31% della memoria di programma dell'Uno e il 25% della sua RAM, quindi c'è molto spazio per aggiungere un secondo sensore del terreno, un buzzer o un modulo orologio RTC. Poiché l'Uno non ha un vero orologio, la regola dei LED funziona come «al massimo 4 ore dopo il tramonto», un buon modo per allungare le brevi giornate invernali. Se vuoi accendere i LED ogni giorno a orari fissi, aggiungi un modulo RTC o passa alla versione con ESP32 qui sotto.

Controllare e irrigare dallo smartphone con un ESP32

Scheda di sviluppo ESP32
ESP32: grazie al Wi-Fi puoi controllare la serra dallo smartphone, e con l'ora presa da Internet gestisce i LED secondo l'orario reale.

L'Uno non ha il Wi-Fi. Se vuoi vedere lo stato della serra sullo smartphone e irrigare a distanza, sostituisci il cervello con un ESP32. Il codice seguente porta la stessa logica di controllo sull'ESP32 e avvia un piccolo web server. Con lo smartphone sulla stessa rete Wi-Fi, apri nel browser l'indirizzo IP che compare nel monitor seriale: vedrai temperatura, umidità, umidità del terreno, luce, stato del serbatoio e se ogni dispositivo è acceso o spento; la pagina si ricarica da sola ogni 10 secondi. Il pulsante Water now (irriga ora) fa girare la pompa una volta per 3 secondi se nel serbatoio c'è acqua. All'avvio l'ESP32 prende l'ora da Internet (NTP); così i LED si accendono solo tra le 6:00 e le 20:00 e solo se è buio, e di notte le piante riposano.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WebServer.h>
3#include <DHT.h>
4#include <time.h>
5
6const char* ssid = "AgAdi";                      // Wi-Fi adın / your Wi-Fi name
7const char* password = "Sifre123";               // Wi-Fi şifren / your Wi-Fi password
8
9// Pinler / Pins
10const int DHT_PIN = 4, FLOAT_PIN = 13, PUMP_RELAY = 25, FAN_RELAY = 26, HEATER_RELAY = 27;
11const int LED_PIN = 18;                          // MOSFET gate (PWM)
12const int SOIL_PIN = 34, LDR_PIN = 35;           // ADC1: Wi-Fi açıkken çalışır / works with Wi-Fi on
13
14// Kalibrasyon (0-4095): kendin ölç / Calibration (0-4095): measure your own
15const int SOIL_DRY_RAW = 2800, SOIL_WET_RAW = 1200;
16const int LDR_DARK_RAW = 200, LDR_BRIGHT_RAW = 3800;
17
18// Eşikler (açma, kapama) / Thresholds (on, off)
19const float FAN_ON_C = 30, FAN_OFF_C = 27, HUM_ON = 85, HUM_OFF = 75;
20const float HEAT_ON_C = 16, HEAT_OFF_C = 19, SOIL_DRY = 35, SOIL_OK = 50;
21const float DARK_ON = 25, DARK_OFF = 35;
22const int LIGHT_FROM = 6, LIGHT_TO = 20;         // LED yalnız 06:00-20:00 / LEDs only 06:00-20:00
23
24const unsigned long READ_MS = 2500, PUMP_MS = 3000, SOAK_MS = 60000;
25const unsigned long EXTRA_LIGHT_MS = 4UL * 3600000UL;  // saat yoksa yedek kural / fallback without a clock
26const int MAX_PULSES = 6, LED_LEVEL = 200;
27const int RELAY_ON = LOW, RELAY_OFF = HIGH;      // aktif LOW röle / active-LOW relay
28const int TANK_OK = LOW;                         // şamandıra suyla kapanır / float closes with water
29
30DHT dht(DHT_PIN, DHT22);
31WebServer server(80);
32
33float tempC = NAN, hum = NAN, soil = 0, light = 0;
34int dhtErrors = 0, pulses = 0;
35bool fanByTemp = false, fanByHum = false, fanOn = false, heaterOn = false;
36bool needWater = false, pumpFault = false, isDark = false, ledOn = false, tankOk = true;
37unsigned long lastRead = 0, pumpAt = 0, darkSince = 0;
38
39enum PumpState { IDLE, PUMPING, SOAKING };
40PumpState pumpState = IDLE;
41
42bool hysteresis(bool state, float value, float onAt, float offAt) {
43  if (onAt > offAt) {                            // yükselince aç / turn on when rising
44    if (value >= onAt) return true;
45    if (value <= offAt) return false;
46  } else {                                       // düşünce aç / turn on when falling
47    if (value <= onAt) return true;
48    if (value >= offAt) return false;
49  }
50  return state;
51}
52
53void setRelay(int pin, bool on) {
54  digitalWrite(pin, on ? RELAY_ON : RELAY_OFF);
55}
56
57float readPercent(int pin, int raw0, int raw100) {
58  long sum = 0;
59  for (int i = 0; i < 16; i++) sum += analogRead(pin);    // 16 ölçüm ortalaması / average of 16
60  long pct = map(sum / 16, raw0, raw100, 0, 100);
61  return constrain(pct, 0, 100);
62}
63
64int hourNow() {
65  struct tm t;
66  if (!getLocalTime(&t, 0)) return -1;           // saat henüz yok / time not set yet
67  return t.tm_hour;
68}
69
70void readSensors() {
71  float t = dht.readTemperature();
72  float h = dht.readHumidity();
73  if (isnan(t) || isnan(h)) {
74    dhtErrors++;
75  } else {
76    tempC = t;
77    hum = h;
78    dhtErrors = 0;
79  }
80  soil = readPercent(SOIL_PIN, SOIL_DRY_RAW, SOIL_WET_RAW);
81  light = readPercent(LDR_PIN, LDR_DARK_RAW, LDR_BRIGHT_RAW);
82  tankOk = digitalRead(FLOAT_PIN) == TANK_OK;
83}
84
85void controlClimate() {
86  if (dhtErrors >= 3) {                          // sensör yok: güvenli durum / no sensor: safe state
87    heaterOn = false;
88    fanByTemp = true;
89  } else {
90    fanByTemp = hysteresis(fanByTemp, tempC, FAN_ON_C, FAN_OFF_C);
91    fanByHum = hysteresis(fanByHum, hum, HUM_ON, HUM_OFF);
92    heaterOn = hysteresis(heaterOn, tempC, HEAT_ON_C, HEAT_OFF_C);
93  }
94  fanOn = fanByTemp || fanByHum;
95  setRelay(FAN_RELAY, fanOn);
96  setRelay(HEATER_RELAY, heaterOn);
97}
98
99void controlLight(unsigned long now) {
100  bool wasDark = isDark;
101  isDark = hysteresis(isDark, light, DARK_ON, DARK_OFF);
102  if (isDark && !wasDark) darkSince = now;
103  int h = hourNow();
104  if (h >= 0) ledOn = isDark && h >= LIGHT_FROM && h < LIGHT_TO;   // saat kuralı / clock rule
105  else ledOn = isDark && (now - darkSince < EXTRA_LIGHT_MS);       // yedek kural / fallback rule
106  analogWrite(LED_PIN, ledOn ? LED_LEVEL : 0);
107}
108
109void controlPump(unsigned long now) {
110  needWater = hysteresis(needWater, soil, SOIL_DRY, SOIL_OK);
111  if (!needWater) {
112    pulses = 0;
113    pumpFault = false;
114  }
115  if (pumpState == IDLE && needWater && tankOk && !pumpFault) {
116    if (pulses >= MAX_PULSES) {
117      pumpFault = true;                          // nem yükselmiyor / moisture is not rising
118    } else {
119      pumpState = PUMPING;
120      pumpAt = now;
121      pulses++;
122    }
123  } else if (pumpState == PUMPING && (now - pumpAt >= PUMP_MS || !tankOk)) {
124    pumpState = SOAKING;
125    pumpAt = now;
126  } else if (pumpState == SOAKING && now - pumpAt >= SOAK_MS) {
127    pumpState = IDLE;
128  }
129  setRelay(PUMP_RELAY, pumpState == PUMPING);
130}
131
132String row(const char* name, const String& value) {
133  return String("<tr><td>") + name + "</td><td><b>" + value + "</b></td></tr>";
134}
135
136String onOff(bool on) {
137  return on ? "ON" : "OFF";
138}
139
140void handleRoot() {
141  String html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
142                "<meta name='viewport' content='width=device-width,initial-scale=1'>"
143                "<meta http-equiv='refresh' content='10'><title>BlueGrays Sera</title>"
144                "<style>body{font-family:sans-serif;max-width:420px;margin:auto;padding:12px}"
145                "td{padding:8px;border-bottom:1px solid #ddd}"
146                "a{display:block;margin-top:16px;padding:16px;border-radius:10px;background:#2e7d32;"
147                "color:#fff;text-align:center;text-decoration:none;font-size:18px}</style>"
148                "</head><body><h2>BlueGrays Sera</h2><table>";
149  html += row("Sıcaklık / Temperature", String(tempC, 1) + " °C");
150  html += row("Hava nemi / Humidity", "%" + String(hum, 0));
151  html += row("Toprak nemi / Soil", "%" + String(soil, 0));
152  html += row("Işık / Light", "%" + String(light, 0));
153  html += row("Fan", onOff(fanOn));
154  html += row("Isıtıcı / Heater", onOff(heaterOn));
155  html += row("Pompa / Pump", onOff(pumpState == PUMPING));
156  html += row("LED", onOff(ledOn));
157  html += row("Depo / Tank", tankOk ? "OK" : "BOŞ / EMPTY");
158  if (pumpFault) html += row("Uyarı / Warning", "Sulama hatası / Watering fault");
159  html += "</table><a href='/water'>Şimdi sula / Water now</a></body></html>";
160  server.send(200, "text/html", html);
161}
162
163void handleWater() {                             // elle tek darbe / one manual pulse
164  if (pumpState == IDLE && tankOk) {
165    pumpState = PUMPING;
166    pumpAt = millis();
167  }
168  server.sendHeader("Location", "/");
169  server.send(303);
170}
171
172void setup() {
173  Serial.begin(115200);
174  setRelay(PUMP_RELAY, false);   pinMode(PUMP_RELAY, OUTPUT);
175  setRelay(FAN_RELAY, false);    pinMode(FAN_RELAY, OUTPUT);
176  setRelay(HEATER_RELAY, false); pinMode(HEATER_RELAY, OUTPUT);
177  pinMode(LED_PIN, OUTPUT);
178  pinMode(FLOAT_PIN, INPUT_PULLUP);
179  dht.begin();
180  WiFi.begin(ssid, password);
181  while (WiFi.status() != WL_CONNECTED) {
182    delay(500);
183    Serial.print('.');
184  }
185  Serial.println();
186  Serial.println(WiFi.localIP());                // bu adresi telefonda aç / open this address on your phone
187  configTime(3 * 3600, 0, "pool.ntp.org");       // Türkiye UTC+3; kendi saat dilimini yaz / set your own time zone
188  server.on("/", handleRoot);
189  server.on("/water", handleWater);
190  server.begin();
191  readSensors();
192  lastRead = millis();
193}
194
195void loop() {
196  server.handleClient();
197  unsigned long now = millis();
198  if (now - lastRead >= READ_MS) {
199    lastRead = now;
200    readSensors();
201  }
202  controlClimate();
203  controlLight(now);
204  controlPump(now);
205}

Ecco cosa cambia rispetto al codice dell'Uno: l'ADC dell'ESP32 è a 12 bit e restituisce valori da 0 a 4095, quindi devi rifare la taratura; dato che alimenti i sensori da 3V3, anche i valori grezzi saranno diversi. Il fuso orario si imposta nella riga configTime(); abbiamo messo 3 × 3600 secondi per la Turchia (UTC+3). In Italia sostituisci la riga con configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"), che gestisce anche l'ora legale. Se non c'è Internet o l'ora non è ancora stata ricevuta, la regola dei LED torna a quella dell'Uno, «al massimo 4 ore». La pagina non ha password, quindi chiunque sulla tua rete di casa potrebbe avviare la pompa; aggiungere una password semplice con la funzione authenticate() della libreria WebServer è una buona abitudine. Se vuoi un LCD anche sull'ESP32, puoi copiare così com'è la funzione updateLcd() dal codice dell'Uno; SDA è GPIO 21 e SCL è GPIO 22.

Se a scuola non c'è il Wi-Fi, sostituisci la riga WiFi.begin() e il ciclo di attesa che la segue con WiFi.softAP("BlueGrays-Sera", "12345678"), così l'ESP32 crea una sua rete; lo smartphone si collega a quella rete e apre 192.168.4.1. In questo caso non c'è Internet, quindi l'ora non si può ricevere e i LED seguono la regola di riserva.
MetodoAccesso fuori casaDifficoltàIdeale per
Web server sull'ESP32 (il codice di questa guida)No, solo sulla stessa reteMediaPresentazione scolastica, dimostrazione senza Internet
Blynk IoTSìFacileIndicatori, grafici e notifiche pronti all'uso
Bot di TelegramSìMediaAvvisi «serbatoio vuoto» e «temperatura troppo alta»
Fogli GoogleSìMediaRegistrazione dei dati per settimane e grafici
MQTT e Home AssistantSì, richiede configurazioneAvanzataVera domotica per casa e giardino

Per una presentazione scolastica il web server dell'ESP32 è la scelta più sicura: non dipende da nessun servizio cloud e funziona con la sua rete anche se in sala manca Internet. Se vuoi controllare la serra anche fuori casa, Blynk IoT con i suoi indicatori e grafici pronti è la strada più semplice. Per ricevere un messaggio sullo smartphone quando il serbatoio si svuota o la temperatura diventa pericolosa, leggi la guida al bot di Telegram con ESP32; per raccogliere settimane di dati per la tua relazione, ESP32 con Fogli Google; e per collegare la serra a una vera domotica, MQTT e Home Assistant.

Versione MicroPython per Raspberry Pi Pico

Puoi gestire la stessa serra anche con un Raspberry Pi Pico in MicroPython. Il firmware MicroPython del Pico include già un driver DHT, quindi non devi copiare altri file. Gli ingressi analogici sono solo GP26, GP27 e GP28, e read_u16() restituisce valori da 0 a 65535; misura i tuoi numeri di taratura su questa scala. Poiché il Pico lavora a 3,3 V, alimenta i sensori da 3V3 e il modulo relè da VBUS (i 5 V dell'USB) o dalla linea dell'LM2596. Il codice seguente contiene tutte le regole di ventola, riscaldatore, LED e irrigazione e scrive i valori nella shell di Thonny. Se salvi il file sul Pico con il nome main.py, la scheda funziona anche senza essere collegata al computer. Se usi il Pico W con Wi-Fi, puoi aggiungere il metodo dell'articolo web server con Pico W per seguire i valori dallo smartphone.

MicroPython
1from machine import Pin, ADC, PWM
2import dht
3import time
4
5sensor = dht.DHT22(Pin(15))              # GP15: DHT22 DATA
6soil_adc = ADC(26)                       # GP26: toprak nem AOUT / soil moisture AOUT
7ldr_adc = ADC(27)                        # GP27: LDR bölücü (3V3) / LDR divider (3V3)
8tank = Pin(14, Pin.IN, Pin.PULL_UP)      # şamandıra: suyla 0 / float switch: 0 with water
9fan = Pin(16, Pin.OUT, value=1)          # aktif LOW röleler kapalı başlar / active-LOW relays start off
10heater = Pin(17, Pin.OUT, value=1)
11pump = Pin(18, Pin.OUT, value=1)
12led = PWM(Pin(19))                       # MOSFET gate
13led.freq(1000)
14
15SOIL_DRY_RAW, SOIL_WET_RAW = 50000, 22000    # kendin ölç / measure your own
16LDR_DARK_RAW, LDR_BRIGHT_RAW = 2000, 60000
17EXTRA_LIGHT_MS = 4 * 3600 * 1000             # günü 4 saat uzat / extend the day by 4 h
18
19
20def percent(adc, raw0, raw100):
21    raw = sum(adc.read_u16() for _ in range(10)) // 10
22    pct = (raw - raw0) * 100 // (raw100 - raw0)
23    return max(0, min(100, pct))
24
25
26def hysteresis(state, value, on_at, off_at):
27    if on_at > off_at:                   # yükselince aç / turn on when rising
28        if value >= on_at:
29            return True
30        if value <= off_at:
31            return False
32    else:                                # düşünce aç / turn on when falling
33        if value <= on_at:
34            return True
35        if value >= off_at:
36            return False
37    return state                         # ölü bölge / dead band
38
39
40def relay(pin, on):
41    pin.value(0 if on else 1)
42
43
44t = h = None
45errors = pulses = 0
46fan_t = fan_h = heat_on = need_water = dark = led_on = False
47pump_state, pump_at, dark_since = "idle", 0, 0
48soil = light = 0
49tank_ok = True
50time.sleep(2)                            # DHT22 açılış süresi / DHT22 start-up time
51last_read = time.ticks_add(time.ticks_ms(), -2500)
52
53while True:
54    now = time.ticks_ms()
55    if time.ticks_diff(now, last_read) >= 2500:      # 2,5 sn'de bir ölç / measure every 2.5 s
56        last_read = now
57        try:
58            sensor.measure()
59            t, h = sensor.temperature(), sensor.humidity()
60            errors = 0
61        except OSError:
62            errors += 1                  # okuma hatası / read error
63        soil = percent(soil_adc, SOIL_DRY_RAW, SOIL_WET_RAW)
64        light = percent(ldr_adc, LDR_DARK_RAW, LDR_BRIGHT_RAW)
65        tank_ok = tank.value() == 0
66
67        if errors >= 3 or t is None:     # güvenli durum / safe state
68            heat_on, fan_t = False, True
69        else:
70            fan_t = hysteresis(fan_t, t, 30, 27)
71            fan_h = hysteresis(fan_h, h, 85, 75)
72            heat_on = hysteresis(heat_on, t, 16, 19)
73        relay(fan, fan_t or fan_h)
74        relay(heater, heat_on)
75
76        was_dark = dark
77        dark = hysteresis(dark, light, 25, 35)
78        if dark and not was_dark:
79            dark_since = now             # akşam oldu / dusk detected
80        led_on = dark and time.ticks_diff(now, dark_since) < EXTRA_LIGHT_MS
81        led.duty_u16(50000 if led_on else 0)
82
83        need_water = hysteresis(need_water, soil, 35, 50)
84        if not need_water:
85            pulses = 0
86        print("T", t, "N", h, "Toprak", soil, "Isik", light, "Pompa", pump_state,
87              "Depo OK" if tank_ok else "Depo BOS", "HATA" if pulses >= 6 else "")
88
89    # Sulama: 3 sn darbe, 60 sn bekleme / Watering: 3 s pulse, 60 s soak
90    if pump_state == "idle" and need_water and tank_ok and pulses < 6:
91        pump_state, pump_at = "pump", now
92        pulses += 1
93    elif pump_state == "pump" and (time.ticks_diff(now, pump_at) >= 3000 or not tank_ok):
94        pump_state, pump_at = "soak", now
95    elif pump_state == "soak" and time.ticks_diff(now, pump_at) >= 60000:
96        pump_state = "idle"
97    relay(pump, pump_state == "pump")
98    time.sleep_ms(100)

Consigli per la presentazione a scuola o alla fiera della scienza

La serra intelligente è un tema così diffuso che le giurie vedono decine di modellini simili. A farti emergere non sarà solo mostrare che il sistema funziona, ma rispondere a una domanda con delle misure. In una fiera della scienza obiettivo, metodo e risultato atteso devono essere chiari; nei concorsi di ricerca ci si aspetta un esperimento comparativo con dati. Una buona domanda di ricerca per questa serra è: «Quanta acqua fa risparmiare l'irrigazione basata sull'umidità del terreno rispetto a quella fatta ogni giorno alla stessa ora, e che effetto ha sulla crescita della pianta?»

  • Forma due gruppi: Dividi piantine della stessa specie e della stessa altezza in due gruppi. Irriga un gruppo con il sistema a sensori e l'altro a mano con una quantità fissa ogni giorno; luce e temperatura devono essere uguali per entrambi.
  • Misura e registra: Tieni una tabella con la quantità d'acqua settimanale (misurando il serbatoio, in mL), l'altezza delle piante (cm) e il numero di foglie. L'uscita CSV del codice o un registro su Fogli Google ti danno già pronto il grafico di temperatura e umidità.
  • Disegna uno schema a blocchi: Sensori a sinistra, Arduino al centro, attuatori a destra; etichetta le frecce con le frasi delle regole («terreno < 35% → pompa»). La giuria non leggerà il tuo codice, ma si ricorderà di questo schema.
  • Spiega l'isteresi: Due grafici che mostrano quante volte in un'ora si accendono e si spengono una ventola a soglia unica e una con isteresi sono il modo migliore per far vedere alla giuria il lato ingegneristico del progetto.
  • Scrivi un copione per la dimostrazione dal vivo: Scalda il DHT22 nel palmo per far partire la ventola, sposta il sensore del terreno in un vaso asciutto per mostrare gli impulsi della pompa, copri la LDR con la mano per accendere i LED. Ogni dimostrazione deve durare 10–20 secondi.
  • Collegalo alla vita reale: Parla di problemi reali come il consumo di acqua ed energia nelle serre, le malattie fungine causate dall'eccesso di umidità e il carico di lavoro degli agricoltori; mostra con i numeri a quale di questi risponde il tuo sistema.
  • Prepara un piano B: Fai un video del sistema in funzione; porta cavi, una pompa e un alimentatore di scorta; scrivi su un foglio i valori di taratura e i dati del Wi-Fi.
  • Preparati alle domande della giuria: Tieni pronte risposte brevi a «Cosa succede se un sensore si guasta?», «Cosa succede alle piante se manca la corrente?» e «Come lo porteresti in una serra vera?». Lo stato sicuro e la protezione contro il traboccamento del codice sono le risposte migliori.
Se il tuo concorso prevede aree tematiche, questo progetto rientra naturalmente nelle tecnologie agricole e alimentari (nel concorso turco TÜBİTAK 4006 è il tema Tecnologie agricole e zootecniche). Per altre idee, tabelle dei costi e criteri di valutazione, leggi l'articolo idee di progetti Arduino per fiere della scienza.

Errori comuni e soluzioni

SintomoCausa probabileSoluzione
Arduino si riavvia o l'LCD impazzisce quando parte la pompaPompa e relè alimentati dal pin 5V di Arduino, disturbi del motoreAlimenta pompa e bobine dei relè dall'LM2596, unisci i GND, aggiungi un diodo in inversa e un condensatore da 470 µF sulla pompa
I relè scattano in continuazioneSi usa una soglia unica o le letture sono rumoroseUsa soglie diverse per accensione e spegnimento (isteresi) e fai la media delle letture analogiche
L'acqua continua a scorrere dopo lo stop della pompaEffetto sifone: l'estremità del tubo è sotto il livello dell'acqua del serbatoioAbbassa il serbatoio o pratica un piccolo foro d'aria in cima al tubo
L'umidità del terreno è sempre 0% o sempre 100%Valori di taratura sbagliati o AOUT sul pin sbagliatoMisura il valore grezzo del sensore in aria e in acqua e scrivilo nelle costanti
I LED si accendono e si spengono di continuoLa LDR vede la luce dei LEDSposta la LDR fuori dalla serra, rivolta verso il cielo
Il DHT22 restituisce nanPin sbagliato, nessuna resistenza di pull-up o letture più frequenti di una ogni 2 secondiControlla il pin, usa un modulo o aggiungi 10 kΩ tra DATA e VCC
La temperatura sembra più alta del dovutoIl sole o la luce dei LED colpiscono direttamente il sensoreFagli ombra con un cappuccio bianco forato e allontanalo dal riscaldatore
Gocce d'acqua e muffa sulle paretiUmidità troppo alta, ventilazione insufficienteAbbassa la soglia di umidità, allarga la presa d'aria, irriga meno
Sull'ESP32 la lettura del terreno impazzisce quando parte il Wi-FiIl sensore è su un pin ADC2Spostalo su un pin ADC1 tra GPIO 32 e 39
Con l'ESP32 il relè non si diseccita maiUn HIGH da 3,3 V non spegne un modulo da 5 V attivo bassoSepara JD-VCC o usa un modulo relè comandabile a 3,3 V

Una serra intelligente unisce in un solo progetto lettura dei sensori, taratura, decisioni con isteresi, comando di relè e MOSFET, piano di alimentazione e monitoraggio senza fili, e alla fine fa crescere qualcosa di davvero vivo: una pianta. Comincia da irrigazione e ventilazione, prova ogni parte da sola e poi aggiungi riscaldatore, LED e smartphone. Nell'app BlueGrays trovi, passo dopo passo e con schemi e codice, progetti per misurare temperatura e umidità con un DHT11, leggere la luce con una LDR, una stazione di irrigazione intelligente e un registratore di temperatura con DHT22.

Domande frequenti

Cosa serve per una serra intelligente con Arduino?

Per una serra intelligente di base bastano un Arduino Uno o un ESP32, un sensore di temperatura e umidità DHT22, un sensore capacitivo di umidità del terreno, una LDR con una resistenza da 10 kΩ, un modulo relè, una mini pompa a immersione e una ventola. La versione completa aggiunge un riscaldatore PTC da 12 V, una striscia LED comandata da un MOSFET, un LCD I2C, un interruttore a galleggiante, un alimentatore da 12 V 5 A e un LM2596. Come struttura, una scatola portaoggetti trasparente con coperchio è la soluzione più semplice ed economica.

Come funziona una serra intelligente?

Una serra intelligente funziona con un ciclo misura, decidi, agisci, mostra. I sensori misurano temperatura, umidità, umidità del terreno e luce; Arduino confronta questi valori con delle soglie e accende o spegne ventola, riscaldatore, pompa e LED tramite relè o un MOSFET. I risultati compaiono su un LCD e, con un ESP32, anche sullo smartphone.

Meglio DHT11 o DHT22 per una serra?

Scegli il DHT22. Il DHT11 è preciso solo a ±2 °C tra 0 e 50 °C e non vede l'umidità sopra il 90%, mentre in serra l'umidità può superare il 90% al mattino e la temperatura scendere sotto zero d'inverno. Il DHT22 misura da −40 a 80 °C con ±0,5 °C e l'umidità con circa ±2%, e il suo codice è quasi identico a quello del DHT11.

Quanto costa una serra intelligente con Arduino?

I prezzi dipendono dal venditore, ma esclusa la scheda la versione base (DHT22, sensore di umidità del terreno, LDR, relè, pompa e ventola) costa di solito circa 25–40 €. La versione completa con riscaldatore, striscia LED, LCD, alimentatore 12 V e contenitore arriva a circa 70–120 €. Se hai già uno starter kit Arduino, il costo scende parecchio.

Come controllo la serra dallo smartphone?

Il modo più semplice è sostituire il cervello con un ESP32 e avviare un web server; dal browser dello smartphone, sulla stessa rete Wi-Fi, vedi i valori e avvii la pompa. Per l'accesso fuori casa puoi usare Blynk IoT, per gli avvisi un bot di Telegram e per la registrazione dei dati a lungo termine Fogli Google.

Come presento una serra intelligente alla fiera della scienza?

Invece di mostrare solo un modellino che funziona, poni una domanda di ricerca misurabile; per esempio misura per alcune settimane, con due gruppi di piante, quanta acqua fa risparmiare l'irrigazione a sensori rispetto a quella a orario fisso. Presenta consumo d'acqua, altezza delle piante e andamento di temperatura e umidità con tabelle e grafici, spiega il sistema con uno schema a blocchi e prepara un breve copione per la dimostrazione dal vivo.

Vedi tutti i progetti
Temperatura e umidità con DHT11
#55Intermedio

Temperatura e umidità con DHT11

Quanti gradi ci sono nella tua stanza, e l'aria è umida o secca? Il sensore DHT11 misura temperatura e umidità, e l'Arduino scrive i valori su uno schermo LCD a due righe, aggiornandoli ogni paio di s

4.7(47)
73
Misurare l'intensità luminosa con LDR
#11Principiante

Misurare l'intensità luminosa con LDR

Vuoi vedere quanto è luminosa la tua stanza sotto forma di numero? L'LDR, un piccolo componente sensibile alla luce, misura la luce ambiente e l'Arduino scrive il valore sullo schermo del computer ogn

4.6(81)
139
Lettura dell'intensità luminosa con il modulo LDR ESP32
#113Principiante

Lettura dell'intensità luminosa con il modulo LDR ESP32

Monitoraggio in tempo reale sul Serial Monitor del valore grezzo dell'ADC del modulo LDR e della percentuale relativa di luce calcolata dai valori di riferimento al buio e alla luce. Questa percentuale non è una misurazione in lux. Spiega il principio di calibrazione del modulo. È consigliabile eseguirlo prima del Progetto 10, poiché serve a determinare il valore di soglia.

4.6(36)
55
Stazione di irrigazione intelligente
#83Avanzato
Premium

Stazione di irrigazione intelligente

Un giardiniere che annaffia le tue piante da solo, così non si seccano mentre sei in vacanza. Un sensore di umidità misura quanto è bagnata la terra; quando diventa secca, l'Arduino accende la pompa d

5.0(42)
57
ESP32 DHT22 Temperatura e umidità (con display LCD)
#117Intermedio

ESP32 DHT22 Temperatura e umidità (con display LCD)

Si tratta di un progetto che misura la temperatura e l'umidità dell'ambiente tramite il sensore DHT22 e le visualizza in tempo reale su un display LCD I2C 16x2. I dati vengono letti dal sensore utilizzando il protocollo one-wire tramite la libreria Adafruit DHT. Questo progetto costituisce la base per stazioni meteorologiche IoT, sistemi di automazione domestica intelligente o sistemi di monitoraggio ambientale nelle serre. La comunicazione con il DHT22 avviene tramite il pin GPIO 4 dell'ESP32.

4.8(38)
56
Data Logger di Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avanzato
Premium

Data Logger di Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

Costruisci un data logger di temperatura con timestamp usando ESP32! Leggi temperatura/umidità dal DHT22 ogni minuto, ottieni l'ora reale dal DS1307 RTC e registra su scheda microSD in formato CSV. L'OLED mostra dal vivo temperatura (font grande), percentuale di umidità, timestamp e contatore di log. Il file /temp_log.csv sulla scheda SD si apre in Excel, Google Sheets o Python pandas per l'analisi — perfetto per il monitoraggio di serre, tracciamento della temperatura domestica, raccolta

4.7(7)
12
Illuminazione Intelligente ESP32 (PIR + LDR + Relè + OLED)
#141Avanzato
Premium

Illuminazione Intelligente ESP32 (PIR + LDR + Relè + OLED)

Trasforma il tuo corridoio, bagno o dispensa in illuminazione intelligente con ESP32! Utilizzando un sensore di movimento PIR, un sensore di luce LDR e un modulo relè, costruiamo lo scenario classico "la luce si accende quando viene rilevato movimento al buio" — se l'ambiente è già luminoso, il movimento NON attiva la luce, risparmiando energia durante il giorno. La luce rimane accesa per 30 secondi; se il movimento continua, il timer si azzera e la luce rimane accesa più a lungo. Il display OLE

4.3(6)
11