fix(pwr): keep the pack above 6.8V and unmute the amp after 12V is up

Show the real battery percent, shut down at 6.8V even while Type-C is connected, and leave the charge screen dark so it does not drain the pack. Initialize the amplifier only after the 12V rail has settled.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yuquanjun 2026-09-26 07:35:09 +08:00
parent 89a3e5f95f
commit e6de959366
8 changed files with 143 additions and 43 deletions

View File

@ -65,16 +65,16 @@ 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 */
/* 包电压标定:MIN≈6.0V(0%) … MAX≈8.4V(100%);关机阈值 6.8V(每节 3.4V) */
#define VBAT_PACK_V_MIN_MV 6000
#define VBAT_PACK_V_MAX_MV 8400
#define VBAT_SHUTDOWN_MV 6200
#define VBAT_SHUTDOWN_MV 6800
#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) */
/* 7.2V 以下加快采样,避免从 6.8V 掉到保护电压时还要等 30s */
#define VBAT_FAST_SAMPLE_RAW \
(VBAT_MIN_RAW + (uint32_t)(500) * (VBAT_MAX_RAW - VBAT_MIN_RAW) / \
(VBAT_MIN_RAW + (uint32_t)(1200) * (VBAT_MAX_RAW - VBAT_MIN_RAW) / \
(VBAT_PACK_V_MAX_MV - VBAT_PACK_V_MIN_MV))
#define BAT_SAMPLE_INTERVAL_LOW_MS 2000
@ -396,7 +396,7 @@ static void app_adc_battery_do_sample(void)
bat_index = (bat_index + 1) % BAT_SAMPLE_COUNT;
}
/* 上报:映射成百分比,变化才发消息;低于 6.2V 触发低电量关机流程 */
/* 上报:映射成百分比,变化才发消息;低于 6.8V 触发低电量关机(插着 Type-C 也执行) */
static void app_adc_battery_report(void)
{
uint16_t filtered = bat_sum / BAT_SAMPLE_COUNT;
@ -407,11 +407,10 @@ static void app_adc_battery_report(void)
else if (filtered > VBAT_MAX_RAW)
pct = 100;
else
pct = 100 * (filtered - VBAT_MIN_RAW) / (VBAT_MAX_RAW - VBAT_MIN_RAW);
pct = (pct / 10) * 10;
pct = (uint8_t)(100 * (filtered - VBAT_MIN_RAW) / (VBAT_MAX_RAW - VBAT_MIN_RAW));
/* 开机且未充电:包电压低于 6.2V → 立即停声并弹关机提示(只触发一次) */
if (powon && !usb_charging_state && !System_LowBat_IsActive() &&
/* 开机使用中包电压低于 6.8V:停负载并关机。插着 Type-C 同样执行,避免边充边用拉过保护电压 */
if (powon && !System_LowBat_IsActive() &&
filtered < (uint16_t)VBAT_SHUTDOWN_RAW)
{
LOG_W("BAT", "lowbat raw=%u thr=%u → tip+shutdown",
@ -451,7 +450,8 @@ static void app_adc_battery_process(void)
{
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)
/* 关机充电不加快采样,避免每 2 秒停一次充电 */
if (powon && 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;
@ -459,7 +459,8 @@ static void app_adc_battery_process(void)
break;
case BAT_START:
if (usb_charging_state) /* 充电中:先停充电,采真实电压 */
/* 关机充电不中断充电芯片。开机中才短暂停充,读负载下的真实电压 */
if (powon && usb_charging_state)
{
BSP_ChargerEnable(0);
bat_charge_paused = 1;

View File

@ -12,7 +12,7 @@
#endif
#ifndef APP_LOG_RAM_SIZE
#define APP_LOG_RAM_SIZE (4U * 1024U)
#define APP_LOG_RAM_SIZE ((4U * 1024U) - 128U)
#endif
#ifndef APP_LOG_SLOT_SIZE

View File

@ -111,7 +111,7 @@ int16_t MIC_VOL_MAP[11]= {-9000,-7000,-5000,-3000,-1000,1000,3000,5000,7000,9000
uint8_t TOUCH_AREA_NUM = (sizeof(Touch_Areas)/sizeof(Touch_AreaTypeDef));
const uint8_t Version[3] = {0,2,31}; /* major.minor.patch — 0.2.31: 充电开机先出模式选择,避免卡在 Logo */
const uint8_t Version[3] = {0,2,35}; /* major.minor.patch — 0.2.35: 12V稳定后再初始化功放,避免开机无声 */
uint8_t Led = 8;
bool PressFlag = 0;
@ -204,7 +204,7 @@ static volatile uint8_t s_pwr_rails_cut = 0;
/* TMR7:关机长按已触发请求后的紧急倒计时(ms) */
static volatile uint16_t s_pwr_off_grace_ms = 0;
/* 电量 <6.2V:提示界面停留后关机 */
/* 电量 <6.8V:提示界面停留后关机(插着 Type-C 也停负载) */
#define LOWBAT_SHUTDOWN_DELAY_MS 10000u
static volatile uint8_t s_lowbat_active = 0;
static volatile uint32_t s_lowbat_deadline_ms = 0;
@ -244,27 +244,111 @@ void System_PowerOn(void)
s_lowbat_deadline_ms = 0u;
/* 本次长按已用于开机:先消耗,再置 powon,防止 TMR7 ISR 把同一按压力即判为关机 */
Power_Key_ConsumePress();
/* 控制电源硬件;蓝牙按 NVM/系统设置开关恢复,勿强制常开 */
/* 彩灯期间只留主电和侧灯。12V 升压、Dream、功放、蓝牙在 Logo 彩灯结束后再开 */
BSP_MainPowerEnable(1);
BSP_DreamCorePowerEnable(1);
BSP_HT7178PowerEnable(1);
/* 蓝牙按 NVM/系统设置开关恢复,勿强制常开 */
BSP_BlueToothPowerEnable((uint8_t)(mGuiData[GUI_BL_SW].Current ? 1 : 0));
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
powon = true; /* 更新开机标志 */
/* 侧灯驱动先就绪,模式页出来前保持全灭(勿在 Logo 前打满 8 颗键灯) */
/* 侧灯驱动先就绪,彩灯由 UI 在 Logo 画面上播放 */
TM1629D_Key_LED_Init();
app_tm1617_init();
/* 先通知 UI 亮屏,避免外设初始化阻塞导致长时间黑屏 */
LOG_I("PWR", "send POWER_ON");
LOG_I("PWR", "send POWER_ON (logo+led, audio rails off)");
MainTask_Sendmsg(MSG_ID_POWER_ON, 0, 0, 0);
}
void Power_LogRails(const char *where)
{
unsigned main_on = (unsigned)(gpio_output_data_bit_read(S_PWR_ON_OFF_GPIO_Port, S_PWR_ON_OFF_Pin) == SET);
unsigned dream_on = (unsigned)(gpio_output_data_bit_read(P_CORE_PWR_GPIO_Port, P_CORE_PWR_Pin) == SET);
unsigned v12_on = (unsigned)(gpio_output_data_bit_read(S_HT7178_EN_GPIO_Port, S_HT7178_EN_Pin) == SET);
unsigned bt_on = (unsigned)(gpio_output_data_bit_read(S_BT_PWR_GPIO_Port, S_BT_PWR_Pin) == SET);
/* 充电使能低有效:引脚为低记 chg=1 */
unsigned chg_on = (unsigned)(gpio_output_data_bit_read(S_CHARGE_CTRL_GPIO_Port, S_CHARGE_CTRL_Pin) == RESET);
unsigned bl_on = (unsigned)(gpio_output_data_bit_read(LCD_BLK_PORT, LCD_BLK_PIN) == SET);
LOG_I("PWR", "%s powon=%u main=%u dream=%u v12=%u bt=%u chg=%u bl=%u usb=%u pct=%u",
where ? where : "?",
(unsigned)powon, main_on, dream_on, v12_on, bt_on, chg_on, bl_on,
(unsigned)usb_charging_state, (unsigned)percentage);
}
/* 关机充电:只留主电给屏幕,其余外设必须关掉 */
static void Power_QuiesceForCharge(const char *where)
{
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
Power_LogRails(where);
}
/* UI 播完开机彩灯后请求;由主循环执行,避免功放 I2C 堵死触摸 */
static volatile uint8_t s_audio_bringup_req = 0;
void System_RequestAudioBringUp(void)
{
s_audio_bringup_req = 1;
}
#define CHARGE_BL_HOLD_MS 3000u
static uint32_t s_charge_bl_off_at = 0;
void Power_ChargeBacklightHold(void)
{
LCD_BLK_Set();
s_charge_bl_off_at = rt_tick_get() + (CHARGE_BL_HOLD_MS * RT_TICK_PER_SECOND / 1000u);
}
void Power_ChargeIdle_LogPoll(void)
{
static uint32_t s_last;
uint32_t now;
if (powon)
{
s_charge_bl_off_at = 0;
return;
}
now = rt_tick_get();
if (s_charge_bl_off_at != 0u && (int32_t)(now - s_charge_bl_off_at) >= 0)
{
LCD_BLK_Clr();
s_charge_bl_off_at = 0;
LOG_I("PWR", "charge backlight off");
}
if (s_last != 0u && (now - s_last) < (5u * RT_TICK_PER_SECOND))
return;
s_last = now;
Power_LogRails("charge-idle");
}
extern uint8_t level;
void System_AudioBringUp_Poll(void)
{
if (!s_audio_bringup_req || !powon)
return;
s_audio_bringup_req = 0;
LOG_I("PWR", "bring up audio rails");
BSP_DreamCorePowerEnable(1);
BSP_HT7178PowerEnable(1);
BSP_BlueToothPowerEnable((uint8_t)(mGuiData[GUI_BL_SW].Current ? 1 : 0));
Power_LogRails("audio-up");
/* 12V 升压刚打开时功放模拟电源未稳,立刻写寄存器会仍处于静音 */
rt_thread_mdelay(80);
AD85020B_Init();
XPT2046_Init();
App_Auto_Init();
/* App 协议走 UART4:须在开机后即解析,不能等到触摸选模式才 StartTask */
StartTask();
rt_thread_mdelay(400);
if (!powon)
return;
AD85020B_Init();
MasterVolume_Reapply();
LoadConfig();
MainTask_Sendmsg(MSG_ID_EC_VOL, app_adc_get_volume(), level, 0);
LOG_I("PWR", "periph ready + BT UART recv started");
}
@ -324,8 +408,13 @@ static void PowerOff(void)
usb_charging_state = 1;
usb_charging_laststate = 1;
LOG_I("PWR", "soft-off with Type-C → charge UI");
Power_QuiesceForCharge("soft-off-charge");
MainTask_Sendmsg(MSG_ID_POWOFF_CHARG, percentage, 1, 0);
}
else
{
Power_LogRails("soft-off");
}
}
}
@ -349,7 +438,7 @@ void System_PowerOff_Poll(void)
}
}
/* 电量 <6.2V:提示界面停留后关机 */
/* 电量 <6.8V:提示界面停留后关机(插着 Type-C 也停负载) */
void System_LowBat_Arm(void)
{
if (s_lowbat_active)
@ -491,6 +580,7 @@ void Power_Key_Scan()
usb_charging_state = 1;
usb_charging_laststate = 1;
LOG_I("PWR", "boot with USB → charge UI (no logo)");
Power_QuiesceForCharge("usb-boot-charge");
CurrUIProcress = IdleProcess;
MainTask_Sendmsg(MSG_ID_POWOFF_CHARG, percentage, 1, 0);
pwr_key_state = KEY_STATE_NONE;

View File

@ -180,6 +180,14 @@ void Power_Key_ConsumePress(void);
void PowerOff_CutRails(void);
void AutoPowerOff_Scan(void);
void System_PowerOn(void);
/* Logo 与开机彩灯结束后再开 12V / Dream / 功放(主循环执行,不堵 UI) */
void System_RequestAudioBringUp(void);
void System_AudioBringUp_Poll(void);
void Power_ChargeIdle_LogPoll(void);
/* 充电页点亮背光数秒后关掉,避免屏幕把电池耗下去 */
void Power_ChargeBacklightHold(void);
/* 读回各路电源输出电平:main/dream/v12/bt 为 1=开;chg 为 1=充电使能(低有效) */
void Power_LogRails(const char *where);
void System_RequestPowerOff(void);
void System_PowerOff_Poll(void);
/* 低电量:停声+提示后 10s 关机(主循环 Poll) */

View File

@ -179,27 +179,16 @@ void IdleProcess(DisplayTaskMessage_Type msg)
LCD_WR_REG(0x29); /* Display ON */
s_charge_ui_drawn = false;
LCD_BLK_Set();
/* 模式页出来前不点 8 颗键灯:低电充电时彩灯+功放会把电压拉垮,卡在 Logo */
/* 彩灯期间必须停在 Logo。此阶段 12V/Dream/功放尚未打开 */
LCD_WR_PIC_FROM_FLASH(0, 0, UI0902_BOOT_LOGO_W, UI0902_BOOT_LOGO_H,
UI0902_BOOT_LOGO_ADDR);
rt_thread_mdelay(200);
LoadConfig();
/* 主音量不在此现场切通道读 ADC(会与扫描任务串通道):
* 待 StartScanTask 后由 MasterVolume_Reapply 统一补发 */
UI_BootLedShow();
UI_DrawModeSelectScreen();
app_tm1629_all_on(LED_COLOR_G);
/* 先启动触摸再返回,功放初始化改到主循环,避免 UI 堵在 I2C 里点击无响应 */
StartTouchTask();
StartScanTask();
/* 充电开机不再跑彩灯,避免 UI 线程堵在模式选择之前;冷开机在触摸已起后再演 */
if (!usb_charging_state)
UI_BootLedShow();
/* Dream 上电稍后可能忽略首包 SysEx:再发主音量 + 调音台(保持关机前设置)。
* 此时扫描任务已启动、模拟开关在轮询,不可现场重读 ADC(会串通道),
* 统一用 MasterVolume_Reapply 取扫描任务维护的最近一次有效音量 */
rt_thread_mdelay(400);
MasterVolume_Reapply();
LoadConfig();
System_RequestAudioBringUp();
Draw_Volume_Bar(UI0902_VOL_X, UI0902_VOL_Y, level, 1, WHITE, GRAY);
break;
@ -291,8 +280,9 @@ void IdleProcess(DisplayTaskMessage_Type msg)
LOG_W("UI", "charge flash empty @0x%08lX — flash ui0902",
(unsigned long)UI0902_CHARGE_SCREEN_ADDR);
}
LCD_BLK_Set();
Power_ChargeBacklightHold();
s_charge_ui_drawn = true;
Power_LogRails("charge-ui");
}
if(usb_charging_state)
{
@ -317,6 +307,7 @@ void IdleProcess(DisplayTaskMessage_Type msg)
}
LCD_WR_PIC_Trans(x, y, g_ChargePctPercent.w, g_ChargePctPercent.h,
g_ChargePctPercent.data, BLACK);
Power_ChargeBacklightHold();
}
else
{
@ -325,6 +316,7 @@ void IdleProcess(DisplayTaskMessage_Type msg)
LCD_FillByColor(0, 0, 240, 320, BLACK);
BSP_MainPowerEnable(0);
s_charge_ui_drawn = false;
Power_LogRails("charge-unplug");
}
}
break;

View File

@ -91,8 +91,12 @@ int main(void)
/* 关机键看门狗:TMR7 + HardFault hook,与主循环/伴奏 ISR 解耦 */
Power_Key_WatchdogInit();
InitMenuUI();
BSP_HT7178PowerEnable (1);
/* GPIO 默认会拉高蓝牙和 12V。关机充电前先全部关掉,只留主电给屏幕 */
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
BSP_ChargerEnable(1);
Power_LogRails("boot");
CurrUIProcress = IdleProcess;
LOG_I("BOOT", "main init done ui=Idle");
/* add user code end 2 */
@ -106,6 +110,8 @@ int main(void)
}
/* add user code begin 3 */
Power_Key_Scan();
System_AudioBringUp_Poll();
Power_ChargeIdle_LogPoll();
AutoPowerOff_Scan();
System_PowerOff_Poll();
System_LowBat_Poll();

View File

@ -144,9 +144,12 @@ void TaskMainThread_entry(void* parameter)
UITask_Sendmsg(xEvent.MessageType, xEvent.ID, xEvent.HiByte, xEvent.LoByte);
break;
case MSG_ID_LOW_BAT_TIP:
/* 立即停声,再交给 UI 画提示;主循环 10s 后关机 */
/* 立即停声并切掉音源/12V/蓝牙,避免提示的 10s 里继续把电池拉过保护电压 */
Stop_AutoBand();
Send_volume(0);
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
System_LowBat_Arm();
UITask_Sendmsg(MSG_ID_LOW_BAT_TIP, xEvent.ID, xEvent.HiByte, xEvent.LoByte);
break;

View File

@ -38,7 +38,7 @@ TONE_HDR = PROJ / "project" / "inc" / "ExtFlash_Tone_Addr.h"
IAP_DIR = REPO / "升级" / "MCU主控升级"
OUT_ROOT = REPO / "tools" / "out"
FW_VER = "0.2.31"
FW_VER = "0.2.35"
UI0902_RES_BASE = 0x00100000
BOOT_FLASH_ADDR = 0x08000000
APP_FLASH_ADDR = 0x08008000