轻松学习Zephyr BSP: 40-公司 BSP 的完整生命周期
摘要:本文是 Zephyr BSP 系列(第 20~40 篇)的收束篇,系统梳理一个公司 SoC 从芯片定义到 BSP 可交付的完整生命周期。全文按 18 个阶段展开:从 SoC 规格、Company HAL、Zephyr SoC Port、Startup、中断控制器、时钟/复位,到 Devicetree、Binding、驱动、Kconfig、CMake、Linker、Board、Flash/Debug、Validation、SoC Family、Release 直至 CI 落地。核心观点是:BSP 不是"一个 boards/xxx/ 目录",而是一条从 Silicon 到 Application 的软件供应链。文章最后给出公司 BSP 需要维护的"四条线"(硬件线、软件抽象线、构建线、验证线),帮助读者建立从"学 Zephyr"到"交付可维护 BSP"的工程视角。公司 BSP 的完整生命周期到第 39 篇,我们已经把SoC Family Architecture讲清楚了。现在进入一个非常重要的收束篇:一个公司 SoC,从"芯片刚出来"到"Zephyr BSP 真正可交付",整个生命周期到底是什么?这篇不再只讲某一个文件,而是把前面20~39 篇全部串起来。一、先建立一个总模型一个真正的公司 BSP,不是:写几个 driver ↓ 能跑 blinky ↓ 结束而应该是:SoC Silicon ↓ Company HAL ↓ Zephyr SoC Port ↓ Devicetree / Binding ↓ Kconfig ↓ Drivers ↓ Board ↓ Build System ↓ Linker / Memory ↓ Flash / Debug / Runner ↓ BSP Validation ↓ Release / Maintenance再进一步:┌──────────────────────┐ │ SoC Silicon │ └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ Company HAL │ │ Clock / IRQ / GPIO │ │ UART / SPI / I2C... │ └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ Zephyr SoC Port │ │ startup / soc.c │ │ CMake / Kconfig │ └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ Devicetree + DT │ │ hardware description │ └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ Zephyr Drivers │ │ UART GPIO SPI I2C... │ └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ Board │ │ pins / LEDs / buttons│ └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ Build / Link / Flash │ └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ BSP Validation │ └──────────────────────┘这就是公司 BSP 的完整生命周期。二、第一阶段:SoC 还没出来这是很多 BSP 工程师容易忽略的一个阶段。芯片甚至还没有回来。但是 BSP 工作已经可以开始。例如公司决定做:Company SoC X1 ├── Cortex-M4 ├──512KB Flash ├──128KB SRAM ├── UART ×3├── SPI ×2├── I2C ×2├── GPIO ├── Timer ├── DMA └── Interrupt Controller此时首先需要建立:SoC specification ↓ memory map ↓ interrupt map ↓ peripheral map ↓ clock tree ↓ reset tree ↓ pinmux也就是说:BSP 的第一份「代码」往往不是 C 代码,而是硬件规格。三、第二阶段:Company HAL 出现芯片团队通常会先提供自己的 HAL。例如:company-hal/ ├── include/ │ ├── company_uart.h │ ├── company_gpio.h │ ├── company_clock.h │ └── company_irq.h │ ├── src/ │ ├── company_uart.c │ ├── company_gpio.c │ ├── company_clock.c │ └── company_irq.c │ └── startup/ └── startup_company.SHAL 负责:寄存器 ↓ Company API例如:company_uart_init();company_uart_write();company_uart_read();这层通常不应该知道:struct device DEVICE_DT_DEFINE()DT_INST_FOREACH_STATUS_OKAY()因为这些是 Zephyr 的概念。所以我们之前第 37 篇讲的边界非常重要:┌─────────────────────────┐ │ Zephyr │ │ │ │ struct device │ │ driver API │ │ devicetree │ │ Kconfig │ └────────────┬────────────┘ │ │ adapter ↓ ┌─────────────────────────┐ │ Company HAL │ │ │ │ company_uart_xxx()│ │ company_clock_xxx()│ │ company_gpio_xxx()│ └────────────┬────────────┘ │ ↓ ┌─────────────────────────┐ │ Registers │ └─────────────────────────┘四、第三阶段:建立 Zephyr SoC Port接下来正式进入 Zephyr。目录可能开始变成:soc/ └── company/ └── cx1/ ├── CMakeLists.txt ├── Kconfig ├── Kconfig.soc ├── soc.c ├── soc.h └──...这时候还不是 Board。而是:告诉 Zephyr:「这个 CPU + SoC 是什么。」例如:Company CX1 ↓ Cortex-M4 ↓ clock ↓ interrupt controller ↓ memory ↓ SoC peripherals这里对应我们之前:21SoC Port Skeleton 下面是一个完整的`soc.c`示例,它把 SoC 初始化、时钟配置和中断控制器初始化串在一起: ```c /* * soc.c - Company CX1 SoC 初始化 * * 该文件是 Zephyr SoC Port 的核心,负责在 Zephyr 内核启动前 * 完成最基本的硬件初始化,让 CPU 具备运行内核的条件。 */#include zephyr/kernel.h#include zephyr/init.h#include soc.h/* 引入 Company HAL 提供的底层接口 */#include company_clock.h#include company_irq.h/* ------------------------------------------------------------------ *1. 时钟配置 * * 目标:把 SoC 的时钟树配置到稳定状态。 * 顺序很重要:先配 PLL,再配总线分频,最后使能外设时钟。 * 如果时钟没配好,后面所有外设(UART/SPI/I2C)都无法工作。 * ------------------------------------------------------------------ */ static void soc_clock_init(void){/*1.1配置系统 PLL:输入8MHz 晶振,倍频到96MHz 主频 */ company_clock_pll_config(COMPANY_PLL_SRC_HSE,8,96);/*1.2配置总线分频:HCLK=96MHz,PCLK1=48MHz,PCLK2=96MHz */ company_clock_bus_config(COMPANY_BUS_HCLK,96);company_clock_bus_config(COMPANY_BUS_PCLK1,48);company_clock_bus_config(COMPANY_BUS_PCLK2,96);/*1.3使能基础外设时钟:GPIO、UART0、SPI0 */ company_clock_enable(COMPANY_CLOCK_GPIO);company_clock_enable(COMPANY_CLOCK_UART0);company_clock_enable(COMPANY_CLOCK_SPI0);}/* ------------------------------------------------------------------ *2. 中断控制器初始化 * * 目标:让 Zephyr 能够正确接收并分发 SoC 的中断。 * 这里只做最基础的 NVIC 配置,具体外设的 IRQ 由各 driver 自行注册。 * ------------------------------------------------------------------ */ static void soc_irq_init(void){/*2.1关闭所有外设中断,保证启动阶段处于干净状态 */ company_irq_disable_all();/*2.2设置中断优先级分组:4 位抢占优先级 +0位子优先级 */ company_irq_set_priority_group(COMPANY_IRQ_PRIO_GROUP_4);/*2.3使能全局中断(由 Zephyr 内核在启动后期统一打开, * 这里只做控制器层面的准备) */ company_irq_global_enable();}/* ------------------------------------------------------------------ *3. SoC 总初始化入口 * * 这是 Zephyr 在启动早期(PRE_KERNEL_1)调用的函数, * 必须在内核调度器运行之前完成所有基础硬件配置。 * ------------------------------------------------------------------ */ static int soc_init(void){/*3.1先配时钟:一切外设工作的前提 */ soc_clock_init();/*3.2再配中断控制器:保证后续 driver 能注册 ISR */ soc_irq_init();/*3.3最后做 SoC 级杂项初始化(如 memory protection 等) */ company_soc_lowlevel_init();return0;}/* 注册为 PRE_KERNEL_1 阶段初始化,优先级0(最早执行) */ SYS_INIT(soc_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);关键点说明:soc_clock_init():先配 PLL 再配分频,最后使能外设时钟,顺序不能颠倒;soc_irq_init():先关中断、再设优先级分组,保证启动阶段干净;SYS_INIT(..., PRE_KERNEL_1, ...):让 Zephyr 在内核启动前自动调用soc_init(),这是 SoC Port 与内核的正式接缝。22 CPU / Architecture23 Startup24 Interrupt Controller25 Clock / Reset下面是一个完整的 Devicetree 节点示例,它把 uart0、gpio0、spi0 等外设统一描述出来: ```dts / { soc { /* UART0:串口控制器,基地址 0x40000000,IRQ 5 */ uart0: uart@40000000 { compatible = "company,cx1-uart"; reg = 0x40000000 0x1000; interrupts = 5; clocks = clk 0; status = "okay"; }; /* GPIO0:通用输入输出控制器,基地址 0x40020000,IRQ 6 */ gpio0: gpio@40020000 { compatible = "company,cx1-gpio"; reg = 0x40020000 0x1000; interrupts = 6; clocks = clk 1; status = "okay"; }; /* SPI0:串行外设接口,基地址 0x40040000,IRQ 7 */ spi0: spi@40040000 { compatible = "company,cx1-spi"; reg = 0x40040000 0x1000; interrupts = 7; clocks = clk 2; status = "okay"; }; }; };对应的 binding YAML 文件内容如下:# dts/bindings/serial/company,cx1-uart.yamldescription:Company