Preload Timer0 for a more "precise" overflow

Discussion in 'Programmer's Corner' started by ke5nnt, Jan 16, 2015.

  1. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    Hi all,

    I am attempting to write a routine using MPLABx and XC8 for the PIC12F629 where timer0 overflows approximately every 50mS. To do this, I have assigned the prescaler to the TMR0 and made its value 1:256. My math & thought process is:

    0.05 seconds / (instruction time)
    0.05 / (0.000001) = 50000

    50000 / 256 (prescaler) = ~195 instruction cycles

    256 - 195 = 61

    So, by preloading TMR0 with 61, I should achieve overflow after 195 cycles (after the prescaler of course).

    My code for this bit:
    Code (Text):
    1.  
    2. void interrupt isr(void)
    3. {
    4.   request = TRUE;  //SET REQUEST FLAG
    5.   TMR0IF = 0;  //CLEAR TMR0 INTERRUPT FLAG
    6.   TMR0 += 61;  //PRELOAD TIMER 0
    7. }
    Now, I understand that writing to Timer 0 skips some instruction cycles, so I am attempting to look at disassembly to determine how many instruction cycles it takes to preload the timer0 register so that I can adjust the preloaded value to account for this, and maintain as close to 50mS overflow time as possible. When I look at the disassembly of the ISR, I am unsure of what is happening between ADDWF TMR0, F and the RETFIE instruction. Can someone help me understand what is happening here?

    Disassembly:
    Code (Text):
    1.  
    2. !  TMR0 += 61;  //PRELOAD TIMER 0
    3. 0x2B: MOVLW 0x3D
    4. 0x2C: MOVWF __pcstackBANK0
    5. 0x2D: MOVF __pcstackBANK0, W
    6. 0x2E: ADDWF TMR0, F
    7. !}
    8. 0x2F: MOVF 0x24, W
    9. 0x30: MOVWF 0x5F
    10. 0x31: MOVF 0x23, W
    11. 0x32: MOVWF PCLATH
    12. 0x33: MOVF 0x22, W
    13. 0x34: MOVWF FSR
    14. 0x35: SWAPF 0x21, W
    15. 0x36: MOVWF STATUS
    16. 0x37: SWAPF 0x5E, F
    17. 0x38: SWAPF 0x5E, W
    18. 0x39: RETFIE
    19.  
     
    Last edited: Jan 16, 2015
  2. JohnInTX

    Moderator

    Jun 26, 2012
    2,345
    1,027
    What's happening is 'context restore' i.e. the interrupt routine has to save the registers it uses before performing any actions then, when done, it has to restore the registers to their values when the interrupt happened. You can't avoid this.

    As for timer 0, the code trys to mitigate some of the problem by ADDing the value to the timer - that will take care counts accumulated in the timer registers that happened while the interrupt was being serviced but ANY write to TMR0 resets the prescaler. You'll always lose some counts when trying to use TMR0 as a period register that way - the bigger the prescaler, the more counts potentially lost.

    Have fun.
     
  3. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    Meaning that it automatically adds an adjustment for the write to the register? As in I shouldn't need to do this manually?

    If I'm understanding your response correctly, the value of the prescaler at the point that the TMR0 register is written to is reset to 0? i.e. with the 256 prescaler, timer 0 overflows and adds 1 to the prescaler value up until the 255 to 0 overflow of the prescaler value at which point the overflow interrupt flag is set. You are referring to the value of the prescaler somewhere between 0 and 255 is lost, not that the prescaler itself is reset to a 1:1 ratio, correct?

    Thanks John
     
  4. JohnInTX

    Moderator

    Jun 26, 2012
    2,345
    1,027
    That's correct. It follows then that the problem is potentially worse with larger prescaler values. You can see how many Tcyc are used by interrupt overhead (context save and restore). If it took 20 Tcyc for interrupt overhead and your prescaler was 32, you'd lose all 20Tcyc (or have to account for them in your preset). If the prescaler were 1:1 (not there) there wouldn't be counts in the prescaler to lose. You'd still have to take the 2Tcyc sync time into account.

    For these reasons, Timer 0 is not the best choice for a period counter unless you can make its free-running period what you want so you don't have to reload it.

    Your code does a += so that's why it's added to TMR0. To be as exact as possible, you'd have to profile the code and make any adjustments there, keeping the prescaler as small as possible.

    Finally, clear TMR0IF after loading the timer to prevent any stray interrupts.
     
    Last edited: Jan 16, 2015
  5. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    Appreciate the responses as always John. I wonder if you might continue your kindness by expanding a bit on what you mean by "profile the code"?

    Best,
    Ryan
     
  6. JohnInTX

    Moderator

    Jun 26, 2012
    2,345
    1,027
    Profiling is a broad term that basically means see what your code is doing in an execution sense - where it spends the most time, what resources its using, how long a process takes to execute, who is hogging the CPU etc. etc. In this case, I was thinking about measuring the time it spent responding to the interrupt i.e. how many Tcyc between timer overflow and the point that you reload it so that you know what to load it with. You can do that with the stopwatch feature in MPSIM by setting a breakpoint at the IRQ entry (in the disassembler listing), clearing the stopwatch then seeing what is accumulated in overhead getting to the point where the timer is written. The problem with counting overhead in C is that it can and will change as the interrupt routine is added to. Sometimes the main code turns off interrupts for a bit which can add some jitter. Again, that's why I don't use timer 0 or manually load any other timer if I want a consistent period.

    Roman Black (used to be here) had some interesting ways of using timer 0. He let it free run. If it wasn't exactly what he wanted he would add fractional amounts to the elapsed time register on each IRQ without loading the timer. For example if you want 50ms but the timer free-ran with a period of 45, you would add 45ms to a manually maintained timer each IRQ. This way you never lose counts due to clearing the prescaler or the 2Tcyc sync time. If you think about it, even with an interrupt-reload timer scheme, your resolution is -0/+1 tik anyway. If you want finer resolution, run the timer faster (1msec maybe) and accumulate IRQs into an integer. Now you have 1ms resolution.

    EDIT: Actually, Roman's method is a bit different than I recalled. Here's a link to his method.
     
    Last edited: Jan 16, 2015
  7. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    Oh okay. Some great tips there John. Thank you for always having such useful input.
     
Loading...