基于NXP-LPC55S69手动移植FreeRTOS教程
FreeRTOS 移植总结 — LPC55S69
目标板: LPC55S69JBD100 (Cortex-M33, 150MHz, 608KB Flash, 272KB RAM)
编译器: ARMCLANG V6.16 (Keil MDK-ARM V5.42)
IDE: Keil µVision V5
FreeRTOS 内核版本: V11.1.0 (LTS — 2024.06)
SDK: MCUXpresso SDK (LPC55S69_DFP.26.06.00)
移植日期: 2026-07-18
一、下载操作系统代码库
下载地址: https://freertos.org/zh-cn
点击 “Download” → 选择 “FreeRTOS Kernel” → 下载
案例包下载网址(chrome/夸克打开):http://103.236.97.252:48791
点击 “Download” → 选择FreeRTOS 202406.05 LTS。解压后得到源码目录
FreeRTOS 内核源码路径:
D:\freertos\FreeRTOSv202406.05-LTS\FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\
关键目录结构:
FreeRTOS-Kernel/ ├── include/ ← 所有头文件(22 个 .h) ├── *.c ← 内核源文件(7 个 .c) │ ├── tasks.c │ ├── queue.c │ ├── list.c │ ├── timers.c │ ├── event_groups.c │ ├── stream_buffer.c │ └── croutine.c ← 协程,不推荐使用 └── portable/ ├── ARMv8M/non_secure/ ← Cortex-M33/ARMv8-M 移植层 │ ├── port.c ← 通用移植代码 │ ├── portasm.h ← 汇编函数声明 │ ├── portmacrocommon.h ← ARMv8-M 公共宏 │ └── portable/ │ ├── GCC/ │ │ └── ARM_CM33_NTZ/ ← 本工程选用此目录 │ │ ├── portmacro.h ← CM33 特定宏 │ │ └── portasm.c ← 汇编实现 (SVC/PendSV/SysTick) │ └── IAR/ │ └── ARM_CM33_NTZ/ │ └── portmacro.h └── MemMang/ ← 内存管理方案 ├── heap_1.c ~ heap_5.c └── heap_4.c ← 本工程选用 heap_4二、工程中新建文件夹
在裸机工程根目录下创建以下目录结构:
hello_world_cm33_core0/ └── FreeRTOS/ ├── include/ ← 存放 FreeRTOS 头文件 ├── src/ ← 存放 FreeRTOS 内核源文件 ├── portable/ │ └── MemMang/ ← 存放内存管理文件 └── config/ ← 存放 FreeRTOSConfig.h与 STM32 移植的差异:LPC55S69 是 Cortex-M33,移植层使用
ARMv8M/non_secure。移植文件不放进编译器子目录(如portable/GCC/ARM_CM33_NTZ/),而是统一放在portable/根目录下,通过 include path 让port.c能找到portmacro.h和portasm.h。这种扁平化布局是因为 ARMv8M 版本的port.c位于non_secure/根目录,它通过#include "portmacro.h"和#include "portasm.h"引用同目录下的文件,不深入编译器子目录。
三、拷贝移植文件
3.1 头文件 — 拷贝到FreeRTOS/include/
从FreeRTOS-Kernel/include/拷贝全部 22 个文件:
| 文件名 | 说明 |
|---|---|
FreeRTOS.h | 主头文件 — TickType 定义、配置校验、API 重定向 |
task.h | 任务管理 API |
queue.h | 队列 API |
semphr.h | 信号量 API |
timers.h | 软件定时器 API |
event_groups.h | 事件组 API |
stream_buffer.h | 流缓冲区 API |
message_buffer.h | 消息缓冲区 API |
list.h | 链表数据结构(内核内部使用) |
croutine.h | 协程 API |
portable.h | 移植层抽象接口 |
projdefs.h | 内核宏定义 |
StackMacros.h | 栈检查宏 |
stack_macros.h | 栈检查宏 — 小写版 |
atomic.h | 原子操作 |
deprecated_definitions.h | 废弃 API 兼容宏 |
mpu_prototypes.h | MPU 内存保护函数原型 |
mpu_wrappers.h | MPU 包装宏 |
mpu_syscall_numbers.h | MPU 系统调用号 |
newlib-freertos.h | Newlib C 库集成 |
picolibc-freertos.h | PicoLibc C 库集成 |
3.2 内核源码 — 拷贝到FreeRTOS/src/
从FreeRTOS-Kernel/拷贝7 个 .c 文件:
| 文件名 | 说明 |
|---|---|
tasks.c | 任务管理核心 — 调度器、任务创建/删除、栈溢出检测、Idle 任务 |
queue.c | 队列核心 — 队列、信号量、互斥锁 |
list.c | 链表操作 — 内核数据结构 |
timers.c | 软件定时器 |
event_groups.c | 事件组 |
stream_buffer.c | 流缓冲区和消息缓冲区 |
croutine.c | 协程(可省略,configUSE_CO_ROUTINES=0时不编译) |
3.3 移植层 — 拷贝到FreeRTOS/portable/
这是 ARMv8M 移植与 STM32(ARMv7-M/ARM_CM4F)最大的不同:移植层拆成了 4 个文件分布于两个层级。
| 序号 | 源文件 | 目标位置 | 说明 |
|---|---|---|---|
| 1 | ARMv8M/non_secure/port.c | portable/port.c | 通用移植代码 |
| 2 | ARMv8M/non_secure/portasm.h | portable/portasm.h | 汇编函数声明 |
| 3 | ARMv8M/non_secure/portmacrocommon.h | portable/portmacrocommon.h | ARMv8-M 公共宏 |
| 4 | ARMv8M/non_secure/portable/GCC/ARM_CM33_NTZ/portmacro.h | portable/portmacro.h | CM33 + ARMCLANG 特定宏 |
| 5 | ARMv8M/non_secure/portable/GCC/ARM_CM33_NTZ/portasm.c | portable/portasm.c | 汇编实现(SVC/PendSV/SysTick handler) |
关键:ARMCLANG 兼容 GCC 内联汇编语法,所以选用
GCC/ARM_CM33_NTZ子目录的portmacro.h和portasm.c。不要选IAR/下的文件。STM32 移植通常只有port.c+portmacro.h两个文件,而 ARMv8M 版本多出portasm.c和portasm.h,漏掉任何一个都会导致编译/链接失败。
3.4 内存管理 — 拷贝到FreeRTOS/portable/MemMang/
从FreeRTOS-Kernel/portable/MemMang/选择1 个文件:
| 文件名 | 说明 |
|---|---|
heap_4.c | 首次适配 + 相邻空闲块合并,最推荐的单核方案 |
四、编写 FreeRTOSConfig.h(FreeRTOS/config/FreeRTOSConfig.h)
以官方模板为起点,针对 LPC55S69 (Cortex-M33)修改 9 处。
此文件来自官方模板文件,源路径为:
D:\freertos\FreeRTOSv202406.05-LTS\FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\examples\template_configuration\FreeRTOSConfig.h
改动 ①:tick 频率 (第 78 行)
| 修改前 | 修改后 |
|---|---|
#define configTICK_RATE_HZ 100 | #define configTICK_RATE_HZ 1000 |
100Hz = 10ms tick,粒度太粗。1000Hz = 1ms tick 是主流选择。
改动 ②:开启时间片轮转 (第 90 行)
| 修改前 | 修改后 |
|---|---|
#define configUSE_TIME_SLICING 0 | #define configUSE_TIME_SLICING 1 |
改动 ③:tick 类型位宽 (第 135 行)
| 修改前 | 修改后 |
|---|---|
#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_64_BITS | #define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS |
Cortex-M33 是 32 位架构,64 位 tick 每个 tick 都要做 64 位运算,白白浪费性能。32 位 tick 在 1000Hz 下约 49 天溢出,完全够用。
改动 ④:增大内核堆 (第 283 行)
| 修改前 | 修改后 |
|---|---|
#define configTOTAL_HEAP_SIZE 4096 | #define configTOTAL_HEAP_SIZE 10*1024 |
每个任务 TCB + stack 从 FreeRTOS 堆中分配。4KB 只能勉强支持 2~3 个轻量任务。10KB 适合入门验证。
改动 ⑤:中断优先级 — 致命配置 (第 311–315 行)
| 修改前 | 修改后 |
|---|---|
#define configKERNEL_INTERRUPT_PRIORITY 0 | #define configPRIO_BITS 3 |
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 0 | #define configKERNEL_INTERRUPT_PRIORITY ( 7 << ( 8 - configPRIO_BITS ) ) |
#define configMAX_API_CALL_INTERRUPT_PRIORITY 0 | #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( 1 << ( 8 - configPRIO_BITS ) ) |
这是 Cortex-M 移植最常见的致命错误。ARM Cortex-M NVIC 使用 bits [7:5] 表示优先级(3 位优先级),宏值必须左移到寄存器位。
0xE0= 逻辑优先级 7(最低,给 SysTick/PendSV),0x20= 逻辑优先级 1(高于此优先级的中断不受 RTOS 管控)。如果全部设为 0,portasm.c内联汇编中basepri=0无法屏蔽任何中断,编译还会触发#error。同时删除多余的
configMAX_API_CALL_INTERRUPT_PRIORITY(已废弃,被configMAX_SYSCALL_INTERRUPT_PRIORITY替代)。
改动 ⑥:关闭 TrustZone (第 578、585 行)
| 修改前 | 修改后 |
|---|---|
#define configENABLE_TRUSTZONE 1 | #define configENABLE_TRUSTZONE 0 |
#define configRUN_FREERTOS_SECURE_ONLY 1 | #define configRUN_FREERTOS_SECURE_ONLY 0 |
原始 hello_world 是普通 non-secure 单核应用,不需要 TrustZone。两个宏同时为 1 构成逻辑矛盾(开启 TZ 又声称只跑在 Secure 侧)。
改动 ⑦:关闭 MPU (第 589 行)
| 修改前 | 修改后 |
|---|---|
#define configENABLE_MPU 1 | #define configENABLE_MPU 0 |
开启 MPU 需要额外链接
mpu_wrappers_v2.c和mpu_wrappers_v2_asm.c,还会使portasm.c中大量#if ( configENABLE_MPU == 1 )的代码路径生效,增大代码体积。入门移植先关闭。
改动 ⑧:关闭 MVE (第 594 行)
| 修改前 | 修改后 |
|---|---|
#define configENABLE_MVE 1 | #define configENABLE_MVE 0 |
会直接导致编译报错
#error。MVE (M-Profile Vector Extension) 是 Cortex-M55/M85 才有的向量扩展指令,Cortex-M33 不支持。portmacro.h第 62 行有硬检查:#elif(configENABLE_MVE!=0)#errorconfigENABLE_MVE must be left undefined,or defined to0forthe Cortex-M33.
改动 ⑨:保留 FPU 开启 (第 593 行)
#defineconfigENABLE_FPU1// ← 保持为 1,LPC55S69 有 FPULPC55S69 集成单精度 FPU (SFPU),工程中
<Cpu>CPUTYPE("Cortex-M33") FPU3(SFPU) TZ DSP</Cpu>已声明。
五、Keil 工程配置
5.1 添加头文件搜索路径
操作: Options for Target (Alt+F7) → C/C++(AC6) 标签页 → Include Paths → 末尾新增 3 个路径:
.\FreeRTOS\include;.\FreeRTOS\config;.\FreeRTOS\portable完整路径:
board;CMSIS;CMSIS/m-profile;component/uart;device;device/periph;drivers;utilities;utilities/debug_console_lite;utilities/str;..\hello_world_cm33_core0;.\source;.\FreeRTOS\include;.\FreeRTOS\config;.\FreeRTOS\portable同样在Asm标签页也添加这三个路径。debug 和 release 两个 Target 都要改。
5.2 在工程中新增 File Group
在 Keil 左侧 Project 窗口中创建以下分组并添加对应文件:
| Group 名称 | 添加的文件 |
|---|---|
FreeRTOS/src | FreeRTOS\src\tasks.c、queue.c、list.c、timers.c、event_groups.c、stream_buffer.c、croutine.c |
FreeRTOS/portable | FreeRTOS\portable\port.c、portasm.c、MemMang\heap_4.c |
注意:
- 文件类型选择All Files才能看到
portasm.c- 不需要添加
.h头文件到分组(编译器通过 include path 自动查找)- debug 和 release 两个 Target 都要添加,确保
Include in Target Build勾选croutine.c虽然添加了,但configUSE_CO_ROUTINES=0时不参与实际编译
5.3 编译器选项
现有 Misc Controls 保持不变:
-include source/mcux_config.h -include source/mcuxsdk_version.h -fno-common -fdata-sections -fno-builtin -mthumbarmclang兼容 GCC 内联汇编语法,无需像 ARMCC V5 那样替换为__asm{}块- 不需要开启 Keil RTX OS 支持选项(
<mOS>0</mOS>保持关闭)
六、修改工程现有文件
6.1 启动文件 —startup/arm/startup_LPC55S69_cm33_core0.S
无需修改。
启动文件中 SVC_Handler、PendSV_Handler、SysTick_Handler 全部是.weak声明:
.weak SVC_Handler .weak PendSV_Handler .weak SysTick_Handler.weak意味着链接器会优先使用port.c/portasm.c中定义的同名强符号。与 STM32 移植不同,不需要在 FreeRTOSConfig.h 中添加#define vPortSVCHandler SVC_Handler等宏映射——因为 ARMv8M 版portasm.c中直接定义了SVC_Handler、PendSV_Handler函数名,与启动文件向量表中的符号名天然匹配。
6.2 主程序 —source/hello_world.c
必须添加栈溢出钩子函数(configCHECK_FOR_STACK_OVERFLOW = 2要求):
voidvApplicationStackOverflowHook(TaskHandle_t xTask,char*pcTaskName){(void)xTask;(void)pcTaskName;for(;;){/* 死循环,调试时在此打断点定位溢出任务 */}}七、调试过程中遇到的错误
| 序号 | 报错/现象 | 根本原因 | 解决方法 |
|---|---|---|---|
| 1 | error: 'portmacro.h' file not found— 9 个错误 | portmacro.h未从编译器子目录复制到portable/根目录 | 从ARMv8M/non_secure/portable/GCC/ARM_CM33_NTZ/portmacro.h复制到FreeRTOS/portable/portmacro.h |
| 2 | error: 'portasm.h' file not found— 1 个错误 | port.c依赖portasm.h,但该文件未复制 | 从ARMv8M/non_secure/portasm.h复制到FreeRTOS/portable/portasm.h |
| 3 | L6218E: Undefined symbol ulSetInterruptMaskL6218E: Undefined symbol vClearInterruptMaskL6218E: Undefined symbol vRestoreContextOfFirstTaskL6218E: Undefined symbol vStartFirstTask— 4 个错误 | portasm.c未加入 Keil 工程编译(这几个函数全部在portasm.c中实现) | Keil 工程 FreeRTOS/portable 分组中添加portasm.c;同时需将portasm.h放到portable/目录(include path 已配置) |
| 4 | L6218E: Undefined symbol vApplicationStackOverflowHook— 1 个错误 | configCHECK_FOR_STACK_OVERFLOW = 2开启栈检测,但未提供回调函数 | 在hello_world.c中添加空实现 |
八、ARMv8M 移植与 STM32 ARMv7-M 移植的关键差异总结
| 对比维度 | STM32F407 (ARMv7-M/CM4F) | LPC55S69 (ARMv8-M/CM33) |
|---|---|---|
| 移植层目录 | portable/RVDS/ARM_CM4F/ | portable/ARMv8M/non_secure/ |
| 移植文件数量 | 2 个(port.c+portmacro.h) | 5 个(port.c+portmacrocommon.h+portasm.h+portmacro.h+portasm.c) |
| 汇编文件 | 汇编内嵌在port.c中 | 独立portasm.c文件 |
| ISR 命名 | vPortSVCHandler等 → 需要宏映射 | SVC_Handler等 → 直接匹配启动文件 |
| 中断优先级位数 | 4 bit(bits [7:4]) | 3 bit(bits [7:5]) |
| 优先级宏值 | 0xF0/0x50 | 0xE0/0x20 |
| FPU 支持 | portable/RVDS/ARM_CM4F内置 | portmacro.h宏configENABLE_FPU=1控制 |
| TrustZone | 不支持 | 支持(本移植中关闭) |
| 编译器 | ARMCC V5 → 需要替换内联汇编语法 | ARMCLANG V6 → 直接兼容 GCC 语法 |
九、验证结果
移植完成后,编译 0 错误 0 警告,链接成功生成hello_world_cm33_core0.out。
最终工程结构:
hello_world_cm33_core0/ ├── FreeRTOS/ │ ├── include/ ← 22 个 .h │ ├── src/ ← 7 个 .c │ ├── portable/ ← port.c, portasm.c, portasm.h, portmacro.h, portmacrocommon.h │ │ └── MemMang/ │ │ └── heap_4.c │ ├── config/ │ │ └── FreeRTOSConfig.h ├── source/ │ └── hello_world.c ├── startup/arm/ │ └── startup_LPC55S69_cm33_core0.S ├── board/ ├── CMSIS/ ├── drivers/ ├── device/ ├── utilities/ ├── component/ └── hello_world_cm33_core0.uvprojx