IL SOFTWARE introduzione alla programmazione

Transcript

IL SOFTWARE introduzione alla programmazione
IL SOFTWARE
introduzione alla programmazione
Cominciamo a vedere di costruire i nostri primi programmi.
Dalle nozioni precedenti abbiamo cominciato a farci l'idea che il nostro programma sarà
semplicemente un insieme di istruzioni molto elementari che, raggruppate assieme e concatenate
nella maniera corretta, faranno fare al nostro microchip il compito richiesto.
Per le prime esperienze ci avvaliamo della schedina per esercitazioni autocostruita nel capitolo
precedente e piuttosto semplice.
I passi da seguire sono: ● scrittura del codice in assembler nell'editor dell'MPLAB
● assemblaggio e creazione del file oggetto
● download del codice oggetto nel microprocessore
● test per vedere se il programma si comporta come richiesto
L'MPLAB è un programma per windows, e pertatnto non si daranno spiegazioni sul suo uso... se
usate un qualunque pc moderno ne saprete più che a sufficienza !
L'ultimo punto invece è quello che richiede maggior esperienza. Che un programma in assembler
funzioni perfettamente al primo tentativo è quasi impossibile !
Chi scrive c'è riuscito solo una volta in una routine.. e tuttora si chiede come sia stato possibile !
Per trovare gli errori occorre controllare come si comporta il sistema e cercare di capire, avendo ben
in mente cosa si è scritto, cosa accada se qualcosa va storto.
Se non lo si riesce a capire si comincia modificando il programma per seminare trappole che ci
facciano scoprire l'arcano... finirete sicuramente dicendo “ma è ovvio !”, e qui introduco una regola
importante: Un microprocessore non da mai errori ! semplicemente si comporta in maniera
differente da quanto desiderato, ma ha sempre ragione lui !
Fannullone
Cominciamo con un esempio molto semplice.
Creiamo un programma chiamato fannullone.
Nell'ide di Mplab create un file nuovo e scrivete esattamente quanto segue:
org 0
Start nop
goto Start
end
Importante è rispettare le tabulazioni e gli spazi, nonché le lettere maiuscole e minuscole.
Ora assemblate il vostro programma se lo desiderate... ma poi ?
Corso microprocessori PIC ARI CARPI
Se tentate di caricarlo nel micro... quando date corrente non succede nulla !
Questo poiché il nostro programma è un vero fannullone... non fa proprio nulla !
Perché ?
Ignorate per ora le prime due righe del programma.
Iniziamo quindi dalla direttiva org 0. Questa non è un'istruzione del microprocessore, infatti
l'abbiamo chiamata direttiva, cioè un ordine impartito al programma assemblatore per dirgli di
considerare la locazione corrente del programma all'indirizzo 0.
Il 16F84 all'accensione comincia l'esecuzione del programma in memoria all'indirizzo zero, quindi
proprio da qui anche noi dobbiamo cominciare a scrivere i nostri programmi.
La scritta Start è semplicemente un'etichetta, un riferimento a cui poter saltare da un'altro punto del
programma.
nop è un'istruzione, significa NoOPeration cioé no operazioni, ovvero non fare nulla !
Non vi scandalizzate, è molto più utile di quanto pensiate. Il microprocessore infatti per eseguirla ha
bisogno di un ciclo macchina di tempo che nel nostro caso (quarzo a 4 Mhz) significa un
microsecondo.
Questa istruzione quindi perde un microsecondo di tempo... bene ! abbiamo già un rudimentale
temporizzatore !
goto Start significa vai all'etichetta Start , ovvero costringi il programma a deviare (o saltare) il suo
corso verso l'etichetta Start che era stata definita all'inizio.
Quando l'assemblatore crea il file oggetto da caricare nel microchip sostituisce i comandi appena
descritti con i codici esadecimali relativi alle istruzioni.
Nell'ordine, comincerà mettendo il valore 0, corrispondente all'istruzione NOP nella prima
locazione della memoria programma.
Poi passerà all'istruzione goto Start.
Qui il l'assemblatore ne sostituirà la parte Start con l'indirizzo di memoria programma
dell'istruzione a cui saltare ... che è zero essendo l'istruzione nop a destra dell'etichetta Start la
prima incontrata dopo la direttiva org 0.
Metterà quindi 10100000000000 nella locazione 1 di programma.
Di questo valore il 101 rappresenta l'istruzione goto mentre i restanti zeri sono l'indirizzo a cui
saltare, cioè zero !
Qui capite anche perché è molto importante la direttiva org0. L'assemblatore deve sapere che
indirizzo di memoria mettere nelle istruzioni di salto, e noi dobbiamo comunicarglielo tramite la
direttiva org senza la quale non saprebbe cosa mettere nella parte indirizzo del goto.
Questo programma quindi:
Perde un microsecondo
Poi salta alla locazione zero e ricomincia da capo.
In sostanza gira su se stesso all'infinito senza fare nulla, e' un fannullone!
La direttiva end a fine programma è obbligatoria per dire all'assemblatore che il programma è
terminato, se la omettete otterrete un'errore.
Complimenti !
Non avete fatto comparire la solita frase 'hello world!' tipica di tutti i corsi di programmazione ma
avete comunque scritto il vostro primo programma in assembler per un pic ! ...e funziona !!!
Corso microprocessori PIC ARI CARPI
Dimostrazione porte
Il prossimo programma farà qualcosa in più.
La schedina di esercitazione che avete costruito ha alcuni led e pulsanti.
In questo esempio leggeremo lo stato dei pulsanti e accenderemo alcuni led in maniera
corrispondente.
Create un file nuovo nell'Mplab e trascrivete quanto segue:
list P=16f84A , R=DEC #INCLUDE P16F84A.inc org 0 bsf STATUS,5 movlw 01110001b movwf TRISB bcf STATUS,5 Loop btfss PORTB,4
goto PB_1
bsf PORTB,1
goto PB_2
PB_1 bcf PORTB,1
PB_2 PB_3 PB_4 PB_5 PB_6 ;testiamo lo stato del pulsante sul pin 10 ;saltiamo se stato pin=1 ;se l'istruzione precedente lo permette, alziamo il pin 7 ;dopodiche saltiamo oltre ;abbassiamo stato pin 7 btfss PORTB,5
goto PB_3
bsf PORTB,2
goto PB_4
;testiamo lo stato del pulsante sul pin 11 ;saltiamo se stato pin=1 ;se l'istruzione precedente lo permette, alziamo il pin 8 ;dopodiche saltiamo oltre bcf PORTB,2
;abbassiamo stato pin 8 btfss PORTB,6
goto PB_5
bsf PORTB,3
goto PB_6
;testiamo lo stato del pulsante sul pin 12 ;saltiamo se stato pin=1 ;se l'istruzione precedente lo permette, alziamo il pin 9 ;dopodiche saltiamo oltre bcf PORTB,3
;abbassiamo stato pin 9 goto Loop end Se siete pigri andate sul cd del corso e dalla cartella esempi prendete il file pulsanti.asm .
Corso microprocessori PIC ARI CARPI
Non vi consiglio però di aprire il file già fatto, tenetelo come risorsa finale se proprio avete grossi
problemi quando lo assemblate, insistete a scrivere tutti i file di questo corso per quanto possibile.
Vi aiuterà a prendere dimestichezza con l'ambiente dell'Mplab, e poi ricorderete meglio cosa
contiene l'esercizio.
Il programma comincia inizializzando la porta di uscita B. Se guardate lo schema elettrico della
scheda di esercitazione consigliata, vedrete che abbiamo 3 pulsanti collegati ai pin 10­11 e 12.
Questi pin appartengono alla porta B cioè PORTB, più precisamente ai bit 4­5 e 6. Questi pin
verranno letti per vedere lo stato dei pulsanti, +5v cioè 1 logico se i pulsanti sono premuti, 0v cioè 0
logico se rilasciati.
Abbiamo anche tre led collegati ai pin 7­8­9 appartenenti rispettivamente ai bit 1­2­3 della stessa
PORTB. Questa porta può tranquillamente pilotare direttamente dei normali led da 20 mA, tre
resistenze da 470 Ohm fanno scorrere una corrente adeguata. Basterà alzare il pin a +5v per
accendere i led.
Se sulla stessa porta B abbiamo quindi tre bit usati come ingresso e altri tre bit usati come uscita...
da qualche parte dovremo pur configurare questa cosa nel pic !
Questa operazione viene effettuata dal registro TRISA che risiede nel banco 1 di memoria dati del
pic. Questo registro ad 8 bit è responsabile della configurazione ingresso/uscita di ciascun pin della
PORTB. A ciascun bit del registro TRISB corrisponde un bit della PORTB, bit per bit, così al bit 4
di TRISB corrisponde la configurazione di PORTB,4 (pin 10) . Se in TRISB mettiamo un valore 1
in un qualche bit, il pin corrispondente sarà un ingresso, potremo cioè leggerne lo stato da
programma,ma non attribuirne un livello logico. Viceversa mettendo in TRISB un valore 0, nel
corrispondente pin di PORTB verrà attivato uno stadio buffer che porterà lo stato logico del registro
PORTB sul pin hardware corrispondente.
La numerazione dei bit avviene sempre da destra a sinistra con peso binario crescente, nel nostro
esempio dovremo quindi configurare TRISB come segue: 01110001b .
La lettera b finale indica che il numero è in binario quindi vale 71hex o 113 decimale.
Con questo valore configuriamo PORTB con i pin 13­9­8­7 come uscite, mentre i pin 12­11­10­6
come ingressi ad alta impedenza.
Questo valore va depositato nel registro TRISB che come già accennato è nel banco 1 di memoria
dati. All'accensione del pic o dopo un reset, il banco selezionato di default è lo zero ed occorre
quindi settare a 1 il bit 5 del registro STATUS .Questo è quanto fa la prima istruzione del nostro
programma bsf STATUS,5 .
BSF è un'istruzione che setta ad 1 un bit 0­7 di qualunque registro nella memoria dati o file
memory, da qui la denominazione BitSetFile.
bsf STATUS,5 significa letteralmente setta ad uno il bit 5 del registro di memoria file STATUS.
Una tabella contenuta nel file P16F84.inc e che avete incluso nell'assemblaggio del programma con
la direttiva #INCLUDE P16F84A.inc contiene poi un'equivalenza fra la parola STATUS ed il valore
03 che è la locazione di memoria file in cui si trova il registro STATUS. Al termine
dell'assemblaggio il codice risultante sarà 01011010000011.
Di questo codice, i primi 4 bit (0101) significano istruzione BSF, poi segue 101 che è 5 in binario
(il numero del bit da settare) e la rimanente parte 0000011 è l'indirizzo della memoria F a cui
puntare cioè 3.
Al termine dell'esecuzione di questa istruzione la memoria file su cui opererete sarà il banco 1 su
cui potete accedere al registro TRISA.
Il passo successivo è finalmente caricare un valore arbitrario in TRISB.
Non esiste un'istruzione che possa direttamente caricare un qualunque valore in una memoria file,
Corso microprocessori PIC ARI CARPI
siamo così costretti ad operare in due passaggi successivi. La prima movlw 01110001b carica il
valore di configurazione desiderato nel registro W, mentre la seconda movwf TRISB muove il
contenuto di W, appena caricato, in TRISB che è il registro finale desiderato.
La sintassi di queste due istruzione e' come sempre abbastanza mnemonica:
● movlw: MOVe Litteral to Work register, muovi un letterale (sinonimo Microchip per indicare un
numero qualunque specificato direttamente nel corpo dell'istruzione) nel registro Work (W).
● movwf: MOVe Work register to File, muovi il contenuto di W nella memoria file.
Al termine di queste due istruzioni, istantaneamente la porta B avrà i pin configurati come in/out a
seconda dello stato scelto dai bit, ricordiamo 0 per uscita, 1 per ingresso.
bcf STATUS,5 riporta a zero il bit 5 del registro STATUS riattivando il banco di memoria dati zero,
necessario per accedere al registro PORTB. L'istruzione BCF significa BitClearFile cioè azzera
(clear) un singolo bit di una memoria dati file, risulta l'esatto opposto dell'istruzione BSF vista in
precedenza.
Arriviamo dunque all'etichetta Loop cui segue l'istruzione btfss PORTB,4 .
Letteralmente questa istruzione corrisponde a BitTestFileSkipifSet cioè testa lo stato del bit
specificato nella memoria file e salta l'istruzione successiva se il risultato è uno (set=settato=uno).
Il significato è di costringere il programma a NON eseguire l'istruzione successiva se un bit
specificato ha valore logico uno.
Nel nostro caso quindi se il pin 10 di PORTB (pin10=bit 4) è a uno logico, e quindi a pulsante
premuto, costringiamo il programma a NON eseguire l'istruzione successiva che devierebbe il
programma verso l'etichetta PB_1. Saltando un'istruzione si arriva così alla bsf PORTB,1 che alza il
livello del pin 7 ad 1 cioè +5 v.
Se invece lo stato dell'ingresso pin10 fosse risultato zero (pulsante premuto) l'istruzione btfss
PORTB,4 NON avrebbe saltato l'istruzione goto PB_1 deviando il programma verso la rispettiva
etichetta e portando all'esecuzione dell'istruzione bcf PORTB,1 che abbassa lo stato dell'uscita a
zero logico, o zero volt.
Questa istruzione btfss è molto importante. Ogni volta che dovrete prendere una decisione in un
porgramma la userete, insieme con la sua controparte btfsc che salta l'istruzione successiva se il bit
specificato e' zero (BitTestFileSkipifClear).
Il blocco di istruzioni che legge lo stato di un ingresso e lo riporta su una uscita è ripetuto tre volte
nello stesso modo, solo l'ingresso letto e l'uscita comandata variano. Ripetendo il blocco tre volte
nella maniera opportuna otteniamo che ciascun pulsante comanda separatamente una uscita
differente.
Al termine della scansione dei tre blocchi il programma incontra una istruzione goto Loop.
Questa obbliga il programma a riprendere da capo creando appunto un Loop infinito. In un
microprocessore programmato in assembler un programma deve SEMPRE girare da una qualche
parte... senza l'istruzione goto Loop dopo pochi microsecondi dall'avvio il nostro programma
sarebbe diventato inservibile al nostro scopo: leggere lo stato dei pulsanti ed accendere dei led in
continuazione.
Perché un sistema reagisca in un qualsiasi momento ai nostri stimoli esterni (pressione di un
pulsante) occorre che il programma che legge lo stato degli ingressi sia sempre attivo.
Possiamo pensare ai nostri programmi come a lavoratori instancabili, non devono mai arrestarsi,
altrimenti ci occorre riavviare il sistema spegnendolo e riaccendendolo, pena l'inservibilità.
Corso microprocessori PIC ARI CARPI
Un Contatore
Proseguiamo con un contatore che incrementi un valore e ce lo mostri sui nostri tre led.
Create un nuovo file vuoto in Mplab e digitate quanto segue:
list P=16f84A , R=DEC #INCLUDE P16F84A.inc CBLOCK 0ch Contatore
Timer1
Timer2
Timer3
ENDC ;il contatore che si incrementerà ad ogni ciclo ;Subcontatore per timer ;Subcontatore per timer ;contatore per timer org 0 bsf STATUS,5 movlw 01110001b movwf TRISB bcf STATUS,5 clrf Contatore Loop PB_1 PB_2 PB_3 PB_4 clrf Timer1 clrf Timer2 movlw 4 movwf Timer3 incf Contatore,f btfss Contatore,0
goto PB_1
bsf PORTB,1
goto PB_2
;testiamo lo stato del pulsante sul pin 10 ;saltiamo se stato pin=1 ;se l'istruzione precedente lo permette, alziamo il pin 7 ;dopodiche saltiamo oltre bcf PORTB,1
;abbassiamo stato pin 7 btfss Contatore,1
goto PB_3
bsf PORTB,2
goto PB_4
;testiamo lo stato del pulsante sul pin 11 ;saltiamo se stato pin=1 ;se l'istruzione precedente lo permette, alziamo il pin 8 ;dopodiche saltiamo oltre bcf PORTB,2
;abbassiamo stato pin 8 Corso microprocessori PIC ARI CARPI
PB_5 PB_6 T1
btfss Contatore,2
goto PB_5
bsf PORTB,3
goto PB_6
;testiamo lo stato del pulsante sul pin 12 ;saltiamo se stato pin=1 ;se l'istruzione precedente lo permette, alziamo il pin 9 ;dopodiche saltiamo oltre bcf PORTB,3
;abbassiamo stato pin 9 nop nop decfsz Timer1,f goto T1 decfsz Timer2,f goto T1 decfsz Timer3,f goto T1 goto Loop end Lungo eh ? Il file già pronto si chiama contatore.asm .
Vediamo qui una nuova direttiva: CBLOCK 0ch Serve ad indicare all'assemblatore di assegnare alle etichette che seguiranno un valore crescente a
partire da quello specificato dalla direttiva stessa.
IN questo caso alle 4 etichette indicate prima della direttiva di termine ENDC verranno attribuiti i
valori 0ch 0dh 0eh 0fh (12 13 14 15).
0ch se guardate nel datasheet del 16F84 è l'inizio della memoria RAM nel file memory !
Quindi non facciamo altro che decidere di attribuire dei nomi a locazioni di memoria RAM in cui
memorizzeremo le variabili del nostro programma.
Quando successivamente depositeremo un valore nella variabile Contatore, in realtà questo verrà
posto alla posizione 12, Timer1 alla 13 e così via. L'istruzione stessa poi indica che il l'indirizzo è
nella memoria file e quindi in RAM. Ricordatevi solo che di queste memorie ne avete a
disposizione 36 nel modello 16C84 e 68 nel 16F84.
Le 5 istruzioni successive all'ENDC sono già state viste in precedenza e servono ad inizializzare le
porte del pic per poter accendere i led.
clrf Contatore invece è nuova, ma molto semplice. CleaRFile vuole solo dire pulisci la locazione
specificata della memoria file, in pratica mettici dentro il numero zero.
Al termine, la locazione di memoria 12 (contatore era la prima variabile attribuita ricordate ?)
conterrà zero.
E' sempre importante pulire o scrivere con un valore le locazioni di memoria RAM prima di usarle,
dopo un reset o all'accensione il contenuto e' dichiaratamente imprevedibile !
Le 4 istruzioni a partire dall'etichetta Loop sono a questo punto già viste, le prime due ripuliscono
anche le due variabili Timer1 e Timer2 mentre le seconde depositano il valore 4 nella variabile
Corso microprocessori PIC ARI CARPI
Timer3.
incf Contatore,f è nuova. Significa INCrementFile, cioè incrementa la locazione di memoria file
specificata (12). Al termine dell'operazione il valore della locazione sarà incrementato di uno
rispetto al valore precedente. Il suffisso ,f significa che il risultato dell'incremento deve essere
ridepositato nella stessa memoria da cui è stato prelevato il valore origine. I valori possibili in
questo caso sono ,f oppure ,w . Se avessimo messo ,w il valore incrementato sarebbe stato lasciato
nel registro W anziché essere ridepositato in memoria.
Perché dico ridepositato se l'operazione viene eseguita sulla locazione Contatore ???
Perché in realtà una qualunque operazione aritmetica o logica non può essere eseguita direttamente
in memoria dato solo la ALU ha capacità matematiche. Il microprocessore preleva il valore dalla
memoria, lo deposita nella ALU, esegue l'operazione richiesta e poi lo rideposita in memoria o nel
registro W.
Quanto segue dopo la virgola specifica appunto al destinazione del risultato. Tenete presente che se specifico una destinazione ,f allora W resterà inalterato, così come se
specifico ,w al termine dell'operazione troverò Contatore incrementato ma solo in W ! Il contenuto
di Contatore in memoria RAM resterà lo stesso che in precedenza !
Da incf Contatore,f fino all'etichetta PB_6 il programma è lo stesso che nell'esempio precedente,
cambia solo il fatto che per accendere i led usiamo il contenuto dei primi 3 bit di Contatore anziché
lo stato dei tre pulsanti.
Compito: Ho solo tre led collegati... il contatore è a 8 bit... cosa mostrano i led ? Cosa
avviene in realtà nella memoria Contatore ? ragionaci...
Dalla Label T1 in poi abbiamo realizzato un temporizzatore per perdere tempo e rendere leggibile
quanto esegue il nostro programma.
Se non l'avessimo implementato, l'intero programma si sarebbe completato in pochi microsecondi, e
la nostra vista non è così veloce da riuscire a leggere i led in quel tempo !
Per ben due volte incontriamo la già nota istruzione nop che perde un microsecondo, quindi già qui
ne perdiamo due.
Poi segue la decfsz Timer1,f . Questa istruzione significa DECrementFileandSkipifZero. Se
cominciate a capire come funziona il set di istruzioni del pic avrete già capito che decrementa di
uno la locazione di memoria specificata e salta l'istruzione successiva se il risultato è zero.
La memoria Timer0 era stata settata a zero, quindi decrementarla di uno significa farla diventare ffh
o 255. In assembler i numeri negativi non esistono di principio, dovete pensarli sempre come
positivi, se sono di un byte il valore massimo è 255. Ora per paragone pensate cosa succede se alla
cifra decimale 20 (due e zero) sottraete 1... vi rimane 19 (uno e nove). La prima cifra decimale era
zero prima della sottrazione ! Avete diminuito di uno la cifra delle decine e la cifra delle unità ha
ricominciato a contare del massimo valore possibile per una singola cifra cioè 9. Allo stesso modo
se decrementiamo di uno una memoria di un byte che era a zero, otterremo 255 come risultato più il
settaggio della flag carry/borrow nel registro STATUS. Questa indica un riporto dalla cifra
superiore, che però qui non esiste ! serve solo ad indicarci che c'è stata una transizione per lo zero
durante una sottrazione.
In questo caso a noi il riporto non interessa, diverso sarebbe stato il caso in una operazione
matematica.....
Corso microprocessori PIC ARI CARPI
Tornando a noi, il test dell'istruzione decfsz viene effettuato DOPO il decremento, inizializzare la
variabile a zero prima di questa istruzione significa ottenere il massimo del conteggio cioè 256 (0­
255 = 256 valori).
Il tempo necessario per eseguire questa istruzione dipende dall'esito del test di valore zero. Se il test
dà valore zero allora viene saltata l'istruzione successiva e questo comporta due cicli di macchina
cioè 2 microsecondi, se il test non viene fatto su valore zero il tempo di esecuzione sarà di 1
microsecondo soltanto.
Se il valore non è zero, la goto T1 verrà eseguita perdendo altri 2 cicli (vedere il datasheet...).
Per 256 cicli quindi perderemo 2+1+2=5 microsecondi, cioè 256x5=1280 microsecondi pari a 1,28
millisecondi. Al termine del ciclo però la goto T1 NON verrà eseguita, in compenso decfsz Timer1
impiegherà 2 cicli anziché' 1 con il risultato che l'ultimo ciclo impiegherà un ciclo di meno.
Se vogliamo essere precisi consideriamo un totale del ciclo di 1279 microsecondi:
T = ( 256 x 5) ­ 1
Al termine di questo tempo Timer0 diverrà finalmente zero ed il programma proseguirà con un'altra
sequenza esattamente identica ma che opera su Timer1.
Questa costringe l'esecuzione del loop precedente per altrettante 256 volte... quindi fino a qui
perdiamo:
T = ( ( ( 256 x 5) ­ 1 ) x 256) ­ 1 = 327,423 millisecondi... quasi un terzo di secondo, ancora troppo
veloce !
La terza decfsz che opera su Timer3 ripete tutto quello appena esposto per 4 volte ancora dato che
Timer3 è stata inizializzata a 4 precedentemente. Quindi:
T = ( ( ( ( 256 x 5 ) ­ 1 ) x 256 ) ­ 1 ) * 4 = 1,311744 secondi ... ci sembra superfluo conteggiare i 4
microsecondi persi dall'ultima decfsz....
Al termine la goto Loop ricomincerà il ciclo da capo incrementando il contatore visualizzato dai led.
Vi rendete conto di quanto veloce è un microprocessore programmato in assembler ???
Abbiamo dovuto far eseguire due istruzioni inutili per ben 256 x 256 x 4 = 262144 volte !
Spesso vi troverete a dover buttare via del tempo con dei loop simili a quello appena visto per
evitare che l'eccessiva velocità del processore vi crei problemi.
Divertitevi a guardare la danza dei led per un po poi passate al capitolo successivo...
Compito: Calcolate un loop che perda 285 microsecondi, alzi a +5v un pin qualunque
configurato come uscita, perda altri 285 microsecondi poi riabbassi lo stesso pin e ricominci
da capo all'infinito.
Cosa avete ottenuto ? (suggerimento: siete radioamatori ??? guardate cosa succede a quel
pin... potrebbe esservi utile !)
Corso microprocessori PIC ARI CARPI