PIC16F882: 14us interrupt overhead when using TMR0

Discussion in 'Embedded Systems and Microcontrollers' started by Robin66, May 24, 2016.

  1. Robin66

    Thread Starter Member

    Jan 5, 2016
    102
    3
    Hi guys, I've spent 2 night trying to suss this and am still perplexed. I've a PIC 16F882 with an external 20MHz crystal giving 5Mips. I've confirmed this with the RB0 output. I want to create an interrupt every ~20us, so I thought 100x 200ns instruction period == 20us. However when I set TMR0 = 255-100, I get an interrupt period of 34us. If I set TMR0=255-200, I get a period of 54us so the marginal change is correct ie. 20us per 100 change in TMR0 seed. But where's the fixed 14us overhead coming from? I expected to lose a few instructions, but 14us is 70 instructions! I've tried using a prescaler on TMR0 but it didn't fix the missing instructions. I've confirmed the interrupt period by observing the RB1 output. Code attached.

    Code (Microchip Assembler):
    1. ;**********************************************************************
    2. ; Li-ion balancer
    3. ; PIC16F882, 20MHz, origin
    4. ;**********************************************************************
    5.  
    6.     list      p=16f882           ; list directive to define processor
    7.     #include <p16F882.inc>       ; processor specific variable definitions
    8.     ;errorlevel  -302              ; suppress message 302 from list file
    9.    
    10. __config _CONFIG1, h'20f2'  ;
    11.             ; 0010 - nyi,nyi,debug disabled,LVP off
    12.             ; 0000 - Failsafe disabled, I/E switchover disabled, BOR disabled
    13.             ; 1111 - Dataprotection disabled,Codeprotection disabled, MCLR, INTOSC on
    14.             ; 0010 - Powerup timer ON, WDT off, 20Mhz crystal on OSC1-2
    15.  
    16.     cblock 0x20    ; Begin General Purpose-Register
    17.     countLED
    18.     nCycles ; cycles per interrupt
    19.     w_temp
    20.     status_temp
    21.     endc
    22.  
    23.     ;***** VARIABLE DEFINITIONS
    24. ;w_temp        EQU     0x71        ; variable used for context saving
    25. ;status_temp   EQU     0x72        ; variable used for context saving
    26.  
    27.  
    28. ;**********************************************************************
    29.     ORG     0x000             ; processor reset vector
    30.     goto    setup              ; go to beginning of program
    31.    
    32.     ORG     0x004             ; interrupt vector location
    33.     movwf   w_temp            ; save off current W register contents
    34.     movf    STATUS,w          ; move status register into W register
    35.     movwf    status_temp       ; save off contents of STATUS register
    36.  
    37.     call isr
    38.        
    39.     ; set TMR0 so that we have nCycles per interrupt
    40.     movfw nCycles
    41.     subwf h'FF',w
    42.     movwf TMR0  
    43.     bcf INTCON,T0IF
    44.     bsf INTCON, T0IE ; enable TMR0 interrupt
    45.    
    46.     movf    status_temp,w     ; retrieve copy of STATUS register
    47.     movwf    STATUS            ; restore pre-isr STATUS register contents
    48.     swapf   w_temp,f
    49.     swapf   w_temp,w          ; restore pre-isr W register contents
    50.     retfie                    ; return from interrupt
    51.  
    52. ;**********************************************************************
    53.  
    54. setup ; init PIC16F882
    55.  
    56.     movlw    b'00000000'        ; Turn off comparator 1
    57.     movwf    CM1CON0
    58.    
    59.     banksel ANSEL
    60.     clrf    ANSEL ; 0 = all pins digital ie. analog off
    61.     clrf    ANSELH
    62.    
    63.     banksel TRISA    ; BSF    STATUS,RP0 Jump to bank 1 use BANKSEL instead
    64.     movlw b'00000000'             ; B: all outputs
    65.     movwf TRISB                          
    66.     movwf TRISA                
    67.     movwf TRISC  
    68.    
    69.     banksel INTCON ; back to bank 0
    70.     clrf    PORTA ; all outputs low
    71.     clrf    PORTB
    72.  
    73.     ; setp TMR0 interrupts
    74.     banksel OPTION_REG ; Reg. 0x81 BANK1
    75.     movlw b'00001000' ; instruction clock, assign prescale to WDT
    76.     movwf OPTION_REG
    77.     banksel INTCON ;
    78.     clrf INTCON
    79.     bsf INTCON, GIE ; enable global interrupt
    80.     bcf INTCON, PEIE ; enable all unmasked interrupts
    81.     bsf INTCON, T0IE ; enable TMR0 interrupt
    82.     clrf TMR0
    83.     movlw d'1'  ; 100 instructions per interrupt = 20us
    84.     movwf nCycles
    85.     goto main
    86.  
    87. main
    88.  
    89.     movlw b'00000001' ; toggle 0th bit
    90.     xorwf PORTB,f  
    91.     goto main
    92.  
    93. ;************************************************************************
    94.        
    95. isr
    96.     movlw b'00000010' ; toggle 1st bit every interupt
    97.     xorwf PORTB,f  
    98.     return
    99.    
    100. END  ; directive 'end of program'
    101.  
    Mod edit: added code tags
     
    Last edited by a moderator: May 24, 2016
  2. atferrari

    AAC Fanatic!

    Jan 6, 2004
    2,647
    759
    Had I that problem I would simulate to verify that there is not an undesired change of some registers.

    Could you test just the minimal amount of code to narrow down the problematic part?
     
  3. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    Try using Timer2 which is designed to automatically repeat at regular intervals without reloading anything.
     
  4. dannyf

    Well-Known Member

    Sep 13, 2015
    1,775
    360
    14 ticks (=3us in your case) sound about right. I typically budget 20us for (C) overhead.

    That sounds wrong - you either didn't implement the prescaler right or there is something else wrong.

    100 ticks sounds plenty to get an isr done. so I think something else is wrong.
     
  5. jpanhalt

    AAC Fanatic!

    Jan 18, 2008
    5,676
    899
    That's your error. Try (255-155). That is, it counts 100 and rolls over.

    John
     
  6. Robin66

    Thread Starter Member

    Jan 5, 2016
    102
    3
    255-155 = 100. If TMR0 is set to 100 then the overflow will occur 155 instructions later since TMR0 increments +1 per instruction and overflows at 255.

    I've demonstrated that my incremental changes to nCycles give the desired change in interrupt period ie. +100 nCycles = 20us change in interrupt period
     
  7. jpanhalt

    AAC Fanatic!

    Jan 18, 2008
    5,676
    899
    I thought you wanted 100 counts. What you actually posted gave 155 counts.

    John

    Edit: Oops. Too much sun today cleaning up dead ash trees. Sorry.
     
    Last edited: May 24, 2016
  8. NorthGuy

    Active Member

    Jun 28, 2014
    603
    121
    Code (Text):
    1.   movfw nCycles
    2.   subwf h'FF',w
    3.   movwf TMR0
    You probably want "sublw" instead of "subwf".
     
    Robin66 likes this.
  9. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    Keep in mind that writing to TMR0 clears the prescaler and you lose the counts in it. Also, setting the timer by moving W to it will overwrite any accumulated counts in the timer itself. That can be mitigated by ADDING the set value to TMR0 but you still lose the prescaler counts. @ErnieM has a better idea.
    FWIW, the context save in your interrupt routine should use swapf to get STATUS, not movf. From the Midrange Family Reference Manual:
    Finally, you don't need to reenable TOIE each time. Clearing TOIF and executing RETFIE is sufficient.
     
    Last edited: May 24, 2016
  10. dannyf

    Well-Known Member

    Sep 13, 2015
    1,775
    360
    That doesn't meant the code is right. It just means that the changes you made didn't impact the error(s). ie. after the changes, your code is consistently wrong.

    I would go back and stare at your code a little bit more - the price you pay for coding in assembly.
     
  11. Robin66

    Thread Starter Member

    Jan 5, 2016
    102
    3
    Ok fantastic work fellas. Good spot by NorthGuy. I replaced subwf with sublw. That changed the period but it was constant regardless of what nCycles was set to. Then I enacted JohnIn TX's suggestion: "Finally, you don't need to reenable TOIE each time....". Removing this now means the interrupt period IS a function of nCycles and in exactly the way I originally wanted ie. nCycles=100 --> 23us period. Changes to nCycles result in +20us change to interrupt period per +100 nCycles.

    I understand NorthGuy's fix. I don't understand JohnIn TX's fix tho. I guess setting TOIE may interfer with the TMR0 value.

    Thx for the help. I've taken onboard all of the suggestions
     
  12. dannyf

    Well-Known Member

    Sep 13, 2015
    1,775
    360
    With a reload of -100, I got a time elapse between ISR executions of 103 ticks, or 20.6us @ 20Mhz.

    As expected.
     
  13. dannyf

    Well-Known Member

    Sep 13, 2015
    1,775
    360
    That actually is exactly the wrong answer.

    But I can recreate it as well and you can see the difference between this execution and the prior execution.

    The difference (and the fix) is easy to spot.
     
    Robin66 likes this.
  14. joeyd999

    AAC Fanatic!

    Jun 6, 2011
    2,675
    2,723
    Oh, Jesus, Danny. You've never stared at C code looking for where you typed '=' instead of '=='? You never did accept my challenge...
     
  15. Robin66

    Thread Starter Member

    Jan 5, 2016
    102
    3
    Ok I made the change below and the period is now 20.6us

    ; movfw nCycles ; this old code introduces 3us period error
    ; sublw h'FF'
    ; movwf TMR0

    movfw nCycles ; this new code is much closer
    subwf TMR0, f
     
  16. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    Your arithmetic seems to be fixed? Here's a snippet of code (with simplified TMR0 setting) that illustrates ADDING the setting to TMR0 to account for accumulated counts in the timer PLUS the 3 cycle write latency present on TMR0 writes. This runs at 20us on the dot in MPSIM.
    See line 6 (addwf TMR0,F). Setting an MPSIM breakpoint at this location (just before the addwf happens) shows that TMR0 has already accumulated 6 counts after rollover. If you MOVWF to TMR0 with your calculated value, you lose those 6 counts and your time will be longer than expected.

    Code (Text):
    1.     ORG     0x004             ; interrupt vector location
    2.     movwf   w_temp            ; save off current W register contents
    3.     swapf    STATUS,W          ; swap status register into W (does not affect flags)
    4.     movwf    status_temp       ; save swapped STATUS register
    5.     movlw   .256 -.97         ; reset timer to 100 tiks-to-rollover less 3 tik latency (200ns * 100 = 20uS)
    6.     addwf TMR0,F              ; see Midrange Family Reference Manual re: latency
    7.  
    8.     call isr                  ; this should be inline instead of called
    9.  
    10.     bcf INTCON,T0IF
    11.     ;bsf INTCON, T0IE ; enable TMR0 interrupt NOT NECESSARY
    12.  
    13.  
    14.     swapf   status_temp,W     ; swap STATUS register back to W
    15.     movwf    STATUS            ; restore pre-isr STATUS register contents
    16.     swapf   w_temp,f
    17.     swapf   w_temp,w          ; restore pre-isr W register contents without changing flags
    18.     retfie                    ; return from interrupt
    From the manual:
    That's why using TMR2 / PR2 is easier.
     
    Last edited: May 24, 2016
    Robin66 likes this.
  17. Robin66

    Thread Starter Member

    Jan 5, 2016
    102
    3
    Thx a lot JohnIn TX. I'll read up more about TMR2 config tomorrow while I'm at work. I've only worked with a 16F628a previously so I'm still learning about the extra peripherals+config for the 882. I'll be using the interrupts to control the charging of an inductor for charge transfer between cells in an Li-ion array so I'm not worried about timing precision. But the TMR0 errors I was first experiencing were so large that I clearly didn't understand how my code was working.

    Once again. thx a lot guys
     
  18. NorthGuy

    Active Member

    Jun 28, 2014
    603
    121
    I wonder what amount of starring it would take to differentiate these two C lines:

    Code (C):
    1. TMR+=-LED_DLY;
    2. TMR+--LED_DLY;
     
  19. joeyd999

    AAC Fanatic!

    Jun 6, 2011
    2,675
    2,723
    Or mistakes using a double dereferenced pointer.
     
  20. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    The particular problem in this thread would have occurred in either/any language - except English - maybe something like "Hey @joeyd999, fix this thing for me...":D
     
    joeyd999 likes this.
Loading...