Programmazione Arduino: basi, variabili, cicli e funzioni
Programmazione Arduino da zero: setup e loop, int, long, byte, overflow, const, #define, if-else, switch-case, cicli for e while, array e funzioni con LED.
La programmazione Arduino consiste nel dire a una scheda Arduino cosa fare con il linguaggio C/C++: sei tu a scrivere quale LED si accende e quando, cosa succede quando premi un pulsante e come interpretare il numero che arriva da un sensore, e la scheda ripete queste istruzioni migliaia di volte al secondo. La buona notizia è che quasi tutti gli sketch che vedrai, dal LED lampeggiante all'auto robot, sono costruiti con gli stessi dieci mattoncini di base o giù di lì. Sono setup() e loop(), le variabili, le costanti, gli operatori, if-else, switch-case, i cicli for e while, gli array e le funzioni.
In questa guida imparerai questi mattoncini uno alla volta, ognuno con un vero esempio di LED o pulsante. Tutti gli esempi funzionano sullo stesso circuito semplice e sono stati compilati e provati per Arduino Uno. I punti in cui i principianti si bloccano più spesso, cioè l'overflow delle variabili, la confusione tra = e ==, un break dimenticato e un contatore che si azzera a ogni giro, sono spiegati a parte. Nelle ultime sezioni vedrai anche come funziona la stessa logica sull'ESP32 e come si scrive in MicroPython sul Raspberry Pi Pico.

Cos'è la programmazione Arduino e quale linguaggio usa?
Il codice Arduino si basa sul C++; non esiste un "linguaggio Arduino" a sé. Il core di Arduino aggiunge al C++ funzioni pronte come pinMode(), digitalWrite() e delay(). Il file che scrivi si chiama sketch e ha l'estensione .ino. Quando premi Verifica, l'Arduino IDE aggiunge in cima al file l'header Arduino.h e genera da solo le dichiarazioni delle funzioni, poi il compilatore trasforma il codice in linguaggio macchina comprensibile dal processore. Il pulsante Carica invia infine il risultato via USB al bootloader della scheda. Se non hai ancora installato l'IDE, parti dalla guida come installare Arduino IDE.
Quando scrivi codice ci sono alcune regole di base. Impararle subito elimina metà degli errori di compilazione che vedresti nei primi giorni:
| Regola | Scrittura corretta | Se non la rispetti |
|---|---|---|
| Ogni istruzione termina con il punto e virgola | digitalWrite(13, HIGH); | expected ';' before '}' token |
| Maiuscole e minuscole contano | pinMode(13, OUTPUT); | 'pinmode' was not declared in this scope |
| I blocchi di codice stanno tra parentesi graffe | void loop() { ... } | Ogni { vuole la sua } |
| Commento su una riga | // questa riga non viene eseguita | I commenti non vengono compilati, servono a spiegare |
| Commento su più righe | /* ... */ | Se non lo chiudi, tutto quello che segue diventa commento |
| Testo tra virgolette doppie, un carattere tra apici singoli | "Ciao" e 'A' | Scambiare le virgolette dà un valore sbagliato o un errore |
Il circuito usato negli esempi
Tutti gli esempi funzionano su un unico circuito. Collega sei LED ai pin D8–D13, ognuno con una resistenza da 220 Ω; la gamba corta (catodo) di ogni LED va a GND. Puoi controllare il valore della resistenza per il tuo LED con il calcolatore della resistenza per LED. Collega una gamba del pulsante a D2 e la gamba opposta in diagonale a GND. Dato che il codice usa INPUT_PULLUP, non serve nessuna resistenza esterna: il pin legge HIGH quando il pulsante è rilasciato e LOW quando è premuto. Per i dettagli guarda come collegare un pulsante ad Arduino e cos'è un LED?.
| Componente | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| 6 LED (ognuno con 220 Ω, catodo a GND) | D8, D9, D10, D11, D12, D13 | GPIO 18, 19, 21, 22, 23, 25 | GP14, GP13, GP12, GP11, GP10, GP9 |
| Pulsante (l'altra gamba a GND) | D2 | GPIO 4 | GP2 |
| LED sulla scheda | D13 (il LED con la scritta L) | GPIO 2 (nella maggior parte dei DevKit) | GP25 ("LED" sul Pico W) |
| Velocità seriale | 9600 baud | 115200 baud | Shell di Thonny |
setup() e loop(): lo scheletro di ogni sketch
Ogni sketch Arduino ha due funzioni obbligatorie. setup() viene eseguita una sola volta, quando la scheda si accende o quando premi il pulsante di reset; qui vanno le preparazioni come l'impostazione dei pin e Serial.begin(). loop() invece si ripete all'infinito dopo che setup() è finita. Dietro le quinte il core di Arduino ha una funzione main() nascosta: prima prepara l'hardware, poi chiama setup() una volta e infine chiama loop() di continuo dentro un ciclo infinito. La zona in cima al file, fuori da queste due funzioni, è la zona globale; qui si definiscono le costanti e le variabili che devono essere raggiungibili da ovunque.
1// 1) Global alan: sabitler ve değişkenler / Global area: constants and variables
2const int LED_PIN = 13; // dahili LED / built-in LED
3unsigned long passCount = 0; // loop() kaç kez döndü / how many times loop() has run
4
5// 2) setup(): açılışta bir kez çalışır / runs once at power-up or reset
6void setup() {
7 pinMode(LED_PIN, OUTPUT);
8 Serial.begin(9600);
9 Serial.println("setup() bitti / setup() done");
10}
11
12// 3) loop(): sonsuza kadar tekrar eder / repeats forever
13void loop() {
14 passCount++; // her turda 1 artır / add 1 on every pass
15 Serial.print("Tur / Pass: ");
16 Serial.println(passCount);
17
18 digitalWrite(LED_PIN, HIGH); // LED'i yak / LED on
19 delay(500); // 500 ms bekle / wait 500 ms
20 digitalWrite(LED_PIN, LOW); // LED'i söndür / LED off
21 delay(500);
22}Carica il codice e apri il Monitor seriale a 9600 baud: vedrai una volta "setup() done" e poi un numero di giro che aumenta ogni secondo, mentre il LED integrato lampeggia allo stesso ritmo. I due delay(500) dentro loop() allungano ogni giro a circa un secondo; un loop() senza attese né stampe seriali può girare centinaia di migliaia di volte al secondo su un Uno. Per sfruttare al meglio il Monitor seriale, dai un'occhiata alla guida Arduino Serial Monitor.
Variabili e tipi di dati: int, long, float, bool, byte
Una variabile è una scatola con un nome nella memoria, che conserva un valore. Quando la definisci scrivi prima il suo tipo, poi il nome e, se vuoi, un valore iniziale, per esempio int counter = 0;. Il tipo decide quanti byte occupa la scatola e quale intervallo di numeri può contenere. Arduino Uno ha solo 2 KB di RAM, quindi scegliere il tipo giusto conta. I nomi delle variabili iniziano con una lettera o un trattino basso e non possono contenere spazi né lettere accentate come à, è o ù.
| Tipo | Dimensione su Uno | Intervallo | Uso tipico |
|---|---|---|---|
| bool | 1 byte | true o false | Il LED è acceso? Il pulsante è stato premuto? |
| byte | 1 byte | 0 – 255 | Numero di pin, luminosità PWM |
| char | 1 byte | -128 – 127 | Un singolo carattere: 'A' |
| int | 2 byte | -32.768 – 32.767 | Contatori, risultato di analogRead() |
| unsigned int | 2 byte | 0 – 65.535 | Solo numeri positivi |
| long | 4 byte | -2.147.483.648 – 2.147.483.647 | Numeri interi grandi |
| unsigned long | 4 byte | 0 – 4.294.967.295 | Misurare il tempo con millis() |
| float | 4 byte | Circa ±3,4 × 10^38, precisione di 6–7 cifre | Temperatura, tensione, medie |
| double | 4 byte (su Uno uguale a float) | Uguale a float | Sull'ESP32 occupa 8 byte |
Alcune regole pratiche ti eviteranno parecchi problemi. analogRead() restituisce un numero da 0 a 1023, quindi un int basta. millis(), invece, restituisce un unsigned long; se salvi il tempo in una variabile int, il valore va in overflow dopo 32,7 secondi e la tua temporizzazione smette di funzionare. Su Uno i calcoli con i float vengono fatti via software, quindi sono più lenti di quelli con gli interi, e oltre le 6–7 cifre il risultato non è più affidabile; per questo non confrontare mai due float con ==. Se vuoi un tipo che abbia la stessa dimensione su qualsiasi scheda, usa quelli che hanno la dimensione nel nome, come uint8_t, int16_t e uint32_t.
Overflow: cosa succede quando una variabile supera il suo limite?
Se provi a salvare un numero più grande di quello che il suo tipo può contenere, Arduino non segnala nessun errore; il numero ricomincia da capo come il contachilometri di un'auto. Su Uno un int contiene al massimo 32.767 e, se aggiungi 1, diventa -32.768. Un byte passa da 255 a 0. Una trappola più subdola riguarda i calcoli con le costanti: un'espressione come 60 * 1000 viene calcolata prima come int e va in overflow, anche se il risultato finisce in una variabile long. Lo sketch qui sotto stampa tutti questi casi nel Monitor seriale, e il suo loop() mostra un overflow di byte su un LED:
1const int LED_PIN = 9; // PWM destekli pin (~9) / PWM-capable pin (~9)
2byte brightness = 0; // byte: 0-255
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 Serial.begin(9600);
7
8 int a = 32767; // Uno'da int'in en büyük değeri / largest int on an Uno
9 a = a + 1; // taşma: -32768 olur / overflow: becomes -32768
10 Serial.println(a);
11
12 long wrong = 60 * 1000; // int ile hesaplanır, taşar: -5536 / computed as int, overflows: -5536
13 long right = 60 * 1000L; // L eki hesabı long yapar: 60000 / the L suffix makes it long math: 60000
14 Serial.println(wrong);
15 Serial.println(right);
16
17 float half = 5 / 2; // tam sayı bölmesi: 2.00 / integer division: 2.00
18 float half2 = 5 / 2.0; // ondalıklı bölme: 2.50 / floating-point division: 2.50
19 Serial.println(half);
20 Serial.println(half2);
21}
22
23void loop() {
24 analogWrite(LED_PIN, brightness); // parlaklığı uygula / apply the brightness
25 brightness++; // 255'ten sonra 0'a döner / wraps to 0 after 255
26 delay(10);
27}| Riga di codice | Risultato su Uno | Risultato su ESP32 e Pico | Perché? |
|---|---|---|---|
| a = 32767 + 1 | -32768 | 32768 | int occupa 2 byte su Uno, 4 byte sugli altri |
| 60 * 1000 | -5536 | 60000 | Le costanti vengono calcolate prima come int |
| 60 * 1000L | 60000 | 60000 | Il suffisso L rende il calcolo long |
| 5 / 2 | 2.00 | 2.00 | La divisione tra due interi dà un intero |
| 5 / 2.0 | 2.50 | 2.50 | Se un numero ha i decimali, li ha anche il risultato |
Guarda il LED su D9: la sua luminosità sale piano da 0 a 255, poi la riga brightness++ trasforma il valore in 0 invece che in 256 e il LED si spegne di colpo. È il modo più semplice per vedere un overflow con i tuoi occhi. D9 è uno dei pin PWM di Uno contrassegnati con ~, per questo analogWrite() lì funziona; il PWM è spiegato nel dettaglio in cos'è il PWM?. Anche l'azzeramento di millis() dopo circa 49,7 giorni è un overflow, e in un codice scritto bene non crea problemi; vedrai come scriverlo nella guida millis() invece di delay().
const e #define: valori che non cambiano mai
Per i numeri che non cambiano mentre il programma gira, come i numeri dei pin o le soglie, ci sono due modi per dare loro un nome. Una costante definita con const ha un tipo, rispetta le regole di visibilità e, se provi a cambiarla per sbaglio, il compilatore ti ferma con assignment of read-only variable 'LED_PIN'. #define, invece, è una direttiva del preprocessore: prima della compilazione sostituisce ogni BUTTON_PIN nel codice con 2. Non ha tipo, e sulla riga non vanno né il punto e virgola né il segno di uguale. Con numeri semplici nessuna delle due occupa RAM, perché il compilatore inserisce il valore direttamente nel codice. Oggi si preferisce const, e scrivere i nomi delle costanti in MAIUSCOLO è un'abitudine molto diffusa. L'esempio qui sotto le usa entrambe: il LED resta acceso finché tieni premuto il pulsante.
1#define BUTTON_PIN 2 // önişlemci: derlemeden önce metin değişimi / preprocessor: text swap before compiling
2const int LED_PIN = 13; // tipi olan, salt okunur sabit / typed, read-only constant
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 pinMode(BUTTON_PIN, INPUT_PULLUP); // dahili pull-up: basılınca LOW / internal pull-up: LOW when pressed
7}
8
9void loop() {
10 if (digitalRead(BUTTON_PIN) == LOW) { // buton basılı mı? / is the button pressed?
11 digitalWrite(LED_PIN, HIGH); // evet: LED yanar / yes: LED on
12 } else {
13 digitalWrite(LED_PIN, LOW); // hayır: LED söner / no: LED off
14 }
15}Operatori: aritmetici, di confronto e logici
Gli operatori ti permettono di fare calcoli con le variabili, confrontarle e combinare condizioni. Quasi tutti quelli che userai nei progetti di tutti i giorni sono in questa tabella:
| Gruppo | Operatore | Significato | Esempio |
|---|---|---|---|
| Aritmetici | + - * / | Le quattro operazioni | x = a * 2; |
| Resto | % | Resto di una divisione (solo con gli interi) | 7 % 3 dà 1 |
| Assegnazione | = += -= *= /= | Assegnare, aggiungere, togliere… | total += 5; |
| Incremento, decremento | ++ -- | Aggiungere 1 o togliere 1 | i++; |
| Confronto | == != < > <= >= | Il risultato è true o false | if (x >= 10) |
| Logici | && || ! | E, O, NON | if (a && !b) |
| Condizionale | ? : | if-else abbreviato | x > 0 ? HIGH : LOW |
| Bit a bit | & | ^ ~ << >> | Lavora sui bit di un numero | 1 << 3 dà 8 |
Le due coppie che si confondono più spesso sono = e == e & e &&. Un solo segno di uguale assegna un valore, due segni confrontano. Un solo & lavora sui bit dei numeri, mentre && chiede se entrambe le condizioni sono vere. Puoi provare le tabelle di verità di E, O e NON nello strumento porte logiche, e gli operatori bit a bit sono spiegati nel dettaglio nella guida sistemi di numerazione e operazioni sui bit. L'esempio qui sotto conta quante volte è stato premuto il pulsante e accende il LED dopo un numero dispari di pressioni:
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4int presses = 0; // toplam basış sayısı / total number of presses
5bool lastPressed = false; // önceki turdaki durum / state on the previous pass
6
7void setup() {
8 pinMode(LED_PIN, OUTPUT);
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10 Serial.begin(9600);
11}
12
13void loop() {
14 bool pressed = (digitalRead(BUTTON_PIN) == LOW); // karşılaştırma: true ya da false / comparison: true or false
15
16 if (pressed && !lastPressed) { // şimdi basılı VE önceden değildi: yeni basış / pressed now AND not before: new press
17 presses++; // 1 artır / increment by 1
18 Serial.print("Basış / Presses: ");
19 Serial.println(presses);
20 delay(50); // basit debounce / simple debounce
21 }
22 lastPressed = pressed;
23
24 // Kalan (%) ile tek mi çift mi? / odd or even using the remainder (%)
25 digitalWrite(LED_PIN, (presses % 2 == 1) ? HIGH : LOW);
26}Il trucco è la condizione pressed && !lastPressed: significa "premuto adesso E non premuto nel giro precedente". In questo modo il numero non continua a salire mentre tieni premuto il pulsante e ogni pressione viene contata una sola volta. presses % 2 dà il resto della divisione del numero per 2: 1 per i dispari, 0 per i pari. L'operatore ? : nell'ultima riga sceglie poi HIGH o LOW in base a quel risultato.
if, else if ed else: prendere decisioni
if esegue il codice tra parentesi graffe quando la condizione tra parentesi tonde è vera. Se è falsa, viene eseguito il blocco else, se c'è. Quando ti servono più alternative, aggiungi else if in mezzo. Le condizioni vengono controllate dall'alto verso il basso e, appena viene eseguito il blocco della prima condizione vera, le altre vengono saltate. In C++ qualsiasi valore diverso da zero conta come vero, quindi if (counter) significa "se il contatore non è zero". Un esempio semplice di if-else l'abbiamo già visto nella sezione su const. Ora scriviamone uno che distingue una pressione breve da una lunga:
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 pinMode(BUTTON_PIN, INPUT_PULLUP);
7 Serial.begin(9600);
8}
9
10void loop() {
11 if (digitalRead(BUTTON_PIN) == LOW) { // basıldı / pressed
12 unsigned long start = millis();
13 while (digitalRead(BUTTON_PIN) == LOW) { // bırakılana kadar bekle / wait until released
14 }
15 unsigned long held = millis() - start; // basılı kalma süresi (ms) / hold time (ms)
16
17 if (held < 50) {
18 return; // 50 ms'den kısa: parazit, yok say / under 50 ms: bounce, ignore
19 } else if (held < 1000) {
20 digitalWrite(LED_PIN, HIGH); // kısa basış: LED yak / short press: LED on
21 Serial.println("Kısa basış / Short press");
22 } else {
23 digitalWrite(LED_PIN, LOW); // uzun basış: LED söndür / long press: LED off
24 Serial.println("Uzun basış / Long press");
25 }
26 }
27}Appena il pulsante viene premuto, il codice registra l'istante con millis(), aspetta in un ciclo while che il pulsante venga rilasciato e calcola il tempo trascorso. Le pressioni sotto i 50 ms sono considerate disturbi dovuti al rimbalzo meccanico del pulsante, e return chiude in anticipo quel giro di loop(). Una pressione inferiore a 1 secondo accende il LED, una più lunga lo spegne. L'ordine delle condizioni è importante: se avessi messo per primo il controllo held < 1000, anche un disturbo di 30 ms verrebbe contato come "pressione breve".
switch-case: scegliere tra molti casi
Se devi fare molte cose diverse a seconda del valore di una variabile, switch-case è più leggibile di una lunga catena di else if. switch confronta il valore tra parentesi con ogni etichetta case e inizia a eseguire da quella che corrisponde. Ogni case va chiuso con break; altrimenti il programma prosegue ed esegue anche il codice del case successivo. Se nessun case corrisponde, viene eseguito il blocco default. switch funziona solo con tipi interi come int, byte, char ed enum; per String, float o intervalli come "tra 10 e 20" ti serve if. Nell'esempio qui sotto ogni pressione porta la lampada alla modalità successiva: spenta, accesa, lampeggio lento, lampeggio veloce.
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4byte mode = 0; // 0 kapalı, 1 açık, 2 yavaş, 3 hızlı / 0 off, 1 on, 2 slow, 3 fast
5bool lastPressed = false;
6
7void setup() {
8 pinMode(LED_PIN, OUTPUT);
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10}
11
12void loop() {
13 bool pressed = (digitalRead(BUTTON_PIN) == LOW);
14 if (pressed && !lastPressed) {
15 mode = (mode + 1) % 4; // 0 → 1 → 2 → 3 → 0
16 delay(50); // basit debounce / simple debounce
17 }
18 lastPressed = pressed;
19
20 switch (mode) {
21 case 0: // kapalı / off
22 digitalWrite(LED_PIN, LOW);
23 break;
24 case 1: // sürekli açık / always on
25 digitalWrite(LED_PIN, HIGH);
26 break;
27 case 2: // 500 ms'de bir değiş / toggle every 500 ms
28 digitalWrite(LED_PIN, (millis() / 500) % 2 ? HIGH : LOW);
29 break;
30 case 3: // 100 ms'de bir değiş / toggle every 100 ms
31 digitalWrite(LED_PIN, (millis() / 100) % 2 ? HIGH : LOW);
32 break;
33 default: // beklenmeyen değer: başa dön / unexpected value: reset
34 mode = 0;
35 break;
36 }
37}La riga mode = (mode + 1) % 4 fa avanzare la modalità per 0, 1, 2, 3 e, grazie all'operatore %, la riporta a 0 quando arriva a 4. Nota che per lampeggiare non usiamo delay(): l'espressione (millis() / 500) % 2 passa da 0 a 1 ogni 500 ms, così il pulsante risponde subito mentre il LED lampeggia. La versione più evoluta di questo approccio a "modalità" si chiama macchina a stati; puoi vedere come si usa in progetti come semafori e menu nella guida macchina a stati con Arduino.
Cicli for, while e do-while
I cicli eseguono un blocco di codice più e più volte. Le parentesi di un ciclo for hanno tre parti: l'inizio (int i = 0), la condizione per continuare (i < 6) e il passo dopo ogni giro (i++). for è ideale quando sai quante volte ripetere. while continua finché la sua condizione è vera e la controlla prima di ogni giro, quindi è adatto alle attese di cui non conosci la durata. do-while controlla la condizione alla fine, perciò viene eseguito sempre almeno una volta. Dentro un ciclo, break esce subito dal ciclo e continue salta il resto del giro corrente e passa al successivo. Siccome loop() è già un ciclo infinito, di solito non serve scriverci dentro while (true).
| Ciclo | Quando viene controllata la condizione? | Quando usarlo? | Esempio |
|---|---|---|---|
| for | Prima di ogni giro | Quando il numero di ripetizioni è noto | for (int i = 0; i < 6; i++) |
| while | Prima di ogni giro | Per aspettare che succeda qualcosa | while (digitalRead(2) == HIGH) |
| do-while | Dopo ogni giro | Quando deve essere eseguito almeno una volta | do { ... } while (x < 10); |
L'esempio qui sotto crea un effetto Supercar con sei LED. Il ciclo for in setup() imposta i sei pin come uscite in una sola riga, e il ciclo while in loop() aspetta finché il pulsante non viene premuto. Finché lo tieni premuto, la luce corre da D8 a D13 e torna indietro:
1const int FIRST_LED = 8; // ilk LED pini / first LED pin
2const int LAST_LED = 13; // son LED pini / last LED pin
3const int BUTTON_PIN = 2;
4
5void setup() {
6 for (int pin = FIRST_LED; pin <= LAST_LED; pin++) {
7 pinMode(pin, OUTPUT); // 6 pini tek döngüyle çıkış yap / make 6 pins outputs with one loop
8 }
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10}
11
12void loop() {
13 while (digitalRead(BUTTON_PIN) == HIGH) {
14 // butona basılana kadar burada bekle / wait here until the button is pressed
15 }
16
17 for (int pin = FIRST_LED; pin <= LAST_LED; pin++) { // ileri: 8 → 13 / forward: 8 → 13
18 digitalWrite(pin, HIGH);
19 delay(80);
20 digitalWrite(pin, LOW);
21 }
22 for (int pin = LAST_LED - 1; pin > FIRST_LED; pin--) { // geri: 12 → 9 / back: 12 → 9
23 digitalWrite(pin, HIGH);
24 delay(80);
25 digitalWrite(pin, LOW);
26 }
27}Far partire il ciclo di ritorno da LAST_LED - 1 e farlo girare solo finché il pin è maggiore di FIRST_LED è una scelta voluta: i LED alle estremità non si accendono due volte di fila e il movimento risulta fluido. Per regolare la velocità, creare un effetto di riempimento e altri motivi, consulta la guida effetto Supercar e luci sequenziali con Arduino.
Array: tanti valori sotto un solo nome
Un array conserva più valori dello stesso tipo, in ordine, sotto un unico nome. La riga const byte LEDS[] = {8, 9, 10, 11, 12, 13}; crea un array di sei elementi. Agli elementi si accede con le parentesi quadre e si conta da 0: LEDS[0] vale 8 e LEDS[5] vale 13. La lunghezza di un array non può cambiare dopo la definizione. Se lasci che sia sizeof(LEDS) / sizeof(LEDS[0]) a calcolare il numero di elementi invece di scriverlo a mano, i tuoi cicli si adattano da soli quando aggiungi un nuovo pin all'array. Un indice fuori dall'array, come LEDS[6], non dà errore; legge o rovina un'altra variabile in memoria. Per questo devi assicurarti che l'indice resti tra 0 e il numero di elementi meno uno. L'esempio qui sotto trasforma i sei LED in un dado elettronico:
1const byte LEDS[] = {8, 9, 10, 11, 12, 13}; // LED pinleri / LED pins
2const byte LED_COUNT = sizeof(LEDS) / sizeof(LEDS[0]); // eleman sayısı: 6 / number of elements: 6
3const byte BUTTON_PIN = 2;
4
5void setup() {
6 for (byte i = 0; i < LED_COUNT; i++) { // indeks 0'dan başlar / index starts at 0
7 pinMode(LEDS[i], OUTPUT);
8 }
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10 randomSeed(analogRead(A0)); // boştaki A0'ın gürültüsüyle tohumla / seed from noise on the unconnected A0
11}
12
13void loop() {
14 if (digitalRead(BUTTON_PIN) == LOW) {
15 byte dice = random(1, 7); // 1 ile 6 arası / between 1 and 6
16 for (byte i = 0; i < LED_COUNT; i++) {
17 digitalWrite(LEDS[i], (i < dice) ? HIGH : LOW); // ilk 'dice' kadar LED yanar / the first 'dice' LEDs light up
18 }
19 delay(300); // basılı tutarsan zar dönmeye devam eder / keeps rolling while held
20 }
21}random(1, 7) genera un numero da 1 a 6, perché il limite superiore non è incluso nel risultato. Si accendono tanti LED quanti indica il numero: se esce 4, si accendono i quattro LED da LEDS[0] a LEDS[3]. La riga randomSeed(analogRead(A0)) usa come valore iniziale il rumore elettrico del pin A0 lasciato scollegato; senza di essa la scheda produrrebbe la stessa sequenza di numeri a ogni accensione. Un altro vantaggio degli array: se sposti i LED su altri pin, ti basta cambiare la prima riga.
Scrivere funzioni: dividere il codice in pezzi
Se fai lo stesso lavoro in più punti del programma, trasformalo in una funzione. Una funzione è composta da un tipo di ritorno, un nome, dei parametri tra parentesi tonde e un corpo tra parentesi graffe. Le funzioni che non restituiscono un valore sono di tipo void; quelle che lo fanno consegnano il risultato con return. I parametri vengono passati per copia, quindi cambiare un parametro dentro la funzione non cambia la variabile nel punto in cui è stata chiamata. L'Arduino IDE genera da solo le dichiarazioni delle funzioni nei file .ino, perciò puoi scrivere una funzione anche sotto loop(). Nei file .cpp o in PlatformIO, invece, devi dichiarare una funzione prima di usarla. Dai alle funzioni nomi che dicano cosa fanno, come blinkLed o isPressed.
1const byte LED_PIN = 13;
2const byte BUTTON_PIN = 2;
3
4// Değer döndürmeyen (void) fonksiyon / function that returns nothing (void)
5void blinkLed(byte pin, byte times, int ms) {
6 for (byte i = 0; i < times; i++) {
7 digitalWrite(pin, HIGH);
8 delay(ms);
9 digitalWrite(pin, LOW);
10 delay(ms);
11 }
12}
13
14// bool döndüren fonksiyon / function that returns a bool
15bool isPressed(byte pin) {
16 return digitalRead(pin) == LOW; // INPUT_PULLUP: basılı = LOW / pressed = LOW
17}
18
19void setup() {
20 pinMode(LED_PIN, OUTPUT);
21 pinMode(BUTTON_PIN, INPUT_PULLUP);
22 blinkLed(LED_PIN, 3, 100); // açılışta 3 hızlı yanıp sönme / 3 quick blinks at start-up
23}
24
25void loop() {
26 if (isPressed(BUTTON_PIN)) {
27 blinkLed(LED_PIN, 2, 250); // basınca 2 yavaş yanıp sönme / 2 slow blinks per press
28 }
29}blinkLed() riceve come parametri il pin, il numero di lampeggi e la velocità, quindi puoi riutilizzare la stessa funzione per un altro LED e un'altra velocità. isPressed() restituisce un bool e tiene in un solo punto la logica "premuto = LOW" di INPUT_PULLUP; se un giorno colleghi il pulsante al contrario, cambi solo quella riga. Il delay() dentro la funzione, però, blocca comunque il programma. Per funzioni che fanno più cose insieme serve una temporizzazione basata su millis().
Variabili globali, locali e static
Il punto in cui una variabile è visibile si chiama visibilità (scope). Le variabili globali, definite fuori dalle funzioni, vivono per tutto il programma e si possono leggere e modificare da ovunque. Le variabili locali, definite dentro una funzione o un blocco tra parentesi graffe, vengono create di nuovo ogni volta che quel blocco viene eseguito e spariscono quando finisce. Da qui nasce la lamentela più comune dei principianti: "il mio contatore resta sempre a 1". Se metti static davanti a una variabile locale, prende il valore iniziale solo la prima volta e conserva il suo valore tra un giro e l'altro, ma resta visibile solo dentro quella funzione.
1const byte LED_PIN = 13;
2const byte BUTTON_PIN = 2;
3
4int globalCount = 0; // global: her yerden görünür, değerini korur / global: visible everywhere, keeps its value
5
6void setup() {
7 pinMode(LED_PIN, OUTPUT);
8 pinMode(BUTTON_PIN, INPUT_PULLUP);
9 Serial.begin(9600);
10}
11
12void loop() {
13 int localCount = 0; // yerel: her turda yeniden 0 olur / local: back to 0 on every pass
14 static int staticCount = 0; // static yerel: değerini korur, yalnız burada görünür / static local: keeps its value, visible only here
15
16 if (digitalRead(BUTTON_PIN) == LOW) {
17 globalCount++;
18 localCount++;
19 staticCount++;
20 Serial.print(globalCount);
21 Serial.print(' ');
22 Serial.print(localCount);
23 Serial.print(' ');
24 Serial.println(staticCount); // 3. basışta: 3 1 3 / on the 3rd press: 3 1 3
25 digitalWrite(LED_PIN, (staticCount % 2) ? HIGH : LOW);
26 delay(300);
27 }
28}Se premi il pulsante tre volte, il Monitor seriale mostra le righe 1 1 1, 2 1 2 e 3 1 3: i contatori globale e static continuano a salire, mentre quello locale resta sempre a 1 perché riparte da 0 a ogni giro di loop(). Se definisci una variabile locale con lo stesso nome di una globale, la locale nasconde la globale e dentro quella funzione non tocchi mai la variabile globale. Le variabili globali modificate da una funzione di interrupt (ISR) vanno inoltre dichiarate volatile; trovi i dettagli nella guida interrupt con Arduino.
Programmare l'ESP32 con Arduino: stesso linguaggio, dimensioni diverse
Quando programmi l'ESP32 con l'Arduino IDE usi lo stesso linguaggio C++ e gli stessi comandi, ma ci sono alcune differenze. Dato che l'ESP32 è un processore a 32 bit, int occupa 4 byte e contiene valori da -2.147.483.648 a 2.147.483.647, quindi 32767 + 1 nell'esempio sull'overflow dà 32768. double occupa 8 byte, analogRead() restituisce valori da 0 a 4095 e i pin lavorano a 3,3 V. Con Serial.printf() puoi stampare in modo formattato. random() usa di default il generatore hardware di numeri casuali, quindi la riga randomSeed() non serve. Il programma qui sotto mette insieme tutto quello che hai imparato in questo articolo: un array, una funzione, switch-case e un pulsante che cambia modalità. Per le caratteristiche generali della scheda guarda cos'è l'ESP32?.
1// ESP32: aynı dil, farklı pinler / ESP32: same language, different pins
2const byte LEDS[] = {18, 19, 21, 22, 23, 25}; // güvenli GPIO'lar / safe GPIOs
3const byte LED_COUNT = sizeof(LEDS) / sizeof(LEDS[0]);
4const byte BUTTON_PIN = 4; // diğer bacağı GND'ye / other leg to GND
5
6byte mode = 0; // 0 kapalı, 1 yürüyen ışık, 2 hepsi yanıp söner / 0 off, 1 chaser, 2 all blink
7bool lastPressed = false;
8
9void setAll(bool on) {
10 for (byte i = 0; i < LED_COUNT; i++) {
11 digitalWrite(LEDS[i], on ? HIGH : LOW);
12 }
13}
14
15void setup() {
16 Serial.begin(115200);
17 for (byte i = 0; i < LED_COUNT; i++) {
18 pinMode(LEDS[i], OUTPUT);
19 }
20 pinMode(BUTTON_PIN, INPUT_PULLUP);
21 Serial.printf("int = %d bayt / bytes\n", (int)sizeof(int)); // ESP32'de 4 / 4 on the ESP32
22
23 int a = 32767;
24 a = a + 1;
25 Serial.println(a); // ESP32'de 32768, taşma yok / 32768 on the ESP32, no overflow
26}
27
28void loop() {
29 bool pressed = (digitalRead(BUTTON_PIN) == LOW);
30 if (pressed && !lastPressed) {
31 mode = (mode + 1) % 3; // 0 → 1 → 2 → 0
32 delay(50);
33 }
34 lastPressed = pressed;
35
36 switch (mode) {
37 case 0:
38 setAll(false);
39 break;
40 case 1: { // case içinde değişken için süslü parantez / braces for a variable inside a case
41 byte step = (millis() / 120) % LED_COUNT; // sıradaki LED / LED whose turn it is
42 for (byte i = 0; i < LED_COUNT; i++) {
43 digitalWrite(LEDS[i], (i == step) ? HIGH : LOW);
44 }
45 break;
46 }
47 case 2:
48 setAll((millis() / 300) % 2); // 300 ms'de bir hepsi / all of them every 300 ms
49 break;
50 }
51}Per far girare questo programma su Uno basta cambiare l'array LEDS in {8, 9, 10, 11, 12, 13}, impostare BUTTON_PIN a 2 e sostituire la riga con Serial.printf(), che su Uno non esiste, con Serial.print(). È proprio questo il vantaggio di definire i numeri dei pin come costanti e array in cima al file. Nota anche che il blocco di case 1 è tra parentesi graffe; siccome al suo interno viene definita la variabile step, sull'ESP32 queste parentesi sono obbligatorie.
Raspberry Pi Pico: la stessa logica in MicroPython
Puoi programmare il Raspberry Pi Pico in C++ aggiungendo all'Arduino IDE il pacchetto di schede RP2040; in quel caso il codice di questo articolo funziona appena cambi i numeri dei pin, e anche qui int occupa 4 byte. L'uso più comune del Pico, però, è con MicroPython. In Python non scrivi il tipo delle variabili, i blocchi sono definiti dall'indentazione invece che dalle parentesi graffe, non c'è il punto e virgola a fine riga e gli interi non vanno in overflow, ma crescono quanto serve. Il codice qui sotto è la versione MicroPython del dado elettronico. Per l'installazione guarda cos'è il Raspberry Pi Pico?, per i dettagli del linguaggio le basi di MicroPython.
1from machine import Pin
2import time, random
3
4leds = [Pin(n, Pin.OUT) for n in (14, 13, 12, 11, 10, 9)] # liste = dizi / list = array
5button = Pin(2, Pin.IN, Pin.PULL_UP) # basılınca 0 / reads 0 when pressed
6
7def show(count): # fonksiyon / function
8 for i, led in enumerate(leds): # for döngüsü / for loop
9 led.value(1 if i < count else 0)
10
11show(0) # setup() karşılığı: bir kez çalışır / like setup(): runs once
12while True: # loop() karşılığı / like loop()
13 if button.value() == 0: # buton basılı mı? / is the button pressed?
14 show(random.randint(1, 6)) # 1-6 arası zar at / roll a number from 1 to 6
15 time.sleep_ms(300)| Concetto | Arduino (C++) | MicroPython |
|---|---|---|
| Parte eseguita una volta | setup() | Il codice prima della riga while True: |
| Parte che si ripete all'infinito | loop() | while True: |
| Variabile | int x = 5; | x = 5 |
| Costante | const int PIN = 2; | PIN = const(2) (from micropython import const) |
| Condizione | if / else if / else | if / elif / else |
| Scelta multipla | switch-case | Catena di if / elif |
| Ciclo di conteggio | for (int i = 0; i < 6; i++) | for i in range(6): |
| Array | byte a[] = {1, 2, 3}; | a = [1, 2, 3] |
| Funzione | void blink(int pin) { ... } | def blink(pin): |
| Logica | && || ! | and or not |
| Commento | // commento | # commento |
Idee di progetto con queste basi
- Semaforo: costruisci un incrocio con pulsante pedonale usando costanti, delay() e if; la guida passo passo è in semaforo con Arduino.
- Supercar e luci sequenziali: crea decine di motivi luminosi diversi con un ciclo for e un array.
- Dado elettronico: porta il dado a 6 LED su un display a 7 segmenti con un array, random() e una funzione; per farlo guarda la guida display a 7 segmenti.
- Test di reazione: accendi un LED in un momento casuale e misura con un unsigned long e millis() quanto velocemente premi il pulsante.
- Lampada a modalità: spenta, fissa, respiro e flash da un solo pulsante con switch-case; per una versione a colori dai un'occhiata a come usare un LED RGB.
- Serratura a codice: raccogli i tasti premuti in un array e confrontali con il codice salvato; i dettagli sono nella guida tastierino con Arduino.
Errori comuni e come risolverli
| Errore | Sintomo | Soluzione |
|---|---|---|
| Usare = in un confronto | Il blocco if viene eseguito sempre | Usa == per confrontare |
| Mettere ; o = nella riga #define | expected ')' before ';' token | Scrivi #define LED_PIN 13 |
| Scrivere male un comando | 'pinmode' was not declared in this scope | Fai attenzione a maiuscole e minuscole, come in pinMode |
| Lettera accentata nel nome di una variabile | stray '\303' in program | Nei nomi usa solo lettere inglesi, cifre e _ |
| Calcolare 60 * 1000 come int | Il risultato è -5536 | Scrivi 60 * 1000L o 60000UL |
| Salvare millis() in un int | La temporizzazione si guasta dopo 32 secondi | Usa unsigned long |
| Divisione intera | 5 / 2 dà 2 | Scrivi 5 / 2.0 o converti in float |
| Dimenticare il break alla fine di un case | Viene eseguito anche il case successivo | Chiudi ogni case con break |
| Scrivere fuori da un array | Valori casuali, blocchi | Tieni l'indice tra 0 e il numero di elementi - 1 |
| Definire un contatore come locale | Il contatore resta sempre a 1 | Rendilo globale o static |
Quando avrai imparato questi mattoncini, il passo successivo è conoscere i comandi più usati: l'elenco dei comandi Arduino spiega con esempi tutto, da pinMode() ad attachInterrupt(). Nell'app BlueGrays trovi anche progetti come accendere un LED, controllare un LED con un pulsante, l'effetto Supercar, un semaforo e un dado, passo passo con schemi di collegamento e codice pronto, così puoi provare su un circuito reale ogni struttura che hai imparato qui.
Domande frequenti
Con quale linguaggio di programmazione si programma Arduino?
Il codice Arduino si basa sul C++. Il core di Arduino aggiunge al linguaggio funzioni pronte come pinMode(), digitalWrite() e delay(), e il codice si scrive in file con estensione .ino. Schede come l'ESP32 e il Raspberry Pi Pico si possono programmare anche in MicroPython, se preferisci.
Da dove iniziare per imparare la programmazione Arduino?
Per prima cosa installa l'Arduino IDE e carica l'esempio Blink, che fa lampeggiare il LED integrato. Poi prova, in quest'ordine, setup() e loop(), le variabili, if-else, i cicli, gli array e le funzioni, ognuno con un circuito con LED o pulsante. Usare ogni nuovo argomento in un piccolo progetto lo fa ricordare molto meglio che leggerlo soltanto.
Qual è la differenza tra int e long su Arduino?
Su Arduino Uno un int occupa 2 byte e contiene numeri da -32.768 a 32.767, mentre un long occupa 4 byte e arriva a circa ±2,1 miliardi. Un numero che supera questo limite ricomincia da capo senza errori, per questo per valori grandi come millis() si usa long o unsigned long. Su ESP32 e Pico anche int occupa 4 byte.
Qual è la differenza tra const e #define su Arduino?
Una costante definita con const ha un tipo, rispetta la visibilità e il compilatore segnala un errore se provi a cambiarla. #define è una sostituzione di testo fatta prima della compilazione, non ha tipo e provoca errori inattesi se termina con un punto e virgola. Con numeri semplici nessuna delle due occupa RAM, ma di solito si preferisce const perché è più sicura.
Qual è la differenza tra ciclo for e ciclo while su Arduino?
Il ciclo for riunisce in una riga inizio, condizione e passo e si usa quando sai quante volte ripetere, per esempio per accendere sei LED uno dopo l'altro. Il ciclo while controlla solo una condizione e continua finché è vera, quindi è adatto a situazioni di durata sconosciuta come aspettare la pressione di un pulsante.
Si può programmare Arduino in Python?
Le schede a 8 bit come Arduino Uno non possono eseguire Python, quindi si programmano in C++. L'ESP32 e il Raspberry Pi Pico, invece, si possono programmare in MicroPython. Inoltre un programma Python in esecuzione sul tuo computer può controllare un Uno in modo indiretto comunicando con lui attraverso la porta seriale.
Progetti da realizzare dopo questo articolo
Vedi tutti i progetti
LED lampeggiante
Il primissimo ciao dell'elettronica: accendere e spegnere un solo LED. L'Arduino accende e spegne il LED a intervalli brevissimi, così la luce lampeggia veloce. Vedi nel modo più semplice come la sche

Accendere un LED con un pulsante (Push)
Come un campanello: la luce resta accesa finché premi e si spegne quando lasci. L'Arduino ascolta il pulsante di continuo; mentre è premuto accende il LED, e appena togli il dito lo spegne. Con questo

Accendere e spegnere un LED con un pulsante
Premi il pulsante una volta e la luce si accende, premilo di nuovo e si spegne, proprio come l'interruttore della tua stanza. L'Arduino si accorge di ogni pressione e porta il LED nello stato opposto,

Effetto Supercar con LED
La classica luce che va e viene: un punto luminoso scorre da un capo all'altro di sei LED e poi torna indietro. L'Arduino accende ogni LED per un decimo di secondo prima di passare al successivo, inve

Semaforo
Una copia in miniatura del semaforo che vedi ogni giorno per strada. Il rosso resta acceso due secondi, il giallo brilla per un attimo, il verde resta due secondi, poi si torna al rosso passando di nu

Controllo LED ESP32 con pulsante (Push)
Il LED si accende quando il pulsante viene premuto e si spegne quando viene rilasciato. Mostra come vengono utilizzati il pin di ingresso digitale dell'ESP32 e la resistenza di pull-up interna. Grazie alla modalità INPUT_PULLUP, non è necessaria alcuna resistenza esterna.

Raspberry Pi Pico Pulsante Toggle LED (GP14 + GP2)
Pulsante + LED — livello successivo! 🔁 A differenza di #155, non serve tenere premuto. Premi e rilascia → LED acceso. Premi di nuovo → LED spento (toggle). Stesso circuito di #155, grande differenza di codice. Impara edge detection, state tracking e debounce.

Progetto Dadi
Basta dadi persi durante i giochi da tavolo: ogni volta che premi il pulsante, sul display a 7 segmenti compare un numero a caso da 1 a 6. Ogni pressione dà un solo risultato, che viene scritto anche
Altri articoli
App BlueGrays
Costruisci questo circuito passo passo sul telefono
Schema, codice ed elenco componenti in un’unica schermata. Oltre 160 progetti Arduino, ESP32 e Pico da realizzare.
Inquadra con il telefono
Punta la fotocamera sul codice: si apre lo store del tuo telefono.
oppure apri lo store

