WDT, TIMER0 16F676 delay calculation problem. Pre-scaling issue in MPASM

101 Views Asked by At

One more question on Delays. Since I'm a starter my head aching around the calculations of delay. I understand delay when somebody explaining it in their code. But when it comes to my code Im totally a mess. (Calculating a delay for 2 days). this is the code; I want to blink-Led in every 1 second on any port pin on pic16f676:


#include "p16F676.inc"

; CONFIG
; __config 0xFFFC
 __CONFIG _FOSC_INTRCIO & _WDTE_ON & _PWRTE_OFF & _MCLRE_ON & _BOREN_ON & _CP_OFF & _CPD_OFF

usec EQU 0x20

sec EQU 0x23

UNKNOWN  EQU 0x5F

    org         H'0000'                     ;Origem no endereço 00h de memória
    goto        INIT                        ;Desvia para a label início

; --- Vetor de Interrupção ---
    org         H'0004'

isr:
    MOVWF 0x5E
    ; Reinit TMR0  : TMR0 = 8
    MOVLW 8
    MOVWF TMR0
    ; CLEAR TMR FLAG T0IF : T0IF=0
    BCF INTCON, 2
    ; increase 1 microsec
    ; usec++; increase
;-----------------------------------------------------------------------------------------------------
    MOVLW 0x1
    ADDWF usec, F

    MOVLW 0x0F
    SUBWF usec, 0
    MOVWF 0x51
    BTFSS STATUS, 0
    GOTO mangata
lalaind:
    BCF STATUS, RP0   ; RA0=~RA0; // togle RA0
    MOVLW 0x01
    (MOVWF PORTA ); XORWF PORTA, F ==> replaced both    
    MOVLW 0
    MOVWF usec

mangata:
    BCF STATUS, 0x5

    RETFIE

INIT:
    BSF STATUS, 5
    CLRF TRISA
    ; TRISA = 0x00;
    CLRF TRISC

    BCF STATUS, 5
    CLRF PORTA
    ; PORTC = 0;
    CLRF PORTC
    ; GIE = 1;
    BSF INTCON, 7
    ; T0IF = 0;
    BCF INTCON, 2
    ; T0IE = 1;
    BSF INTCON, 5
    ; T0CS = 0;
    BSF STATUS, 5
    BCF OPTION_REG, 5 ;(INternal instruuction cycle clock) (clkout)
    ; PSA = 1;
    BSF OPTION_REG, 3 ;Prescalar applied to WDT
    ; TMR0 = 8;
    MOVLW 8
    BCF STATUS, 0x5
    MOVWF TMR0
    CLRF usec
    ; while(1);

loop:
 GOTO loop

 end

The code works fine except delay(In MPLAB(MPASM)).after normal 255 counts timer0 call interrupt. there usec is subtracted with 15: to check is it ignited (i think status carry will toggle when usec > 15) and led on on pin RA0. I assumed the DELAY = 255*(15*10) = 38250cycles(Sorry if im wrong).

2

There are 2 best solutions below

2
the busybee On

Let's start with the calculation for your existing code.

The figure 4-1 on page 31 of the data sheet shows the relevant details:

enter image description here

  • You set T0CS (timer 0 clock select) to 0, selecting Fosc/4 as input frequency. As you commented in your other question, the oscillator frequency is 4 MHz, thus CLKOUT is 1 MHz. This is one clock tick per cycle.
  • You set PSA (prescaler assignment) to 1, assigning the prescaler to the watchdog. We can see in the schematic, that this routes the input clock undivided to the timer.
  • The SYNC block is for the cases if you clock the timer from pin T0CKI. In this case, it does not change the frequency, so the timer is clocked by 1 MHz. The only "visible" consequence is a start delayed by 2 cycles after writing to the TMR0 register, according to the data sheet.
  • Now when the timer overflows from 0xFF to 0x00 (decimal 255 to 0), it sets its interrupt flag T0IF. You have enabled the interrupts by setting T0IE and GIE accordingly.
  • Initializing you write 8 to TMR0 and 0 to usec.
  • In the interrupt service routine you reset TMR0 with 8. With the interrupt latency of 3 cycles, 3 instructions of 1 cycle each, and the mentioned synchronization delay of 2 cycles, this adds up to 8 cycles. By chance (or by purpose?) this is exactly the value you write into TMR0.
  • To the next overflow triggering the interrupt, it will count additional 256 - 8 = 248 cycles. So the interrupts come in at a rate of:
    FT0int = CLKOUT / 256 = 1,000,000 Hz / 256 = = 3906.25 Hz
    Or you can think of 256 cycles per interrupt trigger.
  • Next, you have the variable usec counting from 0 to 14. The PIC has a kind of "inverted" carry logic, setting C to 0 if it has a borrow. In contrast to your analysis, the carry flag is cleared as long as usec is smaller than 15. subwf subtracts W from the variable, and C is set if usec is 15 (or greater).
  • Anyway, it takes 15 interrupts until you detect that overflow. So the complete toggle time is:
    256 cycles per interrupt * 15 interrupts = 3840 cycles.
    Or as frequency:
    Ftoggle = FT0int / 15 = 260,4166... Hz

Now to your goal, you want a 1 second toggle. For this you have several alternatives. These are some:

  1. Add another divider by 260 in an additional variable.
  2. Assign the prescaler to the timer. Set PS2:PS0 to 111, selecting 1:256. The input frequency of the timer is divided by 256.

The result is not exactly 1 second, but quite near. A human will not note a difference on a first glance.

Notes:

You might want to postpone the enabling of the interrupts until everything is prepared. Else depending on the situation, an unwanted interrupt might occur.

Since you write 8 to TMR0 at each overflow, you can consider to avoid this and let the timer run through. It will generate the same overflow rate.

If you want exact times (as much as the crystal allows), change the reload value of the timer so that it interrupts for example every 200 cycles. Then divide by variables for another 5000 (200 * 5000 = 1000000). Or use a prescaler of 1:64 and let the timer divide by 125. With a software divider of another 125 you reach the same goal (64 * 125 * 125 = 1000000). The secret is in integer factors that multiply to 1000000.

6
Dan1138 On

Albpy,

The code you posted will fail in a number of weird ways in the PIC16F676 because this is a crap controller.

This is a solution for your homework that does work with MPLABX v6.15, pic-as(v2.41) assembler and the simulator:

;
; File:     main.S
; Target:   PIC16F676
; Author:   dan1138
; Date:     2023-09-10
; Compiler: pic-as(v2.41)
; IDE:      MPLABX v6.15
;
; Description:
;
;   Example project for the PIC16F676 controller using the pic-as(v2.41) tool chain.
;
;   This application uses the TIMER0 interrupt to toggles the RA0 output once per second.
;
; Add this line in the project properties box, pic-as Global Options -> Additional options: 
;   -Wa,-a -Wl,-pPor_Vec=0h,-pIsr_Vec=4h,-pFacConst=3ffh
;
;                        PIC16F676
;              +------------:_:------------+
;     GND -> 1 : VDD                   VSS : 14 <- 5v0
;         <> 2 : RA5/T1CKI     PGD/AN0/RA0 : 13 <> PGD 
;         <> 3 : RA4/AN3       PGC/AN1/RA1 : 12 <> PGC 
;     VPP -> 4 : RA3/VPP       INT/AN2/RA2 : 11 <>
;         <> 5 : RC5               AN4/RC0 : 10 <> 
;         <> 6 : RC4               AN5/RC1 : 9  <> 
;         <> 7 : RC3/AN7           AN6 RC2 : 8  <> 
;              +---------------------------:
;                         DIP-14

    PROCESSOR   16F676
    PAGEWIDTH   132
    RADIX       DEC


; PIC16F676 Configuration Bit Settings

; Assembly source line config statements

; CONFIG
  CONFIG  FOSC = INTRCIO        ; Oscillator Selection bits (INTOSC oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
  CONFIG  WDTE = OFF            ; Watchdog Timer Enable bit (WDT disabled)
  CONFIG  PWRTE = OFF           ; Power-up Timer Enable bit (PWRT disabled)
  CONFIG  MCLRE = ON            ; RA3/MCLR pin function select (RA3/MCLR pin function is MCLR)
  CONFIG  BOREN = OFF           ; Brown-out Detect Enable bit (BOD disabled)
  CONFIG  CP = OFF              ; Code Protection bit (Program Memory code protection is disabled)
  CONFIG  CPD = OFF             ; Data Code Protection bit (Data memory code protection is disabled)

// config statements should precede project file includes.
#include <xc.inc>

#define _XTAL_FREQ 4000000
#define FCY (_XTAL_FREQ/4)

#define TMR0_RELOAD_VALUE (256-250+TMR0_RELOAD_OVERHEAD)
#define TMR0_RELOAD_OVERHEAD 3
#define BLINK_TIMEOUT_VALUE (FCY/250)-1

;
; Power-On-Reset entry point
;
    PSECT   Por_Vec,global,class=CODE,delta=2
    global  resetVec,start,start2
resetVec:
    pagesel start
    btfsc   STATUS,STATUS_RP0_POSITION  ; skip if we have not tried to get the calibration value
    goto    start2                      ; branch if calibration value not availablee
    goto    start               ;jump to the start up routine

;
;   Data space use by interrupt handler to save context
    PSECT   Isr_Data,global,class=RAM,space=1,delta=1,noexec
;
    GLOBAL  WREG_save,STATUS_save
;
WREG_save:      DS  1
STATUS_save:    DS  1
PCLATH_save:    DS  1
PORTA_shadow:   DS  1
BlinkTimeout:   DS  2
;
;   Interrupt vector and handler
    PSECT   Isr_Vec,global,class=CODE,delta=2
    GLOBAL  IsrVec
;
IsrVec:
    movwf   WREG_save
    swapf   STATUS,W
    banksel STATUS_save
    movwf   STATUS_save
    movf    PCLATH,W
    movwf   PCLATH_save
    pagesel IsrVec
;
TMR0_IsrHandler:
    btfsc   INTCON,INTCON_TMR0IE_POSITION
    btfsS   INTCON,INTCON_TMR0IF_POSITION
    goto    TMR0_IsrHandlerExit
    bcf     INTCON,INTCON_TMR0IF_POSITION
    movlw   TMR0_RELOAD_VALUE
    addwf   TMR0,F
    movf    BlinkTimeout+0,W
    iorwf   BlinkTimeout+1,W
    btfsc   STATUS,STATUS_Z_POSITION
    goto    TMR0_Blink
    movlw   1
    subwf   BlinkTimeout+0,F
    btfss   STATUS,STATUS_C_POSITION
    subwf   BlinkTimeout+1,F
    goto    TMR0_IsrHandlerExit
TMR0_Blink:
    movlw   BLINK_TIMEOUT_VALUE
    movwf   BlinkTimeout+0
    movlw   BLINK_TIMEOUT_VALUE>>8
    movwf   BlinkTimeout+1
    movf    PORTA_shadow,W
    xorlw   (1<<PORTA_RA0_POSITION)
    movwf   PORTA
    movwf   PORTA_shadow
TMR0_IsrHandlerExit:
;
    movf    PCLATH_save,W
    movwf   PCLATH
    swapf   STATUS_save,W
    movwf   STATUS
    swapf   WREG_save,F
    swapf   WREG_save,W
    retfie                      ; Return from interrupt
;
; Get the factory Fosc calibration constant
start:
    bsf     STATUS,STATUS_RP0_POSITION
    call    GetFactoryCal       ; get the calibration value
    movwf   OSCCAL              ; calibrate internal oscillator
start2:
    PAGESEL main                ; Start main application
    goto    main
;
; 
; Initialize main application
    PSECT   MainCode,global,class=CODE,delta=2
main:
    clrf    INTCON              ;starting point
    BANKSEL TRISA
    movlw   0xFF
    movwf   TRISA               ; Make PORTA inputs
    movwf   TRISC               ; Make PORTC inputs
    bsf     OPTION_REG,OPTION_REG_PSA_POSITION  ; assign prescaker to WDT
    bcf     OPTION_REG,OPTION_REG_T0CS_POSITION ; assign Fosc/4 as TIMER0 clock source
    BANKSEL ANSEL
    clrf    ANSEL               ; Trun off ADC analog inputs
    BANKSEL CMCON
    movlw   0x07
    movwf   CMCON               ; Turn off comparator analog inputs

    banksel PORTA_shadow
    clrf    BlinkTimeout+0
    clrf    BlinkTimeout+1

    banksel TMR0
    movlw   TMR0_RELOAD_VALUE
    movwf   TMR0
    bcf     INTCON,INTCON_TMR0IF_POSITION
;
; Application process init
;
    BANKSEL TRISA
    bcf     TRISA,PORTA_RA0_POSITION
    BANKSEL PORTA
    movlw   0
    bsf     INTCON,INTCON_T0IE_POSITION
    bsf     INTCON,INTCON_GIE_POSITION
;
; Application process loop
;
loop:
    goto    loop
;     
;
;   The factory Fosc calibration value is in a RETLW instruction
;   located as the last instruction in code space. When this opcode
;   is not present and the XC8 calibration method is used the 
;   PIC16F676 will loop indefinitely after a reset when a call 
;   to get this value is executed. 
    PSECT   FacConst,global,class=CODE,delta=2
    GLOBAL  GetFactoryCal
GetFactoryCal:

    END     resetVec            ; Tell the linker the entry point

It is up to you to discover why this implementation seems so complex.

Microchip hates developers using assembly language so the tool chain sucks.

Assembly language coding for the PIC16F is not for the weak willed. The IDE is buggy, the assembler is stupid, documentation is incomplete and support is nonexistent.