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 interrupt

 

Un interrupt (interruzione) è un segnale hardware o software che interrompe temporaneamente l’esecuzione del programma principale per eseguire una funzione chiamata ISR (Interrupt Service Routine).
Serve per reagire immediatamente a un evento, ad esempio:

  • variazione di stato su un pin (es. un pulsante, un sensore),
  • timer che scade,
  • ricezione di dati seriali o I²C/SPI,
  • eventi di sistema interni (Wi-Fi, touch, ecc.).

🔹 Tipi di interrupt sull’ESP32-S3

L’ESP32-S3 ha due core (CPU0 e CPU1) e supporta vari livelli di interrupt:

  • GPIO interrupts — per variazioni su pin digitali.
  • Timer interrupts — gestiti da hardware timer (uno per core, più virtuali).
  • Peripherals interrupts — per UART, SPI, I²C, ecc.
  • Software interrupts — generati dal codice.

Ogni interrupt ha un livello di priorità da 1 a 7:

  • Livelli 1–3: gestibili da codice C/C++.
  • Livelli 4–7: riservati a funzioni di sistema e Wi-Fi.

🔹 Interrupt sui pin GPIO

Esempio pratico (ambiente Arduino):

#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!");
  }
}

Note:

  • IRAM_ATTR è obbligatorio sull’ESP32/S3 per mettere la ISR nella RAM interna (non nella flash).
  • attachInterrupt() collega la funzione isrBottone() al pin.
  • Modalità: RISING, FALLING, CHANGE, HIGH, LOW.

🔹 Interrupt dei timer hardware

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!");
  }
}

 

🔹 Limitazioni importanti

  1. 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.

  2. Variabili condivise
    Le variabili modificate sia da ISR che da loop() devono essere dichiarate volatile per evitare ottimizzazioni errate.

  3. Disabilitare temporaneamente
    Per evitare conflitti:

noInterrupts();   // disattiva tutti
interrupts();     // riattiva

 

🔹 Differenze rispetto ad Arduino UNO

 

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 , 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. 👇


⚙️ 1. Differenze strutturali importanti

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.

🌧️ 2. Problema tipico: sensori vento e pioggia

Gli anemometri e pluviometri meccanici (reed switch o contatti magnetici) generano rimbalzi elettrici (bouncing):

  • ogni impulso reale può generare 3–10 impulsi falsi in pochi millisecondi.
  • su Arduino, la ISR scatta per ognuno → conteggi sballati, instabilità, falsi allarmi.

💡 3. Come migliorano sull’ESP32-S3

A. Input filtrabile via software o hardware

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);
}

B. Timer di sicurezza

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

C. Alimentazione più stabile e pull-up interni forti

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.

D. ISR su core dedicato

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.

🧭 4. Quando resta meglio il polling

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.

✅ In sintesi

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.


🔍 Perché, nei sistemi classici, il debouncing software non sempre bastava

Anche con una classe myPuls ben fatta, potevano emergere situazioni problematiche:

  1. 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”.

  2. 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.

  3. 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.

  4. Overhead di computazione e gestione
    Se il debouncing software è troppo “pesante” (più letture, contatori, buffer), può introdurre jitter o divisioni errate nel conteggio.

  5. 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.

⚙️ Con l’ESP32-S3: cosa migliora, cosa resta da tenere d’occhio

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


💡 Strategie efficaci con ESP32-S3 per evitare falsi allarmi

Ecco delle tattiche pratiche che, se ben combinate, possono darti un sistema molto robusto:

  1. 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.

  2. Filtraggio hardware (RC) o Schmitt trigger
    Un piccolo condensatore + resistenza può “smussare” i rimbalzi e ridurre drasticamente la quantità di impulsi parassiti.

  3. 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”.

  4. 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

  5. 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

  6. 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.

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

🔹 1. 50 ms di debounce: cosa significa davvero

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.

⚠️ 2. Perché su Arduino questo può comunque fallire

Su schede tipo UNO/Nano/Every, anche con un debounce “robusto”, restano 3 punti critici:

🔸 A. Rumore elettrico o interferenze

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:

  • Cavi lunghi (anemometri, pluviometri),
  • Ambienti con motori o relè vicini,
  • Alimentazione instabile o massa condivisa male.

🔸 B. Eventi multipli reali ravvicinati

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.

🔸 C. Esecuzione non immediata

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.

⚙️ 3. Perché l’ESP32-S3 cambia il gioco

✅ A. ISR in RAM, con latenza bassissima

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.

✅ B. Gestione dei fronti hardware accurata

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.

✅ C. Possibilità di debounce ultraveloce (pochi ms)

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

✅ D. Hardware Pulse Counter (PCNT)

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!

💡 In sintesi

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:

  • PCNT hardware (conteggio impulsi),
  • filtro integrato da pochi microsecondi,
  • calcolo automatico della velocità del vento ogni secondo.

Quali sono i GPIO che pono essere utilizzati come Interrurpt?

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.

⚙️ 1. Regola generale

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.


🧩 2. Pin sicuri per interrupt (consigliati)

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:

  • supportano input digitale (requisito per attachInterrupt),
  • sono stabili all’avvio (boot-safe),
  • non sono vincolati a funzioni di flash o USB.

🚫 3. Pin da evitare o usare con cautela

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

💡 4. In pratica

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);

🧭 5. Suggerimenti pratici

  • Preferisci pin input-only (35, 36, 37, 45, 46) per sensori: non rischi conflitti con altre periferiche.
  • Evita pin riservati al flash o USB (18–20, 15–17).
  • Usa pinMode(pin, INPUT_PULLUP) per reed switch, così il contatto chiuso manda il pin a GND.
  • Se noti ancora falsi trigger, puoi attivare un filtro software da 3–5 ms come ti mostravo prima.

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 👇

🟢 1. Polling

Nel polling, il loop legge continuamente lo stato del pin:

void loop() {
  if (digitalRead(BTN_PIN) == HIGH) {
    faiQualcosa();
  }
}

👉 Questo significa che:

  • la CPU controlla il pin ad ogni ciclo del loop;
  • se il loop è “pesante” (con delay o altre operazioni lunghe), l’evento può sfuggire perché avviene tra due letture;
  • consumi più energia perché il core è sempre attivo.

In sintesi: semplice, ma poco efficiente e non preciso se il segnale è veloce o breve.

🟡 2. Interrupt

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:

  • il core non deve controllare il pin;
  • l’hardware “avvisa” immediatamente il micro appena il fronte logico cambia;
  • l’ISR impiega pochissimo tempo e può impostare un flag;
  • l’azione può essere eseguita subito dopo, nel loop principale, senza perdita di eventi.

In sintesi: reattività quasi istantanea e nessun carico costante di polling.

⚖️ Confronto diretto

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

🧠 Quando scegliere cosa

  • 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.