Il codice per PIC 16F84A – Routines di supporto
Come anticipato, il codice assembly riportato nel seguito di questo articolo fa riferimento all’uso di un microcontrollore Microchip della famiglia PIC16F e in particolare suppone di usare l’hardware di un PIC16F84A ma può essere facilmente adattato senza troppe modifiche a quasi tutti i PIC della stessa famiglia.
Questo codice non è interamente farina del mio sacco ma è copiato da un programma che ho trovato in internet da una tesina universitaria. Purtroppo non ricordo chi fossero gli autori ma li ringrazio sentitamente per avermi risparmiato un bel po’ di lavoro.
Visto che lo scopo di questo articolo è essenzialmente didattico, commenterò passo passo le istruzioni del programma, ma darò comunque per scontata una certa conoscenza dei PIC, come si programmano e i fondamenti del loro linguaggio assembly.
Visto che nel paragrafo precedente abbiamo accennato alla necessità di effettuare le diverse operazioni sulla linea di trasmissione entro intervalli di tempo ben definiti, la prima cosa da fare è preparare una serie di routines di ritardo da chiamare opportunamente durante l’esecuzione del codice. In questo modo le routines che si occupano di alzare e/o abbassare la linea di comunicazione in funzione di quello che si vuole leggere o scrivere sulla sonda possono usufruire tante piccole routines che eseguono un numero di cicli a vuoto per il tempo necessario.
In questo caso abbiamo cicli di ritardo calcolati per un quarzo a 4MHz e vi sono diverse routines che realizzano ritardi da un minimo di 15µs a 1s.
Tralascerò la descrizione delle varie routines di ritardo dal momento che si tratta di semplici cicli a vuoto, calibrati per durare il tempo richiesto. Ricordo solo che con un quarzo da 4MHz ogni istruzione del PIC dura un microsecondo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
;--------routines di ritardo per il dialogo con DS18B20------------------ delay_600us movlw 0xC6 ; Aspetta 600 us movwf d1 delay_600us_loop decfsz d1, f goto delay_600us_loop nop return delay_50us movlw 0x0F ; Aspetta 50 us movwf d1 delay_50us_loop decfsz d1, f goto delay_50us_loop return delay_30us movlw 30 ; Aspetta 30 us movwf d1 delay_30us_loop decfsz d1, f goto delay_30us_loop return delay_15ms movlw 182 ; Aspetta 15 ms movwf d1 movlw 12 movwf d2 delay_15ms_loop decfsz d1, f goto $+2 decfsz d2, f goto delay_15ms_loop goto $+1 nop return delay_7us goto $+1 ; Aspetta 7 us nop nop return delay_80us movlw 0x19 ; Aspetta 80 us movwf d1 delay_80us_loop decfsz d1, f goto delay_80us_loop return delay_1s movlw 0x07 ; Aspetta 1 s movwf d1 movlw 0x2F movwf d2 movlw 0x03 movwf d3 delay_1s_loop decfsz d1, f goto $+2 decfsz d2, f goto $+2 decfsz d3, f goto delay_1s_loop goto $+1 goto $+1 goto $+1 return |
La routine principale, chiamata “Acquisition” che si occupa di eseguire tutti i passaggi necessari per acquisire il valore di temperatura letto dalla sonda è oggetto del prossimo paragrafo. Qui ci occuperemo di altre routines di supporto che vengono chiamate all’interno della Acquisition. La prima routine che analizziamo è anche la prima ad essere chiamata ed è denominata “inizializza_ds1820”:
1 2 3 4 5 6 7 8 9 |
inizializza_ds1820 bcf PORTA, 4 call delay_600us ; 600 us se e' zero -> resetta bsf PORTA, 4 call delay_80us ; Aspetta 80 us bcf errore,0 btfsc PORTA, 3 bsf errore,0 ;La sonda non ha risposto -> errore return |
Questa routine serve ad eseguire la sequenza di reset descritta al paragrafo precedente e a leggere l’eventuale impulso di presence. Quindi si occupa di abbassare la linea a 0, aspettare 600µs rilasciare la linea, aspettare 80µs e controllare se la linea è andata a 0 perchè una sonda ha risposto all’impulso di reset. Le operazioni di alzare e abbassare la linea coincidono rispettivamente con le istruzioni bsf e bcf applicate al bit 4 del registro PORTA.
Per tenere traccia di questa risposta e eventualmente usarla in altre parti del programma, si usa una variabile denominata errore di cui si usa il bit 0 come flag: bit 0 = 1 -> sonda non presente.
Ogni comunicazione con la sonda consiste sempre di 3 passi: inizializzazione, comando ROM, comando di funzione.
Dopo ogni comando si introduce un ritardo di 600µs per dare il tempo di esaurirsi allo slot di lettura, con un po’ di margine.
Abbiamo appena visto la routine di inizializzazione, che agisce direttamente sulla linea di trasmissione, ma, siccome le altre operazioni richiederanno di scrivere dei valori numerici, per facilitare la trasmissione dei valori 1 e 0 verso la sonda, ciascuno dei quali richiede dei movimenti ben temporizzati della linea, si utilizzano due routines di appoggio: “scrivi_uno” e “scrivi_zero”.
1 2 3 4 5 6 7 8 9 10 11 12 |
scrivi_uno bcf PORTA, 4 call delay_7us ; Aspetta 7 us bsf PORTA,4 call delay_80us ; Aspetta 80 us return scrivi_zero bcf PORTA,4 call delay_80us ; Aspetta 80 us bsf PORTA,4 return |
Per capire il funzionamento di queste due routines, dobbiamo tenere presente che la sonda legge il valore logico sul filo 15 microsecondi dopo che questo è stato tirato giù dal bus-master.
In altre parole se la sonda vede la linea, che è normalmente a 1, andare a 0, saprà che il bus-master le sta per comunicare un bit e quindi aspetterà 15μs e poi leggerà lo stato della linea interpretando il valore come un bit di dato.
Quello che deve fare il PIC è quindi, se vuole trasmettere uno 0, abbassare la linea e continuare a tenerla bassa fino a oltre i 15μs per almeno 75μs.
Questo perché la sonda potrà leggere il valore della linea in un intervallo compreso fra 15 e 75µs da quando il bus-master l’ha abbassata.
Viceversa, se il PIC vuole trasmettere un 1, deve rilasciare la linea entro i 15μs e lasciarla invariata per almeno 75μs.
Ovviamente anche nel caso della trasmissione di un 1 la linea deve essere dapprima abbassata dal PIC che, per dare il tempo alla sonda di accorgersi della variazione sulla linea, la deve mantenere a 0 per almeno 1μs prima di poterla rilasciare entro il limite dei 15μs.
Possiamo adesso dare un senso alle istruzioni sopra riportate. Come si può vedere per scrivere un 1 si mette a 0 la linea su cui è collegata la sonda (in questo caso il piedino RA4) e lo si mantiene per 7μs. Quindi siamo sicuri di averlo tenuto per 1µs e cadiamo a metà dell’intervallo da 15μs.
Successivamente si rimette la linea a 1 e si aspettano 80μs che è un po’ di più dei 75μs per avere un po’ di margine. Nel caso della scrittura di uno 0 è sufficiente mettere la linea a 0 per 80μs e poi rimetterla a 1.
Le due routines “scrivi_zero” e “scrivi_uno” si occupano di controllare a basso livello lo stato della linea, ma abbiamo bisogno ancora di una routine di supporto che si occupi di scrivere un intero valore numerico. Eccola di seguito riportata, la scrivi_ds1820.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
scrivi_ds1820 movlw 0x08 movwf KEY scrivi_ds1820_loop rrf ds1820, F ; sposta verso destra nop btfss STATUS, C goto $+3 call scrivi_uno goto $+2 call scrivi_zero decfsz KEY, f goto scrivi_ds1820_loop ; continua fino a che sono stati inviati 8 bit return |
Per l’utilizzo di questa routine, si suppone che sia stato inserito il valore che si vuole trasmettere nella variabile chiamata ds1820 e ricordiamo che tutte le comunicazioni con la sonda avvengono trasmettendo per primo il bit MENO significativo.
In questa routine si usa l’istruzione rrf per far ruotare verso destra i bit della variabile ds1820 passando per il bit di carry dello STATUS register. Ad ogni ciclo si utilizzano le routines scrivi_uno e scrivi_zero, viste precedentemente, in funzione del fatto che il bit di carry sia zero o uno.
Sapendo questo esaminiamo le righe una per una:
- metti nell’accumulatore il valore decimale 8
- metti il valore dell’accumulatore nel registro KEY
questo serve per avere un contatore per gli 8 bit di trasmissione;
- rrf ds1820,F
come anticipato questa è l’istruzione che effettua una rotazione verso destra del registro specificato e ne salva il contenuto nel registro stesso.
La particolarità consiste nel fatto che la rotazione avviene attraverso il bit di Carry dello STATUS register.
Questo significa che dopo aver eseguito la rrf ci ritroveremo al posto del bit 7 il valore che era nel bit di carry prima della rrf, al posto del bit di carry troveremo il valore del bit 0 (il bit meno significativo) e tutti gli altri bit saranno scalati di una posizione verso il meno significativo.
Questa procedura ci permette di avere ad ogni ciclo, nel bit di carry, proprio il valore da trasmettere partendo dal meno significativo.
Non rimane altro che:
- controllare il valore del bit di carry e saltare se vale 1
- andare avanti di 3 istruzioni
- chiamare la “scrivi_uno” esaminata precedentemente
- andare avanti di 2 istruzioni
- chiamare la “scrivi_zero” esaminata precedentemente
- decrementare il contatore “KEY” saltando se vale 0
- ritornare all’inizio della routine
- ritornare alla chiamante
C’è poco altro da aggiungere a queste righe, si tratta di alcuni salti condizionati organizzati in modo da chiamare la “scrivi_uno” o la “scrivi_zero” in base al valore del bit di carry.
Dobbiamo ora affrontare il problema di “leggere” questo dato che ci viene inviato ancora una volta a partire dal bit meno significativo.
Anche in questo caso è il bus-master che comanda la comunicazione abbassando la linea.
La sonda, se vuole trasmettere uno 0 manterrà la linea a 0 per un massimo di 15μs da quando il bus-master l’ha abbassata. Per il diagramma temporale si faccia riferimento al paragrafo precedente.
Quindi il PIC dovrà smettere di pilotare la linea entro i 15μs e leggere il valore.
Ogni slot di lettura deve durare almeno 60μs e fra uno slot e l’altro deve intercorrere almeno 1 μs.
Alla luce di quanto sopra possiamo interpretare la seguente routine:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
leggi_ds1820 clrf ds1820 bcf STATUS, C movlw 0x08 movwf KEY leggi_ds1820_loop bcf PORTA, 4 ; basso -> 0 nop nop bsf PORTA, 4 ; se e' alto non sara'_ trasmesso nop nop nop nop nop nop nop nop bsf STATUS, C btfss PORTA,3 ; campionamento del bus bcf STATUS,C rrf ds1820,F ; sposta verso destra call delay_80us ; Aspetta 80 us decfsz KEY, f goto leggi_ds1820_loop ; Aspetta fino a che sono stati inviati 8 bit return |
Come prima operazione si pulisce il contenuto del registro ds1820 perchè vi andremo a inserire il dato letto.
Poi si imposta a 8 il contatore dei bit, in questo caso, ricevuti.
Si abbassa la linea di comunicazione.
Si eseguono due nop, cioè due operazioni a vuoto che permettono di far trascorrere 2 microsecondi per far assestare la linea e permettere alla sonda di vedere la variazione.
Si rilascia la linea.
Si effettuano una serie di nop per portarsi a ridosso dei 15μs che è il limite massimo entro cui fare la lettura.
Si preimposta alto il bit di carry.
Finalmente si effettua la lettura del valore.
Se tale valore è 0 si imposta a 0 il bit di carry altrimenti si salta alla istruzione successiva.
Si effettua la rrf per riportare nel registro ds1820 il bit di carry infilandolo da sinistra.
In questo modo dopo le 8 iterazioni il primo bit letto sarà arrivato in posizione 0 che è appunto la meno significativa.
2 Febbraio 2020 at 10:40
Hai anche una versione del codice in C?
2 Febbraio 2020 at 13:53
Mi spiace, quando ho scritto questo codice, specialmente per il 16f84, il C non si usava granché.