杰理 SDK 文档中心
首页
首页
  • 概述

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

电源管理与低功耗

本文档详细说明 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)承担以下职责:

  1. 周期性电压检测:通过 ADC 通道 AD_CH_VBAT 读取电池电压,采用滑动平均((new + old) / 2)抑制噪声,并以慢速定时器(默认 30 秒)周期性触发快速采样。
  2. 电量量化:将电压换算为 0~100% 电量,再映射为 1~9 级(用于同步给手机侧显示),并缓存到 cur_battery_level。
  3. 低电分级告警:按电压阈值区分"低电提醒"(LOW_POWER_WARN_VAL,默认 240)与"低电关机"(LOW_POWER_OFF_VAL,默认 220),通过连续多次采样防抖后触发对应电源事件。
  4. 电源事件驱动:所有状态变化统一封装为 DEVICE_EVENT_FROM_POWER 源头的系统设备事件(sys_event_notify),由应用层事件处理器 app_power_event_handler 响应——低电时删除定时器并调用软关机回调,提醒时启动 60 秒周期的重复提醒定时器。
  5. 开机保护:上电时 check_power_on_voltage() 持续监测电压,若连续 10 次低于关机阈值则直接软关机,防止电池电压不足时开机导致的异常。
  6. 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 次)时进入判定阶段,逻辑分支如下:

  1. 未充电(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 恢复事件。
  2. 充电在线:不判定低电,直接投递 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

电压抖动与防抖

电池电压在射频发射、音频功放峰值时会出现毫秒级跌落。模块通过三层防护避免误动作:

  1. 滑动平均:bat_val = (get_vbat_level() + bat_val) / 2,单次异常采样最多影响一半权重;
  2. 周期内多数判决:low_off_cnt > VBAT_DETECT_CNT/2 才计一次命中,2 次采样中至少 1 次命中即算命中,但对"瞬时脉冲"不敏感;
  3. 跨周期确认:低电关机需 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

  1. 电量曲线重映射:覆盖弱函数 remap_calculate_vbat_percent(u16 bat_val),实现查表式非线性映射。
  2. 软关机回调注入:app_power_event_handler 的第二参数 set_soft_poweroff_call 由宿主注入,可替换为"低功耗保持模式"而非真正关机。
  3. 阈值持久化:通过 syscfg 的 CFG_LOWPOWER_V_ID(AUTO_LOWPOWER_V_CONFIG)支持用户自定义 warning_tone_v / poweroff_tone_v,可在 App 配网/设置页写入。
  4. 提醒节律:low_voice_first_flag 与 low_voice_cnt 控制首次提醒与重复提醒节奏,可调整 60 秒周期(LOW_POWER_WARN_TIME)或首次/后续计数。
  5. 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 工程的电源管理模块)
Prev
消息事件机制
Next
存储与配置系统