一. 创建新工程 (以STM32CubeIDE为例)
-
核心组件:
- 硬件: STM32F103开发板 (如常见的"Blue Pill"板)
- 开发环境:
- 选项1: Keil MDK-ARM (uVision) + STM32F1xx Device Family Pack (DFP)
- 选项2: STM32CubeIDE (推荐,集成度更高)
- 软件:
- FreeRTOS源码 (从官网或STM32CubeF1库获取)
- STM32标准外设库 (StdPeriph) 或 HAL库 (推荐使用CubeIDE自带的HAL)
-
打开STM32CubeIDE。
-
File -> New -> STM32 Project。 -
在
Target Selection窗口:- 在
Part Number搜索框输入STM32F103C8(或你的具体型号,如F103RC)。 - 选择正确的型号 (例如
STM32F103C8Tx)。 - 点击
Next。
- 在
-
输入项目名称 (如
FreeRTOS_Demo),选择项目位置。 -
点击
Finish。IDE会自动生成基于HAL库的初始化代码框架。
二. 启用并配置FreeRTOS
- 在
Project Explorer中双击打开*.ioc文件 (CubeMX配置文件)。 - 转到
Middleware选项卡。 - 在左侧列表中选择
FREERTOS。 - 在
Mode下拉菜单中,选择Interface为CMSIS_V2(这是FreeRTOS的CMSIS-RTOS API v2封装层,标准化且易用)。 - 配置内核参数 (关键步骤!):
CONFIG子选项卡:Kernel settings:TICK_RATE_HZ: 系统时钟节拍频率。通常设置为1000(1ms)。注意: 这个值必须与SysTick中断频率匹配(在Clock Configuration中设置,通常HCLK / 1000)。MAX_PRIORITIES: 最大任务优先级数。默认56通常足够,资源紧张时可减小(如7)。MINIMAL_STACK_SIZE: 最小任务栈大小(字为单位,1字=4字节)。默认128字(512字节)。重要: 根据任务复杂度调整!简单任务可能够用,复杂任务(大量局部变量、函数调用深)需要增加(如256或512字)。栈溢出是常见错误源!TOTAL_HEAP_SIZE: FreeRTOS堆总大小(字节)。这是关键资源! STM32F103C8只有20KB RAM,需谨慎分配。初始值10240(10KB)是常用起点。观察后续应用使用情况调整(使用xPortGetFreeHeapSize()或uxTaskGetStackHighWaterMark()监控)。Memory management scheme: 内存分配方案。推荐heap_4(碎片管理较好)。heap_1或heap_2更简单但碎片严重。
Include parameters: 按需启用API(如vTaskDelayUntil(),Task notifications等),默认通常够用。
Tasks and Queues子选项卡 (可选):可以在这里图形化创建初始任务(设置名称、优先级、栈大小、入口函数等)。对于学习,建议在代码中手动创建。Timers and Semaphores子选项卡 (可选):图形化创建软件定时器、信号量、互斥量、队列等。同样建议在代码中学习创建。
- 配置时钟 (SysTick):
- 转到
Clock Configuration选项卡。 - 确保
HCLK(AHB总线时钟) 是你期望的主频 (如72MHz)。 - 确认
SysTick的时钟源是HCLK(通常默认是)。 - 关键: 确保
SysTick中断频率等于你在FreeRTOS中设置的TICK_RATE_HZ。FreeRTOS依赖SysTick作为时基。例如,HCLK=72MHz,TICK_RATE_HZ=1000,则SysTick Reload Value应设置为72000(72,000,000 / 1000 = 72,000)。
- 转到
- 保存配置: 点击
File -> Save或工具栏的保存图标。CubeIDE会自动生成FreeRTOS配置代码和初始化代码。
三. 理解自动生成的FreeRTOS相关代码
保存.ioc后,CubeIDE会自动更新工程:
Core/Src/freertos.c: 包含MX_FREERTOS_Init()函数。如果你在CubeMX中图形化创建了任务/信号量等,它们的创建代码会在这里生成。这是FreeRTOS对象的初始化入口。Core/Inc/FreeRTOSConfig.h: 极其重要! 这是FreeRTOS的主要配置文件。它定义了:- 你在CubeMX中设置的所有参数 (
configTICK_RATE_HZ,configTOTAL_HEAP_SIZE,configMINIMAL_STACK_SIZE,configMAX_PRIORITIES,configUSE_PREEMPTION等)。 - 硬件相关设置 (如
configCPU_CLOCK_HZ,configSYSTICK_CLOCK_HZ)。 - 启用的API函数 (
configUSE_MUTEXES,configUSE_RECURSIVE_MUTEXES,configUSE_COUNTING_SEMAPHORES,configUSE_QUEUES等)。 - 内存对齐 (
configTOTAL_HEAP_SIZE)。 - 钩子函数 (
configUSE_IDLE_HOOK,configUSE_TICK_HOOK等)。 - 移植层定义:
configKERNEL_INTERRUPT_PRIORITY(通常设置为15,即最低优先级,确保FreeRTOS API调用不会屏蔽其他中断),configMAX_SYSCALL_INTERRUPT_PRIORITY(通常设置为5,高于此优先级的中断不会被FreeRTOS管理,也不能调用FreeRTOS API)。STM32F103的优先级数值越小优先级越高 (0最高, 15最低)。
- 你在CubeMX中设置的所有参数 (
Middlewares/Third_Party/FreeRTOS/Source/: 存放FreeRTOS内核源码 (tasks.c,queue.c,list.c,timers.c等) 和内存管理方案 (heap_x.c)。Middlewares/Third_Party/FreeRTOS/Source/portable/[Compiler]/ARM_CM3/: 包含针对Cortex-M3架构和特定编译器 (如GCC, Keil) 的移植层代码。最关键的文件是port.c(实现任务切换、SysTick中断处理、PendSV中断处理等) 和portmacro.h(定义数据类型、关键宏、内联汇编等)。
四. 编写应用程序代码 (创建任务)
通常在Core/Src/main.c的main()函数中或MX_FREERTOS_Init()之后创建任务。
示例:创建两个简单任务 (LED闪烁 & 串口打印)
c
/* Private includes ----------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "gpio.h" // 确保包含HAL GPIO头文件
#include "usart.h" // 如果使用串口
/* Private function prototypes -----------------------------------------------*/
void vTaskLED(void *pvParameters);
void vTaskPrint(void *pvParameters);
int main(void) {
HAL_Init();
SystemClock_Config(); // CubeIDE自动生成
MX_GPIO_Init(); // CubeIDE自动生成
MX_USART1_UART_Init(); // 如果使用串口,CubeIDE自动生成
MX_FREERTOS_Init(); // **重要!** 初始化FreeRTOS对象(任务/队列等,如果有图形化创建)和内核。这个函数会调用vTaskStartScheduler()。
/* 如果MX_FREERTOS_Init()里没有启动调度器,或者你想在它之后创建任务,需要手动调用vTaskStartScheduler() */
// vTaskStartScheduler();
/* 正常情况下,vTaskStartScheduler() 永远不会返回 */
while (1);
}
/* 任务1:闪烁LED */
void vTaskLED(void *pvParameters) {
(void)pvParameters; // 防止未使用参数警告
const TickType_t xDelay500ms = pdMS_TO_TICKS(500); // 将毫秒转换为节拍数
for (;;) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 假设LED在PC13
vTaskDelay(xDelay500ms); // 阻塞延时500ms,让出CPU
}
}
/* 任务2:串口打印 */
void vTaskPrint(void *pvParameters) {
(void)pvParameters;
const TickType_t xDelay1000ms = pdMS_TO_TICKS(1000);
char msg[] = "Hello from FreeRTOS!\r\n";
for (;;) {
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY); // 假设使用USART1
vTaskDelay(xDelay1000ms); // 阻塞延时1秒
}
}
/* 在合适的地方创建任务 (例如在MX_FREERTOS_Init()之前或在MX_FREERTOS_Init()里面) */
void MX_FREERTOS_Init(void) {
/* 创建LED任务 */
if (xTaskCreate(
vTaskLED, /* 任务函数指针 */
"LED Task", /* 任务名称 (字符串) */
128, /* 栈深度 (单位:字,4字节/字) - 根据需求调整! */
NULL, /* 传递给任务的参数 */
2, /* 任务优先级 (0是最低,configMAX_PRIORITIES-1是最高) */
NULL /* 任务句柄 (用于后续引用任务,可设为NULL) */
) != pdPASS) {
/* 任务创建失败!处理错误 (例如循环或挂起) */
Error_Handler();
}
/* 创建打印任务 */
if (xTaskCreate(
vTaskPrint,
"Print Task",
256, /* 串口任务可能需要稍大一点的栈 */
NULL,
1, /* 优先级低于LED任务 */
NULL
) != pdPASS) {
Error_Handler();
}
/* FreeRTOS内核启动 */
vTaskStartScheduler(); // **核心!启动任务调度器,永不返回(除非出错)**
}
五. 编译与下载
- 点击STM32CubeIDE工具栏上的
Build(锤子图标) 编译项目。 - 连接开发板。
- 点击
Debug(虫子图标) 或Run(播放图标) 将程序下载到目标板并启动调试/运行。
六. 调试与监控
- 调试器 (ST-Link, J-Link): 单步执行、设置断点、查看变量、寄存器、内存、调用栈。FreeRTOS感知调试器能显示任务列表、队列、信号量状态。
- 串口输出: 通过
vTaskPrint任务输出状态信息。 - LED指示: 观察
vTaskLED任务的行为。 - FreeRTOS API 监控:
uxTaskGetStackHighWaterMark(TaskHandle_t xTask): 获取任务栈的历史最小剩余空间(高水位线),强烈推荐用于检测栈是否设置过小。值接近0表示危险。xPortGetFreeHeapSize(): 获取当前剩余堆大小,监控内存使用和碎片。vTaskList(char *pcWriteBuffer): (需要启用configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS) 将任务状态信息格式化为字符串输出(任务名、状态、优先级、栈高水位线)。非常有用!
七. 关键注意事项与常见问题
-
栈大小 (
Stack Size):- 这是最常见的问题!任务栈溢出会导致难以预测的崩溃(覆盖其他内存)。
- 初始值
MINIMAL_STACK_SIZE通常不够用于实际任务。 - 使用
uxTaskGetStackHighWaterMark()在任务运行时监控栈使用。目标是在任务生命周期内保持高水位线有合理的余量(例如>100字节)。 - 复杂的函数调用、大的局部变量数组、递归都会消耗大量栈空间。
- 如果使用
printf等库函数,它们内部使用的栈可能很大。
-
堆大小 (
TOTAL_HEAP_SIZE):- STM32F103 RAM有限 (20KB for C8, 64KB for RC)。
- 分配的堆需要容纳:所有任务栈 + 任务控制块 (TCB) + 动态创建的对象 (队列、信号量、软件定时器、任务通知、动态分配内存)。
- 监控
xPortGetFreeHeapSize()。如果堆耗尽,创建对象会失败。 - 在资源紧张的F103上,尽量静态分配 (在CubeMX中图形化创建或使用
xTaskCreateStatic())。
-
中断优先级 (
configMAX_SYSCALL_INTERRUPT_PRIORITY):- 理解: FreeRTOS需要管理一些中断以进行任务切换(主要是PendSV)和提供API(如
xQueueSendFromISR)。 configMAX_SYSCALL_INTERRUPT_PRIORITY定义了一个中断优先级阈值。- 高于 此阈值的中断:不能被FreeRTOS延迟,绝对禁止 调用任何可能导致上下文切换的FreeRTOS API (如
xQueueSend,xSemaphoreGive,vTaskDelay等)。只能调用带FromISR后缀的API (如xQueueSendFromISR,xSemaphoreGiveFromISR)。 - 低于或等于 此阈值的中断:可以被FreeRTOS延迟,可以调用FreeRTOS API(包括带
FromISR和不带FromISR的,但在ISR中应始终使用FromISR版本)。
- 高于 此阈值的中断:不能被FreeRTOS延迟,绝对禁止 调用任何可能导致上下文切换的FreeRTOS API (如
- STM32F103设置: 通常设
configMAX_SYSCALL_INTERRUPT_PRIORITY = 5(数值优先级),configKERNEL_INTERRUPT_PRIORITY = 15(最低)。确保你的关键硬件中断(如电机控制、高速ADC)优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY(数值上更小,如0-4),并且这些中断的处理函数只使用FromISRAPI或不调用任何FreeRTOS API。
- 理解: FreeRTOS需要管理一些中断以进行任务切换(主要是PendSV)和提供API(如
-
SysTick配置:
- 必须保证在CubeMX的
Clock Configuration中计算的SysTick重装载值 (Reload Value) 产生的实际中断频率精确等于FreeRTOSConfig.h中的configTICK_RATE_HZ。不匹配会导致时间相关函数 (vTaskDelay,xQueueReceivewith timeout) 时间基准错误。
- 必须保证在CubeMX的
-
启动调度器 (
vTaskStartScheduler()):- 这个函数调用后,FreeRTOS接管控制权,开始调度任务。它通常不会返回。
- 如果它返回了,通常意味着:
- 没有创建任何任务 (
configMINIMAL_STACK_SIZE太小导致Idle任务创建失败?)。 - 堆空间不足,无法创建Idle任务或Timer服务任务。
- 移植层配置错误。
- 没有创建任何任务 (
-
阻塞 (
Blocking):- 任务函数中必须 包含能让出CPU的阻塞调用,如
vTaskDelay(),xQueueReceive(),xSemaphoreTake()等。否则高优先级任务会独占CPU,导致低优先级任务永远无法运行。 - Idle任务 (优先级0) 是系统自动创建的最低优先级任务,当所有其他任务阻塞时运行。
- 任务函数中必须 包含能让出CPU的阻塞调用,如
-
HAL库与FreeRTOS:
- HAL库本身不是 线程安全的。如果多个任务访问同一个外设 (如UART),必须 使用FreeRTOS同步原语 (互斥锁
xSemaphoreCreateMutex(), 队列) 进行保护,防止并发访问导致数据损坏。 - HAL延时 (
HAL_Delay()) 基于SysTick,但SysTick已被FreeRTOS接管。在任务中禁止使用HAL_Delay()!必须用vTaskDelay()替代。在中断服务程序 (ISR) 中绝对禁止使用任何阻塞延时。
- HAL库本身不是 线程安全的。如果多个任务访问同一个外设 (如UART),必须 使用FreeRTOS同步原语 (互斥锁
八. 总结关键步骤:
- 创建工程 & 配置时钟: 使用CubeIDE创建项目并正确配置系统时钟、外设时钟。
- 启用FreeRTOS: 在Middleware中选择FreeRTOS (CMSIS_V2)。
- 配置内核参数: 仔细设置
TICK_RATE_HZ,TOTAL_HEAP_SIZE,MINIMAL_STACK_SIZE,MAX_PRIORITIES, 内存方案。 - 配置中断优先级: 设置
configKERNEL_INTERRUPT_PRIORITY和configMAX_SYSCALL_INTERRUPT_PRIORITY(STM32F103常用15和5)。 - 验证SysTick: 确保SysTick中断频率匹配
TICK_RATE_HZ。 - 编写任务函数: 实现任务逻辑,包含阻塞调用。
- 创建任务: 在
main()或MX_FREERTOS_Init()中调用xTaskCreate()。 - 启动调度器: 调用
vTaskStartScheduler()。 - 编译下载调试: 监控栈和堆的使用。
- 添加同步机制: 根据需要使用队列、信号量、互斥量保护共享资源。