这份笔记将围绕 硬件描述、系统配置、驱动开发、通信协议栈、电源管理、调试优化 六大核心能力,通过完整代码示例和实战技巧,帮助你从“会用”走向“精通”。

能力一:设备树 (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 分析
  1. 启用异常栈回溯CONFIG_EXCEPTION_STACK_TRACE=y

  2. 使用 GDBwest debug,崩溃后执行 bt (backtrace) 查看函数调用栈。

  3. 查看寄存器info registers 或 i r,定位 PC 值。

  4. 反汇编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/ 源码、尝试移植到新硬件平台。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐