⚡ Qu'est-ce qu'une interruption ?

Une interruption permet au microcontrôleur de suspendre le programme principal pour exécuter une routine courte (ISR), dès qu’un événement se produit : débordement d’un timer, appui sur un bouton, fin de conversion ADC, réception série, etc.

Le PIC16F877A possède lui aussi un vecteur d’interruption unique à l’adresse 0x0004. Toutes les sources arrivent dans la même ISR : c’est à toi de tester les flags pour savoir quelle source a déclenché l’interruption.

💡 Point important sur le PIC16F877A

  • Pas d’oscillateur interne “moderne” comme sur certains petits PIC : on utilise souvent un quartz externe.
  • Les interruptions externes classiques passent par RB0/INT.
  • Le changement d’état sur PORTB concerne surtout RB4 à RB7.

📌 Rappel DIP-40 : broches utiles

  • RB0 / INT = pin 33 (interruption externe)
  • RB4 à RB7 = pins 37 à 40 (interruptions sur changement PORTB)
  • RD0 = pin 19 (pratique pour une LED dans les exemples)
  • MCLR = pin 1
  • VDD = pins 11 et 32, VSS = pins 12 et 31
  • OSC1 / OSC2 = pins 13 / 14

💡 Astuce : pour éviter les conflits, on met souvent la LED sur PORTD et le bouton d’interruption sur RB0.

📋 Sources d'interruption du PIC16F877A

Sur le PIC16F877A, on retrouve des interruptions “cœur” dans INTCON, puis des interruptions périphériques via PIE1 / PIR1 et PIE2 / PIR2.

Source Flag Enable Où ? Description
INT externe INTF INTE INTCON Interruption externe sur RB0/INT
Timer0 T0IF T0IE INTCON Débordement de TMR0
Changement PORTB RBIF RBIE INTCON Changement sur RB4 à RB7
Timer1 TMR1IF TMR1IE PIR1 / PIE1 Débordement Timer1
Timer2 TMR2IF TMR2IE PIR1 / PIE1 Débordement Timer2
ADC ADIF ADIE PIR1 / PIE1 Fin de conversion ADC
EEPROM EEIF EEIE PIR2 / PIE2 Fin d’écriture EEPROM
USART RX RCIF RCIE PIR1 / PIE1 Réception série
USART TX TXIF TXIE PIR1 / PIE1 Transmission série
CCP1 CCP1IF CCP1IE PIR1 / PIE1 Capture / Compare / PWM
CCP2 CCP2IF CCP2IE PIR2 / PIE2 Deuxième module CCP
SSP (I2C/SPI) SSPIF SSPIE PIR1 / PIE1 Événement I2C / SPI
PSP PSPIF PSPIE PIR1 / PIE1 Parallel Slave Port

⚙️ Activation d'une interruption (méthode simple)

Pour qu’une interruption fonctionne, il faut :

  1. GIE = 1 (interruption globale)
  2. Si l’interruption est périphérique : PEIE = 1
  3. Activer la source voulue (ex : T0IE, INTE, ADIE…)
  4. Effacer le flag au départ (ex : T0IF = 0)
Exemples d’activation (PIC16F877A)
// Timer0 (INTCON)
INTCONbits.T0IF = 0;   // Clear flag
INTCONbits.T0IE = 1;   // Enable Timer0 interrupt
INTCONbits.GIE  = 1;   // Global interrupt ON

// ADC (périphérique)
PIR1bits.ADIF   = 0;   // Clear flag
PIE1bits.ADIE   = 1;   // Enable ADC interrupt
INTCONbits.PEIE = 1;   // Peripheral interrupts ON
INTCONbits.GIE  = 1;   // Global interrupt ON

// Interruption externe sur RB0/INT
INTCONbits.INTF = 0;
INTCONbits.INTE = 1;
OPTION_REGbits.INTEDG = 0; // 0 = front descendant
INTCONbits.GIE  = 1;

// Changement sur PORTB (RB4 à RB7)
INTCONbits.RBIF = 0;
INTCONbits.RBIE = 1;
INTCONbits.GIE  = 1;

⚠️ Important (PORTB change)

Pour remettre correctement RBIF à zéro, il faut en pratique lire PORTB d’abord, puis effacer le flag. Sinon l’interruption peut revenir immédiatement.

📝 Exemple complet : LED clignotante par interruption Timer0 (RD0)

Exemple propre : l’ISR fait le minimum (reload + compteur + bascule de la LED) et la boucle principale reste libre.

C (XC8) — PIC16F877A
/*
 * Projet : LED clignotante par interruption Timer0
 * MCU    : PIC16F877A (DIP-40)
 * LED    : RD0 (pin 19) -> R 330Ω -> LED -> GND
 */

#pragma config FOSC = XT
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config BOREN = ON
#pragma config LVP = OFF
#pragma config CPD = OFF
#pragma config WRT = OFF
#pragma config CP = OFF

#include <xc.h>
#include <stdint.h>

#define _XTAL_FREQ 4000000

volatile uint8_t ticks = 0;

void __interrupt() isr(void) {
    if(INTCONbits.T0IF) {
        INTCONbits.T0IF = 0;
        TMR0 = 6;  // Ajuste selon ton prescaler et ta fréquence

        ticks++;
        if(ticks >= 250) {
            ticks = 0;
            PORTDbits.RD0 = !PORTDbits.RD0;
        }
    }
}

void main(void) {
    // Met les broches analogiques en numérique
    ADCON1 = 0x06;

    // RD0 en sortie
    TRISD = 0b11111110;
    PORTD = 0x00;

    // Timer0 : horloge interne + prescaler 1:8
    OPTION_REG = 0b00000010;
    TMR0 = 6;

    // Interruptions Timer0
    INTCONbits.T0IF = 0;
    INTCONbits.T0IE = 1;
    INTCONbits.GIE  = 1;

    while(1) {
        // Le CPU reste libre pour autre chose
        NOP();
    }
}

⚠️ Règles d’or pour une ISR

  • Toujours remettre le flag à 0
  • Garder l’ISR très courte
  • Utiliser volatile pour les variables partagées
  • Éviter __delay_ms() dans l’ISR

🔘 Exemple propre : bouton sur RB0 (INT) + LED sur RD0

Ici, l’ISR se contente de poser un drapeau. L’anti-rebond est géré dans la boucle principale.

C (XC8) — INT sur RB0
/*
 * Bouton : RB0/INT (pin 33) vers GND avec pull-up externe
 * LED    : RD0 (pin 19)
 */

#pragma config FOSC = XT
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config BOREN = ON
#pragma config LVP = OFF

#include <xc.h>
#define _XTAL_FREQ 4000000

volatile unsigned char btn_event = 0;

void __interrupt() isr(void) {
    if(INTCONbits.INTF) {
        INTCONbits.INTF = 0;
        btn_event = 1;
    }
}

void main(void) {
    ADCON1 = 0x06;

    // RB0 en entrée (bouton), RD0 en sortie (LED)
    TRISBbits.TRISB0 = 1;
    TRISDbits.TRISD0 = 0;

    PORTDbits.RD0 = 0;

    // Interruption externe sur front descendant
    OPTION_REGbits.INTEDG = 0;
    INTCONbits.INTF = 0;
    INTCONbits.INTE = 1;
    INTCONbits.GIE  = 1;

    while(1) {
        if(btn_event) {
            btn_event = 0;

            // Anti-rebond dans main
            __delay_ms(30);

            if(PORTBbits.RB0 == 0) {
                PORTDbits.RD0 = !PORTDbits.RD0;
            }
        }
    }
}

🧠 Exemple : interruption sur changement de PORTB (RB4 à RB7)

Très utile pour détecter l’appui sur plusieurs boutons. Sur le PIC16F877A, cette interruption concerne principalement RB4 à RB7.

PORTB Change
volatile unsigned char portb_event = 0;

void __interrupt() isr(void) {
    if(INTCONbits.RBIF) {
        unsigned char dummy = PORTB; // Lecture obligatoire
        INTCONbits.RBIF = 0;
        portb_event = 1;
    }
}