摘要:本文延续第 28 篇的 UART 驱动路线,系统讲解如何将 Company SoC 的 GPIO、SPI、I2C、Timer 外设接入 Zephyr 驱动模型。核心思想是:不同外设只是挂接不同的 Zephyr Driver API(
gpio_driver_api、spi_driver_api、i2c_driver_api),本质都是「DeviceTree → Binding → Driver →struct device→ 应用 API」这条链路。文章通过 GPIO 的寄存器操作、DeviceTree 的gpio-controller与 Consumer 用法、SPI 的 Controller/Device 层次、I2C 的传输流程,以及 Timer 作为 System Timer 的特殊地位,逐步画出完整的 Company SoC BSP 架构,并强调 Clock、Reset、Pinmux、IRQ 等底层依赖。最终目标是:换一颗 SoC 时只改 Devicetree、Binding、SoC Driver 与底层资源,应用层代码无需重写。
GPIO / SPI / I2C / Timer:把 Company SoC 的外设驱动接入 Zephyr
第 28 篇我们已经完成了:
Company UART Driver → Zephyr Driver Model → struct device → device_is_ready() → 应用层 API
这一篇继续沿着同一条路线,把 UART 扩展到真正的SoC BSP 外设体系:
Zephyr Application │ ┌──────────────────┼──────────────────┐ ▼ ▼ ▼ GPIO API SPI API I2C API │ │ │ ▼ ▼ ▼ Company GPIO Company SPI Company I2C Driver Driver Driver │ │ │ └──────────────────┼──────────────────┘ ▼ Company SoC Registers │ ┌───────────┼───────────┐ ▼ ▼ ▼ GPIO SPI/I2C Timer Registers Registers Registers而Timer稍微特殊:
Application │ ▼ k_timer / counter / watchdog / system timer │ ▼ Zephyr Timer/Counter subsystem │ ▼ Company Timer Driver │ ▼ Company Timer Registers一、先建立一个最重要的认识
前面 UART 学到的是:
UART │ ├── DeviceTree ├── Binding ├── Kconfig ├── Driver ├── struct device └── APIGPIO / SPI / I2C / Timer本质上完全一样。
区别只是:
不同外设属于不同的 Zephyr Driver API / subsystem。
例如:
| 外设 | Zephyr 对应层 |
|---|---|
| UART | UART Driver API |
| GPIO | GPIO Driver API |
| SPI | SPI Driver API |
| I2C | I2C Driver API |
| Timer | Timer/Counter/System Timer 等 API |
所以不要把 BSP 理解成:
写一个 UART driver 写一个 GPIO driver 写一个 SPI driver 写一个 I2C driver更准确的理解应该是:
Zephyr Driver Model │ ┌─────────────────┼─────────────────┐ │ │ │ UART API GPIO API SPI API │ │ │ Company UART Company GPIO Company SPI │ │ │ └─────────────────┼─────────────────┘ │ Company SoC HW二、GPIO 是最适合继续学习的外设
假设 Company SoC 有:
GPIO0 ├── GPIO0_0 ├── GPIO0_1 ├── GPIO0_2 └──...硬件寄存器:
GPIO0_BASE=0x40010000假设:
DIR +0x00 OUT +0x04 IN +0x08 SET +0x0C CLR +0x10 TOGGLE +0x14那么传统裸机代码可能是:
#defineGPIO0_BASE0x40010000#defineGPIO_DIR(*(volatileuint32_t*)(GPIO0_BASE+0x00))#defineGPIO_OUT(*(volatileuint32_t*)(GPIO0_BASE+0x04))GPIO_DIR|=BIT(5);GPIO_OUT|=BIT(5);这是:
Company SoC Hardware API
而 Zephyr 不希望应用程序直接这么干。
三、Zephyr GPIO Driver 做什么?
我们写:
drivers/gpio/gpio_company.c然后实现:
staticintcompany_gpio_pin_configure(conststructdevice*dev,gpio_pin_tpin,gpio_flags_tflags){...}以及:
static int company_gpio_port_get_raw(const struct device *dev, gpio_port_value_t *value){...}staticintcompany_gpio_port_set_masked_raw(conststructdevice*dev,gpio_port_pins_tmask,gpio_port_value_tvalue){...}最后:
staticconststructgpio_driver_apicompany_gpio_api={.pin_configure=company_gpio_pin_configure,.port_get_raw=company_gpio_port_get_raw,.port_set_masked_raw=company_gpio_port_set_masked_raw,};这一步非常关键。
它意味着:
Zephyr GPIO API │ ▼ gpio_driver_api │ ▼ company_gpio_pin_configure()company_gpio_port_get_raw()company_gpio_port_set_masked_raw()四、应用程序就完全不知道 Company SoC
例如:
conststructdevice*gpio=DEVICE_DT_GET(DT_NODELABEL(gpio0));然后:
gpio_pin_configure(gpio,5, GPIO_OUTPUT_ACTIVE);或者:
gpio_pin_set(gpio,5,1);应用层完全不知道:
0x40010000 DIR OUT SET CLR这些细节。
这就是 BSP 最核心的价值:
把 SoC 硬件细节隔离在 Driver 里面。
五、GPIO 的 DeviceTree
现在看:
gpio0:gpio@40010000{compatible="company,gpio";reg=<0x400100000x1000>;gpio-controller;#gpio-cells=<2>;status="okay";};这里出现了一个非常重要的概念:
gpio-controller;它告诉 Zephyr:
这个 DeviceTree node 是一个 GPIO Controller。
六、GPIO Consumer
例如 LED:
led0{gpios=<&gpio05GPIO_ACTIVE_HIGH>;};这里:
└── GPIO Controller5│ └── pin number GPIO_ACTIVE_HIGH │ └── GPIO flags于是:
LED │ └── gpios │ ▼ gpio0 │ ▼ pin5应用程序:
#defineLED_NODEDT_NODELABEL(led0)staticconststructgpio_dt_specled=GPIO_DT_SPEC_GET(LED_NODE,gpios);然后:
**七、GPIO 完整驱动示例** 现在我们把前面分散的函数体补全,写出一份完整的`drivers/gpio/gpio_company.c`。这份代码展示了 Zephyr GPIO Driver 的完整骨架:从寄存器位操作,到`gpio_driver_api`的挂接,再到`DEVICE_DT_DEFINE`的注册。 ```c /* * drivers/gpio/gpio_company.c * * Company SoC GPIO Driver * * 对应 Zephyr 驱动模型环节: * DeviceTreenode->"company,gpio"* Binding ->dts/bindings/gpio/company,gpio.yaml * Driver ->本文件 * struct device ->DEVICE_DT_DEFINE 生成 * API ->gpio_driver_api */#include <zephyr/device.h>#include <zephyr/drivers/gpio.h>#include <zephyr/sys/util.h>/* ---- 寄存器定义(对应 DeviceTree reg=<0x40010000 0x1000>) ---- */#define COMPANY_GPIO_BASE DT_REG_ADDR(DT_DRV_INST(0))#define GPIO_DIR_REG (*(volatile uint32_t *)(COMPANY_GPIO_BASE + 0x00))#define GPIO_OUT_REG (*(volatile uint32_t *)(COMPANY_GPIO_BASE + 0x04))#define GPIO_IN_REG (*(volatile uint32_t *)(COMPANY_GPIO_BASE + 0x08))#define GPIO_SET_REG (*(volatile uint32_t *)(COMPANY_GPIO_BASE + 0x0C))#define GPIO_CLR_REG (*(volatile uint32_t *)(COMPANY_GPIO_BASE + 0x10))#define GPIO_TOGGLE_REG (*(volatile uint32_t *)(COMPANY_GPIO_BASE + 0x14))/* ---- pin_configure:配置引脚方向与初始电平 ---- */ static int company_gpio_pin_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags){/* 对应 Zephyr 驱动模型:gpio_driver_api.pin_configure 回调 */if(pin>=32){return-EINVAL;}/* 处理输入/输出方向 */if(flags&GPIO_OUTPUT){/* 置位 DIR 寄存器对应位,设为输出 */ GPIO_DIR_REG|=BIT(pin);/* 处理初始输出电平 */if(flags&GPIO_OUTPUT_INIT_HIGH){/* 用 SET 寄存器置位,避免读-改-写竞争 */ GPIO_SET_REG=BIT(pin);}elseif(flags&GPIO_OUTPUT_INIT_LOW){/* 用 CLR 寄存器清零 */ GPIO_CLR_REG=BIT(pin);}}else{/* 清除 DIR 寄存器对应位,设为输入 */ GPIO_DIR_REG&=~BIT(pin);}/* 处理内部上拉/下拉(假设硬件支持) */if(flags&GPIO_PULL_UP){/* 置位上拉使能寄存器(此处省略具体寄存器) */}elseif(flags&GPIO_PULL_DOWN){/* 置位下拉使能寄存器(此处省略具体寄存器) */}return0;}/* ---- port_get_raw:读取整个端口原始电平 ---- */ static int company_gpio_port_get_raw(const struct device *dev, gpio_port_value_t *value){/* 对应 Zephyr 驱动模型:gpio_driver_api.port_get_raw 回调 */ /* 直接读取 IN 寄存器,返回32位原始电平 */ *value=GPIO_IN_REG;return0;}/* ---- port_set_masked_raw:按掩码设置端口输出 ---- */ static int company_gpio_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, gpio_port_value_t value){/* 对应 Zephyr 驱动模型:gpio_driver_api.port_set_masked_raw 回调 */ /* 只修改 mask 覆盖的位,其余位保持不变 */ uint32_t out=GPIO_OUT_REG;out=(out&~mask)|(value&mask);GPIO_OUT_REG=out;return0;}/* ---- gpio_driver_api:把 Zephyr GPIO API 映射到 Company 寄存器 ---- */ static const struct gpio_driver_api company_gpio_api={.pin_configure=company_gpio_pin_configure, .port_get_raw=company_gpio_port_get_raw, .port_set_masked_raw=company_gpio_port_set_masked_raw,};/* ---- DEVICE_DT_DEFINE:注册 struct device ---- */ /* * 对应 Zephyr 驱动模型环节: * DeviceTreenode->gpio0: gpio@40010000 * compatible ->"company,gpio"* struct device ->由本宏在启动时自动创建并初始化 */#define COMPANY_GPIO_INIT(n) \DEVICE_DT_DEFINE(DT_DRV_INST(n),\company_gpio_init,\NULL,\NULL,\NULL,\POST_KERNEL,\CONFIG_GPIO_INIT_PRIORITY,\&company_gpio_api)/* 设备初始化函数:可在此处做时钟使能、复位释放等 */ static int company_gpio_init(const struct device *dev){/* 对应 Zephyr 驱动模型:设备初始化阶段(POST_KERNEL) */ /* 例如:使能 GPIO 时钟、释放复位、配置 Pinmux 等 */return0;}/* 实例化设备(对应 DeviceTree 中的 gpio0 节点) */ DEVICE_DT_INST_DEFINE(0, company_gpio_init, NULL, NULL, NULL, POST_KERNEL, CONFIG_GPIO_INIT_PRIORITY,&company_gpio_api);这段代码对应的 Zephyr 驱动模型环节总结:
| 代码片段 | 对应环节 |
|---|---|
#define COMPANY_GPIO_BASE DT_REG_ADDR(...) | DeviceTree → 寄存器基地址 |
company_gpio_pin_configure() | gpio_driver_api.pin_configure回调 |
company_gpio_port_get_raw() | gpio_driver_api.port_get_raw回调 |
company_gpio_port_set_masked_raw() | gpio_driver_api.port_set_masked_raw回调 |
static const struct gpio_driver_api company_gpio_api | Driver API 挂接 |
DEVICE_DT_DEFINE(...) | struct device注册 |
company_gpio_init() | 设备初始化(POST_KERNEL) |
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
**七、这时候你应该能看到完整链路** ```bash DeviceTree │ ▼ gpio0: gpio@40010000 │ │ compatible ▼ "company,gpio" │ ▼ Company GPIO Driver │ ▼ gpio_driver_api │ ▼ struct device │ ▼ gpio_pin_set() │ ▼ HW Register这其实就是你前面 09~28 篇一直在学习的东西。
八、SPI 和 GPIO 最大的不同
SPI 不是简单的:
pin → register它涉及:
SPI Controller ├── clock ├── MOSI ├── MISO ├── CS ├── mode ├── frequency └── transfer假设 Company SoC:
SPI0 BASE=0x40020000寄存器:
CTRL STATUS BAUD TXDATA RXDATA CSZephyr Driver:
**八点五、SPI 完整驱动示例**前面我们看到了 `company_spi_transceive()` 的骨架和 `spi_driver_api` 的挂接方式。现在把它补全,并加上 `DEVICE_DT_DEFINE` 注册,形成一份完整的 `drivers/spi/spi_company.c`,同时给出对应的 DeviceTree binding 文件 `company,spi.yaml`。 ```c/* * drivers/spi/spi_company.c * * Company SoC SPI Controller Driver * * 对应 Zephyr 驱动模型环节: * DeviceTree node -> spi0: spi@40020000 * Binding -> dts/bindings/spi/company,spi.yaml * Driver -> 本文件 * struct device -> DEVICE_DT_DEFINE 生成 * API -> spi_driver_api */#include<zephyr/kernel.h>#include<zephyr/device.h>#include<zephyr/drivers/spi.h>#include<zephyr/sys/util.h>/* ---------- 寄存器定义:对应 Company SoC 硬件手册 ---------- */#defineSPI0_BASE0x40020000/* 每个寄存器都是 32 位,偏移量来自硬件手册 */#defineSPI_CTRL(*(volatileuint32_t*)(SPI0_BASE+0x00))/* 控制:使能、模式、位宽 */#defineSPI_STATUS(*(volatileuint32_t*)(SPI0_BASE+0x04))/* 状态:忙、TX 空、RX 满 */#defineSPI_BAUD(*(volatileuint32_t*)(SPI0_BASE+0x08))/* 波特率分频 */#defineSPI_TXDATA(*(volatileuint32_t*)(SPI0_BASE+0x0C))/* 发送数据寄存器 */#defineSPI_RXDATA(*(volatileuint32_t*)(SPI0_BASE+0x10))/* 接收数据寄存器 */#defineSPI_CS(*(volatileuint32_t*)(SPI0_BASE+0x14))/* 片选控制 *//* 状态寄存器位定义 */#defineSPI_STATUS_BUSYBIT(0)/* 1 = 正在传输 */#defineSPI_STATUS_TX_EMPTYBIT(1)/* 1 = TX 数据寄存器空,可写 */#defineSPI_STATUS_RX_FULLBIT(2)/* 1 = RX 数据寄存器满,可读 *//* ---------- transceive:对应 Zephyr SPI API 的 transceive ---------- */staticintcompany_spi_transceive(conststructdevice*dev,conststructspi_config*config,conststructspi_buf_set*tx_bufs,conststructspi_buf_set*rx_bufs){/* 1. 配置波特率:把 Zephyr 的 frequency 映射到 BAUD 分频寄存器 */SPI_BAUD=config->frequency/1000000;/* 2. 配置模式:CPOL / CPHA 映射到 CTRL 寄存器的 mode 位 */uint32_tctrl=SPI_CTRL;ctrl&=~(BIT(4)|BIT(5));/* 先清 mode 位 */if(config->operation&SPI_MODE_CPOL){ctrl|=