Sistema Operativo de Tiempo Real, RTOS

Introducción.

Los sistemas embebidos pueden ser programados de forma tradicional, o mediente el uso de sistemas operativos de tiempo real, diseñados específicamente para atender a los más exigentes requerimientos de velocidad de respuesta y eficiencia.

Tradicionalmente, un programa en C consta de una inicialización (Setup) y un bucle infinito (While) que se ejecuta indefinidamente. Finalmente, se programan rutinas para atender a interrupciones externas, temporizadores, etc.

Este esquema de funcionamiento secuencial es sencillo y totalmente adecuando cuando todas las tareas a realizar tienen una prioridad parecida. De esta forma, cada ejecución de un ciclo de programa se atiende a todas las tareas.

Sin embargo, en sistemas embebidos, es habitual que el mismo micro-controlador tenga que atender entradas y rutinas de control, con muy diferente prioridad. Por ejemplo, siempre es prioritario supervisar el movimiento de la máquina controlada por el micro-controlador, pero no lo es tanto el atender a la señalización (encender las luces de marcha y paro, avería, etc).

En estos casos, es más eficiente emplear un sistema operativo en tiempo real, conocidos por sus siglas RTOS, ya que permiten crear hilos de ejecución con diferentes prioridades.

Veamos un ejemplo.

En una máquina de cuatro ejes, existirá un hilo asociado a cada eje, y otro para tareas auxiliares. También se puede programar la ejecución de estas tareas en los tiempos muertos, en los que el micro no tiene nada que hacer.

Además, se podría añadir otro hilo, destinado específicamente a la atención de los comandos que llegan por la conexión Bluetooth o la pantalla táctil.

Una vez puesto en marcha el sistema, hay que asignar prioridades de ejecución, que en este podrían ser:

  • Alta prioridad para los cuatro hilos que controlan los ejees de la máquina, y los cuatro con la misma prioridad.
  • Prioridad media para la rutina que atiende a los comandos entrantes por bluetooth o la pantalla táctil.
  • Prioridad baja para la gestión de la señalización.

La utilización de estos sistemas operativos es sencilla, ya que solo hay que incluir su código fuente en C en nuestro proyecto, y compilar el conjunto. Ocupan unas decenas de KB, y por tanto no suponen un problema de espacio.

Selección del Sistema operativo de tiempo Real a emplear: freeRTOS.

He seleccionado freeRTOS después de estudiar las opciones disponibles en el mundo "Open Source", es decir, de libre uso. Su página web es.

http://www.freertos.org/

Como explica su página Web, en el año 2013 freeRTOS ha sido descargado 107000 veces.

Está diseñado para ser facil de usar: Solo tiene tres archivos fuente con el código general, y uno más, especifico de cada micro-controlador.

Está completamente documentado y explicado, y hay multitud de ejemplos y libros de los que aprender.

Estructura de un programa basado en RTOS.

Como se ve en el código mostrado a continuación, la estructura de funcionamiento de un programa que emplea un RTOS, es bastante sencilla de comprender. De heco, la migración de un programa convencional, a uno basado en el uso de un RTOS es muy sencilla y rápida. Como se apreciará en las siguientes explicaciones, la primera ventaja obtenida es la ejecución del código en multitarea (multithread).

Función principal main().

La función prinicipal main() simplemente crea las tareas, y a continuación lanza el "scheduler", es decir, el gestor de tareas. El gestor de tareas opera de forma transparente a nosotros, y es el que va asignado tiempo de ejecuación a cada tarea "Task", en función de su Prioridad. Esta prioridad se asigna a cada tarea en el momento de crearse.

int main(void) {

// MCU Configuration.

................

................

................

// Create Tasks.

xTaskCreate(vTask0, (signed char *)"Task0", STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);

xTaskCreate(vATask1, (signed char *)"Task1", STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);

xTaskCreate(vATask2, (signed char *)"Task2", STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);

// Start scheduler.

vTaskStartScheduler();

// We should never get here as control is now taken by the scheduler .

// Infinite loop.

while (1) { }

}

Y ¿cómo son cada una de las Tareas?

Son funciones convencionales, que ejecutan un bucle permanente, ejecutado nuestro código de control.

void Task1(void * pvParameters) {

for (;;) {

// Code doing our things.

................

................

................

}

}

Ejemplo de Multiproceso, en sistemas embebidos.

Establecemos dos niveles de tareas "Task". Las cuatro de bajo nivel arrancan y paran el motor que tiene asignado cada una de ellas. Las de alto nivel llamarán a estas tareas de manera secuencial, para llevan a cabo movimientos complejos.

Tareas de control de motor PaP.

En esta placa de control, hemos programado una tarea "Task" para cada motor, que ejecuta su máquina de estados (arranque, movimento y paro).

El código fuente en C es único, es decir, las tres tareas ejecutan el mismo código C, en espacios de memoria diferentes. Esto permite ahorrar espacio de programa, y asegura la unicidad del código de control de los tres motores.

Tareas que implementan Movimientos Complejos.

Además, pueden crearse otras tareas "Task" para ejecutar secuencias complejas, por ejemplo, para fabricar un engranaje.

Este tipo de tareas solo tendrían que ir cargando los datos adecuados a las tareas anteriormente descritas, y ejecutar la orden de arranque de cada una de ellas, para que muevan el motor PaP según la velocidad y tiempo que le hayamos asignado.

Haciendo esto tantas veces como sea necesario, se va ejecutando la secuencia de encendido y apagado de motores programada, que hará que nuestra fresadora fabrique el engranaje.

En el vídeo puede apreciarse como al desencadenarse el proceso, se van arrancando y parando motores, en lo que es parte de una secuencia de fabricación de engranjes helicoidales.

Adaptación a estándares internacionales y modernización del código fuente.

Estos meses estoy trabajando en adaptar la aplicación de control a los nuevos estándares que van implantándose en la industria. Los trabajos se han centrado en:

  • Adopción del estándar CMSIS-RTOS como sistema operativo RTOS.
  • Utilización de una capa de abstracción de alto nivel HAL, como middleware de acceso a los periféricos del microcontrolador ARM® Cortex®-3.

Capa de software CMSIS-RTOS

Con la aparición de numerosas empresas desarrolladoras de sistemas operativos de tiempo real RTOS para sistemas embebidos, cada vez se hacía más trabajoso migrar una aplicación de un microcontrolador a otro, o simplemente de un RTOS a otro.

El problema llegó a tal punto, que muchas veces los propios fabricantes de chips definían ya sus propios RTOS. Todo ello conllevaba un elevado coste para las empresas.

En 2012, los desarrolladores de sistemas operativos de tiempo real RTOS y fabricantes de microcontroladores ARM® Cortex®-M como los STM32F2 y STM32F4 utilizados en este proyecto, acordaron utilizar un nuevo estándar "de facto" liderado por la empresa ARM®. Este estándar se llama CMSIS-RTOS.

CMSIS-RTOS es, por tanto, un estándar RTOS definido para mejorar la portabilidad entre las aplicaciones de microcontroladores. El cumplimiento de esta norma se puede lograr por diseño (en el caso de nuevos RTOS) o mediante una capa de adaptación en la parte superior de un RTOS existente.

En este proyecto estamos utilizando el sistema operativo para tiempo real freeRTOS. Para adaptarnos al estándar CMSIS-RTOS utilizaremos una capa superior desarrollada por freeRTOS, que ofrece una interfaz de tipo CMSIS-RTOS hacia la aplicación de control. Pero por debajo seguirá corriendo el mismo sistema operativo freeRTOS.

De esta forma, si en el futuro quiero dejar de utilizar freeRTOS e introducir otro RTOS, la migración será muy sencilla.




Arquitectura de una aplicación con sistema operativo CMSIS-RTOS

Drivers de alto nivel para el hardware periférico del ARM. Capa "hardware abstraction layer" HAL.

Todos los fabricantes de microcontroladores ARM® Cortex®-Mofrecen sus propias librerías de acceso a los periféricos del microcontrolador. El hardware al que nos estamos refiriendo incluye los buses SPI, I2C, USART, USB, Ethernet, etc. Este hardware no está definido dentro del estándar ARM, y por tanto su diseño y forma de uso es totalmente dependiente de cada fabricante. De la misma forma, las liberías que ofrecen a los desarroladores son diferentes, dependiendo del fabricante.

En el caso de los microcontroladores STM32F2 y STM32F4 del fabricante ST Microelectronics utilizados en este proyecto, el fabricante ha pretendido evolucionar sus librerías Standard Peripheral Libraries, a otras que ofrezcan una interfaz de mucho más alto nivel.

Con ello se quiere conseguir que, al menos, pasar de usar un microcontrolador ARM de este fabeicante, a otro de este mismo fabricante, sea una tarea trivial, ya que ST Microelectronics se compromete a desarrollar esta librerías de alto nivel ( hardware abstraction layer HAL) para todos sus microcontroladores ARM, de forma que su interfaz superior, hacia la aplicación, sea siempre la misma, y que todas las particularidades de cada chip ARM queden tratadas dentro de cada librería HAL.





Arquitectura de los drivers HAL

Compatibilidad del software.

De cara a verificar que el código es lo más genérico posible, he compilado el código de programa en los siguientes etornos de desarrollo:

  • Keil (Compilador oficial de la empresa ARM, con un código fuente limitado a 32KB)
  • TueStudio. (En Noviembre de 2015 dejó de estar limitado a 32KB)
  • Eclipse (GNU para C/C++ y sistemas embebidos)
  • CoIDE (un compilador de origen chino).

A pesar de que todos son compiladores de C y C++, la realidad es que cada uno tiene sus preferencias y particularidades, por lo que si nuestro código se comporta correctamente en todos estos entornos, tenemos la tranquilidad de estar generando un software muy compatible y por lo tanto será más difícil que haya comportamientos imprevistos.


Siguiente página ->

<-Página anterior