优化程序架构

This commit is contained in:
2026-08-25 14:59:39 +08:00
parent d5ec6ecde0
commit 5252471aca
25 changed files with 2964 additions and 1608 deletions
+453
View File
@@ -0,0 +1,453 @@
/******************************************************************************
* @file app_power.c
* @brief USB / 充电检测、电量采样与低电策略、深睡请求生命周期实现
* @author cyWu <1917507415@qq.com>
* @date 2026.08.25
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.25, cyWu, 从 usr_jb_main 拆出
******************************************************************************/
#include "app_power.h"
#include "app_time_util.h"
#include "bsp_hw.h"
#include "board_pin.h"
#include "jb_product.h"
#include "app_main.h"
#include "usr_le_api.h"
#include "system/includes.h"
#include "ui_manage.h"
#include <string.h>
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_PWR]"
#define LOG_INFO_ENABLE
#include "debug.h"
#define BAT_SAMPLE_MS 100
#define BAT_AVG_N 8
#define BAT_BOOT_MS 3000 /* 上电 3s 内不因低电深睡 */
#define BAT_LOG_MS 10000
#define BAT_LOW_WIN 3 /* 连续约 2.4s 才进低电告警 */
#define BAT_EMPTY_WIN 5 /* 连续约 4s 才 3.2V 深睡 */
/** 充电检测:CHG/VPWR 消抖后的结果 */
typedef struct {
uint8_t usb_online;
uint8_t charge_led; /**< 0=无 1=充电红 2=充满绿 */
uint8_t charge_led_last; /**< 仅用于变化时打日志 */
uint16_t full_ms; /**< 拔电瞬间 CHG 先变高,需等满 1s 才当充满 */
} AppChargeState_t;
/** 电量采样:滑动平均 + 百分比 */
typedef struct {
uint16_t tick_ms;
uint16_t acc_mv;
uint8_t n;
uint16_t mv;
uint8_t pct;
uint16_t boot_ms;
uint16_t log_ms;
} AppBatterySample_t;
/** 低电告警:进入/退出都要连续窗口消抖,避免电机拉压降误报 */
typedef struct {
uint8_t low_bat;
uint32_t low_ms; /**< 低电已持续时长,满 1 分钟深睡 */
uint8_t empty_win;
uint8_t enter_win;
uint8_t exit_win;
} AppBatteryAlarm_t;
/** 电源模块总状态,替代原来一堆散落的 static 变量 */
typedef struct {
AppChargeState_t charge;
AppBatterySample_t sample;
AppBatteryAlarm_t alarm;
uint8_t req_poweroff;
AppPowerHook_t on_request;
AppPowerHook_t on_finalize;
} AppPowerCtx_t;
static AppPowerCtx_t s_power;
static void power_do_poweroff(void *priv);
static AppPowerEvt_t power_charge_tick(uint16_t dt);
static void power_battery_tick(uint16_t dt);
static uint8_t power_bat_percent(uint16_t mv);
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_low(uint16_t mv);
static void power_battery_check_low_timeout(uint16_t dt);
static void power_battery_periodic_log(uint16_t dt);
/**
* @brief 初始化电源模块状态
* @return 无
*/
void app_power_init(void)
{
memset(&s_power, 0, sizeof(s_power));
s_power.charge.charge_led_last = 0xff;
}
/**
* @brief 注册深睡请求的两段式回调
* @param on_request 刚请求深睡时调用(停电机、灯全灭)
* @param on_finalize 50ms 后调用(真正进深睡,不会返回)
* @return 无
*/
void app_power_set_hooks(AppPowerHook_t on_request, AppPowerHook_t on_finalize)
{
s_power.on_request = on_request;
s_power.on_finalize = on_finalize;
}
/**
* @brief 充电检测 + 电量采样,每个业务 tick 调用一次
* @param dt 距上次调用的毫秒数
* @return USB 插拔边沿事件,多数时候是 APP_POWER_EVT_NONE
*/
AppPowerEvt_t app_power_tick_1ms(uint16_t dt)
{
AppPowerEvt_t evt = power_charge_tick(dt);
power_battery_tick(dt);
return evt;
}
/**
* @brief 查询 USB 是否在位(含正在充电)
* @return 1=在位,0=未接
*/
uint8_t app_power_usb_online(void)
{
return s_power.charge.usb_online;
}
/**
* @brief 查询充电灯状态
* @return 0=无 1=充电中红灯 2=已充满绿灯
*/
uint8_t app_power_charge_led(void)
{
return s_power.charge.charge_led;
}
/**
* @brief 查询是否处于低电告警
* @return 1=低电,0=正常
*/
uint8_t app_power_low_bat(void)
{
return s_power.alarm.low_bat;
}
/**
* @brief 请求深睡:立即触发 on_request 钩子,50ms 后触发 on_finalize
* @return 无
*/
void app_power_request_poweroff(void)
{
if (s_power.req_poweroff) {
return;
}
s_power.req_poweroff = 1;
log_info("request deep off\n");
if (s_power.on_request) {
s_power.on_request();
}
sys_timeout_add(NULL, power_do_poweroff, 50);
}
/**
* @brief 查询深睡请求是否已发起
* @return 1=已发起,0=未发起
*/
uint8_t app_power_poweroff_pending(void)
{
return s_power.req_poweroff;
}
/**
* @brief 50ms 延时到点:真正进入深睡
* @param priv 未使用
* @return 无
*/
static void power_do_poweroff(void *priv)
{
(void)priv;
log_info("enter deep off\n");
if (s_power.on_finalize) {
s_power.on_finalize();
}
}
/**
* @brief 充电检测:CHG 低=充电中;VPWR 在且 CHG 高满 1s=充满;拔电报 USB_OUT
* @param dt 距上次调用的毫秒数
* @return USB 插拔边沿事件
*/
static AppPowerEvt_t power_charge_tick(uint16_t dt)
{
AppChargeState_t *c = &s_power.charge;
uint8_t chg_low = bsp_chg_is_low();
uint8_t vpwr = bsp_vpwr_is_online();
uint8_t usb;
AppPowerEvt_t evt = APP_POWER_EVT_NONE;
if (chg_low) {
c->full_ms = 0;
c->charge_led = 1;
gDevData.charge = CHARGE_1;
usb = 1;
} else if (vpwr) {
/* 拔电时 CHG 先变高、VPWR 还没掉,不能立刻当充满 */
c->full_ms = app_add_ms(c->full_ms, dt);
if (c->full_ms >= BOARD_CHG_FULL_MS) {
c->charge_led = 2;
}
gDevData.charge = (c->charge_led == 1) ? CHARGE_1 : CHARGE_2;
usb = 1;
} else {
c->full_ms = 0;
c->charge_led = 0;
gDevData.charge = CHARGE_2;
usb = 0;
}
if (usb != c->usb_online) {
c->usb_online = usb;
log_info("usb %s vpwr=%d chg=%d mv=%d pair=%d goto=%d\n",
usb ? "in" : "out", vpwr, chg_low, bsp_vpwr_mv(),
usr_get_pair_flag(), usr_var.usr_goto_pair);
usr_var.usr_power_charge_flag = usb ? 1 : 2;
if (usb) {
evt = APP_POWER_EVT_USB_IN;
} else {
ui_update_status(STATUS_NORMAL_POWER);
evt = APP_POWER_EVT_USB_OUT;
}
}
if (c->charge_led != c->charge_led_last) {
c->charge_led_last = c->charge_led;
log_info("charge_led=%d chg=%d vpwr=%d mv=%d\n",
c->charge_led, chg_low, vpwr, bsp_vpwr_mv());
}
return evt;
}
/**
* @brief 电压换算电量:4.2V=100%3.2V=0%
* @param mv 电池电压,单位 mV
* @return 电量百分比,范围 0~100
*/
static uint8_t power_bat_percent(uint16_t mv)
{
int32_t span = (int32_t)BOARD_VBAT_FULL_MV - (int32_t)BOARD_VBAT_EMPTY_MV;
int32_t pct;
if (mv <= BOARD_VBAT_EMPTY_MV) {
return 0;
}
if (mv >= BOARD_VBAT_FULL_MV) {
return 100;
}
pct = ((int32_t)mv - BOARD_VBAT_EMPTY_MV) * 100 / span;
if (pct < 0) {
pct = 0;
}
if (pct > 100) {
pct = 100;
}
return (uint8_t)pct;
}
/**
* @brief 滑动平均采样:每 BAT_SAMPLE_MS 采一次电压,凑够 BAT_AVG_N 个才出新均值
* @note 靠采样点数(而非时间)判满,dt 波动也不会多算/少算样本
* @param dt 距上次调用的毫秒数
* @param out_mv 新均值输出,仅在返回 1 时有效
* @return 1=本次凑出新均值,0=还在累积
*/
static uint8_t power_battery_sample_ready(uint16_t dt, uint16_t *out_mv)
{
AppBatterySample_t *s = &s_power.sample;
s->tick_ms = app_add_ms(s->tick_ms, dt);
if (s->tick_ms < BAT_SAMPLE_MS) {
return 0;
}
s->tick_ms = 0;
s->acc_mv += bsp_vbat_mv();
s->n++;
if (s->n < BAT_AVG_N) {
return 0;
}
*out_mv = (uint16_t)(s->acc_mv / s->n);
s->acc_mv = 0;
s->n = 0;
s->mv = *out_mv;
return 1;
}
/**
* @brief 按新均值刷新电量百分比,仅在变化时才写协议层数据
* @param mv 本次均值电压,单位 mV
* @return 无
*/
static void power_battery_update_percent(uint16_t mv)
{
uint8_t pct = power_bat_percent(mv);
if (pct != s_power.sample.pct) {
s_power.sample.pct = pct;
gDevData.battery = pct;
}
}
/**
* @brief 电量耗尽判定:连续 BAT_EMPTY_WIN 次采样都 <= EMPTY_MV 才认定,直接请求深睡
* @note 充电中不判定;用连续窗口而非单次采样,避免电机拉压降瞬间跌破误报
* @param mv 本次均值电压,单位 mV
* @return 无
*/
static void power_battery_check_empty(uint16_t mv)
{
AppBatteryAlarm_t *a = &s_power.alarm;
if (s_power.charge.usb_online || mv > BOARD_VBAT_EMPTY_MV) {
a->empty_win = 0;
return;
}
if (a->empty_win < 0xFF) {
a->empty_win++;
}
if (a->empty_win < BAT_EMPTY_WIN) {
return;
}
a->low_bat = 1;
gDevData.alm_low_battery = 1;
if (!s_power.req_poweroff) {
log_info("vbat %dmV empty, deep off\n", mv);
app_power_request_poweroff();
}
}
/**
* @brief 低电迟滞判定:进入/退出各自要求连续 BAT_LOW_WIN 次采样,避免电压在门限附近抖动
* @param mv 本次均值电压,单位 mV
* @return 无
*/
static void power_battery_check_low(uint16_t mv)
{
AppBatteryAlarm_t *a = &s_power.alarm;
if (mv < BOARD_VBAT_LOW_MV) {
a->exit_win = 0;
if (a->low_bat || a->enter_win >= 0xFF) {
return;
}
a->enter_win++;
if (a->enter_win >= BAT_LOW_WIN) {
a->low_bat = 1;
a->low_ms = 0;
gDevData.alm_low_battery = 1;
log_info("low battery %dmV %d%%\n", mv, s_power.sample.pct);
}
} else if (mv > (BOARD_VBAT_LOW_MV + BOARD_VBAT_LOW_HYST_MV)) {
a->enter_win = 0;
if (!a->low_bat) {
return;
}
if (a->exit_win < 0xFF) {
a->exit_win++;
}
if (a->exit_win >= BAT_LOW_WIN) {
a->low_bat = 0;
a->low_ms = 0;
gDevData.alm_low_battery = 0;
log_info("battery recover %dmV %d%%\n", mv, s_power.sample.pct);
}
} else {
/* 低电门限与迟滞上限之间的死区,两个窗口都不动 */
a->enter_win = 0;
a->exit_win = 0;
}
}
/**
* @brief 低电持续满 BOARD_LOWBAT_SLEEP_MS1 分钟)才请求深睡
* @note 充电中 / 已请求深睡 / 仍在开机保护期内,计时清零不累加
* @param dt 距上次调用的毫秒数
* @return 无
*/
static void power_battery_check_low_timeout(uint16_t dt)
{
AppBatteryAlarm_t *a = &s_power.alarm;
if (s_power.charge.usb_online || s_power.req_poweroff
|| (s_power.sample.boot_ms < BAT_BOOT_MS)) {
a->low_ms = 0;
return;
}
if (!a->low_bat || a->low_ms >= BOARD_LOWBAT_SLEEP_MS) {
return;
}
a->low_ms += dt;
if (a->low_ms >= BOARD_LOWBAT_SLEEP_MS) {
a->low_ms = BOARD_LOWBAT_SLEEP_MS;
log_info("low battery 1min, deep off\n");
app_power_request_poweroff();
}
}
/**
* @brief 每 BAT_LOG_MS 打印一次电量状态,方便离线看日志排查
* @param dt 距上次调用的毫秒数
* @return 无
*/
static void power_battery_periodic_log(uint16_t dt)
{
AppBatterySample_t *s = &s_power.sample;
s->log_ms = app_add_ms(s->log_ms, dt);
if (s->log_ms >= BAT_LOG_MS) {
s->log_ms = 0;
log_info("bat %d%% %dmV alm=%d\n", s->pct, s->mv, s_power.alarm.low_bat);
}
}
/**
* @brief 电量采样与低电策略,每个业务 tick 调用一次
* @note 电机运转时只记电压、不改百分比/告警,避免电流拉垮 VBAT 误报。
* 电机是否忙由调用方(app_function)决定,本模块不认识电机,
* 这里用「外部传入」而非直接查询,保持单向依赖。
* @param dt 距上次调用的毫秒数
* @return 无
*/
static void power_battery_tick(uint16_t dt)
{
AppBatterySample_t *s = &s_power.sample;
uint16_t mv;
if (s->boot_ms < BAT_BOOT_MS) {
s->boot_ms = app_add_ms(s->boot_ms, dt);
}
if (power_battery_sample_ready(dt, &mv)) {
power_battery_update_percent(mv);
/* 开机保护期内只更新百分比,不判定任何告警 */
if (s->boot_ms >= BAT_BOOT_MS) {
power_battery_check_empty(mv);
power_battery_check_low(mv);
}
}
power_battery_check_low_timeout(dt);
power_battery_periodic_log(dt);
}