Using crystals with PICs and their accuracy ..#2

Thread Starter

cmartinez

Joined Jan 17, 2007
8,768
I've just looked at my circuit, which has been working for 12 hours straight, and it's about 1.5 seconds slower than the time reported by my computer. And that is a significant improvement from the almost 7 missing seconds in my previous version during the same time period.

So yes, evidence points to a glitch caused by my going against the datasheet's advice and changing the value of Timer1 on the fly.

What I am going to do now is implement the compare mode logic that was previously suggested to generate a 1 second interrupt and then get back with my results later tonight.

Thanks all for all your help, it's made an enormous difference.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,768
Nope... I can't make things work...

I'm splitting my brains with this architecture, which is still a bit unfamiliar to me. I tried to set up Timer1 to work in Compare Mode and set its interrupt accordingly, and the stupid thing won't tick!

Here's my code:

Code:
Start_Timer1:

       banksel PIE1               ;Bank 1
       bsf PIE1, CCP1IE           ;enable Timer1 compare mode interrupt
     
       ;Set  Generate Software Interrupt Mode in Timer1 Compare Mode
       ;CCP1CON: CCP1 CONTROL REGISTER
       ;    P1M   |     DC1B   |         CCP1M         |
       ;  7     6     5     4     3     2     1     0
       banksel CCP1CON            ;bank 5
       movlw b'00001010'
       movfw CCP1CON
    
       ;Set Timer1 to count controlled by the Timer1 gate function
       ;T1GCON: TIMER1 GATE CONTROL REGISTER
       ; TMR1GE | T1GPOL | T1GTM | T1GSPM | T1GGO-/DONE | T1GVAL |    T1GSS    |
       ;   7        6        5       4           3          2        1      0 
       banksel T1GCON             ;Bank 0
       movlw b'10000000'
       movfw T1GCON
    
       ;Clearing the interrupt flag and the counter before starting Timer1 is
       ;recommended by the datasheet
       clrf PIR1                  ;clear all peripheral interrupt flags
       clrf TMR1L                 ;clear both bytes of the
       clrf TMR1H                 ;Timer1 counter

       ;and lastly, set Timer1 parameters and turn it on
       ;T1CON:
       ;  TMR1CS    |   T1CKPS    |T1OSCEN| /T1SYNC |  -  | TMR1ON|
       ; 7      6      5      4       3       2        1      0   |

       ;Clock source is pin or oscillator  (10) TMR1CS
       ;Prescaler is 1:1                   (00) T1CKPS
       ;Oscillator circuit enabled          (1) T1OSCEN
       ;External Clock Input Sync disabled  (0) /T1SYNC
       ;bit 1 is not used                   (0) -
       ;Timer1 start                        (1) TMR1ON
       movlw b'10001101'
       movwf T1CON

  return
A little help would be thoroughly appreciated.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,768
I think I found the culprit... according to this document, if one wishes to enable CCP-Compare Mode, the TRIS bit for the CCPx pin must be cleared to place said pin in output mode.

Problem is that, in my design, both pins that can optionally be used as CCP are being used as inputs. The manual says that "The CCP1 does not asset control of the CCP1 in this mode". Which means that the state of said said pin would be unaffected by the module's internal logic, but it must be configured as an output nevertheless... Bummer... I'm going to either have to find a way around the logic that I've already envisioned, or I'm going to have to redesign the PCB.
 

ericgibbs

Joined Jan 29, 2010
21,448
hi C,
For low speed serial data transfer, ie: 9600 Baud, you are over thinking the need for a super accurate MCU clock.

I have/do use 9600 Baud serial data transfers of 50 to 80 characters, at one second intervals, using internal MCU and external crystal clocks, have never seen any suggested 'accumulated' timing errors over these strings.

That is the point of asynchronous data transfer, providing the UART clock falls within the limits of the MCU [image attached], there should be no problems with the timing.

Look thru this link.
E
https://erg.abdn.ac.uk/users/gorry/eg2069/async.html
 

Attachments

AlbertHall

Joined Jun 4, 2014
12,626
This PIC has the ability to move peripheral inputs and outputs to other pins which may help with your problem.
"12.1 Alternate Pin Function
The Alternate Pin Function Control (APFCON)
registers are used to steer specific peripheral input and
output functions between different pins. The APFCON
registers are shown in Register 12-1. For this device
family, the following functions can be moved between
different pins.
• RX/DT
• TX/CK
• SDO
• SS (Slave Select)
• T1G
• P1B
• CCP1/P1A
These bits have no effect on the values of any TRIS
register. PORT and TRIS overrides will be routed to the
correct pin. The unselected pin will be unaffected."
 

JohnInTX

Joined Jun 26, 2012
4,787
I think I found the culprit... according to this document, if one wishes to enable CCP-Compare Mode, the TRIS bit for the CCPx pin must be cleared to place said pin in output mode.
It doesn't have to. In the Software Interrupt Mode - interrupt when timer matches the CCP register (CCPIM = 1010) - or the Special Event Trigger - Interrupt and clear TIMER 1 on a match (CCPIM = 1011) - the output pin is not used. See 24.2.3 and 24.2.4 in the datasheet.
Based on earlier posts you could use the first one and just update the CCP register value on interrupt per MrChips' suggestion OR use the special event trigger to reset the timer to 0 on each second. Note that the special event trigger also starts an ADC conversion if enabled so that may be an issue.

DId you get it ticking?
In #57 do you really want the gate function or just a periodic interrupt?
I don't see you've set the CCP registers in there.
I'm running it up to see if there are other issues.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
In #57 you weren't setting a value for the CCP regsisters.
Also, you used movfw in a couple of places instead of movwf to store W in the register:
banksel CCP1CON ;bank 5
movlw b'00001010'
movfw CCP1CON
I am not a fan of that macro. I much prefer to use
movf reg,dest
instead of movfw because it is too easy to confuse fw with wf. Also, I just like the explicit destination specifier. movwf then stands out as writing to a register with no flags affected. Easier to read.

Anyway, I redid your code to show what I think is a better init sequence and to test an idea I had to use both the CCP compare interrupt at 32768 AND the TIMER1 interrupt when the timer overflows after another 32768 counts. It works just fine on the sim. See the comments at the top.

I also threw in a couple of things I do to make coding easier manage (MESSG, #define the flag etc). Delete if you don't just dig it.
Finally, I use the old MPASM. I don't know if all that fancy stuff works on the new, improved XCASM that comes with MPLABX >= 5.40.

Hope it helps, have fun!
Set flag at one second intervals.:
   ; This sample program sets timer 1 / CCP1 up to interrupt when the timer
    ; reaches a count of 32768.  It ALSO interrupts when the timer overflows
    ; after another 32768 counts.  When uses with a 32768Hz oscillator, the
    ; result is that the OneSecondTik flag gets set once each 32768 counts
    ; i.e. once each second.
    ; Using both interrupts this way means you don't have to use the special
    ; event trigger to reset TIMER1 and interfere with any ADC operations you
    ; might have running.  If you are not using ADC, you can just use the
    ; special event trigger and delete the TMR1 interrupt stuff.
    
    ; NOTE: on MPSIM, there is a little jitter between the interrupt
    ; service time for CCPIF and TMR1IF. The jitter averages out and the
    ; total time for one CCP, TMR1 interrupt cycle is 2x32768 counts meaning 
    ; average, the flag gets set once each second.
    ; I haven't run it on actual hardware to see if that is a sim issue or
    ; something else.
    
    ; This runs on MPASM 5.87 / MLABX 5.10.  MPLABX 5.40 has a stripped down
    ; assembler that may not like some of the switches/defines..


    INCLUDE <p16lf1823.inc>
    ERRORLEVEL -302        ; suppress cross bank messages
 
    ;----------------
UseInternalOSC equ 1        ; for simulator initial tests, set to 0 for ext osc.
                ; see below
    ;----------------
 
    cblock  20h            ; RAM
    TimerFlags :    1   ; declare a byte of flags
    endc
    
#define OneSecondTik TimerFlags,0 ; LS bit is the flag
        
    ORG 0
    nop
    goto    main
    
    ;--------------- INTERRUPT SERVICE  ---------------------
    ; Services interrupts from CCP1 and TIMER1 overflow. 
    ; Expects those as only sources of interrupts as there is
    ; no testing of the flags - expand as req'd
    ; Expects standard PIC16F1xxx context saving
    
    ORG 0004h        
    banksel TimerFlags
    bsf    OneSecondTik    ; indicate that a tik happened
    
    banksel    PIR1
    
    bcf    PIR1,TMR1IF    ; only one will be set at a time
    bcf    PIR1,CCP1IF

    retfie
    
    ;------------------ INIT --------------------------------
    ; Set up TIMER 1 for interrupt on compare AND timer overflow
Start_Timer1:
    ; Because this is callable, kill TMR/CCP interrupts while starting up.
    ; Can be ommitted iff this routine is only executed after a full reset
    banksel    PIE1            ; kill interrupts while starting up
    bcf    PIE1,CCP1IE
    bcf    PIE1,TMR1IE
    
    banksel    T1CON            ; be sure timer is off while configuring
    clrf    T1CON
    
    banksel CCP1CON            ; bank 5
    movlw b'00001010'       ; software interrupt on match
    movwf  CCP1CON

    movlw    low .32768       ; set initial compare value
    movwf    CCPR1L
    movlw    high .32768
    movwf    CCPR1H

       MESSG "Start_Timer1: GATE mode disabled"   
       
    if UseInternalOSC
    MESSG "Start_Timer1: Using internal Fosc"
    movlw    b'00000000'    ; Sim: internal Tcyc, 1:1 prescale, OFF
    movwf    T1CON    
    else
    MESSG "Start_Timer1: Using external oscillator"
        movlw b'10001100'    ; Ext oscillator, 1:1 prescale, no sync, OFF
    movwf T1CON
    endif

       ;Clearing the interrupt flag and the counter before starting Timer1 is
       ;recommended by the datasheet
    banksel    PIR1
    clrf    TMR1L            ; clear both bytes of the
    clrf    TMR1H            ; Timer1 counter
    bcf    PIR1,TMR1IF        ; clear interrupt flags
    bcf    PIR1,CCP1IF
    
    banksel PIE1            ; Bank 1
    bsf PIE1, CCP1IE        ; enable Timer1 compare mode interrupt
    bsf PIE1, TMR1IE        ; enable TMR1 overflow interrupt
    
    
    banksel    T1CON            ; bank 0
    clrf    TimerFlags        ; clear flags
    bsf    INTCON,PEIE        ; enable peripheral interrupts
    bsf    T1CON,TMR1ON        ; start the timer
    return
  
 
    ;--------------- MAIN  ----------------------------------
main:
    call    Start_Timer1        ; configure timer
    bsf    INTCON,GIE        ; finally, enable interrupts
    
    banksel    TimerFlags
wait_loop:
    nop
    nop
    nop
    btfss    OneSecondTik    
    goto    wait_loop
    
    bcf    OneSecondTik        ; one second has elapsed
    nop
    nop
    goto    wait_loop
    
    
    END
 

JohnInTX

Joined Jun 26, 2012
4,787
This one is more flexible. It works by adding a specified value to the CCP register on each interval. It's a take-off from @MrChips ' advice in #45 and incorporates advice from others as well.
According to the sim, it is bang-on.

You could get fractional counts by loading from a list of values e.g. using this list of reload counts over 4 cycles would yield an average period of 32767.75 cycles
32768
32768
32768
32767

The list could be fixed in ROM or dynamically determined (how?) and stored in RAM.

Timer1 period counter with settable period(s):
;FILE: Cmartinez_ChangeCCP.asm
   
    ; This sample program sets CCP1 up to interrupt when the timer
    ; reaches a nominal count of 32768.  It then adds 32768 to the CCP
    ; register value for the next IRQ after another 32768 counts. 
    ; See BONUS below for more control over the interval.
   
    ; As written, when used with a 32768Hz oscillator, the
    ; result is that the OneSecondTik flag gets set once each 32768 counts
    ; i.e. once each second.
       
    ; This version does not exhibit the simulator jitter that the one using
    ; both CCP and TIMER1 interrupts did. 
   
    ; A BONUS is that the number of counts between interrupts is not limited
    ; to 32768.  The number of counts can be tuned at build time by changing
    ; CCPcountsN.
   
    ; There is no reason why the number of counts couldn't be determined on
    ; the fly or fetched for each cycle from a list of values to implement
    ; some of the fine tuning described earlier in the thread.     
    ; On the sim, the cycle count follows exactly the value of CCPcountsN.
   
    ; As before:
    ; Changing the CCP value means you don't have to use the special
    ; event trigger to reset TIMER1 and interfere with any ADC operations you
    ; might have running.  If you are not using ADC, you can just use the
    ; special event trigger and delete the TMR1 interrupt stuff.
      
    ; This runs on MPASM 5.87 / MLABX 5.10.  MPLABX 5.40 has a stripped down
    ; assembler that may not like some of the switches/defines..


    INCLUDE <p16lf1823.inc>
    ERRORLEVEL -302        ; suppress cross bank messages

    ;----------------
UseInternalOSC equ 1        ; for simulator initial tests, set to 0 for ext osc.
                ; see below
    ;----------------

    cblock  20h            ; RAM
    TimerFlags :    1   ; declare a byte of flags
    endc
   
#define OneSecondTik TimerFlags,0 ; LS bit is the flag
CCPcountsN  equ    .32768        ; how much to advance CCPR1 on each IRQ   
       
    ORG 0
    nop
    goto    main
   
    ;--------------- INTERRUPT SERVICE  ---------------------
    ; Services interrupts from CCP1 and TIMER1 overflow.
    ; Expects those as only sources of interrupts as there is
    ; no testing of the flags - expand as req'd
    ; Expects standard PIC16F1xxx context saving
   
    ORG 0004h       
    banksel TimerFlags
    bsf    OneSecondTik    ; indicate that a tik happened
   
    banksel    CCPR1L
    movlw    low CCPcountsN    ; move CCP ahead by N counts
    addwf    CCPR1L,F
    movlw    high CCPcountsN
    addwfc    CCPR1H,F
   
    banksel    PIR1
   
    bcf    PIR1,TMR1IF    ; only one will be set at a time
    bcf    PIR1,CCP1IF

    retfie
   
    ;------------------ INIT --------------------------------
    ; Set up TIMER 1 for interrupt on compare AND timer overflow
Start_Timer1:
    ; Because this is callable, kill TMR/CCP interrupts while starting up.
    ; Can be ommitted iff this routine is only executed after a full reset
    banksel    PIE1            ; kill interrupts while starting up
    bcf    PIE1,CCP1IE
    bcf    PIE1,TMR1IE
   
    banksel    T1CON            ; be sure timer is off while configuring
    clrf    T1CON
   
    banksel CCP1CON            ; bank 5
    movlw b'00001010'       ; software interrupt on match
    movwf  CCP1CON

    movlw    low CCPcountsN       ; set initial compare value
    movwf    CCPR1L
    movlw    high CCPcountsN
    movwf    CCPR1H

       MESSG "Start_Timer1: GATE mode disabled"  
      
    if UseInternalOSC
    MESSG "Start_Timer1: Using internal Fosc"
    movlw    b'00000000'    ; Sim: internal Tcyc, 1:1 prescale, OFF
    movwf    T1CON   
    else
    MESSG "Start_Timer1: Using external oscillator"
        movlw b'10001100'    ; Ext oscillator, 1:1 prescale, no sync, OFF
    movwf T1CON
    endif

       ;Clearing the interrupt flag and the counter before starting Timer1 is
       ;recommended by the datasheet
    banksel    PIR1
    clrf    TMR1L            ; clear both bytes of the
    clrf    TMR1H            ; Timer1 counter
    bcf    PIR1,TMR1IF        ; clear interrupt flags
    bcf    PIR1,CCP1IF
   
    banksel PIE1            ; Bank 1
    bsf PIE1, CCP1IE        ; enable Timer1 compare mode interrupt
;    bsf PIE1, TMR1IE        ; don't enable TMR1 overflow interrupt
   
   
    banksel    T1CON            ; bank 0
    clrf    TimerFlags        ; clear flags
    bsf    INTCON,PEIE        ; enable peripheral interrupts
    bsf    T1CON,TMR1ON        ; start the timer
    return
 

    ;--------------- MAIN  ----------------------------------
main:
    call    Start_Timer1        ; configure timer
    bsf    INTCON,GIE        ; finally, enable interrupts
   
    banksel    TimerFlags
wait_loop:
    nop
    nop
    nop
    btfss    OneSecondTik   
    goto    wait_loop
   
    bcf    OneSecondTik        ; one second has elapsed
    nop
    nop
    goto    wait_loop
   
   
    END
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,768
Also, you used movfw in a couple of places instead of movwf to store W in the register
Those are typos... I don't like to use the movfw either, I'd rather stick to natural and unaltered code instead of macros.... but the compiler is accepting that crap... dang it! ... is there a way to turn that off?

Thanks for the code, John. I'm gonna go through it to make sure I understand it before I try to implement it.

P.S.: You didn't post any pics of your BBQ last night. :-(
 

JohnInTX

Joined Jun 26, 2012
4,787
#undefine movfw ??

BBQ nothing special - pork chops on a gas grill with mesquite ‘ alder smoke from a box. I am going to try a brisket on a Weber kettle with a hardwood charcoal snake for indirect heatsoon, though.

I had to sell my original New Braunfels side draft smoker when we moved recently. Stupid.
 

Tesla23

Joined May 10, 2009
560
I would be very surprised if your oscillator is running within 5ppm of 32.768kHz. Although the crystal is specified to give you this stability (at 25C), this only specifies the stability of the series resonance frequency with a 12.5pF load. Rough calculations suggest that fractions of a pF in your loading capacitance will cost you the full 5ppm. Your board layout is one thing - I can see spare picofarads all over the place, but also important is the loading provided by the PIC. Maybe it is well defined enough and stable to give you the stability you want, but I'm skeptical.

Unless price is an issue, low ppm stability is best left to the experts, a quick look on Digikey shows devices like:

1605499013857.png

for less than $2 for 5ppm you will sleep easier!
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,768
I would be very surprised if your oscillator is running within 5ppm of 32.768kHz. Although the crystal is specified to give you this stability (at 25C), this only specifies the stability of the series resonance frequency with a 12.5pF load. Rough calculations suggest that fractions of a pF in your loading capacitance will cost you the full 5ppm. Your board layout is one thing - I can see spare picofarads all over the place, but also important is the loading provided by the PIC. Maybe it is well defined enough and stable to give you the stability you want, but I'm skeptical.

Unless price is an issue, low ppm stability is best left to the experts, a quick look on Digikey shows devices like:

View attachment 222337

for less than $2 for 5ppm you will sleep easier!
Price IS an issue... but so is accuracy. Your suggestion looks promising, and I'll be taking a serious look at it. Many thanks!
 

jpanhalt

Joined Jan 18, 2008
11,087
Depends on where it’s defined I would guess.
I have a scribble project and tried #undefine movfw to remove it. That does not work as intended. That is, the code assembles fine and continues to work as expected if movfw is used.

If instead, you use #define movfw but do not use "movfw," it also assembles fine and the code works as expected. But, if you should happen to use movfw in the code, you get an error during assembly:
Error(15)...Duplicate label ("Old" or redefining symbol that cannot be redefined)

It is probably easier just to search on "movfw," since I suspect any instances of that will be unintentional.
 

Kjeldgaard

Joined Apr 7, 2016
476
Sorry if this has been suggested.

How about using both the Timer1 Roll Over and the Timer1 Compare with 32768 Interrupts?
Two flags must be checked/cleared, but otherwise it's a one-time setup in the initialization.
 

JohnInTX

Joined Jun 26, 2012
4,787
I have a scribble project and tried #undefine movfw to remove it. That does not work as intended. That is, the code assembles fine and continues to work as expected if movfw is used.

If instead, you use #define movfw but do not use "movfw," it also assembles fine and the code works as expected. But, if you should happen to use movfw in the code, you get an error during assembly:
Error(15)...Duplicate label ("Old" or redefining symbol that cannot be redefined).
Genius.
 

jpanhalt

Joined Jan 18, 2008
11,087
No way. I must admit I am getting ready for dinner...

This assembles and works. That is Old and WREG end up .40, not .39! New = .39 Can you believe it? (Disassembly shown)
Code:
                                                  11:       #define movfw   movwf
                                                  12:    ;*******************************************************************************
                                                  13:         cblock    0x20     
                                                  14:         Old   
                                                  15:         New 
                                                  16:         endc
                                                  17:         
                                                  18:    ;*******************************************************************************
                                                  19:         org     0x0000                           
  0000    3180     MOVLP 0                        20:         pagesel   Start                                        
  0001    2802     GOTO 0x2                       21:         goto      Start                
                                                  22:    Start
  0002    3028     MOVLW 0x28                     23:         movlw     40
  0003    00A1     MOVWF 0x21                     24:         movwf     New
  0004    03A1     DECF 0x21, F                   25:         decf      New
  0005    00A0     MOVWF 0x20                     26:         movfw     Old
  0006    0000     NOP                            27:         nop
                                                  28:
Solves the problem for us dyslexics. Not sure I will use it, though. ;)
 
Top