1. 准备工作:搭建开发环境
第一次接触STM32和FreeRTOS时,我完全是个小白。记得当时连开发环境都配置不好,折腾了好几天。现在回想起来,其实只要按照正确的步骤来,半小时就能搞定。下面我就把最实用的经验分享给大家。
首先需要准备硬件设备。推荐使用STM32F103C8T6开发板,也就是我们常说的"蓝板",价格便宜而且资源丰富。我当初在某宝花了不到20块钱就买到了,还附带下载器。除了开发板,你还需要:
- 一台Windows电脑(Win7/Win10/Win11都可以)
- 一根Micro USB数据线
- Keil MDK-ARM开发环境(建议安装5.25以上版本)
- ST-Link下载器驱动
软件方面,Keil的安装有几个坑需要注意。我建议直接去官网下载最新版,安装时记得勾选"ARM Compiler"和"Device Family Pack"。安装完成后,一定要注册,否则代码大小限制在32KB,而STM32F103C8T6的Flash是64KB,不注册的话连简单的FreeRTOS程序都编译不了。
2. 获取FreeRTOS源码
FreeRTOS的官网是www.freertos.org,但说实话我第一次上去完全找不到北。后来发现直接在GitHub下载更方便:
- 打开GitHub搜索FreeRTOS
- 找到FreeRTOS/FreeRTOS-Kernel仓库
- 点击"Code"→"Download ZIP"
下载完成后解压,你会看到一堆文件夹。真正需要的核心文件在FreeRTOS/Source目录下。这里有个小技巧:建议把整个Source文件夹复制到你的工程目录下,而不是只复制部分文件。这样以后查找头文件路径会方便很多。
我遇到过最坑的问题是版本兼容性。有一次用了最新版的FreeRTOS,结果和STM32标准库冲突,调试了一整天。后来发现用V10.4.1版本最稳定,建议大家先用这个版本练手。
3. 创建Keil工程
打开Keil,点击Project→New μVision Project,选择一个空文件夹存放工程。设备选择STMicroelectronics→STM32F103C8。这里要注意:一定要选对型号,我曾经选错成STM32F103RC,结果下载后完全不能运行。
创建工程后,需要添加必要的库文件:
- 在工程目录下新建Libraries文件夹
- 复制STM32标准外设库(可以从ST官网下载)
- 添加启动文件startup_stm32f10x_md.s(中等容量设备用md)
然后配置工程选项:
- Target选项卡:勾选"Use MicroLIB"(这个对FreeRTOS很重要)
- C/C++选项卡:在Define中添加"STM32F10X_MD,USE_STDPERIPH_DRIVER"
- Debug选项卡:选择你的调试器(ST-Link/J-Link等)
4. 移植FreeRTOS核心文件
现在开始真正的移植工作。首先在工程目录下新建FreeRTOS文件夹,然后把下载的FreeRTOS源码中以下文件复制过来:
- FreeRTOS/Source下的所有.c文件(除了croutine.c,这个一般用不到)
- FreeRTOS/Source/include下的所有头文件
- FreeRTOS/Source/portable/RVDS/ARM_CM3/port.c
- FreeRTOS/Source/portable/MemMang/heap_4.c(内存管理方案)
在Keil中新建两个组:
- FreeRTOS_CORE:添加除了heap_x.c和port.c之外的所有.c文件
- FreeRTOS_PORT:添加port.c和heap_4.c
这里有个重要细节:heap_4.c是内存管理方案,FreeRTOS提供了5种方案(heap_1到heap_5)。我强烈推荐用heap_4,因为它支持内存碎片整理,适合长期运行的系统。
5. 配置FreeRTOS
从FreeRTOS/Demo/CORTEX_STM32F103_Keil目录下复制FreeRTOSConfig.h到你的工程include目录。这个文件是FreeRTOS的配置文件,相当于操作系统的心脏。
打开FreeRTOSConfig.h,有几个关键参数需要修改:
#define configCPU_CLOCK_HZ (SystemCoreClock) // CPU主频 #define configTICK_RATE_HZ (1000) // 系统时钟频率 #define configTOTAL_HEAP_SIZE ((size_t)(10*1024)) // 堆大小 #define configMINIMAL_STACK_SIZE ((unsigned short)128) // 最小任务栈特别注意:STM32F103C8T6只有20KB RAM,所以堆大小不要超过15KB,否则其他变量就没空间了。我曾经设成18KB,结果程序随机崩溃,调试到怀疑人生。
6. 解决中断冲突
这是移植过程中最容易出错的地方。FreeRTOS需要接管三个系统中断:
- SVC_Handler(系统调用)
- PendSV_Handler(上下文切换)
- SysTick_Handler(系统时钟)
在FreeRTOSConfig.h末尾添加:
#define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler #define xPortSysTickHandler SysTick_Handler然后打开stm32f10x_it.c,找到这三个中断处理函数,把它们注释掉。如果不做这一步,链接时会报"multiply defined"错误。
7. 编写测试程序
现在可以写个简单的多任务程序测试了。在main.c中添加:
#include "FreeRTOS.h" #include "task.h" #include "stm32f10x_gpio.h" void LED1_Task(void *pvParameters) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStruct); while(1) { GPIO_WriteBit(GPIOC, GPIO_Pin_13, !GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)); vTaskDelay(500); // 500ms延时 } } void LED2_Task(void *pvParameters) { // 类似LED1_Task,初始化另一个LED while(1) { // 不同的闪烁频率 vTaskDelay(250); } } int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); xTaskCreate(LED1_Task, "LED1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); xTaskCreate(LED2_Task, "LED2", configMINIMAL_STACK_SIZE, NULL, 1, NULL); vTaskStartScheduler(); while(1) {} }8. 常见问题解决
在实际移植中,我遇到过各种奇怪的问题,这里分享几个典型的:
HardFault错误:这通常是因为堆栈设置太小。解决方法是在FreeRTOSConfig.h中增加configMINIMAL_STACK_SIZE,我一般设为128或256。
程序卡在vTaskStartScheduler:检查系统时钟配置是否正确,特别是SystemCoreClock的值。可以用示波器测量一下SysTick中断是否正常。
内存不足:修改configTOTAL_HEAP_SIZE,但要注意STM32F103C8的总RAM只有20KB。可以通过map文件查看内存使用情况。
任务无法切换:确保PendSV的优先级是最低的(在STM32中数值越大优先级越低)。FreeRTOS要求PendSV的优先级必须低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY。
9. 进阶配置
当基本移植完成后,你可能需要根据实际需求调整配置:
钩子函数:FreeRTOS提供了一些可选钩子函数,比如vApplicationIdleHook,可以在这里实现低功耗处理。
软件定时器:在FreeRTOSConfig.h中设置configUSE_TIMERS为1,可以启用软件定时器功能。
任务通知:这是比队列和信号量更高效的通信机制,建议新项目优先使用。
栈溢出检测:设置configCHECK_FOR_STACK_OVERFLOW为2,可以检测任务栈溢出,这对调试非常有用。
10. 性能优化建议
经过多次项目实践,我总结出几个优化技巧:
合理设置任务优先级:优先级差异过大会导致低优先级任务"饿死"。我一般用3-5个优先级级别就够了。
使用静态内存分配:对于确定性的系统,可以在编译时就分配任务栈和TCB,避免运行时内存碎片。
优化系统时钟频率:默认1kHz的tick频率对大多数应用足够了,但对低功耗设备可以降到100Hz。
合理使用互斥量:持有互斥量的时间要尽可能短,避免优先级反转问题。