INTRODUZIONE

Arduino è una piattaforma open-source basata su microcontrollori AVR (principalmente ATmega328P per Arduino Uno). Lo sviluppo avviene tramite l'IDE Arduino, che fornisce un ambiente C/C++ semplificato con funzioni wrapper per I/O, comunicazioni e temporizzazione.

CaratteristicaArduino Uno R3 (ATmega328P)
Clock16 MHz
Flash32 KB (0.5 KB bootloader)
SRAM2 KB
EEPROM1 KB
Digital I/O14 pin (6 PWM: pin 3, 5, 6, 9, 10, 11)
Analog In6 pin (A0–A5, ADC 10-bit)
Interrupt esterni2 (INT0 = pin 2, INT1 = pin 3)
Tensione operativa5V
Alimentazione (Vin / barrel jack)7–12V raccomandata (limite assoluto 6–20V)
Uscita pin 5VMax ~500 mA (limitata dal regolatore onboard)
Uscita pin 3.3VMax 50 mA (fornita dal regolatore LP2985)
Corrente per pin I/O20 mA consigliata (max assoluto 40 mA)
Corrente totale porte I/OMax 200 mA cumulativi su tutti i pin
USBUSB-B (alimentazione + programmazione)
Limiti di tensione/corrente — regole fondamentali:
• I pin digitali e analogici tollerano solo 0–5V. Tensioni superiori danneggiano il chip.
• Non superare mai 20 mA per singolo pin in progetti reali (40 mA è il massimo assoluto del datasheet, non un valore operativo).
• La somma delle correnti di tutti i pin non deve superare 200 mA totali.
• Il pin 5V può erogare al massimo ~500 mA quando alimentato via USB (dipende dalla porta USB).
• Il pin 3.3V può fornire max 50 mA: non alimentare sensori esigenti da questo pin.
• Il regolatore di tensione onboard dissipa calore: con Vin alta (es. 12V) e carico elevato si surriscalda.
Il linguaggio Arduino è C/C++ con un preprocessore che aggiunge automaticamente #include <Arduino.h> e prototipi di funzione.
CONFRONTO SCHEDE: UNO R3 vs UNO R4 WiFi vs MEGA 2560
Caratteristica UNO R3 UNO R4 WiFi Mega 2560
Microcontrollore ATmega328P (AVR 8-bit) Renesas RA4M1 (ARM Cortex-M4F 32-bit) ATmega2560 (AVR 8-bit)
Clock 16 MHz 48 MHz 16 MHz
Flash 32 KB 256 KB 256 KB (8 KB bootloader)
SRAM 2 KB 32 KB 8 KB
EEPROM 1 KB 8 KB (emulata in Data Flash) 4 KB
Digital I/O 14 14 54
Pin PWM 6 (pin 3,5,6,9,10,11) 6 (pin 3,5,6,9,10,11) 15 (pin 2–13, 44–46)
Analog In 6 (10-bit) 6 (14-bit) 16 (10-bit)
DAC No 1 (12-bit, pin A0) No
Interrupt esterni 2 (pin 2, 3) 2 (pin 2, 3) 6 (pin 2, 3, 18, 19, 20, 21)
Pin-change interrupt 20 (tutti i pin digitali + analogici) Tutti i pin (via IRQ controller) 24 (pin 0–15 + A0–A7)
UART / Serial 1 1 (+ 1 USB) 4 (Serial, Serial1–Serial3)
I2C 1 1 (+ 1 aggiuntivo) 1
SPI 1 1 1
USB USB-B USB-C USB-B
WiFi / Bluetooth No WiFi + BLE (modulo ESP32-S3) No
Matrice LED No 12×8 LED matrix onboard No
RTC No Sì (integrato) No
CAN bus No Sì (integrato) No
Op Amp No Sì (integrato) No
Tensione operativa 5V 5V 5V
Vin raccomandata 7–12V 6–24V 7–12V
Corrente per pin I/O 20 mA (max 40 mA) 8 mA (max 20 mA) 20 mA (max 40 mA)
Corrente totale I/O 200 mA ~100 mA 200 mA (per porta)
Dimensioni 68.6 × 53.4 mm 68.6 × 53.4 mm 101.5 × 53.3 mm
Compatibilità shield Standard UNO Standard UNO UNO-compatibile + pin extra
Quando scegliere quale scheda:
UNO R3 — Progetti didattici, prototipazione semplice, massima compatibilità con tutorial e librerie esistenti.
UNO R4 WiFi — Progetti IoT, necessità di WiFi/BLE, DAC, maggiore memoria, stessa forma dell'UNO.
Mega 2560 — Progetti con molti sensori/attuatori (54 pin digitali, 16 analogici), più UART, stampanti 3D (Marlin).
Attenzione UNO R4 WiFi: la corrente massima per pin è 8 mA (consigliata), molto inferiore ai 20 mA dell'UNO R3. Pilotare LED o carichi direttamente potrebbe richiedere transistor/MOSFET esterni. Inoltre, alcune librerie AVR non sono compatibili con l'architettura ARM (Renesas RA4M1).
STRUTTURA SKETCH
// Le variabili globali sono dichiarate qui
int led = 13;

void setup() {
  // Eseguita UNA SOLA volta all'avvio
  pinMode(led, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // Eseguita in loop continuo
  digitalWrite(led, HIGH);
  delay(1000);
  digitalWrite(led, LOW);
  delay(1000);
}
FunzioneDescrizione
setup()Inizializzazione: eseguita una volta dopo power-on o reset
loop()Ciclo principale: eseguita ripetutamente dopo setup()
TIPI DI DATO
TipoDim.Range
boolean1 bytetrue / false
byte1 byte0 – 255
char1 byte-128 – 127
unsigned char1 byte0 – 255
int2 byte-32768 – 32767
unsigned int2 byte0 – 65535
word2 byte0 – 65535
long4 byte-2.147.483.648 – 2.147.483.647
unsigned long4 byte0 – 4.294.967.295
float4 byte±3.4028235E+38 (6-7 cifre decimali)
double4 byteIdentico a float su AVR
StringvariabileOggetto stringa (heap)
Qualificatori
QualificatoreDescrizione
constValore costante (preferire a #define)
volatileVariabile modificata in ISR — evita ottimizzazioni del compilatore
staticVariabile locale persistente fra chiamate
PROGMEMSalva dati in Flash anziché in SRAM
// PROGMEM: dati costanti in Flash
const char msg[] PROGMEM = "Hello from Flash";
char buf[20];
strcpy_P(buf, msg);  // Copia da Flash a SRAM

// F() macro: stringhe direttamente da Flash
Serial.println(F("Salva SRAM!"));
DIGITAL I/O
FunzioneDescrizione
pinMode(pin, mode)Configura il pin: INPUT, OUTPUT, INPUT_PULLUP
digitalWrite(pin, val)Scrive HIGH o LOW su pin digitale
digitalRead(pin)Legge stato pin: ritorna HIGH o LOW
tone(pin, freq)Genera onda quadra alla frequenza Hz (usa Timer2)
tone(pin, freq, dur)Genera tono per durata in ms, poi si ferma
noTone(pin)Ferma il tono generato su quel pin
pulseIn(pin, val)Misura durata impulso HIGH/LOW in µs (timeout 1s)
pulseIn(pin, val, timeout)Come sopra con timeout custom in µs
shiftOut(data, clock, order, val)Invia byte bit a bit: MSBFIRST o LSBFIRST
shiftIn(data, clock, order)Riceve byte bit a bit
void setup() {
  pinMode(13, OUTPUT);       // LED builtin
  pinMode(2, INPUT_PULLUP);  // Pulsante con pull-up interno
}

void loop() {
  int stato = digitalRead(2);
  digitalWrite(13, !stato);   // INPUT_PULLUP: LOW = premuto
}
ANALOG I/O
FunzioneDescrizione
analogRead(pin)Legge valore analogico (0–1023, ADC 10 bit)
analogWrite(pin, val)PWM: valore 0–255 (solo pin PWM: 3, 5, 6, 9, 10, 11)
analogReference(type)Imposta riferimento: DEFAULT (5V), INTERNAL (1.1V), EXTERNAL
int sensorValue = analogRead(A0);           // 0-1023
float voltage = sensorValue * (5.0 / 1023.0); // Conversione in Volt

analogWrite(9, 128);  // PWM 50% duty cycle sul pin 9
I pin analogici (A0–A5) possono essere usati anche come pin digitali (14–19).
TEMPORIZZAZIONE
FunzioneDescrizione
millis()Millisecondi dall'avvio (overflow dopo ~49.7 giorni, unsigned long)
micros()Microsecondi dall'avvio (overflow dopo ~70 minuti, risoluzione 4 µs)
delay(ms)Pausa bloccante in millisecondi
delayMicroseconds(us)Pausa bloccante in microsecondi (max ~16383)
// Pattern NON BLOCCANTE con millis()
unsigned long previousMillis = 0;
const long interval = 1000;

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    // azione periodica ogni 1 secondo
    digitalWrite(13, !digitalRead(13));
  }
  // altro codice eseguito senza blocco
}
Evitare delay() nel loop principale: blocca l'esecuzione e impedisce la gestione di eventi.
COMUNICAZIONE SERIALE
FunzioneDescrizione
Serial.begin(baud)Inizializza UART (9600, 19200, 38400, 57600, 115200 ...)
Serial.print(val)Stampa valore (testo, numero)
Serial.println(val)Stampa con newline
Serial.print(val, fmt)Formato: BIN, OCT, DEC, HEX, o cifre decimali
Serial.write(byte)Invia un byte raw
Serial.write(buf, len)Invia buffer di byte
Serial.available()Byte disponibili nel buffer RX
Serial.read()Legge un byte (-1 se vuoto)
Serial.peek()Legge senza rimuovere dal buffer
Serial.readString()Legge fino a timeout come String
Serial.readStringUntil(ch)Legge fino al carattere delimitatore
Serial.parseInt()Legge e parsa un intero dal buffer
Serial.parseFloat()Legge e parsa un float dal buffer
Serial.setTimeout(ms)Imposta timeout per funzioni di lettura (default 1000 ms)
Serial.flush()Attende completamento trasmissione TX
Serial.end()Chiude la comunicazione seriale
void setup() {
  Serial.begin(9600);
  while (!Serial);  // Attendi connessione (solo Leonardo/Micro)
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    Serial.print("Ricevuto: ");
    Serial.println(c);
  }
}
FUNZIONI MATEMATICHE
FunzioneDescrizione
min(a, b)Minimo tra due valori
max(a, b)Massimo tra due valori
abs(x)Valore assoluto
constrain(x, lo, hi)Limita x nell'intervallo [lo, hi]
map(val, fromLo, fromHi, toLo, toHi)Rimappa un valore da un range a un altro (intera)
pow(base, exp)Potenza (float)
sqrt(x)Radice quadrata (float)
sq(x)Quadrato: x*x
sin(rad) / cos(rad) / tan(rad)Funzioni trigonometriche (float, radianti)
// map(): rimappare lettura ADC a range PWM
int sensor = analogRead(A0);                      // 0-1023
int pwmVal = map(sensor, 0, 1023, 0, 255);       // 0-255
int clamped = constrain(pwmVal, 10, 245);          // Limita range
analogWrite(9, clamped);
map() usa aritmetica intera: map(1, 0, 10, 0, 3) restituisce 0, non 0.3. Per mappatura float, usare calcolo manuale.
RANDOM
FunzioneDescrizione
randomSeed(seed)Inizializza il generatore pseudo-random
random(max)Ritorna long casuale in [0, max)
random(min, max)Ritorna long casuale in [min, max)
void setup() {
  randomSeed(analogRead(0));  // Seed da pin flottante (rumore)
}

void loop() {
  long r = random(1, 7);  // Simula dado: 1-6
}
OPERAZIONI BIT
Operatori Bitwise
OperatoreDescrizioneEsempio
&AND0b1100 & 0b1010 = 0b1000
|OR0b1100 | 0b1010 = 0b1110
^XOR0b1100 ^ 0b1010 = 0b0110
~NOT~0b1100 = 0b0011 (su 4 bit)
<<Shift Left1 << 3 = 0b1000 = 8
>>Shift Right8 >> 2 = 0b0010 = 2
Funzioni Bit Arduino
FunzioneDescrizione
bitRead(val, bit)Legge il bit n-esimo
bitWrite(val, bit, bval)Scrive 0/1 nel bit n-esimo
bitSet(val, bit)Imposta bit a 1
bitClear(val, bit)Imposta bit a 0
bit(n)Equivale a 1 << n
lowByte(val)Byte basso di una word
highByte(val)Byte alto di una word
INTERRUPT
FunzioneDescrizione
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)Collega interrupt a funzione
detachInterrupt(digitalPinToInterrupt(pin))Scollega interrupt
interrupts()Riabilita interrupt globali (sei())
noInterrupts()Disabilita interrupt globali (cli())
ModeTrigger
LOWQuando il pin è LOW
CHANGECambio di stato
RISINGFronte di salita (LOW → HIGH)
FALLINGFronte di discesa (HIGH → LOW)
Arduino Uno: interrupt esterni solo su pin 2 (INT0) e pin 3 (INT1).
volatile bool pressed = false;

void buttonISR() {
  pressed = true;  // ISR: breve e veloce!
}

void setup() {
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), buttonISR, FALLING);
  Serial.begin(9600);
}

void loop() {
  if (pressed) {
    pressed = false;
    Serial.println("Premuto!");
  }
}
Nelle ISR: non usare delay(), millis() non avanza, Serial inaffidabile. Le variabili condivise devono essere volatile.
EEPROM
#include <EEPROM.h>
FunzioneDescrizione
EEPROM.read(addr)Legge un byte dall'indirizzo
EEPROM.write(addr, val)Scrive un byte (consuma un ciclo di scrittura)
EEPROM.update(addr, val)Scrive solo se diverso (risparmia cicli)
EEPROM.get(addr, var)Legge qualsiasi tipo (struct, float, ecc.)
EEPROM.put(addr, var)Scrive qualsiasi tipo (con update interno)
EEPROM.length()Dimensione EEPROM in byte (1024 su Uno)
EEPROM[addr]Accesso con operatore [] (lettura/scrittura)
// Salvare e leggere una struct
struct Config {
  int threshold;
  float calibration;
  byte mode;
};

Config cfg = {512, 1.05, 2};
EEPROM.put(0, cfg);        // Salva in EEPROM

Config loaded;
EEPROM.get(0, loaded);     // Rileggi da EEPROM
EEPROM: ~100.000 cicli di scrittura per cella. Usare update()/put() invece di write() per preservare la durata.
WIRE (I2C)
#include <Wire.h>

Bus I2C (TWI): SDA = pin A4, SCL = pin A5 su Arduino Uno. Velocità standard: 100 kHz.

FunzioneDescrizione
Wire.begin()Inizializza come master
Wire.begin(addr)Inizializza come slave con indirizzo 7-bit
Wire.setClock(freq)Imposta frequenza clock (100000 o 400000)
Wire.beginTransmission(addr)Inizia trasmissione verso slave
Wire.write(val)Accoda byte/dati nel buffer TX
Wire.write(buf, len)Accoda buffer di byte
Wire.endTransmission()Invia dati e rilascia bus (ritorna stato errore)
Wire.requestFrom(addr, qty)Richiede byte da slave
Wire.available()Byte disponibili nel buffer RX
Wire.read()Legge un byte dal buffer RX
Wire.onReceive(handler)Callback slave: dati ricevuti
Wire.onRequest(handler)Callback slave: master richiede dati
// Master: scrive e legge da dispositivo I2C
Wire.begin();
Wire.beginTransmission(0x68);  // Indirizzo MPU-6050
Wire.write(0x3B);              // Registro ACCEL_XOUT_H
Wire.endTransmission(false);   // Repeated start
Wire.requestFrom(0x68, 6);     // Leggi 6 byte
while (Wire.available()) {
  byte b = Wire.read();
}
SPI
#include <SPI.h>

SPI su Uno: MOSI = pin 11, MISO = pin 12, SCK = pin 13, SS = pin 10.

FunzioneDescrizione
SPI.begin()Inizializza SPI come master
SPI.end()Disabilita SPI
SPI.beginTransaction(settings)Configura e avvia transazione
SPI.endTransaction()Termina transazione
SPI.transfer(val)Invia e riceve un byte
SPI.transfer(buf, len)Trasferisce buffer in-place
SPISettings(clock, order, mode)Parametri: clock Hz, MSBFIRST/LSBFIRST, SPI_MODE0-3
SPI.begin();
pinMode(10, OUTPUT);  // Chip Select

SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(10, LOW);         // Seleziona slave
byte result = SPI.transfer(0x42);
digitalWrite(10, HIGH);        // Deseleziona slave
SPI.endTransaction();
Modo SPICPOLCPHADescrizione
SPI_MODE000Clock idle LOW, campionamento fronte salita
SPI_MODE101Clock idle LOW, campionamento fronte discesa
SPI_MODE210Clock idle HIGH, campionamento fronte discesa
SPI_MODE311Clock idle HIGH, campionamento fronte salita
SERVO
#include <Servo.h>
FunzioneDescrizione
Servo myservoCrea oggetto servo
myservo.attach(pin)Collega servo al pin
myservo.attach(pin, min, max)Collega con pulse min/max in µs (default 544, 2400)
myservo.write(angle)Imposta angolo (0–180°)
myservo.writeMicroseconds(us)Imposta pulse diretto in µs
myservo.read()Ultimo angolo impostato
myservo.attached()Verifica se collegato
myservo.detach()Scollega (libera il pin)
La libreria Servo disabilita PWM su pin 9 e 10 (usa Timer1). Supporta fino a 12 servo su Uno.
Servo myservo;

void setup() {
  myservo.attach(9);
}

void loop() {
  for (int pos = 0; pos <= 180; pos++) {
    myservo.write(pos);
    delay(15);
  }
}
STEPPER
#include <Stepper.h>
FunzioneDescrizione
Stepper stepper(steps, p1, p2)2 pin (driver con step/dir)
Stepper stepper(steps, p1, p2, p3, p4)4 pin (motore unipolare/bipolare diretto)
stepper.setSpeed(rpm)Imposta velocità in RPM
stepper.step(steps)Ruota di N step (bloccante, negativo = inverso)
Stepper myStepper(200, 8, 9, 10, 11);  // 200 step/giro, 4 pin

void setup() {
  myStepper.setSpeed(60);  // 60 RPM
}

void loop() {
  myStepper.step(200);     // 1 giro completo
  delay(500);
  myStepper.step(-200);   // 1 giro inverso
  delay(500);
}
LIQUIDCRYSTAL (LCD)
#include <LiquidCrystal.h>
FunzioneDescrizione
LiquidCrystal lcd(rs, en, d4, d5, d6, d7)Costruttore (modalità 4-bit)
lcd.begin(cols, rows)Inizializza display (es. 16, 2)
lcd.print(val)Stampa testo/numero
lcd.setCursor(col, row)Posiziona cursore (0-indexed)
lcd.clear()Cancella display e cursore a (0,0)
lcd.home()Cursore a (0,0) senza cancellare
lcd.display() / noDisplay()Accende/spegne display
lcd.cursor() / noCursor()Mostra/nasconde cursore
lcd.blink() / noBlink()Abilita/disabilita lampeggio cursore
lcd.scrollDisplayLeft()Scorre display a sinistra
lcd.scrollDisplayRight()Scorre display a destra
lcd.autoscroll() / noAutoscroll()Abilita/disabilita scroll automatico
lcd.createChar(num, data)Crea carattere custom (num: 0–7, data: array 8 byte)
lcd.write(byte)Scrive carattere (incluso custom)
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

byte heart[8] = {
  0b00000, 0b01010, 0b11111, 0b11111,
  0b11111, 0b01110, 0b00100, 0b00000
};

void setup() {
  lcd.begin(16, 2);
  lcd.createChar(0, heart);
  lcd.print("Hello!");
  lcd.setCursor(0, 1);
  lcd.write((byte)0);  // Stampa cuore custom
}
SD / FILE
#include <SD.h>
#include <SPI.h>

Usa SPI. CS tipicamente su pin 4 (Ethernet Shield) o pin 10.

FunzioneDescrizione
SD.begin(csPin)Inizializza scheda SD
SD.exists(filename)Verifica esistenza file/dir
SD.mkdir(dirname)Crea directory
SD.rmdir(dirname)Rimuove directory vuota
SD.remove(filename)Cancella file
SD.open(filename, mode)Apre file: FILE_READ o FILE_WRITE
Metodo FileDescrizione
file.print(val) / println(val)Scrive su file
file.write(byte)Scrive byte raw
file.read()Legge un byte
file.available()Byte disponibili
file.seek(pos)Posiziona cursore
file.position()Posizione corrente
file.size()Dimensione file
file.close()Chiude file (flush)
file.flush()Forza scrittura su SD
file.isDirectory()Verifica se directory
file.openNextFile()Prossimo file nella directory
File dataFile = SD.open("log.txt", FILE_WRITE);
if (dataFile) {
  dataFile.println("Temperatura: 23.5");
  dataFile.close();
}
SOFTWARE SERIAL
#include <SoftwareSerial.h>

Emula una porta seriale su pin digitali qualsiasi. Utile per moduli GPS, Bluetooth, ecc.

FunzioneDescrizione
SoftwareSerial ss(rxPin, txPin)Crea porta seriale software
ss.begin(baud)Inizializza con baud rate
ss.available()Byte disponibili nel buffer
ss.read()Legge un byte
ss.print(val) / println(val)Invia dati
ss.listen()Attiva ricezione (una sola SoftwareSerial alla volta)
ss.isListening()Verifica se attiva
SoftwareSerial bluetooth(2, 3);  // RX=2, TX=3

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

void loop() {
  if (bluetooth.available())
    Serial.write(bluetooth.read());
  if (Serial.available())
    bluetooth.write(Serial.read());
}
Limitazioni: max ~57600 baud, un solo RX attivo alla volta, non funziona su tutti i pin (RX richiede pin con PCINT).
ETHERNET
#include <Ethernet.h>
#include <SPI.h>
FunzioneDescrizione
Ethernet.begin(mac)Inizializza con DHCP
Ethernet.begin(mac, ip)Inizializza con IP statico
Ethernet.localIP()Ritorna IP assegnato
Ethernet.maintain()Rinnova lease DHCP
EthernetServer (TCP)
FunzioneDescrizione
EthernetServer server(port)Crea server sulla porta
server.begin()Avvia ascolto
server.available()Ritorna client connesso con dati disponibili
server.print(val)Invia a tutti i client
EthernetClient (TCP)
FunzioneDescrizione
EthernetClient clientCrea oggetto client
client.connect(server, port)Connetti a server
client.connected()Verifica connessione
client.available()Byte disponibili
client.read()Legge un byte
client.print(val)Invia dati
client.stop()Chiude connessione
REGISTRI AVR — INTRODUZIONE

L'ATmega328P espone tutte le periferiche tramite registri I/O mappati in memoria (SFR — Special Function Registers). Manipolare direttamente i registri è più veloce e compatto rispetto alle funzioni Arduino, ma richiede conoscenza dell'hardware.

Operazioni Base sui Registri
OperazioneCodiceDescrizione
Set bitREG |= (1 << BIT)Imposta bit a 1
Clear bitREG &= ~(1 << BIT)Imposta bit a 0
Toggle bitREG ^= (1 << BIT)Inverte il bit
Check bit(REG >> BIT) & 1Legge valore del bit
Set più bitREG |= (1<<B1) | (1<<B2)Imposta più bit contemporaneamente
Clear più bitREG &= ~((1<<B1) | (1<<B2))Azzera più bit contemporaneamente
Macro AVR set_BV(BIT)Equivale a (1 << BIT)
// Equivalenza: digitalWrite(13, HIGH)
PORTB |= (1 << PB5);   // Pin 13 = PB5, circa 2 cicli di clock

// Equivalenza: digitalWrite(13, LOW)
PORTB &= ~(1 << PB5);  // ~125ns vs ~6µs di digitalWrite()

// Equivalenza: digitalRead(2)
if (PIND & (1 << PD2)) { /* pin 2 HIGH */ }
Mappa Pin → Registri (Arduino Uno)
Pin ArduinoPortaBitFunzioni Speciali
0PD0PORTD bit 0RXD (USART)
1PD1PORTD bit 1TXD (USART)
2PD2PORTD bit 2INT0 (interrupt esterno)
3PD3PORTD bit 3INT1, OC2B (PWM Timer2)
4PD4PORTD bit 4T0 (clock esterno Timer0)
5PD5PORTD bit 5OC0B (PWM Timer0), T1
6PD6PORTD bit 6OC0A (PWM Timer0)
7PD7PORTD bit 7
8PB0PORTB bit 0ICP1 (Input Capture Timer1)
9PB1PORTB bit 1OC1A (PWM Timer1)
10PB2PORTB bit 2OC1B (PWM Timer1), SS (SPI)
11PB3PORTB bit 3OC2A (PWM Timer2), MOSI
12PB4PORTB bit 4MISO (SPI)
13PB5PORTB bit 5SCK (SPI), LED builtin
A0PC0PORTC bit 0ADC0
A1PC1PORTC bit 1ADC1
A2PC2PORTC bit 2ADC2
A3PC3PORTC bit 3ADC3
A4PC4PORTC bit 4ADC4, SDA (I2C)
A5PC5PORTC bit 5ADC5, SCL (I2C)
GPIO — DDRx / PORTx / PINx

Ogni porta (B, C, D) ha tre registri a 8 bit:

RegistroFunzioneLettura/Scrittura
DDRxData Direction Register — 0 = Input, 1 = OutputR/W
PORTxOutput/Pull-up — se Output: valore pin; se Input: 1 = pull-up attivoR/W
PINxInput — legge lo stato corrente dei pin; scrittura = toggle PORTxR (W=toggle)
// Configura TUTTI i pin di PORTD come output
DDRD = 0xFF;         // 0b11111111

// Pin 13 (PB5) come output
DDRB |= (1 << DDB5);

// Pin 2 (PD2) come input con pull-up
DDRD &= ~(1 << DDD2);   // Input
PORTD |= (1 << PORTD2); // Pull-up attivo

// Leggere pin 2
if (PIND & (1 << PIND2)) { /* HIGH */ }

// Toggle veloce pin 13 (scrivendo 1 su PINx)
PINB = (1 << PINB5);  // Hardware toggle, 1 ciclo

// Scrivere un intero byte su PORTB (pin 8-13)
PORTB = 0b00101100;   // Pin 10,11,13 HIGH; altri LOW
Scrivere un intero registro (es. PORTD = 0xFF) modifica tutti i pin della porta simultaneamente: ideale per LED multipli o bus dati paralleli.
TIMER / COUNTER

L'ATmega328P ha 3 timer hardware:

TimerBitRegistriPin PWMUsato da Arduino
Timer08-bitTCCR0A/B, TCNT0, OCR0A/B5 (OC0B), 6 (OC0A)millis(), delay(), micros()
Timer116-bitTCCR1A/B/C, TCNT1, OCR1A/B, ICR19 (OC1A), 10 (OC1B)Servo library
Timer28-bitTCCR2A/B, TCNT2, OCR2A/B3 (OC2B), 11 (OC2A)tone()
Registri di Controllo TCCRnA / TCCRnB
Bit (TCCRnA)NomeFunzione
7:6COM0A1:COM0A0Compare Match Output A mode
5:4COM0B1:COM0B0Compare Match Output B mode
1:0WGM01:WGM00Waveform Generation Mode (bit bassi)
Bit (TCCRnB)NomeFunzione
7FOC0AForce Output Compare A
6FOC0BForce Output Compare B
3WGM02Waveform Generation Mode (bit alto)
2:0CS02:CS01:CS00Clock Select (prescaler)
Prescaler (Clock Select)
CS02:01:00Timer0/1Frequenza (16 MHz)
000Fermo
001clk/116 MHz
010clk/82 MHz
011clk/64250 kHz
100clk/25662.5 kHz
101clk/102415.625 kHz
110Ext T0/T1 falling
111Ext T0/T1 rising

Timer2 usa prescaler diversi: 1, 8, 32, 64, 128, 256, 1024.

Waveform Generation Mode (Timer0/2, 8-bit)
WGM2:1:0ModeTOPAggiornamento OCRx
000Normal0xFFImmediato
001Phase Correct PWM0xFFTOP
010CTCOCR0AImmediato
011Fast PWM0xFFBOTTOM
101Phase Correct PWMOCR0ATOP
111Fast PWMOCR0ABOTTOM
Interrupt Timer
RegistroBitDescrizione
TIMSK0TOIE0Overflow Interrupt Enable Timer0
TIMSK0OCIE0ACompare Match A Interrupt Enable Timer0
TIMSK0OCIE0BCompare Match B Interrupt Enable Timer0
TIMSK1TOIE1Overflow Interrupt Enable Timer1
TIMSK1OCIE1A/BCompare Match A/B Interrupt Timer1
TIMSK1ICIE1Input Capture Interrupt Timer1
TIMSK2TOIE2, OCIE2A/BTimer2 interrupt
// Timer1 CTC: interrupt ogni 1 secondo
// f = 16MHz / (prescaler * (1 + OCR1A))
// 1 Hz = 16000000 / (256 * (1 + 62499))
cli();                            // Disabilita interrupt
TCCR1A = 0;                      // Reset
TCCR1B = 0;
TCNT1  = 0;                      // Azzera contatore
OCR1A  = 62499;                   // Compare match value
TCCR1B |= (1 << WGM12);          // Modo CTC
TCCR1B |= (1 << CS12);           // Prescaler 256
TIMSK1 |= (1 << OCIE1A);         // Abilita Compare Match A
sei();                            // Riabilita interrupt

ISR(TIMER1_COMPA_vect) {
  // Eseguito ogni 1 secondo
  PINB = (1 << PB5);              // Toggle LED
}
PWM VIA TIMER
Compare Output Mode (COMnx1:COMnx0)
COMFast PWMPhase Correct
00Pin disconnessoPin disconnesso
01Toggle OC0A on Compare Match (WGM=111)WGM dipendente
10Non-inverting: clear on match, set at BOTTOMClear up-counting, set down-counting
11Inverting: set on match, clear at BOTTOMSet up-counting, clear down-counting
// Fast PWM su pin 6 (OC0A) con frequenza custom
// f_PWM = f_clk / (prescaler * 256)
// Con prescaler 64: 16MHz / (64 * 256) = 976.5 Hz (default Arduino)
DDRD |= (1 << DDD6);             // Pin 6 output
TCCR0A = (1<<COM0A1) | (1<<WGM01) | (1<<WGM00);  // Fast PWM, non-inv
TCCR0B = (1<<CS01) | (1<<CS00);  // Prescaler 64
OCR0A  = 128;                    // Duty cycle 50%  (128/256)

// Phase Correct PWM su pin 9 (OC1A) — Timer1 16-bit
// f = f_clk / (2 * prescaler * TOP)
DDRB |= (1 << DDB1);             // Pin 9 output
TCCR1A = (1<<COM1A1) | (1<<WGM11);
TCCR1B = (1<<WGM13) | (1<<CS11); // Phase Correct, prescaler 8, TOP=ICR1
ICR1   = 20000;                  // TOP: 50 Hz (per servo: 20ms periodo)
OCR1A  = 1500;                   // Pulse 1.5ms (servo centro)
Formule Frequenza PWM
ModoFormula
Fast PWM (TOP=0xFF)f = f_clk / (N * 256)
Fast PWM (TOP=OCRnA)f = f_clk / (N * (1 + OCRnA))
Phase Correct (TOP=0xFF)f = f_clk / (N * 510)
Phase Correct (TOP=OCRnA)f = f_clk / (2 * N * OCRnA)
CTCf = f_clk / (2 * N * (1 + OCRnA))

N = prescaler, f_clk = 16 MHz.

ADC — REGISTRI
RegistroBitDescrizione
ADMUXREFS1:REFS0 (7:6)Riferimento: 00=AREF, 01=AVCC, 11=Internal 1.1V
ADLAR (5)Left Adjust Result (per lettura 8-bit da ADCH)
MUX3:0 (3:0)Selezione canale: 0000=ADC0 ... 0101=ADC5, 1000=Temp, 1110=1.1V, 1111=GND
ADCSRAADEN (7)ADC Enable
ADSC (6)Start Conversion
ADATE (5)Auto Trigger Enable
ADPS2:0 (2:0)Prescaler: 010=/4, 011=/8, 100=/16, 101=/32, 110=/64, 111=/128
ADCL/ADCHRisultato conversione (10 bit). Leggere ADCL prima di ADCH
ADCSRBADTS2:0Auto Trigger Source (Free Running, Timer, Comparatore, ecc.)
DIDR0ADC5D:ADC0DDigital Input Disable (risparmio energetico)
// Lettura ADC manuale (equivale a analogRead(0))
ADMUX  = (1<<REFS0);                 // AVCC come riferimento
ADMUX  = (ADMUX & 0xF0) | (0 & 0x0F); // Canale ADC0
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Enable, prescaler 128

ADCSRA |= (1<<ADSC);                 // Avvia conversione
while (ADCSRA & (1<<ADSC));           // Attendi completamento
uint16_t result = ADC;                // Leggi risultato (ADCL + ADCH)

// Free Running Mode: conversioni continue
ADCSRA |= (1<<ADATE);                // Auto trigger
ADCSRB = 0;                           // Free running (ADTS = 000)
ADCSRA |= (1<<ADSC);                 // Prima conversione
// Da ora ADC converte continuamente, leggibile da ADC

// Lettura rapida a 8 bit
ADMUX |= (1<<ADLAR);                 // Left adjust
// ... avvia conversione ...
uint8_t fast = ADCH;                  // Solo 8 bit alti
USART — REGISTRI
RegistroBitDescrizione
UCSR0ARXC0 (7)Receive Complete (dato pronto)
UDRE0 (5)Data Register Empty (pronto per TX)
U2X0 (1)Double Speed Mode
UCSR0BRXCIE0 (7)RX Complete Interrupt Enable
RXEN0 (4)Receiver Enable
TXEN0 (3)Transmitter Enable
UCSR0CUCSZ01:00 (2:1)Character Size: 11 = 8 bit
USBS0 (3)Stop Bits: 0 = 1 stop, 1 = 2 stop
UBRR0H/LBaud Rate Register: UBRR = (F_CPU / 16 / baud) - 1
UDR0Data Register (TX e RX condividono l'indirizzo)
// Inizializzazione USART a 9600 baud
#define BAUD 9600
#define UBRR_VAL ((F_CPU / 16 / BAUD) - 1)  // = 103 a 16MHz

void uart_init() {
  UBRR0H = (UBRR_VAL >> 8);
  UBRR0L = UBRR_VAL;
  UCSR0B = (1<<RXEN0) | (1<<TXEN0);       // Abilita TX e RX
  UCSR0C = (1<<UCSZ01) | (1<<UCSZ00);    // 8 bit, 1 stop, no parity
}

void uart_tx(uint8_t data) {
  while (!(UCSR0A & (1<<UDRE0)));        // Attendi buffer vuoto
  UDR0 = data;
}

uint8_t uart_rx() {
  while (!(UCSR0A & (1<<RXC0)));         // Attendi dato
  return UDR0;
}

void uart_print(const char *s) {
  while (*s) uart_tx(*s++);
}
INTERRUPT ESTERNI — REGISTRI
INT0 / INT1 (Pin 2, 3)
RegistroBitDescrizione
EICRAISC11:ISC10 (3:2)INT1 Sense Control
EICRAISC01:ISC00 (1:0)INT0 Sense Control
EIMSKINT1, INT0External Interrupt Mask (enable)
EIFRINTF1, INTF0Interrupt Flag (scrivi 1 per clear)
ISCn1:ISCn0Trigger
00Livello LOW
01Cambio logico (CHANGE)
10Fronte discesa (FALLING)
11Fronte salita (RISING)
Pin Change Interrupt (PCINT) — tutti i pin
RegistroDescrizione
PCICRPin Change Interrupt Control: PCIE2 (PORTD), PCIE1 (PORTC), PCIE0 (PORTB)
PCMSK0Mask PORTB: PCINT7:0 (pin 8–13)
PCMSK1Mask PORTC: PCINT14:8 (pin A0–A5)
PCMSK2Mask PORTD: PCINT23:16 (pin 0–7)
PCIFRPin Change Interrupt Flag
// INT0 (pin 2) su fronte di discesa
EICRA |= (1<<ISC01);                // ISC01=1, ISC00=0 = FALLING
EIMSK |= (1<<INT0);                 // Abilita INT0
sei();

ISR(INT0_vect) {
  // Handler interrupt esterno 0
}

// Pin Change Interrupt su pin 8 (PCINT0)
PCICR  |= (1<<PCIE0);               // Abilita PCINT per PORTB
PCMSK0 |= (1<<PCINT0);              // Abilita PCINT0 (pin 8)
sei();

ISR(PCINT0_vect) {
  // Scatta per QUALSIASI pin abilitato in PCMSK0
  // Determinare quale pin è cambiato confrontando con stato precedente
  static uint8_t lastB = 0;
  uint8_t changed = PINB ^ lastB;
  lastB = PINB;
  if (changed & (1<<PB0)) { /* pin 8 cambiato */ }
}
SPI — REGISTRI
RegistroBitDescrizione
SPCRSPIE (7)SPI Interrupt Enable
SPE (6)SPI Enable
DORD (5)Data Order: 0=MSB first, 1=LSB first
MSTR (4)Master/Slave: 1=Master, 0=Slave
SPR1:SPR0 (1:0)Clock Rate: 00=/4, 01=/16, 10=/64, 11=/128
SPSRSPIF (7)SPI Interrupt Flag (transfer complete)
SPSRSPI2X (0)Double Speed: raddoppia clock SPI
SPDRSPI Data Register (R/W)
// SPI Master, modo 0, clock = F_CPU/16
DDRB |= (1<<DDB3) | (1<<DDB5) | (1<<DDB2); // MOSI, SCK, SS output
SPCR  = (1<<SPE) | (1<<MSTR) | (1<<SPR0);    // Enable, Master, /16

uint8_t spi_transfer(uint8_t data) {
  SPDR = data;
  while (!(SPSR & (1<<SPIF)));
  return SPDR;
}
TWI (I2C) — REGISTRI
RegistroBitDescrizione
TWBRBit Rate Register: f_SCL = F_CPU / (16 + 2*TWBR*prescaler)
TWCRTWINT (7)Interrupt Flag: scrivi 1 per avviare operazione
TWEA (6)Enable Acknowledge
TWSTA (5)Start Condition
TWSTO (4)Stop Condition
TWSRTWS7:3 (7:3)Status Code (tabella status nel datasheet)
TWSRTWPS1:0 (1:0)Prescaler: 00=1, 01=4, 10=16, 11=64
TWDRData Register
TWARSlave Address Register (7 bit + general call)
// TWI Master init: 100 kHz con F_CPU=16MHz
// TWBR = ((F_CPU / f_SCL) - 16) / 2 = (160-16)/2 = 72
TWSR = 0;                           // Prescaler = 1
TWBR = 72;                          // 100 kHz

// Invia START
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));       // Attendi completamento

// Invia SLA+W (indirizzo + write)
TWDR = (0x68 << 1) | 0;            // Indirizzo 0x68, bit W=0
TWCR = (1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));

// Invia dato
TWDR = 0x42;
TWCR = (1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));

// STOP
TWCR = (1<<TWINT) | (1<<TWSTO) | (1<<TWEN);
EEPROM — REGISTRI
RegistroBitDescrizione
EEARH/EEARLEEPROM Address Register (10 bit: 0–1023)
EEDREEPROM Data Register
EECREEPM1:0 (5:4)Programming Mode
EERIE (3)Ready Interrupt Enable
EEMPE (2)Master Program Enable (set prima di EEPE)
EEPE (1)Program Enable (avvia scrittura)
EECREERE (0)Read Enable
uint8_t eeprom_read(uint16_t addr) {
  while (EECR & (1<<EEPE));          // Attendi scrittura precedente
  EEAR = addr;
  EECR |= (1<<EERE);                // Avvia lettura
  return EEDR;
}

void eeprom_write(uint16_t addr, uint8_t data) {
  while (EECR & (1<<EEPE));          // Attendi scrittura precedente
  EEAR = addr;
  EEDR = data;
  cli();                            // Sezione critica
  EECR |= (1<<EEMPE);               // Master enable
  EECR |= (1<<EEPE);                // Avvia scrittura (entro 4 cicli)
  sei();
}
WATCHDOG TIMER

Il Watchdog Timer (WDT) resetta il microcontrollore se non viene "alimentato" periodicamente. Utile per recupero da blocchi software.

RegistroBitDescrizione
WDTCSRWDIF (7)Interrupt Flag
WDIE (6)Interrupt Enable
WDE (3)Watchdog Enable (reset mode)
WDP3:0 (5,2:0)Prescaler (timeout)
WDP3:0Timeout
000016 ms
000132 ms
001064 ms
0011125 ms
0100250 ms
0101500 ms
01101 s
01112 s
10004 s
10018 s
#include <avr/wdt.h>

// Abilita WDT con timeout 2 secondi
wdt_enable(WDTO_2S);

// Nel loop: resetta il timer prima del timeout
void loop() {
  wdt_reset();          // "Alimenta" il watchdog
  // ... codice ...
}

// Disabilita WDT
wdt_disable();

// WDT come interrupt (senza reset)
cli();
WDTCSR |= (1<<WDCE) | (1<<WDE);      // Sblocca modifica
WDTCSR  = (1<<WDIE) | (1<<WDP2) | (1<<WDP1); // Interrupt mode, 1s
sei();

ISR(WDT_vect) {
  // Eseguito ogni ~1s (wake da sleep o task periodico)
}
SLEEP MODES
ModoSM2:0CorrentePeriferiche AttiveWake Source
Idle000~15 mATimer, USART, ADC, TWI, SPIQualsiasi interrupt
ADC Noise Reduction001~6.5 mAADCADC, INT, TWI, WDT
Power-down010~0.36 µANessuna (solo WDT opzionale)INT, PCINT, TWI, WDT
Power-save011~0.9 µATimer2 (asincrono)Timer2, INT, PCINT, WDT
Standby110~0.2 mAOscillatore principaleCome Power-down + clock pronto
#include <avr/sleep.h>
#include <avr/power.h>

// Power-down con wake da INT0
void enterSleep() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();

  // Configura INT0 per wake
  EICRA |= (1<<ISC01);              // FALLING edge
  EIMSK |= (1<<INT0);

  sei();
  sleep_cpu();                      // ZZZ... (si ferma qui)
  sleep_disable();                  // Riprende dopo wake
}

ISR(INT0_vect) {
  // Wake-up handler (può essere vuoto)
}

// Disabilitare periferiche non usate per risparmiare energia
power_adc_disable();
power_spi_disable();
power_twi_disable();
power_timer1_disable();
power_timer2_disable();
INLINE ASSEMBLY (asm volatile)

GCC per AVR permette di inserire istruzioni assembly inline con asm volatile("..."). volatile impedisce al compilatore di ottimizzare/rimuovere il blocco. Utile per operazioni a bassissimo livello non esprimibili in C.

IstruzioneCodiceEffetto
Software Resetasm volatile ("jmp 0");Salta all’indirizzo 0 — simula un reset (non reinizializza i registri HW, ma riavvia lo sketch)
No Operationasm volatile ("nop");1 ciclo di clock (~62.5 ns a 16 MHz). Utile per micro-ritardi precisi
Disabilita Interruptasm volatile ("cli");Equivalente a noInterrupts() — clear interrupt flag
Abilita Interruptasm volatile ("sei");Equivalente a interrupts() — set interrupt flag
Sleep CPUasm volatile ("sleep");Entra nel modo sleep configurato (impostare SMCR prima)
Watchdog Resetasm volatile ("wdr");Resetta il timer del Watchdog — equivalente a wdt_reset()
Break (debug)asm volatile ("break");Breakpoint per debugger hardware (JTAG/debugWIRE)
Delay preciso con NOP
// Ritardo esatto di N cicli (N × 62.5 ns a 16 MHz)
#define NOP asm volatile ("nop")

// ~250 ns di pausa (4 cicli)
NOP; NOP; NOP; NOP;

// Utile per rispettare timing stretti di protocolli (es. WS2812B / DHT)
Lettura/Scrittura registri I/O con SBI/CBI
// Set bit — pin HIGH (1 ciclo, molto più veloce di digitalWrite)
asm volatile ("sbi %0, %1" :: "I" (_SFR_IO_ADDR(PORTB)), "I" (5));  // PB5 = pin 13 HIGH

// Clear bit — pin LOW
asm volatile ("cbi %0, %1" :: "I" (_SFR_IO_ADDR(PORTB)), "I" (5));  // PB5 = pin 13 LOW

// Equivalente C (più leggibile, stessa velocità con ottimizzazione -Os):
PORTB |=  (1 << PB5);  // HIGH
PORTB &= ~(1 << PB5);  // LOW
Sezione critica atomica
// Salva SREG, disabilita interrupt, esegue, ripristina
uint8_t oldSREG;
asm volatile ("in %0, __SREG__" : "=r" (oldSREG));  // salva stato
asm volatile ("cli");                                 // disabilita interrupt

// — operazioni atomiche qui —
uint16_t val = volatileCounter;  // lettura 16-bit sicura

asm volatile ("out __SREG__, %0" :: "r" (oldSREG));  // ripristina stato

// Alternativa più idiomatica con avr-libc:
#include <util/atomic.h>
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  val = volatileCounter;
}
jmp 0 non è un vero reset hardware: i registri delle periferiche (Timer, USART, ADC) mantengono il loro stato. Per un reset completo, usare il Watchdog: wdt_enable(WDTO_15MS); while(1);
SERVO — CODICI BEST PRACTICE

Un servomotore standard richiede un impulso PWM ogni 20 ms (50 Hz). Larghezza impulso: ~544 µs = 0°, ~1500 µs = 90°, ~2400 µs = 180°. La libreria Servo.h (built-in) gestisce tutto via Timer1.

Movimento fluido (non-bloccante)
#include <Servo.h>

Servo myServo;

int currentAngle = 90;
int targetAngle  = 90;
unsigned long lastMove = 0;
const int STEP_DELAY = 15;  // ms tra ogni grado (velocità)

void setup() {
  myServo.attach(9, 544, 2400);  // Pin, min µs, max µs
  myServo.write(currentAngle);
  Serial.begin(9600);
}

void smoothMove() {
  if (currentAngle == targetAngle) return;
  if (millis() - lastMove < STEP_DELAY) return;
  lastMove = millis();
  currentAngle += (targetAngle > currentAngle) ? 1 : -1;
  myServo.write(currentAngle);
}

void loop() {
  smoothMove();
  // Il resto del codice gira senza bloccarsi

  // Esempio: comando da seriale
  if (Serial.available()) {
    int angle = Serial.parseInt();
    targetAngle = constrain(angle, 0, 180);
  }
}
Controllo preciso con writeMicroseconds()
// writeMicroseconds() è più preciso di write()
// write(90) internamente chiama writeMicroseconds(1472)
// ma la conversione gradi → µs può perdere risoluzione

// Servo continuo: usare writeMicroseconds per velocità
myServo.writeMicroseconds(1500);  // Fermo
myServo.writeMicroseconds(1300);  // Rotazione lenta oraria
myServo.writeMicroseconds(1000);  // Rotazione veloce oraria
myServo.writeMicroseconds(1700);  // Rotazione lenta antioraria
myServo.writeMicroseconds(2000);  // Rotazione veloce antioraria
Multi-servo con sweep indipendente
#include <Servo.h>

struct ServoCtrl {
  Servo servo;
  int pos, target, step;
  unsigned long lastUpdate, interval;
};

ServoCtrl servos[3];

void setup() {
  servos[0].servo.attach(9);  servos[0].interval = 15;
  servos[1].servo.attach(10); servos[1].interval = 25;
  servos[2].servo.attach(11); servos[2].interval = 10;
  for (int i = 0; i < 3; i++) {
    servos[i].pos = 90; servos[i].target = 90;
    servos[i].servo.write(90);
  }
}

void updateServos() {
  unsigned long now = millis();
  for (int i = 0; i < 3; i++) {
    if (servos[i].pos == servos[i].target) continue;
    if (now - servos[i].lastUpdate < servos[i].interval) continue;
    servos[i].lastUpdate = now;
    servos[i].pos += (servos[i].target > servos[i].pos) ? 1 : -1;
    servos[i].servo.write(servos[i].pos);
  }
}

void loop() {
  updateServos();
  // Imposta target: servos[0].target = 45; ecc.
}
La libreria Servo disabilita analogWrite() su pin 9 e 10 (usa Timer1). Supporta fino a 12 servo su Uno.
Alimentare i servo da alimentazione esterna (5–6V), mai dal pin 5V di Arduino: i servo assorbono picchi di 500 mA+. Collegare solo il GND in comune.
LCD — PARALLELO + I2C (BEST PRACTICE)
1. LCD Parallelo (LiquidCrystal.h built-in)

Connessione diretta 4-bit: usa 6 pin (RS, E, D4–D7). Il pin R/W si collega a GND.

Pin LCDPin ArduinoFunzione
RS (4)12Register Select
E (6)11Enable
D4–D75, 4, 3, 2Bus dati 4-bit
V0 (3)Potenziometro 10kContrasto
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// Carattere custom: simbolo gradi
byte degreeChar[8] = {
  0b00110, 0b01001, 0b01001, 0b00110,
  0b00000, 0b00000, 0b00000, 0b00000
};

void setup() {
  lcd.begin(16, 2);
  lcd.createChar(0, degreeChar);
}

void loop() {
  float temp = readSensor();

  // Best practice: aggiornare solo le parti che cambiano
  // Evitare lcd.clear() nel loop (causa sfarfallio)
  lcd.setCursor(0, 0);
  lcd.print(F("Temp: "));         // F() per risparmiare SRAM
  lcd.print(temp, 1);
  lcd.write((byte)0);             // Carattere custom gradi
  lcd.print(F("C   "));           // Spazi per cancellare cifre residue

  lcd.setCursor(0, 1);
  lcd.print(F("Stato: OK     "));

  delay(500);
}
2. LCD I2C (LiquidCrystal_I2C — libreria esterna)

Con il modulo I2C (PCF8574) bastano 2 pin (SDA=A4, SCL=A5). Indirizzo tipico: 0x27 o 0x3F. Installare: Sketch → Libreria → Gestione librerie → cercare "LiquidCrystal I2C" di Frank de Brabander.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Indirizzo I2C, colonne, righe
LiquidCrystal_I2C lcd(0x27, 16, 2);

byte heart[8] = {
  0b00000, 0b01010, 0b11111, 0b11111,
  0b11111, 0b01110, 0b00100, 0b00000
};

void setup() {
  lcd.init();                    // Inizializza (al posto di begin)
  lcd.backlight();               // Accendi retroilluminazione
  lcd.createChar(0, heart);
  lcd.setCursor(0, 0);
  lcd.print(F("Hello I2C!"));
  lcd.setCursor(0, 1);
  lcd.write((byte)0);
}

// Trovare l'indirizzo I2C con scanner
void i2cScan() {
  Wire.begin();
  Serial.begin(9600);
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.print(F("Trovato: 0x"));
      Serial.println(addr, HEX);
    }
  }
}
Aggiornamento non-bloccante con millis()
unsigned long lastLcdUpdate = 0;
const unsigned long LCD_INTERVAL = 300;  // Aggiorna ogni 300ms

void loop() {
  if (millis() - lastLcdUpdate >= LCD_INTERVAL) {
    lastLcdUpdate = millis();
    updateLcd();  // Aggiorna solo quando serve
  }
  // Altro codice non bloccato...
}

void updateLcd() {
  // Stampa con padding per evitare residui
  char buf[17];  // 16 char + terminatore
  snprintf(buf, sizeof(buf), "T:%5.1fC H:%3d%%", temp, hum);
  lcd.setCursor(0, 0);
  lcd.print(buf);
}
Best Practice LCD
PraticaDettaglio
No lcd.clear() nel loopCausa sfarfallio visibile. Sovrascrivere con spazi o usare snprintf con larghezza fissa.
Usare F() per stringhelcd.print(F("testo")) risparmia SRAM preziosa.
Aggiornare a intervalliNon riscrivere l'LCD ogni ciclo di loop — 200–500 ms sono sufficienti per l'occhio umano.
Buffer con snprintfFormattare in un char[] con larghezza fissa evita residui e semplifica l'allineamento.
LCD I2C: solo 2 pinPreferire I2C quando i pin scarseggiano. Leggermente più lento ma trascurabile per display.
HC-SR04 — SENSORE ULTRASUONI

Sensore di distanza a ultrasuoni. Emette un impulso a 40 kHz e misura il tempo di ritorno dell'eco. Range: 2–400 cm. Non richiede librerie.

Pin HC-SR04Funzione
VCC5V
TrigTrigger: impulso HIGH ≥ 10 µs per avviare misura
EchoEcho: durata impulso HIGH proporzionale alla distanza
GNDMassa
const byte TRIG = 9;
const byte ECHO = 10;

void setup() {
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
  Serial.begin(9600);
}

float readDistanceCm() {
  // Invia impulso trigger di 10µs
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);

  // Misura durata echo in µs
  unsigned long duration = pulseIn(ECHO, HIGH, 30000);
  // timeout 30ms ≈ ~510 cm max

  if (duration == 0) return -1;  // Nessun eco (fuori range)

  // Distanza = (tempo * velocità suono) / 2
  // v_suono ≈ 343 m/s = 0.0343 cm/µs
  return duration * 0.0343 / 2.0;
}

void loop() {
  float dist = readDistanceCm();
  if (dist > 0) {
    Serial.print("Distanza: ");
    Serial.print(dist, 1);
    Serial.println(" cm");
  } else {
    Serial.println("Fuori range");
  }
  delay(100);  // Min 60ms tra misure consecutive
}
Per misure più accurate con temperatura: v = 331.3 + 0.606 * T_celsius m/s. A 20°C: 343.5 m/s.
Il sensore funziona a 5V. Se si usa con schede a 3.3V (ESP32, ecc.), servono level shifter o partitore sul pin Echo.
BUZZER / TONE

Due tipi di buzzer: attivo (suona con solo 5V, no frequenza) e passivo (richiede onda quadra, pilotabile con tone()). tone() usa Timer2, quindi disabilita PWM su pin 3 e 11.

Frequenze Note Musicali (Hz)
NotaCDEFGAB
Ottava 4262294330349392440494
Ottava 5523587659698784880988
const byte BUZZER = 8;

// Definizione note
#define NOTE_C4  262
#define NOTE_D4  294
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_G4  392
#define NOTE_A4  440
#define NOTE_B4  494
#define NOTE_C5  523
#define PAUSE    0

// Melodia: array di coppie {nota, durata_ms}
int melody[] = {
  NOTE_C4, 400, NOTE_D4, 400, NOTE_E4, 400, NOTE_F4, 400,
  NOTE_G4, 400, NOTE_A4, 400, NOTE_B4, 400, NOTE_C5, 800
};
int melodyLen = sizeof(melody) / sizeof(melody[0]) / 2;

void setup() {
  for (int i = 0; i < melodyLen; i++) {
    int nota = melody[i * 2];
    int dur  = melody[i * 2 + 1];
    if (nota != PAUSE)
      tone(BUZZER, nota, dur);
    delay(dur * 1.3);          // Pausa tra note (30%)
    noTone(BUZZER);
  }
}

// Beep di allarme senza tone() (bit-banging)
void beepManual(int freqHz, int durationMs) {
  long period = 1000000L / freqHz;
  long cycles  = (long)freqHz * durationMs / 1000;
  for (long i = 0; i < cycles; i++) {
    digitalWrite(BUZZER, HIGH);
    delayMicroseconds(period / 2);
    digitalWrite(BUZZER, LOW);
    delayMicroseconds(period / 2);
  }
}
PIR — SENSORE DI MOVIMENTO

Il sensore PIR (HC-SR501) rileva movimento tramite radiazione infrarossa. Output digitale: HIGH quando rileva movimento. Ha due trimmer: sensibilità e tempo di ritardo (2s–300s). Richiede ~60s di warm-up all'accensione.

const byte PIR_PIN = 2;
const byte LED_PIN = 13;

void setup() {
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println(F("Calibrazione PIR... attendere 60s"));
  delay(60000);                    // Warm-up sensore
  Serial.println(F("Pronto."));
}

void loop() {
  if (digitalRead(PIR_PIN) == HIGH) {
    digitalWrite(LED_PIN, HIGH);
    Serial.println(F("Movimento rilevato!"));
  } else {
    digitalWrite(LED_PIN, LOW);
  }
  delay(200);
}

// Versione con interrupt (non-bloccante)
volatile bool motionDetected = false;

void motionISR() { motionDetected = true; }

void setup() {
  pinMode(PIR_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIR_PIN), motionISR, RISING);
  Serial.begin(9600);
}

void loop() {
  if (motionDetected) {
    motionDetected = false;
    Serial.println(F("Movimento!"));
    // azione: accendi luce, allarme, ecc.
  }
}
LDR — FOTORESISTORE

Il fotoresistore (LDR) varia la sua resistenza con la luce: alta R al buio (~1 MΩ), bassa R in piena luce (~1 kΩ). Si legge con un partitore di tensione collegato a un pin analogico.

Circuito: 5V → LDR → nodo(A0) → R (10 kΩ) → GND. Più luce = più tensione su A0 (valore analogico alto).

const byte LDR_PIN = A0;
const byte LED_PIN = 9;  // Pin PWM

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

void loop() {
  int lux = analogRead(LDR_PIN);           // 0-1023

  // Accendi LED proporzionalmente al buio
  int brightness = map(lux, 0, 1023, 255, 0);
  analogWrite(LED_PIN, brightness);

  // Calcolo tensione e resistenza LDR
  float voltage = lux * 5.0 / 1023.0;
  float rLdr = 10000.0 * voltage / (5.0 - voltage);  // R partitore = 10k

  Serial.print("ADC: "); Serial.print(lux);
  Serial.print("  R_LDR: "); Serial.print(rLdr, 0);
  Serial.println(" ohm");
  delay(200);
}
Per soglia giorno/notte, calibrare il valore ADC nel proprio ambiente e usare un confronto con isteresi.
TERMISTOR NTC — SENSORE TEMPERATURA

Un termistor NTC (Negative Temperature Coefficient) diminuisce la resistenza con l'aumento della temperatura. Tipico: 10 kΩ a 25°C, B=3950. Si legge con partitore di tensione + equazione di Steinhart-Hart semplificata.

Circuito: 5V → R (10 kΩ) → nodo(A0) → NTC → GND.

const byte  NTC_PIN   = A0;
const float R_SERIES  = 10000.0;  // Resistenza partitore
const float R_NTC_25  = 10000.0;  // R del NTC a 25°C
const float BETA      = 3950.0;   // Coefficiente B (datasheet)
const float T_REF     = 298.15;   // 25°C in Kelvin

float readTemperature() {
  int adc = analogRead(NTC_PIN);
  float rNtc = R_SERIES * (1023.0 / adc - 1.0);

  // Equazione B-parameter (Steinhart-Hart semplificata)
  // 1/T = 1/T0 + (1/B) * ln(R/R0)
  float tKelvin = 1.0 / (1.0/T_REF + (1.0/BETA) * log(rNtc/R_NTC_25));
  return tKelvin - 273.15;  // Converti in °C
}

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

void loop() {
  float temp = readTemperature();
  Serial.print("Temperatura: ");
  Serial.print(temp, 1);
  Serial.println(" C");
  delay(500);
}
Per maggiore precisione, eseguire media di più letture ADC (oversampling) e verificare il valore B dal datasheet del termistor specifico.
RELÈ / MOSFET — PILOTARE CARICHI

Per comandare carichi ad alta potenza (motori, lampade, elettrovalvole) servono relè o MOSFET come switch. Il pin Arduino fornisce solo il segnale di controllo.

Modulo Relè (con optoisolatore)
const byte RELAY_PIN = 7;

void setup() {
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, HIGH);  // Molti moduli sono ACTIVE LOW
}

void loop() {
  digitalWrite(RELAY_PIN, LOW);   // Accendi (ACTIVE LOW)
  delay(5000);
  digitalWrite(RELAY_PIN, HIGH);  // Spegni
  delay(5000);
}
MOSFET N-Channel (IRLZ44N) per DC
// Circuito: Drain → carico → V+
//           Source → GND
//           Gate → pin Arduino (R 100Ω serie, R 10k pulldown)
const byte MOSFET_PIN = 9;  // Pin PWM per controllo velocità

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

void loop() {
  // Controllo velocità motore DC con PWM
  analogWrite(MOSFET_PIN, 64);   // 25%
  delay(2000);
  analogWrite(MOSFET_PIN, 128);  // 50%
  delay(2000);
  analogWrite(MOSFET_PIN, 255);  // 100%
  delay(2000);
  analogWrite(MOSFET_PIN, 0);    // OFF
  delay(2000);
}
Scegliere MOSFET logic-level (es. IRLZ44N, IRL540N): si aprono completamente con gate a 5V. I MOSFET standard (IRF540) richiedono 10V al gate e non funzionano direttamente con Arduino.
LED RGB

LED RGB: tre LED (Rosso, Verde, Blu) in un unico package. Due tipi: anodo comune (pin lungo = 5V) e catodo comune (pin lungo = GND). Si controllano con PWM per miscelare colori.

// LED RGB Catodo Comune
// Pin lungo → GND; R/G/B → resistori 220Ω → pin PWM
const byte R_PIN = 9, G_PIN = 10, B_PIN = 11;

void setColor(byte r, byte g, byte b) {
  analogWrite(R_PIN, r);
  analogWrite(G_PIN, g);
  analogWrite(B_PIN, b);
}

// Per ANODO COMUNE: invertire i valori
// analogWrite(R_PIN, 255 - r); ecc.

void setup() {
  pinMode(R_PIN, OUTPUT);
  pinMode(G_PIN, OUTPUT);
  pinMode(B_PIN, OUTPUT);
}

void loop() {
  setColor(255, 0,   0);    delay(1000);  // Rosso
  setColor(0,   255, 0);    delay(1000);  // Verde
  setColor(0,   0,   255);  delay(1000);  // Blu
  setColor(255, 255, 0);    delay(1000);  // Giallo
  setColor(0,   255, 255);  delay(1000);  // Ciano
  setColor(255, 0,   255);  delay(1000);  // Magenta
  setColor(255, 255, 255);  delay(1000);  // Bianco

  // Dissolvenza arcobaleno
  for (int i = 0; i < 256; i++) {
    setColor(i, 255 - i, 0);
    delay(10);
  }
}
SHIFT REGISTER 74HC595

Lo shift register 74HC595 espande le uscite: con 3 pin Arduino si controllano 8 output digitali. Più 595 si possono collegare in cascata (daisy-chain) per 16, 24, ... uscite.

Pin 595NomePin ArduinoFunzione
14DS (SER)11 (data)Ingresso dati seriale
11SHCP (SRCLK)12 (clock)Clock shift register
12STCP (RCLK)8 (latch)Latch: trasferisce dati alle uscite
13OEGNDOutput Enable (attivo LOW)
10MR (SRCLR)5VMaster Reset (attivo LOW)
Q0–Q78 uscite parallele
9Q7'Uscita seriale (per cascata)
const byte DATA_PIN  = 11;  // DS
const byte CLOCK_PIN = 12;  // SHCP
const byte LATCH_PIN = 8;   // STCP

void updateShiftRegister(byte data) {
  digitalWrite(LATCH_PIN, LOW);
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, data);
  digitalWrite(LATCH_PIN, HIGH);
}

void setup() {
  pinMode(DATA_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(LATCH_PIN, OUTPUT);
}

void loop() {
  // Knight Rider: LED che scorre avanti e indietro
  for (int i = 0; i < 8; i++) {
    updateShiftRegister(1 << i);
    delay(100);
  }
  for (int i = 6; i > 0; i--) {
    updateShiftRegister(1 << i);
    delay(100);
  }
}

// Due 595 in cascata (16 uscite)
void update16bit(uint16_t data) {
  digitalWrite(LATCH_PIN, LOW);
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, data >> 8);   // Byte alto (2° chip)
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, data & 0xFF); // Byte basso (1° chip)
  digitalWrite(LATCH_PIN, HIGH);
}
BEST PRACTICE — GENERALI
PraticaDettaglio
Evitare delay()Usare pattern non-bloccante con millis(). Permette multitasking cooperativo e reattività del sistema.
Usare const e constexprPreferire a #define per costanti: type-safe, visibili nel debugger.
Pin con nomi simboliciconst byte LED_PIN = 13; non numeri magici nel codice.
Macchine a statiPer logica complessa, usare enum + switch invece di flag booleani annidati.
Evitare StringL'oggetto String causa frammentazione heap su MCU con poca RAM. Preferire array char[] e funzioni C (strcpy, snprintf).
ISR breviLe ISR devono impostare flag e uscire. Elaborazione complessa nel loop().
Variabili ISR volatileOgni variabile condivisa tra ISR e codice principale deve essere volatile.
Sezioni criticheProteggere letture multi-byte di variabili volatile con noInterrupts()/interrupts().
Usare F() per stringheSerial.println(F("testo")) salva SRAM usando stringhe da Flash.
Debounce pulsantiSempre implementare debounce (software con millis() o hardware con RC).
// Pattern macchina a stati
enum State { IDLE, HEATING, COOLING, ERROR };
State currentState = IDLE;

void loop() {
  switch (currentState) {
    case IDLE:
      if (temp < target) currentState = HEATING;
      break;
    case HEATING:
      activateHeater();
      if (temp >= target) currentState = COOLING;
      if (temp > MAX_SAFE) currentState = ERROR;
      break;
    // ...
  }
}

// Debounce con millis()
unsigned long lastDebounce = 0;
const unsigned long debounceDelay = 50;
int lastState = HIGH, stableState = HIGH;

void loop() {
  int reading = digitalRead(2);
  if (reading != lastState) lastDebounce = millis();
  if (millis() - lastDebounce > debounceDelay) {
    if (reading != stableState) {
      stableState = reading;
      if (stableState == LOW) { /* pulsante premuto */ }
    }
  }
  lastState = reading;
}
BEST PRACTICE — MEMORIA
PraticaDettaglio
Monitorare SRAMControllare memoria libera con freeMemory() o calcolo manuale. Sotto 200 byte: instabilità.
PROGMEMGrandi array costanti (lookup table, stringhe menu) in Flash con PROGMEM e pgm_read_*.
Dimensionare bufferArray e buffer al minimo necessario. Ogni byte conta su 2 KB SRAM.
Tipi minimiUsare uint8_t (1 byte) invece di int (2 byte) quando il range lo permette.
No allocazione dinamicaEvitare malloc/new: frammentazione fatale su MCU. Allocare staticamente.
Attenzione allo stackRicorsione profonda e variabili locali grandi consumano stack (cresce verso l'heap).
Evitare floatOperazioni float emulate via software su AVR: lente e pesanti. Usare aritmetica a punto fisso se possibile.
// Calcolo memoria libera
int freeMemory() {
  extern int __heap_start, *__brkval;
  int v;
  return (int)&v - (__brkval == 0
    ? (int)&__heap_start : (int)__brkval);
}

// Lookup table in PROGMEM
const uint8_t sineTable[] PROGMEM = {
  128, 131, 134, 137, /* ... 256 valori */
};
uint8_t val = pgm_read_byte(&sineTable[i]);

// Aritmetica a punto fisso (8.8 fixed point)
int16_t temp_fixed = 2350;       // 23.50°C * 100
int16_t result = temp_fixed * 9 / 5 + 3200;  // Fahrenheit * 100
BEST PRACTICE — TIMING
PraticaDettaglio
Overflow di millis()Overflow dopo ~49.7 giorni. La sottrazione current - previous funziona correttamente con unsigned long grazie all'aritmetica modulare. Non usare > ma >=.
Scheduler cooperativoPer task multipli con periodi diversi, usare struttura array di task con tempi indipendenti.
Timer hardware per precisionePer timing critico usare timer/CTC con interrupt, non millis().
Evitare delayMicroseconds() lunghiPer ritardi >16 ms usare delay() o millis(). delayMicroseconds() non gestisce overflow.
// Scheduler cooperativo multi-task
struct Task {
  unsigned long interval;
  unsigned long lastRun;
  void (*callback)();
};

void readSensors() { /* ogni 100ms */ }
void updateDisplay() { /* ogni 500ms */ }
void logData() { /* ogni 5000ms */ }

Task tasks[] = {
  {100,  0, readSensors},
  {500,  0, updateDisplay},
  {5000, 0, logData}
};
const int NUM_TASKS = sizeof(tasks) / sizeof(tasks[0]);

void loop() {
  unsigned long now = millis();
  for (int i = 0; i < NUM_TASKS; i++) {
    if (now - tasks[i].lastRun >= tasks[i].interval) {
      tasks[i].lastRun = now;
      tasks[i].callback();
    }
  }
}
BEST PRACTICE — HARDWARE
PraticaDettaglio
Resistori di protezioneSempre resistori in serie su LED (220–470Ω). Non superare 20 mA per pin, 200 mA totale per chip.
Pull-up/pull-downInput non connessi (floating) producono letture casuali. Usare INPUT_PULLUP o resistori esterni.
Condensatori di bypass100 nF vicino all'alimentazione di ogni IC. 10 µF sul rail principale.
Non pilotare carichi dal pinPer motori, relè, LED ad alta potenza: usare transistor/MOSFET come switch.
Protezione ingressiClamping diodes o partitori di tensione per segnali >5V sui pin analogici/digitali.
Separare alimentazioniAlimentare motori/attuatori da alimentazione separata. Collegare solo il GND comune.
Diodo flybackSempre un diodo (1N4007) in antiparallelo su carichi induttivi (relè, solenoidi, motori).
Pin inutilizzatiConfigurarli come OUTPUT LOW o INPUT_PULLUP per ridurre consumo e rumore.
BEST PRACTICE — DEBUG
TecnicaDettaglio
Serial debugUsare Serial.print() con F() macro. Rimuovere o disabilitare in produzione con #ifdef DEBUG.
LED diagnosticoIl LED builtin (pin 13) è il primo strumento di debug: lampeggi diversi = stati diversi.
Compilazione condizionale#define DEBUG + #ifdef per abilitare/disabilitare log senza rimuovere codice.
Assert sempliceMacro di verifica per catturare condizioni impossibili durante lo sviluppo.
Monitor serialeStampare valori sensori formattati per il Serial Plotter dell'IDE (valori separati da spazio/tab).
Watchdog per sicurezzaAbilitare WDT in produzione per recupero automatico da hang.
// Compilazione condizionale per debug
#define DEBUG  // Commentare per produzione

#ifdef DEBUG
  #define DBG(x)   Serial.print(F(x))
  #define DBGLN(x) Serial.println(F(x))
  #define DBGV(x)  Serial.println(x)
#else
  #define DBG(x)
  #define DBGLN(x)
  #define DBGV(x)
#endif

void setup() {
  #ifdef DEBUG
    Serial.begin(115200);
    DBGLN("Sistema avviato");
  #endif
}

void loop() {
  int val = analogRead(A0);
  DBG("Sensore: ");
  DBGV(val);       // In produzione: zero overhead
}

// Output per Serial Plotter (valori separati da tab)
Serial.print(temp);
Serial.print("\t");
Serial.print(humidity);
Serial.print("\t");
Serial.println(pressure);