STM32F407手动移植FreeRTOS详细教程

STM32F407手动移植FreeRTOS详细教程
FreeRTOS 移植总结目标板: STM32F407VETx (Cortex-M4F, 512KB Flash, 192KB RAM)编译器: ARM Compiler V5.06 update 7 (build 960)IDE: Keil MDK-ARM V5 (µVision)FreeRTOS 内核版本: V11.1.0HAL 库: STM32F4xx_HAL_Driver移植日期: 2026-07-18—案例包下载网址(chrome/夸克打开):http://103.236.97.252:48791一、下载操作系统代码库从 FreeRTOS 官方网站下载内核源码下载地址: https://freertos.org/zh-cn点击 “Download” → 选择FreeRTOS 202406.05 LTS。解压后得到源码目录关键路径如下FreeRTOS LTS └── FreeRTOS/ └── FreeRTOS-Kernel/ ├── include/ ← 所有头文件共 21 个 .h 1 个 .readme ├── *.c ← 内核源文件6 个 .c └── portable/ ├── RVDS/ARM_CM4F/ ← Cortex-M4F 移植层ARMCC 汇编语法版 │ ├── port.c │ └── portmacro.h ├ └── MemMang/ ← 内存管理方案 ├── heap_1.c ├── heap_2.c ├── heap_3.c ├── heap_4.c ← 目前选择heap_4.c方案后续有说明 └── heap_5.c二、工程中新建文件夹在裸机工程根目录下创建以下三级目录结构Freertos_uart_test/ └── Middlewares/ └── FreeRTOS/ ├── include/ ← 存放 FreeRTOS 头文件 ├── src/ ← 存放 FreeRTOS 内核源文件 └── portable/ ├── RVDS/ │ └── ARM_CM4F/ ← 存放 Cortex-M4F 移植层文件 └── MemMang/ ← 存放内存管理文件实际操作Windows 资源管理器或命令行Middlewares\FreeRTOS\include Middlewares\FreeRTOS\src Middlewares\FreeRTOS\portable\RVDS\ARM_CM4F Middlewares\FreeRTOS\portable\MemMang三、拷贝移植文件3.1 头文件 — 拷贝到Middlewares/FreeRTOS/include/从FreeRTOS/FreeRTOS-Kernel/include/拷贝全部 21 个文件序号文件名说明1FreeRTOS.h主头文件 — TickType 定义、配置校验、API 重定向2task.h任务管理 API —xTaskCreate、vTaskDelay、vTaskDelete等3queue.h队列 API —xQueueSend、xQueueReceive等4semphr.h信号量 API — 二值信号量、计数信号量、互斥锁5timers.h软件定时器 API6event_groups.h事件组 API7stream_buffer.h流缓冲区 API8message_buffer.h消息缓冲区 API9list.h链表数据结构内核内部使用10croutine.h协程 APIco-routine11portable.h移植层抽象接口12projdefs.h内核宏定义 —pdTRUE、pdFALSE、pdPASS、pdFAIL13StackMacros.h栈检查宏14stack_macros.h栈检查宏 — 小写版MISRA 合规15atomic.h原子操作16deprecated_definitions.h废弃 API 兼容宏17mpu_prototypes.hMPU 内存保护函数原型18mpu_wrappers.hMPU 包装宏19mpu_syscall_numbers.hMPU 系统调用号20newlib-freertos.hNewlib C 库集成21picolibc-freertos.hPicoLibc C 库集成另有stdint.readme一个说明文件不需要拷贝到 Keil 工程中。3.2 内核源码 — 拷贝到Middlewares/FreeRTOS/src/从FreeRTOS/FreeRTOS-Kernel/拷贝6 个 .c 文件序号文件名说明1tasks.c任务管理核心代码 — 调度器、任务创建/删除、延时、栈溢出检测、Idle 任务2queue.c队列核心代码 — 队列、信号量、互斥锁3list.c链表操作 — 就绪链表、延时链表等内核数据结构4timers.c软件定时器 — 定时器服务任务Daemon Task5event_groups.c事件组 — 多事件同步机制6stream_buffer.c流缓冲区和消息缓冲区3.3 移植层 — 拷贝到Middlewares/FreeRTOS/portable/RVDS/ARM_CM4F/从FreeRTOS/FreeRTOS-Kernel/portable/RVDS/ARM_CM4F/拷贝2 个文件序号文件名说明1port.cCortex-M4F 移植代码 — 使用 ARMCC (RVDS) 汇编语法__asm{}块包含任务栈初始化、SVC 启动第一个任务、PendSV 上下文切换、SysTick 节拍中断、临界区管理、tickless 低功耗模式2portmacro.h移植层宏定义 — 数据类型TickType_t等、临界区portENTER_CRITICAL/portEXIT_CRITICAL、BASEPRI 操作、任务切换portYIELD、中断内执行判定xPortIsInsideInterrupt3.4 内存管理 — 拷贝到Middlewares/FreeRTOS/portable/MemMang/从FreeRTOS/FreeRTOS-Kernel/portable/MemMang/中选择并拷贝1 个文件序号文件名说明1heap_4.cheap_4 动态内存分配 — 采用首次适配算法支持相邻空闲块合并碎片整理提供pvPortMalloc()和vPortFree()。这是最推荐的单核应用方案备选方案对比未选用heap_1.c— 只分配不释放适合简单应用heap_2.c— 可释放但不合并碎片长期运行会碎片化heap_3.c— 包装标准库malloc/free依赖线程安全实现heap_5.c— 在 heap_4 基础上支持跨多块非连续内存分配四、修改工程现有文件4.1 Keil 工程文件 —MDK-ARM/Freertos_uart_test.uvprojx(1) 添加头文件搜索路径操作: Options for Target → C/C → Include Paths → 新增 3 行..\Middlewares\FreeRTOS\include ..\Middlewares\FreeRTOS\portable\RVDS\ARM_CM4F ..\Middlewares\FreeRTOS\portable\MemMang原有路径保持不变../Core/Inc ../Drivers/STM32F4xx_HAL_Driver/Inc ../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy ../Drivers/CMSIS/Device/ST/STM32F4xx/Include ../Drivers/CMSIS/Include(2) 开启硬件浮点支持操作: Options for Target → Target → Floating Point Hardware → 选择“Use FPU”原因STM32F407 集成硬件 FPUFreeRTOS Cortex-M4F port 在上下文切换时需要保存/恢复 FPU 寄存器S16–S31port.c中#ifndef __TARGET_FPU_VFP→#error强制要求开启。(3) 在工程中新增三个 File Group在 Keil 左侧 Project 窗口中右键 → “Add Group”创建以下分组并添加对应文件Group 名称添加的文件路径相对于工程根目录FreeRTOS/includeMiddlewares\FreeRTOS\include\下全部 21 个.h文件FreeRTOS/srcMiddlewares\FreeRTOS\src\tasks.c、queue.c、list.c、timers.c、event_groups.c、stream_buffer.cFreeRTOS/portableMiddlewares\FreeRTOS\portable\RVDS\ARM_CM4F\port.c、portmacro.h、Middlewares\FreeRTOS\portable\MemMang\heap_4.c4.2Core/Inc/FreeRTOSConfig.h— 内核配置文件此文件来自官方模板文件源路径为D:\freertos\FreeRTOSv202406.05-LTS\FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\examples\template_configuration\FreeRTOSConfig.h以该模板为起点放置于Core/Inc/目录共需修改5 处。改动 ①添加中断向量映射宏第 4–6 行在#define FREERTOS_CONFIG_H的下一行添加#definevPortSVCHandlerSVC_Handler#definexPortPendSVHandlerPendSV_Handler#definexPortSysTickHandlerSysTick_Handler为什么必须加: startup 启动文件startup_stm32f407xx.s的中断向量表硬编码的符号名是SVC_Handler、PendSV_Handler、SysTick_Handler。FreeRTOSport.c中的实现在函数命名为vPortSVCHandler、xPortPendSVHandler、xPortSysTickHandler。这三个宏将两组符号链接到一起。如果不加CPU 产生 SVC/PendSV/SysTick 异常时会跳转到启动文件中的 weak 默认实现B .原地死循环调度器永远无法启动烧录后串口无任何输出。改动 ②修正 tick 类型位宽第 100 行修改前修改后#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_64_BITS#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITSSTM32F407 是 32 位架构portmacro.h只支持 16 位和 32 位 tick。改动 ③修正中断优先级第 276、284、288 行行号宏修改前修改后说明276configKERNEL_INTERRUPT_PRIORITY00xF0SysTick/PendSV 设为最低优先级15 4284configMAX_SYSCALL_INTERRUPT_PRIORITY00x50BASEPRI 阈值5 4优先级 0~4 的中断不被屏蔽288configMAX_API_CALL_INTERRUPT_PRIORITY00x50同上Cortex-M4 NVIC 使用 bits [7:4] 表示优先级所以宏值 逻辑优先级 4。0xF0 15 40x50 5 4。如果configMAX_SYSCALL_INTERRUPT_PRIORITY 0port.c会在编译时报错#error因为 BASEPRI0 无法屏蔽任何中断。改动 ④增大内核堆空间第 248 行修改前修改后#define configTOTAL_HEAP_SIZE 4096#define configTOTAL_HEAP_SIZE 16*1024每个任务的 TCB stack 从 FreeRTOS 堆中动态分配。两条 512-word 栈的任务2048 bytes × 2 Idle 任务≈600 bytes Timer 任务≈600 bytes 队列控制块≈100 bytes≈ 7KB原 4KB 不够会导致xTaskCreate()静默失败。4.3Core/Src/stm32f4xx_it.c— 中断服务函数操作: 注释掉 3 个空的 ISR 函数行号函数操作146–154void SVC_Handler(void)整段注释掉172–180void PendSV_Handler(void)整段注释掉185–194void SysTick_Handler(void)整段注释掉例如SVC_Handler注释后的样子//void SVC_Handler(void)//{// /* USER CODE BEGIN SVCall_IRQn 0 */// /* USER CODE END SVCall_IRQn 0 */// /* USER CODE BEGIN SVCall_IRQn 1 */// /* USER CODE END SVCall_IRQn 1 *///}原因: 这三个空壳函数是 CubeMX 裸机工程生成的裸机下 SysTick 供 HAL 使用。移植 FreeRTOS 后真正的 ISR 实现在port.c中通过FreeRTOSConfig.h的宏映射连接。如果不注释掉链接器会报symbol multiply defined重复定义错误。保留不动的函数:函数功能NMI_Handler()不可屏蔽中断异常HardFault_Handler()硬件错误异常MemManage_Handler()内存管理异常BusFault_Handler()总线错误异常UsageFault_Handler()指令/状态异常DebugMon_Handler()调试监控异常USART1_IRQHandler()USART1 中断 → 调用HAL_UART_IRQHandler(huart1)TIM6_DAC_IRQHandler()TIM6 中断 → 调用HAL_TIM_IRQHandler(htim6)为 HAL 提供 1ms 时基4.4 无需修改的文件文件说明MDK-ARM/startup_stm32f407xx.s启动文件SVC/PendSV/SysTick 的 weak 默认实现B .死循环被宏映射覆盖无需改动Core/Src/stm32f4xx_hal_timebase_tim.cHAL 时基文件TIM6 驱动HAL_Delay()与 FreeRTOS 的 SysTick 互不影响Core/Src/system_stm32f4xx.c系统初始化无需改动所有Drivers/STM32F4xx_HAL_Driver/Src/文件全部 HAL 库源码无需改动五、编写调试代码5.1Core/Src/main.c在原有 main.c 基础上做了以下改动(1) 新增头文件引用#includeFreeRTOS.h// FreeRTOS 主头文件必须#includetask.h// 任务 API — xTaskCreate, vTaskDelay 等必须#includequeue.h// 队列 API — xQueueCreate, xQueueSend 等使用队列时加#includestdlib.h// rand() — demo 用非必须(2) 新增全局变量TaskHandle_t Printf_Task_Handle;// 任务句柄QueueHandle_t xMsgQueue;// 队列句柄(3) 新增任务函数放在main()之前/* 定时打印任务 */voidprintf1_Task(void*pvParameters){for(;;){printf(hello_freertos_test\r\n);vTaskDelay(pdMS_TO_TICKS(2000));}}/* 发送任务模拟三路传感器定时逐条发送到队列 */voidvSender_Task(void*pvParameters){int32_tlSensorData[3];for(;;){lSensorData[0]rand()%100;// 温度lSensorData[1]rand()%100100;// 湿度lSensorData[2]rand()%100200;// 压力for(inti0;i3;i){if(xQueueSend(xMsgQueue,lSensorData[i],pdMS_TO_TICKS(100))!pdPASS){printf([发送] 队列满第%d条丢弃\r\n,i);}else{printf([发送] 传感器%d %d\r\n,i,(int)lSensorData[i]);}}vTaskDelay(pdMS_TO_TICKS(1000));}}/* 接收任务从队列读取数据并打印 */voidvReceiver_Task(void*pvParameters){int32_tlReceivedValue;for(;;){if(xQueueReceive(xMsgQueue,lReceivedValue,portMAX_DELAY)pdPASS){printf([接收] 从队列收到: %d\r\n,(int)lReceivedValue);}}}(4) 重写main()函数intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();/* 创建消息队列 */xMsgQueuexQueueCreate(10,sizeof(int32_t));/* 创建任务 */if(xMsgQueue!NULL){xTaskCreate(vSender_Task,Sender,512,NULL,1,NULL);xTaskCreate(vReceiver_Task,Receiver,512,NULL,2,NULL);}xTaskCreate(printf1_Task,printf1_task,256,NULL,1,Printf_Task_Handle);/* 启动调度器 */vTaskStartScheduler();/* 调度器启动失败才会走到这里 */while(1);}(5) 新增栈溢出钩子函数放在main()之后voidvApplicationStackOverflowHook(TaskHandle_t xTask,char*pcTaskName){for(;;);// 死循环调试时在此打断点定位}原因:FreeRTOSConfig.h中configCHECK_FOR_STACK_OVERFLOW 2开启了栈溢出检测链接时必须提供此函数否则报L6218E: Undefined symbol。(6) 保持不变的函数函数功能SystemClock_Config()HSI → PLL → 72MHz 时钟配置fputc()printf 重定向到 USART1HAL_TIM_PeriodElapsedCallback()TIM6 1ms 时基回调Error_Handler()错误处理5.2 验证结果编译通过并烧录后串口输出如下[14:14:17.365] [发送] 传感器0 41 [14:14:17.366] [发送] 传感器1 184 [14:14:17.367] [发送] 传感器2 267 [14:14:17.368] [接收] 从队列收到: 41 [14:14:17.369] [接收] 从队列收到: 184 [14:14:17.370] [接收] 从队列收到: 267 [14:14:19.000] hello_freertos_test ...发送任务和接收任务交替运行证明 FreeRTOS 多任务调度正常工作。六、调试过程中遇到的错误序号报错/现象根本原因解决方法1error: #18: expected a )— 60 个错误移植层文件使用了 GCC 内联汇编语法__asm volatileroperandsARMCC 不支持将port.c和portmacro.h替换为 ARMCC 汇编语法版__asm{}块2#error: configTICK_TYPE_WIDTH_IN_BITS set to unsupported tick type width— 8 个错误配置为 64 位 tickportmacro.h 只支持 16/32 位FreeRTOSConfig.h改为TICK_TYPE_WIDTH_32_BITS3#error: configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0— 1 个错误BASEPRI0 无法屏蔽任何中断FreeRTOSConfig.h改为0x504L6218E: Undefined symbol vApplicationStackOverflowHook— 1 个错误开启栈检测但未提供钩子main.c中添加空实现5error: #20: identifier TaskHandle_t is undefined— 1 个错误未#include task.hmain.c添加头文件6 编译成功烧录后串口无打印FreeRTOSConfig.h缺少#define vPortSVCHandler SVC_Handler等三行宏映射CPU 的svc 0指令跳转到B .死循环添加三个宏映射 注释stm32f4xx_it.c中空 ISR7 Sender 运行但 Receiver 不运行队列持续报满configTOTAL_HEAP_SIZE 4096太小Receiver 创建时内存不足改为16 * 1024