⚡ Interruptions AVR

INT0/INT1, Pin Change (PCINT), Timer, ADC — vecteurs, priorités et programmation avec ISR()

Qu'est-ce qu'une interruption ?

Une interruption est un signal matériel qui suspend temporairement l'exécution du programme principal pour exécuter une fonction spéciale appelée ISR (Interrupt Service Routine). Une fois l'ISR terminée, le programme reprend exactement là où il s'était arrêté.

Les interruptions sont essentielles dans les systèmes embarqués car elles permettent de réagir instantanément à des événements (appui sur un bouton, fin de conversion ADC, overflow de timer) sans perdre de temps en scrutation (polling) permanente.

L'ATmega328P dispose de 26 sources d'interruption, chacune associée à un vecteur dans la table des vecteurs d'interruption. Les plus courantes sont les interruptions externes (INT0/INT1), les Pin Change Interrupts (PCINT) et les interruptions timer.

🔧 Activer les interruptions

Pour qu'une interruption fonctionne, il faut respecter 3 conditions simultanées :

  1. Interruptions globales activées — Le bit I du registre SREG doit être à 1. On l'active avec sei() (Set Enable Interrupts) et on le désactive avec cli() (Clear Interrupts).
  2. Interruption individuelle activée — Chaque source a un bit d'activation dans un registre de masque (EIMSK, PCICR, TIMSK0, etc.).
  3. Condition de déclenchement remplie — Un front montant, descendant, un changement de niveau, un overflow ou un compare match selon le type d'interruption.
sei();   // Activer les interruptions globales (SREG bit I = 1)
cli();   // Désactiver les interruptions globales (SREG bit I = 0)

📌 INT0 et INT1 — Interruptions externes

L'ATmega328P possède 2 interruptions externes dédiées sur des broches spécifiques :

Chaque interruption peut être configurée pour se déclencher sur :

ISCn1:ISCn0Déclenchement
00Niveau bas maintenu (LOW)
01Tout changement de niveau (CHANGE)
10Front descendant (FALLING) — le plus courant pour les boutons
11Front montant (RISING)

Exemple : Bouton sur INT0 avec toggle LED

#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint8_t led_state = 0;

ISR(INT0_vect) {
    led_state ^= 1;                  // Toggle variable
    if (led_state)
        PORTB |= (1 << PB5);         // LED ON
    else
        PORTB &= ~(1 << PB5);        // LED OFF
}

int main(void) {
    DDRB |= (1 << PB5);            // LED en sortie
    DDRD &= ~(1 << PD2);           // PD2 (INT0) en entrée
    PORTD |= (1 << PD2);           // Pull-up interne

    // INT0 sur front descendant
    EICRA |= (1 << ISC01);          // ISC01=1, ISC00=0 → FALLING
    EIMSK |= (1 << INT0);            // Activer INT0
    sei();                             // Interruptions globales ON

    while (1) {
        // Le CPU peut dormir ou faire autre chose
        // L'ISR gère le bouton de manière asynchrone
    }
}

🔀 PCINT — Pin Change Interrupts

Les PCINT (Pin Change Interrupts) permettent de déclencher une interruption sur n'importe quelle broche du microcontrôleur, pas seulement INT0/INT1. C'est extrêmement utile quand vous avez besoin de plus de 2 interruptions externes.

Les 24 broches GPIO sont réparties en 3 groupes :

⚠️ Limitation des PCINT : Contrairement à INT0/INT1, les PCINT se déclenchent sur tout changement (montant ou descendant). Un seul vecteur est partagé par tout le port — c'est à vous de déterminer quelle broche a changé en comparant l'état actuel avec l'état précédent.

Exemple : PCINT sur PB0 (D8)

#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint8_t portb_historique = 0xFF;

ISR(PCINT0_vect) {
    uint8_t changement = PINB ^ portb_historique;  // Quels bits ont changé ?
    portb_historique = PINB;

    if (changement & (1 << PB0)) {  // C'est PB0 qui a changé
        if (!(PINB & (1 << PB0))) { // Front descendant (bouton pressé)
            PORTB ^= (1 << PB5);     // Toggle LED
        }
    }
}

int main(void) {
    DDRB |= (1 << PB5);           // LED sortie
    DDRB &= ~(1 << PB0);          // PB0 entrée
    PORTB |= (1 << PB0);          // Pull-up sur PB0

    PCICR |= (1 << PCIE0);         // Activer PCINT groupe 0 (PORTB)
    PCMSK0 |= (1 << PCINT0);       // Activer PCINT sur PB0
    sei();

    while (1) { }
}

📋 Les principaux vecteurs d'interruption

PrioritéVecteur ISRSource
1RESETReset (non masquable)
2INT0_vectInterruption externe 0 (PD2)
3INT1_vectInterruption externe 1 (PD3)
4–6PCINT0/1/2_vectPin Change (PORTB / PORTC / PORTD)
8TIMER2_COMPA_vectTimer2 Compare Match A
12TIMER1_COMPA_vectTimer1 Compare Match A
14TIMER1_OVF_vectTimer1 Overflow
17TIMER0_OVF_vectTimer0 Overflow
22ADC_vectConversion ADC terminée
20–21USART_RX_vect / TX_vectRéception / Transmission USART

Les interruptions de numéro plus bas ont priorité plus haute. Si deux interruptions se produisent simultanément, celle de priorité supérieure est traitée en premier.

⚠️ Règles essentielles pour les ISR

volatile uint16_t compteur = 0;

// Dans main() — lecture atomique
cli();                       // Désactiver interruptions
uint16_t copie = compteur;   // Copier la valeur
sei();                       // Réactiver interruptions
// Utiliser 'copie' en toute sécurité

📌 Comparaison avec Arduino et PIC

ConceptAVR (bare metal)ArduinoPIC
Activer globalementsei()AutomatiqueGIE = 1
Écrire une ISRISR(INT0_vect)attachInterrupt()void __interrupt()
Nombre de vecteurs26 vecteurs dédiés2 (INT0/INT1) via API1 vecteur unique (mid-range)
PrioritéFixe par numéro de vecteurFixe2 niveaux (high/low) sur PIC18

La différence majeure : les AVR ont des vecteurs dédiés pour chaque source d'interruption, tandis que les PIC mid-range (12F/16F) n'ont qu'un seul vecteur partagé — vous devez tester les drapeaux manuellement pour identifier la source. C'est un avantage net de l'architecture AVR pour la lisibilité du code.