Problem Pic16f887 - Assembly program

Discussion about projects that used PIC Microcontroller, Hardware Interface, Programming Algorithm and etc......

Problem Pic16f887 - Assembly program

Postby carlo_carosi » Mon Dec 30, 2019 9:00 pm

Hello everyone!
I'm trying to make a program for the Pic 16f887 placed in a picboard with mplab using assembly language.
The pic is initially in the sleep state and wakes up every 5 seconds, read the temperature value, print it in the two-digit format on the eusart serial port and then go back to sleep.
I also put the flashing of a LED at each reading.
To count 5 seconds I used timer1 and manage the temperature reading (including ADC conversion) via interrupt.
The problem is that it prints every 5 seconds with the relative flashing, but values ​​without any sense.
Help please!

CODE: SELECT_ALL_CODE

      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      1         ;variabile per decine
tmp      RES           1         ;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
      PAGESEL   serial_print
      call   serial_print
      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
      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 2 cifre decimali e riproduzione in codice morse
      ; input:
      ;   temperature = valore in gradi da riprodurre
      ;   (il valore iniziale di temperature viene perso)
      ; Il metodo di calcolo delle 2 cifre e' il seguente:
      ;  si divide la temperatura per 10, il quoziente rappresenta le
      ;  decine mentre il resto rappresenta le unita'
      ; La divisione per 10 e' effettuata come sopra con sottrazioni
      ;  successive
      movwf tmp
      clrf temperature  ; risultato della divisione per 10 (decine)
loop_div10
      movlw .10
      subwf tmp, w       ; w = tmp - 10
      btfss STATUS, C
      goto end_div10       ; se risultato negativo (C=0): fine divisione
      movwf tmp       ; tmp = tmp - 10
      incf temperature, f      ; incrementa risultato di 1
      goto loop_div10       ; continua sottrazione
end_div10
      
      return
      

;************************************ADC*******************************************************************************************************************         
         
;************************************SERIALE************************************
         
             ; "temperature" contiene le decine, "tmp" le unita'
      ;   (resto della divisione)
serial_print   
      ; Trasmetto decine
      movlw '0'
      addwf temperature,w ; w + temperature = w   
      banksel TXREG
      movwf TXREG
      banksel PIR1
      btfss PIR1,TXIF
      goto $-1      
      ; Trasmetto unità
      movlw '0'
      addwf tmp,w ; w + tmp = 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
;************************************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

      ;Porte I/O:

      banksel TRISE
      clrf   TRISE       ;abilito porta per il sensore
      
      ;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
      bsf   ANSEL,6       ;imposto il pin AN6 come analogico
      banksel PIR1   
      bcf   PIR1,ADIF       ;azzero flag ADC   
      ; 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)
      setRegK TXSTA, B'00100100'
      setRegK RCSTA, B'10000000'
      setReg0 BAUDCTL ;(BRG16=0)
      setRegK SPBRG, .12

      return
      ;fine codice   
   
   END      ;direttiva di fine codice

carlo_carosi
Greenhorn
 
Posts: 2
Joined: Mon Dec 30, 2019 8:46 pm

Re: Problem Pic16f887 - Assembly program

Postby ober » Sun Jan 05, 2020 6:06 am

It has been more than 10 years since we engaged with Assembly code, so sorry I cannot go through the long list of code :D

It seems most of the code is working fine, just the printing of it is not, right? Check the size of the registers, maybe from 16-bit to 8-bit or the conversion from value to ASCII for printing?
Ober Choo
Cytron Technologies Sdn Bhd
www.cytron.com.my
User avatar
ober
Moderator
 
Posts: 1486
Joined: Wed Apr 15, 2009 1:03 pm

Re: Problem Pic16f887 - Assembly program

Postby carlo_carosi » Tue Jan 07, 2020 6:54 pm

Thank you for the answer!
I have double checked the serial bits now but they are all correct!
I really believe that the problem is in the formatting of the values ​​(to get an ASCII code of a number just add it to '0').
So I have no idea what the mistake is.
carlo_carosi
Greenhorn
 
Posts: 2
Joined: Mon Dec 30, 2019 8:46 pm


Return to PIC Microcontroller

Who is online

Users browsing this forum: No registered users and 8 guests