2. 移植FreeRTOS到STM32
任务目标
在STM32F103裸机工程中集成FreeRTOS内核,完成SysTick与PendSV中断配置,创建两个优先级相同的任务,通过串口输出验证任务调度器正常交替运行。
环境与依赖
- 硬件:STM32F103C8T6最小系统板,USART1(PA9/PA10)连接USB转串口模块
- 软件:STM32CubeIDE 或 Keil MDK-ARM(本文以STM32CubeIDE为例)
- FreeRTOS版本:V10.4.6(或更高版本,接口一致)
- 基础工程:已配置好系统时钟(72MHz)和USART1驱动,能通过printf输出调试信息
实现步骤
将FreeRTOS源码添加到工程:复制FreeRTOS/Source目录下的tasks.c、list.c、queue.c、timers.c、event_groups.c及portable/MemMang/heap_4.c到工程,并添加portable/RVDS/ARM_CM3(Keil)或portable/GCC/ARM_CM3(CubeIDE)下的port.c和portmacro.h。在工程头文件搜索路径中添加FreeRTOS源码根目录和portable对应目录。
创建FreeRTOSConfig.h配置文件,核心配置如下:
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 8 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_TIMERS 0
#define configQUEUE_REGISTRY_SIZE 0
#define configPRIO_BITS 4
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
#endif /* FREERTOS_CONFIG_H */
关键完整代码
/* main.c - FreeRTOS双任务交替运行示例 */
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h" /* 包含串口初始化函数 MX_USART1_UART_Init() 和 printf 重定向 */
/* 任务函数声明 */
void vTask1(void *pvParameters);
void vTask2(void *pvParameters);
/* 任务句柄(本示例未使用,但保留以演示创建方式) */
TaskHandle_t xTask1Handle = NULL;
TaskHandle_t xTask2Handle = NULL;
int main(void)
{
/* 初始化系统时钟(CubeIDE生成的SystemClock_Config()) */
SystemClock_Config();
/* 初始化USART1 */
MX_USART1_UART_Init();
/* 创建两个相同优先级的任务 */
xTaskCreate(vTask1, "Task1", 128, NULL, 2, &xTask1Handle);
xTaskCreate(vTask2, "Task2", 128, NULL, 2, &xTask2Handle);
/* 启动调度器(此函数不会返回) */
vTaskStartScheduler();
/* 正常情况下不会执行到这里 */
for( ;; );
}
void vTask1(void *pvParameters)
{
(void)pvParameters;
for( ;; )
{
printf("[Task1] Running\r\n");
vTaskDelay(pdMS_TO_TICKS(500)); /* 延时500ms */
}
}
void vTask2(void *pvParameters)
{
(void)pvParameters;
for( ;; )
{
printf("[Task2] Running\r\n");
vTaskDelay(pdMS_TO_TICKS(500)); /* 延时500ms */
}
}
/* 可选:空闲任务钩子未启用,无需实现 */
中断优先级配置说明
FreeRTOS要求PendSV和SysTick中断优先级设为最低。在STM32CubeIDE中,通过HAL_NVIC_SetPriority()设置:
/* 在main函数中、创建任务前添加 */
HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0);
HAL_NVIC_SetPriority(SysTick_IRQn, 15, 0);
若使用Keil标准库,则在NVIC_Configuration()中设置:
NVIC_SetPriority(PendSV_IRQn, 15);
NVIC_SetPriority(SysTick_IRQn, 15);
注意:configKERNEL_INTERRUPT_PRIORITY必须与上述设置一致(均为15),否则调度异常。
编译与运行
- 将上述
main.c内容替换工程中生成的main.c(保留SystemClock_Config和MX_USART1_UART_Init函数)。 - 确认FreeRTOS源码已添加至工程且头文件路径正确。
- 编译工程,烧录至STM32F103C8T6。
- 打开串口助手,波特率115200,观察输出。
验证方法
串口助手应持续交替收到以下输出(预期输出,非实测结果):
[Task1] Running
[Task2] Running
[Task1] Running
[Task2] Running
...
判定通过条件:
- 两个任务均能周期执行,输出间隔约500ms;
- Task1和Task2交替出现,无任务长期独占CPU;
- 系统不进入HardFault,无卡死现象。
常见问题
- 编译报错“undefined symbol vApplicationStackOverflowHook”:在任意.c文件中添加空实现:
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { (void)xTask; (void)pcTaskName; for(;;); } - 任务不切换:检查PendSV和SysTick优先级是否已设为最低(数值15),且
configKERNEL_INTERRUPT_PRIORITY与之匹配。 - 串口无输出:确认USART1已初始化且printf重定向正确(使用MicroLIB或fputc重定向)。