Introducción al Entorno de Desarrollo
La adopción de STM32CubeMX ha simplificado considerablemente la gestión de la capa HAL en los microcontroladores de ST, especialmente en series donde el soporte se centra exclusivamente en esta biblioteca. Al trabajar con la placa de desarrollo basada en el STM32G431RBT6, la integración de un sistema operativo en tiempo libre como FreeRTOS puede realizarse de manera automatizada, evitando la complejidad asociada a la移植ación manual de archivos fuente y la configuración de interrupciones.
Este enfoque permite generar automáticamente la estructura del proyecto, configurando los temporizadores y las prioridades necesarias para el planificador de tareas.
Configuración del Proyecto en CubeMX
Para iniciar, se crea un nuevo proyecto seleccionando el microcontrolador adecuado. Los pasos críticos para habilitar el RTOS incluyen:
- Reloj y Temporizadores: En la pestaña System Core, configure el pin de debug como Serial Wire. Es fundamental cambiar la fuente de Timebase Source de SysTick a otro temporizador hardware (por ejemplo, TIM6). Esto se debe a que FreeRTOS requiere el SysTick para su tick de sistema (generalmente 1ms), y el núcleo no puede compartirlo con la HAL para funciones de delay básicas.
- Middleware: Active la opción FREERTOS. Se recomienda seleccionar la interfaz CMSIS_V2 para aprovechar las APIs más modernas y una mejor gestión de memoria, aunque la versión V1 es compatible en la mayoría de los casos simples.
- Periféricos: Configure los pines GPIO correspondientes a los LEDs y botones de la placa, así como el periférico USART para la salida de depuración.
Una vez generados los archivos, el entorno estará listo para la implementación de la lógica de tareas.
Arquitectura del Software
El punto de entrada principal inicializa el hardware y arranca el planificador. La lógica de aplicación se separa en tareas independientes definidas dentro del archivo generado para FreeRTOS.
Implementación de Tareas Básicas
El siguiente ejemplo muestra dos tareas concurrentes que controlan indicadores luminosos con periodos distintos. Se ha modificado la estructura de las funciones para mejorar la legibilidad.
/* Función auxiliar para cambiar el estado físico del LED */
void alternar_estado_led(uint8_t indice)
{
/* Habilitar latch */
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
/* Conmutar el pin correspondiente en el puerto C */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8 << (indice - 1));
/* Deshabilitar latch */
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/* Tarea 1: Parpadeo a 250ms */
void tarea_parpadeo_rapido(void *argument)
{
for(;;)
{
alternar_estado_led(1);
osDelay(250);
}
}
/* Tarea 2: Parpadeo a 500ms */
void tarea_parpadeo_lento(void *argument)
{
for(;;)
{
alternar_estado_led(2);
osDelay(500);
}
}
Gestión Dinámica de Tareas
Para consolidar los conocimientos sobre el planificador, se puede implementar un sistema donde los botones físicos controlen el ciclo de vida de las tareas (creación, eliminación, suspensión y reanudación).
Requisitos del sistema:
- Dos tareas de LED con frecuencias diferentes.
- Un botón para destruir o recrear la primera tarea de LED.
- Un botón para suspender o reanudar la segunda tarea de LED.
Nota: Al manejar creación dinámica de tareas, asegúrese de que el heap de FreeRTOS sea suficientemente grande. El uso de CMSIS_V2 facilita esta gestión.
Lógica de Control por Botones
Las siguientes funciones demuestran cómo manipular los identificadores de tarea (osThreadId_t) en tiempo de ejecución.
/* Control de creación y destrucción mediante BTN1 */
void gestion_botones_primario(void *argument)
{
while(1)
{
/* Detección de flanco de bajada */
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20); /* Antirrebote */
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
/* Esperar liberación */
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET);
if(id_tarea_led_1 != NULL)
{
osThreadTerminate(id_tarea_led_1);
id_tarea_led_1 = NULL;
apagar_led_indice(1);
}
else
{
id_tarea_led_1 = osThreadNew(tarea_parpadeo_rapido, NULL, &attr_tarea_led_1);
}
}
}
osDelay(10);
}
}
/* Control de suspensión y reanudación mediante BTN2 */
void gestion_botones_secundario(void *argument)
{
static uint8_t estado_suspendido = 0;
while(1)
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET);
if(estado_suspendido == 0)
{
osThreadSuspend(id_tarea_led_2);
estado_suspendido = 1;
}
else
{
osThreadResume(id_tarea_led_2);
estado_suspendido = 0;
}
}
}
osDelay(10);
}
}
Capa de Drivers y Utilidades
Para completar el sistema, es necesario implementar funciones de bajo nivel para el manejo de hardware y la redirección de la salida estándar hacia la UART.
Control de LEDs y UART
/* Encendido o apagado específico */
void controlar_led(uint8_t indice, uint8_t activo)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
if(activo)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (indice - 1), GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (indice - 1), GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/* Redirección de printf hacia UART1 */
int __io_putchar(int ch)
{
uint8_t dato = (uint8_t)ch;
HAL_UART_Transmit(&huart1, &dato, 1, HAL_MAX_DELAY);
return ch;
}
Escaneo de Matriz de Botones
Una función robusta para leer el estado de los pulsadores evitando lecturas múltiples por rebote mecánico.
#define BTN_1_PIN GPIO_PIN_0
#define BTN_2_PIN GPIO_PIN_1
#define PORT_BOTONES GPIOB
uint8_t leer_entrada_botones(void)
{
static uint8_t estado_anterior = 1;
uint8_t evento = 0;
uint8_t lectura = HAL_GPIO_ReadPin(PORT_BOTONES, BTN_1_PIN) &&
HAL_GPIO_ReadPin(PORT_BOTONES, BTN_2_PIN);
if(estado_anterior && !lectura)
{
osDelay(15);
estado_anterior = 0;
evento = 1; /* Indica presión */
}
else if(!estado_anterior && lectura)
{
estado_anterior = 1;
}
return evento;
}