diff --git a/APP/app_log.c b/APP/app_log.c index 76a62f7..c92c9cd 100644 --- a/APP/app_log.c +++ b/APP/app_log.c @@ -142,6 +142,11 @@ void app_log_write(char level, const char *cat, const char *fmt, ...) va_list ap; int n; + /* TMR6 1ms ISR 会经 AutoBand NoteOn 回调进来:禁止在中断里 take mutex / 打 RTT */ + if (rt_interrupt_get_nest() != 0) { + return; + } + if (level == 'D' && s_debug_level != 'D') { return; } @@ -387,7 +392,7 @@ uint8_t app_log_try_command(const char *cmd) UI_ApplyToneAddress(); AutoBandTop1_Stop(); StartFlag = 0; - ret = AutoBandTop1_LoadPresetItemFromFlash(0); + ret = App_Auto_LoadPresetFromFlash(0); snprintf(line, sizeof(line), "TONE_LOCAL ret=%d addr=0x%08lX map=BIN2@0x%08lX name=%s count=%d %s\n", ret, (unsigned long)ADDRESS, @@ -416,7 +421,7 @@ uint8_t app_log_try_command(const char *cmd) if (count > 0 && (int)idx >= count) idx = (unsigned)(count - 1); ParamGuiData[SONG_MODE_PARAM].Current = (uint8_t)idx; - ret = AutoBandTop1_LoadPresetItemFromFlash((int)idx); + ret = App_Auto_LoadPresetFromFlash((int)idx); snprintf(line, sizeof(line), "TONE_BIN1 ret=%d idx=%u addr=0x%08lX map=BIN1@0x%08lX name=%s count=%d %s\n", ret, idx, (unsigned long)ADDRESS, @@ -445,7 +450,7 @@ uint8_t app_log_try_command(const char *cmd) if (count > 0 && (int)idx >= count) idx = (unsigned)(count - 1); ParamGuiData[EXPRESS_MODE_PARAM].Current = (uint8_t)idx; - ret = AutoBandTop1_LoadPresetItemFromFlash((int)idx); + ret = App_Auto_LoadPresetFromFlash((int)idx); snprintf(line, sizeof(line), "TONE_BIN2 ret=%d idx=%u addr=0x%08lX map=BIN2@0x%08lX name=%s count=%d %s\n", ret, idx, (unsigned long)ADDRESS, @@ -474,7 +479,7 @@ uint8_t app_log_try_command(const char *cmd) if (count > 0 && (int)idx >= count) idx = (unsigned)(count - 1); ParamGuiData[ALL_MODE_PARAM].Current = (uint8_t)idx; - ret = AutoBandTop1_LoadPresetItemFromFlash((int)idx); + ret = App_Auto_LoadPresetFromFlash((int)idx); snprintf(line, sizeof(line), "TONE_BIN3 ret=%d idx=%u addr=0x%08lX map=BIN3@0x%08lX name=%s count=%d %s\n", ret, idx, (unsigned long)ADDRESS, @@ -502,7 +507,7 @@ uint8_t app_log_try_command(const char *cmd) UI_ApplyToneAddress(); AutoBandTop1_Stop(); StartFlag = 0; - (void)AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); + (void)App_Auto_LoadPresetFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); KEY_ID_1629 = 0; PressFlag = 0; Pick_Handle(); diff --git a/Global/Global.c b/Global/Global.c index 967a909..06656e9 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -101,7 +101,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,10}; /* major.minor.patch — 0.2.10: tone 0914 (BIN3 after 2.bin); autoclose label 关闭 */ +const uint8_t Version[3] = {0,2,11}; /* major.minor.patch — 0.2.11: power-key watchdog + refuse play on bad tone pack */ uint8_t Led = 8; bool PressFlag = 0; @@ -159,8 +159,9 @@ void BLTask_Sendmsg(uint16_t ID, uint16_t ID2, uint16_t HiByte, uint16_t LoByte) #define KEY_PRESS_LEVEL RESET -#define PWR_ON_HOLD_MS 3000 /* 关机态长按 3s → 开机 */ -#define PWR_OFF_HOLD_MS 2000 /* 开机态长按 2s → 关机 */ +#define PWR_ON_HOLD_MS 3000 /* 关机态长按 3s → 开机 */ +#define PWR_OFF_HOLD_MS 2000 /* 开机态长按 2s → 关机 */ +#define PWR_OFF_GRACE_MS 1500 /* 请求优雅关机后,超时仍未切轨则紧急断电+复位 */ typedef enum @@ -176,10 +177,37 @@ static uint32_t pwr_press_tick = 0; /* 当前开关机状态(UI_Idle.c 等 extern 引用) */ bool powon = false; +/* 电源轨是否已切断(优雅关机与紧急关机共用;ISR 依此判断是否还需强切) */ +static volatile uint8_t s_pwr_rails_cut = 0; +/* TMR7:关机长按已触发请求后的紧急倒计时(ms) */ +static volatile uint16_t s_pwr_off_grace_ms = 0; + /* ==================== 开机/关机动作 ==================== */ + +/* 仅 GPIO/背光:可在 ISR / HardFault 中调用,禁止 RTOS/Flash/日志 */ +void PowerOff_CutRails(void) +{ + s_pwr_rails_cut = 1u; + powon = false; + LCD_BLK_Clr(); + BSP_MainPowerEnable(0); + BSP_DreamCorePowerEnable(0); + BSP_HT7178PowerEnable(0); + BSP_BlueToothPowerEnable(0); +} + +static void PowerOff_EmergencyReset(void) +{ + PowerOff_CutRails(); + /* 主循环已死时无法画充电页:复位后按 USB 状态进充电/待机 */ + nvic_system_reset(); +} + void System_PowerOn(void) { LOG_I("PWR", "power_on begin"); + s_pwr_rails_cut = 0u; + s_pwr_off_grace_ms = 0u; /* 控制电源硬件;蓝牙按 NVM/系统设置开关恢复,勿强制常开 */ BSP_MainPowerEnable(1); BSP_DreamCorePowerEnable(1); @@ -213,6 +241,7 @@ static void PowerOff(void) LOG_I("PWR", "power_off begin"); powon = false; /* 更新关机标志 */ + s_pwr_off_grace_ms = 0u; /* 主路径已接管,取消紧急倒计时 */ StopFullTask(); /* 关机前把当前调音台推子与蓝牙开关写入 NVM,保证再次开机保持 */ @@ -234,13 +263,9 @@ static void PowerOff(void) app_tm1629_all_off(); app_tm1617_off(); - /* 关掉主电源/音源/功放/蓝牙;MCU 仍跑以便长按开机与 Type-C 检测 */ - BSP_MainPowerEnable(0); - BSP_DreamCorePowerEnable(0); - BSP_HT7178PowerEnable(0); - /* BT 最后关:给已排队的 UART4 ACK(如 05 00)留出模组转发窗口 */ + /* BT 先留窗口转发已排队 ACK,再切全部电源轨 */ rt_thread_mdelay(30); - BSP_BlueToothPowerEnable(0); + PowerOff_CutRails(); CurrUIProcress = IdleProcess; MainTask_Sendmsg(MSG_ID_POWER_OFF, 0, 0, 0); LOG_I("PWR", "power_off done (soft-off)"); @@ -271,6 +296,9 @@ static volatile uint8_t s_sys_poweroff_req = 0; void System_RequestPowerOff(void) { s_sys_poweroff_req = 1; + /* 主循环若已卡死:TMR7 紧急倒计时仍会强切电源 */ + if (s_pwr_off_grace_ms == 0u && !s_pwr_rails_cut) + s_pwr_off_grace_ms = PWR_OFF_GRACE_MS; } void System_PowerOff_Poll(void) { @@ -282,6 +310,77 @@ void System_PowerOff_Poll(void) } } +/* + * TMR7 1ms(优先级高于伴奏 TMR6):与主循环解耦的关机键扫描。 + * 正常:请求优雅关机;超时未切轨 → 紧急断电并复位。 + */ +void Power_Key_IsrTick(void) +{ + static uint16_t isr_hold_ms = 0; + static uint8_t isr_fired = 0; + flag_status st = BSP_GetPowerKey(); + + if (st == KEY_PRESS_LEVEL) + { + if (isr_hold_ms < 60000u) + isr_hold_ms++; + } + else + { + isr_hold_ms = 0; + isr_fired = 0; + } + + /* 仅处理「开机态关机」;开机仍由主循环负责(需完整外设初始化) */ + if (powon && !isr_fired && isr_hold_ms >= PWR_OFF_HOLD_MS) + { + isr_fired = 1; + System_RequestPowerOff(); + } + + if (s_pwr_off_grace_ms > 0u) + { + s_pwr_off_grace_ms--; + if (s_pwr_off_grace_ms == 0u && !s_pwr_rails_cut) + PowerOff_EmergencyReset(); + } +} + +/* HardFault / MemManage / BusFault / UsageFault:死循环中仍可长按关机 */ +void Power_Key_FaultLoop(void) +{ + uint32_t hold = 0; + for (;;) + { + if (BSP_GetPowerKey() == KEY_PRESS_LEVEL) + { + hold++; + if (hold >= PWR_OFF_HOLD_MS) + { + PowerOff_EmergencyReset(); + } + } + else + { + hold = 0; + } + Delay_us(1000); + } +} + +static rt_err_t Power_HardFault_Hook(void *context) +{ + (void)context; + Power_Key_FaultLoop(); + return -RT_ERROR; /* 不可达 */ +} + +void Power_Key_WatchdogInit(void) +{ + rt_hw_exception_install(Power_HardFault_Hook); + wk_tmr7_pwrkey_init(); +} + void Power_Key_Scan() { flag_status status = BSP_GetPowerKey(); @@ -340,19 +439,13 @@ void Power_Key_Scan() } holdtick = now - pwr_press_tick; - /* 用 powon 区分:关机长按 3s 开机,开机长按 2s 关机 */ + /* 关机长按 3s 开机;开机态关机改由 TMR7 ISR(主循环卡死仍可关) */ if(powon == false && holdtick >= PWR_ON_HOLD_MS) { LOG_I("PWR", "key power_on hold=%u", (unsigned)holdtick); PowerOn(); pwr_key_state = KEY_STATE_NONE; } - else if(powon == true && holdtick >= PWR_OFF_HOLD_MS) - { - LOG_I("PWR", "key power_off hold=%u", (unsigned)holdtick); - PowerOff(); - pwr_key_state = KEY_STATE_NONE; - } break; case KEY_STATE_NONE: @@ -814,6 +907,15 @@ void Pick_Handle() uint8_t note_cnt = 0; uint8_t chord_id = KEY_ID_1629; + /* 空库/加载失败时禁止起奏:否则 TMR6 ISR 内 AutoBand 可能 HardFault, + * LCD 卡死且主循环 Power_Key_Scan 不再跑 → 只能 RESET */ + if (!App_Auto_IsPresetReady()) + { + LOG_E("pick", "abort: preset not ready tab=%u addr=0x%08X", + (unsigned)mGuiData[GUI_TAB_INDEX].Current, (unsigned)ADDRESS); + return; + } + /* 左手未触发和弦板(或落在拍速/停止键)时:拨片默认发当前调一级大和弦伴奏; * 普通模式除外——必须左手按住指板再拨,不做一级兜底 */ if (mGuiData[GUI_TAB_INDEX].Current != 2 && diff --git a/Global/Global.h b/Global/Global.h index 865acc5..ebdf71f 100644 --- a/Global/Global.h +++ b/Global/Global.h @@ -166,6 +166,10 @@ extern uint8_t KEY_ID_1629; extern bool powon; void Power_Key_Scan(void); +void Power_Key_IsrTick(void); +void Power_Key_FaultLoop(void); +void Power_Key_WatchdogInit(void); +void PowerOff_CutRails(void); void AutoPowerOff_Scan(void); void System_PowerOn(void); void System_RequestPowerOff(void); diff --git a/MyCode/App_Auto.c b/MyCode/App_Auto.c index fbce757..5762bf1 100644 --- a/MyCode/App_Auto.c +++ b/MyCode/App_Auto.c @@ -416,6 +416,31 @@ static const char *ToneFlashMapBank(uint32_t abs) static uint32_t s_flash_rd_last_base = 0xFFFFFFFFu; static uint16_t s_flash_rd_log_cnt = 0; +/* 最近一次 LoadPreset 是否成功;失败时禁止拨片起奏(防空库 + TMR6 ISR 崩) */ +static volatile uint8_t s_preset_ready = 0; + +int App_Auto_LoadPresetFromFlash(int presetIndex) +{ + int ret = AutoBandTop1_LoadPresetItemFromFlash(presetIndex); + if (ret == 0) + { + const char *name = AutoBandTop1_GetPresetName(); + int count = AutoBandTop1_GetPresetItemCount(); + s_preset_ready = (count > 0 && name != NULL && name[0] != '\0') ? 1u : 0u; + } + else + { + s_preset_ready = 0u; + AutoBandTop1_Stop(); + StartFlag = 0; + } + return ret; +} + +uint8_t App_Auto_IsPresetReady(void) +{ + return s_preset_ready; +} static int Func_CallBack_ReadFlash(int address,int length,uint8_t* pOutput) { @@ -468,7 +493,7 @@ void App_Auto_Init(void) AutoBandTop1_RegisterReadFlashCallback(Func_CallBack_ReadFlash); m_presetCount = AutoBandTop1_GetPresetItemCount(); - int ret = AutoBandTop1_LoadPresetItemFromFlash(0); + int ret = App_Auto_LoadPresetFromFlash(0); if (ret != 0) { uint8_t hdr[16]; diff --git a/MyCode/App_Auto.h b/MyCode/App_Auto.h index 493e433..29b923b 100644 --- a/MyCode/App_Auto.h +++ b/MyCode/App_Auto.h @@ -8,9 +8,14 @@ #ifndef APP_AUTO_H_ #define APP_AUTO_H_ #include "mymidififo1.h" +#include extern void App_Auto_Init(void); +/* 统一加载入口:记录成功/失败,失败时 Stop,避免空库起奏进 ISR 崩溃 */ +int App_Auto_LoadPresetFromFlash(int presetIndex); +uint8_t App_Auto_IsPresetReady(void); + extern MidiFifoSeq_t m_MidiSendFifo; #endif /* APP_AUTO_H_ */ diff --git a/UI/UI_SongMode.c b/UI/UI_SongMode.c index c458ec5..b41ec5f 100644 --- a/UI/UI_SongMode.c +++ b/UI/UI_SongMode.c @@ -180,11 +180,11 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) UI_ApplyToneAddress(); int ret = 0; if (tab == 0) - ret = AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); + ret = App_Auto_LoadPresetFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); else if (mGuiData[GUI_AUTOBAND_SW].Current) - ret = AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[EXPRESS_MODE_PARAM].Current); + ret = App_Auto_LoadPresetFromFlash(ParamGuiData[EXPRESS_MODE_PARAM].Current); else - ret = AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[SONG_MODE_PARAM].Current); + ret = App_Auto_LoadPresetFromFlash(ParamGuiData[SONG_MODE_PARAM].Current); if (ret != 0) LOG_E("tone", "load preset failed tab=%d sw=%d ret=%d addr=0x%08X", tab, mGuiData[GUI_AUTOBAND_SW].Current, ret, (unsigned)ADDRESS); diff --git a/UI/UI_global.c b/UI/UI_global.c index 8004ed9..8e7dd92 100644 --- a/UI/UI_global.c +++ b/UI/UI_global.c @@ -170,7 +170,7 @@ void UI_ReloadTonePreset(void) /* 万能:和弦走向 */ if (mGuiData[GUI_TAB_INDEX].Current == 0) { - int ret = AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); + int ret = App_Auto_LoadPresetFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); if (ret != 0) { uint8_t hdr[16]; @@ -197,7 +197,7 @@ void UI_ReloadTonePreset(void) local->Max = (LOCAL_SONG_COUNT > 0) ? (uint16_t)(LOCAL_SONG_COUNT - 1) : 0; if (local->Current > local->Max) local->Current = local->Max; - int ret = AutoBandTop1_LoadPresetItemFromFlash(local->Current); + int ret = App_Auto_LoadPresetFromFlash(local->Current); if (ret != 0) LOG_E("tone", "load local song idx=%d failed ret=%d addr=0x%08X map=BIN2@0x%08X", local->Current, ret, (unsigned)ADDRESS, @@ -217,7 +217,7 @@ void UI_ReloadTonePreset(void) return; } { - int ret = AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[SONG_MODE_PARAM].Current); + int ret = App_Auto_LoadPresetFromFlash(ParamGuiData[SONG_MODE_PARAM].Current); LOG_I("tone", "load rhythm idx=%d ret=%d name=%s addr=0x%08X map=BIN1@0x%08X %s", ParamGuiData[SONG_MODE_PARAM].Current, ret, AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", diff --git a/project/inc/wk_system.h b/project/inc/wk_system.h index 0d5e3d7..4644d3c 100644 --- a/project/inc/wk_system.h +++ b/project/inc/wk_system.h @@ -41,6 +41,9 @@ void wk_tmr6_1ms_init(void); void wk_tmr6_1ms_tick(void); uint32_t wk_tmr6_tick_get(void); +/* TMR7 1ms:关机键看门狗(优先级高于 TMR6,主循环卡死仍可关) */ +void wk_tmr7_pwrkey_init(void); + #ifdef __cplusplus } #endif diff --git a/project/src/at32f403a_407_int.c b/project/src/at32f403a_407_int.c index e7af26a..392707c 100644 --- a/project/src/at32f403a_407_int.c +++ b/project/src/at32f403a_407_int.c @@ -30,7 +30,9 @@ /* private includes ----------------------------------------------------------*/ /* add user code begin private includes */ - +/* 关机键看门狗:与 Global.c 解耦声明,避免 int.c 拉全量 includes */ +void Power_Key_IsrTick(void); +void Power_Key_FaultLoop(void); /* add user code end private includes */ /* private typedef -----------------------------------------------------------*/ @@ -110,16 +112,7 @@ void NMI_Handler(void) */ void MemManage_Handler(void) { - /* add user code begin MemoryManagement_IRQ 0 */ - - /* add user code end MemoryManagement_IRQ 0 */ - /* go to infinite loop when memory manage exception occurs */ - while (1) - { - /* add user code begin W1_MemoryManagement_IRQ 0 */ - - /* add user code end W1_MemoryManagement_IRQ 0 */ - } + Power_Key_FaultLoop(); } /** @@ -129,16 +122,7 @@ void MemManage_Handler(void) */ void BusFault_Handler(void) { - /* add user code begin BusFault_IRQ 0 */ - - /* add user code end BusFault_IRQ 0 */ - /* go to infinite loop when bus fault exception occurs */ - while (1) - { - /* add user code begin W1_BusFault_IRQ 0 */ - - /* add user code end W1_BusFault_IRQ 0 */ - } + Power_Key_FaultLoop(); } /** @@ -148,16 +132,7 @@ void BusFault_Handler(void) */ void UsageFault_Handler(void) { - /* add user code begin UsageFault_IRQ 0 */ - - /* add user code end UsageFault_IRQ 0 */ - /* go to infinite loop when usage fault exception occurs */ - while (1) - { - /* add user code begin W1_UsageFault_IRQ 0 */ - - /* add user code end W1_UsageFault_IRQ 0 */ - } + Power_Key_FaultLoop(); } /** @@ -241,6 +216,15 @@ void TMR6_GLOBAL_IRQHandler(void) //rt_os_tick_callback(); } +/** + * @brief TMR7:关机键看门狗(先于伴奏 TMR6 响应,主循环死锁仍可关) + */ +void TMR7_GLOBAL_IRQHandler(void) +{ + tmr_flag_clear(TMR7, TMR_OVF_FLAG); + Power_Key_IsrTick(); +} + /* add user code begin 1 */ /* add user code end 1 */ diff --git a/project/src/main.c b/project/src/main.c index 783bc9d..81febac 100644 --- a/project/src/main.c +++ b/project/src/main.c @@ -88,6 +88,8 @@ int main(void) drv_nvm_init(); TaskInit(); timer_init(); + /* 关机键看门狗:TMR7 + HardFault hook,与主循环/伴奏 ISR 解耦 */ + Power_Key_WatchdogInit(); InitMenuUI(); BSP_HT7178PowerEnable (1); BSP_ChargerEnable(1); diff --git a/project/src/wk_system.c b/project/src/wk_system.c index 6a43d73..d71d446 100644 --- a/project/src/wk_system.c +++ b/project/src/wk_system.c @@ -174,3 +174,16 @@ void wk_tmr6_1ms_init(void) tmr_counter_enable(TMR6, TRUE); } +/** + * @brief TMR7 1ms:仅扫关机键(抢占优先级 1,高于 TMR6=5 / 伴奏库) + */ +void wk_tmr7_pwrkey_init(void) +{ + crm_periph_clock_enable(CRM_TMR7_PERIPH_CLOCK, TRUE); + tmr_base_init(TMR7, 999, 239); + tmr_flag_clear(TMR7, TMR_OVF_FLAG); + tmr_interrupt_enable(TMR7, TMR_OVF_INT, TRUE); + nvic_irq_enable(TMR7_GLOBAL_IRQn, 1, 0); + tmr_counter_enable(TMR7, TRUE); +} + diff --git a/tools/publish_k1_release.py b/tools/publish_k1_release.py index d5c684a..ad316dd 100644 --- a/tools/publish_k1_release.py +++ b/tools/publish_k1_release.py @@ -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.10" +FW_VER = "0.2.11" UI0902_RES_BASE = 0x00100000 BOOT_FLASH_ADDR = 0x08000000 APP_FLASH_ADDR = 0x08008000 @@ -135,12 +135,11 @@ def main() -> None: raise SystemExit(f"missing Boot bin: {BOOT_BIN}") if not EXE_BIN.is_file(): raise SystemExit(f"missing MCU bin: {EXE_BIN}") - if not TONE_BIN.is_file(): - # fallback legacy alias written by packer + tone_bin = TONE_BIN + if not tone_bin.is_file(): legacy = PROJ / "tools" / "out" / "extflash_tone_0903.bin" if legacy.is_file(): - global TONE_BIN - TONE_BIN = legacy + tone_bin = legacy else: raise SystemExit(f"missing tone pack: {TONE_BIN} (run pack_extflash_tone_0903.py)") if not UI0902_BIN.is_file(): @@ -165,7 +164,7 @@ def main() -> None: shutil.rmtree(pkg) pkg.mkdir(parents=True) - tone_data = TONE_BIN.read_bytes() + tone_data = tone_bin.read_bytes() verify_tone_layout(tone_data) ui_data = UI0902_BIN.read_bytes() if len(ui_data) < 1024: