Guide · 21 min di lettura

Cestino intelligente con Arduino: sensore HC-SR04, servo e codice

Cestino intelligente con Arduino: rilevamento della mano con HC-SR04, coperchio a servo con chiusura lenta, LED di riempimento e avviso Telegram con ESP32.

Un cestino intelligente con Arduino (detto anche pattumiera automatica) è un progetto di automazione semplice ma molto istruttivo che apre il coperchio senza che tu debba toccarlo, ed è uno dei progetti scolastici più amati. Un sensore a ultrasuoni HC-SR04 sul davanti del cestino rileva la mano che si avvicina, Arduino apre il coperchio con un servomotore e, qualche secondo dopo che hai tolto la mano, lo richiude lentamente invece di lasciarlo sbattere. Un secondo HC-SR04 sotto il coperchio misura l'altezza dei rifiuti: man mano che il cestino si riempie si accendono in sequenza i LED verde, giallo e rosso, e quando è completamente pieno suona un buzzer.

In questa guida partiamo dall'elenco dei componenti e vediamo passo dopo passo dove montare i sensori, come calcolare la coppia del servo, come collegare il coperchio alla cerniera, la calibrazione e il codice completo che gestisce insieme il tempo di attesa e la chiusura lenta. Poi calcoliamo quanto dura davvero il progetto a batterie, inviamo un avviso di riempimento con l'ESP32 sul tuo telefono come messaggio Telegram e scriviamo lo stesso progetto per il Raspberry Pi Pico in MicroPython. Alla fine trovi anche una scaletta per presentare il progetto a scuola e una tabella di prova da compilare con le tue misure.

Sensore di distanza a ultrasuoni HC-SR04
L'HC-SR04 ha due compiti nel cestino intelligente: il sensore sul davanti misura la mano che si avvicina, quello sotto il coperchio misura l'altezza dei rifiuti. Vede da 2 a 400 cm in un fascio stretto.

Come funziona un cestino intelligente?

Il progetto è un ciclo che si ripete decine di volte al secondo. Arduino misura con il sensore della mano ogni 60 millisecondi. Se in due letture consecutive compare un oggetto a meno di 20 cm, decide che è una mano e porta il servo in posizione aperta. Finché la mano resta lì, il tempo di attesa riparte a ogni lettura; 4 secondi dopo che la mano se n'è andata, il coperchio si chiude lentamente a circa 50 gradi al secondo. Se durante la chiusura la mano torna, il coperchio si riapre subito. Un secondo dopo la chiusura, il sensore di riempimento fa 5 letture, usa la mediana come distanza dalla superficie dei rifiuti e la converte in percentuale. Il codice usa una piccola macchina a stati in cui il coperchio è sempre in uno di tre stati: CHIUSO (CLOSED), APERTO (OPEN) e IN CHIUSURA (CLOSING).

ComponenteCompitoNota
Arduino Uno (o Nano)Il cervello: misura, decide e comanda servo e LEDVanno bene anche ESP32 e Pico
2 × HC-SR04Uno rileva la mano, l'altro misura il riempimento5 V, circa 15 mA durante la misura
Servo SG90, MG90S o MG996RApre e chiude il coperchioSi sceglie in base al peso del coperchio (calcolo più sotto)
3 LED (verde, giallo, rosso) e 3 × 220 ΩIndicatore di riempimentoUna resistenza per ogni LED
Buzzer attivoAvviso di cestino pienoUn buzzer passivo richiede tone()
Cerniera, viti, colla a caldoSostiene il coperchio, fissa la squadretta del servoIl peso del coperchio deve reggerlo la cerniera, non il servo
Condensatore da 470 µFSmorza la corrente di spunto del servoTra + e − del servo, attenzione alla polarità
AlimentazioneAlimentatore da 5 V, power bank o batterieCalcolo della durata più sotto
ESP32 (facoltativo)Avviso di riempimento sul telefonoServe il Wi-Fi
Arduino Uno
Arduino Uno
SG90 servo motor
SG90 servo motor
Buzzer
Buzzer
LED
LED

Quale sensore per rilevare la mano?

Diversi sensori possono aprire un cestino, ma non tutti sono adatti allo stesso modo. Quello che ti serve davvero è accorgerti di una mano che si avvicina a 15–25 cm dal cestino e ignorare una persona che passa soltanto davanti. Dato che l'HC-SR04 restituisce la distanza in centimetri, puoi impostare la soglia nel codice esattamente come vuoi; per questo lo usa la maggior parte dei progetti di cestino intelligente. Ecco punti di forza e debolezze delle alternative:

SensoreCome rilevaPer un cestino
HC-SR04 (ultrasuoni)Eco sonora, 2–400 cm, cono strettoSoglia regolabile nel codice; la scelta più equilibrata
Sensore IR di ostacoli (FC-51)Luce infrarossa riflessa, circa 2–30 cmEconomico e veloce; il sole e i guanti scuri possono ingannarlo
PIR (HC-SR501)Movimento del calore corporeo, fino a 7 m, angolo ampioScatta per chiunque passi e resta attivo alcuni secondi; non adatto
APDS-9960Prossimità e gesti della mano a breve distanzaVersione elegante che si apre con un gesto; richiede I2C e 3,3 V

Come funziona un sensore PIR lo trovi nell'articolo Sensore di movimento PIR, mentre una versione comandata a gesti è nella guida al sensore di gesti APDS-9960. Ecco in breve come funziona l'HC-SR04: emette un breve impulso sonoro a 40 kHz e restituisce il tempo di andata e ritorno dell'eco come impulso sul pin ECHO. Poiché il suono nell'aria viaggia a circa 343 m/s a 20 °C e il percorso è di andata e ritorno, dividere il tempo in microsecondi per 58 dà la distanza in centimetri. I dettagli sono nell'articolo Sensore di distanza HC-SR04.

Posizione dei sensori

Il sensore della mano va sul corpo del cestino, nella parte alta del frontale, non sul coperchio: quando il coperchio si apre, il sensore ruoterebbe con lui e guarderebbe il soffitto. Inclinarlo di 10–20 gradi verso l'alto gli permette di cogliere una mano che si allunga sopra il cestino e di ignorare le gambe di chi passa. Se il cestino è vicino a un muro o a un mobile, non deve esserci nessuna superficie fissa entro 30 cm nella direzione in cui guarda il sensore; altrimenti la scambia per una mano e il coperchio non si chiude mai.

Il sensore di riempimento si incolla sotto il coperchio, vicino al centro, rivolto dritto verso il basso. In questa posizione misura qualcosa di utile solo a coperchio chiuso; a coperchio aperto punta verso un muro o il soffitto. Per questo il codice misura il riempimento solo un secondo dopo la chiusura del coperchio. Anche se il cono sonoro del sensore è stretto (15 gradi secondo il datasheet, un po' di più nella pratica), a 35 cm di profondità copre un'area di 10–20 cm di diametro; nei cestini più stretti di 20 cm il suono può rimbalzare sulle pareti laterali e dare letture sbagliate. Ripiega il bordo del sacchetto verso l'esterno del cestino: un bordo che pende all'interno finisce davanti al sensore e fa sembrare il cestino pieno anche quando è vuoto.

Non attivare mai i due HC-SR04 contemporaneamente. Ognuno scambierebbe l'eco dell'altro per la propria ed entrambi leggerebbero valori assurdi. Nel codice i due sensori vengono letti uno dopo l'altro, ad almeno 60 ms di distanza; mentre il sensore di riempimento fa le sue 5 letture, quello della mano aspetta. Anche il davanti dei sensori deve restare libero: il bordo dei fori praticati nel cestino deve essere a filo con la superficie del sensore, e persino un sottile pezzo di plastica o di nastro trasparente davanti blocca l'eco.

Schema di collegamento: Arduino Uno, ESP32 e Pico

La tabella seguente corrisponde esattamente a tutto il codice di questo articolo. Sull'Uno, il sensore della mano va su D2–D3, quello di riempimento su D4–D5, il servo su D9, i LED su D10–D12 e il buzzer su D8. Poiché la libreria Servo usa il Timer1 sull'Uno, disattiva l'uscita PWM su D9 e D10; il LED su D10 lo accendiamo e spegniamo soltanto, quindi non ci riguarda. Metti una resistenza da 220 Ω davanti a ogni LED; puoi calcolare il valore per il tuo LED con lo strumento resistenza per LED. La piedinatura della tua scheda la trovi nello strumento Pinout.

CollegamentoArduino UnoESP32Raspberry Pi Pico
Sensore della mano TRIGD2GPIO 18GP2
Sensore della mano ECHOD3 (diretto)GPIO 19, con partitore 1 kΩ / 2 kΩGP3, con partitore 1 kΩ / 2 kΩ
Sensore di riempimento TRIGD4GPIO 25GP4
Sensore di riempimento ECHOD5 (diretto)GPIO 26, con partitoreGP5, con partitore
VCC degli HC-SR04 (entrambi)5V5V (VIN)VBUS
Segnale del servo (arancione)D9GPIO 13GP15
Servo + (rosso)5V (SG90) o esterno 5–6 V5V (VIN) o esterno 5–6 VVBUS o esterno 5–6 V
LED verde, giallo, rosso (con 220 Ω)D10, D11, D12GPIO 27, 32, 33GP10, GP11, GP12
Buzzer attivo (+)D8GPIO 23GP13
Tutti i GNDGNDGNDGND
Il pin ECHO dell'HC-SR04 fornisce un impulso a 5 V, mentre i pin di ESP32 e Pico lavorano a 3,3 V e non tollerano i 5 V. Monta un partitore di tensione separato sulla linea ECHO di ciascun sensore: ECHO → 1 kΩ → punto che va alla scheda → 2 kΩ → GND. Così ottieni 5 V × 2 / 3 ≈ 3,3 V. TRIG non ha bisogno di partitore: un impulso di attivazione a 3,3 V basta al sensore. Il pin 5V (VIN) dell'ESP32 e il pin VBUS del Pico danno 5 V solo quando la scheda è alimentata via USB.

Scegliere il servo e calcolare la coppia

Il problema più comune nei cestini intelligenti è un coperchio che si apre a metà e trema, o che non si solleva proprio. La causa è quasi sempre un servo troppo debole per il coperchio. Stimare la coppia necessaria è facile: Coppia (kg·cm) = peso del coperchio (kg) × distanza del baricentro dalla cerniera (cm). Un coperchio piatto e di spessore uniforme ha il baricentro esattamente al centro; per un coperchio lungo 24 cm questa distanza è di 12 cm. Il momento più difficile è l'inizio dell'apertura, quando il coperchio è orizzontale; man mano che si alza la coppia necessaria diminuisce, e in verticale è quasi zero. La coppia indicata nei cataloghi dei servo è quella di stallo, il punto in cui il servo si sforza e si ferma; se lavora vicino a quel limite si scalda e consuma gli ingranaggi. Per questo moltiplica il risultato almeno per 2.

CoperchioPesoBaricentroCoppia necessariaCon margine ×2Consiglio
Cartone o pannello in schiuma 20 × 20 cm50 g10 cm0,5 kg·cm1,0 kg·cmSG90
Coperchio di plastica di un cestino da 25 cm120 g12,5 cm1,5 kg·cm3,0 kg·cmMG996R (MG90S al limite)
Coperchio in MDF o compensato da 30 cm300 g15 cm4,5 kg·cm9,0 kg·cmMG996R con contrappeso
ServoCoppia (4,8 V / 6 V)IngranaggiCorrenteNota
SG901,8 kg·cm (si indica solo il valore a 4,8 V)Plastica100–250 mA in movimentoPuò essere alimentato dal pin 5V di Arduino
MG90S1,8 / 2,2 kg·cmMetallo150–300 mA in movimentoStesse dimensioni dell'SG90, più robusto
MG996R9,4 / 11 kg·cmMetallo0,5–0,9 A in movimento, fino a 2,5 A in stalloDa alimentare sempre con una sorgente separata da 5–6 V
Servomotore MG996R con ingranaggi in metallo
MG996R: servo con ingranaggi in metallo e circa 11 kg·cm di coppia a 6 V. Solleva senza fatica il coperchio di plastica di un vero cestino, ma poiché in stallo può assorbire fino a 2,5 A va alimentato con una sorgente separata e non da Arduino.

Invece di comprare un servo più potente, spesso basta bilanciare il coperchio. Se il coperchio ha un piccolo braccio che sporge dietro la cerniera, qualche bullone o moneta incollati lì compensano parte del suo peso; questo contrappeso può ridurre la coppia necessaria fino alla metà. Potresti anche far spingere il coperchio dalla squadretta del servo in un punto lontano dalla cerniera, ma la leva non ti regala coppia: più ti allontani dalla cerniera, meno forza serve, però il punto spinto percorre più strada, quindi ti serve una squadretta più lunga, e con una squadretta più lunga la coppia richiesta al servo torna a salire. Se vuoi aprire il coperchio fin quasi a 90 gradi, la coppia necessaria resta praticamente uguale a quella di un servo montato direttamente sulla cerniera. Un confronto dettagliato tra i tipi di servo è negli articoli Cos'è un servomotore? e servo MG996R.

Consigli di montaggio: cerniera, squadretta del servo e cavi

  • Fai reggere il peso alla cerniera: Il coperchio deve essere fissato a una vera cerniera (quella del cestino o una piccola cerniera da mobile). Se appoggi il coperchio solo sull'albero del servo, tutto il peso grava sul suo cuscinetto e gli ingranaggi di plastica dell'SG90 si rompono presto.
  • Allinea l'albero del servo con l'asse della cerniera: Il modo più semplice è avvitare il servo al bordo posteriore del cestino in modo che il suo albero si trovi esattamente alla fine della linea della cerniera, e fissare la squadretta al coperchio con una vite o con la colla a caldo. Se gli assi sono disallineati, il servo spinge il coperchio di lato a ogni apertura.
  • Fissa bene il corpo del servo: Non tenerlo solo con il biadesivo; quando solleva il coperchio il corpo ruota. Usa due viti nelle alette di fissaggio del servo o una piccola squadretta a L.
  • Non schiacciare il coperchio sul bordo: Imposta l'angolo di chiusura nel punto in cui il coperchio poggia sul bordo ma il servo non lo spinge. Se il servo continua a forzare il coperchio chiuso, ronza, si scalda e consuma corrente inutilmente.
  • Lascia gioco ai cavi: I fili del sensore di riempimento si muovono con il coperchio. Fai passare il cavo accanto alla cerniera, abbastanza lungo da non tendersi neanche a coperchio tutto aperto, e fermalo con una fascetta.
  • Tieni l'elettronica lontana dai rifiuti: Metti Arduino, batterie e breadboard in una scatolina incollata dietro o sotto il cestino. Liquidi o umidità che escono dal sacchetto non devono raggiungere il circuito.
  • Inizia con un modellino di cartone: Per un progetto scolastico, un coperchio leggero ricavato da una scatola da scarpe o da un pannello in schiuma funziona senza problemi anche con un SG90 ed è un ottimo esempio per mostrare il calcolo della coppia nella presentazione.

Montaggio e calibrazione passo dopo passo

Prima di passare al codice principale, carica il piccolo sketch di calibrazione qui sotto. Porta il servo all'angolo che scrivi nel monitor seriale e ogni mezzo secondo stampa la distanza letta dai due sensori. In questo modo misuri, per il tuo cestino, gli angoli di coperchio chiuso e aperto, la profondità del cestino vuoto e la soglia di rilevamento della mano.

Arduino (C++)
1#include <Servo.h>
2
3const int HAND_TRIG = 2, HAND_ECHO = 3;   // el sensörü / hand sensor
4const int FILL_TRIG = 4, FILL_ECHO = 5;   // doluluk sensörü / fill sensor
5const int SERVO_PIN = 9;
6
7Servo lid;
8unsigned long lastPrint = 0;
9
10int pingCm(int trig, int echo) {
11  digitalWrite(trig, LOW);  delayMicroseconds(2);
12  digitalWrite(trig, HIGH); delayMicroseconds(10);   // 10 µs tetik darbesi / 10 µs trigger pulse
13  digitalWrite(trig, LOW);
14  unsigned long t = pulseIn(echo, HIGH, 30000UL);    // en fazla 30 ms bekle / wait at most 30 ms
15  return t == 0 ? 999 : (int)(t / 58);               // 999 = yankı yok / 999 = no echo
16}
17
18void setup() {
19  Serial.begin(9600);
20  pinMode(HAND_TRIG, OUTPUT); pinMode(HAND_ECHO, INPUT);
21  pinMode(FILL_TRIG, OUTPUT); pinMode(FILL_ECHO, INPUT);
22  lid.write(90);                                     // başlangıçta orta konum / start at the middle position
23  lid.attach(SERVO_PIN);
24  Serial.println(F("Açı yaz (0-180) ve Enter'a bas / Type an angle (0-180) and press Enter"));
25}
26
27void loop() {
28  if (Serial.available()) {
29    String s = Serial.readStringUntil('\n');
30    s.trim();
31    if (s.length() > 0) {                            // boş satırı yok say / ignore empty lines
32      int a = constrain(s.toInt(), 0, 180);
33      lid.write(a);
34      Serial.print(F("Servo: "));
35      Serial.println(a);
36    }
37  }
38
39  if (millis() - lastPrint >= 500) {                 // yarım saniyede bir yaz / print every half second
40    lastPrint = millis();
41    int hand = pingCm(HAND_TRIG, HAND_ECHO);
42    delay(60);                                       // iki sensör arasında bekle / wait between the two sensors
43    int fill = pingCm(FILL_TRIG, FILL_ECHO);
44    Serial.print(F("El / Hand: "));
45    Serial.print(hand);
46    Serial.print(F(" cm   Doluluk / Fill: "));
47    Serial.print(fill);
48    Serial.println(F(" cm"));
49  }
50}
  1. Carica il codice senza la squadretta montata e apri il monitor seriale a 9600 baud; imposta il fine riga su A capo (NL) (Newline). Il servo si porta a 90 gradi.
  2. Scrivi 10 e premi Invio. Tieni il coperchio chiuso, monta la squadretta sull'albero in questa posizione e poi fissala al coperchio.
  3. Aumenta l'angolo a passi di 20, 40, 60 finché trovi il valore in cui il coperchio è completamente aperto; di solito è tra 90 e 110 gradi. Non aprire il coperchio oltre la verticale. Questo valore diventa LID_OPEN. Se il tuo servo gira al contrario, scegli di conseguenza i valori di chiuso e aperto; il codice funziona in entrambi i versi.
  4. Torna agli angoli piccoli. Dall'angolo in cui il coperchio poggia completamente sul bordo, torna indietro di 2–3 gradi nel verso di apertura; questo diventa LID_CLOSED. Se il servo ronza a coperchio chiuso, lo sta premendo.
  5. Con il sacchetto montato e il cestino vuoto, annota il valore letto dal sensore di riempimento; è EMPTY_CM. Per FULL_CM scegli 6–10 cm: quando i rifiuti arrivano a quell'altezza, il coperchio deve ancora chiudersi senza fatica e il sensore misurare in modo affidabile, perché l'HC-SR04 non misura sotto i 2–3 cm.
  6. Tieni la mano davanti al sensore della mano a 5, 10, 20 e 30 cm e guarda i valori. Poi passa camminando davanti al cestino e annota il valore più vicino letto. HAND_CM deve restare sotto quel valore; per la maggior parte dei cestini vanno bene 15–25 cm.
  7. Inserisci i valori trovati nelle impostazioni in cima al codice principale e caricalo.

Codice principale: rilevamento della mano, tempo di attesa e chiusura lenta

Nel codice principale non c'è nessun delay() lungo; tutto si basa su marcatori temporali registrati con millis(). Così, anche mentre il coperchio si chiude, il sensore della mano continua a essere letto, e se una mano si riavvicina il coperchio si riapre subito. È una misura di sicurezza semplice ma importante che evita che il coperchio si chiuda sulla mano di un bambino. I dettagli della temporizzazione non bloccante sono in millis() e delay(), il ragionamento per stati nell'articolo macchina a stati con Arduino.

Arduino (C++)
1#include <Servo.h>
2
3// Pinler / Pins
4const int HAND_TRIG = 2, HAND_ECHO = 3;    // el sensörü, kutunun önünde / hand sensor, front of the bin
5const int FILL_TRIG = 4, FILL_ECHO = 5;    // doluluk sensörü, kapağın altında / fill sensor, under the lid
6const int SERVO_PIN = 9;
7const int LED_GREEN = 10, LED_YELLOW = 11, LED_RED = 12;
8const int BUZZER = 8;                      // aktif buzzer / active buzzer
9
10// Ayarlar: kalibrasyon kodunda bulduğun değerleri yaz / Settings: use the values from the calibration sketch
11const int HAND_CM = 20;                    // bundan yakın el kapağı açar / a hand closer than this opens the lid
12const int LID_CLOSED = 10;                 // kapak kenara oturuyor, bastırmıyor / lid rests on the rim, not pressing
13const int LID_OPEN = 110;                  // kapak tam açık / lid fully open
14const unsigned long OPEN_TIME = 4000;      // son el algılamadan sonra açık kalma (ms) / stay open after the last hand (ms)
15const unsigned long CLOSE_STEP_MS = 20;    // kapanırken derece başına ms / ms per degree while closing
16const int EMPTY_CM = 35;                   // boş kutuda sensörden dibe / sensor to bottom when empty
17const int FULL_CM = 8;                     // bu mesafede kutu %100 dolu / the bin is 100% full at this distance
18
19enum LidState { CLOSED, OPEN, CLOSING };
20LidState state = CLOSED;
21Servo lid;
22
23unsigned long lastHand = 0, lastHandPing = 0, closeStart = 0, closedAt = 0, lastFill = 0;
24int fillPercent = 0;
25byte handHits = 0;
26bool fullWarned = false;
27
28int pingCm(int trig, int echo) {
29  digitalWrite(trig, LOW);  delayMicroseconds(2);
30  digitalWrite(trig, HIGH); delayMicroseconds(10);  // 10 µs tetik darbesi / 10 µs trigger pulse
31  digitalWrite(trig, LOW);
32  unsigned long t = pulseIn(echo, HIGH, 30000UL);   // en fazla 30 ms bekle / wait at most 30 ms
33  return t == 0 ? 999 : (int)(t / 58);              // 999 = yankı yok / 999 = no echo
34}
35
36int medianCm(int trig, int echo) {                  // 5 ölçümün ortancası / median of 5 readings
37  int v[5];
38  delay(60);                                        // önce el sensörünün yankısı sönsün / first let the hand sensor's echo die out
39  for (int i = 0; i < 5; i++) {
40    v[i] = pingCm(trig, echo);
41    delay(60);                                      // yankılar sönsün / let the echoes die out
42  }
43  for (int i = 0; i < 4; i++)                       // küçükten büyüğe sırala / sort ascending
44    for (int j = i + 1; j < 5; j++)
45      if (v[j] < v[i]) { int tmp = v[i]; v[i] = v[j]; v[j] = tmp; }
46  return v[2];
47}
48
49int toPercent(int cm) {                             // mesafe → doluluk yüzdesi / distance → fill percent
50  long p = (long)(EMPTY_CM - cm) * 100 / (EMPTY_CM - FULL_CM);
51  if (p < 0) p = 0;
52  if (p > 100) p = 100;
53  return (int)p;
54}
55
56void showLevel() {
57  digitalWrite(LED_GREEN, fillPercent < 50);
58  digitalWrite(LED_YELLOW, fillPercent >= 50 && fillPercent < 80);
59  digitalWrite(LED_RED, fillPercent >= 80);
60}
61
62void beep(int n) {
63  for (int i = 0; i < n; i++) {
64    digitalWrite(BUZZER, HIGH); delay(120);
65    digitalWrite(BUZZER, LOW);  delay(120);
66  }
67}
68
69void openLid() {
70  if (!lid.attached()) lid.attach(SERVO_PIN);
71  lid.write(LID_OPEN);                              // hızlı aç / open quickly
72  state = OPEN;
73  if (fillPercent >= 90) beep(2);                   // açılırken dolu uyarısı / full warning on opening
74}
75
76void setup() {
77  Serial.begin(9600);
78  pinMode(HAND_TRIG, OUTPUT); pinMode(HAND_ECHO, INPUT);
79  pinMode(FILL_TRIG, OUTPUT); pinMode(FILL_ECHO, INPUT);
80  pinMode(LED_GREEN, OUTPUT); pinMode(LED_YELLOW, OUTPUT); pinMode(LED_RED, OUTPUT);
81  pinMode(BUZZER, OUTPUT);
82  lid.write(LID_CLOSED);                            // attach'tan önce: açılışta 90°'ye sıçramasın / before attach: no jump to 90° at power-up
83  lid.attach(SERVO_PIN);
84  delay(500);
85  lid.detach();                                     // kapalıyken servoyu bırak / release the servo while closed
86  closedAt = millis();
87  showLevel();
88}
89
90void loop() {
91  unsigned long now = millis();
92
93  // 1) El algılama, 60 ms'de bir / Hand detection, every 60 ms
94  if (now - lastHandPing >= 60) {
95    lastHandPing = now;
96    int d = pingCm(HAND_TRIG, HAND_ECHO);
97    if (d >= 2 && d <= HAND_CM) {
98      if (handHits < 2) handHits++;
99    } else {
100      handHits = 0;
101    }
102    if (handHits >= 2) {                            // art arda 2 ölçüm / 2 readings in a row
103      lastHand = now;                               // bekleme süresi yeniden başlar / the wait time restarts
104      if (state != OPEN) openLid();                 // kapanırken el gelirse yeniden aç / reopen if a hand appears while closing
105    }
106  }
107
108  // 2) Bekleme süresi doldu mu? / Has the wait time passed?
109  if (state == OPEN && now - lastHand >= OPEN_TIME) {
110    state = CLOSING;
111    closeStart = now;
112  }
113
114  // 3) Yavaş kapanma: açı geçen süreden hesaplanır / Slow close: angle is computed from elapsed time
115  if (state == CLOSING) {
116    int moved = (now - closeStart) / CLOSE_STEP_MS;
117    int range = abs(LID_OPEN - LID_CLOSED);
118    if (moved >= range) {
119      lid.write(LID_CLOSED);
120      state = CLOSED;
121      closedAt = now;
122    } else {
123      lid.write(LID_OPEN > LID_CLOSED ? LID_OPEN - moved : LID_OPEN + moved);
124    }
125  }
126
127  // 4) Kapandıktan 0,4 s sonra servoyu bırak / Release the servo 0.4 s after closing
128  if (state == CLOSED && lid.attached() && now - closedAt > 400) lid.detach();
129
130  // 5) Doluluk: yalnızca kapak kapalı ve durgunken / Fill level: only while the lid is closed and still
131  if (state == CLOSED && now - closedAt > 1000 && now - lastFill >= 3000) {
132    lastFill = now;
133    int cm = medianCm(FILL_TRIG, FILL_ECHO);
134    if (cm < 999) fillPercent = toPercent(cm);     // yankı yoksa eski değeri koru / keep the old value if there is no echo
135    showLevel();
136    Serial.print(F("Doluluk / Fill: "));
137    Serial.print(cm);
138    Serial.print(F(" cm = %"));
139    Serial.println(fillPercent);
140    if (fillPercent >= 90 && !fullWarned) {         // doldu: bir kez öt / just filled: beep once
141      fullWarned = true;
142      beep(3);
143    }
144    if (fillPercent < 70) fullWarned = false;       // boşaltıldı / emptied
145  }
146}
Funzione o impostazioneCosa fa
pingCm()Fa una lettura; restituisce 999 se entro 30 ms non arriva l'eco
medianCm()Fa 5 letture a 60 ms l'una dall'altra, le ordina e restituisce quella centrale
toPercent()Converte la distanza in una percentuale da 0 a 100 in base a EMPTY_CM e FULL_CM
handHitsConta in quante letture consecutive si è vista una mano; una singola lettura errata non apre il coperchio
OPEN_TIMETempo dall'ultimo momento in cui si è vista la mano all'inizio della chiusura (4000 ms)
CLOSE_STEP_MSTempo per grado durante la chiusura: 100 gradi × 20 ms = chiusura in 2 secondi
lid.detach()Smette di inviare impulsi al servo 0,4 s dopo la chiusura del coperchio
fullWarnedFa suonare l'avviso di pieno una sola volta; si azzera quando il riempimento scende sotto il 70%

Il trucco della chiusura lenta è che l'angolo viene calcolato dal tempo trascorso invece di essere aumentato di un grado alla volta. 600 ms dopo l'inizio della chiusura il coperchio deve aver percorso 600 / 20 = 30 gradi; anche se il ciclo viene rallentato per un attimo da una misura di riempimento o dal buzzer, il codice porta il coperchio all'angolo giusto e il movimento non va a scatti. Per una chiusura ancora più lenta aumenta CLOSE_STEP_MS: con 30 ms, 100 gradi richiedono 3 secondi. Quando il coperchio si è chiuso, il servo viene rilasciato con detach(); il coperchio poggia già sul bordo, il servo non deve tenerlo, non ronza e consuma meno batteria. Anche chiamare write() prima di attach() è voluto: all'accensione di Arduino il servo non salta prima a 90 gradi, ma parte direttamente in posizione chiusa.

Misura del riempimento e avvisi con LED e buzzer

Il sensore di riempimento misura la distanza tra il lato inferiore del coperchio e il punto più alto dei rifiuti. Questa distanza è massima con il cestino vuoto (EMPTY_CM) e minima quando è pieno (FULL_CM). La percentuale si ottiene con questa formula: Riempimento (%) = (EMPTY_CM − misura) / (EMPTY_CM − FULL_CM) × 100. Per esempio, con EMPTY_CM = 35 cm, FULL_CM = 8 cm e una misura di 20 cm, il riempimento è (35 − 20) / 27 × 100 ≈ 55% e si accende il LED giallo. Se la misura supera i 35 cm (il suono è rimbalzato in diagonale sul fondo) il risultato viene bloccato allo 0%, se è inferiore a 8 cm al 100%.

RiempimentoLEDBuzzerSignificato
0–49%VerdeSilenziosoC'è ancora spazio
50–79%GialloSilenziosoPieno per più di metà, da svuotare presto
80–89%RossoSilenziosoQuasi pieno
90–100%Rosso3 bip brevi quando si riempie, 2 bip a ogni aperturaDa svuotare subito

La superficie dei rifiuti non è piatta: se una sola bottiglia sta in piedi proprio sotto il sensore, la misura vede quel punto e il cestino sembra più pieno di quanto sia. Il filtro mediano elimina le singole letture anomale, ma non può correggere la forma dei rifiuti; considera quindi soglie come l'80% e il 90% un avviso di "da svuotare" più che una misura esatta. Perché l'avviso non suoni di continuo, il codice usa l'isteresi: l'avviso di pieno suona una volta al 90% e non suona più finché il cestino non viene svuotato e il riempimento non scende sotto il 70%. Spieghiamo in dettaglio le differenze tra i filtri nell'articolo filtrare i dati dei sensori.

La velocità del suono cambia con la temperatura: circa v = 331,3 + 0,606 × T (m/s). Il numero 58 vale per 20 °C; su un balcone a 5 °C il suono è più lento, quindi le misure risultano più lunghe di circa il 3%. In un cestino da 35 cm è un errore inferiore a 1 cm e non conta per il riempimento, ma se la giuria lo chiede, conoscerne il motivo fa un'ottima impressione.

Alimentazione a batterie: quanto dura?

Usare il cestino intelligente senza cavo è allettante, ma l'Arduino Uno non è una scheda adatta alle batterie. Con il suo chip USB, il regolatore e il LED di accensione assorbe circa 45–50 mA senza fare nulla. Aggiungendo la corrente a riposo di due HC-SR04, un LED acceso e l'elettronica del servo rilasciato, la corrente media sale a circa 70–80 mA. I 100–250 mA che il servo assorbe in apertura e chiusura durano solo pochi secondi, quindi in un cestino aperto 50 volte al giorno alzano la media soltanto di circa 1 mA. La durata si calcola approssimativamente come capacità (mAh) / corrente media (mA); per calcolarla con i tuoi valori usa lo strumento Durata della batteria.

AlimentazioneCollegamentoDurata indicativa (75 mA)Giudizio
Batteria da 9 V (circa 500 mAh)VIN5–6 oreSconsigliata: la tensione crolla all'avvio del servo e la scheda si riavvia
6 pile AA alcaline (9 V)Jack DC o VINCirca 1 giornoSufficiente per il giorno della presentazione
2 × 18650 (7,4 V, 2500 mAh)VIN; LM2596 regolato a 5–6 V per il servoCirca 1,5 giorniRicaricabile; servono un caricatore 2S e una scheda di protezione
Power bank da 10.000 mAhUSB3–4 giorniAlcuni power bank si spengono con carichi sotto i 50–100 mA
Alimentatore da 5 V e 2 AUSB o pin 5VIllimitataLa scelta migliore per l'uso fisso in casa
Portabatterie per celle 18650 agli ioni di litio
Portabatterie 18650: due celle in serie danno 7,4 V. Questa tensione è adatta ad alimentare Arduino tramite VIN; per il servo si inserisce in mezzo un modulo step-down regolato a 5–6 V.

La conclusione della tabella è chiara: un cestino con Uno a batterie è ideale per presentazioni e dimostrazioni, ma per un cestino che deve funzionare per mesi in casa usa un alimentatore da 5 V. Come regolare il modulo step-down tra due 18650 e il servo lo spiega l'articolo LM2596. Se vuoi davvero allungare la durata, al posto dell'Uno puoi usare un Arduino Pro Mini senza LED di accensione, mettere il processore in sospensione tra una misura e l'altra e accendere i LED solo per un attimo quando il coperchio si apre; con questi accorgimenti la corrente media scende a 15–20 mA e le stesse batterie durano da 4 a 5 volte di più. Le differenze tra i modi di alimentare un Arduino (USB, jack DC, VIN, pin 5V) sono nell'articolo Come alimentare Arduino.

Tutti i GND di servo, sensori e Arduino devono essere in comune; se il GND di un servo alimentato da una sorgente separata non è collegato ad Arduino, il servo non capisce il segnale e trema. Metti un condensatore da 470 µF sui terminali di alimentazione del servo. Se Arduino si riavvia ogni volta che il coperchio si apre, il servo fa crollare la linea dei 5 V all'avvio; cause e soluzioni sono nell'articolo Arduino si riavvia da solo.

Avviso di riempimento con ESP32 (Telegram)

Per vedere il LED quando il cestino è pieno devi andarci vicino; un ESP32, invece, può mandare l'informazione direttamente sul tuo telefono. Il modo più semplice è Telegram, perché è gratuito e puoi creare il tuo bot in pochi minuti. Su Telegram scrivi a @BotFather, crea un bot con il comando /newbot e annota il token che ti fornisce. Poi invia una volta /start al tuo bot: un bot non può scrivere a chi non gli ha mai parlato. Per conoscere il tuo chat ID puoi scrivere a un bot come @userinfobot oppure usare la richiesta getUpdates della Bot API. Il codice non ha bisogno di librerie aggiuntive: invia una richiesta all'indirizzo sendMessage di Telegram con l'HTTPClient integrato dell'ESP32.

Il codice usa la stessa macchina a stati della versione per Uno; solo il segnale del servo viene generato con LEDC, l'unità PWM dell'ESP32, invece che con la libreria Servo. In un segnale a 50 Hz un impulso di 0,544–2,4 ms corrisponde a 0–180 gradi, e interrompere gli impulsi (scrivere 0 con ledcWrite) rilascia il servo. Il codice è per la versione 3.x del core ESP32 per Arduino; la versione 2.x non ha la funzione ledcAttach(), quindi aggiorna il core dal Gestore schede.

Scheda di sviluppo ESP32
ESP32: grazie al Wi-Fi invia un messaggio Telegram al telefono quando il cestino è pieno. Le linee ECHO degli HC-SR04 non vanno collegate ai suoi pin senza prima ridurle a 3,3 V.
Arduino (C++)
1// ESP32 Arduino çekirdeği 3.x / ESP32 Arduino core 3.x
2#include <WiFi.h>
3#include <WiFiClientSecure.h>
4#include <HTTPClient.h>
5
6const char* WIFI_SSID = "WIFI_ADIN";              // kendi ağın / your network
7const char* WIFI_PASS = "WIFI_SIFREN";
8const char* BOT_TOKEN = "123456789:ABCdefGhIJKlmNoPQRstuVWxyz";  // BotFather'dan / from BotFather
9const char* CHAT_ID   = "123456789";              // senin sohbet kimliğin / your chat ID
10
11// Pinler: ECHO'lar 1 kΩ / 2 kΩ bölücüyle / Pins: ECHOs via a 1 kΩ / 2 kΩ divider
12const int HAND_TRIG = 18, HAND_ECHO = 19;
13const int FILL_TRIG = 25, FILL_ECHO = 26;
14const int SERVO_PIN = 13;
15const int LED_GREEN = 27, LED_YELLOW = 32, LED_RED = 33;
16const int BUZZER = 23;
17
18const int HAND_CM = 20;
19const int LID_CLOSED = 10, LID_OPEN = 110;
20const unsigned long OPEN_TIME = 4000, CLOSE_STEP_MS = 20;
21const int EMPTY_CM = 35, FULL_CM = 8;
22const int NOTIFY_PCT = 85;                        // bu yüzdede bildir / notify at this percent
23const int RESET_PCT = 30;                         // bunun altına inince "boşaltıldı" / below this: "emptied"
24
25enum LidState { CLOSED, OPEN, CLOSING };
26LidState state = CLOSED;
27unsigned long lastHand = 0, lastHandPing = 0, closeStart = 0, closedAt = 0, lastFill = 0, lastTry = 0;
28int fillPercent = 0;
29byte handHits = 0;
30bool notified = false, servoOn = false, fullWarned = false;
31
32void servoWrite(int angle) {                      // 0–180° → 544–2400 µs, 50 Hz
33  uint32_t us = map(angle, 0, 180, 544, 2400);
34  ledcWrite(SERVO_PIN, us * 16384 / 20000);       // 14 bit, 20 ms periyot / 14-bit, 20 ms period
35  servoOn = true;
36}
37
38void servoRelease() {                             // darbe yok: servo serbest kalır / no pulses: the servo relaxes
39  ledcWrite(SERVO_PIN, 0);
40  servoOn = false;
41}
42
43int pingCm(int trig, int echo) {
44  digitalWrite(trig, LOW);  delayMicroseconds(2);
45  digitalWrite(trig, HIGH); delayMicroseconds(10);
46  digitalWrite(trig, LOW);
47  unsigned long t = pulseIn(echo, HIGH, 30000UL);
48  return t == 0 ? 999 : (int)(t / 58);            // 999 = yankı yok / 999 = no echo
49}
50
51int medianCm(int trig, int echo) {                // 5 ölçümün ortancası / median of 5 readings
52  int v[5];
53  delay(60);                                        // önce el sensörünün yankısı sönsün / first let the hand sensor's echo die out
54  for (int i = 0; i < 5; i++) {
55    v[i] = pingCm(trig, echo);
56    delay(60);
57  }
58  for (int i = 0; i < 4; i++)
59    for (int j = i + 1; j < 5; j++)
60      if (v[j] < v[i]) { int tmp = v[i]; v[i] = v[j]; v[j] = tmp; }
61  return v[2];
62}
63
64int toPercent(int cm) {
65  long p = (long)(EMPTY_CM - cm) * 100 / (EMPTY_CM - FULL_CM);
66  return (int)constrain(p, 0L, 100L);
67}
68
69void showLevel() {
70  digitalWrite(LED_GREEN, fillPercent < 50);
71  digitalWrite(LED_YELLOW, fillPercent >= 50 && fillPercent < 80);
72  digitalWrite(LED_RED, fillPercent >= 80);
73}
74
75void beep(int n) {
76  for (int i = 0; i < n; i++) {
77    digitalWrite(BUZZER, HIGH); delay(120);
78    digitalWrite(BUZZER, LOW);  delay(120);
79  }
80}
81
82String urlEncode(const String& s) {               // Türkçe harfler ve boşluk için / for Turkish letters and spaces
83  const char* hex = "0123456789ABCDEF";
84  String out;
85  for (size_t i = 0; i < s.length(); i++) {
86    uint8_t c = s[i];
87    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
88      out += (char)c;
89    } else {
90      out += '%';
91      out += hex[c >> 4];
92      out += hex[c & 15];
93    }
94  }
95  return out;
96}
97
98bool sendTelegram(const String& text) {
99  if (WiFi.status() != WL_CONNECTED) return false;
100  WiFiClientSecure client;
101  client.setInsecure();                           // sertifika denetimi yok, hobi projesi için yeterli / no certificate check, fine for a hobby project
102  HTTPClient http;
103  String url = String("https://api.telegram.org/bot") + BOT_TOKEN +
104               "/sendMessage?chat_id=" + CHAT_ID + "&text=" + urlEncode(text);
105  if (!http.begin(client, url)) return false;
106  int code = http.GET();                          // 200 = gönderildi / 200 = sent
107  Serial.printf("Telegram HTTP %d\n", code);
108  http.end();
109  return code == 200;
110}
111
112void openLid() {
113  servoWrite(LID_OPEN);
114  state = OPEN;
115  if (fillPercent >= 90) beep(2);
116}
117
118void setup() {
119  Serial.begin(115200);
120  pinMode(HAND_TRIG, OUTPUT); pinMode(HAND_ECHO, INPUT);
121  pinMode(FILL_TRIG, OUTPUT); pinMode(FILL_ECHO, INPUT);
122  pinMode(LED_GREEN, OUTPUT); pinMode(LED_YELLOW, OUTPUT); pinMode(LED_RED, OUTPUT);
123  pinMode(BUZZER, OUTPUT);
124  ledcAttach(SERVO_PIN, 50, 14);                  // 50 Hz, 14 bit servo sinyali / 50 Hz, 14-bit servo signal
125  servoWrite(LID_CLOSED);
126  delay(500);
127  servoRelease();
128  WiFi.mode(WIFI_STA);
129  WiFi.begin(WIFI_SSID, WIFI_PASS);               // arka planda bağlanır; kutu Wi-Fi olmadan da çalışır / connects in the background; the bin works without Wi-Fi too
130  closedAt = millis();
131  showLevel();
132}
133
134void loop() {
135  unsigned long now = millis();
136
137  if (now - lastHandPing >= 60) {                 // el sensörü / hand sensor
138    lastHandPing = now;
139    int d = pingCm(HAND_TRIG, HAND_ECHO);
140    if (d >= 2 && d <= HAND_CM) {
141      if (handHits < 2) handHits++;
142    } else {
143      handHits = 0;
144    }
145    if (handHits >= 2) {
146      lastHand = now;
147      if (state != OPEN) openLid();
148    }
149  }
150
151  if (state == OPEN && now - lastHand >= OPEN_TIME) {
152    state = CLOSING;
153    closeStart = now;
154  }
155
156  if (state == CLOSING) {                         // yavaş kapanma / slow close
157    int moved = (now - closeStart) / CLOSE_STEP_MS;
158    int range = abs(LID_OPEN - LID_CLOSED);
159    if (moved >= range) {
160      servoWrite(LID_CLOSED);
161      state = CLOSED;
162      closedAt = now;
163    } else {
164      servoWrite(LID_OPEN > LID_CLOSED ? LID_OPEN - moved : LID_OPEN + moved);
165    }
166  }
167
168  if (state == CLOSED && servoOn && now - closedAt > 400) servoRelease();
169
170  if (state == CLOSED && now - closedAt > 1000 && now - lastFill >= 3000) {
171    lastFill = now;
172    int cm = medianCm(FILL_TRIG, FILL_ECHO);
173    if (cm < 999) fillPercent = toPercent(cm);
174    showLevel();
175    Serial.printf("Doluluk / Fill: %d cm = %%%d\n", cm, fillPercent);
176
177    if (fillPercent >= 90 && !fullWarned) { fullWarned = true; beep(3); }
178    if (fillPercent < 70) fullWarned = false;
179
180    // Bildirim: kapak kapalıyken, en fazla dakikada bir dene / Notify: only with the lid closed, retry at most once a minute
181    if (!notified && fillPercent >= NOTIFY_PCT && (lastTry == 0 || now - lastTry > 60000UL)) {
182      lastTry = now;
183      notified = sendTelegram("Çöp kutusu %" + String(fillPercent) + " dolu, boşaltma zamanı. / The bin is " +
184                              String(fillPercent) + "% full.");
185    }
186    if (notified && fillPercent < RESET_PCT) {   // boşaltıldı: yeniden kur / emptied: re-arm
187      notified = false;
188      lastTry = 0;
189      sendTelegram("Çöp kutusu boşaltıldı. / The bin has been emptied.");
190    }
191  }
192}

L'avviso viene inviato una sola volta quando il cestino arriva all'85% e viene riarmato con un messaggio di "svuotato" quando il riempimento scende sotto il 30%, così il telefono non vibra ogni 3 secondi. Se manca il Wi-Fi o l'invio non riesce, il codice riprova una volta al minuto e nel frattempo il cestino continua a funzionare normalmente. L'invio di un messaggio richiede 1–3 secondi per via della connessione cifrata (TLS), per questo il codice invia solo a coperchio chiuso. setInsecure() si collega senza verificare il certificato del server; per un progetto amatoriale va bene, ma ricorda che il token è un'informazione segreta e cancellalo prima di condividere il codice. Se nel monitor seriale vedi "Telegram HTTP 200" il messaggio è partito; 401 indica un token sbagliato, 400 un chat ID sbagliato e 403 che non hai ancora inviato /start al bot. Per un bot completo che risponde ai comandi, consulta la guida bot Telegram con ESP32.

Cestino intelligente con Raspberry Pi Pico (MicroPython)

Sul Raspberry Pi Pico la stessa logica si realizza in MicroPython. La funzione time_pulse_us() funziona come pulseIn() su Arduino; dato che restituisce un valore negativo quando non c'è eco, lo trattiamo come 999, cioè "nessuna eco". Per il servo si usano il PWM a 50 Hz e duty_ns(), che riceve la larghezza dell'impulso in nanosecondi; duty_ns(0) interrompe gli impulsi e rilascia il servo. I confronti di tempo si fanno con ticks_ms() e ticks_diff(); ticks_diff dà la differenza corretta anche quando il contatore ricomincia da capo. Se salvi il codice sul Pico come main.py con Thonny, parte da solo a ogni accensione della scheda. Con un Pico W puoi aggiungere il metodo dell'articolo web server con Pico W per mostrare il riempimento via Wi-Fi.

MicroPython
1from machine import Pin, PWM, time_pulse_us
2import time
3
4# Pinler: ECHO'lar 1 kΩ / 2 kΩ bölücüyle / Pins: ECHOs via a 1 kΩ / 2 kΩ divider
5hand_trig, hand_echo = Pin(2, Pin.OUT), Pin(3, Pin.IN)
6fill_trig, fill_echo = Pin(4, Pin.OUT), Pin(5, Pin.IN)
7green, yellow, red = Pin(10, Pin.OUT), Pin(11, Pin.OUT), Pin(12, Pin.OUT)
8buzzer = Pin(13, Pin.OUT)
9servo = PWM(Pin(15))
10servo.freq(50)                                  # servo için 50 Hz / 50 Hz for the servo
11
12HAND_CM = 20
13LID_CLOSED, LID_OPEN = 10, 110
14OPEN_MS, CLOSE_STEP_MS = 4000, 20
15EMPTY_CM, FULL_CM = 35, 8
16
17def ping_cm(trig, echo):
18    trig.low(); time.sleep_us(2)
19    trig.high(); time.sleep_us(10)              # 10 µs tetik / 10 µs trigger
20    trig.low()
21    t = time_pulse_us(echo, 1, 30000)           # yankı yoksa negatif / negative if there is no echo
22    return 999 if t < 0 else t // 58
23
24def median_cm(trig, echo):                      # 5 ölçümün ortancası / median of 5 readings
25    v = []
26    time.sleep_ms(60)                           # önce el sensörünün yankısı sönsün / first let the hand sensor's echo die out
27    for _ in range(5):
28        v.append(ping_cm(trig, echo))
29        time.sleep_ms(60)
30    v.sort()
31    return v[2]
32
33def servo_write(angle):                         # 0–180° → 544–2400 µs
34    servo.duty_ns((544 + angle * 1856 // 180) * 1000)
35
36def servo_release():                            # darbe yok: servo serbest / no pulses: servo relaxes
37    servo.duty_ns(0)
38
39def show_level(p):
40    green.value(p < 50)
41    yellow.value(50 <= p < 80)
42    red.value(p >= 80)
43
44def beep(n):
45    for _ in range(n):
46        buzzer.on(); time.sleep_ms(120)
47        buzzer.off(); time.sleep_ms(120)
48
49state = "closed"
50fill, hits, warned, released = 0, 0, False, False
51last_hand = last_ping = close_start = last_fill = time.ticks_ms()
52servo_write(LID_CLOSED)
53time.sleep_ms(500)
54servo_release()
55released = True
56closed_at = time.ticks_ms()
57show_level(fill)
58
59while True:
60    now = time.ticks_ms()
61
62    if time.ticks_diff(now, last_ping) >= 60:   # el sensörü, 60 ms'de bir / hand sensor, every 60 ms
63        last_ping = now
64        d = ping_cm(hand_trig, hand_echo)
65        hits = min(hits + 1, 2) if 2 <= d <= HAND_CM else 0
66        if hits >= 2:
67            last_hand = now
68            if state != "open":                 # kapanırken de yeniden açar / also reopens while closing
69                servo_write(LID_OPEN)
70                state, released = "open", False
71                if fill >= 90:
72                    beep(2)
73
74    if state == "open" and time.ticks_diff(now, last_hand) >= OPEN_MS:
75        state, close_start = "closing", now
76
77    if state == "closing":                      # yavaş kapanma / slow close
78        moved = time.ticks_diff(now, close_start) // CLOSE_STEP_MS
79        if moved >= abs(LID_OPEN - LID_CLOSED):
80            servo_write(LID_CLOSED)
81            state, closed_at = "closed", now
82        else:
83            servo_write(LID_OPEN - moved if LID_OPEN > LID_CLOSED else LID_OPEN + moved)
84
85    if state == "closed" and not released and time.ticks_diff(now, closed_at) > 400:
86        servo_release()
87        released = True
88
89    if state == "closed" and time.ticks_diff(now, closed_at) > 1000 and time.ticks_diff(now, last_fill) >= 3000:
90        last_fill = now
91        cm = median_cm(fill_trig, fill_echo)
92        if cm < 999:                            # yankı yoksa eski değer kalır / keep the old value if no echo
93            fill = max(0, min(100, (EMPTY_CM - cm) * 100 // (EMPTY_CM - FULL_CM)))
94        show_level(fill)
95        print("Doluluk / Fill:", cm, "cm =", fill, "%")
96        if fill >= 90 and not warned:
97            warned = True
98            beep(3)
99        if fill < 70:
100            warned = False
101
102    time.sleep_ms(5)

Consigli per presentare il progetto a scuola

Il cestino intelligente è un progetto molto frequente nelle fiere della scienza e nelle mostre scolastiche come TÜBİTAK 4006, quindi alla giuria interessa meno il coperchio che si apre e si chiude e più come hai definito il problema, come l'hai misurato e che conclusioni ne hai tratto. Ciò che trasforma il tuo progetto da semplice dimostrazione in un lavoro scientifico sono le misure: in quanti tentativi si è aperto, in quanto tempo, con quale precisione ha stimato il riempimento, quante ore sono durate le batterie. Puoi costruire la presentazione in questo ordine:

  1. Problema: Toccare il coperchio con la mano trasmette germi e i cestini che traboccano portano cattivi odori e insetti. Parti da dati reali contando quante persone toccano il coperchio di un cestino della tua scuola durante un intervallo.
  2. Obiettivo: Un cestino che si apre senza contatto, avvisa quando è pieno e, se vuoi, manda una notifica al telefono.
  3. Principio di funzionamento: Disegna un unico schema a blocchi con sensore, Arduino, servo, LED, buzzer, ESP32 e telefono. Spiega la velocità del suono e la formula "diviso 58" in una slide.
  4. Materiali e costi: Presenta l'elenco dei componenti e la scelta del servo in una tabella, insieme al calcolo della coppia.
  5. Dimostrazione dal vivo: Avvicina la mano e lascia che il coperchio si apra. Riempi il cestino di carta e mostra il LED che passa dal verde al giallo e al rosso; proietta sullo schermo il messaggio Telegram arrivato sul telefono.
  6. Risultati dei test: Compila la tabella qui sotto con le tue misure e trasforma i risultati in grafici.
  7. Conclusioni e miglioramenti: Racconta con sincerità cosa ha funzionato bene, cosa no e cosa cambieresti nella prossima versione.
TestCome si faValore da annotare
Distanza di rilevamentoTieni la mano a 5, 10, 15, 20 e 25 cm, 10 volte ciascunaIn quanti tentativi si è aperto a ogni distanza (es. 10/10)
Tempo di rispostaRiprendi al rallentatore (240 fps) con il telefonoTempo tra la comparsa della mano e il primo movimento del coperchio (ms)
Aperture indesideratePassa davanti al cestino 20 volte camminandoNumero di aperture non volute
Precisione del riempimentoRiempi il cestino di carta a un quarto, a metà e a tre quarti, misurando con il righelloDifferenza tra riempimento reale e percentuale misurata
Velocità di chiusuraImposta CLOSE_STEP_MS a 5, 20 e 40 msTempo di chiusura e livello di rumore misurato con il telefono
Durata delle batterieFallo funzionare con batterie cariche e misura la tensione ogni oraPer quante ore ha funzionato il cestino
La giuria chiede soprattutto dei punti deboli, quindi prepara le risposte. L'HC-SR04 vede male le superfici morbide e porose, perciò con un guanto spesso di stoffa il coperchio può aprirsi in ritardo. La misura del riempimento vede il punto più alto dei rifiuti, non la loro media. Un cestino con Uno dura solo 1–2 giorni a batterie. Dimostrare di conoscere questi limiti e proporre una soluzione per ognuno ti fa guadagnare molti più punti che presentare il progetto come se fosse perfetto.

Idee per migliorarlo

  • Cestino con display: Mostra la percentuale di riempimento e una barra su un display OLED.
  • Grazie a voce: Riproduci un breve "grazie" con un DFPlayer Mini ogni volta che il coperchio si apre.
  • Misura del peso: Metti una cella di carico con HX711 sotto il cestino per misurare quanti chili di rifiuti contiene e valuta insieme volume e peso.
  • Separatore per la raccolta differenziata: Usa un sensore di colore e un secondo servo per indirizzare i rifiuti in due scomparti.
  • Avviso di cattivo odore: Rileva con un MQ-135 quando l'aria dentro il cestino peggiora.
  • Cestino solare da esterno: Elimina del tutto il cavo con un piccolo pannello solare, una cella 18650, un modulo di ricarica TP4056 e un modulo step-up a 5 V.
  • Registro del riempimento: Salva le misure su Fogli Google con l'ESP32 e mostra in un grafico in quali giorni il cestino si riempie più in fretta.
  • Per tutta la scuola o il condominio: Controlla da centinaia di metri i cestini del cortile che il Wi-Fi non raggiunge, con un modulo LoRa.

Errori comuni e soluzioni

SintomoCausa probabileSoluzione
Il coperchio si apre e si chiude da soloIl sensore vede un muro vicino o le persone che passanoAbbassa HAND_CM, inclina il sensore verso l'alto, lascia 30 cm liberi davanti
Arduino si riavvia quando il coperchio si apreLa corrente di spunto del servo fa crollare la linea dei 5 VCondensatore da 470 µF, servo potente con alimentazione separata, GND in comune
Il servo ronza e si scalda a coperchio chiusoLID_CLOSED schiaccia il coperchio sul bordoArretra l'angolo di 2–3 gradi; non cancellare la riga con detach()
Il coperchio si ferma a metà o tremaCoppia insufficiente o assi disallineatiServo più potente, coperchio più leggero, contrappeso; allinea l'albero del servo alla cerniera
Il riempimento risulta alto anche a cestino vuotoBordo del sacchetto davanti al sensore o echi dalle pareti in un cestino strettoRipiega il sacchetto verso l'esterno, centra il sensore, misura di nuovo EMPTY_CM
Il riempimento non cambia maiIl sensore non riceve l'eco (999) o il filo ECHO si è staccatoOsserva il valore grezzo in cm nel monitor seriale e controlla i collegamenti
Sull'ESP32 la distanza è sempre 999 o assurdaPartitore su ECHO mancante, sbagliato o fili invertitiControlla il partitore 1 kΩ / 2 kΩ e l'ordine di TRIG ed ECHO
L'ESP32 si riavvia di continuoWi-Fi e servo assorbono corrente insieme, cavo USB scadenteUsa un cavo corto e di qualità, alimenta il servo con una sorgente separata da 5 V
Il messaggio Telegram non arrivaToken o chat ID sbagliati, /start non inviato al botLeggi il codice HTTP nel monitor seriale (401, 400, 403)
Quando cerchi un guasto, prova i componenti uno alla volta: prima verifica con lo sketch di calibrazione che i due sensori diano valori sensati nel monitor seriale, poi che il servo raggiunga senza problemi gli angoli di chiuso e aperto, e solo alla fine carica il codice principale. Se l'ESP32 mostra il messaggio "Brownout detector was triggered", cause e soluzioni sono nell'articolo L'ESP32 si riavvia di continuo.

Poiché riunisce in un solo progetto la lettura dei sensori, il controllo di un servo, la temporizzazione e la pianificazione dell'alimentazione, il cestino intelligente è uno dei modi più divertenti per imparare Arduino. Una volta costruito, realizzare un sensore di parcheggio o una porta automatica con gli stessi componenti diventa molto più facile. Nell'app BlueGrays trovi passo dopo passo i progetti di misura della distanza con l'HC-SR04, controllo dei servo, uso del buzzer, un sensore di parcheggio con ESP32 e un allarme di movimento con notifiche Telegram, con schemi elettrici e codice pronto all'uso.

Domande frequenti

Quali componenti servono per un cestino intelligente con Arduino?

Per la versione base bastano un Arduino Uno o Nano, un sensore a ultrasuoni HC-SR04, un servo adatto al peso del coperchio (SG90 per un coperchio leggero, MG996R per il coperchio di un vero cestino), cavetti jumper e un alimentatore da 5 V. Per misurare il riempimento si aggiungono un secondo HC-SR04, tre LED con resistenze da 220 Ω e un buzzer attivo. Se vuoi le notifiche sul telefono, puoi usare un ESP32 al posto di Arduino.

Come funziona un cestino intelligente?

Il sensore a ultrasuoni sul davanti emette un'onda sonora e misura quanto tempo impiega l'eco a tornare; quando vede una mano a meno di 20 cm, Arduino apre il coperchio con un servomotore. Qualche secondo dopo che la mano se n'è andata, il coperchio si chiude lentamente. Un secondo sensore sotto il coperchio misura l'altezza dei rifiuti e mostra il riempimento con i LED e un buzzer.

Perché il mio servo non riesce a sollevare il coperchio del cestino?

Di solito la coppia del servo è troppo bassa per il coperchio. La coppia necessaria è il peso del coperchio moltiplicato per la distanza del suo baricentro dalla cerniera; un coperchio da 120 g e 25 cm richiede circa 1,5 kg·cm, al limite di un SG90. Scegli un servo per il doppio del tuo calcolo, alleggerisci il coperchio o aggiungi un contrappeso, e verifica che l'alimentazione del servo fornisca abbastanza corrente.

Si può usare un sensore IR al posto dell'HC-SR04?

Sì, un sensore IR di ostacoli come l'FC-51 rileva una mano tra 2 e 30 cm e il codice è ancora più semplice, perché indica solo se c'è qualcosa oppure no. Però risente della luce del sole e vede più tardi le superfici scure. Un sensore PIR non è adatto al coperchio di un cestino, perché scatta per chiunque passi davanti.

Quanto dura a batterie un cestino intelligente con Arduino?

Un cestino con Arduino Uno assorbe in media 70–80 mA, quindi dura circa un giorno con 6 pile AA, un giorno e mezzo con due celle 18650 e 3–4 giorni con un power bank da 10.000 mAh. Per l'uso fisso si consiglia un alimentatore da 5 V. Un Pro Mini, la modalità di sospensione e i LED accesi solo quando serve possono allungare la durata di 4–5 volte.

Come ricevo una notifica sul telefono quando il cestino è pieno?

L'ESP32 si collega al Wi-Fi e, quando il riempimento supera la percentuale che hai impostato, invia una richiesta alla Bot API di Telegram che ti recapita un messaggio sul telefono. Per farlo servono un token del bot ottenuto da BotFather e il tuo chat ID. Per non inviare lo stesso avviso più volte, il codice aspetta che il cestino venga svuotato.

Vedi tutti i progetti
HC-SR04 Misura della distanza (display seriale)
#27Principiante

HC-SR04 Misura della distanza (display seriale)

Il sensore HC-SR04 funziona come un pipistrello che trova la strada al buio: manda un suono che non puoi sentire, ne ascolta l'eco e misura quanto è lontano l'oggetto davanti a sé. L'Arduino scrive qu

4.6(45)
68
Uso del cicalino
#13Principiante

Uso del cicalino

Ti sei mai chiesto come fa un campanello o un allarme a suonare? L'Arduino fa suonare un piccolo cicalino a intervalli brevi: bip, pausa, bip, pausa. Vedi come l'elettricità diventa suono e fai il pri

4.7(89)
158
Utilizzo del servomotore
#35Principiante
Premium

Utilizzo del servomotore

Il braccio di un piccolo servomotore ruota a turno in tre posizioni: tutto all'inizio, esattamente a metà e tutto alla fine, aspettando due secondi a ogni fermata. L'Arduino dice al motore a quale ang

4.7(48)
67
Sensore di parcheggio con HC-SR04
#28Intermedio
Premium

Sensore di parcheggio con HC-SR04

Una versione in miniatura del sensore di parcheggio delle auto: più qualcosa si avvicina, più i bip accelerano. Il sensore di distanza misura l'oggetto davanti a sé; se è a più di 20 centimetri resta

4.8(64)
84
ESP32 HC-SR04 Misuratore di distanza a ultrasuoni
#116Intermedio

ESP32 HC-SR04 Misuratore di distanza a ultrasuoni

Il sensore a ultrasuoni HC-SR04 calcola la distanza misurando il tempo di volo (Time of Flight) dell'onda sonora che emette dopo aver colpito un oggetto e essere rimbalzata indietro. In questo progetto invieremo un impulso di 10 microsecondi dall'ESP32 al pin TRIG del sensore e leggeremo il tempo di eco proveniente dal pin ECHO con `pulseIn()`. Visualizziamo la distanza calcolata in cm su un display LCD I2C 16x2. Poiché l'HC-SR04 funziona a 5V, è necessario un **divisore di tensione** sul pin EC

4.9(28)
44
Servomotore ESP32 (controllo tramite potenziometro)
#118Intermedio
Premium

Servomotore ESP32 (controllo tramite potenziometro)

In questo progetto controlliamo un servomotore SG90 tramite un potenziometro. Regoliamo l'angolo del servomotore mappando il valore analogico letto dal potenziometro su un intervallo compreso tra 0 e 180 gradi. La libreria ESP32Servo genera segnali per servomotori sui GPIO adatti dell'ESP32 che possono funzionare come uscite; in questo progetto si utilizza GPIO 18. Si tratta di un metodo di controllo fondamentale per bracci robotici, gimbal per telecamere e meccanismi di porte automatiche.

4.3(29)
42
Sensore di parcheggio ESP32 (HC-SR04 + barra a 5 LED + cicalino + OLED)
#142Avanzato
Premium

Sensore di parcheggio ESP32 (HC-SR04 + barra a 5 LED + cicalino + OLED)

La versione ESP32 del sensore di parcheggio per auto! Grazie al sensore a ultrasuoni HC-SR04 misuriamo la distanza dall'oggetto davanti al veicolo. Man mano che la distanza diminuisce, la barra composta da 5 LED si riempie (da verde → giallo → rosso) e l'intervallo tra i bip del cicalino si accorcia. Quando ci si avvicina troppo (meno di 10 cm), l'allarme suona ininterrottamente e tutti i LED si accendono. Sul display OLED SSD1306 vengono visualizzati in caratteri grandi il valore istantaneo in

0.0(0)
14
Sistema di Allarme Movimento ESP32 (PIR + Buzzer + LED + Telegram)
#145Avanzato
Premium

Sistema di Allarme Movimento ESP32 (PIR + Buzzer + LED + Telegram)

Un sistema di allarme di sicurezza intelligente che attiva buzzer e LED quando il sensore PIR rileva movimento, inviando contemporaneamente una notifica istantanea al proprietario tramite Telegram. Dispone di un tempo di attesa di 30 secondi per limitare la frequenza degli allarmi ripetuti; questo meccanismo non verifica la validità dei rilevamenti del sensore PIR né elimina le false attivazioni. Adatto a sicurezza domestica, monitoraggio di garage, magazzini o studi. Con l'ESP32-CAM è possibile

0.0(0)
8