1、运行中空电关机改看显示百分比,到 5% 立刻停电机、关绿灯,红灯闪 5 下后再深睡,避免电压先掉、APP 还没到 5% 就关机

2、充电/放电电量缓冲加快,放电突变拦截先关掉,电量曲线满电点收到 4.12V
3、上电串口打印软硬件版本和编译时间
This commit is contained in:
2026-08-27 11:40:57 +08:00
parent fdb43011b7
commit 215e1be6bb
10 changed files with 205 additions and 68 deletions
+54 -33
View File
@@ -2,13 +2,15 @@
* @file app_power.c
* @brief USB / 充电检测、电量采样与低电策略、深睡请求生命周期实现
* @author cyWu <1917507415@qq.com>
* @date 2026.08.26
* @version V1.1.0
* @date 2026.08.27
* @version V1.2.0
* @history
* - V1.0.0, 2026.08.25, cyWu, 首次发布,实现 USB/充电检测、电量采样与深睡请求
* - V1.1.0, 2026.08.26, cyWu, 取消低电闪烁 1 分钟后自动关机;自动关机门限
* 改为约 5%BOARD_VBAT_SHUTDOWN_MV),与开机
* 低电拦截(app_boot)共用同一门限
* - V1.2.0, 2026.08.27, cyWu, 运行中空电关机改用显示百分比,到 5% 立即停电机
* 并请求空电灯效后再掉电,不再做连续次数窗口
******************************************************************************/
#include "app_power.h"
@@ -34,7 +36,6 @@
#define BAT_BOOT_MS 3000 /* 上电 3s 内不因低电深睡 */
#define BAT_LOG_MS 10000
#define BAT_LOW_WIN 3 /* 连续约 2.4s 才进低电告警(仅闪灯,不关机) */
#define BAT_EMPTY_WIN 5 /* 连续约 4s 才判定 <=5%,自动关机 */
/** 充电检测:CHG/VPWR 消抖后的结果 */
typedef struct {
@@ -57,11 +58,10 @@ typedef struct {
/** 低电告警:进入/退出都要连续窗口消抖,避免电机拉压降误报
* @note 低电(BOARD_VBAT_LOW_MV)只负责红灯闪烁提示,不再有超时自动关机;
* 真正的自动关机改由 power_battery_check_empty 在电量约 5%
* BOARD_VBAT_SHUTDOWN_MV)时触发 */
* 真正的自动关机改由 power_battery_check_empty 在显示电量
* <= BOARD_BATTERY_SHUTDOWN_PCT5%)时立刻触发 */
typedef struct {
uint8_t low_bat;
uint8_t empty_win;
uint8_t enter_win;
uint8_t exit_win;
} AppBatteryAlarm_t;
@@ -72,6 +72,7 @@ typedef struct {
AppBatterySample_t sample;
AppBatteryAlarm_t alarm;
uint8_t req_poweroff;
uint8_t empty_off; /**< 1=空电路径:闪完红灯再掉电 */
AppPowerHook_t on_request;
AppPowerHook_t on_finalize;
} AppPowerCtx_t;
@@ -83,7 +84,7 @@ static AppPowerEvt_t power_charge_tick(uint16_t dt);
static void power_battery_tick(uint16_t dt);
static uint8_t power_battery_sample_ready(uint16_t dt, uint16_t *out_mv);
static void power_battery_update_percent(uint16_t mv);
static void power_battery_check_empty(uint16_t mv);
static void power_battery_check_empty(void);
static void power_battery_check_low(uint16_t mv);
static void power_battery_periodic_log(uint16_t dt);
@@ -100,8 +101,8 @@ void app_power_init(void)
/**
* @brief 注册深睡请求的两段式回调
* @param on_request 刚请求深睡时调用(停电机、灯全灭
* @param on_finalize 50ms 后调用(真正进深睡,不会返回)
* @param on_request 刚请求深睡时调用(停电机;灯效由调用方按关机类型决定
* @param on_finalize 延时到点后调用(真正进深睡,不会返回)
* @return 无
*/
void app_power_set_hooks(AppPowerHook_t on_request, AppPowerHook_t on_finalize)
@@ -151,15 +152,16 @@ uint8_t app_power_low_bat(void)
}
/**
* @brief 请求深睡:立即触发 on_request 钩子,50ms 后触发 on_finalize
* @brief 请求深睡(按键 3s 等):立即触发 on_request 钩子,50ms 后触发 on_finalize
* @return 无
*/
void app_power_request_poweroff(void)
{
if (s_power.req_poweroff) {
return; /* 已经在关机流程里,不要重复挂 50ms 定时器 */
return; /* 已经在关机流程里,不要重复挂定时器 */
}
s_power.req_poweroff = 1;
s_power.empty_off = 0;
log_info("request deep off\n");
if (s_power.on_request) {
s_power.on_request(); /* 立刻停电机、灯全灭 */
@@ -167,6 +169,24 @@ void app_power_request_poweroff(void)
sys_timeout_add(NULL, power_do_poweroff, 50); /* 给 GPIO 落稳一点再真正掉电 */
}
/**
* @brief 空电关机:立刻停电机,等红灯闪完 5 下后再真正掉电
* @return 无
*/
void app_power_request_empty_poweroff(void)
{
if (s_power.req_poweroff) {
return;
}
s_power.req_poweroff = 1;
s_power.empty_off = 1;
log_info("request empty off, wait %ums\n", (unsigned)BOARD_LED_EMPTY_OFF_WAIT_MS);
if (s_power.on_request) {
s_power.on_request(); /* 立刻停电机,调用方挂空电红闪 */
}
sys_timeout_add(NULL, power_do_poweroff, BOARD_LED_EMPTY_OFF_WAIT_MS);
}
/**
* @brief 查询深睡请求是否已发起
* @return 1=已发起,0=未发起
@@ -176,6 +196,15 @@ uint8_t app_power_poweroff_pending(void)
return s_power.req_poweroff;
}
/**
* @brief 当前这次关机是否为空电路径
* @return 1=空电关机,0=普通关机
*/
uint8_t app_power_empty_off_pending(void)
{
return s_power.empty_off;
}
/**
* @brief 50ms 延时到点:真正进入深睡
* @param priv 未使用
@@ -304,31 +333,24 @@ static void power_battery_update_percent(uint16_t mv)
}
/**
* @brief 低电自动关机判定:连续 BAT_EMPTY_WIN 次采样都 <= SHUTDOWN_MV(约 5%)才认定,直接请求深睡
* @note 充电中不判定;用连续窗口而非单次采样,避免电机拉压降瞬间跌破误报
* 对应“电量低于 5% 自动关机”,与开机时的低电拦截(app_boot)用同一门限
* @param mv 本次均值电压,单位 mV
* @brief 空电关机判定:显示电量一到 <= 5% 立刻停电机并请求空电关机
* @note 充电中不判定。百分比已经在 app_battery 做过滤波/缓冲,这里不再做连续次数窗口
* 开机拦截(app_boot)仍用 BOARD_VBAT_SHUTDOWN_MV,因为那时电量算法还没跑
* @return 无
*/
static void power_battery_check_empty(uint16_t mv)
static void power_battery_check_empty(void)
{
AppBatteryAlarm_t *a = &s_power.alarm;
uint8_t pct = s_power.sample.pct;
if (s_power.charge.usb_online || mv > BOARD_VBAT_SHUTDOWN_MV) {
a->empty_win = 0; /* 充电中或电压还够,窗口清零 */
if (s_power.charge.usb_online || pct > BOARD_BATTERY_SHUTDOWN_PCT) {
return;
}
if (a->empty_win < 0xFF) {
a->empty_win++;
}
if (a->empty_win < BAT_EMPTY_WIN) {
return; /* 还没连续够次数,可能是电机拉压降的瞬间 */
}
a->low_bat = 1;
s_power.alarm.low_bat = 1;
gDevData.alm_low_battery = 1;
if (!s_power.req_poweroff) {
log_info("vbat %dmV <=5%%, deep off\n", mv);
app_power_request_poweroff();
log_info("battery %d%% <=%d%%, stop motor + blink red then off\n",
pct, BOARD_BATTERY_SHUTDOWN_PCT);
app_power_request_empty_poweroff();
}
}
@@ -391,10 +413,9 @@ static void power_battery_periodic_log(uint16_t dt)
/**
* @brief 电量采样与低电策略,每个业务 tick 调用一次
* @note 显示用的百分比(滤波/充放电基准/突变防抖/缓冲平滑)交给 app_battery
* 模块处理;这里的 check_empty/check_low 是另一套基于原始电压的
* 低电告警迟滞,直接判 mv 阈值,跟 app_battery 各管一段、互不干扰。
* check_low(3.7V)只负责红灯闪烁提示,不再有超时自动关机
* check_empty(约 5%/3.25V)才是真正触发自动关机的判定。
* 模块处理。check_low 仍按原始电压做 3.7V 红灯提示;check_empty
* 按显示电量 <= BOARD_BATTERY_SHUTDOWN_PCT5%)关机,跟 APP 看到
* 的数字对齐,避免电压先掉、百分比还没到 5% 就关机
* @param dt 距上次调用的毫秒数
* @return 无
*/
@@ -411,7 +432,7 @@ static void power_battery_tick(uint16_t dt)
power_battery_update_percent(mv);
/* 开机保护期内只更新百分比,不判定任何告警 */
if (s->boot_ms >= BAT_BOOT_MS) {
power_battery_check_empty(mv);
power_battery_check_empty();
power_battery_check_low(mv);
}
}