欢迎光临
我们一直在努力

RTOS移植实战:FreeRTOS在STM32F103平台的移植步骤详解

文章目录

    • 一、移植前准备
      • 1.1 硬件与软件环境
      • 1.2 核心文件获取
    • 二、移植整体流程
    • 三、详细移植步骤
      • 3.1 步骤1:搭建STM32F103基础工程
      • 3.2 步骤2:复制FreeRTOS源码到工程目录
      • 3.3 步骤3:添加源码文件到Keil工程
      • 3.4 步骤4:配置头文件包含路径
      • 3.5 步骤5:创建并配置FreeRTOSConfig.h
      • 3.6 步骤6:修改STM32中断服务函数
      • 3.7 步骤7:适配系统时钟与任务调度
      • 3.8 步骤8:编写测试任务验证移植效果
      • 3.9 步骤9:编译与下载验证
    • 四、常见问题与解决方法
      • 4.1 编译错误:undefined reference to `vPortSVCHandler`
      • 4.2 任务调度器启动失败
      • 4.3 LED不闪烁/串口无输出
    • 五、移植优化建议
      • 总结

一、移植前准备

1.1 硬件与软件环境

  • 硬件平台:STM32F103C8T6最小系统板(核心板)
  • 软件工具:
    • MDK-ARM (Keil uVision5) V5.38及以上版本
    • STM32CubeMX V6.9.0及以上版本
    • FreeRTOS官方源码(V10.4.3,适配Cortex-M3内核)
    • STM32F103标准库(V3.5.0)或HAL库(V1.8.5)
  • 依赖文件:STM32F103芯片对应的启动文件(startup_stm32f103xb.s)

1.2 核心文件获取

FreeRTOS源码可从官方网站下载(https://www.freertos.org/),解压后核心目录结构如下:

FreeRTOSv10.4.3/
├── FreeRTOS/
│ ├── Source/ # FreeRTOS核心源码
│ │ ├── croutine.c
│ │ ├── event_groups.c
│ │ ├── list.c
│ │ ├── queue.c
│ │ ├── stream_buffer.c
│ │ ├── tasks.c
│ │ ├── timers.c
│ │ ├── portable/ # 移植相关文件
│ │ │ ├── GCC/ # GCC编译器适配
│ │ │ ├── Keil/ # Keil编译器适配
│ │ │ ├── MemMang/ # 内存管理方案
│ │ └── include/ # 核心头文件
│ └── Demo/ # 官方示例(可参考)

二、移植整体流程

以下是FreeRTOS移植到STM32F103的完整流程,通过流程图清晰展示各步骤的依赖关系:

#mermaid-svg-1X3MS1I6wEzIcAU7{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1X3MS1I6wEzIcAU7 .error-icon{fill:#552222;}#mermaid-svg-1X3MS1I6wEzIcAU7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1X3MS1I6wEzIcAU7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .marker.cross{stroke:#333333;}#mermaid-svg-1X3MS1I6wEzIcAU7 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1X3MS1I6wEzIcAU7 p{margin:0;}#mermaid-svg-1X3MS1I6wEzIcAU7 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .cluster-label text{fill:#333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .cluster-label span{color:#333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .cluster-label span p{background-color:transparent;}#mermaid-svg-1X3MS1I6wEzIcAU7 .label text,#mermaid-svg-1X3MS1I6wEzIcAU7 span{fill:#333;color:#333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .node rect,#mermaid-svg-1X3MS1I6wEzIcAU7 .node circle,#mermaid-svg-1X3MS1I6wEzIcAU7 .node ellipse,#mermaid-svg-1X3MS1I6wEzIcAU7 .node polygon,#mermaid-svg-1X3MS1I6wEzIcAU7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1X3MS1I6wEzIcAU7 .rough-node .label text,#mermaid-svg-1X3MS1I6wEzIcAU7 .node .label text,#mermaid-svg-1X3MS1I6wEzIcAU7 .image-shape .label,#mermaid-svg-1X3MS1I6wEzIcAU7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-1X3MS1I6wEzIcAU7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1X3MS1I6wEzIcAU7 .rough-node .label,#mermaid-svg-1X3MS1I6wEzIcAU7 .node .label,#mermaid-svg-1X3MS1I6wEzIcAU7 .image-shape .label,#mermaid-svg-1X3MS1I6wEzIcAU7 .icon-shape .label{text-align:center;}#mermaid-svg-1X3MS1I6wEzIcAU7 .node.clickable{cursor:pointer;}#mermaid-svg-1X3MS1I6wEzIcAU7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .arrowheadPath{fill:#333333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1X3MS1I6wEzIcAU7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1X3MS1I6wEzIcAU7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1X3MS1I6wEzIcAU7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1X3MS1I6wEzIcAU7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1X3MS1I6wEzIcAU7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1X3MS1I6wEzIcAU7 .cluster text{fill:#333;}#mermaid-svg-1X3MS1I6wEzIcAU7 .cluster span{color:#333;}#mermaid-svg-1X3MS1I6wEzIcAU7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1X3MS1I6wEzIcAU7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1X3MS1I6wEzIcAU7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-1X3MS1I6wEzIcAU7 .icon-shape,#mermaid-svg-1X3MS1I6wEzIcAU7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1X3MS1I6wEzIcAU7 .icon-shape p,#mermaid-svg-1X3MS1I6wEzIcAU7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1X3MS1I6wEzIcAU7 .icon-shape rect,#mermaid-svg-1X3MS1I6wEzIcAU7 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1X3MS1I6wEzIcAU7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1X3MS1I6wEzIcAU7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1X3MS1I6wEzIcAU7 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

搭建STM32基础工程

获取FreeRTOS核心源码

复制FreeRTOS源码到工程目录

添加源码文件到Keil工程

配置头文件包含路径

修改FreeRTOS配置文件FreeRTOSConfig.h

修改STM32中断服务函数

适配系统时钟与任务调度

编写测试任务验证移植效果

编译下载验证功能

三、详细移植步骤

3.1 步骤1:搭建STM32F103基础工程

首先需要一个可正常编译、下载的STM32F103基础工程(以HAL库为例),包含以下核心文件:

  • 启动文件:startup_stm32f103xb.s
  • HAL库核心文件:stm32f1xx_hal.c、stm32f1xx_hal_gpio.c等
  • 系统时钟配置:stm32f1xx_hal_rcc.c
  • 中断服务函数:stm32f1xx_it.c
  • 主函数文件:main.c
  • 工程配置文件:stm32f1xx_hal_conf.h

基础工程需实现最基本的时钟配置(72MHz)、GPIO初始化,确保无编译错误,可正常下载到开发板。

3.2 步骤2:复制FreeRTOS源码到工程目录

在基础工程根目录下创建FreeRTOS文件夹,按以下目录结构复制源码:

STM32F103_FreeRTOS/
├── Core/
│ ├── Inc/
│ │ ├── FreeRTOS/ # 存放FreeRTOS头文件
│ │ └── …
│ ├── Src/
│ │ ├── FreeRTOS/ # 存放FreeRTOS源码文件
│ │ │ ├── portable/ # 仅保留Keil和MemMang子目录
│ │ │ ├── croutine.c
│ │ │ ├── event_groups.c
│ │ │ ├── list.c
│ │ │ ├── queue.c
│ │ │ ├── stream_buffer.c
│ │ │ ├── tasks.c
│ │ │ └── timers.c
│ │ └── …
└── FreeRTOSConfig.h # FreeRTOS配置文件(手动创建)

复制规则:

  • 从FreeRTOS源码Source/include目录复制所有.h文件到工程Core/Inc/FreeRTOS
  • 从FreeRTOS源码Source目录复制所有.c文件到工程Core/Src/FreeRTOS
  • 从FreeRTOS源码Source/portable/Keil/ARM_CM3复制port.c和portmacro.h到工程Core/Src/FreeRTOS/portable
  • 从FreeRTOS源码Source/portable/MemMang选择heap_4.c(推荐)复制到工程Core/Src/FreeRTOS/portable/MemMang

3.3 步骤3:添加源码文件到Keil工程

在Keil中创建以下分组,并添加对应文件:

分组名称需添加的文件
FreeRTOS_Core croutine.c、event_groups.c、list.c、queue.c、stream_buffer.c、tasks.c、timers.c
FreeRTOS_Portable port.c(来自ARM_CM3)
FreeRTOS_MemMang heap_4.c(来自MemMang)

操作方式:

  • 右键工程名 → Manage Project Items → 新建分组
  • 选中分组 → Add Files → 选择对应目录下的.c文件
  • 3.4 步骤4:配置头文件包含路径

    在Keil中配置头文件路径,确保编译器能找到FreeRTOS的头文件:

  • 点击魔法棒图标 → C/C++ → Include Paths
  • 添加以下路径:
    • ./Core/Inc/FreeRTOS
    • ./Core/Src/FreeRTOS/portable/Keil/ARM_CM3
    • ./Core/Src/FreeRTOS/portable/MemMang
  • 3.5 步骤5:创建并配置FreeRTOSConfig.h

    文件名:FreeRTOSConfig.h 存放路径:Core/Inc/FreeRTOS

    该文件是FreeRTOS的核心配置文件,以下是适配STM32F103的完整配置代码:

    #ifndef FREERTOS_CONFIG_H
    #define FREERTOS_CONFIG_H

    /* ————————– 内核配置 ————————– */
    #define configUSE_PREEMPTION 1U // 启用抢占式调度
    #define configUSE_PORT_OPTIMISED_TASK_SELECTION 0U // 禁用端口优化的任务选择(通用CM3)
    #define configUSE_TICKLESS_IDLE 0U // 禁用低功耗空闲模式
    #define configCPU_CLOCK_HZ (72000000U) // CPU主频72MHz
    #define configTICK_RATE_HZ (1000U) // 系统滴答频率1kHz
    #define configMAX_PRIORITIES (5U) // 最大任务优先级
    #define configMINIMAL_STACK_SIZE (128U) // 最小任务栈大小
    #define configTOTAL_HEAP_SIZE (1024U * 5) // 堆大小5KB
    #define configMAX_TASK_NAME_LEN (16U) // 任务名最大长度
    #define configUSE_16_BIT_TICKS 0U // 使用32位tick计数
    #define configIDLE_SHOULD_YIELD 1U // 空闲任务让出CPU
    #define configUSE_TASK_NOTIFICATIONS 1U // 启用任务通知
    #define configUSE_MUTEXES 1U // 启用互斥锁
    #define configUSE_RECURSIVE_MUTEXES 1U // 启用递归互斥锁
    #define configUSE_COUNTING_SEMAPHORES 1U // 启用计数信号量
    #define configQUEUE_REGISTRY_SIZE 8U // 队列注册表大小
    #define configUSE_QUEUE_SETS 0U // 禁用队列集
    #define configUSE_TIME_SLICING 1U // 启用时间片调度
    #define configUSE_NEWLIB_REENTRANT 0U // 禁用Newlib重入
    #define configENABLE_BACKWARD_COMPATIBILITY 0U // 禁用向后兼容

    /* ————————– 钩子函数配置 ————————– */
    #define configUSE_IDLE_HOOK 0U // 禁用空闲钩子函数
    #define configUSE_TICK_HOOK 0U // 禁用滴答钩子函数
    #define configCHECK_FOR_STACK_OVERFLOW 0U // 禁用栈溢出检测
    #define configUSE_MALLOC_FAILED_HOOK 0U // 禁用内存分配失败钩子函数
    #define configUSE_DAEMON_TASK_STARTUP_HOOK 0U // 禁用守护任务启动钩子函数

    /* ————————– 定时器配置 ————————– */
    #define configUSE_TIMERS 1U // 启用软件定时器
    #define configTIMER_TASK_PRIORITY (3U) // 定时器任务优先级
    #define configTIMER_QUEUE_LENGTH (10U) // 定时器队列长度
    #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) // 定时器任务栈大小

    /* ————————– 中断配置 ————————– */
    #define configPRIO_BITS 4U // STM32F103使用4位优先级(0-15)
    #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15U // 库函数最低中断优先级
    #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5U // 系统调用最高中断优先级
    #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 configPRIO_BITS) )
    #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 configPRIO_BITS) )

    /* ————————– 函数重定义 ————————– */
    #define vPortSVCHandler SVC_Handler
    #define xPortPendSVHandler PendSV_Handler
    #define xPortSysTickHandler SysTick_Handler

    /* ————————– 包含头文件 ————————– */
    #include "stm32f1xx_hal.h"

    #endif /* FREERTOS_CONFIG_H */

    3.6 步骤6:修改STM32中断服务函数

    需要修改stm32f1xx_it.c文件,注释或替换与FreeRTOS冲突的中断服务函数:

    文件名:stm32f1xx_it.c

    #include "stm32f1xx_it.h"
    #include "FreeRTOS.h"
    #include "task.h"

    // 注释原有的SVC_Handler、PendSV_Handler、SysTick_Handler函数
    /*
    void SVC_Handler(void)
    {
    }

    void PendSV_Handler(void)
    {
    }

    void SysTick_Handler(void)
    {
    HAL_IncTick();
    }
    */

    // 保留其他中断函数(如EXTI、USART等)
    void NMI_Handler(void)
    {
    }

    void HardFault_Handler(void)
    {
    while (1)
    {
    }
    }

    void MemManage_Handler(void)
    {
    while (1)
    {
    }
    }

    void BusFault_Handler(void)
    {
    while (1)
    {
    }
    }

    void UsageFault_Handler(void)
    {
    while (1)
    {
    }
    }

    // 其他中断函数保持不变…

    3.7 步骤7:适配系统时钟与任务调度

    修改stm32f1xx_hal.c文件中的HAL_InitTick函数,避免与FreeRTOS的SysTick冲突:

    文件名:stm32f1xx_hal.c

    HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
    {
    // 注释原有的SysTick初始化代码,由FreeRTOS接管SysTick
    /*
    if (SysTick_Config(SystemCoreClock / (1000U / uwTickFreq)))
    {
    return HAL_ERROR;
    }

    if (TickPriority < (1UL << __NVIC_PRIO_BITS))
    {
    HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
    uwTickPrio = TickPriority;
    }
    else
    {
    return HAL_ERROR;
    }
    */

    return HAL_OK;
    }

    // 添加获取系统tick的函数(可选)
    uint32_t HAL_GetTick(void)
    {
    // 使用FreeRTOS的tick计数替代HAL的uwTick
    return (uint32_t)xTaskGetTickCount();
    }

    3.8 步骤8:编写测试任务验证移植效果

    修改main.c文件,编写两个测试任务(LED闪烁+串口打印),验证FreeRTOS是否正常运行:

    文件名:main.c

    #include "main.h"
    #include "FreeRTOS.h"
    #include "task.h"
    #include "queue.h"
    #include "semphr.h"

    /* 硬件初始化函数声明 */
    static void MX_GPIO_Init(void);
    static void MX_USART1_UART_Init(void);

    /* 任务句柄 */
    TaskHandle_t xTaskLEDHandle = NULL;
    TaskHandle_t xTaskUARTHandle = NULL;

    /* 任务函数声明 */
    void vTaskLED(void *pvParameters);
    void vTaskUART(void *pvParameters);

    int main(void)
    {
    /* 1. 初始化HAL库 */
    HAL_Init();

    /* 2. 配置系统时钟为72MHz */
    SystemClock_Config();

    /* 3. 初始化硬件(LED、串口) */
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    /* 4. 创建FreeRTOS任务 */
    xTaskCreate(vTaskLED, // 任务函数
    "TaskLED", // 任务名
    128, // 任务栈大小
    NULL, // 任务参数
    1, // 任务优先级
    &xTaskLEDHandle); // 任务句柄

    xTaskCreate(vTaskUART, // 任务函数
    "TaskUART", // 任务名
    128, // 任务栈大小
    NULL, // 任务参数
    2, // 任务优先级
    &xTaskUARTHandle); // 任务句柄

    /* 5. 启动任务调度器 */
    vTaskStartScheduler();

    /* 如果程序运行到这里,说明任务调度器启动失败(如堆内存不足) */
    while (1)
    {
    }
    }

    /**
    * @brief LED闪烁任务
    * @param pvParameters 任务参数
    * @retval None
    */

    void vTaskLED(void *pvParameters)
    {
    UNUSED(pvParameters);

    for (;;)
    {
    /* 翻转LED引脚电平(假设LED接PA0) */
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);

    /* 延时500ms(FreeRTOS延时函数,不会占用CPU) */
    vTaskDelay(pdMS_TO_TICKS(500));
    }
    }

    /**
    * @brief 串口打印任务
    * @param pvParameters 任务参数
    * @retval None
    */

    void vTaskUART(void *pvParameters)
    {
    UNUSED(pvParameters);
    uint32_t uartCount = 0;

    for (;;)
    {
    /* 串口打印信息 */
    HAL_UART_Transmit(&huart1, (uint8_t*)"FreeRTOS移植成功!计数:", 22, 100);

    /* 转换数字为字符串并打印 */
    char countBuf[10] = {0};
    sprintf(countBuf, "%d\\r\\n", uartCount++);
    HAL_UART_Transmit(&huart1, (uint8_t*)countBuf, strlen(countBuf), 100);

    /* 延时1000ms */
    vTaskDelay(pdMS_TO_TICKS(1000));
    }
    }

    /**
    * @brief GPIO初始化函数(LED接PA0)
    * @param None
    * @retval None
    */

    static void MX_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 启用GPIOA时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /* 配置PA0为推挽输出 */
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* 初始电平为低 */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
    }

    /**
    * @brief USART1初始化函数(115200 8N1)
    * @param None
    * @retval None
    */

    static void MX_USART1_UART_Init(void)
    {
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /**
    * @brief 系统时钟配置函数(72MHz)
    * @param None
    * @retval None
    */

    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /* 配置HSE振荡器 */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    /* 配置系统时钟 */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /**
    * @brief 错误处理函数
    * @param None
    * @retval None
    */

    void Error_Handler(void)
    {
    __disable_irq();
    while (1)
    {
    }
    }

    3.9 步骤9:编译与下载验证

  • 编译工程:在Keil中点击Build按钮,确保无错误、无警告
  • 下载程序:通过ST-Link将程序下载到STM32F103开发板
  • 验证效果:
    • LED灯以500ms为周期闪烁
    • 串口助手(115200 8N1)每隔1秒收到一行打印信息:FreeRTOS移植成功!计数:x
  • 四、常见问题与解决方法

    4.1 编译错误:undefined reference to vPortSVCHandler

    • 原因:FreeRTOSConfig.h中定义的中断函数名与STM32中断向量表不匹配
    • 解决:确认vPortSVCHandler、xPortPendSVHandler、xPortSysTickHandler的定义与中断函数名一致

    4.2 任务调度器启动失败

    • 原因:configTOTAL_HEAP_SIZE配置过小,不足以创建任务
    • 解决:增大configTOTAL_HEAP_SIZE(如改为1024*10=10KB)

    4.3 LED不闪烁/串口无输出

    • 原因1:系统时钟配置错误(非72MHz),导致延时函数计时不准
    • 解决1:检查SystemClock_Config函数,确保HSE和PLL配置正确
    • 原因2:中断优先级配置错误,FreeRTOS中断优先级需低于系统调用阈值
    • 解决2:确认configMAX_SYSCALL_INTERRUPT_PRIORITY配置正确

    五、移植优化建议

  • 内存管理:根据实际需求选择内存管理方案(heap_1~heap_5),heap_4适合大多数场景
  • 中断优先级:STM32F103的中断优先级分组为4位,确保外设中断优先级不高于configMAX_SYSCALL_INTERRUPT_PRIORITY
  • 任务栈大小:根据任务实际需求调整栈大小,避免过大浪费内存,过小导致栈溢出
  • 低功耗优化:若需低功耗,可启用configUSE_TICKLESS_IDLE,并适配STM32的低功耗模式
  • 总结

  • FreeRTOS移植到STM32F103的核心是适配内核相关的port文件、中断服务函数和系统时钟,关键配置文件为FreeRTOSConfig.h。
  • 移植流程需先搭建基础STM32工程,再逐步添加FreeRTOS源码、配置路径、修改中断函数,最后编写测试任务验证。
  • 常见问题主要集中在中断函数匹配、堆内存大小、中断优先级配置三个方面,需确保FreeRTOSConfig.h的参数与STM32硬件适配。
  • 赞(0)
    未经允许不得转载:171主机测评 » RTOS移植实战:FreeRTOS在STM32F103平台的移植步骤详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址