Arduino da zero: hardware, pin e programmazione

Imparare Arduino da zero significa imparare a collegare un circuito e a scrivere le istruzioni che lo fanno funzionare. In questa lezione conoscerai le schede più comuni, riconoscerai i componenti dell'Arduino UNO e passerai ai primi programmi: LED, pulsanti, letture analogiche, comunicazione seriale e gestione del tempo.

Scheda di riferimento: Arduino UNO R3. I collegamenti a 5 V, i numeri dei pin e gli intervalli numerici degli esempi si riferiscono a questa scheda. Altri modelli possono avere caratteristiche diverse. Ogni riquadro indicato come “Sketch completo” va caricato da solo, non incollato insieme agli altri.

Cos'è Arduino e a cosa serve?

Arduino è una piattaforma elettronica open source che comprende schede programmabili e strumenti software. La scheda legge degli ingressi, come un pulsante o un sensore, esegue il programma e controlla delle uscite, come un LED o un circuito di pilotaggio per un motore.

Un esempio: “se il pulsante è premuto, accendi il LED”. Il pulsante fornisce l'informazione, il programma prende la decisione e il pin di uscita comanda il LED. Lo stesso principio si applica a un termometro, a un piccolo robot o a un sistema di irrigazione.

Un programma Arduino si chiama sketch e viene normalmente salvato in un file .ino. Si scrive sul computer, poi si carica nella scheda. Un UNO R3 alimentato può continuare a eseguirlo senza restare collegato al computer.

Riferimento Arduino: Arduino: What is Arduino? · Arduino UNO R3.

Tipi di Arduino:

Confronto tra schede Arduino UNO, Nano, Mega, UNO Q e LeonardoLe cinque schede mostrate nella foto.

Arduino UNO R3: il riferimento per questa lezione

Ha il microcontrollore ATmega328P, connettori facilmente accessibili e una porta USB per la programmazione. Offre 14 pin digitali, 6 dei quali utilizzabili per il PWM, e 6 ingressi analogici. Qui useremo proprio l'UNO R3, non altri modelli della famiglia UNO.

Arduino Nano classico: piccolo e adatto alla breadboard

Occupa meno spazio e si inserisce comodamente in una breadboard, la basetta che permette di provare circuiti senza saldare. È utile quando vuoi rendere compatto un progetto. Nella foto è rappresentato il Nano classico: gli altri modelli della famiglia Nano non sono tutti elettricamente equivalenti.

Arduino Mega 2560: più collegamenti

Usa l'ATmega2560 e offre 54 pin digitali, 16 ingressi analogici e 4 porte seriali hardware. È adatto a progetti con molti pulsanti, sensori o dispositivi da collegare. La differenza pratica non è soltanto la dimensione: hai più ingressi, uscite e risorse disponibili.

Arduino UNO Q: Linux e microcontrollore sulla stessa scheda

Combina un processore Qualcomm Dragonwing QRB2210, che esegue Debian Linux, e un microcontrollore STM32U585 per gli sketch e il controllo in tempo reale. È pensato anche per applicazioni più complesse, come elaborazione di immagini e intelligenza artificiale. Non va trattato come un UNO R3 con più memoria: architettura, alimentazione e compatibilità dei collegamenti vanno verificate nella sua documentazione.

Arduino Leonardo: comunicazione USB integrata

Il suo ATmega32U4 gestisce direttamente l'USB. Questo permette anche di realizzare dispositivi che si presentano al computer come tastiere o mouse, con il software appropriato. Qui non useremo queste funzioni: gli esempi rimangono concentrati sul core Arduino dell'UNO R3.

Riferimento Arduino: Arduino UNO R3 · Arduino Nano · Arduino Mega 2560 · Arduino UNO Q · Arduino Leonardo.

Hardware Arduino UNO R3: i componenti da 1 a 11

Segui i numeri rossi nella seconda immagine. I paragrafi successivi descrivono i riferimenti da 1 a 11, nell'ordine indicato.

Componenti hardware di Arduino UNO R3 evidenziati in rosso e numeratiArduino UNO R3: usa i numeri della foto per trovare il componente descritto.

1. Microcontrollore ATmega328P

È il circuito integrato lungo al centro della scheda: esegue le istruzioni del tuo programma. Integra elaborazione, memoria e periferiche per leggere e controllare i pin. Nell'UNO R3 lavora a 16 MHz; ha 32 KB di memoria Flash per il programma, in parte occupati dal bootloader, e 2 KB di SRAM per i dati temporanei.

La Flash conserva lo sketch anche senza alimentazione. La SRAM contiene variabili e dati mentre il programma gira: dopo un reset le variabili vengono inizializzate di nuovo. Il bootloader è il piccolo programma che rende possibile caricare lo sketch tramite USB.

2. Porta USB di tipo B

Collega Arduino al computer: serve ad alimentare la scheda, caricare gli sketch e scambiare dati seriali. L'UNO R3 usa un circuito USB-seriale per mettere in comunicazione il computer con il microcontrollore principale. Il cavo deve trasportare anche dati, non soltanto alimentazione.

3. Presa di alimentazione esterna e regolazione della tensione

Il connettore cilindrico accetta un'alimentazione continua esterna. Per l'UNO R3 l'intervallo raccomandato è 7–12 V, con positivo al centro del connettore. Il regolatore nella zona evidenziata ricava la tensione necessaria alla scheda; la differenza di tensione viene in parte dissipata in calore.

Per le prime prove usa l'USB. Non portare 9 V sui pin 5V, 3.3V o sui pin di segnale. La tensione ammessa all'ingresso di alimentazione non è la tensione ammessa dagli ingressi del microcontrollore.

4. Pulsante RESET

Riavvia il programma: la scheda riparte e richiama nuovamente setup(). Non cancella lo sketch salvato in Flash. È utile per ripetere una prova dall'inizio o osservare cosa accade durante l'avvio.

5. LED ON

Indica che la scheda è alimentata. Non conferma che il programma sia corretto e non è il LED che useremo per il lampeggio: negli esempi comanderemo il LED integrato identificato da LED_BUILTIN.

6. LED TX e RX

Segnalano l'attività di trasmissione e ricezione dell'interfaccia USB-seriale, per esempio durante il caricamento o uno scambio con il Monitor Seriale. Non sono indicatori generici di tutti i pin e non vanno confusi con il LED ON.

7. Connettore POWER

Raccoglie i collegamenti di alimentazione e controllo. GND è il riferimento comune; 5V e 3.3V possono alimentare piccoli circuiti compatibili, rispettando i limiti della scheda. Per il pin 3.3V il limite indicato è 50 mA: non è una sorgente per motori o carichi impegnativi.

VIN porta all'ingresso del regolatore e non è un'uscita a 5 V. RESET, portato a GND, riavvia la scheda. IOREF comunica agli accessori il livello logico della scheda, pari a 5 V sull'UNO R3. Il contatto NC è riservato e va lasciato scollegato. Per iniziare non alimentare la scheda attraverso il pin 5V: si bypassano parti del percorso di alimentazione previsto.

8. Pin digitali da 0 a 13

Possono essere configurati come ingressi o uscite. In uscita, LOW corrisponde circa a 0 V e HIGH circa a 5 V sull'UNO R3. I pin contrassegnati con ~, cioè 3, 5, 6, 9, 10 e 11, supportano il PWM.

I pin 0/RX e 1/TX sono usati dalla seriale: lasciali liberi durante queste prove. I pin 11, 12 e 13 hanno anche funzioni SPI. Un pin controlla un segnale, non alimenta qualsiasi carico: il pinout Arduino indica un massimo di 20 mA per pin I/O, e vanno rispettati anche i limiti complessivi del microcontrollore.

9. Ingressi analogici A0–A5

Misurano tensioni tramite il convertitore analogico-digitale, o ADC. Nell'UNO R3 la lettura ha 10 bit: analogRead() restituisce un numero da 0 a 1023. Con il riferimento predefinito, il campo di misura va da GND alla tensione di alimentazione, nominalmente 5 V.

A0–A5 possono anche essere usati come pin digitali. A4 e A5 condividono rispettivamente le funzioni SDA e SCL. Non sono uscite analogiche vere: il nome “analogico” non significa che possano generare una tensione arbitraria.

10. AREF e pin SDA/SCL aggiuntivi

Il riquadro comprende AREF e i due contatti I2C aggiunti sull'UNO R3. AREF riguarda la tensione di riferimento dell'ADC: nei nostri esempi lascialo scollegato. Applicare una tensione esterna senza configurare correttamente il riferimento può causare problemi elettrici.

SDA è la linea dati I2C e SCL è la linea di clock. Sull'UNO R3 sono gli stessi segnali di A4 e A5, non due nuovi canali indipendenti. Servono a collegare periferiche compatibili; in questa lezione non programmeremo il bus I2C.

11. Connettore ICSP del microcontrollore principale

Il connettore a sei contatti permette la programmazione diretta dell'ATmega328P con un programmatore esterno e rende disponibili i segnali SPI. I collegamenti sono MISO/CIPO, MOSI/COPI, SCK, RESET, 5V e GND.

Si usa, per esempio, per ripristinare il bootloader. Non è necessario collegarci nulla per caricare normalmente uno sketch tramite USB. Prima di usarlo, controlla sempre l'orientamento sul pinout: non dedurlo soltanto dalla posizione nella foto.

Riferimento Arduino: Arduino UNO R3 · UNO R3: datasheet · UNO R3: pinout · UNO R3: specifiche / specifications.

Prima di cablare: scollega l’alimentazione, evita cortocircuiti tra 5V e GND e usa una resistenza in serie ai LED esterni. Per motori, relè e carichi ad alta corrente serve un circuito di pilotaggio adatto, non un collegamento diretto al pin.

Come programmare Arduino: IDE, scheda e caricamento

Arduino IDE è il programma con cui scrivi, controlli e carichi gli sketch. Il codice usa C/C++ insieme alle funzioni del core Arduino, cioè il software di base della scheda. Gli esempi qui sotto non richiedono librerie aggiuntive né righe #include.

  1. Installa e collega. Installa Arduino IDE dal sito ufficiale e collega l'UNO R3 al computer con un cavo USB dati.
  2. Seleziona scheda e porta. Scegli Arduino Uno e la porta corrispondente alla scheda. Se manca il supporto, installa Arduino AVR Boards dal Gestore schede: è il pacchetto della scheda, non una libreria del progetto.
  3. Verifica e carica. Il comando Verifica compila il codice e segnala errori; Carica trasferisce il programma alla scheda. Uno sketch che compila non garantisce però che i fili o la logica siano corretti.

Riferimento Arduino: Arduino IDE: upload a sketch.

void setup() e void loop(): da dove parte il programma

01_structure.ino
void setup() {
  // Eseguito una volta all'avvio o dopo un reset.
}

void loop() {
  // Eseguito ripetutamente dopo setup().
}

Sketch completo

void setup(): le istruzioni iniziali

setup() viene eseguita una volta a ogni avvio o reset. Qui imposti i pin e avvii la comunicazione seriale. La parola void significa che la funzione non restituisce un valore; non significa “esegui una volta”. È il nome speciale setup a identificare la funzione di inizializzazione.

void loop(): il lavoro ripetuto

Dopo setup(), Arduino richiama loop() ripetutamente. Arrivato all'ultima istruzione, ricomincia dal principio. Qui leggi pulsanti e sensori e aggiorni le uscite. Le istruzioni seguono un ordine: se inserisci un'attesa, le righe successive aspettano.

Le parentesi tonde contengono gli eventuali parametri; qui sono vuote. Le parentesi graffe racchiudono le istruzioni della funzione. In ogni sketch deve esserci una sola definizione di setup() e una sola di loop().

Riferimento Arduino: setup() · loop().

Sintassi, variabili e costanti: leggere una riga di codice

La maggior parte delle istruzioni termina con ;. I commenti dopo // servono a chi legge e non vengono eseguiti. Le maiuscole contano: il nome corretto è digitalRead(), non DigitalRead(); si scrive analogWrite(), non AnalogWrite().

Una variabile contiene un valore che può cambiare. Una costante, dichiarata con const, conserva il valore assegnato. Queste dichiarazioni, collocate sopra setup(), sono disponibili alle funzioni dello sketch:

Frammento: variables
const int LED_PIN = 9;
int reading = 0;
float voltage = 0.0;
bool enabled = false;
char command = '1';
unsigned long previousTime = 0;

Frammento: segui le indicazioni nel testo

Tipo o parolaSignificato pratico
const int LED_PIN = 9;Crea un nome per il pin 9. Cambiando questo valore aggiorni tutti i punti del programma che usano quel nome.
intNumero intero; sull’UNO R3 va da −32768 a 32767. Adatto, per esempio, a una lettura da 0 a 1023.
floatNumero con parte decimale. Nel codice si usa il punto: 2.5, non la virgola.
boolValore logico: true oppure false.
charUn carattere, come '1'. Una sequenza di testo usa invece doppi apici, come "Ciao".
unsigned longIntero senza segno a 32 bit sull’UNO R3: è il tipo da usare per i valori di millis() e micros().

Una variabile dichiarata dentro una funzione è locale a quel blocco. Per conservare uno stato tra i giri di loop(), puoi dichiararla fuori dalle funzioni, come faremo con il tempo precedente. = assegna un valore; == confronta due valori.

Riferimento Arduino: Arduino Language Reference · millis().

pinMode() e digitalWrite(): controllare un'uscita

pinMode(pin, modalità)

Configura il ruolo del pin. OUTPUT serve a comandare un'uscita; INPUT a leggere un segnale; INPUT_PULLUP a leggere un ingresso attivando una resistenza interna verso il positivo. Un ingresso scollegato e senza una resistenza di richiamo può leggere valori instabili.

digitalWrite(pin, HIGH oppure LOW)

Su un pin configurato come uscita imposta un livello alto o basso. Nella riga digitalWrite(9, HIGH);, 9 è il pin e HIGH è il livello richiesto: non stai inviando il numero 9 al circuito. Prima di usarla per comandare un LED, configura il pin con pinMode().

Riferimento Arduino: pinMode() · digitalWrite().

Non servono componenti esterni. LED_BUILTIN identifica il LED di prova della scheda, collegato al pin 13 sull'UNO R3.

02_blink.ino
const int LED_PIN = LED_BUILTIN;

void setup() {
  pinMode(LED_PIN, OUTPUT); // Configura il pin come uscita.
}

void loop() {
  digitalWrite(LED_PIN, HIGH); // Accende il LED integrato.
  delay(500);                 // Attende mezzo secondo.
  digitalWrite(LED_PIN, LOW);  // Spegne il LED.
  delay(500);                 // Attende mezzo secondo.
}

Sketch completo

const int LED_PIN = LED_BUILTIN; assegna un nome al collegamento. In setup(), pinMode(LED_PIN, OUTPUT); prepara l'uscita. Nel loop(), la prima digitalWrite() accende il LED; delay(500) aspetta 500 millisecondi; la seconda lo spegne; l'ultima attesa mantiene il LED spento prima di ricominciare.

Due attese da 500 ms formano un ciclo di circa un secondo. Cambiale entrambe a 100 per un lampeggio più rapido. Il tempo non lo decide digitalWrite(): lo decidi con le istruzioni di attesa.

Riferimento Arduino: digitalWrite() · delay().

digitalRead() e INPUT_PULLUP: leggere un pulsante

digitalRead(pin) restituisce HIGH o LOW. Con int buttonState = digitalRead(2); leggi il pin 2 e salvi il risultato, così puoi usarlo in una decisione.

Collegamento: metti un pulsante normalmente aperto tra D2 e GND. Con INPUT_PULLUP, a riposo leggi HIGH; premendo il pulsante colleghi il pin a massa e leggi LOW. Se il pulsante ha quattro piedini, usa due contatti che si uniscono soltanto quando premi, non due già collegati internamente.

03_button.ino
const int BUTTON_PIN = 2;
const int LED_PIN = LED_BUILTIN;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  int buttonState = digitalRead(BUTTON_PIN);

  if (buttonState == LOW) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
}

Sketch completo

if (buttonState == LOW) significa “se il pulsante è premuto”. Il blocco else viene eseguito nel caso contrario. Qui il LED segue il pulsante: non memorizza la pressione. I pulsanti meccanici possono produrre rimbalzi; per contare pressioni o commutare ON/OFF servirà anche un filtro antirimbalzo.

Riferimento Arduino: digitalRead() · pinMode().

if, else e operatori: prendere decisioni

if controlla una condizione; else if prova un'alternativa; else gestisce gli altri casi. Nella stessa catena viene eseguito soltanto il primo blocco la cui condizione è vera. Questo frammento va in loop(), dopo aver avviato la seriale con Serial.begin(9600) in setup():

Frammento: conditions
int reading = analogRead(A0);

if (reading < 300) {
  Serial.println("LOW");
} else if (reading < 700) {
  Serial.println("MEDIUM");
} else {
  Serial.println("HIGH");
}

Frammento: segui le indicazioni nel testo

OperatoreUso
== / !=Uguale / diverso.
<, >, <=, >=Minore, maggiore, minore o uguale, maggiore o uguale.
&& / ||AND: devono valere entrambe le condizioni. OR: basta che ne valga almeno una.
!Nega una condizione: !pressed significa “non premuto”.
+, -, *, /, %Operazioni aritmetiche; % è il resto della divisione tra interi.

Attenzione alla divisione tra interi: 5 / 2 produce 2, mentre 5.0 / 2.0 produce 2.5. Un altro errore comune è scrivere if (value = 10): assegna 10, non verifica se il valore è 10.

Riferimento Arduino: Arduino Language Reference.

Serial.begin(), print() e println(): parlare con il computer

Serial.begin(9600);, in setup(), configura la seriale. Apri il Monitor Seriale dell'IDE e scegli lo stesso valore, 9600 baud, per questi esempi su UNO R3.

Serial.print("Valore: "); invia testo senza andare a capo. Serial.println(reading); invia il valore e termina la riga. Serial.println(voltage, 2); mostra un numero decimale con due cifre dopo il punto. Scrivere soltanto print() non è equivalente: Serial indica dove inviare i dati.

Serial.write() invia byte senza convertirli nella rappresentazione numerica leggibile: Serial.print(65) invia il testo “65”, mentre Serial.write(65) invia il carattere ASCII “A”. Per osservare valori durante le prime prove usa soprattutto print() e println().

Riferimento Arduino: Serial.begin() · Serial.print().

Serial.available() e Serial.read(): ricevere un comando

Serial.available() indica quanti byte sono già arrivati; Serial.read() ne preleva uno. Se non ci sono dati, read() restituisce −1: per questo controlliamo prima available(). Carica lo sketch e invia 1 oppure 0 dal Monitor Seriale:

04_serial_control.ino
const int LED_PIN = LED_BUILTIN;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println("Invia 1 per ON, 0 per OFF");
}

void loop() {
  if (Serial.available() > 0) {
    char command = Serial.read();

    if (command == '1') {
      digitalWrite(LED_PIN, HIGH);
      Serial.println("LED ON");
    } else if (command == '0') {
      digitalWrite(LED_PIN, LOW);
      Serial.println("LED OFF");
    }
  }
}

Sketch completo

'1' è il carattere digitato, non il numero intero 1. Eventuali caratteri di fine riga vengono ignorati perché non corrispondono ai due comandi previsti. L'istruzione read() non legge automaticamente un'intera frase.

Riferimento Arduino: Serial.available() · Serial.read().

analogRead(): leggere un potenziometro

Collega un potenziometro da 10 kΩ: i due terminali esterni a 5V e GND, quello centrale, detto cursore, ad A0. Ruotandolo, la tensione del cursore cambia. Nell'UNO R3 analogRead(A0) la rappresenta con un numero da 0 a 1023; non restituisce direttamente volt.

05_analog_read.ino
const int POT_PIN = A0;
const float VREF = 5.0; // Valore nominale, non una misura dei 5 V.

void setup() {
  Serial.begin(9600);
}

void loop() {
  int reading = analogRead(POT_PIN);
  float voltage = reading * (VREF / 1023.0);

  Serial.print("ADC: ");
  Serial.print(reading);
  Serial.print(" | V: ");
  Serial.println(voltage, 2);
  delay(200);
}

Sketch completo

float voltage = reading * (VREF / 1023.0); applica una conversione approssimata degli estremi 0–1023 in 0–VREF. Una lettura intorno a 512 corrisponde a circa 2,5 V quando il riferimento è realmente 5 V. Il .0 permette di eseguire il calcolo con decimali.

Non è una misura di precisione: il valore nominale 5.0 può differire dalla tensione reale. L'ADC ha 1024 livelli, con un passo ideale di circa VREF/1024. Rispetta l'intervallo elettrico dell'ingresso e non applicare tensioni negative o superiori all'alimentazione.

Riferimento Arduino: analogRead().

analogWrite(): regolare la luminosità con il PWM

Sull'UNO R3 analogWrite(pin, valore) usa i pin PWM 3, 5, 6, 9, 10 e 11. Il valore va da 0 a 255: 0 mantiene l'uscita bassa, 255 alta; i valori intermedi cambiano la frazione di tempo trascorsa nello stato alto, detta duty cycle.

analogWrite(9, 128); produce un segnale alto per circa metà del tempo. Non genera una tensione continua precisa di 2,5 V: alterna livelli bassi e alti. Un LED appare meno luminoso perché riceve energia soltanto durante parte del ciclo.

map() e constrain(): passare da una scala all'altra

map(reading, 0, 1023, 0, 255) converte la scala dell'ingresso nella scala PWM. I cinque argomenti sono il valore, i due estremi iniziali e i due estremi finali. Il calcolo usa interi: non conserva le frazioni.

constrain(value, 0, 255) limita invece il risultato a quell'intervallo. map() non limita automaticamente: per esempio, un ingresso oltre la scala può produrre un'uscita oltre i limiti. Nell'esempio la lettura è già nel campo previsto; il limite esplicito mostra come proteggere l'uscita se in seguito modifichi il calcolo.

Collegamenti: conserva il potenziometro su A0 e aggiungi D9 → resistenza da 220 Ω → anodo del LED; catodo → GND. La resistenza è obbligatoria. Usa un LED esterno, non il LED integrato sul pin 13.

06_pwm_pot.ino
const int POT_PIN = A0;
const int LED_PIN = 9;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  int reading = analogRead(POT_PIN);
  int brightness = map(reading, 0, 1023, 0, 255);
  brightness = constrain(brightness, 0, 255);
  analogWrite(LED_PIN, brightness);
  delay(10);
}

Sketch completo

Riferimento Arduino: analogWrite() · map() · constrain().

delay(), millis() e micros(): gestire il tempo

delay(1000) ferma l'avanzamento dello sketch per un secondo. È comodo per le prime prove, ma durante quell'attesa il codice del loop() non legge nuovi pulsanti. La scheda non è spenta: alcune funzioni hardware e gli interrupt continuano a operare.

delayMicroseconds(10) introduce invece un'attesa in microsecondi. millis() e micros() non aspettano: restituiscono il tempo trascorso, rispettivamente in millisecondi e microsecondi. Usali quando devi controllare se un intervallo è passato senza fermare il resto del programma.

07_blink_without_delay.ino
const int LED_PIN = LED_BUILTIN;
const unsigned long INTERVAL_MS = 500;
unsigned long previousTime = 0;
bool ledState = false;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  unsigned long now = millis();

  if (now - previousTime >= INTERVAL_MS) {
    previousTime = now;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState ? HIGH : LOW);
  }

  // Qui puoi leggere pulsanti o sensori senza aspettare 500 ms.
}

Sketch completo

previousTime conserva l'istante dell'ultimo cambiamento. now - previousTime calcola il tempo trascorso; raggiunti 500 ms, aggiorni il riferimento e inverti lo stato con !ledState. L'espressione ledState ? HIGH : LOW sceglie HIGH se lo stato è vero, LOW altrimenti.

Usa unsigned long e la sottrazione, non il confronto con previousTime + intervallo. Sull'UNO R3 millis() torna a zero dopo circa 49,7 giorni: la differenza tra interi senza segno gestisce questo passaggio, purché il controllo avvenga regolarmente senza perdere un intero giro del contatore. Anche micros() riparte da zero, circa ogni 71,6 minuti, e la sua risoluzione sull'UNO R3 è 4 µs.

Riferimento Arduino: delay() · millis() · micros().

for, while e funzioni: organizzare il programma

for: ripetere un'operazione un numero definito di volte

Nel ciclo for (int level = 0; level <= 255; level += 5) il primo elemento inizializza il contatore, il secondo controlla quando proseguire e il terzo lo aggiorna dopo ogni giro. Con lo stesso LED esterno su D9 puoi creare una dissolvenza:

08_fade_for.ino
const int LED_PIN = 9;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  for (int level = 0; level <= 255; level += 5) {
    analogWrite(LED_PIN, level);
    delay(20);
  }

  for (int level = 255; level >= 0; level -= 5) {
    analogWrite(LED_PIN, level);
    delay(20);
  }
}

Sketch completo

Il secondo ciclo diminuisce il valore. Usiamo int, che può scendere sotto zero e far terminare la condizione; un contatore senza segno richiederebbe più attenzione. Questi cicli contengono delay(), quindi la dissolvenza è volutamente bloccante.

while: ripetere finché una condizione rimane vera

Questo frammento, nel loop() con seriale già inizializzata, restituisce al computer i caratteri disponibili:

Frammento: while_example
while (Serial.available() > 0) {
  int received = Serial.read();
  Serial.write(received);
}

Frammento: segui le indicazioni nel testo

Ogni lettura consuma un byte, ma un flusso continuo potrebbe trattenere il programma nel ciclo. break esce da un ciclo; continue salta alla sua iterazione successiva. switch può sostituire molte alternative if su uno stesso valore; i case normalmente terminano con break.

Creare una funzione e restituire un risultato

Una funzione raccoglie istruzioni riutilizzabili. Questa non è una libreria: è una funzione scritta nello stesso sketch. Mantieni il potenziometro collegato ad A0:

09_functions.ino
float readVoltage(int pin) {
  int reading = analogRead(pin);
  return reading * (5.0 / 1023.0);
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  float voltage = readVoltage(A0);
  Serial.println(voltage, 2);
  delay(200);
}

Sketch completo

float davanti a readVoltage indica il tipo restituito; int pin è il parametro ricevuto; return termina la funzione e consegna il risultato al chiamante. Per una funzione che esegue un'azione senza restituire un valore, usa void. Anche return; può terminare in anticipo una funzione void.

Riferimento Arduino: Arduino Language Reference · analogRead().

Altre funzioni native Arduino utili

tone() e noTone(): produrre un suono

Per la prova usa un piccolo trasduttore piezo passivo compatibile con il pilotaggio a bassa corrente: D8 → resistenza da 100 Ω → piezo → GND. Non collegare direttamente un altoparlante da 8 Ω o un buzzer che richiede molta corrente.

10_tone.ino
const int PIEZO_PIN = 8;

void setup() {
  pinMode(PIEZO_PIN, OUTPUT);
}

void loop() {
  tone(PIEZO_PIN, 1000); // Tono a 1000 Hz.
  delay(200);
  noTone(PIEZO_PIN);
  delay(800);
}

Sketch completo

tone(8, 1000) genera una frequenza di 1000 Hz; noTone(8) la interrompe. Puoi anche scrivere tone(8, 1000, 200) per una durata di 200 ms: la funzione avvia il tono senza attendere che finisca. Sul core AVR dell'UNO R3 tone() utilizza un timer che interferisce con il PWM dei pin 3 e 11.

Riferimento Arduino: tone().

pulseIn(): misurare la durata di un impulso

Il frammento seguente richiede D2 configurato come INPUT, una sorgente di impulsi con livelli compatibili 0–5 V e massa comune, e la seriale già avviata. Non lasciare il pin scollegato:

Frammento: pulse_example
unsigned long duration = pulseIn(2, HIGH, 30000UL);

if (duration == 0) {
  Serial.println("Timeout");
} else {
  Serial.println(duration);
}

Frammento: segui le indicazioni nel testo

pulseIn(2, HIGH, 30000UL) misura un impulso alto e restituisce la durata in microsecondi. Il timeout è 30000 µs, cioè 30 ms; il suffisso UL indica un numero unsigned long. Il risultato 0 segnala che non è stato completato un impulso entro il tempo disponibile, non una misura valida da usare senza controllo. La funzione può bloccare l'avanzamento dello sketch durante l'attesa.

Riferimento Arduino: pulseIn().

Calcoli, numeri casuali e testo in memoria Flash

IstruzioneA cosa serve
min(a, b) / max(a, b)Selezionano il valore più piccolo o più grande.
abs(value)Restituisce il valore assoluto di un numero.
random(1, 7)Produce un intero pseudocasuale da 1 a 6: il limite superiore è escluso.
randomSeed(seed)Inizializza la sequenza pseudocasuale. Lo stesso seme produce la stessa sequenza; non è casualità adatta alla sicurezza.
Serial.println(F("Pronto"));Sull’UNO R3 mantiene quel testo costante in Flash, riducendo l’uso di SRAM. Non richiede librerie esterne.

Con le funzioni di minimo, massimo o limite, evita argomenti con effetti collaterali come value++: calcola prima il valore, poi passalo alla funzione.

Riferimento Arduino: random() · constrain() · Serial.print() · Arduino Language Reference.

Passo successivo: attachInterrupt() e detachInterrupt()

Un interrupt richiama una piccola funzione quando si verifica un evento, senza aspettare il normale giro del loop(). Sull'UNO R3 gli interrupt esterni sono disponibili sui pin 2 e 3. La forma consigliata è attachInterrupt(digitalPinToInterrupt(pin), funzione, FALLING); detachInterrupt(digitalPinToInterrupt(pin)) disattiva l'associazione.

La funzione richiamata deve essere molto breve: evita delay() e stampe seriali al suo interno. Le variabili condivise con il codice principale richiedono volatile; per dati più grandi di un byte sull'AVR occorre anche proteggere gli accessi, perché volatile non li rende atomici. Per i primi pulsanti basta normalmente digitalRead().

Riferimento Arduino: attachInterrupt().

Progetto finale: pulsante, luminosità e Monitor Seriale

Metti insieme ciò che hai imparato. Tieni premuto il pulsante per abilitare il LED; ruotando il potenziometro ne regoli la luminosità. Il Monitor Seriale mostra le letture ogni mezzo secondo, senza delay().

Collega il potenziometro da 10 kΩ tra 5V e GND, con cursore su A0; il pulsante tra D2 e GND; il LED su D9 con resistenza da 220 Ω in serie, catodo a GND. Questi collegamenti sono per UNO R3. Apri il Monitor Seriale a 9600 baud.

11_final_project.ino
const int POT_PIN = A0;
const int BUTTON_PIN = 2;
const int LED_PIN = 9;
const unsigned long REPORT_INTERVAL_MS = 500;
unsigned long previousReport = 0;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int reading = analogRead(POT_PIN);
  bool pressed = digitalRead(BUTTON_PIN) == LOW;
  int brightness = map(reading, 0, 1023, 0, 255);
  brightness = constrain(brightness, 0, 255);

  if (!pressed) {
    brightness = 0;
  }
  analogWrite(LED_PIN, brightness);

  unsigned long now = millis();
  if (now - previousReport >= REPORT_INTERVAL_MS) {
    previousReport = now;
    Serial.print("ADC: ");
    Serial.print(reading);
    Serial.print(" | Button: ");
    Serial.print(pressed);
    Serial.print(" | PWM: ");
    Serial.println(brightness);
  }
}

Sketch completo

La riga bool pressed = digitalRead(BUTTON_PIN) == LOW; riunisce lettura e confronto. if (!pressed) impone luminosità zero quando rilasci il pulsante. Il controllo del LED avviene a ogni giro, mentre il blocco con millis() limita soltanto la frequenza delle stampe. Ingresso, decisione e uscita sono ora nello stesso programma.

Errori frequenti quando inizi con Arduino

Errore di compilazione: controlla prima il messaggio iniziale, poi ;, parentesi, maiuscole e nomi delle variabili. Verifica di non avere due setup() o due loop() copiati nello stesso file.

Caricamento non riuscito: controlla scheda e porta, il cavo dati e i collegamenti ai pin 0 e 1. Verifica che un altro programma non stia occupando la porta seriale.

LED spento: controlla anodo, catodo, resistenza, pin dichiarato e pinMode(). Per la regolazione di luminosità usa un vero pin PWM, come D9.

Pulsante invertito o letture instabili: con INPUT_PULLUP una pressione corrisponde a LOW. Un ingresso lasciato libero può leggere valori casuali; un pulsante meccanico può anche rimbalzare.

Programma poco reattivo: cerca delay(), cicli troppo lunghi o attese di impulsi. Le stampe seriali eccessive possono rallentare il programma: aggiorna il monitor a intervalli ragionevoli.

Riferimento Arduino: Arduino IDE: upload a sketch · digitalRead() · delay() · Serial.print().

Tre prove per verificare di aver capito

1. Modifica Blink per tenere il LED acceso 100 ms e spento 900 ms. 2. Inverti la risposta del LED nell'esempio del pulsante, senza modificare il cablaggio. 3. Nel progetto finale cambia l'uscita della map() da 0–255 a 0–120 per ridurre la luminosità massima.

Quando riesci a spiegare perché ogni modifica funziona, hai le basi per leggere un ingresso, decidere cosa fare e controllare un'uscita. Non serve memorizzare tutto: inizia da setup(), loop(), pinMode(), digitalRead(), digitalWrite(), analogRead(), analogWrite(), Serial e millis().

Documentazione e riferimenti

Le immagini di confronto e dell'hardware sono quelle fornite per questa lezione. I numeri 1–11 seguono la seconda immagine; le caratteristiche delle schede e il comportamento delle funzioni sono approfonditi tramite la documentazione ufficiale Arduino collegata nei singoli paragrafi. Gli esempi sono esercizi didattici per UNO R3, non prove di compatibilità con tutte le schede Arduino.

Riferimento Arduino: Arduino Language Reference · Arduino UNO R3 · UNO R3: pinout · Arduino IDE: upload a sketch.