diff --git a/APP/app_adc.c b/APP/app_adc.c index 146449a..d3de8e0 100644 --- a/APP/app_adc.c +++ b/APP/app_adc.c @@ -65,6 +65,18 @@ uint8_t percentage = 0; //static uint32_t charge_step_tick = 0; #define VBAT_MIN_RAW 2520 #define VBAT_MAX_RAW 3528 +/* 包电压标定:MIN≈6.0V(0%) … MAX≈8.4V(100%);关机阈值 6.2V */ +#define VBAT_PACK_V_MIN_MV 6000 +#define VBAT_PACK_V_MAX_MV 8400 +#define VBAT_SHUTDOWN_MV 6200 +#define VBAT_SHUTDOWN_RAW \ + (VBAT_MIN_RAW + (uint32_t)(VBAT_SHUTDOWN_MV - VBAT_PACK_V_MIN_MV) * \ + (VBAT_MAX_RAW - VBAT_MIN_RAW) / (VBAT_PACK_V_MAX_MV - VBAT_PACK_V_MIN_MV)) +/* 接近关机阈值时加快采样(约 <6.5V) */ +#define VBAT_FAST_SAMPLE_RAW \ + (VBAT_MIN_RAW + (uint32_t)(500) * (VBAT_MAX_RAW - VBAT_MIN_RAW) / \ + (VBAT_PACK_V_MAX_MV - VBAT_PACK_V_MIN_MV)) +#define BAT_SAMPLE_INTERVAL_LOW_MS 2000 @@ -72,10 +84,14 @@ uint8_t percentage = 0; static void app_adc_string_process(uint8_t channel) { - - uint16_t adc_val = ADC_ReadChannel(ADC_CHANNEL_0) >> 5; + uint16_t adc_val; uint8_t vel_out = 80; + if (System_LowBat_IsActive()) + return; + + adc_val = ADC_ReadChannel(ADC_CHANNEL_0) >> 5; + if (adc_val >= 127) { /* δ���� �� Note Off */ if (string_state[channel]) { @@ -114,10 +130,16 @@ pick_state_t app_adc_get_pick_input(void) static void app_adc_pick_process(void) { - pick_state_t curstate = app_adc_get_pick_input(); - - uint32_t now = rt_tick_get(); + pick_state_t curstate; + uint32_t now; uint32_t hold_tick; + + if (System_LowBat_IsActive()) + return; + + curstate = app_adc_get_pick_input(); + + now = rt_tick_get(); switch(pick_fsm) { case PICK_FSM_STATE_IDLE: @@ -368,7 +390,7 @@ static void app_adc_battery_do_sample(void) bat_index = (bat_index + 1) % BAT_SAMPLE_COUNT; } -/* 上报:映射成百分比,变化才发消息 */ +/* 上报:映射成百分比,变化才发消息;低于 6.2V 触发低电量关机流程 */ static void app_adc_battery_report(void) { uint16_t filtered = bat_sum / BAT_SAMPLE_COUNT; @@ -382,6 +404,15 @@ static void app_adc_battery_report(void) pct = 100 * (filtered - VBAT_MIN_RAW) / (VBAT_MAX_RAW - VBAT_MIN_RAW); pct = (pct / 10) * 10; + /* 开机且未充电:包电压低于 6.2V → 立即停声并弹关机提示(只触发一次) */ + if (powon && !usb_charging_state && !System_LowBat_IsActive() && + filtered < (uint16_t)VBAT_SHUTDOWN_RAW) + { + LOG_W("BAT", "lowbat raw=%u thr=%u → tip+shutdown", + (unsigned)filtered, (unsigned)VBAT_SHUTDOWN_RAW); + MainTask_Sendmsg(MSG_ID_LOW_BAT_TIP, pct, filtered, 0); + } + if (pct != percentage) { /* 变化才上报 */ percentage = pct; if(usb_charging_state && !powon) @@ -404,15 +435,20 @@ static void app_adc_battery_process(void) switch (bat_state) { case BAT_IDLE: - /* 触发:开机后首采,或距上次 ≥30s */ + /* 触发:开机后首采,或距上次 ≥30s(接近低电时改为 2s) */ if (/*powon && */!bat_poweron_done) { bat_poweron_done = 1; bat_state = BAT_START; } - else if (now - bat_last_sample_tick >= BAT_SAMPLE_INTERVAL_MS) + else { - bat_state = BAT_START; + uint32_t interval = BAT_SAMPLE_INTERVAL_MS; + uint16_t last_avg = bat_sum / BAT_SAMPLE_COUNT; + if (last_avg != 0 && last_avg < (uint16_t)VBAT_FAST_SAMPLE_RAW) + interval = BAT_SAMPLE_INTERVAL_LOW_MS; + if (now - bat_last_sample_tick >= interval) + bat_state = BAT_START; } break; diff --git a/Global/Global.c b/Global/Global.c index 1bfbef6..640596f 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -184,6 +184,11 @@ static volatile uint8_t s_pwr_rails_cut = 0; /* TMR7:关机长按已触发请求后的紧急倒计时(ms) */ static volatile uint16_t s_pwr_off_grace_ms = 0; +/* 电量 <6.2V:提示界面停留后关机 */ +#define LOWBAT_SHUTDOWN_DELAY_MS 10000u +static volatile uint8_t s_lowbat_active = 0; +static volatile uint32_t s_lowbat_deadline_ms = 0; + /* ==================== 开机/关机动作 ==================== */ /* 仅 GPIO/背光:可在 ISR / HardFault 中调用,禁止 RTOS/Flash/日志 */ @@ -214,6 +219,8 @@ void System_PowerOn(void) app_tm1629_reset_key_state(); s_pwr_rails_cut = 0u; s_pwr_off_grace_ms = 0u; + s_lowbat_active = 0u; + s_lowbat_deadline_ms = 0u; /* 本次长按已用于开机:先消耗,再置 powon,防止 TMR7 ISR 把同一按压力即判为关机 */ Power_Key_ConsumePress(); /* 控制电源硬件;蓝牙按 NVM/系统设置开关恢复,勿强制常开 */ @@ -318,6 +325,36 @@ void System_PowerOff_Poll(void) } } +/* 电量 <6.2V:提示界面停留后关机 */ +void System_LowBat_Arm(void) +{ + if (s_lowbat_active) + return; + s_lowbat_active = 1; + s_lowbat_deadline_ms = rt_tick_get() * 1000u / RT_TICK_PER_SECOND + LOWBAT_SHUTDOWN_DELAY_MS; + LOG_I("PWR", "lowbat tip armed, power_off in %ums", (unsigned)LOWBAT_SHUTDOWN_DELAY_MS); +} + +uint8_t System_LowBat_IsActive(void) +{ + return s_lowbat_active; +} + +void System_LowBat_Poll(void) +{ + uint32_t now; + if (!s_lowbat_active || !powon) + return; + now = rt_tick_get() * 1000u / RT_TICK_PER_SECOND; + if ((int32_t)(now - s_lowbat_deadline_ms) >= 0) + { + s_lowbat_active = 0; + LOG_I("PWR", "lowbat tip timeout → power_off"); + System_RequestPowerOff(); + } +} + + /* * TMR7 1ms(优先级高于伴奏 TMR6):与主循环解耦的关机键扫描。 * 正常:请求优雅关机;超时未切轨 → 紧急断电并复位。 diff --git a/Global/Global.h b/Global/Global.h index cb4d15e..1b4e9ae 100644 --- a/Global/Global.h +++ b/Global/Global.h @@ -31,6 +31,7 @@ #define MSG_ID_CHARG 27 #define MSG_ID_BLE_UI_REFRESH 28 #define MSG_ID_BLE_STYLE_SET 29 +#define MSG_ID_LOW_BAT_TIP 30 #define GPIO_PIN_RESET false #define GPIO_PIN_SET true @@ -177,6 +178,10 @@ void AutoPowerOff_Scan(void); void System_PowerOn(void); void System_RequestPowerOff(void); void System_PowerOff_Poll(void); +/* 低电量:停声+提示后 10s 关机(主循环 Poll) */ +void System_LowBat_Arm(void); +void System_LowBat_Poll(void); +uint8_t System_LowBat_IsActive(void); extern uint8_t Led; extern bool PressFlag; diff --git a/project/inc/UI0902_Res_Addr.h b/project/inc/UI0902_Res_Addr.h index d1cdc57..2d55009 100644 --- a/project/inc/UI0902_Res_Addr.h +++ b/project/inc/UI0902_Res_Addr.h @@ -288,7 +288,11 @@ #define UI0902_FLASH_MODE_W 240 #define UI0902_FLASH_MODE_H 320 -#define UI0902_BTN_CANCEL_BIG_ADDR (UI0902_RES_BASE + 0x0F8000UL) /* 11872 bytes */ +#define UI0902_LOWBAT_TIP_ADDR (UI0902_RES_BASE + 0x0F8000UL) /* 153600 bytes */ +#define UI0902_LOWBAT_TIP_W 240 +#define UI0902_LOWBAT_TIP_H 320 + +#define UI0902_BTN_CANCEL_BIG_ADDR (UI0902_RES_BASE + 0x11E000UL) /* 11872 bytes */ #define UI0902_BTN_CANCEL_BIG_W 106 #define UI0902_BTN_CANCEL_BIG_H 56 diff --git a/project/src/main.c b/project/src/main.c index 81febac..507e008 100644 --- a/project/src/main.c +++ b/project/src/main.c @@ -108,6 +108,7 @@ int main(void) Power_Key_Scan(); AutoPowerOff_Scan(); System_PowerOff_Poll(); + System_LowBat_Poll(); LCD_Dump_Poll(); #if !DEBUG_LCD_DUMP app_log_poll(); diff --git a/task/task_init.c b/task/task_init.c index 99aae0c..b1a8d91 100644 --- a/task/task_init.c +++ b/task/task_init.c @@ -43,6 +43,28 @@ void TaskUIThread_entry(void* parameter) StartFlag = 0; UI_ReloadTonePreset(); /* 内含 Stop_AutoBand */ } + /* 低电量提示:任意页面全屏覆盖;倒计时期间吞掉其它 UI 刷新,避免盖住提示 */ + if (xEvent.MessageType == MSG_ID_LOW_BAT_TIP) + { + uint8_t peek[4]; + LCD_WR_REG(0x29); + LCD_BLK_Set(); + LCD_FillByColor(0, 0, 240, 320, BLACK); + W25Q128_Read(peek, UI0902_LOWBAT_TIP_ADDR, 4); + if (peek[0] != 0xFF || peek[1] != 0xFF) + { + LCD_WR_PIC_FROM_FLASH(0, 0, UI0902_LOWBAT_TIP_W, UI0902_LOWBAT_TIP_H, + UI0902_LOWBAT_TIP_ADDR); + } + else + { + LOG_W("UI", "lowbat flash empty @0x%08lX — flash ui0902", + (unsigned long)UI0902_LOWBAT_TIP_ADDR); + } + continue; + } + if (System_LowBat_IsActive()) + continue; CurrUIProcress(xEvent); } } @@ -84,15 +106,29 @@ void TaskMainThread_entry(void* parameter) case MSG_ID_POWER_ON : UITask_Sendmsg(MSG_ID_POWER_ON, xEvent.ID, xEvent.HiByte, xEvent.LoByte); break; case MSG_ID_POWER_OFF : break; //case MSG_ID_ADCIN1KEY : ADC_IN1_KEY_Handle(xEvent.ID);break; - case MSG_ID_BP_KEY : Pick_Handle(); break; - case MSG_ID_LOOP_LIGHT : Loop_Light_Handle(xEvent.ID); break; + case MSG_ID_BP_KEY: + if (!System_LowBat_IsActive()) + Pick_Handle(); + break; + case MSG_ID_LOOP_LIGHT: + if (!System_LowBat_IsActive()) + Loop_Light_Handle(xEvent.ID); + break; case MSG_ID_TM1629_KEY : break; case MSG_ID_TM1617_KEY : break; case MSG_ID_EC_VOL: /* 任意页面都先下发主音量,避免 UI 未处理导致屏显与实际脱节 */ - Send_volume((uint8_t)xEvent.ID); + if (!System_LowBat_IsActive()) + Send_volume((uint8_t)xEvent.ID); UITask_Sendmsg(xEvent.MessageType, xEvent.ID, xEvent.HiByte, xEvent.LoByte); break; + case MSG_ID_LOW_BAT_TIP: + /* 立即停声,再交给 UI 画提示;主循环 10s 后关机 */ + Stop_AutoBand(); + Send_volume(0); + System_LowBat_Arm(); + UITask_Sendmsg(MSG_ID_LOW_BAT_TIP, xEvent.ID, xEvent.HiByte, xEvent.LoByte); + break; case MSG_ID_TOUCH: case MSG_ID_CHARG: case MSG_ID_TAP_SPEED: diff --git a/tools/assets_0902/10_低电量/K1 低电量提醒状态 UI设计-02.png b/tools/assets_0902/10_低电量/K1 低电量提醒状态 UI设计-02.png new file mode 100644 index 0000000..eef6ada Binary files /dev/null and b/tools/assets_0902/10_低电量/K1 低电量提醒状态 UI设计-02.png differ diff --git a/tools/assets_0902/10_低电量/K1 低电量提醒状态图标.png b/tools/assets_0902/10_低电量/K1 低电量提醒状态图标.png new file mode 100644 index 0000000..b25fc85 Binary files /dev/null and b/tools/assets_0902/10_低电量/K1 低电量提醒状态图标.png differ diff --git a/tools/assets_0902/10_低电量/低电量提醒-240x320.png b/tools/assets_0902/10_低电量/低电量提醒-240x320.png new file mode 100644 index 0000000..eef6ada Binary files /dev/null and b/tools/assets_0902/10_低电量/低电量提醒-240x320.png differ diff --git a/tools/assets_0902/10_低电量/拆分文字-1.png b/tools/assets_0902/10_低电量/拆分文字-1.png new file mode 100644 index 0000000..a5c1995 Binary files /dev/null and b/tools/assets_0902/10_低电量/拆分文字-1.png differ diff --git a/tools/assets_0902/10_低电量/拆分文字-2.png b/tools/assets_0902/10_低电量/拆分文字-2.png new file mode 100644 index 0000000..486a9f4 Binary files /dev/null and b/tools/assets_0902/10_低电量/拆分文字-2.png differ diff --git a/tools/assets_0902/10_低电量/无背景.png b/tools/assets_0902/10_低电量/无背景.png new file mode 100644 index 0000000..f9c0775 Binary files /dev/null and b/tools/assets_0902/10_低电量/无背景.png differ diff --git a/tools/gen_ui0902_assets.py b/tools/gen_ui0902_assets.py index d8b9362..d34070d 100644 --- a/tools/gen_ui0902_assets.py +++ b/tools/gen_ui0902_assets.py @@ -225,6 +225,8 @@ EXTERNAL = [ ("BOOT_LOGO", "07_开机/开机界面-20.png"), ("CHARGE_SCREEN", "08_充电界面/充电界面-0902-09.png"), # 0904 最新全屏充电稿;百分比重叠由固件绘制 ("FLASH_MODE", "09_烧录模式/烧录模式-240x320.png"), # Boot IAP 烧录等待画面 + # 低电量关机提示(Doc/UI/K1标准界面图 0904/K1 低电量提醒状态界面.zip 合成全屏) + ("LOWBAT_TIP", "10_低电量/低电量提醒-240x320.png"), ] @@ -274,7 +276,7 @@ def main(): if name in ("ROW_UNIVERSAL_NOT", "ROW_UNIVERSAL_SEL", "ROW_NORMAL_NOT", "ROW_NORMAL_SEL", "ROW_EXPERT_NOT", "ROW_EXPERT_SEL"): im = im.resize(ROW_RESIZE, Image.LANCZOS) data, w, h = rgba_to_rgb565(im) - elif name in ("BOOT_LOGO", "CHARGE_SCREEN", "FLASH_MODE"): + elif name in ("BOOT_LOGO", "CHARGE_SCREEN", "FLASH_MODE", "LOWBAT_TIP"): if im.size[0] >= 241 and im.size[1] >= 321: im = im.crop((0, 0, 240, 320)) elif im.size != (240, 320):