INT0/INT1, Pin Change (PCINT), Timer, ADC — vecteurs, priorités et programmation avec ISR()
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.
Pour qu'une interruption fonctionne, il faut respecter 3 conditions simultanées :
sei() (Set Enable Interrupts) et on le désactive avec cli() (Clear Interrupts).sei(); // Activer les interruptions globales (SREG bit I = 1)
cli(); // Désactiver les interruptions globales (SREG bit I = 0)
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:ISCn0 | Déclenchement |
|---|---|
| 00 | Niveau bas maintenu (LOW) |
| 01 | Tout changement de niveau (CHANGE) |
| 10 | Front descendant (FALLING) — le plus courant pour les boutons |
| 11 | Front montant (RISING) |
#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
}
}
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 :
PCINT0_vectPCINT1_vectPCINT2_vect⚠️ 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.
#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) { }
}
| Priorité | Vecteur ISR | Source |
|---|---|---|
| 1 | RESET | Reset (non masquable) |
| 2 | INT0_vect | Interruption externe 0 (PD2) |
| 3 | INT1_vect | Interruption externe 1 (PD3) |
| 4–6 | PCINT0/1/2_vect | Pin Change (PORTB / PORTC / PORTD) |
| 8 | TIMER2_COMPA_vect | Timer2 Compare Match A |
| 12 | TIMER1_COMPA_vect | Timer1 Compare Match A |
| 14 | TIMER1_OVF_vect | Timer1 Overflow |
| 17 | TIMER0_OVF_vect | Timer0 Overflow |
| 22 | ADC_vect | Conversion ADC terminée |
| 20–21 | USART_RX_vect / TX_vect | Ré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.
delay(), pas de Serial.print(), pas de boucles longues. Positionnez un flag et traitez-le dans la boucle principale.volatile — Toute variable partagée entre l'ISR et le code principal doit être déclarée volatile pour empêcher le compilateur de l'optimiser en cache registre.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é
| Concept | AVR (bare metal) | Arduino | PIC |
|---|---|---|---|
| Activer globalement | sei() | Automatique | GIE = 1 |
| Écrire une ISR | ISR(INT0_vect) | attachInterrupt() | void __interrupt() |
| Nombre de vecteurs | 26 vecteurs dédiés | 2 (INT0/INT1) via API | 1 vecteur unique (mid-range) |
| Priorité | Fixe par numéro de vecteur | Fixe | 2 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.