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.
| Caratteristica | Arduino Uno R3 (ATmega328P) |
|---|---|
| Clock | 16 MHz |
| Flash | 32 KB (0.5 KB bootloader) |
| SRAM | 2 KB |
| EEPROM | 1 KB |
| Digital I/O | 14 pin (6 PWM: pin 3, 5, 6, 9, 10, 11) |
| Analog In | 6 pin (A0–A5, ADC 10-bit) |
| Interrupt esterni | 2 (INT0 = pin 2, INT1 = pin 3) |
| Tensione operativa | 5V |
| Alimentazione (Vin / barrel jack) | 7–12V raccomandata (limite assoluto 6–20V) |
| Uscita pin 5V | Max ~500 mA (limitata dal regolatore onboard) |
| Uscita pin 3.3V | Max 50 mA (fornita dal regolatore LP2985) |
| Corrente per pin I/O | 20 mA consigliata (max assoluto 40 mA) |
| Corrente totale porte I/O | Max 200 mA cumulativi su tutti i pin |
| USB | USB-B (alimentazione + programmazione) |
• 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.
#include <Arduino.h> e prototipi di funzione.| 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 |
• 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).
// 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); }
| Funzione | Descrizione |
|---|---|
setup() | Inizializzazione: eseguita una volta dopo power-on o reset |
loop() | Ciclo principale: eseguita ripetutamente dopo setup() |
| Tipo | Dim. | Range |
|---|---|---|
boolean | 1 byte | true / false |
byte | 1 byte | 0 – 255 |
char | 1 byte | -128 – 127 |
unsigned char | 1 byte | 0 – 255 |
int | 2 byte | -32768 – 32767 |
unsigned int | 2 byte | 0 – 65535 |
word | 2 byte | 0 – 65535 |
long | 4 byte | -2.147.483.648 – 2.147.483.647 |
unsigned long | 4 byte | 0 – 4.294.967.295 |
float | 4 byte | ±3.4028235E+38 (6-7 cifre decimali) |
double | 4 byte | Identico a float su AVR |
String | variabile | Oggetto stringa (heap) |
| Qualificatore | Descrizione |
|---|---|
const | Valore costante (preferire a #define) |
volatile | Variabile modificata in ISR — evita ottimizzazioni del compilatore |
static | Variabile locale persistente fra chiamate |
PROGMEM | Salva 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!"));
| Funzione | Descrizione |
|---|---|
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 }
| Funzione | Descrizione |
|---|---|
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
| Funzione | Descrizione |
|---|---|
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 }
delay() nel loop principale: blocca l'esecuzione e impedisce la gestione di eventi.| Funzione | Descrizione |
|---|---|
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); } }
| Funzione | Descrizione |
|---|---|
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.| Funzione | Descrizione |
|---|---|
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 }
| Operatore | Descrizione | Esempio |
|---|---|---|
& | AND | 0b1100 & 0b1010 = 0b1000 |
| | OR | 0b1100 | 0b1010 = 0b1110 |
^ | XOR | 0b1100 ^ 0b1010 = 0b0110 |
~ | NOT | ~0b1100 = 0b0011 (su 4 bit) |
<< | Shift Left | 1 << 3 = 0b1000 = 8 |
>> | Shift Right | 8 >> 2 = 0b0010 = 2 |
| Funzione | Descrizione |
|---|---|
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 |
| Funzione | Descrizione |
|---|---|
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) | Collega interrupt a funzione |
detachInterrupt(digitalPinToInterrupt(pin)) | Scollega interrupt |
interrupts() | Riabilita interrupt globali (sei()) |
noInterrupts() | Disabilita interrupt globali (cli()) |
| Mode | Trigger |
|---|---|
LOW | Quando il pin è LOW |
CHANGE | Cambio di stato |
RISING | Fronte di salita (LOW → HIGH) |
FALLING | Fronte di discesa (HIGH → LOW) |
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!"); } }
delay(), millis() non avanza, Serial inaffidabile. Le variabili condivise devono essere volatile.#include <EEPROM.h>
| Funzione | Descrizione |
|---|---|
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
update()/put() invece di write() per preservare la durata.#include <Wire.h>
Bus I2C (TWI): SDA = pin A4, SCL = pin A5 su Arduino Uno. Velocità standard: 100 kHz.
| Funzione | Descrizione |
|---|---|
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(); }
#include <SPI.h>
SPI su Uno: MOSI = pin 11, MISO = pin 12, SCK = pin 13, SS = pin 10.
| Funzione | Descrizione |
|---|---|
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 SPI | CPOL | CPHA | Descrizione |
|---|---|---|---|
SPI_MODE0 | 0 | 0 | Clock idle LOW, campionamento fronte salita |
SPI_MODE1 | 0 | 1 | Clock idle LOW, campionamento fronte discesa |
SPI_MODE2 | 1 | 0 | Clock idle HIGH, campionamento fronte discesa |
SPI_MODE3 | 1 | 1 | Clock idle HIGH, campionamento fronte salita |
#include <Servo.h>
| Funzione | Descrizione |
|---|---|
Servo myservo | Crea 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) |
Servo myservo; void setup() { myservo.attach(9); } void loop() { for (int pos = 0; pos <= 180; pos++) { myservo.write(pos); delay(15); } }
#include <Stepper.h>
| Funzione | Descrizione |
|---|---|
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); }
#include <LiquidCrystal.h>
| Funzione | Descrizione |
|---|---|
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 }
#include <SD.h> #include <SPI.h>
Usa SPI. CS tipicamente su pin 4 (Ethernet Shield) o pin 10.
| Funzione | Descrizione |
|---|---|
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 File | Descrizione |
|---|---|
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(); }
#include <SoftwareSerial.h>
Emula una porta seriale su pin digitali qualsiasi. Utile per moduli GPS, Bluetooth, ecc.
| Funzione | Descrizione |
|---|---|
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()); }
#include <Ethernet.h> #include <SPI.h>
| Funzione | Descrizione |
|---|---|
Ethernet.begin(mac) | Inizializza con DHCP |
Ethernet.begin(mac, ip) | Inizializza con IP statico |
Ethernet.localIP() | Ritorna IP assegnato |
Ethernet.maintain() | Rinnova lease DHCP |
| Funzione | Descrizione |
|---|---|
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 |
| Funzione | Descrizione |
|---|---|
EthernetClient client | Crea 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 |
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.
| Operazione | Codice | Descrizione |
|---|---|---|
| Set bit | REG |= (1 << BIT) | Imposta bit a 1 |
| Clear bit | REG &= ~(1 << BIT) | Imposta bit a 0 |
| Toggle bit | REG ^= (1 << BIT) | Inverte il bit |
| Check bit | (REG >> BIT) & 1 | Legge valore del bit |
| Set più bit | REG |= (1<<B1) | (1<<B2) | Imposta più bit contemporaneamente |
| Clear più bit | REG &= ~((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 */ }
| Pin Arduino | Porta | Bit | Funzioni Speciali |
|---|---|---|---|
| 0 | PD0 | PORTD bit 0 | RXD (USART) |
| 1 | PD1 | PORTD bit 1 | TXD (USART) |
| 2 | PD2 | PORTD bit 2 | INT0 (interrupt esterno) |
| 3 | PD3 | PORTD bit 3 | INT1, OC2B (PWM Timer2) |
| 4 | PD4 | PORTD bit 4 | T0 (clock esterno Timer0) |
| 5 | PD5 | PORTD bit 5 | OC0B (PWM Timer0), T1 |
| 6 | PD6 | PORTD bit 6 | OC0A (PWM Timer0) |
| 7 | PD7 | PORTD bit 7 | — |
| 8 | PB0 | PORTB bit 0 | ICP1 (Input Capture Timer1) |
| 9 | PB1 | PORTB bit 1 | OC1A (PWM Timer1) |
| 10 | PB2 | PORTB bit 2 | OC1B (PWM Timer1), SS (SPI) |
| 11 | PB3 | PORTB bit 3 | OC2A (PWM Timer2), MOSI |
| 12 | PB4 | PORTB bit 4 | MISO (SPI) |
| 13 | PB5 | PORTB bit 5 | SCK (SPI), LED builtin |
| A0 | PC0 | PORTC bit 0 | ADC0 |
| A1 | PC1 | PORTC bit 1 | ADC1 |
| A2 | PC2 | PORTC bit 2 | ADC2 |
| A3 | PC3 | PORTC bit 3 | ADC3 |
| A4 | PC4 | PORTC bit 4 | ADC4, SDA (I2C) |
| A5 | PC5 | PORTC bit 5 | ADC5, SCL (I2C) |
Ogni porta (B, C, D) ha tre registri a 8 bit:
| Registro | Funzione | Lettura/Scrittura |
|---|---|---|
DDRx | Data Direction Register — 0 = Input, 1 = Output | R/W |
PORTx | Output/Pull-up — se Output: valore pin; se Input: 1 = pull-up attivo | R/W |
PINx | Input — legge lo stato corrente dei pin; scrittura = toggle PORTx | R (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
PORTD = 0xFF) modifica tutti i pin della porta simultaneamente: ideale per LED multipli o bus dati paralleli.L'ATmega328P ha 3 timer hardware:
| Timer | Bit | Registri | Pin PWM | Usato da Arduino |
|---|---|---|---|---|
| Timer0 | 8-bit | TCCR0A/B, TCNT0, OCR0A/B | 5 (OC0B), 6 (OC0A) | millis(), delay(), micros() |
| Timer1 | 16-bit | TCCR1A/B/C, TCNT1, OCR1A/B, ICR1 | 9 (OC1A), 10 (OC1B) | Servo library |
| Timer2 | 8-bit | TCCR2A/B, TCNT2, OCR2A/B | 3 (OC2B), 11 (OC2A) | tone() |
| Bit (TCCRnA) | Nome | Funzione |
|---|---|---|
| 7:6 | COM0A1:COM0A0 | Compare Match Output A mode |
| 5:4 | COM0B1:COM0B0 | Compare Match Output B mode |
| 1:0 | WGM01:WGM00 | Waveform Generation Mode (bit bassi) |
| Bit (TCCRnB) | Nome | Funzione |
|---|---|---|
| 7 | FOC0A | Force Output Compare A |
| 6 | FOC0B | Force Output Compare B |
| 3 | WGM02 | Waveform Generation Mode (bit alto) |
| 2:0 | CS02:CS01:CS00 | Clock Select (prescaler) |
| CS02:01:00 | Timer0/1 | Frequenza (16 MHz) |
|---|---|---|
| 000 | Fermo | — |
| 001 | clk/1 | 16 MHz |
| 010 | clk/8 | 2 MHz |
| 011 | clk/64 | 250 kHz |
| 100 | clk/256 | 62.5 kHz |
| 101 | clk/1024 | 15.625 kHz |
| 110 | Ext T0/T1 falling | — |
| 111 | Ext T0/T1 rising | — |
Timer2 usa prescaler diversi: 1, 8, 32, 64, 128, 256, 1024.
| WGM2:1:0 | Mode | TOP | Aggiornamento OCRx |
|---|---|---|---|
| 000 | Normal | 0xFF | Immediato |
| 001 | Phase Correct PWM | 0xFF | TOP |
| 010 | CTC | OCR0A | Immediato |
| 011 | Fast PWM | 0xFF | BOTTOM |
| 101 | Phase Correct PWM | OCR0A | TOP |
| 111 | Fast PWM | OCR0A | BOTTOM |
| Registro | Bit | Descrizione |
|---|---|---|
TIMSK0 | TOIE0 | Overflow Interrupt Enable Timer0 |
TIMSK0 | OCIE0A | Compare Match A Interrupt Enable Timer0 |
TIMSK0 | OCIE0B | Compare Match B Interrupt Enable Timer0 |
TIMSK1 | TOIE1 | Overflow Interrupt Enable Timer1 |
TIMSK1 | OCIE1A/B | Compare Match A/B Interrupt Timer1 |
TIMSK1 | ICIE1 | Input Capture Interrupt Timer1 |
TIMSK2 | TOIE2, OCIE2A/B | Timer2 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 }
| COM | Fast PWM | Phase Correct |
|---|---|---|
| 00 | Pin disconnesso | Pin disconnesso |
| 01 | Toggle OC0A on Compare Match (WGM=111) | WGM dipendente |
| 10 | Non-inverting: clear on match, set at BOTTOM | Clear up-counting, set down-counting |
| 11 | Inverting: set on match, clear at BOTTOM | Set 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)
| Modo | Formula |
|---|---|
| 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) |
| CTC | f = f_clk / (2 * N * (1 + OCRnA)) |
N = prescaler, f_clk = 16 MHz.
| Registro | Bit | Descrizione |
|---|---|---|
ADMUX | REFS1: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 | |
ADCSRA | ADEN (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/ADCH | — | Risultato conversione (10 bit). Leggere ADCL prima di ADCH |
ADCSRB | ADTS2:0 | Auto Trigger Source (Free Running, Timer, Comparatore, ecc.) |
DIDR0 | ADC5D:ADC0D | Digital 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
| Registro | Bit | Descrizione |
|---|---|---|
UCSR0A | RXC0 (7) | Receive Complete (dato pronto) |
| UDRE0 (5) | Data Register Empty (pronto per TX) | |
| U2X0 (1) | Double Speed Mode | |
UCSR0B | RXCIE0 (7) | RX Complete Interrupt Enable |
| RXEN0 (4) | Receiver Enable | |
| TXEN0 (3) | Transmitter Enable | |
UCSR0C | UCSZ01:00 (2:1) | Character Size: 11 = 8 bit |
| USBS0 (3) | Stop Bits: 0 = 1 stop, 1 = 2 stop | |
UBRR0H/L | — | Baud Rate Register: UBRR = (F_CPU / 16 / baud) - 1 |
UDR0 | — | Data 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++); }
| Registro | Bit | Descrizione |
|---|---|---|
EICRA | ISC11:ISC10 (3:2) | INT1 Sense Control |
EICRA | ISC01:ISC00 (1:0) | INT0 Sense Control |
EIMSK | INT1, INT0 | External Interrupt Mask (enable) |
EIFR | INTF1, INTF0 | Interrupt Flag (scrivi 1 per clear) |
| ISCn1:ISCn0 | Trigger |
|---|---|
| 00 | Livello LOW |
| 01 | Cambio logico (CHANGE) |
| 10 | Fronte discesa (FALLING) |
| 11 | Fronte salita (RISING) |
| Registro | Descrizione |
|---|---|
PCICR | Pin Change Interrupt Control: PCIE2 (PORTD), PCIE1 (PORTC), PCIE0 (PORTB) |
PCMSK0 | Mask PORTB: PCINT7:0 (pin 8–13) |
PCMSK1 | Mask PORTC: PCINT14:8 (pin A0–A5) |
PCMSK2 | Mask PORTD: PCINT23:16 (pin 0–7) |
PCIFR | Pin 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 */ } }
| Registro | Bit | Descrizione |
|---|---|---|
SPCR | SPIE (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 | |
SPSR | SPIF (7) | SPI Interrupt Flag (transfer complete) |
SPSR | SPI2X (0) | Double Speed: raddoppia clock SPI |
SPDR | — | SPI 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; }
| Registro | Bit | Descrizione |
|---|---|---|
TWBR | — | Bit Rate Register: f_SCL = F_CPU / (16 + 2*TWBR*prescaler) |
TWCR | TWINT (7) | Interrupt Flag: scrivi 1 per avviare operazione |
| TWEA (6) | Enable Acknowledge | |
| TWSTA (5) | Start Condition | |
| TWSTO (4) | Stop Condition | |
TWSR | TWS7:3 (7:3) | Status Code (tabella status nel datasheet) |
TWSR | TWPS1:0 (1:0) | Prescaler: 00=1, 01=4, 10=16, 11=64 |
TWDR | — | Data Register |
TWAR | — | Slave 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);
| Registro | Bit | Descrizione |
|---|---|---|
EEARH/EEARL | — | EEPROM Address Register (10 bit: 0–1023) |
EEDR | — | EEPROM Data Register |
EECR | EEPM1: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) | |
EECR | EERE (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(); }
Il Watchdog Timer (WDT) resetta il microcontrollore se non viene "alimentato" periodicamente. Utile per recupero da blocchi software.
| Registro | Bit | Descrizione |
|---|---|---|
WDTCSR | WDIF (7) | Interrupt Flag |
| WDIE (6) | Interrupt Enable | |
| WDE (3) | Watchdog Enable (reset mode) | |
| WDP3:0 (5,2:0) | Prescaler (timeout) |
| WDP3:0 | Timeout |
|---|---|
| 0000 | 16 ms |
| 0001 | 32 ms |
| 0010 | 64 ms |
| 0011 | 125 ms |
| 0100 | 250 ms |
| 0101 | 500 ms |
| 0110 | 1 s |
| 0111 | 2 s |
| 1000 | 4 s |
| 1001 | 8 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) }
| Modo | SM2:0 | Corrente | Periferiche Attive | Wake Source |
|---|---|---|---|---|
| Idle | 000 | ~15 mA | Timer, USART, ADC, TWI, SPI | Qualsiasi interrupt |
| ADC Noise Reduction | 001 | ~6.5 mA | ADC | ADC, INT, TWI, WDT |
| Power-down | 010 | ~0.36 µA | Nessuna (solo WDT opzionale) | INT, PCINT, TWI, WDT |
| Power-save | 011 | ~0.9 µA | Timer2 (asincrono) | Timer2, INT, PCINT, WDT |
| Standby | 110 | ~0.2 mA | Oscillatore principale | Come 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();
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.
| Istruzione | Codice | Effetto |
|---|---|---|
| Software Reset | asm volatile ("jmp 0"); | Salta all’indirizzo 0 — simula un reset (non reinizializza i registri HW, ma riavvia lo sketch) |
| No Operation | asm volatile ("nop"); | 1 ciclo di clock (~62.5 ns a 16 MHz). Utile per micro-ritardi precisi |
| Disabilita Interrupt | asm volatile ("cli"); | Equivalente a noInterrupts() — clear interrupt flag |
| Abilita Interrupt | asm volatile ("sei"); | Equivalente a interrupts() — set interrupt flag |
| Sleep CPU | asm volatile ("sleep"); | Entra nel modo sleep configurato (impostare SMCR prima) |
| Watchdog Reset | asm volatile ("wdr"); | Resetta il timer del Watchdog — equivalente a wdt_reset() |
| Break (debug) | asm volatile ("break"); | Breakpoint per debugger hardware (JTAG/debugWIRE) |
// 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)
// 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
// 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);
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.
#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); } }
// 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
#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. }
analogWrite() su pin 9 e 10 (usa Timer1). Supporta fino a 12 servo su Uno.Connessione diretta 4-bit: usa 6 pin (RS, E, D4–D7). Il pin R/W si collega a GND.
| Pin LCD | Pin Arduino | Funzione |
|---|---|---|
| RS (4) | 12 | Register Select |
| E (6) | 11 | Enable |
| D4–D7 | 5, 4, 3, 2 | Bus dati 4-bit |
| V0 (3) | Potenziometro 10k | Contrasto |
#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); }
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); } } }
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); }
| Pratica | Dettaglio |
|---|---|
No lcd.clear() nel loop | Causa sfarfallio visibile. Sovrascrivere con spazi o usare snprintf con larghezza fissa. |
Usare F() per stringhe | lcd.print(F("testo")) risparmia SRAM preziosa. |
| Aggiornare a intervalli | Non riscrivere l'LCD ogni ciclo di loop — 200–500 ms sono sufficienti per l'occhio umano. |
Buffer con snprintf | Formattare in un char[] con larghezza fissa evita residui e semplifica l'allineamento. |
| LCD I2C: solo 2 pin | Preferire I2C quando i pin scarseggiano. Leggermente più lento ma trascurabile per display. |
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-SR04 | Funzione |
|---|---|
| VCC | 5V |
| Trig | Trigger: impulso HIGH ≥ 10 µs per avviare misura |
| Echo | Echo: durata impulso HIGH proporzionale alla distanza |
| GND | Massa |
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 }
v = 331.3 + 0.606 * T_celsius m/s. A 20°C: 343.5 m/s.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.
| Nota | C | D | E | F | G | A | B |
|---|---|---|---|---|---|---|---|
| Ottava 4 | 262 | 294 | 330 | 349 | 392 | 440 | 494 |
| Ottava 5 | 523 | 587 | 659 | 698 | 784 | 880 | 988 |
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); } }
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. } }
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); }
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 comandare carichi ad alta potenza (motori, lampade, elettrovalvole) servono relè o MOSFET come switch. Il pin Arduino fornisce solo il segnale di controllo.
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); }
// 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); }
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); } }
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 595 | Nome | Pin Arduino | Funzione |
|---|---|---|---|
| 14 | DS (SER) | 11 (data) | Ingresso dati seriale |
| 11 | SHCP (SRCLK) | 12 (clock) | Clock shift register |
| 12 | STCP (RCLK) | 8 (latch) | Latch: trasferisce dati alle uscite |
| 13 | OE | GND | Output Enable (attivo LOW) |
| 10 | MR (SRCLR) | 5V | Master Reset (attivo LOW) |
| Q0–Q7 | — | — | 8 uscite parallele |
| 9 | Q7' | — | 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); }
| Pratica | Dettaglio |
|---|---|
Evitare delay() | Usare pattern non-bloccante con millis(). Permette multitasking cooperativo e reattività del sistema. |
Usare const e constexpr | Preferire a #define per costanti: type-safe, visibili nel debugger. |
| Pin con nomi simbolici | const byte LED_PIN = 13; non numeri magici nel codice. |
| Macchine a stati | Per logica complessa, usare enum + switch invece di flag booleani annidati. |
Evitare String | L'oggetto String causa frammentazione heap su MCU con poca RAM. Preferire array char[] e funzioni C (strcpy, snprintf). |
| ISR brevi | Le ISR devono impostare flag e uscire. Elaborazione complessa nel loop(). |
Variabili ISR volatile | Ogni variabile condivisa tra ISR e codice principale deve essere volatile. |
| Sezioni critiche | Proteggere letture multi-byte di variabili volatile con noInterrupts()/interrupts(). |
Usare F() per stringhe | Serial.println(F("testo")) salva SRAM usando stringhe da Flash. |
| Debounce pulsanti | Sempre 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; }
| Pratica | Dettaglio |
|---|---|
| Monitorare SRAM | Controllare memoria libera con freeMemory() o calcolo manuale. Sotto 200 byte: instabilità. |
PROGMEM | Grandi array costanti (lookup table, stringhe menu) in Flash con PROGMEM e pgm_read_*. |
| Dimensionare buffer | Array e buffer al minimo necessario. Ogni byte conta su 2 KB SRAM. |
| Tipi minimi | Usare uint8_t (1 byte) invece di int (2 byte) quando il range lo permette. |
| No allocazione dinamica | Evitare malloc/new: frammentazione fatale su MCU. Allocare staticamente. |
| Attenzione allo stack | Ricorsione profonda e variabili locali grandi consumano stack (cresce verso l'heap). |
Evitare float | Operazioni 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
| Pratica | Dettaglio |
|---|---|
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 cooperativo | Per task multipli con periodi diversi, usare struttura array di task con tempi indipendenti. |
| Timer hardware per precisione | Per timing critico usare timer/CTC con interrupt, non millis(). |
Evitare delayMicroseconds() lunghi | Per 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(); } } }
| Pratica | Dettaglio |
|---|---|
| Resistori di protezione | Sempre resistori in serie su LED (220–470Ω). Non superare 20 mA per pin, 200 mA totale per chip. |
| Pull-up/pull-down | Input non connessi (floating) producono letture casuali. Usare INPUT_PULLUP o resistori esterni. |
| Condensatori di bypass | 100 nF vicino all'alimentazione di ogni IC. 10 µF sul rail principale. |
| Non pilotare carichi dal pin | Per motori, relè, LED ad alta potenza: usare transistor/MOSFET come switch. |
| Protezione ingressi | Clamping diodes o partitori di tensione per segnali >5V sui pin analogici/digitali. |
| Separare alimentazioni | Alimentare motori/attuatori da alimentazione separata. Collegare solo il GND comune. |
| Diodo flyback | Sempre un diodo (1N4007) in antiparallelo su carichi induttivi (relè, solenoidi, motori). |
| Pin inutilizzati | Configurarli come OUTPUT LOW o INPUT_PULLUP per ridurre consumo e rumore. |
| Tecnica | Dettaglio |
|---|---|
| Serial debug | Usare Serial.print() con F() macro. Rimuovere o disabilitare in produzione con #ifdef DEBUG. |
| LED diagnostico | Il 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 semplice | Macro di verifica per catturare condizioni impossibili durante lo sviluppo. |
| Monitor seriale | Stampare valori sensori formattati per il Serial Plotter dell'IDE (valori separati da spazio/tab). |
| Watchdog per sicurezza | Abilitare 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);