Zephyr 设备模型、驱动与电源管理
1. struct device
设备模型公共定义在 include/zephyr/device.h。每个设备实例由静态 struct device 表示,主要关系:
1 | struct device |
设备对象不是动态驱动注册表;绝大部分在编译和链接期生成。
2. 设备定义宏
非 DTS 设备:
1 | DEVICE_DEFINE(dev_id, name, init_fn, pm, |
DTS 设备:
1 | DEVICE_DT_DEFINE(node_id, init_fn, pm, |
宏内部完成:
- 定义 device state;
- 定义依赖 handle 数据;
- 创建
struct device; - 创建
struct init_entry; - 放入按 init level/priority 排序的 linker section。
3. Driver class API
每一类设备有统一 vtable,例如 GPIO:
1 | struct gpio_driver_api |
public inline API:
1 | gpio_pin_configure_dt() |
好处:
- 应用与 vendor driver 解耦;
- API 调用通常只有一次 indirect call;
- class 可提供统一参数检查和 syscall verifier;
- driver data/config 保持私有。
4. DTS 驱动实例化
典型驱动结构:
1 |
|
只有 status okay 且对应驱动源码已由 Kconfig/CMake编译时,实例才存在。
5. 获取设备
编译期获取:
1 | const struct device *dev = DEVICE_DT_GET(node); |
它直接取得全局符号地址,没有运行时查找。之后必须:
1 | if (!device_is_ready(dev)) { |
常见 spec:
1 | static const struct gpio_dt_spec led = |
spec ready 检查:
1 | gpio_is_ready_dt(&led) |
device_get_binding(name) 进行运行时名称查找,适合 shell/动态配置;普通静态应用优先 DEVICE_DT_GET。
6. Device init
启动时 z_sys_init_run_level() 遇到 device entry:
- 检查 deferred init;
- 调用 device init function;
- 保存 init result;
- 标记 initialized;
device_is_ready()只有在 initialized 且 result 为 0 时为 true。
依赖 controller 必须比 child 更早初始化。常用 priority:
- interrupt controller、clock、pinctrl:PRE_KERNEL;
- bus controller:PRE_KERNEL/POST_KERNEL;
- bus child sensor:POST_KERNEL;
- application service:APPLICATION。
仅调整 priority 不能修复循环依赖。
7. Device dependency handles
生成系统根据 DTS phandle/dependency ordinal 建立:
- required devices;
- supported devices;
- injected dependencies。
handle 使用 int16_t,比 pointer list 紧凑。多阶段链接读取预链接设备信息后生成最终 dependency arrays。
用途:
- 初始化依赖理解;
- device PM suspend/resume 排序;
- shell/debug dependency 查询。
8. Deferred initialization
节点可设置 zephyr,deferred-init。此时启动阶段不自动 init,应用稍后调用 device init API。
适合:
- 供电域未开启;
- 外部器件需业务控制的 reset sequence;
- 减少启动时间。
使用方必须处理并发首次初始化和依赖设备状态。
9. GPIO
公共 API:include/zephyr/drivers/gpio.h。
重点:
- raw 与 logical API;
- active-low 由 flags 转换;
- pin/port 操作;
- interrupt edge/level;
gpio_callbacklist;gpio_dt_spec。
callback 常在 ISR 上下文执行,复杂处理应提交 work。
10. I2C / SPI
I2C
include/zephyr/drivers/i2c.h:
- controller configure;
i2c_transfer()messages;- write/read/write_read;
- target mode;
i2c_dt_spec。
device address 来自 child reg。驱动应区分 7-bit/10-bit 和 restart/stop flags。
SPI
include/zephyr/drivers/spi.h:
spi_config:frequency、operation、slave、CS;spi_buf_set;- transceive/read/write;
- async;
spi_dt_spec。
CS 可以由 controller hardware 或 cs-gpios 管理。buffer 生命周期必须覆盖异步传输。
11. UART
include/zephyr/drivers/uart.h 提供三层 API:
- polling;
- interrupt-driven FIFO;
- async DMA/event API。
console、shell、logging backend、mcumgr 可能同时竞争 UART。需要明确谁拥有 RX callback、是否共享 TX,以及 panic 阶段是否可轮询输出。
12. Flash
include/zephyr/drivers/flash.h:
- read/write/erase;
- page layout;
- write block size;
- erase value;
- protection。
上层 subsys/storage/flash_map 把 fixed-partitions 转成 flash_area。文件系统、settings、DFU 和 MCUboot 通常使用 flash map,而非硬编码地址。
Flash 写入注意:
- 地址/长度写对齐;
- erase block 边界;
- 从 erased value 到 programmed value 的位变化限制;
- erase/write 期间 XIP 冲突;
- cache 一致性;
- watchdog 和最长阻塞时间。
13. Clock 与 Pinctrl
Clock control
设备 config 保存 clock_control_subsys_t/DT spec,init 时:
1 | clock_control_on |
SoC clock tree 通常在 PRE_KERNEL 初始化。
Pinctrl
DT 定义 pinctrl-0、pinctrl-1 等 state。驱动使用:
1 | PINCTRL_DT_INST_DEFINE |
default/sleep state 可与 PM 联动。pinmux 冲突应在 DTS 设计阶段解决。
14. DMA
DMA API 描述:
- channel;
- direction;
- source/destination data size;
- burst;
- block chain;
- callback。
驱动使用 DMA 时必须考虑:
- cache clean/invalidate;
- buffer alignment;
- memory region 是否 DMA 可访问;
- callback ISR 上下文;
- abort race;
- peripheral request mapping。
15. Interrupt 连接
静态连接:
1 | IRQ_CONNECT(irq, priority, isr, arg, flags); |
DTS 驱动常使用:
1 | DT_INST_IRQN(inst) |
构建系统可能生成 software ISR table。direct ISR 可降低开销,但功能约束更多。zero-latency IRQ 绕过部分内核路径,ISR 不得调用普通内核 API。
16. Device PM
公共 API:
include/zephyr/pm/device.hsubsys/pm/device.csubsys/pm/device_runtime.c
驱动通过:
1 | PM_DEVICE_DT_INST_DEFINE(inst, action_cb); |
实现 action:
- suspend;
- resume;
- turn off/on;
- low power。
System-managed PM
系统进入低功耗前按依赖顺序 suspend devices,唤醒后逆序 resume。
Runtime PM
使用计数控制单设备:
1 | pm_device_runtime_get() |
首次 get 恢复设备,最后 put 可 autosuspend。调用者必须保持 get/put 配对,driver 需处理与 system PM 的交互。
17. 驱动移植步骤
- 确认 class API,避免自造重复接口;
- 编写 binding;
- DTS 添加节点和依赖;
- Kconfig 定义 feature/vendor symbol;
- CMake 按 Kconfig 加源文件;
- 定义 config/data/api;
- 用
DT_INST_FOREACH_STATUS_OKAY实例化; - 在 init 中检查 clock/pinctrl/bus;
- 实现 PM 和 error unwind;
- 添加 ztest、emulator 或测试 board;
- 检查多实例、disabled 节点、无 DT 实例时可编译;
- 测试 ISR、timeout、并发和异常恢复。
18. 常见驱动问题
- DTS compatible 正确但 Kconfig 未开:无 device symbol;
- controller 未 ready:child init 失败;
- init priority 过早:使用了尚不可用的 kernel API;
data错放为 const 或config可变;- callback 中阻塞;
- DMA buffer 在 stack 上提前失效;
- runtime PM 引用泄漏;
- 未处理 active-low;
- 外设 reset/clock 顺序错误;
- DTS pinctrl state 与 board 实际连线不符。