Linguaggio assembly - parte I
Introduzione
Il
PIC 16F84 ha un set di appena 35 istruzioni. Ogni istruzione e' una parola a 14
bit ed e' composta da un opcode che specifica l'istruzione stessa e da
uno o due operandi (per alcune questi sono facoltativi o del tutto
assenti) che specificano ulteriormente l'operazione da eseguire.
Ogni istruzione viene eseguita in un ciclo macchina (4 colpi di clock)
eccetto per le istruzioni di salto (ma solo quando il salto e'
effettuato)
e per le istruzioni che modificano il program counter; in questi due
casi l'istruzione richiede 2 cicli macchina (8 colpi di clock).
Le istruzioni sono variegate, ma possono essere raggruppate in tre
categorie principali: le istruzioni di salto (condizionato o non),
quelle aritmetiche/logiche e di movimentazione dei dati. |
Concetti generali
Molte
istruzioni sono composte dallo
mnemonico (il nome dell'istruzione stessa) e da uno o due parametri (a
volte nessuno). I parametri possono essere di quattro tipi: parametro
F, parametro D, B e K.
- F
- Questo parametro
rappresenta la locazione di memoria di un registro (SFR o GPR) della
memoria dati.
- D
- E'
un singolo bit ed indica se il risultato dell'operazione andra' nel
registro
indicato da F (D=1) oppure nel registro di lavoro W (D=0). Se D viene
omesso si ricade sempre nel caso D=1.
- B - E'
un parametro a 3 bit (valori tra 0 e 7 inclusi) ed indica la posizione
del bit
prescelto all'interno di un registro puntato da F. Ad esempio, se B=2
indica il terzo bit meno significativo del registro indicato da F (il
primo e' il bit numero zero, il secondo il numero uno ed il terzo il
numero due). Alcune
istruzioni necessitano di tale parametro per sapere quale bit deve
essere controllato/modificato all'interno di un dato registro.
- K -
Rappresenta un valore letterale, un byte, cioe' un valore tra 0 e 255.
Alcune istruzioni hanno bisogno di un letterale come parametro per
effettuare operazioni aritmetiche o logiche.
Queste lettere vengono usate nelle descrizioni sintattiche delle varie istruzioni assembly.
Eventuali commenti al codice sono preceduti dal punto e virgola e vengono inseriti a destra della istruzione commentata.
Come si vedra' anche in seguito, in cima al listato del programma
assembly e' possibile definire delle etichette da associare alle
locazioni RAM del PIC. In questo modo ci si puo' riferire piu'
facilmente a tali locazioni specificandone il nome scelto anziche'
l'indirizzo in esadecimale. Per ora e' sufficiente sapere che scrivendo
la seguente riga in cima al listato
ILMIOREGISTRO EQU 0x25
e' possibile riferire la locazione 0x25 tramite il nome
"ILMIOREGISTRO", esattamente come accade per le variabili nei linguaggi
di programmazione di alto livello.
|
Operazioni di base
Set e clear dei bit (BSF e BCF)
Sono
le due istruzioni BSF e BCF.
Vengono utilizzate per impostare a 0 o 1 i singoli bit dei
registri.
BSF setta a uno il bit in posizione B del registro F, mentre BCF setta
a zero il bit in posizione B del registro F.
Sintassi delle istruzioni:
BSF F B
BCF F B
Esempio:
Settare ad uno il sesto bit meno significativo del
registro alla locazione 0x13
BSF 0x13,5
NOTA:
Il sesto bit e' il numero cinque,
perche' il primo bit e' il numero zero!
Esempio:
Portare
a zero il primo e l'ultimo bit del registro FSR
BCF 0x04,0
BCF 0x04,7
oppure, utilizzando le etichette:
FSR EQU 0x04
BCF FSR,0
BCF FSR,7
Esempio:
Portare
a zero il contenuto del registro in posizione 0x19
BCF 0x19,0
BCF
0x19,1
BCF 0x19,2
BCF 0x19,3
BCF 0x19,4
BCF 0x19,5
BCF 0x19,6
BCF 0x19,7
NOTA: ovviamente l'azzeramento di un
registro si puo' fare in modi piu' efficienti e veloci. |
Selezione del banco RAM (banco 0, banco 1)
Per
ora abbiamo toccato solo registri
che non richiedevano la specifica di uno dei due banchi (il registro
FSR e i registri GPR sono mappati in entrambi i banchi). Se pero', ad
esempio, occorre modificare il registro TRIS_A e' necessario
specificare il banco 1.
Il banco RAM desiderato (0 o 1) viene selezionato impostando il sesto
bit del registro STATUS. Se il bit vale zero e' abilitato il banco 0,
se il bit vale uno e' abilitato il banco 1.
All'accensione o al reset del PIC il banco 0 e' quello di default.
Esempio:
selezionare il banco 1 della RAM
BSF 0x03,5
Esempio:
selezionare il banco 0 della RAM
BCF 0x03,5
Esempio:
settare ad uno il terzo bit del registro TRIS_A
supponendo
che attualmente sia selezionato il banco 0.
STATUS EQU 0x03
TRIS_A EQU 0x05
BSF STATUS,5
BSF TRIS_A,3
NOTA:
Chiaramente l'accesso al registro
STATUS (0x03) non richiede la selezione preventiva del banco in quanto
e' mappato in entrambi. |
Spostare i dati (MOVF, MOVWF e MOVLW)
Sono
le tre istruzioni MOVLW, MOVF e
MOVWF. Grazie a queste tre istruzioni e' possibile copiare un byte da
un registro ram (GPR o SFR) al registro W e viceversa, oppure caricare
W con
un valore letterale compreso tra 0 e 255.
MOVLW si usa tutte le volte in cui occorre inizializzare il registro W
con un dato valore.
Sintassi delle istruzioni:
MOVLW K
MOVLW
inizializza il
registro W con il valore K. K e' un valore compreso tra 0..255 inclusi
(0x00..0xFF in esadecimale).
MOVWF F
MOVLW muove
il contenuto del registro W alla
locazione di memoria F. Il registro W non viene modificato.
MOVF F,D
MOVLW muove
il contenuto del registro F nel
registro W se D=0. Se invece D=1 il contenuto di F viene "copiato" su
se stesso senza toccare W. L'istruzione modifica automaticamente
anche un bit del registro STATUS, piu' precisamente il terzo bit, detto
bit Z o ZERO_FLAG. Tale bit viene settato ad uno se e solo se il
registro F (al momento dell'esecuzione dell'istruzione MOVF) vale zero,
altrimenti viene impostato a zero. Ecco quindi che ha un senso anche
copiare un registro su se stesso (con l'opzione D=1) perche' in questo
caso l'unico effetto dell'istruzione e' la modifica opportuna
del bit ZERO_FLAG, bit che viene controllato dal PIC per saltare o meno durante i salti condizionati.
Esempio:
Caricare
il registro W con il valore
letterale 0x34 (52 in decimale)
MOVLW 0x34
Esempio:
Muovere il contenuto del registro W
alla
locazione di memoria 0x41 (in questo caso un registro GPR)
MOVWF
0x41
Esempio:
Muovere
il contenuto del registro
in posizione 0x32 nel registro W
MOVF 0x32,0
Esempio:
Copiare il contenuto della locazione di memoria 0x0F nella locazione
0x10.
MOVF
0x0F,0
MOVWF 0x10
NOTA: In questi casi, siccome le locazioni 0x34,0x32,0x0F e 0x10 sono
relative a
registri GPR non e' mai necessario specificare il banco RAM (0 o 1).
Esempio:
Copiare il contenuto del registro TRIS_A nel registro W
BSF
0x03,5 ;selezione
del banco RAM 1
MOVF 0x05,0
NOTA:
In questo caso e' necessario prima
specificare il banco RAM per accedere a TRIS_A
Esempio:
Copiare il contenuto del registro OPTION_REG in PORT_A
BSF
0x03,5 ;selezione
del banco RAM 1
MOVF
0x01,0
BCF
0x03,5 ;selezione
del banco RAM 0
MOVWF 0x05
|
Azzerare i registri (CLRF e CLRW)
Vengono utilizzate per azzerare i contenuti dei registri.
Sintassi delle istruzioni:
CLRF F
Azzera il contenuto del registro puntato da F. Viene automaticamente settata a 1 la ZERO_FLAG del registro di stato.
CLRW
Azzera il contentuo del registro di lavoro W. Viene automaticamente settata a 1 la ZERO_FLAG del registro di stato.
Esempio:
Azzerare il contenuto di PORT_A
CLRF 0x05
Esempio:
Azzerare il contenuto di TRIS_B
BSF 0x03,5 ;selezione
del banco RAM 1
CLRF 0x05
Esempio:
Azzerare il contenuto del registro W
CLRW
|
No operation (NOP)
Questa
istruzione non produce alcun risultato. La sua "esecuzione" comporta
solamente l'incremento del program counter alla prossima istruzione.
L'istruzione di NOP viene utilizzata per effettuare dei ritardi
nell'esecuzione dei programmi. Supponendo che l'oscillatore esterno sia
di 4MHz, tutte le istruzioni non di salto (come NOP) verranno
eseguite in 1us: se quindi e' necessario ritardare di 10us l'esecuzione
in un punto di un programma, sara' sufficiente inserire in quel punto
10 NOP
consecutivi. Ovviamente la risoluzione del tempo di ritardo sara' pari
ad 1us. Ritardi piu' grandi possono essere effettuati tramite NOP
all'interno di cicli come si vedra' piu' avanti.
L'istruzione NOP si rivela utilissima nei casi in cui si deve generare
un segnale con tempistiche ben precise, ad esempio una comunicazione
seriale RS232, oppure frequenze sonore.
Sintassi della istruzione:
NOP
Esempio:
Effettuare un ritardo di 3us supponendo un oscillatore da 8MHz.
NOP
NOP
NOP
NOP
NOP
NOP
NOTA: Con un oscillatore da 8MHz ogni istruzione viene eseguta in 0,5us. Quindi per un ritardo di 3us occorrono 6 NOP. |
Salto incondizionato (GOTO)
Questa
istruzione permette di saltare incondizionatamente alla linea di
programma desiderata, qualunque essa sia. L'istruzione viene eseguita
in due cicli macchina (8 colpi di clock).
Sintassi della istruzione:
GOTO addr
Salta
ad eseguire l'istruzione posizionata all'indirizzo "addr". "addr" e' l'indirizzo
di memoria di programma (memoria FLASH) alla quale si vuole saltare;
percio' "addr" deve essere un valore compreso tra 0 e 1023.
Spesso e' scomodo scrivere direttamente la locazione di memoria in
forma numerica, percio' molti ambienti di sviluppo, come MPLAB,
permettono di specificare delle label (da NON confondere con le etichette della direttiva EQU vista in precedenza!). La label
e' semplicemente una stringa di testo arbitraria preceduta da ":" e la
si posiziona all'interno del codice, tra una istruzione ed un altra.
Quando si vuole saltare all'istruzione immediatamente successiva alla
label, la si richiama nel goto scrivendo al posto di "addr" il nome dato
alla label. Gli esempi successivi chiariscono ulteriormente il concetto:
Esempio:
Ripetere il caricamento del registro W con il valore 0x30 all'infinito.
:loop ;label di nome "loop"
MOVLW 0x30 ;caricamento di W con il valore 0x30
GOTO loop ;salto alla istruzione dopo "loop"
Esempio:
Saltare tre istruzioni
BSF
0x03,5
MOVF
0x01,0
GOTO salto ;salto
BCF 0x03,5
CLRW
MOVLW 0x10
:salto
BCF
0x03,5 ;prima istruzione dopo il salto
MOVWF 0x05
|
Salti condizionati (BTFSC, BTFSS, DECFSZ e INCFSZ)
Le
quattro istruzioni di salto del microcontrollore sono BTFSC,BTFSS,
DECFSZ e INCFSZ. Anche se i nomi di queste istruzioni possono
apparire complicati, il loro funzionamento e' estremamente semplice.
Quello che differenzia tra loro le istruzioni e' la politica di salto
ed alcuni altri effetti collaterali specifici, ma in linea di massima
si comportano in questo modo: se la condizione di salto e' FALSA si
esegue l'istruzione successiva, altrimenti, se la condizione di salto
e' VERA, si esegue quella immediatamente dopo a quella successiva. La
figura spiega meglio il concetto:
In questo caso e' stata scelta BTFSC, ma poteva tranquillamente essere
un'altra delle quattro. Se la condizione di salto (specifica per ognuna
delle istruzioni) e' FALSA, allora si prosegue normalmente con l'esecuzione del
programma, cioe' eseguendo l'istruzione 1, poi l'istruzione 2 e cosi'
via come mostrato nella figura. Se la condizione e' VERA allora NON si esegue
l'istruzione 1 ma si continua con l'esecuzione del programma
dall'istruzione 2 in poi. Molto spesso l'istruzione 1 e' un salto
incondizionato (GOTO); in questo modo e' possibile redirigere
l'esecuzione del programma ad un altro punto.
Le istruzioni di salto richiedono 2 cicli macchina quando la condizione e' VERA ed 1 solo ciclo macchina quando questa e' FALSA.
Sintassi delle istruzioni:
BTFSC F,B
Se
il B-esimo bit del registro F e' settato a uno (condizione FALSA) viene eseguita
l'istruzione immediatamente successiva; se vale zero (condizione VERA) l'istruzione
immediatamente successiva viene saltata e si passa a quella dopo.
BTFSS F,B
Se
il B-esimo bit del registro F e' settato a zero (condizione FALSA) viene eseguita
l'istruzione immediatamente successiva; se vale uno (condizione VERA) l'istruzione
immediatamente successiva viene saltata e si passa a quella dopo.
Questa istruzione di salto e' simile alla precedente ma il controllo
sul bit B-esimo e' invertito.
DECFSZ F,D
Viene
decrementato di una unita' il contenuto del registro F. Il risultato viene
posto in W se D=0 (lasciando inalterato F), viene posto in F se D=1. Se
inoltre tale risultato e' zero si salta l'istruzione successiva (condizione VERA),
altrimenti (condizione FALSA) si prosegue normalmente.
NOTA: Il decremento e' effettuato PRIMA del controllo della condizione!
INCFSZ F,D
Viene incrementato di una unita' il contenuto del registro F.
Il risultato viene posto in W se D=0 (lasciando inalterato F), viene posto in F se
D=1. Se inoltre tale risultato e' zero (condzione VERA) si salta l'istruzione
successiva, altrimenti (condizione FALSA) si prosegue normalmente.
NOTA: L'incremento e' effettuato PRIMA del controllo!
Esempio:
Se il primo bit del registro 0x20 e' 1 caricare W con il valore 0x00, altrimenti caricare W con il valore 0xFF.
REG EQU 0x20
BTFSC REG,0 ;controlla se il primo bit del registro 0x20 e' 1
GOTO yes ;se vale 1 vai a "yes"
MOVLW 0xFF ;se vale 0 carica W con 0xFF e vai a "end"
GOTO end
:yes
MOVLW 0x00 ;setta W con 0x00
:end
Esempio:
Scrivere nel registro 0x30 tutti i valori in successione da 0 a 255. Infine azzerare W.
MOVLW 0x00 ;carico W con 0x00
MOVWF 0x2F ;scrivo W nel registro di appoggio 0x2F
:loop
MOVLW 0x2F,0 ;carico W con il contenuto di 0x2F
MOVWF 0x30 ;scrivo il contenuto di W nel registro 0x30
INCFSZ 0x2F,1 ;incremento il registro 0x2F e controllo se vale zero
GOTO loop ;non e' zero, vai a "loop"
CLRW ;si e' zero, resetta W. Programma terminato.
|
Cicli di delay
Abbiamo gia' visto come effettuare un ritardo tramite l'uso ripetuto
dell'istruzione NOP. La tecnica non permette di generare grandi ritardi
perche' occorrebbe inserire un gran numero di NOP in sequenza,
occupando tutta la memoria di programma.
Conviene invece scrivere un ciclo che itera per un determinato numero
di volte, decrementando un registro GPR adibito a contatore;
l'istruzione di decremento e' incorporata in quella di salto, quindi il
sistema e' estremamente semplice da implementare. Piu' difficile e'
stabilire il valore iniziale del contatore per ottere uno specifico
tempo di ritardo: occorre infatti calcolare accuratamente tutte le durate di esecuzione delle
istruzioni.
Un tipico ciclo di delay e' quello riportato qui sotto:
Ciclo di delay:
Considerando un oscillatore a 4MHz si crea un ritardo di 500us (mezzo millisecondo)
COUNT EQU 0x30
:delay
MOVLW 0xA7 ;carico W con il valore 167
MOVWF COUNT ;lo memorizzo nel registro COUNT
:loop
DECFSZ COUNT,1 ;decremento di uno, se non e' zero vai a "loop"
goto loop
Il
loop principale prevede una istruzione DECFSZ ed un GOTO. DECFSZ
richiede 1us quando NON salta (cioe' nel caso in cui il registro non
sia zero) e 2us quando salta (e quindi termina la routine di delay).
Considerando N la nostra incognita (il valore di inzializzazione per count), DECFSZ richiede un tempo pari a:
tempo_DEFSZ = (N-1)+2 us
(N-1) volte richiede 1us (quando non salta) e all'ultimo giro richiede 2us (salta).
L'istruzione GOTO viene eseguita N-1 volte e quindi richiede 2(N-1) us.
In definitiva si ha:
tempo_tot = (N-1)+2+2(N-1) us
semplificando:
tempo_tot = 3(N-1)+2 us
semplificando ancora:
tempo_tot = 3N-1 us
risolvendo rispetto a N si ottiene:
N = (tempo_tot+1)/3
se "tempo_tot" e' appunto 500 come da richiesta, si ottiene N = 167 (0xA7 in esadecimale)
|
Per avere ritardi maggiori occorre annidare piu' cicli uno dentro
l'altro utilizzando quindi tanti registri contatori quanti sono i cicli
annidati, ma il calcolo dei valori di inizializzazione si complica
ancora di piu' ed e' necessario risolvere sistemi non lineari molto piu' complessi.
Inoltre, spesso e volentieri le soluzioni prevedono valori non interi
per cui o si sceglie di approssimare oppure si inseriscono NOP
finali esterni ai cicli per compensare gli errori di approssimazione.
Per maggiori informazioni rimando a questo tutorial
|
|