Crear tasks en μiTron

Es necesario remetirse a tres archivos del proyecto: 

kernel_cfg.c

main.c

kernel_id.h


  1. Definir las funciones de las tareas (main.c)

* Primero debemos determinar el objetivo de la tarea (lo que queremos que la tarea haga).

** Las tareas deben ejecutarse en un bucle infinito, y se debe definir un lapso de tiempo para que el sistema operativo maneje su “despacho”. 

*** El parámetro “int exinf” es por defecto, debe ser declarado así.


void test1_task(int exinf)

{

  while(1){ 

    printf("Hello task 1\n");

    tslp_tsk(1000); 

  }

}


void test2_task(int exinf)

{

  while(1){ 

    printf("Hello task 2\n");

    tslp_tsk(2000); 

  }

}


void test3_task(int exinf)

{

  while(1){ 

    printf("Hello task 3\n");

    tslp_tsk(3000); 

  }

}


  1. Añadir los prototipos de las tareas (al inicio de kernel_cfg.c)


extern void test1_task(int exinf);

extern void test2_task(int exinf);

extern void test3_task(int exinf);


  1. Configurar las tareas en kernel_cfg.c


TSK_TBL es una tabla donde se declaran las tareas. Las tareas que tienen el atributo TA_ACT son puestas en estado “run” por el sistema operativo al iniciarse. Si la tarea no incluye este atributo deberá ser iniciada manualmente.

            {ID_TASK_TEST1, {TA_HLNG ,          0,      (FP)test1_task, 5,          0x400,  NULL}},

            {ID_TASK_TEST2, {TA_HLNG ,          0,      (FP)test2_task, 6,          0x400,  NULL}},

            {ID_TASK_TEST3, {TA_HLNG ,          0,      (FP)test3_task, 7,          0x400,  NULL}},

 

No hay una determinada regla para poder definir el valor de “stksz” (el tamaño de la pila), debe el desarrollador ir probando varios valores y verificar el comportamiento del programa. Un ajuste incorrecto de la cantidad de memoria puede dar lugar a “stack overflow” causando comportamientos inesperados, errores, etc.


  1. Añadir los IDs de las tareas en kernel_id.h

* Se puede asignar IDs libremente bajo cualquier criterio que se considere razonable desde los números disponibles aun no utilizados. En μiTron no hay que pasarse del 63.


#define ID_TASK_INIT 1

#define ID_TASK_MAIN 2

#define ID_TASK_IDLE 63

#define ID_TASK_TEST1 10

#define ID_TASK_TEST2 11

#define ID_TASK_TEST3 12


  1. Iniciar las tareas manualmente en main_task (dentro de main.c)


sta_tsk(ID_TASK_TEST1, 0); 

sta_tsk(ID_TASK_TEST2, 0);

sta_tsk(ID_TASK_TEST3, 0);


El primer argumento que recibe sta_tsk() es el id de la tarea, el segundo argumento es un valor de inicialización (con 0 está bien).


Nota a considerar: El sample program que utilicé inicialmente ya tenía una tarea (task) creada. En este caso, se puede eliminar si no es necesaria o, si decidimos conservarla, es importante asegurarnos de que tenga definido un tiempo de suspensión (por ejemplo, tslp_tsk(1000);). Sin esta instrucción, la tarea no permitirá el dispatch (cambio entre tareas en el CPU), lo que afectará la multitarea del sistema. 


Debbuging: 

Para hacer debbuging utilizando IAR, es necesario contar con un ICE conectado a una placa que corra nuestro programa. 

El hecho de hacer Debug en dicha placa no se relaciona con el programa que ya esté flasheado actualmente en la tarjeta, ni lo afecta de ningún modo.


La función sta_tsk (que usamos anteriormente para poner nuestras tasks en modo run) tiene valores de retorno que son códigos de error (0 indica funcionamiento normal). Una técnica valiosa para verificar el buen funcionamiento de la tarea es:

  1. Activar el panel que permite verificar el contenido de las variables locales (View >> Locals)

  2. Crear dentro de main_task() una variable que almacene este código de error devuelto por la función ejecutora del task. 


ER      rtn;

  1. asignar las funciones sta_tsk a esta variable

     

rtn = sta_tsk(ID_TASK_TEST1, 0); 

rtn = sta_tsk(ID_TASK_TEST2, 0);

rtn = sta_tsk(ID_TASK_TEST3, 0);

 

de esta forma (y sin afectar el funcionamiento de las mismas) se podrá confirmar si se reporta algún error o no. 

 

Declaración y uso de semáforos.

1. Definir el semáforo en kernel_cfg.c

const SEM_TBL static_semaphore_table[] = {

    {ID_SEM_TEST, {TA_TFIFO, 0, 1}}, // Semáforo inicializado en 0

    {SEMAPHORE_TBL_END, {0, 0, 0}}

};


2. Declarar el ID del semáforo en kernel_id.h

#define ID_SEM_TEST 1


3. Modificar test1_task para señalar al semáforo

void test1_task(int exinf) {

    tslp_tsk(500); // Retraso inicial para sincronización

    while (1) {

        printf("Hello task 1\n");

        tslp_tsk(1000); // Simula trabajo

        sig_sem(ID_SEM_TEST); // Señalar semáforo

    }

}


4. Modificar test2_task para esperar el semáforo

void test2_task(int exinf) {

    while (1) {

        wai_sem(ID_SEM_TEST); // Esperar señal de test1_task

        printf("Hello task 2\n");

        tslp_tsk(2000); 

    }

}


*) Aclaración

La línea tslp_tsk(2000); en test2_task no es estrictamente necesaria después de implementar el semáforo, porque el semáforo ya introduce el mecanismo de sincronización que bloquea test2_task hasta que reciba una señal de test1_task. 

 



Antes de implementar el semáforo           Después de implementar el semáforo



Mailbox


¿Qué es un Mailbox en μITRON?

Un mailbox es una estructura utilizada para comunicar tareas enviando mensajes entre ellas. Funciona como un buzón:

  1. Una tarea (emisor) coloca un mensaje en el mailbox.

  2. Otra tarea (receptor) recoge ese mensaje.

Es útil para sincronizar tareas y pasar datos entre ellas.


Cómo Funciona el Mailbox

  1. Configuración:

Se declara un mailbox con un identificador único (en este caso, ID_TEST_MBX1).

El mailbox funciona como un contenedor para almacenar mensajes enviados.

  1. Envío (snd_mbx):

Una tarea (por ejemplo, test1_task) usa snd_mbx para colocar un mensaje en el mailbox.

Si el mailbox está lleno, el envío puede fallar o bloquear la tarea hasta que haya espacio disponible.

  1. Recepción (rcv_mbx):

Otra tarea (por ejemplo, test2_task) usa rcv_mbx para recoger mensajes.

Si el mailbox está vacío, la tarea que llama rcv_mbx se bloquea hasta que se reciba un mensaje.




Fuente: Renesas (Manual de R-IN32M3)

Ejemplo de implementación


  1. Definir el mailbox en la tabla del kernel (kernel_cfg.c en este caso).


const MBX_TBL static_mailbox_table[] = {

    {ID_TEST_MBX1, {TA_TFIFO}}, // Mailbox with FIFO policy

    {MAILBOX_TBL_END, {0}}

};


kernel_id.h


#define ID_TEST_MBX1 1


  1. Implementación de las Tareas

Definir una variable que pueda ser vista tanto por Task1 y Task2 para contener el mensaje que será enviado y recibido.


typedef struct {

    T_MSG msgque;         // puntero al inicio del mensaje

    char text[32];            // Text message

} CUSTOM_MSG;

 

!) Las funciones de mailBox como snd_mbx() y rcv_mbx() requieren para funcionar recibir como uno de sus parámetros un dato tipo T_MSG.
Este struct “T_MSG” contiene un vector (“array”) con el texto. Y, un puntero a la dirección de memoria donde comienza el mensaje. 

El contenido del puntero (msgque) es administrado por μiTRON al usar una función, así que no necesitamos preocuparnos de asignarle nada. El requisito es que la variable contenga esta estructura con ambos atributos. 


Fuente: Renesas


test1_task: Enviar mensajes

 

 

Esta tarea envía mensajes al mailbox usando snd_mbx. El mensaje es una estructura T_MSG.


void test1_task(int exinf)

{

  CUSTOM_MSG msg;                              // Struct for the message

  while(1){ 

    printf("Task 1: Sending message...\n");


    strcpy(msg.text, "Hello from Task 1!");              // Se guarda el texto en el array


    // Enviar el mensaje al mailbox

    snd_mbx(ID_TEST_MBX1, (T_MSG *)&msg);

    

    tslp_tsk(1000);

    //sig_sem(ID_TEST_SEM1);           // señala el semáforo. Ya no es necesario, se maneja con el mailbox. 

  }

}

*) El uso del semáforo no es necesario porque el mailbox ya maneja la sincronización entre las tareas.

Expliación de snd_mbx(ID_TEST_MBX1, (T_MSG *)&msg);

Se usa porque snd_mbx espera un puntero a T_MSG.


snd_mbx espera dos parámetros.
Uno el ID del mailbox que estamos utilizando, y otro un tipo T_MSG que es un puntero al inicio del mensaje que se va a enviar. 


¿Qué hace (T_MSG *)&msg? Conversión de tipo (type casting): Convierte el tipo de msg a un puntero de tipo T_MSG.

Por ejemplo supongamos:


typedef struct {

    T_MSG msgque;

    char text[32];

} CUSTOM_MSG;


CUSTOM_MSG msg;



msg es de tipo CUSTOM_MSG.

&msg obtiene la dirección de msg.

(T_MSG *)&msg le indica al compilador que trate msg como si fuera un puntero a T_MSG.


test2_task: Recibir mensajes

Esta tarea espera un mensaje del mailbox con rcv_mbx y lo procesa.



void test2_task(int exinf)

{

  CUSTOM_MSG *msg; // Pointer to the received message

  while(1){ 


//Espera una señal de semáforo que habilite su ejecución

    //wai_sem(ID_TEST_SEM1);

    

    // Recibir mensaje del mailbox

    rcv_mbx(ID_TEST_MBX1, (T_MSG **)&msg);

        

    // Imprimir el contenido del mensaje

    printf("Task 2: Received message: %s\n", msg->text);

//    tslp_tsk(2000);  //Al tener semáforo task1 le da paso a ejecutarse entonces ya no es necesario

  }

}

 

*) El mailbox no solo almacena los mensajes, sino que también gestiona el bloqueo y desbloqueo de las tareas, garantizando que test2_task no intente procesar algo hasta que haya un mensaje. Por eso, agregar un semáforo sería redundante y no aporta valor adicional en este caso. Puedes eliminar la señalización del semáforo y confiar únicamente en el mecanismo del mailbox para la sincronización.


Expliación de rcv_mbx(ID_TEST_MBX1, (T_MSG **)&msg);

Se usa porque snd_mbx espera un DOBLE puntero a T_MSG para almacenar la dirección del mensaje recibido.


rcv_mbx espera dos parámetros.

Uno el ID del mailbox que estamos utilizando, y otro un puntero doble (T_MSG **) que almacena la dirección del mensaje recibido desde el buzón. 


¿Qué hace (T_MSG **)&msg? Conversión de tipo doble puntero: Convierte el tipo de &msg a un puntero doble de tipo T_MSG **.

Por ejemplo supongamos:


CUSTOM_MSG *msg;


msg es un puntero de tipo CUSTOM_MSG *.

&msg obtiene la dirección de msg (lo convierte en un puntero doble: CUSTOM_MSG **).

(T_MSG **)&msg le dice al compilador: "Trata la dirección de msg como si fuera un puntero doble de tipo T_MSG **".


¿Por qué se usa?

rcv_mbx espera un doble puntero (T_MSG **) para almacenar la dirección del mensaje recibido.

Al convertir &msg a (T_MSG **)&msg, garantizamos que el compilador lo acepte y μITRON pueda almacenar el puntero recibido en msg.


Eventflag

¿Qué son las Event Flags?

Un flag (bandera) es como un interruptor que puede estar activado (1) o desactivado (0). En los event flags, cada bit en un número representa un flag diferente.

En μITRON, los event flags usan un grupo de 16 bits. Puedes tener hasta 16 flags diferentes en un solo grupo. Debe entenderse por grupo de event flags, cada entrada en la tabla de configuración de flags, como esta: 

{ID_TEST_FLG1, {TA_TFIFO | TA_CLR, 0}},

Un grupo de flags es una estructura que administra un conjunto de hasta 16 banderas binarias (bits). Todas las tareas que interactúan con este grupo comparten el mismo espacio de flags.

En este ejemplo:

ID_TEST_FLG1 es el identificador del grupo.

TA_TFIFO | TA_CLR son los atributos del grupo:

TA_TFIFO: Procesa las tareas en orden FIFO.

TA_CLR: Limpia automáticamente los flags después de ser usados.

0: Inicializa todas las banderas en 0 (todas desactivadas).


Cada flag es un bit en el rango 0x01 (bit 0) a 0x8000 (bit 15).

Si necesitas más de 16 flags, puedes configurar otro grupo adicional.


Por ejemplo, imagina un número con 16 bits (16 interruptores):

0000 0000 0000 0000

Cada bit (de izquierda a derecha) representa un flag:

Bit 0 → FLAG_1

Bit 1 → FLAG_2

Bit 2 → FLAG_3

... hasta Bit 15 → FLAG_16

Si activas un flag, el bit correspondiente cambia a 1. Por ejemplo:

0000 0000 0000 0001   → FLAG_1 activado

0000 0000 0000 0010   → FLAG_2 activado

0000 0000 0000 0011   → FLAG_1 y FLAG_2 activados


¿Por qué usamos 0x para representar los flags?

Los números en los programas suelen estar en formato hexadecimal (base 16).

0x es una convención en programación para decir: "Este número está en formato hexadecimal".

Hexadecimal es útil porque cada dígito representa 4 bits, lo que hace que sea más compacto que usar binario.

Binario: 00000001 → Hexadecimal: 0x01

Binario: 00000010 → Hexadecimal: 0x02

Binario: 00000100 → Hexadecimal: 0x04


¿Por qué los flags son 0x01, 0x02, 0x04, etc.?

Cada flag es un único bit activado, y en hexadecimal se representa con potencias de 2:

Bit 0 activado → 0x01 (00000001 en binario).

Bit 1 activado → 0x02 (00000010 en binario).

Bit 2 activado → 0x04 (00000100 en binario).

Bit 3 activado → 0x08 (00001000 en binario).

Si activas varios flags al mismo tiempo, se suman en binario:

FLAG_1 (0x01) y FLAG_2 (0x02) → 0x03 (00000011 en binario).

FLAG_1 (0x01) y FLAG_3 (0x04) → 0x05 (00000101 en binario)


Resumen:

  1. Cada bit representa un flag.

Bit 0 → FLAG_1 → 0x01

Bit 1 → FLAG_2 → 0x02

Bit 2 → FLAG_3 → 0x04

Y así sucesivamente.

  1. 0x significa que estás usando formato hexadecimal.

Hexadecimal es más compacto y fácil de leer que binario.

  1. Puedes combinar flags sumando sus valores.

0x01 | 0x02 → Activa FLAG_1 y FLAG_2 al mismo tiempo (0x03 en total).

  1. En un grupo de event flags, puedes usar hasta 16 bits (0x01 a 0x8000).


Ejemplo de implementación


  1. Definir el flag en la tabla del kernel (kernel_cfg.c en este caso).


const FLG_TBL static_event_flag_table[] = {

    { ID_TEST_FLG1, {TA_TFIFO | TA_CLR, 0}}, // Event Flag Group

    {EVENTFLAG_TBL_END, {0, 0}}

};


kernel_id.h


#define ID_TEST_FLG1     3


  1. Implementación de las Tareas

test1_task: Establece las banderas.

Después de enviar un mensaje al mailbox, establece dos banderas:

0x01 para notificar a test2_task.

0x02 para notificar a test3_task


void test1_task(int exinf) {

    CUSTOM_MSG msg;

    while (1) {

        printf("Task 1: Sending message...\n");


        strcpy(msg.text, "Hello from Task 1!");

        snd_mbx(ID_TEST_MBX1, (T_MSG *)&msg);


        // Establecer flags para notificar a las tareas

        set_flg(ID_TEST_FLG1, 0x01); // Flag para test2_task

        set_flg(ID_TEST_FLG1, 0x02); // Flag para test3_task


        tslp_tsk(1000); // Simula trabajo durante 1 segundo

    }

}


set_flg() tiene dos parámetros.

  1. El ID del flag (un entero entre 1 y 64).

  2. El patrón en bits esperado (debe estar dentro de los 16 bits o devolverá un error). 

     

test2_task: Espera el flag 0x01 antes de recibir el mensaje del mailbox.


void test2_task(int exinf) {

    CUSTOM_MSG *msg;

    while (1) {

        wai_flg(ID_TEST_FLG1, 0x01, TWF_ANDW, NULL); // Espera el flag 0x01

        rcv_mbx(ID_TEST_MBX1, (T_MSG **)&msg);

        printf("Task 2: Received message: %s\n", msg->text);

    }

}



wei_flg() tiene cuatro parámetros.

  1. El ID del flag (un entero entre 1 y 64).

  2. El patrón en bits esperado (debe estar dentro de los 16 bits o devolverá un error). 

  3. El modo de espera que define cómo se deben interpretar las banderas que la tarea está esperando. 

Los valores posibles son:

TWF_ANDW: Espera que todas las banderas especificadas en waiptn estén activas simultáneamente.

Por ejemplo, si waiptn es 0x03 (bits 0 y 1), la tarea se desbloqueará solo cuando FLAG_1 (0x01) y FLAG_2 (0x02) estén activas.

TWF_ORW: Espera que al menos una de las banderas especificadas en waiptn esté activa.

Por ejemplo, si waiptn es 0x03, la tarea se desbloqueará si FLAG_1 o FLAG_2 están activas (no es necesario que ambas lo estén).


*) En este ejemplo usamos TWF_ANDW para garantizar que solo se desbloquee cuando la bandera 0x01 esté activa.

  1. Un puntero donde se puede almacenar el patrón actual de banderas activas cuando la tarea se desbloquea.

Si pasas un puntero (&variable):

La función guardará en esa variable el patrón de banderas activas en el momento en que se satisfaga la condición de espera.

Esto es útil si quieres saber qué banderas estaban activas al desbloquearse.

Si pasas NULL:

Indicas que no te interesa saber cuáles banderas estaban activas.

Es útil cuando solo necesitas que la tarea reaccione al evento y no necesitas información adicional.

En este caso, usamos NULL porque no necesitamos verificar el patrón de banderas al desbloquearse; simplemente actúa cuando se activa la bandera esperada.


test3_task: Espera el flag 0x02 antes de imprimir un mensaje diferente.


void test3_task(int exinf) {

    while (1) {

        wai_flg(ID_TEST_FLG1, 0x02, TWF_ANDW, NULL); // Espera el flag 0x02

        printf("Task 3: Flag received, doing its own task...\n");

        tslp_tsk(3000); 

    }

}


Resultados

Cuando ejecutas este programa:

test1_task envía un mensaje y establece dos flags.

test2_task reacciona al flag 0x01, recoge el mensaje y lo imprime.

test3_task reacciona al flag 0x02 y realiza su propia acción.

 

Interrupciones

 

Una interrupción es un evento que detiene temporalmente la ejecución normal de un programa para atender una tarea más urgente. Es como una llamada de emergencia: cuando ocurre, el sistema pausa lo que está haciendo, ejecuta un "manejador de interrupción" (ISR, Interrupt Service Routine) y luego regresa al flujo principal.

En sistemas embebidos, las interrupciones funcionan así: el hardware genera una señal de interrupción, por ejemplo, cuando se presiona un botón, expira un temporizador o llega un dato por UART. En ese momento, el procesador pausa la tarea actual y ejecuta el código del ISR, que se encarga de manejar la interrupción (como leer el dato recibido o reiniciar el temporizador). Una vez que el ISR termina, el procesador regresa al programa principal y continúa donde lo dejó.

En sistemas operativos como μITRON, las interrupciones son eventos críticos que interactúan directamente con el kernel para coordinar tareas. El kernel decide qué hacer después de una interrupción, como priorizar la ejecución de una tarea de mayor prioridad o desbloquear una tarea que estaba esperando un evento, como un flag o un mensaje en el mailbox.

El uso de interrupciones ofrece varias ventajas. Primero, su baja latencia permite responder rápidamente a eventos críticos. Segundo, optimizan el uso del CPU al eliminar la necesidad de que el procesador espere activamente un evento; solo responde cuando ocurre. Finalmente, ofrecen una sincronización natural al coordinarse fácilmente con tareas a través de mecanismos como event flags, mailboxes o semáforos.

Para usarlas eficientemente en μITRON, es fundamental que los ISR sean cortos y rápidos, dejando el procesamiento completo a las tareas que se desbloqueen. El kernel manejará automáticamente las prioridades, ejecutando la tarea más importante después de una interrupción. Además, se pueden usar semáforos, mailboxes o event flags para pasar información entre el ISR y las tareas de manera segura y ordenada.


Ejemplo de implementación


  1. Declarar el ISR. 

Primero, necesitamos un ISR (Interrupt Service Routine) que se ejecute cuando ocurra la interrupción. Este ISR activará el flag.

En el archivo kernel_cfg.c declara en “extern” el prototipo de la nueva función que agregaremos en main.c.

  • Es void y no exinf porque en el caso de un Interrupt Service Routine (ISR) en μITRON, no lleva el parámetro int exinf como las tareas. Esto se debe a que un ISR no se ejecuta como una tarea regular, sino que es llamado directamente por el hardware o el kernel del sistema operativo cuando ocurre la interrupción.


extern void test_timer_isr(void);


En el archivo main.c, define la función:


void test_timer_isr(void) {

    printf("Test Timer ISR: Interrupt triggered (every 5 seconds)!\n");

// Activa FLAG_1 en el nuevo grupo de flags (ID_TEST_FLG2)

    set_flg(ID_TEST_FLG2, 0x01);

}


Qué hace:

Imprime un mensaje indicando que la interrupción ocurrió.

Usa set_flg para activar el FLAG_1 (0x01) en el grupo de flags ID_TEST_FLG2.


  1. Registrar el ISR en la tabla de interrupciones

En el archivo kernel_cfg.c, registra la interrupción en la tabla INT_TBL. Esto vincula el temporizador con el ISR (Interrupt Service Routine).


const INT_TBL static_interrupt_table[] = {

    {TEST_TIMER_IRQn, {TA_HLNG, (FP)test_timer_isr}}, // ISR vinculado al temporizador

    {INT_TBL_END, {0, (FP)NULL}}                     // Fin de la tabla

};


En el archivo kernel_id.h, define el identificador TEST_TIMER_IRQn para el temporizador:


// --- Interrupt ID ---

#define TEST_TIMER_IRQn  5


TEST_TIMER_IRQn es el número de interrupción asignado a nuestro temporizador de prueba.


  1. Configurar el grupo de event flags

En el archivo kernel_cfg.c, asegura que el grupo de flags ID_TEST_FLG2 esté configurado. Si ya existe, no es necesario agregarlo.


const FLG_TBL static_event_flag_table[] = {

    {ID_TEST_FLG1, {TA_TFIFO | TA_CLR, 0}}, // Grupo existente para tareas normales

    {ID_TEST_FLG2, {TA_TFIFO | TA_CLR, 0}}, // Nuevo grupo exclusivo para interrupciones

    {EVENTFLAG_TBL_END, {0, 0}}

};


En kernel_id.h, define el identificador si aún no lo tienes:

#define ID_TEST_FLG2 4

  1. Configurar el temporizador

En la función init_task, inicializamos y configuramos el temporizador para que genere interrupciones cada 5 segundos. Agrega este código al final de init_task en main.c:


void init_task(int exinf) {

    hwos_init(); // Inicializa HW-RTOS


    LED_INIT();

    uart_init(SYS_UART_CH);

    clock_init();


    // Configurar el temporizador para generar interrupciones cada 5 segundos

    timer_init(); // Inicialización básica del temporizador

    timer_set_callback(test_timer_isr); // Vincula el ISR al temporizador

    timer_set_period(5000); // Período en milisegundos (5 segundos)

    timer_start(); // Inicia el temporizador


    ext_tsk();

}

  1. Crear la tarea que espera el flag

Agrega una nueva tarea en main.c que espera el flag 0x01 activado por el ISR:


void task_wait_interrupt(int exinf) {

    while (1) {

        // Espera FLAG_1 del nuevo grupo de flags (ID_TEST_FLG2)

        wai_flg(ID_TEST_FLG2, 0x01, TWF_ANDW, NULL);

        printf("Task: Interrupt event processed!\n");

    }

}

* No olvidar agregarla en la sección “extern” de kernel_cfg.cs declarando el prototipo.


  1. Registrar la tarea

En kernel_cfg.c, agrega la tarea en la tabla de tareas:


const TSK_TBL static_task_table[] = {

    {ID_TASK_WAIT_INTERRUPT, {TA_HLNG | TA_ACT, task_wait_interrupt, 10, 0x100, 0, NULL}},

    // Otras tareas...

};


En kernel_id.h, define el identificador de la nueva tarea:


#define ID_TASK_WAIT_INTERRUPT 5

Flujo del Programa

  1. Temporizador inicia → Cada 5 segundos genera una interrupción.

  2. Interrupción ocurre → El hardware notifica al kernel.

  3. Interrupt handler (test_timer_isr) ejecuta →

Imprime un mensaje.

Activa el flag 0x01 en el grupo ID_TEST_FLG2.

  1. Tarea (task_wait_interrupt) desbloquea →

Detecta el flag activado.

Procesa el evento y realiza la acción asociada.

  1. Reinicio del ciclo → El temporizador espera otros 5 segundos para repetir el proceso.

INHNO, Interrupt Handler Number

ISR, Interrupt Service Routine