低功耗软件开发指南
本文档介绍 HiDiTingV100 的系统低功耗机制、显示功耗管理服务和功耗调试方法,并提供一个按 Native Sample 方式接入目标构建的示例。示例通过 AT 命令演示深睡否决票的成对管理与睡眠统计读取,不会主动强制设备进入睡眠。
低功耗背景知识
低功耗工作原理
HiDiTingV100 提供深睡和浅睡两种系统低功耗模式。
表 1 低功耗模式说明
基本说明:
- AON(Always ON)区域包含 RTC、GPIO、CWDT 等。
- 深睡和浅睡互斥;Tickless、降压和降频可以按业务场景与睡眠模式组合。
- A 核默认阈值在 src/middleware/chips/3322/include/acore/pm_sleep_porting.h 中定义:浅睡阈值为 5 ms、深睡阈值为 10 ms。BT 核采用独立阈值配置。
- 内置 AT 调试路径中,
AT^PM=0关闭 PM;AT^PM=1开启 PM 并添加PM_ID_DEBUG否决票(进入浅睡路径);AT^PM=2开启 PM 并移除PM_ID_DEBUG否决票(进入深睡路径)。AT+PMADDVOTE、AT+PMRMVOTE分别通过非对称接口添加、移除PM_ID_DEBUG否决票。深睡关闭 UART 工作时钟时,唤醒命令可能需要发送两次,首字节才可被正确识别。
操作流程
系统由 IDLE 任务在空闲时进行判决:有深睡否决票时进入 WFI;没有否决票时,空闲时间不大于浅睡阈值则进入 WFI,超过浅睡阈值后启动 Tickless 和唤醒定时器,空闲时间不大于深睡阈值时进入浅睡,否则进入深睡。
图 1 睡眠状态

降低系统功耗的常用手段如下:
- Tickless:移除空闲时的 OS Tick 计数,减少调度和运行时间。
- 降压降频:通过调压调频接口降低模块电压和频率;外设遵循“非用即关”。
- 浅睡:CPU 进入 WFI。
- 深睡:CPU 掉电,依赖唤醒源恢复执行。
特性配置
低功耗能力由 src/drivers/boards/3322_evb/product/product_evb_standard.h 中的 ENABLE_LOW_POWER 控制。该文件不是无条件的单点开关:A 核在定义 WSTP_LPCI_ENABLE 时为 YES,BT 核还会受 DEVICE_ONLY 条件影响。产品需要关闭或开启低功耗时,应先确认目标核和产品条件,再在对应分支配置 ENABLE_LOW_POWER=YES 或 ENABLE_LOW_POWER=NO。
标准应用已在 src/application/3322/3322_app_standard/chip_init.c 调用 uapi_pm_lpc_init 完成 PM 初始化;业务 Demo 不应重复初始化。
深睡模式
深睡由 IDLE 任务管理。当没有即将执行的业务且不存在深睡否决票时,系统允许 CPU、外设等下电并降低 LDO 电压;GPIO、IPC、RTC 等深睡唤醒源可恢复系统。休眠时间采用 Tickless,避免每个 Tick 都唤醒系统。
深睡下,除 S_AGPIO 和 S_UGPIO 外,其余 IO 均处于掉电域。掉电域 IO 的电平保持依赖芯片内部弱上下拉和板级上下拉,不能作为睡眠唤醒源;常电域 IO 支持电平保持和唤醒。
图 2 深睡流程

浅睡模式
浅睡时 CPU 和外设不下电,CPU 进入 WFI,控制子模块通过下电或关时钟降低功耗;任意中断均可使系统退出浅睡。
图 3 浅睡流程

GPIO 唤醒源说明
GPIO 分为 S_AGPIO、S_MGPIO、S_EGPIO、S_HGPIO 和 ULP_GPIO 五组。
- S_AGPIO 和 ULP_GPIO 可以在深睡时作为唤醒源,且深睡期间 PINMUX、PAD(驱动能力、输入使能、上下拉)配置保持。
- S_EGPIO、S_HGPIO 和 S_MGPIO 不能在深睡时作为唤醒源,PINMUX 会失效,但 PAD 配置保持。
- 后三组 GPIO 在唤醒恢复前的 PINMUX 功能为 GPIO。对睡眠电平有强约束的管脚应单独配置:例如 SPI CS 需要持续高电平时应按板级设计保证上拉;I2C 空闲为高电平,应使用外部或芯片侧上拉。
API 接口列表
当前没有独立的 PM API 参考页,以下链接直接指向声明头文件。
表 1 低功耗接口说明
| 接口名称 | 说明 |
|---|---|
| uapi_sys_shipmode | 使芯片进入 Shipmode。 |
| uapi_clocks_config_mode | 调频投票,请求进入 LP、NORMAL、PERFORMANCE 等模式。 |
| uapi_pm_add_sleep_veto | 添加非对称深睡否决票;不可嵌套。 |
| uapi_pm_add_sleep_veto_with_symmetry | 添加对称深睡否决票;可嵌套,必须与对称移除接口配对。 |
| uapi_pm_add_sleep_veto_with_timeout | 添加带超时的睡眠否决票。 |
| uapi_pm_remove_sleep_veto | 移除非对称否决票;应与非对称添加接口成对使用。 |
| uapi_pm_remove_sleep_veto_with_symmetry | 移除对称深睡否决票;支持嵌套计数,必须与对称添加接口配对。 |
| uapi_pm_get_sleep_veto | 查询当前是否存在深睡否决。 |
| uapi_pm_veto_get_info | 获取否决票计数与最近否决信息。 |
| uapi_pm_enter_sleep | 在条件满足时执行睡眠判决;由 IDLE 流程调用,业务 Demo 不直接调用。 |
| uapi_pm_get_sleep_info | 获取睡眠统计信息;未开启睡眠记录时返回 NULL。 |
低功耗管理服务
Power manager service 当前用于屏幕功耗管理,负责亮屏和灭屏;向上对接 UIKit GUI 引擎和 Native 应用,向下对接按键、触屏、显示等驱动模块。服务链路为:PowerMgr Service API/Event → Display power manager service(GUI/Timer Event Rule、Screen Manager、Action)→ BSP API → LCD、TP、Key、Timer。
图 4 Power manager service 系统框架

通过 power_display_svr_get_api 获取显示功耗管理 API。常用成员如下。
表 1 显示功耗管理服务成员函数
| 成员函数 | 说明 | 备注 |
|---|---|---|
| turn_on_screen | 控制屏幕亮屏。 | - |
| turn_off_screen | 控制屏幕黑屏。 | - |
| get_screen_state | 获取当前屏幕状态。 | - |
| set_screen_auto_off_timeout | 设置无操作自动灭屏时间。 | 默认无操作灭屏时间为 5 s;需先开启自动亮灭屏;当前范围为 5 s~1 h;若需重启后生效,需要额外保存 NV 项。 |
| set_auto_timeout_function | 开启或关闭自动超时灭屏。 | - |
| set_screen_set_keepon_timeout | 设置应用保持亮屏时间。 | 最大有效时长为 1 h;大于 1 h 时仍按 1 h 生效,可按需二次开发。 |
| set_brightness | 设置应用自定义亮度。 | 应用亮度会覆盖系统默认亮度。 |
| get_brightness | 获取应用设置的屏幕亮度。 | - |
Native 应用设置长亮屏的基本流程如下。
const power_display_svr_api_t *display_api = power_display_svr_get_api();
if (display_api->get_screen_state() != SCREEN_ON) {
display_api->turn_on_screen();
}
display_api->set_screen_set_keepon_timeout(60000); // 单位:ms
/* 应用退出或不再需要长亮屏时恢复默认超时策略。 */
display_api->set_screen_set_keepon_timeout(0);
进入应用前设置长亮屏、退出应用后恢复 set_screen_set_keepon_timeout(0) 必须成对执行。Power manager service 与 UI 线程任务合并,连续提交长亮屏设置时,若前一次配置尚未从队列生效,后一次设置可能失败。
快速跑通低功耗 Demo
功能说明
Demo 位于 /samples/native_samples/low_power/README.md,使用 /src/middleware/chips/3322/include/shared/pm_veto_porting.h 的 A 核非 BT 专属枚举项 PM_ID_NATIVE_SAMPLE 演示对称深睡否决票。该 ID 与内置 PM 调试命令使用的 PM_ID_DEBUG 分离。Demo 只观察和管理否决状态,不调用 uapi_pm_enter_sleep()、uapi_pm_lpc_init 或 Shipmode 接口。
图 5 低功耗 Demo 流程
| AT 命令 | 调用接口 | 功能 |
|---|---|---|
AT+PMVETOADD |
uapi_pm_add_sleep_veto_with_symmetry | 添加一张 Demo 的对称深睡否决票,并输出当前计数和否决状态。 |
AT+PMSLEEPINFO |
uapi_pm_get_sleep_veto、uapi_pm_veto_get_info、uapi_pm_get_sleep_info | 输出否决票计数以及 WFI、浅睡、深睡次数。 |
AT+PMVETOREMOVE |
uapi_pm_remove_sleep_veto_with_symmetry | 移除 Demo 添加的对称深睡否决票。 |
测试顺序为:PMVETOADD → PMSLEEPINFO → PMVETOREMOVE → PMSLEEPINFO。该顺序验证“业务工作时投票阻止深睡,业务结束后撤票允许系统继续判决”的完整闭环。
编译
准备工作
说明:本文示例命令以一站式 CLI 为主。实际开发可从以下三种环境中选择,推荐使用一站式 CLI。
| 开发环境 | 适用场景 | 使用指南 |
|---|---|---|
| 一站式 CLI(推荐) | 快速完成目标选择、构建、烧录和串口监视 | 一站式 CLI 开发环境使用指南 |
| HiSpark Studio for VS Code | 图形化编辑、编译、烧录和调试 | HiSpark Studio for VS Code 开发环境使用指南 |
| WSL 与 Docker | 在 Windows 上使用一致的 Linux 容器构建环境 | WSL 与 Docker 环境使用指南 |
- 按《一站式 CLI 开发环境使用指南》安装并初始化 FBB CLI。
- 使用完整 SDK 工作区中的
src子目录;该src的同级目录必须包含 samples/native_samples。确认 samples/native_samples/low_power、samples/native_samples/CMakeLists.txt、config.py 和 at_adapter.c 已包含本 Demo 的文件和接入项;仅有src源码子目录时无法发现外层 Native Sample。 diting-community目标已增加CONFIG_ENABLE_LOW_POWER_SAMPLE和low_power_sample。板级ENABLE_LOW_POWER仍由产品配置条件决定。
$env:FBB_SDK_DIR = "<SDK_WORKSPACE>\src"
Set-Location $env:FBB_SDK_DIR
fbb setup
fbb doctor
fbb set-target pack_diting_community
fbb build
构建成功后,从 output/3322/fwpkg/diting-community.fwpkg 获取烧录包。
使用方式
将 COM3 替换为开发板实际串口,烧录并打开串口监视。
fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --port COM3 --chip Hi3322 --manual-reset --timeout 600
fbb monitor --port COM3 --baud 115200
系统完成启动后执行:
Demo 使用 PM_ID_NATIVE_SAMPLE,与 AT+PMADDVOTE、AT+PMRMVOTE、AT^PM=1/2 使用的 PM_ID_DEBUG 分离;内置命令不会改变本 Demo 的专属计数,但会影响全局否决状态。AT^PM=0 会关闭 PM,不应与 Demo 的状态观察混用。
预期结果
日志中的 total_votes、sleep_veto 会受系统其他模块和 UART 调试超时否决影响;重点观察 Demo 的 native_sample_votes 是否随成对命令增加和恢复。
[PMVETOADD] native_sample_votes=<执行前值+1> total_votes=<total> sleep_veto=1 ret=0x00000000
[PMSLEEPINFO] native_sample_votes=<执行前值+1> total_votes=<total> sleep_veto=<0-or-1> ret=0x00000000
[PMSLEEPINFO] WFI_WITH_VETO=<count> WFI_NO_VETO=<count> LIGHT_SLEEP=<count> DEEP_SLEEP=<count>
[PMVETOREMOVE] native_sample_votes=<执行前值> total_votes=<total> sleep_veto=<0-or-1> ret=0x00000000
如果目标未启用 CONFIG_PM_SLEEP_RECORD_ENABLE,统计接口会返回空指针,Demo 会保留否决状态输出并打印:
重复执行 AT+PMVETOADD,或未添加就执行 AT+PMVETOREMOVE,Demo 会返回运行错误,避免产生无法追踪的成对计数。
调试方法
- 用
AT+PMSLEEPINFO比较添加和移除前后的native_sample_votes。 - 用
AT+PMDUMP查看时钟、性能投票、电源状态、睡眠投票和唤醒源。 - 用
AT+LITSTTIMER排查短周期定时器;用AT+STACKINFO查看 CPU 占用与线程运行时间。 - 若深睡次数持续不增长,检查是否仍有业务否决票、即将到期的定时器、频繁 osal_msleep 或外设业务未完成。
文件结构与代码走读
文件结构
/samples/native_samples/low_power/
├── CMakeLists.txt # Demo 组件构建配置
├── low_power_demo.c # AT 命令处理与 PM 状态输出
└── low_power_demo.h # AT 命令表和函数声明
各文件职责
| 文件 | 职责 | 关键内容 |
|---|---|---|
low_power_demo.c |
实现命令表、对称否决票添加、移除和睡眠统计查询。 | at_low_power_demo_veto_add()、at_low_power_demo_veto_remove()、at_low_power_demo_sleep_info() |
low_power_demo.h |
声明供 AT 适配层调用的注册函数。 | at_diting_low_power_example_cmd_register() |
CMakeLists.txt |
定义组件、源文件、AT 导出宏和链接属性。 | low_power_sample、AT_DITING_EXAMPLE_LOW_POWER |
相关接入文件如下:
- samples/native_samples/CMakeLists.txt:
CONFIG_ENABLE_LOW_POWER_SAMPLE开启时引入 Demo。 - src/build/config/target_config/3322/config.py:将功能宏和
low_power_sample纳入diting-community。 - src/middleware/chips/3322/at_adapter/at_adapter.c:条件包含头文件并注册 AT 命令。
- src/middleware/utils/pm/pm_veto/pm_veto.h 与 src/middleware/utils/pm/pm_sleep/pm_sleep.h:公开 PM 接口及数据类型。
- src/application/3322/3322_app_standard/chip_init.c:标准应用完成 PM 初始化。
代码走读
CMakeLists.txt 解析
set(COMPONENT_NAME "low_power_sample")
set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/low_power_demo.c
)
set(PUBLIC_HEADER
${CMAKE_CURRENT_SOURCE_DIR}
)
set(PRIVATE_HEADER
${ROOT_DIR}/middleware/utils/at/at
${ROOT_DIR}/middleware/utils/at/at/include
${ROOT_DIR}/middleware/utils/at/at/src
${ROOT_DIR}/middleware/utils/pm/pm_veto
${ROOT_DIR}/middleware/utils/pm/pm_sleep
${ROOT_DIR}/middleware/chips/3322/include/shared
)
set(PUBLIC_DEFINES
AT_DITING_EXAMPLE_LOW_POWER
)
set(WHOLE_LINK true)
set(MAIN_COMPONENT false)
build_component()
install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*.h")
COMPONENT_NAME为low_power_sample,并在config.py的diting-community配置中加入同名组件。PUBLIC_DEFINES导出AT_DITING_EXAMPLE_LOW_POWER,供at_adapter.c条件注册命令。WHOLE_LINK为true,避免 AT 回调在链接时被裁剪。PRIVATE_DEFINES当前为空;install_sdk()导出本 Sample 的头文件。- PM 组件已经通过目标配置提供公开头文件和实现,本 Demo 不额外复制或初始化 PM 模块。
low_power_demo.h 解析
at_cmd_entry_t 的 cmd 回调类型为 at_ret_t (*)(void),三个无参数命令均放在 cmd 字段,syntax、set、read 和 test 字段保持为空。
static const at_cmd_entry_t g_at_low_power_cmd_table[] = {
{
.name = "PMVETOADD",
.cmd_id = 0x226D,
.attribute = 0,
.syntax = NULL,
.cmd = at_low_power_demo_veto_add,
.set = NULL,
.read = NULL,
.test = NULL,
},
{
.name = "PMVETOREMOVE",
.cmd_id = 0x226E,
.attribute = 0,
.syntax = NULL,
.cmd = at_low_power_demo_veto_remove,
.set = NULL,
.read = NULL,
.test = NULL,
},
{
.name = "PMSLEEPINFO",
.cmd_id = 0x226F,
.attribute = 0,
.syntax = NULL,
.cmd = at_low_power_demo_sleep_info,
.set = NULL,
.read = NULL,
.test = NULL,
},
};
at_diting_low_power_example_cmd_register() 将该表传给 uapi_at_cmd_table_register。在开启 CONFIG_AT_SUPPORT_CMD_TABLE_CHECK 时,框架会通过命令名称查询已注册表并拒绝重名;它不会分配或保留某个 cmd_id 数值区间。新增命令前,应针对当前目标会注册的全部命令表逐项检查命令名称和 cmd_id 都唯一,再调用注册接口。
对称否决票处理
static bool g_low_power_demo_veto_added;
static at_ret_t at_low_power_demo_veto_add(void)
{
if (g_low_power_demo_veto_added) {
printf("[PMVETOADD] native sample veto is already active\n");
return AT_RET_RUN_ERROR;
}
errcode_t ret = uapi_pm_add_sleep_veto_with_symmetry(PM_ID_NATIVE_SAMPLE);
if (ret == ERRCODE_SUCC) {
g_low_power_demo_veto_added = true;
}
low_power_demo_print_veto_status("PMVETOADD", ret);
return (ret == ERRCODE_SUCC) ? AT_RET_OK : AT_RET_RUN_ERROR;
}
PM_ID_NATIVE_SAMPLE 是当前 SDK 在 A 核非 BT 枚举中为 Native Sample 预留的专属投票 ID;它紧跟 PM_ID_PWM 并由枚举自然递增,示例不填写固定数值。g_low_power_demo_veto_added 只记录本 Demo 自己添加的一张票,防止用户重复添加或无配对移除。
static at_ret_t at_low_power_demo_veto_remove(void)
{
if (!g_low_power_demo_veto_added) {
printf("[PMVETOREMOVE] native sample veto is not active\n");
return AT_RET_RUN_ERROR;
}
errcode_t ret = uapi_pm_remove_sleep_veto_with_symmetry(PM_ID_NATIVE_SAMPLE);
if (ret == ERRCODE_SUCC) {
g_low_power_demo_veto_added = false;
}
low_power_demo_print_veto_status("PMVETOREMOVE", ret);
return (ret == ERRCODE_SUCC) ? AT_RET_OK : AT_RET_RUN_ERROR;
}
对称接口按 ID 递增和递减计数;业务开始和结束必须严格配对。示例采用与按键业务一致的对称接口模式,但不混用内置调试命令的非对称接口。
状态读取
static at_ret_t at_low_power_demo_sleep_info(void)
{
const sleep_info_t *sleep_info = uapi_pm_get_sleep_info();
low_power_demo_print_veto_status("PMSLEEPINFO", ERRCODE_SUCC);
if (sleep_info == NULL) {
printf("[PMSLEEPINFO] sleep statistics unavailable\n");
return AT_RET_OK;
}
printf("[PMSLEEPINFO] WFI_WITH_VETO=%llu WFI_NO_VETO=%llu LIGHT_SLEEP=%llu DEEP_SLEEP=%llu\n",
(unsigned long long)sleep_info->sleep_history[PM_WFI_WITHVETO].total_slp_count,
(unsigned long long)sleep_info->sleep_history[PM_WFI_NOVETO].total_slp_count,
(unsigned long long)sleep_info->sleep_history[PM_SLEEP_LS].total_slp_count,
(unsigned long long)sleep_info->sleep_history[PM_SLEEP_DS].total_slp_count);
return AT_RET_OK;
}
uapi_pm_get_sleep_info() 在没有启用 CONFIG_PM_SLEEP_RECORD_ENABLE 时返回 NULL,所以示例先判空。统计包含有否决 WFI、无否决 WFI、浅睡和深睡次数;它用于观察状态,不替代系统的 IDLE 睡眠判决。
AT 注册与目标接入
/* middleware/chips/3322/at_adapter/at_adapter.c */
#ifdef AT_DITING_EXAMPLE_LOW_POWER
#include "low_power_demo.h"
#endif
/* AT 注册流程中 */
#ifdef AT_DITING_EXAMPLE_LOW_POWER
at_diting_low_power_example_cmd_register();
#endif
# build/config/target_config/3322/config.py 的 diting-community 配置
'CONFIG_ENABLE_LOW_POWER_SAMPLE',
# 同一配置的 ram_component 列表
'low_power_sample',
基于低功耗 Demo 开发自己的应用
本节使用 app_run 注册一次性启动入口,用于演示“添加对称否决票 → 读取状态 → 成对撤票”的完整闭环。它与前文 AT Demo 独立:本示例不定义 AT_DITING_* 宏、不修改 at_adapter.c、也不注册 AT 命令。对于需要持续持票的真实业务,应在业务开始和结束边界使用同一对对称接口,而不是在启动入口中长期持票。
代码清单
以下以 my_low_power_app 为例。目录位于外层 Native Sample,组件随 diting-community 目标构建:
/samples/native_samples/my_low_power_app/
├── CMakeLists.txt
├── my_low_power_app.c
└── my_low_power_app.h
完成下列步骤后,组件会在系统启动时执行一次并输出添加、查询、撤票结果:
- 新建
my_low_power_app.c、my_low_power_app.h和CMakeLists.txt。 - 在
config.py、samples/native_samples/CMakeLists.txt 中接入组件。 - 在实际使用的 A 核链接脚本中保留
app_run分段。 - 明确业务开始和结束边界;真实产品只使用已分配且归属本业务的
PM_ID_*。 - 编译、烧录并通过启动日志、PM DFX 或功耗仪确认撤票后系统可继续进行深睡判决。
对称接口可嵌套,但添加和移除次数必须相同。下例沿用 PM_ID_NATIVE_SAMPLE,仅适用于替换本指南提供的 Native Sample;不要让两个并存组件共享该 ID。产品需要并存的新业务时,应在 A 核非 BT 的 pm_veto_id 枚举中、PM_ID_MAX 前增加经评审分配的专属项且不填写固定数值,然后将下例替换为该项。不要借用 PM_ID_DEBUG 或在业务源文件中自行填写枚举数值;A 核与 BT 核的枚举布局不同。
CMakeLists.txt 修改示例
app_run 入口位于静态组件中,WHOLE_LINK 必须为 true,避免仅被链接段引用的对象在链接时被裁剪。组件不依赖 AT;app_init.h 已由 Native Sample 父组件公开,PM 公共头与前文实际低功耗 Sample 一样由目标配置提供。
# src/build/config/target_config/3322/config.py
# diting-community 的 defines 列表
'CONFIG_ENABLE_MY_LOW_POWER_APP_RUN_SAMPLE',
# 同一 diting-community 配置的 ram_component 列表;名称必须与 COMPONENT_NAME 一致
'my_low_power_app_run_sample',
# samples/native_samples/CMakeLists.txt
if("CONFIG_ENABLE_MY_LOW_POWER_APP_RUN_SAMPLE" IN_LIST DEFINES)
add_subdirectory_if_exist(my_low_power_app)
endif()
# samples/native_samples/my_low_power_app/CMakeLists.txt
set(COMPONENT_NAME "my_low_power_app_run_sample")
set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/my_low_power_app.c
)
set(PUBLIC_HEADER
${CMAKE_CURRENT_SOURCE_DIR}
)
set(PRIVATE_HEADER
)
set(PRIVATE_DEFINES
)
set(PUBLIC_DEFINES
)
set(COMPONENT_CCFLAGS
)
set(WHOLE_LINK
true
)
set(MAIN_COMPONENT
false
)
build_component()
install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*.h")
不要为这个组件增加 AT_DITING_EXAMPLE_MY_LOW_POWER,也不要在 at_adapter.c 中添加 include 或注册调用;上述功能宏只负责条件引入 Native Sample。当前 diting-community 目标已开启 OHOS 启动支持,app_run 本身不需要额外的 AT 功能宏。
关键代码片段
app_run(func) 的入口类型必须为 void (*)(void)。下例用 SDK 已定义的 PM_ID_NATIVE_SAMPLE 演示成对调用;若组件与本指南 Demo 并存,必须按前述规则替换为经分配的专属 ID。uapi_pm_get_sleep_info 在未开启睡眠记录时会返回 NULL,因此示例只将其作为“统计是否可用”的状态输出。
/* my_low_power_app.h */
#ifndef MY_LOW_POWER_APP_H
#define MY_LOW_POWER_APP_H
void my_low_power_app_run(void);
#endif
/* my_low_power_app.c */
#include <stdio.h>
#include "app_init.h"
#include "errcode.h"
#include "pm_veto.h"
#include "pm_sleep.h"
#include "my_low_power_app.h"
static unsigned int my_low_power_native_sample_vote_count(void)
{
pm_veto_t *veto_info = uapi_pm_veto_get_info();
if (veto_info == NULL) {
return 0;
}
return (unsigned int)veto_info->veto_counts.sub_counts[PM_ID_NATIVE_SAMPLE];
}
void my_low_power_app_run(void)
{
unsigned int before_count = my_low_power_native_sample_vote_count();
errcode_t ret = uapi_pm_add_sleep_veto_with_symmetry(PM_ID_NATIVE_SAMPLE);
sleep_info_t *sleep_info = uapi_pm_get_sleep_info();
printf("my_low_power_app: add=%u native-sample-votes=%u->%u sleep-record=%s\n", (unsigned int)ret,
before_count, my_low_power_native_sample_vote_count(), sleep_info == NULL ? "off" : "on");
if (ret != ERRCODE_SUCC) {
return;
}
ret = uapi_pm_remove_sleep_veto_with_symmetry(PM_ID_NATIVE_SAMPLE);
printf("my_low_power_app: remove=%u native-sample-votes=%u\n", (unsigned int)ret,
my_low_power_native_sample_vote_count());
}
app_run(my_low_power_app_run);
真实业务应把添加接口放在需要保持资源的开始处、把配对移除接口放在结束处,并在所有成功和错误路径保持数量一致。不要调用 uapi_pm_enter_sleep 或 uapi_pm_lpc_init:前者由 IDLE 任务根据空闲时间和投票状态判决,后者已由标准 A 核初始化。需要了解当前投票计数和最后一次投票信息时,使用 uapi_pm_veto_get_info;睡眠统计字段与错误码说明以对应头文件为准。
测试验证
- 完成前述
config.py、父 CMake、组件 CMake 与链接脚本配置,并按快速 Demo 的构建、烧录和串口监视步骤操作。 - 启动日志应依次出现
my_low_power_app: add=0与my_low_power_app: remove=0;记录启动前的native-sample-votes,确认撤票后的计数恢复至该初始值。不要将全局深睡否决状态简单等同于本示例的计数,其他业务也可能持有否决票。 - 若日志显示
sleep-record=off,说明当前目标未开启CONFIG_PM_SLEEP_RECORD_ENABLE;添加和撤票仍可验证,但不应读取睡眠统计内容。 - 在真实业务的开始和结束边界验证对称接口配对。可通过 uapi_pm_get_sleep_veto、
AT+PMDUMP、20 s 周期 DFX 日志或功耗仪观察撤票后深睡次数和时间。 - 分别验证短周期定时器、业务传输中和业务空闲后的功耗行为;任何一路遗漏撤票都可能长期阻止深睡。
app_run运行配置
app_run 在 /src/middleware/utils/app_init/app_init.h 中定义,会生成 .zinitcall.app_run0.init 链接段。OHOS_SystemInit() 会执行 MODULE_INIT(run),所以必须在当前 diting-community 使用的 A 核链接脚本 /src/drivers/boards/3322_evb/linker/standard/acore/normal/acore.prelds 中,将以下行放在 __zinitcall_run_start 与 __zinitcall_run_end 之间:
__zinitcall_run_start = .;
KEEP (*(.zinitcall.run*.init))
KEEP (*(SORT(.zinitcall.app_run*.init)))
__zinitcall_run_end = .;
链接段必须位于这两个边界之间,否则启动流程不会遍历 app_run 入口。若产品选择了其他 A 核链接脚本变体,应只在该目标实际使用的脚本中加入同一条 KEEP 规则。
app_run 的执行还依赖 OHOS 启动路径。当前 ENABLE_UIKIT 场景下,如果系统检测不到 LCD,ohos_startup() 会直接返回而不调用 OHOS_SystemInit();此时不会执行 app_run 入口。请在 LCD 已连接、对应启动路径可用的板型上验证本示例。无论哪种情况,都不要为了提前运行而在业务线程中重复初始化 PM。
注意事项
投票配对与系统判决
- 一张否决票即可阻止系统进入深睡。业务工作时添加否决票,业务完成后及时移除;否则系统可能长期无法进入深睡。
- 非对称接口适合单次开关,不支持嵌套;对称接口维护计数,支持嵌套,但添加和移除数量必须相同。
- 带超时接口独立使用,超时时间内不允许深睡,适合不便显式撤票的短暂场景。
- 本 Demo 的
PM_ID_NATIVE_SAMPLE与内置 PM 调试路径使用的PM_ID_DEBUG分离。AT+PMADDVOTE、AT+PMRMVOTE、AT^PM=1/2不会改变 Demo 的专属计数,但会改变全局否决状态;AT^PM=0会关闭 PM,也不用于本 Demo 的测试过程。
低功耗调试
调试前应了解硬件电源管脚、硬件版图、PINMUX 和 PAD 配置,并参考硬件用户指南和芯片用户指南。本节聚焦软件侧的功耗分析和优化。
功耗分解
整机电池功耗可分为主芯片功耗、外设芯片功耗和 IO 管脚功耗。主芯片功耗与主频、低功耗状态相关;外设功耗以器件手册为准;IO 功耗与板级硬件和原理图强相关。
典型场景功耗调试
下表涉及的连接周期、IO 漏电和器件电流均为板级或器件相关的参考值,并非 SDK 的固定功耗保证;产品设计与验收应以所选器件手册、硬件配置和实测结果为准。
| 场景 | 说明 | 调试重点 |
|---|---|---|
| 睡眠功耗 | 主芯片处于深睡,仅保留 XO 32k;GPIO、RTC 等可唤醒;外设按业务配置为工作、睡眠或下电。 | 先调最小版本(芯片、NorFlash、PSRAM、NandFlash),再通过 IOLDO1 调 IO,最后逐步加入外设并预留芯片、IO、外设测试点。 |
| 运行功耗 | CPU 工作,所需时钟开启,系统处理任务。 | 主频越高功耗越大;按业务调频,默认亮屏高频、灭屏低频;缩短运行时间并减少无效唤醒。EVB 的 IO 漏电实测可作为参考,实际以板级测量为准。 |
| 蓝牙功耗 | BT 核处理协议和业务,包含 BT sniff、BLE 保连和 BT 待机。 | 受 sniff 周期、单周期收发量、设备距离影响;BT sniff 保连典型 500 ms,手机无业务 BLE 保连典型 240 ms。BLE 同条件功耗更低但稳定性相对较差;可通过 gap_ble_connect_param_update 协商连接间隔。 |
| 关机功耗 | Shipmode 下芯片仅保留最小运行。 | 支持内部 RC 32k 或外部 XO 32k,以及关机闹钟、按键、充电 VBUS 等唤醒;整机还需考虑 Charger 功耗。 |
功耗 DFX
DFX 日志的实际输出周期以当前构建配置和串口日志为准,常见配置约每 20 s 输出一次;内容包括系统上电以来运行时间、MCU 深睡/浅睡时间、系统睡眠历史、MCU 睡眠历史和 UTC 时间戳。历史值 0 表示上一次输出至本次输出期间未进入过深睡,1 表示进入过深睡。

常用 DFX 命令如下:
AT^SETPIN=pin,mode,pull,ie,sleep ie,ds:设置管脚模式、上下拉、输入使能、睡眠输入使能和驱动能力;参数范围以 src/middleware/chips/3322/at_wear_cmd_3322/at/at_wear.c 为准。AT+GETPIN=pin:查询单个管脚状态。AT+GETALLPIN:查询全部 IO 管脚状态。AT+LITSTTIMER:查询系统 Timer 周期和模式。AT+PMDUMP:查询时钟、性能投票、电源电压或状态、睡眠投票和唤醒源。

功耗优化原则
- 稳定性、性能、低功耗依次为优先级;先保证功能和可用性。
- 提高深睡占空比:业务完成后尽快进入深睡,避免任务死循环。
- 在高频完成工作后尽快切回低频,以合适频率完成合适工作。
- 减少唤醒源和不常用外设;外设用时开、用后关。
- 优化代码部署与算法局部性,按性能需求把高频运算放入合适的高速内存。
GPIO 与 IO 配置
IO 配置的目标是避免芯片侧与外设侧电压存在压差而产生漏电。结构体定义位于 /src/drivers/chips/3322/include/shared/pinctrl_porting.h:
typedef struct {
hal_pio_func_t func;
hal_pio_drive_t drive;
hal_pio_pull_t pull;
#if defined(CONFIG_PINCTRL_SUPPORT_IE)
hal_pio_ie_t ie;
#endif
} hal_pio_config_t;
| 字段 | 含义 |
|---|---|
func |
管脚复用功能,例如 GPIO、I2C、UART;具体管脚能力以硬件用户指南为准。 |
drive |
驱动能力,共四档 HAL_PIO_DRIVE_0~HAL_PIO_DRIVE_3;HAL_PIO_DRIVE_MAX 表示使用默认配置。 |
pull |
弱上拉/下拉配置,阻值约 33 kΩ;HAL_PIO_PULL_DOWN、HAL_PIO_PULL_UP 和 HAL_PIO_PULL_MAX 分别表示下拉、上拉和默认配置。 |
ie |
输入使能;HAL_PIO_IE_ENABLE 为打开,HAL_PIO_IE_DISABLE 为关闭。 |
diting-community 的板级管脚配置以 src/drivers/boards/3322_evb/board_config/board_evb_diting.h 为准。该文件会随启用的外设宏选择不同分支;产品改动硬件或功能宏后,应重新以实际目标的板级文件核对。通用原则如下:
- 无外设连接的管脚配置为高阻并关闭 IE。
- 有外设连接时,先保证深睡两端电平一致,再配置工作态。
- 仅输出管脚可关闭 IE。
- 板级上拉/下拉需要与 PAD 配置一致,避免压差漏电。
- 深睡掉电管脚应结合对端器件状态配置上拉或下拉。
- NorFlash、PSRAM、NandFlash 的管脚属于固定优化配置,产品无硬件变更时不应随意修改。
以下代码均取自当前 board_evb_diting.h 的 g_pio_function_config。注释中的 UART、SPI、Cat1、GNSS 说明的是管脚连接或预留用途;当前 diting-community 配置可能选择 GPIO 状态以降低未启用外设的漏电。HAL_PIO_IE_ENBALE 是旧拼写,当前代码使用 HAL_PIO_IE_ENABLE。
/* QSPI NorFlash:时钟和 CS 仅输出,CS 按板级设计保持上拉。 */
{ HAL_PIO_FUNC_QSPI0, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_EGPIO6
{ HAL_PIO_FUNC_QSPI0, HAL_PIO_DRIVE_2, HAL_PIO_PULL_DOWN, HAL_PIO_IE_ENABLE }, // S_EGPIO7
{ HAL_PIO_FUNC_QSPI0, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_EGPIO8
{ HAL_PIO_FUNC_QSPI0, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_EGPIO9
{ HAL_PIO_FUNC_QSPI0, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_DISABLE }, // S_EGPIO10 clk
{ HAL_PIO_FUNC_QSPI0, HAL_PIO_DRIVE_2, HAL_PIO_PULL_UP, HAL_PIO_IE_DISABLE }, // S_EGPIO11 cs
/* 当前 Cat1 GPIO 保持 GPIO 功能和输入使能。 */
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_AGPIO33 gpio36_cat1
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_AGPIO34 gpio37_cat1
/* 当前 GNSS UART 预留脚保持 GPIO;是否切换 UART 由产品板级配置决定。 */
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_DISABLE }, // S_MGPIO0 uart0_txd_gnss
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_MGPIO1 uart0_rxd_gnss
/* 触摸中断唤醒脚保持 IE 打开,并按当前板级配置上拉。 */
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_UP, HAL_PIO_IE_ENABLE }, // S_AGPIO0 tp int
/* 当前 Cat1 SPI 预留脚保持 GPIO;输入使能按每根管脚的板级状态配置。 */
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_MGPIO23 spi1_di_cat1
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_DISABLE }, // S_MGPIO24 spi1_do_cat1
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_DISABLE }, // S_MGPIO25 spi1_clk_cat1
{ HAL_PIO_FUNC_GPIO, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_DISABLE }, // S_MGPIO26 spi1_cs_cat1
/* I2C:当前 EVB 由板级上拉决定,芯片侧保持默认上下拉。 */
{ HAL_PIO_FUNC_I2C0_M1, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_MGPIO4
{ HAL_PIO_FUNC_I2C0_M1, HAL_PIO_DRIVE_2, HAL_PIO_PULL_MAX, HAL_PIO_IE_ENABLE }, // S_MGPIO5
UART 空闲态应保持高电平,常见的产品配置会让 TX 关闭 IE、RX/TX 按硬件需要上拉;SPI CS 的睡眠电平应按外设要求保持,其他 SPI 线按空闲状态处理。上述是管脚电平与外设协议的通用原则,不能替代当前目标 board_evb_diting.h 中的实际复用和上下拉配置。
外围器件功耗
以下电流为典型参考值,具体结果受器件型号、电压、温度和板级连接影响,应以器件手册和实测为准。
- NorFlash 无访问时默认进入 Standby,深睡时进入 Deep Power Down,唤醒后退出;典型 Deep Power Down 电流为 1 μA~5 μA,具体取决于器件选型。
- PSRAM 合封在芯片内部,深睡时进入 Half-sleep,唤醒后退出;25 ℃ 下典型功耗为 20 μA@1.8 V。
- NandFlash 无访问时默认 Standby,典型功耗为 5 μA~50 μA,取决于器件选型。
- 其他外设以器件手册为准。I2C 上拉电源关闭时,I2C 管脚应下拉;上拉电源未关闭时,I2C 管脚应上拉,避免漏电。
常见运行与功耗问题
| 现象 | 原因与处理 |
|---|---|
| 无法进入休眠 | 检查短周期 Timer(例如 10 ms)、任务循环或无主动释放、osal_msleep(10) 造成的周期调度,以及未撤销的否决票。空闲时关闭周期 Timer;使用合理超时的阻塞/非阻塞接口;投票严格配对并缩短持票时间。 |
| SPI 等外设概率性收发异常 | 深睡会使外设下电,异步发送未完成也可能被睡眠中断。业务开始和结束分别使用睡眠否决添加、移除接口。 |
| 功耗高于预期 | 硬件先检查底电流;软件依次检查 AT+PMDUMP、AT+STACKINFO、AT+LITSTTIMER 和 DFX 睡眠统计。 |
| BT、音频、显示与系统低功耗的关系 | 子系统睡眠时关闭自身门控并撤票,工作时添加否决票;系统汇总子系统 suspend 和所有投票,全部允许时进入深睡,否则保持浅睡或 WFI。 |
| 进一步降低功耗 | 不使用的外设关闭时钟;不使用 IO 设为 GPIO 输入、关闭 IE,并按需要配置上下拉。 |
| 驱动与低功耗 | SPI/I2C/UART/eMMC/SDIO 深睡下电、唤醒后默认恢复;NorFlash/PSRAM/NandFlash 已处理睡眠流程;ADC 位于掉电域,使用前调用 uapi_adc_init,用后调用 uapi_adc_deinit;USB 插入和 VICAP 使用期间默认不进入深睡并切换高频。 |
常见编译错误
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
找不到 pm_veto.h 或 pm_sleep.h |
目标未纳入 PM 组件,或源文件缺少 PM 头文件。 | 确认目标包含 pm_3322,并在源文件包含两个公开 PM 头文件。 |
PM_ID_NATIVE_SAMPLE 未定义 |
当前目标未使用已更新的 A 核非 BT /src/middleware/chips/3322/include/shared/pm_veto_porting.h,或未通过 pm_veto.h 引入 PM 投票 ID 定义。 |
使用包含该枚举项的同一 SDK 构建根目录,包含 pm_veto.h;不要在 Sample 源文件中自行定义投票 ID。 |
undefined reference to uapi_pm_* |
PM 组件未被目标链接。 | 检查目标基础配置和 pm_3322 组件,不要在 Sample 中复制 PM 实现。 |
at_ret_t 或 uapi_at_cmd_table_register 未定义 |
AT 头文件或组件接入不完整。 | 在 Demo 头文件包含 at.h,检查 CMake 的 AT 私有头文件路径和 at_adapter.c 注册。 |
| AT 命令不存在 | 功能宏、父目录引入、组件或 AT 注册未生效。 | 检查 CONFIG_ENABLE_LOW_POWER_SAMPLE、low_power_sample、samples/native_samples/CMakeLists.txt 和 AT_DITING_EXAMPLE_LOW_POWER。 |
[PMSLEEPINFO] sleep statistics unavailable |
目标未开启 CONFIG_PM_SLEEP_RECORD_ENABLE。 |
这是可预期降级输出;如需要统计,在产品配置中开启睡眠记录后重新编译。 |
调用 PMVETOREMOVE 失败 |
没有与之配对的 Demo 添加操作,或 Demo 已在重启后丢失其本地配对状态。 | 在同一次启动中依次执行 PMVETOADD 与 PMVETOREMOVE;重启后从添加命令重新开始。 |