Problem with multiple software timers

Discussion in 'Embedded Systems and Microcontrollers' started by johndeaton, Sep 1, 2016.

  1. johndeaton

    Thread Starter Member

    Sep 23, 2015
    48
    3
    Hi All-

    I am programming with a PIC16F1809. I have written code that uses one hardware timer to implement multiple software timers. It works great when I only define one software timer. It is dead on accurate within just a few milliseconds. However, when I define multiple timers, the timing gets thrown way off (20%) or more. Is there anything I can do to keep the timers accurate?

    Thanks
     
    Last edited: Sep 1, 2016
  2. dannyf

    Well-Known Member

    Sep 13, 2015
    1,811
    362
    it probably makes more sense for you to tell people how it works, and show an example where the timer is off.

    I think I might have posted a solution here a short while ago on this sort of things. Essentially a hardware timer produces a tick, and in the isr software timers are checked against the tick to update the flags. The counters for the software timers are updated if an overflow takes place.

    Such a mechanism is accurate up to the tick.
     
  3. NorthGuy

    Active Member

    Jun 28, 2014
    604
    121
    "Call timer_task() from main every 100 microseconds"

    How exactly do you do that?
     
  4. johndeaton

    Thread Starter Member

    Sep 23, 2015
    48
    3
    Hi Guys,

    This is how it works...

    - I call timer_task() from the main routine using an interrupt set from a timer overflow.
    - When I want to start a timer, I call the start_timer() routine.
    - Then, I periodically call the is_tmr_expired() routine to check if the timer is expired.

    By the way... I figured out my problem. What was happening was the timer_task() function was taking too long and causing the function to not be called every 100us as it should. I slowed down the timer to only overflow every 1 ms and changed the function accordingly. It is working great now.
     
    Last edited: Sep 1, 2016
  5. dannyf

    Well-Known Member

    Sep 13, 2015
    1,811
    362
    One limitation of your code is the fixed number of software timers.

    Generally, you want to take one of the two approaches:

    1) on resource limited mcus, declare individual software timers and process them individually; this has the advantage of being simple but puts more burden on the users;
    2) on resource risk mcus, use a linked list to manage timers. this allows fully automatic, and dynamic software timers. It has the disadvantage of being longer to execute.

    On 8-bit timres, I tend to use the 1st approach.
     
    johndeaton likes this.
  6. dannyf

    Well-Known Member

    Sep 13, 2015
    1,811
    362
    here is an example of my implementation:

    Code (Text):
    1.  
    2. typedef uint8_t TICK_Type;                    //8/16/32-bit timer/counter
    3. typedef struct {
    4.     TICK_Type R;                            //timer counter
    5.     TICK_Type PR;                            //period register
    6. } TMR_Type;
    7.  
    8. volatile TICK_Type sTick;                    //timer tick
    9. TMR_Type sTMR0;                    //period for timer0
    10. TMR_Type sTMR1;                    //period for timer1
    11. TMR_Type sTMR2;                    //period for timer2
    12.  
    sTick is the global tick: it has no "time base" so it rolls over on its own, depending on the data types used for TICK_TYPE.

    sTMR0/1/2 are three software timers declared by the user. Each contains its own counter (R) and its own periodic register (PR). PR set by users. and R is advanced synchronously and updated to produce an overflow signal:

    Code (Text):
    1.  
    2. //test timer for overflow
    3. uint8_t sTimer_ovf(TMR_Type *tmr) {
    4.     tmr->R += 1;                            //increment timer
    5.     if (tmr->R == tmr->PR) {                //overflow
    6.         tmr->R = 0;                            //reset timer counter
    7.         return 1;                            //overflow has taken place
    8.     } return 0;
    9. }
    10.  
    11.  
    the isr or loop counting looks like this:

    Code (Text):
    1.  
    2.         sTick_update();                        //update timer
    3.         if (sTimer_ovf(&sTMR0)) IO_FLP(OUT_PORT, OUT0);
    4.         if (sTimer_ovf(&sTMR1)) IO_FLP(OUT_PORT, OUT1);
    5.         if (sTimer_ovf(&sTMR2)) IO_FLP(OUT_PORT, OUT2);
    6.  
    7.  
    once an overflow is detected, the code flip a particular pin.

    the execution is very fast, especially if 8-bit types are used. Adding timers is also quite easy.
     
    johndeaton likes this.
  7. dannyf

    Well-Known Member

    Sep 13, 2015
    1,811
    362
    as it is also truly C, porting it across different hardware is a piece of cake.
     
    johndeaton likes this.
  8. dannyf

    Well-Known Member

    Sep 13, 2015
    1,811
    362
    in case it mattres, the 3-timer sequency takes less than 70 ticks to execute on a 12f675.
     
  9. NorthGuy

    Active Member

    Jun 28, 2014
    604
    121
    Not bad compared to 6 clocks the assembler code would take :)
     
    JohnInTX likes this.
  10. JohnInTX

    Moderator

    Jun 26, 2012
    2,347
    1,029
    Agreed. Using structs and pointers can make things bigger and slower - pointers especially in PICs depending on the compiler..

    FWIW In C or assembler, I usually use one byte per timer, sometimes on a chain of prescalers to keep the times in one byte. They get loaded with the number of ticks, run to 0 then stop. Timeout is detected by the timer == 0. Since reading/writing a single byte is atomic, there is no need to disable interrupts when setting or testing. Its not a suitable approach for all things but handles most system timings, flashes, delays etc. well. For precise event timing, a more detailed approach might be appropriate.

    Code (Microchip Assembler):
    1.  ; Service a timer - usually part of a periodic timer interrupt routine
    2. movf  Timer1,F   ; check for 0, dec if not 0
    3. btfss STATUS,Z
    4. decf Timer1,F
    5. ; service next timer
    6. ;---------
    7. ; how to test the timer
    8. movf Timer1,F  ; set Z flag if timeout
    9. btfsc STATUS,Z
    10. bra   Timer_is_0
    11. .. continue, timer not 0
    Code (C):
    1. unsigned char Timer1;
    2.  
    3. //---------------  Service Timer  ----------
    4. // done on Interrupt TIK
    5.   if(Timer1)Timer1--;  // decrement to 0 then stop
    6.  
    7. //-------------- Test Timer  --------------
    8.   if(Timer1 == 0) Timer_is_0();
    Even C generates just a few bytes of code for each section in most cases. In assembler, testing for zero in the 18F can be usually be done using TSTFSZ to save some code.
     
    Last edited: Sep 2, 2016
Loading...