Sei sulla pagina 1di 37

SOFTWARE & DIDATTICA

by Vincent
Musica - Elettronica Didattica
Dispensa stilata da studente ITIS E. Majorana di Grugliasco (TO)

Relazione sui

Microcontrollori PIC della


MicroChip

APPUNTI
SU

PIC 16F84

INDICE

N.B.: Non si risponde di eventuali imprecisioni e/o inesattezze del testo.

Microcontrollore PIC16F84.................................................................................................................3
PIEDINATURA E AREE DI MEMORIA............................................................................................3
ISTRUZIONI........................................................................................................................................5
ISTRUZIONI DI CARICAMENTO DATI A 8 BIT............................................................................6
ISTRUZIONI ARITMETICHE............................................................................................................7
ISTRUZIONI LOGICHE.....................................................................................................................7
ROTAZIONI E SET/RESET DI SINGOLI BIT..................................................................................8
SALTI E SUBROUTINE.....................................................................................................................8
ISTRUZIONI DI CONTROLLO SISTEMA.......................................................................................8
RIEPILOGO ISTRUZIONI.................................................................................................................9
NOTE PER LA PROGRAMMAZIONE............................................................................................10
GENERALITA'..............................................................................................................................10
EQU................................................................................................................................................10
INCLUDE.......................................................................................................................................11
ORG................................................................................................................................................11
#DEFINE........................................................................................................................................12
CONFIGURAZIONE HARDWARE.................................................................................................12
ESEMPIO DI PROGRAMMA..........................................................................................................13
LOOP..................................................................................................................................................14
LOOKUP TABLE (TABELLE DATI)...............................................................................................15
RITARDI SOFTWARE......................................................................................................................16
PORTE DI INGRESSO/USCITA.......................................................................................................18
TRASMISSIONE SERIALE SOFTWARE.......................................................................................20
POTENZIAMENTO ASSEMBLER..................................................................................................21
CONFRONTO TRA NUMERI A 8 BIT............................................................................................23
RICEZIONE SERIALE.....................................................................................................................25
LOOP CON ALTO NUMERO DI CICLI..........................................................................................26
MULTITASKING..............................................................................................................................30
SISTEMA DI DEBUG.......................................................................................................................35

MCU PIC 16F84 by MicroChip

Pagina 3 di 37

Microcontrollore PIC16F84
PIEDINATURA E AREE DI MEMORIA

Il PIC 16F84 e' un microcontroller e puo' essere pensato come un piccolissimo


computer completo realizzato in un unico integrato a 18 pin. E' dotato infatti di
memoria programma, ram utente, periferiche interne e porte di ingresso/uscita
per ricevere e trasmettere segnali da e verso l'esterno. Puo' essere
alimentato da 3 a 5,5V e assorbe poco piu' di 2 mA. Vss e' la massa (chiamata
anche GND), Vdd e' il positivo di alimentazione (chiamato anche Vcc). OSC1
e OSC2 sono i pin a cui va collegato il quarzo per il clock, a questi vanno
anche collegati due condensatori da 22pF verso massa come indicato nella
figura di sotto. Il quarzo puo' arrivare fino a 20Mhz a seconda del tipo di PIC.
Collegando a massa il pin MCLR si ottiene il reset del micro, normalmente
questo pin va tenuto a 1 con una resistenza da qualche K collegata a Vdd. Gli
altri 13 pin sono ingressi/uscite singolarmente programmabili e ciascuno puo'
pilotare almeno 20mA. Il pin RB0 puo' essere programmato come ingresso di
interrupt. I pin da RB4 a RB7 possono generare un interrupt di gruppo. Tutti i
pin della porta B (RB0..RB7) se configurati come ingressi possono disporre di
una resistenza di pull-up interna. Il pin RA4 in uscita e' un open collector, e in
ingresso puo' essere usato come clock di conteggio per il timer interno. Lo
schema seguente mostra il collegamento base del 16f84. Anche se nello
schema non e' indicato, e' sempre consigliabile collegare un condensatore da
100nF tra i pin Vdd e Vss per filtrare i possibili disturbi sull'alimentazione.

MCU PIC 16F84 by MicroChip

Pagina 4 di 37

Il PIC 16F84 dispone di una memoria programma separata dalla memoria dati.
La memoria programma e' lunga 1024 locazioni (1kwords) con indirizzi da 0 a
1023. Il PIC all'accensione (o dopo un reset) inizia a leggere il programma
dall'indirizzo 0. L'indirizzo 4 e' il punto di partenza dell' interrupt handler, la
discriminazione della sorgente dell'interrupt va effettuata via software.
La memoria dati e' lunga 80 locazioni (indirizzi da 0 a 79) e prende il nome di
register file in quanto ogni locazione puo' essere considerata come un registro
a 8 bit. I primi 12 indirizzi (0..11) servono per il controllo dell'hardware del PIC,
i seguenti 68 indirizzi (12..79) sono usabili dall'utente per memorizzare i propri
dati di lavoro. Inoltre i primi 12 indirizzi dispongono di due banchi di registri,
alcuni dei quali sono visibili in entrambi i banchi, mentre altri sono presenti
solo in un banco. Il registro STATUS per esempio e' visibile in entrambi i
banchi, puo' cioe' essere letto o scritto sia se il banco attivo e' lo 0, sia se e' l'
1. La commutazione da un banco all'altro avviene settando il bit 5 di STATUS
(bit RP0), se vale 0 e' attivo il banco 0, se vale 1 e' attivo il banco 1. I 68
registri utente dall'indirizzo 12 (0CH) in poi sono invece sempre visibili e non
sono influenzati dal banco attivo. Il registro STATUS contiene anche i flags, il
bit 0 e' il flag C, il bit 2 e' il flag Z.

MCU PIC 16F84 by MicroChip

Pagina 5 di 37

Picture by Microchip

ISTRUZIONI
Il PIC dispone di un set di 35 istruzioni elementari, e ogni istruzione occupa
una sola locazione della memoria programma. Quasi tutte le istruzioni
vengono eseguite in 4 cicli di clock, un PIC cloccato a 4Mhz e' percio' in grado
di eseguire 1milione di istruzioni al secondo (1 mips) e ogni istruzione dura
1S. Le istruzioni di branch (salto) possono richiedere 8 cicli di clock anziche'
4. Nella terminologia Microchip un gruppo di 4 cicli di clock e' detto "ciclo", per
cui le istruzioni vengono eseguite in uno o due cicli.
Le aree di memoria su cui si puo' agire sono i registri della memoria dati e il
registro accumulatore W (che non fa parte dell'area dati, ma e' un ulteriore
registro hardware interno al PIC utilizzato nelle operazioni aritmetico logiche).

MCU PIC 16F84 by MicroChip

Pagina 6 di 37

Nelle seguenti liste delle istruzioni si deve considerare n come un valore


(literal) a 8 bit, addr come un valore che indica una locazione di memoria (di
programma o dati a seconda dell'istruzione) e b come un valore compreso tra
0 e 7 che indica il bit all'interno di un byte. Inoltre sono riportati gli effetti sui
flags, una X indica che il valore finale del flag dipende dal risultato
dell'operazione. Molte istruzioni che operano su registri permettono di
trasferire il risultato dell'operazione o nel registro W o nel registro stesso
chiamato in causa dall'istruzione, queste due possibilita' si specificano
scrivendo ,w o ,f alla fine dell'istruzione.

ISTRUZIONI DI CARICAMENTO DATI A 8 BIT


movlw n

n -> W

movwf addr

W -> (addr)

movf

addr,w

Z=X

(addr) -> W

movf

addr,f

Z=X

(addr) -> (addr)

swapf addr,w

swap(addr) -> W

swapf addr,f

swap(addr) -> (addr)

clrf
clrw

addr

Z=1

(addr) = 0

Z=1

W = 0

Le istruzioni movlw e movwf non alterano i flags. L'istruzione movf invece


modifica il flag Z, che risulta settato (s=set) se il valore caricato e' 0, e
resettato (c=clear) se il valore caricato e' diverso da 0. Nell' ultima movf il
valore viene trasferito nella stessa locazione da cui viene preso, il suo valore
percio' non cambia, ma, visto che il flag Z viene modificato, e' un modo rapido
per verificare se contiene zero.
L' istruzione swapf scambia i nibbles (i 4 bit superiori e i 4 bit inferiori) della
locazione addr, e deposita il risultato nell'accumulatore o nella locazione
stessa. In alcuni casi puo' essere vantaggioso usarla, oltre che per swappare i
nibble, anche come spostamento perche' non altera i flags (per esempio e'
usata per salvare il registro STATUS durante un interrupt).
Le istruzioni clrf e clrw sono un caricamento diretto del valore 0 in una
locazione dati o nell'accumulatore. Entrambe impostano il flag Z a 1.

MCU PIC 16F84 by MicroChip

Pagina 7 di 37

ISTRUZIONI ARITMETICHE
addlw n

C=X Z=X

W = W + n

addwf addr,w

C=X Z=X

W = W + (addr)

addwf addr,f

C=X Z=X

(addr) = W + (addr)

sublw n

C=X Z=X

W = n - W

subwf addr,w

C=X Z=X

W = (addr) - W

subwf addr,f

C=X Z=X

(addr) = (addr) - W

incf

addr,w

Z=X

W = (addr) + 1

incf

addr,f

Z=X

(addr) = (addr) + 1

decf

addr,w

Z=X

W = (addr) - 1

decf

addr,f

Z=X

(addr) = (addr) - 1

Le istruzioni di somma e sottrazione alterano entrambi i flags C e Z. Il flag Z e'


sempre clear, viene settato solo quando il risultato dell'operazione e' 0,
Durante una somma il flag C e' sempre resettato, viene settato solo se
l'operazione causa un overflow. Durante una sottrazione il flag C e' sempre
settato, viene resettato solo se l'operazione causa un prestito. Controllando lo
stato di questi flags dopo una sottrazione e' possibile stabilire se un numero e'
maggiore, minore o uguale a un'altro.
Le istruzioni di incremento decremento settano il flag Z se il risultato
dell'operazione e' 0.

ISTRUZIONI LOGICHE
andlw n

Z=X

W = W AND n

andwf addr,w

Z=X

W = W AND (addr)

andwf addr,f

Z=X

(addr) = W AND (addr)

iorlw n

Z=X

W = W OR n

iorwf addr,w

Z=X

W = W OR (addr)

iorwf addr,f

Z=X

(addr) = W OR (addr)

xorlw n

Z=X

W = W XOR n

xorwf addr,w

Z=X

W = W XOR (addr)

xorwf addr,f

Z=X

(addr) = W XOR (addr)

comf

addr,w

Z=X

W = NOT(addr)

comf

addr,f

Z=X

(addr) = NOT(addr)

Tutte le istruzioni logiche settano il flag Z nel caso che il risultato


dell'operazione sia 0.

MCU PIC 16F84 by MicroChip

Pagina 8 di 37

ROTAZIONI E SET/RESET DI SINGOLI BIT


rlf

addr,w

C=X

W = rlf(addr)

rlf

addr,f

C=X

(addr) = rlf(addr)

rrf

addr,w

C=X

W = rrf(addr)

rrf

addr,f

C=X

(addr) = rrf(addr)

bcf

addr,b

Bit b di (addr) = 0

bsf

addr,b

Bit b di (addr) = 1

Le istruzioni rlf e rrf ruotano rispettivamente a sinistra o a destra il contenuto di


un registro. Il risultato e' depositato nell'accumulatore o nella locazione di
memoria stessa e la rotazione avviene sempre attraverso il flag C.

SALTI E SUBROUTINE
btfsc

addr,b

Skip se bit b di (addr) = 0

btfss

addr,b

Skip se bit b di (addr) = 1

incfsz addr,w

W = (addr)+1

incfsz addr,f

(addr) = (addr)+1

decfsz addr,w

W = (addr)-1

decfsz addr,f

(addr) = (addr)-1

goto

addr

Salto a addr

call

addr

Chiamata di subroutine

return
retlw
retfie

Skip se W = 0
Skip se (addr) = 0

Skip se W = 0
Skip se (addr) = 0

Ritorno da subroutine
n

Ritorno da subr. con valore in W


Ritorno da interrupt

Le istruzioni per il salto incondizionato (goto, call, return, retlw, retfie)


richiedono sempre 2 cicli macchina, mentre i salti condizionati (skip) ne
richiedono 2 solo se la condizione e' verificata.

ISTRUZIONI DI CONTROLLO SISTEMA


nop

Nessuna operazione

clrwdt

Azzera watch dog timer

sleep

Standby mode

MCU PIC 16F84 by MicroChip

Pagina 9 di 37

RIEPILOGO ISTRUZIONI
Il valore di d puo' valere 0 o 1 (o, rispettivamente, W o F), addr indica un
registro dati oppure un indirizzo di programma nelle istruzioni goto e call, b
indica un bit all'interno di un byte, n e' un valore costante (literal) a 8 bit.
Mnemonic operands Cyc. Flag Description
Byte oriented file register operations
addwf

addr,d

andwf

addr,d

d = W AND (addr)

clrf

addr

(addr) = 0

W = 0

clrw

C d = W + (addr)

comf

addr,d

d = NOT(addr)

decf

addr,d

d = (addr) - 1

decfsz

addr,d

1(2)

incf

addr,d

incfsz

addr,d

1(2)

iorwf

addr,d

d = W OR (addr)

movf

addr,d

(addr) -> d

movwf

addr

W -> (addr)

rlf

addr,d

C d = rlf(addr)

rrf

addr,d

C d = rrf(addr)

subwf

addr,d

swapf

addr,d

xorwf

addr,d

d = (addr)-1
Z

Skip se d = 0

d = (addr) + 1
d = (addr)+1

Skip se d = 0

C d = (addr) - W
swap(addr) -> d

d = W XOR (addr)

Bit oriented file register operations


bcf

addr,b

Bit b di (addr) = 0

bsf

addr,b

Bit b di (addr) = 1

btfsc

addr,b

1(2)

Skip se bit b di (addr) = 0

btfss

addr,b

1(2)

Skip se bit b di (addr) = 1

Literal and control operations


addlw

andlw

call

addr

Chiamata di subroutine

Azzera watch dog timer


Salto a addr

clrwdt

C W = W + n
W = W AND n

goto

addr

iorlw

movlw

n -> W

Nessuna operazione

nop

W = W OR n

MCU PIC 16F84 by MicroChip


retfie

Pagina 10 di 37

Ritorno da interrupt

Ritorno da subr. con valore in W

return

Ritorno da subroutine

sleep

Standby mode

retlw

sublw

xorlw

C W = n - W
W = W XOR n

NOTE PER LA PROGRAMMAZIONE


GENERALITA'
Le istruzioni dei PIC sono molto veloci ma anche molto elementari. Questo
significa che e' necessario un certo studio e una certa quantita' di istruzioni
anche per realizzare le piu' semplici strutture software (come ad esempio i
loop). Solo alcune operazioni alterano i flags, e i salti condizionati sono dei
semplici skip dell'istruzione successiva. Questo obbliga a pensare alle
condizioni in negativo, nel senso che invece del consueto "salta a se si
verifica che" si deve ragionare come "skip se non si verifica che" seguito da un
salto incondizionato (goto).

EQU
Le 68 locazioni della memoria dati utente, e le 12 per il controllo hardware del
PIC, possono essere indirizzate scrivendo direttamente il loro indirizzo nelle
istruzioni (gli addr nella lista istruzioni precedente) o usando la direttiva EQU
per specificarne un nome simbolico. Il registro di stato all'indirizzo 3 puo'
essere infatti definito con una EQU:
STATUS

EQU

E quindi ci si puo' riferire a lui indifferentemente come STATUS o come


indirizzo 3:
btfss
btfss

STATUS,2
3,2

;Skip se bit 2 di status = 1


;Skip se bit 2 di status = 1

In realta' anche i flags possono essere definiti con una EQU:


Z

EQU

MCU PIC 16F84 by MicroChip

Pagina 11 di 37

...e quindi ci si puo' riferire al flag Z nel seguente modo:


btfss

STATUS,Z

;Skip se il flag Z e' settato

Anche alle locazioni utente puo' essere assegnato un nome, e quindi possono
essere usate come se fossero 68 registri ciascuno col suo nome:
var1
var2
contat
...

EQU
EQU
EQU

12
13
14

INCLUDE
Per ogni tipo di pic esiste un file gia' pronto (fornito assieme all'assemblatore)
che contiene tutte le definizioni standard. Per esempio in un programma scritto
per il pic 16F84A andra' incluso il file P16F84a.INC con la direttiva:
INCLUDE

"P16F84a.INC"

La direttiva INCLUDE puo' servire anche per incorporare nel proprio


programma degli altri pezzi di programma scritti in uno o piu' files esterni.

ORG
La direttiva di compilazione ORG (origine) che serve per due scopi differenti, a
seconda che si applichi al programma o all'area dati. Nel primo caso serve per
indicare all'assemblatore l'indirizzo fisico dove dovranno essere caricate le
istruzioni successive (generalmente un programma inizia sempre all' ORG 0).
Nel secondo caso invece permette di definire l'indirizzo fisico di partenza di
un'area dati, e di definirne poi l'occupazione tramite nomi simbolici e la
direttiva RES (riserva). Le nostre 3 variabili dell'esempio precedente
potrebbero per esempio essere dichiarate con:

var1
var2
contat
...

ORG
RES
RES
RES

0CH
1
1
3

Queste righe indicano al compilatore di riservare 1 byte all'indirizzo 12 (0CH) e


chiamarlo var1, un altro byte all'indirizzo 13 con il nome var2 ecc... Questo
puo' essere comodo ad esempio per spostare un programma da un tipo di pic
all'altro, in quanto basta cambiare l'org dell'area dati senza dover riscrivere

MCU PIC 16F84 by MicroChip

Pagina 12 di 37

tutti gli indirizzi fisici associati a ciascun nome simbolico. Come si vede
nell'esempio, con res si puo' riservare anche piu' di un byte, la variabile contat
e' a 24 bit, e possiamo riferirci ai suoi tre bytes come contat, contat+1 e
contat+2.

#DEFINE
Esiste poi la possibilita' di ridefinire dei comandi comuni usati spesso
assegnando loro un nome piu' comodo. Per esempio per impostare il banco
attivo nella prima parte dell'area dati (quella che controlla l'hardware) si deve
settare o resettare il bit RP0 del registro status:
bsf
bcf

STATUS,RP0
STATUS,RP0

;attiva banco 1
;attiva banco 0

Per evitare di scrivere ogni volta queste istruzioni le possiamo ridefinire con le
parole Bank1 e Bank0:
#define
#define

Bank0
Bank1

bcf STATUS,RP0
bsf STATUS,RP0

A questo punto nel programma possiamo semplicemente scrivere Bank0 o


Bank1

CONFIGURAZIONE HARDWARE
I pic dispongono di un registro di configurazione hardware, che viene scritto
una sola volta al momento della programmazione, e che stabilisce il
funzionamento di alcuni circuiti interni, come il watch dog timer (wdt) e
l'oscillatore di clock. Questa operazione e' conosciuta anche con il nome di
"settaggio dei fuses". Nei micro dotati di memoria flash non si parla
naturalmente di fusibili e questi settaggi possono essere cambiati
semplicemente riprogrammandoli. Ogni programma per pic inizia con una
intestazione in cui si dichiara, oltre al tipo di micro usato e al formato di default
dei numeri (decimale, esadecimale ecc...), anche la configuration word che ne
determinera' il funzionamento (specificata con __CONFIG):
PROCESSOR
RADIX
INCLUDE
__CONFIG

16F84a
DEC
"P16F84a.INC"
1111111110001b

MCU PIC 16F84 by MicroChip

Pagina 13 di 37

Il significato completo dei singoli bit della configuration word e' scritto nel
datasheet, e varia per ogni tipo di micro. Quella qui riportata significa:
oscillatore al quarzo, wtd disabilitato, power up timer abilitato, protezione
programma disabilitata. Se si vuole abilitare il wdt si deve mettere a 1 il bit 2
(ricordandosi sempre che il bit 2 e' il terzo bit a partire da destra!).

ESEMPIO DI PROGRAMMA
Il seguente e' un semplice esempio di programma completo. Funzionalmente
non fa altro che leggere i 3 stati logici presenti sugli ingressi RB0..RB2 e
trasferirli pari pari sulle uscite RA0..RA2.
;---------------------------------------------------------------------; TEST1.ASM - Programma di test per PIC
;---------------------------------------------------------------------; RIEPILOGO USO PORTE:
;
; RA0 out
Uscita 0
; RA1 out
Uscita 1
; RA2 out
Uscita 2
; RA3 out
Non usato, sempre 0
; RA4 out(oc)
Non usato, sempre 0
;
; RB0 in(p-up) Ingresso 0
; RB1 in(p-up) Ingresso 1
; RB2 in(p-up) Ingresso 2
; RB3 in(p-up) Non usato
; RB4 in(p-up) Non usato
; RB5 in(p-up) Non usato
; RB6 in(p-up) Non usato
; RB7 in(p-up) Non usato
;---------------------------------------------------------------------; DEFINIZIONI
;---------------------------------------------------------------------PROCESSOR
16F84a
;clock 4 Mhz
RADIX
DEC
INCLUDE
"P16F84a.INC"
__CONFIG
1111111110001b
#define
#define

Bank0
Bank1

bcf STATUS,RP0
bsf STATUS,RP0

;---------------------------------------------------------------------; PROGRAMMA
;---------------------------------------------------------------------ORG
0
goto inizio
;----------INTERRUPT HANDLER (se usato)
ORG
4

MCU PIC 16F84 by MicroChip

istruzioni

Pagina 14 di 37

...

;qui vanno le eventuali

...

;per la gestione degli

interrupt

;----------PREDISPOSIZIONE HARDWARE
inizio

Bank1
clrf TRISA
bcf OPTION_REG,7
Bank0

;attiva il banco 1
;Predispone porta A come uscite
;Attiva pull-ups su porta B
;attiva il banco 0

;----------CICLO PRINCIPALE DEL PROGRAMMA


mainloop

movf PORTB,w
andlw 00000111B
movwf PORTA
goto mainloop

;legge porta B
;maschera i 3 bit inferiori
;li scrive sulla porta A
;ripete il ciclo

;---------------------------------------------------------------------END

LOOP
Un loop (ciclo) puo' essere realizzato in modo semplice se il numero di
iterazioni non e' maggiore di 256. In questo caso basta un registro a 8 bit
come contatore (Impostando 0 in contat il si ottengono 256 iterazioni):
contat

ciclo

EQU

0CH

movlw
movwf
...
...
...
decfsz
goto

30
contat

;predispone 30 iterazioni

contat,f
ciclo

;decrementa contat, skip se zero


;ritorna a ciclo se non zero

Se occorrono piu' di 256 iterazioni si devono usare piu' di registri. Per esempio
con due registri (contenenti le parti alta e bassa di un numero a 16 bit) si
possono realizzare fino a 65536 iterazioni:
lcont
hcont

EQU
EQU

movlw
movwf
movlw

0CH
0DH

85
lcont
4

MCU PIC 16F84 by MicroChip

ciclo

movwf
...
...
...
decf
incf
btfsc
decf
movf
iorwf
btfss
goto

Pagina 15 di 37

hcont

;predispone 1109 iterazioni

lcont,f
lcont,w
STATUS,Z
hcont,f
lcont,w
hcont,w
STATUS,Z
ciclo

;dec parte bassa


;incr. per controllare se torna a 0
;skip se non tornato a zero
;se tornato a zero decrementa hcont
;carica lcont
;mette in or con hcont
;se tutto zero termina
;altrimenti next

Impostando 0 sia in lcount che in hcount si ottengono 65536 iterazioni.

LOOKUP TABLE (TABELLE DATI)


Usando le istruzioni addwf e retlw e' possibile creare delle tabelle dati nel
programma leggibili con un indice. E' infatti permesso sommare un valore alla
parte bassa del program counter (PCL), quindi basta impostare in W l'indice
dell'elemento voluto e fare una call alla tabella. L' indice parte da 0 (per il
primo elemento) e puo' andare fino a 254. Bisogna ricordare che l'istruzione
addwf lavora solo su 8 bit e non e' in grado di aggiornare la parte alta del
program counter, questo significa che la memoria programma puo' essere
pensata come composta da 4 pagine da 256 bytes ciascuna... le tabelle non
devono percio' "sconfinare" dai bordi delle pagine. Inoltre va anche ricordato
che ad ogni addwf la parte alta del prog.counter viene caricata con il valore del
registro PCLATH (presente all'indirizzo dati 0AH), e pertanto prima di
chiamare una tabella questo registro va impostato a seconda della pagina in
cui risiede la tabella stessa.
table1

addwf
retlw
retlw
retlw
retlw
retlw
....

PCL,f
126
0
44
255
188

movlw
movwf
movlw
call

....
PCLATH
4
table1

;pagina della tabella


;punta il 5 elemento
;acquisisce in W il valore
;letto dalla tabella (188)

Indirizzi
000H - 0FFH

PCLATH
0

MCU PIC 16F84 by MicroChip

Pagina 16 di 37
100H - 1FFH

200H - 2FFH

300H - 3FF

Se si vuole usare un registro come puntatore (per esempio PTAB) agli


elementi della tabella possiamo generalizzare la chiamata includendo nella
tabella stessa anche l'impostazione del PCLATH (in questo caso l'indice puo'
arrivare al massimo a 251):
PTAB

EQU

0CH

table1

movlw
movwf
movf
addwf
retlw
retlw
retlw
retlw
retlw
....

....
PCLATH
PTAB,w
PCL,f
126
0
44
255
188

movlw
movwf
call

2
PTAB
table1

;pagina

;carica 2 nel registro puntatore


;acquisisce in W il valore 44

RITARDI SOFTWARE
In alcuni programmi puo' essere necessario regolare con precisione la durata
temporale di una routine software, inserendo qua e la delle apposite istruzioni
che hanno l'unico scopo di creare un piccolo ritardo. Usando l'istruzione NOP
e' possibile inserire un ritardo di 1S (con clock di 4Mhz), se serve un ritardo
di 2S possono essere scritte due NOP una dietro l'altra, oppure, per
risparmiare memoria programma, si puo' usare una goto fittizia che salta
all'istruzione successiva (una goto infatti dura sempre 2S e occupa una sola
locazione della memoria programma... l'uso delle goto fittizie e' pero' uno stile
di programmazione un po' scorretto, e va usato solo se e' assolutamente
necessario risparmiare quelle poche locazioni di memoria, altrimenti e'
sicuramente preferibile usare due NOP):
Ritardo 1S:

nop

Ritardo 2S:

nop
nop

MCU PIC 16F84 by MicroChip


oppure:

Pagina 17 di 37

goto $+1

Ritardi maggiori possono essere ottenuti con combinazioni di NOP e goto


fittizie. Quando si arriva ai 7S di ritardo diventa conveniente usare un loop,
con il quale si possono realizzare ritardi di centinaia di S. Il ciclo formato dalle
seguenti 4 istruzioni per esempio impiega 301S per essere eseguito da un
PIC cloccato a 4 Mhz:

ciclo

movlw
movwf
decfsz
goto

100
contat
contat,f
ciclo

;predispone 100 iterazioni


;decrementa contat, skip se zero
;prossimo ciclo se non zero

Da dove saltano fuori i 301S? Le prime 2 istruzioni durano 1 S ciascuna, poi


il decremento e il goto vengono eseguiti 99 volte, e il decremento saltando il
goto una sola volta all'ultimo ciclo. Quando il decremento non da risultato 0
dura 1 S, seguito dal goto che dura 2 S, quando invece il decremento da
come risultato 0 dura 2 S e il goto e' saltato... quindi:
S = 2 + 3*(n-1) + 2

2 + 3*99 + 2 = 301

Se vogliamo sapere che valore dobbiamo dare al registro contatore per


ottenere i S desiderati basta fare:
n = ( S - 1 ) / 3

infatti se vogliamo 301 S allora n = (301-1)/3 = 100


Si puo' notare che con il ciclo non e' possibile ottenere 302 e 303 S in quanto
dando il valore 101 al contatore otteniamo gia' 304S. Nel caso si
desiderassero i 302S si scrive una NOP prima o dopo del ciclo, se si
desiderassero i 303S si puo' usare una goto fittizia:

ciclo

goto
movlw
movwf
decfsz
goto

$+1
100
contat
contat,f
ciclo

;goto fittizia 2 cicli


;predispone 100 iterazioni
;decrementa contat, skip se zero
;prossimo ciclo se non zero

MCU PIC 16F84 by MicroChip

Pagina 18 di 37

PORTE DI INGRESSO/USCITA

Picture by Sergio Tanzilli

Il PIC 16F84 ha 13 pin usabili come ingressi o uscite singolarmente


programmabili. La scelta se rendere un pin un ingresso o un' uscita viene
effettuata scrivendo un valore nei registri TRISA e TRISB presenti nel banco1.
TRISA controlla i pin della porta A (RA0..RA4), il bit 0 e' associato al pin RA0, il
bit 4 e' associato al pin RA4. Uno 0 in questi bit rende i pin delle uscite
(0=out), un 1 invece li rende degli ingressi (1=in). TRISB e' il registro che
controlla invece i pin della porta B (RB0..RB7). All'accensione, o dopo un
reset, TRISA e TRISB contengono tutti i bit a 1, pertanto i pin sono
programmati automaticamente come ingressi. E' possibile cambiare la
configurazione dei pin in ogni momento durante il programma, le uscite
possono cioe' essere trasformate in ingressi e viceversa. Una volta scelto
quali pin devono essere delle uscite e quali degli ingressi, si puo' accedere al
loro valore tramite i registri PORTA e PORTB presenti nel banco0.
I pin configurati come uscite sono di tipo push-pull, sono cioe' in grado sia di
assorbire corrente dal positivo dell'alimentazione (25mA), sia di fornirla verso
massa (20mA), ad eccezione di RA4 che in uscita e' di tipo open-collector (che
quindi la puo' solo assorbire come se fosse un interruttore che si chiude verso
massa). I pin configurati come ingressi sono in alta impedenza e quindi
bisogna ricordarsi di non lasciare mai scollegati quelli inutilizzati, ma di
collegarli a +Vcc con una resistenza da 10K. Lo stesso discorso vale per i pin
della porta B, a meno che non si attivino le resistenze interne di pull-up con le
istruzioni:
BANK1
BCF

OPTION_REG,7

;Attiva pull-ups su porta B

Se vengono attivate le resistenze di pull-up della porta B e' possibile collegare


direttamente ai suoi pin interruttori, commutatori e pulsanti (che chiudono
verso massa) senza bisogno di nessun altro componente esterno (i rimbalzi
meccanici di questi componenti vanno pero' eliminati scrivendo apposite
routines software).
Va ricordato che ha senso leggere (dalla porta o dalla portb) i pin programmati
come ingressi, ma non ne ha invece leggere quelli programmati come uscite, il

MCU PIC 16F84 by MicroChip

Pagina 19 di 37

loro valore va semplicemente ignorato. Allo stesso modo ha senso scrivere in


quelli programmati come uscite ma non ne ha in quelli programmati come
ingressi, a meno che non si preveda di trasformarli in uscite, in questo caso
infatti l'uscita assume subito l'ultimo valore che e' stato scritto in quel bit della
porta mentre il pin era ancora un ingresso.
L'esempio seguente mostra come configurare PORTA come uscita e scriverci
un valore (ricordando che per comodita' abbiamo ridefinito le istruzioni di
scambio banco: #DEFINE BANK0 BCF STATUS,RP0 e #DEFINE BANK1
BSF STATUS,RP0):
BANK1
CLRF
BANK0
MOVLW
MOVWF

TRISA

;attiva banco 1
;azzera bit di TRISA (tutte uscite)
;ritorna al banco 0

11000B
PORTA

;scrittura sulla porta

Usando le istruzioni BCF e BSF e' possibile resettare o settare un singolo bit
di una qualsiasi cella di memoria o di un pin di ingresso/uscita. Il seguente
esempio mostra come generare un breve impulso di 1S (con clock 4Mhz) dal
pin di uscita RA2 (naturalmente deve essere attivo il banco0):
BSF
BCF

PORTA,2
PORTA,2

;manda a 1 il pin di uscita


;lo riporta a 0

La ridefinizione (#DEFINE) puo' essere usata anche per assegnare un nome


simbolico (alias) a un qualsiasi pin di ingresso/uscita, e anzi questa e' una
pratica corretta di programmazione, perche' permette modifiche piu' semplici
al software e una maggiore portabilita'. Per esempio potrebbe essere comodo
dare il nome "SEGNALE" al pin RA2:
#DEFINE

SEGNALE

PORTA,2

A questo punto il nostro impulso lo possiamo anche generare scrivendo:


BSF
BCF

SEGNALE
SEGNALE

;manda a 1 il pin di uscita


;lo riporta a 0

Detto questo sappiamo come configurare i pin delle porte, come scrivere o
leggere su di essi (e anche su uno singolo), sappiamo quanta corrente ci puo'
passare e che RA4 e' un open-collector (o meglio open-drain), e abbiamo
visto come attivare le resistenze di pull-up della porta B.

MCU PIC 16F84 by MicroChip

Pagina 20 di 37

TRASMISSIONE SERIALE SOFTWARE


Per mettere in pratica quanto, visto sia riguardo alla parte software che
hardware, possiamo realizzare un semplice trasmettitore seriale che invia
continuamente dal pin RA4 i bit letti dalla porta B. Questo sistema puo' essere
utile per il debug dei programmi, infatti permette di inviare verso un PC il
contenuto di qualsiasi cella di memoria per verificarne il contenuto. Il
programma sul PIC trasmette un bytes ogni 10mS circa nel classico formato
asincrono start-stop 9600 8-N-1. Per poter leggere i dati in arrivo sul PC ho
scritto un semplicissimo visualizzatore binario-esadecimale-decimale (provato
su W95/98):

Lo schema del circuito e' riportato nella figura seguente. I pin RB0..RB7 sono
gli ingressi, il pin RA4 e' l'uscita che comanda un transistor adattatore di livello
TTL/EIA, in modo da inviare verso il PC le tensioni corrette sia in ampiezza
che in polarita'. La tensione negativa richiesta dalla seriale del PC e' presa dal
pin TX della seriale stessa, infatti, visto che il PC non deve trasmettere nulla,
su questo pin sono sempre presenti -12V. Il terminale chiamato RX e' la
ricezione del PC, quello chiamato TX e' la sua trasmissione (e' evidente che il
nostro PIC deve trasmettere verso la ricezione del PC, in caso di dubbi sulla
piedinatura della porta seriale o sul formato dei dati vedere l'apposita pagina).

MCU PIC 16F84 by MicroChip

Pagina 21 di 37

Il programma del PIC: txser.asm


Include per pic16f84a: P16F84a.INC
L'eseguibile: TXSER.HEX

Foto del prototipo montato su bredboard.

POTENZIAMENTO ASSEMBLER
Si e' visto che usando la direttiva di ridefinizione #DEFINE e' possibile
condensare un'intera istruzione in un nome simbolico, piu' comodo da
ricordare e piu' semplice da scrivere. Per esempio i comandi BANK0 e BANK1
usati precedentemente sono ridefinizioni delle istruzioni complete "BCF
STATUS,RP0" e "BSF STATUS,RP0".
Il compilatore mette anche a disposizione la direttiva macro che permette di
racchiudere con un nome unico un gruppo di istruzioni, e consente anche di
usare degli argomenti, cioe' dei parametri che assumono di volta in volta il
valore necessario.
Per comprendere l'utilita' di una macro vediamo un semplicissimo esempio.
Possiamo pensare alle seguenti due istruzioni che servono per caricare 44
nella variabile contatore (che naturalmente va definita prima con una equ o
una res):
MOVLW
MOVWF

44
CONTATORE

MCU PIC 16F84 by MicroChip

Pagina 22 di 37

Non esiste nessun modo per caricare direttamente 44 nella variabile, pero'
possiamo scrivere una macro che racchiuda queste due istruzioni e usarla
come se fosse una nuova istruzione singola (macroistruzione), per esempio:
LDF

CONTATORE,44

...che vorrebbe dire: carica nella variabile contatore il valore 44... la macro da
usare per "creare" questa nuova istruzione e':
LDF

MACRO arg1,arg2
MOVLW arg2
MOVWF arg1
ENDM

Il nome della macro e' LDF, arg1 e arg2 sono gli argomenti, in pratica i nomi e
i valori che noi scriveremo quando useremo l'istruzione LDF e che il
compilatore pensera' poi a sostituire automaticamente nelle istruzioni
all'interno della macro..
Usando ridefinizioni e macro e' quindi in un certo modo possibile "costruire"
delle nuove istruzioni, se non fisicamente per il processore, almeno
idealmente per il programmatore. Per prova ho voluto aggiungere nuove
istruzioni come i salti condizionati dai flag (Jc Jnc Jz Jnz) che sui pic
mancano.
Nel file pwrasm.inc sono contenute le ridefinizioni e le macro necessarie per
implementare le nuove istruzioni (e un breve riepilogo), questo file puo' essere
copiato in ogni programma con un copia/incolla (sconsigliato) o incluso come
le altre definizioni per il pic (metodo corretto).
Le nuove istruzioni comprendono la serie completa di salti (J..) e skip (SK)
condizionati dal valore dei flags (Z C), Abbiamo anche il salto condizionato
dal valore di un bit, per esempio JNB effettua un salto se il bit testato vale 0
(clear). Oltre al caricamento diretto in una variabile (LDF) abbiamo anche lo
spostamento da una variabile a un'altra: MOVFF var1,var2 che trasferisce
var1 in var2. Molte istruzioni lavorano su 16, 24 e 32 bit, comprese rotazioni,
shift, operazioni logiche e aritmetiche. I singoli bit possono essere anche
mossi da un registro all'altro (MOVB) o cambiati di stato (BTG). Ci sono poi le
istruzioni di attesa condizionata sul valore di un bit (WAITB WAITNB) e di
ritardo (DELAY). E per finire la comodissima DJNZ che permette di
decrementare una variabile e saltare se non e' arrivata a zero. Le
macroistruzioni per il cambio banco sono 4 per usare anche i pic 16F628 e la
serie 16F87x.

MCU PIC 16F84 by MicroChip

Pagina 23 di 37

Le macro non sono subroutine, in ogni punto del programma in


cui e' scritta una macro questa verra' sostituita con le
corrispondenti istruzioni assembler.
Una macro puo' utilizzare il registro W per compiere il suo lavoro,
pertanto bisogna tenere in considerazione che il suo valore e
quello dei flags possono essere alterati. Si deve infatti sempre
ricordare che la macro e' una scorciatoia o una comodita' per noi,
ma che in ogni caso bisogna sempre ragionare come se al suo
posto avessimo scritto per esteso tutte le istruzioni in essa
contenute. Il programma piu' compatto e' solo a livello di sorgente,
l'eseguibile rimane comunque lungo come se avessimo scritto
tutte le istruzioni per esteso (e in alcuni casi puo' anche essere
piu' lungo).
La durata di una macroistruzione e' la somma delle durate di tutte
le istruzioni che la compongono.
L'occupazione di memoria programma di una macro e' la somma
delle occupazioni di memoria di tutte le istruzioni che la
compongono, e questa occupazione si ripete per ogni punto in cui
la macro e' scritta.

CONFRONTO TRA NUMERI A 8 BIT


In un programma puo' essere necessario confrontare due valori per stabilire
quale dei due e' maggiore (o minore) dell'altro , o se sono uguali, o tutte le
altre combinazioni possibili (che in tutto sono 6). Il confronto viene effettuato
facendone la sottrazione e verificando poi lo stato dei flags C e Z.
Infatti durante una sottrazione viene settato il flag Z nel caso in cui il risultato
sia 0, e resettato il flag C nel caso in cui si verifichi un prestito. Esistono due
istruzioni di sottrazione, la prima e' sublw (dal valore specificato viene sottratto
l'accumulatore), la seconda e' subwf (dalla variabile specificata viene sottratto
l'accumulatore):
SUBLW 45
SUBWF var,W
SUBWF var,F

=
=
=

W = 45 - W
W = var - W
var = var - W

Facendo la sottrazione si puo' pensare nei termini di: confronto il valore con
quello dell'accumulatore, per vedere se l'accumulatore e' maggiore, minore,
uguale ecc...
Condizioni

Impostazione dei flags

W = var (uguale)

Z=1

MCU PIC 16F84 by MicroChip

Pagina 24 di 37

W <> var (diverso)


W > var (maggiore)
W < var (minore)
W >= var (maggiore o uguale)
W <= var (minore o uguale)

Z=0
C=0
Z=0 and C=1
Z=1 or C=0
C=1

Come si puo' vedere la condizione di uguaglianza e' segnalata dal flag Z


settato (il C in questo caso e' irrilevante) e quella di disuguaglianza dal flag Z
resettato, pertanto puo' essere semplicemente testata con le istruzioni btfss o
btfsc. Le condizioni "minore" e "maggiore o uguale" sono invece abbastanza
complesse da codificare per il fatto che devono essere tenuti in
considerazione entrambi i flags C e Z, ma il lavoro in pratica si semplifica
scambiando l'ordine degli operandi nel seguente modo:
Se devo verificare se
Se devo verificare se
Se devo verificare se
Se devo verificare se

A>B faccio B-A, se C vale 0 la condizione e' vera.


A<B faccio A-B, se C vale 0 la condizione e' vera.
A<=B faccio B-A, se C vale 1 la condizione e' vera.
A>=B faccio A-B, se C vale 1 la condizione e' vera.

ESEMPI:
;IF pippo>26 THEN GOTO label
MOVF
SUBLW
BTFSS
GOTO

pippo,W
26
STATUS,C
label

;IF pippo<120 THEN GOTO label


MOVLW
SUBWF
BTFSS
GOTO

120
pippo,W
STATUS,C
label

;IF pippo<=pluto THEN GOTO label


MOVF
SUBWF
BTFSC
GOTO

pippo,W
pluto,W
STATUS,C
label

;IF pippo>=pluto THEN GOTO label


MOVF
SUBWF
BTFSC
GOTO

pluto,W
pippo,W
STATUS,C
label

MCU PIC 16F84 by MicroChip

Pagina 25 di 37

RICEZIONE SERIALE
Similmente a quanto visto per la trasmissione seriale software e' possibile
realizzare un ricevitore seriale che acquisisce un byte dal PC e lo presenta in
formato binario sui pin RB0..RB7 (questa volta configurati come uscite).
Questo circuito puo' essere utile a tutti coloro che hanno bisogno di un
convertitore seriale/parallelo per comandare attraverso la porta seriale fino a 8
carichi indipendenti come ad esempio dei rele'.
Lo schema del circuito e' riportato nella figura seguente. I pin RB0..RB7 sono
le uscite, il pin RA0 e' l'ingresso da cui si ricevono i dati attraverso un
transistor adattatore di livello EIA/TTL. Il terminale chiamato TX e' la
trasmissione del PC (in caso di dubbi sulla piedinatura della porta seriale o sul
formato dei dati vedere l'apposita pagina).

Il programma del PIC: rxser.asm


Include per pic16f84a: P16F84a.INC
L'eseguibile: RXSER.HEX

Il programma provvede innanzitutto a configurare la porta B come uscita,


avendo l'accortezza di precaricare 0 nel registro di uscita (portb) prima di
renderla un'uscita. In questo modo all'istante dell'accensione questi pin sono
in alta impedenza e subito dopo diventano delle uscite a zero senza glitch,
cioe' senza stati incerti della durata di qualche microsecondo che potrebbero
creare falsi comandi verso l'esterno, e questo permette di pilotare
tranquillamente dei comuni transistor npn (o array di transistor integrati come
gli ULN2003) come indicato nello schema seguente:

MCU PIC 16F84 by MicroChip

Pagina 26 di 37

Il formato di ricezione e' asincrono start-stop 9600 8-N-1. Gli unici controlli
effettuati sulla corretteza dei dati sono ll valore del bit di start (che deve essere
0) e quello del bit di stop (che deve essere 1). I bytes non corretti vengono
ignorati, quelli corretti vengono portati sull'uscita.
Togliendo il transistor adattatore di livello sia da questo circuito che da quello
del trasmettitore e' possibile collegare tra loro due pic (uno trasmettitore e
l'altro ricevitore) e fare in modo di portare sulle uscite dell'uno i valori in
ingresso dell'altro (puo' servire per esempio per trasferire su due soli fili lo
stato di 8 segnali digitali)... attenzione pero' che questo sistema non e' ad alta
affidabilita', e percio' e' adatto solo per scopi didattici o dove la presenza di
errori di trasmissione e' una cosa accettabile.

LOOP CON ALTO NUMERO DI CICLI


Come gia' detto nel paragrafo sui loop, con un registro a 8 bit si puo' realizzare
un numero di cicli massimo di 256 iterazioni. Con combinazioni di registri, o
con loop nidificati uno dentro l'altro, si puo' raggiungere un numero di iterazioni
qualsiasi, con lo scotto pero' di dover scrivere una notevole quantita' di
istruzioni solo per il controllo del ciclo (ben 8 per un ciclo da 65536 iterazioni
massime, realizzato quindi con 2 registri da 8 bit). Ancora una volta ci vengono
incontro le macro, infatti quelle 8 istruzioni possono essere racchiuse in
un'unica macroistruzione chiamata per esempio Djnz16 (decrementa una
variabile a 16 bit e salta se non e' arrivata a 0):

MCU PIC 16F84 by MicroChip


Djnz16

Pagina 27 di 37

macro
decf
incf

var,addr
var,f
var,w

;dec parte bassa


;incr. per controllare se

btfsc
decf

STATUS,Z
var+1,f

;skip se non tornato a zero


;altrimenti decrementa parte

movf
iorwf
btfss
goto
endm

var,w
var+1,w
STATUS,Z
addr

;carica parte bassa


;mette in or con parte alta
;se tutto zero termina
;altrimenti next

torna a 0
alta

Per realizzare un ciclo a 16 bit con questa macro dobbiamo riservare 2 bytes
per una variabile di conteggio, chiamata ad esempio cont:
ORG
cont

0CH
RES

Poi dobbiamo assegnare ai suoi 2 bytes il valore che ci interessa, per esempio
per 25000 iterazioni abbiamo 97 come parte alta e 168 come parte bassa
(256*97+168=25000), possiamo percio' caricare i due valori in cont
ricordandoci che all'indirizzo piu' basso va messa la parte bassa. L'esempio
seguente mostra come realizzare questo loop a 16 bit:

label

movlw
movwf
movlw
movwf
.....
.....
Djnz16

168
cont
97
cont+1
cont,label

Allo stesso modo possiamo anche pensare ad una macro per un loop a 24 bit
(che puo' eseguire fino a 16.777.216 iterazioni):
Djnz24

macro var,addr
decf
var,f
incfsz var,w

;dec parte bassa


;incr. per controllare se

goto
decf
incf

$+5
var+1,f
var+1,w

;decrementa parte media


;incr. per controllare se

btfsc
decf

STATUS,Z
var+2,f

;skip se non tornato a zero


;se tornato a zero decrementa

torna a 0

torna a 0

MCU PIC 16F84 by MicroChip

Pagina 28 di 37

parte alta
movf
iorwf
iorwf
btfss
goto
endm

var,w
var+1,w
var+2,w
STATUS,Z
addr

;carica parte bassa


;mette in or con parte media
;mette in or con parte alta
;se tutto zero termina
;altrimenti next

L'esempio seguente realizza un ciclo di 900000 iterazioni usando la


macroistruzione djnz24:
ORG
cont

0CH
RES

;riserva 3 bytes per il

contatore
movlw
movwf
movlw
movwf
movlw
movwf
label
.....
.....
Djnz24
label se non 0

160
cont
187
cont+1
13
cont+2
cont,label

;carica parte bassa


;carica parte media
;carica parte alta
;decrementa e salta a

Per quanto riguarda i tempi di esecuzione come gia' detto si calcolano


considerando che al posto della macro ci siano scritte le istruzioni in essa
contenute.
Per scrivere poi piu' comodamente le assegnazioni di valori a variabili a 8,16 e
24 bit possiamo scrivere altre tre macro:
Ldf

macro
movlw
movwf
endm

var,n
n
var

Ldf16

macro
movlw
movwf
movlw
movwf
endm

var,n2,n1
n1
var
n2
var+1

MCU PIC 16F84 by MicroChip


Ldf24

macro
movlw
movwf
movlw
movwf
movlw
movwf
endm

Pagina 29 di 37
var,n3,n2,n1
n1
var
n2
var+1
n3
var+2

Queste ci consentono di assegnare un valore a 8,16 o a 24 bit con un'unica


istruzione. Per esempio, se nel nostro contatore a 16 bit vogliamo caricare
25000 e in quello a 24 bit 900000 possiamo scrivere:
Ldf16

cont,97,168

Ldf24

cont,13,187,160

I vari valori vanno scritti dal piu' significativo (parte alta) al meno significativo
(parte bassa), in questo modo e' anche semplice determinarli scrivendone le
doppiette esadecimali:
25000 = 61A8
900000 = 0DBBA0

Ldf16 cont,61H,0A8H
Ldf24 cont,0DH,0BBH,0A0H

...e la scrittura di un ciclo da 8, 16 o 24 bit richiede sempre solo due righe di


programma:

label

Ldf
....
Djnz

cont,165

;165 iterazioni

cont,label

label

Ldf16 cont,44,0
....
Djnz16 cont,label

label

Ldf24 cont,8,0,133
....
Djnz24 cont,label

;11264 iterazioni

;524421 iterazioni

MCU PIC 16F84 by MicroChip

Pagina 30 di 37

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).
Quando un programma viene scritto
sotto forma di FSM, non presenta pi i
caratteristici tempi morti di attesa tipici
della forma convenzionale di scrittura,
ad esempio per l'attesa della pressione
di un pulsante. Ogni attesa

MCU PIC 16F84 by MicroChip

Pagina 31 di 37

trasformata in uno stato, che controlla


rapidamente se l'evento atteso si
verificato e poi ritorna subito il controllo
allo scheduler che chiama il task
successivo. evidente che con questo
sistema si possono tenere sotto
controllo diverse cose nello stesso
tempo, con una semplicit altrimenti
impossibile.
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
ROUTINE0
ELSE IF STATO=1 THEN
ROUTINE1
ELSE IF STATO=2 THEN
ROUTINE2
ecc...
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

MCU PIC 16F84 by MicroChip


routine0

.....
.....
return

routine1

.....
.....
return

Pagina 32 di 37

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 bytes
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
;-------------------------------------------------------------

MCU PIC 16F84 by MicroChip

Pagina 33 di 37

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
#define

stat

Bank0
Bank1

bcf STATUS,RP0
bsf STATUS,RP0

ORG

0CH

RES

;variabile di stato per task1

;----------------------------------------------------------------------------------; INIZIALIZZAZIONE
;----------------------------------------------------------------------------------ORG
0
;---------------PREDISPOSIZIONE TIMER
Bank1

MCU PIC 16F84 by MicroChip


bcf
bcf
movlw
andwf

Pagina 34 di 37
OPTION_REG,PSA ;prescaler assegnato a tmr0
OPTION_REG,T0CS ;conteggio da clock interno
11111100b
OPTION_REG,f
;divisione prescaler per 32

;---------------INIZIALIZZAZIONE VARIABILI
Bank0
clrf

stat

;Azzera variabile stato per task1

;---------------AVVIA CONTEGGIO
clrf
bcf

TMR0
INTCON,T0IF

;azzera contatore del timer


;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

MCU PIC 16F84 by MicroChip


routine2

Pagina 35 di 37

...
...
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
movlw
subwf
btfsc
goto
endm

task1 Cpje
uguale
Cpje
Cpje
Cpje
...

var,val,addr
val
var,w
STATUS,Z
addr

stat,0,routine0

;confronta stat con 0 e jump a routine0 se

stat,1,routine1
stat,2,routine2
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 bytes 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 bytes 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
bytes 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,

MCU PIC 16F84 by MicroChip

Pagina 36 di 37

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 200mS 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

Il programma dimostrativo dbgdemo.asm legge i bit della porta B e li invia come primo
bytes del campo dati della trama, gli altri 3 bytes sono a zero. Prima del campo dati
viene trasmesso il carattere di sincronizzazione 170, alla fine dei dati viene trasmesso il
checksum (la somma a 8 bit di tutti i precedenti compreso il sincronismo). Viene inviato
un byte ogni 4,096 mS, quando sono stati inviati tutti i 6 bytes della trama completa c'e'

MCU PIC 16F84 by MicroChip

Pagina 37 di 37

una pausa di circa 41mS, poi viene inviata un'altra trama. In contemporanea a questa
attivita' il led sul pin RA0 lampeggia costantemente alla frequenza di circa 1Hz.
ADATTATORE TTL --> EIA "IN CABLE"

Per poter collegare rapidamente il PIC alla seriale del PC, senza montare ogni volta un
apposito circuito, ho racchiuso quest'ultimo all'interno del coperchio di un connettore
DB25 femmina. Il cavetto che esce dal connettore ha 3 poli (massa, +5V e segnale a
livello TTL) e va collegato alla bredboard su cui si sta provando il PIC. In questo modo
ci si puo' dimenticare del problema dell'adattamento e si possono inviare i dati al PC
usando una normale uscita TTL (anche open collector). Lo schema e' il seguente:

Potrebbero piacerti anche