Implementing Software Timers


W "małych" mikrokonrolerach rzadko kiedy występuje więcej niż trzy timery sprzętowe,
ale jak to przeważnie bywa, timerów nigdy za dużo ;-)
Receptą na to jest stworzenie "genearatora" timerów, który wykorzystując jeden
z timerów uC będzie nam "tworzył" na nasze życzenie kolejny timer, tyle tylko,
że będzie to już timer programowy.

Jak się tak głębiej zastanowaić nad tym problemem to możemy dojść do wniosku,
że w tym celu możemy przecież wykorzystać opisany w innym miejscu Scheduler Round-Robin,
tyle tylko, że jego główna funkcja dispatchTasks() nie będzie znajdować się w pętli
głównej while(1), ale w obsłudze użytego przez nas timera sprzętowego.

Opis:


A więc do dzieła. Najpierw zmodyfikujemy nieco strukturę:

// basic timer control block (TCB)
typedef struct __tcb_t
{
    uint8_t id; // timer ID
    timer_t timer; // pointer to the timer
    // delay before execution
    uint16_t delay, period;
    uint8_t type;   // single, cyclic
} tcb_t;


następnie, inicjowanie Timerów:

#define MAX_TIMERS  10
#define IDLE           0      // zatrzymany 
#define SINGLE      1      // jednokrotny, ginie po jednokrotnym uzyciu
#define CYCLIC     2      // periodyczny
#define ERROR      3


void initTimers(void)
{
    for(uint8_t i=0; i<MAX_TIMERS; i++)
    {
        timer_list[i].id = 0;
            timer_list[i].timer = (timer_t)0x00;
            timer_list[i].delay = 0;
            timer_list[i].period = 0;
            timer_list[i].type = IDLE;
    }
}

oraz funkcję dodawania kolejnych Timerów:

void addTimer(uint8_t id, timer_t timer, uint16_t period, uint8_t type)
{
    uint8_t idx = 0, done = 0x00;   
         
        while( idx < MAX_TIMERS )
        {
            if( timer_list[idx].type == IDLE )
            {
                timer_list[idx].id = id;
                    timer_list[idx].timer = timer;
                    timer_list[idx].delay = period;
                    timer_list[idx].period = period;
                    timer_list[idx].type = type;           
                    done = 0x01;
            }
            if( done ) break;
                idx++;
        }
     
}

Funkcje usuwania i zwracania statusu wyglądają identycznie jak
w programie schedulera (poza nazwami oczywiście):


void deleteTimer(uint8_t id)
{   
    for(uint8_t i=0;i<MAX_TIMERS;i++)
    {
        if( timer_list[i].id == id )
        {
            timer_list[i].type = IDLE;
                break;
        }
    }
}
 
// gets the timer status
// returns ERROR if id is invalid
uint8_t getTimerStatus(uint8_t id)
{
    for(uint8_t i=0;i<MAX_TIMERS;i++)
    {
        if( timer_list[i].id == id )
            return timer_list[i].type;
    }
    return ERROR;
}


Natomiast zupełnie inaczej wygląda teraz obsługa przerwania od Timera0:

void TIMER0_COMPA_vect(void) __attribute__((interrupt));
void TIMER0_COMPA_vect(void)
{
     
        count ++;
         
        if( count == timer_delay )
        {
             
                count = 0;
             
                for(uint8_t i=0; i<MAX_TIMERS; i++)
                {               
                    if(timer_list[i].type != IDLE)
                    {
                         
                            if(timer_list[i].delay != 0)
                            {
                                timer_list[i].delay--;
                            }
                         
                            if(!timer_list[i].delay)
                            {     
                                 
                                    (*timer_list[i].timer)();
                                     
                                    if(timer_list[i].type == CYCLIC)   // dla cyklicznego odnawiamy licznik
                                    {
                                        timer_list[i].delay = timer_list[i].period;  
                                    }
                                    else  
                                    {
                                        timer_list[i].type = IDLE;      // gasimy timer po jednym razie
                                    }
                            }
                    }
                     
                }
        }
     
}


Porównując program obsługi schedulera i ten można zauważyć pewne różnice.
Otóż zniknął ze struktury wpis traktujący o statusie, a na jego miejsce pojawił się
"timer_list[i].type", który implikuje działanie timera pod względem funkcjonalności.
Mamy teraz do wyboru, timer cykliczny CYCLIC i timer pojedynczy SINGLE.

Użycie:


Spróbujmy więc powołać do życia taki jeden timer:

addTimer(1,BLINK_RED, 1, CYCLIC );

Ostatnim parametrem jest słowo CYCLIC, które informuje
że timer ten będzie "tykał" cały czas.  Czas powtarzania bedzie (1 * OKRES_TIMER0)

Inaczej jest w przypadku, kiedy wystąpi tam słówko SINGLE:

addTimer(5,BLINK_GREEN, 100, SINGLE );

wtedy timer zakończy swój żywot po jednokrotnym zadziałaniu,
a jego stan zmieni się na IDLE. Funkcja BLINK_GREEN() wykona
się po (100 * OKRES_TIMER0) i zakończy działanie.

Ograniczeniem tych timerów jest to, ze nie można na nim uzyskać dowolnego
superdokładnego czasu, zawsze to będzie wielokrotność przerwań od timera,
który wykorzystaliśmy (w tym przypadku TIMER0).
Więc jeśli zaprogramowaliśmy sobie przerwanie od TIMER0 na 10[ms]
to nie uzyskamy mniejszego czasu niż te 10[ms] i jego wielokrotność.

Ku przestrodze:


Pamiętać również należy, że zadania wywoływane w tych timerach muszą być,
krótkie pod względem czasu wykonywania, ponieważ wszystkie te timery
każdorazowo wykonują się w przerwaniu i muszą zdążyć przed nastepnym,
a przecież procesor musi robić jeszcze inne rzeczy, a nie tylko obsługiwać przerwania...