使用 Zephyr 开发中最通用的场景(比如常见的nRF52840或STM32开发板),梳理这份“入门硬件配置实操指南”。
引言:先画一张“脑内地图”——Kconfig 管软件,DTS 管接线
在 Zephyr 里,最让新手崩溃的就是分不清两个东西:
Kconfig(软件功能开关):决定“编译时包含哪些代码”。比如:要不要启用蓝牙协议栈?日志打印得详细还是简略?任务堆栈分配多大?这属于“软件行为”。
Devicetree / DTS(硬件接线图):决定“外设接在哪个物理引脚上”。比如:UART 用的是 TX 是 Pin 1 还是 Pin 2?I2C 传感器的地址是 0x18 还是 0x19?这属于“硬件拓扑”。
记住一句口诀:开功能找prj.conf,改接线找.overlay。
第一步:软件功能开关(Kconfig)——如何打开/关闭功能
场景:我想启用蓝牙(BLE)和日志(Logging)。
1.1 核心配置文件:prj.conf
这是你应用根目录下的文件,所有常驻的功能开关写在这里。
kconfig
# prj.conf CONFIG_BT=y # 开启蓝牙 CONFIG_LOG=y # 开启日志系统 CONFIG_LOG_MODE_IMMEDIATE=y # 立即输出日志(方便调试) CONFIG_MAIN_STACK_SIZE=4096 # 调整主线程栈大小
1.2 可视化操作(不用记宏名)
如果你不确定某个功能的宏定义叫什么,不要硬搜代码。
在项目根目录执行:
west build -t guiconfig
这会在浏览器中打开一个图形界面。你可以按/搜索(如搜Bluetooth),勾选后保存。注意:图形界面的改动会存在build/.config中,如果确认有效,记得反向抄回prj.conf,否则west build -t pristine会丢失设置。
1.3 板级默认值在哪?(极少改动)
板厂会在boards/xxx/xxx_defconfig中提供默认开关。如果你发现某个硬件默认没开(如 ADC),除了在prj.conf开,也可以参考Kconfig.defconfig看默认值,但一般不直接改板级文件,全在应用层的prj.conf覆盖即可。
第二步:外设接线(设备树)——如何查找 GPIO、更改引脚
场景:我想用 UART1 作为调试串口,但默认用的是 UART0,需要切换。
2.1 核心概念:节点(Node)与覆盖(Overlay)
Zephyr 为每块板子提供了基础描述(如nrf52840dk_nrf52840.dts)。永远不要直接修改这个文件!你要在应用根目录创建app.overlay(名字可以不是 app,但常用这个),用来“覆盖”原厂配置。
2.2 实操:把 UART 从 0 改到 1
假设原板子的 DTS 中,UART0 挂在&uart0,现在你想换到&uart1。
第一步:查找&uart1默认接的是哪两个 GPIO。你可以去板级.dts文件里搜uart1,或者直接在你的app.overlay里写:
// app.overlay &uart1 { status = "okay"; // 开启 UART1 current-speed = <115200>; // 设置波特率 pinctrl-0 = <&uart1_default>; // 引用默认的引脚组 }; &uart0 { status = "disabled"; // 关闭 UART0 避免冲突 };第二步:如果我想把 UART1 的 TX 脚从默认的 P0.15 强行改成 P0.20 怎么办?
这需要看板子自带的pinctrl文件(引脚复用定义)。通常你需要在app.overlay中覆盖 pinctrl:
// 方法:在 overlay 中修改引脚组(具体宏名需参考原 dtsi 中的 pinctrl 写法) &uart1_default { group1 { pinmux = <UART1_TX_P20>; // 假设原厂宏定义支持 P20 }; };注:具体UART1_TX_P20这类宏定义由芯片厂商提供,新手可以通过翻看zephyr/boards/下同芯片的其他板子抄作业。
2.3 怎么看当前接线对不对?
编译一次后,去build/zephyr/目录下找到zephyr.dts(这是最终合并后的完整设备树文件)。搜索你的节点,确认status和reg符合预期,这是唯一的“真相来源”。
第三步:操作清单(该怎么处理)——三板斧解决 90% 的需求
当你接到一个任务时,按照这个清单依次操作,绝对不会乱:
🛠️ 任务清单模板
场景 A:我要添加一个新的 I2C 传感器(如温度传感器 BME280)
软件层(Kconfig):在
prj.conf添加CONFIG_BME280=y(如果 Zephyr 有现成驱动)或CONFIG_I2C=y。硬件层(DTS Overlay):在
app.overlay中的&i2c0节点下挂载子节点:&i2c0 { status = "okay"; bme280@76 { compatible = "bosch,bme280"; reg = <0x76>; // I2C 设备地址,查数据手册 label = "BME280"; }; };代码调用:在 C 代码中通过
DEVICE_DT_GET(DT_NODELABEL(bme280))获取设备指针,驱动自动初始化。
场景 B:我要把按键中断引脚从 GPIO_1 改到 GPIO_5
查空闲引脚:确认 GPIO_5 没有被 UART、SPI 等其他
status="okay"的节点占用(去build/zephyr/zephyr.dts里搜5)。改 Overlay:
&my_button_node { // 假设原节点别名 gpios = <&gpio0 5 GPIO_ACTIVE_LOW>; };清理编译:务必执行
west build -t pristine或直接删掉build文件夹再重新编译,因为设备树的改动有时不会触发增量编译。
第四步:常见陷阱(新手容易出错的地方)
陷阱 1:改了 DTS 却没反应(缓存问题)
现象:明明把status改成了disabled,上电外设还在工作。
原因:CMake 没有重新解析 DTS。
解药:rm -rf build然后west build -b xxx强制全量构建。
陷阱 2:引脚冲突(Pin Mux)
现象:开启 UART1 后,SPI 莫名其妙不工作了。
原因:UART1 和 SPI 默认用到了同一个物理引脚(比如都用了 P0.10)。
解药:打开build/zephyr/zephyr.dts,搜索冲突的引脚号(如10),看它出现在几个status="okay"的节点里。一个物理脚只能服务一个功能,必须disabled掉其中一个。
陷阱 3:忘记开启 Kconfig,只加了 DTS
现象:设备树里节点status="okay"完美,但device_is_ready()永远返回false。
解药:Zephyr 的驱动模型是Kconfig + DTS 双验证。DTS 只告诉“硬件怎么接”,Kconfig 才告诉“编译链接驱动代码”。去prj.conf补上CONFIG_xxx=y。
陷阱 4:Overlay 语法不严谨(分号、括号)
现象:编译报错syntax error。
解药:DTS 对分号极其敏感。检查app.overlay里每个属性结尾带;,花括号{ }正确成对。建议用 VSCode 安装 DeviceTree 插件高亮检查。
建议
对于 Zephyr 硬件配置,忘掉单片机裸机开发的“直接改寄存器”思维。
确立优先级:
prj.conf(软件) +app.overlay(硬件)永远是你的主战场,远离boards/目录下的原生文件。善用中间产物:遇到配置不生效,不要猜,去看
build/zephyr/.config(确认宏是否被选中)和build/zephyr/zephyr.dts(确认硬件合并后的最终状态)。这两份文件能回答你 99% 的疑惑。养成习惯:添加外设时,先去 Zephyr 官网或驱动文件夹 (
zephyr/drivers/) 看该驱动绑定的compatible字符串要求和必需的properties,照葫芦画瓢,稳扎稳打,两周后你会爱上这套解耦设计的。祝编译一次通过!