- 操作系统
- 嵌入式
- RTOS
- 嵌入式OS
【免费下载链接】FreeRTOS-Kernel
FreeRTOS kernel files only, submoduled into https://github.com/FreeRTOS/FreeRTOS and various other repos.
本指南以 portable/ThirdParty/GCC/RP2040/README.md 为骨架,系统讲解 FreeRTOS-Kernel 官方为 Raspberry Pi Pico(RP2040 双核 Cortex-M0+)提供的 SMP(对称多处理)移植版本:如何在基于 Raspberry Pi Pico SDK 的 CMake 工程中引入内核、如何将任务运行在 core 0 / core 1 / 双核、如何让 FreeRTOS 任务与 pico_sync / pico_time 等 SDK 原语互通,以及rp2040_config.h中的底层配置项。读完本文,你将能够在 Pico 工程里完整落地 FreeRTOS SMP 移植,并理解其 CMake 集成机制与源码级实现原理。
Overview:这是一个什么样的移植
portable/ThirdParty/GCC/RP2040目录提供了可与 Raspberry Pi Pico SDK 配合使用的 SMP 版 FreeRTOS-Kernel 移植。它支持的三个核心特性(均来自 README):
- 纯 CMake INTERFACE 库集成:以简单、非拷贝的方式同时提供 FreeRTOS-Kernel 与各堆分配器(heap allocator),无需把内核代码复制进用户工程。
- 双核调度:内核与任务可以运行在 core 0、core 1,或同时运行在两个核心上(
configNUMBER_OF_CORES控制)。 - SDK 同步原语互通:FreeRTOS 任务可以与运行在非 FreeRTOS 核心上的代码、或 IRQ 中断处理程序之间,使用 SDK 的同步原语(如 pico_sync 的 mutex、semaphore、queue)。
需要特别说明的是:这个 SMP 版本也可以只跑在单核(任意一个核心)上,但如果你的场景只是单核,官方建议改用主分支中的非 SMP 移植版本,效率通常更高。这一建议在 README 中明确写出,理由是单核场景下 SMP 移植携带的双核同步与自旋锁开销并非必要。
目录结构速览
该移植的完整文件布局如下:
portable/ThirdParty/GCC/RP2040/ ├── CMakeLists.txt # 顶层 CMake 入口,负责定位 SDK 与内核 ├── FreeRTOS_Kernel_import.cmake # 可拷贝进用户工程的导入脚本 ├── library.cmake # 定义各 INTERFACE 库目标(内核、堆、静态分配) ├── pico_sdk_import.cmake # 用于拉取 Pico SDK ├── port.c # 端口核心实现(调度器启动、上下文切换、SysTick、互操作) ├── LICENSE.md ├── README.md └── include/ ├── freertos_sdk_config.h # SDK <-> FreeRTOS 互操作桥接配置 ├── portmacro.h # 端口类型、临界区、自旋锁、多核宏定义 └── rp2040_config.h # 本移植特有的 config 配置项其中portmacro.h与rp2040_config.h定义了端口级宏,port.c(约 1160 行)承载了从xPortStartScheduler到xPortSysTickHandler的全部底层实现,是理解本移植工作原理的关键文件。
Using this port:三种方式将移植引入你的工程
方式一:拷贝 import 脚本(推荐)
将 FreeRTOS_Kernel_import.cmake 拷贝到你的工程中,然后在CMakeLists.txt中添加:
include(FreeRTOS_Kernel_import.cmake)该脚本会按以下优先级定位 FreeRTOS 内核(对应脚本中 FreeRTOS_Kernel_import.cmake 的逻辑):
- 环境变量
FREERTOS_KERNEL_PATH或 CMake 命令行参数-DFREERTOS_KERNEL_PATH=/path/to/FreeRTOS-Kernel(显式指定,优先级最高); - 如果 import 脚本本身位于 FreeRTOS-Kernel 仓库树内(即直接 include 了仓库中的文件),则自动按相对路径反推出
FREERTOS_KERNEL_PATH; - 若
PICO_SDK_PATH已定义,且其同级目录存在FreeRTOS-Kernel(即 SDK 与内核并排布局),自动默认到该路径; - 在以
Source、FreeRTOS-Kernel、FreeRTOS/Source命名的子目录中搜索内核; - 以上均失败则输出
FATAL_ERROR,提示你设置FREERTOS_KERNEL_PATH。
脚本内部对PICO_PLATFORM进行了分支:rp2040平台使用portable/ThirdParty/GCC/RP2040;rp2350-riscv与其余 RP2350 平台会分别指向对应的RP2350_RISC-V/RP2350_ARM_NTZ移植目录。最终通过add_subdirectory(... FREERTOS_KERNEL)将移植与内核接入构建。
Pico SDK 版本相关的放置约束(务必注意):
- 若使用1.3.1 或更早的 Raspberry Pi Pico SDK,
include(FreeRTOS_Kernel_import.cmake)这行必须出现在pico_sdk_init()之前;此时 FreeRTOS 会被包含/要求进工程中所有 RP2040 target(即全局生效)。 - 若使用1.3.2 及之后的 SDK,可以在 CMake 构建的稍后阶段(甚至子目录中)再引入 FreeRTOS-Kernel 支持,此时支持仅作用于显式包含 FreeRTOS 支持的那些 target。
这一版本差异在 CMakeLists.txt 的源码中有直接体现:当检测到_pico_sdk_inclusion_markertarget 存在(即 SDK 已初始化)时,要求 SDK 版本至少 1.3.2 才能直接在pico_sdk_init()之后加载;否则将library.cmake追加到PICO_SDK_POST_LIST_FILES,在pico_sdk_init()结束时由 SDK 回调执行。此外,当 SDK 版本低于 1.3.2 时,会把 freertos_sdk_config.h 注入PICO_CONFIG_HEADER_FILES,使全部 SDK 文件都能感知 FreeRTOS 配置。
方式二:add_subdirectory 直接加入
作为include语句的替代方案,可以直接将该目录加入构建(与上述 Pico SDK 版本的放置限制相同):
add_subdirectory(path/to/this/directory FreeRTOS-Kernel)同样地,若此时代码在 SDK 已初始化后加入,CMakeLists.txt 会通过library.cmake立即注册库目标;若在pico_sdk_init()之前加入,则由 SDK 的 POST_LIST 机制延迟注册。CMakeLists.txt中还要求 SDK 版本不低于 1.2.0,否则直接FATAL_ERROR中止构建。
方式三:作为内核子模块
README 指出该目录是 FreeRTOS 官方仓库布局的一部分——FreeRTOS-Kernel 常作为子模块被引入各类工程。import 脚本第一行注释即说明:This is a copy of <FREERTOS_KERNEL_PATH>/portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_import.cmake,它既可被直接 include,也可通过add_subdirectory使用。若内核是工程的一个直接子模块(sub-module),import 脚本的自动定位逻辑(第 2 步路径反推)即可工作,无需手工指定路径。
import 脚本与 CMakeLists 的版本门槛小结
| 场景 | 最低 Pico SDK 版本 | 说明 |
|---|---|---|
顶层引入(pico_sdk_init()之前) | 1.2.0 | CMakeLists.txt 中强制检查 |
pico_sdk_init()之后引入(子目录/后续阶段) | 1.3.2 | 否则FATAL_ERROR |
| import 脚本放置位置 | 1.3.1 及更早必须放在pico_sdk_init()前 | 全局生效;1.3.2+ 可延后 |
链接哪些库:FreeRTOS-Kernel、堆分配器与静态分配
引入移植后,library.cmake 会为你注册一组 INTERFACE 库目标,全部无需拷贝源码、直接以 target 形式链接:
| 目标名 | 作用 |
|---|---|
FreeRTOS-Kernel-Core | 内核核心源码(croutine.c、event_groups.c、list.c、queue.c、stream_buffer.c、tasks.c、timers.c),并指向仓库根目录的include |
FreeRTOS-Kernel | 移植层:port.c+include/,链接 pico_base_headers、hardware_clocks、hardware_exception、pico_multicore,定义LIB_FREERTOS_KERNEL=1与FREE_RTOS_KERNEL_SMP=1 |
FreeRTOS-Kernel-Static | 静态分配支持(configSUPPORT_STATIC_ALLOCATION=1、configKERNEL_PROVIDED_STATIC_MEMORY=1) |
FreeRTOS-Kernel-Heap1~FreeRTOS-Kernel-Heap5 | 分别链接 portable/MemMang/heap_1.c ~ heap_5.c 五种堆分配器 |
例如,要在任务中动态创建对象,可在target_link_libraries中同时链接FreeRTOS-Kernel与FreeRTOS-Kernel-Heap4(heap_4 是使用最广泛的通用分配器);若整个工程使用静态分配,则链接FreeRTOS-Kernel-Static。port.c编译时由 SDK 1.3.2+ 提供的PICO_CONFIG_RTOS_ADAPTER_HEADER机制引入 freertos_sdk_config.h,从而让 SDK 侧代码也能感知 FreeRTOS 的互操作配置。
让任务跑在哪个核心:SMP 调度的实现机制
configNUMBER_OF_CORES决定调度器运行在单核还是双核。从 portmacro.h 可以看到:
portMAX_CORE_COUNT固定为2(RP2040 双 Cortex-M0+ 核心),若configNUMBER_OF_CORES超出[1, 2]范围会在编译期#error;configNUMBER_OF_CORES == 2时portGET_CORE_ID()映射到 SDK 的get_core_num(),单核时恒为0。
调度器启动路径在 port.c 的xPortStartScheduler(SMP 分支)中:先通过spin_lock_claim声明两个自旋锁,确认从 core 0 启动后调用multicore_reset_core1()与multicore_launch_core1(...)把第二个核心也拉进调度器,再由vPortStartFirstTask()启动首个任务;而configTICK_CORE指定由哪个核心承接 SysTick 中断——只有 primary core 会调用vPortSetupTimerInterrupt()配置 SysTick 并装载xPortSysTickHandler(见 port.c)。
核心间同步依靠 RP2040 硬件自旋锁(spin lock)。configSMP_SPINLOCK_0/configSMP_SPINLOCK_1分别用于 ISR 锁与任务锁,默认取 SDK 为 RTOS 预留的PICO_SPINLOCK_ID_OS1/PICO_SPINLOCK_ID_OS2。portmacro.h中的vPortRecursiveLock实现了可重入递归自旋锁:同一核心重复加锁时仅递增递归计数(上限 255,超出触发configASSERT),计数归零才真正释放自旋锁,从而支持临界区的嵌套进入/退出。
Advanced Configuration:rp2040_config.h 中的底层配置项
README 指出,部分控制底层实现细节的额外config选项定义在 include/rp2040_config.h。该头文件在 freertos_sdk_config.h 中被一并引入,其全部选项均可被用户在FreeRTOSConfig.h中预先定义以覆盖默认值:
| 配置项 | 默认值 | 含义 |
|---|---|---|
configUSE_DYNAMIC_EXCEPTION_HANDLERS | 当PICO_NO_RAM_VECTOR_TABLE == 1时为0,否则为1 | 是否在运行期通过 SDK 的exception_set_exclusive_handler为各核心动态安装异常处理函数(PendSV / SysTick / SVC)。若用户为每个核心设置了不同的向量表偏移(RAM vector table),需要置 1 动态装载;若使用固定向量表则置 0,此时portmacro.h会直接把端口处理函数替换为 SDK 的 weak 符号(isr_pendsv、isr_systick、isr_svcall) |
configSUPPORT_PICO_SYNC_INTEROP | 编译LIB_PICO_SYNC时默认为1 | 使 SDK pico_sync 的 sem/mutex/queue 等原语在 FreeRTOS 任务中调用时行为正确。开启后freertos_sdk_config.h会重定义 SDK 锁实现的相关钩子(lock_get_caller_owner_id、lock_internal_spin_unlock_with_wait/_with_notify/_with_best_effort_wait_or_timeout)指向 port.c 中的端口实现 |
configSUPPORT_PICO_TIME_INTEROP | 编译LIB_PICO_TIME时默认为1 | 使 SDK pico_time 的sleep_ms/sleep_us/sleep_until在 FreeRTOS 任务中被调用时真正在 FreeRTOS 层阻塞(而非忙等),通过sync_internal_yield_until_before钩子调用 port.c 中的xPortSyncInternalYieldUntilBefore实现 |
configTICK_CORE | 0(仅configNUMBER_OF_CORES > 1时生效) | 指定哪个核心处理 SysTick 中断 |
configSMP_SPINLOCK_0/configSMP_SPINLOCK_1 | PICO_SPINLOCK_ID_OS1/PICO_SPINLOCK_ID_OS2 | 端口使用的两个硬件自旋锁编号(ISR 锁与任务锁),默认采用 SDK 为 RTOS 预留的编号,通常无需修改 |
其中configSUPPORT_PICO_SYNC_INTEROP开启后还有两个连锁效果(见 freertos_sdk_config.h):强制PICO_USE_MALLOC_MUTEX=1(malloc 需要线程安全),并把PICO_TIME_SLEEP_OVERHEAD_ADJUST_US默认调整为150(增加可能唤醒延迟的余量)。而 portmacro.h 中的portUSE_DIVIDER_SAVE_RESTORE则由 SDK 的PICO_DIVIDER_DISABLE_INTERRUPTS决定——若硬件除法器未禁中断,任务上下文切换时需保存/恢复除法器状态,栈底填充(portSTACK_LIMIT_PADDING)相应设为4字节。
与 SDK 原生 API 的互通要点
本移植的一个突出价值是 FreeRTOS 任务与非 FreeRTOS 核心/中断上下文可以共用 SDK 同步原语。其实现路径如下:
- pico_sync 互通:开启
configSUPPORT_PICO_SYNC_INTEROP后,SDK 的lock_core系列宏被替换为端口实现。freertos_sdk_config.h将lock_owner_id_t定义为uint32_t,并让lock_get_caller_owner_id()调用 port.c 中的ulPortLockGetCurrentOwnerId();同时以LOCK_INVALID_OWNER_ID = (uint32_t)-1标记无主状态。这样,当 FreeRTOS 任务获取 SDK mutex 时,端口能正确登记当前核心/任务身份,并在释放时唤醒等待者。 - pico_time 互通:开启
configSUPPORT_PICO_TIME_INTEROP后,sync_internal_yield_until_before(t)被替换为xPortSyncInternalYieldUntilBefore(t),使sleep_ms等函数在 FreeRTOS 任务中通过内核阻塞(阻塞到指定absolute_time_t)而不是占用 CPU 忙等。
需要注意的是,这些互通依赖 IRQ FIFO 中断(prvFIFOInterruptHandler):在 port.c 中,调度器启动时会multicore_fifo_clear_irq()/multicore_fifo_drain(),随后为当前核心安装SIO_IRQ_PROC0 + get_core_num()的独占中断处理程序,用于接收来自另一核心的唤醒通知(IRQ 优先级设为portMIN_INTERRUPT_PRIORITY = 255)。
核心配置建议与注意事项
- SMP 开关:在
FreeRTOSConfig.h中设置configNUMBER_OF_CORES(1 或 2)与configTICK_CORE。SMP 移植要求configNUMBER_OF_CORES取值合法(portmacro.h 有编译期检查),双核时portGET_CORE_ID()才映射真实核心号。 - 临界区差异:单核与双核的临界区宏不同——单核(
configNUMBER_OF_CORES == 1)走vPortEnterCritical/vPortExitCritical;双核走vTaskEnterCritical/vTaskExitCritical(及FromISR变体),并配合两个递归自旋锁保护 ISR 与任务调度状态(见 portmacro.h)。 - tick 类型:
TickType_t由configTICK_TYPE_WIDTH_IN_BITS决定(16 位时为uint16_t、portMAX_DELAY = 0xffff;32 位时为uint32_t、portMAX_DELAY = 0xffffffffUL)。32 位 tick 在 32 位架构上读写是原子的(portTICK_TYPE_IS_ATOMIC=1),无需临界区保护。 - 静态分配:若使用
FreeRTOS-Kernel-Static,内核会自动提供静态内存(configKERNEL_PROVIDED_STATIC_MEMORY=1),配合configSUPPORT_STATIC_ALLOCATION=1即可免去堆分配器。
Known Limitations:已知限制
README 明确列出本移植目前唯一的已知限制:
- Tickless idle 尚未经过测试,很可能不可用(Tickless idle has not currently been tested, and is likely non-functional)。
也就是说,低功耗的空闲 tick 抑制模式(configUSE_TICKLESS_IDLE == 1触发的portSUPPRESS_TICKS_AND_SLEEP)在 port.c 中虽有条件编译的实现骨架,但官方未验证、不建议在正式产品中直接依赖。如需低功耗,请等待官方后续验证,或在深入测试vPortSuppressTicksAndSleep行为后再启用。
小结
FreeRTOS-Kernel 的 RP2040 SMP 移植是一套「零拷贝、纯 CMake INTERFACE 库」的现代集成方案:通过include(FreeRTOS_Kernel_import.cmake)或add_subdirectory即可接入基于 Pico SDK 的工程,再链接FreeRTOS-Kernel(+ 任选 Heap1~5)即获得运行在单核/双核上的完整内核;rp2040_config.h中的少量配置项控制异常处理器装载方式、pico_sync/pico_time 互通与自旋锁编号;已知限制为 tickless idle 未经验证。理解 port.c、portmacro.h 与 library.cmake 的实现,能帮助你在遇到调度或同步问题时有据可查、快速定位。
- 操作系统
- 嵌入式
- RTOS
- 嵌入式OS
【免费下载链接】FreeRTOS-Kernel
FreeRTOS kernel files only, submoduled into https://github.com/FreeRTOS/FreeRTOS and various other repos.
相关推荐
深入解析仓颉 stringbuilder 架构:StringBuilder 工具类目录结构、接口设计与工程实践指南
深入解析仓颉 stringbuilder 架构:StringBuilder 工具类目录结构、接口设计与工程实践指南 stringbuilder 是一个 更自由、
并发编程vue-admin-better 组件库怎么选?省 230KB、表格快 33%,实测告诉你答案
vue admin better 组件库怎么选?省 230KB、表格快 33%,实测告诉你答案 管理后台首屏白屏 3 秒、大表格滚到第 50 行就掉帧——这就是
嵌入式硬件开发ggwave 在 RP2040 上的声音数据接收实战:基于 Raspberry Pi Pico 的 rp2040-rx 示例全解析
ggwave 在 RP2040 上的声音数据接收实战:基于 Raspberry Pi Pico 的 rp2040 rx 示例全解析 导读 本文以 ggwave
通信物联网嵌入式
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考