GD32F470ZGT6 FreeRTOS CMake 模板适配指南
本文档详细说明如何基于 GD32F470ZGT6 裸机模板,移植 FreeRTOS V10.4.3 实时操作系统。
一、参考标准
| 参考来源 | 版本/路径 | 用途 |
|---|---|---|
| FreeRTOS 官方 | V10.4.3 | 内核源码、端口代码 |
| FreeRTOS Cortex-M 移植指南 | FreeRTOS/Source/portable/GCC/ARM_CM4F/ |
ARM_CM4F 端口标准实现 |
| FreeRTOS 内存管理 | FreeRTOS/Source/portable/MemMang/heap_4.c |
动态内存分配方案 |
| FreeRTOS 配置参考 | FreeRTOS 官方 Demo 工程 | FreeRTOSConfig.h 标准配置 |
| GD32F4xx 标准外设库 | V3.3.3 | GPIO、时钟初始化 |
| ARM Cortex-M4 权威指南 | ARM 官方 | NVIC 优先级分组、中断管理 |
二、FreeRTOS 官方标准解析
2.1 Cortex-M4F 端口要求
FreeRTOS 为 Cortex-M4F 提供了专门的端口(portable/GCC/ARM_CM4F/),关键特性:
| 特性 | 说明 |
|---|---|
| 端口目录 | portable/GCC/ARM_CM4F/ |
| 核心文件 | port.c --- 上下文切换、PendSV/SysTick 处理 |
| FPU 支持 | 自动保存/恢复 FPU 寄存器 |
| SysTick 映射 | xPortSysTickHandler → SysTick_Handler |
| PendSV 映射 | xPortPendSVHandler → PendSV_Handler |
| SVC 映射 | vPortSVCHandler → SVC_Handler |
2.2 中断优先级标准
FreeRTOS 对 Cortex-M 的中断优先级有严格要求:
优先级分配(4 bit,共 16 级 0-15):
├── 0-4 : FreeRTOS 管理(内核中断)
├── 5 : configMAX_SYSCALL_INTERRUPT_PRIORITY(系统调用最高优先级)
└── 6-15 : 用户中断(不受 FreeRTOS 管理)
关键规则:
- FreeRTOS 内核中断(PendSV、SysTick)使用最低优先级(15)
configMAX_SYSCALL_INTERRUPT_PRIORITY定义可调用 FreeRTOS API 的最高中断优先级- 优先级高于此值的中断不能使用 FreeRTOS API
2.3 内存管理方案
FreeRTOS 提供 5 种内存管理方案:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| heap_1 | 只分配,不释放 | 简单嵌入式系统 |
| heap_2 | 最佳匹配算法 | 碎片化不严重的场景 |
| heap_3 | 包装标准 malloc/free | 需要标准 C 库的场景 |
| heap_4 | 首次匹配 + 合并 | 通用场景(本模板使用) |
| heap_5 | 多区域内存管理 | 复杂内存布局 |
三、适配步骤
步骤 1:准备 FreeRTOS 源码
将 FreeRTOS 源码放置到 SDK 目录:
sdk/utilities/Third_Party/FreeRTOS/
├── include/ # FreeRTOS 内核头文件
├── tasks.c # 任务管理
├── queue.c # 队列
├── list.c # 链表
├── timers.c # 软件定时器
├── event_groups.c # 事件组
├── stream_buffer.c # 流缓冲区
├── croutine.c # 协程
└── portable/
├── GCC/
│ └── ARM_CM4F/ # Cortex-M4F 端口
│ ├── port.c
│ └── portmacro.h
└── MemMang/
└── heap_4.c # 内存管理方案
步骤 2:创建 FreeRTOSConfig.h
在 myprog/include/FreeRTOSConfig.h 中创建配置文件:
c
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include "gd32f4xx.h"
/* 重命名 CMSIS 中断处理函数(关键!) */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
关键说明 :FreeRTOS 的 port.c 中定义了 SVC_Handler、PendSV_Handler、SysTick_Handler 函数。由于启动文件的向量表中已经声明了这些函数名,需要通过宏定义将 FreeRTOS 的内部函数名映射到 CMSIS 标准名称。
步骤 3:配置核心参数
c
/* 调度器配置 */
#define configUSE_PREEMPTION 1 /* 抢占式调度 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 /* 不使用硬件位带优化 */
#define configUSE_TICKLESS_IDLE 0 /* 不使用 Tickless 低功耗 */
/* 时钟配置 */
#define configCPU_CLOCK_HZ (SystemCoreClock) /* 240MHz */
#define configTICK_RATE_HZ 1000 /* 1ms tick */
/* 任务配置 */
#define configMAX_PRIORITIES 5 /* 5 个优先级 (0-4) */
#define configMINIMAL_STACK_SIZE 128 /* 最小栈 128 字 = 512 字节 */
#define configTOTAL_HEAP_SIZE (64 * 1024) /* 总堆 64KB */
#define configMAX_TASK_NAME_LEN 16 /* 任务名最大长度 */
/* 功能开关 */
#define configUSE_TASK_NOTIFICATIONS 1 /* 任务通知 */
#define configUSE_MUTEXES 1 /* 互斥量 */
#define configUSE_RECURSIVE_MUTEXES 1 /* 递归互斥量 */
#define configUSE_COUNTING_SEMAPHORES 1 /* 计数信号量 */
#define configUSE_TIMERS 1 /* 软件定时器 */
步骤 4:配置中断优先级
c
/* 中断优先级配置(4 bit NVIC) */
#define configPRIO_BITS 4 /* GD32F4xx 使用 4 位优先级 */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 /* 最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* 可调用 API 的最高优先级 */
/* 计算内核优先级值(移位到 8 位格式) */
#define configKERNEL_INTERRUPT_PRIORITY \
(configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
/* = 15 << 4 = 0xF0 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
/* = 5 << 4 = 0x50 */
标准参考:FreeRTOS 官方文档 "Interrupt priority configuration on ARM Cortex-M"。
步骤 5:配置内存分配
c
/* 内存分配方案 */
#define configSUPPORT_STATIC_ALLOCATION 0 /* 不支持静态分配 */
#define configSUPPORT_DYNAMIC_ALLOCATION 1 /* 支持动态分配 */
#define configAPPLICATION_ALLOCATED_HEAP 0 /* 堆由 FreeRTOS 管理 */
步骤 6:配置 CMakeLists.txt
cmake
# FreeRTOS V10.4.3
set(FREERTOS_ROOT "${SDK_ROOT}/utilities/Third_Party/FreeRTOS")
set(FREERTOS_PORT "${FREERTOS_ROOT}/portable/GCC/ARM_CM4F")
set(FREERTOS_HEAP "${FREERTOS_ROOT}/portable/MemMang")
# 头文件路径
include_directories(
${FREERTOS_ROOT}/include
${FREERTOS_PORT}
)
# FreeRTOS 源码
set(FREERTOS_SOURCES
${FREERTOS_ROOT}/tasks.c
${FREERTOS_ROOT}/queue.c
${FREERTOS_ROOT}/list.c
${FREERTOS_ROOT}/timers.c
${FREERTOS_ROOT}/event_groups.c
${FREERTOS_ROOT}/stream_buffer.c
${FREERTOS_ROOT}/croutine.c
${FREERTOS_PORT}/port.c
${FREERTOS_HEAP}/heap_4.c
)
# 加入编译
set(PROJECT_SOURCES
${PERIPHERAL_SOURCES}
${SYSTEM_SOURCES}
${STARTUP_SOURCES}
${APP_SOURCES}
${FREERTOS_SOURCES}
)
步骤 7:实现 main.c
c
#include "gd32f4xx.h"
#include "FreeRTOS.h"
#include "task.h"
/* LED 任务 */
static void led_task(void *pvParameters)
{
while(1) {
gpio_bit_write(LED1_PORT, LED1_PIN, 1);
gpio_bit_write(LED2_PORT, LED2_PIN, 0);
gpio_bit_write(LED3_PORT, LED3_PIN, 0);
gpio_bit_write(LED4_PORT, LED4_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(500));
/* 切换下一个 LED ... */
}
}
int main(void)
{
SystemCoreClockUpdate();
/* NVIC 优先级分组(FreeRTOS 要求 4 bit 全用于子优先级) */
NVIC_SetPriorityGrouping(4);
/* GPIO 初始化 */
rcu_periph_clock_enable(RCU_GPIOE);
/* ... */
/* 创建 LED 任务 */
xTaskCreate(led_task, "LED",
configMINIMAL_STACK_SIZE * 2, /* 栈大小 */
NULL, 2, NULL); /* 参数、优先级、句柄 */
/* 启动调度器 */
vTaskStartScheduler();
/* 理论上不会到达这里 */
while(1) {
debug_printf("Error: FreeRTOS scheduler stopped!\n");
}
}
关键说明:
NVIC_SetPriorityGrouping(4)配置所有 4 bit 为抢占优先级(0 位子优先级),FreeRTOS 只管理抢占优先级
注意 :不同 CMSIS 版本中 NVIC_SetPriorityGrouping() 的参数含义可能不同。部分系统中 FreeRTOS 官方推荐使用 NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4),建议根据实际平台的 CMSIS 实现验证。实际配置需保证 FreeRTOS 管理的中断(PendSV、SysTick)优先级最低(15)。
vTaskStartScheduler()启动调度器后,不再返回(除非内存不足)- 不需要手动配置 SysTick,FreeRTOS 的
port.c会自动处理
步骤 8:FreeRTOS SysTick 处理(由 port.c 自动处理)
FreeRTOS 的 port.c 内部实现了 SysTick_Handler:
c
/* port.c 内部实现(不需要用户编写) */
void xPortSysTickHandler(void)
{
/* 增加 tick 计数 */
xTaskIncrementTick();
/* 触发 PendSV 进行任务切换 */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
与裸机的区别 :裸机需要用户手动实现 SysTick_Handler,而 FreeRTOS 的 port.c 已内置实现,通过 FreeRTOSConfig.h 中的宏映射到 SysTick_Handler。
四、关键差异对比
| 项目 | 裸机模板 | FreeRTOS 模板 |
|---|---|---|
| SysTick 处理 | 用户手动实现 | port.c 自动实现 |
| 延时方式 | delay_ms() 阻塞等待 |
vTaskDelay() 非阻塞挂起 |
| 中断管理 | 用户手动配置 | FreeRTOS 统一管理 |
| 内存分配 | 静态分配 | heap_4.c 动态分配 |
| NVIC 分组 | 用户自由配置 | 必须为 NVIC_SetPriorityGrouping(4) |
| 程序入口 | main() 循环 |
vTaskStartScheduler() 启动调度器 |
五、编译与烧录
5.1 编译
bash
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make -j8
5.2 烧录
bash
sudo ./flash/flash.sh
5.3 验证
通过串口查看启动信息:
=== FreeRTOS LED Demo ===
4 个 LED 每 500ms 依次切换,形成跑马灯效果。
六、关键配置汇总
| 项目 | 值 | 说明 |
|---|---|---|
| FreeRTOS 版本 | V10.4.3 | 稳定版 |
| 端口 | ARM_CM4F | Cortex-M4F 硬件浮点 |
| 内存管理 | heap_4.c | 首次匹配 + 合并 |
| 总堆大小 | 64KB | configTOTAL_HEAP_SIZE |
| Tick 频率 | 1000Hz (1ms) | configTICK_RATE_HZ |
| 优先级数量 | 5 | configMAX_PRIORITIES |
| 调度方式 | 抢占式 | configUSE_PREEMPTION = 1 |
| 最大系统调用优先级 | 5 | configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY |
| 内核中断优先级 | 0xF0 | 最低优先级 |
七、注意事项
- 中断函数名映射 :必须在
FreeRTOSConfig.h中定义vPortSVCHandler、xPortPendSVHandler、xPortSysTickHandler宏,否则链接时会找到多个函数定义 - NVIC 优先级分组 :必须调用
NVIC_SetPriorityGrouping(4),FreeRTOS 只使用子优先级 - 栈大小 :任务栈不能小于
configMINIMAL_STACK_SIZE(128 字 = 512 字节),否则会导致栈溢出 - 中断中调用 API :优先级高于
configMAX_SYSCALL_INTERRUPT_PRIORITY的中断不能使用 FreeRTOS API - heap_4 合并机制:释放的内存块会自动与相邻空闲块合并,减少碎片