☰
FreeRTOS-Kernel 在 Raspberry Pi Pico RP2040 上的 SMP 移植:使用指南与源码解析
2026/10/3 8:26:57 网站建设 项目流程
  • 操作系统
  • 嵌入式
  • RTOS
  • 嵌入式OS

【免费下载链接】FreeRTOS-Kernel

FreeRTOS kernel files only, submoduled into https://github.com/FreeRTOS/FreeRTOS and various other repos.

项目地址:https://gitcode.com/GitHub_Trending/fr/FreeRTOS-Kernel
点击查看免费下载

本指南以 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 的逻辑):

  1. 环境变量FREERTOS_KERNEL_PATH或 CMake 命令行参数-DFREERTOS_KERNEL_PATH=/path/to/FreeRTOS-Kernel(显式指定,优先级最高);
  2. 如果 import 脚本本身位于 FreeRTOS-Kernel 仓库树内(即直接 include 了仓库中的文件),则自动按相对路径反推出FREERTOS_KERNEL_PATH;
  3. 若PICO_SDK_PATH已定义,且其同级目录存在FreeRTOS-Kernel(即 SDK 与内核并排布局),自动默认到该路径;
  4. 在以Source、FreeRTOS-Kernel、FreeRTOS/Source命名的子目录中搜索内核;
  5. 以上均失败则输出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.0CMakeLists.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_CORE0(仅configNUMBER_OF_CORES > 1时生效)指定哪个核心处理 SysTick 中断
configSMP_SPINLOCK_0/configSMP_SPINLOCK_1PICO_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)。

核心配置建议与注意事项

  1. SMP 开关:在FreeRTOSConfig.h中设置configNUMBER_OF_CORES(1 或 2)与configTICK_CORE。SMP 移植要求configNUMBER_OF_CORES取值合法(portmacro.h 有编译期检查),双核时portGET_CORE_ID()才映射真实核心号。
  2. 临界区差异:单核与双核的临界区宏不同——单核(configNUMBER_OF_CORES == 1)走vPortEnterCritical/vPortExitCritical;双核走vTaskEnterCritical/vTaskExitCritical(及FromISR变体),并配合两个递归自旋锁保护 ISR 与任务调度状态(见 portmacro.h)。
  3. 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),无需临界区保护。
  4. 静态分配:若使用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.

项目地址:https://gitcode.com/GitHub_Trending/fr/FreeRTOS-Kernel
点击查看免费下载

相关推荐

上一篇:为什么这款开源工具能让你的网盘下载速度翻倍?八大网盘直链解析实战指南
下一篇:Ryujinx 使用指南:从安装到性能调优的 5 步实操教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询