[Precedente] [Indice principale]
[Multitasking] [Sistema di debug] [Adattatore TTL --> EIA "in cable"]
MULTITASKING
Multitasking
significa avere in esecuzione su un unico micro piu' programmi contemporaneamente. Ogni
programma svolge una particolare funzione di controllo e puo' essere chiamato
processo o task. I task possono essere indipendenti tra loro oppure svolgere
un compito di gruppo scambiandosi informazioni attraverso celle di memoria.
Naturalmente il parallelismo e' solo apparente, in quanto in realta' il
micro porta avanti singolarmente i vari programmi un'istruzione per volta,
ma se questo meccanismo e' sufficientemente rapido si ottiene l'apparenza
(e di fatto l'esistenza) di piu' programmi contemporanei in esecuzione.
L'uso del multitasking permette di semplificare enormemente la scrittura
di programmi complessi che devono tenere sotto controllo molti processi
nello stesso tempo.
Il multitasking puo' essere di tipo preempitive
o non preempitive. Nel primo caso i programmi sono scritti nel modo consueto
(come se fossero l'unico programma presente nel micro) ed e' compito dello
scheduler del sistema operativo avviarli e interromperli a intervalli di
tempo prestabiliti, questa soluzione pero' e' molto complessa e sicuramente
non realizzabile con un piccolo microcontroller come il PIC 16F84.
Il modo non preempitive invece prevede
uno scheduler formato da un semplicissimo loop senza fine che richiama
ciclicamente tutti i task come come normali subroutines. E' compito di
ogni task usare meno tempo possibile e ritornare subito il controllo allo
scheduler. Per fare questo i programmi vanno scritti sotto forma di macchine
a stati finiti. Le macchine a stati finiti (FSM) sono rappresentabili da diversi
stati in cui possono venirsi a trovare, e ogni stato stato rappresenta una condizione
di attesa di un qualche evento.
Quando quell'evento si verifica si possono
eseguire delle operazioni, ed effettuare la transizione verso un altro stato
(che verrà eseguito alla successiva chiamata del task). |
|
Una macchina a stati finiti
e' composta da una serie di stati in cui puo' permanere finche' non si
verifica un evento o una condizione. Quando questa si verifica avviene
una transizione ad un altro stato. Dal punto di vista programmativo ogni
stato e' una subroutine che viene chiamata solo se corrisponde allo stato
attivo del momento. Ogni task deve quindi disporre almeno di una variabile
di stato, e quando viene chiamato deve eseguire la specifica routine interna.
IF STATO=0 THEN
Il controllo di una macchina a stati finiti si effettua quindi cambiando il valore della variabile di stato all' interno delle singole routines (stati) , in questo modo alla successiva chiamata del task verra' eseguito il nuovo stato. |
In assembler possiamo usare l'istruzione addwf applicata al program counter per costruire l'equivalente di un "on n goto" del BASIC, o un "case" del Pascal, o uno "switch" del C, e un generico scheletro di task puo' essere il seguente:
;------------------------------------------------------------- task1 movf stat,w ;carica in w la variabile stat addwf PCL,f ;somma al program counter goto routine0 ;goto eseguito se stat=0 goto routine1 ;goto eseguito se stat=1 goto routine2 ;goto eseguito se stat=2 routine0 ..... ..... return routine1 ..... ..... return routine2 ..... ..... return ;-------------------------------------------------------------
ATTENZIONE: usando
l'istruzione addwf PCL (detta anche computed goto cioè salto calcolato)
e' semplice creare delle strutture "on n goto", pero'
bisogna stare molto attenti all'impostazione del PCLATH e ai bordi delle
pagine di programma da 256 byte ciascuna come gia' visto nel paragrafo
sulle lookup tables. In pratica
nessuna parte del task deve essere posta ad indirizzi che si trovano a
cavallo tra due pagine. E'percio' meglio ricorrere al controllo numerico
del valore della variabile di stato come mostrato piu' avanti.
Un modo per risolvere il problema delle
pagine è quello di impostare correttamente il registro PCLATH, che
contiene i bit più significativi dell'indirizzo di memoria programma
quando vengono effettuate operazioni che coinvolgono direttamente il registro
PCL. Nell'esempio seguente si usano le direttive per il compilatore HIGH e LOW
per indicare la parte più alta e più bassa dell'indirizzo della
memoria programma a cui iniziano le istruzioni di salto. A questo indirizzo viene
sommato lo stato corrente aggiustando il PCLATH se la parte bassa dell'indirizzo
supera il valore 255:
;------------------------------------------------------------- task1 movlw HIGH Ttask1 ;carica in w parte alta indirizzo Ttask1 movwf PCLATH ;la mette nel PCLATH movlw LOW Ttask1 ;carica in w parte bassa indirizzo Ttask1 addwf stat,w ;la somma allo stato btfsc STATUS,C ;se non overflow skip incf PCLATH,F ;altrimenti incrementa PCLATH movwf PCL ;mette parte bassa indirizzo nel program counter Ttask1 goto routine0 ;goto eseguito se stat=0 goto routine1 ;goto eseguito se stat=1 goto routine2 ;goto eseguito se stat=2 routine0 ..... ..... return routine1 ..... ..... return routine2 ..... ..... return ;-------------------------------------------------------------
Prima di iniziare a richiamare i processi e' naturalmente indispensabile inizializzare le loro variabili di stato ed eventualmente impostare dei parametri di partenza se l'applicazione lo richiede, in generale per predisporre un sistema multitasking su un PIC si dovrebbero mettere in sequenza le seguenti operazioni:
1) IMPOSTAZIONE
PORTE DI I/O E ALTRE PERIFERICHE
2) IMPOSTAZIONE
VARIABILI DI STATO E ALTRI PARAMETRI
3) LOOP SENZA
FINE CHE CHIAMA I TASK
L'uso di questo sistema non preempitive permette inoltre di regolare con assoluta precisione il tempo di avanzamento dei task. Infatti, se si sincronizza il loop dello scheduler con il timer interno del PIC, si possono avviare i processi ad intervalli di tempo ben definiti, per esempio ogni 4,096 ms. A questo punto sappiamo che tra una chiamata e l'altra di un task passera' esattamente questo tempo, e questo rende semplice realizzare processi di temporizzazione e controllo durate. I vincoli naturalmente sono da un lato che la somma dei tempi di esecuzione di tutti i task non deve essere mai superiore al ciclo dello scheduler, e dall'altro che i processi non possono gestire segnali o eventi piu' veloci di questo ciclo. Avere processi che avanzano ogni 4,096 ms significa poter realizzare controlli in tempo reale che si aggiornano circa 244 volte al secondo.
Nota: Il motivo per cui ho scritto 4,096 ms e non 4 e' che il timer interno puo' dividere il clock per un numero limitato e fisso di valori, e 4,096 ms e' uno di quelli ottenibili con un normale quarzo da 4 Mhz.
L'esempio seguente indica come predisporre
il timer0 di un PIC16F84 per far avanzare i task di un passo ogni 8,192
ms esatti:
;------------------------------------------------------------------------------------ ; DEFINIZIONI ;------------------------------------------------------------------------------------ PROCESSOR 16F84a ;clock 4 Mhz RADIX DEC INCLUDE "P16F84a.INC" __CONFIG 11111111110001b #define Bank0 bcf STATUS,RP0 #define Bank1 bsf STATUS,RP0 ORG 0CH stat RES 1 ;variabile di stato per task1 ;------------------------------------------------------------------------------------ ; INIZIALIZZAZIONE ;------------------------------------------------------------------------------------ ORG 0 ;---------------PREDISPOSIZIONE TIMER Bank1 bcf OPTION_REG,PSA ;prescaler assegnato a tmr0 bcf OPTION_REG,T0CS ;conteggio da clock interno movlw 11111100b andwf OPTION_REG,f ;divisione prescaler per 32 ;---------------INIZIALIZZAZIONE VARIABILI Bank0 clrf stat ;Azzera variabile stato per task1 ;---------------AVVIA CONTEGGIO clrf TMR0 ;azzera contatore del timer bcf INTCON,T0IF ;azzera bit overflow del timer ;------------------------------------------------------------------------------------ ; LOOP PRINCIPALE CHE CHIAMA I PROCESSI OGNI 8,192 ms ;------------------------------------------------------------------------------------ mainloop btfss INTCON,T0IF ;test se overflow timer (viene settato T0IF) goto mainloop ;se no attendi bcf INTCON,T0IF ;altrimenti riazzera bit timer call task1 ;e chiama il task 1 goto mainloop ;poi torna ad attendere il timer ;------------------------------------------------------------------------------------
Come si diceva prima, per evitare ogni
problema di di indirizzamento in cui si puo' incorrere modificando direttamente
il program counter (PCL), si può realizzare la struttura del task in un
modo un po' piu' complesso, e cioe' facendo dei confronti numerici con
il valore della variabile di stato:
;------------------------------------------------------------- task1 movf stat,f btfsc STATUS,Z goto routine0 movlw 1 subwf stat,w btfsc STATUS,Z goto routine1 movlw 2 subwf stat,w btfsc STATUS,Z goto routine2 goto routine3 routine0 ... ... return routine1 ... ... return routine2 ... ... return routine3 ... ... return ;-------------------------------------------------------------
Usando intensivamente le strutture task,
si si puo' anche ricorrere ad una macroistruzione di confronto e salto
in modo da ridurre al minimo le istruzioni da scrivere per realizzare
il selettore delle routines, di seguito la macro e il selettore dell'esempio
precedente riscritto (che da 12 righe di programma diventa lungo 4!);
Cpje macro var,val,addr
movlw val
subwf var,w
btfsc STATUS,Z
goto addr
endm
task1 Cpje stat,0,routine0 ;confronta stat con 0 e jump a routine0 se uguale
Cpje stat,1,routine1
Cpje stat,2,routine2
Cpje stat,3,routine3
...
Come in tutti i sistemi multitasking bisogna
fare un po' di attenzione a non creare conflitti tra i processi. Un esempio
tipico e' la scrittura su porta di uscita o in memoria. Se due processi
vogliono scrivere nello stesso indirizzo la scrittura dell'uno potrebbe
annullare quella dell'altro causando effetti indesiderati difficili da
scoprire. Una soluzione puo' essere quella di usare un solo "processo scrittore"
incaricato di questa funzione che prende i dati inviati dagli altri processi
unendoli senza creare conflitti. In generale e'
bene che ogni processo "tocchi" solo quello che gli compete,
lasciando del tutto inalterato il resto del sistema, come se non fosse
mai stato eseguito.
SISTEMA
DI DEBUG
Per
mettere in pratica tutto quello che si e' detto riguardo a trasmissione
seriale e multitasking si puo' creare un task di debug che trasmette ciclicamente
4 byte di dati verso un PC. I 4 valori da trasmettere li possiamo prendere
da 4 celle di memoria adibite a celle di debug (chiamate per esempio dbg1
dbg2 dbg3 dbg4). Il protocollo puo' consistere in un byte di allineamento
(sincronismo) seguito dai 4 byte dati inviati nel consueto formato start/stop
9600,N,8,2 e da un carattere di checksum che e' la somma a 8 bit di tutti
i precedenti. Sul PC un programma apposito deve ricevere questi byte e
visualizzarli in formato binario, esadecimale e decimale. A questo punto
si ha a disposizione un metodo semplice per visualizzare i valori interni
al programma del PIC, infatti basta scrivere nelle celle dbg1..4 i valori
per vederli trasferire immediatamente sul video, e questo puo' servire
per verificare lo stato delle porte di ingresso, o il risultato di un'operazione
aritmetico/logica, o l'evoluzione dello stato di un task al verificarsi
degli eventi previsti. Per sperimentare il multitasking possiamo anche
far contemporaneamente lampeggiare un led, per esempio collegato sul pin
RA0.
Durante il debug deve essere usato un altro pin configurato come uscita, che va collegato ad un adattatore TTL/EIA per essere compatbile con la seriale del PC. Il programma di ricezione su PC contolla continuamente la congruenza dei dati in arrivo, se ci sono errori segnala sulla barra del titolo "NO SYNC OR FRAME ERROR", o se per 200 ms non riceve dati segnala "NO SIGNAL".
[Download
visualizzatore picdebug.zip (87K)]
(Scompattarlo e lanciare
picdebug.exe, riceve solo da COM1)
Il programma
del PIC: dbgdemo.asm
Include per pic16f84a: P16F84a.INC Include powerasm: pwrasm.inc L'eseguibile: DBGDEMO.HEX |
ADATTATORE TTL --> EIA "IN CABLE"
[Precedente]
[Indice principale]
|
|