电源管理与低功耗
本文档详细说明 AC63 系列蓝牙 SDK(fw-AC63_BT_SDK)中的电源管理与低功耗实现,涵盖电池电压检测(ADC 采样)、电量百分比换算、低电状态机、电源事件分发、开机电压校验、软关机流程、TWS 电量同步以及低功耗时钟配置,全部内容基于 apps/hid 工程的实际源码。
Purpose and Scope
本页覆盖与电源管理、低功耗相关的完整机制:
- 电池电压采集与电量换算(
get_vbat_level/get_vbat_percent/battery_value_to_phone_level) - 低电状态机(
VBAT_NORMAL/VBAT_WARNING/VBAT_LOWPOWER)与防抖计数逻辑 - 电源事件的定义、投递与处理(
power_event_to_user/app_power_event_handler) - 开机电压校验与软关机(
check_power_on_voltage/power_set_soft_poweroff) - TWS 左右耳电量同步与低功耗时钟配置
以下主题属于相邻页面,本页不做展开:蓝牙协议栈电源 profile(bluetooth_powerprofile.lua)、充电管理(asm/charge.h 的充电 IC 驱动)、系统内核事件框架(system/event.h 的 sys_event_notify 通用机制)。
Overview
在真无线蓝牙耳机(TWS)这类电池供电设备上,电源管理直接决定产品的续航、安全性与用户体验。该 SDK 的电源管理模块(apps/hid/modules/power/app_power_manage.c)承担以下职责:
- 周期性电压检测:通过 ADC 通道
AD_CH_VBAT读取电池电压,采用滑动平均((new + old) / 2)抑制噪声,并以慢速定时器(默认 30 秒)周期性触发快速采样。 - 电量量化:将电压换算为 0~100% 电量,再映射为 1~9 级(用于同步给手机侧显示),并缓存到
cur_battery_level。 - 低电分级告警:按电压阈值区分"低电提醒"(
LOW_POWER_WARN_VAL,默认 240)与"低电关机"(LOW_POWER_OFF_VAL,默认 220),通过连续多次采样防抖后触发对应电源事件。 - 电源事件驱动:所有状态变化统一封装为
DEVICE_EVENT_FROM_POWER源头的系统设备事件(sys_event_notify),由应用层事件处理器app_power_event_handler响应——低电时删除定时器并调用软关机回调,提醒时启动 60 秒周期的重复提醒定时器。 - 开机保护:上电时
check_power_on_voltage()持续监测电压,若连续 10 次低于关机阈值则直接软关机,防止电池电压不足时开机导致的异常。 - TWS 同步:左右耳通过
app_power_set_tws_sibling_bat_level/get_tws_sibling_bat_level交换电量等级,实现双耳电量一致显示。
该模块设计上采用"采集-滤波-防抖-事件"的分层结构:硬件层只负责 ADC 读数,软件层负责滤波与状态判定,事件层负责与应用解耦,使 UI 提示、蓝牙指令(USER_CTRL_HFP_CMD_UPDATE_BATTARY)、软关机等行为都可以独立扩展。
Architecture
flowchart TD
subgraph sg_HW["硬件层"]
ADC["ADC 通道 AD_CH_VBAT"]
CHG["充电检测 charge_online"]
LVD["LVD 低电压检测 adc_check_vbat_lowpower"]
end
subgraph sg_CORE["电源管理核心模块 app_power_manage.c"]
VCHECK["vbat_check 快速采样任务"]
VSLOW["vbat_check_slow 慢速周期任务"]
PERCENT["电量换算 get_vbat_percent"]
LEVEL["等级映射 battery_value_to_phone_level"]
STATEM["低电状态机 cur_bat_st"]
EVT["power_event_to_user 事件投递"]
end
subgraph sg_EVENT["事件与响应层"]
HANDLER["app_power_event_handler"]
BOOT["check_power_on_voltage 开机校验"]
SOFTOFF["power_set_soft_poweroff 软关机"]
HFP["USER_CTRL_HFP_CMD_UPDATE_BATTARY 手机同步"]
TWS["TWS 电量同步接口"]
end
subgraph sg_CFG["配置来源"]
SYSVAR["app_var.poweroff_tone_v / warning_tone_v"]
SYSCFG["syscfg CFG_LOWPOWER_V_ID"]
MACRO["宏 TCFG_SYS_LVD_EN / TCFG_CHARGE_ENABLE"]
end
ADC --> VCHECK
CHG --> VSLOW
LVD --> VCHECK
VSLOW -->|"定时触发"| VCHECK
VCHECK --> STATEM
VCHECK --> LEVEL
LEVEL --> PERCENT
STATEM --> EVT
EVT --> HANDLER
HANDLER --> SOFTOFF
HANDLER --> HFP
HANDLER -->|"POWER_EVENT_SOFTOFF"| SOFTOFF
BOOT --> SOFTOFF
VCHECK --> TWS
SYSCFG --> SYSVAR
SYSVAR --> STATEM
MACRO --> VCHECK
架构说明:
- 硬件层(左)是数据的唯一来源:
get_vbat_level()内部调用adc_get_voltage(AD_CH_VBAT)读取电压,adc_check_vbat_lowpower()提供硬件 LVD 低电压信号,充电在线状态由get_charge_online_flag()提供。 - 核心模块(中)负责将原始采样转化为稳定的状态:快速任务
vbat_check做滤波与防抖,慢速任务vbat_check_slow决定采样周期(充电时 60s、非充电 30s),状态机输出电源事件。 - 事件与响应层(右)与应用解耦:
app_power_event_handler接收事件后执行软关机、更新手机电量、播放提示音等动作;check_power_on_voltage是开机阶段的特殊入口。 - 配置来源(下)说明阈值并非硬编码常量:
app_var.poweroff_tone_v/warning_tone_v在启动时从syscfg(CFG_LOWPOWER_V_ID)读取,缺省时回落到头文件中的LOW_POWER_OFF_VAL/LOW_POWER_WARN_VAL。
核心实现:电压检测与电量换算
ADC 电压读取
电压读取是整条链路的源头,实现在 app_power_manage.c:
u16 get_vbat_level(void)
{
//return 370; //debug
return (adc_get_voltage(AD_CH_VBAT) * 4 / 10);
}
adc_get_voltage 返回的是以 0.01V 为单位的电压值,* 4 / 10 将范围归一化到内部使用的电压单位(约 0.1V),例如 4.2V 满电对应约 420。该值被 vbat_check 缓存到静态变量 bat_val,供 get_vbat_value() 等接口直接读取,避免重复 ADC 采样。
电量百分比换算
get_vbat_percent 把电压线性映射为 0~100% 电量:
u8 get_vbat_percent(void)
{
u16 tmp_bat_val;
u16 bat_val = get_vbat_level();
if (battery_full_value == 0) {
#if TCFG_CHARGE_ENABLE
battery_full_value = (get_charge_full_value() - 100) / 10; //防止部分电池充不了这么高电量,充满显示未满的情况
#else
battery_full_value = 420;
#endif
}
if (bat_val <= app_var.poweroff_tone_v) {
return 0;
}
tmp_bat_val = remap_calculate_vbat_percent(bat_val);
if (!tmp_bat_val) {
tmp_bat_val = ((u32)bat_val - app_var.poweroff_tone_v) * 100 / (battery_full_value - app_var.poweroff_tone_v);
if (tmp_bat_val > 100) {
tmp_bat_val = 100;
}
}
return (u8)tmp_bat_val;
}
设计要点:
- 满电基准动态化:启用充电(
TCFG_CHARGE_ENABLE)时,满电值取自充电 IC 的充满电压get_charge_full_value()并减去 100(约 0.1V 余量),注释明确说明这是"防止部分电池充不了这么高电量,充满显示未满"——即用校准余量换取显示一致性;未启用充电时固定为 420(4.2V)。 - 关机点作为映射下界:电压等于或低于
poweroff_tone_v(可配置,默认 220)时直接返回 0%,保证显示"没电"与真正关机行为对齐。 - 可重映射扩展点:
remap_calculate_vbat_percent声明为__attribute__((weak))的弱函数,默认返回 0(表示不干预),厂商可覆盖实现非线性曲线(如锂电池放电平台区)以匹配真实放电特性。 - 线性映射:
(voltage - poweroff) * 100 / (full - poweroff),结果截断到 100%。
手机侧电量等级
battery_value_to_phone_level 将百分比转换为 HFP/AVRCP 上报用的 1~9 级:
//将当前电量转换为1~9级发送给手机同步电量
u8 battery_value_to_phone_level(u16 bat_val)
{
u8 battery_level = 0;
u8 vbat_percent = get_vbat_percent();
if (vbat_percent < 5) { //小于5%电量等级为0,显示10%
return 0;
}
battery_level = (vbat_percent - 5) / 10;
return battery_level;
}
该映射刻意保留 5% 的"死区":低于 5% 返回等级 0(手机显示 10%),避免电池在低电边缘时等级在 0/1 之间抖动;(percent - 5) / 10 将 5%~100% 均分为 0~9 档,接近线性且整数运算零开销。结果缓存于 cur_battery_level,由 get_self_battery_level() 对外提供。
低电状态机与防抖逻辑
状态定义
模块内部用枚举 VBAT_STATUS 表达电池状态,并缓存于静态变量 cur_bat_st(见 app_power_manage.c):
enum {
VBAT_NORMAL = 0,
VBAT_WARNING,
VBAT_LOWPOWER,
} VBAT_STATUS;
#define VBAT_DETECT_CNT (2*1) //2*N
#define VBAT_DETECT_ADC_MS (10) //unint:ms
#define VBAT_PERIOD_CHECK_S (30) //unint:s
static int vbat_slow_timer = 0;
static int vbat_fast_timer = 0;
static int lowpower_timer = 0;
static u8 old_battery_level = 9;
static u16 bat_val = 0;
static volatile u8 cur_battery_level = 0;
static u16 battery_full_value = 0;
static u8 cur_bat_st = VBAT_NORMAL;
stateDiagram-v2
[*] --> VBAT_NORMAL
VBAT_NORMAL --> VBAT_WARNING: "连续超过半数采样 < warning_tone_v"
VBAT_WARNING --> VBAT_LOWPOWER: "连续超过半数采样 < poweroff_tone_v 且 6 次周期确认"
VBAT_WARNING --> VBAT_NORMAL: "连续 3 次周期采样正常"
VBAT_LOWPOWER --> [*]: "触发 POWER_EVENT_POWER_LOW → 软关机"
VBAT_NORMAL --> VBAT_NORMAL: "充电在线,仅上报 POWER_EVENT_POWER_CHARGE"
状态迁移的设计意图:电池电压在负载波动(如蓝牙发射瞬间)会短暂跌落,直接按单次采样判定会造成误告警/误关机。因此 vbat_check 引入多层防抖——每个采样周期内先做多次 ADC 检测(VBAT_DETECT_CNT 次),只有"超过半数"采样命中阈值才累加对应计数,再叠加多周期连续确认(low_power_cnt > 6),最终才进入低电关机动作。
vbat_check 采样主逻辑
vbat_check 是状态机的核心,其关键路径如下:
void vbat_check(void *priv)
{
static u8 unit_cnt = 0;
static u8 low_warn_cnt = 0;
static u8 low_off_cnt = 0;
static u8 low_voice_cnt = 0;
static u8 low_power_cnt = 0;
static u8 power_normal_cnt = 0;
static u8 charge_ccvol_v_cnt = 0;
static u8 charge_online_flag = 0;
static u8 low_voice_first_flag = 1;//进入低电后先提醒一次
if (!bat_val) {
bat_val = get_vbat_level();
} else {
bat_val = (get_vbat_level() + bat_val) / 2;
}
cur_battery_level = battery_value_to_phone_level(bat_val);
printf("bv:%d, bl:%d , check_vbat:%d\n", bat_val, cur_battery_level, adc_check_vbat_lowpower());
unit_cnt++;
if (adc_check_vbat_lowpower() || (bat_val <= app_var.poweroff_tone_v)) {
low_off_cnt++;
}
if (bat_val <= app_var.warning_tone_v) {
low_warn_cnt++;
}
每次进入先更新 bat_val:首次采样直接赋值,之后采用滑动平均 (新值 + 旧值) / 2,等效于低通滤波,进一步抑制单次毛刺。随后按阈值累加两个"命中计数器":low_off_cnt(关机命中,包含硬件 LVD 信号)与 low_warn_cnt(提醒命中)。
当 unit_cnt 达到 VBAT_DETECT_CNT(2 次)时进入判定阶段,逻辑分支如下:
- 未充电(
get_charge_online_flag() == 0):low_off_cnt > VBAT_DETECT_CNT/2(多数采样低于关机点)→cur_bat_st = VBAT_LOWPOWER,low_power_cnt++;当low_power_cnt > 6(约 6 个周期持续确认)后,投递POWER_EVENT_POWER_LOW并删除快速定时器,应用层随即执行软关机。此处low_power_cnt计数器的存在是为了避免"进低电后电压轻微回升又退出"造成的反复开关机。- 否则若
low_warn_cnt > VBAT_DETECT_CNT/2→cur_bat_st = VBAT_WARNING,按low_voice_first_flag控制提醒节律:首次进入低电后第 2 个周期提醒一次,之后每 5 个周期提醒一次;提醒通过power_event_to_user(POWER_EVENT_POWER_WARNING)发出。 - 否则视为正常 →
power_normal_cnt++,连续 3 个周期正常且状态非VBAT_NORMAL时投递POWER_EVENT_POWER_NORMAL恢复事件。
- 充电在线:不判定低电,直接投递
POWER_EVENT_POWER_CHARGE;若charge_ccvol_v_cnt过半数(说明电压已达恒压阶段CHARGE_CCVOL_V),调用set_charge_mA(get_charge_mA_config())恢复配置电流,防止涓流阶段电流被错误压低。
判定结束后统一清零 unit_cnt 及各级命中计数,随后比较新旧电量等级:等级变化或充电在线状态变化时投递 POWER_EVENT_POWER_CHANGE,驱动手机端电量刷新;old_battery_level 与 charge_online_flag 用于记录基线。
定时器机制与周期自适应
模块使用两个定时器协作(vbat_check_init / vbat_check_slow):
- 慢速周期定时器
vbat_slow_timer:sys_timer_add(NULL, vbat_check_slow, VBAT_PERIOD_CHECK_S * 1000),默认每 30 秒触发一次vbat_check_slow。 - 快速采样定时器
vbat_fast_timer:usr_timer_add(NULL, vbat_check, VBAT_DETECT_ADC_MS, 1),单次触发(计数 1),每 10ms 执行一次vbat_check。
vbat_check_slow 的作用是把两种工作模式串联起来:每次慢速周期到来,若快速定时器未在运行则启动一次快速采样;同时根据充电状态自适应调整慢速周期——充电时改为 60 秒,非充电维持 30 秒(vbat_check_slow):
void vbat_check_slow(void *priv)
{
if (vbat_fast_timer == 0) {
vbat_fast_timer = usr_timer_add(NULL, vbat_check, VBAT_DETECT_ADC_MS, 1);
}
if (get_charge_online_flag()) {
sys_timer_modify(vbat_slow_timer, 60 * 1000);
} else {
sys_timer_modify(vbat_slow_timer, VBAT_PERIOD_CHECK_S * 1000);
}
}
设计意图:充放电场景的电压变化速率不同。放电时电压缓慢下降,30 秒采样足够且省电;充电时电压快速爬升且存在充满判定需求,60 秒周期可减少不必要的 ADC 唤醒开销。快速定时器只跑一轮(count=1)即自停,避免在慢速周期之外持续占用 CPU。低电确认后 vbat_timer_delete() 会同时删除两个定时器,进入只等软关机的静默状态,最大限度降低功耗。
状态查询接口
get_vbat_need_shutdown():bat_val <= LOW_POWER_SHUTDOWN(220)或硬件 LVD 触发时返回 TRUE,供业务层做"拔电/开盖不开机"等保护判断。vbat_is_low_power():返回cur_bat_st != VBAT_NORMAL,供 UI 层查询是否处于低电。get_self_battery_level()/get_cur_battery_level():返回缓存的 0~9 级电量。
电源事件与事件分发
事件定义
电源模块通过系统事件框架与应用层通信。事件源标识与事件类型定义在 app_power_manage.h:
#define LOW_POWER_SHUTDOWN 220 //低电直接关机电压-拔出不开机-开盖不开机
#define LOW_POWER_OFF_VAL 220 //低电关机电压
#define LOW_POWER_WARN_VAL 240 //低电提醒电压
#define LOW_POWER_WARN_TIME (60 * 1000) //低电提醒时间
#define DEVICE_EVENT_FROM_POWER (('P' << 24) | ('O' << 16) | ('W' << 8) | '\0')
enum {
POWER_EVENT_POWER_NORMAL,
POWER_EVENT_POWER_WARNING,
POWER_EVENT_POWER_LOW,
POWER_EVENT_POWER_CHANGE,
POWER_EVENT_SYNC_TWS_VBAT_LEVEL,
POWER_EVENT_POWER_CHARGE,
POWER_EVENT_POWER_SOFTOFF
};
DEVICE_EVENT_FROM_POWER 用四个 ASCII 字符 POW\0 拼成 32 位整数作为事件源标记,确保与 SYS_DEVICE_EVENT 中其他来源(如按键、蓝牙)互不冲突。
事件投递与处理
power_event_to_user 把内部事件包装成系统事件并广播:
void power_event_to_user(u8 event)
{
struct sys_event e;
e.type = SYS_DEVICE_EVENT;
e.arg = (void *)DEVICE_EVENT_FROM_POWER;
e.u.dev.event = event;
e.u.dev.value = 0;
sys_event_notify(&e);
}
应用侧通过 app_power_event_handler 接收并响应:
int app_power_event_handler(struct device_event *dev, void (*set_soft_poweroff_call)(void))
{
int ret = false;
switch (dev->event) {
#if(TCFG_SYS_LVD_EN == 1)
case POWER_EVENT_POWER_NORMAL:
break;
case POWER_EVENT_POWER_WARNING:
r_printf(" POWER_EVENT_POWER_WARNING");
if (lowpower_timer == 0) {
lowpower_timer = sys_timer_add((void *)POWER_EVENT_POWER_WARNING, (void (*)(void *))power_event_to_user, LOW_POWER_WARN_TIME);
}
break;
case POWER_EVENT_POWER_LOW:
r_printf(" POWER_EVENT_POWER_LOW");
vbat_timer_delete();
if (lowpower_timer) {
sys_timer_del(lowpower_timer);
lowpower_timer = 0 ;
}
if (set_soft_poweroff_call) {
log_info("POWER_LOW TO SOFT_POWEROFF");
set_soft_poweroff_call();
}
break;
case POWER_EVENT_POWER_CHANGE:
/* log_info("POWER_EVENT_POWER_CHANGE\n"); */
user_send_cmd_prepare(USER_CTRL_HFP_CMD_UPDATE_BATTARY, 0, NULL);
break;
case POWER_EVENT_POWER_CHARGE:
if (lowpower_timer) {
sys_timer_del(lowpower_timer);
lowpower_timer = 0 ;
}
break;
#endif
case POWER_EVENT_POWER_SOFTOFF:
set_soft_poweroff_call();
default:
break;
}
return ret;
}
各事件的处理策略:
| 事件 | 处理动作 | 设计意图 |
|---|---|---|
POWER_EVENT_POWER_NORMAL | 空操作 | 恢复正常的提示由 UI/语音层自行决定 |
POWER_EVENT_POWER_WARNING | 若未在提醒中,注册 60 秒(LOW_POWER_WARN_TIME)周期的 sys_timer 重复投递同一事件 | 实现"每 60 秒提醒一次"的低电语音循环,且不阻塞主流程 |
POWER_EVENT_POWER_LOW | 删除全部电压检测定时器与提醒定时器,调用 set_soft_poweroff_call() 软关机 | 低电确认后立刻停止一切耗电采样,干净利落地进入关机路径 |
POWER_EVENT_POWER_CHANGE | 下发 USER_CTRL_HFP_CMD_UPDATE_BATTARY 用户命令 | 通过 HFP/AVRCP 将新电量同步给手机 |
POWER_EVENT_POWER_CHARGE | 取消低电提醒定时器 | 插入充电仓后不再打扰用户,同时规避"边充边报低电"的误提醒 |
POWER_EVENT_POWER_SOFTOFF | 直接调用软关机回调 | 供其他模块(如按键长按关机)复用同一关机入口 |
注意 set_soft_poweroff_call 是以回调函数指针传入的,app_power_manage.c 本身不直接调用底层 power_set_soft_poweroff(),而是由宿主应用(app_main.c)注入,保持模块可移植性。
开机电压校验与软关机
开机校验
check_power_on_voltage 在系统启动早期(注册事件处理之前)运行,防止电池亏电时开机:
void check_power_on_voltage(void)
{
#if(TCFG_SYS_LVD_EN == 1)
u16 val = 0;
u8 normal_power_cnt = 0;
u8 low_power_cnt = 0;
while (1) {
clr_wdt();
val = get_vbat_level();
printf("vbat: %d\n", val);
if ((val < app_var.poweroff_tone_v) || adc_check_vbat_lowpower()) {
low_power_cnt++;
normal_power_cnt = 0;
if (low_power_cnt > 10) {
os_time_dly(100);
log_info("power on low power , enter softpoweroff!\n");
power_set_soft_poweroff();
}
} else {
normal_power_cnt++;
low_power_cnt = 0;
if (normal_power_cnt > 10) {
vbat_check_init();
return;
}
}
}
#endif
}
该函数是一个带看门狗喂狗的忙等循环:连续 10 次读到低电压才软关机(power_set_soft_poweroff()),连续 10 次正常才初始化检测定时器并返回。之所以不用 vbat_check 的慢速定时器路径,是因为开机早期事件框架与定时器调度可能尚未就绪,且必须在蓝牙协议栈启动前完成"能否开机"的裁决——这是"拔出不开机、开盖不开机"(LOW_POWER_SHUTDOWN 注释)的产品需求在代码层面的落地。
软关机入口
软关机的应用层入口在 app_main.c:
log_info("enter softpoweroff\n");
power_set_soft_poweroff();
power_set_soft_poweroff() 是底层库(asm/power.h)提供的接口,负责关停射频、保存系统状态并进入关机流程。SDK 的配套提示音资源(TONE_LOW_POWER、TONE_POWER_OFF,见 tone_table.c)由 UI 层在收到对应事件时播放。
TWS 电量同步
TWS 场景下左右耳各自独立采样电池,但手机侧只显示一个电量。模块提供两套接口实现双耳电量交换(见 app_power_manage.h):
- 上报侧:
app_power_set_tws_sibling_bat_level(u8 vbat, u8 percent)写入对侧耳机的电压与百分比; - 读取侧:
get_tws_sibling_bat_level()/get_tws_sibling_bat_persent()读取对侧数据; - 主动同步:
tws_sync_bat_level()由链路层在连接事件中调用,把本机电量推送给对侧; - 事件
POWER_EVENT_SYNC_TWS_VBAT_LEVEL用于触发重同步。
电量等级(1~9)本身也是 TWS 间轻量同步的常用载体,因为一个字节即可承载,且与手机显示粒度一致。
核心流程:低电告警 → 关机
sequenceDiagram
participant ADC as ADC 采样
participant V as vbat_check
participant S as vbat_check_slow 定时器
participant E as power_event_to_user
participant H as app_power_event_handler
participant APP as app_main 应用
S->>V: 每 30s 启动快速采样
V->>ADC: get_vbat_level() / adc_check_vbat_lowpower()
ADC-->>V: 电压值(滑动平均滤波)
V->>V: 防抖计数 low_off_cnt / low_warn_cnt
alt 连续 6 周期确认低电关机
V->>E: POWER_EVENT_POWER_LOW
E->>H: sys_event_notify(SYS_DEVICE_EVENT, POW)
H->>H: vbat_timer_delete()
H->>APP: set_soft_poweroff_call()
APP->>APP: power_set_soft_poweroff()
else 低电提醒档
V->>E: POWER_EVENT_POWER_WARNING
E->>H: sys_event_notify(SYS_DEVICE_EVENT, POW)
H->>H: 注册 60s 重复提醒定时器
H-->>APP: 播放低电提示音
else 电量等级变化
V->>E: POWER_EVENT_POWER_CHANGE
E->>H: sys_event_notify(SYS_DEVICE_EVENT, POW)
H->>APP: USER_CTRL_HFP_CMD_UPDATE_BATTARY
end
整个流程的核心设计思想是分层裁决、事件驱动:采样层只做"读数+滤波",状态层只做"计数+状态迁移",事件层只做"分发+动作"。任何一层都可以在不影响其他层的前提下被替换或扩展(例如把提示音换成 LED 闪烁、把软关机回调换成低功耗保持模式)。
Usage Examples
在应用启动阶段接入电源模块
电源模块在应用侧的使用方式是"初始化 + 注册事件回调"。以下代码展示了 app_power_manage.c 对外暴露的初始化接口(app_power_manage.c):
void vbat_check_init(void)
{
if (vbat_slow_timer == 0) {
vbat_slow_timer = sys_timer_add(NULL, vbat_check_slow, VBAT_PERIOD_CHECK_S * 1000);
} else {
sys_timer_modify(vbat_slow_timer, VBAT_PERIOD_CHECK_S * 1000);
}
if (vbat_fast_timer == 0) {
vbat_fast_timer = usr_timer_add(NULL, vbat_check, VBAT_DETECT_ADC_MS, 1);
}
}
调用方(app_main.c 的电源初始化流程)通常在系统上电完成 LVD 配置后调用 check_power_on_voltage() 做开机裁决,通过后调用 vbat_check_init() 启动周期检测,并将 app_power_event_handler 连同软关机回调一起注册到系统设备事件分发器中。回调采用"函数指针注入"的方式传入(见头文件 app_power_manage.h):
int app_power_event_handler(struct device_event *dev, void (*set_soft_poweroff_call)(void));
读取电量供 UI/蓝牙上报
业务层(UI 显示、HFP 电量上报)通过无副作用的只读接口获取电量:
u16 get_vbat_value(void); // 原始电压(滤波后缓存值)
u8 get_vbat_percent(void); // 0~100%
u8 get_self_battery_level(void); // 0~9 级,供手机同步
u8 get_tws_sibling_bat_level(void); // 对侧耳机电量等级
bool get_vbat_need_shutdown(void); // 是否满足强制关机条件
例如在 POWER_EVENT_POWER_CHANGE 事件中,处理器调用 user_send_cmd_prepare(USER_CTRL_HFP_CMD_UPDATE_BATTARY, 0, NULL) 触发手机端电量刷新,其数据来源正是 get_cur_battery_level() 缓存的等级(app_power_manage.c):
u8 get_cur_battery_level(void)
{
return cur_battery_level;
}
覆盖电量换算曲线(扩展点示例)
若产品使用非线性放电特性的电池,可在应用层重定义弱函数 remap_calculate_vbat_percent,替代默认的线性映射:
// 在任意应用源文件中重定义(原函数为 weak)
__attribute__((weak)) u8 remap_calculate_vbat_percent(u16 bat_val)
{
// 自定义查表:把电压映射为更贴近真实放电曲线的百分比
return custom_lookup_table(bat_val);
}
Configuration Options
电源模块的行为由宏、运行时可配置参数(app_var)与 syscfg 存储共同决定:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
LOW_POWER_SHUTDOWN | 宏 (头文件) | 220 | 强制关机电压,用于"拔出不开机/开盖不开机"保护(app_power_manage.h) |
LOW_POWER_OFF_VAL | 宏 (头文件) | 220 | 低电关机电压默认值,被 app_var.poweroff_tone_v 覆盖 |
LOW_POWER_WARN_VAL | 宏 (头文件) | 240 | 低电提醒电压默认值,被 app_var.warning_tone_v 覆盖 |
LOW_POWER_WARN_TIME | 宏 (头文件) | 60000 ms | 低电重复提醒间隔 |
VBAT_DETECT_CNT | 宏 (源文件) | 2 | 每个采样单元的 ADC 检测次数,防抖基数 |
VBAT_DETECT_ADC_MS | 宏 (源文件) | 10 ms | 快速采样定时器间隔 |
VBAT_PERIOD_CHECK_S | 宏 (源文件) | 30 s | 慢速周期检测间隔(非充电态) |
TCFG_SYS_LVD_EN | 编译宏 | 1 | 是否启用 LVD 低电压检测逻辑(#if 门控整个事件处理) |
TCFG_CHARGE_ENABLE | 编译宏 | - | 启用充电时满电值取自充电 IC;否则固定 420 |
TCFG_VAD_LOWPOWER_CLOCK | 编译宏 | 0 | 低功耗时钟配置,导出到 config_vad_lowpower_clock(lib_driver_config.c) |
app_var.poweroff_tone_v | 运行变量 | 220 | 关机电压阈值,启动时从 syscfg CFG_LOWPOWER_V_ID 加载 |
app_var.warning_tone_v | 运行变量 | 240 | 提醒电压阈值,启动时从 syscfg CFG_LOWPOWER_V_ID 加载 |
运行阈值(warning_tone_v / poweroff_tone_v)支持用户自定义并持久化:user_cfg.c 通过 syscfg_read(CFG_LOWPOWER_V_ID, &auto_lowpower, sizeof(AUTO_LOWPOWER_V_CONFIG)) 读取 AUTO_LOWPOWER_V_CONFIG 结构体,读取失败时回落到头文件默认值(user_cfg.c):
#if USE_CONFIG_LOWPOWER_V_SETTING
AUTO_LOWPOWER_V_CONFIG auto_lowpower;
ret = syscfg_read(CFG_LOWPOWER_V_ID, &auto_lowpower, sizeof(AUTO_LOWPOWER_V_CONFIG));
if (ret > 0) {
app_var.warning_tone_v = auto_lowpower.warning_tone_v;
app_var.poweroff_tone_v = auto_lowpower.poweroff_tone_v;
}
#else
app_var.warning_tone_v = LOW_POWER_WARN_VAL;
app_var.poweroff_tone_v = LOW_POWER_OFF_VAL;
#endif
API Reference
以下为电源管理模块对外接口(定义于 app_power_manage.h):
int app_power_event_handler(struct device_event *dev, void (*set_soft_poweroff_call)(void))
电源设备事件处理器,由系统事件分发器回调。
参数:
dev(struct device_event *):事件结构体,dev->event为POWER_EVENT_*枚举值。set_soft_poweroff_call(函数指针):软关机回调,POWER_EVENT_POWER_LOW/POWER_EVENT_POWER_SOFTOFF时调用。
返回: int,当前实现恒为 false(0)。
void check_power_on_voltage(void)
开机阶段电压裁决:忙等循环采样,连续 10 次低电则 power_set_soft_poweroff(),连续 10 次正常则 vbat_check_init() 后返回。仅在 TCFG_SYS_LVD_EN == 1 时编译。
u16 get_vbat_level(void)
读取 ADC 原始电压(换算单位)。返回: adc_get_voltage(AD_CH_VBAT) * 4 / 10。
u8 get_vbat_percent(void)
计算 0~100% 电量。电压 ≤ poweroff_tone_v 时返回 0;默认线性映射,可由弱函数 remap_calculate_vbat_percent 覆盖。
u16 get_vbat_value(void)
返回: 滤波后的缓存电压 bat_val(最后一次 vbat_check 的滑动平均值)。
u8 get_vbat_percent(void) / u8 get_self_battery_level(void) / u8 get_cur_battery_level(void)
返回: 当前 0~9 级电量缓存 cur_battery_level,供手机侧同步与 UI 显示。
void vbat_check_init(void)
注册慢速周期定时器与快速采样定时器(幂等:已存在则只修改周期)。
void vbat_timer_delete(void)
删除慢速/快速定时器,进入低电静默状态。
void tws_sync_bat_level(void)
主动向 TWS 对侧同步本机电量。
u8 get_tws_sibling_bat_level(void) / u8 get_tws_sibling_bat_persent(void)
返回: 对侧耳机的电量等级 / 百分比。
void app_power_set_tws_sibling_bat_level(u8 vbat, u8 percent)
写入对侧耳机电压与百分比(由 TWS 链路层收到对端数据时调用)。
bool get_vbat_need_shutdown(void)
返回: bat_val <= LOW_POWER_SHUTDOWN 或硬件 LVD 触发时为 TRUE,用于强制关机保护。
bool vbat_is_low_power(void)
返回: cur_bat_st != VBAT_NORMAL(位于 app_power_manage.c,头文件未导出)。
Failure Modes, Edge Cases & Concurrency
电压抖动与防抖
电池电压在射频发射、音频功放峰值时会出现毫秒级跌落。模块通过三层防护避免误动作:
- 滑动平均:
bat_val = (get_vbat_level() + bat_val) / 2,单次异常采样最多影响一半权重; - 周期内多数判决:
low_off_cnt > VBAT_DETECT_CNT/2才计一次命中,2 次采样中至少 1 次命中即算命中,但对"瞬时脉冲"不敏感; - 跨周期确认:低电关机需
low_power_cnt > 6(连续 6 个周期命中),低电恢复需power_normal_cnt > 2。
边界条件
- 进低电后电压回升:
low_power_cnt在正常分支被清零,状态保持VBAT_LOWPOWER直至软关机执行,避免"关机-开机-再关机"振荡。 - 插上充电:
POWER_EVENT_POWER_CHARGE会删除低电提醒定时器;充电状态下完全不进入低电判定分支(get_charge_online_flag() == 0才判定),从根源上避免充电时误关机。 - 首次采样:
bat_val初值为 0,vbat_check首轮直接赋值而非平均,避免"0 与真实值平均"造成的假低电。 - 满电基准缓存:
battery_full_value只计算一次(== 0判断),若电池充满电压测量异常可能长期偏高/偏低;设计上通过-100(约 0.1V)余量缓解。
并发与定时器生命周期
cur_battery_level声明为volatile,供中断/其他任务读取时避免编译器缓存陈旧值。- 快速定时器(
usr_timer)与慢速定时器(sys_timer)分属不同调度域,模块通过"快速定时器单次触发 + 慢速定时器负责补发"的握手避免双重启动:vbat_check_slow中if (vbat_fast_timer == 0)判断保证任一时刻只有一个快速采样任务在跑。 - 低电关机路径删除定时器后,
app_power_event_handler仍可能收到后续事件;lowpower_timer判空保护(if (lowpower_timer))防止重复注册/重复删除。 check_power_on_voltage是阻塞循环(while(1)),必须配合clr_wdt()喂狗,否则系统会触发看门狗复位——这是开机阶段唯一允许的忙等场景。
已知依赖
- 所有 LVD 相关逻辑以
TCFG_SYS_LVD_EN == 1为编译前提,若关闭该宏,低电告警/关机事件将完全不产生,需确保产品硬件有独立的电量保护。 get_charge_full_value()/set_charge_mA()依赖充电 IC 驱动(asm/charge.h),仅在TCFG_CHARGE_ENABLE下可用。
Performance & Operational Notes
- 功耗预算:慢速周期 30 秒(充电 60 秒)一次 ADC 采样,快速定时器每次只跑 1 轮(10ms)后自停,整体采样开销可忽略,符合低功耗蓝牙耳机的电流预算。
- 日志成本:
vbat_check内每条采样都打印printf("bv:%d, bl:%d ..."),量产固件建议关闭LOG_DEBUG_ENABLE或移除该行,避免 UART 输出延长唤醒时间。 - 开机时序:
check_power_on_voltage必须在协议栈/音频启动前完成,否则低电开机后可能短暂发出爆音或蓝牙广播;该函数内os_time_dly(100)在关机前留出 100 tick 让底层完成收尾。 - 诊断手段:
power_event_to_user的事件源DEVICE_EVENT_FROM_POWER与r_printf日志(POWER_EVENT_POWER_WARNING/POWER_EVENT_POWER_LOW)可用于抓取状态迁移时间线,定位"误关机/不关机"问题。
Extension Points
- 电量曲线重映射:覆盖弱函数
remap_calculate_vbat_percent(u16 bat_val),实现查表式非线性映射。 - 软关机回调注入:
app_power_event_handler的第二参数set_soft_poweroff_call由宿主注入,可替换为"低功耗保持模式"而非真正关机。 - 阈值持久化:通过
syscfg的CFG_LOWPOWER_V_ID(AUTO_LOWPOWER_V_CONFIG)支持用户自定义warning_tone_v/poweroff_tone_v,可在 App 配网/设置页写入。 - 提醒节律:
low_voice_first_flag与low_voice_cnt控制首次提醒与重复提醒节奏,可调整 60 秒周期(LOW_POWER_WARN_TIME)或首次/后续计数。 - TWS 同步载体:
POWER_EVENT_SYNC_TWS_VBAT_LEVEL事件 +app_power_set_tws_sibling_bat_level接口可对接任意 TWS 传输通道(私有协议或标准 GATT 服务)。
Tests
SDK 未提供针对电源模块的独立单元测试工程,但代码中的防抖参数(VBAT_DETECT_CNT、low_power_cnt > 6)与日志输出(bv: / bl: / check_vbat:)即为调试验证手段。常见验证路径:
- 低电关机:用可调电源模拟电池,将电压降至 220 以下,观察
POWER_LOW TO SOFT_POWEROFF日志与设备关机; - 低电提醒:电压降至 240~220 区间,验证每 60 秒一次
POWER_EVENT_POWER_WARNING; - 充电恢复:接入充电器,验证
POWER_EVENT_POWER_CHARGE取消提醒且无低电关机; - 开机保护:在低于 220 电压下上电,验证进入
power on low power, enter softpoweroff!分支。
Related Links
- 电源管理头文件 app_power_manage.h
- 电源管理实现 app_power_manage.c
- 应用主流程 app_main.c(软关机入口)
- 驱动配置 lib_driver_config.c(低功耗时钟)
- 用户配置 user_cfg.c(低电阈值持久化)
- 提示音表 tone_table.c(低电/关机音)
- 配置工具电源 profile(bluetooth_powerprofile.lua,属配置工具页面)
- 同类实现参考:
apps/spp_and_le/modules/power/app_power_manage.c(spp_and_le 工程的电源管理模块)