Yield vs delay¿Cuantas veces en nuestras aplicaciones tenemos que situar demoras para esperar la ocurrencia de un evento determinado? Por ejemplo, para eliminar rebotes en un teclado, esperar a que el conversor AD termine y quién sabe cuantas cosas más.
Normalmente estas demoras se hacen poniendo al procesador a decrementar contadores y dar saltos recursivos como si fuese un loco. Durante todo el tiempo de la demora, nuestro microcontrolador, estará ocupado en perder el tiempo, y es por eso que a este mecanismo se le llama
espera ocupada.
Sin embargo un RTOS nos ofrece un conjunto de herramientas para eliminar este molesto inconveniente, el más sencillo de ellos es aquel que le permite a una tarea decirle al RTOS: ponme a dormir hasta que me toque de nuevo mi turno de ejecutarme. Para ese efecto el RTOS de CCS implementa la función
rtos_yield().
Este mecanismo es muy bueno puesto que mientras la tarea “se duerme” nuestro microcontrolador puede dedicarse a realizar otras tareas útiles y hacer de la espera ocupada una espera eficiente. Para la tarea que está dormida esto no representa nada, a ella le da lo mismo ocupar al procesador en hacer nada que en hacer algo productivo, sin embargo no ocurre lo mismo para el resto de las tareas que están esperando que se les entregue el procesador.
Otro caso en que yield() nos puede ser útil es para entregar el procesador cuando nos hemos pasado de tiempo en la ejecución de alguna tarea. Ya sabemos que el RTOS de CCS es cooperativo, por lo que si una tarea consume más tiempo de la cuenta puede hacer que el sistema colapse, ya que hay que entregar explícitamente el procesador al RTOS para que se lo de a otra tarea.
Sin embargo con la función de las estadísticas habilitadas, podemos comprobar si alguna tarea se ha pasado de tiempo, y con ello implementar mecanismos adecuados para que la tarea en cuestión reajuste su dinámica y ceda el procesador oportunamente, para ello podemos auxiliarnos de la función
rtos_overrun(). Esta función no tiene valor si se usa dentro de una tarea para comprobar si ella misma se ha pasado porque la actualización de las estadísticas se hace después de ceder el procesador, considero que esta es una de las debilidades de este RTOS, en ese sentido.
El uso de
rtos_overrun(), debería poderse utilizar dentro de una misma tarea para comprobar si desde que me cedieron el procesador para ejecutar, me he pasado de tiempo o no y en consecuencia entregar el procesador al RTOS.
En el ejemplo que les traigo hoy vamos a emplear
rtos_yield() para ceder el procesador y
rtos_overrun() para conocer si una tarea se ha pasado de tiempo.
Sin embargo yield() no es una función realmente poderosa, al menos en este RTOS, porque pone a dormir a la tarea durante un período completo del valor rate, que especificamos al declarar la función como una tarea del RTOS, y eso en ocasiones no es lo que deseamos. Aún así es mejor que el procesador de nuestro PIC esté haciendo algo útil y no perdiendo el tiempo.
En las entregas futuras veremos otras funciones que nos ofrece este RTOS para hacer esperas más eficientes vinculadas al uso de recursos compartidos en nuestras aplicaciones. El uso eficiente del procesador, los recursos del sistema y la no linealidad en la ejecución de las tareas en un sistema que emplea SO, ha obligado a los diseñadores de SO a crear mecanismos para proteger los datos y hacer uso de esos recursos de forma ordenada y segura. Estos mecanismos se clasifican en dos grupos: La
sincronización y la
coordinación que comenzaremos a ver en la próxima entrega.
Ejemplo:
Implemente en un PIC16F877 una aplicación en la que se ejecuten tres tareas, con las siguientes características:
La tarea No. 1 Tendrá un contador el cual se incrementa en un lazo hasta que alcanza el valor de 1000. Cuando llegue a ese valor imprime el siguiente mensaje: “Tarea contadora completada” y además pone una bandera a 1, para indicar que ha concluido. Debe colocar en esta tarea código para que la tarea ceda el procesador en algún momento al RTOS. El tiempo máximo de ejecución de esta tarea es de 10ms y debe ejecutarse cada 30ms
La tarea No. 2 debe esperar a que la tarea No. 1 termine para enviar por el puerto serie un mensaje similar al de la tarea No. 1, sin embargo, esta tarea también enviará, por el puerto serie, un mensaje cada vez que le cede el procesador al RTOS. Esta debe ejecutarse en un tiempo de 10ms y debe ejecutarse cada 40ms
Por último, existe una tarea que se encarga de hacer parpadear un LED conectado en RB0, cada 100ms y enviar un menaje por el puerto serie en caso de que la Tarea 1 o la Tarea 2 se hayan pasado en algún momento de su tiempo de ejecución. El tiempo de procesador para esta tarea debe ser de 10ms
Código:
#include "D:\Documentos\Projects\RTOS\RTOS.h"
#use rs232(baud=19200,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=9)
#use RTOS(timer=0, minor_cycle=10ms, statistics) //se utilizan las estadísticas
//hace falta para usar rtos_overrun()
int32 iT1Counter = 0;
int1 bT1Flag = 0;
int1 bLed = 0;
#task (rate=30ms, max=10ms)
void Tarea1();
#task (rate=40ms, max=10ms)
void tarea2();
#task (rate=100ms, max=10ms)
void Tarea3();
void main()
{
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
rtos_run();
}
void Tarea1()
{
bT1Flag = 0;
for(iT1Counter = 0; iT1Counter <= 1000; iT1Counter++)
{ if(!(iT1Counter%100)) //mecanismo para ceder el procesador cada cierto tiempo
rtos_yield(); //cuando la tarea entra en contexto se comienza a ejecutar
//la línea a continuación de esta
}
printf("Tarea contadora completada\r"); bT1Flag = 1;
}
void tarea2()
{
//Aunque esta tarea no tiene que preocuparse mucho por ceder el procesador
//porque no tiene lazos infinitos o algo parecido puse de ejemplo tambien a
//rtos yield
if(bT1Flag)
printf("Espera por Tarea1 concluida\r"); else
{
rtos_yield();
}
}
void Tarea3()
{
bLed = !bLed;
output_bit( PIN_B0, bLed);
if(rtos_overrun(Tarea1)) //Si las tareas se pasaron de su tiempo de ejecución
//se envían los mensajes por el pto serie
if(rtos_overrun(Tarea2))
}
Como pueden observar, este ejemplo realmente no se corresponde con una aplicación que haga algo útil, sin embargo, me ha servido para ilustrar el uso de las funciones rtos_yield() y rtos_overrun() de la manera más sencilla que encontré. Las posibilidades de estas funciones están ahora en sus manos y en lo que sus cabezas puedan crear para ellas. Hay muchísimas aplicaciones en las que pueden ser útiles, sin embargo, ya el tema de los RTOS es bastante complicado como para meternos a hacer programas complejos que demuestren su uso.
El tiempo invertido para hacer el código y el texto, fue de 2:30 hrs, la decisión de que ejemplo utilizar me tomó más de un día.