基于NXP-LPC55S69手动移植FreeRTOS教程

📅 2026/7/19 22:49:26 👁️ 阅读次数 📝 编程学习
基于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.hportasm.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.hMPU 内存保护函数原型
mpu_wrappers.hMPU 包装宏
mpu_syscall_numbers.hMPU 系统调用号
newlib-freertos.hNewlib C 库集成
picolibc-freertos.hPicoLibc 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 个文件分布于两个层级。

序号源文件目标位置说明
1ARMv8M/non_secure/port.cportable/port.c通用移植代码
2ARMv8M/non_secure/portasm.hportable/portasm.h汇编函数声明
3ARMv8M/non_secure/portmacrocommon.hportable/portmacrocommon.hARMv8-M 公共宏
4ARMv8M/non_secure/portable/GCC/ARM_CM33_NTZ/portmacro.hportable/portmacro.hCM33 + ARMCLANG 特定宏
5ARMv8M/non_secure/portable/GCC/ARM_CM33_NTZ/portasm.cportable/portasm.c汇编实现(SVC/PendSV/SysTick handler)

关键:ARMCLANG 兼容 GCC 内联汇编语法,所以选用GCC/ARM_CM33_NTZ子目录的portmacro.hportasm.c。不要选IAR/下的文件。STM32 移植通常只有port.c+portmacro.h两个文件,而 ARMv8M 版本多出portasm.cportasm.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.cmpu_wrappers_v2_asm.c,还会使portasm.c中大量#if ( configENABLE_MPU == 1 )的代码路径生效,增大代码体积。入门移植先关闭。

改动 ⑧:关闭 MVE (第 594 行)

修改前修改后
#define configENABLE_MVE 1#define configENABLE_MVE 0

会直接导致编译报错#errorMVE (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 有 FPU

LPC55S69 集成单精度 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/srcFreeRTOS\src\tasks.cqueue.clist.ctimers.cevent_groups.cstream_buffer.ccroutine.c
FreeRTOS/portableFreeRTOS\portable\port.cportasm.cMemMang\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 -mthumb
  • armclang兼容 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_HandlerPendSV_Handler函数名,与启动文件向量表中的符号名天然匹配。

6.2 主程序 —source/hello_world.c

必须添加栈溢出钩子函数(configCHECK_FOR_STACK_OVERFLOW = 2要求):

voidvApplicationStackOverflowHook(TaskHandle_t xTask,char*pcTaskName){(void)xTask;(void)pcTaskName;for(;;){/* 死循环,调试时在此打断点定位溢出任务 */}}

七、调试过程中遇到的错误

序号报错/现象根本原因解决方法
1error: 'portmacro.h' file not found— 9 个错误portmacro.h未从编译器子目录复制到portable/根目录ARMv8M/non_secure/portable/GCC/ARM_CM33_NTZ/portmacro.h复制到FreeRTOS/portable/portmacro.h
2error: 'portasm.h' file not found— 1 个错误port.c依赖portasm.h,但该文件未复制ARMv8M/non_secure/portasm.h复制到FreeRTOS/portable/portasm.h
3L6218E: Undefined symbol ulSetInterruptMask
L6218E: Undefined symbol vClearInterruptMask
L6218E: Undefined symbol vRestoreContextOfFirstTask
L6218E: Undefined symbol vStartFirstTask— 4 个错误
portasm.c未加入 Keil 工程编译(这几个函数全部在portasm.c中实现)Keil 工程 FreeRTOS/portable 分组中添加portasm.c;同时需将portasm.h放到portable/目录(include path 已配置)
4L6218E: 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.h5 个(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/0x500xE0/0x20
FPU 支持portable/RVDS/ARM_CM4F内置portmacro.hconfigENABLE_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