2. nRF GPIO外设驱动与板级适配
在 nRF 系列 SoC 上,GPIO 是最基础也最容易被低估的外设。它既是 LED、按键、继电器、传感器中断等板级功能的直接承载者,也是 pinctrl、SPI、I2C、UART 等外设引脚复用的底层资源。理解 GPIO 寄存器与 Zephyr GPIO API 的对应关系,再结合 pinctrl 设备树绑定,才能把“芯片引脚”真正落到“板级功能”上。
一、nRF GPIO 寄存器模型
nRF52/nRF53 的 GPIO 以 P0、P1 两个端口组织,每个端口有独立的寄存器组。核心寄存器包括:
- OUT / OUTSET / OUTCLR / OUTINV:输出写、置位、清零、翻转。OUTSET 与 OUTCLR 是原子操作,避免读改写竞争。
- IN:读取引脚当前电平。
- DIR / DIRSET / DIRCLR:方向控制,1 为输出,0 为输入。
- PIN_CNF[n]:每引脚配置,含 DIR、INPUT、PULL、DRIVE、SENSE。
- DETECTMODE:选择沿检测来自 PIN_CNF 还是 LATCH。
其中 PIN_CNF 的 PULL 字段决定上下拉:0 为禁用,1 为下拉,3 为上拉;DRIVE 决定输出驱动能力(S0S1、H0H1、S0H1 等);SENSE 决定是否使能沿检测,配合 GPIOTE 使用。按键输入通常配置为 INPUT=1、PULL=上拉、SENSE=高电平或低电平触发。
二、Zephyr GPIO API 与寄存器映射
Zephyr 通过 gpio_pin_configure()、gpio_pin_set()、gpio_pin_get()、gpio_pin_interrupt_configure() 等 API 封装寄存器。以按键为例:
static const struct gpio_dt_spec key = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
gpio_pin_configure_dt(&key, GPIO_INPUT | GPIO_PULL_UP);
gpio_pin_interrupt_configure_dt(&key, GPIO_INT_EDGE_TO_ACTIVE);
gpio_init_callback(&cb, key_isr, BIT(key.pin));
gpio_add_callback(key.port, &cb);
这里 GPIO_DT_SPEC_GET 从设备树读取端口、引脚号与标志,gpio_pin_configure_dt 最终写入 PIN_CNF,gpio_pin_interrupt_configure_dt 则配置 SENSE 并连接 GPIOTE 通道。LED 输出则使用 GPIO_OUTPUT_ACTIVE 或 GPIO_OUTPUT_INACTIVE,通过 gpio_pin_set_dt() 控制亮灭。
三、引脚复用与 pinctrl 设备树绑定
nRF 的引脚复用不是通过 GPIO 寄存器完成,而是由 pinctrl 在设备树中声明。每个外设节点通过 pinctrl-0 引用一个 pinctrl 状态,状态内用 psels 指定引脚与功能,例如 UART0 的 TX/RX:
&uart0 {
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
};
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 6)>,
<NRF_PSEL(UART_RX, 0, 8)>;
};
};
};
关键点:同一引脚不能同时被两个外设占用;NRF_PSEL 的端口与引脚号必须与原理图一致;sleep 状态通常把引脚设为 NRF_PSEL_DISCONNECTED 以降低功耗。GPIO 本身不需要 pinctrl,但一旦某引脚被 pinctrl 分配给 UART/SPI,就不能再用作普通 GPIO。
四、按键与 LED 驱动实现
按键驱动通常采用“设备树别名 + 回调”的方式。以 sw0 为例,先通过 GPIO_DT_SPEC_GET 获取端口与引脚,再配置为输入上拉,最后注册中断回调。回调中应避免长时间阻塞,只做标志置位或消息投递,由工作队列处理消抖与业务逻辑。消抖可在回调中记录时间戳,若两次触发间隔小于 20 ms 则忽略。
LED 驱动更简单:配置为输出,通过 gpio_pin_set_dt() 控制。若设备树中标志为 GPIO_ACTIVE_LOW,则 gpio_pin_set_dt(&led, 1) 实际输出低电平点亮 LED,逻辑与物理电平解耦,这是板级适配中必须注意的极性约定。
五、自定义板级引脚映射实战
假设有一块自定义 nRF52840 板,按键接 P0.11(低有效),LED 接 P0.15(低有效),UART TX/RX 接 P0.06/P0.08。板级适配步骤如下:
- 在
boards/<vendor>/<board>/<board>.dts中定义leds、buttons节点,并设置gpios属性与极性。 - 在
&pinctrl中新增uart0_default与uart0_sleep,用NRF_PSEL指定 P0.06 与 P0.08。 - 在
&uart0中引用pinctrl-0与pinctrl-1,并设置pinctrl-names。 - 在
board.c或应用层通过DT_ALIAS(sw0)、DT_ALIAS(led0)获取设备,调用 GPIO API 完成初始化。 - 编译后用
west build -b <board>验证设备树是否通过,再用逻辑分析仪或示波器观察 P0.06 波形确认 UART 输出。
GPIO_ACTIVE_LOW 与硬件实际极性不符,会造成 LED 逻辑反相。掌握寄存器与 API 的对应关系、pinctrl 的绑定规则,以及按键/LED 的极性约定,就能把 nRF 的 GPIO 从“能亮能读”提升到“可维护、可移植”的板级驱动层。