Che materia stai cercando?

Anteprima

ESTRATTO DOCUMENTO

fatto partire da 256-78 = 178 poiché il periodo di un singolo incremento (tick) vale 256μs (il prescaler è stato

impostato a 256 nel registro OPTION_REG, quindi il clock vale 3.90625 kHz), moltiplicando 256μs ∙ 78 ottengo

proprio 20ms.

Il secondo timer è determinante nello svolgimento corretto del cronometro: esso infatti determina la

risoluzione dello stesso, è se implementato in maniera errata, produrrà una discrepanza tra il tempo contato

e quello effettivamente trascorso. Poiché il valore è consistente (1s), viene utilizzato il Timer1, la cui

peculiarità è quella di avere 16 bit nel proprio registro; come settato nel registro T1CON, il prescaler utilizzato

è settato a 1, la frequenza è 32768 Hz, tipica del quarzo esterno. Il timer viene fatto partire da 65536 – 32768

= 32768, ovvero l’esatta metà. Infatti, valendo un singolo tick 30,518μs, ricavo con una semplice

moltiplicazione che 30,518μs ∙ 32768 = 1s.

4. Definizioni variabili

Il passo successivo è definire le variabili che saranno poi necessarie per l’esecuzione del programma. Viene

utilizzata la direttiva RES che permette di riservare il numero di byte desiderati e di associarli ad un nome a

scelta del programmatore. Prima della direttiva RES si deve però specificare in quale punto della memoria

RAM devono essere prese le locazioni di memoria da dedicare alle variabili. A questo scopo si utilizza la

direttiva UDATA la quale indica appunto l’inizio di una sezione dati in cui è possibile riservare locazioni di

memoria RAM da utilizzare come variabili. La direttiva UDATA può essere seguita da un indirizzo che specifica

il punto preciso in cui far iniziare la sezione (codice non rilocabile) oppure, come nel nostro caso, se non viene

specificato alcun indirizzo la sezione viene allocata automaticamente dal linker (codice rilocabile).

UDATA_SHR

secondi_uni RES 1

secondi_dec RES 1

minuti_uni RES 1

minuti_dec RES 1

In questo modo riservo 1 byte di memoria ad ognuna di queste label, in maniera tale da poterle gestire

successivamente all’interno del mio codice. La direttiva UDATA_SHR che qui viene usata è simile a UDATA

con la differenza che in questo caso viene riservata una porzione di RAM condivisa tra tutti i banchi.

5. Definizione di reset

Nel codice è incluso il “vettore di reset”. Sarà il comando eseguito una volta che sulla scheda sarà attivato il

pulsante di reset: RST_VECTOR CODE 0x0000

pagesel start

goto start

La direttiva “pagesel” si occupa proprio di generare automaticamente il codice necessario per commutare la

pagina di ROM a quella in cui si trova l’indirizzo associato alla label indicata (in questo caso START).

6. Programma principale

Viene indicato l’inizio della porzione di codice che costituisce il corpo principale del programma; la direttiva

CODE dichiara una sezione di codice da allocare in ROM.

Con la label start comincia la stesura del codice; questo è inoltre il punto in cui riparte il codice dopo la

pressione del tasto di reset Pag. 5 di 22

start pagesel INIT_HW

call INIT_HW

Andando così a chiamare immediatamente la subroutine INIT_HW, che andrà ad impostare tutti i dispositivi

necessari al funzionamento del programma. Essendo una call, deve essere selezionata prima la pagina di

memoria adeguata con “pagesel”.

INIT_HW

OPTION_REG

Il primo elemento ad essere impostato è il Timer0:

banksel OPTION_REG

movlw B'00000111'

movwf OPTION_REG

per prima cosa vado a configurare il registro OPTION_REG che gestisce il Timer0 il quale verrà

utilizzato per generare l’attesa di 20ms necessaria per il debouncing e varie altre funzionalità. Per

poter andare a lavorare su un registro è necessario portarsi sul banco di memoria in cui si trova quel

registro. Questa operazione viene eseguita grazie alla direttiva “banksel”.

Muovendo la literal scelta in OPTION_REG, imposto pull-up porta B abilitati (bit7=0), TMR0

incrementato da clock interno (1 MHz) (bit5=0), prescaler assegnato a TMR0 (bit3=0) e un prescaler

a 256 (bit0-2=111).

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

call DELAY

banksel PORTD

bsf PORTD, 0

goto main_loop2

Nel momento in cui entro, sono passato da timer spento a timer acceso: per questo motivo,

dopo aver atteso il rilascio del codice e impostato il solito debouncing, setto il bit 0 di PORTD

per l’accensione del led, e ritorno a main_loop2 riprendendo la normale attività del

cronometro.

Se però il TMR1ON era impostato a 0, svolgo le stesse azioni svolte per il caso precedente,

settando tuttavia lo spegnimento del LED invece dell’accensione.

Se invece il pulsante non viene premuto, non avviene alcuno stop del cronometro, che continua a scorrere e

ad incrementare i numeri, grazie alla subroutine presente in main_loop2:

main_loop2 banksel PIR1

btfss PIR1,TMR1IF

goto main_loop1

pagesel increments

Pag. 11 di 22

call increments

pagesel transfer_data

call transfer_data

goto main_loop

Con le seguenti stringhe di codice attendo che il flag del timer raggiunga il valore 1: in caso negativo ritorno

a controllare se il pulsante è stato premuto. Una volta che TMR1IF=1, è arrivato il momento di incrementare

il valore del timer, con la call increments, la quale racchiude in realtà gli incrementi di ogni cifra del

cronometro:

incr_sec_uni incf secondi_uni

movf secondi_uni, w

sublw .10

btfss STATUS, Z

return

Il primo controllo viene fatto sulle unità dei secondi: copiato il valore all’interno del registro, si va a

sottrarlo al numero 10, senza salvarlo nel registro. In questo modo si controlla se il valore preso in

considerazione è <10, infatti se il bit Zero del registro STATUS vale 1, (la sottrazione dà come risultato

0), allora si salta ad aumentare le decine dei secondi, mentre se il bit ha valore 0, si ritorna alla

chiamata della call.

incr_sec_dec pagesel reset_secondi_uni

call reset_secondi_uni

incf secondi_dec

movf secondi_dec, w

sublw .6

btfss STATUS, Z

return

Essendo ora giunto nelle decine dei secondi, bisogna in primo momento resettare il valore delle unità

dei secondi, in maniera tale da passare da 9 a 0. L’unica differenza di questa subroutine con la

precedente è il valore da sottrarre: non sarà più 10, ma 6, poiché i secondi si azzereranno una volta

toccato il traguardo di 59.

Anche per i minuti il codice rimane pressoché identico, prestando particolare attenzione ad annullare

le cifre precedentemente trattate e sottrarre il numero corretto dal registro accumulatore.

Al termine degli incrementi, con la call ritorno al punto di chiamata e continuo andando a stampare il valore

i nuovi valori ricavati.

In chiusura è necessario ritornare al main_loop dopo il generale reset e stampa del timer per dare ciclicità al

sistema.

Al termine del codice, la end segnala la fine della scrittura.

; ****************************Tesina 150****************************************

; Descrizione software:

; - Implementazione di un cronometro (mm:ss) con risoluzione un secondo.

; - Visualizzazione cronometro tramite porta seriale.

; - Gestione debounce pulsante tramite timer.

; - LED acceso quando il cronometro è attivato.

;

; Descrizione hardware: Pag. 12 di 22

; - scheda Cedar Pic Board.

; - MCU: PIC16F887 (clock interno 4 MHz)

; - Pulsanti: RB0

; - Led: RD0

;

; ******************************************************************************

list p=16f887 ; direttiva che definisce il tipo di processore

#include <p16f887.inc> ; file che contiene le definizioni dei simboli (nomi registri, nomi bit

; dei registri, ecc).

;*******************************************************************************

;*** Configuration bits ***

; I bit di configurazione (impostazioni dell'hardware settate in fase di

; programmazione del dispositivo) sono definiti

; tramite una direttiva nel codice.

; Impostazioni importanti:

; - Watchdog timer non attivato (_WDT_OFF).

; - Low voltage programming disattivato (_LVP_OFF), altrimenti il pin RB3 (porta B)

; non può essere utilizzato come I/O generico, anche se qui non è necessario.

__CONFIG _CONFIG1, _INTRC_OSC_NOCLKOUT & _CP_OFF & _WDT_OFF & _BOR_OFF &

_PWRTE_OFF & _LVP_OFF & _DEBUG_OFF & _CPD_OFF

;*******************************************************************************

;*** Definizione costanti ***

; TIMER0:

; Costanti con cui settare il contatore del timer per contare un determinato intervallo di tempo.

; Poiché le impostazioni del timer (vedere subroutine INIT_HW) determinano un periodo per un singolo

; incremento di 256 us, un periodo di 20 ms (ad esempio) equivale a 78 incrementi (ticks).

; Il timer setta il flag di oveflow (bit T0IF del registro INTCON) al

; passaggio da 0xFF a 0x00, per cui occorre impostare il contatore TMR0

; al valore 256 - 78.

; (La direttiva EQU assegna una costante ad una label).

tmr_20ms EQU (.256 - .78) ; valore iniziale del contatore di TMR0 per contare 20 ms

; TIMER1:

; In base alle impostazioni del timer, un periodo di 1 s (necessario per

; eseguire lo scorrimento delle unità dei secondi) corrisponde a

; 32768 incrementi, se il prescaler è 1:1. Il contatore di timer1 e' a 16 bit, per cui occorre

; impostare il contatore inizialmente a 65536 - 32768

tmr_1s EQU (.65536 - .32768) ; valore iniziale del contatore di timer1 per contare 1 s

;*******************************************************************************

Pag. 13 di 22


ACQUISTATO

1 volte

PAGINE

22

PESO

684.85 KB

PUBBLICATO

+1 anno fa


DESCRIZIONE APPUNTO

Breve tesina sull'implementazione di un firmware in Assembly. Descrizione del software: lo scopo è implementare un cronometro con risoluzione di un secondo che visualizzi il tempo tramite porta seriale nel formato "mm:ss". Un pulsante permetta di arrestare e riattivare il cronometro, mentre un LED sia acceso quando il cronometro è attivo.
Viene utilizzata una scheda Cedar "PicBoard - Studio".


DETTAGLI
Corso di laurea: Corso di laurea in ingegneria elettronica
SSD:
A.A.: 2017-2018

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher brandontesla di informazioni apprese con la frequenza delle lezioni di Sistemi elettronici e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Politecnico delle Marche - Univpm o del prof Falaschetti Laura.

Acquista con carta o conto PayPal

Scarica il file tutte le volte che vuoi

Paga con un conto PayPal per usufruire della garanzia Soddisfatto o rimborsato

Recensioni
Ti è piaciuto questo appunto? Valutalo!

Altri appunti di Sistemi elettronici

4 Esercitazioni Assembly Sistemi elettronici - No codice
Esercitazione
Appunti Sistemi Elettronici
Appunto
Sistemi Elettronici, Prof. Laura Falaschetti, Tutti gli argomenti del corso
Appunto
Rielaborazione - Sistemi Elettronici
Appunto