2. 移植FreeRTOS到STM32
任务目标
在STM32F407开发板上完成FreeRTOS v10.4.6的移植,创建两个任务(LED闪烁任务与串口打印任务),通过优先级调度验证内核能否正常切换运行。最终通过串口输出与LED状态确认移植成功。
环境与依赖
- 硬件:STM32F407VET6开发板,板载LED(PC13),USART2(PA2/PA3)
- 工具链:STM32CubeIDE 1.13+(含arm-none-eabi-gcc)
- 固件库:STM32CubeF4 v1.27(HAL库)
- FreeRTOS源码:v10.4.6(从FreeRTOS官网获取,路径
FreeRTOS/Source)
移植步骤与关键配置
- 将FreeRTOS源码中的
tasks.c、queue.c、list.c、timers.c、event_groups.c、stream_buffer.c、croutine.c加入工程。 - 添加
portable/GCC/ARM_CM4F目录下的port.c与portmacro.h,以及portable/MemMang/heap_4.c。 - 在工程头文件路径中添加FreeRTOS源码根目录、
include目录及portable/GCC/ARM_CM4F目录。 - 创建
FreeRTOSConfig.h,配置时钟频率、堆大小、任务数量等参数(见下文代码)。 - 在
main.c中实现两个任务函数并启动调度器。
完整实现代码
// FreeRTOSConfig.h —— 放在工程根目录
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include "stm32f4xx_hal.h"
#define configUSE_PREEMPTION 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ (1000)
#define configMAX_PRIORITIES (5)
#define configMINIMAL_STACK_SIZE (128)
#define configTOTAL_HEAP_SIZE (16 * 1024)
#define configMAX_TASK_NAME_LEN (16)
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_QUEUE_SETS 0
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY 2
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 256
#define configASSERT(x) if((x)==0) {taskDISABLE_INTERRUPTS(); for(;;);}
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskDelayUntil 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
extern void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName);
extern void vApplicationMallocFailedHook(void);
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_MALLOC_FAILED_HOOK 1
#endif
// main.c —— 核心移植与任务代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "FreeRTOS.h"
#include "task.h"
void vLEDTask(void *argument) {
for(;;) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vPrintTask(void *argument) {
uint32_t count = 0;
char msg[64];
for(;;) {
count++;
sprintf(msg, "PrintTask running, count=%lu\r\n", count);
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
xTaskCreate(vLEDTask, "LED", 128, NULL, 1, NULL);
xTaskCreate(vPrintTask, "PRINT", 256, NULL, 2, NULL);
vTaskStartScheduler();
for(;;);
}
运行与验证
- 编译下载后,打开串口助手(115200-8-N-1),复位开发板。
- 通过条件:串口每秒输出一条
PrintTask running, count=N(N递增),同时板载LED以1Hz频率闪烁(亮500ms灭500ms)。 - 若串口无输出但LED闪烁,检查USART2引脚配置或波特率;若两者均无反应,检查
configTOTAL_HEAP_SIZE是否足够(当前16KB可满足两个任务)。 - 若出现HardFault,优先检查
configASSERT是否触发——在调试器中暂停查看PC指针位置,通常为堆栈溢出或中断优先级配置错误。
常见问题
- 编译报错找不到portmacro.h:确认头文件路径已添加
portable/GCC/ARM_CM4F目录。 - 任务不切换:确认
configUSE_PREEMPTION为1,且SysTick中断已由vPortSetupTimerInterrupt()正确配置(port.c自动完成)。 - 串口乱码:检查系统时钟频率是否与
configCPU_CLOCK_HZ一致,若使用外部晶振需确保SystemCoreClock正确。