Introduzione alla programmazione Arduino
Primi passi verso il mondo della pototipazione: microcontrollore, dati, cicli e funzioni base

Arduino è una piattaforma pensata per avvicinare elettronica e programmazione in modo pratico. Una scheda Arduino consente di leggere segnali provenienti da pulsanti, sensori, potenziometri o moduli esterni, elaborarli tramite un programma caricato nella memoria della scheda e produrre risposte fisiche come l’accensione di un LED, il movimento di un motore, l’invio di dati al computer o l’attivazione di un relè.
Cos’è un microcontrollore
Il microcontrollore è il componente principale di molte schede Arduino. Può essere considerato un piccolo computer integrato in un singolo circuito: contiene una CPU, memoria per il programma, memoria per i dati, porte di ingresso e uscita, timer, convertitori analogico-digitali e periferiche di comunicazione.
A differenza di un computer tradizionale, un microcontrollore non è progettato per eseguire sistemi operativi complessi o molte applicazioni contemporaneamente. Il suo compito è eseguire un programma specifico, spesso in modo continuo, reagendo agli stimoli provenienti dal mondo esterno. Per questo motivo è ideale per applicazioni embedded, automazione, robotica, domotica, didattica elettronica e prototipazione.
In una scheda come Arduino UNO R3, il microcontrollore principale è l’ATmega328P. Lo sketch scritto dall’utente viene compilato, trasformato in istruzioni comprensibili dal microcontrollore e caricato nella memoria flash. Una volta alimentata la scheda, il microcontrollore inizia a eseguire il programma in modo autonomo.
Esempio di dati tecnici riferiti ad Arduino UNO R3 Microcontrollore principale: ATmega328P Tensione operativa: 5 V Pin digitali I/O: 14 Pin PWM: 6 Ingressi analogici: 6 Memoria Flash: 32 kB SRAM: 2 kB EEPROM: 1 kB Frequenza di lavoro tipica su Arduino UNO: 16 MHz
Come funziona un microcontrollore
Il funzionamento di un microcontrollore si basa su un ciclo continuo di lettura, elaborazione e uscita. Il programma legge lo stato dei pin o dei registri interni, esegue istruzioni logiche e matematiche, poi modifica lo stato delle uscite o invia informazioni ad altri dispositivi.
Quando un sensore è collegato a un ingresso analogico, il microcontrollore può convertire una tensione in un numero. Quando un pulsante è collegato a un ingresso digitale, può distinguere tra due stati logici, HIGH e LOW. Quando un LED è collegato a un’uscita digitale, il microcontrollore può portare il pin a livello alto o basso per accenderlo o spegnerlo.
Il microcontrollore non “capisce” direttamente concetti come temperatura, luce o distanza. Questi valori devono essere tradotti in segnali elettrici da un sensore. Il programma interpreta poi questi segnali tramite variabili, condizioni, cicli e funzioni.
Lo sketch Arduino
Un programma Arduino viene comunemente chiamato sketch. La sua struttura minima prevede due funzioni obbligatorie: setup() e loop(). La prima viene eseguita una sola volta all’avvio, mentre la seconda viene ripetuta continuamente finché la scheda rimane alimentata o finché non viene caricato un nuovo programma.
void setup() {
// Istruzioni eseguite una sola volta
}
void loop() {
// Istruzioni ripetute continuamente
}
Questa struttura riflette il modo in cui lavora un sistema embedded: prima si configurano le risorse necessarie, poi si entra in una fase operativa continua. Un termostato, per esempio, inizializza il sensore e il display nel setup(), poi nel loop() misura la temperatura, la confronta con una soglia e decide se attivare o disattivare un’uscita.
Il ciclo setup()
La funzione setup() serve a preparare la scheda prima dell’esecuzione continua del programma. Qui si impostano i pin come ingressi o uscite, si avvia la comunicazione seriale, si inizializzano librerie, sensori, display o moduli esterni.
void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);
}
In questo esempio il pin 13 viene configurato come uscita e la comunicazione seriale viene avviata a 9600 baud. Queste operazioni non devono essere ripetute continuamente: è sufficiente eseguirle una volta all’accensione o al reset della scheda.
Il ciclo loop()
La funzione loop() rappresenta il cuore operativo dello sketch. Tutto ciò che deve essere controllato in modo continuo viene inserito qui: lettura di sensori, controllo di pulsanti, aggiornamento di display, invio di dati seriali, gestione di motori o LED.
void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
Questo esempio accende e spegne ciclicamente un LED collegato al pin 13. Dopo l’ultima istruzione della funzione loop(), Arduino torna automaticamente alla prima istruzione della stessa funzione.
Variabili e costanti
Le variabili sono contenitori di dati. Consentono di memorizzare valori che possono cambiare durante l’esecuzione del programma, come la lettura di un sensore, lo stato di un pulsante, un contatore o una soglia regolabile.
int valoreSensore = 0; float temperatura = 23.5; bool ledAcceso = false;
Ogni variabile ha un tipo, cioè una forma precisa di rappresentazione in memoria. Scegliere il tipo corretto è importante perché i microcontrollori hanno risorse limitate. Usare un tipo troppo grande spreca memoria; usarne uno troppo piccolo può causare errori o overflow.
Tipologie di dato comuni in Arduino bool Valori ammessi: true oppure false Uso tipico: stati logici, condizioni, flag byte Valori tipici: da 0 a 255 Uso tipico: dati grezzi, comunicazione, valori compatti char Valori tipici: singolo carattere Uso tipico: lettere, simboli, caratteri seriali int Valori interi Uso tipico: contatori, letture numeriche, pin, soglie unsigned int Interi senza segno Uso tipico: valori solo positivi long Interi estesi Uso tipico: conteggi più grandi unsigned long Interi positivi estesi Uso tipico: gestione del tempo con millis() float Numeri con decimali Uso tipico: temperatura, tensione, grandezze fisiche double Numeri con decimali Nota: su molte schede Arduino basate su AVR ha la stessa precisione di float String Sequenze di caratteri Uso tipico: testi e messaggi, con attenzione all’uso della memoria array Collezione di valori dello stesso tipo Uso tipico: serie di letture, tabelle, sequenze di pin
Le costanti, invece, rappresentano valori che non devono cambiare durante l’esecuzione. Sono utili per rendere il programma più leggibile e più facile da modificare.
const int pinLed = 13; const int sogliaLuce = 500;
Usare nomi descrittivi al posto di numeri sparsi nel programma migliora la manutenzione dello sketch. Se in futuro il LED viene spostato su un altro pin, sarà sufficiente modificare una sola riga.
Variabili globali e locali
Una variabile globale viene dichiarata fuori da setup() e loop(), quindi può essere usata da tutte le funzioni dello sketch. Una variabile locale viene dichiarata all’interno di una funzione o di un blocco e può essere usata solo in quella zona del programma.
int contatoreGlobale = 0;
void loop() {
int valoreLocale = analogRead(A0);
contatoreGlobale++;
}
Le variabili globali sono comode quando più funzioni devono condividere uno stesso dato, ma vanno usate con attenzione. Troppe variabili globali rendono il programma più difficile da controllare. Le variabili locali, quando possibile, aiutano a scrivere codice più ordinato e meno soggetto a effetti indesiderati.
Condizioni con if
L’istruzione if permette di eseguire una parte del programma solo quando una condizione è vera. È uno degli strumenti fondamentali per far reagire Arduino agli eventi esterni.
if (condizione) {
// istruzioni da eseguire se la condizione è vera
}
Un esempio classico è il controllo di un sensore analogico. Se il valore letto supera una soglia, si accende un LED; altrimenti lo si spegne.
int valore = analogRead(A0);
if (valore > 500) {
digitalWrite(13, HIGH);
} else {
digitalWrite(13, LOW);
}
La condizione viene scritta tra parentesi tonde e può usare operatori di confronto come maggiore di, minore di, uguale a o diverso da. È importante distinguere l’operatore di assegnazione = dall’operatore di confronto ==.
Operatori comuni nelle condizioni == uguale a != diverso da > maggiore di < minore di >= maggiore o uguale a <= minore o uguale a && AND logico || OR logico ! NOT logico
Il ciclo for
Il ciclo for viene usato quando si vuole ripetere un blocco di istruzioni per un numero noto di volte. È molto utile per gestire sequenze, contatori, array o gruppi di pin.
for (inizializzazione; condizione; incremento) {
// istruzioni da ripetere
}
Un esempio tipico consiste nel far lampeggiare un LED per dieci volte.
for (int i = 0; i < 10; i++) {
digitalWrite(13, HIGH);
delay(200);
digitalWrite(13, LOW);
delay(200);
}
La variabile i parte da 0. A ogni ripetizione viene incrementata di 1. Il ciclo continua finché i è minore di 10. Quando la condizione diventa falsa, l’esecuzione prosegue con l’istruzione successiva al ciclo.
Il ciclo while
Il ciclo while ripete un blocco di istruzioni finché una condizione rimane vera. È utile quando non si conosce in anticipo il numero di ripetizioni, ma si sa quale condizione deve essere rispettata per continuare.
while (condizione) {
// istruzioni da ripetere
}
Per esempio, si può attendere che il valore di un sensore superi una soglia.
int valore = analogRead(A0);
while (valore < 500) {
valore = analogRead(A0);
}
Con while bisogna prestare attenzione ai cicli infiniti involontari. Se la condizione non diventa mai falsa, il programma resta bloccato all’interno del ciclo e non può eseguire il resto dello sketch.
Quando usare if, for e while
if serve a prendere decisioni. for serve a ripetere istruzioni per un numero definito di volte. while serve a ripetere istruzioni finché una condizione resta vera. Queste tre strutture permettono di costruire la logica principale di quasi tutti gli sketch Arduino.
Uso consigliato if: quando bisogna scegliere tra due o più azioni for: quando il numero di ripetizioni è noto while: quando la ripetizione dipende da una condizione dinamica
Funzione analogRead()
analogRead() legge il valore presente su un ingresso analogico. Su Arduino UNO, gli ingressi analogici sono indicati come A0, A1, A2, A3, A4 e A5. Il convertitore analogico-digitale trasforma la tensione letta in un numero intero.
Sintassi analogRead(pin)
Parametro pin: il pin analogico da leggere, per esempio A0
Valore restituito Un numero intero. Su Arduino UNO, normalmente il valore va da 0 a 1023. 0 corrisponde a 0 V. 1023 corrisponde circa alla tensione di riferimento analogica.
Esempio int valore = analogRead(A0); Serial.println(valore);
Questa funzione è usata con potenziometri, fotoresistenze, sensori di temperatura analogici, sensori di pressione e in generale con dispositivi che producono una tensione variabile.
Funzione analogWrite()
analogWrite() genera un segnale PWM su un pin compatibile. Non produce una vera tensione analogica continua sulla maggior parte delle schede Arduino classiche, ma un’onda quadra molto veloce con duty cycle variabile. Questo permette di simulare livelli intermedi di potenza, per esempio per regolare la luminosità di un LED o la velocità di un motore tramite un driver.
Sintassi analogWrite(pin, valore)
Parametri pin: pin PWM su cui generare il segnale valore: numero compreso normalmente tra 0 e 255 0 indica duty cycle minimo 255 indica duty cycle massimo
Esempio analogWrite(9, 128);
Un valore di 128 corrisponde circa a metà scala. Se un LED è collegato correttamente a un pin PWM, apparirà meno luminoso rispetto al valore massimo.
Funzione digitalRead()
Il nome corretto della funzione è digitalRead(). Serve a leggere lo stato logico di un pin digitale. Il risultato può essere HIGH oppure LOW.
Sintassi digitalRead(pin)
Parametro pin: numero del pin digitale da leggere
Valore restituito HIGH oppure LOW
Esempio
int statoPulsante = digitalRead(2);
if (statoPulsante == HIGH) {
digitalWrite(13, HIGH);
}
digitalRead() è spesso usata per leggere pulsanti, interruttori, sensori digitali o uscite logiche provenienti da altri moduli. Prima di usarla, il pin deve essere configurato come ingresso tramite pinMode().
Configurazione tipica pinMode(2, INPUT);
Quando si lavora con pulsanti meccanici, è importante evitare ingressi flottanti. Per questo si usano resistenze di pull-up o pull-down, oppure la modalità INPUT_PULLUP quando disponibile.
Esempio con pull-up interno pinMode(2, INPUT_PULLUP);
Funzione digitalWrite()
digitalWrite() imposta lo stato logico di un pin digitale. Se il pin è configurato come uscita, HIGH porta il pin a livello alto, mentre LOW lo porta a livello basso.
Sintassi digitalWrite(pin, valore)
Parametri pin: numero del pin digitale da controllare valore: HIGH oppure LOW
Esempio pinMode(13, OUTPUT); digitalWrite(13, HIGH);
Questa funzione è utilizzata per accendere LED, pilotare relè tramite circuiti adeguati, inviare segnali digitali a moduli esterni o controllare ingressi logici di driver e circuiti integrati.
Funzione Serial.print()
Serial.print() invia dati sulla porta seriale senza andare automaticamente a capo. È molto utile per visualizzare valori sul Monitor Seriale dell’IDE Arduino e per fare debug durante lo sviluppo.
Sintassi Serial.print(valore) Serial.print(valore, formato)
Parametri valore: dato da inviare, per esempio numero, carattere o testo formato: parametro opzionale usato per specificare base numerica o numero di decimali
Esempio
int valore = analogRead(A0);
Serial.print("Valore letto: ");
Serial.print(valore);
Poiché Serial.print() non aggiunge una nuova riga, eventuali stampe successive continueranno sulla stessa linea.
Funzione Serial.println()
Serial.println() funziona in modo simile a Serial.print(), ma aggiunge un ritorno a capo alla fine della stampa. È comoda quando si vogliono visualizzare letture successive una sotto l’altra.
Sintassi Serial.println(valore) Serial.println(valore, formato)
Parametri valore: dato da inviare formato: parametro opzionale usato per base numerica o decimali
Esempio
int valore = analogRead(A0);
Serial.print("Valore letto: ");
Serial.println(valore);
In questo caso il testo e il valore vengono mostrati sulla stessa riga, ma la stampa successiva inizierà su una nuova riga.
Esempio completo di sketch introduttivo
Il seguente esempio combina lettura analogica, condizione if, uscita digitale e stampa seriale. Un valore viene letto dal pin A0; se supera una soglia, il LED sul pin 13 viene acceso, altrimenti viene spento.
const int pinSensore = A0;
const int pinLed = 13;
const int soglia = 500;
void setup() {
pinMode(pinLed, OUTPUT);
Serial.begin(9600);
}
void loop() {
int valore = analogRead(pinSensore);
Serial.print("Valore sensore: ");
Serial.println(valore);
if (valore > soglia) {
digitalWrite(pinLed, HIGH);
} else {
digitalWrite(pinLed, LOW);
}
delay(200);
}
Questo sketch mostra la logica tipica di un progetto Arduino: si definiscono costanti leggibili, si configurano i pin nel setup(), si leggono dati nel loop(), si prende una decisione con if e si produce un’uscita fisica tramite digitalWrite().
Errori comuni da evitare
Uno degli errori più frequenti è dimenticare pinMode() prima di usare un pin digitale come uscita o ingresso. Un altro errore comune è confondere = con == nelle condizioni. Il primo assegna un valore, il secondo confronta due valori.
Errore comune
if (stato = HIGH) {
digitalWrite(13, HIGH);
}
Forma corretta
if (stato == HIGH) {
digitalWrite(13, HIGH);
}
Un altro aspetto importante riguarda i ritardi. delay() è semplice da usare, ma blocca l’esecuzione dello sketch per il tempo indicato. In progetti più complessi, dove bisogna leggere pulsanti, aggiornare display e controllare più eventi contemporaneamente, è spesso preferibile usare millis().




