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)

移植步骤与关键配置

  1. 将FreeRTOS源码中的tasks.c、queue.c、list.c、timers.c、event_groups.c、stream_buffer.c、croutine.c加入工程。
  2. 添加portable/GCC/ARM_CM4F目录下的port.c与portmacro.h,以及portable/MemMang/heap_4.c。
  3. 在工程头文件路径中添加FreeRTOS源码根目录、include目录及portable/GCC/ARM_CM4F目录。
  4. 创建FreeRTOSConfig.h,配置时钟频率、堆大小、任务数量等参数(见下文代码)。
  5. 在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(;;);
}

运行与验证

  1. 编译下载后,打开串口助手(115200-8-N-1),复位开发板。
  2. 通过条件:串口每秒输出一条PrintTask running, count=N(N递增),同时板载LED以1Hz频率闪烁(亮500ms灭500ms)。
  3. 若串口无输出但LED闪烁,检查USART2引脚配置或波特率;若两者均无反应,检查configTOTAL_HEAP_SIZE是否足够(当前16KB可满足两个任务)。
  4. 若出现HardFault,优先检查configASSERT是否触发——在调试器中暂停查看PC指针位置,通常为堆栈溢出或中断优先级配置错误。

常见问题

  • 编译报错找不到portmacro.h:确认头文件路径已添加portable/GCC/ARM_CM4F目录。
  • 任务不切换:确认configUSE_PREEMPTION为1,且SysTick中断已由vPortSetupTimerInterrupt()正确配置(port.c自动完成)。
  • 串口乱码:检查系统时钟频率是否与configCPU_CLOCK_HZ一致,若使用外部晶振需确保SystemCoreClock正确。