☰
Zephyr BSP: 21-Zephyr SoC Port 的最小骨架
2026/9/29 4:18:29 网站建设 项目流程

摘要:本文是 Zephyr BSP 系列的开篇,目标是在不编写任何 UART/GPIO/Clock Driver 的前提下,搭建一个"示例公司 SoC"(ACME123)的最小 SoC Port 骨架。文章依次讲解 SoC Port 与 Board Port 的本质区别,并逐一拆解六个核心文件:soc.yml(SoC 身份)、soc.h(C 语言定义)、Kconfig.soc(SoC 选择)、Kconfig(硬件能力)、Kconfig.defconfig(默认值)与CMakeLists.txt(构建入口)。最后强调 SoC Port 只建立身份与构建骨架,真正的硬件描述需进入dtsi,并梳理了从 SoC Port 到完整 BSP 的演进路径与 ARM 架构边界。

Zephyr SoC Port 的最小骨架
前面 16~20 篇,我们已经把:

Devicetree → dependency ordinal → struct device → Driver → SoC/Board

这一整条链路拆开了。
从这一篇开始,真正进入你的目标:
把公司的 SoC 做成 Zephyr BSP。

Zephyr 当前的 SoC Porting Guide 明确规定了 SoC 支持的基本目录和文件;其中 soc.yml、soc.h、Kconfig.soc、CMakeLists.txt 是核心骨架。

这一篇先不写 UART Driver、不写 GPIO Driver、不写 Clock Driver。

我们只做一件事:

让一个"示例公司 SoC"能够被 Zephyr 识别、配置并参与编译。

1. 先明确:SoC Port 到底在做什么?

你最终想达到的是:

Zephyr │ ▼ ┌────────────┐ │ Your SoC │ │ MYSoC1234 │ └────────────┘ │ ┌────────────┼────────────┐ ▼ ▼ ▼ Cortex-M4 UART GPIO │ │ │ └────────────┼────────────┘ ▼ Your Board │ ▼ hello_world

注意一个非常重要的概念:
SoC Port ≠ Board Port

SoC 描述的是:
芯片本身有什么。

Board 描述的是:
这块开发板上把这些东西怎么接出来。

例如你的公司芯片:

MYSoC1234 CPU ├── Cortex-M4 ├── NVIC └── SysTick Memory ├── Flash └── SRAM Peripherals ├── UART0 ├── UART1 ├── GPIO0 ├── TIMER0 └── SPI0

而开发板:

MY-BOARD-A MYSoC1234 │ ├── UART0 → USB-UART ├── GPIO0 → LED ├── GPIO1 → Button └── SPI0 → Sensor

因此:

SoC Port ↓ 让 Zephyr 认识 MYSoC1234 Board Port ↓ 告诉 Zephyr MY-BOARD-A 怎么使用 MYSoC1234

这也是为什么官方 Board Porting Guide 要求 Board 建立在已经支持的 SoC 之上。

2. 今天我们先造一个示例 SoC

为了以后你的公司 SoC 可以直接套这个结构,我们假设:

Vendor: acme SoC: acme123 Architecture: ARM CPU: Cortex-M4

最终希望形成:

zephyr/ └── soc/ └── acme/ └── acme123/ ├── soc.yml ├── soc.h ├── CMakeLists.txt ├── Kconfig ├── Kconfig.soc └── Kconfig.defconfig

这正是当前 Zephyr SoC Porting Guide 给出的基本 SoC 骨架。

3. 第一个文件:soc.yml

这是一个非常容易被误解的文件。

它不是 Devicetree。

它也不是:

SoC 有哪些 UART? SoC 有哪些 GPIO? UART 地址是多少?

这些信息不应该放在这里。
soc.yml 描述的是:
SoC 在 Zephyr Hardware Model 中的身份。

最简单:

socs: - name: acme123

也就是说:

Zephyr Hardware Model Vendor │ └── ACME │ └── acme123

如果你的公司以后有:

ACME123 ACME125 ACME128

甚至:

ACME SoC Family │ ├── ACME100 Series │ ├── ACME101 │ └── ACME102 │ └── ACME200 Series ├── ACME201 └── ACME202

那么 soc.yml 就可以描述 family / series / SoC 的层级。

例如:

family: - name: acme_m series: - name: acme1 socs: - name: acme123 - name: acme125

官方文档也是通过 family → series → socs 来描述这种层级关系。

4. 第二个文件:soc.h

这个文件看起来很普通:

#ifndefZEPHYR_SOC_ACME_ACME123_SOC_H_#defineZEPHYR_SOC_ACME_ACME123_SOC_H_#defineACME123_SOC_ID0x1234#endif

它的意义是:
给 Zephyr 的 C/C++ 世界提供 SoC-specific 定义。

例如以后可能出现:

#defineACME123_SOC_ID0x1234#defineACME123_FLASH_SIZE0x00080000#defineACME123_SRAM_SIZE0x00010000

或者:

#defineACME123_HAS_FPU#defineACME123_HAS_CACHE

但要注意:

不要把整个 SoC 硬件描述都塞进 soc.h。

例如:

#defineUART0_BASE0x40000000#defineGPIO0_BASE0x40010000#defineTIMER0_BASE0x40020000

这种内容未来通常应该通过:

Devicetree

来描述。

5. 第三个文件:Kconfig.soc

这是整个 SoC Port 最重要的文件之一。

假设:

soc.yml acme123

那么我们需要告诉 Kconfig:

SOC=acme123

最小版本可以是:

config SOC_ACME123 bool config SOC default"acme123"ifSOC_ACME123

于是:

Kconfig │ ▼SOC_ACME123=y │ ▼SOC="acme123"

6. 为什么需要两个东西?

你可能会问:
为什么不是直接:

config SOC default"acme123"

而要:

SOC_ACME123 │ ▼SOC="acme123"

因为 Zephyr 的 Hardware Model 需要一个可选择的 SoC symbol。
也就是说:

SOC_ACME123

表示:
当前选择了哪个 SoC。
而:

SOC="acme123"

表示:
当前 SoC 的名字是什么。

官方当前的 Kconfig.soc 示例也是通过 SOC_<SOC_NAME> 最终给全局 SOC 字符串提供 default。

7. 如果有 Series / Family

假设你的公司 SoC 以后是:

ACME Family │ └── ACME1 Series │ ├── ACME123 ├── ACME125 └── ACME128

那么:

config SOC_FAMILY_ACME bool config SOC_SERIES_ACME1 boolselectSOC_FAMILY_ACME config SOC_ACME123 boolselectSOC_SERIES_ACME1 config SOC_FAMILY default"acme"ifSOC_FAMILY_ACME config SOC_SERIES default"acme1"ifSOC_SERIES_ACME1 config SOC default"acme123"ifSOC_ACME123

于是:

SOC_FAMILY │ ▼ acme │ ▼ SOC_SERIES │ ▼ acme1 │ ▼ SOC │ ▼ acme123

这就是以后公司真正做产品线时非常重要的结构。

8. 第四个文件:Kconfig

这个文件和 Kconfig.soc 不一样。

可以把它理解成:

Kconfig.soc ↓"我是谁?"Kconfig ↓"我有什么硬件能力?"

例如:

ifSOC_ACME123 config ACME123_HAS_UART bool default y config ACME123_HAS_GPIO bool default y endif

或者 CPU 相关:

config SOC_ACME123selectARMselectCPU_HAS_FPU

官方文档特别强调:
Kconfig.soc 主要用于 SoC tree 本身的选择;需要选择一般 Zephyr Kconfig 能力时,应使用 Kconfig。
这个区别以后会非常重要。

9. 第五个文件:Kconfig.defconfig

这个文件主要用来提供:

SoC-specific 默认值。

例如:

ifSOC_ACME123 config NUM_IRQS default64endif

意思:

如果选择 ACME123 │ ▼ NUM_IRQS 默认=64

而不是强制用户:

CONFIG_NUM_IRQS=64

官方要求 Kconfig.defconfig 中的内容通常放在:

ifSOC_ACME123... endif

之内。

10. 第六个文件:CMakeLists.txt

这是 SoC Port 进入 Build System 的入口。

最小可以:

zephyr_include_directories(.)

以后慢慢增加:

zephyr_sources(soc.c clock.c)

或者:

zephyr_include_directories(.include)

再以后可能变成:

CMakeLists.txt │ ├── startup ├── clock ├── pinctrl ├── interrupt └── system

官方说明 CMakeLists.txt 可以定义 SoC 所需要的 source files、include paths,并指定基础 linker script。

11. 到这里,我们已经有了一个"最小 SoC 骨架"

完整结构:

soc/ └── acme/ └── acme123/ │ ├── soc.yml │ ├── soc.h │ ├── CMakeLists.txt │ ├── Kconfig.soc │ ├── Kconfig │ └── Kconfig.defconfig

可以把它记成:

ACME123 SoC │ ┌──────────┼──────────┐ │ │ │ ▼ ▼ ▼ soc.yml Kconfig CMake │ │ │ │ │ └── 编译什么 │ │ │ └───────────── SoC 配置 │ └──────────────────────── SoC 身份

12. 但是这里有一个关键问题

到目前为止:

ACME123

虽然已经存在于 Zephyr 的 SoC tree 中,
但是 Zephyr 还不知道 ACME123 的硬件长什么样。
我们现在没有:

CPU RAM Flash UART GPIO Timer Interrupt Clock

所以还不能真正运行:

device_is_ready(...)

因为:

SoC Port

只是建立了SoC 身份和构建骨架。

真正的硬件描述下一步要进入:

dts/<ARCH>/<VENDOR>/<soc>.dtsi

12.1 最小实战:acme123.dtsi

既然真正的硬件描述要进入dtsi,那我们就直接动手写一个最小可用的acme123.dtsi。它位于:

dts/arm/acme/acme123.dtsi

最小版本只需要三个节点:cpus、sram0、flash0。

/* * dts/arm/acme/acme123.dtsi * ACME123 SoC 最小 Devicetree 描述 */ /dts-v1/; #include <arm/armv7-m.dtsi> / { soc { sram0: memory@20000000 { compatible = "mmio-sram"; reg = <0x20000000 0x00010000>; }; flash0: flash@08000000 { compatible = "soc-nv-flash"; reg = <0x08000000 0x00080000>; }; }; }; &cpus { cpu0: cpu@0 { compatible = "arm,cortex-m4"; }; };

每个节点与 soc.yml / Kconfig 的对应关系

很多初学者会困惑:dtsi里的节点和前面写的soc.yml、Kconfig到底是什么关系?其实它们各司其职:

soc.yml │ └── 告诉 Zephyr Hardware Model:ACME123 是谁 │ Kconfig.soc / Kconfig │ └── 告诉 Build System:ACME123 有哪些可配置能力 │ acme123.dtsi │ └── 告诉 Devicetree:ACME123 的硬件长什么样

具体到每个节点:

cpus │ ├── 对应 soc.yml 中的 CPU 描述(Cortex-M4) ├── 对应 Kconfig 中的selectARM /selectCPU_HAS_FPU └── 告诉 Zephyr:这个 SoC 用哪个 CPU 核
sram0 │ ├── 对应 soc.h 中的 ACME123_SRAM_SIZE(0x00010000) ├── 对应 Kconfig.defconfig 中的 SRAM 相关默认值 └── 告诉 Zephyr:SRAM 的起始地址和大小
flash0 │ ├── 对应 soc.h 中的 ACME123_FLASH_SIZE(0x00080000) ├── 对应 Kconfig.defconfig 中的 Flash 相关默认值 └── 告诉 Zephyr:Flash 的起始地址和大小

Board DTS 如何 include 这个 dtsi

acme123.dtsi描述的是芯片本身,而 Board DTS 描述的是开发板怎么用这颗芯片。所以 Board DTS 的第一件事就是把这个 SoC 的 dtsi 包含进来:

/* * boards/acme/acme_devkit/acme_devkit.dts */ /dts-v1/; #include <acme/acme123.dtsi> / { model = "ACME ACME123 DevKit"; compatible = "acme,acme_devkit"; chosen { zephyr,console = &uart0; zephyr,sram = &sram0; zephyr,flash = &flash0; }; };

注意这里:

#include <acme/acme123.dtsi>

这个 include 路径是相对于 Zephyr 的dts/目录的。也就是说,Zephyr 会自动在:

zephyr/dts/

下面搜索acme/acme123.dtsi。这也是为什么官方要求 SoC Devicetree 文件必须放在dts/<ARCH>/<VENDOR>/目录下的原因——Board DTS 才能用统一的 include 路径找到它。

一旦 Board DTS include 了acme123.dtsi,sram0和flash0就自动成为 Board 的一部分,chosen节点再把它们指定为 Zephyr 的默认 SRAM 和 Flash,这样hello_world才能知道把代码和数据放在哪里。

官方 SoC Porting Guide 规定 SoC Devicetree 文件位于架构/厂商对应目录,并且每个使用该 SoC 的 Board DTS 都需要包含它。

13. 所以整个 SoC Port 可以分成几个层次

这点你以后做公司 BSP 时一定要记住:

SoC Port │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ Hardware Model Build Model Hardware Description │ │ │ soc.yml Kconfig DTSI │ │ │ │ │ ├── CPU │ │ ├── RAM │ │ ├── UART │ │ ├── GPIO │ │ └── Timer │ │ │ ├── SOC │ ├── CPU │ └── features │ └── Family/Series/SoC

这就是你从"会写 Zephyr Driver"进入"做 Zephyr BSP"之后需要建立的思维方式。

14. 最小骨架和完整 BSP 的区别

今天的最小骨架

soc/acme/acme123/ ├── soc.yml ├── soc.h ├── Kconfig ├── Kconfig.soc ├── Kconfig.defconfig └── CMakeLists.txt

目标:

Zephyr │ ▼ 知道 ACME123 是一个合法 SoC

后面的完整 SoC Port

最终会逐渐变成:

soc/ └── acme/ └── acme1/ │ ├── acme123/ │ │ ├── soc.yml │ ├── soc.h │ ├── CMakeLists.txt │ ├── Kconfig │ ├── Kconfig.soc │ ├── Kconfig.defconfig │ │ │ ├── startup.c │ ├── system.c │ ├── clock.c │ └──... │ └──...

同时:

dts/ └── arm/ └── acme/ ├── acme123.dtsi └── acme125.dtsi

然后:

drivers/ ├── serial/ │ └──... ├── gpio/ │ └──... ├── clock_control/ │ └──... └── pinctrl/ └──...

14.1 从最小骨架到完整 BSP 的演进路线图

把上面「今天的最小骨架」和「后面的完整 SoC Port」串起来,整个演进过程可以拆成 5 个阶段。每个阶段都有明确的目标、新增文件、验证方法和预计工作量,方便你规划公司 BSP 的落地节奏。

阶段目标新增文件验证方法预计工作量
1. 最小骨架让 Zephyr 识别 ACME123 是一个合法 SoC,能参与编译soc.yml、soc.h、Kconfig.soc、Kconfig、Kconfig.defconfig、CMakeLists.txtwest build能通过,CONFIG_SOC="acme123"生效0.5~1 天
2. DTSI 硬件描述让 Devicetree 知道 ACME123 的 CPU、SRAM、Flash 长什么样dts/arm/acme/acme123.dtsiwest build -t dtc无报错,chosen能指向sram0/flash01~2 天
3. 启动代码让 CPU 能完成复位、时钟、内存初始化,跑通hello_worldstartup.c、system.c、clock.c、linker.ldhello_world在板子上打印输出3~5 天
4. 驱动移植让 UART/GPIO/Clock 等外设能被device_is_ready()识别并驱动drivers/serial/、drivers/gpio/、drivers/clock_control/、drivers/pinctrl/对应samples跑通,外设读写正常5~10 天
5. Board 集成把 SoC 接到具体开发板上,形成完整 BSPboards/acme/acme_devkit/(board.c、board.dts、board_defconfig、Kconfig.board)官方samples全量跑通,twister测试通过3~5 天

几点说明:

  • 阶段 1 和 2 可以并行:soc.yml建立身份,acme123.dtsi建立硬件描述,两者互不依赖,可以同时开工。
  • 阶段 3 是分水岭:从「能编译」到「能运行」,需要真正理解 ARM Cortex-M 的启动流程和链接脚本,工作量会明显上升。
  • 阶段 4 是长期投入:每个外设驱动都是独立的一块,建议按「先 UART(能打印调试)→ 再 GPIO(能点灯)→ 最后 Clock/Pinctrl」的顺序推进。
  • 阶段 5 是收尾:Board 集成本身不难,但要做twister回归测试,确保整个 BSP 在 CI 里稳定。

最后才有:

boards/ └── acme/ └── acme_devkit/

15. 最重要的一张图

把你前面 01~20 篇和现在的 BSP 学习串起来:

Zephyr BSP │ ┌────────────┴────────────┐ │ │ SoC Board │ │ ┌──────┼──────┐ │ │ │ │ │ ▼ ▼ ▼ ▼ soc.yml Kconfig DTSI board DTS │ │ │ │ │ │ ├── CPU │ │ │ ├── RAM │ │ │ ├── UART │ │ │ └── GPIO │ │ │ │ │ └──────────┐ │ │ │ │ └─────────────────┼──────────────┘ ▼ Zephyr Build │ ▼ Devicetree │ ▼ dependency ordinal │ ▼ DEVICE_DT_DEFINE │ ▼ struct device │ ▼ Driver │ ▼ Application

这张图其实就是你这个专栏从:

01 →20

最终要汇聚到:

公司 SoC BSP

的主线。

16. 还有一个非常重要的边界

如果你的公司 SoC 是:

ARM Cortex-M4

那么:
你不需要重新做 Architecture Port。
因为 ARM Cortex-M 架构已经是 Zephyr 支持的架构。

你的工作是:

Architecture │ │ 已经存在 ▼ ARM Cortex-M │ ▼ SoC Port │ ▼ ACME123 │ ▼ Board Port │ ▼ ACME123-EVK

只有当你们公司的 SoC 使用一个 Zephyr 尚未支持的 ISA/ABI,例如一个全新的 CPU 架构时,才会上升到 Architecture Port。Zephyr 官方也明确把 Architecture Port 定义为支持新的 ISA/ABI,并涉及启动、异常/中断、线程上下文切换等核心机制。

17. 因此,"SoC Port 最小骨架"的真正目标

不是:

“创建几个文件。”

而是建立下面这个认识:

公司 SoC │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ Hardware Software Build identity config integration │ │ │ soc.yml Kconfig CMake │ │ │ └────────────┼────────────┘ │ ▼ Zephyr recognizes your SoC

然后下一层才是:

ACME123 │ ▼ acme123.dtsi │ ┌────────────┼────────────┐ ▼ ▼ ▼ CPU RAM UART0 │ ▼ UART Driver │ ▼ struct device

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

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

立即咨询