子系统使用¶
学习目标
通过本节学习,您将能够:
- 掌握 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:
在设备树中定义存储分区(如前面 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");
}
功耗测量¶
测量系统功耗的方法:
硬件测量¶
使用电流表或功耗分析仪:
- 断开开发板的 USB 供电
- 使用外部电源供电
- 在电源和开发板之间串联电流表
- 记录不同模式下的电流值
软件估算¶
使用 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 的应用,实现运行时日志级别调整。
项目结构¶
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;
}
编译和运行¶
测试¶
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¶
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;
}
编译和测试¶
预期输出¶
[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:日志输出不完整
症状:日志输出被截断或丢失
原因: - 日志缓冲区太小 - 异步日志模式下,程序退出前未刷新缓冲区
解决方案:
在程序退出前刷新日志:
问题 2:NVS 写入失败
症状:nvs_write() 返回负数错误码
原因: - Flash 分区未正确配置 - Flash 未擦除 - 数据大小超过限制
解决方案:
问题 3:设备无法进入低功耗模式
症状:配置了电源管理,但功耗仍然很高
原因: - 有外设未挂起 - 有线程处于就绪状态 - 定时器或中断频繁唤醒
解决方案:
确保所有外设都已挂起:
下一步¶
完成本节学习后,您已经掌握了 Zephyr 的核心子系统。接下来可以:
- 进入第三阶段:高级深耕期,学习源码分析和系统优化
- 开始实际项目开发,将所学知识应用到实践中
- 深入学习特定子系统的高级特性
💬 讨论与反馈
欢迎在下方评论区分享您的学习心得、提出问题或给出建议。评论系统基于 GitHub Discussions,需要 GitHub 账号登录。