3. Zephyr设备模型与nRF54外设驱动
在nRF54系列上做Zephyr开发,真正决定“外设能不能用、怎么用”的并不是C代码本身,而是设备树(Devicetree)与驱动模型之间的配合。Zephyr的设备模型可以理解为三层:设备树描述硬件、绑定(binding)约束属性、驱动在编译期被实例化并注册到设备链表。应用层通过 DEVICE_DT_GET() 拿到设备指针,再调用统一的驱动API,而不需要直接操作寄存器。理解这条链路,是后续GPIO、PWM、UART乃至自定义外设开发的基础。
设备树、绑定与驱动的分工
设备树源文件(.dts/.dtsi/.overlay)用节点和属性描述板级硬件:某个引脚接了什么、外设基地址在哪、时钟频率多少、是否启用。绑定文件(*.yaml)则规定“这个兼容字符串的节点允许有哪些属性、哪些必填、类型是什么”。构建时Zephyr会校验设备树节点是否符合绑定,不符合就报错,这比运行时才发现问题要早得多。
驱动侧通过 DT_DRV_COMPAT 与绑定中的 compatible 对应,使用 DT_INST_FOREACH_STATUS_OKAY 之类的宏为每个“status = okay”的节点生成一个设备实例。因此,设备树里把节点状态从 disabled 改成 okay,就等于让驱动多实例化一个设备。nRF54的外设寄存器映射由SoC级 .dtsi 提供,例如GPIO、UART、PWM的基地址和中断号已经写好,板级文件只需补充引脚、通道等板级信息。
GPIO驱动实例:从设备树到点灯
以nRF54L15 DK上的LED为例。板级设备树中通常已有 leds 节点,每个LED是一个子节点,带 gpios = <&gpio1 10 GPIO_ACTIVE_LOW> 这样的属性。应用代码不需要知道引脚号,只需:
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
int main(void) {
if (!gpio_is_ready_dt(&led)) return -1;
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
while (1) {
gpio_pin_toggle_dt(&led);
k_msleep(500);
}
}
这里 GPIO_DT_SPEC_GET 在编译期就把端口设备、引脚号、有效电平打包成结构体。GPIO_ACTIVE_LOW 表示低电平点亮,驱动在 gpio_pin_set_dt 时会自动取反,应用层只表达“逻辑开/关”。若设备树里 status 不是 okay,gpio_is_ready_dt 会返回false,这是排查“灯不亮”的第一检查点。
PWM与UART驱动实例
PWM在nRF54上由PWM外设提供,设备树节点需要指定通道和引脚。典型overlay片段:
&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-names = "default";
};
&pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 4)>;
};
};
应用侧用 PWM_DT_SPEC_GET(DT_NODELABEL(pwm0), 0) 获取通道,再调用 pwm_set_dt(&spec, period_ns, pulse_ns)。周期和脉宽都用纳秒表示,占空比 = pulse/period。例如周期1 ms、脉宽250 µs,即25%占空比。nRF54的PWM支持多通道共用同一周期,适合RGB调光或电机控制。
UART方面,nRF54常用UARTE(带EasyDMA)。设备树节点需配置 current-speed、tx-pin/rx-pin(通过pinctrl)以及 status。应用可直接用 uart_poll_out() 发送,或用异步API配合DMA。若使用控制台,Zephyr会自动把 chosen { zephyr,console = &uart20; } 指向的节点作为日志输出。
使用overlay配置板级外设
实际项目很少直接改板级 .dts,而是新建 boards/nrf54l15dk_nrf54l15_cpuapp.overlay,只写差异部分。overlay会与基础设备树合并,同名节点属性覆盖、新节点追加。常见操作包括:把某个外设 status 改为 okay、增加pinctrl子节点、添加自定义 compatible 节点、修改 chosen 指定控制台或存储设备。
build/zephyr/zephyr.dts,这是合并、展开宏之后的最终设备树,能确认引脚、状态、属性是否如预期。若节点没出现,多半是overlay路径或节点标签写错。寄存器映射与驱动边界
nRF54的寄存器基址在SoC级 nrf54l15.dtsi 中定义,例如GPIO、UARTE、PWM的 reg 属性。驱动通过 DEVICE_MMIO_MAP 或直接使用 DT_INST_REG_ADDR 获得地址,再按数据手册的偏移操作寄存器。应用层不应绕过驱动直接写寄存器,否则会破坏时钟、引脚复用和中断配置的一致性。只有在驱动尚未覆盖的外设或极特殊时序场景下,才考虑在驱动内补充寄存器级操作。
把这条链路串起来:overlay描述板级差异 → binding校验属性 → 驱动实例化设备 → 应用通过API调用 → 驱动操作nRF54寄存器。掌握它之后,换一块板子或换一个外设,工作就变成“改设备树、选对API”,而不是重写底层代码。