Salve a tutti!
Sto provando a realizzare un programma per il Pic 16f887 montato in una picboard per un progetto universitario:
Il pic è inizialmente in stato di sleep per poi risvegliarsi ogni 5 secondi, leggere il valore di temperatura, stamparlo nel formato a due cifre sulla porta seriale eusart e poi tornare in sleep.
Ho messo anche il lampeggio di un led ad ogni lettura.
Per contare 5 secondi ho utilizzato il timer1 e gestisco la lettura della temperatura (conversione adc inclusa) tramite interrupt.
Il problema è che stampa ogni 5 secondi con il relativo lampeggio, ma valori senza alcun senso.
Aiuto please!
;
; Descrizione software:
; - Microcontrollore in sleep
; - Uscita dallo sleep ogni 5 secondi (all'overflow del timr1)
; - Lettura e stampa temperatura su seriale EUSART
; - Accensione/spegnimento LED ad indicare stato sleep
;
; Descrizione hardware:
; - scheda Cedar Pic Board (vedere schematico).
; - MCU: PIC16F887 (clock interno 4 MHz)
; ***********************************************************
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).
#include "macro.inc" ; definizione di macro utili
;**********************************************************************
; *** Configuration bits ***
; I bit di configurazione (impostazioni dell'hardware settate in fase di
; programmazione del dispostitivo) sono definiti
; tramite una direttiva nel codice.
; Impostazioni importanti:
; - Wathcdog timer disattivato (_WDT_OFF).
; - Low voltage programming disattivato (_LVP_OFF), altrimenti il pin RB3 (porta B)
; non puo' essere utilizzato come I/O generico.
__CONFIG _CONFIG1, _INTRC_OSC_NOCLKOUT & _CP_OFF & _WDT_OFF & _BOR_OFF & _PWRTE_OFF & _LVP_OFF & _DEBUG_OFF & _CPD_OFF
tmr_5s EQU (.65536 - .20480) ;(setto prescaler per contare 5s)
; variabili in RAM (shared RAM)
UDATA_SHR
fsr_temp RES .1 ;variabile per salvare fsr quando entro/esco da routine interrupt
temperature RES .2 ;variabile per decine
tmp RES .2 ;variabile temporanea e per unita
w_temp RES .1 ;variabile per salvare W quando entro/esco da routine interrupt
status_temp RES .1 ;variabile per salvare contenuto registro STATUS quando entro/esco da routine interrupt
pclath_temp RES .1 ;variabile per salvare contenuto registro PCLATH quando entro/esco da routine interrupt
cansleep RES .1
; reset vector (quando viene premuto il pulsante di reset questa è la prima operazione che viene eseguita)
rst_vector CODE 0x0000
pagesel start
goto start
;********************************************MAIN***************************************************************************
; programma principale
MAIN CODE
start
pagesel initHw
call initHw ; inizializzazione hardware
; abilita interrupt timer1
setRegK PIE1, B'00000001'
; Abilita interrupt delle periferiche (tra cui timer1)
BANKSEL INTCON
bsf INTCON, PEIE
PAGESEL reload_timer1
call reload_timer1
bsf cansleep,0
main_loop
; Dato che tutto il lavoro e' svolto dalla routine di interrupt,
; il programma principale potrebbe mandare il microcontrollore
; in modalita' sleep per essere risvegliato dal successivo
; interrupt.
; Utilizziamo percio' un bit che indica quando il programma puo'
; andare in sleep, che sara' settato dall'interrupt quando opportuno.
waitSleep
bcf INTCON, GIE ; disabilita interrupt globalmente
btfsc cansleep, 0 ; verifico possibilità di ingresso nello stato di sleep
goto goSleep
bsf INTCON, GIE
goto waitSleep
goSleep
BANKSEL PORTD
bcf PORTD,3 ; spegne LED4 prima di sleep
sleep ; la CPU si ferma!
;(*******************************INTERRUPT********************************************)
; a questo punto la CPU si e' risvegliata per via di un
; interrupt, che nel nostro caso puo' essere solo il timer1.
; Avendo riabilitato gli interrupt (bit GIE), viene subito
; eseguita la routine di interrupt, quindi il programma
; continua.
;(*******************************INTERRUPT********************************************)
BANKSEL PORTD
bsf PORTD,3 ; accende LED4 dopo risveglio
bsf INTCON, GIE
goto main_loop ; ripete il loop principale del programma
;********************************************MAIN***************************************************************************
reload_timer1
; ricarica contatore timer1 per ricominciare conteggio.
; In modalita' asincrona, occorre arrestare il timer prima
; di aggiornare i due registri del contatore
banksel T1CON
bcf T1CON, TMR1ON ; arresta timer
; le funzioni "low" e "high" forniscono il byte meno e piu'
; significativo di una costante maggiore di 8 bit
banksel TMR1L
movlw low tmr_5s
movwf TMR1L
movlw high tmr_5s
movwf TMR1H
banksel PIR1
bcf PIR1, TXIF
banksel T1CON
bsf T1CON, TMR1ON ; riattiva timer
bsf cansleep,0
return
;************************************INIZIO ROUTINE INTERRUPT************************************
IRQ CODE 0x0004
INTERRUPT
; Salvataggio stato registri CPU (context saving).
; A differenza di quasi tutte le altre architetture, il PIC non salva lo stato
; della CPU automaticamente all'ingresso di un interrupt (e non lo ripristina
; all'uscita). Questo perche' non esiste uno stack utilizzabile genericamente,
; ma solo uno stack limitato al salvataggio ed al ripristino di PC.
; In genere quindi, per assicurare che il programma principale funzioni sempre
; correttamente anche in presenza di interrupt, occorre gestire queste due
; fasi manualmente. I registri da salvare per il PIC16 sono W, STATUS e PCLATH.
movwf w_temp ; copia W in w_temp
swapf STATUS,w ; inverte i nibble di STATUS salvando il risultato in W.
; Questo trucco permette di copiare STATUS senza alterarlo
; (swapf e' una delle poche istruzioni che non alterano i bit di stato).
movwf status_temp
movf PCLATH,w ; copia il registro PCLATH in W (registro da salvare perché contiene i
; bit più significativi del program counter, usati da GOTO e CALL,
; e settati dalla direttiva pagesel).
movwf pclath_temp ; copia W (= PCLATH) in pclath_temp.
movf FSR, w
movwf fsr_temp ; altro registro usato dalla routine
; e quindi da salvare
banksel PIR1
btfss PIR1, TMR1IF ; controllo se proprio il timer1 mi ha fatto entrare in interrupt
goto $-1
banksel PIE1
btfss PIE1, TMR1IE
goto $-1
banksel PIR1
bcf PIR1, TMR1IF
;*******************************************ADC************************************************************************************
PAGESEL readAdc
call readAdc ; chiama routine lettura ADC con canale 6
PAGESEL computeTemp
call computeTemp ; calcola valore effettivo di temperatura
PAGESEL formatNumber
call formatNumber; formatta numero in 2 cifre decimali
banksel PIR1
bcf PIR1,ADIF
PAGESEL reload_timer1
call reload_timer1
goto irq_end
readAdc
; Legge valore analogico dal canale 6
banksel ADCON0
bsf ADCON0,GO ; inizia conversione
btfsc ADCON0,GO ; controllo go sia 0
goto $-1
banksel ADRESH
movf ADRESH,w; copia valore campionato in W
movwf tmp
banksel ADCON0
bcf ADCON0, ADON ; disattiva modulo ADC
return
computeTemp
; routine di conversione da tensione a gradi centigradi
; input:
; W: tensione campionata (0-255, corrispondente a 0-3.3 V)
; output:
; W: risultato in gradi
;
; Dal datasheet del sensore di temperatura MCP9701A:
; T = (Vadc - V0) / Tc [ dove V0 = 400 mV, Tc = 19.5 mV/C]
; Convertendo da tensioni a valori binari, si ha:
; T = (Nadc - 31) / 1.51
; che puo' essere approssimata in calcoli interi a 8 bit come:
; T = (Nadc - 31) * 2 / 3 [ approssimaz. 1.51 ~= 1.5 = 3/2 ]
; Questa formula permette di calcolare temperature fino a 84 C
; senza incorrere nell'overflow della variabile a 8 bit
;movwf tmp ; questo è il valore della temperatura bufferizzato nell'ADC (non è ancora formattato per la stampa!)
movlw .31
subwf tmp, f ; tmp = tmp - 31
bcf STATUS, C
rlf tmp, f ; tmp = tmp * 2 (usando lo shift a sinistra)
; divisione per 3, effettuata con semplice algoritmo di sottrazioni
; successive del valore 3, incrementando ogni volta il risultato,
; fino a che il minuendo non diventa negativo
clrf temperature ; valore iniziale del risultato = 0
loop_div3
movlw .3
subwf tmp, w ; w = tmp - 3
btfss STATUS, C
goto end_div3 ; se risultato negativo (C=0): fine divisione, se c'è il carry continuo
movwf tmp ; tmp = tmp - 3
incf temperature, f ; incrementa risultato di 1
goto loop_div3 ; continua sottrazione
end_div3
movf temperature,w
return
formatNumber
; calcolo delle 3 cifre decimali di un numero e scrittura
; delle stesse in codice ASCII su printBuff
; input:
; W: valore da formattare
;
movwf tmp ; salva temporaneamente il valore
; divisione per 100
clrf temperature ; risultato della divisione (centinaia)
loop_div100
movlw .100
subwf tmp, w ; w = tmp - 100
btfss STATUS, C
goto end_div100 ; se risultato negativo (C=0): fine divisione
movwf tmp ; tmp = tmp - 100
incf temperature, f ; incrementa risultato di 1
goto loop_div100 ; continua sottrazione
end_div100
; "temperature" contiene le centinaia, "tmp" le decine e unita'
; (resto della divisione)
movlw '0' ; il codice ASCII del carattere '0' sommato
addwf temperature,w ; alla cifra fornisce il codice ASCII della
;movwf printBuff ; cifra in questione
; divisione per 10
clrf temperature ; risultato della divisione (decine)
loop_div10
movlw .10
subwf tmp, w ; w = tmp - 10
btfss STATUS, C
goto serial_print ; se risultato negativo (C=0): fine divisione
movwf tmp ; tmp = tmp - 10
incf temperature, f ; incrementa risultato di 1
goto loop_div10 ; continua sottrazione
serial_print
; Trasmetto decine
movlw '0'
addwf tmp,w ; w + tmp = w ; il codice ASCII del carattere '0' sommato alla cifra fornisce il codice ASCII della cifra in questione
banksel TXREG
movwf TXREG
banksel PIR1
btfss PIR1,TXIF
goto $-1
; Trasmetto unità
movlw '0'
addwf temperature,w ; w + temperature = w
banksel TXREG
movwf TXREG
banksel PIR1
btfss PIR1,TXIF
goto $-1
; Trasmetto invio
movlw .10
BANKSEL TXREG
movwf TXREG ;scrivo il carattere invio
banksel PIR1
btfss PIR1,TXIF
goto $-1
return
;************************************ADC*******************************************************************************************************************
;************************************SERIALE************************************
; "tmp" contiene le decine, "temperature" le unita'
; (resto della divisione)
;************************************SERIALE************************************
irq_end
movf fsr_temp,w
movwf FSR
movf pclath_temp,w ; copia pclath_temp in W
movwf PCLATH ; copia W in PCLATH
swapf status_temp,w ; inverte i nibble di status_temp salvando il risultato in W
; anche in questo caso serve a non alterare STATUS stesso
movwf STATUS ; copia W (che contiene lo STATUS originale ripristinato dopo 2 inversioni) in STATUS
; per ripristinare W senza alterare STATUS appena ripristinato, si utilizza sempre swapf
swapf w_temp,f ; prima inversione di w_temp, risultato su se stesso
swapf w_temp,w ; seconda inversione di w_temp, risultato in W (W contiene il valore precedente all'interrupt)
bsf cansleep,0
retfie ; uscita da interrupt e ritorno al punto in cui il programma era stato interrotto
;************************************FIME INTERRUPT************************************
initHw ; inizializzazione hardware per scheda PIC Board - Studio
; registro INTCON:
; - tutti gli interrupt inzialmente disabilitati
; (verranno abilitati nel programma principale, quando tutte
; le periferiche saranno correttamente inizializzate)
clrf INTCON
;port A:
; RA0-RA5: analog inputs
; RA6-RA7: digital outputs (flash_ce, bus_switch)
setRegK PORTA, B'01000000' ; flash_ce = 1
setRegK ANSEL, B'11111111' ; set RE0-RE2 as analog too
setRegK TRISA, B'00111111'
;porta D:1..3settato come output (LED)
setRegK TRISD, 0xF0
setReg0 PORTD
;ADC
setRegK ADCON0, B'11011000' ; clock = RC, ch. 6, ADC off
setReg0 ADCON1 ; use vdd and vss as reference
banksel ANSEL
setRegK ANSEL, B'11111111' ;imposto i pin adc come analogici
banksel PIR1
bcf PIR1,ADIF ;azzero flag ADC
;portE:
setReg0 TRISE ;abilito porta per il sensore
; Timer1
; Impostazioni:
; - usa quarzo esterno (32768 Hz)
; - modalita' asincrona (funziona con quarzo esterno anche durante sleep)
; - prescaler = 1:8
; - Avvio timer in stop
; Con la frequenza del quarzo ed il prescaler a 8 si ha:
; - singolo tick ~= 24.414 us
; - periodo max = 5 s (contatore a 16 bit)
; - 24.414us ? 1tick = 5 ? x -> x = 5/0,000244141 = 20480 tick -> 65536 - 20480
banksel T1CON
movlw B'00111110';(abilito clock esterno ma non sincronizzo con input clock esterno)
movwf T1CON
;EUSART
; baud rate = 19200 (BRGH = 1, BRG16 = 0) ;(high speed, 8 bit baud rate)
; TXEN = 1 (abilito trasmissione)
; SPEN = 1 (abilito seriale)
; SYNC = 0 (configuro per operazioni asincrone)
banksel TXSTA
bsf TXSTA,TXEN
bsf TXSTA,SPEN
bcf TXSTA,SYNC
bsf TXSTA,BRGH
banksel RCSTA
bsf RCSTA,SPEN
banksel BAUDCTL
bcf BAUDCTL,BRG16
setRegK SPBRG, .12
return
;fine codice
END ;direttiva di fine codice