Gli Interrupt Ottimo argomento, Silvano — gli interrupt sull’ESP32-S3 sono una parte molto potente dell’architettura ESP-IDF (e di conseguenza anche dell’ambiente Arduino). Ti spiego in modo chiaro come funzionano e come si usano. 🔹 Cos’è un interruptUn interrupt (interruzione) è un segnale hardware o software che interrompe temporaneamente l’esecuzione del programma principale per eseguire una funzione chiamata ISR (Interrupt Service Routine).
|
#define PIN_BOTTONE 15
volatile bool flagPremuto = false;
void IRAM_ATTR isrBottone() {
flagPremuto = true;
}
void setup() {
pinMode(PIN_BOTTONE, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_BOTTONE), isrBottone, FALLING);
}
void loop() {
if (flagPremuto) {
flagPremuto = false;
Serial.println("Bottone premuto!");
}
} |
L’ESP32-S3 ha 4 timer hardware per gruppo, e due gruppi (uno per core).
Con Arduino puoi gestirli così:
hw_timer_t *timer = NULL;
volatile bool flagTimer = false;
void IRAM_ATTR onTimer() {
flagTimer = true;
}
void setup() {
Serial.begin(115200);
timer = timerBegin(0, 80, true); // timer 0, prescaler 80 → 1 tick = 1 µs
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 1000000, true); // 1.000.000 µs = 1 secondo
timerAlarmEnable(timer);
}
void loop() {
if (flagTimer) {
flagTimer = false;
Serial.println("1 secondo!");
}
} |
Niente funzioni lente nella ISR
Non si possono usare funzioni che aspettano o stampano (no Serial.print, delay, millis, ecc.).
Si impostano solo flag o si scrivono dati in variabili volatile.
Variabili condivise
Le variabili modificate sia da ISR che da loop() devono essere dichiarate volatile per evitare ottimizzazioni errate.
Disabilitare temporaneamente
Per evitare conflitti:
noInterrupts(); // disattiva tutti interrupts(); // riattiva |
| Aspetto | Arduino UNO | ESP32-S3 |
|---|---|---|
| Numero di interrupt GPIO | 2–3 | Tutti i pin |
| ISR in RAM | non necessaria | obbligatoria (IRAM_ATTR) |
| Timer | 1 (limitato) | 8 hardware timer indipendenti |
| Multi-core | No | Sì, gestione separata per CPU0/CPU1 |
| Priorità interrupt | No | Sì, livelli 1–7 |
sull’ESP32-S3 gli interrupt sono nettamente più affidabili rispetto a quelli dell’Arduino classico (UNO, Nano, Every), a patto di usarli correttamente.
Vediamo perché, con un confronto tecnico ma pratico. 👇
| Aspetto | Arduino UNO / Nano / Every | ESP32-S3 |
|---|---|---|
| MCU | AVR 8 bit a 16 MHz | Xtensa LX7 dual-core 240 MHz |
| Interrupt controller | molto semplice, 2 linee esterne | sistema complesso con priorità, filtri e masking |
| Gestione ISR | spesso bloccante | non bloccante, in RAM, prioritaria |
| Anti-rimbalzo hardware | assente | filtrabile via software o logica interna GPIO |
| Alimentazione/EMI | facilmente disturbabile | molto più robusta ai disturbi elettrici |
| Sensibilità al rumore | alta | molto più bassa |
In pratica, gli interrupt su AVR sono “primitivi”: se arriva un impulso sporco (rimbalzo meccanico o disturbo elettrico), il micro entra comunque nella ISR, e se gli impulsi arrivano ravvicinati può perdere conteggi o andare in crash.
L’ESP32-S3, invece, ha un controller d’interrupt con logica di priorità, mascheramento e buffer hardware, quindi filtra meglio e non “impazzisce” se arrivano impulsi troppo rapidi o ravvicinati.
Gli anemometri e pluviometri meccanici (reed switch o contatti magnetici) generano rimbalzi elettrici (bouncing):
Puoi introdurre un piccolo filtro software nella ISR per ignorare impulsi troppo ravvicinati:
#define PIN_ANEMOMETRO 15
volatile unsigned long lastMicros = 0;
volatile unsigned long impulsi = 0;
void IRAM_ATTR isrVento() {
unsigned long ora = micros();
if (ora - lastMicros > 5000) { // ignora impulsi entro 5 ms
impulsi++;
lastMicros = ora;
}
}
void setup() {
pinMode(PIN_ANEMOMETRO, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_ANEMOMETRO), isrVento, FALLING);
} |
Puoi usare un timer hardware per contare gli impulsi ogni secondo o ogni 10 secondi, in modo preciso e senza blocchi.
Questo evita di fare elaborazioni dentro la ISR (che resta leggerissima).
L’ESP32-S3 lavora a 3,3 V ma con pull-up più robusti; se serve, puoi usare resistenze esterne da 10k o 4.7k ai 3,3V per stabilizzare ulteriormente le linee.
Puoi persino decidere che le ISR vengano gestite solo da CPU0, lasciando CPU1 libera per il codice principale — cosa impossibile con Arduino.
Questo elimina conflitti se hai anche altre attività come WiFi, display, ecc.
Nonostante i vantaggi, in certi casi il polling (lettura periodica) resta più pratico:
se gli impulsi sono lenti (es. pluviometro: 1 impulso ogni 30 secondi),
se vuoi filtrare il segnale con algoritmi più sofisticati (media, debounce dinamico),
se il sensore è molto disturbato o collegato con cavo lungo.
Una soluzione intermedia è polling temporizzato (es. ogni 5 ms) con memoria dello stato precedente: molto stabile e senza overhead.
| Arduino UNO/Nano | ESP32-S3 | |
|---|---|---|
| Affidabilità interrupt | ⚠️ sensibile a disturbi | ✅ molto stabile |
| Possibilità di filtraggio | minima | ✅ elevata (hardware/software) |
| ISR parallele | no | ✅ multi-core |
| Rumore da cavi lunghi | facilmente disturbato | più robusto, ma comunque da schermare |
| Consigliato per anemometro | ❌ no | ✅ sì (con debounce minimo) |
| Consigliato per pluviometro | ⚠️ meglio polling | ✅ interrupt con filtro OK |
anche con debouncing “software” ben progettato, su microclassici (UNO, Nano, Every) non è raro incorrere in falsi allarmi, soprattutto con sensori meccanici che generano rumore (anemometri, pluviometri, reed switch, etc.). Quello che dobbiamo valutare è perché succedeva, e se con l’ESP32-S3 quei motivi si attenuano o scompaiono del tutto, oppure rimangono ma gestibili meglio.
Vediamo i punti deboli tipici nei sistemi classici + debouncing software e come la piattaforma ESP32 cambia le carte in tavola, con pro & contro.
Anche con una classe myPuls ben fatta, potevano emergere situazioni problematiche:
Rimbalzi multipli molto ravvicinati
Il meccanismo meccanico dei reed switch o contatti genera “scatti” rimbalzanti (bounce) nell’ordine di decine di microsecondi o qualche millisecondo. Se il debouncing accetta una soglia troppo bassa (es. 1 ms) o salta qualche impulso, potresti comunque captare impulsi “falsi”.
Tempi di risposta limitati / latenza
Su Arduino UNO l’ISR e il contesto del loop possono introdurre latenza: se arriva un impulso “in ritardo” rispetto al momento in cui il sistema riesce a processarlo, può essere ignorato o duplicato erroneamente.
Interferenze elettriche / crosstalk / rumore
Cavi lunghi, accoppiamenti elettromagnetici, carichi vicini: quegli “scatti” possono generare spike “veloci” che la logica di debouncing può fallire nel filtrare.
Overhead di computazione e gestione
Se il debouncing software è troppo “pesante” (più letture, contatori, buffer), può introdurre jitter o divisioni errate nel conteggio.
Risorse limitate e vincoli hardware (no priorità, vincoli di interrupt)
Sull’AVR non hai una gerarchia di priorità, spesso solo pochi interrupt esterni, e ogni ISR può “bloccare” temporaneamente altri eventi. Se hai vari sensori, contesti più complessi, il sistema può saturarsi.
Questi problemi, combinati, possono spiegare perché “non bastava” anche con una classe di debouncing decente.
L’ESP32-S3 introduce diversi elementi che possono rendere gli interrupt + debouncing molto più affidabili, ma non automaticamente “magici”. Ecco cosa aiuta e cosa tenere in considerazione:
| Vantaggio / cambiamento | Effetto favorevole | Note / attenzioni |
|---|---|---|
| Controller d’interrupt più sofisticato (priorità, mascheramento) | riduzione del rischio di “collisioni” o momenti perduti | serve gestire bene i livelli / priorità |
| Core dual / capacità parallela | puoi liberare un core per la logica “pesante” mentre l’altro gestisce gli interrupt | attenzione alla sincronizzazione tra core |
| Tempo di risposta più veloce | la ISR viene invocata con latenza ridotta | serve che i segnali di input abbiano bordi netti |
| Maggiore potenza e memoria | puoi implementare algoritmi di debouncing più complessi senza grande penalità | va comunque tenuto leggero l’ISR |
| Migliore tolleranza elettrica | minore sensibilità a rumori, con cavi decentemente schermati | non elimina ogni possibile disturbo |
Ma attenzione: non tutto è risolto
Se il segnale ha bordi “lenti” (rise/fall times non rapidi), l’ESP32 può generare interrupt spurii. In un issue di Github si segnala che transizioni lente (più di ~2 microsecondi da 10 % a 90 %) possono causare triggering errati. GitHub
In forum, qualcuno ha segnalato che usando un sensore di flusso, con interrupt l’ESP32 tendeva a sovrastimare la frequenza proprio per rimbalzi o trigger extra, mentre col polling + small delay il comportamento diventava più stabile. Arduino Forum
Il debouncing tramite ISR da solo non può “annullare” il fatto che l’hardware ha già inviato impulsi: tu puoi solo ignorare impulsi troppo ravvicinati, scartare quelli fuori soglia, etc.
Se l’input è rumoroso, serve intervenire anche a livello hardware: filtraggio RC, linee schermate, resistenze pull-up/pull-down ben dimensionate, eventualmente circuiti con Schmitt triggers. Electrical Engineering Stack Exchange+1
Ecco delle tattiche pratiche che, se ben combinate, possono darti un sistema molto robusto:
Limite di tempo minimo (debounce “dead time”)
Nella ISR, registra il tempo (micros() o cycle counter) dell’ultimo impulso valido, e ignora impulsi che arrivano troppo presto dopo quello precedente. Esempio: > 3–5 ms.
Filtraggio hardware (RC) o Schmitt trigger
Un piccolo condensatore + resistenza può “smussare” i rimbalzi e ridurre drasticamente la quantità di impulsi parassiti.
Validazione in “secondo livello”
La ISR può alzare un flag e un task o routine fuori dall’ISR esegue una verifica (es. conferma il livello stabile del pin dopo 1–2 ms). Questo multi-step aiuta a scartare glitch “istantanei”.
Utilizzare periferiche hardware dedicate
Sull’ESP32, il PCNT (pulse counter peripheral) può contare impulsi hardware direttamente, senza carico continuo della CPU. Questo riduce la possibilità d’errore software. Alcuni utenti nelle comunità suggeriscono proprio questo approccio per flussi sensibili. Arduino Forum
Garantire bordi rapidi e segnale ben definito
Se il segnale ha transizioni troppo lente, aggiungi buffer, circuiti con Schmitt, filtri, o miglioramenti elettrici per migliorare la qualità del fronte. (Come segnalato: transizioni lente possono generare interrupt spuri) GitHub
Uso di priorità interrupt / masking temporaneo
Durante la gestione di un impulso, puoi disattivare temporaneamente l’interrupt sullo stesso pin per il tempo minimo di debouncing, poi riattivarlo.
Monitoraggio con oscilloscopio / scope
Prima di tutto: misura coloscopio il segnale del sensore al pin microcontrollore, vedrai quanti “scatti” ci sono e ogni quanto tempo. Questo ti dà la soglia corretta per il debounce.
perché 50 ms non sempre basta (anzi, in certi casi è troppo) — e come la stessa logica cambia radicalmente se applicata su un ESP32-S3.
Quando nella tua classe MyPuls imposti un debounce di 50 ms, stai dicendo al sistema:
“Dopo che rilevo una variazione sul pin, ignoro qualsiasi altra variazione nei 50 ms successivi.”
Quindi, se il contatto rimbalza per qualche millisecondo, quei rimbalzi non vengono contati.
Fin qui bene — ma il problema è cosa succede nel resto del tempo.
Su schede tipo UNO/Nano/Every, anche con un debounce “robusto”, restano 3 punti critici:
Il debounce software filtra solo variazioni logiche vere e proprie.
Ma se un disturbo elettrico fa “saltare” il livello del pin dopo i 50 ms, il sistema lo considera un nuovo impulso valido.
→ E questo succede facilmente con:
Con un anemometro, 50 ms corrispondono a:
1 impulso ogni 50 ms → 20 impulsi/sec → 1200 giri/min
Se il vento aumenta oltre una certa soglia, l’anemometro genera impulsi più frequenti — e il debounce li ignora come se fossero rimbalzi!
Risultato: letture troppo basse o irregolari.
Su AVR, la ISR o la funzione di polling che chiama la tua MyPuls può subire ritardi per altre routine (millis, seriale, timer).
Così, un impulso reale può arrivare mentre il codice è impegnato, e venire interpretato in ritardo o come rimbalzo.
Sull’ESP32-S3 la ISR è eseguita direttamente dalla IRAM, senza flash e con latenza nell’ordine di microsecondi.
Questo significa che l’impulso viene catturato esattamente quando arriva, senza ritardi dovuti ad altre funzioni.
I GPIO dell’ESP32 hanno circuiti Schmitt trigger integrati, quindi riconoscono in modo più netto il passaggio logico da 0→1 o 1→0, riducendo i falsi trigger dovuti a segnali sporchi.
Puoi scendere tranquillamente a 3–5 ms di dead time, molto più reattivo ma stabile, perché l’ESP32 non soffre dei ritardi che avevi su AVR.
Puoi anche fare un filtro dinamico (tempo di blocco adattato alla frequenza media degli impulsi).
Puoi bypassare del tutto la logica di interrupt e usare un contatore hardware interno che conta gli impulsi su un pin.
Questo modulo fa già il debouncing hardware a livello di periferica — niente ISR, niente flag, niente rimbalzi.
Esempio:
#include "driver/pcnt.h"
#define PIN_ANEMOMETRO 15
#define PCNT_UNIT PCNT_UNIT_0
void setup() {
Serial.begin(115200);
pcnt_config_t pcntConfig = {};
pcntConfig.pulse_gpio_num = PIN_ANEMOMETRO;
pcntConfig.ctrl_gpio_num = PCNT_PIN_NOT_USED;
pcntConfig.unit = PCNT_UNIT;
pcntConfig.channel = PCNT_CHANNEL_0;
pcntConfig.pos_mode = PCNT_COUNT_INC;
pcntConfig.neg_mode = PCNT_COUNT_DIS;
pcntConfig.counter_h_lim = 32767;
pcntConfig.counter_l_lim = 0;
pcnt_unit_config(&pcntConfig);
pcnt_counter_clear(PCNT_UNIT);
pcnt_counter_resume(PCNT_UNIT);
}
void loop() {
int16_t count = 0;
pcnt_get_counter_value(PCNT_UNIT, &count);
Serial.println(count);
delay(1000);
} |
→ L’hardware conta gli impulsi, senza che la CPU se ne accorga.
E puoi aggiungere il filtro antirimbalzo integrato:
pcnt_set_filter_value(PCNT_UNIT, 500); // ignora impulsi < 500 µs pcnt_filter_enable(PCNT_UNIT); |
Questo è molto più robusto del debounce software da 50 ms su Arduino!
| Aspetto | Arduino UNO/Nano | ESP32-S3 |
|---|---|---|
| Debounce software (50 ms) | può perdere impulsi veri o contare falsi | stabile se ben regolato |
| Sensibilità ai disturbi | alta | bassa |
| Timer/ISR | lenti | rapidissimi |
| PCNT hardware | ✖️ | ✅ presente |
| Ideale debounce | 30–50 ms | 2–5 ms o filtro hardware |
Se vuoi, Silvano, ti preparo un mini esempio per anemometro su ESP32-S3 che combina:
I grandi vantaggi dell’ESP32-S3 rispetto ad Arduino classico:
➡️ quasi tutti i pin GPIO possono generare interrupt.
Vediamo però in dettaglio quali sì, quali no, e quali conviene evitare per altri motivi.
Sull’ESP32-S3, tutti i GPIO da 0 a 48 possono teoricamente essere usati come sorgente di interrupt — cioè puoi fare:
attachInterrupt(digitalPinToInterrupt(pin), isrFunction, mode); |
su quasi tutti.
👉 Tuttavia, non tutti i pin sono consigliati perché alcuni hanno funzioni speciali, sono collegati al flash o all’USB, o non hanno la funzione GPIO disponibile all’avvio.
Questi pin sono perfettamente adatti a interrupt da sensori, pulsanti, reed switch ecc.:
GPIO1 – GPIO14 GPIO21 – GPIO23 GPIO25 – GPIO33 GPIO34 – GPIO37 GPIO38 – GPIO48
Tutti questi:
| GPIO | Motivo da evitare | Note |
|---|---|---|
| 0 | modalità boot | può bloccare l’avvio se LOW all’accensione |
| 15, 16, 17 | connessi a PSRAM / flash / UART su molte schede | meglio non usarli per input |
| 18, 19, 20 | usati dal controller USB (D+, D–) | se usi USB nativa, NON toccarli |
| 24 | riservato a funzioni interne | generalmente non disponibile |
| 35, 36, 37 | input-only (ok come interrupt, ma non output) | funzionano bene come ingresso sensore |
| 45, 46 | anche questi input-only | ottimi per sensori tipo reed switch |
Quindi, se stai collegando anemometro e pluviometro su ESP32-S3, puoi usare senza problemi pin come:
GPIO4, GPIO5, GPIO6, GPIO7, GPIO8, GPIO9, GPIO10, GPIO33, GPIO34, GPIO35, GPIO36, GPIO37, GPIO38, GPIO45, GPIO46
Tutti perfetti per interrupt su fronte FALLING o RISING, con ISR stabile.
Ad esempio:
#define PIN_ANEMOMETRO 36 attachInterrupt(digitalPinToInterrupt(PIN_ANEMOMETRO), isrVento, FALLING); |
L’uso di un interrupt e l’uso del polling sembrano simili, perché in entrambi i casi il programma “reagisce” a un evento.
La vera differenza sta in come e quando il microcontrollore rileva quell’evento.
Vediamolo nel concreto 👇
Nel polling, il loop legge continuamente lo stato del pin:
void loop() {
if (digitalRead(BTN_PIN) == HIGH) {
faiQualcosa();
}
} |
👉 Questo significa che:
In sintesi: semplice, ma poco efficiente e non preciso se il segnale è veloce o breve.
Con un interrupt, il pin viene “sorvegliato” in hardware:
volatile bool flag = false;
void IRAM_ATTR onButtonPress() {
flag = true;
}
void loop() {
if (flag) {
flag = false;
faiQualcosa();
}
} |
👉 Qui:
In sintesi: reattività quasi istantanea e nessun carico costante di polling.
| Aspetto | Polling | Interrupt |
|---|---|---|
| Precisione temporale | Bassa (dipende dal loop) | Alta (hardware) |
| Carico CPU | Costante | Quasi nullo |
| Rischio di perdita eventi | Alto se loop lento | Minimo |
| Complessità | Semplice | Maggiore (serve attenzione a ISR, flag, debouncing) |
| Adatto per | Eventi lenti e poco critici | Eventi veloci o asincroni |
Se stai leggendo pulsanti lenti e rimbalzanti, meglio un buon polling con debouncing software (come la tua MyPuls).
Se devi reagire a segnali brevi e precisi (tachimetro, encoder, impulsi sensore), allora interrupt è la scelta giusta.