[Tutorial] FreeRTOS

Breve introducción

¿Qué es un RTOS?

Es un SO de tiempo real, en el cual hay un gran control en el tiempo de ejecución de c/tarea que se está realizando.

La mayoría de los SO operativos permiten ejecutar tareas al mismo tiempo, esto se llama multi-tasking (multi-tarea). En realidad lo que sucede es que cada core del procesador puede ejecutar una tarea a la vez, sin embargo hay una parte del SO llamada Scheduler (organizador) que se encarga de decidir que tarea se ejecuta, cuanto tiempo dicha tarea permanece en estado de ejecución y cuando se pasa a ejecutar otra tarea, esto permite la ilusión de estar ejecutar varias tareas al mismo tiempo.

En un RTOS, el scheduler está diseñado de tal forma que permita ejecutar tareas en tiempos predecibles (normalmente descrito como determinístico). Esto resulta de gran utilidad en sistemas embebidos donde se busca que el sistema responda en un tiempo estricto.

Para conseguir ese control, el usuario evaluará y definirá que prioridades tendrán c/tarea a ejecutar, para luego ser ejecutadas por el scheduler.

¿Qué es FreeRTOS?

Un SO de tiempo real de código abierto, lo suficientemente chico como para funcionar en un microcontrolador (uC a partir de ahora). Esto significa que lo podemos usar en nuestros proyectos comerciales, simplemente cumpliendo con las exigencias que índica su licencia.

¿Qué ventajas tiene usar un RTOS?

Ejemplo muy completo que compara las distintas soluciones para una cierta aplicación:

http://www.freertos.org/tutorial/

Resumiendo, en una aplicación de este tipo:

syscontext.gif


Si empleamos una solución convencional (típico loop principal), nuestras ventajas/desventajas serán:

Loop.png

En cambio si empleamos un RTOS, nuestras ventajas/desventajas serán:

RTOS.png

¿Cuándo usar un RTOS?

Está pregunta está directamente relacionada con el tipo de aplicación que necesitamos realizar y la capacidad que tendrá nuestros uC.

- El tipo de aplicación ya se vió en el ejemplo anterior, distintos periféricos que trabajan a velocidades muy distintas, implican un mayor control en los tiempos de ejecución de c/periférico, haciendo complicado el código y el mantenimiento de soluciones del tipo LOOP.

- Por el lado de la capacidad de nuestros uC, a medida que crezca el poder de procesamiento y los recursos que este posea, se llega un punto en el que conviene pasarse a una solución del tipo RTOS, tal como se vé en este gráfico:

Procesamiento vs solucion.png

Video muy completo que explica cuando nos conviene usar un RTOS y como funciona el FreeRtos:


En el próximo mensaje empezaré con código aplicado en arquitectura de PC (para que les resulte sencillo traducirlo a sus propias arquitecturas de uC) y en ARM-Cortex M3 (familia LPC176x).

Yo conseguí el libro con un excelente tutorial del propio creador del FreeRTOS (Richard Barry) aplicado en arquitectura de PC, lo cual no me resultó para nada difícil llevarlo a ARM. El libro se llama "Using the FreeRTOS Real Time Kernel - A Practical Guide" y lo pueden conseguir en su tienda electrónica favorita (;)), sé que también es conseguible el tutorial para PIC de 32 bits.
 
Quedaré atento a tu tutorial. Yo por razones históricas me he quedado con la arquitectura antigua de los primeros Macintosh que es facil de implementar y su lógica es sencilla. En el tomo 1 del libro de "Inside Macintosh" se describe ese SO!
 
Me apunto, en mi caso tengo la LaunchPad Stellaris y pienso conseguir la Tiva C Series luego, espero que no me sea tan complicado adaptar tu aporte para el Cortex-M4F
 
Como mencioné voy a tratar de hacer el tutorial para la arquitectura de Pc y ARM Cortex, en la práctica solo implementé la última, por lo tanto en este primer ejemplo luego de explicar el código voy a dedicar un mensaje especial de como crear el proyecto en keil para la familia LPC176x (en LPCXpresso debería resultar más sencillo).

¿Qué es una tarea?

Es un segmento de código independiente, que si lo vemos en el contexto de assembler durante su ejecución tendrá su propio stack, flags de estados, registros de datos, etc; es decir si lo llevamos al concepto típico del “Loop principal”, sería como ver diversos loops que harán diversas acciones en forma independiente unos de otros.

Prototipo de una tarea:

void tarea_1( void *pvParameters)

No devuelve nada, por lo tanto es muy importante que nuestra tarea no tenga un fin, siempre deberá terminar en un loop infinito.

Puede recibir parámetros de entrada, útil durante su creación.

Ejemplo de una típica función de tarea:

PHP:
void tarea_1( void *pvParameters ) 
{ 
	….// Inicialización

	while(1) 
	{ 
		…. // Acción a tomar durante la ejecución de la tarea, ej. encender un LED	 
	} 
	
	…// Destrucción de la tarea, esto será optativo y dependerá de como se compile el kernel, pero en principio solo estaría en caso de que por alguna razón se produzca un “break” en el while.
}

Los estados más básicos en lo que podrá estar una tarea serán (luego veremos más):

- Siendo ejecutadas (corriendo)

- Sin ser ejecutadas (paradas)

estados_basicos.png

Solo una tarea podrá estar corriendo, el resto se mantendrán en estado espera y será el Scheduler el encargado de decidir que tarea se ejecuta.

Crear una tarea

Se usa la función xTaskCreate() cuyo prototipo es el siguiente:

prototipo_xTaskCreate.png

pvTaskCode: la dirección donde se encuentra nuestra tarea, si lo analizamos, a larga una tarea terminasiendo una simple función en C, que tendrá un loop infinito y que nunca retornará nada.

pcName: será un string con el nombre que le asignemos nosotros. Este string será útil cuando querramos debuggear y saber en que tarea estanis parados.

usStackDepth: definimos que tan grande será el stack dedicado para esa tarea. Habrá que tener mucho cuidado con el valor que le asignemos, ya que nos podemos quedar cortos cuando se ejecute la tarea o si es muy grande puede impedir la creación de la tarea por falta de memoria. Una unidad implican 4bytes (1 word).

pvParameters: será el parámetro que podremos pasarle a nuestra tarea ni bien sea creada.

uxPriority: define el nivel de prioridad que tendrá la tarea durante su ejecución. La mínima prioridad será la “0” y la máxima quedará definida por nosotros cuando complilemos el kernel (más adelante lo explicaré).

pxCreatedTask: sirve para pasarle la dirección de un handler de otra tarea ya creada, por ejemplo para poder eliminar dicha tarea en un futuro (lo veremos más adelante).

La función devolverá:

- pdTrue: si la tarea se creo correctamente.

- errCOULD_NOT_ALLOCATE_REQUIERED_MEMORY: como se darán cuenta por el nombré, indicará si hubo un error debido a la falta de memoria RAM.

Con esto estamos en condiciones de crear una tarea y empezar a probar.

Ejemplo 1:

Se crearán dos tareas con la misma prioridad y c/tarea lo único que hará es imprimir que está siendo ejecutada y entrará en un delay improvisado.

Prototipo de tarea 1:

PHP:
void vTask1( void *pvParameters )
{
  const char *pcTaskName = "Task 1 is running\r\n";
  volatile unsigned long ul;

  /* As per most tasks, this task is implemented in an infinite loop. */
  for( ;; )
  {
    /* Print out the name of this task. */
    vPrintString( pcTaskName );
    
    /* Delay for a period. */
   for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
     {
     /* This loop is just a very crude delay implementation. There is
     nothing to do in here. Later examples will replace this crude
     Loop with a proper delay/sleep function. */
     }
  }
}

Prototipo de tarea 2:

PHP:
void vTask2( void *pvParameters )
{
  const char *pcTaskName = "Task 2 is running\r\n";
  volatile unsigned long ul;

  /* As per most tasks, this task is implemented in an infinite loop. */
  for( ;; )
  {
    /* Print out the name of this task. */
    vPrintString( pcTaskName );
    
    /* Delay for a period. */
   for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
     {
     /* This loop is just a very crude delay implementation. There is
     nothing to do in here. Later examples will replace this crude
     Loop with a proper delay/sleep function. */
     }
  }
}

Main:

PHP:
int main( void )
{
  /* Create one of the two tasks. Note that a real application should check
  the return value of the xTaskCreate() call to ensure the task was created
  successfully. */

  xTaskCreate(
                      vTask1, /* Pointer to the function that implements the task. */
                     "Task 1",/* Text name for the task. This is to facilitate debugging only. */
                         1000, /* Stack depth - most small microcontrollers will use much less stack than this. */
                        NULL, /* We are not using the task parameter. */
                              1, /* This task will run at priority 1. */
                        NULL ); /* We are not going to use the task handle. */

  /* Create the other task in exactly the same way and at the same priority. */
  xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );

  /* Start the scheduler so the tasks start executing. */
  vTaskStartScheduler();

  /* If all is well then main() will never reach here as the scheduler will
  now be running the tasks. If main() does reach here then it is likely that
  there was insufficient heap memory available for the idle task to be created.
  CHAPTER 5 provides more information on memory management. */

  for( ;; ); //Puede estar o no!
}

La función vPrintString es propia de la arquitura de PC (así que olvidensé de la misma cuando estén con el uC).

Lo más destacable en main:
- Se crean 2 tareas.
- Ambas tareas tienen la misma prioridad “1”.
- Se reservar 4kBytes de stack para c/tarea (muchísima memoria en términos de uC).
- No se verifica la devolución de la función xTaskCreate , por tratarse de un ej. sencillo, pero es recomendable hacerlo.
- Se llama al Scheduler que se encargará de manejar los tiempos de ejecución, es muy importante hacerlo.
- Se coloca un loop infinito, pero idealmente si todo va bien nunca sale de la función Scheduler.

Ejecutando en DOS el ejemplo, se obtiene la siguiente salida:

salida_1.png

¿Qué es lo que pasa?

Como ambas tareas tienen la mismas prioridades, se reparten el core del uP/uC por igual, es decir el Scheduler utiliza una base de tiempo configurable por nosotros llamado “tick” que será el tiempo mínimo de ejecución, mientras mayor sea la prioridad de una tarea frente a la otra, mayor cantidad de “ticks” por segundo recibirá (mayor cantidad de tiempo en estado de ejecución), como en este ejemplo las tareas tienen la mismas prioridades, ambas tareas tendrán la misma cantidad de ticks.

Si lo analizamos gráficamente, el comportamiento será algo así:

estado de las tareas.png

Ejemplo 2:

Similar al ejemplo 1, en vez de crear dos funciones distintas para crear dos tareas similares, simplemente usando solo una función y pasandole los parámetros a c/tarea, podemos realizar lo mismo que en el ejemplo anterior.

Prototipo de tarea:

PHP:
void vTaskFunction( void *pvParameters )
{
  char *pcTaskName;
  volatile unsigned long ul;

  /* The string to print out is passed in via the parameter.
  Cast this to a character pointer. */
  pcTaskName = ( char * ) pvParameters;
 
  /* As per most tasks, this task is implemented in an infinite loop. */
  for( ;; )
    {
    /* Print out the name of this task. */
    vPrintString( pcTaskName );
   
    /* Delay for a period. */
    for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
      {
      /* This loop is just a very crude delay implementation. There is
      nothing to do in here. Later exercises will replace this crude
      loop with a proper delay/sleep function. */
      }
    }
}

Main:

PHP:
/* Define the strings that will be passed in as the task parameters. These are
defined const and not on the stack to ensure they remain valid when the tasks are
executing. */
static const char *pcTextForTask1 = “Task 1 is running\r\n”;
static const char *pcTextForTask2 = “Task 2 is running\t\n”;

int main( void )
{
  /* Create one of the two tasks. */
  xTaskCreate(
                     vTaskFunction, /* Pointer to the function that implements the task. */
                              "Task 1", /* Text name for the task. This is to facilitate debugging only. */
                                   1000, /* Stack depth - most small microcontrollers will use much less stack than this. */
         (void*)pcTextForTask1, /* Pass the text to be printed into the task using the task parameter. */
                                         1, /* This task will run at priority 1. */
                                 NULL ); /* We are not using the task handle. */

  /* Create the other task in exactly the same way. Note this time that multiple
  tasks are being created from the SAME task implementation (vTaskFunction). Only
  the value passed in the parameter is different. Two instances of the same
  task are being created. */
  xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );
 
  /* Start the scheduler so our tasks start executing. */
  vTaskStartScheduler();

  /* If all is well then main() will never reach here as the scheduler will
  now be running the tasks. If main() does reach here then it is likely that
  there was insufficient heap memory available for the idle task to be created.
  CHAPTER 5 provides more information on memory management. */

  for( ;; );
}

Lo más destacable en main:

- Se crean dos tareas distintas con las mismas prioridades, usando el mismo prototipo de tarea (pero es importante entender que hay 2 tareas ejecutandose).
- Durante la creación de ambas tareas, se pasa por argumentos un string que luego identificará a c/tarea.

Ejemplo 3:

En base al ejemplo 2, modificar las prioridades de las tareas, tal que la tarea 2 tenga una prioridad mayor.

Simplemente se modifica el main cambiando las prioridades en la creación de las tareas.

El resultado será:

Ver el archivo adjunto 97146

Y el gráfico temporal de las tareas tendrá esta forma:

temporal_3.png

Es evidente que si tenemos dos tareas, una tiene mayor prioridad y esta última nunca sale de estado de ejecución por si misma (ya veremos más adelante otros estados posibles), el Scheduler siempre entregará el procesamiento a la tarea de mayor prioridad, por lo tanto se quedará con todos los “ticks” por segundos disponibles y nunca la tarea de menor prioridad entrará en estado de ejecución.

Para el próximo mensaje repetiré los ejemplos 2 y 3 usando un LPC1768 mediante el compilador keil, entraré en más detalle de como se debe configurar el kernel para uC (ya sean ARM, PIC o AVR) y que kernel se debe bajar.
 

Adjuntos

  • Salida_3.png
    Salida_3.png
    117.1 KB · Visitas: 74
Primero debemos obtener el kernel apto para nuestra familia de uC, en este caso ARM Cortex M3 familia LPC176x y al mismo tiempo adaptarlo al entorno de desarrollo que pienso utilizar que es el keil.

Para lo cual deberé bajar source code de dos proyectos que se encuentran en esta página.

  • FreeRTOS-simple-demo-for-Cortex-M3-using-the-Keil-MDK-simulator
  • FreeRTOS-simple-demo-for-LPCXpresso-LPC1768

El primero me sirve para que el proyecto esté en el entorno de keil, en cambio el segundo me sirve para obtener el port del kernel de la familia LPC176x (vean que si usan el entorno LPCXpresso, solo necesitan el 2do demo).

Los pasos a seguir:

  1. Con los dos proyectos, descomprimimos el genérico de Cortex M3 para keil, hacemos un back-up de la carpeta “FreeRTOS-Source” y la eliminamos.
  2. Con el proyecto LPCXpresso, reeplazamos la carpeta “FreeRTOS-Source” del proyecto de Cortex M3 que habíamos borrado.
  3. Mediante el back-up que hicimos en el punto 1) reemplazamos la carpeta.
  4. Reemplazamos el archivo “FreeRTOSConfig.h” que subo en el mensaje.

El proyecto debería quedar así:

- Carpeta del proyecto

  • Carpeta con mí código (yo lo llamo “App_Source”)
  • Carpeta “FreeRTOS-Source”
  • Archivo “FreeRTOSConfig.h” y un par de archivos necesarios del Keil

- Dentro de la carpeta FreeRTOS-Source

  • Carpeta “include”
  • Carpeta “portable” (esta es la importante que definirá a c/familia de uC)
  • Archivos “*.C” que contienen las funciones necesarias para que funcione el kernel.

- Dentro de la carpeta portable

  • Carpeta “MemMang” (librerías encargadas del manejo de la memoria)
  • Carpeta “RVDS” (acá estará el port de nuestro uC - importante)

- Dentro de la carpeta RVDS

  • port.c (funciones específicas según el uC)
  • portmacro

Con eso ya estamos en condiciones de usar Keil para para agergar nuestro código al proyecto y compilarlo.

Ni bien se abra el keil, veremos nuestro proyecto de esta forma:

Proyecto_keil.PNG

Donde nosotros simplemente agregaremos el código de nuestra aplicación en la carpeta App_Source.

En nuestro código debemos llamar a las siguientes librerías:

PHP:
#include "FreeRTOS.h"
#include "task.h"

A medida que avancemos, tendremos que agregar más librerías.

De todas formas voy a subir el ejemplo con el proyecto, así no tienen que hacer nada raro y pueden empezar a probar sin inconvenientes, pero la idea es que si en el futuro aparece una versión nueva del kernel, puedan agregarlo sin inconvenientes.

Antes de seguir con los ejemplos, debemos entender que el kernel tiene muchas “variables” a configurar según el entorno donde se ejecutará, esa configuración la hacemos nosotros en forma manual desde el achivo “FreeRTOSConfig.h”, las cosas más destacadas que podemos configurar ahora son:

  • Stack mínimo (configMINIMAL_STACK_SIZE)
  • El heap total a utilizar (configTOTAL_HEAP_SIZE)
  • El valor máximo de prioridad (configMAX_PRIORITIES)
  • La cantidad de “Ticks” por segundos (configTICK_RATE_HZ)

Y depués aparecen cosas “raras” que todavía no vimos, pero con el tiempo vamos a ver.

Para los ejemplos mi configuración fue:

  • configMINIMAL_STACK_SIZE=100
  • configTOTAL_HEAP_SIZE= 2*1024
  • configMAX_PRIORITIES=5 (prioridades de 0 a 4)
  • configTICK_RATE_HZ=100 (10 mS => 1 tick; por default es 1 mS => 1 tick)

Solo para este ejemplo hice que el tick sea muy lento para poder ver la salida del puerto serie sin que se corte el frame, ya que al pasar de una tarea a la otra quedan los frames cortados.

Para el código utilicé varias librerías y variables globales que ya fueron explicadas en este mensaje:

https://www.forosdeelectronica.com/f24/aporte-tutorial-arm-cortex-m3-lpc1768-96797/

Funciones propias usadas:

  • configurar_pll: sirve para fijar la frecuencia de trabajo del uC
  • configurar_uart0: configura la velocidad y el modo de trabajo de la uart0
  • habilitar_interrupciones: habilita las interrupciones necesarias
  • enviar_string_uart0: envia un string a través de la uart0

Código compilado en keil para un LPC1768:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

void vTask1( void *pvParameters )
{	
	
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!
	}
}

void vTask2( void *pvParameters )
{			
	
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!			
	}
}

int main()
{					
	static const char *pcTextForTask1 = "T1\r\n";
	static const char *pcTextForTask2 = "T2\r\n";
	portBASE_TYPE ret;
	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_19200); //19200bps
	
	habilitar_interrupciones();
	
	enviar_string_uart0((u8 *)("Creando T1\r\n"));

	ret=xTaskCreate(vTask1,(const signed char *)("Task 1"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask1,1,NULL ); 
	
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T1\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T1\r\n"));
	
	enviar_string_uart0((u8 *)("Creando T2\r\n"));
	
	ret=xTaskCreate( vTask2, (const signed char *)("Task 2"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask2,1, NULL );
  
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T1\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T1\r\n"));
	
	enviar_string_uart0((u8 *)("Llamando al Scheduler\r\n"));
	vTaskStartScheduler();

	while(1);	
}

A diferencia de los ejercicios anteriores, no uso un delay improvisado y verifico que realmente las tareas fueron creadas.

La salida que se obtiene es la siguiente:

Salida.PNG

Como podrán ver, el código empleado para usar el kernel es exactemante el mismo que se vió en la arquitectura de PC y sucederá lo mismo con otras familias de uC.

Les dejo el proyecto para el ejemplo 2 (modifiquen las prioridades para hacer el ejemplo 3) y les recomiendo que usen el “debug” del keil o del entorno que usen para que vean como el Scheduler cambia de tarea en tarea.

Por último les dejo también el archivo FreeRTOSConfig.h que viene por default.
 

Adjuntos

  • ejemplo 2.zip
    682.1 KB · Visitas: 68
  • FreeRTOSConfig.h.zip
    2 KB · Visitas: 65
Hasta ahora vimos que las tareas pueden tener dos estados posibles, siendo ejecutadas (running) o no (not running). La idea de este mensaje será ampliar los posibles estados cuando una tarea no se encuentra siendo ejecutada. Entonces resumiendo, el estado “not running” puede sub-dividirse en varios estados:

- Tarea bloqueada

Estado en el cual la tarea está esperando que suceda un “evento” para que la misma pase a ser ejecutada. Los tipos de eventos pueden ser:

  • Temporal: típico delay, donde la tarea no tiene nada que hacer hasta que transcurra un cierto periodo de tiempo. Ej. un time-out para una Uart.
  • Sincronización: más adelante vamos a ver que existen varias formas de sincronizar las tareas mediante el uso de interrupciones, semáforos, colas, etc. Por el momento solo menciono esta posibilidad.

- Tarea suspendida

Es un estado donde prácticamente se saca la tarea de la cola de ejecución que tendrá el Scheduler, para lo cual se usan las funciones vTaskSuspend() para entrar en ese modo o vTaskResume() para salir del mismo. En la mayoría de las aplicaciones que vamos a encarar es poco común el uso de este estado.

- Tarea preparada

Se llega a este estado cuando la tarea está lista para ser ejecutada, pero previamente el Scheduler tiene prioridad sobre otra tarea, posiblemente por tener una mayor prioridad o porque era el turno de otra tarea con la misma prioridad. Siempre que se sale de un estado bloqueado/suspendido se pasa por este estado previamente a ser ejecutada la tarea.

Para englobar los posibles estados, veamos este diagrama:

estados_tareas.png

Bloquear temporalmente una tarea

Simplemente debemos utilizar la función vTaskDelay() que nos permitirá bloquear la tarea durante el tiempo deseado. Prototipo:

PHP:
void vTaskDelay( portTickType xTicksToDelay );

Como argumento requiere la cantidad de “Ticks” en los cuales la tarea permanecerá bloqueada. Resulta engorroso trabajar con cantidad de “Ticks” en vez de cantidad de mS (importante, la resolución mínima de tiempo estará dada por la definición de Tick que realizamos en la configuración, si Tick=1mS es evidente que no podremos realizar un delay menor a ese tiempo), entonces para evitar este enredo, podemos usar una relación de Tick vs tiempo de esta forma:

PHP:
portTickType delay_en_ticks=250 / portTICK_RATE_MS;

Independientemente del valor del Tick base, nosotros podremos obtener 250mS de delay, esto resulta muy útil si a futuro deseamos modificar el Tick base tal que el código no se vea afectado.

Ejemplo 4:

En base al ejemplo 3, modificar el prototipo de tarea, tal que incluya un delay de 250mS, sin modificar las prioridades de las tareas (es decir, tarea 2 seguirá teniendo una mayor prioridad frente a la tarea 1).

Prototipo de tarea:

PHP:
void vTaskFunction( void *pvParameters )
{
  char *pcTaskName;

  /* The string to print out is passed in via the parameter. Cast this to a character pointer. */
  pcTaskName = ( char * ) pvParameters;

  /* As per most tasks, this task is implemented in an infinite loop. */
  for( ;; )
    {
    /* Print out the name of this task. */
    vPrintString( pcTaskName );
    
    /* Delay for a period. This time a call to vTaskDelay() is used which places the task into the Blocked state         until the delay period has expired.The delay period is specified in 'ticks', but the                                                                             
    constant portTICK_RATE_MS can be used to convert this to a more user friendly value
    in milliseconds. In this case a period of 250 milliseconds is being
    specified. */

    vTaskDelay( 250 / portTICK_RATE_MS );
    }
}

El main se mantiene igual.

Ejecutando en DOS el ejemplo, se obtiene la siguiente salida:

salida.png

¿Qué cambio que ahora se ejecutan las dos tareas?

Veamos este diagrama temporal que explicará con mayor detalle lo que sucede:

diagrama_temp_tareas.png

Vamos a tratar de entender lo que pasa:

1) Aparece una nueva tarea IDLE (prioridad “0”, la más baja posible), en español, tarea de reposo. ¿Cuándo se ejecuta esta tarea? Cuando el uC no tiene ninguna tarea pendiente (todas las tareas están en un estado “not running”, en este ejemplo por estar en estado bloqueado). Idealmente se la usará para que el uC/uP entre en un estado de bajo consumo (vean que fácil es aprovechar esta característica con delays fáciles de implementar).

2) Supongamos que un primer momento ambas tareas salen del estado bloqueado, como T2 tiene prioridad frente a T1, se ejecuta primero y entra nuevamente en estado bloqueado.

3) T1 obviamente tendrá mayor prioridad frente a la tarea IDLE y como T2 está bloqueada, el Scheduler pasará automáticamente a esta tarea hasta que la misma entre en estado bloqueado.

A partir de ahí se repite 1 a 3, tarea IDLE => T2 => T1 => tarea IDLE......

Ya con este ejemplo se empiezan a ver cuales son las ventajas de usar el kernel, aparecen controles temporales en conjunto a la posibilidad de optimizar el consumo del uC/uP que con soluciones tradicionales costaban un poco más.

Vean que ahora usar un delay no es un desperdicio de procesamiento, no hay un while involucrado ni tampoco obligamos al uC a entrar en estado de reposo, simplemente el Scheduler seguirá ejecutando todas las tareas pendientes y recién ahí si podremos dejar al uC en estado de reposo.

Mismo código implementado en ARM Cortex:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

void vTask1( void *pvParameters )
{
	
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!
		
		vTaskDelay( 250 / portTICK_RATE_MS );		
	}
}

void vTask2( void *pvParameters )
{		
	
	
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!
		
		vTaskDelay( 250 / portTICK_RATE_MS );		
	}
}

int main()
{					
	static const char *pcTextForTask1 = "Tarea 1 esta corriendo\r\n";
	static const char *pcTextForTask2 = "Tarea 2 esta corriendo\r\n";
		
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_9600); //9600bps
	
	habilitar_interrupciones();
	
	enviar_string_uart0((u8 *)("Creando Tarea1\r\n"));

	
	xTaskCreate(vTask1,(const signed char *)("Task 1"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask1,1,NULL ); 
	
	xTaskCreate( vTask2, (const signed char *)("Task 2"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask2, 2, NULL );

	
	enviar_string_uart0((u8 *)("Llamando al Scheduler\r\n"));
	vTaskStartScheduler();

	while(1);
	
}

La diferencia con el ejemplo anterior (además del que ya se mencionó), es que ahora tengo 250mS para enviar por Uart el mensaje, por lo tanto dejo el Tick base definido en 1mS.

Les dejo el proyecto en keil para el ejemplo 4.
 

Adjuntos

  • ejemplo 4 - vTaskDelay.zip
    671.9 KB · Visitas: 50
Siguiendo con la idea de bloquear temporalmente a una tarea, nos encontramos con otra función similar a vTaskDelay(), llamada vTaskDelayUntil().

La diferencia más importante entre estas dos funciones es que vTaskDelay() cuenta los “ticks” en forma relativa, desde que fue llamda la función hasta alcanzar la cantidad de “ticks” necesarios para salir del estado bloqueado. En cambio vTaskDelayUntil() cuenta los “ticks” en forma absoluta, es decir cuando la tarea abandona el estado de bloqueo y pasa a un estado preparado por un lado almacena el nº de “ticks” actual en una variable y por el otro la cantidad de “ticks” necesarios para salir del estado bloqueado, entonces cuando se llega a una cierta cantidad de “ticks” comparandolo con los “ticks” iniciales, la diferencia debería dar la cantidad de “ticks” necesarios.

Si bien suena confuso y pareciera que ambas funciones hacen exactamente lo mismo (la segunda de una manera más rebuscada por trabajar en forma absoluta), el creador del FreeRTOS afirma que usando la segunda función se obtienen tiempos más ajustados que usando la primera y recomienda su uso cuando se requieran tareas periódicas. Esto se debe a que corrige la diferencia de tiempo que se produce cuando la tarea pasa de un estado bloqueado a uno preparado.

Prototipo de vTaskDelayUntil():

PHP:
void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );

  • pxPreviousWakeTime: almacena en una variable por referencia, el valor del “tick” actual donde la tarea pasa de un estado bloqueado a un estado preparado. Esta variable solo la inicializaremos al principio y luego es el propio kernel que se encarga de actualizarla.
  • xTimeIncrement: el incremento de “ticks” que necesitamos implementar (igual que en vTaskDelay()).

Ejemplo 5:

Implementar el ejemplo 4 usando vTaskDelayUntil().

PHP:
void vTaskFunction( void *pvParameters )
{
  char *pcTaskName;
  portTickType xLastWakeTime;
  
  /* The string to print out is passed in via the parameter. Cast this to a
  character pointer. */
  pcTaskName = ( char * ) pvParameters;
  
  /* The xLastWakeTime variable needs to be initialized with the current tick
  count. Note that this is the only time the variable is written to explicitly.
  After this xLastWakeTime is updated automatically internally within
  vTaskDelayUntil(). */
  xLastWakeTime = xTaskGetTickCount();
  
  /* As per most tasks, this task is implemented in an infinite loop. */
  for( ;; )
   {
    /* Print out the name of this task. */
    vPrintString( pcTaskName );
    /* This task should execute exactly every 250 milliseconds. As per
    the vTaskDelay() function, time is measured in ticks, and the
    portTICK_RATE_MS constant is used to convert milliseconds into ticks.
    xLastWakeTime is automatically updated within vTaskDelayUntil() so is not
    explicitly updated by the task. */
    vTaskDelayUntil( &xLastWakeTime, ( 250 / portTICK_RATE_MS ) );
   }
}

La salida y el comportamiento temporal es exactamente el mismo que en el ejemplo 4.

Código en Cortex-M3:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

void vTask( void *pvParameters )
{		
	portTickType xLastWakeTime;
	
	// xTaskGetTickCount() devuelve la cuenta de ticks actuales -> Referencia inicial
	xLastWakeTime = xTaskGetTickCount();
		
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!
		
		vTaskDelayUntil( &xLastWakeTime, ( 250 / portTICK_RATE_MS ) );	//Fija la cantidad de ticks exactos a partir de la refencia inicial, xLastWakeTime se va actualizando => 	Ideal para tareas periodicas
	}
}

int main()
{					
	static const char *pcTextForTask1 = "Tarea 1 esta corriendo\r\n";
	static const char *pcTextForTask2 = "Tarea 2 esta corriendo\r\n";
	portBASE_TYPE ret;
	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_9600); //9600bps
	
	habilitar_interrupciones();
	
	enviar_string_uart0((u8 *)("Creando Tarea1\r\n"));	
	ret=xTaskCreate(vTask,(const signed char *)("Task 1"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask1,1,NULL ); 
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T1\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T1\r\n"));
	
    enviar_string_uart0((u8 *)("Creando Tarea2\r\n"));
	ret=xTaskCreate( vTask, (const signed char *)("Task 2"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask2, 2, NULL );
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T2\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T2\r\n"));
	
	enviar_string_uart0((u8 *)("Llamando al Scheduler\r\n"));
	vTaskStartScheduler();

	while(1);
	
}

Ejemplo 6:

Se pide:

  • Crear dos tareas con prioridad “1” y que simplemente impriman un String. Estas tareas nunca deberán ser bloqueadas en forma temporal.
  • Crear una tarea con prioridad “2” que imprimirá un String y será bloqueada temporalmente usando vTaskDelayUntil() para que sea periódica.

Prototipo de tareas continuas:

PHP:
void vContinuousProcessingTask( void *pvParameters )
{
  char *pcTaskName;
  
  /* The string to print out is passed in via the parameter. Cast this to a
  character pointer. */
  pcTaskName = ( char * ) pvParameters;
  
  /* As per most tasks, this task is implemented in an infinite loop. */
  for( ;; )
   {
    /* Print out the name of this task. This task just does this repeatedly
    without ever blocking or delaying. */
    vPrintString( pcTaskName );
   }
}

Prototipo de la tarea periódica:

PHP:
void vPeriodicTask( void *pvParameters )
{
  portTickType xLastWakeTime;
  
  /* The xLastWakeTime variable needs to be initialized with the current tick
  count. Note that this is the only time the variable is explicitly written to.
  After this xLastWakeTime is managed automatically by the vTaskDelayUntil()
  API function. */
  xLastWakeTime = xTaskGetTickCount();
  
  /* As per most tasks, this task is implemented in an infinite loop. */
  for( ;; )
   {
   /* Print out the name of this task. */
   vPrintString( "Periodic task is running\r\n" );
   /* The task should execute every 10 milliseconds exactly. */
   vTaskDelayUntil( &xLastWakeTime, ( 10 / portTICK_RATE_MS ) );
   }
}

Salida en DOS:

salida_6.png

La salida puede ser confusa, ya que da la sensación que las tareas continuas cambian cuando se ejecuta la tarea periódica, esto no es así y se va a ver en el comportamiento temporal y en la salida Cortex-M3.

Comportamiento temporal:

comporamiento_temporal.png

Viendo el comportamiento temporal y la salida en DOS obtenida, es evidente que el "tick" base y el periodo de la tarea 3 son iguales, algo que no va a suceder en el ejemplo con Cortex-M3.

Código en Cortex-M3:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

void vTask_continua( void *pvParameters )
{
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!		
	}
}

void vTask_periodica( void *pvParameters )
{		
	portTickType xLastWakeTime;
	
	/* xTaskGetTickCount() devuelve la cuenta de ticks actuales -> Referencia inicial */
	xLastWakeTime = xTaskGetTickCount();
	
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!		
		
		vTaskDelayUntil( &xLastWakeTime, ( 100 / portTICK_RATE_MS ) );	//Fija la cantidad de ticks exactos a partir de la refencia inicial, xLastWakeTime se va actualizando => 	Ideal para tareas periodicas
	}
}

int main()
{					
	static const char *texto_task_continua_1 = "T1\r\n";
	static const char *texto_task_continua_2 = "T2\r\n";
	static const char *texto_task_periodica = "Corriendo tarea periodica\r\n";
	portBASE_TYPE ret;
	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_115200); //115200bps
	
	habilitar_interrupciones();
	
	enviar_string_uart0((u8 *)("Creando Tarea1\r\n"));
	ret=xTaskCreate(vTask_continua,(const signed char *)("Tarea1"),configMINIMAL_STACK_SIZE,(void *)texto_task_continua_1,1,NULL ); 
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T1 continua\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T1 continua\r\n"));
	
	enviar_string_uart0((u8 *)("Creando Tarea2\r\n"));	
	ret=xTaskCreate(vTask_continua,(const signed char *)("Tarea2"),configMINIMAL_STACK_SIZE,(void *)texto_task_continua_2,1,NULL ); 
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T2 continua\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T2 continua\r\n"));
	
	enviar_string_uart0((u8 *)("Creando Tarea3\r\n"));	
	ret=xTaskCreate(vTask_periodica, (const signed char *)("Tarea3"),configMINIMAL_STACK_SIZE,(void *)texto_task_periodica, 2, NULL );
        if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T3 periodica\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T3 periodica\r\n"));
	
	enviar_string_uart0((u8 *)("Llamando al Scheduler\r\n"));
	vTaskStartScheduler();

	while(1);
	
}

Salida obtenida del puerto serie:

salida_6_Cortex.PNG

Subo ambos ejemplos y como en el ej. 6 al haber 2 tareas continuas (tal como se vió en los primeros ejemplos), tuve que modificar el "tick" base y hacer que el baud-rate del puerto serie sea el más veloz posible.
 

Adjuntos

  • ejemplo 5 - vTaskDelayUntil (delay para periodizar).zip
    678.7 KB · Visitas: 26
  • ejemplo 6 - FreeRTOSconfig.h modificado time slice.zip
    679.8 KB · Visitas: 26
Última edición:
El kernel nos permite usar la tarea de reposo (IDLE) para usarla como una tarea, donde hay casos en los que puede resultar útil:

  • Tarea de baja prioridad con un proceso continuo.
  • Medir los ciclos de trabajos disponibles, es decir cuando el uC no tenga una tarea pendiente para ejecutar.
  • Colocar al uC en modo de bajo consumo.

Las limitaciones que deberíamos tener en cuenta al hora de usar la tarea de reposo (IDLE):

  • Nunca deberá ser bloqueada o suspendida, eso implicaría que el Scheduler no tenga asignada una tarea en ejecución.
  • Cuando se elimina una tarea (ya lo vamos a ver), es la tarea de reposo (IDLE) la encargada de liberar los recursos de la tarea eliminada, por lo tanto nuestro código no debe impedir dicha limpieza, entregando su control lo antes posible.

Configuraciones necesarias:

Para poder usar la tarea de reposo, necesitamos habilitar las funciones de enlace en el archivo “FreeRTOSConfig.h”.

PHP:
#define configUSE_IDLE_HOOK 1

Ejemplo 7

Crear dos tareas periódicas con prioridad “1” y “2” respectivamente, cuyos periodos sean de 250 mS y contar la cantidad de ciclos de reloj disponibles mientras las tareas se encuentran bloqueadas.

Esta vez solo voy a subir el ejemplo en Corte-M3, ya que tiene pequeñas modificaciones respecto al ejemplo del libro.

Código Corte-M3:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

/* Declare a variable that will be incremented by the hook function. */
u32 ulIdleCycleCount = 0UL;

void vTask_periodica( void *pvParameters )
{		
	portTickType xLastWakeTime;
	
	/* xTaskGetTickCount() devuelve la cuenta de ticks actuales -> Referencia inicial */
	xLastWakeTime = xTaskGetTickCount();
	
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!		
		enviar_string_uart0((u8 *)("Cantidad de Ciclos de Reloj disponibles: \n"));
		envia_u32_string_uart0(ulIdleCycleCount);
		enviar_string_uart0((u8 *)("\r\n"));
		vTaskDelayUntil( &xLastWakeTime, ( 250 / portTICK_RATE_MS ) );	//Fija la cantidad de ticks exactos a partir de la refencia inicial, xLastWakeTime se va actualizando => 	Ideal para tareas periodicas
	}
}

/* Idle hook functions MUST be called vApplicationIdleHook(), take no parameters,
and return void. */
void vApplicationIdleHook( void )
{
  /* This hook function does nothing but increment a counter. */
  ulIdleCycleCount++;
}

int main()
{					
	static const char *texto_task_periodica = "T1\r\n";
	static const char *texto_task_periodica_2 = "T2\r\n";
	portBASE_TYPE ret;
	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_9600); //9600bps
	
	habilitar_interrupciones();
	
	enviar_string_uart0((u8 *)("Creando Tarea1\r\n"));	
	ret=xTaskCreate(vTask_periodica,(const signed char *)("Task 1"),configMINIMAL_STACK_SIZE,(void *)texto_task_periodica,2,NULL ); 
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T1\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T1\r\n"));
	
	enviar_string_uart0((u8 *)("Creando Tarea2\r\n"));	
	ret=xTaskCreate(vTask_periodica, (const signed char *)("Tarea2"),configMINIMAL_STACK_SIZE,(void *)texto_task_periodica_2, 1, NULL ); 
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T2\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T2\r\n"));
	
	enviar_string_uart0((u8 *)("Llamando al Scheduler\r\n"));
	vTaskStartScheduler();

	while(1);	
}

Lo más destacable:

  • No necesitamos hacer ninguna inicialización para la tarea de reposo (IDLE), ya que esta siempre es creada por el propio kernel.
  • Para acceder a la tarea de reposo (IDLE), se debe crear un prototipo de tarea llamada vApplicationIdleHook, es muy importante respetar ese nombre, ya que en ningún momento asignamos la dirección de la función prototipo de tarea como se hace con el resto de las tareas.
  • La tarea de reposo (IDLE), no requiere de un LOOP.

Salida obtenida de la Uart0:

Salida_IDLE_HOOK.PNG

Si analizamos el comportamiento temporal:

  1. Se ejecuta primero la tarea 1 por ser de mayor prioridad, imprime que no hay ciclos de reloj disponibles y se bloquea temporalmente por 250mSeg.
  2. Se ejecuta la tarea 2, también imprime que no hay ciclos de reloj disponibles y se bloquea temporalmente por 250mSeg.
  3. A partir de este momento la tarea de reposo (IDLE) toma el control y suma una cuenta cada vez que el Scheduler la llama.
  4. Cuando la tarea 1 se desbloquea, imprime los ciclos de reloj disponibles cuando la tarea de reposo (IDLE) tuvo el control y se bloquea temporalmente por 250mSeg.
  5. Cuando la tarea 2 se desbloquea, imprime los ciclos de reloj disponibles cuando la tarea de reposo (IDLE) tuvo el control y se bloquea temporalmente por 250mSeg.

Variante interesante:

PHP:
/* Idle hook functions MUST be called vApplicationIdleHook(), take no parameters,
and return void. */
void vApplicationIdleHook( void )
{
  __wfi();	//Sleep-Mode
}

Cada vez que el Scheduler elija la tarea de reposo (IDLE), el uC entrará en sleep mode y se despertará en función del "tick" base o de alguna otra interrupción que hayamos configurado.

Hay algo que se me pasó mencionar y es un dato muy interesante, el FreeRTOS usa el System Tick Timer para generar la base de tiempo ("tick" base). Entonces en su código podrán usar todos los timers que tengan disponibles, pero no el System Tick Timer.

En el próximo mensaje vamos a ver como modificar la prioridad de una tarea luego de ser creada.
 

Adjuntos

  • ejemplo 7 - FreeRTOSconfig.h modificado idlehook - IdleHook.zip
    706.8 KB · Visitas: 32
Última edición:
Para modificar la prioridad que tendrá una tarea se utiliza la función vTaskPrioritySet, cuyo prototipo es el siguiente:

PHP:
void vTaskPrioritySet( xTaskHandle pxTask, unsigned portBASE_TYPE uxNewPriority );

  • pxTask: el handler que obtuvimos de la tarea cuando se la creo (el último argumento de la función, pxCreatedTask). En caso de modificar la prioridad de la propia tarea que llamó a la función, no es necesario pasar por argumentos el handler, simplemente se usa NULL.
  • uxNewPriority: la nueva prioridad que le asignaremos a la tarea.

Para obtener la prioridad actual de una tarea se utiliza la función uxTaskPriorityGet, cuyo prototipo es el siguiente:

PHP:
unsigned portBASE_TYPE uxTaskPriorityGet( xTaskHandle pxTask );

  • pxTask: el handler de la tarea que se desea obtener su prioridad. En caso de querer obtener la prioridad de la propia tarea que llamó a la función, no es necesario pasar por argumentos el handler, simplemente se usa NULL.

Devolverá la prioridad de la tarea requerida.

Ejemplo 8

Se pide:

  • Crear dos tareas con diferentes prioridades, tarea 1 con mayor prioridad.
  • No bloquearlas temporalmente.
  • A medida que se ejecuten la tareas, modificar su prioridad tal que permita la ejecución de la otra tarea.

Prototipo de tarea 1:

PHP:
void vTask1( void *pvParameters )
{
  unsigned portBASE_TYPE uxPriority;

  /* This task will always run before Task2 as it is created with the higher
  priority. Neither Task1 nor Task2 ever block so both will always be in either
  the Running or the Ready state.
  Query the priority at which this task is running - passing in NULL means
  "return my priority". */
  uxPriority = uxTaskPriorityGet( NULL );

  for( ;; )
    {
    /* Print out the name of this task. */
    vPrintString( "Task1 is running\r\n" );

    /* Setting the Task2 priority above the Task1 priority will cause
    Task2 to immediately start running (as then Task2 will have the higher
    priority of the two created tasks). Note the use of the handle to task
    2 (xTask2Handle) in the call to vTaskPrioritySet(). Listing 24 shows how
    the handle was obtained. */
    vPrintString( "About to raise the Task2 priority\r\n" );

    vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );
    /* Task1 will only run when it has a priority higher than Task2.
    Therefore, for this task to reach this point Task2 must already have
    executed and set its priority back down to below the priority of this
    task. */

    }
}

Prototipo de tarea 2:

PHP:
void vTask2( void *pvParameters )
{
  unsigned portBASE_TYPE uxPriority;
  
  /* Task1 will always run before this task as Task1 is created with the
  higher priority. Neither Task1 nor Task2 ever block so will always be
  in either the Running or the Ready state.
  Query the priority at which this task is running - passing in NULL means
  "return my priority". */
  uxPriority = uxTaskPriorityGet( NULL );

  for( ;; )
    {
    /* For this task to reach this point Task1 must have already run and
    set the priority of this task higher than its own.
    Print out the name of this task. */
    vPrintString( "Task2 is running\r\n" );

    /* Set our priority back down to its original value. Passing in NULL
    as the task handle means "change my priority". Setting the
    priority below that of Task1 will cause Task1 to immediately start
    running again – pre-empting this task. */
    vPrintString( "About to lower the Task2 priority\r\n" );

    vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
    }
}

Este prototipo tiene un pequeño error cuando se usa la variable uxPriority, ya que nunca actualiza su valor c/vez que la tarea 2 toma el control. En la versión Cortex-M3 el error será corregido.

Main:

PHP:
/* Declare a variable that is used to hold the handle of Task2. */
xTaskHandle xTask2Handle;

int main( void )
{
  /* Create the first task at priority
  2. The task parameter is not used
  and set to NULL. The task handle is
  also not used so is also set to NULL. */
  xTaskCreate( vTask1, "Task 1", 1000,NULL, 2, NULL );

  /* The task is created at priority 2 ______^. */
  /* Create the second task at priority 1 - which is lower than the priority
  given to Task1. Again the task parameter is not used so is set to NULL -
  BUT this time the task handle is required so the address of xTask2Handle
  is passed in the last parameter. */
  xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );
  /* The task handle is the last parameter _____^^^^^^^^^^^^^ */

  /* Start the scheduler so the tasks start executing. */
  vTaskStartScheduler();
  
  /* If all is well then main() will never reach here as the scheduler will
  now be running the tasks. If main() does reach here then it is likely that
  there was insufficient heap memory available for the idle task to be created.
  CHAPTER 5 provides more information on memory management. */
  for( ;; );
}

Lo más destable:

  • Se crean 2 tareas tal como se pide
  • Durante la creación de la tarea 2, se guarda la dirección de su handler en la variable global "xTask2Handle".
  • La tarea 1 usa ese "xTask2Handle" para modificar la prioridad de la tarea 2.
  • La tarea 2 no necesita usar el handler para modificar su propia prioridad.

Salida en DOS:

salida_dos.png

Comportamiento temporal:

comportamiento_temporal.png

Se observa que:

  1. La tarea 1 toma el control por ser de mayor prioridad, escribe un mensaje y mediante vTaskPrioritySet cambia la prioridad de la tarea 2 usando su handler, tal que la tarea 2 sea de mayor prioridad.
  2. La tarea 2 toma el control, escribe un mensaje y mediante vTaskPrioritySet cambia su propia prioridad tal que sea menor que la prioridad de la tarea 1. Vean que no requiere el uso del handler, ya que el cambio de prioridad es en la propia tarea.
  3. Se repite el ciclo una y otra vez.

Código en Cortex M3:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

/* Declare a variable that is used to hold the handle of Task2. */
xTaskHandle Handle_tarea2;

void vTask1( void *pvParameters )
{
	unsigned portBASE_TYPE uxPriority;
	
	uxPriority = uxTaskPriorityGet( NULL );	//Obtiene el valor de su propia prioridad
	
	while(1)
	{
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!
		enviar_string_uart0((u8 *)("Se elevara la prioridad de la tarea 2\r\n"));
		
		vTaskPrioritySet( Handle_tarea2, ( uxPriority + 1 ) );	// Se eleva la prioridad de la tarea2 tal que sea mayor al de la prioridad de la tarea1		
	}
}

void vTask2( void *pvParameters )
{		
	unsigned portBASE_TYPE uxPriority;
			
	while(1)
	{
		uxPriority = uxTaskPriorityGet( NULL ); //Obtiene el valor de su propia prioridad
		
		enviar_string_uart0((u8 *)(pvParameters));//Imprimir Tarea!
		enviar_string_uart0((u8 *)("Se disminuira la prioridad de la tarea 2\r\n"));
		
		vTaskPrioritySet( NULL, ( uxPriority - 2 ) ); //Bajo el valor de su propia prioridad
	}
}

int main()
{					
	static const char *pcTextForTask1 = "Tarea 1 esta corriendo\r\n";
	static const char *pcTextForTask2 = "Tarea 2 esta corriendo\r\n";
	portBASE_TYPE ret;
	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_9600); //9600bps
	
	habilitar_interrupciones();
	
	enviar_string_uart0((u8 *)("Creando Tarea1\r\n"));
	ret=xTaskCreate(vTask1,(const signed char *)("Task 1"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask1,2,NULL ); 
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T1\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T1\r\n"));
	
	enviar_string_uart0((u8 *)("Creando Tarea2\r\n"));
	ret=xTaskCreate(vTask2, (const signed char *)("Task 2"),configMINIMAL_STACK_SIZE,(void *)pcTextForTask2, 1, &Handle_tarea2 ); //Obtengo por referencia la direccion del handler de la tarea 2
	if(ret==pdTRUE)
		enviar_string_uart0((u8 *)("Se creo T2\r\n"));
	else
		enviar_string_uart0((u8 *)("No se creo T2\r\n"));
	
	enviar_string_uart0((u8 *)("Llamando al Scheduler\r\n"));
	vTaskStartScheduler();

	while(1);
	
}

La única diferencia, es la corrección que mencioné antes, la actualización del valor de la prioridad actual de la tarea 2 c/vez que esta tenga el control.

Salida en Cortex-M3:

Salida_cortex_m3.PNG

En el próximo mensaje vamos a meternos ya con las opciones que nos da el kernel para poder comunicar las distintas tareas entre si.
 

Adjuntos

  • ejemplo 8 - Cambio de pioridad.zip
    688.5 KB · Visitas: 24
Los dos protipos tienen el mismo error en uxPriority por que solo es inicializada una vez al inicio de cada función concurrente y luego solo se hace uxPriority-1 en vez de hacer uxPriority-=1 (o +... depende cual sea)
 
Los dos protipos tienen el mismo error en uxPriority por que solo es inicializada una vez al inicio de cada función concurrente y luego solo se hace uxPriority-1 en vez de hacer uxPriority-=1 (o +... depende cual sea)

Fijate que son variables locales, entonces siguiendo el código en DOS:

1- En main, la tarea 1 inicia con prioridad 2 y la tarea 2 inicia con prioridad 1.
2- Cuando tarea 1 toma el control inicializa su variable local uxPriority con 2 y siempre cambia la prioridad de la tarea 2 a uxPriority+1=3.
3- Cuando tarea 2 toma el control solo inicializa su variable local uxPriority con 1 (acá esta el problema que mencioné) y siempre cambia su propia prioridad a uxPriority-2=-1 :confused: (por eso está mal).

En cambio en el código que implementé en Cortex-M3:

1 y 2 se mantienen.
3- Cuando tarea 2 toma el control actualiza su variable local uxPriority con 3 (valor que se le otorgó en la tarea 1) y siempre cambia su propia prioridad a uxPriority-2=1.

Es evidente que era más sencillo poner explícitamente cambia a prioridad "3" y prioridad "1", pero la idea del ejemplo era incluir el uso de la función uxTaskPriorityGet.

Otras opciones:

Alt 1- Usar la solución de DOS, pero en vez de restar 2 cuando la tarea 2 cambia su propia prioridad, mantener el uxPriority inicial.
Alt 2- Cuando tarea 2 inicializa uxPriority, podría hacerlo en base a la prioridad de la tarea 1 (requiere handler), y luego cambiar su propia prioridad por uxPriority-1.
 
Última edición:
Ahhh... entendí que querías hacer otra cosa :oops:
De todas formas, esa suerte de "multitarea cooperativo" donde una tarea cambia la prioridad de otra o de si misma para permitir la ejecución de la otra no es nada recomendable.
Es mas conveniente usar un kernel preemptivo que las desaloje cuando sea necesario....
 
Ahhh... entendí que querías hacer otra cosa :oops:
De todas formas, esa suerte de "multitarea cooperativo" donde una tarea cambia la prioridad de otra o de si misma para permitir la ejecución de la otra no es nada recomendable.
Es mas conveniente usar un kernel preemptivo que las desaloje cuando sea necesario....

Es como decís.

Pero la idea del tutorial es explicar bien las funciones que trae el kernel y mostrar su funcionamiento.

Después está en c/uno saber elegir que herramientas conviene usar.
 
Después está en c/uno saber elegir que herramientas conviene usar.
Tal cual! Solo que creí que el FreeRTOS era Real Time en serio y no había que andar jugando con las prioridades para liberar la ejecución a otras tareas.
Vos analizaste el código del kernel??.. digo, para ver que onda con el scheduler.
Te pregunto por que hace muuuchos años (como 20 :oops:) yo diseñé un sistema multitarea EXTREMADAMENTE SIMILAR a este desde cero para DOS, y tuve que reescribir muchas funciones I/O bloqueantes (incluso una interfaz gráfica no tan simple) para que hubiera algún dejo de Real Time involucrado. Ese sistema se usó para controlar un robot BOSCH manteniendo comunicaciones en red (usando Novell en esa época :oops:) entre la computadora de control y la de supervisión, y estábamos por debajo de los 10ms de muestreo desde la PC controladora. Pero claro, no eran muy predecibles los retardos de atención de eventos externos.. así que el RT era mas un deseo que otra cosa. Finalmente se usó QNX para el control... y listo... solo que QNX no corre sobre un micro tan chico.. bueno, no que yo sepa, así que este tipo de soluciones son muy buenas... aunque "aparentemente" no sean taaaan RT.

Le voy a dar una mirada en mas profundidad....
 
Tal cual! Solo que creí que el FreeRTOS era Real Time en serio y no había que andar jugando con las prioridades para liberar la ejecución a otras tareas.

No me parece mal que te den la herramienta para hacer un cambio de prioridad y como te dije, este es un ejemplo que no apunta tanto al mejor uso del kernel, sino a entender el uso de esa herramienta.

Todavía no llegue con el tutorial, pero la correcta sincronización entre las tareas las hacés con semáforos o incluso mutex para compartir un mismo espacio de memoria, además del ya visto por el bloqueo temporal.

Vos analizaste el código del kernel??.. digo, para ver que onda con el scheduler.

La verdad que no, debería, más tratándose de la columna vertebral de lo que será tu código. Pero hasta ahora no llegué hacer ningún proyecto usando este kernel, solo me preocupe de entenderlo para tenerlo como una alternativa.

Te pregunto por que hace muuuchos años (como 20 :oops:) yo diseñé un sistema multitarea EXTREMADAMENTE SIMILAR a este desde cero para DOS, y tuve que reescribir muchas funciones I/O bloqueantes (incluso una interfaz gráfica no tan simple) para que hubiera algún dejo de Real Time involucrado. Ese sistema se usó para controlar un robot BOSCH manteniendo comunicaciones en red (usando Novell en esa época :oops:) entre la computadora de control y la de supervisión, y estábamos por debajo de los 10ms de muestreo desde la PC controladora. Pero claro, no eran muy predecibles los retardos de atención de eventos externos..así que el RT era mas un deseo que otra cosa. Finalmente se usó QNX para el control... y listo... solo que QNX no corre sobre un micro tan chico.. bueno, no que yo sepa, así que este tipo de soluciones son muy buenas... aunque "aparentemente" no sean taaaan RT.

A pero ud. es groso mal, viene dios... y después Dr. Zoidberg :LOL:

Le voy a dar una mirada en mas profundidad....

Lo que te puedo decir del FreeRtos después de "jugar" un rato con el, es que los tiempos te dan bien, por lo menos en la familia de uC que manejo, o sea es bastante determinístico que es lo que se busca en un RTOS.
 
¿Para qué sirven?:

Son una forma de comunicar dos o más tareas en las cuales es necesario cierta transferencia de datos.

Características:

Una cola puede almacenar un conjunto de datos (bytes, words, etc) según el tamaño que se le asigne durante su creación. Se las suele usar como buffers FIFO (el primer elemento almacenado es el primero en salir).

Al escribir un dato en la cola se almacena en la misma, mientras que al leer un dato desde la cola se obtiene una copia del dato y se la remueve de la cola.

Acceso a una cola:

Las colas son objetos que no pertenecen a ninguna tarea, permitiendo que cualquier tarea pueda escribir o leer en una cola sin importar su prioridad. En otras palabras, una cola puede tener múltiples escritores y múltiples lectores.

Bloqueo por lectura:

Cuando una tarea se dispone a leer una cola, como opción puede bloquearse durante un determinado tiempo hasta que haya un dato disponible para leer.

Entonces mientras la cola este vacía, la tarea que se dispone a leer se encuentra bloqueada hasta que otra tarea (o incluso una interrupción) escriba un dato en la cola, a partir de ese momento la tarea de lectura pasa al estado “preparado”.

En caso de sobrepasar el tiempo de espera debido a la ausencia de datos en la cola, la tarea pasa al estado “preparado”.

Si hubiera dos tareas de lectura, siempre la de mayor prioridad pasará al estado “preparado” para obtener el dato almacenado en la cola. En cambio, si ambas tareas fueran de la misma prioridad, la tarea que mayor tiempo se encuentra en estado “bloqueado” será la que tenga prioridad.

Bloqueo por escritura:

Al igual que el bloqueo por lectura, una tarea opcionalmente puede bloquearse durante un determinado tiempo a la espera de que la cola tenga lugar para un nuevo dato.

Si hubiera dos o más tareas que quisieran escribir la cola, el funcionamiento de las prioridades serían similares al explicado en el bloqueo por lectura.

Secuencia de escritura y lectura de una cola:

Colas_1.png

Se crea una cola la cual puede almacenar hasta 5 enteros (int), que permitirá la comunicación entre las tareas “a” y “b”.

La tarea “a” escribe en la cola un valor de una variable local. La cola pasa de estar vacía a tener un dato disponible para su lectura.

Colas_2.png

La tarea “a” vuelve a escribir un valor en la cola, por lo tanto hay dos valores disponibles para leer. La tarea “b” hace una lectura sobre la cola, obteniendo el primer valor escrito por “a”.

Colas_3.png

Luego de la lectura de “b”, la cola libera el espacio del primer valor, quedando solo disponible la lectura del segundo valor que escribió “a”.

Crear una cola:

Para crear una cola se usa la función xQueueCreate la cual nos devolverá el handle para poder operar con la cola. Su prototipo es el siguiente:

PHP:
xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,
                           unsigned portBASE_TYPE uxItemSize);

  • uxQueueLength: indica el tamaño de la cola, es decir de cuantos “items” será.
  • uxItemSize: indica el peso de c/”item”, si será por ej. del tipo “int”.

Si se pudo crear la cola, devolverá un puntero con el handle distinto de “null”, de lo contrario hubo algún inconveniente a la hora de crear la cola.

Escribir en la cola:

La función que vamos a ver es xQueueSendToBack() que sirve para escribir al final de la cola. Su prototipo es el siguiente:

PHP:
portBASE_TYPE xQueueSendToBack(	xQueueHandle xQueue,
						const void * pvItemToQueue,
						portTickType xTicksToWait);

  • xQueue: es el handle de la cola que será escrita.
  • pvItemToQueue: un puntero del dato original desde el cual se copiara a la cola, por ej. una variable local de una tarea que tendrá el mismo tamaño de item que tiene la cola.
  • xTicksToWait: la cantidad de “ticks” máxima en los cuales la tarea que desea escribir la cola permanecerá en estado bloqueado hasta que haya lugar disponible en la cola. En caso de valer “0”, si la cola está llena, automáticamente la tarea entrará en estado “preparado”.

Devolverá:

  • pdPASS: escribió un dato válido de la cola.
  • errQUEUE_FULL: la cola estaba llena.

Nunca se debe usar esta función en una rutina de interrupción, para esos casos se deberá usar xQueueSendToBackFromISR(), función optimizada para esos casos.

Leer un item de la cola:

La función que vamos a ver es xQueueReceive() que sirve para leer y eliminar el item de la lista. Su prototipo es el siguiente:

PHP:
portBASE_TYPE xQueueReceive(xQueueHandle xQueue,
				           const void * pvBuffer,
                                       portTickType xTicksToWait);

  • xQueue: es el handle de la cola que será leída.
  • pvBuffer: un puntero donde se almacenará el dato leído, por ej. una variable local de una tarea que tendrá el mismo tamaño de item que tiene la cola.
  • xTicksToWait: la cantidad de “ticks” máxima en los cuales la tarea que desea leer la cola permanecerá en estado bloqueado hasta que haya un dato disponible. En caso de valer “0”, si la cola está vacía, automáticamente la tarea entrará en estado “preparado”.

Devolverá:

  • pdPASS: escribió un dato válido de la cola.
  • errQUEUE_EMPTY: la cola estaba vacía.

Nunca se debe usar esta función en una rutina de interrupción, para esos casos se deberá usar xQueueReceiveFromISR(), función optimizada para esos casos.

Obtener la cantidad de items disponibles en la cola:

La función uxQueueMessagesWaiting() nos permite saber cuantos items disponibles para leer hay en la cola. Su prototipo es el siguiente:

PHP:
unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );

  • xQueue: es el handle de la cola.

Devolverá la cantidad de items disponibles.

Nunca se debe usar esta función en una rutina de interrupción, para esos casos se deberá usar uxQueueMessagesWaitingFromISR(), función optimizada para esos casos.

Ejemplo 10

En este ejemplo se demostrará como funciona una cola mediante tres tareas, de las cuales dos escribirán en la cola y la restante solo la leerá.

Las dos tareas que escribirán tendrán una prioridad menor a la de la tarea de lectura, por lo tanto la cola nunca estará llena ya que solo podrá llenarse un item.

Prototipo de las tareas de escritura:

PHP:
static void vSenderTask( void *pvParameters )
{
  long lValueToSend;
  portBASE_TYPE xStatus;
  
  /* Two instances of this task are created so the value that is sent to the
  queue is passed in via the task parameter - this way each instance can use
  a different value. The queue was created to hold values of type long,
  so cast the parameter to the required type. */
  lValueToSend = ( long ) pvParameters;

  /* As per most tasks, this task is implemented within an infinite loop. */
  for( ;; )
    {
      /* Send the value to the queue.
      The first parameter is the queue to which data is being sent. The
      queue was created before the scheduler was started, so before this task
      started to execute.
      
      The second parameter is the address of the data to be sent, in this case
      the address of lValueToSend.
    
      The third parameter is the Block time – the time the task should be kept
      in the Blocked state to wait for space to become available on the queue
      should the queue already be full. In this case a block time is not
      specified because the queue should never contain more than one item and
      therefore never be full. */
      xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
      if( xStatus != pdPASS )
        {
          /* The send operation could not complete because the queue was full -
          this must be an error as the queue should never contain more than
          one item! */
          vPrintString( "Could not send to the queue.\r\n" );
        }
      
       /* Allow the other sender task to execute. taskYIELD() informs the
       scheduler that a switch to another task should occur now rather than
       keeping this task in the Running state until the end of the current time
       slice. */
       taskYIELD();
     }  
}

La función taskYIELD() simplemente le permite al Scheduler que le ceda la ejecución a la otra tarea que tiene su mismo nivel de prioridad.

Prototipo de la tarea de lectura:

PHP:
static void vReceiverTask( void *pvParameters )
{
  /* Declare the variable that will hold the values received from the queue. */
  long lReceivedValue;
  portBASE_TYPE xStatus;
  const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

  /* This task is also defined within an infinite loop. */
  for( ;; )
    {
      /* This call should always find the queue empty because this task will
      immediately remove any data that is written to the queue. */
      if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
          vPrintString( "Queue should have been empty!\r\n" );
        }
     
      /* Receive data from the queue.
      The first parameter is the queue from which data is to be received. The
      queue is created before the scheduler is started, and therefore before this
      task runs for the first time.
      
      The second parameter is the buffer into which the received data will be
      placed. In this case the buffer is simply the address of a variable that
      has the required size to hold the received data.
      
      The last parameter is the block time – the maximum amount of time that the
      task should remain in the Blocked state to wait for data to be available
      should the queue already be empty. In this case the constant
      portTICK_RATE_MS is used to convert 100 milliseconds to a time specified in
      ticks. */
      xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
      if( xStatus == pdPASS )
        { 
          /* Data was successfully received from the queue, print out the received
          value. */
          vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
      else
        {
          /* Data was not received from the queue even after waiting for 100ms.
          This must be an error as the sending tasks are free running and will be
          continuously writing to the queue. */
          vPrintString( "Could not receive from the queue.\r\n" );
        }
     }
}

Prototipo de main:

PHP:
/* Declare a variable of type xQueueHandle. This is used to store the reference
to the queue that is accessed by all three tasks. */
xQueueHandle xQueue;

int main( void )
{
  /* The queue is created to hold a maximum of 5 values, each of which is
  large enough to hold a variable of type long. */
  xQueue = xQueueCreate( 5, sizeof( long ) );
  if( xQueue != NULL )
    {
      /* Create two instances of the task that will send to the queue. The task
      parameter is used to pass the value that the task will write to the queue,
      so one task will continuously write 100 to the queue while the other task
      will continuously write 200 to the queue. Both tasks are created at
      priority 1. */
      xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
      xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );

      /* Create the task that will read from the queue. The task is created with
      priority 2, so above the priority of the sender tasks. */
      xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

     /* Start the scheduler so the created tasks start executing. */
     vTaskStartScheduler();
    }
  else
    {
      /* The queue could not be created. */
    }

  /* If all is well then main() will never reach here as the scheduler will 
  now be running the tasks. If main() does reach here then it is likely that
  there was insufficient heap memory available for the idle task to be created.
  CHAPTER 5 provides more information on memory management. */

  for( ;; );
}

Salida en DOS:

Salida en dos.png

Comportamiento temporal:

Comportamiento temporal.png

Se observa que:

  1. Primero se entra en la tarea de lectura, debido a tener mayor prioridad.
  2. Al estar vacía la cola, tarea de lectura obtiene la cantidad de items y comprueba que la cola está vacía, por lo tanto no imprime nada y se bloquea.
  3. La tarea de escritura “2” toma el control, escribe en la cola, verifica que la escritura sea correcta y sede su prioridad a la tarea de escritura “1” mediante taskYIELD().
  4. En este punto, la tarea de lectura se desbloquea debido a la presencia de datos en la cola y le gana en prioridad a la tarea de escritura “1”, leyendo el contenido de la cola e imprimiendo su valor. Luego, nuevamente repite 2.
  5. Esta vez es la tarea de escritura “1” la que toma el control (debido al taskYIELD()). Se repite 3, pero con la tarea de escritura “1”.

El ciclo se repite desde 2.

Código en Cortex-M3:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

/* Declare a variable of type xQueueHandle. This is used to store the reference
to the queue that is accessed by all three tasks. */
xQueueHandle xQueue;

void tarea_envia( void *pvParameters )
{		
	u32 valor_a_enviar;
	portBASE_TYPE xStatus;
	
	valor_a_enviar=(u32)(pvParameters);	
	
	while(1)
	{				
		xStatus = xQueueSendToBack( xQueue, &valor_a_enviar, 0 );
		
		if(xStatus!= pdPASS)
			enviar_string_uart0((u8 *)("No se pudo enviar dato!\r\n"));//Imprimir Tarea!
		
		taskYIELD();
	}
}

void tarea_recibe( void *pvParameters )
{		
	
	portBASE_TYPE xStatus;
	
	u32 dato_recibido;
	
	while(1)
	{
		if( uxQueueMessagesWaiting( xQueue ) != 0 )
			enviar_string_uart0((u8 *)("La cola debe estar vacia\r\n" ));
		
		xStatus = xQueueReceive( xQueue, &dato_recibido, (100 / portTICK_RATE_MS) );	// 100mSeg Time-out
				
		if( xStatus == pdPASS )
		{
			/* Data was successfully received from the queue, print out the received
			value. */
			enviar_string_uart0((u8 *)("Dato recibido: \n"));
			envia_u32_string_uart0(dato_recibido);
			enviar_string_uart0((u8 *)("\r\n"));
		}
		else
		{
			/* Data was not received from the queue even after waiting for 100ms.
			This must be an error as the sending tasks are free running and will be
			continuously writing to the queue. */
			enviar_string_uart0((u8 *)("No pudo recibirse datos desde la cola.\r\n" ));
		}
		
		
	}
}


int main()
{						
	portBASE_TYPE ret;	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_115200); //115200bps
	
	habilitar_interrupciones();
	
	xQueue = xQueueCreate( 5, sizeof( u32 ) );	//Creo una cola de 5 datos u32
	
	if( xQueue != NULL )
	{
		
		ret=xTaskCreate( tarea_envia, (const signed char *)("Tx1"), configMINIMAL_STACK_SIZE, ( void * ) (100), 1, NULL );
		if(ret== pdPASS)
			enviar_string_uart0((u8 *)("Tarea 1 creada!.\r\n" ));
		else
			enviar_string_uart0((u8 *)("Tarea 1 no pudo ser creada!.\r\n" ));
		
		ret=xTaskCreate( tarea_envia, (const signed char *)("Tx2"), configMINIMAL_STACK_SIZE, ( void * ) (200), 1, NULL );
		if(ret== pdPASS)
			enviar_string_uart0((u8 *)("Tarea 2 creada!.\r\n" ));
		else
			enviar_string_uart0((u8 *)("Tarea 2 no pudo ser creada!.\r\n" ));
		
		ret=xTaskCreate( tarea_recibe, (const signed char *)("Rx"), configMINIMAL_STACK_SIZE, NULL, 2, NULL );
		if(ret== pdPASS)
			enviar_string_uart0((u8 *)("Tarea recepcion creada!.\r\n" ));
		else
			enviar_string_uart0((u8 *)("Tarea recepcion no pudo ser creada!.\r\n" ));
		
		vTaskStartScheduler();
	
	}
	else
		enviar_string_uart0((u8 *)("No pudo crear la cola.\r\n" ));
	
	
	while(1);
	
}

Se observa que es necesario agregar la librería necesarias para el manejo de las colas "queue.h".

En este código aparece el primer problema relacionado con la memoria originalmente asignada por default, ya que la creación de la cola exigía un heap mayor. La solución fue aumentar el heap de 2kBytes a 5kBytes desde el archivo “FreeRTOSConfig.h”.

Otro cambio importante es el nuevo administrador del heap, hasta ahora se venía usando las funciones de heap1.c, a partir del ejemplo 9 las cambié por las funciones heap4.c. Este “ejemplo 9” no lo expliqué debido a que es necesario entender el comportamiento de estas funciones, que las explicaré más adelante, pero tiene que ver con la liberación de recursos.
 

Adjuntos

  • ejemplo 10 - heap_4.c y heap total aumentado de 2 a 5kBytes - Colas.zip
    762.2 KB · Visitas: 29
Siguiendo con las colas, en el ejemplo anterior se vió como dos tareas se encargaban de escribir en la cola y solo una de leer.

Un uso interesante de la colas sería que además de escribir el dato, identifiquemos que tarea escribió ese dato para luego con la tarea de lectura tomar una cierta acción.

Ejemplo para darle un sentido práctico a lo que menciono:

Fig23.png

  • La cola fue creada para trabajar con estructuras del tipo :LOL:ata. Dicha estructura contiene el dato propiamente dicho “iValue” y el significado de dicho dato o mejor dicho de que tarea proviene “iMeaning”.
  • La tarea principal del sistema será la de control, la cual actuará en función de los datos leídos de la cola.
  • La tarea CAN se encarga de recibir datos mediante ese protocolo para luego decodificarlo según la estructura :LOL:ata y enviarlo a la tarea de control. En este ejemplo el campo “iMeaning” indica que el dato recibido en “iValue” es la velocidad actual de un motor.
  • La tarea HMI (interfaz de usuario) se encargará de recibir datos ingresados por el usuario para luego según la estructura :LOL:ata y enviarlo a la tarea de control. En este ejemplo el campo “iMeaning” indica que el dato recibido en “iValue” es un nuevo punto de trabajo.

Ejemplo 11

A diferencia del ejemplo anterior, la tarea de lectura tendrá la menor prioridad y la cola trabajará con una estructura en vez de un simple “int”.

Se define la estructura :LOL:ata y se inicializa un vector global del tipo :LOL:ata:

PHP:
#define mainSENDER_1 0
#define mainSENDER_2 1

/* Define the structure type that will be passed on the queue. */
typedef struct
{
  unsigned char ucValue;
  unsigned char ucSource;
} xData;

/* Declare two variables of type xData that will be passed on the queue. */
static const xData xStructsToSend[ 2 ] =
{
  { 100, mainSENDER_1 }, /* Used by Sender1. */
  { 200, mainSENDER_2 } /* Used by Sender2. */
};

Prototipo de las tareas de escritura:

PHP:
static void vSenderTask( void *pvParameters )
{
  portBASE_TYPE xStatus;
  const portTickType xTicksToWait = 100 / portTICK_RATE_MS;
  /* As per most tasks, this task is implemented within an infinite loop. */
  for( ;; )
    {
    /* Send to the queue.
    The second parameter is the address of the structure being sent. The
    address is passed in as the task parameter so pvParameters is used
    directly.
    
    The third parameter is the Block time - the time the task should be kept
    in the Blocked state to wait for space to become available on the queue
    if the queue is already full. A block time is specified because the
    sending tasks have a higher priority than the receiving task so the queue
    is expected to become full. The receiving task will on execute and remove
    items from the queue when both sending tasks are in the Blocked state. */
    xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );

    if( xStatus != pdPASS )
      {
        /* The send operation could not complete, even after waiting for 100ms.
        This must be an error as the receiving task should make space in the
        queue as soon as both sending tasks are in the Blocked state. */
        vPrintString( "Could not send to the queue.\r\n" );
      }
    
    /* Allow the other sender task to execute. */
    taskYIELD(); 
  }
}

Prototipo de la tarea de lectura:

PHP:
static void vReceiverTask( void *pvParameters )
{
  /* Declare the structure that will hold the values received from the queue. */
  xData xReceivedStructure;
  portBASE_TYPE xStatus;
  
   /* This task is also defined within an infinite loop. */
   for( ;; )
     {
     /* Because it has the lowest priority this task will only run when the
     sending tasks are in the Blocked state. The sending tasks will only enter
     the Blocked state when the queue is full so this task always expects the
     number of items in the queue to be equal to the queue length – 3 in this
     case. */
     if( uxQueueMessagesWaiting( xQueue ) != 3 )
       {
         vPrintString( "Queue should have been full!\r\n" );
       }

     /* Receive from the queue.
     The second parameter is the buffer into which the received data will be
     placed. In this case the buffer is simply the address of a variable that
     has the required size to hold the received structure.

    The last parameter is the block time - the maximum amount of time that the
    task will remain in the Blocked state to wait for data to be available
    if the queue is already empty. In this case a block time is not necessary
    because this task will only run when the queue is full. */
    xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
  
    if( xStatus == pdPASS )
      {
        /* Data was successfully received from the queue, print out the received
        value and the source of the value. */
        if( xReceivedStructure.ucSource == mainSENDER_1 )
          {
            vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );
          }
        else
          {
            vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
          }
       }
    else
      {
        /* Nothing was received from the queue. This must be an error
        as this task should only run when the queue is full. */
        vPrintString( "Could not receive from the queue.\r\n" );
      }
    }
}

Main:

PHP:
int main( void )
{
  /* The queue is created to hold a maximum of 3 structures of type xData. */
  xQueue = xQueueCreate( 3, sizeof( xData ) );
  
  if( xQueue != NULL )
    {
      /* Create two instances of the task that will write to the queue. The
      parameter is used to pass the structure that the task will write to the
      queue, so one task will continuously send xStructsToSend[ 0 ] to the queue
      while the other task will continuously send xStructsToSend[ 1 ]. Both
      tasks are created at priority 2 which is above the priority of the receiver. */
      xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL );
      xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL );

      /* Create the task that will read from the queue. The task is created with
      priority 1, so below the priority of the sender tasks. */
      xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
   
      /* Start the scheduler so the created tasks start executing. */
      vTaskStartScheduler();
    }
  else
    {
      /* The queue could not be created. */
    }

  /* If all is well then main() will never reach here as the scheduler will
  now be running the tasks. If main() does reach here then it is likely that
  there was insufficient heap memory available for the idle task to be created.
  CHAPTER 5 provides more information on memory management. */
  for( ;; );
}

Salida en DOS:

Salida en DOS.png

Comportamiento temporal:

Salida temporal.png

Se observa que:

  1. Se crea una cola que tendrá un tamaño de 3 datos del tipo :LOL:ata.
  2. La dos tarea de escritura tendrán una prioridad “2”, mientras que la tarea de lectura tendrá una prioridad “1”.
  3. Arranca la tarea de escritura “1” y escribe el dato recibido por argumento en la cola, identificando la procedencia del dato dentro de la estructura. Luego cede la prioridad con la función taskYield.
  4. La tarea de escritura “2” toma el control y escribe el dato recibido por argumento en la cola, identificando la procedencia del dato dentro de la estructura. Luego cede la prioridad con la función taskYield.
  5. La tarea de escritura “1” toma el control y escribe otro dato en la cola. Luego cede la prioridad con la función taskYield.
  6. Hasta este punto la cola contiene 3 datos que no fueron leídos.
  7. La tarea de escritura “2” toma el control e intenta escribir un dato en la cola, pero al estar llena se bloquea con un tiempo de espera máximo de 100mSeg.
  8. Al bloquearse la tarea de escritura “2”, la tarea escritura “1” toma el control e intenta escribir un dato en la cola, pero al estar llena se bloquea también con un tiempo de espera máximo de 100mSeg.
  9. Al estar bloqueadas tarea de escritura “1” y “2”, la tarea de lectura toma el control y lee un dato y automáticamente libera un espacio en la cola. Por lo tanto pierde la prioridad.
  10. La tarea de escritura “2” toma el control y ahora si escribe un dato en la cola (siempre toma el control la tarea que más tiempo lleva intentando escribir en la cola). Luego cede la prioridad con la función taskYield.
  11. Al estar bloqueada la tarea de escritura “1”, la tarea de escritura “2” toma nuevamente el control e intenta escribir un dato en la cola, pero al estar llena se bloquea con un tiempo de espera máximo de 100mSeg.
  12. Nuevamente al estar bloqueadas tarea de escritura “1” y “2”, la tarea de lectura toma el control, pero esta vez escribe en pantalla el dato leído la vez anterior. Luego lee otro dato, libera un espacio en la cola y pierde prioridad.
  13. La tarea de escritura “1” toma el control y ahora si escribe un dato en la cola (siempre toma el control la tarea que más tiempo lleva intentando escribir en la cola). Luego cede la prioridad con la función taskYield.
  14. Al estar bloqueada la tarea de escritura “2”, la tarea de escritura “1” toma nuevamente el control e intenta escribir un dato en la cola, pero al estar llena se bloquea con un tiempo de espera máximo de 100mSeg.
  15. Nuevamente al estar bloqueadas tarea de escritura “1” y “2”, la tarea de lectura toma el control, pero esta vez escribe en pantalla el dato leído la vez anterior. Luego lee otro dato, libera un espacio en la cola y pierde prioridad.
  16. Se repite el ciclo desde el paso 10.

Código en Cortex-M3:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

#define TX_1	0
#define TX_2	1

typedef struct
{
	u8 ucValue;
	u8 ucSource;
} xData;


/* Declare a variable of type xQueueHandle. This is used to store the reference
to the queue that is accessed by all three tasks. */
xQueueHandle xQueue;

void tarea_envia( void *pvParameters )
{		
	portBASE_TYPE xStatus;
	
	while(1)
	{				
		xStatus = xQueueSendToBack( xQueue, pvParameters, 100 / portTICK_RATE_MS ); // 100mSeg Time-out
		
		if(xStatus!= pdPASS)
			enviar_string_uart0((u8 *)("No se pudo enviar dato!\r\n"));//Imprimir Tarea!
		
		taskYIELD();
	}
}

void tarea_recibe( void *pvParameters )
{		
	
	portBASE_TYPE xStatus;
	
	xData estructura_recibida;
	
	while(1)
	{
		if( uxQueueMessagesWaiting( xQueue ) != 3 )
			enviar_string_uart0((u8 *)("La cola debe estar llena!\r\n" ));
		
		xStatus = xQueueReceive( xQueue, &estructura_recibida, 0 );
				
		if( xStatus == pdPASS )
		{
			
			if(estructura_recibida.ucSource==TX_1)
			{
				enviar_string_uart0((u8 *)("Dato recibido de Tx 1: \n"));
				envia_u8_string_uart0(estructura_recibida.ucValue);
				enviar_string_uart0((u8 *)("\r\n"));
			}
			else
			{
				enviar_string_uart0((u8 *)("Dato recibido de Tx 2: \n"));
				envia_u8_string_uart0(estructura_recibida.ucValue);
				enviar_string_uart0((u8 *)("\r\n"));
			}			
		}
		else
		{
			/* Data was not received from the queue even after waiting for 100ms.
			This must be an error as the sending tasks are free running and will be
			continuously writing to the queue. */
			enviar_string_uart0((u8 *)("No pudo recibirse datos desde la cola.\r\n" ));
		}			
	}
}


int main()
{						
	portBASE_TYPE ret;	
	static const xData datos_a_enviar[ 2 ] =
		{ { 100, TX_1 },
		  { 200, TX_2 }
		};
	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_9600); //9600
	
	habilitar_interrupciones();
	
	xQueue = xQueueCreate( 5, sizeof( u32 ) );	//Creo una cola de 5 datos u32
	
	if( xQueue != NULL )
	{
		
		ret=xTaskCreate( tarea_envia, (const signed char *)("Tx1"), configMINIMAL_STACK_SIZE, (void *)(&( datos_a_enviar[ 0 ] )), 2, NULL );
		if(ret== pdPASS)
			enviar_string_uart0((u8 *)("Tarea 1 creada!.\r\n" ));
		else
			enviar_string_uart0((u8 *)("Tarea 1 no pudo ser creada!.\r\n" ));
		
		ret=xTaskCreate( tarea_envia, (const signed char *)("Tx2"), configMINIMAL_STACK_SIZE,(void *)(&( datos_a_enviar[ 1 ] )), 2, NULL );
		if(ret== pdPASS)
			enviar_string_uart0((u8 *)("Tarea 2 creada!.\r\n" ));
		else
			enviar_string_uart0((u8 *)("Tarea 2 no pudo ser creada!.\r\n" ));
		
		ret=xTaskCreate( tarea_recibe, (const signed char *)("Rx"), configMINIMAL_STACK_SIZE, NULL, 1, NULL );
		if(ret== pdPASS)
			enviar_string_uart0((u8 *)("Tarea recepcion creada!.\r\n" ));
		else
			enviar_string_uart0((u8 *)("Tarea recepcion no pudo ser creada!.\r\n" ));
		
		vTaskStartScheduler();
	
	}
	else
		enviar_string_uart0((u8 *)("No pudo crear la cola.\r\n" ));
	
	
	while(1);
	
}

Para el próximo mensaje ya vamos a empezar a meternos con las interrupciones y el uso de otros elementos para sincronizar tareas llamados semáforos.
 

Adjuntos

  • ejemplo 11 - heap_4.c y heap 5kBytes - Colas usando estructuras y alternando prioridad.zip
    767.8 KB · Visitas: 25
Se los puede utilizar para desbloquear una tarea cuando ocurre una interrupción, permitiendo la sincronización de tareas.

Por ej. en presencia de una interrupción, la rutina de interrupción tomará el control, se encargará de desbloquear una tarea que tenga mayor prioridad frente a la tarea de ejecución actual (tarea previa a la interrupción) mediante el uso de semáforos binarios.

Ejemplo de lo mencionado:

Pasar de una tarea a otra mediante una IRQ.png

Pasos:

  1. La tarea “Handler” toma el semáforo y se mantiene bloqueada hasta que ocurra un evento. Mientras tanto es “Task 1” la que se encuentra en ejecución.
  2. Al ocurrir una interrupción, en su rutina (ISR) entrega el semáforo para que la tarea “Handler” se desbloquee. Como “Handler” tiene una prioridad mayor frente a “Task 1”, pasa a ejecución desplazando a “Task 1”.
  3. Luego “Handler” se bloquea por el mismo semáforo y se repite el ciclo.

Crear un semáforo binario:

Para crear un semáforo binario se usa la función vSemaphoreCreateBinary. Su prototipo es el siguiente:

PHP:
void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );

Argumentos:

  • xSemaphore: el handler del semáforo creado.

No devuelve nada.

Tomar un semáforo:

Para tomar un semáforo se usa la función xSemaphoreTake() . Su prototipo es el siguiente:

PHP:
portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore,portTickType xTicksToWait );

Argumentos:
  • xSemaphore: el handler del semáforo a tomar.
  • xTicksToWait: la cantidad máxima de “ticks” que la tarea permanecerá bloqueada.
    • Si vale 0, desbloqueará de en inmediato la tarea.
    • Si vale portMAX_DELAY, la tarea permanecerá bloqueada indefinidamente hasta que se produzca el evento y permita tomar el semáforo.
Devolverá:
  • pdPASS: la tarea pudo tomar el semáforo.
  • pdFALSE: la tarea no pudo tomar el semáforo.

Entregar un semáforo desde una rutina de interrupción:

Para entregar un semáforo desde una ISR se usa la función xSemaphoreGiveFromISR(). Su prototipo es el siguiente:

PHP:
portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore,portBASE_TYPE *pxHigherPriorityTaskWoken);

Argumentos:
  • xSemaphore: el handler del semáforo a entregar.
  • pxHigherPriorityTaskWoken: en caso de haber dos o más tareas bloqueadas por el mismo semáforo, permite entregar el semáforo a la tarea de mayor prioridad.
Devolverá:
  • pdPASS: la ISR pudo entregar el semáforo.
  • pdFAIL: el semáforo ya estaba disponible, no pudo ser entregado nuevamente por la ISR.

Ejemplo gráfico de como funciona un semáforo binario:

Funcionamiento del semáforo binario.png

Ejemplo 12:

Utilizar un semáforo binario para desbloquear una tarea debido a la presencia de un evento en una interrupción.

Para el ejemplo en DOS, el autor de FreeRtos crea una interrupción por soft (diriamos virtual) cada 500mS mediante el uso de una tarea.

Tarea que simula una interrupción:

PHP:
static void vPeriodicTask( void *pvParameters )
{
  for( ;; )
    {
    /* This task is just used to 'simulate' an interrupt by generating a
    software interrupt every 500ms. */
    vTaskDelay( 500 / portTICK_RATE_MS );
   
    /* Generate the interrupt, printing a message both before and after
   so the sequence of execution is evident from the output produced when
   the example is executed. */
   vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
   __asm{ int 0x82 } /* This line generates the interrupt.*/
   vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
   }
}

Cuando suba el ejemplo en Cortex-M3 no voy usar una tarea que simule una interrupción, sino que voy a usar una interrupción real mediante el uso de un timer.

La rutina de interrupción (ISR):

PHP:
static void __interrupt __far vExampleInterruptHandler( void )
{
  static portBASE_TYPE xHigherPriorityTaskWoken;
  xHigherPriorityTaskWoken = pdFALSE;

  /* 'Give' the semaphore to unblock the task. */
  xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
  if( xHigherPriorityTaskWoken == pdTRUE )
    {
      /* Giving the semaphore unblocked a task, and the priority of the
      unblocked task is higher than the currently running task - force
      a context switch to ensure that the interrupt returns directly to
      the unblocked (higher priority) task.
  
      NOTE: The actual macro to use to force
      a context switch from an
      ISR is dependent on the port. This is
      the correct macro for the
      Open Watcom DOS port. Other ports may
      require different syntax.
      Refer to the examples provided for the
      port being used to determine
      the syntax required. */
      portSWITCH_CONTEXT();
   }
}

La función portSWITCH_CONTEXT() se encarga de que al finalizar la ISR vuelva inmediatamente a la tarea desbloqueada. En Cortex-M3 se utiliza otra función.

Tarea que tomará el semáforo binario:

PHP:
static void vHandlerTask( void *pvParameters )
{
  /* As per most tasks, this task is implemented within an infinite loop. */
  for( ;; )
   {
     /* Use the semaphore to wait for an event. The semaphore was created
     before the scheduler was started so before this task ran for the first
     time. The task blocks indefinitely so the function call will only
     return once the semaphore has been successfully taken (obtained). There
     is therefore no need to check the function return value. */
     xSemaphoreTake( xBinarySemaphore, portMAX_DELAY ); // El semáforo bloquea hasta que ocurra un evento, NO HAY time-out!.

     /* To get here the event must have occurred. Process the event.
     In this case processing is simply a matter of printing out a message. */
     vPrintString( "Handler task - Processing event.\r\n" );
   }
}

Main:

PHP:
xSemaphoreHandle xBinarySemaphore;

int main( void )
{
  /* Before a semaphore is used it must be explicitly created. In this example a binary semaphore is created.*/
  vSemaphoreCreateBinary( xBinarySemaphore );

  /* Install the interrupt handler. */
  _dos_setvect( 0x82, vExampleInterruptHandler );

  /* Check the semaphore was created successfully. */
  if( xBinarySemaphore != NULL )
    {
      /* Create the 'handler' task. This is the task that will be synchronized
      with the interrupt. The handler task is created with a high priority to
      ensure it runs immediately after the interrupt exits. In this case a
      priority of 3 is chosen. */
      xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
      
      /* Create the task that will periodically generate a software interrupt.
      This is created with a priority below the handler task to ensure it will
      get pre-empted each time the handler task exits the Blocked state. */
      xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );
    }

  /* Start the scheduler so the created tasks start executing. */
  vTaskStartScheduler();

  /* If all is well then main() will never reach here as the scheduler will
  now be running the tasks. If main() does reach here then it is likely that
  there was insufficient heap memory available for the idle task to be created.
  CHAPTER 5 provides more information on memory management. */
  for( ;; );
}

Salida en DOS:

Salida en DOS.png

Comportamiento temporal:

Comportamiento temporal.png

Se observa que:

  1. Luego del arranque, la tarea "handler" toma el semáforo y se bloquea, toma el control la tarea "periódica" que también se bloquea por 500mS, quedando el control en la tarea IDLE.
  2. Al pasar los 500mS, se envía una señal de interrupción por software.
  3. Inmediatamente toma el control la ISR, entregando el semáforo.
  4. Al finalizar la ISR, retorna a la tarea "handler", la cual imprime el mensaje y se bloquea al tomar nuevamente el semáforo.
  5. Toma el control la tarea "periódica" hasta que se vuelve a bloquear por 500mS, quedando el control en la tarea IDLE.
  6. Se repite el ciclo desde 2.

Código en Cortex-M3:

Rutina de interrupción del timer0 (ISR):

PHP:
//--------- Rutina de la interrupcion timer0 ---------------------// 
void TIMER0_IRQHandler (void)  
{ 
	static portBASE_TYPE xHigherPriorityTaskWoken;
	
	flag_timer0=1; 
	 
	LPC_TIM0->IR=1; // Limpio la interrupción por match0 --> Pag. 493
	
	//---------- FreeRTOS -> semáforo binario -----------------//
	
	xHigherPriorityTaskWoken = pdFALSE;
	
	xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
	
	if( xHigherPriorityTaskWoken == pdTRUE )
	{
		/* Giving the semaphore unblocked a task, and the priority of the
		unblocked task is higher than the currently running task - force
		a context switch to ensure that the interrupt returns directly to
		the unblocked (higher priority) task.
		*/
		vPortYieldFromISR();
	}
	//---------- FreeRTOS -> semáforo binario -----------------//
} 
//--------- Rutina de la interrupcion timer0 ---------------------//

En Cortex-M3 en vez de usar la función portSWITCH_CONTEXT() se usa vPortYieldFromISR(). La rutina es similar al ejemplo en DOS, solo que la interrupción es real.

Main:

PHP:
#include <LPC17xx.H>

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;

xSemaphoreHandle xBinarySemaphore;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

void tarea_timer0( void *pvParameters )
{				
	while(1)
	{				
		xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
		
		enviar_string_uart0((u8 *)("Se registro un evento!\r\n"));//Imprimir Tarea!				
	}
}


int main()
{						
	portBASE_TYPE ret;	
		
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
			
	configurar_uart0(UART_115200); //115200bps
	configura_timer0(25000,500);	//Interrupción c/500mSeg

	habilitar_interrupciones();
	
	vSemaphoreCreateBinary( xBinarySemaphore );
	
	if( xBinarySemaphore != NULL )
	{
		
		ret=xTaskCreate( tarea_timer0, (const signed char *)("Timer0"), configMINIMAL_STACK_SIZE, NULL, 3, NULL );
		if(ret== pdPASS)
			enviar_string_uart0((u8 *)("Tarea Timer0 creada!.\r\n" ));
		else
			enviar_string_uart0((u8 *)("Tarea Timer0 no pudo ser creada!.\r\n" ));
				
		
		vTaskStartScheduler();
	
	}
	else
		enviar_string_uart0((u8 *)("No pudo crear el semaforo.\r\n" ));
	
	
	while(1);
	
}

Lo más destacado:

  • Es necesario incluir la librería "semphr.h".
  • En caso de no crearse el semáforo, el puntero del handler tomará el valor NULL.
  • Se configura el Timer 0 para que se produzca una interrupción c/500mS.
  • A diferencia del ejemplo en DOS, en este ejemplo solo se creó una tarea, la cual se desbloqueará c/500mS. Se podría crear una tarea periódica como en el ejemplo de DOS para ver mejor como una tarea de menor prioridad es adelantada (pre-empt) por otra cuando se produce un evento.

En el próximo mensaje vamos a ver semáforos con contadores.
 

Adjuntos

  • ejemplo 12 - heap_4.c y heap 5kBytes - Semaforo binario en ISR.zip
    761.6 KB · Visitas: 34
El gran problema que tienen los semáforos binarios que vimos, es que solo permiten manejar un evento a la vez durante la ejecución de la “Handler Task”, ejemplo:

Semáforo_binario_1_evento.png

  • La “Handler Task” se bloquea debido al semáforo a la espera de un evento.
  • Se produce el evento, salta la interrupción y entrega el semáforo.
  • La “Handler Task” toma el semáforo y durante su ejecución (procesamiento del evento), se produce un nuevo evento que provoca otra interrupción.
  • Esta nueva interrupción entrega nuevamente el semáforo. Finaliza la rutina de interrupción volviendo a la previa ejecución de la “Handler Task”
  • Luego de terminar el procesamiento del primer evento, la “Handler Task” intenta bloquearse a la espera de un nuevo evento, pero como ese evento ya ocurrió y la interrupción ya había entregado el semáforo, la “Handler Task” vuelve a tomar el semáforo y empieza a procesar este nuevo evento sin haberse bloqueado.

Este sería el caso límite donde un semáforo binario puede tomar todos los eventos sin perder ninguno durante la ejecución de la “Handler Task”. En resumen, un semáforo binario solo sirve cuando la frecuencia de los eventos es baja.

Sin embargo, si durante la ejecución de la “Handler Task” suceden más de un evento, irremediablemente con un semáforo binario se estarían perdiendo uno o más eventos.

Para remediar ese inconveniente, se utiliza un semáforo con contador, que permite contar la cantidad de eventos que se produjeron durante la ejecución de la “Handler Task”, ejemplo:

Semáforo_con_contador_eventos.png

  • La “Handler Task” se bloquea a la espera de un evento debido a que el contador de eventos del semáforo es “0”.
  • Se produce el evento, salta la interrupción y entrega el semáforo. Esto provoca que la cuenta del semáforo pase a “1”.
  • La “Handler Task” toma el semáforo, decrementando la cuenta de eventos en el semáforo nuevamente a “0” y se ejecuta.
  • Durante su ejecución, ocurren dos eventos, es decir que la rutina de interrupción se ejecutó dos veces, provocando por c/vez un incremento en el contador de eventos.
  • Al terminar de procesar el primer evento, la “Handler Task” intenta bloquearse a la espera de un nuevo evento, pero como esos eventos ya ocurrieron y el contador de eventos del semáforo se encuentra en “2”, toma de inmediato el semáforo, decrementando la cuenta de evento del semáforo a “1” y procesa el segundo evento.
  • Al terminar de procesar el segundo evento, nuevamente la “Handler Task” toma el semáforo, decrementa su cuenta en “0” y procesa el tercer evento.

Resumiendo, los semáforos con contadores permiten procesar varios eventos mientras la “Handler Task” todavía se encuentra en ejecución.

Crear un semáforo con contador:

Para crear un semáforo con contador se usa la función xSemaphoreCreateCounting. Su prototipo es el siguiente:

PHP:
xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,unsigned portBASE_TYPE uxInitialCount );

  • uxMaxCount: la máxima cantidad de eventos que contará el semáforo.
  • uxInitialCount: la cuenta de eventos inicial que tendrá el semáforo.

Devolverá:

  • xSemaphore: el handler del semáforo creado.

Estos semáforos se entregan y se toman usando las mismas funciones que se vió con los semáforos binarios, solo cambia la creación del semáforo.

Ejemplo 13:

Implementar el ejemplo anterior utilizando semáforos con contadores.

Las modificaciones en base al ejemplo anterior serán:

Main

PHP:
/* Before a semaphore is used it must be explicitly created. In this example
a counting semaphore is created. The semaphore is created to have a maximum
count value of 10, and an initial count value of 0. */

xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );
...

Rutina de interrupción

PHP:
static void __interrupt __far vExampleInterruptHandler( void )
{
  static portBASE_TYPE xHigherPriorityTaskWoken;
  xHigherPriorityTaskWoken = pdFALSE;

  /* 'Give' the semaphore multiple times. The first will unblock the handler
  task, the following 'gives' are to demonstrate that the semaphore latches
  the events to allow the handler task to process them in turn without any
  events getting lost. This simulates multiple interrupts being taken by the
  processor, even though in this case the events are simulated within a single
  interrupt occurrence.*/
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );

  if( xHigherPriorityTaskWoken == pdTRUE )
    {
    /* Giving the semaphore unblocked a task, and the priority of the
    unblocked task is higher than the currently running task - force
    a context switch to ensure that the interrupt returns directly to
    the unblocked (higher priority) task.
   
    NOTE: The actual macro to use to force
    a context switch from an
    ISR is dependent on the port. This is
    the correct macro for the
    Open Watcom DOS port. Other ports may
    require different syntax.
    Refer to the examples provided for the
    port being used to determine
    the syntax required. */

    portSWITCH_CONTEXT();
    }
}

A diferencia del ejercicio anterior, se simulan 3 eventos por c/interrupción.

Salida de DOS:

Salida DOS.png

En la salida se puede ver como la "Handler Task" se ejecuta 3 veces debido a los 3 eventos.

El código en Cortex-M3 tiene modificaciones similares, por lo tanto no vale la pena subir el código al foro. Si subiré los archivos fuente del ejemplo.

En el próximo mensaje vamos a ver el uso de colas durante la ejecución de una rutina de interrupción.
 

Adjuntos

  • ejemplo 13 - heap_4.c y heap 5kBytes - Semaforo con contador en ISR.zip
    762.8 KB · Visitas: 26
Cosme, muy, pero muy bueno este tutorial! una lástima que no lo hayas continuado. Evidentemente en su momento no había muchas respuestas... de casualidad tendrás algo de freeRTOS con ejemplos, como lo que estabas haciendo acá?

Saludos!
 
Atrás
Arriba