# Principio de multitarea. Desterrando a los delays.



## Saint_ (May 13, 2018)

Este artículo tiene como base proponer una metodología para efectuar programas multitarea en microcontroladores usando el compilador CCS PicC.

Todo está en función de un temporizador y la directiva _#use timer,_ no se hace uso de las interrupciones ya que estas estarán reservadas para tareas de mayor prioridad.

*La directiva #use timer.*
Esta directiva crea un _timer tick_ usando uno de los temporizadores del Pic. El _timer tick_ es inicializado a cero al iniciar el programa. Esta directiva crea una constante con nombre TICKS_PER_SECOND que especifica el número de _ticks_ que ocurren en un segundo.

… mayor información en el help de CCS PicC.

La rutina principal _main()_, se convertirá en el lanzador de tareas y las tareas serán funciones.

Este modo es ideal para las tareas repetitivas, mientras que las secuenciales requieren algunos “trucos”.

Este método lo vi en uno de los ejemplos que CCS tiene disponible.

*#use timer(timer=1,tick=10ms,bits=16,noisr)*

Usa el temporizador 1, tick cada 10 milisegundos, tamaño de los ticks 16 bits, no se usa interrupción para actualizar los ticks.

*Ejemplo.*
Generar el parpadeo de tres leds, el primero con un periodo de 100ms, el segundo con en periodo de 300ms y el tercero con un periodo de 1s.

Es importante que el tiempo mínimo de repetición de la tarea sea mayor o igual a tick usado en el _use timer_.

*En el archivo main.h*

```
/*****************************************************************************/
#include <16F628A.h>
/*****************************************************************************/
#FUSES NOWDT                    //
#FUSES PUT                      //Power Up Timer
#FUSES MCLR                     //Master Clear pin enabled
#FUSES BROWNOUT                 //Reset when brownout detected
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD                    //No EE protection
#FUSES NOPROTECT                //Code not protected from reading
/*****************************************************************************/
#use delay(internal=4MHz)
/*****************************************************************************/
#use timer(timer=1,tick=10ms,bits=16,noisr)
/*****************************************************************************/
```
*En el archivo main.c.*

```
*****************************************************************************/
#include <main.h>
/*****************************************************************************/
void tarea1()
{
   output_toggle(pin_a0);  //cambia de estado el bit 0 del puerto A
}
/*****************************************************************************/
void tarea2()
{
   output_toggle(pin_a1);  //cambia de estado el bit 1 del puerto A
}
/*****************************************************************************/
void tarea3()
{
   output_toggle(pin_a2);  //cambia de estado el bit 2 del puerto A
}
/*****************************************************************************/
void main()
{
   unsigned int16 tick,tick_tarea1=0,tick_tarea2=0,tick_tarea3=0;
   while(true)                                //bucle infinito
   {
      tick=get_ticks();     //lee el valor actual de los ticks
      if((tick-tick_tarea1)>=TICKS_PER_SECOND/10)//aprox 100ms
      {
         tick_tarea1=tick;//actualiza el los tick de la tarea1
         tarea1();                          //lanza la tarea 1
      }
      if((tick-tick_tarea2)>=TICKS_PER_SECOND/3.3)//aprox 300ms
      {
         tick_tarea2=tick; //actualiza el los tick de la tarea2
         tarea2();                           //lanza la tarea 2
      }
      if((tick-tick_tarea3)>=TICKS_PER_SECOND)       //aprox 1s
      {
         tick_tarea3=tick; //actualiza el los tick de la tarea3
         tarea3();                           //lanza la tarea 3
      }
   }
}
/*****************************************************************************/
```


----------



## Miembro eliminado 356005 (May 14, 2018)

Hay un problema: cuando el temporizador de 16 bits desborda, el valor de *get_ticks()* vuelve a 0.

Como las variables *tick_tarea** siempre crecen, eso quiere que los if() que comprueban si se ha superado el lapso de tiempo por tarea dejarán de cumplirse, por lo que el sistema dejará de funcionar al cabo de 10 minutos, 55 segundos, y 36 centésimas.

La solución es poner un control más, para comprobar si *get_ticks()* ha vuelto a cero, y en ese caso, poner a cero las variables *tick_tarea**, antes de las comprobaciones.

Por ejemplo (no probado):

```
unsigned int16 tick,ant_tick,tick_tarea1=0,tick_tarea2=0,tick_tarea3=0;

    // hemos añadido una variable ant_tick para llevar el control de desborde

    while(true) {                          // bucle infinito

        tick = get_ticks();                 // lee el valor actual de los ticks

        if (tick < ant_tick) {                // caso de desborde
            // colocamos lapsos al principio de la cuenta
            if (tick < tick_tarea1)
                tick_tarea1 = 65535 - tick_tarea1;
           
            if (tick < tick_tarea2)
                tick_tarea2 = 65535 - tick_tarea2;

            if (tick < tick_tarea3)
                tick_tarea3 = 65535 - tick_tarea3;
        }

        // ... resto de líneas
       
        // ... al final del bucle while(), añadimos
        ant_tick = tick;
    }
```

Atención: NO vale con poner las variables a 0 cuando se detecta un desborde: hay que respetar la posición de lapso de tiempo en que se encuentra cada una.

Atención 2: con un par de trucos, nos podemos ahorrar la variable *ant_tick*.


----------



## Saint_ (May 14, 2018)

JoaquinFerrero dijo:


> Hay un problema: cuando el temporizador de 16 bits desborda, el valor de *get_ticks()* vuelve a 0.


Ese fue el primer detalle que tuve que reflexionar cuando vi este método en el ejemplo de CCS picC.


JoaquinFerrero dijo:


> Como las variables *tick_tarea** siempre crecen, eso quiere que los if() que comprueban si se ha superado el lapso de tiempo por tarea dejarán de cumplirse, por lo que el sistema dejará de funcionar al cabo de 10 minutos, 55 segundos, y 36 centésimas.


En realidad no, debido a que:

Por ejemplo.

Si la diferencia entre los ticks fuese 6 y se llegara al siguiente caso (asumiremos un tamaño de 8 bits sin signo... ya que es más fácil asi).

Tick actual=253
Tick anterior=247

253-247=6   ->    0xFD-0xF7=0x06

Si expresamos esta operación en binario.

0xFD  ->    11111101
0xF7  ->    11110111

Tenemos: 11111101-11110111

Una resta puede hacerse mediante una suma con complemento a 2.

El complemento a 2 de 11110111->00001001

11111101-11110111=11111101+00001001...como se trata de un entero sin signo de 8 bits el valor final del resultado también es sinsigno de 8 bits y el acarreo que pudiera existir no se toma en cuenta.

```
11111101
                  +
                   00001001
                  ---------
        Acarreo->1|00000110->0x06
```
En este caso la diferencia es correcta tick actual>tick anterior.

Cuando ocurre el desborde por ejemplo de 0xFD a 0x03.

tick actual=0x03
tick anterior=0xFD

0x03-0xFD->00000011-11111101

Igual que en el anterior caso la resta mediante complemento a 2 se hace suma.

Complemento a 2 de 11111101->00000011

```
00000011
                  +
                   00000011
                  ---------
        Acarreo->0|00000110->0x06
```
En ambos casos la diferencia como entero sin signo de 8 bits es de 6 por tanto no importa si ocurre el desborde, el programa sigue funcionando sin fallar.

Dentro de poco subiré algún ejemplo más interesante que el parpadeo de leds.
Un saludo y hasta la próxima vez.

Solo para verificar la hipótesis deje en simulación el circuito con el programa y después de 27 minutos el programa sigue funcionado, sé que una simulación no es lo mismo que el circuito real, pero en este caso considero que puedo confiar en la simulación.

De todos modos en el parámetro _tick_ de _#use timer_ se pueden elegir tamaños de 8,16 y 32 bits.


----------



## Scooter (May 15, 2018)

Otro detalle es que las tareas no se programan "libremente", dentro de una tarea no pueden haber delays ni bucles cerrados esperando a que pase algo.
Por ejemplo, no puede haber un bucle que se quede esperando a que se pulse una tecla, por decir algo.


----------



## Miembro eliminado 356005 (May 15, 2018)

Saint_ dijo:


> Solo para verificar la hipótesis deje en simulación el circuito con el programa y después de 27 minutos el programa sigue funcionado, sé que una simulación no es lo mismo que el circuito real, pero en este caso considero que puedo confiar en la simulación.
> 
> De todos modos en el parámetro _tick_ de _#use timer_ se pueden elegir tamaños de 8,16 y 32 bits.


Gracias por la aclaración. De todos modos, no queda indicado en el código, y queda al criterio del compilador decir que la diferencia de enteros sin signo debe ser así para usarse con la expresión de la derecha. Humm... mejor poner algún _cast_, por si acaso.


----------



## Saint_ (May 15, 2018)

Scooter dijo:


> Otro detalle es que las tareas no se programan "libremente", dentro de una tarea no pueden haber delays ni bucles cerrados esperando a que pase algo.
> Por ejemplo, no puede haber un bucle que se quede esperando a que se pulse una tecla, por decir algo.


Es cierto, dentro de un sistema multare no caben los delays y los bucles que esperan algún evento, por lo menos no intencionalmente. Seguramente cuando se usan algunas librerías probablemente estas tengan en su código algún delay pequeñísimo, de buena fe esperamos que esas librerías no desestabilicen al sistema.

Por lo menos el objetivo de lo que voy a ir subiendo es no tener ningún delay mas dentro del código que efectuemos.


JoaquinFerrero dijo:


> ... mejor poner algún _cast_, por si acaso.


Eso sí, a nadie le hace daño un _cast_.


----------



## Gerson strauss (May 15, 2018)

Hace poco utilice el timer por primera vez, en un programa para contar objetos con Displays de leds. Use la interrupción por RB0 (gracias a D@rkbytes) debía hacer un delay para evitar el rebote al contar y use el timer0 para eso. Todo funciono muy bien, pero hasta ahora no se que tiempo esta haciendo el timer ni como aumentarlo; simplemente use todo el rango (0 - 255 con el máximo prescaler y bandera de overflow). Seria bueno que explicaran como configurar el timer (leí algo pero no me queda claro) para contar el tiempo que uno necesite. Es un tema muy interesante. Gracias.


----------



## Dr. Zoidberg (May 15, 2018)

Me gusta la idea...pero yo lo escribiría de otra forma por que si nó, el codigo de despacho se enrieda muy rápidamente. Yo hice algo muy parecido a esto hace mas de 20 años pero con el timer de la PC. Si algun dia encuentro los diskettes donde lo tenía, prometo subirlo.

Ahí vá... lo escribí en 10 minutos y no lo revisé mucho, pero creo que se entiende el mecanismo de despacho (que es el mismo publicado mas arriba) pero con algunos cambios menores para explotar un poco la potencia expresiva del lenguaje C.


```
typedef struct {
    void (*ftarea)();
    int16 ticks;
    int16 tickCnt;
} TAREA;

void disparaTareas( TAREA tareas[], int tcant ) {
int16 i, tk;

while( true ) {
    tk = get_ticks();
    for( i = 0; i < tcant; i++ ) {
        if( ( tk-tareas[i].ticksCnt ) >= tareas[i].ticks ) {
            tareas[i].tickCnt = tk;
            tareas[i].ftarea();
        }
    }
}

}

/*---------------------
Aca ponen las funciones de cada tarea
igual que en el primer ejemplo
--------------------*/

void main() {

TAREA tareas[ ] = {
        { tarea1, 10, 0 },
        { tarea2, 30, 0 },
        { tarea3, 100, 0 }
        };

disparaTareas( tareas, 3 );

}
```

De mas esta decir que se admiten criticas, cambios y mejoras.

*PD:* Se me ocurre que se podría leer el contador de ticks dentro del* for* y no dentro del *while*....pero hay que analizarlo.

*PD2:* Tal como comenta Scooter, esto es multitarea cooperativo, así que hay que agregar una pequeña API para cosas tales como liberar el control de la CPU en un loop "ciego" y ese tipo de cosas. No es gran trabajo pero hay que hacerlo.


----------



## Miembro eliminado 356005 (May 15, 2018)

Como complemento a este artículo estaría bien crear otro explicando el modo RTOS (_Real Time Operating System_) del CCS (multitarea cooperativa).

*En el manual* está a partir de la página 94.

En principio, lo que hacen las funciones *rtos_** es "esconder" buena parte de la infraestructura necesaria que *Saint_* nos ha mostrado con *#use timer*.


----------



## Saint_ (May 16, 2018)

Buenas noches, aunque para mí son buenas madrugadas (01:45 en este instante).

Me simpatiza mucho el modo que Dr. Zoidberg propone para el despacho de las tareas, lo lamentable es que CCS PicC no soporta punteros a funciones así que no podríamos ir por ese lado. En cambio si se podría aplicar para implementar pseudo objetos y asi poder manejar de 1 a N motores pap por ejemplo o más de un LCD alfanumérico sin necesidad de estar creando nuevas librerías para cada caso.



Gerson strauss dijo:


> Seria bueno que explicaran como configurar el timer (leí algo pero no me queda claro) para contar el tiempo que uno necesite. Es un tema muy interesante. Gracias.


habria que crear un post donde se explique a detalle las interrupciones y su modo de uso segun el compilador que se use.



JoaquinFerrero dijo:


> Como complemento a este artículo estaría bien crear otro explicando el modo RTOS (_Real Time Operating System_) del CCS (multitarea cooperativa).



Me parece una Genial idea. Después que termine estas entregas y si nadie más se anima entonces subiria unos artículos sobre el Rtos de CCS.

*Mientras tanto:*
Este el segundo ejemplo sobre esta metologia para multiatrea en CCS.
Para este ejemplo tendremos:

Se tendrán 4 displays de 7 segmentos, dos de los cuales serán un contador ascendente descendente según la posición de un switch.

Los restantes displays serán un contador de pulsos provenientes de un pulsador.

Se tendrá un motor paso a paso girando a razón de 50ms por paso y un led que parpadea a razón de 500ms.

Para el manejo de los displays se usara el método de multiplexacion, podría usarse latchs pero la idea es tener la menor cantidad de hardware posible usando como base al pic16f628A.

*Dividiremos el programa en 5 tareas.*

Tarea 1, mantener girando el motor paso a paso

Tarea 2, mantener parpadeando al led.

Tarea 3, lectura del pulsador.

Tarea 4, multiplexar al 4 displays.

Tarea 5, lectura del estado del switch.

Se colocara en el puerto A al motor paso a paso, al led, al pulsador y al switch. Como en este puerto existen pines de entrada y otros de salida usaremos la directiva *#USE FIXED_IO* para no tener problemas.

La forma de enviar información de una tarea a otra tarea será mediante variables globales.

*En el archivo .h*

```
/*****************************************************************************/
#include <16F628A.h>
/*****************************************************************************/
#FUSES NOWDT
#FUSES PUT                      //Power Up Timer
#FUSES MCLR                     //Master Clear pin enabled
#FUSES BROWNOUT                 //Reset when brownout detected
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD                    //No EE protection
#FUSES NOPROTECT                //Code not protected from reading
/*****************************************************************************/
#use delay(internal=4MHz)
/*****************************************************************************/
#USE TIMER(TIMER=1,TICK=1ms,BITS=16,NOISR)
/*****************************************************************************/
#use fixed_io(a_outputs=PIN_A0,PIN_A1,PIN_A2,PIN_A3,PIN_A7)
#use fixed_io(b_outputs=PIN_B0,PIN_B1,PIN_B2,PIN_B3,PIN_B4,PIN_B5,PIN_B6, PIN_B7)
/*****************************************************************************/
unsigned int8 pulsos=0;//contador de pulsos
unsigned int8 cuenta=0;//contador de cuenta
/*****************************************************************************/
```
*En el archivo .c*

```
#include <main.h>
/*****************************************************************************/
void tareaContador()
{
   if(input(pin_a6))    //si es 1 cuenta ascendente
   {
      if(++cuenta==100) //si llega a 100
      {
         cuenta=0;
      }
   }
   else                 //si es 0 cuenta descendente
   {
      if(--cuenta==255) //si se desborda
      {
         cuenta=99;
      }
   }
}
/*****************************************************************************/
void tareaPulsador()
{
   static unsigned int8 pulsadorEstadoAnterior=1;
   unsigned int8 pulsadorEstadoActual;
   pulsadorEstadoActual=input(pin_a4);
   if((!pulsadorEstadoActual)&&(pulsadorEstadoAnterior))//si existió un cambio
   {                                                     //de estado de 1 a 0
      if(++pulsos==100) //si la cuenta llega a 100
      {
         pulsos=0;
      }
   }
   pulsadorEstadoAnterior=pulsadorEstadoActual;//actualiza el estado del pulsador
}
/*****************************************************************************/
void tareaDisplay()
{
   char disp[5];
   char dispN[]={0xe0,0xd0,0xb0,0x70};
   static unsigned int n;
   sprintf(disp,"%02u%02u",pulsos,cuenta);
   output_b(dispN[n]|(disp[n]&0x0f));
   (++n)&=0x03;
}
/*****************************************************************************/
void tareaLed()
{
   output_toggle(pin_a7);//cambia d estado a led
}
/*****************************************************************************/
void tareaMotor()
{
   char pasos[]={1,2,4,8};//secuencia del motor paso a paso
   char temp;             //variable de uso general
   static char paso;      //contador de pasos
   temp=input_a()&0xf0;//se rescata los 4 bits más significativos del puerto A
   temp|=pasos[paso++];//escribe en los bits menos significativos el paso
                       //correspondiente al motor
   paso&=0x03;         //forza a que no se exceda de paso 3
   output_a(temp);     //escribe en el puerto A
}
/*****************************************************************************/
void main()
{
   unsigned int16 tick,tickTareaMotor=0,tickTareaLed=0;
   unsigned int16 tickTareaDisplay=0,tickTareaPulsador=0,tickTareaContador=0;
 
   while(TRUE)
   {
      tick=get_ticks();
      if((tick-tickTareaMotor)>=TICKS_PER_SECOND/20)//aprox 50ms
      {
         tickTareaMotor=tick;
         tareaMotor();
      }
      if((tick-tickTareaLed)>=TICKS_PER_SECOND/2)//aprox 500ms
      {
         tickTareaLed=tick;
         tareaLed();
      }
      if((tick-tickTareaDisplay)>=TICKS_PER_SECOND/200)//aprox 5ms
      {
         tickTareaDisplay=tick;
         tareaDisplay();
      }
      if((tick-tickTareaPulsador)>=TICKS_PER_SECOND/10)//aprox 100ms
      {
         tickTareaPulsador=tick;
         tareaPulsador();
      }
      if((tick-tickTareaContador)>=TICKS_PER_SECOND/3.3)//aprox 300ms
      {
         tickTareaContador=tick;
         tareaContador();
      }
   }
}
/*****************************************************************************/
```


----------



## Dr. Zoidberg (May 16, 2018)

El CCS si soporta punteros a funciones, si bien tenia algunos problemas con la forma declararlos.
Aca explican una forma de zafar del problema...pero hay que cuidar cual version del compilador se utiliza: CCS :: View topic - Function pointer in a structure makes a compiler mess-SOLVED

Es muy simple de solucionar. Si podes, probalo.


----------



## pandacba (May 16, 2018)

Eso fue resuelto hace tiempo ya, creo que por la 5.050 o 5.051 si mal no recuerdo y ya estamos en la 5.076


----------



## Saint_ (May 16, 2018)

La versión en la que estoy escribiendo el código es en 5.07 y en el help menciona:

*Attempt to create a pointer to a constant*

Constant tables are implemented as functions. Pointers cannot be created to functions. For example CHAR CONST MSG[9]={"HI THERE"}; is permitted, however you cannot use &MSG. You can only reference MSG with subscripts such as MSG_ and in some function calls such as Printf and STRCPY._



_Pero haciendo pruebas, si soporta puntero a función, pero sigue generando error cuando el puntero a función está dentro de una estructura._

_Pero la solución esta tal cual lo mencionan en el enlace que recomienda Dr. Zoidberg._

_Aqui el mismo ejemplo del primer post pero con la modificacion que  Dr. Zoidberg propone, por lo menos ami me gusta mas este nuevo modo.


		C:
	

#include <16f84a.h>
#fuses nowdt,put,noprotect
#use delay(crystal=4M)
#use timer(timer=0,tick=10ms,bits=16,noisr)
/******************************************************************************
Estructura para la creación de tareas (esto no debería cambiarse)
******************************************************************************/
typedef void(*punteroFuncion)(void);
typedef struct {
                  punteroFuncion pf;
                  unsigned int16 periodo;
                  unsigned int16 tickCont;
               }TAREA;
/******************************************************************************
Función despachadora de tareas (esto no debería cambiarse)
******************************************************************************/
void despachadorTareas(TAREA tareas[],unsigned int8 tCant)
{
   unsigned int16 tick;
   unsigned int8 i=0;
   while(true)
   {
      tick=get_ticks();
      for(i=0;i<tCant;i++)
      {
         if((tick-tareas[i].tickCont)>=tareas[i].periodo)
         {
            tareas[i].tickCont=tick;//actualiza los ticks
            tareas[i].pf();                //despacha la tarea correspondiente
         }
      }
   }
}
/*****************************************************************************/
void tarea1()
{
   output_toggle(pin_a0);
}
/*****************************************************************************/
void tarea2()
{
   output_toggle(pin_a1);
}
/*****************************************************************************/
void tarea3()
{
   output_toggle(pin_a2);
}
/*****************************************************************************/
void main()
{ 
   TAREA tareas[]={
                  {tarea1,TICKS_PER_SECOND/10,0}, //crea la tarea1,aprox 100ms,0->la tarea inicia en aprox 100ms
                  {tarea2,TICKS_PER_SECOND/3.3,0},//crea la tarea2,aprox 300ms,0->la tarea inicia en aprox 300ms
                  {tarea3,TICKS_PER_SECOND/1,1}   //crea la tarea3,aprox 1s,1->la tarea inicia inmediatamente
                  };
   despachadorTareas(tareas,3);  //llama al despachador de tareas, 3 es la
                                 //cantidad de tareas creadas
}
/*****************************************************************************/

_


----------



## Dr. Zoidberg (May 16, 2018)

No se entiende nada lo que quoteste del help...jaja.
O le tradujo un chino trasnochado o el que lo escribio fumaba cosas raras...

PD: no me gusta usar divisiones en punto flotante para un parametro que termina siendo entero.

PD2:fijate de meter la invocacion a get_ticks antes del if para tener un control mas cercano del momento del despacho. No es garantia de nada pero "se me ocurre" que puede ayudar..


----------



## Saint_ (May 16, 2018)

Dr. Zoidberg dijo:


> No se entiende nada lo que quoteste del help...jaja.
> O le tradujo un chino trasnochado o el que lo escribio fumaba cosas raras...


Puede ser, pero es copia fiel del lo que esta escrito en el help de CCS.


Dr. Zoidberg dijo:


> PD: no me gusta usar divisiones en punto flotante para un parametro que termina siendo entero.


Algún rato ese "mal" es necesario. De todos modos uno siempre es libre de ponerle un valor entero dependiendo de la frecuencia de ejecución de la tarea.


Dr. Zoidberg dijo:


> PD2:fijate de meter la invocacion a get_ticks antes del if para tener un control mas cercano del momento del despacho. No es garantia de nada pero "se me ocurre" que puede ayudar..


La diferencia entre _get_ticks();_ fuera del bucle _for_ o dentro son unos cuantos _us_  no hace alguna diferencia notoria. De todos modos esa sería una opción que el programador podría usar si desearía, en mi caso lo dejaria fuera del _for._


----------



## Dr. Zoidberg (May 16, 2018)

Saint_ dijo:


> La diferencia entre _get_ticks();_ fuera del bucle _for_ o dentro son unos cuantos _us_  no hace alguna diferencia notoria. De todos modos esa sería una opción que el programador podría usar si desearía, en mi caso lo dejaria fuera del _for._


Si seguis de cerca el tiempo de despacho podrias hacer cosas interesantes, tales como medir la desviacion del despacho real de cada tarea respecto al tiempo previsto, y eso podria ayudar a optimizar la secuencia de despacho o el codigo de las tareas.
Por supuesto, hay que armar una API con acceso a los datos de cada tarea y que permita hacer otras cosas interesantes, y todo esto solo vale cuando los intervalos de tiempo son pequeños.
Pero bue....era una idea


----------



## Dr. Zoidberg (May 17, 2018)

Otra cosa que recomiendo hacer es consteuir un archivo .h (ponele... tareas.h) que contenga los typedef y el prototipo del despachador de tareas, y luego un .c (tareas.c) que contenga la definicion del despachador.
De esa manera solo se hace
#include "tareas.h"
En el programa y agregando tareas.c al proyecto se protege el mecanismo de despacho de modificaciones involuntarias..o nó.


----------



## Eduardo (May 18, 2018)

Saint_ dijo:


> ....
> Algún rato ese "mal" es necesario. De todos modos uno siempre es libre de ponerle un valor entero dependiendo de la frecuencia de ejecución de la tarea.



Usar  _get_ticks()_  es quizás de las peores elecciones para llevar varios temporizadores.  Para eso no solo no hace falta punto flotante, ni siquiera hace falta dividir, solamente restar 1.

Por ejemplo:
- Se crea una interrupción por overflow de un timer y se decrementan contadores (int8 o int16, lo que haga falta)
- Cuando cada contador llega a 0 se recarga y activa un bit de estado.
- En el bucle principal se comparan los bits de estado y cuando se activan, se resetean y llaman a la tarea correspondiente.

Declaro variables
`int1 ovfw1=0 , ovfw2=0 , ovfw3=0 ;   // Overflows
int8  tmr1=0 ,  tmr2=0 ,  tmr3=0 ;   // Contadores
int8  tmr1_0 ,  tmr2_0 ,  tmr3_0 ;   // Valor inicial`

En la interrupción
`if(--tmr1==0){ tmr1 = tmr1_0 ; ovfw1 = 1 ;}  // Tiempo 1
    if(--tmr2==0){ tmr2 = tmr2_0 ; ovfw2 = 1 ;}  // Tiempo 2
    if(--tmr3==0){ tmr3 = tmr3_0 ; ovfw3 = 1 ;}  // Tiempo 3`

En main
`void main(){
....................................
    tmr1_0 = 200 ;
    tmr2_0 = 300 ;
    tmr3_0 = 100 ;
.....................................    
    while(true){
        if(ovfw1){ ovfw1=0 ; tarea1() ;}
        if(ovfw2){ ovfw2=0 ; tarea2() ;}
        if(ovfw3){ ovfw3=0 ; tarea3() ;}
        ...................................................
    }
   .....................................
}`


----------



## Dr. Zoidberg (May 18, 2018)

Eduardo dijo:


> Usar _get_ticks()_ es quizás de las peores elecciones para llevar varios temporizadores


Es que no estas llevando varios temporizadores, sino uno solo sobre el cual se despachan todas las tareas.
Esto no es, ni por cerca, una implementacion hard-real-time pero tampoco lo son los multiples temporizadores, donde el polling de los bits de estado es un equivalente a get_ticks.
Entiendo que este tema pretende proporcionar una metodologia mas o menos simple y estandarizada para sincronizar tareas sobre un timer y asi eliminar los delays....pero puedo estar equivocado.


----------



## Miembro eliminado 356005 (May 18, 2018)

Es decir... que nos debemos ajustar a

las capacidades del propio micro: velocidad, memoria, número de temporizadores libres, su resolución, tiempo de cambio de contexto, etc.
las tareas a realizar: cuántas, cuánto tiempo van a consumir, si son cooperativas o apropiativas, o incluso bloqueantes
la resolución exigida por el cliente: en algunas aplicaciones podemos asumir un fallo de 1 segundo/día (regar las plantas), pero en otros casos, no (regular un parque semafórico, controlar una máquina herramienta)
Particularmente, de lo mostrado hasta ahora, no estoy de acuerdo con lo de los bucles infinitos (#use timer también pueden funcionar en modo ISR), pero es porque siempre intento que el chip consuma lo menos posible.


----------



## Dr. Zoidberg (May 18, 2018)

JoaquinFerrero dijo:


> Particularmente, de lo mostrado hasta ahora, no estoy de acuerdo con lo de los bucles infinitos (#use timer también pueden funcionar en modo ISR), pero es porque siempre intento que el chip consuma lo menos posible.


Finalmente, todo depende de la aplicacion destino.
Los lazos infinitos son imprescindibles en cualquier app con microcontroladores ya que su ejecucion nunca finaliza, por que no hay un S.O. que tome el control cuando el micro no hace nada (y en este caso el loop lo haria el S.O.).
Si usas el modo ISR de la #use timer, entonces vas a tener que hacer un loop infinito esperando que ocurra la interrupcion y quedamos igual que antes. YO prefiero usar un loop que haga algo y dejar la isr lo mas reducida posible, y si tengo que despachar tareas temporizadas y no-criticas, entonces lo hago fuera de la isr.

De todas formas, a mi no me termina de gustar esta forma de trabajo por que me "queda incompleta". YO haria polling sobre una cola de eventos y despacharia las tareas basado en los eventos presentes en la cola. Esto permitiria ampliar las carateristicas del despacho de tareas, incluyendo otras que nada tienen que ver con el tiempo. Pero claro, los costos de memoria y de otros recursos son mayores que en este simple caso.


----------



## Eduardo (May 18, 2018)

Dr. Zoidberg dijo:


> Es que no estas llevando varios temporizadores, sino uno solo sobre el cual se despachan todas las tareas.
> Esto no es, ni por cerca, una implementacion hard-real-time pero tampoco lo son los multiples temporizadores, donde el polling de los bits de estado es un equivalente a get_ticks.
> Entiendo que este tema pretende proporcionar una metodologia mas o menos simple y estandarizada para sincronizar tareas sobre un timer y asi eliminar los delays....pero puedo estar equivocado.



No me expresé bien,  me refería mas o menos a eso.   get_ticks solo incrementa un contador y eso obliga a verificar el evento haciendo cuentas con cada uno de los temporizadores,  cuando lo sensato es que en cada tic se actualicen los contadores y en el bucle se chequee un bit de estado.

De multitarea coincido en que esto no tiene nada.   Un multitarea consiste en ir saltando de un proceso a otro por interrupción junto a funciones como lanzar nuevos procesos, interrumpirlos, delays consistentes mientras en ejecutar los demas procesos en lugar clavarse ahí,  y obviamente mas pero que en microcontroladores solo tienen sentido implementar algunas.

-----------------------------------
Joaquin Ferrero:
_Particularmente, de lo mostrado hasta ahora, no estoy de acuerdo con lo de los bucles infinitos (#use timer también pueden funcionar en modo ISR), pero es porque siempre intento que el chip consuma lo menos posible. _

No estoy seguro si referís a hacer el salto a las tareas dentro de la interrupción.
Si es así no lo veo conveniente porque si la tarea por detrás es consumidora de tiempo (como ser evaluar expresiones con funciones trigonométricas) te puede hacer perder la llamada a procesos de alta frecuencia.


----------



## Miembro eliminado 356005 (May 18, 2018)

Dr. Zoidberg dijo:


> Los lazos infinitos son imprescindibles en cualquier app con microcontroladores ya que su ejecucion nunca finaliza, por que no hay un S.O. que tome el control cuando el micro no hace nada (y en este caso el loop lo haria el S.O.).
> Si usas el modo ISR de la #use timer, entonces vas a tener que hacer un loop infinito esperando que ocurra la interrupcion y quedamos igual que antes.


Bueno, sí, son imprescindibles porque la CPU corre continuamente, pero... de lo que estoy hablando es que la CPU se pare cuando no tenga nada que hacer.

Casi todos los microcontroladores disponen de un modo de bajo consumo, con una o varias instrucciones SLEEP, que pone el microcontrolador en modo bajo consumo, o directamente para el reloj interno, dejando solo en funcionamiento las interrupciones que llegan del exterior o las interrupciones propias de los temporizadores.

En uno de mis últimos proyectos con un ATtiny85, el bucle principal era un

*while(1) {*
*    sleep();*
*}*

O sea, no hace nada más que dormir. La gestión de una entrada, el reloj interno, generación de un breve pitido de aviso, iluminación momentánea de un LED... todo se hace con interrupciones.

Esto no es nada raro incluso en monstruos como Linux: cuando no tienen nada que hacer, el procesador ejecuta un proceso para dormir los procesadores o para reducir su velocidad.



			
				Eduardo dijo:
			
		

> Si es así no lo veo conveniente porque si la tarea por detrás es consumidora de tiempo (como ser evaluar expresiones con funciones trigonométricas) te puede hacer perder la llamada a procesos de alta frecuencia.


Entonces hablamos de tareas apropiativas (se apropian de la CPU y no la sueltan hasta que terminan). Al final, depende de cómo sean las tareas y la precisión que necesitemos en cuanto al inicio de las tareas. A lo que me refería era a lo que le he respondido a Zoidberg antes, sobre lo de intentar reducir el bucle principal a la nada, y si es posible, solo dormir.

Esto parece exagerado, pero hay muchas aplicaciones para correr con baterías, en sitios aislados de una alimentación general.


----------



## Eduardo (May 18, 2018)

JoaquinFerrero dijo:


> ........
> Entonces hablamos de tareas apropiativas (se apropian de la CPU y no la sueltan hasta que terminan). Al final, depende de cómo sean las tareas y la precisión que necesitemos en cuanto al inicio de las tareas. A lo que me refería era a lo que le he respondido a Zoidberg antes, sobre lo de intentar reducir el bucle principal a la nada, y si es posible, solo dormir.
> Esto parece exagerado, pero hay muchas aplicaciones para correr con baterías, en sitios aislados de una alimentación general.



Pienso igual, solo que no al extremo de vaciar completamente el bucle principal porque la administración de las tareas igual se tienen que hacer en algún lado.
Prefiero hacer:

*while(1) {*
*    ...........*
*    chequeo de estados, pines y llamado a tareas*
*    ...........  *
*    sleep();*
*}*


----------



## Dr. Zoidberg (May 18, 2018)

Ahhh...bueno....el contexto lo es todo.
Si necesitas reducir el consumo por el motivo que sea, no vale el esquema de saint tal como esta, pero nada impide cambiar el ritmo de los ticks a unp que sea comun denominador de todas las tareas y que despierte a la cpu cada mas tiempo...si no hay nada que hacer, dormimos de nuevo.
Esto ya es muy dependiente de la aplicacion y se puede, dentro de ciertos limites, utilizar la metodologia de saint. Fuera de esos limites habra que usar otras tecnicas.

Ooopppssss...ya contesto Eduardo.


----------



## D@rkbytes (May 18, 2018)

RTOS va muy bien para microcontroladores PIC de gran memoria, consume demasiada RAM (Flash) y ROM.
Es inimaginable llevar un proyecto grande con RTOS para  un PIC promedio de 1024 o 2048 bytes.
Con un simple proceso de hacer destellar LEDS's, ya uno se puede ir dando cuenta del gran consumo de RAM.

Con esto no quiero decir que no sirva, pues es obvio que es una alternativa para crear un sistema pseudo multitarea.
De hecho, lo uso bastante en algunos proyectos y siempre trato de elegir qué microcontrolador PIC se ajusta a lo que necesito en cuanto a memoria y pines.
Yo pienso que si no se ha difundido mucho este tema, es precisamente por el hecho del consumo de RAM.
Aparte porque las personas que recién inician en este mundo de los microcontroladores, ya lo hacen sin ningún tipo de preparación académica.
Internet les ha abierto las puertas a muchas personas sin conocimientos, ni de electrónica básica, ni de electrónica digital.
Así que ahora cualquiera puede programar un microcontrolador siguiendo instrucciones de otra persona que posiblemente tampoco sabe nada pero que también por ahí leyó algún "tutorial".
Entonces, este tipo de programación ya les parecerá complejo y simplemente se dedicarán a copiar y por consecuencia a seguir creando programas con un Copy/Paste.
Por eso existen los temas de: ¿Cómo elevar un voltaje de 5 V. a 12 V.?
¿Qué pasa si mi fuente de poder es de 10 A. y mi circuito apenas consume 100 mA? O preguntas similares.
Y esto ya es tan común que realmente alarma. (Al menos a mi, sí me preocupa)

Ahora, retomando el tema que va de la mano sobre lo citado, veo que cada quien opina diferente, cuando la realidad es llegar al objetivo.
Yo aprendí sobre este proceso leyendo la ayuda del compilador y viendo sus ejemplos, que por cierto, son varios.
Si uno, como programador del entorno, los ve, encontrará que la historia ya está escrita.


----------



## Saint_ (May 19, 2018)

Dr. Zoidberg dijo:


> En el programa y agregando tareas.c al proyecto se protege el mecanismo de despacho de modificaciones involuntarias..o nó.


Aunque no estaba pensado ser implementar como una librería me parece buena idea.


Dr. Zoidberg dijo:


> Entiendo que este tema pretende proporcionar una metodologia mas o menos simple y estandarizada para sincronizar tareas sobre un timer y asi eliminar los delays....pero puedo estar equivocado.


Estas en lo cierto, el objetivo es mostrar que mucho esfuerzo se puede hacer que el microcontrolador (en este caso PIC gama 16) puede efectuar varias tareas.


Dr. Zoidberg dijo:


> YO haria polling sobre una cola de eventos y despacharia las tareas basado en los eventos presentes en la cola. Esto permitiria ampliar las carateristicas del despacho de tareas, incluyendo otras que nada tienen que ver con el tiempo. Pero claro, los costos de memoria y de otros recursos son mayores que en este simple caso.


De todos modos sigue siendo una metodología que sea accesible y relativamente fácil de implementar. De otro modo el título del post seria “implementando un RTOS”, CCS ya tiene uno que es cooperativo así que tampoco lo haría.


Eduardo dijo:


> De multitarea coincido en que esto no tiene nada. Un multitarea consiste en ir saltando de un proceso a otro por interrupción junto a funciones como lanzar nuevos procesos, interrumpirlos, delays consistentes mientras en ejecutar los demas procesos en lugar clavarse ahí, y obviamente mas pero que en microcontroladores solo tienen sentido implementar algunas.


En el caso de un multitarea real tendríamos que usar un uC de varios núcleos como el propiller o de plano hacer trabajar a varios uC de manera sincronizada, pero en este segundo caso para que usar varios uC cuando uno solo podría realizar todo.

Sobre los modos de bajo consumo y su uso dependerá del programador.


----------



## Dr. Zoidberg (May 19, 2018)

Saint_ dijo:


> En el caso de un multitarea real tendríamos que usar un uC de varios núcleos como el propiller o de plano hacer trabajar a varios uC de manera sincronizada, pero en este segundo caso para que usar varios uC cuando uno solo podría realizar todo.


Lo que normalmente se refiere como multitarea en un solo nucleo es lo que se denomina "time slicing", que consiste en un despachador preemptivo que asigna pequeños tiempos de ejecucion a cada tarea y conmuta entre ellas cuando se les cumple el plazo o cuando si inicia una operacion I/O bloqueante.
Claro que esto implica un rediseño cpmpleto de la base de software disponible para estos micros y es algo que casi no vale la pena en un dispositivo de pocos recursos de CPU y memoria.


----------



## Saint_ (May 19, 2018)

Entonces desde esa perspectiva, ¿Un RTOS cooperativo no es un multitarea y RTOS preventivo si lo es?


----------



## Dr. Zoidberg (May 19, 2018)

Ambos son multitarea, solo que en el cooperativo las decisiones las toma el "programador" y la eficiencia queda librada a sus decisiones, mientras que en el preemptivo las decisiones las toma el despachador basado en alguna política dependiente del objetivo del multitarea (no es lo mismo el despachador de Linux estándar que el despachador de Real Time Linux, por ejemplo).
Otra diferencia mas simple de analizar es que en el cooperativo el despachador corre en el mismo espacio de prioridades que las tareas de usuario, mientras que en el preemptivo el despachador corre con prioridad "suprema"... superior a cualquier tarea de usuario. Esto ultimo no es muy simple de lograr en un microcontrolador tipo PIC por que no tiene idea de niveles de prioridad de ejecución (por ejemplo, lo que sería el Ring 0 y el Ring 3 en los chips X86) y cualquier programador puede desahbilitar las interrupciones impunemente y decir adios al despachador.

Digamos... ambos son multitarea, pero para objetivos diferentes.


----------



## Eduardo (May 19, 2018)

Saint_ dijo:


> ....
> En el caso de un multitarea real tendríamos que usar un uC de varios núcleos como el propiller o de plano hacer trabajar a varios uC de manera sincronizada, pero en este segundo caso para que usar varios uC cuando uno solo podría realizar todo.



Eso no sería multitarea sino procesamiento paralelo.  Parece lo mismo, pero no lo es.

En una versión mínima de multitarea,  se debe guardar el estado y los registros internos del proceso interrumpido para luego cargar los del entrante --> esto obliga a necesitar stacks independientes para cada proceso mas un buffer LIFO  con punteros a los stacks.
El problema de implementarlo en un PIC comunardo no es tanto la memoria sino que el stack es interno y de pocos niveles. Si se cortó un proceso en medio de una subrutina y los otros llaman o retornan de subrutinas van a saltar a donde no quieren.

Acá la discusión apunta a lo inapropiado de "multitarea" en el título no a las ventajas de reemplazar el delay() por algo basado en interrupciones. Con lo que estoy plenamente de acuerdo. 



> Sobre los modos de bajo consumo y su uso dependerá del programador.



Depende de la aplicación. El programador puede elegir como, pero como formas eficientes no tiene tantas para elegir...


----------



## Dr. Zoidberg (May 19, 2018)

D@rkbytes dijo:


> Si uno, como programador del entorno, los ve, encontrará que la historia ya está escrita.


Estoy de acuerdo que la historia ya está escrita, al menos para CCS, pero desconozco los otro entornos, de los que hay varios para desarrollo en PICs. Lo propuesto en este tema - creo yo -  podría ser de aplicacion no solo a los PICs sino a cualquier micro de "nivel" parecido, tales como los ATMega de los Arduino...o los MSP de Texas. En todos los casos, si no se tiene la directiva *#use timer*, pues es muy simple configurar un timer para interrumpir cada cierto tiempo, colgar una ISR que incremente un contador y armar una función que devuelva el estado de la cuenta....y lo demás sigue igual.
Es mas, con un par de archivos como los anteriores, con directivas de compilación condicional, se puede incluir el código para todas las plataformas que se quiera...


----------



## Miembro eliminado 356005 (May 19, 2018)

Multitarea
«La multitarea es la característica de los sistemas operativos modernos de permitir que varios procesos o aplicaciones se ejecuten aparentemente al mismo tiempo, compartiendo uno o más procesadores».

*Tipos de multitarea*

Cooperativa: El sistema operativo da el control a un proceso, y es este el que cede de nuevo el control pasando a estar en espera cuando decide voluntariamente que no puede seguir su ejecución.
Apropiativa o preferente: El sistema operativo es el encargado de administrar el/los procesador(es) repartiendo el tiempo de uso entre los procesos que estén esperando para utilizarlo.
Real: Solo se da en sistemas con multiprocesador; varios procesos se ejecutan realmente al mismo tiempo en distintos microprocesadores.


----------



## Saint_ (May 19, 2018)

Dr. Zoidberg dijo:


> Digamos... ambos son multitarea, pero para objetivos diferentes.


Eso mismo pensé


Eduardo dijo:


> En una versión mínima de multitarea, se debe guardar el estado y los registros internos del proceso interrumpido para luego cargar los del entrante --> esto obliga a necesitar stacks independientes para cada proceso mas un buffer LIFO con punteros a los stacks.



Esto significa que si se tiene un programa donde se tiene varias tareas ejecutándose no es multitarea y que solamente es multitarea sí obligatoriamente hay que guardar estados y pilas y punteros a pilas, etc.

Cuando en los ejemplos que subí una tarea se apropia de la CPU, termina su pequeña operación cambiando de estado un led y luego retornar al despachador de tareas para que este pueda lanzar a las otras tareas…aparentando simultaneidad ¿no es multitarea cooperativo?



Eduardo dijo:


> Acá la discusión apunta a lo inapropiado de "multitarea"


Si esto es cierto y está fundamentado les pido a los moderadores que cambien el título y con eso se acaba la discusión.

Por otro lado.


Dr. Zoidberg dijo:


> Estoy de acuerdo que la historia ya está escrita, al menos para CCS, pero desconozco los otro entornos, de los que hay varios para desarrollo en PICs. Lo propuesto en este tema - creo yo - podría ser de aplicacion no solo a los PICs sino a cualquier micro de "nivel" parecido, tales como los ATMega de los Arduino...o los MSP de Texas. En todos los casos, si no se tiene la directiva *#use timer*, pues es muy simple configurar un timer para interrumpir cada cierto tiempo, colgar una ISR que incremente un contador y armar una función que devuelva el estado de la cuenta....y lo demás sigue igual.
> Es mas, con un par de archivos como los anteriores, con directivas de compilación condicional, se puede incluir el código para todas las plataformas que se quiera...


...Le diste en el clavo 

Mientras voy trabajando en la siguiente entrega


----------



## Dr. Zoidberg (May 19, 2018)

Saint_ dijo:


> Si esto es cierto y está fundamentado les pido a los moderadores que cambien el título y con eso se acaba la discusión.


Es que no sé si es taaaan inapropiado el nombre *"multitarea"*, ya que podés pensar que están todas las tareas _"esperando simultáneamente"_ que se cumpla su tiempo de ejecución y entonces - la que le toca - se apropia de la CPU hasta que termina...mientras la otras _"siguen esperando"_.
No es muy diferente a lo que sucedería con un despachador preemptivo, solo que este - además - corta la ejecución de la tarea que tiene la CPU si se le acaba el plazo asignado.


----------



## Miembro eliminado 356005 (May 19, 2018)

Sí que es multitarea porque, de cara al usuario, da la sensación de que se realizan varias tareas de forma simultánea. En concreto, cooperativa, ya que las tareas ceden el control al despachador una vez que terminan.

En mi mensaje *#33* he puesto las definiciones, y ahí no se especifica si hay cambios de contexto, registros, varias CPU... El concepto es más general que su implantación física.


----------



## Saint_ (May 19, 2018)

Para aclara un poco la idea de los ejemplos que subí.

En los ejemplos anteriores una tarea se apropia de la CPU, termina sus acciones y retorna al despachador de tareas. Esto puede generar la impresión de que si una tarea hace varias cosas entre esas tener que esperar a que por ejemplo llegue un dato del puerto serie y solo cuando haya terminado todas sus acciones recién retorna al despachador de tareas haciendo que el resto de tareas esperen “las ganas” de la que está ocupando la CPU.

Bueno esa no es la idea. La idea es dividir las tareas de modo que se tengan estados que permitan retornar al despachador de tareas aunque no hayan terminado todas sus acciones y luego de un tiempo (cuando le vuelva a tocar el turno) retornar a donde se quedó para continuar lo que le queda pendiente… pero esto será recién planteado en las siguientes entregas.


----------



## Dr. Zoidberg (May 19, 2018)

Saint_ dijo:


> La idea es dividir las tareas de modo que se tengan estados que permitan retornar al despachador de tareas aunque no hayan terminado todas sus acciones y luego de un tiempo (cuando le vuelva a tocar el turno) retornar a donde se quedó para continuar lo que le queda pendiente


A eso me referia en mis primeros mensajes cuando hablaba de una pequeña API para liberar la CPU cuando fuera necesario.
Para eso existe una primitiva denominada *yield* que cumple precisamente esa tarea: devuelve el control al despachador hasta que este decida retornarselo. Ojo que aca la politica de retorno a la tarea no nececesariamente tiene que ver con el tiempo, y eso exige modificar un poco el despachador.


----------



## Scooter (May 20, 2018)

En aplicaciones sencillas basta con no poner bucles que esperen a que pasen cosas y hacer estas funciones verificando flags, activar interrupciones siempre que se pueda, no poner delays etc.

Hay que programar de forma que se quede todo a medias y en la siguiente pasada se siga por dónde se quedó. Haciendo eso sencillamente puedes poner una tarea tras otra en el bucle principal.
Si alguna tiene más prioridad la llamas más veces:
Loop{
Tarea1
Tarea2
Tarea1
Tarea3
}
Aquí la tarea 1 se ejecuta el doble de veces que las otras
Y se ejecutan de forma "asíncrona" osea siempre que se pueda, no sé si cada ms o cada cuando sea.
Si hay una tarea que tenga que ser "síncrona" esa se cuelga de la interrupcion del timer.
Para saber lo que tardan las tareas puedes añadir unos pulsos por un pin y medirlos en un osciloscopio 
Loop{
Tarea1
Pulso
Tarea2
Pulso
Tarea1
Pulso
Tarea3
Pulso
}
La pega es que programar las rutinas así obliga a pensar de otro modo, usar flags para indicar si está activo o no algo, contadores etc.

Por ejemplo, en un sistema de control de tráfico, entre otras cosas se verificaba que las salidas a triacs estuviesen en el estado en el que se había programado leyendo por el lado de los 230V que había tensión o no y en caso de no coincidir marcar una avería o apagar el cruce. Además se verificaba la corriente en las luces rojas. Como en los pasos por cero etc no coincide lo programado con lo leído y eso no es avería, la programación clásica haría un bucle en una salida verificando que lo escrito coincida con lo leído y daría un tiempo para que coincidiese.
Yo verificaba las treinta señales una sola vez por pasada y aumentaba o disminuía un contador por cada luz según si estaba "bien" o "mal". Al cabo de X fallos consecutivos se disparaba la alarma, este valor de sensibilidad era programable. La rutina no tenía ningún bucle, comprobaba en cada pasada las treinta señales se le llamaba siempre que se podía digamos que siempre que se volvía al main estabas verificando sin parar. Era ensamblador, no era C.
El resto de rutinas era algo semejante, cada vez que se pulsaba una tecla se metía su valor en un buffer, se actualizaba el display y se seguía. Solo al pulsar intro se analizaba el contenido del buffer y se ejecutaba la acción, y se seguía...
La parte de los tiempos sí que pendía de la interrupción del timer. El resto se ejecutaba sin parar en un bucle, activando en watchdog de paso.

Otra opción que se me ha ocurrido es editar la función delay dentro poner código que lea el teclado o lo que sea necesario, así cada vez que esperas mantienes el sistema en lugar de fundir ciclos.


----------



## Dr. Zoidberg (May 20, 2018)

@Scooter
Lo "malo" de tu propuesta es el lio de codigo que se produce y la dificultad de mantenimiento del programa... Y ni hablar si ademas va en assembler.
De esta otra forma es posible aislar las tareas entre si usando funciones independientes para cada una de ellas. Incluso es muy simple modificar el despachador para que ejecute una tarea determinada cuando no hay otra cosa por hacer, de forma tal que se ejecute asincronicamente con el timer...atada a ciertas restricciones, claro.

Si basaramos el despachador en una cola de mensajes la potencia de la solucion escala de manera importante, por que ahora las interrupciones de los perifericos pueden realizar la tarea necesaria y mandar un mensaje que advierta al despachador que ocurrio la interrupcion, que fue procesada y que los datos estan disponibles para que la tarea destino los utilice, de manera tal que pueda despachar la tarea asociada que hasta ese instante estaba "esperando".


----------



## Scooter (May 20, 2018)

Sin dudas. Doy fé de que el mantenimiento es muy complicado. Solo usando C con variables locales ya se ganó muchísimo a la hora de mantener el código. 
De todos modos con un despachador de tareas necesitas programar bastante parecido; con código que no pare, que pase de largo en los problemas y deje banderas o algo de lo que ha pasado para la próxima; si paras a esperar a algo ya no funciona el sistema.
Otra opción es que el despachador de tareas sea una interupción del timer que corte la tarea esté donde esté, guarde una "foto" de que estaba pasando, registros, punteros etc y salte a otra tarea. Eso lo sabría hacer en ensamblador pero no lo sabría hacer en C. Supongo que hay compiladores que ya llevan esto incorporado.


----------



## Dr. Zoidberg (May 20, 2018)

Scooter dijo:


> De todos modos con un despachador de tareas necesitas programar bastante parecido; con código que no pare, que pase de largo en los problemas y deje banderas o algo de lo que ha pasado para la próxima; si paras a esperar a algo ya no funciona el sistema.


Por eso mencionaba la cola de mensajes: un mensaje marca la ocurrencia de algun suceso y luego el despachador dispara la tarea asociada a ese evento cuando le llegue el turno (hasta es posible priorizar los mensajes). Ahora no quedan flags descolgados ni cosas raras, una ISR solo manda un mensaje entendible por el desarrollador (que no es mas que una constante con nombre en un #define), previa atencion de la interrupcion y el proceso inmediato de sus datos (por ejemplo, completar una conversión y meter el resultado en un buffer)



Scooter dijo:


> Otra opción es que el despachador de tareas sea una interupción del timer que corte la tarea esté donde esté, guarde una "foto" de que estaba pasando, registros, punteros etc y salte a otra tarea. Eso lo sabría hacer en ensamblador pero no lo sabría hacer en C.


Eso es un despachador preemptivo, y sí... es bastante mas complejo. Dudo que haya compiladores que lo traigan, pero ya tenemos el *FreeRTOS* que hasta donde sé, maneja todo eso, pero no está disponible para los PICs "chicos" de las series 16 y 18.


----------



## Eduardo (May 22, 2018)

Dr. Zoidberg dijo:


> ....
> Eso es un despachador preemptivo, y sí... es bastante mas complejo. Dudo que haya compiladores que lo traigan, pero ya tenemos el *FreeRTOS* que hasta donde sé, maneja todo eso, pero no está disponible para los PICs "chicos" de las series 16 y 18.



CCS tiene su RTOS y corre en los PIC de la serie 16.   

El primer ejemplo que activaba leds con diferentes períodos quedaría con este aspecto:

`#include <16F628A.h>
#use delay(clock=4000000)
#use rtos(timer=1,minor_cycle=100ms)

#task(rate=1000ms,max=50ms)
void tarea1() { output_toggle(pin_a0); }

#task(rate=900ms,max=50ms)
void tarea2() { output_toggle(pin_a1); }

#task(rate=800ms,max=50ms)
void tarea3() { output_toggle(pin_a2); }

void main()
{
   rtos_run();
}`

Aunque  jamás me puse a investigar respecto a su eficiencia consumo de recursos.  Es mas, jamás lo he usado


----------



## Dr. Zoidberg (May 23, 2018)

Eduardo dijo:


> CCS tiene su RTOS y corre en los PIC de la serie 16.


Por lo que he leído *en este archivo*, el RTOS del CCS es cooperativo, no preemptivo.


----------



## Saint_ (May 25, 2018)

Dr. Zoidberg dijo:


> Por lo que he leído *en este archivo*, el RTOS del CCS es cooperativo, no preemptivo.


Exactamente el RTOS de CCS es cooperativo.

Para esta segunda parte propongo un ejemplo simple el cual efectuá el parpado de dos leds.

El primer led tiene un periodo de 1s, este se queda encendido 100ms. Y el resto del tiempo se queda apagado.

El segundo led tiene in periodo de 1s, este se enciende dos veces, el tiempo que se queda encendido cada vez es de 100ms. Y el resto del tiempo permanece apagado.

El objetivo en efectuar un programa que mantenga a las tareas en distintos estados de modo que se puedan atender otras tareas en medio de la ejecución de una.

Se opta por tres modos de resolver este problema y comparar cantidad de recursos que se consume. Básicamente RAM y ROM.

Primer modo. Este el modo con el que se inició al principio de este post.

```
/*****************************************************************************/
#include <16F84a.h>
/*****************************************************************************/
#FUSES PUT                      //Power Up Timer
#FUSES NOPROTECT                //Code not protected from reading
/*****************************************************************************/
#use delay(crystal=20000000)
/*****************************************************************************/
#USE TIMER(TIMER=0,TICK=10ms,BITS=16,NOISR)
/*****************************************************************************/
void tarea1()
{
   static unsigned int8 estado_tarea1;
   switch(estado_tarea1)
   {
      case 0: output_high(pin_a0);estado_tarea1++;break;
      case 1:output_low(pin_a0);estado_tarea1++;break;
      case 9: estado_tarea1=0;break;
      default: estado_tarea1++;
   }
}
/*****************************************************************************/
void tarea2()
{
   static unsigned int8 estado_tarea2;
   switch(estado_tarea2)
   {
      case 0:output_high(pin_a1);estado_tarea2++; break;
      case 1:output_low(pin_a1);estado_tarea2++;break;
      case 2:output_high(pin_a1);estado_tarea2++;break;
      case 3:output_low(pin_a1);estado_tarea2++; break;
      case 9:estado_tarea2=0;break;
      default:estado_tarea2++;
   }
}
/*****************************************************************************/
void main()
{
   unsigned int16 tick,tick_tarea1=0,tick_tarea2=0;
   while(true)                            
   {
      tick=get_ticks();  
      if((tick-tick_tarea1)>TICKS_PER_SECOND/10)
      {
         tick_tarea1=tick;
         tarea1();
      }
      if((tick-tick_tarea2)>TICKS_PER_SECOND/10)
      {
         tick_tarea2=tick;
         tarea2();
      }
   }
}
/*****************************************************************************/
```


Este segundo método tiene como base la propuesta de *Dr. Zoidberg* usando punteros a funciones y estructuras, lamentablemente el modo en que logre implementarlo ocupa mucho espacio tanto en la RAM como en la ROM. Seguramente PICs de mayor cantidada de memoria no habria mucho problema y probablemete exista un mejor método para lograrlo pero por el momento no lo encontré.


```
#include <16f84a.h>
#fuses nowdt,noprotect,put
#use delay(crystal=20M)
/*****************************************************************************/
#include "setjmp.h"
#use timer(timer=0,tick=10ms,bits=16,noisr)
typedef void(*punteroFuncion)();
typedef struct {
                  punteroFuncion pf;
                  unsigned int16 periodo;
                  unsigned int16 tickCont;
                  jmp_buf despachador;
                  jmp_buf tarea;
                  char bandera;
               }TAREA;
unsigned int8 i;
/*****************************************************************************/
void tarea1();
void tarea2();
void tarea3();
TAREA tareas[]={
               {tarea1,TICKS_PER_SECOND/10,0}, //crea la tarea1,aprox 100ms,0->la tarea inicia en aprox 100ms
               {tarea2,TICKS_PER_SECOND/10,0}//crea la tarea2,aprox 100ms,0->la tarea inicia en aprox 300ms
               };
/*****************************************************************************/
void despachadorTareas(TAREA *tareas,unsigned int8 tCant)
{
   punteroFuncion pf1;
   unsigned int16 tick;

   for(i=0;i<tCant;i++)
   {
      (tareas+i)->tarea[0]=0;
      (tareas+i)->tarea[1]=0;
      (tareas+i)->bandera=0;
   }
   while(true)
   {
      tick=get_ticks();
      for(i=0;i<tCant;i++)
      {
         if((tick-(tareas+i)->tickCont)>(tareas+i)->periodo)
         {
            (tareas+i)->tickCont=tick;//actualiza los ticks          
           
            if((tareas+i)->bandera==0)
            {
               if(setjmp((tareas+i)->despachador)==0)
               {
                  pf1=(tareas+i)->pf;
                  pf1();            
               }
            }
            else
            {
               (tareas+i)->bandera=0;
               longjmp((tareas+i)->tarea,1);
             
            }
         }
      }
   }
}
/*****************************************************************************/
#inline
void yield(TAREA *tareas)
{
    if(setjmp((tareas+i)->tarea)==0)
    {
      (tareas+i)->bandera=1;
      longjmp((tareas+i)->despachador,1);
    }
}
/*****************************************************************************/
void tarea1()
{
   static unsigned int8 s;
   output_high(pin_a0);
   yield(&tareas);
   output_low(pin_a0);
   for(s=0;s<8;yield(&tareas),s++);
}
/*****************************************************************************/
void tarea2()
{
   static unsigned int8 s;
   output_high(pin_a1);
   yield(&tareas);
   output_low(pin_a1);
   yield(&tareas);
   output_high(pin_a1);
   yield(&tareas);
   output_low(pin_a1);
   for(s=0;s<6;yield(&tareas),s++);
}
/*****************************************************************************/
void main()
{
   despachadorTareas(tareas,2);     //llama al despachador de tareas, 2 es la
                                    //cantidad de tareas creadas
}
```


Este último es usando el RTOS de CCS.


```
#include <16F84a.h>
/*****************************************************************************/
#FUSES PUT                      //Power Up Timer
#FUSES NOPROTECT                //Code not protected from reading
/*****************************************************************************/
#use delay(crystal=20000000)
/*****************************************************************************/
#use rtos(timer=0, minor_cycle=10ms)
#task(rate=100ms)
void tarea1();
#task(rate=100ms)
void tarea2();
/*****************************************************************************/
void tarea1()
{
   static unsigned int8 n;
   output_high(pin_a0);
   rtos_yield();
   output_low(pin_a0);
   for(n=0;n<8;rtos_yield(),n++);
}
/*****************************************************************************/
void tarea2()
{
   static unsigned int8 n;
   output_high(pin_a1);
   rtos_yield();
   output_low(pin_a1);
   rtos_yield();
   output_high(pin_a1);
   rtos_yield();
   output_low(pin_a1);
   for(n=0;n<6;rtos_yield(),n++);
}
/*****************************************************************************/
void main()
{
   rtos_run();
}
/*****************************************************************************/
```


La implementación de múltiples tareas es totalmente posible en los micro controladores, solo es cuestión de elegir las herramientas que mejor se ajunten a nuestros requerimientos.


----------



## Dr. Zoidberg (May 25, 2018)

Bue.....pero el primer ejemplo y el segundo son completamente diferentes en cuanto al mecanismo de despacho.
En la segundo, la inicializacion estatica de las estructuras es bastante dudosa, y la presencia del yield complica la comparacion (pero esta muy bien hecho ) y el codigo.

Separando el proceso en mas funciones vinculadas por variables globales reduce mucho el lio de codigo y elimina el yield.

Yo tengo armado para Arduino la primer version que subi, el codigo me ocupa 962 bytes y las variables solo 32 bytes.


----------



## Miembro eliminado 356005 (May 25, 2018)

Perdón, pero, ¿por qué hay tantas llamadas a *rtos_yield()*?

Según el manual, se debe usar cuando se quiere ceder el control a *rtos_run()*. Por ejemplo, al final de la tarea. Pero en este caso, ¿son necesarias 9 llamadas en cada tarea?


----------



## Dr. Zoidberg (May 26, 2018)

JoaquinFerrero dijo:


> Perdón, pero, ¿por qué hay tantas llamadas a *rtos_yield()*?
> Según el manual, se debe usar cuando se quiere ceder el control a *rtos_run()*. Por ejemplo, al final de la tarea. Pero en este caso, ¿son necesarias 9 llamadas en cada tarea?


*yield *es una forma _no-muy-eficiente_ de devolver el control al despachador cuando no tenés nada mas que hacer y podés esperar a que te toque el turno de ejecución nuevamente. 
El problema con los nuevos ejemplos es que requieren dos timers diferentes: uno para encender el LED y otro para apagarlo en un tiempo diferente y menor que el primero, de esa forma el primero define el período de encendido y el segundo la duración del encendido. *YO* hubiera hecho algo diferente:  una función para encender el LED y otra para apagarlo, cada una con su timer y tratar de sincronizarlas con una variable global. En el caso del *rtos *esto es muy fácil con el *rtos_wait* o con el *rtos_await*. En el caso del despachador es mas complejo por que no tenemos una API como la de *rtos*, aunque con el *yield *de Saint_ y un poco de imaginación podría funcionar.


----------



## Saint_ (May 26, 2018)

Dr. Zoidberg dijo:


> *YO* hubiera hecho algo diferente: una función para encender el LED y otra para apagarlo, cada una con su timer y tratar de sincronizarlas con una variable global.


Seguramente que si hubiera logrado… por fortuna siempre hay distintos modos de resolver los problemas.


----------



## Miembro eliminado 356005 (May 26, 2018)

Mi pregunta era porque la solución de Eduardo en *#43* parece mínima, elegante y correcta, aún sin llamar a rtos_yield().

Bueno, lo que dice Saint_: hay modos distintos.


----------



## Dr. Zoidberg (May 26, 2018)

JoaquinFerrero dijo:


> Mi pregunta era porque la solución de Eduardo en *#43* parece mínima, elegante y correcta, aún sin llamar a rtos_yield().


Si, pero son problemas diferentes. El de Eduardo enciende los LEDs con "ondas cuadradas" y el ultimo de Saint_ lo hace con "pulsos"..... Por asi decirlo..


----------



## Dr. Zoidberg (May 28, 2018)

Acá les traigo una versión de este minikernel cooperativo para Arduino.
El ejemplo es el primero que trajo Saint_ y este es el video:





Este es el código:

```
#include "CompuIIBoard.h"
#include "dispatcher.h"

#define TICK_PERIOD_MS  10

byte LEDs_stat = 0;

void tarea1() {
  LEDs_stat ^= 0x01;
  writeLEDs( LEDs_stat );
}

void tarea2() {
  LEDs_stat ^= 0x04;
  writeLEDs( LEDs_stat );
}

void tarea3() {
  LEDs_stat ^= 0x10;
  writeLEDs( LEDs_stat );
}

void setup() {
  // put your setup code here, to run once:
  // Acá iniciamos el timer que cuenta los ticks
  initBoardCompuII();
  writeLEDs( LEDs_stat );
  use_timer1( TICK_PERIOD_MS );
}

void loop() {
  // put your main code here, to run repeatedly:
  // No tiene mucho caso por que el dispatcher es un loop infinito, pero bue...
  // para que quede según el estandard Arduino
  TASK tareas[] = {
      createTask( tarea1, 10 ),
      createTask( tarea2, 30 ),
      createTask( tarea3, 100 )
      };

  runDispatcher( tareas, 3 );
}
```

Y les dejo todos los archivos.
Aún debo verificar la configuración del Timer 1, pero hasta donde pude medir el tiempo funciona bien. Si hay alguna modificación lo subo de nuevo.

El Timer 1 del Atmega328 corre en modo CTC y la frecuencia del cristal está dividida correctamente por 10^6 como debe ser. La ISR del timer 1 se dispara por comparación y solo incrementa el tick_timer del sistema (es un unsigned int así que pueden poner tiempos muuuy largos a cada tarea). También se incluye un macro llamado createTask que permite reducir la cantidad de valores al inicializar el arreglo de tareas solo al nombre de la tarea y a la cantidad de ticks a la cual se ejecuta.
La autoconfiguración del preescaler está tomada de Google Code Archive - Long-term storage for Google Code Project Hosting. y por lo tanto toda la biblioteca tiene licencia GPL3 y no se aplica la Creative Commons del foro.


----------



## Dr. Zoidberg (Jun 1, 2018)

Esperando que se me termine de arreglar la rodilla, estaba medio sin hacer nada y me dije:
"Voy a arreglar el despachador para que funcione  con cualquier Timer del Arduino (0, 1 0 2) ya que estaba fijo solo para el Timer 1".
Bueno...nada muy evolucionado. Solo un poco de copiar y pegar lo del Timer 1, arreglar algunos registros y poner una parva de directivas de compilación condicional para minimizar la cantidad de código generado.
El unico nombre de función "visible" que ha cambiado es *use_timer1* que ahora se llama *use_timer*.
Tampoco hace falta contar cuantas tareas ponemos en la lista, ya que hay un macro *endTaskList()* que pone en la lista los valores necesarios para que el despachador se avive donde termina la lista.
Por ultimo, y culpa del IDE de Arduino, hay una definición del timer en el archivo _*dispatcher.h*_. Esto no es mas que un macro que puede valer:
#define USE_TIMER0   ó
#define USE_TIMER1   ó
#define USE_TIMER2
tienen que poner el que corresponda para seleccionar el timer que quieren usar y el compilador se encarga de arreglar todo lo demás para que el despachador funcione con el timer elegido.

Les dejo el mismo ejemplo de antes con los pequeños cambios y un .zip con todos los archivos.

```
#include "CompuIIBoard.h"
#include "dispatcher.h"

#define TICK_PERIOD_MS  10

byte LEDs_stat = 0;

void tarea1() {
  LEDs_stat ^= 0x01;
  writeLEDs( LEDs_stat );
}

void tarea2() {
  LEDs_stat ^= 0x04;
  writeLEDs( LEDs_stat );
}

void tarea3() {
  LEDs_stat ^= 0x10;
  writeLEDs( LEDs_stat );
}

void setup() {
  // put your setup code here, to run once:
  // Acá iniciamos el timer que cuenta los ticks
  initBoardCompuII();
  writeLEDs( LEDs_stat );
  use_timer( TICK_PERIOD_MS );
}

void loop() {
  // put your main code here, to run repeatedly:
  // No tiene mucho caso por que el dispatcher es un loop infinito, pero bue...
  // para que quede según el estandard Arduino
  TASK tareas[] = {
      createTask( tarea1, 5 ),
      createTask( tarea2, 15 ),
      createTask( tarea3, 50 ),
      endTaskList()
      };

  runDispatcher( tareas );
}
```

y este es el dispatcher.h

```
#ifndef DISPATCHER_H
#define DISPATCHER_H

#include <Arduino.h>

#define USE_TIMER0

#define createTask( fptr, tickCnt )   { fptr, tickCnt, 0 }
#define endTaskList()                 { NULL, 0, 0 }

typedef void (*taskPtr)();

typedef struct {
    taskPtr ftask;
    unsigned int ticks;
    unsigned int ticksCnt;
} TASK;

void use_timer( int tickPeriod );
void runDispatcher( TASK tasks[] );
unsigned int get_ticks();

#endif
```


----------



## Dr. Zoidberg (Jun 4, 2018)

Para los que siempre preguntan como se hace, acá les dejo la imitación del "Auto Fantástico" hecho con la simple función *barreLED()* y el kernel(cito) multitarea. Esto mismo se puede hacer muy estúpidamente con un lazo *for *y un *delay()*, pero no queda tiempo de CPU para hacer nada mas. De esta otra forma pueden controlar las luces, las puertas o lo que se les ocurra y es tanto o mas simple que el otro.


```
#include "CompuIIBoard.h"
#include "dispatcher.h"

#define TICK_PERIOD_MS  300
#define UP    1
#define DOWN  0

byte LEDs_stat = 0;

void barreLED() {
  static byte i = 0, up = UP;  // = 0

  LEDs_stat = _BV( i );
  writeLEDs( LEDs_stat );
  if( i >= 7 ) up = DOWN;
  else if( i <= 0 ) up = UP;
  if( up == UP ) i++;
  else i--;
}

void setup() {
  // put your setup code here, to run once:
  // Acá iniciamos el timer que cuenta los ticks
  initBoardCompuII();
  writeLEDs( LEDs_stat );
  use_timer( TICK_PERIOD_MS );
}

void loop() {
  // put your main code here, to run repeatedly:
  // No tiene mucho caso por que el dispatcher es un loop infinito, pero bue...
  // para que quede según el estandard Arduino
  TASK tareas[] = {
      createTask( barreLED, 5 ),
      endTaskList()
      };

  runDispatcher( tareas );
}
```

Y un video de como queda:







*WARNING!!!*
En el ejemplo el tiempo de barrido no coincide con la realidad por que estaba configurado el Timer2 que no puede interrumpir cada 300ms y la autoconfiguración lo pone al máximo tiempo que dá que no coincide con lo programado. OJO!!! Para esto hay que usar el Timer1 o en su defecto poner un menor timer-tick y una mayor cantidad en la cuenta de ticks de la tarea. Algo como esto:


```
#include "CompuIIBoard.h"
#include "dispatcher.h"

#define TICK_PERIOD_MS  1 //OJO que va con el Timer2!!
#define UP    1
#define DOWN  0

byte LEDs_stat = 0;

void barreLED() {
  static byte i = 0, up = UP;  // = 0

  LEDs_stat = _BV( i );
  writeLEDs( LEDs_stat );
  if( i >= 7 ) up = DOWN;
  else if( i <= 0 ) up = UP;
  if( up == UP ) i++;
  else i--;
}

void setup() {
  // put your setup code here, to run once:
  // Acá iniciamos el timer que cuenta los ticks
  initBoardCompuII();
  writeLEDs( LEDs_stat );
  use_timer( TICK_PERIOD_MS );
}

void loop() {
  // put your main code here, to run repeatedly:
  // No tiene mucho caso por que el dispatcher es un loop infinito, pero bue...
  // para que quede según el estandard Arduino
  TASK tareas[] = {
      createTask( barreLED, 150 ),
      endTaskList()
      };

  runDispatcher( tareas );
}
```

Mejor que lo hagan y prueben ustedes. Luego, si tengo ganas, le pongo un warning al compilador para que les avise de esta situación.

*WARNING_2!!!*
También les subo una nueva versión del dispatcher.cpp que asegura la lectura atómica de la variable tickCounter (en la función *get_ticks() *)que es actualizada por las interrupciones del timer elegido. ES IMPORTANTE QUE SIEMPRE USEN ESTA NUEVA VERSIOOOOONNNNN!!!!!


----------



## Saint_ (Jun 5, 2018)

*Dr. Zoidberg,* un muy buen trabajo el que hiciste con el dispatcher.ccp que implementaste y al más puro estilo de avr-gcc.

También hice ligeros cambios al despachador de tareas en ccs PicC para simplificar un poquito más la creación de tareas.
el siguiente ejemplo es el mismo que subiste (el de la imitación del auto fantástico). Los leds están conectados a puerto B del pic16f628a

*main.c*

```
/*****************************************************************************/
#include <main.h>
/*****************************************************************************/
void barrerLed()
{
   const unsigned int8 secuencia[]={1,2,4,8,16,32,64,128,64,32,16,8,4,2};
   static unsigned int8 n;
   output_b(secuencia[n]);
   if(++n>13){n=0;}
}
/*****************************************************************************/
void main()
{
   TAREA tareas[]={
                  crearTarea(barrerLed,TICKS_PER_SECOND/10)//crea la tarea1,aprox 100ms
                  };
   despachadorDeTareas(tareas);
}
/*****************************************************************************/
```
*main.h*

```
/*****************************************************************************/
#include <16F628.h>
/*****************************************************************************/
#FUSES PUT                      //Power Up Timer
#FUSES NOMCLR                   //Master Clear pin used for I/O
#FUSES BROWNOUT                 //Reset when brownout detected
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD                    //No EE protection
#FUSES NOPROTECT                //Code not protected from reading
/*****************************************************************************/
#use delay(internal=4MHz)
/*****************************************************************************/
//!#define temporizador 0
//!#define  tick_ms 10ms
//!#define  nBits 8
#include "tareas.c"
/*****************************************************************************/
```
*tareas.c*

```
/******************************************************************************
Parámetros configurables:
*******************************************************************************
#define temporizador, este puede tomar 3 valores 0,1,2.

Ejemplo:
#define temporizador   0   //usa el temporizador 0 para el contero de los ticks
#define temporizador   1   //usa el temporizador 1 para el contero de los ticks
#define temporizador   2   //usa el temporizador 2 para el contero de los ticks

Si no es declarado, usa el temporizador 1.
*******************************************************************************
#define tick_ms, define el tiempo transcurrido en cada tick

Ejemplo:
#define   tick_ms      1   //1 milisegundo por cada tick
#define   tick_ms      15   //15 milisegundos por cada tick

Si no se declara, usa 1 milisegundo como tiempo para cada tick
*******************************************************************************
#define nBits, define la cantidad de bits de retorno para la función get_ticks();
sus valores pueden ser 8,16,32.

Ejemplo:
#define nBits   8   //usa 8 bits (0-255) para la función get_ticks();
#define nBits   16   //usa 8 bits (0-65535) para la función get_ticks();
#define nBits   32   //usa 8 bits (0-4294967295) para la función get_ticks();

Si no se define usa 16 bits para la función get_ticks();
/*****************************************************************************/
#ifndef  temporizador
   #define  temporizador 1
#endif
/*****************************************************************************/
#ifndef  tick_ms
   #define  tick_ms 1ms
#endif
/*****************************************************************************/
#ifndef  nBits
   #define  nBits 16
   #define  tipoVariable unsigned int16
#else
   #if nBits==8
      #define  tipoVariable unsigned int8
   #elif nBits==16
      #define  tipoVariable unsigned int16
   #elif nBits==32
      #define  tipoVariable unsigned int32
   #else
      #error  nBits, solo puede ser 8,16 o 32
   #endif
#endif
/*****************************************************************************/
#use timer(timer=temporizador,tick=tick_ms,bits=nBits,noisr)
/*****************************************************************************/
typedef void(*punteroFuncion)();
typedef struct {
                  punteroFuncion pf;
                  tipoVariable periodo;
                  tipoVariable tickCont;
               }TAREA;
/*****************************************************************************/
#define crearTarea(pf,periodo) {pf,periodo,0}
#define despachadorDeTareas(tareas) despachador(tareas,sizeof(tareas)/(2+2*nBits/8))
/*****************************************************************************/
void despachador(TAREA tareas[],tCant)
{
   tipoVariable tick;
   unsigned int8 i;
   while(true)
   {
      tick=get_ticks();
      for(i=0;i<tCant;i++)
      {
         if((tick-tareas[i].tickCont)>tareas[i].periodo)
         {
            tareas[i].tickCont=tick;
            tareas[i].pf();
         }
      }
   }
}
/*****************************************************************************/
```


----------



## Dr. Zoidberg (Jun 5, 2018)

Me gusta tu propuesta 
Solo que dejaría a get_ticks que siempre retorne un valor del mismo sizeof. Total, es una variable que se actualiza en una ISR , así que puede ser de 16 bits sin problemas.... es una idea.



Saint_ dijo:


> *Dr. Zoidberg,* un muy buen trabajo el que hiciste con el dispatcher.ccp que implementaste y al más puro estilo de avr-gcc.


Naaaa......es puro C... solo usé recursos del avr-gcc para la ISR y para la lectura atómica de tickCounter.
PD: No quise usar un arreglo de constantes para no chupar tanta memoria de datos, pero va muy bien si la tenés disponible 

Ya que se establicen ambos despachadores, veremos si podemos integrarlos en un solo paquete para cualquier micro PIC o ATMEL.
Por ahora, estoy pensando como agregar una función *delay()* que use el sistema de despacho y no los busy-wait del avr-gcc o del ccs.


----------



## Saint_ (Jun 9, 2018)

Dr. Zoidberg dijo:


> Solo que dejaría a get_ticks que siempre retorne un valor del mismo sizeof. Total, es una variable que se actualiza en una ISR , así que puede ser de 16 bits sin problemas.... es una idea.


Si creo que no habría ningún problema.


Dr. Zoidberg dijo:


> Ya que se establicen ambos despachadores, veremos si podemos integrarlos en un solo paquete para cualquier micro PIC o ATMEL.


Sería muy interesante, pero habría que definir algunos detalles, por ejemplo:

Para que familias de PIC y AVR, para que compiladores (CCS, MikroC, AVRGCC, arduino).

Otro detalle es si se pretende hacer una alternativa simple a los RTOS existentes (RTOS CCS, OSA para MikroC y FreeRtos para Avr) o tratar de competir.

Desde mi punto de vista seria generar la alternativa simple.

En cuanto al despachador de tareas, en CCS en imprescindible usar la constante TICS_PER_SECOND ya que la directiva #use timer(…) no logra hacer un cálculo muy preciso del tiempo, por ejemplo si se elige un tiempo de 1ms y dependiendo de la velocidad del oscilador pude que ese tiempo sea al final 1.2ms. Para corregir ese erros es que se genera la constante TICS_PER_SECOND que guarda el valor real de tics que suceden en un segundo.

Aunque para solucionar esto y tratar de uniformar el método de creación de tareas y su respectivo  tiempo de despacho podría elegirse un tiempo fijo por ejemplo 1ms y las tareas seria despachadas en un múltiplo de este tiempo.


Dr. Zoidberg dijo:


> Por ahora, estoy pensando como agregar una función *delay()* que use el sistema de despacho y no los busy-wait del avr-gcc o del ccs.


Seria genial, obviamente lo complicado es retornar a la instrucción siguiente de donde se llamó a la función.


----------



## Dr. Zoidberg (Jun 9, 2018)

Saint_ dijo:


> Desde mi punto de vista seria generar la alternativa simple.


Totalmente de acuerdo. Los RTOS existentes, en especial los preemptivos, requieren conocer algunas técnicas para semaforización y para evitar dead-locks que si bien no son complicadas, por lo general están totalmente fuera del alcance del común de los programadores (léase: los que hacen copy & paste sin entender nada de lo que sucede).
Creo que acá la idea es un conjunto de primitivas para terminar ya con los famosos *delay()* y el consumo inútil de tiempo de CPU en las tareas que requieren una ejecución periódica y "aparentemente" concurrente.


Saint_ dijo:


> En cuanto al despachador de tareas, en CCS en imprescindible usar la constante TICS_PER_SECOND ya que la directiva #use timer(…) no logra hacer un cálculo muy preciso del tiempo, por ejemplo si se elige un tiempo de 1ms y dependiendo de la velocidad del oscilador pude que ese tiempo sea al final 1.2ms. Para corregir ese erros es que se genera la constante TICS_PER_SECOND que guarda el valor real de tics que suceden en un segundo.


Ese es un problema que ocurre en cualquier microprocesador/microcontrolador. Si la frecuencia de reloj no es un múltiplo exacto del tick del timer, la constante te va a dar en punto flotante... pero no la podés usar así por que el timer y el kernel cuentan en enteros, y tarde o temprano vas a perder presición en el intervalo del tick: la única diferencia es el error final que vas a tener.
De todas maneras yo no me preocuparía mucho por esto por que el despacho es en una suerte de very-soft-real-time y sobre todo depende de la carga de tiempo que usa cada tarea en la secuencia que se despacha, así que muuuucha presición no vas a tener.


Saint_ dijo:


> Seria genial, obviamente lo complicado es retornar a la instrucción siguiente de donde se llamó a la función.


Ya tengo algo hecho, pero estoy teniendo problemas por intentar minimizar el consumo de memoria...


----------

