Semaforo Arduino: circuito, codice, pulsante pedonale e incrocio
Come fare un semaforo con Arduino: circuito con 3 LED e 220 Ω, codice con delay e millis, pulsante pedonale, incrocio a due vie, Tinkercad, ESP32 e Pico.
Il semaforo con Arduino è il classico primo progetto di elettronica: tre LED, rosso, giallo e verde, si accendono e si spengono in sequenza proprio come un semaforo vero. Ti bastano un Arduino Uno, tre LED, tre resistenze da 220 Ω e qualche cavetto jumper, e la prima versione funzionante è pronta in mezz'ora. Eppure questo progetto semplice insegna moltissimo: uscite digitali, calcolo della resistenza per il LED, temporizzazione, lettura di un pulsante e la macchina a stati, il modo più pulito per organizzare il codice.
In questa guida vedrai prima in che ordine e per quanto tempo cambia un semaforo vero, poi monterai il circuito e scriverai il codice più semplice con delay(). Dopo aggiungerai un pulsante per i pedoni, riscriverai la stessa logica con millis() e una macchina a stati per non bloccare mai il programma e controllerai un incrocio a due direzioni. Nelle ultime sezioni trovi come provare il progetto su Tinkercad senza installare nulla, le versioni per ESP32 e Raspberry Pi Pico (MicroPython) e i consigli per scrivere la relazione del progetto per la scuola.

Come funziona un semaforo vero?
Un semaforo è un temporizzatore che dà la precedenza a turno a ciascun flusso di traffico. In Germania, Regno Unito e Turchia il ciclo è rosso → rosso + giallo → verde → giallo → rosso. Rosso e giallo accesi insieme significano "sta per scattare il verde, preparati ma non partire ancora", mentre il giallo da solo significa "sta per scattare il rosso, preparati a fermarti". In Italia, come negli Stati Uniti, in Francia e in Spagna, la fase rosso + giallo non esiste: si passa direttamente dal rosso al verde. Anche la durata del giallo non è casuale: in Germania sono previsti 3 secondi di giallo sulle strade con limite di 50 km/h, 4 secondi a 60 km/h e 5 secondi a 70 km/h, perché chi è troppo vicino per fermarsi quando scatta il giallo deve poter liberare l'incrocio in sicurezza.
| Fase | Luce accesa | Significato | Durata in questa guida |
|---|---|---|---|
| 1 | Rosso | Fermati | 5 s |
| 2 | Rosso + giallo | Sta per scattare il verde, preparati | 1 s |
| 3 | Verde | Passa | 5 s |
| 4 | Giallo | Sta per scattare il rosso, fermati | 3 s |
| All'incrocio | Tutte le direzioni rosse | Margine di sicurezza per liberare l'incrocio | 1 s |
Materiali e calcolo della resistenza da 220 Ω
| Componente | Quantità | Nota |
|---|---|---|
| Arduino Uno (o Nano) | 1 | Va bene anche un ESP32 o un Raspberry Pi Pico |
| LED da 5 mm: rosso, giallo, verde | 1 per colore | +1 rosso e +1 verde per il semaforo pedonale, +1 set per l'incrocio |
| Resistenza da 220 Ω | 3 | Una per LED; 8 per tutte le versioni |
| Pulsante (6×6 mm) | 1 | Solo nella versione pedonale |
| Breadboard | 1 | Basta una mezza breadboard (400 fori) |
| Cavetti jumper | 10–20 | Maschio-maschio |
| Cavo USB | 1 | Per caricare il codice e alimentare la scheda |
I LED non limitano da soli la corrente: un LED collegato direttamente a 5 V assorbe più di quanto lui e il pin possano sopportare. Per questo ogni LED ha una resistenza in serie. Il valore si ricava con la legge di Ohm: R = (tensione di alimentazione − tensione diretta del LED) / corrente. Per un LED rosso, (5 V − 2,0 V) / 0,015 A ≈ 200 Ω; con il valore standard più vicino, 220 Ω, la corrente è di circa 13,6 mA. Il LED si accende con una buona luminosità e si resta sotto i 20 mA consigliati per un pin di Arduino. Una resistenza da 220 Ω ha i colori rosso-rosso-marrone-oro se ha quattro fasce e rosso-rosso-nero-nero-marrone se ne ha cinque; per sicurezza usa lo strumento codice colori delle resistenze e, per altri colori e tensioni, il calcolatore della resistenza per LED.
| Colore del LED | Tensione diretta (circa) | Corrente a 5 V (220 Ω) | Corrente a 3,3 V (220 Ω) |
|---|---|---|---|
| Rosso | 1,8–2,2 V | ≈ 13,6 mA | ≈ 5,9 mA |
| Giallo | 1,9–2,2 V | ≈ 13,2 mA | ≈ 5,5 mA |
| Verde (verde giallastro) | 2,0–2,4 V | ≈ 13,2 mA | ≈ 5,5 mA |
| Verde (brillante, verde puro) | 2,8–3,4 V | ≈ 9,1 mA | ≈ 1,4 mA (molto debole) |
Collegamenti del circuito e tabella dei pin
La gamba lunga di ogni LED (anodo, +) va a un pin digitale di Arduino attraverso una resistenza, quella corta (catodo, −) va a GND. Non importa se la resistenza sta dal lato dell'anodo o del catodo, basta che sia in serie. Sulla breadboard il modo più ordinato è collegare il pin GND di Arduino alla linea blu (−) e portarci le gambe corte dei tre LED. La tabella qui sotto riporta i pin usati da tutti gli sketch di questa guida; il primo sketch usa solo le prime tre righe. Puoi vedere la piedinatura della tua scheda nello strumento Pinout.
| Funzione | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Rosso veicoli (direzione A) | D10 | GPIO 5 | GP13 |
| Giallo veicoli (direzione A) | D9 | GPIO 18 | GP12 |
| Verde veicoli (direzione A) | D8 | GPIO 19 | GP11 |
| Rosso pedoni | D7 | GPIO 21 | GP15 |
| Verde pedoni | D6 | GPIO 22 | GP14 |
| Pulsante pedonale (altra gamba a GND) | D2 | GPIO 23 | GP4 |
| Direzione B rosso / giallo / verde | D5 / D4 / D3 | GPIO 25 / 26 / 27 | GP8 / GP7 / GP6 |
| Catodi dei LED e pulsante | GND | GND | GND |
Passo 1: il codice del semaforo più semplice con delay()
1// Arduino trafik lambası: delay() ile en basit sürüm / simplest version with delay()
2const int KIRMIZI = 10; // kırmızı LED / red LED
3const int SARI = 9; // sarı LED / yellow LED
4const int YESIL = 8; // yeşil LED / green LED
5
6// Üç LED'i tek satırda ayarla / set all three LEDs in one line
7void lambalar(int k, int s, int y) {
8 digitalWrite(KIRMIZI, k);
9 digitalWrite(SARI, s);
10 digitalWrite(YESIL, y);
11}
12
13void setup() {
14 pinMode(KIRMIZI, OUTPUT);
15 pinMode(SARI, OUTPUT);
16 pinMode(YESIL, OUTPUT);
17}
18
19void loop() {
20 lambalar(HIGH, LOW, LOW); // DUR: kırmızı / STOP: red
21 delay(5000);
22 lambalar(HIGH, HIGH, LOW); // HAZIRLAN: kırmızı + sarı / GET READY: red + yellow
23 delay(1000);
24 lambalar(LOW, LOW, HIGH); // GEÇ: yeşil / GO: green
25 delay(5000);
26 lambalar(LOW, HIGH, LOW); // YAVAŞLA: sarı / SLOW DOWN: yellow
27 delay(3000);
28}Lo sketch imposta i tre pin come uscite in setup() ed esegue le quattro fasi in ordine dentro loop(). Invece di scrivere ogni volta tre righe digitalWrite usiamo una piccola funzione chiamata lambalar() ("luci"), così ogni fase sta in una riga e gli errori si vedono subito. Per caricarlo scegli Arduino Uno come scheda e la porta giusta nel menu Strumenti, poi premi Carica. Se il caricamento non va a buon fine, segui la lista di controllo in Arduino non carica lo sketch. Per cambiare i tempi basta modificare i millisecondi dentro delay(): 5000 significa 5 secondi.
Passo 2: semaforo con pulsante pedonale
I semafori a chiamata pedonale che vedi in città lasciano il verde alle auto e danno il passaggio ai pedoni solo quando qualcuno preme il pulsante. In questa versione aggiungiamo al semaforo dei veicoli un semaforo pedonale a due LED (rosso e verde) e un pulsante su D2. Una gamba del pulsante va a D2 e quella opposta in diagonale a GND; dato che il codice usa INPUT_PULLUP, non serve alcuna resistenza esterna e il pin legge LOW quando il pulsante è premuto. I collegamenti interni di un pulsante a quattro gambe e il debounce sono spiegati in Come collegare un pulsante ad Arduino.

1// Yaya butonlu trafik lambası (delay ile) / pedestrian push-button light (with delay)
2const int KIRMIZI = 10, SARI = 9, YESIL = 8; // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6; // yaya lambası / pedestrian light
4const int BUTON = 2; // diğer ucu GND / other leg to GND
5
6void setup() {
7 pinMode(KIRMIZI, OUTPUT);
8 pinMode(SARI, OUTPUT);
9 pinMode(YESIL, OUTPUT);
10 pinMode(YAYA_KIRMIZI, OUTPUT);
11 pinMode(YAYA_YESIL, OUTPUT);
12 pinMode(BUTON, INPUT_PULLUP); // basılınca LOW okunur / reads LOW when pressed
13 digitalWrite(YESIL, HIGH); // araçlar geçer / cars go
14 digitalWrite(YAYA_KIRMIZI, HIGH); // yayalar bekler / pedestrians wait
15}
16
17void loop() {
18 if (digitalRead(BUTON) == LOW) { // yaya butona bastı / pedestrian pressed
19 delay(2000); // araçlara kısa süre tanı / short grace time for cars
20 digitalWrite(YESIL, LOW);
21 digitalWrite(SARI, HIGH); // sarı / yellow
22 delay(3000);
23 digitalWrite(SARI, LOW);
24 digitalWrite(KIRMIZI, HIGH); // araçlar durur / cars stop
25 delay(1000); // tüm kırmızı güvenlik payı / all-red safety gap
26 digitalWrite(YAYA_KIRMIZI, LOW);
27 digitalWrite(YAYA_YESIL, HIGH); // yayalar geçer / pedestrians cross
28 delay(6000);
29 for (int i = 0; i < 6; i++) { // yaya yeşili 3 kez yanıp söner / green man flashes 3 times
30 digitalWrite(YAYA_YESIL, i % 2 == 0 ? LOW : HIGH);
31 delay(500);
32 }
33 digitalWrite(YAYA_YESIL, LOW);
34 digitalWrite(YAYA_KIRMIZI, HIGH);
35 delay(1000); // tüm kırmızı / all red
36 digitalWrite(SARI, HIGH); // kırmızı + sarı / red + yellow
37 delay(1000);
38 digitalWrite(KIRMIZI, LOW);
39 digitalWrite(SARI, LOW);
40 digitalWrite(YESIL, HIGH); // araçlar yeniden geçer / cars go again
41 delay(8000); // en kısa yeşil süresi / minimum green time
42 }
43}Questo sketch funziona, ma ha un grosso punto debole: dopo la pressione del pulsante Arduino resta fermo per circa 25 secondi nelle righe delay(). Una seconda pressione in quel periodo non viene vista, non si può leggere la porta seriale né aggiornare un display. Le pressioni durante l'attesa finale di 8 secondi vanno perse del tutto: il pedone preme, rilascia, non succede nulla e deve premere di nuovo. Aggiungere un suono o un conto alla rovescia con questa struttura diventa ancora più complicato. La soluzione è smettere di aspettare e cominciare a misurare il tempo.
Passo 3: senza blocchi con millis() e una macchina a stati
Una macchina a stati è un modo di progettare in cui il sistema si trova sempre in un solo stato e passa al successivo quando si verificano certe condizioni. Il semaforo ne è l'esempio perfetto: "verde per le auto", "giallo per le auto" e "pedoni che attraversano" sono ciascuno uno stato. Il codice non aspetta più con delay(); a ogni giro loop() si fa solo due domande: in che stato sono e da quanto tempo ci sono? Il tempo trascorso si misura con millis() - durumBaslangic (l'istante in cui è iniziato lo stato) e, quando è scaduto, si passa allo stato successivo. Se millis() è nuovo per te, leggi prima Arduino millis() invece di delay(). Scrivere gli stati in una tabella prima di programmare semplifica molto il lavoro:
| Stato | Semaforo veicoli | Semaforo pedonale | Durata | Stato successivo |
|---|---|---|---|---|
| ARAC_YESIL | Verde | Rosso | Almeno 8 s, finché arriva una richiesta | ARAC_SARI |
| ARAC_SARI | Giallo | Rosso | 3 s | YAYA_BEKLE |
| YAYA_BEKLE | Rosso | Rosso | 1 s | YAYA_GECER |
| YAYA_GECER | Rosso | Verde | 6 s | YAYA_FLAS |
| YAYA_FLAS | Rosso | Verde lampeggiante | 3 s | YAYA_BITTI |
| YAYA_BITTI | Rosso | Rosso | 1 s | KIRMIZI_SARI |
| KIRMIZI_SARI | Rosso + giallo | Rosso | 1 s | ARAC_YESIL |
1// Yaya butonlu trafik lambası: millis() + durum makinesi / pedestrian light: millis() + state machine
2const int KIRMIZI = 10, SARI = 9, YESIL = 8; // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6; // yaya lambası / pedestrian light
4const int BUTON = 2; // diğer ucu GND / other leg to GND
5
6enum Durum { ARAC_YESIL, ARAC_SARI, YAYA_BEKLE, YAYA_GECER, YAYA_FLAS, YAYA_BITTI, KIRMIZI_SARI };
7const char* const ISIM[] = { "ARAC_YESIL", "ARAC_SARI", "YAYA_BEKLE", "YAYA_GECER",
8 "YAYA_FLAS", "YAYA_BITTI", "KIRMIZI_SARI" };
9
10// Süreler (ms) / durations (ms)
11const unsigned long MIN_YESIL = 8000; // araçlara en az yeşil / minimum green for cars
12const unsigned long SARI_SURE = 3000; // sarı / yellow
13const unsigned long GUVENLIK = 1000; // tüm kırmızı / all red
14const unsigned long YAYA_SURE = 6000; // yaya yeşili / pedestrian green
15const unsigned long FLAS_SURE = 3000; // yanıp sönen yaya yeşili / flashing green man
16const unsigned long KS_SURE = 1000; // kırmızı + sarı / red + yellow
17
18Durum durum = ARAC_YESIL;
19unsigned long durumBaslangic = 0; // durumun başladığı an / when the state started
20bool yayaTalebi = false; // butona basıldı mı? / was the button pressed?
21
22void aracLamba(bool k, bool s, bool y) {
23 digitalWrite(KIRMIZI, k);
24 digitalWrite(SARI, s);
25 digitalWrite(YESIL, y);
26}
27
28void yayaLamba(bool k, bool y) {
29 digitalWrite(YAYA_KIRMIZI, k);
30 digitalWrite(YAYA_YESIL, y);
31}
32
33// Yeni duruma geç: lambaları ayarla, saati başlat / enter a state: set the lamps, restart the clock
34void gec(Durum yeni) {
35 durum = yeni;
36 durumBaslangic = millis();
37 switch (yeni) {
38 case ARAC_YESIL: aracLamba(0, 0, 1); yayaLamba(1, 0); break;
39 case ARAC_SARI: aracLamba(0, 1, 0); yayaLamba(1, 0); break;
40 case YAYA_BEKLE: aracLamba(1, 0, 0); yayaLamba(1, 0); break;
41 case YAYA_GECER: aracLamba(1, 0, 0); yayaLamba(0, 1); yayaTalebi = false; break;
42 case YAYA_FLAS: aracLamba(1, 0, 0); yayaLamba(0, 1); break;
43 case YAYA_BITTI: aracLamba(1, 0, 0); yayaLamba(1, 0); break;
44 case KIRMIZI_SARI: aracLamba(1, 1, 0); yayaLamba(1, 0); break;
45 }
46 Serial.println(ISIM[yeni]); // durum günlüğü / state log
47}
48
49void setup() {
50 Serial.begin(9600);
51 pinMode(KIRMIZI, OUTPUT);
52 pinMode(SARI, OUTPUT);
53 pinMode(YESIL, OUTPUT);
54 pinMode(YAYA_KIRMIZI, OUTPUT);
55 pinMode(YAYA_YESIL, OUTPUT);
56 pinMode(BUTON, INPUT_PULLUP);
57 gec(ARAC_YESIL);
58}
59
60void loop() {
61 unsigned long gecen = millis() - durumBaslangic; // bu durumda geçen süre / time spent in this state
62
63 // yaya yeşili yanarken basış yok sayılır / presses during the green man are ignored
64 if (digitalRead(BUTON) == LOW && durum != YAYA_GECER) {
65 yayaTalebi = true; // talebi hatırla / remember the request
66 }
67
68 switch (durum) {
69 case ARAC_YESIL:
70 if (yayaTalebi && gecen >= MIN_YESIL) gec(ARAC_SARI);
71 break;
72 case ARAC_SARI:
73 if (gecen >= SARI_SURE) gec(YAYA_BEKLE);
74 break;
75 case YAYA_BEKLE:
76 if (gecen >= GUVENLIK) gec(YAYA_GECER);
77 break;
78 case YAYA_GECER:
79 if (gecen >= YAYA_SURE) gec(YAYA_FLAS);
80 break;
81 case YAYA_FLAS:
82 digitalWrite(YAYA_YESIL, (gecen / 250) % 2 == 0); // 250 ms'de bir yanıp söner / toggles every 250 ms
83 if (gecen >= FLAS_SURE) gec(YAYA_BITTI);
84 break;
85 case YAYA_BITTI:
86 if (gecen >= GUVENLIK) gec(KIRMIZI_SARI);
87 break;
88 case KIRMIZI_SARI:
89 if (gecen >= KS_SURE) gec(ARAC_YESIL);
90 break;
91 }
92}Il cuore dello sketch è la funzione gec() ("passa a"): salva il nuovo stato, fa ripartire il tempo, imposta le luci per quello stato e scrive il suo nome sul monitor seriale. Lo switch dentro loop() controlla solo se il tempo è scaduto. Premere il pulsante non provoca nulla subito: alza il flag yayaTalebi (richiesta pedonale) e il ciclo parte quando le auto hanno avuto almeno 8 secondi di verde. Ora nessuna pressione va persa e, siccome il pulsante cambia solo un flag, nemmeno i rimbalzi meccanici creano problemi. Le pressioni mentre il verde pedonale è già acceso vengono ignorate; altrimenti partirebbe un nuovo ciclo inutile subito dopo l'attraversamento dei pedoni. Apri il monitor seriale a 9600 baud per vedere il nome di ogni transizione: questo output aiuta nel debug e anche nella relazione.
Passo 4: un incrocio a due direzioni
In un incrocio vero i semafori delle due strade sono collegati: mentre la strada A è verde, la B deve essere rossa. Questa versione usa sei LED: D10, D9 e D8 per la direzione A e D5, D4 e D3 per la direzione B. Un dettaglio importante è il breve intervallo di tutto rosso: quando una direzione diventa rossa, l'altra non diventa subito verde; il controllore aspetta 1 secondo perché i veicoli ancora dentro l'incrocio possano uscire. Questa volta gli stati stanno in una tabella (un array) invece che in uno switch: per ogni passo le luci accese sono salvate come bit di un solo byte e la durata in millisecondi.
1// İki yönlü kavşak: tablo tabanlı durum makinesi / two-way junction: table-driven state machine
2const byte A_KIRMIZI = 10, A_SARI = 9, A_YESIL = 8; // A yönü (ana yol) / direction A (main road)
3const byte B_KIRMIZI = 5, B_SARI = 4, B_YESIL = 3; // B yönü (ara yol) / direction B (side road)
4const byte PINLER[6] = { A_KIRMIZI, A_SARI, A_YESIL, B_KIRMIZI, B_SARI, B_YESIL };
5
6struct Adim {
7 byte maske; // bit sırası: A_K A_S A_Y B_K B_S B_Y / bit order: A_R A_Y A_G B_R B_Y B_G
8 unsigned long sure; // ms
9};
10
11const Adim PLAN[] = {
12 { 0b001100, 8000 }, // A yeşil, B kırmızı / A green, B red
13 { 0b010100, 3000 }, // A sarı, B kırmızı / A yellow, B red
14 { 0b100100, 1000 }, // tüm yönler kırmızı / all red
15 { 0b100110, 1000 }, // A kırmızı, B kırmızı + sarı / A red, B red + yellow
16 { 0b100001, 5000 }, // A kırmızı, B yeşil / A red, B green
17 { 0b100010, 3000 }, // A kırmızı, B sarı / A red, B yellow
18 { 0b100100, 1000 }, // tüm yönler kırmızı / all red
19 { 0b110100, 1000 }, // A kırmızı + sarı, B kırmızı / A red + yellow, B red
20};
21const byte ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
22
23byte adim = 0;
24unsigned long adimBaslangic = 0;
25
26void uygula(byte maske) {
27 if ((maske & 0b001000) && (maske & 0b000001)) { // iki yeşil aynı anda yanamaz / never two greens
28 maske = 0b100100; // güvenli durum: tüm kırmızı / fail-safe: all red
29 }
30 for (byte i = 0; i < 6; i++) {
31 digitalWrite(PINLER[i], (maske >> (5 - i)) & 1); // soldaki bit A_KIRMIZI / leftmost bit is A_RED
32 }
33}
34
35void setup() {
36 for (byte i = 0; i < 6; i++) pinMode(PINLER[i], OUTPUT);
37 uygula(PLAN[0].maske);
38 adimBaslangic = millis();
39}
40
41void loop() {
42 if (millis() - adimBaslangic >= PLAN[adim].sure) {
43 adimBaslangic += PLAN[adim].sure; // kaymasız zamanlama / drift-free timing
44 adim = (adim + 1) % ADIM_SAYISI;
45 uygula(PLAN[adim].maske);
46 }
47 // Burada buton, sensör ya da ekran işleri çalışabilir / buttons, sensors or a display can run here
48}Ogni riga dell'array PLAN è un passo. Nei numeri binari come 0b001100 i bit da sinistra a destra rappresentano A rosso, A giallo, A verde, B rosso, B giallo e B verde; 1 significa acceso e 0 spento. La funzione uygula() ("applica") scrive in ordine questi sei bit sui pin. Il controllo all'inizio della funzione impedisce che per un errore di battitura entrambe le direzioni siano verdi insieme e mette tutto sul rosso. Anche i veri controllori semaforici hanno un circuito di sicurezza separato che rileva i verdi in conflitto. Per cambiare i tempi o aggiungere passi modifichi solo la tabella, senza toccare il resto del codice. Se con più LED i pin non bastano, uno shift register 74HC595 ti dà otto uscite usando tre pin.
Provalo su Tinkercad: simulazione senza componenti
- Crea un account gratuito su tinkercad.com e avvia un nuovo circuito nella sezione Circuits (Circuiti).
- Trascina dall'elenco dei componenti a destra un Arduino Uno R3, una breadboard, tre LED e tre resistenze. Fai clic su un LED per sceglierne il colore e su una resistenza per impostarla a 220 Ω.
- Disegna i fili: anodi dei LED a D10, D9 e D8 passando per le resistenze, catodi a GND. Dare un colore ai fili (nero per GND, per esempio) rende lo schema più leggibile.
- Premi Code (Codice), scegli Text (Testo) dal menu a tendina, cancella il codice di esempio e incolla lo sketch di questa guida.
- Premi Start Simulation (Avvia simulazione); i LED iniziano a cambiare in sequenza. L'output seriale compare nel Serial Monitor in fondo al pannello del codice.
Tinkercad è il modo più semplice per provare collegamenti e codice prima di usare componenti veri; se dimentichi una resistenza, il simulatore mostra sul LED un avviso che la corrente è troppo alta. Per provare la versione pedonale basta cliccare con il mouse sul pulsante durante la simulazione. Su un computer lento la simulazione può andare più piano del tempo reale, quindi i tempi possono sembrare un po' più lunghi. Per un compito scolastico puoi consegnare il link di condivisione di Tinkercad o uno screenshot del circuito. Trovi istruzioni dettagliate in Simulazione Arduino con Tinkercad e un'alternativa che supporta anche ESP32 e Pico in Wokwi.
Semaforo con ESP32 e modalità notturna
Gli sketch Arduino di questa guida funzionano anche su ESP32: scegli ESP32 Dev Module come scheda nell'IDE di Arduino e cambia le costanti dei pin come nella tabella. La versione qui sotto aggiunge una funzione: la modalità notturna. Nelle ore di poco traffico molti incroci passano al giallo lampeggiante. Le schede ESP32 DevKit hanno un pulsante BOOT collegato a GPIO 0, quindi non serve nemmeno un pulsante in più: dopo l'avvio si legge come un normale ingresso. Ogni pressione alterna tra modalità normale e notturna.
1// ESP32 trafik lambası + gece modu (BOOT düğmesi) / ESP32 traffic light + night mode (BOOT button)
2const int KIRMIZI = 5; // GPIO 5
3const int SARI = 18; // GPIO 18
4const int YESIL = 19; // GPIO 19
5const int BOOT_BUTON = 0; // kart üzerindeki BOOT düğmesi / on-board BOOT button
6
7struct Adim { bool k, s, y; unsigned long sure; };
8const Adim PLAN[] = {
9 { true, false, false, 5000 }, // kırmızı / red
10 { true, true, false, 1000 }, // kırmızı + sarı / red + yellow
11 { false, false, true, 5000 }, // yeşil / green
12 { false, true, false, 3000 }, // sarı / yellow
13};
14const int ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
15
16int adim = 0;
17unsigned long adimBaslangic = 0;
18bool geceModu = false;
19int sonOkuma = HIGH, kararli = HIGH; // debounce için / for debouncing
20unsigned long degisimZamani = 0;
21
22void lambalar(bool k, bool s, bool y) {
23 digitalWrite(KIRMIZI, k);
24 digitalWrite(SARI, s);
25 digitalWrite(YESIL, y);
26}
27
28void setup() {
29 Serial.begin(115200);
30 pinMode(KIRMIZI, OUTPUT);
31 pinMode(SARI, OUTPUT);
32 pinMode(YESIL, OUTPUT);
33 pinMode(BOOT_BUTON, INPUT_PULLUP);
34 lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
35 adimBaslangic = millis();
36}
37
38void loop() {
39 unsigned long simdi = millis();
40
41 // 50 ms kararlı kalan basışı kabul et / accept a press that stays stable for 50 ms
42 int okuma = digitalRead(BOOT_BUTON);
43 if (okuma != sonOkuma) {
44 sonOkuma = okuma;
45 degisimZamani = simdi;
46 }
47 if (simdi - degisimZamani >= 50 && okuma != kararli) {
48 kararli = okuma;
49 if (kararli == LOW) { // yeni basış / new press
50 geceModu = !geceModu;
51 Serial.println(geceModu ? "Gece modu / Night mode" : "Normal mod / Normal mode");
52 adim = 0; // normal mod kırmızıdan başlar / normal mode restarts on red
53 adimBaslangic = simdi;
54 lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
55 }
56 }
57
58 if (geceModu) {
59 lambalar(false, (simdi / 500) % 2 == 0, false); // yanıp sönen sarı / flashing yellow
60 return;
61 }
62
63 if (simdi - adimBaslangic >= PLAN[adim].sure) {
64 adimBaslangic = simdi;
65 adim = (adim + 1) % ADIM_SAYISI;
66 lambalar(PLAN[adim].k, PLAN[adim].s, PLAN[adim].y);
67 }
68}La lettura del pulsante include un semplice debounce: una pressione conta solo se il valore letto resta invariato per 50 ms. La tabella PLAN con gli stati delle luci è una forma più leggibile dell'idea usata nello sketch dell'incrocio. Dato che i pin dell'ESP32 forniscono 3,3 V, i LED assorbono meno corrente che su Arduino e risultano un po' meno luminosi. I LED rosso e giallo vanno bene con 220 Ω, ma un LED verde brillante con tensione diretta di circa 3 V può restare molto debole; in quel caso scendi a 100 Ω per il verde. Se sei alle prime armi con l'ESP32, Cos'è l'ESP32? è un buon punto di partenza per installazione e pin sicuri.
Semaforo pedonale con Raspberry Pi Pico (MicroPython)
Sul Raspberry Pi Pico scriviamo la stessa macchina a stati in MicroPython. Questa volta gli stati stanno in una lista: ogni riga contiene il nome dello stato, i valori delle cinque luci e la durata. Il pulsante usa un interrupt: quando GP4 scende a LOW viene eseguita la funzione buton_kesmesi() ("interrupt del pulsante"), che alza soltanto il flag di richiesta. Se salvi il codice sul Pico come main.py con Thonny, partirà da solo a ogni accensione.
1# Raspberry Pi Pico (MicroPython): yaya butonlu trafik lambası / pedestrian traffic light
2from machine import Pin
3import time
4
5yesil = Pin(11, Pin.OUT) # araç yeşil / vehicle green
6sari = Pin(12, Pin.OUT) # araç sarı / vehicle yellow
7kirmizi = Pin(13, Pin.OUT) # araç kırmızı / vehicle red
8yaya_yesil = Pin(14, Pin.OUT) # yaya yeşil / pedestrian green
9yaya_kirmizi = Pin(15, Pin.OUT) # yaya kırmızı / pedestrian red
10buton = Pin(4, Pin.IN, Pin.PULL_UP) # GP4, diğer ucu GND / other leg to GND
11
12# (durum, kırmızı, sarı, yeşil, yaya kırmızı, yaya yeşil, süre ms)
13# (state, red, yellow, green, ped red, ped green, duration ms)
14DURUMLAR = [
15 ("ARAC_YESIL", 0, 0, 1, 1, 0, 8000), # en az yeşil süresi / minimum green
16 ("ARAC_SARI", 0, 1, 0, 1, 0, 3000),
17 ("YAYA_BEKLE", 1, 0, 0, 1, 0, 1000), # tüm kırmızı / all red
18 ("YAYA_GECER", 1, 0, 0, 0, 1, 6000),
19 ("YAYA_FLAS", 1, 0, 0, 0, 1, 3000),
20 ("YAYA_BITTI", 1, 0, 0, 1, 0, 1000), # tüm kırmızı / all red
21 ("KIRMIZI_SARI", 1, 1, 0, 1, 0, 1000),
22]
23
24talep = False
25adim = 0 # şu anki durumun sırası / index of the current state
26
27def buton_kesmesi(pin): # kesmede yalnız bayrak kaldır / only set a flag in the IRQ
28 global talep
29 if DURUMLAR[adim][0] != "YAYA_GECER": # yaya yeşilinde basış yok sayılır / ignored during the green man
30 talep = True
31
32buton.irq(trigger=Pin.IRQ_FALLING, handler=buton_kesmesi)
33
34def uygula(i):
35 global talep
36 ad, k, s, y, yk, yy, _ = DURUMLAR[i]
37 kirmizi.value(k)
38 sari.value(s)
39 yesil.value(y)
40 yaya_kirmizi.value(yk)
41 yaya_yesil.value(yy)
42 if ad == "YAYA_GECER":
43 talep = False # talep karşılandı / request served
44 print("Durum / state:", ad)
45
46uygula(adim)
47baslangic = time.ticks_ms()
48
49while True:
50 gecen = time.ticks_diff(time.ticks_ms(), baslangic) # taşmaya karşı güvenli / wrap-safe
51 ad = DURUMLAR[adim][0]
52
53 if ad == "YAYA_FLAS":
54 yaya_yesil.value((gecen // 250) % 2 == 0) # yanıp sönen yaya yeşili / flashing green man
55
56 if gecen >= DURUMLAR[adim][6] and (ad != "ARAC_YESIL" or talep):
57 adim = (adim + 1) % len(DURUMLAR) # yeşil, talep gelene kadar sürer / green lasts until a request
58 uygula(adim)
59 baslangic = time.ticks_ms()
60
61 time.sleep_ms(10)In MicroPython si usa time.ticks_ms() al posto di millis() e time.ticks_diff() al posto di una semplice sottrazione; il contatore dei ticks riparte da zero dopo un certo valore, quindi sottrarre direttamente due letture può dare risultati sbagliati. Tieni corta la funzione di interrupt: niente print, sleep o calcoli lunghi, cambia solo un flag. Anche i pin del Pico lavorano a 3,3 V, quindi la nota sul LED verde della sezione ESP32 vale anche qui. Come installare MicroPython e la numerazione dei pin sono spiegati in Cos'è il Raspberry Pi Pico?, le basi del linguaggio in Basi di MicroPython.
Errori comuni e soluzioni
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| Non si accende nessun LED | GND non collegato o linea di alimentazione della breadboard interrotta a metà | Collega il GND di Arduino alla linea blu; sulle breadboard lunghe unisci le due metà con un ponticello |
| Un LED resta spento | LED al contrario o sul pin sbagliato | Gamba lunga (anodo) al pin tramite la resistenza, gamba corta a GND; confronta il numero di pin con il codice |
| Si accende il colore sbagliato | Pin del rosso e del verde invertiti nel codice | Correggi i fili o le costanti secondo la tabella dei pin |
| LED molto luminoso che si scalda | Manca la resistenza | Metti una resistenza da 220 Ω in serie a ogni LED |
| Il pulsante non risponde o sembra sempre premuto | Pulsante collegato su due gambe già unite all'interno oppure INPUT_PULLUP dimenticato | Usa le gambe in diagonale e scrivi pinMode(BUTON, INPUT_PULLUP) |
| Il pulsante non ha effetto immediato | Il verde minimo delle auto non è ancora scaduto | Non è un errore; riduci MIN_YESIL |
| Il programma impazzisce dopo un po' | Variabile di tempo dichiarata come int | Dichiara unsigned long tutte le variabili usate con millis() |
Consigli per il compito e la relazione del progetto
Il semaforo è uno dei compiti con Arduino più assegnati nelle lezioni di tecnologia, informatica e robotica. Di solito l'insegnante vuole vedere non solo un circuito che funziona, ma che lo hai capito e sai spiegarlo. Se organizzi la relazione con queste sezioni, risulterà ordinata e non dimenticherai nulla di importante:
- Obiettivo: Spiega in una o due frasi cosa dimostra il progetto, per esempio "temporizzare un semaforo con le uscite digitali".
- Elenco dei materiali: Indica nome, quantità e valore (220 Ω) di ogni componente, come nella tabella sopra.
- Schema elettrico: Aggiungi uno screenshot di Tinkercad o Fritzing; se lo disegni a mano, usa i simboli corretti. Come leggere uno schema elettrico ti sarà utile.
- Calcolo della resistenza: Mostra R = (5 − 2) / 0,015 ≈ 200 Ω e spiega perché hai scelto 220 Ω.
- Tabella degli stati o diagramma di flusso: Inserisci la tabella con stati, durate e transizioni; è la prova più forte che hai capito il codice.
- Codice e spiegazione: Inserisci il codice completo e spiega con parole tue le righe importanti.
- Prove e osservazioni: Cronometra le fasi, misura con il multimetro la corrente di un LED e confrontala con il tuo calcolo.
- Conclusioni e miglioramenti: Descrivi i problemi incontrati, come li hai risolti e quali funzioni potresti aggiungere (pulsante pedonale, suono, modalità notturna).
Idee per far evolvere il tuo semaforo
- Conto alla rovescia: Mostra su un display a 7 segmenti i secondi che mancano alla fine del verde.
- Segnale acustico per non vedenti: Con il verde pedonale fai suonare un buzzer con bip veloci, con il rosso con bip lenti.
- Incrocio intelligente: Rileva con un sensore di distanza HC-SR04 un'auto in attesa sulla strada laterale e dai il verde alla direzione B solo se c'è.
- Modalità notturna automatica: Usa una LDR per passare al giallo lampeggiante quando fa buio.
- Controllo remoto: Crea una pagina web sull'ESP32 e cambia modalità e tempi dal telefono.
- Passaggio a livello: Con la stessa logica realizza le luci di un passaggio a livello con due LED rossi che lampeggiano alternati; i progetti Supercar e luci in sequenza usano idee di temporizzazione simili.
Un semaforo sembra un progetto piccolo, ma le sue idee, macchina a stati, temporizzazione senza blocchi e transizioni sicure, sono anche la base dei robot, degli antifurti e dei dispositivi domotici che costruirai in futuro. Per scegliere il prossimo progetto, dai un'occhiata alle idee per progetti Arduino. Nell'app BlueGrays trovi progetti di semaforo per Arduino, ESP32 e Raspberry Pi Pico e i loro sviluppi, come il semaforo a quattro modalità per Pico e le luci di avviso del passaggio a livello con LED, ciascuno con schema passo passo e codice pronto da provare sulla tua scheda.
Domande frequenti
Come si fa un semaforo con Arduino?
Collega le gambe lunghe di un LED rosso, uno giallo e uno verde a pin digitali di Arduino (per esempio D10, D9 e D8) tramite resistenze da 220 Ω, e le gambe corte a GND. Nel codice imposta quei pin come OUTPUT e accendi e spegni i LED in sequenza dentro loop(). Per una prima versione basta delay(); per pulsanti e funzioni in più serve millis().
Che resistenza serve per i LED del semaforo?
Su un Arduino Uno a 5 V, 220 Ω per LED è una scelta diffusa e sicura; un LED rosso assorbe allora circa 13–14 mA. Con 330 Ω la luce è più debole ma ancora ben visibile. Anche su ESP32 e Pico, che lavorano a 3,3 V, vanno bene 220 Ω; scendi a 100 Ω solo se un LED verde brillante resta troppo debole.
Perché in alcuni semafori si accendono insieme rosso e giallo?
In paesi come Germania, Regno Unito e Turchia, rosso e giallo insieme annunciano che sta per scattare il verde: il conducente si prepara ma parte solo con il verde. In Italia, come negli Stati Uniti, in Francia e in Spagna, questa fase non esiste e si passa direttamente dal rosso al verde. Nel tuo progetto Arduino puoi aggiungerla o toglierla a piacere.
Meglio delay o millis per il semaforo con Arduino?
Se devi solo accendere i LED in sequenza, delay() è la soluzione più semplice e va benissimo. Appena aggiungi un pulsante, un sensore, un display o un suono, delay() mette in pausa il programma e quegli ingressi vengono persi. Il codice scritto con millis() e una macchina a stati non aspetta mai e riesce a seguire tutto contemporaneamente.
Si può fare il semaforo con Arduino su Tinkercad?
Sì. Tinkercad Circuits offre Arduino Uno, LED, resistenze e pulsanti come componenti pronti; disegni il circuito, incolli il codice nell'editor di testo e avvii la simulazione. È perfetto per provare circuito e codice senza componenti reali e per ottenere uno screenshot o un link di condivisione da consegnare.
Come funziona un semaforo pedonale a chiamata?
Normalmente le auto hanno il verde e la pressione del pulsante registra una richiesta. Quando il verde minimo delle auto è scaduto, il semaforo passa al giallo e poi al rosso e, dopo un breve margine di sicurezza, si accende il verde pedonale. Nel codice di questa guida il verde pedonale lampeggia prima di spegnersi, poi il ciclo torna alle auto.
Progetti da realizzare dopo questo articolo
Vedi tutti i progetti
Semaforo
Progetto che simula un vero semaforo con LED rossi, gialli e verdi. Insegna la temporizzazione e la logica di controllo sequenziale.

Semaforo ESP32
Simulazione di un vero semaforo con 3 LED rosso-giallo-verde. I tempi si avvicinano alla logica del traffico reale: rosso lungo, giallo corto, verde lungo. Classico esempio di controllo GPIO sequenziale.

Simulatore Semaforo Raspberry Pi Pico (3 LED Sequenziali, GP11/GP12/GP13)
Simuliamo un vero semaforo con LED rosso, giallo e verde. Verde 4 sec "vai", giallo 1,5 sec "preparati", rosso 3 sec "fermati" e rosso+giallo 1 sec "partenza" (pattern europeo). Impara la definizione di funzione, la logica della macchina a stati e la sincronizzazione multi-GPIO. Circuito semplice: 3 LED su GP11/GP12/GP13.

Due LED lampeggianti alternati
Un progetto che crea un effetto flip-flop accendendo e spegnendo due LED in sequenza. Insegna a controllare più di un pin di uscita.

Accendere un LED con un pulsante (Push)
Progetto di controllo istantaneo (push) che accende il LED quando il pulsante viene premuto e lo spegne quando viene rilasciato. Insegna l'uso della lettura dei pulsanti e dell'espressione condizional

Lampeggiante della polizia a LED
Progetto che crea un effetto di luce stroboscopica della polizia facendo lampeggiare due LED a intervalli veloci. Insegna il controllo del loop e della temporizzazione.

Semaforo Raspberry Pi Pico con selettore a 4 posizioni (macchina a stati, GP11-13 + GP4)
4 diverse modalità del semaforo con un solo pulsante! Ad ogni pressione del pulsante, la modalità cambia: **Ciclo normale** (rosso → giallo → verde in sequenza), **Solo rosso** (segnale di stop), **Giallo lampeggiante** (attenzione/modalità notturna), **Spento** (manutenzione). Concetti: **state machine** (macchina a stati — la variabile `mode` determina quale comportamento verrà eseguito), **modulo mod cycling** (`(mode + 1) % 4` → 0→1→2→3→0 ciclo infinito), **temporizzazione non bloccante** (l

Segnalatori lampeggianti a LED per passaggi a livello
Segnalatori a LED per passaggi a livello.

