4. 设备树与Kconfig实战
在nRF Connect SDK(NCS)中,设备树(Devicetree)与Kconfig是两条并行的配置主线:设备树描述“硬件长什么样、外设挂在哪个地址、引脚如何连接”,Kconfig决定“哪些驱动和子系统被编译进镜像、以什么策略运行”。Zephyr构建系统在CMake阶段把两者合并,生成zephyr.dts、autoconf.h与devicetree_generated.h,最终驱动代码通过DT_*宏和CONFIG_*宏访问这些信息。本节以nRF52840 DK为例,完成一次从设备树节点新增、GPIO与UART配置、Kconfig裁剪到硬件行为验证的完整闭环。
一、先看清构建期两条配置线的汇合点
设备树源文件(.dts/.dtsi/.overlay)经预处理、合并、展开后生成最终设备树;Kconfig通过prj.conf、板级defconfig、Kconfig依赖关系求解出配置符号。两者在编译期分别产出宏定义,供C代码使用。理解这条链路,才能判断“改了配置为什么没生效”。
二、为nRF52840 DK添加自定义设备节点
nRF52840 DK的板级设备树已经定义了leds、buttons、uart0等节点。我们要新增一个自定义节点,例如把P1.00~P1.03定义为“四路状态指示器”,并挂在一个自定义兼容字符串下,便于应用层通过DT_NODELABEL引用。
在工程根目录创建boards/nrf52840dk_nrf52840.overlay,内容如下:
/ {
status_leds: status-leds {
compatible = "demo,status-leds";
status-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>,
<&gpio1 1 GPIO_ACTIVE_LOW>,
<&gpio1 2 GPIO_ACTIVE_LOW>,
<&gpio1 3 GPIO_ACTIVE_LOW>;
label = "status-leds";
};
};
&gpio1 {
status = "okay";
};
这里的关键点有三处:第一,compatible使用自定义字符串"demo,status-leds",Zephyr不会自动为其绑定驱动,但应用层仍可通过设备树宏读取属性;第二,status-gpios是自定义属性名,值类型为phandle-array,每个元素由GPIO控制器引用、引脚号、标志位组成;第三,&gpio1必须显式status = "okay",否则节点存在但驱动不会初始化。nRF52840的P1口对应gpio1,P0口对应gpio0,这是Nordic SoC的固定映射。
三、配置GPIO与UART外设
GPIO部分,除了设备树节点,还需要在prj.conf中打开GPIO驱动:
CONFIG_GPIO=y
CONFIG_GPIO_NRFX=y
应用代码中通过DEVICE_DT_GET获取GPIO控制器,再用GPIO_DT_SPEC_GET从自定义节点提取引脚描述:
#define STATUS_NODE DT_NODELABEL(status_leds)
static const struct gpio_dt_spec led0 =
GPIO_DT_SPEC_GET_BY_IDX(STATUS_NODE, status_gpios, 0);
int main(void)
{
if (!gpio_is_ready_dt(&led0)) {
return -1;
}
gpio_pin_configure_dt(&led0, GPIO_OUTPUT_INACTIVE);
gpio_pin_set_dt(&led0, 1);
return 0;
}
UART部分,nRF52840 DK默认使用uart0,引脚为P0.06(TX)和P0.08(RX),波特率115200。若要在自定义节点上启用第二路UART,可在overlay中追加:
&uart1 {
status = "okay";
current-speed = <115200>;
tx-pin = <6>;
rx-pin = <8>;
};
并在prj.conf中确认CONFIG_SERIAL=y与CONFIG_UART_INTERRUPT_DRIVEN=y。注意:nRF52840的UART1与SPI1、TWI1共享外设资源,若同时启用需检查CONFIG_NRFX_UARTE1与相关引脚冲突。
四、通过Kconfig裁剪功能
裁剪的目标是“只保留本次验证需要的功能”。一个典型的裁剪前后对比如下:
| 配置项 | 裁剪前 | 裁剪后 | 影响 |
|---|---|---|---|
| CONFIG_BT | y | n | 移除蓝牙协议栈,节省约120KB Flash |
| CONFIG_NETWORKING | y | n | 移除网络子系统 |
| CONFIG_LOG | y | y | 保留日志,便于验证 |
| CONFIG_LOG_MODE_IMMEDIATE | n | y | 日志立即输出,避免缓冲延迟 |
| CONFIG_ASSERT | y | y | 保留断言,便于排障 |
| CONFIG_GPIO | y | y | 保留GPIO驱动 |
| CONFIG_SERIAL | y | y | 保留串口驱动 |
裁剪后执行west build -b nrf52840dk_nrf52840,观察构建输出中的FLASH与RAM占用变化。若裁剪后编译报错,常见原因是某个驱动被应用代码直接引用但对应CONFIG_*已关闭,此时应检查Kconfig依赖链,而不是盲目重新打开全部配置。
五、编译并验证硬件行为
完整构建命令如下:
west build -b nrf52840dk_nrf52840 -p always
west flash
验证分三步。第一步,用nrfjprog --memrd或J-Link RTT Viewer观察日志输出,确认gpio_is_ready_dt返回真、UART打印出启动信息。第二步,用万用表或示波器测量P1.00电平,确认执行gpio_pin_set_dt(&led0, 1)后引脚由高变低(因为配置了GPIO_ACTIVE_LOW)。第三步,通过串口发送字符,确认UART回显正常。若GPIO无动作,优先检查status = "okay"是否遗漏;若UART无输出,检查current-speed与终端波特率是否一致。
status-gpios对应status_gpios;GPIO_DT_SPEC_GET_BY_IDX的索引从0开始,越界不会报编译错误,但运行时会读到无效phandle。
-p always或删除build目录重新构建,否则CMake可能复用旧的设备树缓存,导致“改了没生效”的假象。
本节完成了一次从设备树节点定义、GPIO/UART配置、Kconfig裁剪到硬件行为验证的完整实践。核心判断依据是:设备树决定“引脚和地址”,Kconfig决定“驱动是否编译”,两者缺一不可;验证时先看日志、再测电平、最后查串口,逐层缩小问题范围。