Kernel FreeRTOS. Guía para Desarrolladores
Kernel FreeRTOS. Guía para Desarrolladores
Amazon's trademarks and trade dress may not be used in connection with any product or service that is not Amazon's,
in any manner that is likely to cause confusion among customers, or in any manner that disparages or discredits
Amazon. All other trademarks not owned by Amazon are the property of their respective owners, who may or may not
be affiliated with, connected to, or sponsored by Amazon.
Kernel FreeRTOS Guía para desarrolladores
Table of Contents
Acerca del kernel FreeRTOS .............................................................................................................. 1
Propuesta de valor ..................................................................................................................... 1
Nota acerca de la terminología ..................................................................................................... 1
Razones para utilizar un kernel en tiempo real ................................................................................ 2
Características del kernel FreeRTOS ............................................................................................. 3
Licencias ................................................................................................................................... 4
Archivos y proyectos originales incluidos ........................................................................................ 4
Distribución del kernel FreeRTOS ........................................................................................................ 5
Descripción de la distribución del kernel FreeRTOS ......................................................................... 5
Creación del kernel FreeRTOS ..................................................................................................... 5
FreeRTOSConfig.h ...................................................................................................................... 5
La distribución del kernel FreeRTOS oficial .................................................................................... 5
Los principales directorios de la distribución de FreeRTOS ............................................................... 6
Archivos de origen de FreeRTOS comunes para todos los puertos ..................................................... 6
Archivos de origen de FreeRTOS específicos de un puerto ............................................................... 7
Archivos de encabezado .............................................................................................................. 8
Aplicaciones de demostración .............................................................................................. 9
Creación de una proyecto de FreeRTOS ...................................................................................... 10
Creación de un proyecto nuevo a partir de cero ............................................................................ 11
Guía de tipos de datos y estilo de codificación .............................................................................. 12
Nombres de variables ................................................................................................................ 12
Nombre de funciones ................................................................................................................ 12
Formato ................................................................................................................................... 12
Nombres de macro ................................................................................................................... 12
Lógica del exceso de conversiones de tipos ................................................................................. 13
Administración de la memoria en montón ............................................................................................ 14
Requisitos previos ..................................................................................................................... 14
Asignación de memoria dinámica y su relevancia para FreeRTOS .................................................... 14
Opciones para la asignación de memoria dinámica ........................................................................ 15
Métodos de asignación de memoria de ejemplo .................................................................... 15
Heap_1 .................................................................................................................................... 15
Heap_2 .................................................................................................................................... 16
Heap_3 .................................................................................................................................... 18
Heap_4 .................................................................................................................................... 18
Configuración de una dirección de inicio para la matriz usada por Heap_4 ......................................... 20
Heap_5 .................................................................................................................................... 20
La función de API vPortDefineHeapRegions() ............................................................................... 20
Funciones de utilidades relacionadas con el montón .............................................................. 24
La función de API xPortGetFreeHeapSize() .................................................................................. 24
La función de API xPortGetMinimumEverFreeHeapSize() ................................................................ 25
Funciones de enlace erróneas de malloc ...................................................................................... 25
Administración de tareas .................................................................................................................... 26
Las funciones de las tareas ........................................................................................................ 26
Los estados de las tareas de nivel superior .................................................................................. 27
Creación de tareas ................................................................................................................... 28
La función de API xTaskCreate() ........................................................................................ 28
Creación de tareas (ejemplo 1) ........................................................................................... 29
Uso del parámetro de tarea (ejemplo 2) ............................................................................... 32
Prioridades de las tareas ........................................................................................................... 34
La medida del tiempo y la interrupción de ciclo ............................................................................. 35
Experimentación con prioridades (ejemplo 3) ........................................................................ 36
Ampliación del estado No está en ejecución ................................................................................. 38
El estado Bloqueado ......................................................................................................... 38
El estado suspendido ........................................................................................................ 39
iii
Kernel FreeRTOS Guía para desarrolladores
iv
Kernel FreeRTOS Guía para desarrolladores
v
Kernel FreeRTOS Guía para desarrolladores
vi
Kernel FreeRTOS Guía para desarrolladores
vii
Kernel FreeRTOS Guía para desarrolladores
Propuesta de valor
El kernel FreeRTOS es ideal para aplicaciones en tiempo real profundamente integradas que utilizan
microcontroladores o pequeños microprocesadores. Normalmente, este tipo de aplicaciones incluyen una
combinación de requisitos "duros" y "blandos" en tiempo real.
Los requisitos "blandos" en tiempo real son los que establecen un plazo de tiempo, pero aunque se
incumpla el plazo, el sistema puede seguir funcionando. Por ejemplo, si la respuesta a las pulsaciones de
teclas es muy lenta, es posible que ese problema haga que el sistema sea incómodo de usar, pero sigue
funcionando.
Los requisitos "duros" en tiempo real son los que establecen un plazo de tiempo y, si se incumple ese
plazo, se generaría un error absoluto del sistema. Por ejemplo, si un airbag de conductor responde con
demasiada lentitud a las entradas de los sensores de choque, podría hacer más mal que bien.
El kernel FreeRTOS es un kernel en tiempo real (o un programador en tiempo real) sobre el que se pueden
construir aplicaciones integradas para satisfacer los requisitos "duros" en tiempo real. Permite organizar
las aplicaciones como una colección de subprocesos de ejecución independientes. En un procesador
que solo tiene un núcleo, solo se puede ejecutar un subproceso al mismo tiempo. El kernel decide qué
subproceso se debe ejecutar examinando la prioridad que ha asignado a cada subproceso el diseñador de
la aplicación. En el caso más sencillo, el diseñador de la aplicación podría asignar prioridades más altas a
los subprocesos que implementan los requisitos "duros" en tiempo real y las prioridades más bajas a los
subprocesos que implementan requisitos "blandos" en tiempo real. Esto garantizaría que los subprocesos
"duros" en tiempo real se ejecuten siempre antes que los subprocesos "blandos" en tiempo real, aunque
las decisiones sobre la asignación de prioridades no son siempre tan simplistas.
No se preocupe si aún no comprende del todo los conceptos del apartado anterior. En esta guía, se
explican detalladamente y se proporcionan muchos ejemplos que le ayudarán a entender cómo utilizar un
kernel en tiempo real y el kernel FreeRTOS en particular.
Propuesta de valor
El éxito global sin precedentes del kernel FreeRTOS se debe a su atractiva propuesta de valor. El kernel
FreeRTOS está desarrollado con profesionalidad, se ha sometido a un control de calidad estricto, es
robusto, tiene soporte, no contiene ambigüedades sobre la propiedad intelectual y es realmente de uso
gratuito en aplicaciones comerciales sin el requisito de exponer el código fuente propietario. Puede
comercializar un producto con el kernel FreeRTOS sin pagar ninguna tasa, y miles de personas ya lo
están haciendo. Si, en cualquier momento, desea recibir soporte adicional o si su equipo jurídico requiere
garantías adicionales o una cláusula de indemnización por escrito, hay una ruta de actualización comercial
muy sencilla de bajo costo. Puede trabajar con la tranquilidad de saber que puede utilizar esa ruta
comercial en el momento que lo desee.
1
Kernel FreeRTOS Guía para desarrolladores
Razones para utilizar un kernel en tiempo real
La priorización de tareas ayuda a garantizar que una aplicación cumpla sus plazos de procesamiento, pero
un kernel puede tener también otros beneficios que no son tan evidentes:
El kernel es responsable del tiempo de ejecución y proporciona una API relacionada con el tiempo a
la aplicación. Esto permite que la estructura del código de la aplicación sea más sencilla y que tamaño
general del código sea más reducido.
• Capacidad de mantenimiento/ampliación
Al abstraer los detalles del tiempo, se obtienen menos interdependencias entre los módulos y el software
puede evolucionar de una forma controlada y predecible. Además, el kernel es responsable del tiempo, por
lo que el rendimiento de la aplicación es menos susceptible a sufrir cambios en el hardware subyacente.
• Modularidad
Las tareas son módulos independientes, cada uno de los cuales debe tener un objetivo bien definido.
• Desarrollo en equipo
Las tareas también deben tener interfaces bien definidas, lo que facilita el desarrollo a los equipos.
Si las tareas son módulos independientes bien definidos con interfaces claras, se pueden probar de
manera aislada.
• Mayor eficacia
El uso de un kernel permite que el software pueda basarse por completo en eventos, por lo que no se
desperdicia ningún tiempo de procesamiento sondeando eventos que no se han producido. El código solo
se ejecuta cuando hay algo que debe hacerse.
Algo que afecta negativamente a la eficacia es la necesidad de procesar la interrupción de ciclos de RTOS
y cambiar la ejecución de una tarea a otra. Sin embargo, las aplicaciones que no utilizan un RTOS suelen
incluir algún tipo de interrupción de ciclos.
• Tiempo de inactividad
2
Kernel FreeRTOS Guía para desarrolladores
Características del kernel FreeRTOS
• Administración de energía
La mejora de la eficiencia gracias al uso de un RTOS permite que el procesador esté más tiempo en un
modo de bajo consumo.
Los controladores de interrupciones pueden hacerse muy cortos retrasando el procesamiento a una tarea
creada por el programador de aplicaciones o a una tarea de demonio de FreeRTOS.
3
Kernel FreeRTOS Guía para desarrolladores
Licencias
• Pila de interrupciones administrada por software cuando es apropiado (esto puede ayudar a ahorrar
RAM)
Licencias
El kernel FreeRTOS está disponible para los usuarios en virtud de los términos de la licencia MIT.
Las capturas de pantalla incluidas en este libro se realizaron ejecutando los ejemplos en un entorno de
Microsoft Windows, utilizando el puerto de Windows de FreeRTOS. El proyecto que utiliza el puerto de
Windows de FreeRTOS está preconfigurado para compilarse con la edición Express gratuita de Visual
Studio, que se puede descargar de https://www.visualstudio.com/vs/community/. Aunque el puerto de
Windows de FreeRTOS proporciona una plataforma de evaluación, prueba y desarrollo muy cómoda, no
refleja el verdadero comportamiento en tiempo real.
4
Kernel FreeRTOS Guía para desarrolladores
Descripción de la distribución del kernel FreeRTOS
FreeRTOS se suministra como un conjunto de archivos de origen C. Algunos de los archivos de origen son
comunes para todos los puertos, mientras que otros son específicos de un puerto. Compile los archivos
de origen como parte de su proyecto para que la API de FreeRTOS esté disponible para su aplicación.
Para facilitarle esta tarea, todos los puertos FreeRTOS oficiales se proporcionan con una aplicación de
demostración. La aplicación de demostración está preconfigurada para compilar los archivos de origen
correctos e incluir los archivos de encabezado correctos.
Las aplicaciones de demostración deben compilarse tal y como se entregan. Si, por el contrario, se efectúa
un cambio en las herramientas de compilación desde que se lanzó la demostración, pueden generarse
problemas. Para obtener más información, consulte Aplicaciones de demostración más adelante en este
tema.
FreeRTOSConfig.h
FreeRTOS se configura con un archivo de encabezado llamado FreeRTOSConfig.h.
FreeRTOSConfig.h se utiliza para adaptar FreeRTOS para usarlo en una aplicación específica.
Por ejemplo, FreeRTOSConfig.h contiene constantes como configUSEPREEMPTION, cuya
configuración define si se usará el algoritmo de programación de reemplazo o programación cooperativa.
FreeRTOSConfig.h contiene definiciones específicas de aplicaciones, por lo que debería estar en un
directorio que forme parte de la aplicación que se crea, no en un directorio que contenga el código fuente
de FreeRTOS.
Se proporciona una aplicación de demostración para cada puerto FreeRTOS y cada aplicación de
demostración contiene un archivo FreeRTOSConfig.h. Por lo tanto, no necesitará nunca crear un archivo
FreeRTOSConfig.h desde cero. En su lugar, le recomendamos que comience con el FreeRTOSConfig.h
que usa la aplicación de demostración proporcionada y lo adapte para el puerto FreeRTOS en uso.
5
Kernel FreeRTOS Guía para desarrolladores
Los principales directorios de la distribución de FreeRTOS
También contiene una selección de componentes del ecosistema de FreeRTOS+ y una selección de
aplicaciones de demostración del ecosistema de FreeRTOS+.
FreeRTOS
││
││
FreeRTOS-Plus
├─Directorio de origen que contiene código fuente para algunos componentes del ecosistema FreeRTOS+.
└─Directorio de demostración que contiene proyectos de demostración para componentes del ecosistema
FreeRTOS+.
El archivo zip contiene solo una copia de los archivos de origen de FreeRTOS, todos los proyectos de
demostración de FreeRTOS y todos los proyectos de demostración de FreeRTOS+. Debe encontrar
los archivos de origen de FreeRTOS en el directorio FreeRTOS/Source. Puede que los archivos no se
compilen si la estructura de directorios ha cambiado.
• queue.c
6
Kernel FreeRTOS Guía para desarrolladores
Archivos de origen de FreeRTOS específicos de un puerto
• eventgroups.c
eventgroups.c proporciona una funcionalidad de grupos de eventos. Debe incluirlo en la compilación solo
si se van a usar grupos de eventos.
• croutine.c
FreeRTOS
└─Origen
Se ha demostrado que los nombres de archivo podrían dar lugar a problemas por el espacio de nombres,
ya que muchos proyectos ya incluirán archivos con los mismos nombres. Sin embargo, cambiar los
nombres de los archivos ahora sería problemático, ya que se acabaría con la compatibilidad de miles de
proyectos que utilizan FreeRTOS, así como con herramientas de automatización y complementos de IDE.
FreeRTOS proporciona cinco métodos de asignación de montón de ejemplo. Los cinco métodos reciben
nombres correlativos entre heap1 y heap5, y los implementan respectivamente los archivos de origen
heap1.c a heap5.c. Los métodos de asignación de montón de ejemplo se encuentran en el directorio
7
Kernel FreeRTOS Guía para desarrolladores
Archivos de encabezado
En la figura siguiente se muestran los archivos de origen específicos de los puertos en el árbol de
directorios de FreeRTOS.
FreeRTOS
└─Origen
└─Directorio portátil que contiene todos los archivos de origen específicos de puerto
├─Directorio MemMang que contiene los 5 archivos de origen de asignación de montón alternativos
││
└─[etc.]
Rutas de inclusión
FreeRTOS requiere que se incluyan tres directorios en la ruta de inclusión del compilador:
Archivos de encabezado
Un archivo de origen que utiliza la API FreeRTOS debe incluir "FreeRTOS.h", seguido del archivo de
encabezado que contiene el prototipo para la función de API que se usa, ya sea "task.h", "queue.h",
"semphr.h", "timers.h" o "eventgroups.h".
8
Kernel FreeRTOS Guía para desarrolladores
Aplicaciones de demostración
Aplicaciones de demostración
Cada puerto FreeRTOS incluye como mínimo una aplicación de demostración que debe compilarse sin
errores ni advertencias, aunque algunas demostraciones son más antiguas que otras y, en ocasiones,
un cambio en las herramientas de compilación creadas desde el lanzamiento de la demostración pueda
provocar un problema.
Nota para usuarios de Linux: FreeRTOS se desarrolla y prueba en un host de Windows. En ocasiones
esto crea errores de compilación cuando se compilan proyectos de demostración en un host de Linux.
Los errores de compilación casi siempre están relacionados con el uso de mayúsculas o minúsculas
en las letras que se utilizan para hacer referencia a los nombres de archivos, o la dirección de la barra
inclinada que se usa en las rutas de archivos. Utilice el formulario de contacto de FreeRTOS (http://
www.FreeRTOS.org/contact) para alertarnos de cualquiera de estos errores.
• Proporcionar un ejemplo de un proyecto configurado previamente que funciona que tiene los archivos
correctos incluidos y el conjunto de opciones de compilador correcto.
• Permitir una experimentación directa con una configuración o conocimientos previos mínimos.
• Como demostración de cómo la API de FreeRTOS puede utilizarse.
• Como base a partir de la cual pueden crearse aplicaciones reales.
Cada aplicación de demostración también se describe en una página web en el sitio web FreeRTOS.org.
La página web proporciona información acerca de:
Los proyectos de demostración más recientes también pueden crear un proyecto "blinky" para
principiantes. Los proyectos blinky son muy básicos. Por lo general crean solo dos tareas y una cola.
Todos los proyectos de demostración incluyen un archivo llamado main.c. Esto contiene la función main(),
desde donde se crean las tareas de la aplicación de demostración. Consulte los comentarios de los
archivos main.c individuales para recibir información específica de esa demostración.
FreeRTOS
9
Kernel FreeRTOS Guía para desarrolladores
Creación de una proyecto de FreeRTOS
Seguir estos pasos creará un proyecto que incluye los archivos de origen de FreeRTOS correctos, pero no
definirá ninguna funcionalidad.
prvSetupHardware();
vTaskStartScheduler();
/* Execution will only reach here if there was insufficient heap to start the scheduler. */
for( ;; );
return 0;
10
Kernel FreeRTOS Guía para desarrolladores
Creación de un proyecto nuevo a partir de cero
1. Utilizando la cadena de herramientas que prefiera, cree un nuevo proyecto que todavía no incluya
archivos de origen de FreeRTOS.
2. Asegúrese de que el nuevo proyecto se pueda compilar, descargarse en el hardware de destino y
ejecutarse.
3. Solo cuando esté seguro del proyecto de trabajo es funcional, añada los archivos de origen de
FreeRTOS que se detallan en la tabla 1 del proyecto.
4. Copie el archivo de encabezado FreeRTOSConfig.h utilizado por el proyecto de demostración que se
proporciona para el puerto en uso en el directorio del proyecto.
5. Añada los directorios siguientes a la ruta donde el proyecto realizará búsquedas para localizar archivos
de encabezado:
• FreeRTOS/Source/include
• FreeRTOS/Source/portable/[compilador]/[arquitectura] (donde
En la tabla siguiente se muestra una lista de los archivos de origen de FreeRTOS que se van a incluir en el
proyecto.
Archivo Ubicación
tasks.c FreeRTOS/Source
queue.c FreeRTOS/Source
list.c FreeRTOS/Source
timers.c FreeRTOS/Source
eventgroups.c FreeRTOS/Source
Los proyectos que utilizan una versión de FreeRTOS anterior a la V9.0.0 deben compilar uno
de los archivos heapn.c. A partir de FreeRTOS V9.0.0, solo se necesita un archivo heapn.c
11
Kernel FreeRTOS Guía para desarrolladores
Guía de tipos de datos y estilo de codificación
Algunos compiladores convierten a todas las variables char no cualificadas en variables sin signo, mientras
que otros las convierten en variables con signo. Por este motivo, el código fuente de FreeRTOS califica
explícitamente todos los usos de char con la mención "signed" o "unsigned", a menos que el char se use
para almacenar un carácter ASCII o un puntero a char se use para apuntar a una cadena.
Nombres de variables
Las variables llevan un prefijo que indica su tipo: "c" para char, "s" para int16t (breve), "l" para int32t (largo)
y "x" para BaseTypet y cualquier otro tipo no estándar (estructuras, controladores de tareas, controladores
de cola, etc.).
Si una variable no tiene signo, se le asigna el prefijo "u". Si una variable es un puntero, se le asigna el
prefijo "p". Por ejemplo, una variable de tipo uint8t llevará el prefijo "uc" y una variable de tipo puntero a
char llevará el prefijo "pc".
Nombre de funciones
Las funciones tienen un prefijo que indica tanto el tipo que devuelven como el archivo donde están
definidas. Por ejemplo:
Formato
Una tabulación se establece siempre en cuatro espacios.
Nombres de macro
La mayoría de las macros están escritas en mayúsculas y llevan un prefijo en minúsculas que indica dónde
está definida la macro.
12
Kernel FreeRTOS Guía para desarrolladores
Lógica del exceso de conversiones de tipos
La API de semáforo está escrita prácticamente toda como un conjunto de macros, pero sigue la
convención de nomenclatura de funciones en lugar de la convención de nomenclatura de macros.
En la tabla siguiente se muestra una lista de las macros usadas en el código fuente de FreeRTOS.
Macro Valor
pdTRUE 1
pdFALSE 0
pdPASS 1
pdFAIL 0
13
Kernel FreeRTOS Guía para desarrolladores
Requisitos previos
Administración de la memoria en
montón
A partir de la versión V9.0.0, las aplicaciones de FreeRTOS pueden asignarse de forma totalmente
estática; es decir, no es necesario incluir un administrador de memoria en montón.
Requisitos previos
Necesita tener buenos conocimientos de programación en C para usar FreeRTOS. En concreto tiene que
estar familiarizado con:
La memoria se puede asignar utilizando las funciones de biblioteca de C estándar malloc() y free(), pero
puede darse el caso de que no sean adecuadas o apropiadas por uno o varios de los motivos siguientes:
14
Kernel FreeRTOS Guía para desarrolladores
Opciones para la asignación de memoria dinámica
todo intento de asignar un bloque generará error si ningún bloque libre del montón es lo suficientemente
grande para contener el bloque, incluso si el tamaño total de todos los bloques libres independientes del
montón es varias veces mayor que el volumen del bloque que no se puede asignar.
• Pueden complicar la configuración del vinculador.
• Pueden ser una fuente de errores difíciles de depurar si se permite que el espacio del montón crezca en
la memoria que usan otras variables.
Ahora FreeRTOS trata la asignación de memoria como parte de la capa portátil (en lugar de tratarla
como parte de la base de código principal). Con esto se reconocen los requisitos variables de tiempo y
asignación de memoria dinámica de los sistemas integrados. Un único algoritmo de asignación de memoria
dinámica es adecuado solo para un subconjunto de aplicaciones. Además, la eliminación de la asignación
de memoria dinámica de la base de código principal permite que los programadores de aplicaciones
proporcionen sus propias implementaciones específicas cuando proceda.
Cuando FreeRTOS necesita RAM, llama a pvPortMalloc() en lugar de llamar a malloc(). Cuando se libera
RAM, el kernel llama a vPortFree() en lugar de llamar a free(). pvPortMalloc() tiene el mismo prototipo que
la función malloc() de biblioteca C estándar. vPortFree() tiene el mismo prototipo que la función free() de la
biblioteca C estándar.
pvPortMalloc() y vPortFree() son funciones públicas, por lo que también se pueden llamar desde el código
de aplicación.
Los cinco ejemplos están definidos en los archivos de origen heap_1.c, heap_2.c, heap_3.c, heap_4.c y
heap_5.c ubicados en el directorio FreeRTOS/Source/portátil/MemMang.
Heap_1
Es frecuente que los pequeños sistemas integrados dedicados creen las tareas y otros objetos de kernel
solo antes de que se inicie el programador. El kernel asigna dinámicamente la memoria antes de que la
aplicación comience a ejecutar las funcionalidades en tiempo real, y la memoria permanece asignada
durante toda la vida útil de la aplicación. Esto significa que el método de asignación elegido no ha de tener
en cuenta ninguno de los problemas de asignación de memoria más complejos, como la fragmentación y el
determinismo. En su lugar, puede considerar atributos como el tamaño o la simplicidad del código.
Heap_1.c implementa una versión muy básica de pvPortMalloc(). No implementa vPortFree(). Las
aplicaciones que nunca eliminan una tarea ni ningún otro objeto de kernel usan heap_1.
15
Kernel FreeRTOS Guía para desarrolladores
Heap_2
Algunos sistemas críticos desde el punto de vista comercial o de la seguridad que, de lo contrario,
prohibirían el uso de la asignación de memoria dinámica, podrían usar heap_1. Los sistemas críticos
a menudo prohíben la asignación de memoria dinámica debido a las incertidumbres que conllevan el
no determinismo, la fragmentación de memoria y las asignaciones erróneas, pero heap_1 siempre es
determinista y no puede fragmentar la memoria.
Cuando se realizan llamadas a pvPortMalloc(), el método de asignación de heap_1 subdivide una simple
matriz en bloques más pequeños. La matriz se denomina el montón de FreeRTOS.
Cada tarea que se crea requiere un bloque de control de la tarea (TCB) y una pila que debe asignarse
desde el montón.
En la figura siguiente se muestra cómo heap_1 subdivide una matriz sencilla a medida que se crean
tareas. La memoria RAM se asigna desde la matriz de heap_1 cada vez que se crea una tarea.
• A muestra la matriz antes de que se cree ninguna tarea. Toda la matriz está libre.
• B muestra la matriz después de que se haya creado una tarea.
• C muestra la matriz después de que se hayan creado tres tareas.
Heap_2
Heap_2 se incluye en la distribución de FreeRTOS para la compatibilidad con versiones anteriores. No se
recomienda usar heap_2 con diseños nuevos. Considere la posibilidad de utilizar heap_4 en su lugar, ya
que aporta más funcionalidades.
Heap_2.c también funciona subdividiendo una matriz cuya dimensión se define con
configTOTAL_HEAP_SIZE. Para asignar memoria usa un algoritmo de mejor opción. A diferencia de
heap_1, no permite liberar memoria. También en este caso la matriz se declara estáticamente, por lo que
puede dar la sensación de que la aplicación consume una gran cantidad de memoria RAM incluso antes
de que se asigne memoria desde la matriz.
El algoritmo de mejor opción garantiza que pvPortMalloc() utilice el bloque de memoria libre cuyo tamaño
sea el más próximo al número de bytes solicitado. Por ejemplo, piense en una situación en la que:
• El montón contenga tres bloques de memoria libre cuyo tamaño respectivo es de 5 bytes, 25 bytes y 100
bytes.
16
Kernel FreeRTOS Guía para desarrolladores
Heap_2
El bloque de RAM libre más pequeño en el que cabe el número de bytes solicitados es el bloque 25 bytes,
así que pvPortMalloc() dividirá el bloque de 25 bytes en un bloque de 20 bytes y otro de 5 bytes antes de
devolver un puntero al bloque de 20 bytes. (En este caso hemos simplificado en exceso, ya que heap_2
almacena información sobre los tamaños de bloque en el área de montón, por lo que la suma de los dos
bloques divididos será inferior a 25). El nuevo bloque de 5 bytes permanecerá disponible para futuras
llamadas a pvPortMalloc().
A diferencia de heap_4, heap_2 no combina bloques libres adyacentes en un único bloque de mayor
tamaño. Por este motivo, es más sensible a la fragmentación. Sin embargo, la fragmentación no es un
problema si los bloques que se asignan y posteriormente se liberan siempre tienen el mismo tamaño.
Heap_2 es adecuado para una aplicación que crea y elimina tareas de forma repetida, siempre y cuando el
tamaño de la pila asignada a las tareas creadas no cambie.
En la figura se muestra cómo funciona el algoritmo de mejor opción cuando se crea, se elimina y luego se
vuelve a crear una tarea.
• A muestra la matriz después de que se hayan creado tres tareas. Queda un bloque
bloque libre de gran tamaño de la parte superior de la matriz permanece. Ahora hay también
dos bloques libres más pequeños que se han asignado previamente al TCB y la pila de la tarea
eliminada.
• C muestra la matriz después de que se haya creado otra tarea. Crear
la tarea ha generado dos llamadas a pvPortMalloc(): una para asignar un nuevo TCB y otra para
asignar la pila de la tarea. Las tareas se crean mediante la función de API xTaskCreate(), que se
describe en Creación de tareas (p. 28). Las llamadas a pvPortMalloc() se producen internamente
en xTaskCreate().
Cada TCB tiene exactamente el mismo tamaño, por lo que el algoritmo de mejor opción garantiza
que el bloque de RAM asignado anteriormente al TCB de la tarea eliminada se vuelve a usar para
asignar el TCB de la nueva tarea.
17
Kernel FreeRTOS Guía para desarrolladores
Heap_3
El tamaño de la pila asignada a la tarea que acaba de crear es igual al tamaño de la pila asignada a
la tarea eliminada anteriormente, por lo que el algoritmo de mejor opción garantiza que el bloque de
RAM asignado previamente a la pila de la tarea eliminada se vuelva a usar para asignar la pila de la
nueva tarea.
El bloque sin asignar más grande de la parte superior de la matriz permanece intacto.
Heap_2 no es determinista, pero es más rápido que la mayoría de las implementaciones de biblioteca
estándar de malloc() y free().
Heap_3
Heap_3.c utiliza las funciones malloc() y free() de biblioteca estándar, por lo que el tamaño del montón
se define mediante la configuración del vinculador. El valor de configTOTAL_HEAP_SIZE no tiene ningún
efecto.
Heap_3 hace que malloc() y free() estén a salvo de subprocesos ya que suspende temporalmente
el programador de FreeRTOS. Para obtener información sobre la seguridad de los subprocesos y la
suspensión del programador, consulte la sección Administración de recursos (p. 161).
Heap_4
Al igual que heap_1 y heap_2, heap_4 subdivide una matriz en bloques más pequeños. La matriz se
declara estáticamente y su dimensión se indica mediante configTOTAL_HEAP_SIZE, por lo que puede dar
la sensación de que la aplicación consume una gran cantidad de memoria RAM incluso antes de que se
asigne memoria desde la matriz.
Heap_4 usa un algoritmo de primera opción adecuada para asignar memoria. A diferencia de heap_2,
combina (fusiona) bloques libres adyacentes de memoria en un único bloque más grande. De este modo
se minimiza el riesgo de fragmentación de la memoria.
El algoritmo de primera opción adecuada garantiza que pvPortMalloc() utilice el primer bloque de memoria
libre cuyo tamaño sea suficiente para contener el número de bytes solicitado. Por ejemplo, piense en una
situación en la que:
• El montón contiene tres bloques de memoria libre. Aparecen en este orden en la matriz: 5 bytes, 200
bytes y 100 bytes.
• Se llama a pvPortMalloc() para solicitar 20 bytes de RAM.
El primer bloque de RAM libre en el que cabe el número de bytes solicitados es el bloque de 200 bytes, así
que pvPortMalloc() dividirá el bloque de 200 bytes en un bloque de 20 bytes y otro de 180 bytes antes de
devolver un puntero al bloque de 20 bytes. (En este caso hemos simplificado en exceso, ya que heap_4
almacena información sobre los tamaños de bloques en el área de montón, por lo que la suma de los dos
bloques divididos será inferior a 200 bytes). El nuevo bloque de 180 bytes permanecerá disponible para
futuras llamadas a pvPortMalloc().
Heap_4 combina (fusiona) bloques libres adyacentes en un único bloque más grande, lo que minimiza
el riesgo de fragmentación. Heap_4 es adecuado para aplicaciones que asignan y liberan repetidamente
bloques de tamaños diferentes de RAM.
En la figura siguiente se muestra la asignación y liberación de RAM de la matriz de heap_4. Muestra cómo
funciona el algoritmo de primera opción con fusión de memoria de heap_4, a medida que se asigna y libera
memoria.
18
Kernel FreeRTOS Guía para desarrolladores
Heap_4
A muestra la matriz después de que se hayan creado tres tareas. Queda un bloque libre de gran tamaño
en la parte superior de la matriz.
B muestra la matriz después de que se haya eliminado una de las tareas. El bloque libre de gran tamaño
de la parte superior de la matriz se conserva. También hay un bloque libre que anteriormente tenía
asignados el TCB y la pila de la tarea eliminada. Tanto la memoria liberada al eliminar el TCB como la
que se ha liberado al eliminar la pila no permanecen como dos bloques libres independientes, sino que se
combinan para crear un único bloque libre y más grande.
C muestra la matriz después de que se haya creado una cola de FreeRTOS. Las colas se crean usando la
función de API xQueueCreate(), que se describe en Uso de una cola (p. 70). xQueueCreate() llama a
pvPortMalloc() para asignar la RAM que la cola usa. Dado que heap_4 usa un algoritmo de primera opción
adecuada, pvPortMalloc() asigna RAM del primer bloque de RAM libre cuyo tamaño permita almacenar la
cola. En la figura vemos que se trata de la RAM liberada al eliminar la tarea. La cola no consume toda la
RAM del bloque libre, por lo que este se divide en dos. La parte sin utilizar permanecerá disponible para
futuras llamadas a pvPortMalloc().
D muestra la matriz después de que se haya llamado directamente a pvPortMalloc() desde el código de
la aplicación en lugar de llamarlo indirectamente mediante una función de API de FreeRTOS. El bloque
asignado por el usuario era lo suficientemente pequeño para caber en el primer bloque libre, que es el
bloque que está entre la memoria asignada a la cola y la memoria asignada al TCB siguiente. Ahora la
memoria liberada al eliminar la tarea se ha dividido en tres bloques independientes. El primer bloque
contiene la cola. El segundo bloque contiene la memoria asignada por el usuario. La tercera sigue estando
libre.
E muestra la matriz después de que se haya eliminado la cola, lo que libera automáticamente la memoria
que se había asignado a la cola eliminada. Ahora hay memoria libre a ambos lados del bloque asignado
por el usuario.
F muestra la matriz después de que también se haya liberado la memoria asignada por el usuario. La
memoria que había usado el bloque asignado por el usuario se ha combinado con la memoria libre de
ambos lados para crear un bloque libre más grande.
Heap_4 no es determinista, pero es más rápido que la mayoría de las implementaciones de biblioteca
estándar de malloc() y free().
19
Kernel FreeRTOS Guía para desarrolladores
Configuración de una dirección de
inicio para la matriz usada por Heap_4
En ocasiones el programador de aplicaciones tiene que colocar la matriz que heap_4 utiliza en una
dirección de memoria específica. Por ejemplo, la pila que una tarea de FreeRTOS usa se asigna desde el
montón, por lo que puede ser necesario asegurarse de que el montón esté en una memoria interna rápida
en lugar de estar en memoria externa lenta.
De forma predeterminada, la matriz que usa heap_4 se declara dentro del archivo de origen heap_4.c. Su
dirección de inicio se establece automáticamente mediante el vinculador. Sin embargo, si la constante de
configuración del tiempo de compilación configAPPLICATION_ALLOCATED_HEAP se establece en 1 en
FreeRTOSConfig.h, la aplicación que usa FreeRTOS tendrá que declarar la matriz. Si la matriz se declara
como parte de la aplicación, el programador de la aplicación puede establecer su dirección de inicio.
La sintaxis necesaria para introducir una variable en una dirección de memoria específica depende del
compilador. Para obtener más información, consulte la documentación del compilador.
A continuación se muestra la sintaxis requerida para que el compilador GCC declare la matriz y la ponga
en una asignación de memoria llamada .my_heap:
A continuación se muestra la sintaxis requerida para que el compilador IAR declare la matriz y la ponga en
la dirección de memoria absoluta 0x20000000:
Heap_5
El algoritmo que usa heap_5 para asignar y liberar memoria es idéntico al que usa heap_4. A diferencia de
heap_4, heap_5 no se limita a asignar memoria desde una sola matriz declarada estáticamente. Heap_5
puede asignar memoria desde varios espacios de memoria independientes. Heap_5 es útil cuando la
RAM proporcionada por el sistema en el que se ejecuta FreeRTOS no se muestra como un único bloque
contiguo (sin espacios) en el mapa de memoria del sistema.
Heap_5 es el único de los métodos de asignación de memoria proporcionados que tiene que inicializarse
para poder llamar a pvPortMalloc(). Se inicializa con la función de API vPortDefineHeapRegions(). Cuando
se utiliza heap_5, hay que llamar a vPortDefineHeapRegions() antes de crear objetos de kernel (tareas,
colas, semáforos, etc.).
20
Kernel FreeRTOS Guía para desarrolladores
La función de API vPortDefineHeapRegions()
Cada área de memoria independiente se describe mediante una estructura del tipo HeapRegion_t. Se pasa
una descripción de todas las áreas de memoria disponibles a vPortDefineHeapRegions() como una matriz
de estructuras HeapRegion_t.
/* The start address of a block of memory that will be part of the heap.*/
uint8_t *pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;
Tomemos, por ejemplo, el mapa de memoria hipotético que se muestra en la figura siguiente, que contiene
tres bloques de RAM independientes: RAM1, RAM2 y RAM3. El código ejecutable se pone en memoria de
solo lectura, pero no se muestra.
21
Kernel FreeRTOS Guía para desarrolladores
La función de API vPortDefineHeapRegions()
El código siguiente muestra una matriz de estructuras HeapRegion_t. Juntas describen las los tres bloques
de RAM.
/* Define the start address and size of the three RAM regions. */
/* Create an array of HeapRegion_t definitions, with an index for each of the three RAM
regions, and terminating the array with a NULL address. The HeapRegion_t structures must
appear in start address order, with the structure that contains the lowest start address
appearing first. */
{ RAM1_START_ADDRESS, RAM1_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
};
22
Kernel FreeRTOS Guía para desarrolladores
La función de API vPortDefineHeapRegions()
/* Initialize heap_5. */
vPortDefineHeapRegions( xHeapRegions );
Aunque el código describe correctamente la RAM, no es un ejemplo útil, ya que asigna toda la RAM al
montón y no deja RAM libre para que la usen otras variables.
Cuando se crea un proyecto, la fase de vinculación del proceso de compilación asigna una dirección de
RAM a cada variable. La RAM que está disponible para que la use el vinculador normalmente se describe
mediante un archivo de configuración del vinculador, como un script de vinculador. En B, en la figura
anterior, se presupone que el script del vinculador incluía información sobre RAM1, pero no sobre RAM2
o RAM3. El vinculador, por lo tanto, ha colocado variables en RAM1 y ha dejado solo la parte de RAM1
por encima de la dirección 0x0001nnnn disponible para que heap_5 la use. El valor real de 0x0001nnnn
dependerá del tamaño combinado de todas las variables incluidas en la aplicación que se está vinculando.
El vinculador ha dejado todo RAM2 y RAM3 sin utilizar, así que están disponibles para que los use heap_5.
El código siguiente muestra un ejemplo más cómodo y mantenible. Declara una matriz denominada
ucHeap. ucHeap es una variable normal, por lo que se convierte en parte de los datos asignados a RAM1
por el vinculador. La primera estructura HeapRegion_t de la matriz xHeapRegions describe la dirección
de inicio y el tamaño de ucHeap, por lo que ucHeap se convierte en parte de la memoria que heap_5
administra. El tamaño de ucHeap puede aumentarse hasta que la RAM que utiliza el vinculador consuma
toda la RAM1, tal y como se muestra en C, en la figura anterior.
/* Define the start address and size of the two RAM regions not used by the linker. */
/* Declare an array that will be part of the heap used by heap_5. The array will be placed
in RAM1 by the linker. */
/* Create an array of HeapRegion_t definitions. Whereas in previous code listing, the first
entry described all of RAM1, so heap_5 will have used all of RAM1, this time the first
23
Kernel FreeRTOS Guía para desarrolladores
Funciones de utilidades relacionadas con el montón
entry only describes the ucHeap array, so heap_5 will only use the part of RAM1 that
contains the ucHeap array. The HeapRegion_t structures must still appear in start address
order, with the structure that contains the lowest start address appearing first. */
{ ucHeap, RAM1_HEAP_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
};
En el código anterior, una matriz de estructuras HeapRegion_t describe toda la memoria RAM2, toda la
memoria RAM3, pero solo una parte de RAM1.
24
Kernel FreeRTOS Guía para desarrolladores
La función de API xPortGetMinimumEverFreeHeapSize()
La función de API
xPortGetMinimumEverFreeHeapSize()
La función de API xPortGetMinimumEverFreeHeapSize() devuelve el número de bytes más pequeño sin
asignar que ha habido en el montón desde que comenzó a ejecutarse la aplicación de FreeRTOS.
Al igual que la función malloc() estándar de biblioteca, si pvPortMalloc() no puede devolver un bloque
de RAM porque no existe un bloque del tamaño solicitado, devolverá el valor NULL. Si se ejecuta
pvPortMalloc() porque el programador de la aplicación está creando un objeto de kernel y la llamada a
pvPortMalloc() devuelve NULL, no se creará el objeto de kernel.
Todos los métodos de asignación de montón de ejemplo se pueden configurar para llamar a una función
de enlace (o devolución de llamada) si una llamada a pvPortMalloc() devuelve NULL.
25
Kernel FreeRTOS Guía para desarrolladores
Las funciones de las tareas
Administración de tareas
Los conceptos que se introducen en esta sección son fundamentales para comprender cómo usar
FreeRTOS y como se comportan las aplicaciones de FreeRTOS. En esta sección se explica lo siguiente:
Cada tarea es un pequeño programa en sí mismo. Tiene un punto de entrada, se ejecuta normalmente de
forma continua en un bucle infinito y no se cierra.
No debe permitirse que las tareas de FreeRTOS vuelvan de ningún modo desde su función de
implementación. No deben contener una instrucción "return" ni debe permitirse que se ejecuten al finalizar
la función. Si una tarea ya no es necesaria, debe eliminarse explícitamente.
Se puede utilizar una única definición de función de tarea para crear tantas tareas como se quiera. Cada
tarea creada es una instancia de ejecución independiente, con su propia pila y su propia copia de todas las
variables (pila) automáticas definidas en la tarea misma.
/* Variables can be declared just as per a normal function. Each instance of a task created
using this example function will have its own copy of the lVariableExample variable. This
26
Kernel FreeRTOS Guía para desarrolladores
Los estados de las tareas de nivel superior
would not be true if the variable was declared static, in which case only one copy of the
variable would exist, and this copy would be shared by each created instance of the task.
(The prefixes added to variable names are described in Data Types and Coding Style Guide.)
*/
int32_t lVariableExample = 0;
for( ;; )
/* Should the task implementation ever break out of the above loop, then the task must be
deleted before reaching the end of its implementing function. The NULL parameter passed to
the vTaskDelete() API function indicates that the task to be deleted is the calling (this)
task. The convention used to name API functions is described in Data Types and Coding
Style Guide. */
vTaskDelete( NULL );
Cuando una tarea se encuentra en el estado En ejecución, el procesador está ejecutando el código de la
tarea. Cuando una tarea no se encuentra en el estado En ejecución, significa que está latente; es decir, su
estado se ha guardado y está lista para reanudar la ejecución la siguiente vez que el programador decida
que debe entrar en el estado En ejecución. Cuando una tarea reanuda su ejecución, lo hace desde la
instrucción que estaba a punto de ejecutar en el momento en que dejó de estar en el estado En ejecución.
En la figura siguiente se muestran las transiciones y los estados de las tareas de nivel superior.
Se considera que una tarea que ha pasado del estado No está en ejecución al estado En ejecución se
ha cambiado o intercambiado. Por el contrario, se considera que una tarea que ha pasado del estado En
27
Kernel FreeRTOS Guía para desarrolladores
Creación de tareas
Creación de tareas
La función de API xTaskCreate()
FreeRTOS V9.0.0 también incluye la función xTaskCreateStatic(), que asigna la memoria necesaria para
crear una tarea de forma estática durante la compilación. Las tareas se crean utilizando la función de
API xTaskCreate() de FreeRTOS. Se trata probablemente de la más compleja de todas las funciones de
API. Como las tareas son los componentes más básicos de un sistema multitarea, es preciso dominarlas
primero. En esta guía encontrará numerosos ejemplos de la función xTaskCreate().
Para obtener más información acerca de los tipos de datos y las convenciones de nomenclatura usados,
consulte la guía de tipos de datos y estilo de codificación en la sección de distribución de kernel de
FreeRTOS (p. 5).
A continuación se muestra una lista de los parámetros de xTaskCreate() y los valores de retorno.
• pvTaskCode: las tareas son simplemente funciones C que nunca finalizan y, en este sentido,
normalmente se implementan en bucle infinito. El parámetro pvTaskCode es simplemente un puntero
que señala a la función que implementa la tarea (en efecto, solo el nombre de la función).
• pcName: nombre descriptivo de la tarea. FreeRTOS no lo utiliza en modo alguno. Se incluye solo como
ayuda para la depuración. Es más sencillo identificar una tarea con un nombre legible que intentar
identificarla por su controlador. La constante definida por la aplicación configMAX_TASK_NAME_LEN
define la longitud máxima del nombre de una tarea, incluido el terminador NULL. Si suministra una
cadena más larga que este valor máximo la cadena se truncará silenciosamente.
• usStackDepth: cada tarea tiene su propia pila exclusiva que el kernel asigna a la tarea cuando esta
se crea. El valor usStackDepth indica al kernel el tamaño que ha de tener la pila. El valor especifica
el número de palabras que la pila puede contener, no su número de bytes. Por ejemplo, si la pila
tiene 32 bits de ancho y se pasa usStackDepth con un valor de 100, se asignarán 400 bytes de
espacio de pila (100* 4 bytes). La profundidad de la pila multiplicada por el ancho de la pila no debe
superar el valor máximo que puede contenerse en una variable de tipo uint16_t. El tamaño de la
pila que utiliza la tarea de inactividad se define con la constante configMINIMAL_STACK_SIZE
definida por la aplicación. Esta es la única forma de que el código fuente de FreeRTOS use la
configuración configMINIMAL_STACK_SIZE. La constante también se utiliza dentro de las aplicaciones
de demostración para poder transportar la demostración entre varias arquitecturas de procesador. El
valor asignado a esta constante en la aplicación de demostración FreeRTOS para la arquitectura del
procesador que se usa es el valor mínimo recomendado para cualquier tarea. Si la tarea utiliza una
gran cantidad de espacio de la pila, es preciso asignar un valor más grande. No es fácil determinar
el espacio de pila que necesita una tarea. Es posible calcularlo, pero la mayoría de los usuarios
simplemente asignan un valor que consideran razonable y luego usan las características de FreeRTOS
para asegurarse de que el espacio asignado sea realmente suficiente y que no se desperdicie RAM.
Para obtener información acerca de cómo consultar el espacio máximo de pila que se ha utilizado para
una tarea, consulte la sección sobre desbordamiento de pila (p. 249) en la sección de resolución de
problemas.
• pvParameters: las funciones de tareas aceptan un parámetro del tipo puntero a void (void*). El valor que
se asigna a pvParameters es el valor que se pasa a la tarea. En esta guía se mencionan varios ejemplos
que demuestran cómo se puede usar el parámetro.
28
Kernel FreeRTOS Guía para desarrolladores
Creación de tareas (ejemplo 1)
Existen dos posibles valores de devolución: pdPASS, que indica que la tarea se ha creado correctamente
y pdFAIL, que indica que la tarea no se ha creado porque no hay bastante memoria en montón disponible
para que FreeRTOS asigne RAM suficiente para contener estructuras de datos de la tarea y una pila. Para
obtener más información, consulte Administración de memoria en montón (p. 14).
for( ;; )
vPrintString( pcTaskName );
29
Kernel FreeRTOS Guía para desarrolladores
Creación de tareas (ejemplo 1)
for( ;; )
vPrintString( pcTaskName );
/* 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 thetask. */ "Task 1",/
* Text name for the task. This is to facilitate debugging only. */ 1000, /* Stack depth -
small microcontrollers will use much less stack than this. */ NULL, /* This example does
not use the task parameter. */ 1, /* This task will run at priority 1. */ NULL ); /* This
example does not use the task handle. */
/* Create the other task in exactly the same way and at the same priority. */
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. For more information, see Heap
Memory Management. */
for( ;; );
30
Kernel FreeRTOS Guía para desarrolladores
Creación de tareas (ejemplo 1)
Las dos tareas parecen ejecutarse simultáneamente. Sin embargo, dado que ambas tareas se ejecutan en
el mismo núcleo de procesador, no puede ser así. En realidad ambas tareas entran y salen rápidamente
del estado En ejecución. Las dos tareas se ejecutan con la misma prioridad, por lo que comparten tiempo
en el mismo núcleo de procesador. Su patrón de ejecución se muestra en la siguiente figura.
La flecha que recorre la parte inferior de esta figura muestra el paso del tiempo desde t1 en adelante.
La líneas en color muestran qué tarea se está ejecutando en cada momento (por ejemplo, la tarea 1 se
ejecuta entre los tiempos t1 y t2).
Solo puede haber una tarea en el estado En ejecución en un momento dado, por lo que cuando una tarea
entra en el estado En ejecución (la tarea se activa), la otra entra en el estado No está en ejecución (la
tarea se desactiva).
Ambas tareas se crean desde main(), antes de iniciar el programador. También es posible crear una tarea
desde otra tarea. Por ejemplo, la tarea 2 se puede haber creado desde tarea 1.
El código siguiente muestra una tarea creada desde otra tarea después de que el programador se haya
iniciado.
31
Kernel FreeRTOS Guía para desarrolladores
Uso del parámetro de tarea (ejemplo 2)
/* If this task code is executing then the scheduler must already have been started.
Create the other task before entering the infinite loop. */
for( ;; )
vPrintString( pcTaskName );
A continuación se muestra el código de la función de tarea única (vTaskFunction). Esta función única
sustituye las dos funciones de tareas (vTask1 y vTask2) utilizadas en el ejemplo 1. El parámetro de tarea
se convierte a char * para obtener la cadena que la tarea debe imprimir.
char *pcTaskName;
/* The string to print out is passed in via the parameter. Cast this to a character
pointer. */
32
Kernel FreeRTOS Guía para desarrolladores
Uso del parámetro de tarea (ejemplo 2)
for( ;; )
vPrintString( pcTaskName );
Aunque ahora solo hay una implementación de tarea (vTaskFunction), se puede crear más de una
instancia de la tarea definida. Cada instancia creada se ejecutará de forma independiente bajo el control
del programador FreeRTOS.
El código siguiente muestra cómo el parámetro pvParameters de la función xTaskCreate() se utiliza para
pasar la cadena de texto a la tarea.
/* 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. */
/* 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. */
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
33
Kernel FreeRTOS Guía para desarrolladores
Prioridades de las tareas
heap memory available for the idle task to be created. For more information, see Heap
Memory Management. */
for( ;; );
El programador de FreeRTOS puede optar entre dos métodos para decidir qué tarea estará en el estado
En ejecución. El valor máximo en que puede establecerse configMAX_PRIORITIES depende del método
utilizado:
1. Método genérico
Este método se implementa en C y se puede utilizar en todos los puertos de arquitectura de FreeRTOS.
Cuando se usa el método genérico, FreeRTOS no limita el valor máximo en el que puede
establecerse configMAX_PRIORITIES. Sin embargo, le recomendamos que mantenga el valor de
configMAX_PRIORITIES en el mínimo requerido, ya que cuánto más elevado sea el valor, más RAM se
consumirá y más durará el tiempo de ejecución en el peor de los casos.
Este método utiliza una pequeña cantidad de código de ensamblador y es más rápido que el método
genérico. El valor configMAX_PRIORITIES no influye en el tiempo de ejecución en el peor de los casos.
Si utiliza este método, configMAX_PRIORITIES no puede ser mayor que 32. Al igual que ocurre con el
método genérico, debe tener configMAX_PRIORITIES en el valor mínimo requerido, ya que cuánto más
alto sea el valor, más RAM se consumirá.
El programador de FreeRTOS siempre se asegura de que la tarea de máxima prioridad que puede
ejecutarse sea la tarea seleccionada para entrar en el estado En ejecución. Cuando se puede ejecutar más
de una tarea con la misma prioridad, el programador pasa por turnos cada tarea al estado En ejecución y
la saca.
34
Kernel FreeRTOS Guía para desarrolladores
La medida del tiempo y la interrupción de ciclo
Para poder seleccionar la siguiente tarea que debe ejecutarse, el mismo programador tiene que
ejecutarse al final de cada intervalo de tiempo. El final de un intervalo de tiempo no es el único lugar
donde el programador puede seleccionar una nueva tarea para ejecutarla. El programador también
seleccionará una nueva tarea para ejecutarla inmediatamente después de que la tarea que se está
ejecutando actualmente entre en el estado Bloqueado, o cuando una interrupción mueva una tarea de
mayor prioridad al estado Listo. Con este fin se usa una interrupción periódica denominada interrupción
de ciclo. La longitud del intervalo de tiempo se establece de forma efectiva con la frecuencia de
interrupción del ciclo, que se configura con la constante de configuración de tiempo de compilación
configTICK_RATE_HZ definida por la aplicación, dentro de FreeRTOSConfig.h. Por ejemplo, si se
establece configTICK_RATE_HZ en 100 (Hz), el intervalo de tiempo será de 10 milisegundos. El tiempo
entre dos interrupciones de ciclo se denomina el período de ciclo. Un intervalo de tiempo equivale a un
período de ciclo.
35
Kernel FreeRTOS Guía para desarrolladores
Experimentación con prioridades (ejemplo 3)
Las llamadas a la API de FreeRTOS siempre especifican el tiempo en múltiplos de periodos de ciclos
que por lo general se denominan simplemente ciclos. La macro pdMS_TO_TICKS() convierte un tiempo
especificado en milisegundos en un tiempo especificado en ciclos. La resolución disponible depende
de la frecuencia de ciclos definida. pdMS_TO_TICKS() no se puede utilizar si la frecuencia de ciclos
es superior a 1 KHz (si configTICK_RATE_HZ es superior a 1000). El código siguiente muestra cómo
utilizar pdMS_TO_TICKS() para convertir un tiempo especificado como 200 milisegundos en un tiempo
equivalente especificado en ciclos.
/* pdMS_TO_TICKS() takes a time in milliseconds as its only parameter, and evaluates to the
equivalent time in tick periods. This example shows xTimeInTicks being set to the number
of tick periods that are equivalent to 200 milliseconds. */
Nota: No recomendamos que especifique los tiempos en ciclos directamente en la aplicación. En su lugar
utilice la macro pdMS_TO_TICKS() para especificar los tiempos en milisegundos. Esto garantiza que los
tiempos especificados en la aplicación no cambien si se modifica la frecuencia de ciclos.
El valor de recuento de ciclos es el número total de interrupciones de ciclo que se han producido
desde que se inició el programador, suponiendo que el recuento de ciclos no se haya desbordado. Las
aplicaciones de usuario no han de tener en cuenta los desbordamientos cuando especifican periodos de
retraso porque FreeRTOS administra internamente la coherencia de tiempo.
Para obtener información acerca de las constantes de configuración que afectan a cuándo el programador
seleccionará una nueva tarea para ejecutarla y cuándo se ejecutará una interrupción de ciclo consulte
Programación de algoritmos (p. 58).
36
Kernel FreeRTOS Guía para desarrolladores
Experimentación con prioridades (ejemplo 3)
Aquí se ve el código de muestra utilizado para crear las tareas con diferentes prioridades. La función única
que implementa ambas tareas no ha cambiado. Sigue imprimiendo sencillamente una cadena de forma
periódica mediante un bucle nulo para crear un plazo.
/* 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. */
/* Create the first task at priority 1. The priority is the second to last parameter.
*/
/* Create the second task at priority 2, which is higher than a priority of 1. The
priority is the second to last parameter. */
vTaskStartScheduler();
return 0;
La salida producida por este ejemplo se muestra aquí. El programador siempre seleccionará la tarea de
máxima prioridad que puede ejecutarse. La tarea 2 tiene una prioridad más alta que la tarea 1 y siempre
puede ejecutarse. Por lo tanto, la tarea 2 es la única tarea que entrará en el estado En ejecución. Dado
que la tarea 1 nunca entra en el estado En ejecución, nunca imprime su cadena. Se dice, pues, que la
tarea 1 se ve privada de tiempo de procesamiento por la tarea 2.
37
Kernel FreeRTOS Guía para desarrolladores
Ampliación del estado No está en ejecución
La tarea 2 siempre puede ejecutarse, ya que nunca tiene que esperar a nada. Está reproduciendo un bucle
nulo o imprimiendo en el terminal.
Para que las tareas sean útiles, es preciso volver a escribirlas para que dependan de los eventos. Un tarea
que depende de un evento ejecuta un trabajo (procesamiento) solo después de que se produzca el evento
que la desencadena, y no puede entrar en el estado En ejecución antes de que se produzca el evento.
El programador siempre selecciona la tarea de máxima prioridad que puede ejecutarse. Si las tareas de
prioridad alta no se pueden ejecutar significa que el programador no puede seleccionarlas y que, en su
lugar, tiene que seleccionar una tarea de menor prioridad que pueda ejecutarse. Por lo tanto, el uso de
tareas que dependen de eventos implica que se pueden crear tareas con diferentes prioridades sin que las
tareas de máxima prioridad agoten el tiempo de procesamiento para las tareas de menor prioridad.
El estado Bloqueado
Se dice que una tarea que está a la espera de un evento se encuentra en estado Bloqueado, que es un
subestado del estado No está en ejecución.
Las tareas pueden entrar en el estado Bloqueado para esperar dos tipos de eventos:
1. Eventos temporales (relacionados con el tiempo), donde los eventos son la caducidad de un plazo o que
se llega a un tiempo absoluto. Por ejemplo, una tarea puede entrar en estado Bloqueado a esperar que
pasen 10 milisegundos.
2. Eventos de sincronización, donde los eventos se originan en otra tarea o interrupción. Por ejemplo, una
tarea puede entrar en el estado Bloqueado a esperar a que lleguen datos a una cola. Los eventos de
sincronización cubren una amplia gama de tipos de eventos.
38
Kernel FreeRTOS Guía para desarrolladores
El estado suspendido
Se pueden usar colas de FreeRTOS, semáforos binarios, semáforos de recuento, exclusiones mutuas,
exclusiones mutuas recursivas, grupos de eventos y notificaciones directas en la tarea para crear eventos
de sincronización.
Es posible que una tarea se pueda bloquear en un evento de sincronización con un tiempo de espera, lo
que bloquea efectivamente ambos tipos de evento de forma simultánea. Por ejemplo, una tarea puede
elegir esperar un máximo de 10 milisegundos para que lleguen datos a una cola. La tarea abandonará
el estado Bloqueado si llegan los datos en un plazo de 10 milisegundos o si pasan 10 milisegundos y no
llegan datos.
El estado suspendido
El estado suspendido es también un subestado de No está en ejecución. Las tareas que se encuentran
en estado suspendido no están disponibles para el programador. La única forma de entrar en el
estado suspendido es mediante una llamada a la función de API vTaskSuspend(). La única manera
de salir del estado suspendido es mediante una llamada a las funciones de API vTaskResume() o
xTaskResumeFromISR(). La mayoría de las aplicaciones no utilizan el estado suspendido.
El estado Listo
Se dice que las tareas que no se encuentran en el estado No está en ejecución, pero no están bloqueadas
ni suspendidas se encuentran en el estado Listo. Pueden ejecutarse y, por lo tanto, están listas para
ejecutarse, pero no están actualmente en el estado En ejecución.
39
Kernel FreeRTOS Guía para desarrolladores
Uso del estado Bloqueado para crear un retraso (ejemplo 4)
Usar cualquier forma de sondeo presenta muchas desventajas, y su poca eficiencia no es la menor.
Durante el sondeo, la tarea en realidad no tiene ningún trabajo que hacer, pero sigue usando un tiempo de
procesamiento máximo, derrochando de esta manera ciclos de procesador. En el ejemplo 4 se corrige este
comportamiento reemplazando el bucle nulo de sondeo por una llamada a la función de API vTaskDelay().
La función de API vTaskDelay() solo está disponible cuando INCLUDE_vTaskDelay está establecido en 1
en FreeRTOSConfig.h.
vTaskDelay() pone la tarea de llamada en el estado Bloqueado durante un número fijo de interrupciones de
ciclos. La tarea no utiliza tiempo de procesamiento mientras se encuentra en el estado Bloqueado, por lo
que solo utiliza tiempo de procesamiento cuando es realmente necesario llevar a cabo el trabajo.
40
Kernel FreeRTOS Guía para desarrolladores
Uso del estado Bloqueado para crear un retraso (ejemplo 4)
En el código siguiente la tarea de ejemplo posterior al retraso del bucle nulo se ha sustituido con una
llamada a vTaskDelay(). Este código muestra la nueva definición de tarea.
char *pcTaskName;
/* The string to print out is passed in via the parameter. Cast this to a character
pointer. */
for( ;; )
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 parameter takes a
time specified in 'ticks', and the pdMS_TO_TICKS() macro is used (where the xDelay250ms
constant is declared) to convert 250 milliseconds into an equivalent time in ticks. */
vTaskDelay( xDelay250ms );
Aunque ambas tareas se siguen creado con diferentes prioridades, ahora ambas se ejecutan. El resultado
confirma el comportamiento esperado.
41
Kernel FreeRTOS Guía para desarrolladores
Uso del estado Bloqueado para crear un retraso (ejemplo 4)
La secuencia de ejecución que se muestra en la figura siguiente explica por qué ambas tareas se ejecutan
aunque se hayan creado con diferentes prioridades. La ejecución del programador en sí se omite para
mayor simplicidad.
La tarea de inactividad se crea automáticamente cuando el programador se inicia para garantizar que
siempre haya al menos una tarea que pueda ejecutarse (al menos una tarea en el estado Listo). Para
obtener más información acerca de esta tarea, consulte La tarea de inactividad y el enlace de tareas de
inactividad (p. 48).
Esta figura muestra la secuencia de ejecución cuando las tareas utilizan vTaskDelay() en lugar del bucle
NULL.
Solo ha cambiado la implementación de ambas tareas, no su funcionalidad. Si compara esta cifra con la
cifra de Medida del tiempo e interrupción de ciclo (p. 35), verá que esta funcionalidad se consigue de
una manera mucho más eficiente.
42
Kernel FreeRTOS Guía para desarrolladores
Uso del estado Bloqueado para crear un retraso (ejemplo 4)
Esta figura muestra el patrón de ejecución cuando las tareas entran en el estado Bloqueado durante todo
el periodo de retraso, por lo que utilizan tiempo del procesador solo cuando en realidad tienen trabajo que
debe realizarse (en este caso simplemente imprimir un mensaje).
Cada vez que las tareas salen del estado Bloqueado, se ejecutan durante una fracción de un periodo
de ciclo antes de volver a entrar en el estado Bloqueado. En la mayoría de los casos no hay tareas de
aplicación que puedan ejecutarse (sin tareas de aplicación en el estado Listo) y, por lo tanto, no hay
tareas de aplicación que puedan seleccionarse para entrar en el estado En ejecución. Mientras este sea
el caso, la tarea de inactividad se ejecutará. La cantidad de tiempo de procesamiento asignado a la tarea
de inactividad es una medida de la capacidad de procesamiento libre en el sistema. El uso de un RTOS
puede aumentar significativamente la capacidad de procesamiento libre simplemente permitiendo que una
aplicación dependa completamente de un evento.
La líneas en negrita de la siguiente figura muestran las transiciones realizadas por las tareas en el ejemplo
4; ahora con una transición de cada una por el estado Bloqueado antes de volver al estado Listo.
Las líneas en negrita indican las transiciones de estado realizadas por las tareas.
43
Kernel FreeRTOS Guía para desarrolladores
La función de API vTaskDelayUntil()
Los parámetros para vTaskDelayUntil() especifican el valor de recuento de ciclos exacto en el que la
tarea de llamada debe pasar del estado Bloqueado al estado Listo. vTaskDelayUntil() es la función de
API que debe utilizarse cuando necesita un periodo de ejecución fijo (en el que quiera que su tarea se
ejecute periódicamente con una frecuencia fija), ya que la hora a la que se desbloquea la tarea de llamada
es absoluta, en vez de ser en relación con la hora en que se llamó a la función (como es el caso con
vTaskDelay()).
44
Kernel FreeRTOS Guía para desarrolladores
Conversión de la tareas de ejemplo para
utilizar vTaskDelayUntil() (ejemplo 5)
char *pcTaskName;
TickType_t xLastWakeTime;
/* The string to print out is passed in by the parameter. Cast this to a character
pointer. */
/* The xLastWakeTime variable needs to be initialized with the current tick count.
This is the only time the variable is written to explicitly. After this, xLastWakeTime is
automatically updated within vTaskDelayUntil(). */
xLastWakeTime = xTaskGetTickCount();
45
Kernel FreeRTOS Guía para desarrolladores
Combinación de tareas de bloqueo y
tareas que no bloquean (ejemplo 6)
for( ;; )
vPrintString( pcTaskName );
/* This task should execute every 250 milliseconds exactly. As per the
vTaskDelay() function, time is measured in ticks, and the pdMS_TO_TICKS() macro is
used to convert milliseconds into ticks. xLastWakeTime is automatically updated within
vTaskDelayUntil(), so is not explicitly updated by the task. */
1. Se crean dos tareas con la prioridad 1. No hacen nada, salvo imprimir continuamente una cadena.
Estas tareas nunca realizan llamadas a funciones de API que pudieran hacerles entrar en el estado
Bloqueado, por lo que siempre se encuentran en el estado Listo o en Ejecución. Las tareas de este tipo
se denominan tareas de procesamiento continuo, ya que siempre tienen trabajo que hacer (aunque sea
trivial como en este caso).
2. A continuación se crea otra tarea, esta vez con prioridad 2, por lo que la prioridad es superior a la
de las otras dos tareas. La tercera tarea también imprime solamente una cadena, aunque esta vez
periódicamente, por lo que utiliza la función de API vTaskDelayUntil() para colocarse a sí misma en el
estado Bloqueado entre cada iteración de impresión.
char *pcTaskName;
/* The string to print out is passed in by the parameter. Cast this to a character
pointer. */
for( ;; )
/* Print out the name of this task. This task just does this repeatedly without
ever blocking or delaying. */
46
Kernel FreeRTOS Guía para desarrolladores
Combinación de tareas de bloqueo y
tareas que no bloquean (ejemplo 6)
vPrintString( pcTaskName );
TickType_t 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();
for( ;; )
/* The task should execute every 3 milliseconds exactly. See the declaration of
xDelay3ms in this function. */
47
Kernel FreeRTOS Guía para desarrolladores
La tarea de inactividad y el enlace de tareas de inactividad
Tiene que haber siempre al menos una tarea que pueda entrar en el estado En ejecución. Este es el caso
incluso cuando se usan las características de bajo consumo especiales de FreeRTOS. El microcontrolador
en el que se ejecuta FreeRTOS se pondrá en modo de bajo consumo si ninguna de las tareas creadas por
la aplicación se puede ejecutar.
Para asegurarse de que al menos una tarea pueda entrar en el estado En ejecución, el programador crea
una tarea de inactividad cuando se llama a vTaskStartScheduler(). La tarea de inactividad apenas hace
algo más que estar en bucle de modo que, al igual que las tareas del primer ejemplo, siempre puede
ejecutarse.
La tarea de inactividad tiene la prioridad más baja posible (prioridad cero) para asegurarse de que nunca
impida que una aplicación de más prioridad entre en el estado En ejecución. Sin embargo, no hay nada
que le impida crear tareas con la prioridad de las tareas de inactividad y, por lo tanto, de compartirlas.
48
Kernel FreeRTOS Guía para desarrolladores
Funciones de enlace de tareas de inactividad
La ejecución con la prioridad más baja garantiza que la tarea de inactividad salga del estado En ejecución
tan pronto como la tarea de más prioridad entre en el estado Listo. Esto se puede ver en el momento
tn del gráfico de la secuencia de ejecución del ejemplo 4, donde la tarea de inactividad se intercambia
inmediatamente para permitir que la tarea 2 se ejecute tan pronto como abandone el estado Bloqueado. Se
dice que la tarea 2 ha reemplazado la tarea de inactividad. El reemplazo se produce automáticamente y sin
el conocimiento de la tarea que se está reemplazando.
Nota: Si una aplicación utiliza la función de API vTaskDelete(), es fundamental que la tarea de inactividad
no se quede sin tiempo de procesamiento. Esto se debe a que la tarea de inactividad es responsable de
limpiar los recursos de kernel después de que se elimine una tarea.
1. Una función de enlace de tarea de inactividad nunca debe intentar bloqueos ni suspensiones.
Nota: Bloquear la tarea de inactividad, sea cual sea la forma en que se realice, puede provocar una
situación en la que no haya ninguna tarea disponible para entrar en el estado En ejecución.
2. Si la aplicación usa la función de API vTaskDelete(), el enlace de la tarea de inactividad siempre tiene
que volver a su intermediario en un período de tiempo razonable. Esto se debe a que la tarea de
inactividad es responsable de limpiar los recursos de kernel después de que se elimine una tarea. Si la
tarea de inactividad permanece de forma permanente en la función de enlace de inactividad, no puede
producirse esta limpieza.
Las funciones de enlace de tarea de inactividad deben tener el nombre y el prototipo que se muestran
aquí.
49
Kernel FreeRTOS Guía para desarrolladores
Definición de una función de enlace
de tarea de inactividad (ejemplo 7)
ulIdleCycleCount++;
La función que implementa las tareas creadas se modifica ligeramente para imprimir el valor
ulIdleCycleCount, tal y como se muestra aquí. En el código siguiente se muestra cómo imprimir el valor
ulIdleCycleCount.
char *pcTaskName;
/* The string to print out is passed in by the parameter. Cast this to a character
pointer. */
for( ;; )
/* Print out the name of this task AND the number of times ulIdleCycleCount has
been incremented. */
50
Kernel FreeRTOS Guía para desarrolladores
Cambio de la prioridad de una tarea
vTaskDelay( xDelay250ms );
La salida producida por el ejemplo 7 se muestra aquí. Verás que se llama a la función de enlace de la
tarea de inactividad alrededor de 4 millones de veces entre cada iteración de las tareas de aplicación. (El
número de iteraciones depende de la velocidad del hardware a la que se ejecuta la demostración).
51
Kernel FreeRTOS Guía para desarrolladores
Función de API uxTaskPriorityGet()
El código de muestra utilizado aquí crea dos tareas con dos diferentes prioridades. Ninguna tarea realiza
ninguna llamada de función de API que pueda hacer que entre en estado Bloqueado, por lo que ambas
tareas siempre están en el estado Listo o el estado En ejecución. Por lo tanto, la tarea de máxima prioridad
relativa siempre será la tarea que el programador seleccione para entrar en el estado En ejecución.
1. La tarea 1 (que se muestra en el código que está justo abajo) se crea con la máxima prioridad, por lo
que está garantizado que se ejecute en primer lugar. La tarea 1 imprime un par de cadenas antes de
aumentar la prioridad de la tarea 2 (mostrada en segundo lugar) por encima de su propia prioridad.
52
Kernel FreeRTOS Guía para desarrolladores
Cambio de prioridades de tareas (ejemplo 8)
2. La tarea 2 comienza a ejecutarse (entra en el estado En ejecución) tan pronto como adquiera la mayor
prioridad relativa. Solo una tarea puede encontrarse en el estado En ejecución en un momento dado, de
manera que cuando la tarea 2 se encuentra en el estado En ejecución, la tarea 1 está en el estado Listo.
3. La tarea 2 imprime un mensaje antes de volver a establecer su propia prioridad de nuevo por debajo de
la prioridad de la tarea 1.
4. Cuando la tarea 2 establece vuelve a bajar su prioridad, una vez más la tarea 1 vuelve a ser la tarea
de máxima prioridad. La tarea 1 vuelve a entrar en el estado En ejecución, lo que obliga a la tarea 2 a
retomar el estado Listo.
UBaseType_t uxPriority;
/* This task will always run before Task 2 because it is created with the higher
priority. Task 1 and Task 2 never 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 the calling task's priority". */
for( ;; )
/* Setting the Task 2 priority above the Task 1 priority will cause Task 2 to
immediately start running (Task 2 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(). The
code that follows shows how the handle was obtained. */
/* Task 1 will only run when it has a priority higher than Task 2. Therefore, for
this task to reach this point, Task 2 must already have executed and set its priority back
down to below the priority of this task. */
UBaseType_t uxPriority;
/* Task 1 will always run before this task because Task 1 is created with the higher
priority. Task 1 and Task 2 never block so they 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 the calling task's priority". */
53
Kernel FreeRTOS Guía para desarrolladores
Cambio de prioridades de tareas (ejemplo 8)
for( ;; )
/* For this task to reach this point Task 1 must have already run and set the
priority of this task higher than its own. Print out the name of this task. */
/* Set the priority of this task back down to its original value. Passing in NULL
as the task handle means "change the priority of the calling task". Setting the priority
below that of Task 1 will cause Task 1 to immediately start running again, preempting this
task. */
Cada tarea puede consultar y establecer su propia prioridad sin usar un controlador de tarea válido,
usando sencillamente NULL. Se necesita un controlador de tarea solo cuando una tarea hace referencia
a una tarea que no sea ella misma, como cuando la tarea 1 cambia la prioridad de la tarea 2. Para que la
tarea 1 pueda efectuar esta operación, se obtiene y se guarda el controlador de la tarea 2 cuando se crea
la tarea 2, tal y como se muestra en los comentarios del código aquí.
/* 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. */
/* Create the second task at priority 1, which is lower than the priority given to
Task 1. Again, the task parameter is not used so it is set to NULL, but this time the task
handle is required so the address of xTask2Handle is passed in the last parameter. */
vTaskStartScheduler();
/* If all is well, then main() will never reach here because the scheduler will now be
running the tasks. If main() does reach here, then it is likely there was insufficient
heap memory available for the idle task to be created. For information, see Heap Memory
Management. */
for( ;; );
54
Kernel FreeRTOS Guía para desarrolladores
Eliminación de una tarea
55
Kernel FreeRTOS Guía para desarrolladores
Eliminación de tareas (ejemplo 9)
Es responsabilidad de la tarea de inactividad liberar memoria asignada a tareas que ya han sido
eliminadas. Por lo tanto, es importante que las aplicaciones que usen la función de API vTaskDelete() no
agoten el tiempo de procesamiento para la tarea de inactividad.
Nota: Solo la memoria que haya sido asignada a una tarea por el kernel se liberará automáticamente
cuando se elimine la tarea. Toda memoria u otro recurso que la implementación de la tarea haya asignado
deberá liberarse explícitamente.
1. La tarea 1 se crea con main() con prioridad 1. Cuando se ejecuta, crea la tarea 2 con una prioridad de 2.
Ahora la tarea 2 es la tarea de máxima prioridad, por lo que comienza a ejecutarse de forma inmediata.
El origen de main() se muestra en la primera lista de códigos. El origen de la tarea 1 se muestra en la
segunda lista de códigos.
2. La tarea 2 se limita a eliminarse a sí misma. Podría eliminarse pasando NULL a vTaskDelete(), pero en
su lugar, para fines de demostración, utiliza su propio controlador de tarea. El origen de la tarea 2 se
muestra en la tercera lista de códigos.
3. Una vez que se ha eliminado la tarea 2, la tarea 1 vuelve a ser la tarea de máxima prioridad, por lo
que sigue ejecutándose, momento en el cual llama a vTaskDelay() para bloquearse durante un breve
periodo.
4. La tarea de inactividad se ejecuta mientras la tarea 1 se encuentra en estado bloqueado y libera la
memoria que se ha asignado a la tarea 2 que ahora está eliminada.
5. Cuando la tarea 1 sale del estado de bloqueo, se convierte de nuevo en la tarea Listo de máxima
prioridad, por lo que reemplaza a la tarea de inactividad. Cuando entra en el estado En ejecución,
vuelve a crear la tarea 2, y así sucesivamente.
/* Create the first task at priority 1. The task parameter is not used so is set to
NULL. The task handle is also not used so likewise is set to NULL. */
56
Kernel FreeRTOS Guía para desarrolladores
Eliminación de tareas (ejemplo 9)
vTaskStartScheduler();
/* main() should never reach here as the scheduler has been started.*/
for( ;; );
for( ;; )
/* Create task 2 at a higher priority. Again, the task parameter is not used so it
is set to NULL, but this time the task handle is required so the address of xTask2Handle
is passed as the last parameter. */
/* Task 2 has/had the higher priority, so for Task 1 to reach here, Task 2 must
have already executed and deleted itself. Delay for 100 milliseconds. */
vTaskDelay( xDelay100ms );
/* Task 2 does nothing but delete itself. To do this, it could call vTaskDelete() using
NULL as the parameter, but for demonstration purposes, it calls vTaskDelete(), passing its
own task handle. */
vTaskDelete( xTask2Handle );
57
Kernel FreeRTOS Guía para desarrolladores
Programación de algoritmos
Programación de algoritmos
Un resumen de estados de tareas y eventos
La tarea que se está ejecutando realmente (usando tiempo de procesamiento) se encuentre en el estado
En ejecución. En un procesador de núcleo único solo puede haber una tarea en el estado En ejecución en
un momento dado.
58
Kernel FreeRTOS Guía para desarrolladores
Configuración del algoritmo de programación
Las tareas que no se están ejecutando pero no se encuentran en el estado Bloqueado o el estado
Suspendido se encuentran en el estado Listo. Las tareas que se encuentran en el estado Listo están
disponibles para que el programador las seleccione como tarea que debe entrar en el estado En ejecución.
El programador siempre elegirá la tarea de estado Listo que tenga el nivel de prioridad más alto para que
entre en el estado En ejecución.
Las tareas pueden esperar en el estado Bloqueado a que se produzca un evento y se muevan
automáticamente al estado Listo cuando dicho evento se produce. Los eventos temporales se producen
en un momento determinado (por ejemplo, cuando un tiempo de bloqueo vence) y se utilizan normalmente
para implementar un comportamiento periódico o de tiempo de espera. Los eventos de sincronización
se producen cuando una tarea o una rutina de servicio de interrupción envía información mediante una
notificación de tarea, cola, grupo de eventos o uno de los muchos tipos de semáforo. Por lo general se
utilizan para señalar actividad asíncrona de señales, por ejemplo, una llegada de datos a un periférico.
Todos los ejemplos mostrados hasta ahora utilizan el mismo algoritmo de programación,
pero puede cambiarlo mediante las constantes de configuración configUSE_PREEMPTION y
configUSE_TIME_SLICING. Ambas constantes están definidas en FreeRTOSConfig.h.
En todas las configuraciones posibles, el programador FreeRTOS se asegurará de que las tareas que
comparten la misma prioridad se seleccionen para entrar en el estado En ejecución por turnos. Esta
política de asunción por turnos a menudo se denomina programación de turno rotativo. Un algoritmo de
programación de turno rotativo no garantiza que el tiempo se comparta por igual entre todas las tareas que
tengan la misma prioridad, sino que las tareas con el estado En ejecución que tengan la misma prioridad
entrarán en el estado En ejecución por turnos.
Constant Valor
configUSE_PREEMPTION 1
configUSE_TIME_SLICING 1
• tareas de prioridad fija: los algoritmos de programación descritos como algoritmos de prioridad fija no
cambian la prioridad asignada a las tareas en proceso de programación, pero tampoco impiden que las
tareas mismas cambien su prioridad o la de otras tareas.
59
Kernel FreeRTOS Guía para desarrolladores
Programación de reemplazo
prioritario con intervalos de tiempo
• estado preferente: los algoritmos de programación preferente sustituirán inmediatamente la tarea con
el estado En ejecución si una tarea que tiene una mayor prioridad entra en el estado Listo. Sustituir
inmediatamente por prioridad significa que una tarea sale involuntariamente (sin cesión ni bloqueo
explícito) del estado En ejecución y entra en el estado Listo para que otra tarea entre en el estado En
ejecución.
• intervalos de tiempo: los intervalos de tiempo se utilizan para compartir el tiempo de procesamiento entre
tareas que tienen la misma prioridad, incluso cuando estas no ceden explícitamente al estado Bloqueado
ni entran en dicho estado explícitamente. Los algoritmos de programación que usan intervalos de tiempo
seleccionarán una nueva tarea para que entre en el estado En ejecución al final de cada intervalo de
tiempo si hay otras tareas con el estado Listo que tienen la misma prioridad que la tarea que se está
ejecutando. Un intervalo de tiempo es igual al tiempo transcurrido entre dos interrupciones de ciclos
RTOS.
La siguiente figura muestra la secuencia de selección de las tareas para que entren en el estado En
ejecución cuando todas las tareas de una aplicación tiene una prioridad única.
En esta figura se muestra el patrón de ejecución, donde se resalta la priorización y la preferencia de tareas
en una hipotética aplicación en la que se ha asignado a cada tarea una prioridad única.
1. Tarea de inactividad
La tarea de inactividad se está ejecutando con la prioridad más baja por lo que se reemplaza cada vez
que una tarea con más prioridad entre en el estado Listo (por ejemplo, en las horas t3, t5 y t9).
2. Tarea 3
La tarea 3 es una tarea basada en eventos que se ejecuta con una prioridad relativamente baja, aunque
por encima de la prioridad de inactividad. Pasa la mayor parte de su tiempo en el estado Bloqueado
a la espera de que se produzca su evento de interés, y se transfiere desde el estado Bloqueado al
estado Listo cada vez que se produce el evento. Todos los mecanismos de comunicación entre tareas
de FreeRTOS (notificaciones de tareas, colas, semáforos, grupos de eventos, etc.) se pueden utilizar
para señalar eventos y desbloquear tareas de este modo.
Los eventos se producen en las horas t3 y t5, y en algún punto entre y t9 y t12. Los eventos que se
producen en las horas t3 y t5 se procesan inmediatamente, ya que en esas horas la tarea 3 es la tarea
de máxima prioridad tarea que puede ejecutarse. El evento que se produce en algún punto entre t9
y t12 no se procesa hasta t12, ya que hasta entonces las tareas de mayor prioridad, las tareas 1 y 2,
siguen ejecutándose. Solo en el momento t12 ambas tareas (1 y 2) están en el estado Bloqueado, lo
que convierte a la tarea 3 en la tarea de estado Listo con mayor prioridad.
3. Tarea 2
60
Kernel FreeRTOS Guía para desarrolladores
Programación de reemplazo
prioritario con intervalos de tiempo
La tarea 2 es una tarea periódica que se ejecuta con una prioridad superior a la prioridad de la tarea 3,
pero por debajo de la prioridad de la tarea 1. El intervalo del periodo de la tarea significa que la tarea 2
quiere ejecutarse en las horas t1, t6 y t9. En t6, la tarea 3 se encuentra en el estado En ejecución, pero
la tarea 2 tiene la mayor prioridad relativa por lo que reemplaza a la tarea 3 y comienza a ejecutarse
de forma inmediata. La tarea 2 completa su procesamiento y vuelve a entrar en el estado Bloqueado
en el momento t7, momento en el que la tarea 3 puede volver a entrar en el estado En ejecución para
completar su procesamiento. La tarea 3 se bloquea en el t8.
4. Tarea 1
La tarea 1 también es una tarea basada en eventos. Se ejecuta con la prioridad más alta de todas las
tareas, por lo que puede reemplazar a cualquier otra tarea del sistema. El único evento de tarea 1 que
se muestra se produce en el momento t10, momento en el que la tarea 1 reemplaza a la tarea 2. La
tarea 2 puede completar su procesamiento solo después de que la tarea 1 haya vuelto a entrar en el
estado Bloqueado en el momento t11.
La tarea de tiempo de inactividad y la tarea 2 son ambas tareas de procesamiento continuo y ambas
tienen una prioridad de 0 (la prioridad más baja posible). El programador solo asigna el tiempo de
procesamiento a tareas de prioridad 0 cuando no hay tareas de mayor prioridad que puedan ejecutarse,
y comparte el tiempo que se asigna a las tareas de prioridad 0 mediante intervalos de tiempo. En cada
interrupción de ciclo se inicia un intervalo de tiempo nuevo, que se produce en los momentos t1, t2, t3,
t4, t5, t8, t9, t10 y t11.
La tarea de tiempo de inactividad y la tarea 2 entran en el estado En ejecución por turnos, lo que a su
vez puede dar lugar a que ambas tareas se encuentren en el estado En ejecución durante parte del
mismo intervalo de tiempo, tal y como ocurre entre las horas t5 y t8.
2. Tarea 1
61
Kernel FreeRTOS Guía para desarrolladores
Programación de reemplazo
prioritario (sin intervalos de tiempo)
de procesamiento a la tarea de inactividad si las tareas con prioridad de inactividad creadas por el
programador tienen trabajo que hacer, pero la tarea de inactividad no tiene. Puede utilizar la constante de
configuración de tiempo de compilación configIDLE_SHOULD_YIELD constante para cambiar la forma en
que la tarea de inactividad se programa:
El patrón de ejecución que se muestra en el gráfico siguiente es lo que se observaría en la misma situación
cuando configIDLE_SHOULD_YIELD se establece en 1. Muestra la secuencia de selección de las tareas
para que entren en el estado En ejecución cuando dos tareas de una aplicación comparten una misma
prioridad.
Constant Valor
configUSE_PREEMPTION 1
62
Kernel FreeRTOS Guía para desarrolladores
Programación de reemplazo
prioritario (sin intervalos de tiempo)
configUSE_TIME_SLICING 0
Si se usan los intervalos de tiempo y hay más de una tarea en el estado Listo con la prioridad más alta
que puede ejecutarse, el programador seleccionará una nueva tarea para entrar en el estado En ejecución
durante cada interrupción de ciclo RTOS (una interrupción de ciclo marca el final de un intervalo de
tiempo). Si no se usan los intervalos de tiempo, el programador seleccionará una nueva tarea para entrar
en el estado En ejecución solo si:
Cuando no se utilizan los intervalos de tiempo, hay menos cambios de contexto de tareas. Por lo tanto,
desactivar los intervalos de tiempo se traduce en una reducción de la sobrecarga de procesamiento del
programador. Sin embargo, desactivar los intervalos de tiempo también puede dar lugar a que tareas que
tienen la misma prioridad reciban cantidades de tiempo de procesamiento muy diferentes. Ejecutar el
programador sin intervalos de tiempo se considera una técnica avanzada. Solo deberían utilizarla usuarios
experimentados.
En este gráfico se muestra cómo tareas que tienen la misma prioridad pueden recibir cantidades muy
diferentes de tiempo de procesamiento cuando no se usan los intervalos de tiempo.
1. Interrupciones de ciclo
Las interrupciones de ciclo se producen en t1, t2, t3, t4, t5, t8, t11, t12 y t13.
2. Tarea 1
La tarea 1 es una de alta prioridad basada en eventos que pasa la mayor parte de su tiempo en el
estado Bloqueado a la espera de su evento de interés. La tarea 1 pasa del estado Bloqueado al estado
Listo (y posteriormente, porque es la tarea con el estado Listo de prioridad más alta, al estado En
ejecución) cada vez que se produce el evento. En el gráfico anterior se muestra a la tarea 1 procesando
un evento entre las horas t6 y t7 y después, de nuevo, entre las horas t9 y t10.
3. Tarea de inactividad y tarea 2
El tarea de tiempo de inactividad y la tarea 2 son ambas tareas de procesamiento continuo y ambas
tienen una prioridad de 0 (la prioridad de tiempo de inactividad). Las tareas de procesamiento continuo
no entran en el estado Bloqueado.
Los intervalos de tiempo no se usan, por lo que una tarea de prioridad de inactividad que se encuentre
en el estado En ejecución permanecerá en dicho estado hasta que la reemplace la tarea 1 con más
prioridad.
63
Kernel FreeRTOS Guía para desarrolladores
Programación cooperativa
La tarea 2 comienza a ejecutarse en el momento t7, que es cuando la tarea 1 vuelve a entrar en el
estado Bloqueado para esperar otro evento. La tarea 2 permanece en el estado En ejecución hasta que
la reemplaza la tarea 1 en el momento t9 que es menos de un periodo de ciclos después de que entre
en el estado En ejecución.
En el momento t10, la tarea de inactividad vuelve a entrar en el estado En ejecución, a pesar de que ha
recibido cuatro veces más tiempo de procesamiento que la tarea 2.
Programación cooperativa
FreeRTOS también puede utilizar la programación cooperativa. En la siguiente tabla se muestran los
valores de FreeRTOSConfig.h que configuran el programador FreeRTOS para que use la programación
cooperativa.
Constant Valor
configUSE_PREEMPTION 0
Cuando se usa el programador cooperativo, se producirá un cambio de contexto solo cuando el estado En
ejecución entre en el estado Bloqueado o la tarea en estado En ejecución ceda de forma explícita (solicite
manualmente una reprogramación) llamando a taskYIELD(). Las tareas nunca se reemplazan, por lo que
no se pueden utilizar los intervalos de tiempo.
En el siguiente gráfico se muestra el comportamiento del programador cooperativo. Las líneas discontinuas
horizontales muestran cuándo una tarea se encuentra en el estado Listo.
1. Tarea 1
En el momento t3, una interrupción da el semáforo, lo que hace que la tarea 1 deje el estado Bloqueado
y entre en el estado Listo. Para obtener más información acerca de cómo dar semáforos a partir de
interrupciones, consulte la sección de administración de interrupciones (p. 125).
En el momento t3, la tarea 1 es la tarea en estado Listo que tiene la máxima prioridad y si se hubiera
usado el programador de reemplazos, la tarea 1 se habría convertido en la tarea con el estado En
64
Kernel FreeRTOS Guía para desarrolladores
Programación cooperativa
ejecución. Sin embargo, como se está utilizando el programador cooperativo, la tarea 1 permanece
en el estado Listo hasta el momento t4, que es cuando la tarea con el estado En ejecución llama a
taskYIELD().
2. Tarea 2
En el momento t2, la tarea 2 es la tarea en estado Listo que tiene la máxima prioridad y si se hubiera
usado el programador de reemplazos, la tarea 2 se habría convertido en la tarea con el estado En
ejecución. Sin embargo, como se está utilizando el programador cooperativo, la tarea 2 permanece en
el estado Listo hasta que la tarea que está en el estado En ejecución entre en el estado En ejecución o
llame a taskYIELD().
La tarea en el estado En ejecución llama a taskYIELD() en el momento t4, pero para entonces la tarea
1 es la tarea de estado Listo con la más alta prioridad, por lo que la tarea 2 no se convierte en realidad
en la tarea con el estado En ejecución hasta que la tarea 1 vuelve a entrar en el estado Bloqueado en el
momento t5.
En el momento t6, la tarea 2 vuelve a entrar en el estado Bloqueado para esperar al siguiente mensaje,
momento en el cual la tarea 3 vuelve a ser la tarea en el estado Listo con la máxima prioridad.
En una aplicación multitarea, el programador debe asegurarse de que no acceden a un mismo recurso
más de una tarea a la vez, ya que el acceso simultáneo puede dañar al recurso. Tenga en cuenta la
situación siguiente en la que el recurso al que se accede es un UART (puerto serie). Dos tareas escriben
cadenas en el UART. La tarea 1 está escribiendo "abcdefghijklmnop" y la tarea 2 está escribiendo
"123456789":
Puede evitar los problemas causados por el acceso simultáneo usando el programador cooperativo. Los
métodos para compartir recursos de forma segura entre tareas se abordan más adelante en esta guía. Los
recursos proporcionados por FreeRTOS, como, por ejemplo, colas y semáforos, son siempre seguros y se
pueden compartir entre tareas.
• Cuando se usa el programador de reemplazos, la tarea que tiene el estado En ejecución puede
reemplazarse en cualquier momento, incluso cuando un recurso que comparte con otra tarea se
encuentra en estado incoherente. Tal y como muestra el ejemplo del UART, dejar un recurso en un
estado incoherente puede dar lugar a que los datos resulten dañados.
• Cuando se usa el programador cooperativo, el programador controla cuándo se puede producir un
cambio a otra tarea. Por consiguiente, el programador puede asegurar que no se produzca un cambio a
otra tarea mientras haya un recurso en estado incoherente.
• En el ejemplo del UART, el programador puede garantizar que la tarea 1 no salga del estado En
ejecución hasta que su cadena completa se haya escrito en el UART y, al hacerlo, se elimina la
posibilidad de que la cadena resulte dañada por la activación de otra tarea.
65
Kernel FreeRTOS Guía para desarrolladores
Programación cooperativa
Los sistemas tendrán menos capacidad de respuesta cuando se utilice el programador cooperativo.
66
Kernel FreeRTOS Guía para desarrolladores
Características de una cola
Administración de colas
Las colas proporcionan mecanismo de comunicación de una a otra tarea, de una tarea a una interrupción
y de una interrupción a una tarea. En esta sección, se explica la comunicación de una tarea a otra. Para
obtener más información acerca de la comunicación de una tarea a una interrupción y de una interrupción
a una tarea, consulte Administración de interrupciones (p. 125).
Almacenamiento de datos
Una cola puede contener un número finito de elementos de datos de tamaño fijo. El número máximo
de elementos que puede contener una cola es su longitud. Tanto la longitud como el tamaño de cada
elemento de datos se establecen en el momento de crear la cola.
Normalmente, las colas se utilizan como búferes FIFO (primera en entrar, primera en salir), en las que los
datos se escriben al final de la cola y se eliminan de la parte delantera de la cola.
En la siguiente figura, se muestra cómo se escriben y se leen datos de una cola que se está utilizando
como FIFO. También es posible escribir en la parte delantera de una cola y sobrescribir los datos que ya
se encuentran allí.
67
Kernel FreeRTOS Guía para desarrolladores
Almacenamiento de datos
Los datos que se envían a la cola se copian byte por byte en la cola.
68
Kernel FreeRTOS Guía para desarrolladores
Acceso de varias tareas
La cola contiene punteros a los datos que se envían a la cola, no los datos propiamente dichos.
FreeRTOS utiliza la cola por copia. Este método se considera más útil y más fácil de usar que la cola por
referencia porque:
• Se puede enviar una variable de pila directamente a una cola, aunque la variable no exista después de
que la función en la que se ha declarado haya salido.
• Los datos se pueden enviar a una cola sin asignar primero un búfer en el que almacenar los datos y, a
continuación, copiar los datos en el búfer asignado.
• La tarea de envío puede reutilizar inmediatamente la variable o el búfer que se enviaron a la cola.
• La tarea de envío y la tarea de recepción están completamente desacopladas. No es necesario que los
diseñadores de aplicaciones se preocupen de qué tarea tiene los datos o qué tarea es responsable de la
liberación de los datos.
• La cola por copia no impide que la cola también se utilice para la cola por referencia. Por ejemplo,
cuando el tamaño de los datos que se ponen en cola hace que sea poco práctico copiar los datos en la
cola, se puede copiar un puntero a los datos en la cola en su lugar.
• RTOS asume toda la responsabilidad de asignar la memoria utilizada para almacenar datos.
• En un sistema con protección de memoria, la memoria RAM a la que puede acceder una tarea está
restringida. En ese caso, la cola por referencia solo se puede utilizar si las tareas de envío y recepción
de tareas pueden acceder a la RAM en la que se almacenan los datos. La cola por copia no impone
dicha restricción. El kernel siempre se ejecuta con todos los privilegios, lo que permite usar una cola
para pasar datos entre los límites de protección de memoria.
Las colas pueden tener varios lectores, por lo que es posible que una única cola tenga bloqueada más
de una tarea a la espera de datos. En ese caso, solo se desbloquea una tarea cuando los datos vuelven
a estar disponibles. La tarea que se desbloquea siempre será la tarea de máxima prioridad que está a la
espera de datos. Si las tareas bloqueadas tienen la misma prioridad, se desbloqueará la tarea que lleve
esperando datos más tiempo.
69
Kernel FreeRTOS Guía para desarrolladores
Bloqueo en varias colas
mantenerse en el estado Bloqueado a la espera de que haya espacio disponible en la cola si la cola ya
está llena.
Las colas pueden tener varios escritores, por lo que es posible que una cola llena tenga bloqueada más de
una tarea a la espera de que se complete una operación de envío. En ese caso, solo se desbloquea una
tarea cuando vuelve a haber espacio disponible en la cola. La tarea que se desbloquea siempre será la
tarea de máxima prioridad que está a la espera de que se libere espacio. Si las tareas bloqueadas tienen la
misma prioridad, se desbloqueará la tarea que lleve más tiempo esperando a que se libere espacio.
A las colas se les hace referencia por medio de controladores, que son las variables de tipo
QueueHandle_t. La función de API xQueueCreate() crea una cola y devuelve un QueueHandle_t que hace
referencia a la cola que se ha creado.
FreeRTOS V9.0.0 también incluye la función xQueueCreateStatic(), que asigna la memoria necesaria para
crear una cola estáticamente durante la compilación. FreeRTOS asigna RAM del montón de FreeRTOS
cuando se crea una cola. La RAM se utiliza para almacenar las estructuras de datos de la cola y los
elementos que se encuentran en la cola. xQueueCreate() devuelve NULL si no hay suficiente RAM del
montón para la cola que se va a crear.
70
Kernel FreeRTOS Guía para desarrolladores
Funciones de API xQueueSendToBack()
y xQueueSendToFront()
Después de crear una cola, la función de API xQueueReset() se puede utilizar para devolver la cola a su
estado vacío original.
TickType_t xTicksToWait );
TickType_t xTicksToWait );
71
Kernel FreeRTOS Guía para desarrolladores
Función de API xQueueReceive()
xQueueSendToFront() y xQueueSendToBack()
se devolverán inmediatamente si xTicksToWait es
igual a cero y la cola ya está llena.
1. pdPASS
Nota: No llame a xQueueReceive() desde una rutina del servicio de interrupciones. En su lugar, utilice la
función de API a prueba de interrupciones xQueueReceiveFromISR().
72
Kernel FreeRTOS Guía para desarrolladores
Función de API xQueueReceive()
TickType_t xTicksToWait );
En la siguiente tabla, se muestran los parámetros de la función xQueueReceive() y sus valores de retorno.
1. pdPASS
73
Kernel FreeRTOS Guía para desarrolladores
Función de API uxQueueMessagesWaiting()
Nota: No llame a uxQueueMessagesWaiting() desde una rutina del servicio de interrupciones. En su lugar,
utilice la versión a prueba de interrupciones, uxQueueMessagesWaitingFromISR().
La prioridad de las tareas que envían a la cola es menor que la prioridad de la tarea que recibe de la cola.
Esto significa que la cola no debería contener nunca más de un elemento, ya que, en el momento en el
que los datos se envíen a la cola, la tarea de recepción se desbloqueará, tendrá preferencia sobre la tarea
de envío y eliminará los datos, dejando la cola vacía de nuevo.
El código siguiente muestra la implementación de la tarea que escribe en la cola. Se crean dos instancias
de esta tarea: una que escribe de forma continua el valor 100 en la cola y otra que escribe de forma
74
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al recibir de una cola (Ejemplo 10)
continua el valor 200 en la misma cola. El parámetro de la tarea se utiliza para pasar estos valores a cada
instancia de la tarea.
int32_t lValueToSend;
BaseType_t xStatus;
/* Two instances of this task are created so the value that is sent to the queue
is passed in through the task parameter. This way, each instance can use a different
value. The queue was created to hold values of type int32_t, so cast the parameter to the
required type. */
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. */
/* The send operation could not complete because the queue was full. This must
be an error because the queue should never contain more than one item! */
El código siguiente muestra la implementación de la tarea que recibe datos de la cola. La tarea de
recepción especifica un tiempo de bloqueo de 100 milisegundos, por lo que pasará al estado Bloqueado
a la espera de que haya disponibles datos. Saldrá del estado Bloqueado cuando haya disponibles datos
en la cola o pasen 100 milisegundos sin que haya disponible ningún dato. En este ejemplo, el tiempo de
espera de 100 milisegundos no debía vencer nunca, porque hay dos tareas escribiendo de forma continua
en la cola.
/* Declare the variable that will hold the values received from the queue. */
int32_t lReceivedValue;
75
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al recibir de una cola (Ejemplo 10)
BaseType_t xStatus;
for( ;; )
/* This call should always find the queue empty because this task will immediately
remove any data that is written to the queue. */
/* 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 will remain in the Blocked state to wait
for data to be available should the queue already be empty. */
/* Data was successfully received from the queue, print out the received value.
*/
else
/* Data was not received from the queue even after waiting for 100ms.This must
be an error because the sending tasks are free running and will be continuously writing to
the queue. */
El siguiente código contiene la definición de la función main(). Simplemente, crea la cola y las tres tareas
antes de iniciar el programador. La cola se crea para almacenar un máximo de cinco valores int32_t,
aunque las prioridades de las tareas se establecen de forma que la cola no contendrá nunca más de un
elemento a la vez.
76
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al recibir de una cola (Ejemplo 10)
/* Declare a variable of type QueueHandle_t. This is used to store the handle to the queue
that is accessed by all three tasks. */
QueueHandle_t xQueue;
/* The queue is created to hold a maximum of 5 values, each of which is large enough to
hold a variable of type int32_t. */
/* 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. */
/* Create the task that will read from the queue. The task is created with priority
2, so above the priority of the sender tasks. */
vTaskStartScheduler();
else
/* 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
FreeRTOS heap memory available for the idle task to be created. For more information, see
Heap Memory Management. */
for( ;; );
Ambas tareas que envían a la cola tiene una prioridad idéntica. Esto hace que las dos tareas de envío
envíen datos a la cola por turnos.
77
Kernel FreeRTOS Guía para desarrolladores
Recepción de datos de varios orígenes
La siguiente figura muestra una situación de ejemplo en la que se envían estructuras en una cola.
• Se crea una cola que contiene estructuras de tipo Data_t. Los miembros de la estructura permiten enviar
un valor de datos y un tipo enumerado a la cola en un mensaje. El tipo enumerado se utiliza para indicar
qué significan los datos.
• Se utiliza una tarea de controlador central para realizar la función del sistema principal. Este tiene
reaccionar a las entradas y los cambios en el estado del sistema que se le comunica en la cola.
78
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al enviar a una cola y envío de
estructuras en una cola (Ejemplo 11)
• Se utiliza una tarea del bus CAN para encapsular la funcionalidad de interfaz del bus CAN. Cuando
la tarea del bus CAN ha recibido y descodificado un mensaje, envía el mensaje ya descodificado a la
tarea del controlador en una estructura Data_t. El miembro eDataID de la estructura transferida se utiliza
para permitir que la tarea del controlador sepa cuáles son los datos (en este caso, un valor de velocidad
del motor). El miembro lDataValue de la estructura transferida se utiliza para permitir que la tarea del
controlador sepa el valor de velocidad del motor.
• Se utiliza una tarea de la interfaz hombre máquina (HMI) para encapsular toda la funcionalidad de HMI.
Probablemente, el operario de la máquina puede introducir comandos y consultar valores de varias
formas que se tienen que detectar e interpretar dentro de la tarea de HMI. Cuando se introduce un nuevo
comando, la tarea de HMI envía el comando a la tarea del controlador en una estructura Data_t. El
miembro eDataID de la estructura transferida se utiliza para hacer saber a la tarea del controlador cuáles
son los datos (en este caso, un valor de punto de ajuste nuevo). El miembro lDataValue de la estructura
transferida se utiliza para hacer saber a la tarea del controlador el valor del punto de ajuste.
El código siguiente muestra la definición de la estructura que se va a pasar en una cola y la declaración de
dos variables.
typedef enum
eSender1,
eSender2
} DataSource_t;
typedef struct
uint8_t ucValue;
DataSource_t eDataSource;
} Data_t;
/* Declare two variables of type Data_t that will be passed on the queue. */
79
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al enviar a una cola y envío de
estructuras en una cola (Ejemplo 11)
};
En el ejemplo anterior, la tarea de recepción tiene la prioridad más alta, por lo que la cola nunca contiene
más de un elemento. El resultado es que la tarea de recepción tiene preferencia sobre las tareas de
envío en cuanto los datos se colocan en la cola. En el siguiente ejemplo, las tareas de envío tienen la
mayor prioridad, por lo que normalmente la cola estará llena. Esto se debe a que, en cuanto la tarea de
recepción elimina un elemento de la cola, una de las tareas de envío asume la preferencia, lo que rellena
de inmediato la cola. A continuación, la tarea de envío vuelve a pasar al estado Bloqueado a la espera de
que vuelva a haber espacio disponible en la cola.
El código siguiente muestra la implementación de la tarea de envío. La tarea de envío especifica un tiempo
de bloqueo de 100 milisegundos, por lo que entra en el estado Bloqueado a la espera de que haya espacio
disponible cada vez que se llena la cola. Sale del estado Bloqueado cuando hay disponible espacio en
la cola o pasan 100 milisegundos sin que haya disponible espacio. En este ejemplo, el tiempo de espera
de 100 milisegundos no debería vencer nunca, porque la tarea de recepción está liberando espacio
continuamente eliminando elementos de la cola.
BaseType_t xStatus;
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 remove items from the
queue when both sending tasks are in the Blocked state. */
/* The send operation could not complete, even after waiting for 100 ms. This
must be an error because the receiving task should make space in the queue as soon as both
sending tasks are in the Blocked state. */
La tarea de recepción tiene la prioridad más baja, por lo que se ejecutará solo cuando las tareas de envío
se encuentren en el estado Bloqueado. Las tareas de envío pasarán al estado Bloqueado solo cuando la
cola esté llena, por lo que la tarea de recepción solo se ejecutará cuando la cola ya esté llena. Por lo tanto,
siempre espera recibir datos incluso cuando no especifica un tiempo de bloqueo.
80
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al enviar a una cola y envío de
estructuras en una cola (Ejemplo 11)
/* Declare the structure that will hold the values received from the queue. */
Data_t xReceivedStructure;
BaseType_t xStatus;
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, which is 3 in this case. */
/* 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
required because this task will only run when the queue is full. */
/* Data was successfully received from the queue, print out the received value
and the source of the value. */
else
81
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al enviar a una cola y envío de
estructuras en una cola (Ejemplo 11)
else
/* Nothing was received from the queue. This must be an error because this task
should only run when the queue is full. */
La función main() solo cambia ligeramente con respecto al ejemplo anterior. La cola se crea para
almacenar tres estructuras Data_t y las prioridades de las tareas de envío y recepción se invierten. Aquí se
muestra la implementación de main().
/* 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. */
/* Create the task that will read from the queue. The task is created with priority
1, so below the priority of the sender tasks. */
vTaskStartScheduler();
else
/* 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 2 provides more information
on heap memory management. */
82
Kernel FreeRTOS Guía para desarrolladores
Bloqueo al enviar a una cola y envío de
estructuras en una cola (Ejemplo 11)
for( ;; );
La siguiente figura muestra la secuencia de ejecución que se produce cuando la prioridad de las tareas de
envío es superior a la prioridad de la tarea de recepción.
Esta tabla describe por qué los primeros cuatro mensajes provienen de la misma tarea.
Tiempo Descripción
83
Kernel FreeRTOS Guía para desarrolladores
Uso de datos de tamaño grande o variable
Cuando utilice un puntero para compartir memoria entre tareas, debe asegurarse de que ambas tareas
no modifiquen el contenido de la memoria de forma simultánea ni realicen ninguna otra acción que pueda
provocar que el contenido de la memoria no sea válido o sea incoherente. Lo ideal es que solo se permita
el acceso a la memoria a la tarea de envío hasta que se ponga en cola un puntero a la memoria y solo se
debe permitir el acceso a la memoria a la tarea de recepción después de que el puntero se haya recibido
de la cola.
No debe utilizar nunca un puntero para obtener acceso a los datos que se han asignado en una pila de
tareas. Los datos no serán válidos si el marco de la pila ha cambiado.
En los siguientes ejemplos de código, se muestra cómo usar una cola para enviar un puntero a un búfer de
una tarea a otra.
84
Kernel FreeRTOS Guía para desarrolladores
Colocación de punteros en la cola
El siguiente código crea una cola que puede contener hasta cinco punteros.
/* Declare a variable of type QueueHandle_t to hold the handle of the queue being created.
*/
QueueHandle_t xPointerQueue;
/* Create a queue that can hold a maximum of 5 pointers (in this case, character pointers).
*/
El siguiente código asigna un búfer, escribe una cadena en el búfer y, a continuación, envía un puntero al
búfer a la cola.
/* A task that obtains a buffer, writes a string to the buffer, and then sends the address
of the buffer to the queue created in the previous listing. */
char *pcStringToSend;
BaseType_t xStringNumber = 0;
for( ;; )
xStringNumber++;
/* Send the address of the buffer to the queue that was created in the previous
listing. The address of the buffer is stored in the pcStringToSend variable.*/
El siguiente código recibe un puntero a un búfer de la cola y luego imprime la cadena que contiene el búfer.
/* A task that receives the address of a buffer from the queue created in the first listing
and written to in the second listing. The buffer contains a string, which is printed out.
*/
85
Kernel FreeRTOS Guía para desarrolladores
Uso de una cola para enviar
distintos tipos y longitudes de datos
char *pcReceivedString;
for( ;; )
vPrintString( pcReceivedString );
prvReleaseBuffer( pcReceivedString );
La pila TCP/IP, que se ejecuta en su propia tarea, debe procesar eventos de diferentes orígenes. Cada
tipo de evento diferente está asociado a diferentes tipos y longitudes de datos. Todos los eventos que se
producen fuera de la tarea de TCP/IP se describen por medio de una estructura de tipo IPStackEvent_t y
se envían a la tarea TCP/IP en una cola. El miembro pvData de la estructura IPStackEvent_t es un puntero
que se puede utilizar para almacenar un valor directamente o para apuntar a un búfer.
Aquí se muestra la estructura IPStackEvent_t que se utiliza para enviar eventos a la tarea de la pila TCP/IP
en FreeRTOS+TCP.
/* A subset of the enumerated types used in the TCP/IP stack to identify events. */
typedef enum
/* Other event types appear here but are not shown in this listing. */
86
Kernel FreeRTOS Guía para desarrolladores
Uso de una cola para enviar
distintos tipos y longitudes de datos
} eIPEvent_t;
/* The structure that describes events and is sent on a queue to the TCP/IP task. */
/* An enumerated type that identifies the event. See the eIPEvent_t definition. */
eIPEvent_t eEventType;
void *pvData;
} IPStackEvent_t;
Los datos recibidos de la red se envían a la tarea de TCP/IP mediante una estructura de tipo
IPStackEvent_t. El miembro eEventType de la estructura se establece en eNetworkRxEvent. El miembro
pvData de la estructura se utiliza para apuntar al búfer que contiene los datos recibidos.
Este pseudocódigo demuestra cómo se utiliza una estructura IPStackEvent_t para enviar datos desde la
red a la tarea de TCP/IP.
IPStackEvent_t xEventStruct;
xEventStruct.eEventType = eNetworkRxEvent;
xSendEventStructToIPTask( &xEventStruct );
Los eventos de aceptación se envían desde la tarea que ha llamado a la tarea de TCP/IP
FreeRTOS_accept() utilizando una estructura de tipo IPStackEvent_t. El miembro eEventType de la
estructura se establece en eTCPAcceptEvent. El miembro pvData de la estructura se establece en el
controlador del socket que acepta una conexión.
Este pseudocódigo demuestra cómo se utiliza una estructura IPStackEvent_t para enviar el controlador
de un socket que acepta una conexión a la tarea de TCP/IP.
87
Kernel FreeRTOS Guía para desarrolladores
Uso de una cola para enviar
distintos tipos y longitudes de datos
IPStackEvent_t xEventStruct;
xEventStruct.eEventType = eTCPAcceptEvent;
xSendEventStructToIPTask( &xEventStruct );
Los eventos de caída de la red se envían a la interfaz de red mediante una tarea de TCP/IP utilizando
una estructura de tipo IPStackEvent_t. El miembro eEventType de la estructura se establece en
eNetworkDownEvent. Los eventos de caída de la red no están asociados a los datos, por lo que el
miembro pvData de la estructura no se utiliza.
Este pseudocódigo demuestra cómo se utiliza una estructura IPStackEvent_t para enviar un evento de
caída de la red a la tarea de TCP/IP.
IPStackEvent_t xEventStruct;
xEventStruct.eEventType = eNetworkDownEvent;
xSendEventStructToIPTask( &xEventStruct );
Aquí se muestra el código que recibe y procesa estos eventos dentro de la tarea TCP/IP. El miembro
eEventType de las estructuras IPStackEvent_t que se reciben de la cola se utiliza para determinar cómo
se va a interpretar el miembro pvData. Este pseudocódigo demuestra cómo se utiliza una estructura
IPStackEvent_t para enviar una caída de la red a la tarea de TCP/IP.
IPStackEvent_t xReceivedEvent;
/* Block on the network event queue until either an event is received, or xNextIPSleep
ticks pass without an event being received. eEventType is set to eNoEvent in case the
call to xQueueReceive() returns because it timed out, rather than because an event was
received. */
xReceivedEvent.eEventType = eNoEvent;
switch( xReceivedEvent.eEventType )
88
Kernel FreeRTOS Guía para desarrolladores
Recepción desde varias colas
case eNetworkDownEvent :
prvProcessNetworkDownEvent();
break;
case eNetworkRxEvent:
/* The network interface has received a new packet. A pointer to the received data
is stored in the pvData member of the received IPStackEvent_t structure. Process the
received data. */
prvHandleEthernetPacket( ( NetworkBufferDescriptor_t * )
( xReceivedEvent.pvData ) );
break;
case eTCPAcceptEvent:
/* The FreeRTOS_accept() API function was called. The handle of the socket that
is accepting a connection is stored in the pvData member of the received IPStackEvent_t
structure. */
xTCPCheckNewClient( pxSocket );
break;
/* Other event types are processed in the same way, but are not shown here. */
Conjuntos de colas
Con frecuencia, los diseños de las aplicaciones exigen que una única tarea reciba datos de diferentes
tamaños, datos de significado diferente y datos de diferentes orígenes. En la sección anterior, se explicó
cómo hacer esto de una forma eficiente y clara mediante una única cola que recibe estructuras. Sin
embargo, es posible que a veces trabaje con restricciones que limitan las opciones de diseño, lo que
requiere el uso de una cola independiente para algunos orígenes de datos. Por ejemplo, un código de
terceros que se esté integrando en un diseño podría suponer que existe una cola especial. En estos casos,
puede utilizar un conjunto de colas.
Los conjuntos de colas permiten que una tarea reciba datos de más de una cola sin sondear tareas por
turnos en cada cola para determinar cuál contiene datos.
Un diseño que utilice un conjunto de colas para recibir datos de varios orígenes es menos claro y eficiente
que un diseño que logra la misma funcionalidad mediante una única cola que recibe estructuras. Por este
motivo, le recomendamos que solo utilice conjuntos de colas si es absolutamente necesario debido a las
restricciones de diseño.
89
Kernel FreeRTOS Guía para desarrolladores
Función de API xQueueCreateSet()
Cuando una cola que es miembro de un conjunto recibe datos, el controlador de la cola de recepción
se envía al conjunto de colas y se devuelve cuando una tarea llama a una función que lee del conjunto
de colas. Por lo tanto, si se devuelve un controlador de colas de un conjunto de colas, se sabe que la
cola a la que hace referencia el controlador contiene datos, por lo que la tarea puede leer de la cola
directamente.
Nota: Si una cola es miembro de un conjunto de colas, no lee datos de la cola a menos que el
controlador de la cola se haya leído antes del conjunto de colas.
A los conjuntos de colas se les hace referencia mediante controladores, que son variables de tipo
QueueSetHandle_t. La función de API xQueueCreateSet() crea un conjunto de colas y devuelve un
QueueSetHandle_t que hace referencia al conjunto de colas que se ha creado.
90
Kernel FreeRTOS Guía para desarrolladores
Función de API xQueueAddToSet()
Cuando una cola o un semáforo que es miembro de un conjunto recibe datos, el controlador del
semáforo o la cola de recepción se envía al conjunto de colas y se devuelve cuando una tarea llama a
xQueueSelectFromSet(). Si se devuelve un controlador de una llamada a xQueueSelectFromSet(), se sabe
que la cola o el semáforo al que hace referencia el controlador contiene datos y la tarea de llamada debe
leer directamente de la cola o el semáforo.
91
Kernel FreeRTOS Guía para desarrolladores
Uso de un conjunto de colas (Ejemplo 12)
Nota: No lea datos de una cola o semáforo que sea miembro de un conjunto a menos que el controlador
de la cola o el semáforo se hayan devuelto antes de una llamada a xQueueSelectFromSet(). Solo lea un
elemento de una cola o semáforo cada vez que se devuelva el controlador de cola o el controlador de
semáforo desde una llamada a xQueueSelectFromSet().
xQueueSet
El controlador del conjunto de colas desde el que se está recibiendo un controlador de cola o un
controlador de semáforo (lectura). El controlador del conjunto de colas se habrá devuelto desde la llamada
a xQueueCreateSet() que se utiliza para crear el conjunto de colas.
xTicksToWait
El tiempo máximo durante el que la tarea de llamada debe permanecer en el estado Bloqueado a la espera
de recibir un controlador de cola o semáforo del conjunto de colas si todas las colas y el semáforo del
conjunto están vacíos. Si xTicksToWait es cero, xQueueSelectFromSet() se devuelve de inmediato si
todas las colas y semáforos del conjunto están vacíos. El tiempo de bloqueo se especifica en periodos
de ciclos, por lo que el tiempo absoluto que representa depende de la frecuencia de ciclos. La macro
pdMS_TO_TICKS() se puede usar para convertir a ciclos un tiempo especificado en milisegundos.
Al establecer xTicksToWait en portMAX_DELAY, la tarea tendrá que esperar indefinidamente (sin
agotar el tiempo de espera), siempre y cuando INCLUDE_vTaskSuspend esté establecido en 1 en
FreeRTOSConfig.h.
Valor de retorno
Un valor de retorno que no sea NULL será el controlador de una cola o semáforo que se sabe que
contiene datos. Si se ha especificado un tiempo de bloqueo (xTicksToWait no es cero), es posible
que la tarea que realizó la llamada entrara en el estado Bloqueado a la espera de que hubiera datos
disponibles en una cola o semáforo del conjunto, pero un controlador se ha leído correctamente en
el conjunto de colas antes de que el tiempo de bloqueo venciera. Los controladores se devuelven
como un tipo QueueSetMemberHandle_t, que se puede convertir en el tipo QueueHandle_t o el tipo
SemaphoreHandle_t.
Si el valor de retorno es NULL, eso significa que no se ha podido leer un controlador en el conjunto de
colas. Si se había especificado un tiempo de bloqueo (xTicksToWait no era cero), la tarea de llamada se
habrá puesto en el estado Bloqueado a la espera de que otra tarea o interrupción envíe datos a una cola o
semáforo del conjunto, pero el tiempo de bloqueo ha vencido antes de que eso ocurriera.
/* Declare two variables of type QueueHandle_t. Both queues are added to the same queue
set. */
92
Kernel FreeRTOS Guía para desarrolladores
Uso de un conjunto de colas (Ejemplo 12)
/* Declare a variable of type QueueSetHandle_t. This is the queue set to which the two
queues are added. */
/* Create the two queues, both of which send character pointers. The priority of the
receiving task is above the priority of the sending tasks, so the queues will never have
more than one item in them at any one time*/
/* Create the queue set. Two queues will be added to the set, each of which can contain
1 item, so the maximum number of queue handles the queue set will ever have to hold at one
time is 2 (2 queues multiplied by 1 item per queue). */
xQueueSet = xQueueCreateSet( 1 * 2 );
/* Create the task that reads from the queue set to determine which of the two queues
contain data. */
vTaskStartScheduler();
for( ;; );
return 0;
La primera tarea de envío utiliza xQueue1 para enviar un puntero de carácter a la tarea de recepción cada
100 milisegundos. La segunda tarea de envío utiliza xQueue2 para enviar un puntero de carácter a la tarea
de recepción cada 200 milisegundos. Se establecen punteros de caracteres que apuntan a una cadena
que identifica a la tarea de envío. Aquí se muestra la implementación de ambas tareas de envío.
93
Kernel FreeRTOS Guía para desarrolladores
Uso de un conjunto de colas (Ejemplo 12)
for( ;; )
vTaskDelay( xBlockTime );
/* Send this task's string to xQueue1. It is not necessary to use a block time,
even though the queue can only hold one item. This is because the priority of the task
that reads from the queue is higher than the priority of this task. As soon as this task
writes to the queue, it will be preempted by the task that reads from the queue, so the
queue will already be empty again by the time the call to xQueueSend() returns. The block
time is set to 0. */
/*-----------------------------------------------------------*/
for( ;; )
vTaskDelay( xBlockTime );
/* Send this task's string to xQueue2. It is not necessary to use a block time,
even though the queue can only hold one item. This is because the priority of the task
that reads from the queue is higher than the priority of this task. As soon as this task
writes to the queue, it will be preempted by the task that reads from the queue, so the
queue will already be empty again by the time the call to xQueueSend() returns. The block
time is set to 0. */
Las colas en las que escriben las tareas de envío son miembros del mismo conjunto de colas. Cada vez
que una tarea envía a una de las colas, el controlador de la cola se envía al conjunto de colas. La tarea de
recepción llama a xQueueSelectFromSet() para leer los controladores de cola del conjunto de colas. Una
vez que la tarea de recepción ha recibido un controlador de cola del conjunto, sabe que la cola a la que
hace referencia el controlador recibido contiene datos, por lo que lee los datos de la cola directamente. Los
datos que lee de la cola son un puntero a una cadena, que la tarea de recepción imprime.
94
Kernel FreeRTOS Guía para desarrolladores
Uso de un conjunto de colas (Ejemplo 12)
QueueHandle_t xQueueThatContainsData;
char *pcReceivedString;
for( ;; )
/* Block on the queue set to wait for one of the queues in the set to contain
data. Cast the QueueSetMemberHandle_t value returned from xQueueSelectFromSet() to a
QueueHandle_t because it is known all the members of the set are queues (the queue set
does not contain any semaphores). */
/* An indefinite block time was used when reading from the queue set, so
xQueueSelectFromSet() will not have returned unless one of the queues in the set contained
data, and xQueueThatContainsData cannot be NULL. Read from the queue. It is not necessary
to specify a block time because it is known the queue contains data. The block time is set
to 0. */
vPrintString( pcReceivedString );
El resultado se muestra aquí. La tarea de recepción recibe cadenas de ambas tareas de envío. El tiempo
de bloqueo que utiliza vSenderTask1() es la mitad del tiempo de bloqueo que utiliza vSenderTask2(), lo
que hace que las cadenas que ha enviado vSenderTask1() se impriman con el doble de frecuencia que las
que ha enviado vSenderTask2().
95
Kernel FreeRTOS Guía para desarrolladores
Casos de uso de conjuntos de colas más realistas
El código siguiente muestra cómo utilizar el valor devuelto desde xQueueSelectFromSet() cuando el
conjunto cuenta con los siguientes miembros:
1. Un semáforo binario
2. Una cola desde la que se leen punteros de caracteres
3. Una cola desde la que se leen valores de uint32_t
En este código, se presupone que las colas y los semáforos ya se han creado y se han añadido al conjunto
de colas.
/* The handle of the queue from which character pointers are received. */
QueueHandle_t xCharPointerQueue;
/* The handle of the queue from which uint32_t values are received.*/
QueueHandle_t xUint32tQueue;
SemaphoreHandle_t xBinarySemaphore;
/* The queue set to which the two queues and the binary semaphore belong. */
QueueSetHandle_t xQueueSet;
96
Kernel FreeRTOS Guía para desarrolladores
Casos de uso de conjuntos de colas más realistas
QueueSetMemberHandle_t xHandle;
char *pcReceivedString;
uint32_t ulRecievedValue;
for( ;; )
/* Block on the queue set for a maximum of 100ms to wait for one of the members of
the set to contain data. */
xQueueReceive(xUint32tQueue, &ulRecievedValue, 0 );
97
Kernel FreeRTOS Guía para desarrolladores
Uso de una cola para crear un buzón de correo
xSemaphoreTake( xBinarySemaphore, 0 );
• Una cola se utiliza para enviar datos de una tarea a otra o desde una rutina del servicio de interrupciones
a una tarea. El remitente coloca un elemento en la cola y el receptor saca el elemento de la cola. Los
datos pasan a través de la cola desde el remitente al receptor.
• Un buzón de correo sirve para almacenar datos que puede leer cualquier tarea o rutina del servicio de
interrupciones. Los datos no pasan a través del buzón de correo. En su lugar, permanecen en él hasta
que se sobrescriben. El remitente sobrescribe el valor en el buzón de correo. El receptor lee el valor del
buzón de correo, pero no saca el valor del buzón.
Las funciones de API xQueueOverwrite() y xQueuePeek() permiten utilizar una cola como un buzón de
correo.
El código siguiente muestra cómo se crea una cola para usarla como un buzón de correo.
/* A mailbox can hold a fixed-size data item. The size of the data item is set when the
mailbox (queue) is created. In this example, the mailbox is created to hold an Example_t
structure. Example_t includes a timestamp to allow the data held in the mailbox to note
the time at which the mailbox was last updated. The timestamp used in this example is for
demonstration purposes only. A mailbox can hold any data the application writer wants, and
the data does not need to include a timestamp. */
TickType_t xTimeStamp;
uint32_t ulValue;
} Example_t;
QueueHandle_t. */
QueueHandle_t xMailbox;
98
Kernel FreeRTOS Guía para desarrolladores
Función de API xQueueOverwrite()
/* Create the queue that is going to be used as a mailbox. The queue has a length of 1
to allow it to be used with the xQueueOverwrite() API function, which is described below.
*/
xQueueOverwrite() solo debe utilizarse con colas que tienen una longitud de uno. Esa restricción evita la
necesidad de que la implementación de la función tome una decisión arbitraria respecto a qué elemento de
la cola debe sobrescribir si la cola está llena.
Nota: No llame a xQueueOverwrite() desde una rutina del servicio de interrupciones. En su lugar, utilice la
versión a prueba de interrupciones, xQueueOverwriteFromISR().
El código siguiente muestra cómo se usa xQueueOverwrite() para escribir en el buzón de correo (cola) que
se ha creado anteriormente.
99
Kernel FreeRTOS Guía para desarrolladores
Función de API xQueuePeek()
Example_t xData;
xData.ulValue = ulNewValue;
/* Use the RTOS tick count as the timestamp stored in the Example_t structure. */
xData.xTimeStamp = xTaskGetTickCount();
/* Send the structure to the mailbox, overwriting any data that is already in the
mailbox. */
Nota: No llame a xQueuePeek() desde una rutina del servicio de interrupciones. En su lugar, utilice la
versión a prueba de interrupciones, xQueuePeekFromISR().
xQueuePeek() tiene los mismos parámetros de función y valor de retorno que xQueueReceive().
El código siguiente muestra cómo se utiliza xQueuePeek() para recibir el elemento publicado en el buzón
de correo (cola) creado en un ejemplo anterior.
TickType_t xPreviousTimeStamp;
BaseType_t xDataUpdated;
/* This function updates an Example_t structure with the latest value received from the
mailbox. Record the timestamp already contained in *pxData before it gets overwritten by
the new data. */
xPreviousTimeStamp = pxData->xTimeStamp;
/* Update the Example_t structure pointed to by pxData with the data contained in
the mailbox. If xQueueReceive() was used here, then the mailbox would be left empty
and the data could not then be read by any other tasks. Using xQueuePeek() instead of
xQueueReceive() ensures the data remains in the mailbox. A block time is specified, so
the calling task will be placed in the Blocked state to wait for the mailbox to contain
data should the mailbox be empty. An infinite block time is used, so it is not necessary
to check the value returned from xQueuePeek(). xQueuePeek() will only return when data is
available. */
/* Return pdTRUE if the value read from the mailbox has been updated since this
function was last called. Otherwise, return pdFALSE. */
100
Kernel FreeRTOS Guía para desarrolladores
Función de API xQueuePeek()
xDataUpdated = pdTRUE;
else
xDataUpdated = pdFALSE;
return xDataUpdated;
101
Kernel FreeRTOS Guía para desarrolladores
Las funciones de devolución de
llamada del temporizador de software
• Las características de un temporizador de software en comparación con las características de una tarea.
• La tarea de demonio RTOS.
• La cola de comandos del temporizador.
• La diferencia entre un temporizador de software de una sola activación y un temporizador de software
periódico.
• Cómo crear, comenzar, restablecer y cambiar el periodo de un temporizador de software.
Los temporizadores de software se utilizan para programar la ejecución de una función en un momento
determinado del futuro o periódicamente con una frecuencia fija. La función que ejecuta el temporizador de
software se denomina función de devolución de llamada del temporizador de software.
Los temporizadores de software se implementan mediante el control del kernel de FreeRTOS kernel y con
el control de este. No requieren soporte de hardware. No están relacionados con los temporizadores de
hardware ni con los contadores de hardware.
En consonancia con la filosofía de FreeRTOS de usar un diseño innovador para conseguir una máxima
eficiencia, los temporizadores de software no utilizan tiempo de procesamiento a menos que se esté
ejecutando una función de devolución de llamada de un temporizador de software.
Las funciones de devolución de llamada del temporizador de software se ejecutan de principio a fin y se
cierran de forma normal. Deben mantenerse breves y no tienen que entrar en el estado Bloqueado.
Nota: Las funciones de devolución de llamada del temporizador de software se ejecutan en el contexto de
una tarea que se crea automáticamente cuando se inicia el programador de FreeRTOS. Por lo tanto, nunca
deben llamar a funciones de API de FreeRTOS que hagan que la tarea de la llamada entre en el estado
Bloqueado. Pueden llamar a funciones como xQueueReceive(), pero solo si el parámetro xTicksToWait de
la función (que especifica el tiempo de bloqueo de la función) está establecido en 0. No pueden llamar a
funciones como vTaskDelay() ya que esto siempre pondrá la tarea de llamada en el estado Bloqueado.
102
Kernel FreeRTOS Guía para desarrolladores
Atributos y estados de un temporizador de software
Cuando se inicia, el temporizador de una activación ejecuta su función de devolución de llamada una
sola vez. Un temporizador de una activación se puede reiniciar manualmente, pero él mismo no se
reiniciará.
• Temporizadores de recarga automática
Cuando se inicia, el temporizador de recarga automática se reinicia él mismo cada vez que vence, lo que
se traduce en una ejecución periódica de su función de devolución de llamada.
• El temporizador 1 es un temporizador de una sola activación que tiene un periodo de 6 ciclos. Comienza
en el momento t1, por lo que su función de devolución de llamada se ejecuta 6 ciclos después, en el
momento t7. Como el temporizador 1 es de una sola activación, su función de devolución de llamada no
se vuelve a ejecutar.
• El temporizador 2 es un temporizador de recarga automática que tiene un periodo de 5 ciclos. Comienza
en el momento t1, por lo que su función de devolución de llamada se ejecuta cada 5 ciclos después del
momento t1. En la figura, dicha ejecución se produce en los momentos t6, t11 y t16.
103
Kernel FreeRTOS Guía para desarrolladores
Estados de los temporizadores de software
• Latente
Hay un temporizador de software latente y se puede hacer referencia a él utilizando su controlador, pero
no se está ejecutando, lo que significa que sus funciones de devolución de llamada no se ejecutan.
• En ejecución
En las dos figuras siguientes se muestran las posibles transiciones entre los estados Latente y En
ejecución de un temporizador de recarga automática y un temporizador de una activación. La diferencia
clave entre las dos figuras radica en el estado en que entra el temporizador cuando caduca. El
temporizador de recarga automática ejecuta su función de devolución de llamada y después vuelve a
entrar en el estado En ejecución. El temporizador de una activación ejecuta su función de devolución de
llamada y luego entra en el estado Latente.
En la figura siguiente se muestran los estados y las transiciones del temporizador de software de recarga
automática.
En la figura siguiente se muestran los estados y las transiciones del temporizador de software de una
activación.
104
Kernel FreeRTOS Guía para desarrolladores
El contexto de los temporizadores de software
La tarea de demonio es una tarea de FreeRTOS estándar que se crea automáticamente cuando se inicia el
programador. Su prioridad y el tamaño de pila se establecen con las constantes de configuración de tiempo
de compilación configTIMER_TASK_PRIORITY y configTIMER_TASK_STACK_DEPTH respectivamente.
Ambas constantes están definidas en FreeRTOSConfig.h.
Las funciones de temporizador de software no deben llamar a funciones de API de FreeRTOS que hagan
que la tarea de llamada entre en el estado Bloqueado, ya que si esto ocurre, la tarea de demonio también
entrará en el estado Bloqueado.
La cola de comandos del temporizador es una cola de FreeRTOS estándar que se crea automáticamente
cuando se inicia el programador. La longitud de la cola de comandos del temporizador se establece
mediante la constante de configuración del tiempo de compilación configTIMER_QUEUE_LENGTH en
FreeRTOSConfig.h.
En la figura siguiente se muestra cómo función de API de temporizador de software usa la cola de
comandos para comunicarse con la tarea de demonio RTOS.
105
Kernel FreeRTOS Guía para desarrolladores
Programación de tareas de demonio
En esta figura se muestra el patrón de ejecución que se genera cuando la prioridad de la tarea de demonio
está por debajo de la prioridad de una tarea que llama a la función de API xTimerStart().
1. En el momento t1
La tarea 1 se encuentra en el estado En ejecución, mientras que la tarea de demonio está en el estado
Bloqueado.
xTimerStart() envía un comando a la cola de comandos del temporizador, lo que hace que la tarea de
demonio abandone el estado Bloqueado. La prioridad de la tarea 1 es superior a la prioridad de la tarea
de demonio, por lo que esta última tarea no reemplaza a la tarea 1.
La tarea 1 llama a una función de API y esto hace que entre en el estado Bloqueado. Ahora la tarea de
demonio es la tarea de máxima prioridad que se encuentra en el estado Listo, por lo que el programador
selecciona la tarea de demonio como la tarea que tiene que entrar en el estado En ejecución. La tarea
de demonio comienza a procesar el comando que la tarea 1 ha enviado a la cola de comandos del
temporizador.
Nota: El momento en que expira el temporizador de software que se está iniciando se calcula a partir
del momento en que se envía el comando "start a timer" a la cola de comandos del temporizador. No se
106
Kernel FreeRTOS Guía para desarrolladores
Programación de tareas de demonio
calcula a partir del momento en que la tarea de demonio recibe el comando "start a timer" de la cola de
comandos del temporizador.
5. En el momento t5
La tarea de demonio ha acabado de procesar el comando que ha recibido de la tarea 1 e intenta recibir
más datos desde la cola de comandos del temporizador. La cola de comandos del temporizador está
vacía, por lo que la tarea de demonio vuelve a entrar en el estado Bloqueado. La tarea de demonio
dejará de nuevo el estado Bloqueado si se envía un comando a la cola de comandos del temporizador o
caduca un temporizador de software.
Ahora la tarea de inactividad es la tarea de máxima prioridad que se encuentra en el estado Listo, por
lo que el programador selecciona la tarea de inactividad como la tarea que tiene que entrar en el estado
En ejecución.
En la siguiente figura se muestra una situación similar a la que se enseña en la figura anterior. Esta vez la
prioridad de la tarea de demonio supera la prioridad de la tarea que llama a xTimerStart().
1. En el momento t1
Igual que en la situación anterior, la tarea 1 se encuentra en el estado En ejecución, mientras que la
tarea de demonio se encuentra en el estado Bloqueado.
2. En el momento t2
xTimerStart() envía un comando a la cola de comandos del temporizador, lo que hace que la tarea de
demonio abandone el estado Bloqueado. La prioridad de la tarea de demonio es superior a la prioridad
de la tarea 1, por lo que el programador selecciona la tarea de demonio como la tarea que debe entrar
en el estado En ejecución.
La tarea de demonio ha reemplazado a la tarea 1 antes de que esta acabara de ejecutar la función
xTimerStart() y ahora se encuentra en el estado Listo.
La tarea de demonio comienza a procesar el comando que la tarea 1 ha enviado a la cola de comandos
del temporizador.
3. En el momento t3
La tarea de demonio ha acabado de procesar el comando que ha recibido de la tarea 1 e intenta recibir
más datos desde la cola de comandos del temporizador. La cola de comandos del temporizador está
vacía, por lo que la tarea de demonio vuelve a entrar en el estado Bloqueado.
107
Kernel FreeRTOS Guía para desarrolladores
Creación e inicio de un temporizador de software
Ahora la tarea 1 es la tarea de máxima prioridad que se encuentra en el estado Listo, por lo que el
programador selecciona la tarea 1 como la tarea que debe entrar en el estado En ejecución.
4. En el momento t4
La tarea 1 ha sido reemplazada por la tarea de demonio antes de acabar de ejecutar la función
xTimerStart() y sale (regresa) de xTimerStart() solo después de volver a entrar en el estado En
ejecución.
5. En el momento t5
La tarea 1 llama a una función de API y esto hace que entre en el estado Bloqueado. Ahora la tarea
de inactividad es la tarea de máxima prioridad que se encuentra en el estado Listo, por lo que el
programador selecciona la tarea de inactividad como la tarea que tiene que entrar en el estado En
ejecución.
En la figura de la primera situación, el tiempo transcurrido entre el momento en que la tarea 1 envía un
comando a la cola de comandos del temporizador y el momento en que la tarea de demonio recibe y
procesa el comando. La tarea de demonio ha recibido y procesado el comando que le ha enviado la tarea
1 antes de que dicha tarea regrese de la función que ha enviado el comando.
Los comandos enviados a la cola de comandos del temporizador contienen una marca de tiempo. La
marca de tiempo se utiliza para tener en cuenta el tiempo transcurrido entre el momento en que una tarea
de aplicación envía un comando y el momento en que lo procesa una tarea de demonio. Por ejemplo, si se
envía un comando "start a timer" para iniciar un temporizador que tiene un periodo de 10 ciclos, la marca
temporal se utiliza para garantizar que el temporizador que se está iniciando venza 10 ciclos después
de haberse enviado el comando y no 10 ciclos después de que la tarea de demonio haya procesado el
comando.
La referencia a los temporizadores de software se lleva a cabo con variables del tipo TimerHandle_t.
xTimerCreate() se utiliza para crear un temporizador de software y devuelve un TimerHandle_t para hacer
referencia al temporizador de software que crea. Los temporizadores de software se crean en el estado
Latente.
Los temporizadores de software se pueden crear antes de que el programador se ejecute o desde una
tarea una vez que se haya iniciado el programador.
108
Kernel FreeRTOS Guía para desarrolladores
La función de API xTimerStart()
Puede llamar a xTimerStart() antes de que el programador se inicie, pero el temporizador de software no
se iniciará hasta el momento en que se inicie el programador.
109
Kernel FreeRTOS Guía para desarrolladores
La función de API xTimerStart()
Nota: No llame a xTimerStart() desde una rutina de servicio de interrupción. En su lugar, utilice la versión a
prueba de interrupciones, xTimerStartFromISR().
Si INCLUDE_vTaskSuspend se establece
en 1 en FreeRTOSConfig.h, significa que si
establece xTicksToWait portMAX_DELAY
en portMAX_DELAY, la tarea que realiza la
llamada se quedará indefinidamente en el estado
Bloqueado (sin tiempo de espera agotado) a
esperar a que quede espacio disponible en la cola
de comandos del temporizador.
1. pdPASS
110
Kernel FreeRTOS Guía para desarrolladores
Creación de temporizadores de una activación y
temporizadores de recarga automática (ejemplo 13)
/* The periods assigned to the one-shot and auto-reload timers are 3.333 second and half a
second, respectively. */
111
Kernel FreeRTOS Guía para desarrolladores
Creación de temporizadores de una activación y
temporizadores de recarga automática (ejemplo 13)
/* Create the one-shot timer, storing the handle to the created timer in xOneShotTimer.
*/
/* Create the auto-reload timer, storing the handle to the created timer in
xAutoReloadTimer. */
/* Start the software timers, using a block time of 0 (no block time). The
scheduler has not been started yet so any block time specified here would be ignored
anyway. */
vTaskStartScheduler();
for( ;; );
Las funciones de devolución de llamada del temporizador se limitan a imprimir un mensaje cada vez que
reciben una llamada. A continuación se muestra la implementación de la función de devolución de llamada
del temporizador de una sola activación.
112
Kernel FreeRTOS Guía para desarrolladores
Creación de temporizadores de una activación y
temporizadores de recarga automática (ejemplo 13)
TickType_t xTimeNow;
xTimeNow = xTaskGetTickCount();
/* Output a string to show the time at which the callback was executed. */
ulCallCount++;
TickType_t xTimeNow;
xTimeNow = xTaskGetTickCount();
/* Output a string to show the time at which the callback was executed. */
ulCallCount++;
113
Kernel FreeRTOS Guía para desarrolladores
El ID de los temporizadores
El ID de los temporizadores
Cada temporizador de software tiene un ID, que es un valor de etiqueta que puede utilizar el programador
de aplicaciones para cualquier objetivo que desee. El ID se almacena en un puntero vacío (void *),
para que pueda almacenar un valor entero directamente, apuntar a cualquier otro objeto o utilizarse como
puntero de función.
A diferencia de lo que ocurre con otras funciones de API del temporizador de software, vTimerSetTimerID()
y pvTimerGetTimerID() acceden directamente al temporizador de software. No envían un comando a la
cola de comandos del temporizador.
114
Kernel FreeRTOS Guía para desarrolladores
La función de API pvTimerGetTimerID()
En el ejemplo 13 se han usado dos funciones de devolución de llamada independientes: una para el
temporizador de una activación y otra para el temporizador de recarga automática. En este ejemplo se
crea una funcionalidad similar, pero se asigna una única función de devolución de llamada a ambos
temporizadores de software.
115
Kernel FreeRTOS Guía para desarrolladores
Uso del parámetro de función de devolución de llamada
y el ID del temporizador de software (ejemplo 14)
TickType_t xTimeNow;
uint32_t ulExecutionCount;
/* A count of the number of times this software timer has expired is stored in the
timer's ID. Obtain the ID, increment it, then save it as the new ID value. The ID is a
void pointer, so is cast to a uint32_t. */
ulExecutionCount++;
xTimeNow = xTaskGetTickCount();
/* The handle of the one-shot timer was stored in xOneShotTimer when the timer was
created. Compare the handle passed into this function with xOneShotTimer to determine if
it was the one-shot or auto-reload timer that expired, then output a string to show the
time at which the callback was executed. */
else
/* xTimer did not equal xOneShotTimer, so it must have been the auto-reload timer
that expired. */
if( ulExecutionCount == 5 )
/* Stop the auto-reload timer after it has executed 5 times. This callback
function executes in the context of the RTOS daemon task, so must not call any functions
that might place the daemon task into the Blocked state. Therefore, a block time of 0 is
used. */
xTimerStop( xTimer, 0 );
El resultado se muestra aquí. El temporizador de recarga automática se ejecuta solo cinco veces.
116
Kernel FreeRTOS Guía para desarrolladores
Cambio de los periodos de un temporizador
Algunos proyectos de ejemplo realizan las comprobaciones automáticas de una tarea y utilizan la función
vTaskDelay() para controlar la velocidad a la que se alterna el LED. Otros proyectos de ejemplo realizan
las comprobaciones automáticas en una función de devolución de llamada del temporizador de software y
utilizan el periodo del controlador para controlar la velocidad a la que se alterna el LED.
Nota: No llame a xTimerChangePeriod() desde una rutina de servicio de interrupción. En su lugar, utilice la
versión a prueba de interrupciones, xTimerChangePeriodFromISR().
117
Kernel FreeRTOS Guía para desarrolladores
La función de API xTimerChangePeriod()
Si INCLUDE_vTaskSuspend se establece
en 1 en FreeRTOSConfig.h, significa que si
establece xTicksToWait portMAX_DELAY
en portMAX_DELAY, la tarea que realiza la
llamada se quedará indefinidamente en el estado
Bloqueado (sin tiempo de espera agotado) a
esperar a que quede espacio disponible en la cola
de comandos del temporizador.
1. pdPASS
118
Kernel FreeRTOS Guía para desarrolladores
La función de API xTimerChangePeriod()
/* The check timer is created with a period of 3000 milliseconds, resulting in the LED
toggling every 3 seconds. If the self-checking functionality detects an unexpected state,
then the check timer's period is changed to just 200 milliseconds, resulting in a much
faster toggle rate. */
/* No errors have yet been detected. Run the self-checking function again.
The function asks each task created by the example to report its own status, and also
checks that all the tasks are actually still running (and so able to report their status
correctly). */
119
Kernel FreeRTOS Guía para desarrolladores
Restablecimiento de un temporizador de software
xErrorDetected = pdTRUE;
/* Toggle the LED. The rate at which the LED toggles will depend on how
often this function is called, which is determined by the period of the check
timer. The timer's period will have been reduced from 3000ms to just 200ms if
CheckTasksAreRunningWithoutError() has ever returned pdFAIL. */
ToggleLED();
120
Kernel FreeRTOS Guía para desarrolladores
La función de API xTimerReset()
También puede usar xTimerReset() para iniciar un temporizador que se encuentra en el estado Latente.
Nota: No llame a xTimerReset() desde una rutina de servicio de interrupción. En su lugar, utilice la versión
a prueba de interrupciones, xTimerResetFromISR().
Si INCLUDE_vTaskSuspend se establece
en 1 en FreeRTOSConfig.h, significa que si
establece xTicksToWait portMAX_DELAY
en portMAX_DELAY, la tarea que realiza la
llamada se quedará indefinidamente en el estado
Bloqueado (sin tiempo de espera agotado) a
esperar a que quede espacio disponible en la cola
de comandos del temporizador.
1. pdPASS
121
Kernel FreeRTOS Guía para desarrolladores
Restablecimiento de un temporizador
de software (ejemplo 15)
• La iluminación de fondo (simulada) se activa cuando se pulsa una tecla y se desactiva en la función de
devolución de llamada del temporizador de software.
• El temporizador de software se reinicia cada vez que se pulsa una tecla.
• Por lo tanto, el periodo de tiempo durante el cual debe pulsarse una tecla para evitar que se desactive la
iluminación de fondo es igual al periodo del temporizador de software. Si antes de que el temporizador
caduque no se restablece el temporizador de software pulsando una tecla, la función de devolución de
llamada del temporizador se ejecutará y se desactivará la iluminación de fondo.
xSimulatedBacklightOn = pdFALSE;
122
Kernel FreeRTOS Guía para desarrolladores
Restablecimiento de un temporizador
de software (ejemplo 15)
El uso de FreeRTOS permite controlar la aplicación mediante eventos. Los diseños controlados mediante
eventos usan con eficiencia el tiempo. Solo se asigna tiempo en caso de que se produzca un evento.
El tiempo de procesamiento no se malgasta realizando sondeos para buscar eventos que no se han
producido. En el ejemplo no se ha podido aplicar un diseño de control mediante eventos porque no es
práctico procesar las interrupciones de teclado cuando se usa el puerto de Windows FreeRTOS. Por este
motivo ha sido preciso utilizar la técnica de sondeo mucho menos eficiente.
En el código siguiente se muestra la tarea de sondeo del teclado. Si fuera una rutina de interrupción de
servicio, se utilizaría xTimerResetFromISR() en lugar de xTimerReset().
TickType_t xTimeNow;
for( ;; )
if( _kbhit() != 0 )
xTimeNow = xTaskGetTickCount();
/* The backlight was off, so turn it on and print the time at which it was
turned on. */
xSimulatedBacklightOn = pdTRUE;
else
/* The backlight was already on, so print a message to say the timer is
about to be reset and the time at which it was reset. */
123
Kernel FreeRTOS Guía para desarrolladores
Restablecimiento de un temporizador
de software (ejemplo 15)
/* Reset the software timer. If the backlight was previously off, then this
call will start the timer. If the backlight was previously on, then this call will restart
the timer. A real application might read key presses in an interrupt. If this function
was an interrupt service routine, then xTimerResetFromISR() must be used instead of
xTimerReset(). */
/* Read and discard the key that was pressed. It is not required by this simple
example. */
( void ) _getch();
• La primera tecla se pulsó cuando el recuento de ciclos era de 812. En ese momento se activó la
iluminación de fondo y se inició el temporizador de una activación.
• Se volvieron a pulsar teclas cuando el recuento de ciclos era de 1813, 3114, 4015 y 5016. En todas esas
pulsaciones de teclas el temporizador se restableció antes de caducar.
• El temporizador caducó cuando el recuento de ciclos era de 10016. En ese momento se desactivó la
iluminación de fondo.
124
Kernel FreeRTOS Guía para desarrolladores
API a prueba de interrupciones
Administración de interrupciones
En esta sección se explica lo siguiente:
• Qué funciones de API de FreeRTOS se pueden utilizar desde una rutina del servicio de interrupciones
• Métodos para diferir el procesamiento de una interrupción a una tarea
• Cómo crear y utilizar semáforos binarios y semáforos de recuento
• Las diferencias entre los semáforos binarios y de recuento
• Cómo utilizar una cola para pasar datos dentro y fuera de una rutina del servicio de interrupciones
• El modelo de anidamiento de interrupciones disponible con algunos puertos de FreeRTOS
Los sistemas en tiempo real incorporados tienen que realizar acciones en respuesta a eventos que se
originan en el entorno. Por ejemplo, es posible que un paquete que llega a un periférico de la Ethernet
(el evento) necesite pasarse a una pila TCP/IP para procesarlo (la acción). Los sistemas no triviales
tendrán que dar servicio a eventos que provienen de diversos orígenes, todos los cuales tendrán diferentes
requisitos de sobrecarga de procesamiento y tiempo de respuesta. Piense en estas preguntas al elegir su
estrategia de implementación del procesamiento de eventos:
1. ¿Cómo se debe detectar el evento? Las interrupciones se utilizan normalmente, pero las entradas
también se pueden sondear.
2. Cuando se utilizan interrupciones, ¿qué cantidad de procesamiento debe realizarse dentro de la rutina
del servicio de interrupciones y qué cantidad debe realizarse fuera? Se recomienda que cada rutina del
servicio de interrupciones sea lo más corta posible.
3. ¿Cómo se comunican los eventos al código principal (no ISR) y cómo se puede estructurar este código
para adaptarse mejor al procesamiento de casos potencialmente asíncronos?
Es importante establecer una distinción entre la prioridad de una tarea y la prioridad de una interrupción:
• Una tarea es una característica de software que no está relacionada con el hardware en el que se
ejecuta FreeRTOS. La prioridad de una tarea la asigna en el software el programador de la aplicación.
Un algoritmo de software (el programador) decide qué tarea estará en estado En ejecución.
• Aunque la rutina del servicio de interrupciones está escrita en el software, es una característica del
hardware, porque el hardware controla qué rutina del servicio de interrupciones se ejecuta y cuándo. Las
tareas solo se ejecutarán cuando no haya en ejecución ninguna rutina del servicio de interrupciones, de
forma que la interrupción de menor prioridad interrumpirá a la tarea de mayor prioridad. No hay forma de
una tarea tenga prioridad sobre una rutina del servicio de interrupciones.
Todas las arquitecturas en las que se ejecuta FreeRTOS son capaces de procesar interrupciones, pero los
detalles sobre la entrada de interrupciones y la asignación de prioridades a las interrupciones varían en
cada arquitectura.
125
Kernel FreeRTOS Guía para desarrolladores
Ventajas de utilizar una API a prueba
de interrupciones independiente
acciones que no son válidas dentro de una rutina del servicio de interrupciones. La más evidente consiste
en colocar la tarea que ha llamado a la función de API en el estado Bloqueado. Si una función de API se
llama desde una rutina del servicio de interrupciones, eso significa que no se está llamando desde una
tarea, por lo que no hay ninguna tarea de llamada que se pueda poner en el estado Bloqueado. Para
resolver este problema, FreeRTOS proporciona dos versiones de algunas funciones de API: una versión
que puede utilizarse desde tareas y otra que puede utilizarse desde rutinas del servicio de interrupciones.
Al nombre de las funciones que están diseñadas para usarse desde ISR se le añade la palabra "FromISR".
Nota: En una ISR, no llame a una función de API de FreeRTOS que no tenga "FromISR" en su nombre.
• Las funciones de API necesitarían lógica adicional para determinar si se han llamado desde una tarea
o una ISR. La lógica adicional introduciría nuevas rutas a través de la función, lo que haría que esas
funciones fueran más largas, más complejas y más difíciles de probar.
• Algunos parámetros de funciones de API se quedarían obsoletas cuando una tarea llamara a la función,
mientras que otras se quedarían obsoletas cuando una ISR llamara a la función.
• Cada puerto de FreeRTOS tendría que incluir un mecanismo para determinar el contexto de ejecución
(tarea o ISR).
• Aquellas arquitecturas en las que no es fácil determinar el contexto de ejecución (tarea o ISR),
necesitarían un código de entrada de interrupciones adicional, derrochador, más complejo y no estándar
que permitiría que el software proporcionase el contexto de ejecución.
Normalmente, este problema solo se da al integrar código de terceros, ya que es el único momento en
que el diseño del software se sale del control del programador de la aplicación. Puede utilizar una de las
siguientes técnicas:
1. Diferir el procesamiento de la interrupción a una tarea para que la función de API solo se llame desde el
contexto de una tarea.
2. Si utiliza un puerto de FreeRTOS que admite el anidamiento de interrupciones, utilice la versión de la
función de API que termina en "FromISR", porque esa versión se puede llamar desde tareas e ISR. (Lo
contrario no es posible. No se debe llamar a funciones de API que no terminan en "FromISR" desde una
ISR).
3. Normalmente, el código de terceros incluye una capa de abstracción de RTOS que se puede
implementar para probar el contexto desde el que se llama a la función (tarea o interrupción) y, a
continuación, llamar a la función de API que sea adecuada para el contexto.
126
Kernel FreeRTOS Guía para desarrolladores
Parámetro xHigherPriorityTaskWoken
Parámetro xHigherPriorityTaskWoken
Si una interrupción realiza un cambio de contexto, la tarea que se está ejecutando cuando la interrupción
sale puede ser diferente a la tarea que se estaba ejecutando cuando se introdujo la interrupción. La
interrupción tendrá interrumpida una tarea, pero se devuelve a otra tarea.
Algunas funciones de API de FreeRTOS pueden mover una tarea del estado Bloqueado al estado Listo.
Esto ya ocurre con funciones como xQueueSendToBack(), que desbloquean una tarea si había una tarea
en estado Bloqueado a la espera de que haya disponibles datos en la cola del asunto.
Si la prioridad de una tarea que desbloquea una función de API de FreeRTOS es mayor que la prioridad
de la tarea que está en estado En ejecución, según la política de programación de FreeRTOS, se debe
producir un cambio a la tarea de mayor prioridad. El momento en que se produce el cambio a la tarea de
mayor prioridad depende de la función desde el que se llama a la función de API.
El cambio a una tarea de mayor prioridad no se produce de forma automática dentro de una interrupción.
En su lugar, se establece una variable para informar al programador de la aplicación que debe
realizarse un cambio de contexto. Las funciones de API a prueba de interrupciones (las que terminan
en "FromISR") poseen un parámetro de puntero denominado pxHigherPriorityTaskWoken que se utiliza
para este fin.
Existen varias razones por las que los cambios de contexto no se producen automáticamente dentro de la
versión a prueba de interrupciones de una función de API:
Una interrupción puede ejecutarse más de una vez antes de que sea necesario que una tarea realice
algún procesamiento. Por ejemplo, supongamos que una tarea procesa una cadena que se ha recibido
a través de un UART basado en interrupciones. Sería un desperdicio que la ISR de UART cambiara
a la tarea cada vez que se recibe un carácter, ya que la tarea únicamente tendría que realizar algún
procesamiento cuando haya recibido la cadena completa.
2. Controlar la secuencia de ejecución
127
Kernel FreeRTOS Guía para desarrolladores
Macros portYIELD_FROM_ISR()
y portEND_SWITCHING_ISR()
específicos de su aplicación. Para ello, puede usar el mecanismo de bloqueo del programador de
FreeRTOS.
3. Portabilidad
Es el mecanismo más sencillo que se puede utilizar en todos los puertos de FreeRTOS.
4. Eficiencia
Los puertos que están pensados para arquitecturas de procesadores más pequeños solo permiten
solicitar un cambio de contexto al final de una ISR. Para eliminar esa restricción, sería necesario
disponer de código adicional más complejo. También permite realizar más de una llamada a una función
de API de FreeRTOS en el mismo ISR sin generar más de una solicitud para un cambio de contexto
dentro del mismo ISR.
5. Ejecución en la interrupción de ciclos de RTOS
Puede añadir código de aplicación a la interrupción de ciclos de RTOS. El resultado del intento de un
cambio de contexto dentro de la interrupción de ciclos depende del puerto de FreeRTOS en uso. En el
mejor de los casos, se producirá una llamada innecesaria al programador.
Macros portYIELD_FROM_ISR() y
portEND_SWITCHING_ISR()
taskYIELD() es una macro que se puede llamar en una tarea para solicitar un cambio de contexto.
portYIELD_FROM_ISR() y portEND_SWITCHING_ISR() son versiones a prueba de interrupciones de
taskYIELD (). portYIELD_FROM_ISR() y portEND_SWITCHING_ISR() se utilizan de la misma forma
y hacen lo mismo. Antiguamente, portEND_SWITCHING_ISR() era el nombre que se utilizaba en los
puertos de FreeRTOS en los que era obligatorio utilizar un contenedor de código de ensamblado.
portYIELD_FROM_ISR() era el nombre que se utilizaba en los puertos de FreeRTOS que permitían escribir
todo el controlador de interrupciones en C. Algunos puertos de FreeRTOS solo incluyen una de las dos
macros. Los puertos de FreeRTOS más modernos incluyen ambas macros.
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
La mayoría de los puertos de FreeRTOS permiten llamar a portYIELD_FROM_ISR() desde cualquier lugar
de una ISR. Algunos puertos de FreeRTOS (principalmente para arquitecturas más pequeñas) permiten
llamar a portYIELD_FROM_ISR() solo al final de una ISR.
128
Kernel FreeRTOS Guía para desarrolladores
Procesamiento diferido de interrupciones
• Aunque las tareas tengan asignada una prioridad muy alta, solo se ejecutan si el hardware no está
atendiendo ninguna interrupción.
• Las ISR pueden interrumpir (añadir fluctuación) al inicio y al tiempo de ejecución de una tarea.
• En función de la arquitectura en la que se esté ejecutando FreeRTOS, no sería posible aceptar nuevas
interrupciones, o al menos un subconjunto de nuevas interrupciones, mientras que una ISR se está
ejecutando.
• El programador de la aplicación debe tener en cuenta las consecuencias derivadas de que una tarea y
una ISR accedan al mismo tiempo a recursos como variables, periféricos y búferes de memoria, por lo
que debe evitarlo.
• Algunos puertos de FreeRTOS permiten el anidamiento de interrupciones, pero esto puede aumentar
la complejidad y reducir la previsibilidad. Cuanto más corta sea una interrupción, menor será la
probabilidad de que se anide.
Una rutina del servicio de interrupciones debe registrar la causa de la interrupción y borrarla. Con
frecuencia, cualquier otro procesamiento que necesite la interrupción se puede realizar en una tarea, lo
que permite que la rutina del servicio de interrupciones salga lo más rápido posible. Esto se denomina
procesamiento diferido de interrupciones, porque el procesamiento que requiere la interrupción se difiere
de la ISR a una tarea.
Diferir el procesamiento de interrupciones a una tarea también permite que el programador de aplicaciones
priorice el procesamiento en relación con otras tareas de la aplicación y utilice todas las funciones de API
de FreeRTOS.
En la siguiente figura, se muestra una situación en la que la tarea 1 es una tarea de aplicación normal y la
tarea 2 es la tarea a la que se difiere el procesamiento de interrupciones.
No existe ninguna regla que indique cuándo es mejor realizar todo el procesamiento que requiere
una interrupción en la ISR y cuándo es mejor diferir parte del procesamiento a una tarea. Diferir el
procesamiento a una tarea es más útil cuando:
129
Kernel FreeRTOS Guía para desarrolladores
Semáforos binarios utilizados para la sincronización
• El procesamiento que requiere la interrupción no es trivial. Por ejemplo, si la interrupción solo está
almacenando el resultado de una conversión de analógico a digital, es mejor realizarlo en la ISR, pero
si el resultado de la conversión también debe pasarse a través de un filtro de software, podría ser mejor
ejecutar el filtro en una tarea.
• Es conveniente que el procesamiento de la interrupción realice una acción que no se pueda realizar
dentro de una ISR, como, por ejemplo, escribir en una consola o asignar memoria.
• El procesamiento de interrupciones no es determinista (es decir, no se sabe de antemano cuánto tiempo
tardará el procesamiento).
En la siguiente figura, se utiliza el ejemplo de la figura anterior, pero se ha cambiado el texto para describir
cómo se puede controlar la ejecución de la tarea de procesamiento diferido mediante un semáforo.
La tarea de procesamiento diferido utiliza una llamada "take" de bloqueo a un semáforo como medio para
entrar en el estado Bloqueado a la espera de que se produzca el evento. Cuando se produce el evento,
la ISR utiliza una operación "give" en el mismo semáforo para desbloquear la tarea, de modo que el
procesamiento de eventos necesario pueda continuar.
130
Kernel FreeRTOS Guía para desarrolladores
Semáforos binarios utilizados para la sincronización
Tomar un semáforo y dar un semáforo son conceptos que tienen distintos significados, en función
de cada situación. En este ejemplo de sincronización de interrupciones, el semáforo binario puede
considerarse conceptualmente como una cola con una longitud de uno. La cola puede contener un
máximo de un elemento en un momento dado, por lo que siempre está llena o vacía (binario). Al llamar
a xSemaphoreTake(), la tarea a la que se difiere el procesamiento de interrupciones intenta leer de
la cola con un tiempo de bloqueo, lo que hace que la tarea pase al estado Bloqueado si la cola está
vacía. Cuando se produce el evento, la ISR utiliza la función xSemaphoreGiveFromISR() para colocar
un token (el semáforo) en la cola, lo que hace que la cola se llene. Esto hace que la tarea salga del
estado Bloqueado y elimine el token, dejando la cola vacía una vez más. Cuando la tarea ha completado
su procesamiento, intenta leer otra vez la cola y, al descubrir que está vacía, vuelve a pasar al estado
Bloqueado a la espera del siguiente evento. La secuencia se muestra aquí.
131
Kernel FreeRTOS Guía para desarrolladores
Semáforos binarios utilizados para la sincronización
Esta figura muestra cómo la interrupción da el semáforo, a pesar de que no lo ha tomado previamente y
cómo la tarea toma el semáforo, pero nunca lo vuelve a dar. Esta es la razón por la que se dice que este
caso es algo similar a escribir y leer de una cola. Suele causar confusión, ya que no sigue las mismas
132
Kernel FreeRTOS Guía para desarrolladores
Función de API xSemaphoreCreateBinary()
reglas que otros casos de uso de semáforos, donde una tarea que toma un semáforo siempre debe volver
a darlo, como en los ejemplos que se describen en la sección Administración de recursos (p. 161).
Para poder utilizar un semáforo, antes hay que crearlo. Para crear un semáforo binario, utilice la función de
API xSemaphoreCreateBinary().
Todos los tipos de semáforos de FreeRTOS, excepto los mutex recursivos, se pueden tomar con la función
xSemaphoreTake(). xSemaphoreTake() no debe utilizarse desde una rutina del servicio de interrupciones.
133
Kernel FreeRTOS Guía para desarrolladores
Función de API xSemaphoreGiveFromISR()
1. pdPASS
134
Kernel FreeRTOS Guía para desarrolladores
Uso de un semáforo binario para sincronizar
una tarea con una interrupción (Ejemplo 16)
xSemaphore
El semáforo que se está dando. Una variable de tipo SemaphoreHandle_t hace referencia al semáforo y
debe crearse de forma explícita antes de usarse.
pxHigherPriorityTaskWoken
Es posible que un único semáforo tenga una o varias tareas bloqueadas a la espera de que el semáforo
esté disponible. Al llamar a xSemaphoreGiveFromISR(), puede hacer que el semáforo esté disponible y,
por tanto, que una tarea que estuviera esperando al semáforo salga del estado Bloqueado. Si al llamar a
xSemaphoreGiveFromISR(), una tarea sale del estado Bloqueado y la prioridad de la tarea desbloqueada
es mayor que la de la tarea que se está ejecutando en ese momento (la tarea que se interrumpió),
xSemaphoreGiveFromISR() establecerá internamente *pxHigherPriorityTaskWoken en pdTRUE. Si
xSemaphoreGiveFromISR() establece este valor en pdTRUE, normalmente debe efectuarse un cambio
de contexto antes de salir de la interrupción. De este modo, se asegurará de que la interrupción vuelve
directamente a la tarea con el estado Listo de máxima prioridad.
pdPASS
pdFAIL
Se utiliza una tarea periódica simple para generar una interrupción de software cada 500 milisegundos.
La interrupción de software se utiliza por comodidad debido a la complejidad que supone enlazarse a una
interrupción real en algunos entornos de destino.
El código siguiente muestra la implementación de la tarea periódica. La tarea imprime una cadena antes y
después de que se genere la interrupción. Puede ver la secuencia de la ejecución en la salida.
/* The number of the software interrupt used in this example. The code shown is from the
Windows project, where numbers 0 to 2 are used by the FreeRTOS Windows port itself, so 3
is the first number available to the application. */
#define mainINTERRUPT_NUMBER 3
for( ;; )
135
Kernel FreeRTOS Guía para desarrolladores
Uso de un semáforo binario para sincronizar
una tarea con una interrupción (Ejemplo 16)
vTaskDelay( xDelay500ms );
/* Generate the interrupt, printing a message both before and after the interrupt
has been generated, so the sequence of execution is evident from the output. The syntax
used to generate a software interrupt is dependent on the FreeRTOS port being used.
The syntax used below can only be used with the FreeRTOS Windows port, in which such
interrupts are only simulated.*/
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
Aunque este código sea suficiente como ejemplo de dónde genera el software las interrupciones, no es
adecuado en los casos en los que las interrupciones las generen periféricos de hardware. Hay que cambiar
la estructura del código para adecuarla a las interrupciones generadas por hardware.
for( ;; )
/* Use the semaphore to wait for the event. The semaphore was created before
the scheduler was started, so before this task ran for the first time. The task blocks
indefinitely, meaning this function call will only return once the semaphore has been
successfully obtained so there is no need to check the value returned by xSemaphoreTake().
*/
/* To get here the event must have occurred. Process the event (in this case, just
print out a message). */
En el siguiente código se muestra la ISR. No hace mucho más que dar el semáforo para desbloquear la
tarea en la que se ha diferido el procesamiento de la interrupción.
136
Kernel FreeRTOS Guía para desarrolladores
Uso de un semáforo binario para sincronizar
una tarea con una interrupción (Ejemplo 16)
El prototipo de la ISR y la macro que se ha llamado para forzar un cambio de contexto son correctos para
el puerto de Windows de FreeRTOS, pero es posible que no lo sean para otros puertos de FreeRTOS.
Para encontrar la sintaxis necesaria para el puerto que está utilizando, consulte las páginas específicas
de los puertos en la documentación del sitio web FreeRTOS.org y los ejemplos que se proporcionan en la
descarga de FreeRTOS.
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
La función main() crea el semáforo binario y las tareas, instala el controlador de interrupciones e inicia el
programador. En el siguiente código, se muestra la implementación.
La sintaxis de la función que se llama para instalar un controlador de interrupciones es específica del
puerto de Windows de FreeRTOS. Podría ser diferente para otros puertos de FreeRTOS. Para encontrar la
sintaxis necesaria para el puerto que está utilizando, consulte la documentación específica de los puertos
del sitio web FreeRTOS.org y los ejemplos que se proporcionan en la descarga de FreeRTOS.
xBinarySemaphore = xSemaphoreCreateBinary();
137
Kernel FreeRTOS Guía para desarrolladores
Uso de un semáforo binario para sincronizar
una tarea con una interrupción (Ejemplo 16)
/* Create the 'handler' task, which is the task to which interrupt processing is
deferred. 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. */
/* 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 preempted each time
the handler task exits the Blocked state. */
/* Install the handler for the software interrupt. The syntax required to do this
is depends on the FreeRTOS port being used. The syntax shown here can only be used with
the FreeRTOS Windows port, where such interrupts are only simulated. */
vTaskStartScheduler();
for( ;; );
El código produce la siguiente salida. Como era de esperar, vHandlerTask() entra en el estado En
ejecución en cuanto se genera la interrupción, por lo que la salida de la tarea divide la salida que produce
la tarea periódica.
138
Kernel FreeRTOS Guía para desarrolladores
Mejora de la implementación de
la tarea utilizada en el Ejemplo 16
1. Se ha produce la interrupción.
2. la ISR se ejecuta y da el semáforo para desbloquear la tarea.
3. La tarea se ejecuta inmediatamente después de la ISR y toma el semáforo.
4. La tarea procesa el evento y, a continuación, intenta tomar el semáforo de nuevo y pasa al estado
Bloqueado porque el semáforo todavía no está disponible (aún no se ha producido otra interrupción).
Cuando se ejecutara el segundo ISR, el semáforo estaría vacío, por lo que la ISR daría el semáforo y la
tarea procesaría el segundo evento inmediatamente después de que se hubiera terminado de procesar el
primer evento. Aquí se muestra esa situación.
139
Kernel FreeRTOS Guía para desarrolladores
Mejora de la implementación de
la tarea utilizada en el Ejemplo 16
Cuando se ejecutase el tercer ISR, el semáforo ya estaría disponible, lo que impediría que la ISR diera de
nuevo el semáforo, por lo que la tarea no sabría que se ha producido el tercer evento. Aquí se muestra esa
situación.
140
Kernel FreeRTOS Guía para desarrolladores
Mejora de la implementación de
la tarea utilizada en el Ejemplo 16
141
Kernel FreeRTOS Guía para desarrolladores
Mejora de la implementación de
la tarea utilizada en el Ejemplo 16
La tarea de control de interrupciones diferidas que se utiliza en el Ejemplo 16 tenía otro problema. No
utilizó un tiempo de espera al llamar a xSemaphoreTake(). En lugar de ello, la tarea pasó portMAX_DELAY
como parámetro xTicksToWait de xSemaphoreTake(), lo que se traduce en que la tarea debe esperar de
forma indefinida (sin un tiempo de espera) a que el semáforo esté disponible. En el código de ejemplo,
suelen utilizarse tiempos de espera indefinidos porque se simplifica la estructura del ejemplo y hace
que sea más fácil de entender. Sin embargo, en aplicaciones reales, no es conveniente utilizar tiempos
de espera indefinidos, ya que dificultan la recuperación de un error. Supongamos que una tarea está
esperando a que una interrupción dé un semáforo, pero un estado de error en el hardware impide que se
genere la interrupción.
• Si la tarea está esperando sin un tiempo de espera, no conocerá la existencia del estado de error y
esperará para siempre.
• Si la tarea está esperando con un tiempo de espera, xSemaphoreTake() devolverá pdFAIL cuando ese
tiempo de espera se agote y la tarea puede detectar y borrar el error la próxima vez que se ejecute. Esta
situación también se demuestra aquí.
for( ;; )
/* The semaphore is given by the UART's receive (Rx) interrupt. Wait a maximum of
xMaxExpectedBlockTime ticks for the next interrupt. */
/* The semaphore was obtained. Process ALL pending Rx events before calling
xSemaphoreTake() again. Each Rx event will have placed a character in the UART's receive
FIFO, and UART_RxCount() is assumed to return the number of characters in the FIFO. */
142
Kernel FreeRTOS Guía para desarrolladores
Semáforos de recuento
UART_ProcessNextRxEvent();
/* No more Rx events are pending (there are no more characters in the FIFO),
so loop back and call xSemaphoreTake() to wait for the next interrupt. Any interrupts
occurring between this point in the code and the call to xSemaphoreTake() will be latched
in the semaphore, so will not be lost. */
else
/* An event was not received within the expected time. Check for and, if
necessary, clear any error conditions in the UART that might be preventing the UART from
generating any more interrupts. */
UART_ClearErrors();
Semáforos de recuento
De la misma forma que los semáforos binarios son como colas con una longitud de uno, los semáforos de
recuento son como colas que tienen una longitud de más de uno. A las tareas no les interesan los datos
que se almacenan en la cola, solo el número de elementos que hay en la cola. Para que los semáforos de
recuento estén disponibles, en FreeRTOSConfig.h, establezca configUSE_COUNTING_SEMAPHORES en
1.
Cada vez que se dé un semáforo de recuento, se utiliza otro espacio en su cola. El número de elementos
de la cola es el valor de "recuento" del semáforo.
1. Eventos de recuento
En este caso, un controlador de eventos dará un semáforo cada vez que se produzca un evento, lo
que hace que el valor de recuento del semáforo se incremente en cada ocasión. Una tarea tomará
un semáforo cada vez que procese un evento, lo que hace que el valor de recuento del semáforo se
reduzca en cada ocasión. El valor del recuento es la diferencia entre el número de eventos que se
han producido y el número de eventos que se han procesado. En la siguiente figura se muestra este
mecanismo.
Los semáforos de recuento que se utilizan para contar eventos se crean con un valor de recuento inicial
de cero.
2. Administración de recursos
En esta situación, el valor de recuento indica el número de recursos disponibles. Para obtener el
control de un recurso, una tarea debe obtener primero un semáforo para reducir el valor de recuento
de semáforos. Cuando el valor de recuento alcanza cero, eso significa que no hay recursos libres.
Cuando una tarea termina con el recurso, devuelve el semáforo y se incrementa el valor de recuento de
semáforos.
Los semáforos de recuento que se utilizan para administrar los recursos se crean de forma que su valor
de recuento inicial sea igual al número de recursos que están disponibles.
143
Kernel FreeRTOS Guía para desarrolladores
Semáforos de recuento
144
Kernel FreeRTOS Guía para desarrolladores
Función de API xSemaphoreCreateCounting()
Para poder utilizar un semáforo, antes hay que crearlo. Para crear un semáforo de recuento, utilice la
función de API xSemaphoreCreateCounting().
145
Kernel FreeRTOS Guía para desarrolladores
Uso de un semáforo de recuento para sincronizar
una tarea con una interrupción (Ejemplo 17)
Para simular que se producen varios eventos a una frecuencia alta, se cambia la rutina del servicio de
interrupciones para dar el semáforo más de una vez por interrupción. Cada evento se bloquea en el valor
de recuento del semáforo.
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
/* Give the semaphore multiple times. The first will unblock the deferred interrupt
handling task. The following gives are to demonstrate that the semaphore latches the
events to allow the task to which interrupts are deferred to process them in turn, without
events getting lost. This simulates multiple interrupts being received by the processor,
even though in this case the events are simulated within a single interrupt occurrence. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
Todas las demás funciones son las mismas que las que se utilizan en el Ejemplo 16.
Esta es la salida que se genera cuando se ejecuta el código. Como verá, la tarea a la que se difiere el
control de interrupciones procesa los tres eventos (simulados) cada vez que se genera una interrupción.
146
Kernel FreeRTOS Guía para desarrolladores
Diferir el trabajo en la tarea de demonio de RTOS
Los eventos se bloquean en el valor de recuento del semáforo, lo que permite que la tarea los procese por
turnos.
La tarea de demonio se denominaba originalmente tarea de servicio del temporizador, porque solo se
usaba para ejecutar funciones de devolución de llamadas del temporizador de software. Por este motivo,
se implementa xTimerPendFunctionCall() en timers.c y, de acuerdo con la convención de usar como prefijo
en el nombre de la función el nombre del archivo en la que se ha implementado la función, el nombre de la
función tiene el prefijo "Timer".
• Menos flexibilidad
147
Kernel FreeRTOS Guía para desarrolladores
Función de API xTimerPendFunctionCallFromISR()
No se puede establecer la prioridad de cada tarea de control de interrupciones diferidas por separado.
Cada función de control de interrupciones diferidas se ejecuta con la prioridad de la tarea de demonio.
La prioridad de la tarea de demonio se establece por medio de la constante de configuración en tiempo
de compilación configTIMER_TASK_PRIORITY en FreeRTOSConfig.h.
• Menos determinismo
Cada interrupción tiene restricciones de tiempo diferentes, por lo que es común utilizar ambos métodos en
la misma aplicación.
Aquí se muestra el prototipo al que debe ajustarse una función que se pasa en el parámetro
xFunctionToPend de xTimerPendFunctionCallFromISR().
xFunctionToPend
Puntero a la función que se ejecutará en la tarea de demonio (en la práctica, solo el nombre de la función).
El prototipo de la función debe ser el mismo que se muestra en el código.
pvParameter1
Valor que se pasará a la función que ejecuta la tarea de demonio como parámetro pvParameter1 de la
función. El parámetro tiene un tipo * nulo para que pueda utilizarse para pasar cualquier tipo de datos. Por
ejemplo, los tipos enteros se pueden convertir directamente en un * nulo. De forma alternativa, el * nulo
puede utilizarse para apuntar a una estructura.
ulParameter2
El valor que se pasará a la función que ejecuta la tarea de demonio como parámetro ulParameter2 de la
función.
pxHigherPriorityTaskWoken
148
Kernel FreeRTOS Guía para desarrolladores
Procesamiento de interrupciones
diferido centralizado (Ejemplo 18)
• pdPASS
• pdFAIL
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
149
Kernel FreeRTOS Guía para desarrolladores
Procesamiento de interrupciones
diferido centralizado (Ejemplo 18)
/* Send a pointer to the interrupt's deferred handling function to the daemon task.
The deferred handling function's pvParameter1 parameter is not used, so just set to NULL.
The deferred handling function's ulParameter2 parameter is used to pass a number that is
incremented by one each time this interrupt handler executes. */
ulParameterValue++;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
/* Process the event - in this case, just print out a message and the value of
ulParameter2. pvParameter1 is not used in this example. */
Aquí se muestra la función main(). vPeriodicTask() es la tarea que genera periódicamente interrupciones
de software. Se crea con una prioridad por debajo de la prioridad de la tarea de demonio para garantizar
que la tarea de demonio tenga prioridad en cuanto salga del estado Bloqueado.
/* The task that generates the software interrupt is created at a priority below
the priority of the daemon task. The priority of the daemon task is set by the
configTIMER_TASK_PRIORITY compile time configuration constant in FreeRTOSConfig.h. */
/* Install the handler for the software interrupt. The syntax necessary to do this is
dependent on the FreeRTOS port being used. The syntax shown here can only be used with the
FreeRTOS windows port, where such interrupts are only simulated. */
vTaskStartScheduler();
150
Kernel FreeRTOS Guía para desarrolladores
Procesamiento de interrupciones
diferido centralizado (Ejemplo 18)
for( ;; );
El código produce la salida que se muestra aquí. La prioridad de la tarea de demonio es mayor que
la prioridad de la tarea que genera la interrupción de software, por lo que la tarea de demonio ejecuta
vDeferredHandlingFunction() en cuanto se genera la interrupción. Como resultado, la salida del mensaje
de vDeferredHandlingFunction() aparece entre las dos salidas de mensajes de la tarea periódica, igual que
cuando se utilizó un semáforo para desbloquear una tarea de procesamiento de interrupciones diferido
especial.
151
Kernel FreeRTOS Guía para desarrolladores
Uso de colas en una rutina del servicio de interrupciones
xQueue
El controlador de la cola a la que se envían los datos (escritos). El controlador de cola se habrá devuelto
desde la llamada a xQueueCreate() que se utiliza para crear la cola.
pvItemToQueue
Un puntero a los datos que se copiarán en la cola. El tamaño de cada elemento que puede contener la cola
se establece al crear la cola, por lo que esta cantidad de bytes se copiará desde pvItemToQueue al área
de almacenamiento de la cola.
pxHigherPriorityTaskWoken
Es posible que una única cola tenga una o varias tareas bloqueadas a la espera de que los datos
estén disponibles. Al llamar a xQueueSendToBackFromISR() o xQueueSendToFrontFromISR(), los
datos pueden estar disponibles y, por tanto, eso hace que una tarea salga del estado Bloqueado. Si al
llamar a la función de API, una tarea sale del estado Bloqueado y la prioridad de la tarea desbloqueada
es mayor que la de la tarea que se está ejecutando en ese momento (la tarea que se interrumpió),
la función de API establecerá internamente *pxHigherPriorityTaskWoken en pdTRUE. Si
xQueueSendToFrontFromISR() o xQueueSendToBackFromISR() establecen este valor en pdTRUE, debe
efectuarse un cambio de contexto antes de salir de la interrupción. De este modo, se asegurará de que la
interrupción vuelve directamente a la tarea con el estado Listo de máxima prioridad.
152
Kernel FreeRTOS Guía para desarrolladores
Aspectos a tener en cuenta al usar una cola desde una ISR
• pdPASS
• errQUEUE_FULL
Se devuelve si los datos no se pueden enviar a la cola, porque la cola ya está llena.
• Uso de hardware de acceso directo a memoria (DMA) para recibir y almacenar caracteres en el búfer
Este método no tiene prácticamente ninguna sobrecarga para el software. Luego, se puede usar una
notificación directa a tareas para desbloquear la tarea que procesará el búfer solo después de que
se haya detectado un corte en la transmisión. Las notificaciones directas a tareas son el método más
eficiente para desbloquear una tarea de una ISR. Para obtener más información, consulte Notificaciones
de tareas (p. 204).
• Copia de cada carácter recibido en un búfer de RAM seguro para subprocesos. Para este fin, se puede
utilizar el ''búfer de transmisión" que se proporciona como parte de FreeRTOS+TCP. Una vez más, se
puede usar una notificación directa a tareas para desbloquear la tarea que procesará el búfer después
de que se haya recibido un mensaje completo o después de que se haya detectado un corte en la
transmisión.
• Procesamiento de los caracteres recibidos directamente en la ISR y, a continuación, uso de una cola
para enviar solo el resultado del procesamiento de los datos (en lugar de los datos sin procesar) a una
tarea.
Se crea una tarea periódica que envía cinco números a una cola cada 200 milisegundos. Se genera una
interrupción de software solo después de haber enviado los cinco valores. La implementación de la tarea
se muestra aquí.
153
Kernel FreeRTOS Guía para desarrolladores
Envío y recepción en una cola
desde una interrupción (Ejemplo 19)
TickType_t xLastExecutionTime;
uint32_t ulValueToSend = 0;
int i;
xLastExecutionTime = xTaskGetTickCount();
for( ;; )
/* This is a periodic task. Block until it is time to run again. The task will
execute every 200 ms. */
/* Send five numbers to the queue, each value one higher than the previous value.
The numbers are read from the queue by the interrupt service routine. The interrupt
service routine always empties the queue, so this task is guaranteed to be able to write
all five values without needing to specify a block time. */
ulValueToSend++;
/* Generate the interrupt so the interrupt service routine can read the values from
the queue. The syntax used to generate a software interrupt depends on the FreeRTOS port
being used. The syntax used below can only be used with the FreeRTOS Windows port, in
which such interrupts are only simulated.*/
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulReceivedNumber;
154
Kernel FreeRTOS Guía para desarrolladores
Envío y recepción en una cola
desde una interrupción (Ejemplo 19)
/* The strings are declared static const to ensure they are not allocated on the
interrupt service routine's stack, and so exist even when the interrupt service routine is
not executing. */
"String 0\r\n",
"String 1\r\n",
"String 2\r\n",
"String 3\r\n"
};
xHigherPriorityTaskWoken = pdFALSE;
/* Truncate the received value to the last two bits (values 0 to 3 inclusive), and
then use the truncated value as an index into the pcStrings[] array to select a string
(char *) to send on the other queue. */
/* If receiving from xIntegerQueue caused a task to leave the Blocked state, and if the
priority of the task that left the Blocked state is higher than the priority of the task
in the Running state, then xHigherPriorityTaskWoken will have been set to pdTRUE inside
xQueueReceiveFromISR(). If sending to xStringQueue caused a task to leave the Blocked
state, and if the priority of the task that left the Blocked state is higher than the
priority of the task in the Running state, then xHigherPriorityTaskWoken will have been
set to pdTRUE inside xQueueSendToBackFromISR(). xHigherPriorityTaskWoken is used as the
parameter to portYIELD_FROM_ISR(). If xHigherPriorityTaskWoken equals pdTRUE, then calling
portYIELD_FROM_ISR() will request a context switch. If xHigherPriorityTaskWoken is still
pdFALSE, then calling portYIELD_FROM_ISR() will have no effect. The implementation of
portYIELD_FROM_ISR() used by the Windows port includes a return statement, which is why
this function does not explicitly return a value. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
La tarea que recibe los punteros de caracteres de la rutina del servicio de interrupciones se bloquea en la
cola hasta que llega un mensaje e imprime cada cadena a medida que la recibe. La implementación se
muestra aquí.
155
Kernel FreeRTOS Guía para desarrolladores
Envío y recepción en una cola
desde una interrupción (Ejemplo 19)
char *pcString;
for( ;; )
vPrintString( pcString );
Como es normal, la función main() crea las colas y las tareas requeridas antes de iniciar el programador.
La implementación se muestra aquí.
/* Before a queue can be used, it must first be created. Create both queues used by
this example. One queue can hold variables of type uint32_t. The other queue can hold
variables of type char*. Both queues can hold a maximum of 10 items. A real application
should check the return values to ensure the queues have been successfully created. */
/* Create the task that uses a queue to pass integers to the interrupt service routine.
The task is created at priority 1. */
/* Create the task that prints out the strings sent to it from the interrupt service
routine. This task is created at the higher priority of 2. */
/* Install the handler for the software interrupt. The syntax required to do this is
depends on the FreeRTOS port being used. The syntax shown here can only be used with the
FreeRTOS Windows port, where such interrupts are only simulated. */
vTaskStartScheduler();
/* If all is well, then main() will never reach here because 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. */
for( ;; );
156
Kernel FreeRTOS Guía para desarrolladores
Envío y recepción en una cola
desde una interrupción (Ejemplo 19)
El resultado se muestra aquí. Como verá, la interrupción recibe los cinco números enteros y produce como
respuesta cinco cadenas.
157
Kernel FreeRTOS Guía para desarrolladores
Anidamiento de interrupciones
Anidamiento de interrupciones
Es frecuente confundir las prioridades de las tareas y las prioridades de las interrupciones. En esta
sección, se describen las prioridades de las interrupciones, que son las prioridades con las que se ejecutan
las ISR en relación entre sí. La prioridad asignada a una tarea no está relacionada de ningún modo con la
prioridad asignada a una interrupción. El hardware decide cuándo se ejecutará una ISR. El software decide
cuándo se ejecutará una tarea. Una ISR que se ejecuta en respuesta a una interrupción de hardware
interrumpirá una tarea, pero una tarea no puede tener preferencia sobre una ISR.
Los puertos que admiten el anidamiento de interrupciones requieren que se defina una o
ambas de las constantes que se muestran en la siguiente tabla en FreeRTOSConfig.h. Tanto
configMAX_SYSCALL_INTERRUPT_PRIORITY como configMAX_API_CALL_INTERRUPT_PRIORITY
definen la misma propiedad. Los puertos de FreeRTOS más antiguos utilizan
configMAX_SYSCALL_INTERRUPT_PRIORITY. Los puertos de FreeRTOS más recientes utilizan
configMAX_API_CALL_INTERRUPT_PRIORITY.
Constant Descripción
Si el puerto de FreeRTOS en
uso tampoco utiliza la constante
configMAX_SYSCALL_INTERRUPT_PRIORITY,
cualquier interrupción que utilice funciones de
API de FreeRTOS a prueba de interrupciones
también debe ejecutarse con la prioridad definida
por configKERNEL_INTERRUPT_PRIORITY.
• Numérica
El número asignado a la prioridad de la interrupción. Por ejemplo, si a una interrupción se le asigna una
prioridad de 7, entonces su prioridad numérica es 7. Igualmente, si a una interrupción se le asigna una
prioridad de 200, su prioridad numérica es 200.
• Lógica
Una interrupción puede interrumpir (anidar en) cualquier interrupción que tenga una prioridad lógica
menor, pero no puede interrumpir una que tenga una prioridad lógica igual o mayor.
La relación entre la prioridad numérica y la prioridad lógica de una interrupción depende de la arquitectura
del procesador. En algunos procesadores, cuanto mayor sea la prioridad numérica asignada a una
interrupción, mayor será la prioridad lógica de esa interrupción. En las arquitecturas de otros procesadores,
158
Kernel FreeRTOS Guía para desarrolladores
Usuarios de Cortex-M y GIC de ARM
cuanto mayor sea la prioridad numérica asignada a una interrupción, menor será la prioridad lógica de esa
interrupción.
Esta figura muestra las constantes que afectan al comportamiento de anidamiento de las interrupciones.
• Las interrupciones que utilizan las prioridades 1 a 3, inclusive, no se ejecutan mientras el kernel o la
aplicación estén dentro de una sección crítica. Las ISR que se ejecutan con estas prioridades pueden
utilizar funciones de API de FreeRTOS a prueba de interrupciones. Para obtener más información acerca
de las secciones críticas, consulte Administración de recursos (p. 161).
• Las interrupciones que utilizan una prioridad de 4 o superior no se ven afectadas por las secciones
críticas, por lo nada de lo que haga el programador impide que estas interrupciones se ejecuten de
forma inmediata dentro de las limitaciones del hardware. Las ISR que se ejecutan con estas prioridades
no pueden utilizar ninguna de las funciones de API de FreeRTOS.
• Normalmente, una funcionalidad que requiera una precisión de tiempo muy
estricta (el control de un motor, por ejemplo) utilizaría una prioridad por encima de
configMAX_SYSCALL_INTERRUPT_PRIORITY para garantizar que el programador no introdujera
fluctuaciones en el tiempo de respuesta de las interrupciones.
159
Kernel FreeRTOS Guía para desarrolladores
Usuarios de Cortex-M y GIC de ARM
Los núcleos de Cortex de ARM y los controladores de interrupciones genéricos (GIC) de ARM utilizan
números de baja prioridad numérica para representar interrupciones de alta prioridad lógica. Esto parece
contradictorio. Si desea asignar a una interrupción una prioridad baja lógica, se le debe asignar un valor
numérico alto. Si desea asignar a una interrupción una prioridad alta lógica, se le debe asignar un valor
numérico bajo.
El controlador de interrupciones de Cortex-M permite utilizar un máximo de ocho bits para especificar la
prioridad de cada interrupción y 255 es la prioridad más baja posible. Cero es la prioridad más alta. Sin
embargo, normalmente los microcontroladores de Cortex-M solo implementan un subconjunto de los ocho
bits posibles. El número de bits implementados depende de la familia de microcontroladores.
Cuando únicamente se implementa un subconjunto, solo se pueden utilizar los bits más significativos del
byte. Los bits menos significativos se quedan sin implementar. Los bits no implementados pueden tomar
cualquier valor, pero lo normal es que establezcan en 1. Esta figura muestra cómo una prioridad de 101
binaria se almacena en un microcontrolador de Cortex-M que implementa cuatro bits de prioridad.
Como verá, el valor binario 101 se ha cambiado por los cuatro bits más significativos porque los cuatro bits
menos significativos no se han implementado. Los bits no implementados se han establecido en 1.
Algunas funciones de biblioteca esperan que se especifiquen valores de prioridad después de que se
hayan cambiado a los bits implementados (más significativos). Cuando se utiliza esta función, la prioridad
que se muestra en esta figura se puede especificar como 95 decimal. 95 decimal es 101 binario con
un desvío de cuatro para obtener 101nnnn binario (donde n es un bit no implementado). Los bits no
implementados se establecen en 1 para obtener 1011111 binario.
Algunas funciones de biblioteca esperan que se especifiquen valores de prioridad antes de que se hayan
desviado a los bits implementados (más significativos). Cuando se utiliza esta función, la prioridad que
se muestra en esta figura se debe especificar como 5 decimal. El 5 decimal 5 es el 101 binario sin ningún
desvío.
Las interrupciones de Cortex-M utilizan una prioridad de cero de forma predeterminada, que es
la máxima prioridad posible. La implementación del hardware de Cortex-M no permite establecer
configMAX_SYSCALL_INTERRUPT_PRIORITY en 0, por lo que la prioridad de una interrupción que utilice
la API de FreeRTOS nunca debe dejarse en su valor predeterminado.
160
Kernel FreeRTOS Guía para desarrolladores
Administración de recursos
En esta sección se explica lo siguiente:
En un sistema multitarea, existe la posibilidad de que se produzca un error si una tarea comienza a
acceder a un recurso, pero no completa su acceso antes de que salga del estado En ejecución. Si la tarea
deja el recurso en un estado incoherente, cuando cualquier otra tarea o interrupción acceden a ese mismo
recurso, los datos podrían dañarse o producirse otro problema similar.
1. Acceso a periféricos
Pongamos como ejemplo la siguiente situación en la que dos tareas intentan escribir en una pantalla de
cristal líquido (LCD).
a. La Tarea A se ejecuta y comienza a escribir la cadena "Hola mundo" en la pantalla LCD.
b. La Tarea B asume la preferencia sobre la Tarea A después de producir solo el principio de la cadena
"Hola m".
c. La Tarea B escribe "¿Anular, Reintentar, Error?" en la pantalla LCD antes de pasar al estado
Bloqueado.
d. La Tarea A continúa desde el punto en que perdió la preferencia y produce el resto de caracteres de
su cadena ("undo").
Ahora, en la pantalla LCD aparece la cadena dañada "Hola m¿Anular, Reintentar, Error?undo".
2. Operaciones de lectura, modificación y escritura
A continuación, se muestra una línea de código en C y un ejemplo de cómo se suele traducirse en código
de ensamblado. Como verá, el valor de PORTA se lee primero de la memoria en un registro, se modifica
en el registro y, a continuación, se vuelve a escribir en la memoria. Esto es lo que se conoce como una
operación de lectura, modificación y escritura.
PORTA |= 0x01;
161
Kernel FreeRTOS Guía para desarrolladores
Se trata de una operación no atómica porque tarda más que una instrucción en completarse y se puede
interrumpir. Considere la siguiente situación en la que dos tareas intentan actualizar un registro asignado a
la memoria denominado PORTA.
En este caso, la Tarea A actualiza y escribe de nuevo un valor desactualizado para PORTA. La Tarea B
modifica PORTA después de que la Tarea A tome una copia del valor de PORTA y antes de que la Tarea
A escriba su valor modificado en el registro de PORTA. Cuando la Tarea A escribe en PORTA, sobrescribe
la modificación que ya ha realizado la Tarea B, por lo que daña el valor de registro de PORTA.
En este ejemplo, se utiliza un registro de periférico, pero este mismo principio se aplica al realizar
operaciones de lectura, modificación y escritura en variables.
La actualización de varios miembros de una estructura o la actualización de una variable que es mayor
que el tamaño real de la arquitectura (por ejemplo, actualizar una variable de 32 bits en una máquina
de 16 bits) son ejemplos de operaciones no atómicas. Si se interrumpen, los datos podrían perderse o
dañarse.
2. Reentrada de funciones
Una función es reentrante si es seguro llamar a la función desde más de una tarea o desde tareas
e interrupciones. Se dice que las funciones reentrantes son seguras para subprocesos porque se
puede acceder a ellas desde más de un subproceso de ejecución sin el riesgo de que se dañen las
operaciones de datos o lógicas. Cada tarea mantiene su propia pila y su propio conjunto de valores
de registro de procesadores (hardware). Si una función no tiene acceso a datos que no sean los datos
almacenados en la pila o que se mantienen en un registro, la función es reentrante y es segura para
subprocesos. A continuación se muestra un ejemplo de una función reentrante.
/* A parameter is passed into the function. This will either be passed on the stack, or
in a processor register. Either way is safe because each task or interrupt that calls
the function maintains its own stack and its own set of register values, so each task or
interrupt that calls the function will have its own copy of lVar1. */
long lAddOneHundred( long lVar1 )
/* This function scope variable will also be allocated to the stack or a register,
depending on the compiler and optimization level. Each task or interrupt that calls this
function will have its own copy of lVar2. */
long lVar2;
return lVar2;
162
Kernel FreeRTOS Guía para desarrolladores
Exclusión mutua
/* In this case lVar1 is a global variable, so every task that calls lNonsenseFunction will
access the same single copy of the variable. */
long lVar1;
/* lState is static, so is not allocated on the stack. Each task that calls this
function will access the same single copy of the variable. */
long lReturn;
switch( lState )
lState = 1;
break;
lState = 0;
break;
Exclusión mutua
Para garantizar la coherencia de los datos en todo momento, utilice la técnica de exclusión mutua para
administrar el acceso a un recurso que se comparte entre tareas o entre tareas e interrupciones. El
objetivo es garantizar que, una vez que la tarea comience a acceder a un recurso compartido que no sea
reentrante y seguro para subprocesos, la misma tarea tenga acceso exclusivo al recurso hasta que el
recurso se haya devuelto a un estado coherente.
FreeRTOS dispone de varias características que se pueden utilizar para implementar la exclusión mutua,
pero el mejor método de exclusión mutua es, siempre que resulte práctico, diseñar la aplicación de tal
forma que los recursos no se compartan y que solo se acceda a cada recurso desde una sola tarea.
163
Kernel FreeRTOS Guía para desarrolladores
Secciones críticas básicas
Aquí se muestra su uso, donde se utiliza una sección crítica para proteger el acceso a un registro.
taskENTER_CRITICAL();
/* A switch to another task cannot occur between the call to taskENTER_CRITICAL() and the
call to taskEXIT_CRITICAL(). Interrupts might still execute on FreeRTOS ports that allow
interrupt nesting, but only interrupts whose logical priority is above the value assigned
to the configMAX_SYSCALL_INTERRUPT_PRIORITY constant. Those interrupts are not permitted
to call FreeRTOS API functions. */
PORTA |= 0x01;
taskEXIT_CRITICAL();
Varios de los ejemplos utilizan una función denominada vPrintString() para escribir cadenas en una
salida estándar, que es la ventana de terminal cuando se utiliza el puerto de Windows de FreeRTOS.
vPrintString() se llama desde muchas tareas diferentes, por lo que, en teoría, su implementación podría
proteger el acceso a la salida estándar mediante una sección crítica, tal y como se muestra aquí.
/* Write the string to stdout, using a critical section as a crude method of mutual
exclusion. */
taskENTER_CRITICAL();
fflush( stdout );
taskEXIT_CRITICAL();
Las secciones críticas que se implementan de esta forma son un método muy rudimentario de proporcionar
exclusión mutua. Funcionan desactivando las interrupciones, ya sea por completo o hasta la prioridad
de interrupción que establece configMAX_SYSCALL_INTERRUPT_PRIORITY, en función del puerto de
FreeRTOS que se esté utilizando. Los cambios de contexto preferentes solo pueden realizarse desde una
interrupción, por lo que, mientras las interrupciones estén deshabilitadas, se garantiza que la tarea que ha
llamado a taskENTER_CRITICAL() permanecerá en el estado En ejecución hasta que la sección crítica
salga.
Las secciones críticas básicas deben ser muy cortas. De lo contrario, afectarían negativamente a los
tiempos de respuesta de las interrupciones. Cada llamada a taskENTER_CRITICAL() debe emparejarse
estrechamente con una llamada a taskEXIT_CRITICAL(). Por este motivo, la salida estándar (stdout o la
164
Kernel FreeRTOS Guía para desarrolladores
Secciones críticas básicas
secuencia donde un equipo escribe sus datos de salida) no se debe proteger mediante una sección crítica
(tal y como se muestra en el código anterior), ya que la escritura en el terminal puede ser una operación
relativamente larga. En los ejemplos de esta sección se exploran soluciones alternativas.
No hay problema en que las secciones críticas se aniden porque el kernel mantiene el recuento de la
profundidad de anidamiento. Solo se saldrá de la sección crítica cuando la profundidad de anidamiento
vuelva a cero, es decir, cuando se haya ejecutado una llamada a taskEXIT_CRITICAL() por cada llamada
anterior a taskENTER_CRITICAL().
UBaseType_t uxSavedInterruptStatus;
/* This part of the ISR can be interrupted by any higher priority interrupt. */
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* This part of the ISR can be interrupted by any higher priority interrupt. */
Es un desperdicio utilizar más tiempo de procesamiento para ejecutar el código que entra y sale
posteriormente de una sección crítica que el código que está protegido por la sección crítica. Las
secciones críticas entran muy rápido y salen muy rápido y siempre son deterministas, por lo que su uso es
ideal cuando la región de código que se protege es muy corta.
165
Kernel FreeRTOS Guía para desarrolladores
Suspensión (o bloqueo) del programador
Las secciones críticas básicas protegen una región de código del acceso de otras tareas e interrupciones.
Una sección crítica implementada mediante la suspensión del programador solo protege una región de
código del acceso de otras tareas, porque las interrupciones permanecen habilitadas.
Una sección crítica que es demasiado larga para implementarla simplemente desactivando las
interrupciones se puede implementar suspendiendo el programador. Sin embargo, la actividad de las
interrupciones mientras el programador está suspendido puede hacer que el programador tarde en
reanudarse (o en cancelar la suspensión) un tiempo relativamente largo. Piense en el mejor método para
cada caso.
No hay que llamar a las funciones de API de FreeRTOS mientras el programador está suspendido.
No hay ningún problema en que las llamadas a vTaskSuspendAll() y xTaskResumeAll() se aniden porque
el kernel mantiene el recuento de la profundidad de anidamiento. El programador solo se reanudará
166
Kernel FreeRTOS Guía para desarrolladores
Mutex (y semáforos binarios)
cuando la profundidad de anidamiento vuelva a cero, es decir, cuando una llamada a xTaskResumeAll() se
haya ejecutado por cada llamada anterior a vTaskSuspendAll().
vTaskSuspendScheduler();
fflush( stdout );
xTaskResumeScheduler();
Cuando se utiliza en una situación de exclusión mutua, el mutex puede considerarse un token que se
asocia al recurso que se está compartiendo. Para que una tarea acceda al recurso de forma legítima,
primero debe tomar correctamente el token (ser el titular del token). Cuando el titular del token ha
terminado con el recurso, debe devolver el token. Solo cuando el token se haya devuelto otra tarea puede
tomar correctamente el token y, a continuación, acceder de forma segura al mismo recurso compartido.
Una tarea no tiene permitido el acceso al recurso compartido a menos que tenga el token.
Aunque los mutex y los semáforos binarios comparten muchas características, la situación en la que se
usa un mutex es totalmente diferente a otra en la que se utiliza un semáforo binario para la sincronización.
La principal diferencia es lo que le ocurre al semáforo después de que se haya obtenido:
167
Kernel FreeRTOS Guía para desarrolladores
Reescritura de vPrintString() para
utilizar un semáforo (Ejemplo 20)
Los controladores de todos los distintos tipos de semáforos de FreeRTOS se almacenan en una variable
de tipo SemaphoreHandle_t.
Para poder utilizar un mutex, antes hay que crearlo. Para crear un semáforo de tipo mutex, utilice la
función de API xSemaphoreCreateMutex().
/* The mutex is created before the scheduler is started, so already exists by the time
this task executes. Attempt to take the mutex, blocking indefinitely to wait for the mutex
if it is not available right away. The call to xSemaphoreTake() will only return when the
mutex has been successfully obtained, so there is no need to check the function return
value. If any other delay period was used, then the code must check that xSemaphoreTake()
returns pdTRUE before accessing the shared resource (which in this case is standard out).
Indefinite timeouts are not recommended for production code. */
/* The following line will only execute after the mutex has been successfully
obtained. Standard out can be accessed freely now because only one task can have the mutex
at any one time. */
fflush( stdout );
xSemaphoreGive( xMutex );
Dos instancias de una tarea implementada por prvPrintTask() llaman de forma repetida a
prvNewPrintString(). Se utiliza un tiempo de retardo aleatorio entre cada llamada. El parámetro de la tarea
168
Kernel FreeRTOS Guía para desarrolladores
Reescritura de vPrintString() para
utilizar un semáforo (Ejemplo 20)
se utiliza para pasar una cadena única a cada instancia de la tarea. La implementación de prvPrintTask()
se muestra aquí.
char *pcStringToPrint;
/* Two instances of this task are created. The string printed by the task is passed
into the task using the task's parameter. The parameter is cast to the required type. */
for( ;; )
prvNewPrintString( pcStringToPrint );
/* Wait a pseudo random time. Note that rand() is not necessarily reentrant,
but in this case it does not really matter because the code does not care what value is
returned. In a more secure application, a version of rand() that is known to be reentrant
should be used or calls to rand() should be protected using a critical section. */
Como es normal, la función main() simplemente crea el mutex y las tareas y luego inicia el programador.
Las dos instancias de prvPrintTask() se crean con diferentes prioridades, por lo que la tarea de mayor
prioridad asumirá la preferencia sobre la tarea de menor prioridad. Dado que se utiliza un mutex para
garantizar que cada tarea obtiene acceso mutuamente excluyente al terminal, incluso cuando se aplica
la preferencia, las cadenas que se muestran serán correctas y no estarán dañadas. Puede aumentar la
frecuencia de la aplicación de la prioridad reduciendo el tiempo máximo que las tareas están en el estado
Bloqueado, que establece la constante xMaxBlockTimeTicks.
• La llamada a printf() genera una llamada del sistema Windows. Las llamadas del sistema Windows
quedan fuera del control de FreeRTOS y pueden producir inestabilidad.
• La forma en que se ejecutan las llamadas del sistema Windows hace que no sea frecuente ver una
cadena dañada, incluso cuando no se utiliza el mutex.
xMutex = xSemaphoreCreateMutex();
/* Check that the semaphore was created successfully before creating the tasks. */
169
Kernel FreeRTOS Guía para desarrolladores
Reescritura de vPrintString() para
utilizar un semáforo (Ejemplo 20)
/* Create two instances of the tasks that write to stdout. The string they write
is passed in to the task as the task's parameter. The tasks are created at different
priorities so some preemption will occur. */
vTaskStartScheduler();
/* If all is well, then main() will never reach here because 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. */
for( ;; );
Esta es la salida.
170
Kernel FreeRTOS Guía para desarrolladores
Inversión de prioridades
Como era de esperar, las cadenas que se muestran en el terminal no están dañadas. El orden aleatorio se
debe a los periodos de retardo aleatorios que utilizan las tareas.
Inversión de prioridades
La figura anterior ilustra uno de los posibles inconvenientes de utilizar un mutex para proporcionar la
exclusión mutua. La secuencia de ejecución que se ilustra muestra que la tarea 2 de mayor prioridad
tiene que esperar a que la tarea 1 de menor prioridad deje el control del mutex. El hecho de que una
tarea de menor prioridad retrase a una de mayor prioridad de esta manera se denomina inversión de
prioridades. Este comportamiento inapropiado se agrava si una tarea de prioridad media comienza a
ejecutarse mientras la tarea de mayor prioridad está esperando al semáforo. Como resultado, una tarea
de alta prioridad tendría que esperar a una tarea de baja prioridad y la tarea de baja prioridad ni siquiera
podría ejecutarse. En esta figura, se muestra el peor de los casos.
171
Kernel FreeRTOS Guía para desarrolladores
Herencia de prioridades
La inversión de prioridades puede ser un problema importante, pero en sistemas integrados pequeños, se
puede evitar teniendo en cuenta cómo se accede a los recursos al diseñar el sistema.
Herencia de prioridades
Los mutex de FreeRTOS y los semáforos binarios son muy similares. La diferencia es que los mutex
incluyen un mecanismo de herencia de prioridades básico y los semáforos binarios no. La herencia de
prioridades es un sistema que minimiza los efectos negativos de la inversión de prioridades. No soluciona
la inversión de prioridades, pero reduce su impacto asegurándose de que la inversión siempre esté
limitada en el tiempo. Sin embargo, la herencia de prioridades complica el análisis del tiempo del sistema.
No es conveniente basarse en ella para que el sistema funcione correctamente.
La herencia de prioridades aumenta de forma temporal la prioridad del titular del mutex hasta la prioridad
de la tarea de máxima prioridad que está intentando obtener el mismo mutex. La tarea de baja prioridad
que contiene el mutex hereda la prioridad de la tarea que está a la espera del mutex. En la siguiente figura,
se muestra que la prioridad del titular del mutex se restablece automáticamente a su valor original cuando
devuelve el mutex.
172
Kernel FreeRTOS Guía para desarrolladores
Interbloqueo (o abrazo mortal)
La funcionalidad de herencia de prioridades afecta a la prioridad de las tareas que utilizan el mutex. Por
este motivo, no deben utilizarse mutex desde rutinas del servicio de interrupciones.
El interbloqueo se produce cuando dos tareas no pueden continuar porque ambas están a la espera de
un recurso que tiene el otro. Considere la siguiente situación en la que la Tarea A y la Tarea B tienen que
adquirir el mutex X y el mutex Y para realizar una acción.
La Tarea A está esperando un mutex que tiene la Tarea B y la Tarea B está esperando un mutex que tiene
la Tarea A. Se ha producido un interbloqueo porque ninguna de las tareas puede continuar.
Al igual que ocurre con la inversión de prioridades, el mejor método para evitar el interbloqueo consiste en
diseñar el sistema de forma que no pueda producirse ese interbloqueo. Normalmente, no es conveniente
que una tarea espere de forma indefinida (sin un tiempo de espera) para obtener un mutex. En su lugar,
utilice un tiempo de espera que sea un poco más largo que el tiempo máximo que es normal que tenga que
173
Kernel FreeRTOS Guía para desarrolladores
Mutex recursivos
esperar al mutex. Si no se obtiene el mutex en ese tiempo, eso significa que hay un error de diseño, que
podría ser un interbloqueo.
Mutex recursivos
También es posible que una tarea se interbloquee consigo misma. Esto ocurre si una tarea intenta tomar el
mismo mutex más de una vez, sin antes devolverlo. Veamos la siguiente situación:
La tarea se encuentra en el estado Bloqueado a la espera de que se devuelva el mutex, pero la tarea ya
tiene el mutex. Se ha producido un interbloqueo porque la tarea se encuentra en el estado Bloqueado
esperándose a sí misma.
Puede evitar este tipo de interbloqueo mediante un mutex recursivo en lugar de un mutex estándar. Una
misma tarea puede tomar un mutex recursivo más de una vez. Solo se devolverá después de que se
haya ejecutado una llamada para dar el mutex recursivo por cada llamada anterior para tomar el mutex
recursivo.
Los mutex estándar y los mutex recursivos se crean y se utilizan de forma similar:
• Los mutex estándar se crean con xSemaphoreCreateMutex(). Los mutex recursivos se crean con
xSemaphoreCreateRecursiveMutex(). Las dos funciones de API tienen el mismo prototipo.
• Los mutex estándar se toman con xSemaphoreTake(). Los mutex recursivos se toman con
xSemaphoreTakeRecursive(). Las dos funciones de API tienen el mismo prototipo.
• Los mutex estándar se dan con xSemaphoreGive(). Los mutex recursivos se dan con
xSemaphoreGiveRecursive(). Las dos funciones de API tienen el mismo prototipo.
SemaphoreHandle_t xRecursiveMutex;
xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
174
Kernel FreeRTOS Guía para desarrolladores
Mutex y programación de tareas
configASSERT( xRecursiveMutex );
for( ;; )
/* ... */
/* The recursive mutex was successfully obtained. The task can now access the
resource the mutex is protecting. At this point the recursive call count (which is the
number of nested calls to xSemaphoreTakeRecursive()) is 1 because the recursive mutex has
only been taken once. */
/* While it already holds the recursive mutex, the task takes the mutex
again. In a real application, this is only likely to occur inside a subfunction called
by this task because there is no practical reason to knowingly take the same mutex
more than once. The calling task is already the mutex holder, so the second call to
xSemaphoreTakeRecursive() does nothing more than increment the recursive call count to 2.
*/
/* ... */
/* The task returns the mutex after it has finished accessing the resource the
mutex is protecting. At this point the recursive call count is 2, so the first call to
xSemaphoreGiveRecursive() does not return the mutex. Instead, it simply decrements the
recursive call count back to 1. */
xSemaphoreGiveRecursive( xRecursiveMutex );
xSemaphoreGiveRecursive( xRecursiveMutex );
175
Kernel FreeRTOS Guía para desarrolladores
Mutex y programación de tareas
Es habitual presuponer de forma incorrecta el orden en que se ejecutarán las tareas cuando tienen la
misma prioridad. Si las Tareas 1 y 2 tienen la misma prioridad y la tarea 1 está en el estado Bloqueado a la
espera de un mutex que tiene la tarea 2, la tarea 1 no tendrá preferencia sobre la tarea 2 cuando la tarea
2 dé el mutex. En su lugar, la tarea 2 permanecerá en el estado En ejecución. La tarea 1 simplemente
pasará del estado Bloqueado al estado Listo.
En esta figura, las líneas verticales marcan en qué momento se produce una interrupción de ciclo.
Como verá, el programador de FreeRTOS no pasa la tarea 1 al estado En ejecución en cuanto el mutex
está disponible porque:
1. La tarea 1 y 2 tienen la misma prioridad, por lo que, a menos que la tarea 2 pase al estado Bloqueado,
no se debería producir un cambio a la tarea 1 hasta la siguiente interrupción de ciclo (suponiendo que
configUSE_TIME_SLICING esté establecido en 1 en FreeRTOSConfig.h).
2. Si una tarea utiliza un mutex en un bucle cerrado y se produce un cambio de contexto cada vez que la
tarea da el mutex, la tarea permanecerá en el estado En ejecución durante un breve periodo de tiempo.
Si dos o más tareas utilizan el mismo mutex en un bucle cerrado, se desperdiciará rápidamente tiempo
de procesamiento al cambiar entre las tareas.
Si más de una tarea utiliza un mutex en un bucle cerrado y las tareas que utilizan el mutex tienen la
misma prioridad, asegúrese de que las tareas reciben aproximadamente la misma cantidad de tiempo de
procesamiento. La figura anterior muestra una secuencia de ejecución que podría producirse si se crean
dos instancias de la tarea que se muestra en el siguiente código con la misma prioridad.
/* The implementation of a task that uses a mutex in a tight loop. The task creates a text
string in a local buffer, and then writes the string to a display. Access to the display
is protected by a mutex. */
176
Kernel FreeRTOS Guía para desarrolladores
Mutex y programación de tareas
for( ;; )
vGenerateTextInALocalBuffer( cTextBuffer );
vCopyTextToFrameBuffer( cTextBuffer );
/* The text has been written to the display, so return the mutex. */
xSemaphoreGive( xMutex );
En los comentarios del código se advierte que la creación de la cadena es una operación rápida y la
actualización de la pantalla es una operación lenta. Por lo tanto, dado que el mutex se mantiene mientras
se está actualizando la pantalla, la tarea tendrá el mutex durante la mayor parte de su tiempo de ejecución.
En la siguiente figura, las líneas verticales marcan en qué momento se produce una interrupción de ciclo.
177
Kernel FreeRTOS Guía para desarrolladores
Mutex y programación de tareas
El paso 7 de esta figura muestra la tarea 1 volviendo a pasar al estado Bloqueado. Todo esto sucede en el
interior la función de API xSemaphoreTake().
La tarea 1 no podrá obtener el mutex hasta que el inicio de un intervalo de tiempo coincida con uno de los
breves periodos de tiempo durante los cuales la tarea 2 no es el titular del mutex.
Para evitar esta situación, añada una llamada a taskYIELD() después de la llamada a xSemaphoreGive().
Esto se demuestra en el código que se muestra a continuación, donde se llama a taskYIELD() si el
recuento de ciclos cambia mientras la tarea tiene el mutex. El código garantiza que las tareas que utilizan
un mutex en un bucle reciban una cantidad más igualitaria de tiempo de procesamiento, además de
asegurarse de que no se pierda tiempo de procesamiento al cambiar entre tareas con demasiada rapidez.
TickType_t xTimeAtWhichMutexWasTaken;
for( ;; )
vGenerateTextInALocalBuffer( cTextBuffer );
xTimeAtWhichMutexWasTaken = xTaskGetTickCount();
vCopyTextToFrameBuffer( cTextBuffer );
/* The text has been written to the display, so return the mutex. */
xSemaphoreGive( xMutex );
/* If taskYIELD() was called on each iteration, then this task would only ever
remain in the Running state for a short period of time, and processing time would be
wasted by rapidly switching between tasks. Therefore, only call taskYIELD() if the tick
count changed while the mutex was held. */
taskYIELD();
178
Kernel FreeRTOS Guía para desarrolladores
Tareas de guardián
Tareas de guardián
Las tareas de guardián son un método claro para implementar la exclusión mutua sin el riesgo de que se
produzca una inversión de prioridades o un interbloqueo.
Una tarea de guardián es una tarea que tiene la propiedad exclusiva de un recurso. La tarea de guardián
es la única que tiene permitido el acceso al recurso directamente. Cualquier otra tarea que tenga que
acceder al recurso solo puede hacerlo de forma indirecta mediante los servicios del guardián.
La tarea de guardián utiliza una cola de FreeRTOS para serializar el acceso a una salida estándar. La
implementación interna de la tarea no tiene que tener en cuenta la exclusión mutua porque es la única
tarea a la que se le permite el acceso directo a la salida estándar.
La tarea de guardián pasa la mayor parte de su tiempo en el estado Bloqueado esperando que lleguen
mensajes a la cola. Cuando llega un mensaje, el guardián simplemente escribe el mensaje en una salida
estándar antes de volver al estado Bloqueado a esperar al siguiente mensaje.
Se pueden enviar interrupciones a las colas, por lo que las rutinas del servicio de interrupciones también
pueden utilizar de forma segura los servicios de guardián para escribir mensajes en el terminal. En este
ejemplo, se utiliza una función de enlace de ciclos para escribir un mensaje cada 200 ciclos.
Un enlace de ciclos (o devolución de llamada de ciclos) es una función que llama el kernel durante cada
interrupción de ciclo. Para utilizar una función de enlace de ciclos:
Las funciones de enlace de ciclos se ejecutan en el contexto de la interrupción de ciclo y, por tanto, deben
ser muy cortas. Solo deben usar una cantidad moderada de espacio de pila y no deben llamar a ninguna
función de API de FreeRTOS que no termine con "FromISR()".
char *pcMessageToPrint;
179
Kernel FreeRTOS Guía para desarrolladores
Reescritura de vPrintString() para utilizar
una tarea de guardián (Ejemplo 21)
/* This is the only task that is allowed to write to standard out. Any other task
wanting to write a string to the output does not access standard out directly, but
instead sends the string to this task. Because this is the only task that accesses
standard out, there are no mutual exclusion or serialization issues to consider within the
implementation of the task itself. */
for( ;; )
fflush( stdout );
Aquí se muestra la tarea que escribe en la cola. Como antes, se crean dos instancias independientes de la
tarea y la cadena que la tarea escribe en la cola se pasa a la tarea mediante el parámetro de tareas.
int iIndexToString;
/* Two instances of this task are created. The task parameter is used to pass an index
into an array of strings into the task. Cast this to the required type. */
for( ;; )
/* Print out the string, not directly, but by passing a pointer to the string to
the gatekeeper task through a queue. The queue is created before the scheduler is started
so will already exist by the time this task executes for the first time. A block time is
not specified because there should always be space in the queue. */
/* Wait a pseudo random time. Note that rand() is not necessarily reentrant, but in
this case it does not really matter because the code does not care what value is returned.
In a more secure application, a version of rand() that is known to be reentrant should be
used or calls to rand() should be protected using a critical section. */
180
Kernel FreeRTOS Guía para desarrolladores
Reescritura de vPrintString() para utilizar
una tarea de guardián (Ejemplo 21)
La función de enlace de ciclos cuenta la cantidad de veces que se llama y envía su mensaje a la tarea de
guardián cada vez que el recuento llega a 200. Solo para fines de demostración, el enlace de ciclos escribe
en la parte delantera de la cola y las tareas escriben en la parte posterior de la cola. La implementación del
enlace de ciclos se muestra aquí.
/* Print out a message every 200 ticks. The message is not written out directly, but
sent to the gatekeeper task. */
iCount++;
/* Reset the count ready to print out the string again in 200 ticks time. */
iCount = 0;
Como es habitual, la función main() crea las colas y las tareas necesarias para ejecutar el ejemplo y, a
continuación, inicia el programador. Aquí se muestra la implementación de main().
/* Define the strings that the tasks and interrupt will print out via the gatekeeper. */
};
/*-----------------------------------------------------------*/
/* Declare a variable of type QueueHandle_t. The queue is used to send messages from the
print tasks and the tick interrupt to the gatekeeper task. */
QueueHandle_t xPrintQueue;
/*-----------------------------------------------------------*/
181
Kernel FreeRTOS Guía para desarrolladores
Reescritura de vPrintString() para utilizar
una tarea de guardián (Ejemplo 21)
/* Before a queue is used it must be explicitly created. The queue is created to hold a
maximum of 5 character pointers. */
/* Create two instances of the tasks that send messages to the gatekeeper. The
index to the string the task uses is passed to the task through the task parameter (the
4th parameter to xTaskCreate()). The tasks are created at different priorities, so the
higher priority task will occasionally preempt the lower priority task. */
/* Create the gatekeeper task. This is the only task that is permitted to directly
access standard out. */
vTaskStartScheduler();
/* If all is well, then main() will never reach here because 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.*/
for( ;; );
El resultado se muestra aquí. Como verá, las cadenas procedentes de las tareas y las cadenas
procedentes de la interrupción se imprimen correctamente sin ningún daño.
182
Kernel FreeRTOS Guía para desarrolladores
Reescritura de vPrintString() para utilizar
una tarea de guardián (Ejemplo 21)
A la tarea de guardián se le asigna una prioridad menor que a las tareas de impresión, por lo que los
mensajes que se envían al guardián permanecen en la cola hasta que ambas tareas de impresión se
encuentran en el estado Bloqueado. En algunas situaciones, no es adecuado asignar al guardián una
prioridad mayor para que los mensajes se procesen de inmediato. Si lo hace, el guardián retrasaría las
tareas de menor prioridad hasta que haya terminado de acceder al recurso protegido.
183
Kernel FreeRTOS Guía para desarrolladores
Características de un grupo de eventos
Grupos de eventos
En esta sección se explica lo siguiente:
Los grupos de eventos son otra característica de FreeRTOS que permite comunicar eventos a las tareas. A
diferencia de las colas y los semáforos, los grupos de eventos:
• Permiten que una tarea espere en el estado Bloqueado a que se produzca una combinación de uno o
más eventos.
• Desbloquean todas las tareas que estaban a la espera del mismo evento, o combinación de eventos,
cuando se produce el evento.
Estas propiedades únicas de los grupos de eventos hacen que sean útiles para sincronizar varias tareas y
transmitir eventos a más de una tarea, lo que permite que una tarea espere en el estado Bloqueado a que
se produzca un evento de un conjunto y que una tarea espere en el estado Bloqueado a que se completen
varias acciones.
Asimismo, los grupos de eventos ofrecen la oportunidad de reducir la RAM que utiliza una aplicación
porque, con frecuencia, es posible reemplazar numerosos semáforos binarios por un único grupo de
eventos.
La funcionalidad de los grupos de eventos es opcional. Para incluir la funcionalidad de los grupos de
eventos, incluya el archivo de origen de FreeRTOS event_groups.c como parte de su proyecto.
Una marca de eventos solo puede ser 1 o 0, lo que permite almacenar su estado en un único bit y
almacenar el estado de todas las marcas de eventos de un grupo de eventos en una única variable.
El estado de cada marca de eventos de un grupo de eventos está representado por un solo bit en una
variable de tipo EventBits_t. Por este motivo, las marcas de eventos también se conocen como bits de
eventos. Si un bit está establecido en 1 en la variable EventBits_t, eso significa que el evento representado
por ese bit se ha producido. Si un bit está establecido en 0 en la variable EventBits_t, eso significa que el
evento representado por ese bit no se ha producido.
Esta figura muestra cómo se asignan marcas de eventos individuales a bits individuales en una variable de
tipo EventBits_t.
184
Kernel FreeRTOS Guía para desarrolladores
Más acerca del tipo de datos EventBits_t
Por ejemplo, si el valor de un grupo de eventos es 0x92 (1001 0010 binario), solo se establecen los bits de
eventos 1, 4 y 7, por lo que únicamente se han producido los eventos representados por los bits 1, 4 y 7.
Esta figura muestra una variable de tipo EventBits_t que tiene establecidos los bits de eventos 1, 4 y 7 y
todos los demás bits de eventos se han borrado, por lo que el grupo de eventos tiene un valor de 0x92.
• Definir que el bit 0 del grupo de eventos signifique que se ha recibido un mensaje de la red.
• Definir que el bit 1 del grupo de eventos signifique que hay un mensaje listo para ser enviado a la red.
• Definir que el bit 2 del grupo de eventos signifique que se anula la conexión de red actual.
• Si configUSE_16_BIT_TICKS es 1, eso significa que cada grupo de eventos contiene 8 bits de eventos
que se pueden utilizar.
• Si configUSE_16_BIT_TICKS es 0, eso significa que cada grupo de eventos contiene 24 bits de eventos
que se pueden utilizar.
Un conector TCP debe responder a muchos eventos diferentes, incluidos los eventos de aceptación, de
vinculación, de lectura y de cierre. Los eventos que puede esperar recibir un socket en cualquier momento
dado dependen del estado del socket. Por ejemplo, si se ha creado un socket, pero aún no se ha vinculado
a una dirección, puede esperar recibir un evento de enlace, pero no un evento de lectura. (No puede leer
datos si no tiene una dirección).
185
Kernel FreeRTOS Guía para desarrolladores
Administración de eventos mediante grupos de eventos
El grupo de eventos también contiene un bit de "anulación", que permite anular una conexión TCP
independientemente del evento que esté esperando el socket en ese momento.
Se hace referencia a los grupos de eventos con variables de tipo EventGroupHandle_t. La función de API
xEventGroupCreate() se utiliza para crear un grupo de eventos. Devuelve un EventGroupHandle_t para
hacer referencia al grupo de eventos que crea.
Nota: No llame a xEventGroupSetBits() desde una rutina del servicio de interrupciones. En su lugar, utilice
la versión a prueba de interrupciones, xEventGroupSetBitsFromISR().
186
Kernel FreeRTOS Guía para desarrolladores
Función de API xEventGroupSetBitsFromISR()
Dar un semáforo es una operación determinista, ya que se sabe de antemano que, al dar un semáforo,
lo máximo que puede ocurrir es que una tarea salga del estado Bloqueado. El establecimiento de bits en
un grupo de eventos no es una operación determinista, porque cuando se establecen bits en un grupo de
eventos, no se sabe de antemano cuántas tareas saldrán del estado Bloqueado.
187
Kernel FreeRTOS Guía para desarrolladores
Función de API xEventGroupWaitBits()
xEventGroup
El controlador del grupo de eventos en el que se están estableciendo los bits. El controlador del grupo de
eventos se ha devuelto desde la llamada a xEventGroupCreate(), que se ha utilizado para crear el grupo
de eventos.
uxBitsToSet
Una máscara de bits que especifica el bit o los bits de eventos que se van a establecer en 1 en el grupo de
eventos. El valor del grupo de eventos se actualiza mediante ORing bit a bit para cambiar el valor existente
del grupo de eventos por el valor que se pasa a uxBitsToSet. Por ejemplo, al establecer uxBitsToSet en
0x05 (0101 binario), dará como resultado que se establezca el bit de eventos 3 y el bit de eventos 0 (si aún
no se han establecido), mientras que todos los demás bits de eventos del grupo de eventos permanecen
sin cambios.
pxHigherPriorityTaskWoken
pdPASS solo se devuelve si los datos se han enviado correctamente a la cola de comandos del
temporizador.
pdFALSE se devuelve si el comando "set bits" no se ha podido escribir en la cola de comandos del
temporizador porque ya estaba llena.
La condición de desbloqueo es la condición que utiliza el programador para determinar si una tarea se
pondrá en estado Bloqueado y cuándo saldrá de ese estado. La condición de desbloqueo se especifica
mediante una combinación de los valores de los parámetros uxBitsToWaitFor y xWaitForAllBits:
• uxBitsToWaitFor, que especifica los bits de eventos en el grupo de eventos que se va a probar.
• xWaitForAllBits, que especifica si se debe usar una prueba O una operación bit a bit o una prueba Y una
operación bit a bit.
188
Kernel FreeRTOS Guía para desarrolladores
Función de API xEventGroupWaitBits()
En la siguiente tabla, se proporcionan ejemplos de las condiciones que producen que una tarea entre
o salga del estado Bloqueado. Solo muestra los cuatro bits binarios menos significativos del grupo de
eventos y los valores de uxBitsToWaitFor. Se presupone que los demás bits de estos dos valores son cero.
189
Kernel FreeRTOS Guía para desarrolladores
Función de API xEventGroupWaitBits()
ambos bits, el 2 y el 3,
estén establecidos en el
grupo de eventos.
La tarea que realiza la llamada especifica los bits que se van a probar con el parámetro uxBitsToWaitFor.
Lo más probable que es la tarea que realiza la llamada tenga que borrar estos bits para volver a cero
después de que se haya cumplido su condición de desbloqueo. Los bits de eventos pueden borrarse con
la función de API xEventGroupClearBits(), pero si se usa esa función para borrar manualmente los bits de
eventos, se producirán condiciones de carrera en el código de la aplicación si:
El parámetro xClearOnExit tiene como finalidad evitar estas condiciones de carrera potenciales. Si
xClearOnExit se establece en pdTRUE, las pruebas y el borrado de bits de eventos aparecen a la
tarea que realiza la llamada como una operación atómica (que no pueden interrumpir otras tareas o
interrupciones).
190
Kernel FreeRTOS Guía para desarrolladores
Función de API xEventGroupWaitBits()
xEventGroupWaitBits() se devolverá
inmediatamente si xTicksToWait es cero o se
cumple la condición de desbloqueo en el momento
en que se llama a xEventGroupWaitBits().
191
Kernel FreeRTOS Guía para desarrolladores
Experimentación con grupos de eventos (Ejemplo 22)
El bit de eventos 0 y el bit de eventos 1 se establecen desde una tarea. El bit de eventos 2 se establece
desde una ISR. Estos tres bits reciben nombres descriptivos utilizando las instrucciones #define que se
muestran aquí.
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* Event bit 0, which is set by a task. */
#define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* Event bit 1, which is set by a task. */
#define mainISR_BIT ( 1UL << 2UL ) /* Event bit 2, which is set by an ISR. */
El código siguiente muestra la implementación de la tarea que establece el bit de eventos 0 y el bit de
eventos 1. Se queda en un bucle, estableciendo de manera repetida un bit y luego otro, con un retardo de
200 milisegundos entre cada llamada a xEventGroupSetBits(). Se imprime una cadena antes de establecer
cada bit para permitir que la secuencia de ejecución se vea en la consola.
192
Kernel FreeRTOS Guía para desarrolladores
Experimentación con grupos de eventos (Ejemplo 22)
for( ;; )
vTaskDelay( xDelay200ms );
/* Print out a message to say event bit 0 is about to be set by the task, and then
set event bit 0. */
vTaskDelay( xDelay200ms );
/* Print out a message to say event bit 1 is about to be set by the task, and then
set event bit 1. */
Como en ejemplos anteriores, la rutina del servicio de interrupciones se activa por medio de una sencilla
tarea periódica que fuerza una interrupción del software. En este ejemplo, la interrupción se genera cada
500 milisegundos.
/* The string is not printed within the interrupt service routine, but is instead
sent to the RTOS daemon task for printing. It is therefore declared static to ensure the
compiler does not allocate the string on the stack of the ISR because the ISR's stack
frame will not exist when the string is printed from the daemon task. */
static const char *pcString = "Bit setting ISR -\t about to set bit 2.\r\n";
/* Print out a message to say bit 2 is about to be set. Messages cannot be printed from
an ISR, so defer the actual output to the RTOS daemon task by pending a function call to
run in the context of the RTOS daemon task. */
193
Kernel FreeRTOS Guía para desarrolladores
Experimentación con grupos de eventos (Ejemplo 22)
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
El código siguiente muestra la implementación de la tarea que llama a xEventGroupWaitBits() para realizar
el bloqueo en el grupo de eventos. La tarea imprime una cadena para cada bit que se establece en el
grupo de eventos.
EventBits_t xEventGroupValue;
for( ;; )
/* Block to wait for event bits to become set within the event group.*/
194
Kernel FreeRTOS Guía para desarrolladores
Experimentación con grupos de eventos (Ejemplo 22)
La función main() crea el grupo de eventos y las tareas antes de iniciar el programador. En el siguiente
código, se muestra su implementación. La prioridad de la tarea que lee el grupo de eventos es mayor que
la prioridad de la tarea que escribe en el grupo de eventos, lo que garantiza que la tarea de lectura tendrá
preferencia sobre la tarea de escritura cada vez que se cumpla la condición de desbloqueo de la tarea de
lectura.
xEventGroup = xEventGroupCreate();
/* Create the task that sets event bits in the event group. */
/* Create the task that waits for event bits to get set in the event group. */
/* Install the handler for the software interrupt. The syntax required to do this is
depends on the FreeRTOS port being used. The syntax shown here can only be used with the
FreeRTOS Windows port, where such interrupts are only simulated. */
vTaskStartScheduler();
for( ;; );
return 0;
Aquí se muestra la salida que se obtiene cuando se ejecuta el Ejemplo 22 con el parámetro xWaitForAllBits
de xEventGroupWaitBits() establecido en pdFALSE. Como verá, dado que el parámetro xWaitForAllBits en
la llamada a xEventGroupWaitBits() está establecido en pdFALSE, la tarea que lee del grupo de eventos
sale del estado Bloqueado y se ejecuta inmediatamente cada vez que se establece cualquiera de los bits
de eventos.
195
Kernel FreeRTOS Guía para desarrolladores
Sincronización de tareas mediante un grupo de eventos
Aquí se muestra la salida que se obtiene cuando se ejecuta el código con el parámetro xWaitForAllBits
de xEventGroupWaitBits() establecido en pdTRUE. Como verá, dado que el parámetro xWaitForAllBits se
ha establecido en pdTRUE, la tarea que lee desde el grupo de eventos solo sale del estado Bloqueado
cuando se han establecido los tres bits de eventos.
196
Kernel FreeRTOS Guía para desarrolladores
Sincronización de tareas mediante un grupo de eventos
de cada tarea se produce después de que dicha tarea haya completado su procesamiento y no puede
continuar hasta que cada una de las otras tareas hayan hecho lo mismo. La Tarea A solo puede recibir otro
evento después de que las cuatro tareas hayan alcanzado su punto de sincronización.
Encontrará un ejemplo menos abstracto de la necesidad de utilizar este tipo de sincronización de tareas
en uno de los proyectos de demostración de FreeRTOS+TCP. La demostración comparte un socket
TCP entre dos tareas. Una tarea envía datos al socket y la otra recibe datos de ese mismo socket.
(Actualmente, esta es la única forma de que un solo socket de FreeRTOS+TCP se pueda compartir entre
tareas). No es seguro que ninguna tarea cierre el socket TCP hasta que esté garantizado que la otra tarea
no intentará obtener acceso de nuevo al socket. Si una de las dos tareas quiere cerrar el socket, debe
informar a la otra tarea de su intención y esperar a que la otra tarea deje de utilizar el socket antes de
continuar.
La situación en la que se encuentra la tarea que envía datos al socket y que desea cerrarlo es trivial, ya
que solo hay dos tareas que deben sincronizarse entre sí. Es fácil ver cómo se complicaría la situación y
sería necesario que más tareas se unieran a la sincronización si otras tareas realizaran el procesamiento
que depende de que el socket esté abierto.
xSocket_t xSocket;
for( ;; )
/* Create a new socket. This task will send to this socket, and another task
will receive from this socket. */
/* Use a queue to send the socket to the task that receives data. */
break;
/* Let the Rx task know the Tx task wants to close the socket. */
197
Kernel FreeRTOS Guía para desarrolladores
Sincronización de tareas mediante un grupo de eventos
TxTaskWantsToCloseSocket();
xEventGroupSync( ... );
/* Neither task is using the socket. Shut down the connection, and then close
the socket. */
WaitForSocketToDisconnect();
FreeRTOS_closesocket( xSocket );
/*-----------------------------------------------------------*/
xSocket_t xSocket;
for( ;; )
/* Wait to receive a socket that was created and connected by the Tx task. */
/* Keep receiving from the socket until the Tx task wants to close the socket.
*/
ProcessReceivedData();
xEventGroupSync( ... );
198
Kernel FreeRTOS Guía para desarrolladores
Función de API xEventGroupSync()
El pseudocódigo anterior muestra dos tareas que se sincronizan entre sí para garantizar que un socket
TCP compartido ya no esté siendo utilizado por ninguna de las tareas antes de que el socket se cierre.
• A cada tarea que debe participar en la sincronización se le asigna un bit de eventos único en el grupo de
eventos.
• Cada tarea establece su propio bit de eventos cuando alcanza el punto de sincronización.
• Al establecer su propio bit de eventos, cada tarea se bloquea en el grupo de eventos a la espera de que
se establezcan también los bits de eventos que representan a todas las demás tareas de sincronización.
1. La Tarea A y la Tarea B ya han alcanzado el punto de sincronización, por lo que sus bits de eventos se
establecen en el grupo de eventos. Están en estado Bloqueado a la espera de que se establezca el bit
de eventos de la Tarea C.
2. La Tarea C alcanza el punto de sincronización y utiliza xEventGroupSetBits() para establecer su bit en el
grupo de eventos. En cuanto se establece el bit de la Tarea C, la Tarea A y la Tarea B salen del estado
Bloqueado y borran los tres bits de eventos.
3. Luego, la Tarea C llama a xEventGroupWaitBits() a la espera de que se establezcan los tres bits de
eventos, pero en ese tiempo, los tres bits de eventos se han borrado, la Tarea A y la Tarea B han salido
de sus respectivos puntos de sincronización y, por tanto, la sincronización ha fallado.
Para utilizar correctamente un grupo de eventos para crear un punto de sincronización, el establecimiento
de un bit de evento y la prueba subsiguiente de los bits de eventos deben realizarse como una única
operación ininterrumpida. La función de API xEventGroupSync() sirve para ese fin.
199
Kernel FreeRTOS Guía para desarrolladores
Función de API xEventGroupSync()
200
Kernel FreeRTOS Guía para desarrolladores
Sincronización de tareas (Ejemplo 23)
TickType_t xDelayTime;
EventBits_t uxThisTasksSyncBit;
/* Three instances of this task are created - each task uses a different event bit in
the synchronization. The event bit to use is passed into each task instance using the task
parameter. Store it in the uxThisTasksSyncBit variable. */
for( ;; )
/* Simulate this task taking some time to perform an action by delaying for a
pseudo-random time. This prevents all three instances of this task from reaching the
synchronization point at the same time, and so allows the example's behavior to be
observed more easily. */
201
Kernel FreeRTOS Guía para desarrolladores
Sincronización de tareas (Ejemplo 23)
vTaskDelay( xDelayTime );
/* Print out a message to show this task has reached its synchronization point.
pcTaskGetTaskName() is an API function that returns the name assigned to the task when the
task was created. */
/* Wait for all the tasks to have reached their respective synchronization points.
*/
/* Print out a message to show this task has passed its synchronization point. As
an indefinite delay was used the following line will only be executed after all the tasks
reached their respective synchronization points. */
La función main() crea el grupo de eventos, crea las tres tareas y luego inicia el programador. En el
siguiente código, se muestra su implementación.
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* Event bit 0, set by the first task. */
#define mainSECOND_TASK_BIT( 1UL << 1UL ) /* Event bit 1, set by the second task. */
#define mainTHIRD_TASK_BIT ( 1UL << 2UL ) /* Event bit 2, set by the third task. */
EventGroupHandle_t xEventGroup;
/* Create three instances of the task. Each task is given a different name, which
is later printed out to give a visual indication of which task is executing. The event bit
to use when the task reaches its synchronization point is passed into the task using the
task parameter. */
202
Kernel FreeRTOS Guía para desarrolladores
Sincronización de tareas (Ejemplo 23)
vTaskStartScheduler();
for( ;; );
return 0;
Aquí se muestra la salida que se genera cuando se ejecuta el Ejemplo 23. Como verá, aunque cada
tarea alcanza el punto de sincronización en un momento diferente (pseudoaleatorio), cada tarea sale del
punto de sincronización al mismo tiempo (que es el momento en el que la última tarea alcanza el punto de
sincronización). En la figura, se muestra el ejemplo que se ejecuta en el puerto de Windows de FreeRTOS,
que no representa el comportamiento auténtico en tiempo real (especialmente si se utilizan llamadas del
sistema Windows para imprimir en la consola). Por lo tanto, habrá alguna variación en lo que se refiere al
tiempo.
203
Kernel FreeRTOS Guía para desarrolladores
Comunicación a través de objetos intermediarios
Notificaciones de tareas
En esta sección se explica lo siguiente:
Las aplicaciones que utilizan FreeRTOS se estructuran como un conjunto de tareas independientes y estas
tareas autónomas tienen que comunicarse entre sí de manera que, de forma colectiva, puedan ofrecer una
funcionalidad de sistema útil.
Cuando se utiliza un objeto de comunicación, los eventos y los datos no se envían directamente a una
tarea que los recibe o una ISR de recepción, sino al objeto de comunicación. Del mismo modo, las tareas y
las ISR reciben eventos y datos desde el objeto de comunicación, en vez de recibirlos directamente desde
la tarea o la ISR que envió el evento o los datos.
Este gráfico muestra un objeto de comunicación que se usa para enviar un evento de una tarea a otra.
Esta figura muestra una notificación de tarea que se usa para enviar un evento directamente de una tarea
a otra.
204
Kernel FreeRTOS Guía para desarrolladores
Beneficios y limitaciones de las notificaciones de tareas
Una tarea puede esperar en el estado Bloqueado, con un tiempo de espera opcional, a que su estado de
notificación pase a convertirse en pendiente.
Asimismo, usar una notificación de tarea para enviar un evento o datos a una tarea requiere una cantidad
claramente inferior de RAM que usar una cola, un semáforo o un grupo de eventos. Esto se debe a que
cada objeto de comunicación (cola, semáforo o grupo de eventos) tiene que crearse para poderlo utilizar,
mientras que habilitar la funcionalidad de notificación de tareas tiene una sobrecarga fija de tan solo ocho
bytes de RAM por tarea.
Los objetos de comunicación se pueden usar para enviar eventos y datos desde una ISR a una tarea o
desde una tarea a una ISR.
Las notificaciones de tareas se pueden utilizar para enviar eventos y datos desde una ISR a una tarea,
pero no se pueden utilizar para enviar eventos o datos desde una tarea a una ISR.
• Habilitar más de una tarea de recepción.
Cualquier tarea o ISR puede obtener acceso a un objeto de comunicación cuyo controlador conozca
(puede ser el controlador de una cola, un semáforo o un grupo de eventos). Las tareas y las ISR, sea
cual sea su cantidad, pueden procesar eventos o datos enviados a cualquier objeto de comunicación.
205
Kernel FreeRTOS Guía para desarrolladores
Uso de notificaciones de tareas
Las notificaciones de tareas se envían directamente a la tarea que las recibe, por lo que solo las puede
procesar la tarea a la que se envía la notificación. En la mayoría de los casos esto es rara vez una
limitación puesto que, si bien es normal que varias tareas e ISR realicen envíos al mismo objeto de
comunicación, no suele producirse lo contrario, es decir que varias tareas e ISR reciban notificaciones
del mismo objeto de comunicación.
• Almacenar en búfer varios elementos de datos.
Una cola es un objeto de comunicación que puede contener más de un elemento de datos a la vez. Los
datos que se envían a la cola pero que esta todavía no ha recibido se almacenan en búfer dentro del
objeto de cola.
Las notificaciones de tareas envían datos a una tarea actualizando el valor de notificación de la tarea
que los recibe. El valor de notificación de una tarea solo puede contener un valor a la vez.
• Retransmitir a más de una tarea.
Un grupo de eventos es un objeto de comunicación que se puede utilizar para enviar un evento a más
de una tarea a la vez.
Las notificaciones de tareas se envían directamente a la tarea de recepción, por lo que solo las puede
procesar la tarea que las recibe.
• Esperar en el estado Bloqueado para que se complete un envío.
Si una tarea intenta enviar una notificación de tarea a una tarea que ya tiene una notificación pendiente,
la tarea que realiza el envío no puede esperar en el estado Bloqueado a que la tarea que recibe el envío
restablezca su estado de notificación. Esto es rara vez una limitación en la mayoría de los casos en los
que se utiliza una tarea de notificación.
Sin embargo, en la mayoría de los casos, la flexibilidad que proporcionan las funciones de API
xTaskNotify() y xTaskNotifyWait() no es necesaria. Basta con funciones más sencillas. La función de API
xTaskNotifyGive() es una alternativa más sencilla pero menos flexible que xTaskNotify(). La función de API
ulTaskNotifyTake() es una alternativa más sencilla pero menos flexible que xTaskNotifyWait().
206
Kernel FreeRTOS Guía para desarrolladores
La función de API xTaskNotifyGive()
La función de API xTaskNotifyGive() se proporciona para permitir usar una notificación de tarea como
alternativa más ligera y rápida a un semáforo binario o de recuento. Aquí se muestra el prototipo de la
función de API xTaskNotifyGive().
207
Kernel FreeRTOS Guía para desarrolladores
La función de API ulTaskNotifyTake()
La función de API ulTaskNotifyTake() se proporciona para permitir usar una notificación de tarea como
alternativa más ligera y rápida a un semáforo binario o de recuento. Aquí se muestra el prototipo de la
función de API ulTaskNotifyTake().
208
Kernel FreeRTOS Guía para desarrolladores
Método 1 para usar una tarea notificación
en lugar de un semáforo (ejemplo 24)
Los eventos de interrupción que se producen entre llamadas a ulTaskNotifyTake se integran en el valor de
notificación de la tarea y las llamadas a ulTaskNotifyTake() regresarán de inmediato si la tarea que realiza
la llamada ya tiene notificaciones pendientes.
209
Kernel FreeRTOS Guía para desarrolladores
Método 1 para usar una tarea notificación
en lugar de un semáforo (ejemplo 24)
uint32_t ulEventsToProcess;
for( ;; )
/* Wait to receive a notification sent directly to this task from the interrupt
service routine. */
if( ulEventsToProcess != 0 )
/* To get here at least one event must have occurred. Loop here until all
the pending events have been processed (in this case, just print out a message for each
event). */
ulEventsToProcess--;
else
/* If this part of the function is reached, then an interrupt did not arrive
within the expected time. In a real application, it might be necessary to perform some
error recovery operations. */
La tarea periódica que se utiliza para generar interrupciones de software imprime un mensaje antes y
después de que se genere la interrupción. Esto permite que la secuencia de ejecución se respete en la
salida.
210
Kernel FreeRTOS Guía para desarrolladores
Método 1 para usar una tarea notificación
en lugar de un semáforo (ejemplo 24)
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
Como era de esperar, es idéntica a la salida que se genera cuando se ejecuta el ejemplo 16.
vHandlerTask() entra en el estado En ejecución tan pronto como se genera la interrupción, por lo que la
salida de la tarea divide la salida que la tarea periódica produce. La secuencia de ejecución se muestra
aquí.
211
Kernel FreeRTOS Guía para desarrolladores
Método 2 para usar una tarea notificación
en lugar de un semáforo (ejemplo 25)
Cuando xClearOnExit es pdFALSE, llamar a ulTaskNotifyTake() solo reducirá (en uno) el valor de
notificación de la tarea que realiza la llamada, en lugar de borrarla y ponerla en cero. El recuento de
notificaciones es, por lo tanto, la diferencia entre el número de eventos que se han producido y el número
de eventos que se han procesado. Esto permite simplificar la estructura de vHandlerTask() de dos formas:
1. El número de eventos a la espera de ser procesados está contenido en el valor de notificación, por lo
que no es necesario integrarlo en una variable local.
2. Solo es necesario procesar un evento entre cada llamada a ulTaskNotifyTake().
212
Kernel FreeRTOS Guía para desarrolladores
Método 2 para usar una tarea notificación
en lugar de un semáforo (ejemplo 25)
for( ;; )
/* Wait to receive a notification sent directly to this task from the interrupt
service routine. The xClearCountOnExit parameter is now pdFALSE, so the task's
notification value will be decremented by ulTaskNotifyTake() and not cleared to zero. */
/* To get here, an event must have occurred. Process the event (in this case,
just print out a message). */
else
/* If this part of the function is reached, then an interrupt did not arrive
within the expected time. In a real application, it might be necessary to perform some
error recovery operations. */
Para facilitar la demostración, también se ha modificado la rutina de servicio a fin de enviar más de una
notificación de tarea por interrupción y, al hacerlo, simular varias interrupciones que se producen a alta
frecuencia. Aquí se muestra la implementación de la rutina del servicio de interrupciones.
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
/* Send a notification to the handler task multiple times. The first give will unblock
the task. The following gives are to demonstrate that the receiving task's notification
value is being used to count (latch) events, allowing the task to process each event in
turn. */
vTaskNotifyGiveFromISR(xHandlerTask, &xHigherPriorityTaskWoken);
vTaskNotifyGiveFromISR(xHandlerTask, &xHigherPriorityTaskWoken);
vTaskNotifyGiveFromISR(xHandlerTask, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
El resultado se muestra aquí. Verá que vHandlerTask() procesa los tres eventos cada vez que se genera
una interrupción.
213
Kernel FreeRTOS Guía para desarrolladores
Funciones de API xTaskNotify() y xTaskNotifyFromISR()
• Incrementando o añadiendo uno al valor de notificación de la tarea que recibe la llamada, en cuyo caso
xTaskNotify() es equivalente a xTaskNotifyGive().
• Estableciendo uno o varios bits en el valor de notificación de la tarea que recibe la llamada. Esto permite
usar el valor de notificación de una tarea como alternativa ligera y más rápida a un grupo de eventos.
• Escribiendo un número totalmente nuevo en el valor de notificación de la tarea que recibe la llamada,
pero solo si esta ha leído su valor de notificación desde que se actualizó por última vez. Esto permite
que el valor de notificación de una tarea proporcione una funcionalidad similar a la que proporciona una
cola que tiene una longitud de uno.
• Escribiendo un número totalmente nuevo en el valor de notificación de la tarea que recibe la llamada,
incluso si esta no ha leído su valor de notificación desde que se actualizó por última vez. Esto permite
que el valor de notificación de una tarea proporcione una funcionalidad similar a la que proporciona la
función de API xQueueOverwrite(). El comportamiento obtenido se denomina en ocasiones buzón de
correo.
La función xTaskNotify() es más flexible y potente que xTaskNotifyGive(). También es algo más compleja
de utilizar.
xTaskNotifyFromISR() es una versión de xTaskNotify() que se puede utilizar en una rutina de servicio de
interrupción. Por lo tanto, tiene un parámetro pxHigherPriorityTaskWoken adicional.
Llamar a xTaskNotify() siempre establecerá el estado de notificación de la tarea que recibe la llamada en
pendiente si esta todavía no estaba en este estado.
214
Kernel FreeRTOS Guía para desarrolladores
Funciones de API xTaskNotify() y xTaskNotifyFromISR()
En la siguiente tabla se muestran los valores de los parámetros eNotifyAction de xTaskNotify() válidos y el
efecto que producen en el valor de notificación de la tarea que recibe la llamada.
215
Kernel FreeRTOS Guía para desarrolladores
La función de API xTaskNotifyWait()
216
Kernel FreeRTOS Guía para desarrolladores
La función de API xTaskNotifyWait()
217
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de UART
1. pdTRUE
1. pdFALSE
218
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de UART
operación la opción de esperar en el estado Bloqueado a que se complete la operación. De esta forma
las tareas de menor prioridad pueden ejecutarse mientras que la tarea que realiza la operación más larga
se encuentra en estado Bloqueado y ninguna tarea usa tiempo de procesamiento a menos que pueda
utilizarlo de manera productiva.
Es habitual que las bibliotecas de controladores compatibles con RTOS usen un semáforo binario para
poner tareas en el estado Bloqueado. La técnica se demuestra con el siguiente pseudocódigo, que
proporciona el esquema de una función de biblioteca compatible con RTOS que transmite datos en un
puerto UART. En la siguiente lista de códigos:
• xUART es una estructura que describe el periférico UART y contiene información de estado. El miembro
xTxSemaphore de la estructura es una variable de tipo SemaphoreHandle_t. Se presupone que el
semáforo ya se ha creado.
• La función xUART_Send() no incluye ninguna lógica de exclusión mutua. Si más de una tarea va a
utilizar la función xUART_Send(), el programador de la aplicación deberá administrar la exclusión mutua
dentro mismo de la aplicación. Por ejemplo, es posible que se necesite una tarea para obtener una
exclusión mutua antes de llamar a xUART_Send().
• La función de API xSemaphoreTake() se utiliza para poner la tarea de llamada en el estado Bloqueado
después de que se haya iniciado la transmisión UART.
• La función de API xSemaphoreGiveFromISR() se utiliza para sacar la tarea del estado Bloqueado
después de que la transmisión se haya completado, que es cuando se ejecuta la rutina de servicio de
interrupciones de fin de transmisión del periférico UART.
BaseType_t xReturn;
/* Ensure the UART's transmit semaphore is not already available by attempting to take
the semaphore without a timeout. */
xSemaphoreTake( pxUARTInstance->xTxSemaphore, 0 );
/* Block on the semaphore to wait for the transmission to complete. If the semaphore is
obtained, then xReturn will get set to pdPASS. If the semaphore take operation times out,
then xReturn will get set to pdFAIL. If the interrupt occurs between UART_low_level_send()
being called and xSemaphoreTake() being called, then the event will be latched in the
binary semaphore, and the call to xSemaphoreTake() will return immediately. */
return xReturn;
/*-----------------------------------------------------------*/
/* The service routine for the UART's transmit end interrupt, which executes after the last
byte has been sent to the UART. */
219
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de UART
UART_low_level_interrupt_clear( pxUARTInstance );
/* Give the Tx semaphore to signal the end of the transmission. If a task is Blocked
waiting for the semaphore, then the task will be removed from the Blocked state. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
La técnica demostrada en este código es viable y se usa con frecuencia, pero tiene algunos
inconvenientes:
El código siguiente muestra cómo evitar estos inconvenientes gracias al uso de una notificación de tarea
en lugar de un semáforo binario.
Nota: Si una biblioteca utiliza notificaciones de tareas, la documentación de la biblioteca debe indicar
claramente que llamar a una función de biblioteca puede cambiar el estado y el valor de la notificación de
la tarea de llamada.
En el siguiente código:
Se accede al miembro xTaskToNotify de la estructura xUART desde una tarea y una rutina de servicio de
interrupción, que requiere tener en cuenta cómo el procesador actualizará su valor:
• Si xTaskToNotify se actualiza con una única operación de escritura de memoria, se puede actualizar
fuera de una sección crítica, exactamente tal y como se muestra en el siguiente código. Este sería
el caso si xTaskToNotify fuera una variable de 32 bits (TaskHandle_t fuera un tipo de 32 bits) y el
procesador en el que se ejecuta FreeRTOS fuera un procesador de 32 bits.
220
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de UART
BaseType_t xReturn;
/* Save the handle of the task that called this function. The book text contains notes
as to whether the following line needs to be protected by a critical section or not. */
pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle();
/* Ensure the calling task does not already have a notification pending by calling
ulTaskNotifyTake() with the xClearCountOnExit parameter set to pdTRUE, and a block time of
0 (don't block). */
ulTaskNotifyTake( pdTRUE, 0 );
return xReturn;
/*-----------------------------------------------------------*/
/* The ISR that executes after the last byte has been sent to the UART. */
/* This function should not execute unless there is a task waiting to be notified.
Test this condition with an assert. This step is not strictly necessary, but will aid
debugging. configASSERT() is described in Developer Support.*/
221
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de UART
UART_low_level_interrupt_clear( pxUARTInstance );
/* Send a notification directly to the task that called xUART_Send(). If the task
is Blocked waiting for the notification, then the task will be removed from the Blocked
state. */
/* Now there are no tasks waiting to be notified. Set the xTaskToNotify member of the
xUART structure back to NULL. This step is not strictly necessary, but will aid debugging.
*/
pxUARTInstance->xTaskToNotify = NULL;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
Las notificaciones de tareas también pueden sustituir semáforos en la recepción de funciones, tal y como
se demuestra en el siguiente pseudocódigo, que proporciona el esquema de una función de biblioteca
compatible con RTOS que recibe datos en un puerto UART.
• La función xUART_Receive() no incluye ninguna lógica de exclusión mutua. Si más de una tarea va a
utilizar la función xUART_Receive(), el programador de la aplicación deberá administrar la exclusión
mutua dentro mismo de la aplicación. Por ejemplo, es posible que se necesite una tarea para obtener
una exclusión mutua antes de llamar a xUART_Receive().
• La rutina del servicio de interrupción de recepciones de UART pone los caracteres que UART recibe en
un búfer de RAM. La función xUART_Receive() devuelve caracteres desde el búfer de RAM.
• El parámetro uxWantedBytes de xUART_Receive() se utiliza para especificar el número de caracteres
que se deben recibir. Si el búfer de RAM todavía no contiene el número de caracteres solicitado, la tarea
de llamada se pone en el estado Bloqueado a la espera de recibir una notificación de que el número
de caracteres que hay en el búfer ha aumentado. El bucle while() se usa para repetir esta secuencia
hasta que el búfer de recepción contenga el número de caracteres solicitado o bien se agote el tiempo
de espera.
• La tarea de llamada puede entrar en el estado Bloqueado más de una vez. El tiempo de bloqueo, por
lo tanto, se ajusta para que tenga en cuenta la cantidad de tiempo que ya ha transcurrido desde que
se llamara a xUART_Receive(). Los ajustes sirven para garantizar que el tiempo total que se pasa
en xUART_Receive() no supere el tiempo de bloqueo especificado por el miembro xRxTimeout de la
estructura xUART, El tiempo de bloqueo se ajusta con las funciones auxiliares vTaskSetTimeOutState()
y xTaskCheckForTimeOut() de FreeRTOS.
size_t uxReceived = 0;
TickType_t xTicksToWait;
TimeOut_t xTimeOut;
vTaskSetTimeOutState( &xTimeOut );
222
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de UART
xTicksToWait = pxUARTInstance->xRxTimeout;
pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle();
/* Loop until the buffer contains the wanted number of bytes or a timeout occurs. */
/* Look for a timeout, adjusting xTicksToWait to account for the time spent in this
function so far. */
/* Timed out before the wanted number of bytes were available, exit the loop.
*/
break;
/* The receive buffer does not yet contain the required amount of bytes. Wait for
a maximum of xTicksToWait ticks to be notified that the receive interrupt service routine
has placed more data into the buffer. It does not matter if the calling task already had a
notification pending when it called this function. If it did, it would just iterate around
this while loop one extra time. */
/* No tasks are waiting for receive notifications, so set xTaskToNotify back to NULL.
*/
pxUARTInstance->xTaskToNotify = NULL;
/* Attempt to read uxWantedBytes from the receive buffer into pucBuffer. The actual
number of bytes read (which might be less than uxWantedBytes) is returned. */
return uxReceived;
/*-----------------------------------------------------------*/
/* Copy received data into this UART's receive buffer and clear the interrupt. */
UART_low_level_receive( pxUARTInstance );
223
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de ADC
vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify,
&xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
}
• Se presupone que comienza una conversión ADC como mínimo cada 50 milisegundos.
• ADC_ConversionEndISR() es la rutina del servicio de interrupciones de interrupción final de la
conversión de ADC, que es la interrupción que se ejecuta cada vez que hay un nuevo valor de ADC
disponible.
• La tarea que vADCTask() implementa procesa cada uno de los valores generados por ADC. Se
presupone que el controlador de la tarea se ha almacenado en xADCTaskToNotify cuando se creó la
tarea.
• ADC_ConversionEndISR() utiliza xTaskNotifyFromISR() con el parámetro eAction establecido en
eSetValueWithoutOverwrite para enviar una notificación de tarea a la tarea vADCTask() y escribir el
resultado de la conversión ADC en el valor de notificación de la tarea.
• La tarea vADCTask() utiliza xTaskNotifyWait() para esperar a recibir una notificación de que hay un
nuevo valor ADC disponible y para recuperar el resultado de la conversión ADC desde su valor de
notificación.
uint32_t ulADCValue;
BaseType_t xResult;
224
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas en controladores
de dispositivos periféricos: ejemplo de ADC
for( ;; )
xResult = xTaskNotifyWait( /* The new ADC value will overwrite the old value, so
there is no need to clear any bits before waiting for the new notification value. */ 0, /*
Future ADC values will overwrite the existing value, so there is no need to clear any bits
before exiting xTaskNotifyWait(). */ 0, /* The address of the variable into which the
task's notification value (which holds the latest ADC conversion result) will be copied.
*/ &ulADCValue, /* A new ADC value should be received every xADCConversionFrequency ticks.
*/ xADCConversionFrequency * 2 );
ProcessADCResult( ulADCValue );
else
/* The call to xTaskNotifyWait() did not return within the expected time.
Something must be wrong with the input that triggers the ADC conversion or with the ADC
itself. Handle the error here. */
/*-----------------------------------------------------------*/
/* The interrupt service routine that executes each time an ADC conversion completes. */
uint32_t ulConversionResult;
/* If the call to xTaskNotifyFromISR() returns pdFAIL then the task is not keeping
up with the rate at which ADC values are being generated. configASSERT() is described in
section 11.2.*/
225
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas
directamente dentro de una aplicación
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
1. La aplicación se comunica a través de una conexión a Internet lenta para enviar y solicitar datos a un
servidor de datos remoto (el servidor de la nube).
2. Después de solicitar datos al servidor de la nube, la tarea que realiza la solicitud debe esperar en el
estado Bloqueado a recibir los datos solicitados.
3. Después de enviar datos al servidor de la nube, la tarea que realiza el envío tiene que esperar en
el estado Bloqueado a recibir un reconocimiento de que el servidor de la nube ha recibido los datos
correctamente.
226
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas
directamente dentro de una aplicación
El código siguiente muestra la estructura que las funciones CloudRead() y CloudWrite() envían a la tarea
de servidor.
} Operation_t;
uint32_t ulDataValue; /* Only used when writing data to the cloud server. */
} CloudCommand_t;
El pseudocódigo para CloudRead() se muestra aquí. La función envía su solicitud a la tarea de servidor
y, a continuación, llama a xTaskNotifyWait() para que espere en el estado Bloqueado hasta que reciba la
notificación de que los datos solicitados están disponibles.
/* ulDataID identifies the data to read. pulValue holds the address of the variable into
which the data received from the cloud server is to be written. */
CloudCommand_t xRequest;
BaseType_t xReturn;
/* Set the CloudCommand_t structure members to be correct for this read request. */
/* Ensure there are no notifications already pending by reading the notification value
with a block time of 0, and then send the structure to the server task. */
xTaskNotifyWait( 0, 0, NULL, 0 );
/* Wait for a notification from the server task. The server task writes the value
received from the cloud server directly into this task's notification value, so there
is no need to clear any bits in the notification value on entry to or exit from the
xTaskNotifyWait() function. The received value is written to *pulValue, so pulValue is
passed as the address to which the notification value is written. */
227
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas
directamente dentro de una aplicación
return xReturn;
El pseudocódigo que muestra cómo la tarea de servidor administra una solicitud de lectura se muestra
aquí. Una vez que se han recibido los datos del servidor de la nube, la tarea de servidor desbloquea la
tarea de aplicación y envía los datos recibidos a la tarea de aplicación llamando a xTaskNotify() con el
parámetro eAction establecido en eSetValueWithOverwrite.
Se trata de una situación simplificada, ya que presupone que GetCloudData() no tiene que esperar a
obtener un valor del servidor de la nube.
CloudCommand_t xCommand;
uint32_t ulReceivedValue;
for( ;; )
case eRead:
/* Obtain the requested data item from the remote cloud server. */
/* Call xTaskNotify() to send both a notification and the value received from
the cloud server to the task that made the request. The handle of the task is obtained
from the CloudCommand_t structure. */
break;
228
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas
directamente dentro de una aplicación
La tarea borra los cuatro bits de estado, envía su solicitud a la tarea de servidor y, a continuación, llama a
xTaskNotifyWait() para que espere en el estado Bloqueado a la notificación de estado.
OPERATION_TIMED_OUT_BIT \|
NO_INTERNET_CONNECTION_BIT \|
CANNOT_LOCATE_CLOUD_SERVER_BIT )
CloudCommand_t xRequest;
uint32_t ulNotificationValue;
/* Set the CloudCommand_t structure members to be correct for this write request. */
/* Wait for a notification from the server task. The server task writes a bitwise
status code into this task's notification value, which is written to ulNotificationValue.
*/
229
Kernel FreeRTOS Guía para desarrolladores
Notificaciones de tarea usadas
directamente dentro de una aplicación
pdMS_TO_TICKS( 250 ) ); /* Wait a maximum of 250ms. */ /* Return the status code to the
calling task. */ return ( ulNotificationValue & CLOUD_WRITE_STATUS_BIT_MASK );
El pseudocódigo que demuestra cómo la tarea de servidor administra una solicitud de escritura se
muestra aquí. Una vez que se han enviado los datos al servidor de la nube, la tarea de servidor
desbloquea la tarea de aplicación y envía el código de estado bit a bit a la tarea de aplicación llamando
a xTaskNotify() con el parámetro eAction establecido en eSetBits. Solo los bits definidos por la constante
CLOUD_WRITE_STATUS_BIT_MASK pueden resultar alterados en el valor de notificación de la tarea que
recibe la llamada, por lo que esta tarea puede utilizar otros bits en su valor de notificación para otros fines.
Esto es una situación simplificada, ya que presupone que SetCloudData() no tiene que esperar a obtener
un reconocimiento del servidor de la nube remoto.
CloudCommand_t xCommand;
uint32_t ulBitwiseStatusCode;
for( ;; )
switch( xCommand.eOperation )
case eWrite:
/* Send the data to the remote cloud server. SetCloudData() returns a bitwise
status code that only uses the bits defined by the CLOUD_WRITE_STATUS_BIT_MASK definition
(shown in the preceding code). */
/* Send a notification to the task that made the write request. The eSetBits
action is used so any status bits set in ulBitwiseStatusCode will be set in the
notification value of the task being notified. All the other bits remain unchanged. The
handle of the task is obtained from the CloudCommand_t structure. */
break;
230
Kernel FreeRTOS Guía para desarrolladores
configASSERT()
Developer Support
En esta sección, se explican características que maximizan la productividad de la siguiente manera:
configASSERT()
En C, la macro assert() se utiliza para verificar una aserción (suposición) que realiza el programa. La
aserción se escribe como una expresión de C. Si la expresión se evalúa como false (0), se considera que
la aserción ha fallado. Por ejemplo, este código prueba la aserción que indica que el puntero pxMyPointer
no es NULL.
El programador de la aplicación especifica la acción que debe realizarse si una aserción falla
proporcionando una implementación de la macro assert().
El código fuente de FreeRTOS no llama a assert(), porque assert() no está disponible en todos los
compiladores en los que se compila FreeRTOS. En su lugar, el código fuente de FreeRTOS contiene una
gran cantidad de llamadas a una macro denominada configASSERT(), que el programador de la aplicación
puede definir en FreeRTOSConfig.h y que se comporta exactamente como el assert() estándar de C.
El error de una aserción debe tratarse como un error fatal. No intente realizar la ejecución después de una
línea que tiene un error de aserción.
/* Disable interrupts so the tick interrupt stops executing, and then sit in a loop so
execution does not move past the line that failed the assertion. If the hardware supports
231
Kernel FreeRTOS Guía para desarrolladores
Tracealyzer
a debug break instruction, then the debug break instruction can be used in place of the
for() loop. */
for(;;); }
La definición de configASSERT() es útil cuando no se está ejecutando una aplicación bajo el control de un
depurador. Imprime o registra de alguna forma la línea de código fuente en la que ha fallado una aserción.
Puede identificar la línea en la que ha fallado la aserción mediante la macro estándar de C __FILE__ para
obtener el nombre del archivo de origen y la macro estándar de C __LINE__ para obtener el número de
línea en el archivo de origen.
Este código muestra una definición configASSERT() que registra la línea de código fuente en la que ha
fallado una aserción.
Tracealyzer
Tracealyzer es una herramienta de diagnóstico y optimización en tiempo de ejecución de nuestro socio
Percepio.
Tracealyzer captura valiosa información sobre el comportamiento dinámico y la presenta en vistas gráficas
interconectadas. La herramienta también puede mostrar varias vistas sincronizadas.
La información que obtiene es útil para realizar análisis, resolver problemas o simplemente optimizar una
aplicación de FreeRTOS.
Tracealyzer se puede utilizar en paralelo con un depurador tradicional. Complementa la vista del depurador
con una perspectiva basada en el tiempo de nivel superior.
232
Kernel FreeRTOS Guía para desarrolladores
Tracealyzer
233
Kernel FreeRTOS Guía para desarrolladores
Tracealyzer
234
Kernel FreeRTOS Guía para desarrolladores
Funciones de enlace relacionadas con
la depuración (devolución de llamadas)
Las estadísticas del tiempo de ejecución están pensadas como ayuda para crear perfiles y realizar la
depuración durante la fase de desarrollo de un proyecto. La información que proporcionan solo es válida
hasta que el contador que se utiliza como reloj para las estadísticas de tiempo de ejecución se desborda.
La recopilación de las estadísticas de tiempo de ejecución aumenta el tiempo de cambio de contexto de las
tareas.
235
Kernel FreeRTOS Guía para desarrolladores
Reloj de estadísticas de tiempo de ejecución
Para obtener la información de las estadísticas de tiempo de ejecución binario, llame a la función de API
uxTaskGetSystemState(). Para obtener la información de las estadísticas de tiempo de ejecución en una
tabla ASCII legible, llame a la función auxiliar vTaskGetRunTimeStats().
1. Configurar un periférico para generar una interrupción periódica en la frecuencia de reloj de las
estadísticas de tiempo de ejecución deseada y, a continuación, usar un recuento del número de
interrupciones generadas como reloj de estadísticas de tiempo de ejecución.
Este método es muy poco eficaz si la interrupción periódica solo se utiliza con el fin de proporcionar un
reloj de estadísticas de tiempo de ejecución. Sin embargo, si la aplicación ya utiliza una interrupción
periódica con una frecuencia adecuada, resulta sencillo y eficaz añadir un recuento del número de
interrupciones generadas en la rutina del servicio de interrupciones existente.
2. Generar un valor de 32 bits utilizando el valor actual de un temporizador periférico de 16 bits de
libre ejecución como los 16 bits menos importantes del valor de 32 bits y el número de veces que el
temporizador se ha desbordado como los 16 bits más importantes del valor de 32 bits.
Con una manipulación un poco compleja, puede generar un reloj de estadísticas de tiempo de ejecución
combinando el recuento de ciclos de RTOS con el valor actual de un temporizador Cortex-M SysTick de
ARM. Este procedimiento se explica en algunos de los proyectos de demostración que se incluyen en la
descarga de FreeRTOS.
Macro Descripción
236
Kernel FreeRTOS Guía para desarrolladores
Función de API uxTaskGetSystemState()
pulTotalRunTime Si configGENERATE_RUN_TIME_STATS se
establece en 1 en FreeRTOSConfig.h,
<problematic>*</problematic>
uxTaskGetSystemState() se establece por medio
de pulTotalRunTime() en el tiempo de ejecución
total (tal y como define el reloj de estadísticas de
tiempo de ejecución que proporciona la aplicación)
desde que se inició el destino.
237
Kernel FreeRTOS Guía para desarrolladores
Función de API uxTaskGetSystemState()
TaskHandle_t xHandle;
UBaseType_t xTaskNumber;
eTaskState eCurrentState;
UBaseType_t uxCurrentPriority;
UBaseType_t uxBasePriority;
uint32_t ulRunTimeCounter;
uint16_t usStackHighWaterMark;
} TaskStatus_t;
238
Kernel FreeRTOS Guía para desarrolladores
Función auxiliar vTaskList()
vTaskList() es una función que utiliza mucho procesador. Deja al programador suspendido durante un
largo periodo de tiempo. Por lo tanto, le recomendamos que utilice la función solo para la depuración y no
en un sistema de producción en tiempo real.
239
Kernel FreeRTOS Guía para desarrolladores
Función auxiliar vTaskList()
En la salida:
240
Kernel FreeRTOS Guía para desarrolladores
Función auxiliar vTaskGetRunTimeStats()
vTaskGetRunTimeStats() es una función que utiliza mucho procesador. Deja al programador suspendido
durante un largo periodo de tiempo. Por este motivo, le recomendamos que utilice la función solo para la
depuración y no en un sistema de producción en tiempo real.
En la salida:
241
Kernel FreeRTOS Guía para desarrolladores
Generación y visualización de estadísticas
en tiempo de ejecución: ejemplo de trabajo
El valor de 32 bits se crea utilizando el recuento de casos de desbordamiento como los dos bytes más
significativos del valor de 32 bits y el valor actual del contador de 16 bits como los dos bytes menos
significativos del valor de 32 bits. A continuación, se muestra el pseudocódigo de la rutina del servicio de
interrupciones.
ulOverflowCount++;
ClearTimerInterrupt();
#define configGENERATE_RUN_TIME_STATS 1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
vSetupTimerForRunTimeStats()
/* Disconnect the clock from the counter so it does not change while its value is being
used. */
242
Kernel FreeRTOS Guía para desarrolladores
Generación y visualización de estadísticas
en tiempo de ejecución: ejemplo de trabajo
PauseTimer();
/* The number of overflows is shifted into the most significant two bytes of the
returned 32-bit value. */
/* The current counter value is used as the least significant two bytes of the returned
32-bit value. */
ResumeTimer(); \\
La tarea que se muestra aquí imprime las estadísticas de tiempo de ejecución recopiladas cada 5
segundos.
/* For clarity, calls to fflush() have been omitted from this codelisting. */
TickType_t xLastExecutionTime;
/* The buffer used to hold the formatted runtime statistics text needs to be quite
large. It is therefore declared static to ensure it is not allocated on the task stack.
This makes this function non-reentrant. */
/* Initialize xLastExecutionTime to the current time. This is the only time this
variable needs to be written to explicitly. Afterward, it is updated internally within the
vTaskDelayUntil() API function. */
xLastExecutionTime = xTaskGetTickCount();
for( ;; )
/* Generate a text table from the runtime stats. This must fit into the
cStringBuffer array. */
vTaskGetRunTimeStats( cStringBuffer );
243
Kernel FreeRTOS Guía para desarrolladores
Macros de enlace de seguimiento
printf( "\nTask\t\tAbs\t\t\t%%\n" );
printf("-------------------------------------------------------------\n" );
/* Print out the runtime stats themselves. The table of data contains multiple
lines, so the vPrintMultipleLines() function is called instead of calling printf()
directly. vPrintMultipleLines() simply calls printf() on each line individually, to ensure
the line buffering works as expected. */
vPrintMultipleLines( cStringBuffer );
Muchas de las descripciones de esta tabla se refieren a una variable llamada pxCurrentTCB.
pxCurrentTCB es una variable privada de FreeRTOS que contiene el controlador de la tarea en estado
En ejecución. Está disponible para cualquier macro que se llame desde el archivo de origen FreeRTOS/
Source/tasks.c.
Macro Descripción
244
Kernel FreeRTOS Guía para desarrolladores
Macros de enlace de seguimiento disponibles
245
Kernel FreeRTOS Guía para desarrolladores
Definición de macros de enlace de seguimiento
traceQUEUE_RECEIVE_FROM_ISR_FAILED(pxQueue)
Se llama desde xQueueReceiveFromISR() cuando
la operación de recepción falla debido a que la
cola ya está vacía. El parámetro pxQueue pasa el
controlador de la cola de destino a la macro.
Según la práctica recomendada en ingeniería de software, FreeRTOS mantiene una política estricta de
ocultación de datos. Las macros de seguimiento permiten añadir el código de usuario a los archivos fuente
de FreeRTOS para que los tipos de datos visibles para las macros de seguimiento sean diferentes de los
visibles para el código de la aplicación:
Tenga mucho cuidado si una macro de seguimiento accede directamente a una estructura de datos de
FreeRTOS que normalmente es privada. Las estructuras de datos privadas pueden cambiar de una versión
de FreeRTOS a otra.
• Eclipse (StateViewer)
246
Kernel FreeRTOS Guía para desarrolladores
Complementos de depurador compatibles con FreeRTOS
• Eclipse (ThreadSpy)
• IAR
• ARM DS-5
• Atollic TrueStudio
• Microchip MPLAB
• iSYSTEM WinIDEA
La siguiente figura muestra el complemento FreeRTOS ThreadSpy Eclipse de Code Confidence Ltd.
247
Kernel FreeRTOS Guía para desarrolladores
Introducción y ámbito del capítulo
Solución de problemas
Introducción y ámbito del capítulo
En esta sección se describen los problemas más frecuentes con que se encuentran los usuarios que no
están familiarizados con FreeRTOS, en concreto:
Prioridades de interrupción
Nota: Esta es la principal causa de solicitud de soporte. En la mayoría de los puertos, definir
configASSERT() permitirá capturar el error inmediatamente.
Si el puerto FreeRTOS que se está usando admite el anidamiento de interrupciones y la rutina de servicio
de una interrupción usa la API de FreeRTOS, tiene que establecer la prioridad de interrupción en el valor
de configMAX_SYSCALL_INTERRUPT_PRIORITY o por debajo, tal y como se describe en Administración
de interrupciones (p. 125). Si no configura la prioridad correctamente, sus secciones críticas no serán
efectivas, lo que, a su vez, dará lugar a errores intermitentes.
• Las prioridades de interrupción tengan de manera predeterminada la prioridad más alta posible, lo que
ocurre con algunos procesadores ARM Cortex. En dichos procesadores la prioridad de una interrupción
que utilice la API de FreeRTOS no se puede dejar sin inicializar.
• Los números de alta prioridad numérica representen lógicamente prioridades de baja interrupción, lo que
a primera vista puede parecer contradictorio. En este caso, de nuevo, se trata de los procesadores ARM
Cortex y posiblemente también de otros procesadores.
• Por ejemplo, en este tipo de procesador una interrupción que se está ejecutando con una
prioridad de 5 puede interrumpirse con una prioridad de 4. Por lo tanto, si se establece
configMAX_SYSCALL_INTERRUPT_PRIORITY en 5, solo se puede asignar a las interrupciones que
usen la API de FreeRTOS una prioridad numérica que sea igual o superior a 5. En dicho caso, las
prioridades de interrupción de 5 o 6 son válidas, pero una prioridad de interrupción que sea de 3 es
definitivamente no válida.
• Haya diferentes implementaciones de biblioteca que esperen que la prioridad de una interrupción se
especifique de otra manera. Esto es especialmente pertinente en el caso de las bibliotecas dirigidas
a procesadores ARM Cortex, donde las prioridades de interrupción se desplazan con bits antes de
escribirlas en los registros de hardware. Algunas bibliotecas realizan ellas mismas los desplazamientos
de bits, mientras que otras esperan a que se ejecute el desplazamiento de bits antes de que la prioridad
se transfiera a la función de biblioteca.
248
Kernel FreeRTOS Guía para desarrolladores
Desbordamiento de pila
Desbordamiento de pila
El desbordamiento de pila es la segunda causa más frecuente de solicitudes de soporte. FreeRTOS
dispone de varias características útiles para capturar y depurar problemas relacionados con las pilas.
(Estas características no están disponibles en el puerto Windows de FreeRTOS).
249
Kernel FreeRTOS Guía para desarrolladores
Información general de la comprobación
de pila en tiempo de ejecución
El enlace de desbordamiento de pila se proporciona para facilitar la captura y la depuración de los errores
de pila, pero no hay una verdadera manera de recuperarse de un desbordamiento de pila cuando este se
produce. Los parámetros de la función pasan a la función de enlace el controlador y el nombre de la tarea
que ha desbordado su pila.
El enlace de desbordamiento de pila recibe una llamada desde el contexto de una interrupción.
Algunos microcontroladores generan una excepción de error cuando detectan que se ha producido un
acceso a la memoria incorrecto. Es posible que se desencadene un error antes de que el kernel tenga la
posibilidad de llamar a una función de enlace de desbordamiento de pila.
El contexto completo de ejecución de una tarea se guarda en su pila cada vez que se
intercambia. Es probable que sea cuando el uso de la pila llegue a su máximo. Cuando
configCHECK_FOR_STACK_OVERFLOW se establece en 1, el kernel comprueba que el puntero de pila
permanezca dentro del espacio de pila válido después de que se haya guardado el contexto. Se llama al
enlace de desbordamiento de pila si el puntero de pila está fuera de su rango válido.
Cuando se crea una tarea, su pila se llena con un patrón conocido. El método 2 comprueba los últimos
20 bytes válidos del espacio de pila de la tarea para verificar que no se haya sobrescrito este patrón. La
función de enlace de desbordamiento de pila se llama si el valor de alguno de los 20 bytes ha cambiado en
relación con los valores previstos.
250
Kernel FreeRTOS Guía para desarrolladores
Uso incorrecto de printf() y sprintf().
El método 2 no se ejecuta tan rápido como el método 1, aunque sigue siendo relativamente rápido ya que
solo se prueban 20 bytes. Es muy probable que capture todos los desbordamientos de pila.
Muchos proveedores de compiladores cruzados proveen una implementación de printf() adecuada para
usarla en pequeños sistemas integrados. Incluso cuando ese es el caso, la implementación podría no ser
a prueba de subprocesos, probablemente no sea adecuada para usarla dentro de una rutina de servicio
de interrupción y en función de adónde se dirija la salida, podría tardar un tiempo relativamente largo en
ejecutarse.
• Solo con incluir una llamada a printf() o sprintf() puede incrementar masivamente el tamaño del archivo
ejecutable de la aplicación.
• printf() y sprintf() podrían llamar a malloc(), lo que podría ser una operación no válida si se usa un
método de asignación de memoria que no sea heap_3. Para obtener más información, consulte Métodos
de asignación de memoria de ejemplo (p. 15).
• printf() y sprintf() podrían necesitar una pila muchas veces más grande de lo que se necesitase.
Printf-stdarg.c
Muchos de los proyectos de demostración de FreeRTOS utilizan un archivo llamado printf-stdarg.c que
proporciona una implementación mínima y eficiente para pilas de sprintf() que se puede utilizar en lugar de
la versión de biblioteca estándar. En la mayoría de los casos, esto permitirá asignar una pila mucho más
pequeña a cada tarea que llame a sprintf() y sus funciones relacionadas.
printf stdarg.c también proporciona un mecanismo para dirigir la salida de printf() a un puerto, carácter a
carácter. Aunque es un proceso lento, esto permite reducir aún más el uso de la pila.
No todas las copias de printf-stdarg.c implementan snprintf(). Las copias que no implementan snprintf()
simplemente no tienen en cuenta el parámetro de tamaño del búfer que asignan directamente a sprintf().
Printf-stdarg.c es código abierto, pero es propiedad de un tercero y, por lo tanto, tiene una licencia
independiente de FreeRTOS. Los términos de licencia se encuentran en la parte superior del archivo de
origen.
251
Kernel FreeRTOS Guía para desarrolladores
Síntoma: si se usa una función de la API en
una interrupción la aplicación se bloquea.
las tareas de demostración, por lo que después de crear las tareas, el montón que quede será insuficiente
para añadir más tareas, colas, grupos de eventos o semáforos.
Para poder añadir más tareas, aumente el tamaño del montón o elimine algunas de las tareas de
demostración existentes. Para obtener más información, consulte Métodos de asignación de memoria de
ejemplo (p. 15).
La forma en que se definen y se usan las interrupciones varía según los puertos y los compiladores. Por lo
tanto, en segundo lugar hay que comprobar que la sintaxis, las macros y las convenciones de llamada que
se usan en la rutina del servicio de interrupción sean exactamente como las que se describen en la página
de documentación proporcionada para el puerto que se usa y sean exactamente tal y como se demuestra
en la aplicación de demostración proporcionada con el puerto.
Si la aplicación se ejecuta en un procesador que utiliza números de baja prioridad numéricamente para
representar altas prioridades, asegúrese de que la prioridad asignada a cada interrupción lo tenga en
cuenta, ya que puede parecer contrario a la lógica. Si la aplicación se ejecuta en un procesador que aplica
de forma predeterminada a la prioridad de cada interrupción la máxima prioridad posible, asegúrese
de que no se deje la prioridad de cada interrupción en su valor predeterminado. Para obtener más
información, consulte Anidamiento de interrupciones (p. 158).
Algunos procesadores tienen que estar en modo privilegiado para poder iniciar el programador. La forma
más sencilla de hacerlo es poner el procesador en un modo privilegiado en el código de inicio de C, antes
de que se llame a main().
252
Kernel FreeRTOS Guía para desarrolladores
Síntoma: las interrupciones se quedan desactivadas
de forma inesperada o bien hay secciones
críticas que no se anidan correctamente
Muchas funciones de API solo se pueden llamar después de que se haya iniciado el programador. Es
mejor restringir el uso de la API a la creación de objetos, como tareas, colas y semáforos, en lugar de usar
estos objetos hasta después llamar a vTaskStartScheduler().
No llame a funciones de API desde dentro de una sección crítica o mientras el programador esté
suspendido.
253