电池检测与电源管理
本文档介绍 AC82N GP-MCU SDK 中供电电源(电池)检测与电量管理子系统:它基于 GPADC 电压采样与分段线性插值将电源电压换算为 0~100% 电量,通过系统定时器周期扫描实现电量缓变跟踪、充放电状态判断,并在电量耗尽时通过系统消息事件通知业务层进入低电处理流程。
Purpose and Scope
本页面覆盖电源管理子系统的完整实现链路,包括:
- 电量管理模块
sdk/apps/common/battery/battery.c与其头文件battery.h(电压-电量映射表、初始化/扫描/反初始化逻辑、充放电状态检测); - 底层电压采集所依赖的 GPADC 驱动配置(
sdk/cpu/cd09/gpadc.c中的电池检测 IO 与采样通道注册); - 应用层配置项
TCFG_BATTERY_TYPE_SEL、TCFG_BAT_DET_IO、TCFG_BAT_AD_CHANNEL等(sdk/apps/gp_mcu/include/app_config.h)及其对电量模型的影响; - 低电事件
MSG_EVENT_BATTERY_LOWPOWER的发送时机与业务层的消费方式。
以下内容属于其他页面,不在本文展开:具体低电事件在业务层(如 UI 提示、关机流程)的详细处理逻辑、GPADC 驱动本身的采样机制细节、以及充电管理硬件方案(充电芯片驱动)的实现。SDK 中 doc/软件工具/.../Battery.ctrl 与 UI 工程中的电池图标资源属于 UI 表现层,仅作参考。
Overview
本 SDK 面向 GP-MCU 系列(AC82N)嵌入式应用,常见产品形态为使用干电池或锂电池供电的消费电子设备。电源管理子系统需要解决三个核心问题:
- 电压怎么采:通过 PMU 内部
VPWR通道(1/4 分压)或外部 GPIO 复用为 GPADC 通道(默认 1/3 分压)读取电源电压,单位为 mV。 - 电压怎么换算成电量:不同电池类型(2/3/4 节干电池、锂电池)的放电曲线差异很大,SDK 为每种类型预置一张 11 档电压表(0%~100%),在相邻档位之间做线性插值得到电量百分比。
- 电量怎么更新与上报:初始化时阻塞采样 8 次求平均值作为初值;之后由系统定时器每 30 秒触发一次
battery_scan()周期采样,采用每次 ±1% 的缓变策略避免 UI 电量跳变;当电量降到 0% 且处于放电状态时,停止监测并通过sys_msg_event_notify()广播MSG_EVENT_BATTERY_LOWPOWER事件。
该设计刻意保持轻量:模块仅依赖 gpadc_api.h、timer.h、event.h、gpio.h,无独立线程、无阻塞等待(仅初始化时的一次性阻塞采样),非常适合资源受限的 MCU 环境。
Architecture
flowchart TD
subgraph sg_App["应用层 (sdk/apps)"]
AppInit["系统启动/初始化流程"]
BMS["battery.c<br/>电量管理模块"]
UI["业务/UI层"]
end
subgraph sg_Driver["驱动与系统服务"]
GPADC["gpadc.c / gpadc_api.h<br/>GPADC 驱动"]
TM["sys_timer 系统定时器"]
EVT["event 系统消息事件"]
end
subgraph sg_HW["硬件"]
VPWR["PMU VPWR 电压<br/>(1/4 分压)"]
ExtIO["外部IO采样<br/>(默认1/3 分压)"]
ChargeIO["充电检测IO<br/>(仅锂电池)"]
end
AppInit -->|"battery_init()"| BMS
BMS -->|"adc_get_voltage / adc_get_voltage_by_blocking"| GPADC
BMS -->|"sys_timer_add(battery_scan, 30s)"| TM
TM -->|"周期回调 battery_scan()"| BMS
BMS -->|"sys_msg_event_notify(MSG_EVENT_BATTERY_LOWPOWER)"| EVT
EVT -->|"事件分发"| UI
UI -->|"get_battery_lvl() / get_battery_charge_state()"| BMS
GPADC -->|"采样"| VPWR
GPADC -->|"采样"| ExtIO
BMS -->|"gpio_read(TCFG_CHARGE_DET_IO)"| ChargeIO
架构说明
- battery.c 电量管理模块是整个子系统的核心,持有唯一的全局状态
struct _battery_info(当前电量battery_lvl+ 定时器句柄battery_timer),对外暴露battery_init()、get_battery_lvl()、get_battery_charge_state()等接口,对内通过 GPADC 驱动读取电压、通过系统定时器驱动周期扫描、通过事件机制上报低电状态。 - gpadc.c 驱动层负责把硬件 IO 配置成 GPADC 功能并注册采样通道。源码中
#ifdef TCFG_BAT_DET_IO分支会将该 IO 设置为PORT_INPUT_FLOATING+PORT_FUNC_GPADC,并调用adc_add_sample_ch(TCFG_BAT_AD_CHANNEL)注册采样通道(见 gpadc.c)。 - 系统定时器与事件服务是模块与外界交互的两个通道:定时器负责低频(30 秒)周期采样;事件通道负责把低电告警异步通知给业务层,使业务层(如 UI 提示、强制关机)不必轮询。
- 硬件侧存在两条电压采样路径:
AD_ANA_PMU_CH_VPWR_DIV_4走 PMU 内部 1/4 分压通道;其他外部 IO 通道按 1/3 分压处理。代码中根据通道宏选择 ×4 或 ×3 的还原系数(见 battery.c)。
电量模型与电压表
电量百分比由电压-电量映射表 + 线性插值计算而来,映射表由 TCFG_BATTERY_TYPE_SEL 在编译期决定。battery.h 针对四种电池类型分别定义了 0%~100% 共 11 档电压(单位 mV),例如锂电池的映射关系:
#elif (TCFG_BATTERY_TYPE_SEL == BATTERY_TYPE_LITHIUM_DELL)
#define BATTERY_LVL_0 (3300)
#define BATTERY_LVL_10 (3680)
#define BATTERY_LVL_20 (3740)
#define BATTERY_LVL_30 (3770)
#define BATTERY_LVL_40 (3790)
#define BATTERY_LVL_50 (3820)
#define BATTERY_LVL_60 (3870)
#define BATTERY_LVL_70 (3920)
#define BATTERY_LVL_80 (3980)
#define BATTERY_LVL_90 (4060)
#define BATTERY_LVL_100 (4100)
#endif
Source: battery.h
四种电池类型的电压表差异显著,体现了不同的放电特性:
| 电池类型 | 宏定义 | 0% 电压 | 100% 电压 | 特性说明 |
|---|---|---|---|---|
| 2 节干电池 | BATTERY_TYPE_2_DRY_DELL | 2000 mV | 3200 mV | 近似线性,每 10% 间隔 120 mV |
| 3 节干电池 | BATTERY_TYPE_3_DRY_DELL | 3000 mV | 4800 mV | 近似线性,每 10% 间隔 180 mV(默认) |
| 4 节干电池 | BATTERY_TYPE_4_DRY_DELL | 4000 mV | 6400 mV | 近似线性,每 10% 间隔 240 mV |
| 锂电池 | BATTERY_TYPE_LITHIUM_DELL | 3300 mV | 4100 mV | 非线性:10%~40% 区间间隔很小(30~60 mV),贴近锂电池平台期特性 |
设计意图:干电池放电曲线接近线性,采用等间隔电压表即可;锂电池在平台期(3.7~3.9V 附近)电压变化极缓,若用等间隔表会长期显示同一电量,因此低电量段(10%~50%)电压间隔刻意收窄,使平台期内的电量变化仍可被分辨。
核心实现详解
模块状态与全局结构
模块使用单一静态全局实例保存运行时状态,并通过 __this 宏访问,避免传入指针的样板代码:
struct _battery_info {
u8 battery_lvl; //供电电源电量
u16 battery_timer; //供电电源检测定时器
};
static struct _battery_info battery_info;
#define __this (&battery_info)
const u16 voltage_table[] = {
BATTERY_LVL_0, BATTERY_LVL_10, BATTERY_LVL_20, BATTERY_LVL_30, BATTERY_LVL_40,
BATTERY_LVL_50, BATTERY_LVL_60, BATTERY_LVL_70, BATTERY_LVL_80, BATTERY_LVL_90,
BATTERY_LVL_100,
};
Source: battery.c
voltage_table[] 是编译期根据 battery.h 中宏生成的 11 项常量数组,battery_lvl 为 0~100 的电量值,battery_timer 为系统定时器句柄(0 表示未注册)。
电压采集:分压还原
battery_get_voltage() 依据采样通道决定电压还原系数——这是硬件分压比在软件侧的补偿:
static u16 battery_get_voltage(void)
{
//锂电池和4节电池时,采用外部IO采集电压,默认分压1/3
if (TCFG_BAT_AD_CHANNEL == AD_ANA_PMU_CH_VPWR_DIV_4) {
return (adc_get_voltage(TCFG_BAT_AD_CHANNEL) * 4);
} else {
return (adc_get_voltage(TCFG_BAT_AD_CHANNEL) * 3);
}
}
Source: battery.c
设计要点:
- 当
TCFG_BAT_AD_CHANNEL == AD_ANA_PMU_CH_VPWR_DIV_4时,走 PMU 内部VPWR通道,硬件已做 1/4 分压,软件乘 4 还原真实电压; - 其他外部 IO 通道(如
AD_CH_PA0)按 1/3 分压设计(外部电阻分压网络),软件乘 3 还原; - 注释特别说明"锂电池和 4 节电池时采用外部 IO 采集"——高电压场景(4.2V 锂电池满电、4 节干电池 6.4V)超出内部通道量程或精度需求时,必须改用外部电阻分压网络接入 GPADC IO。
电量换算:分段线性插值
cal_battery_lvl() 是电压→电量的核心算法:先做上下界钳制,再在相邻电压档位间线性插值:
static u8 cal_battery_lvl(u16 voltage)
{
u16 tmp0, tmp1;
if (voltage >= BATTERY_LVL_100) {
return 100;
} else if (voltage <= BATTERY_LVL_0) {
return 0;
}
for (u8 i = 1; i < ARRAY_SIZE(voltage_table); i++) {
if (voltage <= voltage_table[i]) {
tmp0 = voltage_table[i] - voltage_table[i - 1];
tmp1 = voltage - voltage_table[i - 1];
return (tmp1 * 10 / tmp0 + (i - 1) * 10);
}
}
return 0;
}
Source: battery.c
算法说明:
- 边界钳制:电压 ≥ 100% 档直接返回 100,≤ 0% 档返回 0,避免插值越界;
- 线性插值:从低到高找到第一个
voltage <= voltage_table[i]的档位,按比例tmp1 * 10 / tmp0计算档内偏移,再加上(i-1)*10的基准百分比;整数运算避免浮点,适合无 FPU 的 MCU; - 由于各档间隔不同(如锂电池 10%~40% 间隔仅 30~60 mV),同一电压落在不同档位时换算分辨率不同,这是有意的设计取舍。
充放电状态检测
get_battery_charge_state() 区分干电池与锂电池的充电管理差异——干电池只有放电状态,锂电池通过 IO 电平判断充电:
enum charge_state get_battery_charge_state(void)
{
#if (TCFG_BATTERY_TYPE_SEL != BATTERY_TYPE_LITHIUM_DELL)
return DISCHARGE_STATE;
#else
if (gpio_read(TCFG_CHARGE_DET_IO)) {
return CHARGE_STATE;
} else {
return DISCHARGE_STATE;
}
#endif
}
Source: battery.c
设计意图:干电池不可充电,直接编译期短路返回 DISCHARGE_STATE(零开销);锂电池配置了充电检测 IO TCFG_CHARGE_DET_IO,充电器插入时 IO 为高电平,返回 CHARGE_STATE。该状态被 battery_scan() 用来决定电量是否允许回升(充电中才允许 +1)。
周期扫描与低电告警
battery_scan() 是注册到系统定时器的回调(周期 30 秒),完成一次"采样 → 换算 → 缓变更新 → 低电判断"的完整周期:
void battery_scan(void *priv)
{
u16 battery_volt;
u8 curr_battery_lvl;
enum charge_state state = get_battery_charge_state();
battery_volt = battery_get_voltage();
curr_battery_lvl = cal_battery_lvl(battery_volt);
if (curr_battery_lvl < __this->battery_lvl) {
__this->battery_lvl--;
} else if (curr_battery_lvl > __this->battery_lvl) {
if (state == CHARGE_STATE) {
__this->battery_lvl++;
}
}
log_info("scan volt: %dmV, lvl: %d, cur_lvl: %d", battery_volt, __this->battery_lvl, curr_battery_lvl);
if (!__this->battery_lvl && (state == DISCHARGE_STATE)) {
battery_uninit();
sys_msg_event_notify(MSG_EVENT_BATTERY_LOWPOWER, 0);
}
}
Source: battery.c
关键行为:
- 缓变策略(防抖):
battery_lvl每次扫描最多变化 1 个百分点。即使采样电压突变导致curr_battery_lvl大幅下降,对外电量也只递减 1,避免 UI 电量"跳水";同理,仅当处于充电状态时电量才允许递增,防止放电瞬间的电压回弹造成电量虚高。这是典型的软件滤波/迟滞设计。 - 低电判定与自停:当电量已降到 0 且处于放电状态时,调用
battery_uninit()主动停止采样与定时器(进入"节能自关闭"),并广播MSG_EVENT_BATTERY_LOWPOWER事件,由业务层决定提示用户或强制关机。 - 日志可观测:每次扫描输出
volt/lvl/cur_lvl三个量,便于现场用log_info排查电量异常。
初始化与反初始化
battery_init() 是模块入口,阻塞采样 8 次求平均作为电量初值;若初值即低电则直接返回低电状态且不启动定时器,否则注册 30 秒周期扫描:
enum battery_state battery_init(void)
{
u16 battery_volt;
enum charge_state state = get_battery_charge_state();
if (TCFG_BAT_AD_CHANNEL == AD_ANA_PMU_CH_VPWR_DIV_4) {
battery_volt = adc_get_voltage_by_blocking(TCFG_BAT_AD_CHANNEL, 8) * 4;
} else {
battery_volt = adc_get_voltage_by_blocking(TCFG_BAT_AD_CHANNEL, 8) * 3;
}
__this->battery_lvl = cal_battery_lvl(battery_volt);
if (!__this->battery_lvl && (state == DISCHARGE_STATE)) {
return BATTERY_LOWPOWER;
} else {
if (!__this->battery_timer) {
__this->battery_timer = sys_timer_add(NULL, battery_scan, 30 * 1000);
}
return BATTERY_NORMAL;
}
}
Source: battery.c
配套的 battery_uninit() 负责反初始化:移除 PMU 采样通道并删除定时器(句柄置 0,保证可重复初始化):
void battery_uninit(void)
{
adc_remove_sample_ch(AD_ANA_PMU_CH_VPWR_DIV_4);
if (__this->battery_timer) {
sys_timer_del(__this->battery_timer);
__this->battery_timer = 0;
}
}
Source: battery.c
设计要点:
- 初始化使用
adc_get_voltage_by_blocking(ch, 8)一次性阻塞采样 8 次,以平均抑制上电瞬间的采样噪声;而周期扫描使用非阻塞的adc_get_voltage(),避免在定时器回调中长时间阻塞; battery_timer判空后再sys_timer_add,保证battery_init()可重复调用不会产生多个定时器;- 低电自检前置:开机即电量 0% 且未充电时,直接跳过定时器注册,系统可立即进入低电流程,不浪费任何资源。
核心流程
电量监测生命周期
sequenceDiagram
participant App as 应用层
participant Bat as battery.c
participant ADC as GPADC 驱动
participant TM as 系统定时器
participant UI as 业务/UI层
App->>Bat: battery_init()
Bat->>Bat: 读取充放电状态 (get_battery_charge_state)
Bat->>ADC: adc_get_voltage_by_blocking(ch, 8) 阻塞采样8次
ADC-->>Bat: 原始采样值
Bat->>Bat: 按分压比 ×3/×4 还原电压,cal_battery_lvl 求初值
alt 初值 0% 且放电中
Bat-->>App: 返回 BATTERY_LOWPOWER(不启动定时器)
else 电量正常
Bat->>TM: sys_timer_add(battery_scan, 30*1000)
Bat-->>App: 返回 BATTERY_NORMAL
end
loop 每 30 秒
TM->>Bat: 触发 battery_scan(priv)
Bat->>ADC: adc_get_voltage(ch) 非阻塞采样
Bat->>Bat: 换算电量,按 ±1 缓变更新 battery_lvl
opt 电量已为 0 且放电中
Bat->>Bat: battery_uninit() 移除通道、删除定时器
Bat->>UI: sys_msg_event_notify(MSG_EVENT_BATTERY_LOWPOWER, 0)
UI->>UI: 业务层低电处理(提示/关机)
end
end
电量状态机
stateDiagram-v2
[*] --> Normal: battery_init() 返回 BATTERY_NORMAL,注册30s定时器
Normal --> LowPower: battery_scan() 检测到 0% 且处于放电状态
LowPower --> [*]: battery_uninit() 停止采样与定时器,广播 MSG_EVENT_BATTERY_LOWPOWER
[*] --> LowPower: battery_init() 初值即 0% 且放电中(开机即低电)
状态说明:
- Normal(正常):定时器活跃,每 30 秒扫描一次;电量随扫描缓变(放电递减、充电递增);
- LowPower(低电):模块已自停(采样通道移除、定时器删除),系统仅靠
MSG_EVENT_BATTERY_LOWPOWER事件驱动业务层后续动作; - 状态机不存在从 LowPower 自动回到 Normal 的路径——低电后需业务层重新调用
battery_init()(例如用户更换电池/插入充电器后重启监测)才能恢复,这是由battery_uninit()把battery_timer置 0、battery_init()可重复调用共同保证的。
使用示例
初始化并获取电量
以下代码展示应用层如何启动电量监测并周期读取电量值(电量缓变更新,UI 可直接使用):
enum battery_state battery_init(void);
u8 get_battery_lvl(void);
Source: battery.h
// 系统启动流程中调用,返回 BATTERY_NORMAL 或 BATTERY_LOWPOWER
enum battery_state bs = battery_init();
if (bs == BATTERY_LOWPOWER) {
// 开机即低电:提示用户更换电池/接入充电器
}
// 业务层任意位置读取当前电量(0~100,定时器后台维护)
u8 lvl = get_battery_lvl();
// 刷新 UI 电池图标:显示 lvl 对应档位图标
说明:
get_battery_lvl()在 battery.c 中实现,直接返回__this->battery_lvl,无任何加锁/阻塞,可在任意上下文(包括定时器回调、中断外上下文)安全读取。
充放电状态判断(锂电池)
锂电池场景下,业务层可通过充电状态决定是否显示充电动画:
enum charge_state get_battery_charge_state(void)
{
#if (TCFG_BATTERY_TYPE_SEL != BATTERY_TYPE_LITHIUM_DELL)
return DISCHARGE_STATE;
#else
if (gpio_read(TCFG_CHARGE_DET_IO)) {
return CHARGE_STATE;
} else {
return DISCHARGE_STATE;
}
#endif
}
Source: battery.c
if (get_battery_charge_state() == CHARGE_STATE) {
// 显示"充电中"动画;电量只增不减(由 battery_scan 保证)
}
低电事件订阅
系统消息事件 MSG_EVENT_BATTERY_LOWPOWER 由 battery_scan() 在电量耗尽时通过 sys_msg_event_notify() 广播(见 battery.c)。业务层在自身事件处理循环中订阅该事件,典型的处理是低电提示音 + 若干秒后强制关机,这部分逻辑属于业务层范畴,本文不再展开。
硬件层配置接线(gpadc.c)
gpadc.c 在初始化阶段根据 TCFG_BAT_DET_IO 配置电池检测引脚:先把 IO 设为浮空输入,再切换为 GPADC 功能,并注册采样通道:
#ifdef TCFG_BAT_DET_IO
#if (TCFG_BAT_DET_IO != NO_CONFIG_PORT)
gpio_set_mode(IO_PORT_SPILT(TCFG_BAT_DET_IO), PORT_INPUT_FLOATING);
gpio_set_function(IO_PORT_SPILT(TCFG_BAT_DET_IO), PORT_FUNC_GPADC);
#endif
adc_add_sample_ch(TCFG_BAT_AD_CHANNEL);
#endif
Source: gpadc.c
设计意图:
- 当
TCFG_BAT_DET_IO == NO_CONFIG_PORT(默认)时,不配置外部 IO,直接注册AD_ANA_PMU_CH_VPWR_DIV_4内部通道——此时电池直接供电给系统,由 PMU 内部 1/4 分压采样; - 当配置了具体 IO(如
IO_PORT_PA0)时,外部电阻分压网络接入该引脚,必须先设为浮空输入(避免上/下拉影响分压精度)再切换 GPADC 功能。
配置选项
电源管理相关配置集中在 sdk/apps/gp_mcu/include/app_config.h,编译期生效:
#define BATTERY_TYPE_4_DRY_DELL (2) //4节干电池(6.4V),放电管理
#define BATTERY_TYPE_LITHIUM_DELL (3) //锂电池(4.2V),充放电管理
#define TCFG_BATTERY_TYPE_SEL BATTERY_TYPE_3_DRY_DELL
#define TCFG_BAT_DET_IO NO_CONFIG_PORT //检测电池的IO
//若TCFG_BAT_DET_IO为NO_CONFIG_PORT,则TCFG_BAT_AD_CHANNEL默认配置为AD_ANA_PMU_CH_VPWR_DIV_4
//若TCFG_BAT_DET_IO为实际IO(例如IO_PORT_PA0),则TCFG_BAT_AD_CHANNEL需配置为对应AD通道(如AD_CH_PA0)
#define TCFG_BAT_AD_CHANNEL AD_ANA_PMU_CH_VPWR_DIV_4 //电池检测的AD通道
Source: app_config.h
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_BATTERY_TYPE_SEL | 枚举宏 | BATTERY_TYPE_3_DRY_DELL | 电池类型,决定 battery.h 中电压表与充放电管理方式;可选 BATTERY_TYPE_2_DRY_DELL(2 节干电池)、BATTERY_TYPE_3_DRY_DELL(3 节干电池)、BATTERY_TYPE_4_DRY_DELL(4 节干电池)、BATTERY_TYPE_LITHIUM_DELL(锂电池) |
TCFG_BAT_DET_IO | IO 端口宏 | NO_CONFIG_PORT | 外部电压采样 IO;NO_CONFIG_PORT 时使用 PMU 内部 VPWR 通道,配置实际 IO 时走外部电阻分压网络 |
TCFG_BAT_AD_CHANNEL | AD 通道宏 | AD_ANA_PMU_CH_VPWR_DIV_4 | 电池检测的 AD 采样通道;与 TCFG_BAT_DET_IO 配套,外部 IO 需配置为对应 AD 通道(如 AD_CH_PA0) |
TCFG_CHARGE_DET_IO | IO 端口宏 | 锂电池工程必配 | 充电检测 IO(锂电池类型时使用),gpio_read() 高电平表示充电中;干电池类型下该宏不参与编译 |
配置约束:
- 内部通道与外部 IO 二选一:
TCFG_BAT_DET_IO = NO_CONFIG_PORT时只能使用AD_ANA_PMU_CH_VPWR_DIV_4;配置外部 IO 时需同步切换TCFG_BAT_AD_CHANNEL,否则采样通道与引脚不匹配; - 高电压场景建议外部采样:4 节干电池(满电 6.4V)与锂电池满电(4.2V)场景注释明确建议走外部 IO 分压采样(默认 1/3 分压);
- 修改
TCFG_BATTERY_TYPE_SEL后需全量重编译,因为电压表宏在battery.h中以#if/#elif编译期展开。
API 参考
enum battery_state battery_init(void)
供电电源初始化函数,阻塞采样 8 次得到电量初值,电量正常时注册 30 秒周期扫描定时器。
- 参数:无
- 返回:
BATTERY_NORMAL(正常状态,监测已启动);BATTERY_LOWPOWER(低电状态,监测未启动) - 说明:可重复调用;
battery_timer非 0 时不会重复注册定时器;低电时返回BATTERY_LOWPOWER且不注册定时器 - Source: battery.h / battery.c
u8 get_battery_lvl(void)
获取当前供电电源电量百分比。
- 参数:无
- 返回:0~100 的电量值(0 为耗尽,100 为满电)
- 说明:读取全局结构体字段,无阻塞,可在任意非中断上下文调用;数值由 30 秒扫描缓变维护,不会突跳
- Source: battery.h / battery.c
enum charge_state get_battery_charge_state(void)
获取供电电源充电状态(实现在 .c 中,.h 未声明,同编译单元内或自行声明后使用)。
- 参数:无
- 返回:
CHARGE_STATE(充电中,仅锂电池且TCFG_CHARGE_DET_IO为高电平);DISCHARGE_STATE(放电中,干电池恒返回此值) - Source: battery.c
void battery_scan(void *priv)
供电电源电量检测回调,一般半分钟扫描一次(注册于系统定时器)。
- 参数:
priv(定时器回调私有参数,当前实现未使用,传NULL) - 返回:无
- 行为:采样电压 → 换算电量 → 按 ±1 缓变更新 → 电量 0% 且放电时
battery_uninit()并广播MSG_EVENT_BATTERY_LOWPOWER - Source: battery.c
void battery_uninit(void)
退出供电电源检测:移除 PMU 采样通道、删除定时器并将句柄置 0。
- 参数:无
- 返回:无
- 说明:低电自停与业务主动关停共用此函数;调用后模块回到未初始化状态,可再次
battery_init() - Source: battery.c
失败模式、边界情况与并发
- 开机即低电:
battery_init()初值为 0% 且放电中,直接返回BATTERY_LOWPOWER且不注册定时器。业务层必须处理该返回值,否则设备会"无感知"地跳过整个电量监测生命周期。 - 电量耗尽自停:
battery_scan()检测到 0% 且放电时调用battery_uninit()主动停止采样。此时若电池电压在负载移除后回弹(如干电池静置回升),电量也不会自动恢复——需业务层重新初始化;这是刻意设计,避免死机临界区反复唤醒。 - 充电电量回升受限:
battery_lvl仅在CHARGE_STATE时才允许 +1。若锂电池充电检测 IO 配置错误(恒为低),充电中电量将长期不变;反之干电池类型即使电压上升也不会增加电量显示。 - 采样噪声与缓变:单次采样电压抖动会被"每次 ±1"的缓变策略吸收,但代价是电量反映存在最长 30 秒的滞后;对需要实时电压的应用,应直接调用 GPADC 接口而非读取
battery_lvl。 - 并发与重入:模块无锁。
battery_lvl由定时器回调写入、业务上下文读取,均为单字节原子操作,MCU 单核环境下无撕裂风险;battery_init()/battery_uninit()不应在定时器回调内调用自身(battery_uninit由battery_scan内部调用是受控路径:先删通道删定时器再发事件,不会重入)。 - VPWR 通道反初始化不对称:
battery_uninit()固定移除AD_ANA_PMU_CH_VPWR_DIV_4通道;若工程配置为外部 IO 通道,该调用不会移除对应通道,外部通道采样会持续运行(功耗略增),属于当前实现的已知局限。
性能与运维
- CPU 开销极低:每 30 秒一次定时器回调,每次仅一次 ADC 采样 + 一次整数插值运算,无浮点、无阻塞(初始化除外),对 MCU 几乎无负载。
- 初始化阻塞时间:
battery_init()内adc_get_voltage_by_blocking(ch, 8)连续阻塞采样 8 次,在低主频下可能耗时数毫秒,建议在系统启动流程早期(业务未繁忙时)调用。 - 日志观测:
log_info("scan volt: %dmV, lvl: %d, cur_lvl: %d", ...)输出每次扫描的电压、缓变后电量与即时电量,现场排查电量异常(电压跳变、电量不更新)可直接依赖该日志。 - 功耗注意:低电自停后 GPADC 外部通道(若配置)未被移除,对超低功耗待机场景,业务层可自行调用
adc_remove_sample_ch()关闭对应通道。
扩展点
- 新增电池类型:在
app_config.h增加电池类型枚举,并在battery.h增加对应的#elif电压表分支(0%~100% 共 11 档),即可支持新的电池规格,无需改动battery.c算法逻辑。 - 调整扫描周期:修改
battery_init()中sys_timer_add(NULL, battery_scan, 30 * 1000)的周期参数即可改变电量刷新频率;更快的扫描可提高实时性,但会放大采样噪声影响,建议配合缓变策略调整。 - 自定义低电行为:
MSG_EVENT_BATTERY_LOWPOWER是标准的系统消息事件,业务层可在事件分发中注册自己的处理(提示、关机、进入低功耗模式等),模块本身不耦合具体业务。 - 自定义电压还原系数:若外部分压网络不是 1/3,可修改
battery_get_voltage()中×3分支的系数,或按通道宏扩展新的分支。
Related Links
- 电池检测硬件通道配置:gpadc.c(
sdk/cpu/cd09/gpadc.c) - 应用层电池配置项:app_config.h(
sdk/apps/gp_mcu/include/app_config.h) - 电量管理实现:battery.c / battery.h(
sdk/apps/common/battery/) - UI 电池图标资源(表现层参考):Battery.ctrl(
sdk/UI工程/UI界面/示例/project/config/ctrl/Battery.ctrl) - 低电事件
MSG_EVENT_BATTERY_LOWPOWER的系统消息定义见 SDK 事件头文件(event.h),业务层消费逻辑详见对应业务模块文档。