Guida alla Programmazione in C del PIC, 10° Parte

Guida alla Programmazione in C del PICNella Decima Parte della guida si Parlerà degli Interrupt dei Pin di I/O, dei Timer e gli altri Interrupt, in Particolare nel PIC18F252. Saranno Presenti 4 Esempi…


 
 
 
 




 

INTRO

Vi sono vari modi per eseguire porzioni di codice quando si verifica un evento, sostanzialmente si dividono in due categorie, polling e interrupt. Il polling consiste in un controllo continuo in attesa dell’evento, quindi il codice non esegue nessun’altra istruzione tranne quella di controllo se si vuole una risposta immediata del sistema. Questa soluzione si utilizza nel caso di eventi ricorrenti molto spessi o di programmi che devono solo controllare un evento e fare poche altre istruzioni. Se invece si ha un evento non molto ricorrente o un codice che deve eseguire molte istruzioni si deve utilizzare, in modo intelligente, un interrupt. Questo significa che quando avviene un evento, viene generato un segnale elettrico, che blocca la normale esecuzione del codice e rimanda ad una subroutine di interrupt, finita questa subroutine il codice riprende esecuzione da dove si era lasciata. Esistono vari interrupt all’interno del PIC18F252, come l’interrupt per eventi esterni, l’interrupt per il cambiamento dei segnali su PORTB, gli interrupt sui timer e sul modulo CCP, gli interrupt dell’ADC e delle memorie e anche gli interrupt dei moduli di comunicazione.

Gli interrupt inoltre possono avere priorità, ovvero, se si verificano due eventi di interrupt contemporaneamente, se uno ha priorità maggiore viene eseguito quella con priorità maggiore e l’altro non viene considerato.

Quasi per tutti gli interrupt vi sono 3 bit, due di setting e uno di controllo; vi è il bit per attivare l’interrupt, il cui nome termina con le lettere –IE, poi vi è il bit di controllo per la priorità il cui nome termina con le lettere –IP e infine vi è il bit che è il flag dell’interrupt il cui nome termina in –IF e serve per sapere se si è verificato o meno l’interrupt.

 




 

INTERRUPT ESTERNO INT1

Se si deve controllare un segnale esterno, come ad esempio un interruttore, si può usare un interrupt esterno, visto che il PIC sotto esame dispone di tre possibili interrupt esterni. Questo vuol dire che posso avere 3 interrupt generati da segnali esterni. I pin che servono per gli interrupt sono RB0, RB1, RB2. Prima di tutto bisogna abilitare gli interrupt, e per fare ciò bisogna settare il registro INTCON:

registro intcon interrupt

  • GIE/GIEH: Abilita tutti gli interrupt se settato a 1 e anche gli interrupt ad alta priorità.
  • PEIE/GIEL: Se settato abilita tutti gli interrupt delle periferiche e gli interrupt a bassa priorità.

Gli altri bit non menzionati sono i bit di enable del timer counter 0, dell’interrupt esterno 0 e dell’interrupt sul cambio delle PORTB, inoltre vi sono i relativi flag. Gli altri registri per attivare l’interrupt esterno 1 sono:

INTCON2:

registro intcon2 interrupt

  • INTEDG1: è il bit per selezionare quando attivare l’interrupt, ovvero se sul fronte di salita o discesa, se 1 si ha l’interrupt sul fronte di salita, se si scrive uno 0 invece si ha l’interrupt sul fronte di discesa.

INTCON3:

registro intcon3 interrupt

  • INT1IP: è il bit per la priorità dell’interrupt esterno 1, se è a 0 non si ha priorità, se 1 invece si assegna la priorità alta.
  • INT1IE: è il bit per abilitare l’interrupt esterno 1, se 0 è spento, se 1 è attivo.
  • INT1IF: è il flag per il controllo dell’interrupt esterno 1, se settato vuol dire che si è verificato un evento di interrupt esterno, va poi resettato in modo software.

Quindi per attivare l’interrupt bisogna settare il pin INT1IE a 1, poi INTEDG1 = 1 se si vuole l’interrupt sul fronte di salita. Supponiamo di voler scrivere un programma che non fa nulla (potrebbe fare tante istruzioni) e che inverte lo stato di un led quando si preme un pulsante connesso al pin dell’interrupt INT1. Il codice sarà:

//subroutine di interrupt
void EXT_INT() iv 0x0008 ics ICS_AUTO {    //0018h per bassa priorità, 0008h per alta priorità

 PORTB.B0 = ~ PORTB.B0;                    //inverti lo stato del led
 Delay_ms(10);                             //ritardo antirimbalzo
 INTCON3.INT1IF = 0;                       //reset del flag

}

void main() {

TRISB.B0 = 0;                              //pin di uscita
PORTB.B0 = 0;                              //pin di uscita
TRISB.B1 = 1;                              //pin di ingresso

RCON.IPEN = 0;                              //disabilito la priorità degli interrupt
INTCON.GIE = 1;                             //attivo gli interrupt

INTCON2.INTEDG1 = 1;                        //interrupt sul fronte di salita
INTCON3.INT1IE = 1;

  while(1){                                 //ciclo infinito
  }                                         //inserire istruzioni qui da fare
                                            //quando non vi è l'interrupt
}

Nella subroutine di interrupt inverto lo stato del led, poi aspetto 10ms per evitare rimbalzi e infine resetto il flag dell’interrupt in modo tale da riabilitarlo. Il nome della subroutine dipende dalla priorità dell’interrupt, si usa 0x0008 se sono a bassa priorità o non si specifica la priorità, mentre si usa 0x0018 per gli interrupt ad alta priorità. Nel codice principale invece si settano le porte di I/O, si disabilita la priorità, si attivano gli interrupt e si selezione il fronte di salita come interrupt. Infine si attiva l’interrupt INT1. Nel ciclo infinito while si possono inserire tutte le istruzioni che si vogliono.

Il circuito di prova è il seguente:

schema interrupt esterno pic18

 




 

INTERRUPT DEI TIMER COUNTER

I timer counter sono delle periferiche del PIC e per le periferiche vi sono dei registri opportuni per settare gli interrupt. questi registri sono:

registro pir1 interruptIl registro PIR1 contiene i flag degli interrupt di dei moduli di comunicazione, dell’ADC, del timer 1 e due e del modulo CCP1.

registro pir2 interruptNel registro PIR2 contiene i flag degli interrupt della memoria, del low voltage detector, del timer counter 3 e del modulo CCP2.

Poi vi sono i registri PIE1 e PIE2 strutturati nello stesso modo dei due appena visti che però servono per abilitare gli interrupt e i registri IPR1 e IPR2 che invece settano la priorità negli interrupt appena visti. Se si usano gli interrupt delle periferiche, bisogna inoltre settarli, ponendo a 1 il bit PEIE nel registro INTCON.

Supponiamo di voler attivare l’interrupt di overflow del timer counter 1, per fare ciò oltre che l’interrupt bisogna abilitare anche il timer counter e settarlo correttamente. Il codice sarà:

//subroutine di interrupt
void TMR_INT() iv 0x0008 ics ICS_AUTO {    //0018h per bassa priorità, 0008h per alta priorità

 PORTB.B0 = ~ PORTB.B0;                    //inverti lo stato del led
 PIR1.TMR1IF = 0;                          //reset del flag

}

void main() {

TRISB.B0 = 0;                              //pin di uscita
PORTB.B0 = 0;                              //pin di uscita

RCON.IPEN = 0;                             //disabilito la priorità degli interrupt
INTCON.GIE = 1;                            //attivo gli interrupt
INTCON.PEIE = 1;                           //attivo gli interrupt delle periferie

PIE1.TMR1IE = 1;                           //attivo l'interrupt di overflow di tmr1
IPR1.TMR1IP = 0;                           //disabilito la priorità

T1CON = 0b10110001;                        //tmr1 acceso a 16bit, prescaler 8 e clk interno

  while(1){                                //ciclo infinito
  }                                        //inserire istruzioni qui da fare
                                           //quando non vi è l'interrupt
}

Prima di tutto si scrive la routine di interrupt a bassa priorità e si inserisce l’istruzione per invertire lo stato del led e si resetta il flag. Non è necessario l’antirimbalzo visto che il segnale non proviene da un interruttore.

Nel programma principale si settano la porta di uscita dove sarà connesso il led, si disabilita la priorità degli interrupt e si abilita gli interrupt generali e quelli delle periferiche. Si abilita l’interrupt di overflow del timer counter 1 e si attiva quest’ultimo in modalità 16 bit con prescaler 8 e clock interno.

Infine è presente il ciclo infinito dove vanno inserite le istruzioni volute. Lo schema questa volta sarà un semplice microcontrollore alimentato a 5V, con una tensione di 5V sul pin MCLR e con un led e la sua resistenza di protezione:

schema interrupt timer counter 1

 

INTERRUPT MULTIPLI CON E SENZA PRIORITA’

Posso aver bisogno di più interrupt all’interno di uno stesso programma e vi sono due soluzioni per eseguirli. La prima soluzione semplice è quella di avere tutti gli interrupt a bassa priorità, avere una sola subroutine di interrupt e in essa con degli if controllare i flag per vedere quale interrupt in particolare si è verificato e successivamente eseguire la giusta porzione di codice. Se si hanno due soli interrupt, invece, si può assegnare ad uno una priorità alta e all’altro una bassa priorità, creare due subroutine differenti e il codice sarà indirizzato alla subroutine giusta. Avere le priorità però vuol dire che se è avvenuta una richiesta di interrupt ad alta priorità, e la si sta eseguendo, la richiesta di interrupt a bassa priorità non viene vista se avviene durante l’esecuzione della subroutine ad alta priorità.

Supponiamo di avere due interrupt, uno esterno e uno dell’ADC, con uguale priorità bassa, il codice sarà:

//subroutine di interrupt
void EXT_INT() iv 0x0008 ics ICS_AUTO {    //0018h per bassa priorità, 0008h per alta priorità

 if(INTCON3.INT1IF){                       //controllo interrupt esterno

 PORTB.B0 = ~ PORTB.B0;                    //inverti lo stato del led
 Delay_ms(10);                             //ritardo antirimbalzo
 }
 
 if(PIR1.ADIF){                            //controllo interrupt ADC
 
 PORTC = ADRESH;                           //inserisci in uscita il valore convertito

 }
 
 PIR1.ADIF = 0;                            //reset flag interrupt ADC
 INTCON3.INT1IF = 0;                       //reset flag interrupt esterno

}                                          //fine subruotine interrupt

void main() {

TRISB.B0 = 0;                              //pin di uscita
TRISC = 0;                                 //port c uscite
PORTB.B0 = 0;                              //pin di uscita
TRISB.B1 = 1;                              //pin di ingresso

RCON.IPEN = 0;                             //disabilito la priorità degli interrupt
INTCON.GIE = 1;                            //attivo gli interrupt
INTCON.PEIE = 1;

INTCON2.INTEDG1 = 1;                       //interrupt sul fronte di salita
INTCON3.INT1IE = 1;

ADCON0 = 0b01000101;    //adc oN, channel 0, clock/16
ADCON1 = 0b01001110;    //left ADJ, clock/16, Vref+=VDD, Vref-=gnd, only channel0 analog

PIE1.ADIE = 1;                              //abilito interrupt ADC


  while(1){                                 //ciclo infinito
  }                                         //inserire istruzioni qui da fare
                                            //quando non vi è l'interrupt
}

Prima di tutto nella routine di interrupt controllo con un if se è l’interrupt esterno ad essere attivo, e questo lo faccio grazie al flag INTCON3.INT1IF. Se INTCON3,INT1IF=1 toglo il led e utilizzo un delay antirimbalzo. Per verificare invece se si è attivato l’interrupt dell’ADC si controlla il flag PIR1.ADIF, e se settato si invia in uscita il valore convertito. Infine si resettano tutti e due i flag.

Nel programma principale si settano le porte di I/O, si settano gli interrupt generali e delle periferiche e si setta l’interrupt esterno. Successivamente si setta l’ADC e in particolare si attiva con un clock prescalato di 16, con ingresso il canale 0 ovvero AN0, con il left adj e come tensione di riferimento VCC. Infine si setta l’interrupt dell’ADC e si scrive il ciclo infinito dove inserire le istruzioni volute. Lo schema è il seguente:

schema interrupt esterno e adc senza priorità pic18

Per la soluzione con priorità, il codice è il seguente:

//subroutine di interrupt
void EXT_INT_HIGH() iv 0x0008 ics ICS_AUTO {    //0018h per bassa priorità, 0008h per alta priorità

 PORTB.B0 = ~ PORTB.B0;                    //inverti lo stato del led
 Delay_ms(10);                             //ritardo antirimbalzo
 INTCON3.INT1IF = 0;                       //reset flag interrupt esterno
}

void EXT_INT_LOW() iv 0x0018 ics ICS_AUTO {    //0018h per bassa priorità, 0008h per alta priorità

 PORTC = ADRESH;                           //inserisci in uscita il valore convertito
 PIR1.ADIF = 0;                            //reset flag interrupt ADC
}

void main() {
TRISB.B0 = 0;                              //pin di uscita
TRISC = 0;                                 //port c uscite
PORTB.B0 = 0;                              //pin di uscita
TRISB.B1 = 1;                              //pin di ingresso

RCON.IPEN = 1;                             //abilito la priorità degli interrupt
INTCON.GIE = 1;                            //attivo gli interrupt
INTCON.PEIE = 1;                           //attivo interrupt delle periferiche

INTCON2.INTEDG1 = 1;                       //interrupt sul fronte di salita
INTCON3.INT1IE = 1;                        //interrupt esterno abilitato
INTCON3.INT1IP = 1;                        //priorità alta interrupt esterno

ADCON0 = 0b01000101;    //adc oN, channel 0, clock/16
ADCON1 = 0b01001110;    //left ADJ, clock/16, Vref+=VDD, Vref-=gnd, only channel0 analog

PIE1.ADIE = 1;                             //abilito interrupt ADC
IPR1.ADIP = 0;                             //priorità bassa interrupt ADC

  while(1){                                //ciclo infinito
  }                                        //inserire istruzioni qui da fare
                                           //quando non vi è l'interrupt
}

Vi saranno quindi due subroutine per gli interrupt, una ad alta priorità con l’indirizzo 0x0008 e una a bassa priorità con l’indirizzo 0x0018. Nella prima subroutine si va ad invertire il led, si implementa l’antirimbalzo software e si resetta il flag, nella seconda subroutine, quella a bassa priorità si va semplicemente a visualizzare il valore convertito in uscita e si resetta il flag.

Nel programma principale si configurano le porte di I/O, si attivano gli interrupt generali, quello delle periferiche e si abilita la priorità. Dopo di che seleziono il fronte di salita di INT1 come interrupt, abilito l’interrupt INT1 e gli assegno la priorità alta settando il bit INTCON3.INT1IP.

Visto che utilizzo l’ADC lo configuro come visto prima e attivo il suo interrupt senza priorità.
 

DOWNLOAD

Potete scaricare tutti i file dal seguente LINK!!!





 

[Voti Totali: 0 Media Voti: 0]
Segui la Nostra Pagina Facebook: Facebook

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *