电源管理 (SOFT OFF / POWER DOWN)
AW30N BLE SDK 的电源管理子系统负责整机的低电压检测(LVD)、VBAT 电量采样、低电告警与自动关机触发,以及上电时的开机键检测与软关机(SOFT OFF)/ 下电(POWER DOWN)流程。本文基于实际源码,从应用层 app_power_mg 到系统层 sys_softoff() / sys_power_down() 端到端展开说明。
Purpose and Scope
本文档覆盖电源管理中与 SOFT OFF / POWER DOWN 直接相关的全部代码路径:
- 低电压检测(LVD)警戒线与低电告警机制(
app_power_mg.c); - VBAT 电压采样与去抖计数、低电自动关机消息
MSG_POWER_OFF的触发; - 上电阶段的开机键检测(
check_power_on_key())与软关机sys_softoff(); - SOFT OFF 模式(
SOFTOFF_MODE→softoff_app())与系统下电sys_power_down(); - NorFlash 掉电保护(
_norflash_power_down())。
不涉及的内容(请参见对应目录页):
- 充电管理(
charge.h/charge.c):本文仅引用charge_get_lvcmp_det()作为充电接入判断; - ADC 驱动本身(
adc_api.h):本文只描述其使用方式; - 低功耗睡眠/唤醒(Deep Sleep / Standby 等):属于独立的功耗优化主题;
- 看门狗(WDT)与时钟管理:仅作为流程中的外部依赖提及。
Overview
设计目标
电源管理在嵌入式蓝牙音频 SoC 中承担三重职责:
- 可靠供电监控:通过 ADC 持续采样 VBAT,及时发现电池电压跌落到危险阈值附近,为软件提供"最后关机机会";
- 用户体验:低电时先告警,持续低电才自动关机;充电接入时不触发低电关机,避免边充边用被误关机;
- 安全的关机路径:无论是用户长按关机还是低电自动关机,都必须走受控的 SOFT OFF / POWER DOWN 流程,先保存现场、再关断外设(如 NorFlash),防止数据损坏。
关键概念
| 概念 | 说明 |
|---|---|
| LVD | Low Voltage Detect,芯片内部低电压检测单元,硬件级电压跌落保护 |
| LVD 警戒线 | lvd_warning_voltage = get_lvd_vol() + 300mV,比硬件 LVD 高 300mV,给软件留出反应时间 |
AD_CH_PMU_VBAT | ADC 采样通道,读取电池电压;读取结果 ×4 还原为毫伏值 |
MSG_POWER_OFF | 应用消息,由低电检测在满足持续时长后投递,触发系统关机动作 |
| SOFT OFF | 软关机:保存状态、关断外设后进入掉电状态,需开机键重新上电 |
| POWER DOWN | 下电:直接执行系统下电动作(sys_power_down()),NorFlash 进入掉电模式以省电 |
使用场景
- 电池供电的蓝牙耳机/音箱/对讲机在电量耗尽前自动告警并关机;
- 用户在开机过程中松开开机键超过阈值(100ms 级)时立即软关机,避免误开机;
- 对讲机等设备在长时间无操作或电压过低时调用
sys_power_down()强制下电。
Architecture
flowchart TD
subgraph sg_HW["硬件层"]
ADC["ADC 通道 AD_CH_PMU_VBAT"]
LVD["LVD 低电压检测单元"]
CHG["充电检测 charge_get_lvcmp_det()"]
KEY["开机键 Port Edge Wakeup"]
end
subgraph sg_APP["应用电源管理层"]
INIT["app_power_init()"]
SCAN["app_power_scan()"]
GETVBAT["app_power_get_vbat()"]
POWERONKEY["check_power_on_key()"]
end
subgraph sg_SYS["系统电源动作层"]
MSG["消息队列 MSG_POWER_OFF"]
SOFTOFF["SOFTOFF_MODE / softoff_app()"]
SYSPD["sys_softoff() / sys_power_down()"]
NORFLASH["_norflash_power_down()"]
end
LVD -->|"get_lvd_vol() + 300mV"| INIT
ADC -->|"adc_get_voltage()"| SCAN
CHG -->|"0=未充电"| SCAN
SCAN -->|"去抖计数达阈值"| MSG
MSG -->|"消息分发"| SOFTOFF
KEY -->|"松开超过 100ms"| POWERONKEY
POWERONKEY -->|"enter softpoweroff"| SYSPD
SOFTOFF -->|"关机路径"| SYSPD
SYSPD -->|"SPI 命令 WINBOND_POWER_DOWN"| NORFLASH
SCAN -->|"刷新"| GETVBAT
架构解读:整个子系统呈"硬件采样 → 应用策略 → 系统动作"三层结构。应用层 app_power_mg.c 是策略中枢——它不直接操作寄存器,而是通过 ADC 读数 + 充电状态判断"是否该关机",再通过消息队列把决策交给系统层执行。系统层(sys_softoff() / sys_power_down() / softoff_app())负责真正的电源动作,包括 NorFlash 掉电保护。这种分层让低电策略与硬件动作解耦,便于在不同机型(TCFG_CHARGE_ENABLE、LVD_WARNING_FOR_LOW_POWER 等配置)间复用。
核心实现
1. 低电警戒线(LVD Warning)
警戒线由 app_power_mg.h 中的 LVD_WARNING_FOR_LOW_POWER 宏控制:为 1 时使用 LVD 电压 + 300mV 作为动态警戒线;为 0 时使用固定的 3300mV(3.3V)。
#define LVD_WARNING_FOR_LOW_POWER 1//1:LVD作为低电警戒线 0:固定值作为警戒线
#if LVD_WARNING_FOR_LOW_POWER
#define LOW_POWER_VOL lvd_warning_voltage
#define LOW_POWER_LOG "Waring!!! Vbat is near to lvd!\n"
#define low_power_warning_init lvd_warning_init
#else
#define LOW_POWER_VOL 3300//3.3V
#define LOW_POWER_LOG "low power\n"
#define low_power_warning_init
#endif
设计意图:硬件 LVD 一旦触发会直接复位/断电,软件来不及做任何收尾。因此在 LVD 之上叠加 300mV 余量作为软件警戒线——电池电压先跌到软件警戒线,软件有充足时间告警并主动关机,从而把"被动断电"变成"主动关机"。
static u16 lvd_warning_voltage; //低电检测电压,比lvd电压大300mV
static u16 vbat_voltage;
static void lvd_warning_init(void)
{
extern u32 get_lvd_vol(void);
u32 lvd_voltage = get_lvd_vol();
lvd_warning_voltage = lvd_voltage + 300;
log_info("lvd_warning_voltage : %d\n", lvd_warning_voltage);
}
2. 初始化与周期采样
app_power_init() 完成三件事:初始化警戒线、注册 VBAT 采样通道、立即执行一次扫描。app_power_scan() 是电源管理的主入口,必须在应用主循环中被周期调用(配合 TCFG_SHUTDOWN_TIME 的时间粒度推算,调用周期应为 500ms 级)。
void app_power_init(void)
{
low_power_warning_init();
adc_add_sample_ch(AD_CH_PMU_VBAT);
app_power_scan();
}
u16 app_power_get_vbat(void)
{
return vbat_voltage;
}
设计意图:adc_get_voltage(AD_CH_PMU_VBAT) * 4 是 ADC 原始码到毫伏的缩放换算(典型 12 位 ADC + 分压电阻网络,×4 归一化)。0 == vol 直接 return,避免采样尚未稳定(首次调用时 ADC 未就绪返回 0)时把真实电压误判为"0V 低电"。
3. 低电去抖与关机触发
void app_power_scan(void)
{
static u16 low_power_cnt = 0;
u16 vol = adc_get_voltage(AD_CH_PMU_VBAT) * 4;
if (0 == vol) {
return;
}
/* log_info("vbat voltage : %d", vol); */
if ((vol <= LOW_POWER_VOL) && (0 == CHARGE_IN_DET)) {
//充电时不进入低功耗
low_power_cnt++;
if (low_power_cnt == (TCFG_SHUTDOWN_TIME / 500)) {
log_error(LOW_POWER_LOG);
post_msg(1, MSG_POWER_OFF);
}
} else {
low_power_cnt = 0;
}
vbat_voltage = vol;
}
算法要点:
- 双重门限 + 去抖:仅当
vol <= LOW_POWER_VOL且未充电(CHARGE_IN_DET == 0)时计数加一,否则清零。去抖计数以 500ms 为步进,累加到TCFG_SHUTDOWN_TIME / 500才投递MSG_POWER_OFF——即"持续低电才关机",瞬时跌落(如大电流发射瞬间的压降)不会误触发; - 充电豁免:充电器插入时
charge_get_lvcmp_det()返回非 0,CHARGE_IN_DET为真,低电计数被复位,实现"边充边用不关机"; - 投递方式:
post_msg(1, MSG_POWER_OFF)将关机请求放入应用消息队列,由主循环分发到对应消息处理(见SOFTOFF_MODE分支),保证关机动作在任务上下文中串行执行,避免并发竞争。
4. 开机键检测与软关机(SOFT OFF)
上电阶段 check_power_on_key() 会以 10ms 周期轮询开机键状态:按键持续按住超过 100ms(10 次计数)则正常开机返回;若松开超过 100ms 则判定为误开机,立即执行 sys_softoff() 软关机。轮询期间持续喂狗(wdt_clear()),防止看门狗在上电检测阶段误复位。
void check_power_on_key(void)
{
extern bool is_port_edge_wkup_source(void);
if (0 == is_port_edge_wkup_source()) {
printf("not port edge wakeup!\n");
return;
}
u32 delay_10ms_cnt = 0;
u32 delay_10msp_cnt = 0;
while (1) {
wdt_clear();
mdelay(10);
if (get_power_on_status()) {
log_char('+');
delay_10msp_cnt = 0;
delay_10ms_cnt++;
if (delay_10ms_cnt > 10) {
return;
}
} else {
log_char('-');
delay_10ms_cnt = 0;
delay_10msp_cnt++;
if (delay_10msp_cnt > 10) {
log_info("enter softpoweroff\n");
sys_softoff();
}
}
}
}
来源:init_app.c
设计意图:is_port_edge_wkup_source() 用于区分唤醒源——仅当本次启动由开机键沿触发(而非 RTC/IO 等其它唤醒源)时才需要按键状态仲裁。100ms 的去抖窗口兼顾了"按键抖动"与"快速响应",避免用户在开机瞬间松手造成不必要的软关机;而一旦判定误开机,sys_softoff() 立即执行,用户再按电源键即可重新上电。
5. SOFT OFF 模式与系统下电
应用主循环中,MSG_POWER_OFF 消息(及开机流程)最终进入 SOFTOFF_MODE 分支,调用 softoff_app() 执行软关机应用逻辑:
case SOFTOFF_MODE:
log_info("-SoftOff Mode\n");
softoff_app();
break;
来源:app.c
系统级下电则通过 sys_softoff()(软关机)与 sys_power_down(-2)(带参数强制下电,如对讲机在射频异常后的恢复流程中调用)完成。二者最终都会走到外设掉电保护:
void _norflash_power_down()
{
spi_cs_l();
spi_write_byte(WINBOND_POWER_DOWN);
spi_cs_h();
}
void _norflash_release_power_down()
{
...
}
来源:norflash.c
设计意图:NorFlash 在系统下电前必须先发 WINBOND_POWER_DOWN(0xB9 类命令)进入低功耗模式,否则 Flash 保持高电流状态会显著消耗电池,甚至因电压跌落产生写错误。先拉低 CS、写入命令、再拉高 CS 的时序保证了命令被 Flash 正确锁存。
核心流程
低电自动关机时序
sequenceDiagram
participant LOOP as 应用主循环
participant ADC as ADC 采样
participant PM as app_power_scan()
participant MSG as 消息队列
participant APP as 应用层 (SOFTOFF_MODE)
participant SYS as sys_softoff() / sys_power_down()
participant FLASH as NorFlash
LOOP->>PM: 周期调用 (500ms 粒度)
PM->>ADC: adc_get_voltage(AD_CH_PMU_VBAT) * 4
ADC-->>PM: 电压值 (mV)
alt 电压 <= LOW_POWER_VOL 且未充电
PM->>PM: low_power_cnt++ (去抖)
Note over PM: low_power_cnt == TCFG_SHUTDOWN_TIME / 500
PM->>MSG: post_msg(1, MSG_POWER_OFF)
MSG->>APP: 分发 MSG_POWER_OFF
APP->>APP: log_info("-SoftOff Mode") / softoff_app()
APP->>SYS: 执行软关机/下电
SYS->>FLASH: _norflash_power_down()
FLASH-->>SYS: Flash 进入低功耗
else 电压正常 或 充电中
PM->>PM: low_power_cnt = 0 (复位计数)
end
上电按键仲裁时序
sequenceDiagram
participant BOOT as 系统上电
participant KEY as check_power_on_key()
participant WDT as 看门狗
participant SYS as sys_softoff()
BOOT->>KEY: 启动按键检测
KEY->>KEY: is_port_edge_wkup_source() 校验唤醒源
loop 每 10ms 轮询
KEY->>WDT: wdt_clear() 喂狗
KEY->>KEY: get_power_on_status()
alt 持续按住 > 100ms
KEY-->>BOOT: return,继续正常开机
else 持续松开 > 100ms
KEY->>SYS: log_info("enter softpoweroff") → sys_softoff()
end
end
电源状态机
stateDiagram-v2
[*] --> POWER_ON: 上电 (Port Edge Wakeup)
POWER_ON --> CHECK_KEY: check_power_on_key()
CHECK_KEY --> RUNNING: 开机键持续按住 > 100ms
CHECK_KEY --> SOFT_OFF: 开机键松开 > 100ms (误开机)
RUNNING --> LOW_POWER_WARN: VBAT <= lvd_warning_voltage
LOW_POWER_WARN --> RUNNING: 充电接入 或 电压回升
LOW_POWER_WARN --> SOFT_OFF: 持续低电 TCFG_SHUTDOWN_TIME
RUNNING --> SOFT_OFF: 用户长按关机
SOFT_OFF --> [*]: 掉电 (NorFlash POWER DOWN)
状态说明:RUNNING 是唯一"正常业务"状态;LOW_POWER_WARN 是软件警戒区,此时只计数不立即关机,给充电/负载波动留出恢复窗口;SOFT_OFF 是终态,进入后系统掉电,仅能通过重新上电(开机键沿触发)离开。
使用示例
示例 1:电源管理初始化(应用启动时调用一次)
void app_power_init(void)
{
low_power_warning_init();
adc_add_sample_ch(AD_CH_PMU_VBAT);
app_power_scan();
}
示例 2:读取当前电池电压(供 UI 电量显示)
u16 app_power_get_vbat(void)
{
return vbat_voltage;
}
返回值为毫伏(mV),由 app_power_scan() 每次扫描后刷新。UI 层可据此换算百分比电量;头文件中的 LOW_POWER_WARN_VAL (240) 与 LOW_POWER_OFF_VAL (220) 预留了百分比形式的告警/关机阈值(标注 TODO by bt,当前未启用)。
示例 3:开机键仲裁(上电流程中调用)
void check_power_on_key(void)
{
extern bool is_port_edge_wkup_source(void);
if (0 == is_port_edge_wkup_source()) {
printf("not port edge wakeup!\n");
return;
}
// ... 10ms 轮询 get_power_on_status(),松开超时则 sys_softoff()
}
来源:init_app.c
示例 4:NorFlash 掉电保护(关机路径末端)
void _norflash_power_down()
{
spi_cs_l();
spi_write_byte(WINBOND_POWER_DOWN);
spi_cs_h();
}
来源:norflash.c
配置选项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
LVD_WARNING_FOR_LOW_POWER | 宏 | 1 | 低电警戒线来源:1 用 LVD 电压+300mV 动态值;0 用固定 3300mV |
LOW_POWER_VOL | 宏 | lvd_warning_voltage(或 3300) | 低电判定阈值(mV),由上述宏推导 |
LOW_POWER_LOG | 宏 | "Waring!!! Vbat is near to lvd!\n" | 低电告警日志文案 |
LOW_POWER_WARN_VAL | 宏 | 240 | 预留的低电告警电量值(TODO by bt,当前未使用) |
LOW_POWER_OFF_VAL | 宏 | 220 | 预留的低电关机电量值(TODO by bt,当前未使用) |
TCFG_SHUTDOWN_TIME | 配置(ms) | 由 app_config.h 定义 | 低电持续该时长后关机;app_power_scan() 按 TCFG_SHUTDOWN_TIME / 500 折算去抖次数 |
TCFG_CHARGE_ENABLE | 配置 | 由 app_config.h 定义 | 是否使能充电检测;为 0 时 CHARGE_IN_DET 恒为 0,低电时直接计数 |
CHARGE_IN_DET | 宏 | charge_get_lvcmp_det() | 充电接入检测:非 0 表示充电中,屏蔽低电关机 |
AD_CH_PMU_VBAT | 枚举 | ADC 通道 | 电池电压采样通道,在 app_power_init() 中注册 |
| 采样周期 | 约定 | 500ms | 主循环调用 app_power_scan() 的周期,与去抖步进对齐 |
宏定义见 app_power_mg.h,采样逻辑见 app_power_mg.c。
API 参考
void app_power_init(void)
电源管理初始化,应用启动时调用一次。
- 功能:初始化低电警戒线(
lvd_warning_init(),LVD+300mV)、注册 VBAT ADC 采样通道、执行首次扫描。 - 参数:无
- 返回:无
- 依赖:
get_lvd_vol()(芯片 LVD 电压)、adc_add_sample_ch()、app_power_scan()
void app_power_scan(void)
周期扫描电池电压,更新低电状态与 vbat_voltage。
- 功能:读取
AD_CH_PMU_VBAT电压(×4 缩放);当电压 ≤LOW_POWER_VOL且未充电时递增去抖计数low_power_cnt,计数达TCFG_SHUTDOWN_TIME / 500后打印告警日志并post_msg(1, MSG_POWER_OFF);否则清零计数;最后刷新vbat_voltage。 - 参数:无
- 返回:无
- 边界:采样值为 0 时直接返回,不更新状态(避免 ADC 未就绪误判)
u16 app_power_get_vbat(void)
获取最近一次扫描的电池电压。
- 功能:返回静态变量
vbat_voltage(单位 mV)。 - 参数:无
- 返回:
u16电压值(mV),未扫描时为 0
void check_power_on_key(void)(上电流程)
开机键状态仲裁,上电初始化阶段调用。
- 功能:校验唤醒源为 Port Edge 后,以 10ms 周期轮询
get_power_on_status();持续按住 >100ms 正常开机返回;持续松开 >100ms 则打印"enter softpoweroff"并调用sys_softoff()。 - 参数:无
- 返回:无(软关机分支不返回)
- 依赖:
is_port_edge_wkup_source()、get_power_on_status()、wdt_clear()、sys_softoff()
void sys_softoff(void) / void sys_power_down(int)(系统层)
系统软关机/下电入口,由底层电源模块(power_api / power_app)实现,本文档链路中作为动作终点。
- 行为:关闭外设(含
_norflash_power_down()使 Flash 进入低功耗)后掉电;sys_power_down(-2)带负参数强制下电,常见于异常恢复流程。
void _norflash_power_down(void) / void _norflash_release_power_down(void)
NorFlash 掉电保护与恢复。
- 功能:通过 SPI 发送
WINBOND_POWER_DOWN命令使 Flash 进入低功耗模式;恢复命令用于退出该模式。 - 时序:CS 拉低 → 写命令字节 → CS 拉高,确保命令锁存。
失败模式、边界情况与并发
失败模式
| 失败场景 | 表现 | 源码防护 |
|---|---|---|
| ADC 未就绪返回 0 | 误判 0V 低电 | app_power_scan() 对 0 == vol 直接 return,不计数、不更新电压 |
| 电压瞬时跌落(射频发射压降) | 误触发关机 | 去抖计数 + TCFG_SHUTDOWN_TIME 持续时长门限,短时跌落会被清零 |
| 充电器插入瞬间 | 边充边用被关机 | CHARGE_IN_DET 非 0 时低电计数清零,充电期间完全屏蔽低电关机 |
| 非开机键唤醒(RTC/IO 等) | 上电阶段误软关机 | is_port_edge_wkup_source() 前置校验,非沿唤醒直接跳过按键仲裁 |
| 开机键抖动 | 误判松开 | 100ms(10×10ms)连续采样窗口,get_power_on_status() 状态需稳定保持 |
| 上电检测期间看门狗超时 | 意外复位 | 每 10ms 轮询循环内 wdt_clear() 喂狗 |
| 低电关机时 Flash 写操作 | 数据损坏 | 关机路径末端执行 _norflash_power_down(),先保护存储再掉电 |
边界情况
- 低电计数不饱和:
low_power_cnt在达到阈值投递消息后不再继续增长(==判断),且下一次扫描若电压回升即清零,不会出现"粘滞"; - 电压阈值动态性:启用
LVD_WARNING_FOR_LOW_POWER时阈值随芯片 LVD 配置漂移(get_lvd_vol()),更换电池/供电方案无需改代码;禁用时固定 3.3V 适合稳压供电场景; - 预留阈值未生效:
LOW_POWER_WARN_VAL (240)/LOW_POWER_OFF_VAL (220)标注TODO by bt,说明基于"电量百分比"的第二套关机策略尚未启用,当前仅靠电压阈值。
并发与一致性
- 消息串行化:低电关机通过
post_msg(1, MSG_POWER_OFF)投递到应用消息队列,由主循环在单一任务上下文中分发执行(SOFTOFF_MODE分支),从触发到关机的整个路径不存在多线程竞争; - 静态变量访问:
vbat_voltage由app_power_scan()写、app_power_get_vbat()读。若 UI 任务与扫描任务分属不同任务/中断上下文,读取方获得的是上次扫描的"快照"值(最大滞后一个采样周期),设计中以可容忍的延迟换取无锁简单性; - 阻塞式关机:
sys_softoff()/sys_power_down()为不可逆阻塞调用,调用后不返回,业务代码不得在关机路径上安排需等待的异步操作。
性能与运维
- 采样开销:单次
app_power_scan()仅一次 ADC 读取 + 比较 + 计数,开销可忽略;ADC 通道在初始化时一次性注册,无重复配置开销; - 时间对齐:去抖步进硬编码为 500ms(
TCFG_SHUTDOWN_TIME / 500),主循环必须以约 500ms 周期调用app_power_scan(),周期漂移会直接改变实际关机延迟——调大TCFG_SHUTDOWN_TIME时注意同步检查调用周期; - 看门狗协同:上电按键仲裁阶段独占 CPU 且主动喂狗,与正常运行期的 WDT 服务策略互补,保证仲裁期间不被复位;
- 调试观测:
log_info("lvd_warning_voltage : %d")输出动态警戒线;log_error(LOW_POWER_LOG)输出低电告警;log_char('+')/('-')输出上电按键轮询轨迹,可直接串口抓取验证阈值与时序。
扩展点
- 低电策略切换:通过
LVD_WARNING_FOR_LOW_POWER宏一键切换动态/固定警戒线;LOW_POWER_WARN_VAL/LOW_POWER_OFF_VAL预留了百分比电量策略的接入点; - 关机动作定制:
softoff_app()(softoff_mode.h)是软关机的应用层钩子,可在其内部追加状态保存(如配对信息、音量、音效设置)后再进入系统下电; - 充电联动:
TCFG_CHARGE_ENABLE使能后CHARGE_IN_DET挂钩charge_get_lvcmp_det(),可替换为其它充电检测源(GPIO 电平、充电 IC 状态寄存器)以满足不同硬件方案; - 外设掉电扩展:
_norflash_power_down()是关机路径外设保护的代表实现,其它易失外设(如传感器、射频前端)可在同一关机路径中追加对应的低功耗命令。
相关链接
- app_power_mg.h(阈值宏与 API 声明)
- app_power_mg.c(低电检测与采样实现)
- init_app.c(开机键仲裁与软关机)
- app.c(SOFTOFF_MODE 消息分发)
- norflash.c(NorFlash 掉电保护)
- 芯片底层电源接口:power_api.h、power_manage.h
- 相关目录页:充电管理(charge)、低功耗睡眠/唤醒、ADC 驱动、应用消息机制(msg)