⚡ 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 bouton, fin de conversion ADC, changement d’état d’une broche, etc.

Le PIC12F683 possède 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 qui a déclenché l’interruption.

🔁 Si tu viens d’un tuto “PIC16F…”

  • PORTA / TRISAGPIO / TRISIO
  • Broches RAxGPx (GP0…GP5)
  • Interruptions “PORTA change” → IOC (Interrupt-On-Change) via IOC + GPIF/GPIE

📌 Rappel DIP-8 : broches utiles

  • GP0 = pin 7 (souvent pratique pour une LED)
  • GP2 / INT = pin 5 (interruption externe INT)
  • MCLR / GP3 = pin 4 (si MCLRE=ON)
  • VDD = pin 1, VSS = pin 8

💡 Astuce : si tu ne veux pas câbler MCLR, mets MCLRE = OFF (GP3 devient une entrée).

📋 Sources d'interruption du PIC12F683

Sur PIC12F683, on a des interruptions “cœur” (dans INTCON) et des interruptions “périphériques” (via PIE1/PIR1 + PEIE).

Source Flag Enable Où ? Description
INT externe INTF INTE INTCON Interruption externe sur GP2/INT
Timer0 T0IF T0IE INTCON Débordement de TMR0
IOC (changement GPIO) GPIF GPIE INTCON (+ IOC) Changement sur GP0..GP5 selon masque IOC
Timer1 TMR1IF TMR1IE PIR1/PIE1 Débordement Timer1 (périphérique)
Timer2 TMR2IF TMR2IE PIR1/PIE1 Débordement Timer2 (périphérique)
ADC ADIF ADIE PIR1/PIE1 Fin de conversion ADC
EEPROM EEIF EEIE PIR1/PIE1 Fin d’écriture EEPROM
Comparateur CMIF CMIE PIR1/PIE1 Événement comparateur
CCP1 CCP1IF CCP1IE PIR1/PIE1 Capture/Compare/PWM (selon mode)

Note : Les noms de flags/enables ci-dessus correspondent aux registres INTCON/PIE1/PIR1 du PIC12F683.

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

Pour que ça déclenche, il faut :

  1. GIE = 1 (interruption globale)
  2. Si périphérique : PEIE = 1 (Timer1/Timer2/ADC/EEPROM/CCP1/Comparateur…)
  3. Activer l’interruption spécifique (ex : T0IE, INTE, TMR1IE, etc.)
  4. Effacer le flag au départ (ex : T0IF=0)
Exemples d’activation (PIC12F683)
// Timer0 (INTCON)
INTCONbits.T0IF = 0;   // Clear flag
INTCONbits.T0IE = 1;   // Enable Timer0 interrupt
INTCONbits.GIE  = 1;   // Global interrupt ON

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

// IOC (Interrupt-On-Change sur GPIO)
IOC = 0b00000100;      // Exemple : surveiller GP2 (bit2) (adapte selon besoin)
INTCONbits.GPIF = 0;   // Clear flag
INTCONbits.GPIE = 1;   // Enable IOC interrupt
INTCONbits.GIE  = 1;

⚠️ Important (IOC)

Après un changement GPIO, le flag GPIF peut rester actif tant que l’état “mismatch” n’est pas résolu. En pratique : lis GPIO puis remets GPIF=0 dans l’ISR.

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

Exemple “propre” : l’ISR fait le minimum (recharge + compteur + toggle), et la boucle principale reste libre.

C (XC8) — PIC12F683
/*
 * Projet : LED clignotante par interruption Timer0
 * MCU    : PIC12F683 (DIP-8)
 * LED    : GP0 (pin 7) -> R 330Ω -> LED -> GND
 */

#pragma config FOSC = INTRCIO   // Oscillateur interne + I/O sur GP4/GP5
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config MCLRE = ON       // GP3/MCLR actif (mets OFF si tu veux GP3 en entrée)
#pragma config BOREN = ON
#pragma config CP = OFF
#pragma config CPD = OFF

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

#define _XTAL_FREQ 4000000

volatile uint8_t ms = 0;

// ISR unique (vecteur 0x0004)
void __interrupt() isr(void) {
    if(INTCONbits.T0IF) {
        INTCONbits.T0IF = 0;   // TOUJOURS effacer le flag
        TMR0 = 6;              // Ajuste pour ~1ms selon prescaler

        ms++;
        if(ms >= 250) {        // ~250 ms
            ms = 0;
            GPIO ^= 0b00000001; // Toggle GP0
        }
    }
}

void main(void) {
    // Oscillateur interne ~4MHz (IRCF=110)
    OSCCON = 0b01100000;

    // Tout en numérique
    ANSEL = 0;          // ADC OFF (canaux en numérique)
    CMCON0 = 0x07;      // Comparateur OFF (recommandé pour I/O)

    // GP0 en sortie, le reste en entrée (bits GP5..GP0)
    TRISIO = 0b00111110;
    GPIO = 0;

    // Timer0 : prescaler + source interne
    OPTION_REG = 0b00000010;   // PSA=0, PS=010 (1:8) - exemple, adapte si besoin
    TMR0 = 6;

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

    while(1) {
        // CPU libre : tu peux faire autre chose ici
        NOP();
    }
}

⚠️ Règles d’or pour une ISR

  • Toujours remettre le flag à 0 (sinon ISR en boucle)
  • ISR la plus courte possible
  • Variables partagées : volatile
  • Éviter __delay_ms() dans l’ISR

🔘 Exemple propre : bouton sur GP2 (INT) + anti-rebond sans delay dans l’ISR

Ici, l’ISR se contente de poser un drapeau. Le debounce se fait dans la boucle principale.

C (XC8) — INT sur GP2
/*
 * Bouton : GP2/INT (pin 5) vers GND (avec pull-up)
 * LED    : GP0 (pin 7)
 */

#pragma config FOSC = INTRCIO
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config MCLRE = ON
#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; // On traite dans main
    }
}

void main(void) {
    OSCCON = 0b01100000;
    ANSEL = 0;
    CMCON0 = 0x07;

    // GP0 sortie (LED), GP2 entrée (bouton)
    TRISIO = 0b00111110; // GP0=0 sortie, le reste entrée
    GPIO = 0;

    // Pull-up interne sur GPIO (si tu veux) + activer WPU sur GP2 si besoin
    // OPTION_REGbits.nGPPU = 0;  // Pull-ups globaux ON (selon config)
    // WPUbits.WPU2 = 1;          // Pull-up sur GP2 (si registre dispo dans ton header)

    // INT externe sur GP2
    OPTION_REGbits.INTEDG = 0; // 0 = front descendant (bouton vers GND)
    INTCONbits.INTF = 0;
    INTCONbits.INTE = 1;
    INTCONbits.GIE  = 1;

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

            // Anti-rebond (dans main, OK)
            __delay_ms(30);
            if( (GPIO & 0b00000100) == 0 ) { // GP2 toujours à 0 ?
                GPIO ^= 0b00000001; // Toggle LED (GP0)
                // Attendre relâchement si tu veux :
                // while((GPIO & 0b00000100) == 0) { }
            }
        }
        NOP();
    }
}