Salta la barra di navigazione [1] - Vai alla barra di navigazione [3] - Scrivimi una mail [4]

Le interruzioni

PIC18 in C - Sommario - Novità - Tutorial - Progetti - Risorse - Non solo elettronica

Emulatore ICD3 con header per PIC18 su schedine demo Microchip

In questa pagina verrà mostrato come gestire le interruzioni del PIC18 generate da una sorgente esterna, quale un semplice interruttore. Qui una breve descrizione del concetto di interruzione.

Anche molte periferiche interne possono generare interruzioni: verranno esaminati nelle pagine dedicate alle singole periferiche.

Livelli di interruzioni

PIC18 possiede due livelli di interruzioni: low_priority e high_priority, entrambe mascherabili.

Le differenze:

Questa modalità a due livelli è disattiva al momento del reset, ma meglio abituarsi ad usarla sempre, attivandola in modo esplicito fin dall'inizio del programma settando l'apposito bit nel registro RCON:

 RCONbits.IPEN = 1; // Enable priority levels on interrupts

Di seguito non sono riportati esempi che utilizzano la modalità legacy senza priorità, tipica dei PIC16.

Non è presente alcuna interruzione non mascherabile.

Sorgenti di interruzioni esterne

Preliminarmente occorre leggere sui fogli tecnici quali sono le sorgenti esterne di interruzioni presenti nel modello di PIC18 che abbiamo a disposizione. Due le situazioni comuni:

Usare una External Interrupt

Il primo esempio proposto utilizza un'interruzione generata dalla pressione di un pulsante per spegnere un LED:

In questo esempio il LED non può essere nuovamente acceso se non resettando il PIC18 e ricominciando da capo l'esecuzione del programma.

Per comprendere il funzionamento dell'esempio (ed anche per individuare eventuali errori) è consigliabile inserire un breakpoint all'interno della funzione di gestione dell'interruzione (ISR) descritta più avanti: è infatti complesso utilizzare in questo contesto l'esecuzione passo-passo del codice.

 

Il microcontrollore di riferimento in questo esempio è il PIC18F14K50, ma dovrebbe funzionare su qualunque PIC18 che possiede i pin utilizzati. Il codice completo è a fondo pagina.

I riferimenti da consultare (indicati tra parentesi quadra nel codice) sono le tabelle presenti sui fogli tecnici del PIC18F14K50:

  1. REGISTER 9-12: TRISC: PORTC TRI-STATE REGISTER - Pagina 90
  2. REGISTER 7-10: RCON: RESET CONTROL REGISTER - Pagina 74
  3. REGISTER 7-3: INTCON3: INTERRUPT CONTROL 3 REGISTER - Pagina 67
  4. REGISTER 7-2: INTCON2: INTERRUPT CONTROL 2 REGISTER - Pagina 66
  5. REGISTER 9-11: PORTC: PORTC REGISTER - Pagina 90
  6. REGISTER 9-15: ANSEL: ANALOG SELECT REGISTER 1 - Pagina 94

Esaminiamo ora la struttura della funzione main() il cui compito è quello di configurare PORTC, abilitare le interruzioni ed accendere il LED:

Configurazione delle porte di I/O

Il pin RC0, collegato al LED, viene configurato come uscita digitale e viene acceso il LED:
TRISCbits.RC0 = 0; // Set RC0 pin as output [1]
PORTCbits.RC0 = 1; // Turn on RC0 pin [5]

Il pin RC1, collegato al pulsante, viene configurato come ingresso digitale:
TRISCbits.RC1 = 1;  // Set RC1 pin as input [1]
ANSELbits.ANS5 = 0; // Digital input buffer of RC1 is enabled [6]

Configurazione generale delle interruzioni

Come già scritto, deve essere inizialmente impostata la modalità di funzionamento con doppia priorità:
RCONbits.IPEN = 1; // Enable priority levels on interrupts [2]

Configurazione di INT1

Abilito RC1 (INT1) per generare un'interruzione ad alta priorità sul fronte di discesa del segnale esterno, cioè alla pressione del pulsante:
INTCON3bits.INT1IP = 1;  // INT1 External Interrupt Priority set to High [3]
INTCON2bits.INTEDG1 = 0; // INT1 on falling edge [4]
INTCON3bits.INT1E = 1;   // Enables the INT1 external interrupt [3]

Infine azzero il flag che segnala la generazione di un'interruzione da parte di INT1, che al momento dell'attivazione potrebbe già essere alto; se fosse lasciato potrebbe immediatamente essere generato un'interruzione inesistente, senza attendere la reale pressione del pulsante. Questo comportamento è comune a molte  periferiche:
INTCON3bits.INT1F = 0; // Clear INT1 flag [3]

Abilitazione delle interruzioni

Terminate tutte le operazioni di configurazione delle interruzioni per le singole periferiche, possiamo abilitare le interruzioni in modo globale:

INTCONbits.GIEH = 1; // Global interrupt enable

In alternativa è possibile anche utilizzare l'apposita macro ei().

Se viene usato interruzioni a bassa priorità, occorre ricordarsi di abilitarle, con un ulteriore assegnamento, simile al precedente:
INTCONbits.GIEL = 1; // Enables all low priority interrupts

Esecuzione del super-loop

Dopo la configurazione delle periferiche, il processore può eseguire la parte "principale" del codice. In questo breve esempio si tratta semplicemente di un loop infinito:
while (1); // Do nothing, forever

La ISR (Interrup Service Routine)

Il termine ISR identifica la funzione che viene eseguita quando una periferica precedentemente abilitata genera un'interruzione. Nel dettaglio si tratta di codice che deve obbligatoriamente iniziare all'indirizzo di memoria 0x08 (oppure 0x18), ma scrivendo codice C possiamo ignorare questi aspetti, gestiti automaticamente dal compilatore che provvede anche al salvataggio dei registri nello stack.

Questa funzione appare come una funzione C di tipo void, senza parametri. Sono presenti due parole chiave:  interrupt  e __high_priority, dall'ovvio significato. Il nome della funzione è arbitrario:
void interrupt __high_priority my_isr_h(void)

Questa funzione viene eseguita solo nel momento in cui una periferica genera una interruzione.

La struttura di una ISR è fissa:

  1. Occorre preliminarmente verificare quale periferica ha generato l'interruzione, attraverso l'esame del flag associato a ciascuna di esse
    if (INTCON3bits.INT1F == 1)  // INT1 external interrupt occurred ? [3]
  2. Passiamo quindi ad eseguire il codice vero e proprio della ISR  (in questo esempio: spegnere il LED)
    PORTCbits.RC0 = 0; // Turn off RC0 pin [5]
  3. Al termine, prima del return, occorre cancellare il flag relativo alla periferica che ha generato l'interruzione, lo stesso utilizzato poche righe sopra. Se non viene fatta questa "pulizia", appena terminata l'esecuzione della ISR, viene immediatamente generata, in un loop infinito, una nuova interruzione... inesistente. Si legga la nota generalmente presente nel foglio tecnico del PIC18 che state usando
    INTCON3bits.INT1F = 0; // Clear INT1 flag [3]

Dato che tutte le periferiche condividono una sola ISR, in genere è necessario verificare più flag, uno per ciascuna periferica per cui è abilitata l'interruzione. Nel codice è presente anche lo scheletro del controllo di una seconda sorgente di interruzioni, commentato.

Nel codice è infine presente anche lo scheletro di una ISR a bassa priorità, qui non utilizzata.

Possono esistere solo due funzioni di tipo interrupt, una __high_priority ed una __low_priority.

Un altro esempio: Interrupt On-Pin-Change

Questo ulteriore esempio è relativo al PIC18F2431. In sintesi:

La descrizione dettagliata del codice è oggetto del secondo esercizio. Aspetti da tenere in primo piano:

Uno schema dimostrativo con PIC18F2431, LED e pulsante

Approfondimento: la latenza

La latenza, in questo contesto, misura il tempo necessario al PIC18 per "rispondere" ad una richiesta di interruzione. Dipende da diversi fattori:

A titolo di esempio si riporta il grafico associato al primo esempio presentato:

Il clock utilizzato è 1 MHz, il tempo complessivo circa 130 µs, pari all'esecuzione di 32 istruzioni macchina, tante rispetto a quanto si può ottenere in assembly. Ovviamente se si aumenta la frequenza di clock, tale tempo diminuisce proporzionalmente.

Latenza: interrupt ad alta priorià

Modificando il codice per utilizzare interruzioni a bassa priorità (esercizio 1), il grafico corrispondente è il seguente, coerente col fatto che la latenza aumenta (di poco) passando da 130 a 170 µs.

Latenza: interrupt ad bassa priorià

 

Codice

Esercizi

  1. Riscrivere il primo esempio utilizzando una interruzione a bassa priorità
  2. Esaminare e commentare il codice di Interrupt On-Pin Change
  3. Collegare due pulsanti, uno ad INT0 ed uno ad INT1. Scrivere una ISR che accende il LED se l'interruzione deriva dal primo pulsante, lo spenge se deriva dal secondo pulsante. Entrambe le interruzioni devono avere la stessa priorità
  4. Esaminare il codice relativo ad INT0 per PIC18F2xK20, sopra riportato. Il codice è stato scritto per il circuito PICdemo, ma può essere utilizzato con un qualunque circuito con due LED collegati a RC6 e RC7 ed un interruttore collegato a RB0 (INT0)
  5. Esaminare il codice relativo ad INT0 e INT1 per PIC18F2xK20. Il codice è stato scritto per il circuito PICdemo e sono utilizzati tre LED collegati a RC5, RC6 e RC7 e due interruttori collegati a RB0 (INT0) e RB1 (INT1)

Ultima modifica di questa pagina: 25 febbraio 2016 - Una pagina simile: Le interruzioni nel PIC18 (in assembly)


PIC18 in C

Appendici:

Qualche utile informazione integrativa: C per sistemi embedded

Licenza Creative Commons Attribuzione 4.0 Internazionale


EN - Pagina principale - Sommario - Accessibilità - Note legali e privacy policy - Posta elettronica

XHTML 1.0 Strict - CSS 3