Wednesday, March 28, 2012

Watch-Dog

Il Watch-Dog e' un oscillatore RC secondario inserito all'interno del microcontrollore. Tale oscillatore non richiede alcun componente esterno per funzionare ed e' completamente indipendente da quello che regola il funzionamento del PIC: cio' significa che il Watch-Dog continua a funzionare anche nel caso in cui viene a mancare il segnale di clock esterno. Il Watch-Dog ha associato un tempo di default pari a 18 ms terminato il quale effettua il reset del microcontrollore (oppure, se in modo sleep, lo risveglia). Se si desiderano tempi piu' lunghi e' possibile associare il prescaler al Watch-Dog settando a 0 il bit PSA del registro OPTION_REG e poi impostare il rapporto di moltiplicazione tramite i bit PS0,PS1 e PS2 (il prescaler, quando viene associato al Watch-Dog, funziona da moltiplicatore anziche' da divisore come invece accadeva quando associato al TIMER0). In sostanza lo scopo del Watch-Dog e' quello di attendere il periodo di tempo selezionato (di default 18 ms) e poi scatenare automaticamente il reset del microcontrollore (nel caso il PIC sia in modalita' sleep viene semplicemente risvegliato continuando l'esecuzione del programma). L'unico modo per prevenire il reset del PIC e' quello di resettare il Watch-Dog, cioe' quello di riazzerare il suo conto alla rovescia tramite l'esecuzione dell'istruzione CLRWDT.

Il Watch-Dog e' utile in tutti quei casi in cui non si vuole che, per qualche errore di programmazione, il microcontrollore si blocchi in un loop infinito in qualche parte del programma. Si predispongono le istruzioni di CLRWDT lungo tutto il codice del programma in maniera tale da riazzerare ogni volta il Watch-Dog senza quindi farlo mai scattare; se il PIC si inserisce in qualche loop indesiderato dal quale non puo' piu' uscire non avra' modo di riazzerare il Watch-Dog che quindi provvedera' (dopo il tempo di time-out) al reset. Ovviamente questo esempio vale solo nel caso in cui nel codice di loop non sia presente anche la stessa istruzione CLRWDT.
Il Watch-Dog parte automaticamente all'accensione del PIC, ma puo' essere disabilitato del tutto in fase di programmazione.
In figura si riporta lo schema logico di collegamento del Watch-Dog e del prescaler (in questo caso chiamato postscaler): 



Indirizzamento indiretto

L'indirizzamento indiretto e' una delle caratteristiche piu' interessanti del PIC 16F84. Grazie a questo sistema e' possibile accedere sia in lettura che in scrittura ad un dato registro GPR in modo indirietto, ovverosia specificandone l'indirizzo sotto forma di dato contenuto in un altro registro.
All'apparenza il meccanismo potrebbe apparire come una complicazione non strettamente necessaria, in realta' e' estremamente utile. Un esempio aiutera' a capirne l'ambito di utilizzo: normalmente per poter accedere in scrittura ad un dato registro occorre specificarne l'indirizzo direttamente nell'istruzione.
Ad esempio, il codice seguente carica il registo 0x0C con il valore 0xA3:

MOVLW 0xA3
MOVWF 0x0C

Il valore 0x0C e' ovviamente specificato nell'istruzione MOVWF. Fin qui niente di male. Ma come si potrebbe fare per caricare tutti i registri GPR con il medesimo valore 0xA3? Con il metodo tradizionale l'unico sistema e' quello di ripetere l'istruzione MOVWF per tutti i registri, in questo modo:

MOVLW 0xA3
MOVWF 0x0C
MOVWF 0x0E
MOVWF 0x0F
...
...
...
MOVWF 0x4F

L'operazione richiede quindi la ripetizione di MOVWF ben 68 volte, con grande spreco di memoria di programma. In effetti non si possono creare cicli per ripetere MOVWF in quanto l'indirizzo del registro corrente deve essere necessariamente codificato nell'istruzione (non e' un dato che si puo' incrementare di uno).
Per risolvere il problema ci viene in contro il sistema dell'indirizzamento indiretto. Grazie ad esso si puo' accedere ad un dato registro GPR tramite due registri di appoggio:

FSR
INDF

FSR si trova all'indirizzo di memoria 0x04 ed e' mappato in entrambi i banchi RAM.
INDF si trova all'indirizzo di memoria 0x00 ed e' mappato in entrambi i banchi RAM.
Per vedere la loro posizione in memoria RAM consultare la sezione "Memoria RAM/FLASH/EEPROM".

Il registro FSR serve per specificare l'indirizzo di memoria del registro che vogliamo accedere; per farlo e' sufficiente scrivere il relativo indirizzo in questo registro. Una volta fatto questo, tutte le eventuali letture e scritture effettuate nel registro INDF saranno svolte come se si stesse effettivamente accedendo al registro in questione e puntato da FSR.

Esempio

Scrivere una subroutine che cancella tutti i registri GPR (il primo registro GPR e' alla locazione 0x0C e l'ultimo alla locazione 0x4F)

FSR EQU 0x04
INDF EQU 0x00
STATUS EQU 0x03

:clearall

MOVLW 0x0C ;inizializzo il puntatore al primo registro GPR
MOVWF FSR ;e lo scrivo nel registro FSR
next:
CLRF INDF ;cancello il contenuto di INDF
INCF FSR ;incremento di uno il puntatore
MOVF FSR,0
SUBLW 0x50
BTFSS STATUS,2 ;se FSR=0x50 vai a RETURN, altrimenti vai a next
GOTO next
RETURN

Modalità sleep

Lo sleep e' una particolare modalita' nella quale il microcontrollore cessa momentaneamente di eseguire istruzioni e si porta in uno stato di stand-by durante il quale i consumi di energia sono ridotti al minimo; e' possibile poi "risvegliare il PIC" in qualsiasi momento successivo. Questa modalita' e' utile nel caso in cui si voglia usare il PIC in sistemi alimentati a batteria o comunque dove i consumi sono un aspetto critico.

Entrare in modalita' sleep

Per far entrare il microcontrollore in modo sleep occorre eseguire l'istruzione SLEEP. I bit PD e TO del registroSTATUS vengono rispettivamente portati a 0 e 1, e l'oscillatore interno del pic viene arrestato (l'esecuzione del programma si ferma all'istruzione SLEEP). Inoltre le porte di I/O mantengono lo stato che avevano prima che il sistema si fermasse.

Risveglio dalla modalita' sleep

Il microcontrollore puo' essere risvegliato dalla modalita' grazie al verificarsi di uno dei seguenti eventi esterni: Reset del microcontrollore (pin MCLR a massa) Watch-Dog time-out Interrupt dal piedino RB0/INT Cambio di stato dei piedini RB0..RB7 Completamento della fase di scrittura della EEPROM Al risveglio il microcontrollore si comportera' consistentemente all'evento esterno verificatosi: nel primo caso (Reset) si avra' effettivamente un reset, nel secondo il PIC continuera' con l'esecuzione del programma dall'istruzione successiva a SLEEP. Negli ultimi tre casi invece, se gli interrupt sono abilitati (GIE=1) verranno "eseguiti" gli interrupt (esecuzione del programma a partire dalla locazione 0x0004 e cosi' via) altrimenti (GIE=0) si proseguira' con l'istruzione successiva a SLEEP. 

Memoria EEPROM

La memoria EEPROM e' un modulo di memoria all'interno del microcontrollore in grado di mantenere le informazioni anche a sistema spento. La memoria e' costituita da 64 byte in tutto, accessibili sia in lettura che in scrittura tramite quattro appositi registri, cioe':

EECON1
EECON2
EEDATA
EEADR

Il registro EEDATA, mappato in RAM alla locazione 0x08 e banco 0, funziona da porta di ingresso/uscita per i dati rispettivamente da e verso la EEPROM; in soldoni si puo' dire che tale registro contiene il byte da leggere/scrivere nella EEPROM.
EEADR, mappato in RAM alla locazione 0x09 e banco 0, serve per specificare la locazione di memoria da accedere in memoria EEPROM (cioe' per specificare in che locazione della EEPROM andra' scritto il byte contenuto in EEDATA, oppure per specificare la locazione della EEPROM da leggere) .
La EEPROM permette unicamente di leggere o scrivere un byte alla volta. Una scrittura implica automaticamente la cancellazione del byte precedentemente memorizzato in quella cella di memoria, mentre una lettura non produce alcuna modifica nei dati della EEPROM.
I registri EECON1 e EECON2 sono due registri ausiliari utilizzati per "comandare" la EEPROM posizionati rispettivamente alle locazioni 0x08 e 0x09 del banco 1 della RAM.
EECON1 e' il registro di controllo con implementati solo i cinque bit meno significativi. I bit di controllo RD e WR avviano rispettivamente il processo di lettura e scrittura delle EEPROM; questi due bit possono solo essere settati ad 1, mentre invece vengono riportati a 0 dal pic al completamento della fase di lettura (se RD) e scrittura (se WR).
Il bit WREN (WRite ENable) abilita o disabilita le scritture nella EEPROM; quando viene settato ad 1 permette di effettuare le scritture, quando e' settato a 0 le scritture sono inibite. Al reset del microcontrolle tale bit e' settato a 0.
Il bit WRERR viene settato ad 1 dal pic quando o un reset oppure un time-out del watch dog ha interrotto prematuramente una scrittura nella EEPROM (va ricordato che la scrittura nella EEPROM e' una operazione relativamente lenta se rapportata alla velocita' di esecuzione delle istruzioni di programma).
Il bit EEIF funge da indicatore per il completamento della fase di scrittura. Prima di iniziare una scrittura deve essere posto a 0 e viene posto automaticamente ad 1 dal pic quando la scrittura e' terminata.





Registro EECON1




Il registro EECON2 e' un semplice registro di appoggio utilizzato esclusivamente nella fase di scrittura della EEPROM. Prima di poter scrivere un byte nella EEPROM occorre scrivere in EECON2 una serie prestabilita di valori, cioe' una specie di sequenza di avvio, in grado di abilitare la EEPROM a ricevere il byte in questione. La sequenza deve essere ripetuta ogni volta che si vuole memorizzare un byte.

Lettura

Leggere un byte dalla EEPROM e' estremamente semplice. Per prima cosa occorre scrivere in EEADR l'indirizzo del byte che si vuole leggere e poi settare ad 1 il bit di avvio di lettura (bit RD del registro EECON1). Al prossimo ciclo macchina (4 colpi di clock) il registro EEDATA si carica automaticamente con il valore del byte richiesto che quindi puo' essere letto.

Esempio:

Leggere dalla EEPROM il byte alla locazione 0x0C e memorizzarlo nel registro di lavoro W

BCF 0x03,5 ;selezione del banco RAM 0

MOVLW 0x0C ;l'indirizzo al quale leggere
MOVWF 0x09 ;lo metto in EEADR BSF 0x03,5 ;selezione del banco RAM 1
BSF 0x08,0 ;setto a 1 il bit RD di EECON1;adesso EEDATA contiene il valore del byte cercato

BCF 0x03,0 ;selezione del banco RAM 0
MOVF 0x08,0 ;copio il valore di EEDATA in W

Scrittura
L'operazione di scrittura di un byte in EEPROM e' relativamente piu' complicata. Per prima cosa occorre disabilitare gli interrupt impostando a 0 il bit GIE di INTCON: in questo modo eventuali interruzioni non verranno prese in considerazione e non andranno a disturbare il processo di scrittura in EEPROM. Adesso occorre impostare in EEADR l'indirizzo al quale vogliamo scrivere il byte ed il registro EEDATA deve essere caricato con il byte in questione. Poi il bit WREN di EECON1 deve essere portato ad 1 per abilitare la scrittura.
Fatto cio' occorre seguire una sequenza standard in grado di attivare la scrittura in EEPROM:

Scrittura del valore 0x55 in EECON2 
Scrittura del valore 0xAA in EECON2 
Impostare ad 1 il bit WR di EECON1 per avviare la scrittura 
Riabilitare gli interrupt impostando a 1 il bit GIE di INTCON 

Tale sequenza deve essere ripetuta per ogni byte che si vuole scrivere in EEPROM. La disabilitazione degli interrupt non e' obbligatoria ma e' considerata una buona pratica per evitare problemi di scrittura qualora questa venisse interrotta prematuramente da una interruzione esterna.
Al termine della scrittura il bit WR viene automaticamente impostato a 0 mentre il bit EEIF viene posto a 1.
Il programma utente puo' sapere quando avviene il completamento della scrittura semplicemente verificando in un loop il valore del bit WR oppure tramite il piu' efficiente meccanismo delle interruzioni.
Da notare che il bit WREN di abilitazione NON viene riportato a 0 automaticamente, quindi per effettuare una nuova scrittura occorrera' reipostarlo ad 1.
Ecco un esempio di scrittura in EEPROM:

Esempio:

Scrive nella EEPROM il valore 0x02 alla locazione 0x10

BCF 0x03,5 ;selezione del banco RAM 0

MOVLW 0x02
MOVWF 0x08 ;scrive 0x02 in EEDATAMOVLW 0x10
MOVWF 0x09 ;scrive 0x10 in EEADR

BSF 0x03,5 ;selezione del banco RAM 1BCF 0x0B,7 ;disabilita tutti gli interrupt
;inizio sequenza di avvio scrittura
BSF 0x08,2 ;imposta ad 1 il bit WREN
MOVLW 0x55
MOVWF 0x09 ;inserisco 0x55 in EECON2
MOVLW 0xAA
MOVWF 0x09 ;inserisco 0xAA in EECON2
BSF 0x08,1 ;bit WR ad 1 -> inizio scrittura
BSF 0x0B,7 ;riabilita gli interrupt
;fine della sequenza

Interrupt

Il sistema di interrupt e' stato gia' introdotto a grandi linee alla sezione "Memoria RAM/FLASH/EEPROM". Grazie alla gestione degli interrupt, il microcontrollore puo' essere istruito in maniera tale da "porsi in ascolto" di eventi esterni durante l'esecuzione normale del programma; quando uno di questi eventi si manifesta il program counter viene temporaneamente salvato nello stack e si salta alla locazione di memoria 0x0004 (vettore di interrupt). Compito del programmatore e' quello di inserire, a partire da questa locazione, una subroutine in grado di gestire l'attuale istanza di interruzione. La subroutine di gestione delle interruzioni dovra' terminare con l'istruzione RETFIE: questa istruzione ricarica automaticamente l'ultimo program counter salvato nello stack e cosi' permette la normale continuazione del programma precedentemente interrotto. E' molto simile a RETURN e RETLW ma in piu' riporta a 1 il bit GIE del registro INTCON.
La figura sottostante ripropone il flusso di controllo che il microcontrollore segue al manifestarsi di una interruzione esterna:





Gli eventi di interruzione che possono essere gestiti dal pic 16F84 sono di quattro tipi:
  • Interrupt dal pin RB0/INT
  • Interrupt per cambio di stato dei pin RB4..RB7
  • Interrupt per completamento della fase di scrittura della EEPROM interna
  • Interrupt dal timer
Esiste un registro opportuno, tale INTCON, grazie al quale e' possibile abilitare o disabiltare tutti o in parte i quattro eventi di interrupt elencati sopra. Inoltre alcuni suoi bit vengono settati dal controllore stesso e permettono (se si vuole) di sapere, all'interno della subroutine, quale interrupt tra i quattro possibili si e' appena verificato.
Gli interrupt possono essere disabilitati del tutto settando a 0 il bit GIE (Global Interrupt Enable) del registroINTCON (il valore di default al reset e' proprio questo). Un programma che voglia far uso del sistema di interrupt dovra' preoccuparsi di porre ad 1 questo bit. Quando si verifica una interruzione, il microcontrollore setta automaticamente a 0 il bit GIE: in questo modo si evita che possano verificarsi altri interrupt durante la gestione di quello attuale. L'istruzione RETFIE, oltre a ripassare il controllo al programma interrotto, riabilita automaticamente gli interrupt, settando ad 1 il bit GIE.

Interrupt dal pin RB0/INT

L'interruzione sul piedino RB0/INT e' scatenata dal comportamento del segnale TTL su questo piedino. L'interruzione su RB0/INT viene abilitata settando ad 1 il bit INTE del registro INTCON. Ovviamente deve essere settato ad 1 anche il bit GIE.Quando il bit INTE e' settato a 0 oppure il GIE a 0 il piedino RB0/INT e' a tutti gli effetti la prima linea della porta B (RB0) e come tale puo' essere usata. Questo e' il comportamento di default del piedino. Quando pero' si attiva l'interrupt il pin non puo' piu' essere usato come linea della porta B ma esclusivamente come linea per ricevere le interruzioni dall'esterno.Esistono due modalita' di interrupt:
  • Rising edge
  • Falling edge
Con la prima (rising edge) l'interrupt viene attivato nel momento in cui la tensione sulla linea INT passa da 0 a +5 volt, quindi si presume che normalmente il livello di tensione sia il primo (0 volt). La seconda invece attiva l'interrupt quando la tensione sul piedino INT passa da +5 volt a 0, presupponendo quindi che +5 volt sia la tensione standard.
La modalita' (rising/falling edge) viene impostata settando opportunamente il bit INTDEG del registroOPTION_REG.
Una volta che il microcontrollore ha lanciato l'interruzione viene automaticamente settato ad 1 il bit INTF del registro INTCON. Questo bit puo' essere controllato dalla routine di gestione dell'interrupt per sapere se si e' verificato proprio l'interrupt associato alla linea INT oppure era qualche altra interruzione. Nel primo caso la routine dovra' provvedere a riportare a 0 tale bit altrimenti saranno inibiti i prossimi interrupt dal piedino INT.

Interrupt dal timer


Quando il contatore TIMER0 va in overflow (cioe' passa dal valore 0xFF a 0x00) viene lanciata una interruzione. L'interruzione deve pero' essere prima abilitata settando ad 1 il bit T0IE del registro INTCON. Inoltre, al verificarsi della interruzione, viene automaticamente settato ad 1 il bit T0IF del registro INTCON, e deve essere riportato a 0 dalla routine di gestione definita dal programmatore per poter mantenere abilitato il sistema di interruzione dal timer.

Interrupt per cambio di stato dei pin RB4..RB7

In questo caso viene sollevata una interruzione quando avviene un cambio di stato qualsiasi (cioe' un cambio di tensione) tra uno o piu' dei piedini RB4,RB5,RB6 e RB7. Questo tipo di interrupt puo' essere abilitato o disabilitato settando opportunamente il bit RBIE del registro INTCON.
Al lancio della interruzione viene automanticamente settato ad 1 il bit RBIF del registro INTCON.

Interrupt per completamento della fase di scrittura della EEPROM interna

Senza addentrarci troppo nel sistema di lettura/scrittura della EEPROM interna si puo' dire che, siccome la fase di scrittura e' estremamente lenta (20 ms) comparata con la velocita' tipica di esecuzione delle istruzioni del microcontrollore, e' stato pensato di permettere alla EEPROM di lanciare una interruzione al completamento della fase di scrittura. In questo modo il programma utente puo' dare il comando di scrittura e continuare subito con le istruzioni successive, per poi gestire con comodo il completamento della scrittura rilevando l'interruzione.
Questo tipo di interruzione si abilita tramite il bit EEIE del registro INTCON. Per rilevare questa interruzione occorre controllare assieme i bit RBIF,INTF e T0IF: se sono tutti a 0 allora l'interruzione e' stata lanciata dalla EEPROM.

Timer interno


Il TIMER0 e' un dispositivo interno al microcontrollore in grado di assolvere alla duplice funzione di contatore e temporizzatore. Le due modalita' sono mutualmente esclusive e possono essere scelte via software. In ogni caso il modulo e' ad 8 bit, cio' significa che e' solo in grado di contare in step di 1 unita' e ripetere ciclicamente i valori nell'intervallo da 0 a 255. Al TIMER0 e' associato un prescaler in grado di rallentare la velocita' di incremento dei valori in 8 possibili modalita' (1:2, 1:4, 1:8, 1:16, 1:32, 1:64, 1:128, 1:256). Inoltre, se si vuole, e' possibile impostare il modulo in maniera tale da produrre un interrupt ogni volta che si ha un overflow del TIMER0, cioe' ogni volta che il modulo passa dal valore 255 (0xFF) a 0 (0x00). Al TIMER0 e' associato un registro, il TMR0 (mappato nel banco 0 alla locazione di memoria 0x01); in questo registro e' possibile leggere lo stato attuale di avanzamento del contatore oppure impostarne il valore.

Modalita' temporizzatore o contatore

Il TIMER0 funziona in due modalita': temporizzatore o contatore. Nella prima il TIMER0 si incrementa ad ogni istruzione (questo in assenza di prescaler, altrimenti secondo il rapporto imposto). Il valore del registro TMR0 e' impostato a 0 per default, ma puo' essere modificato in qualsiasi momento tramite operazioni di scrittura.
Per selezionare la modalita' temporizzatore occorre impostare a 0 il bit T0CS del registro OPTION_REG.
Veniamo ora alla modalita' contatore. In questa modalita' il TIMER0 si incrementa ad ogni rising-edge o falling-edge del segnale applicato sul pin RA4/T0CKl; la modalita' di incremento (rising o falling-edge) e' selezionata impostando in maniera opportuna il bit T0SE del registro OPTION_REG (T0SE=0 per il rising-edge, T0SE=1 per il falling-edge). T0CS a 1 imposta la modalita' contatore.

Generazione dell'interrupt dal TIMER0

Ogni volta che il TIMER0 passa dal valore 255 (0xFF) a 0 (0x00) viene lanciata una interruzione che automaticamente setta ad 1 il bit T0IF del registro INTCON. L'interrupt puo' essere inibito settando a 0 il bit T0IE. T0IF deve necessariamente essere riportato a 0 dall'eventuale routine di gestione dell'interrupt affinche' possa verificarsi un nuovo interrupt. 

Prescaler

Il prescaler e' un modulo per "rallentare" la frequenza di aggiornamento del TIMER0 tramite 8 fattori di divisione prestabiliti. Il prescaler puo' essere assegnato al TIMER0 oppure (in maniera mutualmente esclusiva) al Watch-Dog interno (il Watch-Dog e' un modulo aggiuntivo del microcontrollore spiegato piu' avanti nella guida). Il bit PSA del registro OPTION_REG determina a chi associare il prescaler (al TIMER0 con PSA=0 oppure al Watch-Dog con PSA=1). I tre bit PS0,PS1 e PS2 determinano una tra gli 8 possibili rapporti di divisione di frequenza cioe' 1:2, 1:4, 1:8, 1:16, 1:32, 1:64, 1:128, 1:256. Ovviamente se il prescaler non viene assegnato al TIMER0 (cioe' PSA=1) otteniamo automaticamente un rapporto di divisione di 1:1.
Quando il prescaler e' associato al TIMER0, ogni istruizione che vada a modificare il registro TMR0 (ad esempio, CLRF 0x01, MOVWF 0x01, BSF 0x01,3 etc...) comporta automaticamente il reset a 0 dei tre bit PS0,PS1 e PS2.
Per finire, nella figura seguente sono mostrati i collegamenti interni che collegano il prescaler al TIMER0 e al Watch-Dog:





Esempio pratico: led lampeggiante


A titolo dimostrativo viene proposto un piccolo circuito in cui un led viene fatto lampeggiare. Le fasi da seguire sono essenzialmente quelle descritte alla sezione precedente e sono: creazione del progetto in MPLAB, scrittura del programma, compilazione, debugging, inserimento del codice nel microcontrollore e realizzazione del circuito. 

Il programma

Una possibile soluzione per poter far lampeggiare un led e' quella ad esempio di avere una linea di I/O che emette un segnale in cui si alternano le tensioni di 0 e +5 volt. A tale linea collegheremo in serie il led ed una resistenza limitatrice di corrente. Allo scopo puo' essere utilizzato il programma precedente che alterna i valori 0 e 1 sul bit meno significativo (il primo bit) del registro PORTB.Riportiamo il codice del programma:


;****************************************************** ; Programma di prova ; Alterna 1 e 0 sul primo bit di PORTB ogni 100ms ;****************************************************** ;direttive PROCESSOR 16F84A RADIX HEX ;definizioni R1 EQU 0x20 R2 EQU 0x21 R3 EQU 0x22 PORTB EQU 0x06 TRISB EQU 0x06 TRISA EQU 0x05 PORTA EQU 0x05 PORTB EQU 0x06 STATUS EQU 0x03 FSR EQU 0x04 INDF EQU 0x00 ;inizio programma ORG 0x0000 bsf STATUS,5 movlw 0x00 movwf TRISB bcf STATUS,5 loop: bsf PORTB,0 call wait bcf PORTB,0 call wait goto loop ;routine di attesa (circa 100ms) wait: movlw 0xBE movwf R1 longloop1: movlw 0xBE movwf R2 longloop2: decfsz R2,1 goto longloop2 decfsz R1,1 goto longloop1 return ;fine del programma END

In ogni caso andremo ad utilizzare un oscillatore al quarzo da 4Mhz quindi in ICPROG occorrera' selezionare l'oscillatore XT. Il Watch-Dog non serve percio' deve essere disabilitato (WDT non selezionato). Possiamo lasciare disabilitate le rimanenti due opzioni PWRT e CP. 

Circuito di prova

Una volta compilato e programmato correttamente il PIC occorrera' realizzare questo piccolo circuito:






In alto abbiamo il modulo alimentatore, costituito da un regolatore di tensione 78L05. DCIN deve essere collegato ad una tensione continua da almeno 9 volt.
In basso abbiamo il circuito principale costituito dal pic e da pochi altri componenti. Al piedino #MCLR e' collegato il circuito standard di reset visto nella prima sezione mentre in OSC1 e OSC2 e' collegato l'oscillatore da 4Mhz al quarzo (tipo XT). VDD e VSS sono ovviamente il piedino del positivo e di massa. Ad RB0 sono collegati il led e la resistenza limitatrice: dato che i pin di I/O del pic possono assorbire/fornire all'incirca 20mA ciascuno, allora la resistenza dovra' avere un valore non inferiore a:
(5-0.7)volt / 0.02A = 215 ohm

Dove 0.7 e' la caduta di potenziale introdotta dal led e 5 e' la tensione massima sul piedino (corrispondente al livello logico 1). Per stare sicuri e non stressare la linea con assorbimenti al limite e' stata inserita una resistenza da 330ohm.
Di default le linee della porta A sono impostate come input e siccome non vengono utilizzate occorre inserire delle resistenze di pull-up da 10K per definirne i valori di tensione. Queste resistenze possono essere evitate semplicemente impostando da programma le linee della porta A come output.

Programmazione del PIC


Fasi di sviluppo

Realizzare un progetto con il PIC 16F84 comporta quattro fasi principali:
  • Scrittura del programma
  • Debug e testing del codice 
  • Compilazione 
  • Inserimento del programma nel PIC
Nella prima fase si scrive il codice del programma, cioe' la sequenza di istruzioni assembly necessarie al corretto funzionamento del PIC; a tal fine si impiega un ambiente di programmazione che nel nostro caso sara' MPLAB. MPLAB e' un tool estremamente potente per realizzare velocemente programmi per una vasta gamma di processori, 16F84 e 16F84A compresi.

La seconda fase prevede il debug ed il testing del codice. In effetti, siccome le operazioni di scrittura nella memoria flash di programma del PIC non sono illimitate (nel caso della famiglia 16F8X possiamo programmare i PIC "solo" circa un migliaio di volte) non conviene affatto inserire subito il programma nella memoria flash e vedere se funziona ma conviene invece testare il programma con il simulatore del incluso in MPLAB: in questo modo possiamo verificare l'efficacia e la correttezza del programma senza necessariamente inserirlo nel chip, risparimiando molti cicli di scrittura inutili.

La terza fase (compilazione) prevede la creazione, tramite MPLAB, del file .HEX relativo al programma assembly appena realizzato. Questo file contiene la rappresentazione binaria del codice assembly appena realizzato ed e' l'unica forma del programma in grado di essere "compresa" dal microcontrollore. Il programmatore hardware non fa altro che prendere il file .HEX (il programmatore deve quindi essere collegato al computer) ed inserirlo bit a bit nella memoria flash del PIC, realizzando cosi' il processo di programmazione.

Una volta che il PIC e' stato programmato e' sufficiente collegare ad esso la circuiteria di base vista nelle sezioni precedenti (alimentazione, circuito per il piedino MCLR, oscillatore esterno) ed il chip iniziera' ad eseguire automaticamente il programma, istruzione dopo istruzione.

Quanto descritto completa il ciclo di sviluppo per il microcontrollore.

In figura si mostra graficamente la sequenza temporale delle varie fasi:




Costruzione del programmatore

In questa sezione viene descritta la costruzione di un programmatore per i PIC della famiglia 16F8X, perfettamente compatibile con i modelli 16F84 e 16F84A. Se si dispone gia' di un programmatore e' possibile saltare la sezione. In ogni caso le istruzioni per la configurazione di ICPROG saranno in funzione di questo specifico programmatore e quindi potrebbero non essere adatte per altri tipi di programmatori in commercio o autocostruiti.

Una piccola nota a riguardo: il programmatore e' un interfaccia da collegare fisicamente alla porta parallela del PC percio' occorre prestare ESTREMA attenzione nella realizzazione dei collegamenti, anche perche' entrano in gioco tensioni in grado di danneggiare irreparabilmente il PC stesso (una fra tutte la 220 di alimentazione!). Sebbene abbia provato e realizzato con successo questo programmatore, declino ogni responsabilita' per eventuali danni a persone e cose nella realizzazione dello stesso. 

Funzionamento e schema elettrico

Il programmatore si rifa' al modello SCHAER2 di tipo parallelo, cioe' ad un programmatore che si interfaccia al PC tramite porta LPT (detta anche "porta della stampante"). Lo schema elettrico del programmatore e' abbastanza semplice: in effetti solo 5 dei 18 piedini vengono impegnati per la programmazione del pic e sono: 
  • VCC +5Volt (piedino 14)
  • GND (piedino 5)
  • MCLR (piedino 4)
  • Linea dati (piedino 3)
  • Clock (piedino 2) 
Il pic viene messo in modalita' programmazione tramite l'applicazione di 13.8 Volt al piedno MCLR, anziche' il classico segnale +5 Volt descritto nelle sezioni precedenti.

In questa modalita' il microcontrollore assegna al piedino RB6 la funzione di linea dati seriale di tipo half-duplex e al piedino RB7 la funzione di linea di clock.

Il programmatore preleva il segnale dati in arrivo dalla porta parallela (cioe' i dati contenuti nel file .HEX) e lo replica sulla linea RB6 del pic; il pic a sua volta preleva il segnale sul piedino RB6 e lo trasmette alla memoria flash interna.

Il segnale di clock serve solo per la corretta decodifica dei bit trasmessi sulla linea dati; a titolo di esempio si riporta in figura la trasmissione della parola 10110:




I segnali dati e di clock trasmessi dal pc al microcontrollore sono semplici onde quadre in banda base (cioe' senza modulazione).

Questo e' lo schema elettrico del programmatore:














Lista dei componenti:

  • 4 diodi 1N4004
  • 1 condensatore elettrolitico da 330uF 16V
  • 2 condensatori elettrolitici da 0.1uF 16V
  • 1 regolatore di tensione 78L05
  • 1 regolatore di tensione 70L09
  • 4 resistenze da 10Kohm 1/4 watt
  • 1 integrato 7407N o equivalente
I segnali DATAIN, DATAOUT, CLOCK e MCLR vengono convogliati rispettivamente ai pin 2,3,11 e 4 della porta parallela del pc come mostrato nello schema. Tali segnali vengono rigenerati dallo stadio buffer composto dall' integrato 7407 e dalle resistenze di pull-up da 10k ciascuna.

Le due tensioni necessarie al corretto funzionamento del programmatore (5 e 14 Volt) vengono invece fornite dallo stadio di alimentazione (in alto nello schema) realizzato con i regolatori di tensione 78L05 e 78L09 e dal solito ponte rettificatore.

Il circuito puo' essere costruito su una semplice basetta millefori ed utilizzato cosi' come e' ma personalmente ho preferito rinchiudere il tutto all'interno di un contenitore plastico utilizzando un classico scatolotto di derivazione 20x15 per impianti elettrici. Lo schema della figura e' la versione minimale del programmatore: c'e' solo il minimo indispensabile per programmare il PIC. Quello che ho realizzato io invece assolve anche alla funzione di tester per provare i programmi in situ: sul pannello frontale sono presenti 8 led rossi per il bus della porta B, 5 pulsanti per la porta A, un tasto di reset ed un selettore per dirottare i 13 segnali di I/O del pic sulla porta di espansione situata sul retro del programmatore. La porta di espansione e' stata realizzata con un connettore maschio da 26 pin e permette di interfacciarsi alle 13 linee di I/O del microcontrollore: in questo modo e' possibile collegare periferiche esterne al programmatore e provare i propri programmi con apparati reali e non solo con semplici led di segnalazione.

Ecco alcune foto del programmatore che ho realizzato:

Lato sinistro

(Porta di collegamento con il PC)


 


Stadio alimentazione

(fase di montaggio)

 

Circuito completo su bread-board

(fase di montaggio)



Inserimento del trasformatore nel case

(fase di montaggio)



Saldature sul retro del circuito stampato

 

Circuito stampato completato

(vista dall'alto)



Circuito stampato completato

(particolare della porta di I/O)



Collegamenti interni relativi al pannello superiore

 

Il programmatore pronto per essere chiuso



Configurazione di ICPROG

Come detto in precedenza il programmatore viene comandato da un apposito software di controllo. Nel nostro caso useremo l'ultima versione di ICPROG, scaricabile a questo indirizzo.

Oltre ad ICPROG e' necessario scaricare anche i driver NT/2000 per poter utilizzare la porta parallela sui Windows XP,NT e 2000. Il contenuto dei due archivi zip deve essere riversato in una stessa directory ad esempio in "C:\icprog".

Fatto cio' avviamo ICPROG ed andiamo in "settings->options", selezioniamo "misc" ed assicuriamoci che sia selezionata l'opzione "Enable NT/2000/XP Driver", come da figura:



Chiudiamo e riavviamo ICPROG per rendere effettive le modifiche (nel caso in cui le impostazioni non fossero state modificate il riavvio non e' necessario). A questo punto entriamo in "settings->Hardware" ed impostiamo le opzioni come in figura:



Adesso ICPROG e' pronto per essere usato con il programmatore. Prima pero' occorre verificare che i segnali da e verso il PC vengano inviati ed interpretati correttamente: a tal fine colleghiamo il programmatore al PC ed andiamo in "Settings->Hardware Check". Questa e' la finestra che otteniamo:



La finestra e' una sorta di banco di prova grazie alla quale possiamo attivare a piacimento i segnali sulla porta parallela e controllare facilmente con un tester se arrivano ai pin del microcontrollore. Le opzioni "Enable Data Out", "Enable Clock" e "Enable MCLR" portano rispettivamente al livello logico alto (+5 Volt) i pin 2,3 e 4 della porta parallela. L'opzione "Data In" non e' impostabile ma si setta se "Enable Data Out" e' settato e viceversa (questo solo se il programmatore funziona correttamente). "Enable VCC" non ci interessa perche' il nostro modello di programmatore non ne fa uso.

Detto questo selezioniamo "Enable Data Out": "Data In" dovrebbe selezionarsi automaticamente. Verifichiamo che quando "Enable Data Out" e' selezionato sia presente una tensione di circa +5 Volt sul pin RB7 del microcontrollore (possiamo prende il riferimento di massa dal pin GND dello pic stesso). Selezioniamo "Enable MCLR" e dovrebbe presentarsi una tensione di circa +13.8 Volt sul piedino MCLR del pic.

Se almeno uno di questi test e' fallito occorre controllare che i segnali imposti dal PC si presentino almeno sulla porta parallela: se e' cosi' allora ICPROG funziona correttamente mentre il problema e' nel programmatore, se no il problema e' almeno in ICPROG dato che non riesce a controllare correttamente la porta.

Per esperienza personale posso garantire che e' estremamente improbabile che tutto funzioni correttamente al primo tentativo (montaggio del programmatore, collegamento e configurazione di ICPROG) quindi occorre avere un po' di pazienza e sistemare gli errori commessi. Inoltre, prima di collegare per la prima volta il PC al programmatore e' bene verificare che sulla porta parallela vengano sempre presentate tensioni non superiori ai 5 Volt, per non arrecare danni al PC.

L'ambiente MPLAB

MPLAB e' la suite di sviluppo per la realizzazione dei programmi per i microcontrollori della Microchip. Consiste di un editor grafico per la stesura del programma, di un assembler per la generazione del codice macchina e di un debugger per la simulazione dei programmi. MPLAB e' completamente gratuito e puo' essere scaricato direttamente dal sito della Microchip a questo indirizzo.

Creazione del progetto e del programma

Una volta scaricato ed installato avviamo MPLAB.

Questa e' la schermata principale:



Per prima cosa occorre creare un nuovo progetto. Andiamo in "Project->Project Wizard"



Clicchiamo su "Avanti". Apparira' la seguente schermata:



Selezioniamo il modello di microcontrollore: nel nostro caso va bene sia "PIC16F84" che "PIC16F84A". Clicchiamo quindi su "Avanti".



Qui occorre selezionare il linguaggio di programmazione da utilizzare. Noi utilizzeremo direttamente l'assembly, quindi lasciamo le impostazioni cosi' come sono e clicchiamo su "Avanti"; otteniamo questa finestra:



In questo passo occorre specificare la directory di progetto. Possiamo inserire "C:\prova\prova.mcp" nel form ad indicare che vogliamo creare il progetto "prova.mcp" (mcp e' l'estensione standard per i progetti MPLAB) nella directory "C:\prova". Clicchiamo su "Avanti" due volte e poi, alla schermata di riepilogo, su "Fine". Otteniamo la seguente schermata:



Andiamo ora in "File->New"; apparira' una nuovo editor di testo vuoto (bianco). A questo punto clicchiamo su "File->Save as..." ed apparira' la seguente finestra:



Posizioniamoci all'interno della directory di progetto "C:\prova" appena creata dal programma e scriviamo "prova.asm" nell'apposito form come mostrato nella figura precedente. Chicchiamo quindi su "Salva". La finestra si chiude, il file "prova.asm" viene immediatamente creato ed aperto in un nuovo editor (sempre vuoto ovviamente), come da figura:



Clicchiamo ora col tasto destro su "Source Files" all'interno della finestrella bianca in alto a sinistra e selezioniamo "Add Files..." come mostrato in figura:



A questo punto ci verra' chiesto di selezionare un percorso per il file di testo che conterra' il listato del nostro programma: selezioniamo "C:\prova\prova.asm" cioe' il file di testo che abbiamo creato al passo precedente.

Clicchiamo sulla nuova voce "prova.asm" che apparira' sotto "Source Files" per riaprire l'editor di testo.

Adesso siamo pronti per inserire, una dopo l'altra, tutte le istruzioni assembly necessarie per realizzare il nostro programma.

Un tipico programma e' composto essenzialmente da 5 parti principali: 
  • Commento di intestazione 
  • Direttive per il compilatore 
  • Definizioni
  • Istruzioni di programma 
  • Direttiva "END" 
Ecco come si presenta un listato:



Il listato in figura e' scaricabile qui.

P.S. La routine di attesa da 100ms e' riferita ad un clock di 4MHz.

Questo programma non fa altro che alternare 1 e 0 sul primo bit della porta B ogni 100ms. Cioe', ogni 100ms il primo bit della porta B cambia stato.

In alto abbiamo il commento generale sul funzionamento del programma (i commenti possono essere sparsi ovunque, l'importante e' che inizino sempre con il punto e virgola).

Dopo il commento abbiamo le due direttive PROCESSOR e RADIX. La prima istruisce il compilatore a considerare uno specifico modello di microcontrollore (nel nostro caso il pic 16F84A). RADIX invece indica la base con la quale andremo a rappresentare i valori numerici nel listato del programma: HEX significa che utilizzeremo la notazione esadecimale, DEC quella decimale.

Abbiamo ora le definizioni: con la direttiva EQU possiamo associare a dei valori numerici una etichetta testuale: "PORTB EQU 0x06" permette di utilizzare la scritta "PORTB" nel listato del programma al posto del suo indirizzo di memoria); in questo modo sara' piu' semplice accedere al registro. Notare comunque che, ad esempio, per accedere a PORTB o TRISB occorre sempre e comunque selezionare opportunamente il banco RAM.

Dopo le definizioni e' il listato del programma vero e proprio. La direttiva ORG indica la prima posizione in memoria di programma a partire dalla quale verranno inserite una dopo l'altra tutte le nostre istruzioni assembly: nel nostro caso con ORG 0x0000 imponiamo l'inserimento a partire dalla primissima locazione (locazione 0x0000) anche perche' e' da qui che il pic comincia ad eseguire le istruzioni, ma nulla vieta di scegliere altre posizioni. Possono essere presenti piu' direttive ORG sparse lungo il codice di programma: ogni volta che se ne incontra una il compilatore iniziera' ad allocare le istruzioni assembly a partire dalla locazione specificata. La direttiva e' molto utile in caso si voglia lasciare un "buco" nella memoria di programma per far posto ad una routine di gestione delle interruzioni che, come visto in precedenza, deve necessariamente cominciare a partire dalla locazione di memoria 0x0004: in questo caso avremo una cosa del genere:

ORG 0x0000

goto start

ORG 0x0004

;routine di gestione delle interruzioni

;fine routine

start:

;inizio programma vero e proprio

END

Il microcontrollore iniziera' l'esecuzione a partire da 0x0000 come di consueto, saltando pero' immediatamente al programma vero e proprio, tramite il goto. La routine di gestione delle interruzioni e' posizionata invece a partire dalla locazione 0x0004 e viene invocata solo quando necessario.

Ritornando al listato precedente, abbiamo poi le istruzioni di programma. Il programma principale si risolve in un singolo loop infinito che effettua queste operazioni: 

Imposta come linee di output i bit di PORTB

Setta a 1 il primo bit di PORTB

Chiama una routine di attesa

Setta a 0 il primo bit di PORTB

Chiama la routine di attesa

Ritorna al passo 2

Infine abbiamo la direttiva "END" che decreta la fine del listato.

Il programma mostrato puo' essere copiato ed incollato sul nostro "prova.asm" cosi' come e' (cliccare qui per scaricare il listato in formato testo).

Compilazione del programma

In questa fase andiamo a "compilare" il codice, andando cosi' a creare il file .HEX da passare poi a ICPROG.

Andiamo in "Project->Build ALL" oppure premiamo "CTRL+F10"; se non abbiamo fatto alcun errore dovrebbe comparire una finestra di dialogo simile a questa:




La scritta "BUILD SUCCEEDED" in basso ci informa del successo dell'operazione. Nella directory di progetto (in questo caso "C:\prova") e' appena stato creato il file "prova.HEX", cioe' i dati che dovremo inserire nella memoria del pic tramite l'ausilio di ICPROG e del programmatore.

Nel caso siano presenti errori di sintassi otterremo la scritta "BUILD FAILED" e occorrera' correggere tali errori.

Debug del codice

Il debugger e' uno strumento molto potente fornito da MPLAB per poter verificare la correttezza logica del codice appena compilato (il compilatore rileva solo gli errori sintattici) senza necessariamente programmare fisicamente il PIC.

Andiamo in "Debugger->Select tool" e selezioniamo l'opzione 3 cioe' "MPLAB SIM". Abbiamo appena abilitato il debugger di MPLAB. Il debugger ci permette di controllare il funzionamento interno del microcontrollore istruzione dopo istruzione, come vengono modificati i registri SFR e GPR, la correttezza del flusso di programma, lo stato della EEPROM e molto altro.

Se non lo abbiamo gia' fatto premiamo "CTRL+F10" per ricompilare il programma.

Andiamo in "Debugger->Reset" e simuliamo un reset MCLR (come ad esempio l'accensione del pic) selezionando l'opzione "MCLR Reset". apparira' un puntatore verde alla prima istruzione del programma come in figura: questo puntatore indica l'istruzione che sta per essere eseguita al prossimo colpo di clock. Premiamo una volta F8 ed otteniamo l'esecuzione dell'istruzione puntata, adesso e' il turno della prossima istruzione. Premendo di nuovo F8 avanziamo lungo tutto il programma. Con F7 avanziamo lo stesso ma possiamo entrare all'interno delle chiamate a funzione.

Se vogliamo controllare lo stato attuale dei registri SFR sara' sufficiente andare in "View->Special Function Registers": in questo modo possiamo controllare come si modificano tali registri parallelamente all'avanzamento del programma. Oltre ai registri SFR possiamo controllare: 

La EEPROM con l'opzione "View->EEPROM"

La memoria di programma con "View->Program Memory"

Lo stack con "View->Hardware Stack"

Tutti i registri (SFR e GPR) con "View->File Registers"

Una volta attivate tutte queste opzioni ecco come si presenta MPLAB in modo debugger:



Programmazione del PIC

Possiamo ora inserire il file .HEX all'interno della memoria di programma del pic. Apriamo ICPROG ed andiamo in "File->Open File..". Selezioniamo il file "C:\prova\prova.HEX" creato da MPLAB all'atto della compilazione:



ICPROG visualizza i byte relativi al codice macchina del nostro programma nella finestra principale (evidenziata in blu). Nel pannello a lato (evidenziato in rosso) abbiamo invece alcune opzioni da settare opportunamente a seconda di come verra' impiegato effettivamente il pic nel sistema reale. Occorre innanzitutto selezionare il tipo di oscillatore (RC, XT, LP o HS) effettivamente impiegato nel circuito che ospitera' il pic.

Piu' in basso occorre decidere se attivare il Watch-Dog agendo sull'opzione WDT (normalmente lo si tiene disabilitato fintanto che non se ne fa espressamente uso all'interno del codice), se attivare il Power-Up Timer (opzione PWRT) che introduce un piccolo delay temporale all'accensione del pic per permettere di stabilizzare l'alimentazione e se attivare la protezione del codice (opzione CP). La protezione del codice e' utile nel caso in cui si voglia impedire la lettura a terzi del codice macchina inserito nel pic: ogni tentativo di lettura scatenera' automaticamente la cancellazione della memoria di programma del microcontrollore.

Fatto questo e' sufficiente accendere il programmatore ed effettuare la programmazione con il pulsante evidenziato in figura:



Il pulsante in questione fa parte dei quattro che permettono rispettivamene di leggere il pic, scrivere, cancellare e compararne il contenuto rispetto a quanto mostrato nella finestra principale.
Il pic e' ora pronto per essere montato nel circuito.

Linguaggio assembly - parte 2

Operazioni logiche 

AND logico (ANDWF e ANDLW)

Per l'operazione logica di AND sono messe a disposizione le due istruzioni ANDWF e ANDLW.

Sintassi delle istruzioni:

ANDWF F,DANDLW K

ANDLW permette di effettuare l'AND tra il registro di lavoro W ed un valore letterale K (un byte); il risultato viene posto di nuovo in W ed inoltre se questo (dopo l'esecuzione dell'istruzione) vale zero, viene settato ad 1 il bit Z del registro STATUS, altrimenti viene settato a 0. 

ANDWF permette invece di effettuare l'AND tra il registro di lavoro W ed un registro F qualsiasi;. Anche in questo caso viene modificato il bit Z del registro STATUS, esattamente come sopra. 

Esempio:

AND logico tra W ed il valore 0xAA.

ANDLW 0xAA,1

Esempio:

AND logico tra W ed il contenuto del registro 0x34. Porre il risultato in W.

ANDWF 0x34,0

OR logico (IORWF e IORLW)

Per l'operazione logica di OR sono messe a disposizione le due istruzioni IORWF e IORLW.

Sintassi delle istruzioni:

IORWF F,D

IORLW K 


Funzionano esattamente come le istruzioni di AND, ma in questo caso si effettua l'OR. 


Esempio:

OR logico tra W ed il valore 0xAA 

IORLW 0xAA 

Esempio:

OR logico tra W ed il contenuto del registro 0x34. Porre il risultato in W. 

IORWF 0x34,0 

XOR logico (XORWF e XORLW) 

Per l'operazione logica di XOR sono messe a disposizione le due istruzioni XORWF e XORLW. 

Sintassi delle istruzioni: 



XORWF F,D 

XORLW K 


Funzionano esattamente come le istruzioni di AND e OR, ma in questo caso si effettua l'operazione di XOR. 

Esempio:

XOR logico tra W ed il valore 0xAA 

XORLW 0xAA 

Esempio:

XOR logico tra W ed il contenuto del registro 0x34. Porre il risultato in W. 

XORWF 0x34,0 

NOT logico (COMF) 

L'operazione di NOT si effettua con l'istruzione COMF 

Sintassi della istruzione: 



COMF F,D

L'istruzione inverte tutti i bit del registro puntato da F. Se D=0 il risultato invertito viene posto in W (senza modificare F), altrimenti viene scritto in F. Come le altre istruzioni logiche, anche questa setta il bit Z del registro STATUS in funzione del risultato dell'operazione.

Esempio:

Invertire i bit del registro alla locazione 0x30 e porre il risultato in W senza modificare il primo.

COMF 0x30,0

Operazioni aritmetiche

Addizione (ADDWF e ADDLW)

Per l'operazione di somma sono messe a disposizione le due istruzioni ADDWF e ADDLW.

Sintassi delle istruzioni:

ADDWF F,DADDLW K

ANDLW effettua la somma tra un byte K ed il contenuto del registro di lavoro W. Il risultato viene posto di nuovo in W. L'istruzione inoltre modifica opportunamente i bit Z,C e DC del registro STATUS.

ANDWF e' simile ed effettua la somma tra il contenuto del registro puntato da F e quello del registro di lavoro W. Il risultato viene posto in W se D=0, altrimenti viene posto in F. Anche in questo caso vengono impostate opportunamente i bit Z,C e DC del registro STATUS.

Esempio:

Sommare il contenuto di W con 12

ADDLW 0x12

Esempio:

Sommare il contenuto del registro alla locazione 0x0E con W e porre il risultato nel primo.

ADDWF 0x0E,1

Sottrazione (SUBWF e SUBLW)

Per la sottrazione sono messe a disposizione le due istruzioni SUBWF e SUBLW.

Sintassi delle istruzioni:

SUBWF F,DSUBLW K

Funzionano esattamente come le relative operazioni di somma; l'unica differenza e' ovviamente l'operazione effettuata.

Decremento (DECF)

Operazione di decremento unitario.

Sintassi dela istruzione:

DECF F,D

Decrementa di una unita' il contenuto del registro puntato da F. Il risultato viene posto in W se D=0, altrimenti viene posto in F. L'istruzione imposta ad 1 il bit Z del registro STATUS se il risultato dell'operazione di decremento e' zero, altrimenti lo imposta a 0.

Esempio:

Decrementare di una unita' il registro 0x32 e porre il risultato in W

DECF 0x32,0

Incremento (INCF)


Operazione di decremento.

Sintassi dela istruzione:

INCF F,D

Decrementa di una unita' il contenuto del registro puntato da F. Il risultato viene posto in W se D=0, altrimenti viene posto in F. L'istruzione imposta ad 1 il bit Z del registro STATUS se il risultato dell'operazione di decremento e' zero, altrimenti lo imposta a 0.

Esempio:
Decrementare di una unita' il registro 0x32 e porre il risultato in W

DECF 0x32,0

Operazioni di modifica dei registri


































Rotazione (RRF e RLF)














Il microcontrollore possiede due istruzioni di rotazione: RRF e RLF. La rotazione consiste nel "traslare di un posto" tutti i bit all'interno di un dato registro. La rotazione puo' quindi essere destra (RRF) o sinistra (RLF) a seconda che la traslazione avvenga appunto verso destra o verso sinistra. La rotazione ovviamente e' ciclica, cioe' il bit che esce rientra "dall'altra parte", ma non subito...prima "passa attraverso" il bit C del registroSTATUS. Un esempio chiarira' le idee. Supponiamo che il contenuto di un dato registro F sia il seguente (sopra ai bit del registro poniamo i rispettivi indici):


















76543210






















00000001






























Affianchiamo alla sinistra del registro il bit C (bit carry) del registro STATUS. All'inizio tale bit potra' trovarsi in uno stato qualsiasi (dipendera' da quello che e' successo prima nel programma, il suo stato non e' noto) quindi, anziche' scrivere 0 oppure 1, scriveremo una 'x'.














C 76543210






















x 00000001






























Ora, effettuando in sequeza tante istruzioni RLF (Rotate Left) ecco come cambia il contenuto del nostro registro e del bit C:


















C 76543210














x 00000001






















RLF 0 00000010






















RLF 0 00000100










RLF 0 00001000










RLF 0 00010000










RLF 0 00100000










RLF 0 01000000










RLF 0 10000000










RLF 1 00000000










RLF 0 00000001










RLF 0 00000010


















e cosi' via...






Analogamente, ecco cosa succede con RRF (Rotate Right) partendo sempre dal medesimo contenuto di registro:


















C 76543210






















x 00000001






















RRF 1 00000000






















RRF 0 10000000










RRF 0 01000000










RRF 0 00100000










RRF 0 00010000










RRF 0 00001000










RRF 0 00000100










RRF 0 00000010










RRF 0 00000001RRF 1 00000000










RRF 0 10000000










RRF 0 01000000










RRF 0 00100000










RRF 0 00010000






















e cosi' via...














Ecco perche' abbiamo detto che la rotazione avviene "attraverso" il bit C. Questo sistema permette la lettura in sequenza di tutti i bit di un dato registro semplicemente effettuando le rotazioni e controllando lo stato del bit C.






























































Sintassi delle istruzioni:


















RRF F,DRLF F,D














RRF effettua la rotazione destra di un dato registro F. Il risultato viene posto in F se D=1, altrimenti viene posto in W. Il parametro D ci viene in aiuto nel caso in cui volessimo lasciare inalterato il registro F; infatti se D=1 tutte le versioni traslate del registro F si susseguono in W. Ovviamente il bit C viene aggiornato opportunamente sia con D=0 che con D=1.






RLF effettua la rotazione sinistra di un dato registro F. Valgono le stesse considerazioni viste per RRF.






















Esempio:














Inviare in forma seriale e sulla linea RB0 della PORTA B tutti e otto i bit del registro alla locazione 0x45. Il primo bit inviato deve essere quello meno significativo. L'invio dei bit deve avvenire ogni 2us. Si suppone un clock di 4MHz






















STATUS EQU 0x03PORT_B EQU 0x06






TRIS_B EQU 0x06






BSF STATUS,5 ;selezione del banco RAM 1MOVLW 0xFF ;imposta tutte le linee B come output






MOVWF TRIS_B






BCF STATUS,5 ;selezione del banco RAM 0






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






RRF 0x45






MOVF STATUS,0 ;carica il registro STATUS in W






MOVWF PORT_B ;scrive W in PORT_B (il bit C va in RB0)






NOP






NOP






































Nibble-swap (SWAPF)














Effettua uno scambio dei quattro bit piu' significativi con i quattro meno significativi. In gergo il termine "nibble" significa "gruppo di quattro bit". L'istruzione scambia tra loro il nibble alto ed il nibble basso di un dato registro.






Sintassi della istruzione:














SWAPF F,D














Effettua lo scambio delle due meta' del registro F. Se D=1 il risultato viene posto in F, altrimenti viene posto in W senza toccare F.






















Esempio:














Scambiare tra loro le due meta' del registro 0x32 e porre il risultato nel registro 0x33 lasciando inalterato 0x32.






















SWAPF 0x32,0






MOVWF 0x33


































































Chiamata a funzione


























Il microcontrolle 16F84 mette a disposizione del programmatore anche l'utilissimo sistema di chiamata a funzione, un classico per qualsiasi CPU. In pratica tramite apposite istruzioni e' possibile richiamare una pezzo di codice di programma; in questo senso il sistema e' simile ad un GOTO con ritorno, cioe' si salta in un altro punto della memoria di programma, si esegue un certo pezzo di codice e poi si ritorna all'istruzione immediatamente successiva a quella dove abbiamo saltato. L'istruzione di salto e' CALL, mentre l'istruzione di ritorno puo' essere o RETURN, oppure RETLW. Il pezzo di codice eseguito e' detto "subroutine". In pratica, quando si vuole richiamare una subroutine, e' sufficiente invocare la CALL specificando l'indirizzo della prima istruzione di tale subroutine (o una label, come visto per le istruzioni di salto): il codice viene quindi eseguito dal punto specificato in poi sino a che non si giunge ad una istruzione di RETURN o RETLW. Giunti a questo punto si ritorna ad eseguire il programma dall'istruzione immediatamente successiva a CALL. Si capisce facilmente quindi che l'ultima istruzione di una data subroutine deve essere o RETURN o RETLW; ovviamente se tali istruzioni non sono presenti il ritorno non avra' luogo. Inoltre, piu' CALL possono riferirsi ad una stessa subroutine, esattamente come piu GOTO possono riferirsi ad uno stesso punto di codice.






La figura sottostante mostra il sistema di chiamata a funzione:





















































La subroutine e' in rosso, con in evidenza la prima istruzione e l'ultima (RETURN). Il programma principale non e' mostrato per intero ma puo' trovarsi ovunque, prima della subroutine, dopo, oppure tutt'attorno alla subroutine stessa.






Quando nel programma principale si incontra una CALL viene eseguito il salto verso la locazione specificata (cioe' alla prima istruzione della subroutine), si esegue il corpo della subroutine e poi si ritorna verso l'istruzione immediatamente successiva alla CALL grazie a RETURN (o RETLW).






La CALL richiede sempre due cicli macchina (e' un salto).






In che modo il microcontrollore riesce a "ricordarsi" il punto al quale tornare indietro? Esiste una piccola memoria ausiliaria, detta stack, in grado di memorizzare l'indirizzo di ritorno (l'indirizzo della istruzione immediatamente successiva alla CALL); quando si esegue un RETURN tale indirizzo viene ripescato dallo stack e si puo' ritornare al programma chiamante. Lo stack e' una memoria di tipo LIFO (Last In, First Out) che puo' contenere sino ad un massimo di otto indirizzi di programma; grazie a cio' e' possibile effettuare una CALL anche dentro una subroutine e ripetere tale processo ricorsivamente sino ad un massimo di otto volte (cioe' otto CALL consecutive). In sostanza, ogni volta che si effettua una CALL si salta e viene inserito automaticamente nello stack l'indirizzo di ritorno, ogni volta che si effettua un RETURN si "toglie" un indirizzo dallo stack e si salta a quello (la rimozione consiste nel togliere l'ultimo elemento inserito) .






















Chiamata a funzione (CALL)














Effettua la chiamata a funzione (subroutine).






Sintassi della istruzione:


















CALL addr














Salta all'indirizzo di programma "addr" e carica nello stack l'indirizzo della prossima istruzione per rendere possibile il ritorno. "addr" puo' essere direttamente un indirizzo esadecimale della memoria di programma che punta alla prima istruzione della subroutine, oppure piu' semplicemente una label testuale come visto per il GOTO. Come detto in precedenza, per eseguire l'istruzione CALL occorrono due cicli macchina.


















Ritorno semplice (RETURN)














Ritorna indietro verso la CALL chiamante.






Sintassi della istruzione:


















RETURN














L'istruzione non ha parametri. L'esecuzione comporta il recupero dell'ultimo indirizzo di programma inserito nello stack ed il salto verso tale locazione. L'istruzione prevede due cicli macchina per essere eseguita.














Esempio:














Chiamare una subroutine che azzera il contenuto del registro 0x30, poi incrementare di una unita' tale registro.






























CALL azzera ;vai alla subroutine "azzera"






INCF 0x30










azzera: ;label di inizio della subroutine "azzera"






CLRF 0x30 ;azzera 0x30






RETURN ;ritorna indietro al programma principale










































Ritorno con parametro (RETLW)














Questa istruzione e' simile a RETURN ma con un'aggiunta significativa: oltre a tornare indietro viene inizializzato il registro di lavoro W con un valore prefissato.






Sintassi della istruzione:


















RETLW K














Si effettua il ritorno all'istruzione immediatamente successiva alla CALL chiamante (esattamente come RETURN) ma in piu' provvede a caricare il registro W con il valore K che e' stato specificato nell'istruzione RETLW. Queso tipo di ritorno puo' essere molto utile in tutti quei casi in cui occorre passare un byte di risultato al programma chiamante.










































Esempio:














Effettuare la somma dei due registri alle locazioni 0x20 e 0x21 utilizzando una apposita subroutine. Passare il risultato al programma chiamante tramite il registro W.






























MOVLW 0x10 ;carica i registri 0x20 e 0x21 con i valori






MOVWF 0x20 ;da sommare






MOVLW 0x3A






MOVWF 0x21






CALL somma ;vai alla subroutine "somma"














somma: ;label di inizio della subroutine "somma"






MOVF 0x20




,0         ;copia il contenuto di 0x20 in W
ADDWF  0x21,0         ;effettua la somma
RETLW                 ;ritorna indietro al programma principale