跳转至

子系统使用

学习目标

通过本节学习,您将能够:

  • 掌握 Zephyr 日志系统的配置和使用
  • 熟练使用 Shell 控制台进行运行时调试
  • 理解存储子系统(Flash、文件系统、NVS)的使用
  • 掌握低功耗管理的配置和优化技巧

Zephyr 提供了丰富的子系统,涵盖日志、Shell、存储、低功耗管理等核心功能。合理使用这些子系统可以大大提升开发效率和应用质量。

日志系统(Logging)

日志系统是调试和监控应用的重要工具。Zephyr 的日志系统支持多种后端、日志级别和格式化选项。

日志架构

graph LR
    A[应用模块] --> B[日志核心]
    C[驱动模块] --> B
    D[内核模块] --> B
    B --> E[UART 后端]
    B --> F[RTT 后端]
    B --> G[文件后端]

    style B fill:#e3f2fd
    style E fill:#c8e6c9
    style F fill:#c8e6c9
    style G fill:#c8e6c9

配置选项

prj.conf 中配置日志系统:

# 启用日志系统
CONFIG_LOG=y

# 设置默认日志级别(0=OFF, 1=ERR, 2=WRN, 3=INF, 4=DBG)
CONFIG_LOG_DEFAULT_LEVEL=3

# 启用 UART 后端
CONFIG_LOG_BACKEND_UART=y

# 配置日志缓冲区大小(字节)
CONFIG_LOG_BUFFER_SIZE=2048

# 启用日志时间戳
CONFIG_LOG_TIMESTAMP=y

# 启用日志颜色(需要终端支持)
CONFIG_LOG_BACKEND_SHOW_COLOR=y

# 启用异步日志(提升性能)
CONFIG_LOG_MODE_DEFERRED=y

使用方法

在源文件中使用日志系统:

#include <zephyr/logging/log.h>

// 注册日志模块(模块名,默认日志级别)
LOG_MODULE_REGISTER(my_module, LOG_LEVEL_INF);

void my_function(void)
{
    int value = 42;

    // 不同级别的日志
    LOG_ERR("Error message: %d", value);
    LOG_WRN("Warning message: %d", value);
    LOG_INF("Info message: %d", value);
    LOG_DBG("Debug message: %d", value);

    // 十六进制数据输出
    uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
    LOG_HEXDUMP_INF(data, sizeof(data), "Data dump:");
}

日志级别说明

级别 用途 示例
0 LOG_LEVEL_OFF 关闭日志 生产环境
1 LOG_LEVEL_ERR 错误信息 致命错误、异常
2 LOG_LEVEL_WRN 警告信息 潜在问题、降级运行
3 LOG_LEVEL_INF 一般信息 状态变化、关键事件
4 LOG_LEVEL_DBG 调试信息 详细执行流程

日志格式化

日志输出格式示例:

[00:00:01.234,567] <inf> my_module: Info message: 42
[00:00:01.234,890] <wrn> my_module: Warning message: 42
[00:00:01.235,123] <err> my_module: Error message: 42

格式说明: - [00:00:01.234,567]:时间戳(小时:分钟:秒.毫秒,微秒) - <inf>:日志级别 - my_module:模块名 - Info message: 42:日志内容

性能影响分析

日志系统对性能的影响:

模式 延迟 CPU 占用 适用场景
同步日志 高(阻塞) 调试阶段
异步日志 低(非阻塞) 生产环境
关闭日志 性能关键场景

性能优化建议

  • 生产环境使用异步日志(CONFIG_LOG_MODE_DEFERRED=y
  • 根据需要调整日志级别,避免输出过多调试信息
  • 关键性能路径中避免使用日志
  • 使用条件编译控制日志代码(#if CONFIG_LOG

完整示例:多模块日志系统

// sensor.c
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sensor, LOG_LEVEL_DBG);

void sensor_read(void)
{
    LOG_INF("Reading sensor data...");
    int value = 25;
    LOG_DBG("Sensor value: %d", value);
}

// main.c
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

extern void sensor_read(void);

int main(void)
{
    LOG_INF("Application started");

    while (1) {
        sensor_read();
        k_sleep(K_SECONDS(1));
    }

    return 0;
}

配置文件 prj.conf

CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_MODE_DEFERRED=y
CONFIG_LOG_BUFFER_SIZE=2048

Shell 控制台

Shell 控制台提供了运行时交互式调试和控制功能,可以在不重新编译的情况下查看系统状态、修改参数、执行命令。

Shell 架构

Shell 系统采用命令树结构,支持命令分组和子命令。

配置 Shell

prj.conf 中启用 Shell:

# 启用 Shell
CONFIG_SHELL=y

# 启用串口后端
CONFIG_SHELL_BACKEND_SERIAL=y

# 配置 Shell 缓冲区大小
CONFIG_SHELL_CMD_BUFF_SIZE=256

# 启用命令历史
CONFIG_SHELL_HISTORY=y
CONFIG_SHELL_HISTORY_BUFFER=512

# 启用 Tab 自动补全
CONFIG_SHELL_TAB=y
CONFIG_SHELL_TAB_AUTOCOMPLETION=y

# 启用 VT100 颜色
CONFIG_SHELL_VT100_COLORS=y

使用内置命令

Zephyr 提供了丰富的内置 Shell 命令:

# 查看所有命令
uart:~$ help

# 查看内核信息
uart:~$ kernel version
uart:~$ kernel uptime
uart:~$ kernel threads

# 查看设备列表
uart:~$ device list

# 查看日志
uart:~$ log status
uart:~$ log enable inf my_module
uart:~$ log disable my_module

# 查看内存使用
uart:~$ kernel stacks

注册自定义命令

创建自定义 Shell 命令:

#include <zephyr/shell/shell.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(sensor_shell, LOG_LEVEL_INF);

// 传感器读取命令
static int cmd_sensor_read(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int value = 25;  // 模拟读取传感器
    shell_print(sh, "Sensor value: %d", value);

    return 0;
}

// 传感器配置命令
static int cmd_sensor_config(const struct shell *sh, size_t argc, char **argv)
{
    if (argc < 2) {
        shell_error(sh, "Usage: sensor config <rate>");
        return -EINVAL;
    }

    int rate = atoi(argv[1]);
    shell_print(sh, "Sensor rate set to: %d Hz", rate);

    return 0;
}

// 传感器校准命令
static int cmd_sensor_calibrate(const struct shell *sh, size_t argc, char **argv)
{
    shell_print(sh, "Calibrating sensor...");
    k_sleep(K_MSEC(500));
    shell_print(sh, "Calibration complete");

    return 0;
}

// 定义子命令
SHELL_STATIC_SUBCMD_SET_CREATE(sub_sensor,
    SHELL_CMD(read, NULL, "Read sensor value", cmd_sensor_read),
    SHELL_CMD(config, NULL, "Configure sensor rate", cmd_sensor_config),
    SHELL_CMD(calibrate, NULL, "Calibrate sensor", cmd_sensor_calibrate),
    SHELL_SUBCMD_SET_END
);

// 注册顶层命令
SHELL_CMD_REGISTER(sensor, &sub_sensor, "Sensor commands", NULL);

使用自定义命令:

uart:~$ sensor read
Sensor value: 25

uart:~$ sensor config 100
Sensor rate set to: 100 Hz

uart:~$ sensor calibrate
Calibrating sensor...
Calibration complete

命令处理函数

命令处理函数的参数说明:

static int cmd_handler(const struct shell *sh, size_t argc, char **argv)
{
    // sh: Shell 实例指针,用于输出
    // argc: 参数个数(包括命令本身)
    // argv: 参数数组

    // 输出普通信息
    shell_print(sh, "Normal message");

    // 输出警告信息(黄色)
    shell_warn(sh, "Warning message");

    // 输出错误信息(红色)
    shell_error(sh, "Error message");

    // 输出信息(绿色)
    shell_info(sh, "Info message");

    return 0;  // 返回 0 表示成功,负数表示错误
}

运行时调试技巧

使用 Shell 进行运行时调试:

# 1. 查看线程状态
uart:~$ kernel threads
Scheduler: 257 since last call
Threads:
 0x20000a00 main            
        options: 0x0, priority: 0 timeout: 0
        state: pending
        stack size 2048, unused 1024, usage 1024 / 2048 (50 %)

# 2. 查看线程栈使用情况
uart:~$ kernel stacks
0x20000a00 main            (real size 2048):    unused 1024 usage 1024 / 2048 (50 %)

# 3. 动态调整日志级别
uart:~$ log enable dbg sensor
uart:~$ log disable main

# 4. 查看设备状态
uart:~$ device list
devices:
- gpio@50000000 (READY)
- uart@40002000 (READY)
- i2c@40003000 (READY)

# 5. 查看系统运行时间
uart:~$ kernel uptime
Uptime: 12345 ms

存储子系统

Zephyr 提供了多种存储方案,包括 Flash 存储、文件系统和非易失性存储(NVS)。

存储方案对比

存储方案 特性 性能 适用场景
Flash API 直接访问 Flash 固件升级、原始数据存储
LittleFS 嵌入式文件系统 日志文件、配置文件
FAT 通用文件系统 SD 卡、USB 存储
NVS 键值对存储 配置参数、校准数据

Flash 存储

Flash 存储提供了对 Flash 分区的直接访问。

配置 Flash 分区

在设备树中定义 Flash 分区:

&flash0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        boot_partition: partition@0 {
            label = "mcuboot";
            reg = <0x00000000 0x0000C000>;
        };

        slot0_partition: partition@c000 {
            label = "image-0";
            reg = <0x0000C000 0x00076000>;
        };

        storage_partition: partition@82000 {
            label = "storage";
            reg = <0x00082000 0x0001E000>;
        };
    };
};

使用 Flash API

#include <zephyr/storage/flash_map.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(flash_example, LOG_LEVEL_INF);

void flash_example(void)
{
    const struct flash_area *fa;
    uint8_t write_buf[256];
    uint8_t read_buf[256];
    int rc;

    // 打开 Flash 分区
    rc = flash_area_open(FLASH_AREA_ID(storage), &fa);
    if (rc != 0) {
        LOG_ERR("Failed to open flash area: %d", rc);
        return;
    }

    // 擦除 Flash(必须先擦除才能写入)
    rc = flash_area_erase(fa, 0, fa->fa_size);
    if (rc != 0) {
        LOG_ERR("Failed to erase flash: %d", rc);
        flash_area_close(fa);
        return;
    }

    // 准备写入数据
    for (int i = 0; i < sizeof(write_buf); i++) {
        write_buf[i] = i;
    }

    // 写入数据
    rc = flash_area_write(fa, 0, write_buf, sizeof(write_buf));
    if (rc != 0) {
        LOG_ERR("Failed to write flash: %d", rc);
        flash_area_close(fa);
        return;
    }

    // 读取数据
    rc = flash_area_read(fa, 0, read_buf, sizeof(read_buf));
    if (rc != 0) {
        LOG_ERR("Failed to read flash: %d", rc);
        flash_area_close(fa);
        return;
    }

    // 验证数据
    if (memcmp(write_buf, read_buf, sizeof(write_buf)) == 0) {
        LOG_INF("Flash read/write successful");
    } else {
        LOG_ERR("Flash data mismatch");
    }

    // 关闭 Flash 分区
    flash_area_close(fa);
}

LittleFS 文件系统

LittleFS 是专为嵌入式系统设计的文件系统,具有掉电保护和磨损均衡功能。

配置 LittleFS

prj.conf 中启用 LittleFS:

# 启用文件系统
CONFIG_FILE_SYSTEM=y

# 启用 LittleFS
CONFIG_FILE_SYSTEM_LITTLEFS=y

# 配置 LittleFS 参数
CONFIG_FS_LITTLEFS_NUM_FILES=4
CONFIG_FS_LITTLEFS_NUM_DIRS=4
CONFIG_FS_LITTLEFS_CACHE_SIZE=256

使用 LittleFS

#include <zephyr/fs/fs.h>
#include <zephyr/fs/littlefs.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(littlefs_example, LOG_LEVEL_INF);

// 定义挂载点
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage);
static struct fs_mount_t lfs_mnt = {
    .type = FS_LITTLEFS,
    .fs_data = &storage,
    .storage_dev = (void *)FLASH_AREA_ID(storage),
    .mnt_point = "/lfs",
};

void littlefs_example(void)
{
    struct fs_file_t file;
    int rc;

    // 挂载文件系统
    rc = fs_mount(&lfs_mnt);
    if (rc != 0) {
        LOG_ERR("Failed to mount filesystem: %d", rc);
        return;
    }
    LOG_INF("Filesystem mounted at %s", lfs_mnt.mnt_point);

    // 初始化文件结构
    fs_file_t_init(&file);

    // 打开文件(创建或覆盖)
    rc = fs_open(&file, "/lfs/test.txt", FS_O_CREATE | FS_O_WRITE);
    if (rc != 0) {
        LOG_ERR("Failed to open file: %d", rc);
        return;
    }

    // 写入数据
    const char *data = "Hello, LittleFS!";
    rc = fs_write(&file, data, strlen(data));
    if (rc < 0) {
        LOG_ERR("Failed to write file: %d", rc);
        fs_close(&file);
        return;
    }
    LOG_INF("Wrote %d bytes", rc);

    // 关闭文件
    fs_close(&file);

    // 重新打开文件读取
    rc = fs_open(&file, "/lfs/test.txt", FS_O_READ);
    if (rc != 0) {
        LOG_ERR("Failed to open file for reading: %d", rc);
        return;
    }

    // 读取数据
    char read_buf[64];
    rc = fs_read(&file, read_buf, sizeof(read_buf) - 1);
    if (rc < 0) {
        LOG_ERR("Failed to read file: %d", rc);
        fs_close(&file);
        return;
    }
    read_buf[rc] = '\0';
    LOG_INF("Read: %s", read_buf);

    // 关闭文件
    fs_close(&file);

    // 列出目录内容
    struct fs_dir_t dir;
    fs_dir_t_init(&dir);

    rc = fs_opendir(&dir, "/lfs");
    if (rc != 0) {
        LOG_ERR("Failed to open directory: %d", rc);
        return;
    }

    struct fs_dirent entry;
    while (fs_readdir(&dir, &entry) == 0) {
        if (entry.name[0] == 0) {
            break;
        }
        LOG_INF("File: %s, size: %zu", entry.name, entry.size);
    }

    fs_closedir(&dir);
}

NVS(非易失性存储)

NVS 提供了简单的键值对存储,适合保存配置参数和校准数据。

配置 NVS

prj.conf 中启用 NVS:

# 启用 NVS
CONFIG_NVS=y

# 配置 Flash
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y

在设备树中定义存储分区(如前面 Flash 存储部分所示)。

使用 NVS

#include <zephyr/fs/nvs.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(nvs_example, LOG_LEVEL_INF);

// NVS 文件系统实例
static struct nvs_fs fs;

// 配置参数 ID
#define NVS_ID_SENSOR_RATE    1
#define NVS_ID_SENSOR_OFFSET  2
#define NVS_ID_DEVICE_NAME    3

void nvs_example(void)
{
    int rc;

    // 获取 Flash 分区信息
    struct flash_sector sectors[8];
    const struct flash_area *fa;

    rc = flash_area_open(FLASH_AREA_ID(storage), &fa);
    if (rc != 0) {
        LOG_ERR("Failed to open flash area: %d", rc);
        return;
    }

    // 初始化 NVS
    fs.flash_device = fa->fa_dev;
    fs.offset = fa->fa_off;
    fs.sector_size = 4096;
    fs.sector_count = fa->fa_size / fs.sector_size;

    rc = nvs_mount(&fs);
    if (rc != 0) {
        LOG_ERR("Failed to mount NVS: %d", rc);
        flash_area_close(fa);
        return;
    }
    LOG_INF("NVS mounted successfully");

    // 写入整数值
    uint32_t sensor_rate = 100;
    rc = nvs_write(&fs, NVS_ID_SENSOR_RATE, &sensor_rate, sizeof(sensor_rate));
    if (rc < 0) {
        LOG_ERR("Failed to write sensor rate: %d", rc);
    } else {
        LOG_INF("Wrote sensor rate: %u", sensor_rate);
    }

    // 写入浮点数值
    float sensor_offset = 1.5f;
    rc = nvs_write(&fs, NVS_ID_SENSOR_OFFSET, &sensor_offset, sizeof(sensor_offset));
    if (rc < 0) {
        LOG_ERR("Failed to write sensor offset: %d", rc);
    } else {
        LOG_INF("Wrote sensor offset: %.2f", sensor_offset);
    }

    // 写入字符串
    const char *device_name = "MyDevice";
    rc = nvs_write(&fs, NVS_ID_DEVICE_NAME, device_name, strlen(device_name) + 1);
    if (rc < 0) {
        LOG_ERR("Failed to write device name: %d", rc);
    } else {
        LOG_INF("Wrote device name: %s", device_name);
    }

    // 读取整数值
    uint32_t read_rate;
    rc = nvs_read(&fs, NVS_ID_SENSOR_RATE, &read_rate, sizeof(read_rate));
    if (rc > 0) {
        LOG_INF("Read sensor rate: %u", read_rate);
    } else {
        LOG_ERR("Failed to read sensor rate: %d", rc);
    }

    // 读取浮点数值
    float read_offset;
    rc = nvs_read(&fs, NVS_ID_SENSOR_OFFSET, &read_offset, sizeof(read_offset));
    if (rc > 0) {
        LOG_INF("Read sensor offset: %.2f", read_offset);
    } else {
        LOG_ERR("Failed to read sensor offset: %d", rc);
    }

    // 读取字符串
    char read_name[32];
    rc = nvs_read(&fs, NVS_ID_DEVICE_NAME, read_name, sizeof(read_name));
    if (rc > 0) {
        LOG_INF("Read device name: %s", read_name);
    } else {
        LOG_ERR("Failed to read device name: %d", rc);
    }

    // 删除条目
    rc = nvs_delete(&fs, NVS_ID_SENSOR_RATE);
    if (rc == 0) {
        LOG_INF("Deleted sensor rate");
    }

    flash_area_close(fa);
}

NVS 最佳实践

NVS 使用建议

  • 使用有意义的 ID 常量,避免硬编码数字
  • 定期检查返回值,处理读写错误
  • 避免频繁写入同一个键(Flash 有写入次数限制)
  • 使用版本号管理配置数据格式
  • 在写入前验证数据有效性

存储性能优化

磨损均衡

Flash 存储有写入次数限制,需要实现磨损均衡:

  • LittleFS 和 NVS 都内置了磨损均衡机制
  • 避免频繁写入同一位置
  • 使用循环缓冲区分散写入

缓存策略

优化存储访问性能:

// 使用内存缓存减少 Flash 访问
static struct {
    uint32_t sensor_rate;
    float sensor_offset;
    bool dirty;
} config_cache;

void config_load(void)
{
    nvs_read(&fs, NVS_ID_SENSOR_RATE, &config_cache.sensor_rate, 
             sizeof(config_cache.sensor_rate));
    nvs_read(&fs, NVS_ID_SENSOR_OFFSET, &config_cache.sensor_offset, 
             sizeof(config_cache.sensor_offset));
    config_cache.dirty = false;
}

void config_save(void)
{
    if (config_cache.dirty) {
        nvs_write(&fs, NVS_ID_SENSOR_RATE, &config_cache.sensor_rate, 
                  sizeof(config_cache.sensor_rate));
        nvs_write(&fs, NVS_ID_SENSOR_OFFSET, &config_cache.sensor_offset, 
                  sizeof(config_cache.sensor_offset));
        config_cache.dirty = false;
    }
}

void config_set_rate(uint32_t rate)
{
    config_cache.sensor_rate = rate;
    config_cache.dirty = true;
}

低功耗管理

低功耗管理是物联网设备的关键需求。Zephyr 提供了完善的电源管理框架。

电源管理架构

graph TB
    A[应用层] --> B[电源管理核心]
    B --> C[设备电源管理]
    B --> D[CPU 电源管理]
    C --> E[外设驱动]
    D --> F[睡眠模式]

    style B fill:#e3f2fd
    style C fill:#c8e6c9
    style D fill:#c8e6c9

睡眠模式

Zephyr 支持多种睡眠模式:

模式 功耗 唤醒时间 保持状态 适用场景
Active 最高 - 全部 正常运行
Idle 微秒级 全部 短暂空闲
Standby 毫秒级 RAM 周期性任务
Suspend 秒级 部分 RAM 长时间休眠
Deep Sleep 最低 秒级 极低功耗

配置电源管理

prj.conf 中启用电源管理:

# 启用电源管理
CONFIG_PM=y

# 启用设备电源管理
CONFIG_PM_DEVICE=y

# 启用设备运行时电源管理
CONFIG_PM_DEVICE_RUNTIME=y

# 配置空闲时自动进入低功耗模式
CONFIG_PM_POLICY_DEFAULT=y

# 启用电源管理调试
CONFIG_PM_DEBUG=y

使用电源管理

设备电源管理

控制外设的电源状态:

#include <zephyr/pm/device.h>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(pm_example, LOG_LEVEL_INF);

void device_power_example(void)
{
    const struct device *i2c_dev = DEVICE_DT_GET(DT_NODELABEL(i2c0));

    if (!device_is_ready(i2c_dev)) {
        LOG_ERR("I2C device not ready");
        return;
    }

    // 使用设备前确保其处于活动状态
    pm_device_action_run(i2c_dev, PM_DEVICE_ACTION_RESUME);

    // 使用 I2C 设备
    // ... I2C 操作 ...

    // 使用完毕后挂起设备以节省功耗
    pm_device_action_run(i2c_dev, PM_DEVICE_ACTION_SUSPEND);

    LOG_INF("Device suspended");
}

系统睡眠

应用主动进入睡眠模式:

#include <zephyr/kernel.h>
#include <zephyr/pm/pm.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(sleep_example, LOG_LEVEL_INF);

void sleep_example(void)
{
    LOG_INF("Entering sleep mode");

    // 睡眠 5 秒(系统会自动选择合适的睡眠模式)
    k_sleep(K_SECONDS(5));

    LOG_INF("Woke up from sleep");
}

唤醒源配置

配置 GPIO 中断作为唤醒源:

#include <zephyr/drivers/gpio.h>
#include <zephyr/pm/pm.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(wakeup_example, LOG_LEVEL_INF);

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
static struct gpio_callback button_cb_data;

void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    LOG_INF("Button pressed - woke up from sleep");
}

void wakeup_source_setup(void)
{
    if (!device_is_ready(button.port)) {
        LOG_ERR("Button device not ready");
        return;
    }

    // 配置按钮为输入,带上拉
    gpio_pin_configure_dt(&button, GPIO_INPUT);

    // 配置中断(下降沿触发)
    gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);

    // 注册中断回调
    gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
    gpio_add_callback(button.port, &button_cb_data);

    LOG_INF("Wakeup source configured");
}

功耗测量

测量系统功耗的方法:

硬件测量

使用电流表或功耗分析仪:

  1. 断开开发板的 USB 供电
  2. 使用外部电源供电
  3. 在电源和开发板之间串联电流表
  4. 记录不同模式下的电流值

软件估算

使用 Zephyr 的功耗统计功能:

#include <zephyr/pm/pm.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(power_stats, LOG_LEVEL_INF);

void print_power_stats(void)
{
    // 获取电源管理统计信息
    struct pm_state_info info;

    for (int i = 0; i < PM_STATE_COUNT; i++) {
        if (pm_state_get(i, &info)) {
            LOG_INF("State %d: min_residency=%u, exit_latency=%u",
                    i, info.min_residency_us, info.exit_latency_us);
        }
    }
}

低功耗应用示例

完整的低功耗数据采集器示例:

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/pm.h>
#include <zephyr/pm/device.h>
#include <zephyr/fs/nvs.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(low_power_app, LOG_LEVEL_INF);

// 采集间隔(秒)
#define SAMPLE_INTERVAL 60

// NVS 实例
static struct nvs_fs fs;

void sensor_sample_and_store(void)
{
    const struct device *sensor = DEVICE_DT_GET(DT_NODELABEL(sensor0));
    struct sensor_value temp;

    // 唤醒传感器
    pm_device_action_run(sensor, PM_DEVICE_ACTION_RESUME);

    // 读取传感器数据
    sensor_sample_fetch(sensor);
    sensor_channel_get(sensor, SENSOR_CHAN_AMBIENT_TEMP, &temp);

    LOG_INF("Temperature: %d.%06d C", temp.val1, temp.val2);

    // 存储数据到 NVS
    uint32_t timestamp = k_uptime_get_32();
    nvs_write(&fs, timestamp, &temp, sizeof(temp));

    // 挂起传感器
    pm_device_action_run(sensor, PM_DEVICE_ACTION_SUSPEND);
}

int main(void)
{
    LOG_INF("Low power data logger started");

    // 初始化 NVS
    // ... NVS 初始化代码 ...

    while (1) {
        // 采集和存储数据
        sensor_sample_and_store();

        // 进入睡眠模式
        LOG_INF("Entering sleep for %d seconds", SAMPLE_INTERVAL);
        k_sleep(K_SECONDS(SAMPLE_INTERVAL));
    }

    return 0;
}

配置文件 prj.conf

# 电源管理
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y

# 传感器
CONFIG_SENSOR=y

# 存储
CONFIG_NVS=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y

# 日志
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3

功耗优化技巧

功耗优化建议

1. 外设管理 - 不使用的外设及时关闭 - 使用设备运行时电源管理(CONFIG_PM_DEVICE_RUNTIME) - 优先使用低功耗外设(如 LPUART)

2. 时钟管理 - 降低 CPU 时钟频率 - 关闭不需要的时钟源 - 使用低功耗时钟(如 32kHz RTC)

3. 内存优化 - 减少 RAM 使用(RAM 需要持续供电) - 使用 Flash 存储常量数据 - 关闭不需要的内存区域

4. 任务调度 - 合并周期性任务,减少唤醒次数 - 使用更长的睡眠周期 - 避免频繁的上下文切换

5. 通信优化 - 批量传输数据,减少通信次数 - 使用低功耗通信协议(如 BLE) - 优化通信参数(如 BLE 连接间隔)

功耗测试案例

不同配置下的功耗对比:

配置 平均功耗 峰值功耗 电池寿命(2000mAh)
无优化(Active) 10 mA 20 mA 8 天
启用 Idle 5 mA 20 mA 16 天
启用 Standby 500 µA 20 mA 166 天
深度优化 50 µA 20 mA 4.5 年

优化效果

通过合理的电源管理配置,可以将平均功耗从 10mA 降低到 50µA,电池寿命从 8 天延长到 4.5 年!

子系统集成实战

实操任务 1:集成日志和 Shell

创建一个集成日志和 Shell 的应用,实现运行时日志级别调整。

项目结构

my_app/
├── CMakeLists.txt
├── prj.conf
└── src/
    └── main.c

CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(log_shell_app)

target_sources(app PRIVATE src/main.c)

prj.conf

# 日志系统
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_MODE_DEFERRED=y

# Shell 系统
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_SERIAL=y
CONFIG_SHELL_HISTORY=y
CONFIG_SHELL_TAB_AUTOCOMPLETION=y

# 串口
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y

src/main.c

#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

// 模拟传感器模块
LOG_MODULE_DECLARE(sensor, LOG_LEVEL_DBG);

// 传感器读取函数
void sensor_read(void)
{
    LOG_DBG("Reading sensor...");
    int value = 25;
    LOG_INF("Sensor value: %d", value);
}

// Shell 命令:读取传感器
static int cmd_sensor_read(const struct shell *sh, size_t argc, char **argv)
{
    sensor_read();
    return 0;
}

// Shell 命令:设置日志级别
static int cmd_log_level(const struct shell *sh, size_t argc, char **argv)
{
    if (argc < 3) {
        shell_error(sh, "Usage: loglevel <module> <level>");
        shell_print(sh, "Levels: 0=OFF, 1=ERR, 2=WRN, 3=INF, 4=DBG");
        return -EINVAL;
    }

    const char *module = argv[1];
    int level = atoi(argv[2]);

    shell_print(sh, "Setting %s log level to %d", module, level);

    // 这里简化处理,实际应该使用 log_filter_set()
    return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(sub_app,
    SHELL_CMD(sensor, NULL, "Read sensor", cmd_sensor_read),
    SHELL_CMD(loglevel, NULL, "Set log level", cmd_log_level),
    SHELL_SUBCMD_SET_END
);

SHELL_CMD_REGISTER(app, &sub_app, "Application commands", NULL);

int main(void)
{
    LOG_INF("Application started");

    while (1) {
        sensor_read();
        k_sleep(K_SECONDS(5));
    }

    return 0;
}

编译和运行

west build -b nrf52840dk_nrf52840
west flash

测试

uart:~$ app sensor
[00:00:05.123,456] <inf> main: Sensor value: 25

uart:~$ app loglevel main 4
Setting main log level to 4

uart:~$ kernel threads
Scheduler: 123 since last call
Threads:
 0x20000a00 main            
        options: 0x0, priority: 0 timeout: 0
        state: pending

实操任务 2:使用 NVS 保存传感器校准参数

创建一个应用,使用 NVS 保存和读取传感器校准参数。

prj.conf

CONFIG_NVS=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3

src/main.c

#include <zephyr/kernel.h>
#include <zephyr/fs/nvs.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(calibration, LOG_LEVEL_INF);

static struct nvs_fs fs;

#define NVS_ID_CAL_OFFSET  1
#define NVS_ID_CAL_SCALE   2

typedef struct {
    float offset;
    float scale;
} sensor_calibration_t;

int calibration_init(void)
{
    int rc;
    const struct flash_area *fa;

    rc = flash_area_open(FLASH_AREA_ID(storage), &fa);
    if (rc != 0) {
        LOG_ERR("Failed to open flash area: %d", rc);
        return rc;
    }

    fs.flash_device = fa->fa_dev;
    fs.offset = fa->fa_off;
    fs.sector_size = 4096;
    fs.sector_count = fa->fa_size / fs.sector_size;

    rc = nvs_mount(&fs);
    if (rc != 0) {
        LOG_ERR("Failed to mount NVS: %d", rc);
        flash_area_close(fa);
        return rc;
    }

    LOG_INF("NVS initialized");
    flash_area_close(fa);
    return 0;
}

int calibration_save(sensor_calibration_t *cal)
{
    int rc;

    rc = nvs_write(&fs, NVS_ID_CAL_OFFSET, &cal->offset, sizeof(cal->offset));
    if (rc < 0) {
        LOG_ERR("Failed to write offset: %d", rc);
        return rc;
    }

    rc = nvs_write(&fs, NVS_ID_CAL_SCALE, &cal->scale, sizeof(cal->scale));
    if (rc < 0) {
        LOG_ERR("Failed to write scale: %d", rc);
        return rc;
    }

    LOG_INF("Calibration saved: offset=%.2f, scale=%.2f", cal->offset, cal->scale);
    return 0;
}

int calibration_load(sensor_calibration_t *cal)
{
    int rc;

    rc = nvs_read(&fs, NVS_ID_CAL_OFFSET, &cal->offset, sizeof(cal->offset));
    if (rc < 0) {
        LOG_WRN("Failed to read offset, using default");
        cal->offset = 0.0f;
    }

    rc = nvs_read(&fs, NVS_ID_CAL_SCALE, &cal->scale, sizeof(cal->scale));
    if (rc < 0) {
        LOG_WRN("Failed to read scale, using default");
        cal->scale = 1.0f;
    }

    LOG_INF("Calibration loaded: offset=%.2f, scale=%.2f", cal->offset, cal->scale);
    return 0;
}

int main(void)
{
    sensor_calibration_t cal;

    LOG_INF("Calibration example started");

    // 初始化 NVS
    if (calibration_init() != 0) {
        LOG_ERR("Failed to initialize calibration");
        return -1;
    }

    // 加载校准参数
    calibration_load(&cal);

    // 模拟校准过程
    LOG_INF("Performing calibration...");
    k_sleep(K_SECONDS(2));

    cal.offset = 1.5f;
    cal.scale = 0.98f;

    // 保存校准参数
    calibration_save(&cal);

    // 验证:重新加载
    sensor_calibration_t cal_verify;
    calibration_load(&cal_verify);

    if (cal_verify.offset == cal.offset && cal_verify.scale == cal.scale) {
        LOG_INF("Calibration verified successfully");
    } else {
        LOG_ERR("Calibration verification failed");
    }

    return 0;
}

实操任务 3:低功耗数据采集器

创建一个低功耗数据采集器,周期性唤醒、采集数据、存储、然后休眠。

prj.conf

# 电源管理
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y

# 存储
CONFIG_NVS=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y

# 日志
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_MODE_DEFERRED=y

# GPIO(用于唤醒)
CONFIG_GPIO=y

src/main.c

#include <zephyr/kernel.h>
#include <zephyr/pm/pm.h>
#include <zephyr/pm/device.h>
#include <zephyr/fs/nvs.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(low_power_logger, LOG_LEVEL_INF);

// 采集间隔(秒)
#define SAMPLE_INTERVAL 10

// NVS 实例
static struct nvs_fs fs;

// 按钮配置(用于唤醒)
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw0), gpios, {0});
static struct gpio_callback button_cb_data;

// 数据结构
typedef struct {
    uint32_t timestamp;
    int16_t temperature;
    uint16_t humidity;
} sensor_data_t;

// 按钮中断回调
void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    LOG_INF("Button pressed - manual wakeup");
}

int nvs_init(void)
{
    int rc;
    const struct flash_area *fa;

    rc = flash_area_open(FLASH_AREA_ID(storage), &fa);
    if (rc != 0) {
        return rc;
    }

    fs.flash_device = fa->fa_dev;
    fs.offset = fa->fa_off;
    fs.sector_size = 4096;
    fs.sector_count = fa->fa_size / fs.sector_size;

    rc = nvs_mount(&fs);
    flash_area_close(fa);

    return rc;
}

void sensor_sample(sensor_data_t *data)
{
    // 模拟传感器读取
    data->timestamp = k_uptime_get_32();
    data->temperature = 2500;  // 25.00°C
    data->humidity = 6500;     // 65.00%

    LOG_INF("Sampled: T=%d.%02d°C, H=%d.%02d%%",
            data->temperature / 100, data->temperature % 100,
            data->humidity / 100, data->humidity % 100);
}

void data_store(sensor_data_t *data)
{
    static uint16_t data_id = 100;

    int rc = nvs_write(&fs, data_id++, data, sizeof(sensor_data_t));
    if (rc < 0) {
        LOG_ERR("Failed to store data: %d", rc);
    } else {
        LOG_INF("Data stored with ID: %d", data_id - 1);
    }
}

void wakeup_source_init(void)
{
    if (!device_is_ready(button.port)) {
        LOG_WRN("Button not available");
        return;
    }

    gpio_pin_configure_dt(&button, GPIO_INPUT);
    gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
    gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
    gpio_add_callback(button.port, &button_cb_data);

    LOG_INF("Wakeup source configured");
}

int main(void)
{
    sensor_data_t data;
    int sample_count = 0;

    LOG_INF("Low power data logger started");

    // 初始化 NVS
    if (nvs_init() != 0) {
        LOG_ERR("Failed to initialize NVS");
        return -1;
    }
    LOG_INF("NVS initialized");

    // 配置唤醒源
    wakeup_source_init();

    while (1) {
        // 采集数据
        sensor_sample(&data);

        // 存储数据
        data_store(&data);

        sample_count++;
        LOG_INF("Sample count: %d", sample_count);

        // 进入睡眠模式
        LOG_INF("Entering sleep for %d seconds...", SAMPLE_INTERVAL);

        // 刷新日志缓冲区(异步日志模式)
        k_sleep(K_MSEC(100));

        // 睡眠
        k_sleep(K_SECONDS(SAMPLE_INTERVAL));

        LOG_INF("Woke up from sleep");
    }

    return 0;
}

编译和测试

# 编译
west build -b nrf52840dk_nrf52840

# 烧录
west flash

# 查看输出
minicom -D /dev/ttyACM0

预期输出

[00:00:00.123,456] <inf> low_power_logger: Low power data logger started
[00:00:00.123,789] <inf> low_power_logger: NVS initialized
[00:00:00.124,012] <inf> low_power_logger: Wakeup source configured
[00:00:00.124,345] <inf> low_power_logger: Sampled: T=25.00°C, H=65.00%
[00:00:00.124,678] <inf> low_power_logger: Data stored with ID: 100
[00:00:00.124,901] <inf> low_power_logger: Sample count: 1
[00:00:00.125,234] <inf> low_power_logger: Entering sleep for 10 seconds...
[00:00:10.125,567] <inf> low_power_logger: Woke up from sleep
[00:00:10.125,890] <inf> low_power_logger: Sampled: T=25.00°C, H=65.00%
...

功耗测试

使用电流表测量不同阶段的功耗:

阶段 电流 持续时间 占比
采集和存储 10 mA 100 ms 1%
睡眠 50 µA 9.9 s 99%
平均功耗 ~150 µA - -

优化效果

通过合理的睡眠策略,平均功耗降低到 150µA,使用 2000mAh 电池可以运行约 1.5 年!

学习总结

通过本节学习,您应该掌握了:

  • ✓ 日志系统的配置和使用,能够进行多模块日志管理
  • ✓ Shell 控制台的使用,能够创建自定义命令进行运行时调试
  • ✓ 存储子系统的使用,包括 Flash、LittleFS 和 NVS
  • ✓ 低功耗管理的配置和优化,能够设计低功耗应用

常见问题

问题 1:日志输出不完整

症状:日志输出被截断或丢失

原因: - 日志缓冲区太小 - 异步日志模式下,程序退出前未刷新缓冲区

解决方案

# 增加日志缓冲区
CONFIG_LOG_BUFFER_SIZE=4096

# 或使用同步日志模式(调试阶段)
CONFIG_LOG_MODE_IMMEDIATE=y

在程序退出前刷新日志:

k_sleep(K_MSEC(100));  // 等待日志输出完成

问题 2:NVS 写入失败

症状nvs_write() 返回负数错误码

原因: - Flash 分区未正确配置 - Flash 未擦除 - 数据大小超过限制

解决方案

// 检查返回值
int rc = nvs_write(&fs, id, data, size);
if (rc == -ENOSPC) {
    LOG_ERR("NVS full, need to clear");
    // 清理旧数据或重新格式化
} else if (rc < 0) {
    LOG_ERR("NVS write failed: %d", rc);
}

问题 3:设备无法进入低功耗模式

症状:配置了电源管理,但功耗仍然很高

原因: - 有外设未挂起 - 有线程处于就绪状态 - 定时器或中断频繁唤醒

解决方案

# 使用 Shell 检查线程状态
uart:~$ kernel threads

# 检查设备状态
uart:~$ device list

确保所有外设都已挂起:

pm_device_action_run(device, PM_DEVICE_ACTION_SUSPEND);

下一步

完成本节学习后,您已经掌握了 Zephyr 的核心子系统。接下来可以:

  • 进入第三阶段:高级深耕期,学习源码分析和系统优化
  • 开始实际项目开发,将所学知识应用到实践中
  • 深入学习特定子系统的高级特性

需要帮助?

如果您在学习过程中遇到问题,可以:

💬 讨论与反馈

欢迎在下方评论区分享您的学习心得、提出问题或给出建议。评论系统基于 GitHub Discussions,需要 GitHub 账号登录。

正在加载评论系统...