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