Let's GO PIC!!!

cap. 6

Gli interrupt

Sesto capitolo del tutorial Let's GO PIC !!! in cui si introdurrà una tipologia di programmazione più avanzata e nello stesso tempo più comune e funzionale. Quanto presentato nelle puntate precedenti tiene impegnato il processore distogliendone l'attenzione dai segnali provenienti dal bordo macchina verso i PORT di input, e un nuovo task verrà eseguito solo dopo che sia terminato quello in corso. In pratica questa modalità di programmazione non è solo più lenta ma realmente pericolosa dato che un segnale rapido di emergenza non solo sarà soddisfatto in un secondo momento ma potrebbe addirittura venire ignorato se la sua permanenza all'input fosse più breve del tempo necessario ad terminare il task in corso.

Il contesto
Prima di addentrarci nella programmazione forniamo ai "nuovi del settore" alcuni concetti di base che coinvolgono non solo il software ma anche l'architettura interna del processore o analogamente del microcontrollore. Anche se questa sesta puntata vuole essere estremamente succinta, facciamo comunque qualche breve divagazione introduttiva come si addice al classico stile "ad.noctis".

In primis, facciamo una domanda:  Quanti di voi riconosco quest'uomo?

 

 

 

Nessuno ???  Male!.. molto Male!!   perchè quest'uomo è uno che ha ben contribuito a fare grade il nostro paese. E' nato in una frazione della citta di Vicenza e si chiama Federico Faggin. Grazie a lui possiamo usare i computer odierni dato che è l'inventore del microprocessore.  Hooo   ma che sguardi stupiti, pensavate che il processore lo avessero inventato gli americani, o i giapponesi o che ne so i russi?  No no, è un prodotto nostrano, esattamente come la polenta, la pizza,  o più tecnologicamente il telefono ecc ecc.

Quando faccio questo indovinello ai miei giovani allievi molti rispondo: Bill Gate, ovvio lo sanno tutti.    Bhè magari sarà stato un ottimo commerciale e forse programmatore ma non mi sembra che l'hardware fosse il suo campo. 

Comunque, facciamo un saltino a questa pagina, e vediamo chi sono le persone che con il loro genio e il loro lavoro hanno reso comoda e semplice la nostra vita:

 

http://e-ducation.net/inventors.htm

 

Se siete tornati dal salto al link, avete lo spirito giusto per continuare. Avete notato che visi semplici hanno queste persone che hanno cambiato il mondo? Bene, potrebbero essere ogniuno di voi,  questo vi da la carica è?.

 

 

 

Vediamo cos'è il "contesto".

 

Si dice contesto, lo stato attuale di una elaborazione di programma, rappresentata dal contenuto di ogni registro interno del microprocessore / microcontrollore.

 

 E' indispensabile conoscere l'architettura interna, o almeno le parti fondamentali del microprocessore, e a tal fine rimane sempre valida l'architettura di base ideata da Faggin.

 

Descriviamo brevemente gli oggetti visibili nello schema a blocchi:

Bus: Con il termine bus si identificano le linee fisiche su cui sono trasferiti i bit di informazione.

I bus contenuti in un generico microprocessore (ovvero un oggetto da studio) sono:

  1. Un bus interno che collega tra loro i singoli elementi: Registri, A.L.U., Registro accumulatore, unità di controllo C.U., ecc. Tutti i trasferimenti dei dati nella C.P.U. avvengono su questo B.U.S..
  2. Bus dati, necessario allo scambio di informazioni con l'esterno. Il bus dati è collegato in maniera bidirezionale alla memoria e ai dispositvi di I/O.
  3. Bus indirizzi, necessario per selezionare le righe e le colonne della matrice di memoria.
  4. Bus di controllo, necessario per trasmettere o ricevere segnali di controllo col mondo esterno.

 

La A.L.U.

Aritmetic Logic Unit, solitamente disegnato con forma trapeziodale o simile, contiene la rete logica sequenziale/combinatoria utilizzata dal microprocessore per l'elaborazione dei dati.      La ALU è un elemento privo di memoria, percui deve essere assistita in ingresso da due (o più) registri temporanei (nel caso dei P.L.C. Siemens, ad esempio, si chiamano ACCU1 e ACCU2). L'accumulatore, per i PIC denominato registro di lavoro, o working register, ( registro W)  sarà considerato la destinazione canonica di tutti i movimenti di dati da e per la memoria, ed anche il luogo nel quale si depositano i risultati delle operazioni.  I dati che vanno alla A.L.U. transitano e sono memorizzati nei registri accumulatori (nel caso dei PIC nel W).  Per quanto riguarda l'hardware della ALU deve essere in grado di eseguire almeno le seguenti operazioni minime: Addizione, Incremento, AND, Ex-Or, Shift destro e sinistro, roll, Clear dell'accumulatore. Sempre sotto controllo da parte della ALU è il registro di STATO, il quale contiene i FLAG, (bit che indicano lo stato di funzionamento).  

 

Registri specifici.

Questi dispositivi memorizzano in modo temporaneo i dati da elaborare o le impostazioni di alcune sezioni hardware del microprocessore/microcontrollore. Uno di questi registri è ad esempio TRISx dove x indica il PORT di I/O a cui si fa riferimento. Anche lo status register fa parte di questi registri e mantiene dei bit di significato speciale (flag). Le informazioni riposte in questi registri vi permangono (Latch) fino al momento di una sovrascrittura.

Registro di lavoro W.

Costituisce il registro principale di un PIC e sarà la parte più in uso durante il normale funzionamento di qualunque programma. Ogni operazione che interessa il W register  ne cancella il contenuto precedente a causa di una sovrascrittura.  Il working register può operare direttamente con la memoria o i dispositivi di I/O, esistono infatti, in assembly, operazioni di trasferimento del tipo:

Registri di uso generale A,B,C,D.

Vengono utilizzati come se fossero delle celle di memoria R.A.M., e quindi sono indirizzabili (accessibili) anche un byte alla volta. Questi registri sono molto utili per trattenere dati parziali di un'addizione (o operazioni in genere) contestualmente al contenuto del W register.

Status Register (SR).

in questo registro sono contenuti i flags i quali hanno lo scopo di memorizzare i risultati di alcuni test eseguiti dalla ALU. I flags (bandierine di segnalazione) normalmente presenti in un microprocessore sono:

C (carry), segnala se durante un'operazione algebrica si è verificato un riporto. Normalmente una segnalazione di carry corrisponde ad un errore.

Z (zero),  segnala quando il risultato di una operazione è nullo.

N (negative), segnala se il primo bit di una rappresentazione flating point è 1, quindi se il numero rappresentato è negativo. Inoltre, quando si lavora in complemento a due N risulta alto.

P (parity), questo flag è in grado di segnalare se durante un trasferimento dati (sia interno che esterno) si è verificato un errore di trasmissione.

Program counter (PC).

IL PC contiene l'indirizzo dell'istruzione che si sta eseguendo e predispone il microprocessore all'esecuzione della prossima tramite il processo dell'auto incremento. Considerando la memoria come una matrice, ad ogni sua riga corrisponde un valore esadecimale "indirizzo". Per estrarre un'istruzione o dato dalla memoria è necessario "puntare" a tale cella caricando il suo indirizzo nel PC. La lunghezza di tale registro determina la massa di dati indirizzabili in memoria cioè la "locazione" di memoria più lontana dall'origine (estensione). Durante l'esecuzione di un programma si possono verificare due casi:

  1. il programma è una lista sequenziale di istruzioni da eseguire una dopo l'altra, è questo il caso dell'incremento automatico e progressivo del PC.  Nota bene, il PC viene incrementato di un valore che dipende da come è organizzata la memoria. Esempio: Una memoria organizzata in Byte impone al PC un incremento di +1, una memoria organizzata in word di 16 bit impone un incremento del PC pari a +2.
  2. Il program counter  (PC) viene caricato direttamente con istruzioni software che impongono il salto ad indirizzi noti: vedi salto a subroutine.

Stack pointer (SP).

Questo registro che consente la tecnica di programmazione a blocchi, cioè consente la chiamata a sottoprogrammi. Lo stack è una struttura di tipo L.I.F.O. (last in first out) situata in una particolare area di memoria dipendente dal tipo di microprocessore. Lo stack di tipo LIFO è organizzato in modo che l'ultimo dato ad essere inserito sia necessariamente il primo ad essere letto "estratto". un'immagine mentale della struttura la possiamo ottenere pensando a una pila di piatti che dopo essere stati lavati sono in attesa di venire asciugati. Questa modalità (stack) è quella che riguarda direttamente il salvataggio dei contesti durante l'attivazione di una routine di servizio che risolve un interrupt.

 

 

Architettura interna dei PIC


La famiglia dei PIC è estremamente vasta ed a ogni esemplare corrisponde una simile ma diversa architettura interna. Nella sostanza il procedimendo di commutazione del contesto non varia ma la differenza sarà la quantità di registri e lo spazio nella stak occupato dal singolo contesto.

L'immagine riassume le cose principali da coinvolgere nella programmazione basata sull'attivazione e test dei segnali di interrupt.

Sul PORTB, colorati di rosso e cerchiati in rosa le principali fonti che possono provenire dall'esterno, ovvero la presenza al PORTB  pin RB0 oppure il cambio di stato sempre nel PORTB ma nei pin da RB4 a RB7, mentre la fonte interna più sfruttata è senzaltro il segnale di interrupt generato dallo scadere (overflow) del timer hardware TMR0, che dovrà essere interpretato e gestito come un contatore di ciclo, dato che non è possibile inserire funzioni all'interno della routine di servizio.

In rosa ho indicato il working regester, ovvero la destinazione di tutti i caricamenti dei dati che devono essere processati, il program counter è evidenziato in alto in giallo,e l'area dello stack pointer evidenziata in azzurro. Tutte informazioni fotografate e salvate perché fondamentali per il corretto ritorno dopo l'esecuzione della routine di servizio ISR (iterrupt service routine).

Visto dall'esterno, il PIC 16F876A mette a disposizine gli interrupt dall'esterno sui pin evidenziati in rosso:

La memoria del PIC 16F876 come quella del modello superiore 877 è mappata, rispetto al vettore di interrupt in questa maniera:

Questa informazione potrà essere marginale se programmiamo in C ma se programmiamo in assembly è fondamentale, difatti dovremmo inizializzare la memoria all'indirizzo 4h con una direttiva "org", dove ha origine il vettore di interrupt. Subito sopra possimao vedere gli otti livelli di stack che corrispondono anche alla quantità di contesti commutabili senza un preventivo rientro dalla routine di servizio dell'eccezione (evento che scatena l'interrupt) che ha lanciato l'interrupt in corso.

Fonti di interrupt

Il segnale di interruzione del task in corso può arrivare sia dell'esterno del micro che dall'interno. Può arrivare ad esempio dalla periferica UART o dal modulo di capture e compare (fonti interne) o dal PORTB, su variazione di stato o su presenza di RB0 (fonti esterne).

Nello specifico caso del processore di riferimento del corso "Let's GO PIC !!!" è il 16F877A o il fratello minore 16F876A di cui si invita a tenere sotto occhio il databook.

scarica il data book -> download 16F87x

Se state seguendo il corso "Let's GO PIC!!!" muniti della Micro-GT versatile IDE potete svolgere le prove con una moltitudine di modelli di processori, ma se usate la Micro-GT mini potrete sfruttare i processori a 28 pin, che comunque non sono pochi e molto potenti. Possiamo installare il PIC16F876A (come noto), oppure i 18F2550 o 18F2580 per i quali vi rimando al sito della MicroChip per il download del PDF.

Nella documentazione troverete quante e quali sono le fonti di interrup dei vari processori, ma per quanto riguarda il microcontrollore di riferimenti della Micro-GT mini, 16F876A, troverete 14 differenti fonti, alcune generate internamente e altre che possono provenire dall'esterno ma che devono giungere in posizioni preassegnate dell'I/O, ad esempio il RB0.

Nelle prime fasi sperimentali useremo solo le fonti più semplici, come ad esempio il segnale al pin RB0 o il cambio di stato in alcuni pin del PORT B.

La prima cosa da comprendere è quindi la necessità di specificare da quale di queste fonti disponibili si vuole accettare il seganle di interrupt, tramite il settaggio software negli apposii registri.

La seconda cosa è che l'attivazione di interrupt comporta l'attivazione di una routine di servizio, che in Ansi C, o nel compilatore Hitech C16 che abitualmente utilizziamo, viene implementata* (significa svolta, realizzata, scritta) come una funzione, ovvero una void che obbilgatoriamente dovrà chiamarsi "interrupt" seguita dal nome custom prescelto dal programmatore.

esempio:

void interrupt led_blink( ){ //inizio routine di servizio

      //corpo della routine di servizio.

     //assolutamente vietato inserire qui dentro altre funzioni.

     //inserire solo comandi direttamente eseguibili.

}//fine routine di servizio

Eventuali temporizzazioni dell'evento svolto dalla routine di interrupt vanno eseguite in due maniere:

  1. nel corpo principale del programma, prima di chimare l'interrup.
  2. tramite operazioni di conteggio delle chiamate del TMR0 all'interno della routine di servizio.

Nel primo caso possiamo usare le "funzioni" delay, dato che si trovano fuori dalla funzione di interrupt, nel secondo caso non si può fare.

Dobbiamo tenere presente che questo microcontrollore ha cinque registri da configurare, ma che non saremo costretti a configurare ogni bit, alcuni li potremmo lasciare nel suo default. 

La terza cosa che ci dovremmo preocupare di verificare è da dove si è sviluppato l'interrupt, ovvero identificarne la fonte e quindi leggere e impostare i bit dei predetti registri. 

Gli interrupt, all'interno del PIC 16F876 sono manipolati dalla logica sottorappresentata che riceve come input dei vari operatori logici, i Flag provenienti dalle 13 fonti di interrupt (la quattordicesima nell'876 non è implementata come nell'877, ovvero la PSPIF) e dai vari registri di configurazione.

 

E' molto evidente che l'ultimo AND lascia passare qulsiasi fonte di interrupt solo se è alto il flag GIE (general interruput enable) che risulta essere il settimo bito del registro INTCON.

 

Della documentazione da me rielaborata, di fonte internet, il registro INTCON risulta essere questo: 

 

Registro INTCON

Bit7

Bit6

Bit5

Bit4

Bit3

Bit2

Bit1

Bit0

 

GIE

PEIE

T0IE

INTE

RBIE

T0IF

INTF

RBIF

 

Il registro INTCON è un area di lettura e scrittura (R/W) in cui è possibile abilitare dei bit abbinati alle varie fonti di interrupt.

INTCONT, anche se non specificato nel databook dovrebbe signigicare  INTurrpt CONtrol.

Ogni bit di flag viene impostato alto quando si verifica la richiesta di interrupt dall'eseterno o da una periferica interna (di quelle in grado di farlo) ma la richiesta di interruzione viene ignorata se non è abilitato il bit GIE, che come già detto è il settimo bit.

In definitiva questo registro è il controllo di ingresso della rete logica vista sopra, difatti i bit da 0 a 6 sono usati per la configurazione e abilitazione gestendo anche le 4 fonti esterne

Ribadiamo che non ci sarà alcun effetto senza la presenza di GIE in settima posizione del registro ovvero il bit 6.

.

R/W-0

R/W-0

R/W-0

R/W-0

R/W-0

R/W-0

R/W-0

R/W-x

 

GIE

PEIE

T0IE

INTE

RBIE

T0IF

INTF

RBIF

 

Bit7

Bit6

Bit5

Bit4

Bit3

Bit2

Bit1

Bit0

R = bit leggibile

W= bit scrivibile

U = bit libero, letto come ‘0’ reset

 

 

bit 7:

GIE: bit Global Interrupt Enable
1 = Abilita il mascheramento (lascia passare) tutti i segnali di interrupt presenti.
0 = Disabilita tutti gli interrupt anche se presenti a 1 nei flag.

bit 6

PEIE: Perifeferal interrupt enable bit 

1 = abilita tutti gli interrupt dalle periferiche non mascherate
0 = disabilita tutti gli interrupt dalle periferiche

 

bit 5

T0IE: TMR0 abilita interrupt che segnala Overflow  del Timer0

1 = abilita interrupt TMR0
0 = Disabilita interrupt TMR0

 

bit 4:

INTE: RB0/INT abilita Interrupt sul pin del PIC
1 = abilita interrupt  RB0/INT
0 = disabilita interrupt  RB0/INT 
 

bit 3:

RBIE: RB bit di abilitazione interrupt sul cambio di stato sul PORTB

1 = abilita su cambio di stato del PORT RB
0 = disabilita sul cambio di stato del PORT RB

 

bit 2:

T0IF: interrupt su tempo scaduto di TMR0 (overflow interrupt flag bit)
1 = Se posto a 1 il TMR0 è scaduto e deve essere ripristinato a zero via software
0 = se TMR0 non è scaduto

 

bit 1:

INTF: RB0/INT Interrupt Flag bit
1 = alto significa che c'è richiesta di interrupt su RB0/INT 

0 = basso significa che non c'è richiesta di interrupt  RB0/INT 

 

BIT 0:

RBIF: RB alza il falg se c'è cambio di stato su uno dei bit a PORTB

1 = Quando almeno un bit  tra RB7 e RB4 cambia stato (deve essere resettato via software)
0 = nessuno dei pin tra RB7 e RB4 ha cambiato stato

  

Notiamo che alcuni flag sono indicati con "if" e altri con "E", con ovvio significato di IF->testa la condizione ovvero se si è verificato o meno l'interrup in questione, mentre "E" enable, abilita il suddetto interrupt a lanciare la routine di servizio, ovviamente se presente anche GIE.

Tra le fonti di interrupt figurano anche quelle collegate ai convertitori analogici digitali, d'ora in poi abbreviati A/D.

Quando il corventitore A/D ha eseguito la conversione, il risultato è caricato nei registri  ADRESH e ADRESL.

Il bit GO/DONE (ADCON0 bit2)viene resettao e il flag di interrupt ADIF viene posto a 1. Gli altri bit coinvolti  con funzioni di impostazione dell'interrupt per la conversione AD sono ADIE, che si pone a 1 all'inizio della conversione, PEIE va posto a 1 e ovvimente il GIE.

I flag relativi alle interruzioni provenienti dalle periferiche sono contenuti nei registri di funzione speciale PIR1 e PIR2 i cui corrispondenti bit di enable (abilitazione) sono contenuti nei registri di funzione speciale PIE1 e PIE2.

Durante la "risposta" a un interrupt , ovvero durante l'esecuzione dell'ISR, il flag GIE viene azzerato con l'effetto di impedire in questo momento il lancio del servizio di un altro interrupt.

Rispondento all'interrupt avviene come prima cosa il salvataggio dell'indirizzo di ritorno dall'ISR con un pusch (o caricamento in testa) nell'area di stack, e il program counter viene caricato con l'indirizzo 4h in cui è allocato il vettore di interrupt.

Una volta all'interno dell'ISR, viene testato quale degli interrupt flags è alto ovvero quale fonte ha causato l'eccezzione.

Tale falg, una volta identificato va resettato come ultima istruzione all'interno della routine di servizio, al fine di non rientrarci per ricorsione e non per la vera presenza di una eccezzione che chiede di essere servita con priorità.

Il nostro programma di test.

 scarica il programma di test degli interrupt e modificalo a seconda delle tue esigenze -> download interrupt test.


#include <pic.h>
//Libreria standard dei PIC che permette di definire i registri TRIS, PORT e altre funzioni base
#include <htc.h>  //Libreria specifica di hitech che permette le inline delay e di definire la funzione interrupt

#define _XTAL_FREQ 4000000   // frequenza quarzo, molto importante se si usano porzioni di esso nei prescaler
#define led RB5    //definizione di etichette mnemoniche, per comodità di lettura, ai pin del PIC
#define led1 RB6
#define led2 RB7

void configura();
//predichiarazione della funzione di settaggio dei registri da modificare internamente a seconda dei casi 

void main(){ //inizio programma principale
    led1=1; led2=0; //condizione iniziale dell'oscillazione astabile dei due led 
    configura(); //chiama la funzione di settaggio registri, il corpo si trova sotto il main, funziona perchè predidichiarata
            
    while(1){ //ciclo infinito, non ha senso ripetere i settaggi a ogni ripetizione del programma
                
             led1=!led1; 
//inverte lo stato del led1
            __delay_ms(49);__delay_ms(49);    __delay_ms(49);__delay_ms(49);
            led2=!led2;
//inverte lo stato del led2
            __delay_ms(49);__delay_ms(49);    __delay_ms(49);__delay_ms(49);
       
          
    }
// fine del while
// fine del main

void interrupt TIS(){
//funzione interrupt che contiene la routine di servizio

      if(INTF==0x01){  //test del flag dell'interrupt

         INTF=0x00; //se siamo entrati qui allora il flag ha già fatto la sua funzione e lo devo resettare per prossimo uso
         led=!led; //inversione dello stato del led che si attiva su interrup esterno presente su RB0

        }
                     
 }

void configura(){ 
//funzione che imposta tutti i registri prima di cominciare l'esecuzione del programma

                 TRISA=0x00; PORTA=0x00;
                 TRISB=0b00000001;
//PORTB=0x00; 
                 TRISC=0x00; PORTC=0x00;
                 
                 TMR0=0x00;
//abilita e resetta il Timer hardware TMR0
                 INTCON=0b10000000; // INTurrpt CONtrol register
                 PIE1=0x00;
                 PIE2=0x00;
                 PIR1=0x00;
                 PIR2=0x00;
                 OPTION_REG=0b10000111;
                 GIE=0x01;  
//impostiamo a 1 il flag del general interrupt enable
                 //T0IE=0x01;
                 INTE=0X01;
//interrupt enable abilitato
                 RBIE=0x00; //abilita interrupt sul cambio di stato del PORTB
                 //T0CS=0x00;
                 //PSA=0x00;
                 //PS2=0x01;
                 //PS1=0x01;
                 //PS0=0x01;

}

Fase di sperimentazione e collaudo


http://www.youtube.com/watch?feature=player_profilepage

http://youtu.be/0l59zQtAbSQ

 

Cenni a un prossimo esempio. 

In un prossimo episodio di "Let's GO PIC!!!" vedremo come creare un segnale PWM utilizzando il TMR0 e il segnale di interrupt genarato dal suo overflow.

Accenno che il TMR0 è un dispositivo hardware che si incrementa automaticamente e che funge più da contatore che da reale timer.

Sarà appunto contando le sue attivazioni che ne otteniamo un tempo, generalmente come multiplo di una frazione del clock che giunge ad overflow in 255 iterazioni.

All'overflow viene generato l'interrupt che sfrutteremo o per l'incremento di un secondo contatore o solo per il suo reset.

L'incremento del TMR0 genera una rampa che potremmo intercettare con i comparatori integrati.

Spostando la soglia di comparazione andiamo ad agire sul duty cycle del segnale PWM di cui staimo tenedo costante la frequenza ad esempio a 22khz.

La soglia di comparazione la potremmo ottenere da un segnale analogico proveniente da un potenziometro conesso al pin 2 ovvero AN0.

Ecco cosa otteremo:

          

Per la soluzione di questo esercizio basato sull'interrupt su overflow del TMR0 vi rimando a una specifica puntata del corso.

Sperando di essere riuscito a trasmettere con chiarezza questa introduzione all'uso degli interrupt auguro a tutti buon divertimento

 

I circuiti stampati utilizzati nel corso “Let’s GO PIC !!!”  sono disponibili, Micro-GT IDE, Micro-GT mini, mini shield, ecc ecc.

Chi fosse interessato mi contatti alla mail   ad.noctis@gmail.com

 

This opera "Let's GO PIC!!! cap 6" is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Italy License

 

   Pagina precedente