Introducción a FreeRTOS
FreeRTOS es un sistema operativo en tiempo real (RTOS) de código abierto optimizado para microcontroladores y sistemas embebidos con recursos limitados. Se distingue por su bajo consumo de memoria y eficiencia, siendo ideal para aplicaciones que requieren respuesta temporal determinista. Este sistema operativo priroiza la estabilidad y el cumplimiento de plazos en tareas críticas.
Características Principales de FreeRTOS
- Modularidad: Permite adaptar el tamaño del sistema eliminando componentes no esenciales para optimizar el uso de memoria.
- Escalabilidad: A pesar de ser compacto, ofrece funcionalidades completas como gestión de tareas, semáforos y colas de mensajes.
- Licencia Abierta: Es software libre con soporte comunitario activo y permisos para uso comercial.
Aplicaciones Típicas
Debido a su naturaleza ligera, FreeRTOS se emplea en dispositivos electrónicos de consumo, sistemas automotrices, controladores industriales y equipos médicos, donde la confiabilidad y el rendimiento en tiempo real son esenciales.
Gestión y Creación de Tareas
Las tareas constituyen las unidades básicas de ejecución en un RTOS. En FreeRTOS, su administración implica la inicialización de bloques de control de tarea (TCB), asignación de pilas y control de estados.
Proceso de Creación de Tareas
Inicialización del Bloque de Control de Tarea (TCB)
El TCB almacena el contexto y estado de una tarea. Su configuración incluye asignar memoria, establecer la función de entrada, parámetros y prioridad, e inicializar el puntero de pila.
// Definición de estructura para el bloque de control de tarea
typedef struct ControlTarea {
// Otros campos...
StackType_t *punteroPila; // Apunta a la cima de la pila
// Otros campos...
} ControlTarea_t;
// Función pseudocódigo para crear una tarea
ControlTarea_t *ct;
void iniciarTarea( void *codigoTarea, const char *nombre, uint32_t tamPila, void *parametros, uint32_t prioridad, TaskHandle_t *manejador ) {
// Reservar espacio para el TCB
ct = asignarMemoria( sizeof( ControlTarea_t ) );
// Comprobar asignación exitosa
if( ct != NULL ) {
// Configurar campos del TCB, incluyendo punteroPila
// ...
// Inicializar pila de la tarea
// ct->punteroPila = ...;
// Vincular el manejador de tarea
*manejador = (TaskHandle_t) ct;
// Pasos adicionales de inicialización
// ...
} else {
// Manejo de error por falta de memoria
// ...
}
}
Asignación y Configuración de la Pila
Cada tarea posee una pila independiente para variables locales y estado. La pila se inicializa durante la creación de la tarea, estableciendo la dirección de retorno y parámetros iniciales, adaptándose a la arquitectura del procesador.
StackType_t *prepararPila( StackType_t *cimaPila, TaskFunction_t codigo, void *parametros ) {
// Preparar la pila para la primera ejecución de la tarea
cimaPila--;
*cimaPila = ( StackType_t ) codigo;
cimaPila--;
*cimaPila = ( StackType_t ) parametros;
// Inicializar marcos adicionales según el CPU
// ...
return cimaPila; // Devuelve la nueva posición de la pila
}
Planificación de Tareas
El planificador de FreeRTOS determina qué tarea ejecuta el CPU basándose en prioridades. Utiliza un enfoque preemptivo con partición de tiempo para tareas de igual prioridad.
Asignación y Ajuste de Prioridades
Las prioridades se definen numéricamente, donde valores menores indican mayor prioridad. La prioridad 0 está reservada para la tarea ociosa. Es posible modificar dinámicamente las prioridades mediante funciones como ajustarPrioridadTarea.
void ajustarPrioridadTarea( TaskHandle_t tarea, UBaseType_t nuevaPrioridad ) {
// Implementación interna para actualizar prioridad y reprogramar
// ...
}
Control de Estados de Tareas
Las tareas pueden estar en estados como ejecución, lista, blqoueada o suspendida. Las transiciones se gestionan mediante eventos o decisiones del planificador.
Mecanismos de Suspensión y Restauración
Las tareas pueden suspenderse temporalmente para salir de la lista de ejecución, conservando su contexto. Esto es útil para ajustes dinámicos o manejo de recursos.
void suspenderTarea( TaskHandle_t tarea ) {
// Cambia estado de la tarea a suspendida
// ...
}
void reanudarTarea( TaskHandle_t tarea ) {
// Restaura la tarea al estado listo
// ...
}
Mecanismo de Cambio de Contexto
El cambio de tarea permite la multitarea al alternar rápidamente entre ejecuciones. Implica guardar y restaurar el contexto del CPU, crucial para mantener la capacidad de respuesta en sistemas embebidos.
Principios del Cambio de Contexto
Este proceso se activa cuando una tarea de mayor prioridad llega a estar lista o durante interrupciones. Incluye salvar registros, actualizar el TCB y transferir el control a la siguiente tarea.
Estrategias de Optimización
Para minimizar la sobrecarga, se recomienda simplificar el código de cambio de contexto, ajustar tamaños de pila y aprovechar características de hardware que aceleren la operación. Un balance entre eficiencia y tiempo real es esencial, logrado mediante algoritmos de prioridad y diseño por interrupciones.
// Ejemplo simplificado de cambio de tarea
void alternarContextoTarea( void ) {
// Guardar contexto actual
guardarEstado();
// Seleccionar siguiente tarea según prioridad
TaskHandle_t siguiente = obtenerSiguienteTarea();
// Restaurar contexto de la siguiente tarea
restaurarEstado( siguiente );
}
// Hook de tick del sistema para disparar cambios
void ganchoTickAplicacion( void ) {
alternarContextoTarea();
}
Mecanismos de Sincronización
La sincronización coordina el acceso a recursos compartidos y el orden de ejecución entre tareas, previniendo condiciones de carrera y asegurando coherencia de datos.
Semáforos
Los semáforos facilitan la notificación entre tareas e interrupciones. Operaciones básicas incluyen creación, adquisición (con posible bloqueo) y liberación.
SemaphoreHandle_t semaforo = NULL;
void tareaSincronizada( void *parametros ) {
// Crear semáforo binario
semaforo = crearSemáforoBinario();
if( semaforo != NULL ) {
// Intentar adquirir semáforo
if( tomarSemáforo( semaforo, 10 ) == pdTRUE ) {
// Ejecutar sección crítica
// ...
}
// Liberar semáforo
liberarSemáforo( semaforo );
}
}
Mutexes
Los mutexes protegen recursos exclusivos mediante bloqueo, incorporando herenica de prioridad para mitigar inversiones de prioridad. Se diferencian de los semáforos al estar diseñados para exclusión mutua.
SemaphoreHandle_t mutex = NULL;
void tareaProtegida( void *parametros ) {
// Crear mutex
mutex = crearMutex();
if( mutex != NULL ) {
// Bloquear recurso compartido
if( tomarMutex( mutex, portMAX_DELAY ) == pdTRUE ) {
// Acceder a recurso crítico
// ...
// Liberar mutex
liberarMutex( mutex );
}
}
}
Colas
Las colas implementan comunicación FIFO entre tareas, permitiendo transferencia segura de datos. Se utilizan para desacoplar tareas productoras y consumidoras.
QueueHandle_t cola = NULL;
#define LONGITUD_COLA 10
#define TAM_ELEMENTO sizeof( uint32_t )
void tareaEmisora( void *parametros ) {
// Crear cola
cola = crearCola( LONGITUD_COLA, TAM_ELEMENTO );
if( cola != NULL ) {
// Enviar datos a la cola
uint32_t valor = 100;
enviarACola( cola, ( void * ) &valor, portMAX_DELAY );
// Recibir datos de la cola
uint32_t recibido;
recibirDeCola( cola, ( void * ) &recibido, portMAX_DELAY );
}
}
Gestión del Tiempo
El manejo del tiempo en FreeRTOS se basa en ticks del sistema, proporcionando funciones de retardo y temporizadores para ejecutar tareas en intervalos específicos.
Funciones de Retardo
retardoTarea suspende la tarea actual durante un número definido de ticks, liberando el CPU para otras tareas.
void tareaPeriodica( void *parametros ) {
for( ;; ) {
// Ejecutar lógica de tarea
// ...
// Retrasar 100 ticks
retardoTarea( 100 / portTICK_PERIOD_MS );
}
}
Temporizadores
Los temporizadores ejecutan callbacks en intervalos regulares o una sola vez. Se configuran con periodos específicos y pueden usarse para tareas de mantenimiento o actualización dinámica.
// Crear temporizador periódico
TimerHandle_t temporizador = crearTemporizador(
"Temporizador",
5000 / portTICK_PERIOD_MS,
pdTRUE,
( void * ) 0,
funcionCallback
);
// Iniciar temporizador
if( temporizador != NULL ) {
iniciarTemporizador( temporizador, 0 );
}
// Función de callback
void funcionCallback( TimerHandle_t temp ) {
// Acciones a ejecutar al expirar el temporizador
// ...
}