☰
Zephyr BSP: 38-多板多芯片支持
2026/9/29 4:19:38 网站建设 项目流程

摘要:本文围绕 Zephyr BSP 中 Multi-Board / Multi-Chip 的核心问题展开:哪些能力放在 SoC 层、哪些放在 Board 层、哪些通过 Devicetree/Kconfig 表达。文章从「SoC 描述芯片有什么,Board 描述板子实际用了什么」这一第一原则出发,依次讲解 Family → Variant 的 SoC 组织方式、Board 层与 Devicetree 的职责划分、Board 如何选择 SoC、Kconfig 与 Devicetree 的分工、Pinmux 与 Package Variant 的处理,

目录

  • 引言
  • Multi-Board / Multi-Chip:一个 SoC,如何同时支持多个 Board?
  • 一、先建立一个非常重要的层次
  • 二、举一个 Company SoC Family
  • 三、正确思路:Family → Variant
  • 四、Board 层又是什么?
  • 五、Devicetree 在这里就发挥作用了
  • 六、Multi-Board 的典型结构
  • 六点五、同一 SoC 下不同封装 Board 的目录组织
  • 七、那么 Board 如何选择 SoC?
  • 七点五、实战示例:cs32a1_evb 的完整 SoC 选择链路
  • 七点五·补、实战示例:cs32a2_evb 的 SoC 选择差异
  • 七点七、实战示例:Pinmux 与 Package Variant 处理

引言

本文面向正在或即将从事 Zephyr BSP 开发的工程师。阅读本文前,建议你已熟悉 Devicetree 的基本语法(节点、属性、覆盖)以及 Kconfig 的配置机制(symbol、select、source),这样能更顺畅地跟上后面的实战示例。读完本文,你将掌握 Multi-Board / Multi-Chip 的层次划分原则——清楚哪些能力放在 SoC 层、哪些放在 Board 层、哪些通过 Devicetree/Kconfig 表达,并能独立设计出可维护的公司级 BSP 目录结构,让「一个 SoC 家族同时支持多个 Board」变得优雅而可控。

最后给出公司级 BSP 的目录设计与「Board 是产品、SoC 是平台、Driver 是能力、Devicetree 是连接三者的描述语言」的脑内模型。

Multi-Board / Multi-Chip:一个 SoC,如何同时支持多个 Board?

前面我们已经把一条完整的Company SoC → Zephyr BSP链路走完了:

Zephyr │ ┌──────┴──────┐ │ Application │ └──────┬──────┘ │ Zephyr Driver │ Devicetree │ Board / SoC │ Company HAL │ Company SoC

但真实公司的 BSP 通常不会只有:

company_soc_x └── board_x

而是更像:

Company SoC Family │ ├── SoC-A │ ├── Board-A1 │ └── Board-A2 │ ├── SoC-B │ ├── Board-B1 │ └── Board-B2 │ └── SoC-C └── Board-C1

甚至:

SoC-A │ ├──128-pin package ├──64-pin package └──48-pin package

这时候最重要的问题就来了:

哪些东西应该放在 SoC 层?哪些放在 Board 层?哪些应该通过 Devicetree/Kconfig 选择?

这就是今天这一篇的核心。

一、先建立一个非常重要的层次

建议先记住:

Board │ ┌───────┴────────┐ │ │ SoC/Chip Board HW │ │ ┌────┴────┐ ┌────┴────┐ │ │ │ │ CPU Peripherals LED Button │ │ └────┬────┘ │ Vendor HAL

换句话说:

SoC 描述「芯片有什么」。
Board 描述「这块板子实际上用了什么」。

这是 Multi-Board / Multi-Chip 的第一原则。

二、举一个 Company SoC Family

假设你的公司有两个芯片:

Company SoC Family │ ├── CS32A1 │ └── CS32A2

它们非常接近:

FeatureCS32A1CS32A2
Cortex-M4✓✓
UART46
SPI24
I2C23
GPIO3264
Timer48
ADC12

那么 Zephyr 不应该复制两套完整 BSP。

错误:

soc/ ├── cs32a1/ │ ├── uart.c │ ├── spi.c │ ├── clock.c │ ├── timer.c │ └──... │ └── cs32a2/ ├── uart.c ├── spi.c ├── clock.c ├── timer.c └──...

这样很快会出现:

uart.c uart.c uart.c uart.c

大量代码重复。

三、正确思路:Family → Variant

更合理的是:

soc/ └── company/ └── cs32/ ├── common/ │ ├── clock.c │ ├── reset.c │ ├── uart.c │ └──... │ ├── cs32a1/ │ └── soc.c │ └── cs32a2/ └── soc.c

也就是:

CS32 Family │ ┌──────────┴──────────┐ │ │ Common HAL Variant │ │ ┌─────┼─────┐ ┌────┴────┐ │ │ │ │ │ UART SPI I2C A1 A2

核心思想:
共享能力共享代码,芯片差异单独处理。

四、Board 层又是什么?

假设:

CS32A1

有两块开发板:

CS32A1 │ ├── cs32a1_evb │ └── cs32a1_devkit

两个板子芯片完全一样。
但是:

EVB: LED → GPIOA.5 Button → GPIOC.2 UART → UART0

而:

DevKit: LED → GPIOB.3 Button → GPIOA.7 UART → UART2

那么:

SoC │ ├── UART0 ├── UART1 ├── UART2 ├── GPIOA ├── GPIOB └── GPIOC

是 SoC 的能力。
而:

LED → GPIOA.5

是 Board 的连接关系。
所以不能把:

LED_GPIO=GPIOA_5;

硬编码进 SoC driver。

五、Devicetree 在这里就发挥作用了

例如:

/{aliases{led0=&user_led;};leds{compatible="gpio-leds";user_led: led_0{gpios=<&gpioa5GPIO_ACTIVE_HIGH>;};};};

另一个 Board:

/{aliases{led0=&user_led;};leds{compatible="gpio-leds";user_led: led_0{gpios=<&gpiob3GPIO_ACTIVE_HIGH>;};};};

Application 完全不需要知道:

GPIOA.5

还是:

GPIOB.3

Application 只知道:

GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios)

这就是:
Board variation 被 Devicetree 吸收了。

六、Multi-Board 的典型结构

最终可能变成:

boards/ └── company/ └── cs32a1/ ├── cs32a1_evb/ │ ├── cs32a1_evb.dts │ ├── cs32a1_evb.yaml │ └── CMakeLists.txt │ └── cs32a1_devkit/ ├── cs32a1_devkit.dts ├── cs32a1_devkit.yaml └── CMakeLists.txt

六点五、同一 SoC 下不同封装 Board 的目录组织

上一节我们看到了cs32a1_evb和cs32a1_devkit两块板子共用同一颗 CS32A1 的目录结构。这一节我们把视角再往下推一层:同一颗 SoC,还有不同封装——cs32a1有 64-pin 和 48-pin 两种封装,对应两块板子cs32a1_64evb和cs32a1_48evb。它们用的是同一颗 SoC、同一个 dtsi、同一套 driver,唯一的差异就是封装引出的引脚数量不同。

完整目录树:

boards/ └── company/ └── cs32/ ├── cs32a1_64evb/# ★ 64-pin 封装的 EVB 板│ ├── cs32a1_64evb.dts# 板级 Devicetree:使能外设 + 选择引脚│ ├── cs32a1_64evb.yaml# 声明 soc: cs32a1 + 板卡信息│ ├── Kconfig.defconfig# 选择 SOC_CS32A1_PACKAGE_64│ └── CMakeLists.txt │ └── cs32a1_48evb/# ★ 48-pin 封装的 EVB 板├── cs32a1_48evb.dts# 板级 Devicetree:使能外设 + 选择引脚├── cs32a1_48evb.yaml# 声明 soc: cs32a1 + 板卡信息├── Kconfig.defconfig# 选择 SOC_CS32A1_PACKAGE_48└── CMakeLists.txt

对应的 SoC 层(同一颗 SoC,不复制):

soc/ └── company/ └── cs32/ ├── CMakeLists.txt ├── Kconfig │ ├── cs32a1/# ★ 唯一的 SoC 目录,被两块板子共用│ ├── soc.c │ └── Kconfig# 内含 Package Variant choice│ └── cs32a2/ ├── soc.c └── Kconfig

dts 层(同一份 dtsi,被两块板子共用):

dts/ └── arm/ └── company/ └── cs32/ └── cs32a1.dtsi# ★ 唯一的 SoC dtsi,含 #if 条件包含

关键点:

  1. 两块板子共享同一颗 SoC:cs32a1_64evb和cs32a1_48evb的board.yaml里soc: cs32a1完全一样,都指向soc/company/cs32/cs32a1/和dts/arm/company/cs32/cs32a1.dtsi。
  2. 差异只在封装:64-pin 封装有 PB13,48-pin 没有。这个差异通过Kconfig.defconfig选择SOC_CS32A1_PACKAGE_64或SOC_CS32A1_PACKAGE_48来表达,dtsi 里用#if defined(CONFIG_SOC_CS32A1_HAS_PB13)裁掉不可用的引脚节点。
  3. Board 层只做两件事:在board.yaml声明soc: cs32a1,在Kconfig.defconfig选择封装,在.dts里使能外设并选择具体引脚。

这就是「同一 SoC、不同封装」的目录组织方式——SoC 层只维护一份,封装差异通过 Kconfig + 条件包含表达,Board 层各自独立。这正是 Multi-Board 模型在 Package Variant 层面的落地。

而 SoC:

soc/ └── company/ └── cs32/ ├── CMakeLists.txt ├── Kconfig │ ├── cs32a1/ │ ├── soc.c │ └── Kconfig │ └── cs32a2/ ├── soc.c └── Kconfig

七、那么 Board 如何选择 SoC?

这是整个 Multi-Chip BSP 最关键的一层。

Board:

cs32a1_evb

需要告诉 Zephyr:

我的 SoC 是 CS32A1

概念上:

BOARD │ ▼ SOC │ ▼ CPU / HAL / Drivers

也就是:

cs32a1_evb │ ▼ CS32A1 │ ▼ Cortex-M4

七点五、实战示例:cs32a1_evb 的完整 SoC 选择链路

下面以cs32a1_evb为例,把「Board 如何选择 SoC」这条链路完整走一遍。整个过程分三步:board.yaml 声明 SoC → Kconfig 使能 SoC → Devicetree 使能外设。

第一步:board.yaml —— 声明「这块板子用哪个 SoC」

# boards/company/cs32/cs32a1_evb/cs32a1_evb.yamlidentifier:cs32a1_evb# Board 的唯一标识,构建时用 -b cs32a1_evb 指定name:CS32A1 Evaluation Board# 人类可读的板卡名称type:board# 类型固定为 boardarch:arm# 架构:ARM Cortex-M4toolchain:-zephyr# 使用 Zephyr SDK 工具链ram:64# 板载 RAM 大小(KB),供链接脚本使用flash:256# 板载 Flash 大小(KB)soc:cs32a1# ★ 关键:声明本 Board 使用的 SoC 是 cs32a1# Zephyr 据此找到 soc/company/cs32/cs32a1/ 目录

作用:soc: cs32a1是 Board 与 SoC 之间的「契约」。Zephyr 构建系统读取这一行后,会自动把cs32a1对应的 SoC 目录加入编译,并触发后续 Kconfig 的默认选择。

第二步:Kconfig —— 让 CONFIG_SOC_CS32A1 生效

Board 声明了soc: cs32a1后,Zephyr 会去 SoC 的 Kconfig 里找到对应的 symbol:

# soc/company/cs32/cs32a1/Kconfig config SOC_CS32A1 bool "CS32A1 SoC" select CPU_CORTEX_M4 # 自动选中 Cortex-M4 内核 select HAS_FLASH_LOAD_OFFSET # 声明支持 Flash 加载偏移 help Company CS32A1 SoC, Cortex-M4 based.
# soc/company/cs32/Kconfig —— 家族入口,把各 Variant 挂进来 config SOC_SERIES_CS32 bool "CS32 Series" select SOC_FAMILY_COMPANY # 选中公司 SoC Family source "soc/company/cs32/cs32a1/Kconfig" source "soc/company/cs32/cs32a2/Kconfig"

构建时,Zephyr 根据board.yaml的soc: cs32a1自动生成默认配置:

# build/zephyr/.config 中自动生成的关键项CONFIG_SOC_CS32A1=y# ★ 由 board.yaml 的 soc 字段自动推导CONFIG_SOC_CS32A2=n# 另一个 Variant 未被选中CONFIG_SOC_SERIES_CS32=y# 家族被选中CONFIG_CPU_CORTEX_M4=y# 由 select CPU_CORTEX_M4 自动带上

作用:CONFIG_SOC_CS32A1=y让 Zephyr 知道「当前编译的是 CS32A1」,从而把soc/company/cs32/cs32a1/soc.c编进来,并让soc/company/cs32/common/里的共享代码(uart.c、clock.c 等)以 CS32A1 的寄存器定义编译。

第三步:Devicetree —— 使能 Board 实际用到的外设

SoC 的.dtsi里,UART0 默认是disabled(芯片有,但板子不一定用):

/* dts/arm/company/cs32/cs32a1.d

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

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

立即咨询