Arduino

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

Admin | MST-Tutorial

Appassionato di elettronica, elettrotecnica e programmazione. Fondatore di MST-Tutorial, condivido con entusiasmo conoscenze, esperienze e progetti legati all’innovazione tecnologica, con l’obiettivo di rendere accessibili concetti tecnici complessi attraverso tutorial chiari e pratici.

Articoli Correlati

Check Also
Chiudi
Pulsante per tornare all'inizio