Gestor de tasques periòdiques simple

Un model força habitual de tractar el temps d’una forma senzilla és fent ús de tasques programades. Aquestes tasques son funcions que es criden de forma periòdica i realitzen les tasques necessàries per l’aplicació final. Així, podem tenir un codi similar al següent:

void tasca1(void) {
 // codi tasca 1
}

void tasca2(void) {
  // codi tasca 2
}

void main(void) {
  // inicialitzacions
  ...
  
  // es registren les dues tasques, una es crida cada 5 segons, l'altra cada 15 segons
  Registra_tasca(tasca1, 5000);
  Registra_tasca(tasca2, 15000);

  while(1) {
    Executa_kernel();
  }
}

La funció Registra_tasca() registra la funció que se li passa per a que es cridi cada cert temps. El kernel s’encarrega de programar algun timer per a que notifiqui el microcontrolador el temps corresponent i, per exemple, pugui posar en un mode de baix consum el microcontrolador mentre aquest temps no arribi.
Com que el microcontrolador es pot despertar per diversos esdeveniments, dins el bucle infinit de la funció main() es crida a la funció per a que el kernel, si s’escau cridi al a funció registrada, torni a programar el timer com calgui i torni a dormir el microcontrolador. El pseudocodi per aquesta funció seria així:

void Executa_kernel(void) {
  Configurar els timers necessaris
  Posar processador en mode de baix consum
  /* Aqui el processador esta suspes esperant algun esdeveniment o que es dispari un timer */

  Averiguar quina tasca toca executar-se
  Executar tasca 
  (Opcional) Cridar a una funcio generica de l'usuari
}

Implementació en EFM32

En el cas de treballar amb la plataforma EFM32 part d’aquestes funcionalitats les tenim implementades a la biblioteca RTCDRV i SLEEP que pertanyen a la biblioteca d’alt nivell EMDRV del fabricant.

La primera de les biblioteques, RTCDRV, permet gestionar timers virtuals fent servir només un Timer real, com el nom indica, es fa servir el RTC del microcontrolador (veure el post sobre el RTC). D’aquesta manera es simplifica tenir múltiples timers i permet registrar callbacks per quan un timer arriba al seu temps programat de manera que quan un timer arriba al seu temps d’expiració es crida a la funció de callback registrada.

La segona llibreria, SLEEP, gestionar automàticament els modes de baixa energia del microcontrolador, fent que aquest entri al mode més baix possible segons els perifèrics que es tenen activats. D’aquesta manera, amb una sola crida a una funció de la biblioteca s’aconsegueix posar el microcontrolador en mode de baix consum.

D’aquesta manera, la funció Registra_tasca() passaria a ser una crida a la funció RTCDRV_StartTimer() amb la funció periòdica com a callback i la resta de paràmetres com calgui (rtcdrvTimerTypePeriodic, el temps en mil·lisegons, etc.). I la funció Executa_kernel() no hauria de fer gran cosa.

Aquesta estratègia te un problema, i és que la funció de callback se la crida dins del context d’interrupció, cosa no sempre desitjable, ja que les ISR haurien de ser sempre funcions molt curtes, sense gaire funcionalitat, tal com es va explicar al post sobre IRQ. Per solucionar això es pot canviar una mica l’estratègia i mantenir una estructura que permeti saber quina funció cal cridar i tenir una funció de callback única que mantingui aquesta informació. Llavors, a la funció Executa_kernel() es comprova si s’ha de cridar alguna funció i llavors es crida des d’allà, fora del context d’interrupció. Això es pot veure a continuació:

/* Aquesta funcio es crida des d'una ISR, ha de ser curta */
static void TimerCallback(RTCDRV_TimerID_t id, void* param) {
  int timer;
  
  index = *(int*)param;
  my_timers[index].semaphores = true;
}

void Registra_tasca(mycallback_t func, uint32_t period) {
  static int i = 0;
  
  my_timers[i].callbacks = func;
  my_timers[i].value = i;
  
  RTCDRV_AllocateTimer(&my_timers[i].timers_array);
  RTCDRV_StartTimer(my_timers[i].timers_array, rtcdrvTimerTypePeriodic, period, TimerCallback, &i);
  
  i++;
}

static void Executa_tasca(int timeout) {
  int i;
  
  for (i = 0; i < EMDRV_RTCDRV_NUM_TIMERS; i++) {
    if (my_timers[i].semaphores == true) {
      my_timers[i].semaphores = false;
        if (my_timers[i].callbacks) {
          my_timers[i].callbacks();
        }
     }
  }
}

I la forma de fer-ho servir dins el loop principal pot ser d’aquesta manera:

int main() {
  ...

  RegisterTask(Task1, 250);
  RegisterTask(Task2, 500);

  while (1) {
    SLEEP_Sleep();
    KernelExec();
}

D’aquesta manera el microcontrolador es posa a dormir fins que no es dispari un dels timers i s’executi la tasca que pertoqui, aconseguint un consum mínim del microcontrolador.

Aquest model de programació és força senzill i es podria veure com un pas previ a l’ús d’un RTOS on el maneig de les tasques és mes complex i ens ofereixen mecanismes de sincronització i comunicació entre les tasques més enllà de variables compartides.

Anuncis

Deixa un comentari

Fill in your details below or click an icon to log in:

WordPress.com Logo

Esteu comentant fent servir el compte WordPress.com. Log Out /  Canvia )

Google+ photo

Esteu comentant fent servir el compte Google+. Log Out /  Canvia )

Twitter picture

Esteu comentant fent servir el compte Twitter. Log Out /  Canvia )

Facebook photo

Esteu comentant fent servir el compte Facebook. Log Out /  Canvia )

S'està connectant a %s

Aquest lloc utilitza Akismet per reduir els comentaris brossa. Apreneu com es processen les dades dels comentaris.