Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
INTCON
Concluso questo passaggio, si osserva che gli interrupt non verranno mai utilizzati in questo firmware,
perciò è nell’inizializzazione che devono essere disattivati.
clrf INTCON
Porte I/O
Le varie porte A, C, B, E vengono settate come input digitali per tutti i pin, tranne RA0 settato come
input analogico. Nella porta D i pin 0…3 sono settati come output (LED), mentre i pin 4…7 come input.
banksel TRISA
movlw 0xFF
movwf TRISA
movwf TRISC
movwf TRISB
movwf TRISE
movlw 0xF0
movwf TRISD
Anche qui, come già fatto in precedenza viene per prima cosa selezionato il banco di TRISA, lo stesso
degli altri registri utilizzati in seguito (per questo motivo è necessario un solo banksel). Muovendo la
literal FF (in binario ‘11111111’), si può soddisfare le richieste volute. Tuttavia copiando F0 in TRISD
faccio in modo che solo i primi 4 bit della porta vengano impostati come output, 4 bit che coincidono
con i 4 LED.
ANSELH Pag. 6 di 22
Di default, tutti i pin connessi all'ADC sono settati come input analogici, impedendone l'uso come I/O
digitali. L'impostazione seguente rende I/O digitali i pin RB0...RB3:
banksel ANSELH
clrf ANSELH
Selezionando come al solito banco di memoria e disattivando così AN8...AN13.
TIMER1
Nella seguente sezione vado ad impostare i valori del timer1 con i valori già descritti nel paragrafo
della definizione delle costanti. banksel T1CON
movlw B'00001010'
movwf T1CON
Quindi, prescaler impostato a 1 (bit 4-5 = 00), oscillatore del timer abilitato (bit 3 = 1) e input
sincronizzato col clock esterno (bit 2 = 0), clock esterno (bit1 = 1) e Timer1 non ancora attivato (bit0
= 0).
SERIALE banksel RCSTA
movlw B'10010000'
movwf RCSTA
Andando a copiare quel valore in RCSTA, modifico il Receive Status e il Control Register. Infatti
settando a 1 bit 7 e bit 4 faccio in modo che la connessione seriale sia abilitata (bit7, SPEN=1) così
come la ricezione (bit4, CREN). banksel SPBRG
movlw .12
movwf SPBRG
Queste tre righe di codice indicano la velocità di connessione della porta. Il registro SPBRG controlla
infatti il periodo di un timer indipendente.
banksel TXSTA
movlw B'00100100'
movwf TXSTA
viene settato il bit5 TXEN per la trasmissione, il bit4 SYNC è settato a 0 per connessione asincrona e
il bit 2 (non utilizzato in modo sincrono) è settato a 1 come high speed baud rate.
banksel BAUDCTL
clrf BAUDCTL
Infine, viene imposta una velocità di trasmissione/ricezione pari a 19.2 Kbit/s. Con queste righe viene
terminata l’inizializzazione dell’hardware.
Con la direttiva return si può ritornare a dove la call di INIT_HW è stata chiamata.
Pag. 7 di 22
Ritornati sul programma principale, con una semplice istruzione imposto lo stato dei LED all’accensione del
programma: banksel PORTD
clrf PORTD
ovvero tutti i LED (in particolare il primo led, l’unico che si va ad usare in questo firmware) sono inizializzati
come spenti.
Terminate le prime inizializzazioni, si entra nel primo polling, che va ad attendere il momento di pressione
del pulsante. Il loop di attesa della pressione del pulsante è il seguente:
wait_press
clrwdt
banksel PORTB
btfsc PORTB,0
goto wait_press
Si può notare infatti che fin quando il bit 0 della porta B è 1 (pulsante non premuto), il programma si racchiude
in un ciclo continuo, che potrà essere interrotto solo dal momento in cui viene rilevato il valore 0 sul bit 0
della portaB.
Per evitare il solito problema del bouncing, si va a richiamare subito il delay di 20ms, già descritto nella
dichiarazione delle costanti: pagesel DELAY
movlw tmr_20ms
call DELAY
con la pagesel viene impostata la pagina della memoria di programma in cui risiede la subroutine DELAY,
viene poi caricata la costante (precedentemente definita) per la misura di 20ms e chiamata quindi la routine
di DELAY.
DELAY
Anche in questo caso si lavora con un polling: settato il contatore al valore iniziale voluto, si azzera il
flag e si attende tramite un loop che il flag venga settato di nuovo.
banksel TMR0
movwf TMR0
bcf INTCON, T0IF
wait_delay
btfss INTCON, T0IF
goto wait_delay
return
copiato il registro accumulatore W in TMR0, ovvero i 20ms, si azzera il bit di overflow T0IF del timer0.
Dopodiché si entra nel loop, fin quando il flag di overflow del timer non risulta 1. Con il return c’è
l’uscita dalla subroutine al punto del codice in cui era stata chiamata.
Risolto il problema del debouncing alla pressione, bisogna risolvere lo stesso problema al rilascio del
pulsante. Anche in questo caso, il codice consiste in un loop di attesa del rilascio del pulsante.
wait_rel
clrwdt
banksel PORTB
btfss PORTB,0
goto wait_rel
pagesel DELAY
movlw tmr_20ms
Pag. 8 di 22
call DELAY
Il codice è molto simile al codice sulla pressione del pulsante, con la sola differenza che qui si attende che il
bit 0 di PORTB venga settato a 1 (pulsante rilasciato).
Il cronometro comincia a scorrere grazie alla subroutine START_STOPWATCH: si arriva qui dopo la pressione
del pulsante, perciò il timer1 può essere attivato:
banksel T1CON
movlw B'00001011'
movwf T1CON
Viene di fatto modificato un solo bit rispetto alla literal copiata nel registro T1CON nell’INIT_HW: il bit 0
(TMR1ON) viene settato a 1, dando il via al timer che, come impostato nella definizione delle costanti,
impiegherà 1s per arrivare a settare il flag a 1.
Passaggio successivo è quello di resettare tutte le singole cifre del cronometro:
pagesel reset_secondi_uni
call reset_secondi_uni
pagesel reset_secondi_dec
call reset_secondi_dec
pagesel reset_minuti_uni
call reset_minuti_uni
pagesel reset_minuti_dec
call reset_minuti_dec
Ogni due righe di codice viene chiamata la subroutine che va ad azzerare tutte 4 le cifre del cronometro; è
necessario solo muovere un registro accumulatore vuoto nelle variabili precedentemente dichiarate:
reset_secondi_uni clrw
movwf secondi_uni
return
reset_secondi_dec clrw
movwf secondi_dec
return
reset_minuti_uni clrw
movwf minuti_uni
return
reset_minuti_dec clrw
movwf minuti_dec
return
in questo modo infatti mi assicuro di far cominciare le quattro cifre tutte da 0.
Tuttavia, questi reset sarebbero inutili se poi non fossero mandati in stampa. Ed è ciò che viene fatto subito
dopo: pagesel transfer_data
call transfer_data
Con la call transfer_data il programma si occupa di inviare i dati
transfer_data
Viene eseguita la trasmissione dei dati nella seriale:
Pag. 9 di 22
movf minuti_dec, w
addlw .48
banksel TXREG
movwf TXREG
banksel PIR1
btfss PIR1, TXIF
goto $-1
Il primo carattere che voglio trasmettere è quello delle decine dei minuti, in modo da mandare in
stampa il cronometro come solitamente è fatto negli orologi da polso. Per prima cosa viene copiato
il registro accumulatore precedentemente definito in minuti_dec. Il valore .48 si spiega grazie alla
tabella dei valori ASCII: in questo modo infatti il codice partirà a trasmettere dal carattere 0, che si
trova proprio alla quarantottesima posizione nella tabella. Fatto ciò bisogna impostare la
trasmissione, selezionando il banco e andando muovere il registro accumulatore in TXREG, ed
aspettare poi che il flag TXIF del registro PIR1 assuma valore 1.
Queste righe di codice vengono quindi riproposte anche per gli altri valori, con la differenza del
nome del registro coinvolto.
Risulta più semplificata la trasmissione dei due punti che dividono minuti da secondi e del carriage
return, che permette di ritornare alla riga e colonna iniziale: per i primi, è sufficiente muovere il
carattere .58 del codice ASCII (carattere “:”) nel registro TXREG, per il secondo la literal da muovere
.13 (o anche /r).
Entro allora nel main_loop, la cui prima routine è la carica del contatore del timer1 con valore iniziale per
contare 1s. Sarà necessario ritornare in main_loop ogni qualvolta che è si è verificata una stampa, ovvero
almeno uno dei valori del cronometro è aumentato.
reload_timer1
Con questa subroutine viene ricaricato il timer1 in modo tale da poter ricominciare a contare il
secondo necessario a far assumere al flag il valore 1.
banksel T1CON
bcf T1CON, TMR1ON
Essendo in modalità asincrona, occorre arrestare il timer prima di aggiornare il registro del contatore.
Come al solito, si prende il bit 0 (TRM1ON) di T1CON e si fa un clear.
banksel TMR1L
movlw low tmr_1s
movwf TMR1L
movlw high tmr_1s
movwf TMR1H
La particolarità del timer1 rispetto gli altri timer è quella di avere 16 bit, a differenza di timer0 e
timer2 che sono forniti di “soli” 8 bit. Per questa ragione non è possibile muovere il valore del
contatore direttamente in un registro, ma è necessario dividerlo in due registri da 8 bit ognuno, high
e low. banksel PIR1
bcf PIR1, TMR1IF
banksel T1CON
bsf T1CON, TMR1ON
return
Al fine di poter ricominciare il conteggio in maniera corretta, è fondamentale azzerare il flag. Infine
viene riattivato il timer.
Come da richiesta, una volta reimpostato il timer, viene acceso il LED1:
banksel PORTD
Pag. 10 di 22
bsf PORTD, 0
Il main_loop1 introduce la situazione in cui il pulsante venga premuto durante lo svolgimento del
cronometro: è una sorta di evento esterno, che anche in questo caso viene gestito in polling:
banksel PORTB
btfss PORTB, 0
goto STOP_COUNTING
Nel caso il pulsante venga premuto, il codice salta direttamente all’istruzione STOP_COUNTING, la quale
porterà allo stop del timer e di conseguenza del cronometro.
STOP_COUNTING pagesel DELAY
movlw tmr_20ms
call DELAY
Anche questo caso, essendo premuto un pulsante, viene applicata un’operazione di debouncing.
banksel T1CON
movlw 0x01
xorwf T1CON, f
btfsc T1CON, 0
goto wait_rel2
Lo xor permette di leggere e analizzare in modo compatto (eliminando l’uso di due if) l’andamento
del timer: nel caso in cui sia attivo, lo xor tra la literal 01 e il bit 1 di T1CON fa sì che il timer venga
disattivato (e viceversa). Tuttavia, si noti che a seconda del nuovo comportamento assunto dal timer,
varia il settaggio del LED. Per questo motivo si suddivide in due ulteriori subroutine: nel caso in cui il
timer sia attivato (TMR1ON=1), entro nella subroutine wait_rel2:
❖ wait_rel2
grazie a questa subroutine il led si riaccende non quando scorre il numero sulla seriale ma
quando riparte il timer: banksel PORTB
btfss PORTB,0
goto wait_rel2
pagesel DELAY
movlw tmr_20ms