Basi · 19 min di lettura

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.

Scheda Arduino Uno e i suoi pin digitali
Arduino Uno: tutti gli esempi di questo articolo usano un pulsante sul pin D2 e sei LED sui pin D8–D13. Grazie al LED integrato su D13, per i primi esempi non servono nemmeno i cavi.

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:

RegolaScrittura correttaSe non la rispetti
Ogni istruzione termina con il punto e virgoladigitalWrite(13, HIGH);expected ';' before '}' token
Maiuscole e minuscole contanopinMode(13, OUTPUT);'pinmode' was not declared in this scope
I blocchi di codice stanno tra parentesi graffevoid loop() { ... }Ogni { vuole la sua }
Commento su una riga// questa riga non viene eseguitaI 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?.

ComponenteArduino UnoESP32Raspberry Pi Pico
6 LED (ognuno con 220 Ω, catodo a GND)D8, D9, D10, D11, D12, D13GPIO 18, 19, 21, 22, 23, 25GP14, GP13, GP12, GP11, GP10, GP9
Pulsante (l'altra gamba a GND)D2GPIO 4GP2
LED sulla schedaD13 (il LED con la scritta L)GPIO 2 (nella maggior parte dei DevKit)GP25 ("LED" sul Pico W)
Velocità seriale9600 baud115200 baudShell di Thonny
Non collegare mai un LED senza resistenza: si possono danneggiare sia il LED sia il pin di Arduino. Se inserendo il pulsante nella breadboard usi due gambe dallo stesso lato, potrebbero essere già collegate all'interno e il pulsante si comporterebbe come se non venisse mai premuto; per questo usare le gambe in diagonale è la scelta più sicura. Puoi trovare i numeri dei pin della tua scheda nello strumento Pinout.

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.

Arduino (C++)
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 ù.

TipoDimensione su UnoIntervalloUso tipico
bool1 bytetrue o falseIl LED è acceso? Il pulsante è stato premuto?
byte1 byte0 – 255Numero di pin, luminosità PWM
char1 byte-128 – 127Un singolo carattere: 'A'
int2 byte-32.768 – 32.767Contatori, risultato di analogRead()
unsigned int2 byte0 – 65.535Solo numeri positivi
long4 byte-2.147.483.648 – 2.147.483.647Numeri interi grandi
unsigned long4 byte0 – 4.294.967.295Misurare il tempo con millis()
float4 byteCirca ±3,4 × 10^38, precisione di 6–7 cifreTemperatura, tensione, medie
double4 byte (su Uno uguale a float)Uguale a floatSull'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:

Arduino (C++)
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 codiceRisultato su UnoRisultato su ESP32 e PicoPerché?
a = 32767 + 1-3276832768int occupa 2 byte su Uno, 4 byte sugli altri
60 * 1000-553660000Le costanti vengono calcolate prima come int
60 * 1000L6000060000Il suffisso L rende il calcolo long
5 / 22.002.00La divisione tra due interi dà un intero
5 / 2.02.502.50Se 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().

Nell'Arduino IDE apri File > Impostazioni e imposta gli Avvisi del compilatore (Compiler warnings) su "Di più" o "Tutti". Così, quando scrivi un'espressione che va in overflow come 60 * 1000, il compilatore mostra l'avviso integer overflow in expression e scopri l'errore prima ancora che il codice arrivi sulla scheda.

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.

Arduino (C++)
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}
Se scrivi #define LED_PIN 13;, il punto e virgola diventa parte del valore e la riga pinMode(LED_PIN, OUTPUT) si trasforma in pinMode(13;, OUTPUT). Il compilatore segnala expected ')' before ';' token; la nota in expansion of macro 'LED_PIN' che compare sotto indica che il problema emerge dove la costante viene usata, ma la riga da correggere è quella del #define. Anche scrivere #define LED_PIN = 13 dà errore: expected primary-expression before '=' token.

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:

GruppoOperatoreSignificatoEsempio
Aritmetici+ - * /Le quattro operazionix = 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 1i++;
Confronto== != < > <= >=Il risultato è true o falseif (x >= 10)
Logici&& || !E, O, NONif (a && !b)
Condizionale? :if-else abbreviatox > 0 ? HIGH : LOW
Bit a bit& | ^ ~ << >>Lavora sui bit di un numero1 << 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:

Arduino (C++)
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:

Arduino (C++)
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".

Scrivere if (mode = 2) è l'errore classico: questa riga non confronta niente, assegna 2 a mode e, siccome il risultato non è zero, la condizione è sempre vera. Un altro errore subdolo è il punto e virgola subito dopo la parentesi, come in if (x == 5);: l'if termina con un'istruzione vuota e il blocco sottostante viene eseguito ogni volta, qualunque sia la condizione.

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.

Arduino (C++)
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.

Se definisci una variabile dentro un case, racchiudi il codice di quel case tra parentesi graffe: case 1: { byte step = 3; ... break; }. Senza parentesi Uno lo compila con un semplice avviso, ma l'ESP32 dà l'errore jump to case label e il codice non si compila affatto.

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).

CicloQuando viene controllata la condizione?Quando usarlo?Esempio
forPrima di ogni giroQuando il numero di ripetizioni è notofor (int i = 0; i < 6; i++)
whilePrima di ogni giroPer aspettare che succeda qualcosawhile (digitalRead(2) == HIGH)
do-whileDopo ogni giroQuando deve essere eseguito almeno una voltado { ... } 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:

Arduino (C++)
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.

Il ciclo for (byte i = 0; i < 256; i++) non finisce mai: un byte contiene al massimo 255, quindi i torna a 0 prima di arrivare a 256 e la condizione resta sempre vera. Allo stesso modo, un ciclo while che aspetta una condizione che non si verifica mai, per esempio un sensore con il filo staccato, blocca il programma per sempre.

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:

Arduino (C++)
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.

Arduino (C++)
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.

Arduino (C++)
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.

Tieni le variabili il più locali possibile: static per i contatori che servono a una sola funzione, globali per i valori condivisi da più funzioni. La riga Global variables use … bytes of dynamic memory che l'IDE stampa dopo la compilazione mostra quanta RAM occupano le tue variabili globali. Su Uno, oltre il 75 per cento possono comparire comportamenti instabili; trovi le soluzioni nella guida memoria insufficiente su 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?.

Arduino (C++)
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.

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)
ConcettoArduino (C++)MicroPython
Parte eseguita una voltasetup()Il codice prima della riga while True:
Parte che si ripete all'infinitoloop()while True:
Variabileint x = 5;x = 5
Costanteconst int PIN = 2;PIN = const(2) (from micropython import const)
Condizioneif / else if / elseif / elif / else
Scelta multiplaswitch-caseCatena di if / elif
Ciclo di conteggiofor (int i = 0; i < 6; i++)for i in range(6):
Arraybyte a[] = {1, 2, 3};a = [1, 2, 3]
Funzionevoid 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

ErroreSintomoSoluzione
Usare = in un confrontoIl blocco if viene eseguito sempreUsa == per confrontare
Mettere ; o = nella riga #defineexpected ')' before ';' tokenScrivi #define LED_PIN 13
Scrivere male un comando'pinmode' was not declared in this scopeFai attenzione a maiuscole e minuscole, come in pinMode
Lettera accentata nel nome di una variabilestray '\303' in programNei nomi usa solo lettere inglesi, cifre e _
Calcolare 60 * 1000 come intIl risultato è -5536Scrivi 60 * 1000L o 60000UL
Salvare millis() in un intLa temporizzazione si guasta dopo 32 secondiUsa unsigned long
Divisione intera5 / 2 dà 2Scrivi 5 / 2.0 o converti in float
Dimenticare il break alla fine di un caseViene eseguito anche il case successivoChiudi ogni case con break
Scrivere fuori da un arrayValori casuali, blocchiTieni l'indice tra 0 e il numero di elementi - 1
Definire un contatore come localeIl contatore resta sempre a 1Rendilo globale o static
Quando ricevi un errore di compilazione, leggi solo la prima riga di errore e correggi quella; la maggior parte degli errori nelle righe successive è una conseguenza del primo. Il significato dei messaggi è spiegato nella guida errori di compilazione Arduino. Se non hai una scheda a portata di mano, puoi provare il codice anche in una simulazione con Tinkercad.

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.

Vedi tutti i progetti
LED lampeggiante
#1Principiante

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

4.6(179)
978
Accendere un LED con un pulsante (Push)
#9Principiante

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

4.9(112)
198
Accendere e spegnere un LED con un pulsante
#10Principiante

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,

4.8(100)
162
Effetto Supercar con LED
#6Intermedio

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

4.8(104)
196
Semaforo
#3Principiante

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

4.8(129)
367
Controllo LED ESP32 con pulsante (Push)
#104Principiante

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.

4.6(36)
55
Raspberry Pi Pico Pulsante Toggle LED (GP14 + GP2)
#156Principiante
Premium

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.

0.0(0)
9
Progetto Dadi
#79Intermedio
Premium

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

5.0(48)
67