Introduzione alla programmazione di microcontrollori (Terza parte)
Giuseppe Dal Cero   dcg@dei.unipd.it
[Prima parte] [Seconda parte] [Quarta parte]

Continuiamo l’esplorazione della programmazione in C per il PIC e facciamo conoscenza con due tecniche importantissime nel campo della programmazione di microcontrollori e dei sistemi embedded in generale.

LE INTERRUZIONI

Gli interrupt sono, come dice il nome, delle interruzioni al normale flusso del programma, che viene sospeso per passare ad eseguire la ISR (Interrupt Service Routine); al termine di questa riprende l’esecuzione del programma nel punto in cui era stato interrotto. Grazie a questa potente tecnica l’unità elaborativa può continuare ad elaborare dati mentre i dispositivi periferici svolgono la loro funzione, al termine della quale "comunicano" all’unità centrale di aver svolto il loro lavoro.

Quello che il PIC fa è di testare ad ogni ciclo di istruzione lo stato delle possibili sorgenti di interrupt e se c’è una richiesta di interruzione pendente salvare il Program Counter (il registro che contiene l’indirizzo della istruzione da eseguire) sullo stack, quindi caricare il PC con l’indirizzo del vettore di interrupt. Quando la ISR termina dallo stack viene prelevato l’indirizzo dell’istruzione che il PIC stava eseguendo all’arrivo dell’interruzione e l’esecuzione continua.

Come sappiamo spesso nei sistemi embedded è necessario che al verificarsi di un evento esterno consegua l’esecuzione di una serie di istruzioni entro un determinato tempo massimo (cioè in real-time), perciò se si usano gli interrupt è bene controllare che siano verificati i vincoli temporali previsti dall’applicazione. In particolare il passaggio dal programma principale alla ISR e viceversa richiede un certo tempo per eseguire le istruzioni di prologo ed epilogo (salvataggio delle condizioni iniziali e successivo ripristino). Nei microcontrollori come il PIC in genere non ci sono problemi particolari (se serve una alta velocità al limite si scrive la ISR in assembly al posto che in C) ma nei PC sotto Windows è impossibile scrivere applicazioni in real-time, mentre è abbastanza difficile sotto DOS. L’efficienza di un sistema di interrupt, quindi, dipende dal tempo impiegato per il context-switching. Il PIC è abbastanza veloce: 3 cicli per iniziare ad eseguire la ISR, ma poi bisogna spenderne almeno altri 2 per salvare lo status register o il Working Register. Sotto Windows, invece, non si può fare alcuna stima (per questo è un sistema non-realtime) ma in ogni caso l’architettura degli 80x86 penalizza parecchio la gestione degli interrupt, soprattutto in modalità protetta.

Per poter usare gli interrupt bisogna innanzitutto abilitarli tramite la modifica del registro INTCON. Vediamo l’assegnazione dei bit in questo registro:

bit 7: GIE (Global Interrupt Enable) se a 1 abilita gli interrupt non mascherati

bit 6: EEIE abilita l’interrupt di fine ciclo scrittura EEPROM se a 1

bit 5: T0IE abilita interrupt di overflow timer 0 se a 1

bit 4: INTE abilita l’interrupt sul pin PB0 se a 1

bit 3: RBIE abilita l’interrupt al cambio di stato sulla porta B se a 1

bit 2: T0IF flag: se a 1 il timer è andato in overflow. Deve essere resettato a 0 via software dalla ISR.

bit 1: INTF flag: se a 1 c’è una richiesta di interruzione dal pin PB0

bit 0: RBIF flag: se è a 1 è cambiato lo stato di uno tra i pin PB4-PB7. Deve essere poi riportato a 0 via software.

Bisognerà quindi settare il bit GIE e poi quello relativo alla sorgente di interrupt da abilitare ed infine scrivere una ISR.

Con l’Hitech-C basta dichiarare una funzione come interrupt perché questa diventi una ISR.

Vediamo un semplice esempio: luci sequenziali ad un solo senso.

#include <pic1684.h>

#define NLED 4

void inizializza();

int n;

int val;

void inizializza()

{

TRISB=0; /* porta B: tutti i pin come Output */

PORTB=0;

OPTION=0x84; /* setta il periodo del timer ad 1 sec. se f=6.7Khz */

TMR0=38;

GIE=1; /* abilita gli interrupt */

T0IE=1; /* abilita l'interrupt del timer */

}

void interrupt isr() /* ISR */

{

/* save_context;*/

if (T0IF)

{

T0IF=0; /* resetta il flag interrupt del timer */

PORTB=val;

val<<=1;

n--;

if(n==0){

n=NLED;

val=1;

}

TMR0=38; /* ricarica il timer */

}

/*restore_context;*/

}

main(void)

{

val=1;

n=NLED;

inizializza();

while(1); /* il programma puo' fare qualsiasi altra cosa */

}

 

Come si vede con l’Hitech-C non è difficile scrivere ISR, dal momento che il compilatore genera automaticamente il codice per il salvataggio ed il ripristino del contesto. Con l’MPLAB-C, invece, bisognerebbe farlo a mano chiamando la macro save_context definita nel file di inclusione int16c2k.h. Questa macro è un pezzo di codice assembly in-line che salva il Working Register e lo Status Register, in modo che alla fine della ISR si possa con restore_context tornare al programma principale e trovare l’ALU(unità aritmetico-logica) esattamente nello stesso stato. Questa implementazione è automatica nel caso dell’Hitech-C, ma alla fine il codice generato è simile: se non si salvasse il contesto il programma principale non funzionerebbe poiché capiterebbe sicuramente una interruzione durante una fase cruciale, come una somma od un confronto, che richiedono la coerenza del Working Register o dello Status Register in una successione di due o più istruzioni per dare un risultato corretto.

Poiché nel PIC c’è un solo vettore di interrupt e quindi una unica ISR essa deve testare gli interrupt flag delle periferiche che intende servire e poi eventualmente richiamare delle sobroutine che gestiscano i vari dispositivi richiedenti l’interruzione. Questa tecnica di gestione delle interruzioni è detta del polling poiché si testa ad uno ad uno i flag relativi a diversi dispositivi finché non si trova quello che ha richiesto il servizio. Nel nostro caso c’è solo un dispositivo con le interruzioni abilitate (il timer) ma ce ne potrebbero essere stati altri 3 come abbiamo visto.

Il supporto alle interruzioni, la presenza di periferiche di tipo timer in grado di richiedere interruzioni e la possibilità di manipolare lo stack sono requisiti indispensabili per il funzionamento di un sistema multitasking. Il PIC, come sappiamo, non supporta i sistemi multitasking a causa del suo stack non manipolabile, tuttavia soddisfa agli altri 2 requisiti e questo rende possibili varie tecniche di esecuzione contemporanea di più funzioni.

La maniera più’ semplice è quella di associare una funzione al programma principale e le altre a interruzioni, timer o cambio di stato sulla porta B. Si possono creare anche soluzioni più complesse spezzando gli algoritmi in piccoli pezzi autonomi alla cui fine al posto di eseguire il prossimo della stessa funzione si passa ad una altra funzione. Questo è in pratica lo schema di un semplice multitasking cooperativo o non-preemptive (alla Windows 3.x per intenderci). Il multitasking normale si dice anche preemptive e mentre sui personal computer è approdato solo nell’85 con il Commodore Amiga 1000 e per il grande pubblico solo nel ’95 con Windows 95 nel campo dei sistemi embedded è la norma da ormai 20 anni. In effetti non mi risulta che ci siano altri microcontrollori ad 8 bit che non supportino un multitasking preemptive oltre al PIC: ironia della sorte adesso che si è diffusa così tanto nel campo PC la moda del preemptive nel campo dei sistemi embedded per progettare sistemi complessi sul PIC si torna al cooperativo!

LE INTERRUZIONI

Il multiplexing è una tecnica molto usata in campo tecnico-ingegneristico: il problema che risolve è quello della piccola disponibilità di canali fisici per trasmettere informazioni. Consiste nella combinazione di due o più segnali per trasmettere le loro informazioni su un numero minore di linee di trasmissione. Di solito si tratta di una operazione di scambio temporale di segnali su una stessa linea fisica (per esempio la connessione alla rete del vostro provider è una sola e su di essa transitano in momenti diversi dati e richieste provenienti da vari utenti) ma si parla di multiplexing anche per combinazioni come le codifiche (se so che ho 16 output digitali e che in un dato momento solo uno di essi può essere a 1 al posto di portarmi in giro per il circuito 16 linee me ne porto 4 che riassumono completamente l’informazione originale) oppure ancora per il mixing di segnali con diverse frequenze (il cavo dell’antenna TV porta le informazioni per tante emittenti). Nella programmazione di microcontrollori il multiplexing si usa in particolare per interfacciarsi a tastiere o display, ma anche per scambiare segnali su bus paralleli o dati in varie codifiche.

Nel caso del pilotaggio di un display con 4 moduli da 7 led, per esempio, servirebbero 7x4=28 pin di output digitale per controllarlo con un microcontrollore, invece grazie al multiplexing ne bastano 7+4=11. Come si vede basta quindi un PIC16x84, con pochi pin e ridotto ingombro. Ma come è possibile? Ovviamente la riduzione del numero di linee ha un costo in termini di informazioni che si possono trasmettere, perciò per ogni campo di applicazione della tecnica del multiplexing bisogna studiare la soluzione che riduce al minimo gli svantaggi.

Nel caso dei display si sfruttano 2 cose: la persistenza di un’immagine sulla retina dell’occhio e l’inerzia dei led.

Quello che si fa in pratica è di trasmettere per pochi millisecondi lo stato dei 7 led ad un modulo attivato tramite un solo pin, poi attivare con un altro pin un altro modulo e usare le stesse 7 linee di prima per controllarlo. Se i moduli sono 4 e si accende ognuno di loro solo per 5 ms ogni 20 ms (50Hz) si reinizia il ciclo.

Nel caso di una tastiera a matrice 4x4 al posto di 16 pin ne bastano, come ormai avete indovinato, 8. Anche questa volta si tratta di accendere qualcosa con cui scambiare informazioni mentre tutto il resto è spento. In particolare si organizza la tastiera in 4 righe e 4 colonne. Le colonne sono collegate ad altrettanti pin configurati come output di un microcontrollore e sono quelle che andranno accese singolarmente, mentre le righe sono collegate a 4 pin di input.

I pulsanti sulle righe sono tenuti a livello alto da resistenza di pull-up e le colonne vengono accese imponendo un livello basso, così se non c’è alcun pulsante premuto si leggono tutti 1, mentre se un pulsante è premuto il corrispondente bit viene letto come 0.

Il particolare circuito impone di fare attenzione nella stesura della routine di servizio: prima di passare ad una nuova colonna bisogna scaricare i pin di input portandoli a 0 Volt per non creare disturbi, inoltre bisogna implementare una routine anti rimbalzo.

LA TECNICA DEL PWM

Il PWM (Pulse Widht Modulation, modulazione a larghezza d’impulso) è un sistema di modulazione ad impulsi la cui durata viene variata in funzione del segnale da trasmettere. Con questa tecnica è possibile generare dei suoni collegando un altoparlante ad un pin che venga ciclicamente azzerato ed alzato con la frequenza della nota che si vuole suonare, oppure modulare la velocità di un motore o la luminosità di un led variando il tempo in cui il pin resta alto rispetto a quello in cui resta basso (cioè il duty-cycle). La tecnica del PWM viene utilizzata nel campo dei microcontrollori per creare tramite segnali digitali (1-0 del pin) segnali analogici (un suono, una luce più o meno intensa), tuttavia si potrebbe usare anche come codifica in una linea di trasmissione, per esempio un 1-0-0 potrebbe rappresentare uno 0 ed un 1-1-0 un 1.

Nel caso in cui lo scopo sia quello di creare un segnale analogico non periodico si mette un filtro in cascata al pin; la natura di tale filtro dipende dalla frequenza con cui si svolge il PWM e dalle caratteristiche del dispositivo da controllare.

Lo schema elettrico di un mini-progetto per far variare la luminosità di un led con la tecnica del PWM. La modulazione si può variare con i 2 pulsanti collegati ai primi 2 pin della porta B. Il principio di funzionamento è semplicissimo: dato un tempo base tb e un periodo di PWM m x tb si mette a 1 un pin per un periodo n x tb e a 0 per (m-n)x tb. Di solito m è una potenza di 2 e l’esponente da dare a 2 per ottenere m rappresenta la risoluzione o il numero di bit della PWM. Ecco una possibile realizzazione:

/* PWM per luminosita' led */

#include <pic1684.h>

void aspetta(unsigned int nvol);

void inizializza();

void getkey();

void inizializza()

{

INTCON=0; /* disabilita gli IRQ */

PORTB=0;

TRISB=0xff; /* porta B: tutti i pin come input */

OPTION=0; /* abilita resistenze di pull-up sulla porta B */

TRISA=0; /* porta A: tutti i pin come output */

PORTA=0;

}

void aspetta(unsigned int nvol) /* Con una frequenza di 6.7Khz */

{ /* aspetta circa nvol x 4 ms */

unsigned int i;

unsigned int ib;

for(i=0;i!=nvol;i++) {

for (ib=0;ib!=1;ib++);

}

}

unsigned int n1,n2; /* valori di attesa per PWM */

unsigned int key; /* stato della porta B */

unsigned int key1,key2,kp1,kp2; /* variabili per filtro anti-rimbalzo */

void main() {

unsigned int dummy;

n1=8; /* PWM a 4 bit */

n2=15-n1;

key1=0;

key2=0;

kp1=0;

kp2=0;

inizializza();

do {

PORTA=0x0f; /* ciclo PWM */

aspetta(n1); /* con f.lavoro=6.7 Khz si raggiunge */

dummy=PORTB&key; /* una frequenza di PWM di 15Hz circa */

if (dummy!=0xff)

getkey();

PORTA=0;

aspetta(n2);

} while(1);

}

void getkey() { /* calibrata per funzionare con questo prg */

key=PORTB; /* ossia chiamata ogni 66ms con f=6.7Khz */

if ((key&1)==0) { /* se sembra premuto il tasto su PB0 */

if (kp1) { /* e se sembrava già premuto 66ms fa */

key1++; /* incrementa il contatore del filtro */

if (key1==15) { /* se sono 66x16 ms che il tasto sembra premuto */

key1=0; /* allora vuol dire che è effettivamente premuto */

if (n1!=0) /* e diminuisce la luce */

n1--;

else n1=15;

n2=15-n1;

}

}

else kp1=1; /* anche se non sembrava premuto 66 ms fa ora lo sembra! */

}

else { /* se non sembra premuto */

kp1=0; /* non e' premuto (non gestisce il rilascio) */

key1=0; /* resetta il contatore del filtro */

}

if ((key&2)==0) {

if (kp2) {

key2++;

if (key2==15) {

key2=0;

n1++;

n1=n1&0x0f;

n2=15-n1;

}

}

else kp2=1;

}

else {

kp2=0;

key2=0;

}

}

Chi proverà ad costruirsi questo progettino tale e quale noterà un fastidiosissimo sfarfallio alle alte luminosità e a quelle inferiori addirittura la percezione del lampeggio. Inoltre qundo si preme un tasto la luminosità auementa poiché la gestione della tastiera aumenta il tempo in cui il segnale è a livello alto. Con una frequenza di lavoro di soli 6.7 Khz non si può far molto di meglio con una PWM a 4 bit (16 livelli di luminosità): il PIC non ha un attimo libero. Si potrebbe certamente far di meglio in assembly o anche solo usando il timer per misurare il tempo e gli interrupt per la gestione dei tasti, ma difficilmente si riuscirebbe a fare un salto di un ordine di grandezza. Infatti il tempo base minimo è quello di esecuzione del ciclo di ritardo una sola volta e supponendo di averlo scritto in assembly sono almeno 2 cicli. Con una risoluzione di 4 bit il ciclo di PWM è quindi di ((2^4)x2)+14 cicli (il 10 finale è per il GOTO per ripetere il ciclo e per le impostazioni delle variabili e del pin)= 46 cicli. Con una frequenza di lavoro di 6.7Khz si arriva quindi a 145 Hz circa. Anche in assembly comunque resterebbe il problema della gestione della tastiera che sfalsa la PWM, a meno di non usare il timer per aspettare, ma allora servirebbe un tempo base di almeno 20 cicli e la frequenza massima diventerebbe di circa 20 Hz, anche se finalmente stabile e con una buona linearità (cioè segnale direttamente proporzionale al duty cycle su tutta la scala). Questo significa che il massimo a cui si può arrivare con un PIC 16x84 con quarzo da 10Mhz è circa 10Mhz/(4x20x16)=~8Khz per una buona PWM a 4 bit e circa 500 Hz per una buona PWM a 8 bit (ricordo che per conoscere la frequenza di esecuzione delle istruzioni basta dividere la frequenza di oscillazione per 4).

In effetti una PWM via software è molto onerosa e per questo molti microcontrollori hanno dispositivi che la implementano in hardware. Di solito si tratta di speciali timer con 2 registri in cui si impostano due valori di timeout; il timer viene ricaricato automaticamente una volta al raggiungimento del valore di un registro e la successiva al raggiungimento del valore dell’altro registro.

BIBLIOGRAFIA:

S. Congiu, "Calcolatori Elettronici", Pàtron editore, 1995

Stan D’Souza, Microchip Application Note 529: Multiplexing LED Drive and 4x4 Keypad Sampling, 1994

Dan Matthews, Microchip Application Note 590: A Clock Design Using the PIC16C54 for LED Displays and Switch Inputs, 1994

Download dei listati