From 215e1be6bb2b0c32f2d9e1195e9e429d064fdb22 Mon Sep 17 00:00:00 2001 From: Wuchuyuan <1917507415@qq.com> Date: Thu, 27 Aug 2026 11:40:57 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E8=BF=90=E8=A1=8C=E4=B8=AD=E7=A9=BA?= =?UTF-8?q?=E7=94=B5=E5=85=B3=E6=9C=BA=E6=94=B9=E7=9C=8B=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E7=99=BE=E5=88=86=E6=AF=94=EF=BC=8C=E5=88=B0=205%=20=E7=AB=8B?= =?UTF-8?q?=E5=88=BB=E5=81=9C=E7=94=B5=E6=9C=BA=E3=80=81=E5=85=B3=E7=BB=BF?= =?UTF-8?q?=E7=81=AF=EF=BC=8C=E7=BA=A2=E7=81=AF=E9=97=AA=205=20=E4=B8=8B?= =?UTF-8?q?=E5=90=8E=E5=86=8D=E6=B7=B1=E7=9D=A1=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E7=94=B5=E5=8E=8B=E5=85=88=E6=8E=89=E3=80=81APP=20=E8=BF=98?= =?UTF-8?q?=E6=B2=A1=E5=88=B0=205%=20=E5=B0=B1=E5=85=B3=E6=9C=BA=202?= =?UTF-8?q?=E3=80=81=E5=85=85=E7=94=B5/=E6=94=BE=E7=94=B5=E7=94=B5?= =?UTF-8?q?=E9=87=8F=E7=BC=93=E5=86=B2=E5=8A=A0=E5=BF=AB=EF=BC=8C=E6=94=BE?= =?UTF-8?q?=E7=94=B5=E7=AA=81=E5=8F=98=E6=8B=A6=E6=88=AA=E5=85=88=E5=85=B3?= =?UTF-8?q?=E6=8E=89=EF=BC=8C=E7=94=B5=E9=87=8F=E6=9B=B2=E7=BA=BF=E6=BB=A1?= =?UTF-8?q?=E7=94=B5=E7=82=B9=E6=94=B6=E5=88=B0=204.12V=203=E3=80=81?= =?UTF-8?q?=E4=B8=8A=E7=94=B5=E4=B8=B2=E5=8F=A3=E6=89=93=E5=8D=B0=E8=BD=AF?= =?UTF-8?q?=E7=A1=AC=E4=BB=B6=E7=89=88=E6=9C=AC=E5=92=8C=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/usr_app/app_battery.c | 12 ++--- apps/usr_app/app_battery.h | 6 +-- apps/usr_app/app_led.c | 39 +++++++++++++-- apps/usr_app/app_led.h | 13 +++++ apps/usr_app/app_power.c | 87 ++++++++++++++++++++------------- apps/usr_app/app_power.h | 27 +++++++--- apps/usr_app/led.h | 3 +- apps/usr_app/led_manager.c | 27 ++++++++++ apps/usr_jb_proto/usr_jb_main.c | 28 +++++++++-- apps/usr_periph/board_pin.h | 31 +++++++----- 10 files changed, 205 insertions(+), 68 deletions(-) diff --git a/apps/usr_app/app_battery.c b/apps/usr_app/app_battery.c index 8e70680..50fb0c1 100644 --- a/apps/usr_app/app_battery.c +++ b/apps/usr_app/app_battery.c @@ -21,8 +21,8 @@ /*========================= 时间/阈值配置 =========================*/ #define BAT_SWITCH_WAIT_CHARGE_MS 60000u /**< 切到充电后等 60s 才记基准 */ #define BAT_SWITCH_WAIT_DISCHARGE_MS 60000u /**< 切到放电后等 60s 才记基准 */ -#define BAT_CHARGE_BUFFER_MS 60000u /**< 充电缓冲:1 分钟走 1% */ -#define BAT_DISCHARGE_BUFFER_MS 20000u /**< 放电缓冲:20 秒走 1% */ +#define BAT_CHARGE_BUFFER_MS 40000u /**< 充电缓冲:40 秒走 1% */ +#define BAT_DISCHARGE_BUFFER_MS 10000u /**< 放电缓冲:10 秒走 1% */ #define BAT_DISCHARGE_FILTER_W 90 /**< 放电指数滤波权重(/100),压掉电机拉载噪声 */ #define BAT_SUDDEN_DROP_PCT 1 /**< 突变阈值:单次超过 1% 判定为突变 */ #define BAT_DROP_RECOVERY_MS 60000u /**< 突变超过此时长强制接受新电量 */ @@ -213,10 +213,10 @@ void app_battery_tick(uint16_t dt, uint16_t mv, uint8_t charging) } target = (uint8_t)new_percent; - /* 放电时单次跳变过大先当噪声,稳住后再更新 */ - if (!b->charging && battery_check_sudden_drop(dt, target)) { - return; - } + // /* 放电时单次跳变过大先当噪声,稳住后再更新 */ + // if (!b->charging && battery_check_sudden_drop(dt, target)) { + // return; + // } if (target != b->target_percent) { b->target_percent = target; diff --git a/apps/usr_app/app_battery.h b/apps/usr_app/app_battery.h index 6fb48f7..81a3868 100644 --- a/apps/usr_app/app_battery.h +++ b/apps/usr_app/app_battery.h @@ -10,9 +10,9 @@ * * @note 本模块只认识"电压(mV) + 是否在充电",不碰 ADC/GPIO,电压采样、 * USB 在位判定由调用方(app_power)负责,保持单向依赖。 - * 低电告警迟滞(app_power.c 里的 check_low/check_empty)不受本模块 - * 影响,两者各管一段:本模块只负责"电量数字怎么变得好看",低电告警/ - * 深睡门限仍按原始电压判断。 + * 低电告警迟滞(app_power.c 里的 check_low)不受本模块影响: + * 本模块负责显示电量怎么变得好看;低电红灯仍按原始电压判断, + * 5% 自动关机则读本模块给出的显示百分比。 ******************************************************************************/ #ifndef __APP_BATTERY_H__ diff --git a/apps/usr_app/app_led.c b/apps/usr_app/app_led.c index c9e5517..353f23d 100644 --- a/apps/usr_app/app_led.c +++ b/apps/usr_app/app_led.c @@ -11,6 +11,7 @@ * - V2.1.0, 2026.08.25, cyWu, 自己从 app_power/app_pair 取数并内部组装 * - V2.2.0, 2026.08.25, cyWu, 重启/清场逻辑各提一个小函数,把意图写进注释 * - V2.3.0, 2026.08.27, cyWu, 新增 OTA 红绿交替灯效 + * - V2.4.0, 2026.08.27, cyWu, 新增空电关机红闪 5 下灯效 ******************************************************************************/ #include "app_led.h" @@ -21,7 +22,8 @@ #include "app_pair.h" static uint8_t s_initialized; -static uint8_t s_ota; /* 1=OTA 灯效中,app_led_run 不再叠其它灯 */ +static uint8_t s_ota; /* 1=OTA 灯效中,app_led_run 不再叠其它灯 */ +static uint8_t s_empty_off; /* 1=空电关机红闪中,只跑红灯 5 下 */ static void led_start_pair_result(uint8_t ok); @@ -89,7 +91,7 @@ AppLed_Ret_t app_led_get_status(void) */ void app_led_request_mode_blink(void) { - if (!s_initialized || s_ota) { + if (!s_initialized || s_ota || s_empty_off) { return; } led_restart(LED_EVENT_MODE); @@ -101,7 +103,7 @@ void app_led_request_mode_blink(void) */ void app_led_hint_power_on(void) { - if (!s_initialized || s_ota) { + if (!s_initialized || s_ota || s_empty_off) { return; } led_restart(LED_EVENT_STANDBY); @@ -131,6 +133,30 @@ uint8_t app_led_is_ota(void) return s_ota; } +/** + * @brief 空电关机:清掉其它灯(含绿灯),只挂红灯闪 5 下 + * @return 无 + */ +void app_led_request_empty_off(void) +{ + if (!s_initialized || s_ota) { + return; /* OTA 灯效优先,不打断升级提示 */ + } + s_empty_off = 1; + LED_EventAllDelete(); + bsp_led_all_off(); /* 先关绿灯 */ + LED_EventAdd(LED_EVENT_EMPTY); +} + +/** + * @brief 查询是否处于空电关机灯效 + * @return 1=空电红闪中,0=否 + */ +uint8_t app_led_is_empty_off(void) +{ + return s_empty_off; +} + /** * @brief 播放一次性配对结果灯 * @param ok 1=成功常绿,0=失败红绿交替 @@ -186,6 +212,13 @@ void app_led_run(uint16_t dt, uint8_t app_power) return; } + /* 空电关机:绿灯已关,只跑红灯闪 5 下;事件在 request 时已挂上,这里不要再 Add, + * 否则闪完被删后又会重新开始闪。 */ + if (s_empty_off) { + user_led_handle(); + return; + } + /* 配对成功/失败是边沿事件,只在那一拍切灯效 */ pair_evt = app_pair_tick_1ms(dt); if (pair_evt == APP_PAIR_EVT_OK) { diff --git a/apps/usr_app/app_led.h b/apps/usr_app/app_led.h index a047877..5d7bb99 100644 --- a/apps/usr_app/app_led.h +++ b/apps/usr_app/app_led.h @@ -11,6 +11,7 @@ * - V2.1.0, 2026.08.25, cyWu, 改为自己从 app_power/app_pair 取数, * 调用方不用再手填 AppLedCtx_t * - V2.3.0, 2026.08.27, cyWu, 新增 OTA 红绿交替灯效 + * - V2.4.0, 2026.08.27, cyWu, 新增空电关机红灯闪 5 下 ******************************************************************************/ #ifndef __APP_LED_H__ @@ -62,6 +63,18 @@ void app_led_request_ota(void); */ uint8_t app_led_is_ota(void); +/** + * @brief 空电关机灯效:关掉绿灯,只闪红灯 5 下 + * @return 无 + */ +void app_led_request_empty_off(void); + +/** + * @brief 查询是否处于空电关机灯效 + * @return 1=空电红闪中,0=否 + */ +uint8_t app_led_is_empty_off(void); + /** * @brief 清空全部灯事件并灭灯(关机前调用) * @return 无 diff --git a/apps/usr_app/app_power.c b/apps/usr_app/app_power.c index 98f9894..94768f5 100644 --- a/apps/usr_app/app_power.c +++ b/apps/usr_app/app_power.c @@ -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_PCT(5%)时立刻触发 */ 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_PCT(5%)关机,跟 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); } } diff --git a/apps/usr_app/app_power.h b/apps/usr_app/app_power.h index bd3772b..8d59323 100644 --- a/apps/usr_app/app_power.h +++ b/apps/usr_app/app_power.h @@ -2,10 +2,11 @@ * @file app_power.h * @brief USB / 充电检测、电量采样与低电策略、深睡请求生命周期 * @author cyWu <1917507415@qq.com> - * @date 2026.08.25 - * @version V1.0.0 + * @date 2026.08.27 + * @version V1.2.0 * @history * - V1.0.0, 2026.08.25, cyWu, 首次发布,实现 USB/充电检测、电量采样与深睡请求 + * - V1.2.0, 2026.08.27, cyWu, 显示电量到 5% 立即走空电关机(停电机、红灯闪 5 下再掉电) * * @note 本模块只管“电从哪来、还剩多少、要不要关机”,不认识电机/灯/按键。 * 需要在 USB 插拔 / 深睡时联动其它模块的地方,一律走 app_power_set_hooks @@ -24,7 +25,7 @@ typedef enum { APP_POWER_EVT_USB_OUT } AppPowerEvt_t; -/** 深睡请求的钩子:on_request 用于立即停机(马达/灯),on_finalize 用于真正掉电 */ +/** 深睡请求的钩子:on_request 立刻停电机并按关机类型处理灯;on_finalize 真正掉电 */ typedef void (*AppPowerHook_t)(void); /** @@ -35,8 +36,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); @@ -67,15 +68,29 @@ uint8_t app_power_charge_led(void); 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); +/** + * @brief 空电关机:立刻停电机,等红灯闪完 5 下后再真正掉电 + * @note 调用方在 on_request 里根据 app_power_empty_off_pending() 挂空电灯效, + * 不要立刻灭灯。延时为 BOARD_LED_EMPTY_OFF_WAIT_MS。 + * @return 无 + */ +void app_power_request_empty_poweroff(void); + /** * @brief 查询深睡请求是否已发起(发起后其它模块应停止响应新操作) * @return 1=已发起,0=未发起 */ uint8_t app_power_poweroff_pending(void); +/** + * @brief 当前这次关机是否为空电路径(红灯闪 5 下再掉电) + * @return 1=空电关机,0=普通关机 + */ +uint8_t app_power_empty_off_pending(void); + #endif /* __APP_POWER_H__ */ diff --git a/apps/usr_app/led.h b/apps/usr_app/led.h index 5473db6..86c1d09 100644 --- a/apps/usr_app/led.h +++ b/apps/usr_app/led.h @@ -52,6 +52,7 @@ typedef enum { LED_EVENT_PAIR_OK, /**< 配对成功常绿若干秒 */ LED_EVENT_PAIR_FAIL, /**< 配对失败红绿交替 */ LED_EVENT_OTA, /**< OTA:红绿交替闪,直到升级结束/复位 */ + LED_EVENT_EMPTY, /**< 电量 5% 关机:关绿灯,红灯闪 5 下 */ LED_EVENT_MAX } LED_Event_t; @@ -62,7 +63,7 @@ typedef enum { PRIORITY_1 = 0, /**< 待机绿灯 */ PRIORITY_2, /**< 低电红闪 */ PRIORITY_3, /**< 充电红 / 充满绿 */ - PRIORITY_4, /**< 配对 / 切模式 / 配对结果 / OTA */ + PRIORITY_4, /**< 配对 / 切模式 / 配对结果 / OTA / 5% 关机红闪 */ PRIORITY_MAX } LED_EventPriority_t; diff --git a/apps/usr_app/led_manager.c b/apps/usr_app/led_manager.c index 49b007c..cc3c44c 100644 --- a/apps/usr_app/led_manager.c +++ b/apps/usr_app/led_manager.c @@ -20,6 +20,7 @@ static uint8_t led_mode_handler(LED_Config_t *led_config); static uint8_t led_pair_ok_handler(LED_Config_t *led_config); static uint8_t led_pair_fail_handler(LED_Config_t *led_config); static uint8_t led_ota_handler(LED_Config_t *led_config); +static uint8_t led_empty_handler(LED_Config_t *led_config); /** * @brief 只改掩码里的通道,其它灯保持原样 @@ -124,6 +125,20 @@ static uint8_t led_ota_handler(LED_Config_t *led_config) return 1; } +/** + * @brief 空电关机红闪:拦住其它灯,绿灯已关,只剩红灯闪 5 下 + * @param led_config 配置 + * @return 1=还在闪(拦住更低优先级),0=闪完已删事件 + */ +static uint8_t led_empty_handler(LED_Config_t *led_config) +{ + if (LED_Update(led_config) == 0) { + LED_EventDelete(LED_EVENT_EMPTY); /* 闪完 5 下后删,灯保持灭直到真正掉电 */ + return 0; + } + return 1; +} + /** * @brief 注册回调并装载 CBC2 事件表(GPIO 已在 bsp_hw_init) * @return 无 @@ -232,6 +247,18 @@ void user_led_init(void) .config.runNum = 0, /* 0=无限 */ .ledEventHandler = led_ota_handler, }, + [LED_EVENT_EMPTY] = { + /* 电量 5%:关绿灯后只闪红灯 5 下,然后掉电 */ + .event = LED_EVENT_EMPTY, + .priority = PRIORITY_4, + .config.ledMask = LED_CHANNEL(LED_CH_RED), + .config.state = LED_BLINKING, + .config.blinkParams.orDer = 0, /* 先亮后灭 */ + .config.blinkParams.onTime = BOARD_LED_EMPTY_ON_MS, + .config.blinkParams.offTime = BOARD_LED_EMPTY_OFF_MS, + .config.runNum = BOARD_LED_EMPTY_BLINK_N, + .ledEventHandler = led_empty_handler, + }, }; LED_SetStateInit(set_led_multi); diff --git a/apps/usr_jb_proto/usr_jb_main.c b/apps/usr_jb_proto/usr_jb_main.c index 44c96bf..f1eb888 100644 --- a/apps/usr_jb_proto/usr_jb_main.c +++ b/apps/usr_jb_proto/usr_jb_main.c @@ -94,6 +94,7 @@ static UsrSchedulerCtx_t s_sched; static void usr_timer_tick(void *priv); static void usr_timer_rearm(UsrTickMode_t mode); +static void usr_log_boot_version(void); /** * @brief USB 插拔的联动:插入即停电机,拔出即软关并刷新系统电源图标 @@ -116,13 +117,18 @@ static void usr_on_power_evt(AppPowerEvt_t evt) } /** - * @brief 深睡请求钩子:立即停电机、灯全灭(真正掉电在 50ms 后) + * @brief 深睡请求钩子:立刻停电机;空电路径挂红闪,按键关机则灯全灭 * @return 无 */ static void usr_poweroff_request_hook(void) { app_function_stop_all(); - app_led_clear(); + if (app_power_empty_off_pending()) { + /* 电量到 5%:关绿灯,红灯闪 5 下,真正掉电等闪完 */ + app_led_request_empty_off(); + } else { + app_led_clear(); + } } /** @@ -220,8 +226,8 @@ static void usr_timer_tick(void *priv) /* 重新查询而非复用上面的判断:user_key_handle() 可能在本拍内刚请求关机, * 要用最新状态决定这一拍还要不要播灯效,否则会多播一帧才熄灭。 - * OTA 期间灯效优先:关机钩子也不要把红绿交替清掉。 */ - if (app_led_is_ota()) { + * OTA / 空电红闪期间灯效优先:关机钩子不要把提示灯清掉。 */ + if (app_led_is_ota() || app_led_is_empty_off()) { app_led_run(dt, app_function_is_power_on()); } else if (app_power_poweroff_pending()) { app_led_clear(); @@ -235,6 +241,18 @@ static void usr_timer_tick(void *priv) } } +/** + * @brief 上电打印固件版本与编译时间,方便串口对应当前是哪一包 + * @return 无 + */ +static void usr_log_boot_version(void) +{ + log_info("sw V%d.%d.%d hw V%d.%d.%d build %s %s\n", + JB_MCU_SW_VERSION_MAJOR, JB_MCU_SW_VERSION_MINOR, JB_MCU_SW_VERSION_PATCH, + JB_MCU_HW_VERSION_MAJOR, JB_MCU_HW_VERSION_MINOR, JB_MCU_HW_VERSION_PATCH, + __DATE__, __TIME__); +} + /** * @brief 业务初始化(在 BLE 栈起来之前由 app_main 调用) * @note 内部会等按键 3s/5s;不足 3s 直接深睡,不会启动业务定时器。 @@ -245,6 +263,8 @@ static void usr_timer_tick(void *priv) */ void usr_jb_init(void) { + usr_log_boot_version(); /* UART 已在 app_main 起来,按键判定前就能看到版本 */ + bsp_hw_init(); app_boot_wait_key(); /* 深睡分支不会返回;返回即代表确定要开机 */ diff --git a/apps/usr_periph/board_pin.h b/apps/usr_periph/board_pin.h index e87e708..9ebb3b2 100644 --- a/apps/usr_periph/board_pin.h +++ b/apps/usr_periph/board_pin.h @@ -2,8 +2,8 @@ * @file board_pin.h * @brief CBC2 门夹弹力绳板级引脚、电平与时序(换板只改本文件) * @author cyWu <1917507415@qq.com> - * @date 2026.08.26 - * @version V1.4.0 + * @date 2026.08.27 + * @version V1.5.0 * @history * - V1.0.0, 2026.08.21, cyWu, 首次发布 * - V1.1.0, 2026.08.24, cyWu, 收口:去掉实验开关与未贴件限位 @@ -12,6 +12,7 @@ * - V1.4.0, 2026.08.26, cyWu, 新增 5% 自动关机门限、开机低电闪灯拦截、 * 12s 长按 OTA、自动模式单次最长运行时长; * 取消低电闪烁 1 分钟后自动关机门限 + * - V1.5.0, 2026.08.27, cyWu, 运行中空电关机红闪 5 下时序宏 ******************************************************************************/ #ifndef __BOARD_PIN_H__ @@ -54,16 +55,17 @@ /* 电量 / 充电 */ /*============================================================================*/ -#define BOARD_VBAT_FULL_MV 4200 /**< 100% */ -#define BOARD_VBAT_EMPTY_MV 3200 /**< 0%,电量曲线下限(仅用于百分比换算) */ -#define BOARD_VBAT_CHARGE_FULL_MV 4250 /**< 充电基准未记录时 100%(充电内阻压降,比放电阈值高)*/ -#define BOARD_VBAT_CHARGE_EMPTY_MV 3350 /**< 充电基准未记录时 0% */ -#define BOARD_VBAT_LOW_MV 3700 /**< 低电预警阈值:只闪红灯提示,不关机 */ -#define BOARD_VBAT_LOW_HYST_MV 50 /**< 低电恢复回差 */ -#define BOARD_VBAT_SHUTDOWN_MV 3250 /**< 约 5%,运行中低于此值自动关机;关机时低于此值拒绝开机 */ -#define BOARD_VPWR_ONLINE_MV 4000 /**< VPWR 判定 USB 插入 */ -#define BOARD_VPWR_OFF_MV 1500 /**< 低于此值认定已拔出 */ -#define BOARD_CHG_FULL_MS 1000 /**< CHG 已高且 VPWR 在,满 1s 认充满 */ +#define BOARD_VBAT_FULL_MV 4120 /**< 100% */ +#define BOARD_VBAT_EMPTY_MV 3200 /**< 0%,电量曲线下限(仅用于百分比换算) */ +#define BOARD_VBAT_CHARGE_FULL_MV 4200 /**< 充电基准未记录时 100%(充电内阻压降,比放电阈值高)*/ +#define BOARD_VBAT_CHARGE_EMPTY_MV 3400 /**< 充电基准未记录时 0% */ +#define BOARD_VBAT_LOW_MV 3700 /**< 低电预警阈值:只闪红灯提示,不关机 */ +#define BOARD_VBAT_LOW_HYST_MV 50 /**< 低电恢复回差 */ +#define BOARD_VBAT_SHUTDOWN_MV 3250 /**< 约 5%,仅开机拦截用:关机状态下 app_battery 尚未运行,只能摸裸电压 */ +#define BOARD_BATTERY_SHUTDOWN_PCT 5 /**< 运行中显示电量 <= 此值才自动关机,跟 APP 看到的百分比对齐 */ +#define BOARD_VPWR_ONLINE_MV 4000 /**< VPWR 判定 USB 插入 */ +#define BOARD_VPWR_OFF_MV 1500 /**< 低于此值认定已拔出 */ +#define BOARD_CHG_FULL_MS 1000 /**< CHG 已高且 VPWR 在,满 1s 认充满 */ /*============================================================================*/ /* 按键 / 灯效 */ @@ -79,6 +81,11 @@ #define BOARD_BOOT_LOWBAT_BLINK_N 5 /**< 关机时电量过低:拒绝开机前红灯闪烁次数 */ #define BOARD_BOOT_LOWBAT_ON_MS 200 /**< 拒绝开机提示:闪烁亮时间 */ #define BOARD_BOOT_LOWBAT_OFF_MS 200 /**< 拒绝开机提示:闪烁灭时间 */ +#define BOARD_LED_EMPTY_BLINK_N 5 /**< 运行中电量到 5%:红灯闪烁次数 */ +#define BOARD_LED_EMPTY_ON_MS 200 /**< 5% 关机提示:闪烁亮时间 */ +#define BOARD_LED_EMPTY_OFF_MS 200 /**< 5% 关机提示:闪烁灭时间 */ +#define BOARD_LED_EMPTY_OFF_WAIT_MS ((BOARD_LED_EMPTY_ON_MS + BOARD_LED_EMPTY_OFF_MS) \ + * BOARD_LED_EMPTY_BLINK_N + 50u) /**< 闪完再掉电,多留 50ms 让 GPIO 落稳 */ #define BOARD_LED_FAST_ON_MS 100 /**< 配对绿快闪:亮 */ #define BOARD_LED_FAST_OFF_MS 100 /**< 配对绿快闪:灭 */