zephyr从会用走向精通
这份笔记将围绕 硬件描述、系统配置、驱动开发、通信协议栈、电源管理、调试优化 六大核心能力,通过完整代码示例和实战技巧,帮助你从“会用”走向“精通”。
能力一:设备树 (Devicetree) —— 硬件与软件的契约
目标:学会用 devicetree 描述任何外设,并在 C 代码中无缝使用。
1.1 设备树语法速查
dts
/ {
/* 自定义设备节点 */
my_sensor: sensor@0 {
compatible = "mycompany,pressure";
reg = <0>;
status = "okay";
irq-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
max-pressure-hpa = <110000>;
};
};
/* 修改已有外设引脚 */
&uart1 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart1_default>;
pinctrl-names = "default";
};
1.2 在 C 代码中获取设备树信息
#include <zephyr/device.h> #include <zephyr/drivers/gpio.h> #include <zephyr/drivers/uart.h> /* 获取节点标识符 */ #define MY_SENSOR_NODE DT_NODELABEL(my_sensor) #define UART1_NODE DT_NODELABEL(uart1) /* 获取 GPIO 规格 */ static const struct gpio_dt_spec irq_pin = GPIO_DT_SPEC_GET(MY_SENSOR_NODE, irq_gpios); /* 获取普通属性 */ static const uint32_t max_pressure = DT_PROP(MY_SENSOR_NODE, max_pressure_hpa); /* 获取 UART 设备 */ static const struct device *uart_dev = DEVICE_DT_GET(UART1_NODE);
1.3 实战技巧
-
验证设备树:
west build -t devicetree生成build/zephyr/zephyr.dts,检查最终合并结果。 -
别名 (alias) 使用:在
/aliases节点中定义my-sensor = &my_sensor;,然后使用DT_ALIAS(my_sensor)获取,方便更换不同型号传感器。 -
覆盖 overlay:项目根目录下的
app.overlay会覆盖板级默认设备树,适合定制化修改。
能力二:Kconfig —— 功能组装与裁剪
目标:灵活控制哪些模块被编译,避免代码膨胀。
2.1 配置层次
-
prj.conf:应用全局配置 -
boards/<board>.conf:板级专属配置 -
子目录中的
Kconfig:模块内部配置
2.2 常用配置实战
kconfig
# prj.conf 示例 CONFIG_LOG=y # 开启日志 CONFIG_LOG_DEFAULT_LEVEL=3 # INF 级别 CONFIG_HEAP_MEM_POOL_SIZE=4096 # 动态内存池大小 CONFIG_GPIO=y CONFIG_I2C=y CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_NUS=y # Nordic UART Service # 栈溢出检测 CONFIG_THREAD_STACK_INFO=y CONFIG_STACK_SENTINEL=y # 电源管理 CONFIG_PM_DEVICE=y CONFIG_PM_DEVICE_RUNTIME=y
2.3 实战技巧
-
查看最终配置:
west build -t menuconfig或查看build/zephyr/.config。 -
条件编译:在 C 代码中使用
#ifdef CONFIG_XXX包裹特定功能代码。 -
依赖关系:Kconfig 会自动解决依赖,但注意
select和depends on的使用。
能力三:驱动开发 —— 从零编写可复用驱动
目标:为任何外设编写标准 Zephyr 驱动,实现设备模型集成。
3.1 驱动框架模板
假设我们有一个虚拟的 pressure 传感器,兼容字符串 "mycompany,pressure"。
1. 定义设备树绑定 (项目 dts/bindings/pressure/mycompany,pressure.yaml):
yaml
compatible: "mycompany,pressure"
include: [base.yaml]
properties:
irq-gpios:
type: phandle-array
required: true
max-pressure-hpa:
type: int
required: false
2. 实现驱动 (drivers/sensor/my_pressure.c):
#define DT_DRV_COMPAT mycompany_pressure
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
struct pressure_config {
struct gpio_dt_spec irq;
uint32_t max_pressure;
};
struct pressure_data {
struct k_sem lock;
int32_t pressure_value;
};
static int pressure_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct pressure_data *data = dev->data;
// 实际读取传感器的代码 (I2C/SPI 等)
k_sem_take(&data->lock, K_FOREVER);
// 模拟读取
data->pressure_value = 100000 + (k_uptime_get() % 10000);
k_sem_give(&data->lock);
return 0;
}
static int pressure_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
struct pressure_data *data = dev->data;
k_sem_take(&data->lock, K_FOREVER);
val->val1 = data->pressure_value / 1000;
val->val2 = (data->pressure_value % 1000) * 1000;
k_sem_give(&data->lock);
return 0;
}
static const struct sensor_driver_api pressure_api = {
.sample_fetch = pressure_sample_fetch,
.channel_get = pressure_channel_get,
};
static int pressure_init(const struct device *dev)
{
const struct pressure_config *cfg = dev->config;
struct pressure_data *data = dev->data;
if (!device_is_ready(cfg->irq.port)) {
return -ENODEV;
}
// 配置中断引脚
gpio_pin_configure_dt(&cfg->irq, GPIO_INPUT);
k_sem_init(&data->lock, 1, 1);
return 0;
}
#define PRESSURE_INIT(n) \
static struct pressure_config pressure_config_##n = { \
.irq = GPIO_DT_SPEC_INST_GET(n, irq_gpios), \
.max_pressure = DT_INST_PROP_OR(n, max_pressure_hpa, 100000), \
}; \
static struct pressure_data pressure_data_##n; \
DEVICE_DT_INST_DEFINE(n, pressure_init, NULL, \
&pressure_data_##n, &pressure_config_##n, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
&pressure_api);
DT_INST_FOREACH_STATUS_OKAY(PRESSURE_INIT)
3.2 在应用中使用驱动
const struct device *sensor = DEVICE_DT_GET(DT_NODELABEL(my_sensor));
sensor_sample_fetch(sensor);
struct sensor_value val;
sensor_channel_get(sensor, SENSOR_CHAN_PRESS, &val);
printk("Pressure: %d.%03d kPa\n", val.val1, val.val2);
3.3 实战技巧
-
使用
DT_INST_FOREACH_STATUS_OKAY自动生成多个实例。 -
优先级
POST_KERNEL确保内核基础服务已初始化;传感器通常使用CONFIG_SENSOR_INIT_PRIORITY。 -
利用
DEVICE_DT_INST_DEFINE宏减少重复代码。
能力四:蓝牙与网络 —— 构建通信中枢
目标:实现 BLE NUS 服务、WiFi/以太网 Socket 通信。
4.1 蓝牙 NUS 服务端 (Peripheral)
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/services/nus.h>
static void nus_recv_handler(struct bt_conn *conn, const uint8_t *data, uint16_t len)
{
// 处理接收数据
char buf[64];
memcpy(buf, data, min(len, sizeof(buf)-1));
buf[len] = 0;
printk("Received: %s\n", buf);
// 回复
const char *resp = "OK\r\n";
bt_nus_send(conn, resp, strlen(resp));
}
static struct bt_nus_cb nus_cb = {
.received = nus_recv_handler,
};
void ble_init(void)
{
bt_enable(NULL);
bt_nus_cb_register(&nus_cb, NULL);
// 开始广播
const struct bt_le_adv_param *adv_param = BT_LE_ADV_CONN_NAME;
struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),
};
bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), NULL, 0);
}
4.2 Socket 客户端 (WiFi 或以太网)
#include <zephyr/net/socket.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_context.h>
void wifi_connect(const char *ssid, const char *psk)
{
struct net_if *iface = net_if_get_default();
// 实际 WiFi 配置通常通过 Kconfig + 设备树,此处仅示意
// 若使用 `CONFIG_WIFI_ESP_AT` 或 `CONFIG_WIFI_NM_WM` 有专用 API
}
void send_data_via_tcp(void)
{
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = inet_addr("192.168.1.100")
};
connect(sock, (struct sockaddr*)&server, sizeof(server));
const char *data = "{\"temp\":25.6}\n";
send(sock, data, strlen(data), 0);
close(sock);
}
4.3 实战技巧
-
BLE 连接参数:使用
bt_conn_le_param_update调整连接间隔,优化功耗。 -
多连接管理:通过
bt_conn_cb_register跟踪连接/断开事件。 -
DNS 解析:
gethostbyname()或net_dns_resolve()。 -
MQTT/CoAP:Zephyr 内置轻量级客户端,直接配置 Kconfig 启用。
能力五:电源管理 —— 让设备续航翻倍
目标:实现 Tickless 空闲、设备挂起/恢复,达到 μA 级休眠。
5.1 系统级电源管理
// prj.conf 必须启用
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y
// 在 main loop 或 idle 线程中
while (1) {
// 处理事件...
k_sleep(K_SECONDS(5)); // 空闲时自动进入低功耗
}
5.2 设备驱动挂起/恢复
static int my_driver_pm_action(const struct device *dev, enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
// 关闭外设时钟、设置引脚为低功耗状态
break;
case PM_DEVICE_ACTION_RESUME:
// 恢复时钟、重新配置寄存器
break;
default:
return -ENOTSUP;
}
return 0;
}
// 在设备实例化时注册
PM_DEVICE_DT_INST_DEFINE(n, my_driver_pm_action);
5.3 应用层手动控制外设电源
const struct device *sensor = DEVICE_DT_GET(DT_NODELABEL(my_sensor)); pm_device_action_run(sensor, PM_DEVICE_ACTION_SUSPEND); // 关闭 // 需要时再恢复 pm_device_action_run(sensor, PM_DEVICE_ACTION_RESUME);
5.4 实战技巧
-
测量功耗:使用 Nordic 的 Power Profiler Kit,通过
CONFIG_PM_DEVICE_RUNTIME和sys_pm_force_resume()测试各状态。 -
Wake-up 源:设备树中配置
wakeup-source,并在驱动中处理中断唤醒。 -
查看 PM 状态:
pm_device_state_str()打印设备电源状态。
能力六:调试与优化 —— 准确定位问题
目标:使用断言、日志、内存监控、崩溃分析等手段快速定位 bug。
6.1 日志系统高级用法
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(my_module, LOG_LEVEL_DBG);
// 带颜色/时间戳的日志
LOG_DBG("debug: x=%d", x);
LOG_INF("info: string %s", str);
LOG_WRN("warning");
LOG_ERR("error: %d", errno);
// 十六进制 dump
LOG_HEXDUMP_INF(buffer, len, "RX data");
6.2 内存监控
// prj.conf 配置
CONFIG_HEAP_MEM_POOL_SIZE=8192
CONFIG_SYS_HEAP_RUNTIME_STATS=y
// 代码中定期打印
#include <zephyr/sys/mem_manage.h>
void print_heap_stats(void) {
struct sys_memory_stats stats;
sys_heap_runtime_stats_get(&_system_heap, &stats);
printk("Heap: free %zu, min_free %zu\n", stats.free_bytes, stats.min_free_bytes);
}
6.3 栈溢出检测
// prj.conf
CONFIG_THREAD_STACK_INFO=y
CONFIG_STACK_SENTINEL=y
// 在线程中获取栈使用情况
struct k_thread *thread = K_CURRENT;
size_t unused = thread->stack_info.size - thread->stack_info.used;
printk("Thread stack unused: %zu\n", unused);
6.4 Hard Fault 分析
-
启用异常栈回溯:
CONFIG_EXCEPTION_STACK_TRACE=y -
使用 GDB:
west debug,崩溃后执行bt(backtrace) 查看函数调用栈。 -
查看寄存器:
info registers或i r,定位 PC 值。 -
反汇编:
arm-zephyr-eabi-objdump -dS build/zephyr/zephyr.elf > listing.txt,找到 PC 所在行。
6.5 性能分析
-
使用
k_cycle_get_32()测量代码块时间(单位:CPU cycles)。 -
启用
CONFIG_TRACING配合 SEGGER SystemView 或 Tracealyzer。 -
线程状态监控:
thread_analyzer_print()打印所有线程的栈使用和调度次数。
6.6 实战技巧
-
断言:
__ASSERT(cond, "message")在 Debug 构建中检查不变量。 -
编译期优化分析:
west build -t rom_report查看内存占用明细。 -
静态分析:启用
CONFIG_COMPILER_OPT="-Werror -Wall -Wextra"提升代码质量。
📌 综合实战示例:构建低功耗蓝牙传感器节点
结合以上六项能力,我们可以快速构建一个完整的项目:周期性读取压力传感器,通过 BLE NUS 发送数据,并支持手机命令控制。
项目结构
text
my_sensor_node/
├── app.overlay # 定义传感器引脚、BLE 名称
├── prj.conf # Kconfig 配置
├── CMakeLists.txt
├── boards/
│ └── nrf52dk_nrf52832.conf # 板级配置覆盖
├── src/
│ ├── main.c
│ ├── sensor_driver.c # 自定义压力传感器驱动
│ └── ble_service.c
└── dts/
└── bindings/
└── mycompany,pressure.yaml
核心代码片段
app.overlay:
dts
/ {
aliases {
pressure-sensor = &pressure0;
};
pressure0: pressure@0 {
compatible = "mycompany,pressure";
irq-gpios = <&gpio0 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
max-pressure-hpa = <110000>;
};
};
&uart0 {
status = "disabled"; // 释放引脚供 BLE 使用
};
prj.conf:
kconfig
CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_NUS=y CONFIG_BT_DEVICE_NAME="PressureNode" CONFIG_PM_DEVICE=y CONFIG_PM_DEVICE_RUNTIME=y CONFIG_LOG=y CONFIG_HEAP_MEM_POOL_SIZE=2048
src/ble_service.c:
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/services/nus.h>
static void nus_recv_handler(struct bt_conn *conn, const uint8_t *data, uint16_t len)
{
if (len == 1 && data[0] == 'S') { // 命令 'S' 触发发送传感器数据
struct sensor_value val;
sensor_sample_fetch(pressure_sensor);
sensor_channel_get(pressure_sensor, SENSOR_CHAN_PRESS, &val);
char msg[32];
snprintf(msg, sizeof(msg), "Pressure: %d.%03d kPa\r\n", val.val1, val.val2);
bt_nus_send(conn, msg, strlen(msg));
}
}
static struct bt_nus_cb nus_cb = {
.received = nus_recv_handler,
};
void ble_init(void)
{
bt_enable(NULL);
bt_nus_cb_register(&nus_cb, NULL);
bt_le_adv_start(BT_LE_ADV_CONN_NAME, NULL, 0, NULL, 0);
}
src/main.c:
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "ble_service.h"
#include "sensor_driver.h" // 假设已实现
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
void main(void)
{
sensor_init();
ble_init();
while (1) {
k_sleep(K_SECONDS(30));
// 可选:定期进入低功耗
LOG_INF("System alive, free heap: %zu", k_mem_free_get());
}
}
🧠 总结
通过这六项关键能力的深度学习与实践,你已经能够:
-
用 Devicetree 精准描述硬件,实现跨平台移植。
-
用 Kconfig 灵活剪裁系统功能,控制代码体积。
-
开发 标准驱动,集成到 Zephyr 设备模型中。
-
搭建 BLE 和网络通信,实现 IoT 数据交互。
-
利用 电源管理 框架,大幅降低功耗。
-
借助 丰富的调试工具 快速定位问题。
接下来的提升方向可以是:参与 Zephyr 社区贡献、阅读官方 samples/ 和 tests/ 源码、尝试移植到新硬件平台。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)