使用 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)

  1. 软件层(Kconfig):在 prj.conf 添加 CONFIG_BME280=y(如果 Zephyr 有现成驱动)或 CONFIG_I2C=y

  2. 硬件层(DTS Overlay):在 app.overlay 中的 &i2c0 节点下挂载子节点:

    &i2c0 {
        status = "okay";
        bme280@76 {
            compatible = "bosch,bme280";
            reg = <0x76>;          // I2C 设备地址,查数据手册
            label = "BME280";
        };
    };
  3. 代码调用:在 C 代码中通过 DEVICE_DT_GET(DT_NODELABEL(bme280)) 获取设备指针,驱动自动初始化。

场景 B:我要把按键中断引脚从 GPIO_1 改到 GPIO_5

  1. 查空闲引脚:确认 GPIO_5 没有被 UART、SPI 等其他 status="okay" 的节点占用(去 build/zephyr/zephyr.dts 里搜 5)。

  2. 改 Overlay

    &my_button_node { // 假设原节点别名
        gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
    };
  3. 清理编译:务必执行 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 硬件配置,忘掉单片机裸机开发的“直接改寄存器”思维。

  1. 确立优先级prj.conf(软件) + app.overlay(硬件) 永远是你的主战场,远离 boards/ 目录下的原生文件。

  2. 善用中间产物:遇到配置不生效,不要猜,去看 build/zephyr/.config(确认宏是否被选中)和 build/zephyr/zephyr.dts(确认硬件合并后的最终状态)。这两份文件能回答你 99% 的疑惑。

  3. 养成习惯:添加外设时,先去 Zephyr 官网或驱动文件夹 (zephyr/drivers/) 看该驱动绑定的 compatible 字符串要求和必需的 properties,照葫芦画瓢,稳扎稳打,两周后你会爱上这套解耦设计的。祝编译一次通过!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐