跳转至

低功耗软件开发指南

本文档介绍 HiDiTingV100 的系统低功耗机制、显示功耗管理服务和功耗调试方法,并提供一个按 Native Sample 方式接入目标构建的示例。示例通过 AT 命令演示深睡否决票的成对管理与睡眠统计读取,不会主动强制设备进入睡眠。


低功耗背景知识

低功耗工作原理

HiDiTingV100 提供深睡和浅睡两种系统低功耗模式。

表 1 低功耗模式说明

模式 芯片状态 特点
深睡模式 CPU 和外设下电;Memory 进入睡眠保持或下电;XO 32M 关闭;数字模块除 AON/ULP AON 外下电;PMU BUCK 进入 ECO 并降压。仅支持 GPIO、RTC、UART L0、IPC 等唤醒源。 CPU 掉电、OS Tick 关闭;支持蓝牙/星闪保连、睡眠唤醒通知、外设低功耗与快速恢复、模块可控下电和多种唤醒源。
浅睡模式 CPU 进入 WFI,RAM 不下电,外设可按需下电或关时钟;任意中断可退出。 支持 Tickless、蓝牙/星闪保连、外设正常工作及可控下电、模块可控掉电和全部中断唤醒。

基本说明:

  • 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+PMADDVOTEAT+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=YESENABLE_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 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 流程

低功耗 Demo 流程:PMVETOADD、PMSLEEPINFO、PMVETOREMOVE、PMSLEEPINFO

AT 命令 调用接口 功能
AT+PMVETOADD uapi_pm_add_sleep_veto_with_symmetry 添加一张 Demo 的对称深睡否决票,并输出当前计数和否决状态。
AT+PMSLEEPINFO uapi_pm_get_sleep_vetouapi_pm_veto_get_infouapi_pm_get_sleep_info 输出否决票计数以及 WFI、浅睡、深睡次数。
AT+PMVETOREMOVE uapi_pm_remove_sleep_veto_with_symmetry 移除 Demo 添加的对称深睡否决票。

测试顺序为:PMVETOADDPMSLEEPINFOPMVETOREMOVEPMSLEEPINFO。该顺序验证“业务工作时投票阻止深睡,业务结束后撤票允许系统继续判决”的完整闭环。

编译

准备工作

说明:本文示例命令以一站式 CLI 为主。实际开发可从以下三种环境中选择,推荐使用一站式 CLI。

开发环境 适用场景 使用指南
一站式 CLI(推荐) 快速完成目标选择、构建、烧录和串口监视 一站式 CLI 开发环境使用指南
HiSpark Studio for VS Code 图形化编辑、编译、烧录和调试 HiSpark Studio for VS Code 开发环境使用指南
WSL 与 Docker 在 Windows 上使用一致的 Linux 容器构建环境 WSL 与 Docker 环境使用指南
  1. 《一站式 CLI 开发环境使用指南》安装并初始化 FBB CLI。
  2. 使用完整 SDK 工作区中的 src 子目录;该 src 的同级目录必须包含 samples/native_samples。确认 samples/native_samples/low_powersamples/native_samples/CMakeLists.txtconfig.pyat_adapter.c 已包含本 Demo 的文件和接入项;仅有 src 源码子目录时无法发现外层 Native Sample。
  3. diting-community 目标已增加 CONFIG_ENABLE_LOW_POWER_SAMPLElow_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

系统完成启动后执行:

AT+PMVETOADD
AT+PMSLEEPINFO
AT+PMVETOREMOVE
AT+PMSLEEPINFO

Demo 使用 PM_ID_NATIVE_SAMPLE,与 AT+PMADDVOTEAT+PMRMVOTEAT^PM=1/2 使用的 PM_ID_DEBUG 分离;内置命令不会改变本 Demo 的专属计数,但会影响全局否决状态。AT^PM=0 会关闭 PM,不应与 Demo 的状态观察混用。

预期结果

日志中的 total_votessleep_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 会保留否决状态输出并打印:

[PMSLEEPINFO] sleep statistics unavailable

重复执行 AT+PMVETOADD,或未添加就执行 AT+PMVETOREMOVE,Demo 会返回运行错误,避免产生无法追踪的成对计数。

调试方法

  1. AT+PMSLEEPINFO 比较添加和移除前后的 native_sample_votes
  2. AT+PMDUMP 查看时钟、性能投票、电源状态、睡眠投票和唤醒源。
  3. AT+LITSTTIMER 排查短周期定时器;用 AT+STACKINFO 查看 CPU 占用与线程运行时间。
  4. 若深睡次数持续不增长,检查是否仍有业务否决票、即将到期的定时器、频繁 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_sampleAT_DITING_EXAMPLE_LOW_POWER

相关接入文件如下:

代码走读

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_NAMElow_power_sample,并在 config.pyditing-community 配置中加入同名组件。
  • PUBLIC_DEFINES 导出 AT_DITING_EXAMPLE_LOW_POWER,供 at_adapter.c 条件注册命令。
  • WHOLE_LINKtrue,避免 AT 回调在链接时被裁剪。
  • PRIVATE_DEFINES 当前为空;install_sdk() 导出本 Sample 的头文件。
  • PM 组件已经通过目标配置提供公开头文件和实现,本 Demo 不额外复制或初始化 PM 模块。

low_power_demo.h 解析

at_cmd_entry_tcmd 回调类型为 at_ret_t (*)(void),三个无参数命令均放在 cmd 字段,syntaxsetreadtest 字段保持为空。

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.cmy_low_power_app.hCMakeLists.txt
  • config.pysamples/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_sleepuapi_pm_lpc_init:前者由 IDLE 任务根据空闲时间和投票状态判决,后者已由标准 A 核初始化。需要了解当前投票计数和最后一次投票信息时,使用 uapi_pm_veto_get_info;睡眠统计字段与错误码说明以对应头文件为准。

测试验证

  1. 完成前述 config.py、父 CMake、组件 CMake 与链接脚本配置,并按快速 Demo 的构建、烧录和串口监视步骤操作。
  2. 启动日志应依次出现 my_low_power_app: add=0my_low_power_app: remove=0;记录启动前的 native-sample-votes,确认撤票后的计数恢复至该初始值。不要将全局深睡否决状态简单等同于本示例的计数,其他业务也可能持有否决票。
  3. 若日志显示 sleep-record=off,说明当前目标未开启 CONFIG_PM_SLEEP_RECORD_ENABLE;添加和撤票仍可验证,但不应读取睡眠统计内容。
  4. 在真实业务的开始和结束边界验证对称接口配对。可通过 uapi_pm_get_sleep_vetoAT+PMDUMP、20 s 周期 DFX 日志或功耗仪观察撤票后深睡次数和时间。
  5. 分别验证短周期定时器、业务传输中和业务空闲后的功耗行为;任何一路遗漏撤票都可能长期阻止深睡。

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+PMADDVOTEAT+PMRMVOTEAT^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:查询时钟、性能投票、电源电压或状态、睡眠投票和唤醒源。

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_0HAL_PIO_DRIVE_3HAL_PIO_DRIVE_MAX 表示使用默认配置。
pull 弱上拉/下拉配置,阻值约 33 kΩ;HAL_PIO_PULL_DOWNHAL_PIO_PULL_UPHAL_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.hg_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+PMDUMPAT+STACKINFOAT+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.hpm_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_tuapi_at_cmd_table_register 未定义 AT 头文件或组件接入不完整。 在 Demo 头文件包含 at.h,检查 CMake 的 AT 私有头文件路径和 at_adapter.c 注册。
AT 命令不存在 功能宏、父目录引入、组件或 AT 注册未生效。 检查 CONFIG_ENABLE_LOW_POWER_SAMPLElow_power_samplesamples/native_samples/CMakeLists.txtAT_DITING_EXAMPLE_LOW_POWER
[PMSLEEPINFO] sleep statistics unavailable 目标未开启 CONFIG_PM_SLEEP_RECORD_ENABLE 这是可预期降级输出;如需要统计,在产品配置中开启睡眠记录后重新编译。
调用 PMVETOREMOVE 失败 没有与之配对的 Demo 添加操作,或 Demo 已在重启后丢失其本地配对状态。 在同一次启动中依次执行 PMVETOADDPMVETOREMOVE;重启后从添加命令重新开始。