From f179a46c5dabbfee7b41b064e547650a63dd86e7 Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Fri, 4 Sep 2026 17:06:02 +0800 Subject: [PATCH] Fix teacher accomp issues and add ExtFlash chip-erase helper. Pick fires on downstroke; #IV drops an octave; local songs use 2.bin; transpose updates notes without restart; intro starts on key I; boot string volume/PC corrected. Co-authored-by: Cursor --- APP/app_adc.c | 4 +- APP/app_log.c | 8 ++ Global/Global.c | 118 ++++++++++++++----------- Global/Global.h | 2 + UI/UI_Idle.c | 22 ++--- UI/UI_SongMode.c | 55 +++--------- UI/UI_global.c | 55 +++++++++--- UI/UI_global.h | 3 + tools/erase_extflash_chip.py | 165 +++++++++++++++++++++++++++++++++++ 9 files changed, 311 insertions(+), 121 deletions(-) create mode 100644 tools/erase_extflash_chip.py diff --git a/APP/app_adc.c b/APP/app_adc.c index d3d3828..b4667e3 100644 --- a/APP/app_adc.c +++ b/APP/app_adc.c @@ -125,6 +125,8 @@ static void app_adc_pick_process(void) { pick_hold_tick = now; pick_fsm = PICK_FSM_STATE_DOWN; + /* 音师:拨片下拨即响(进入 DOWN 触发),不要等复位 */ + MainTask_Sendmsg(MSG_ID_BP_KEY,PICK_STATE_DOWN,0,0); } if(curstate == PICK_STATE_UP) { @@ -136,8 +138,6 @@ static void app_adc_pick_process(void) if(curstate != PICK_STATE_DOWN) { pick_fsm = PICK_FSM_STATE_IDLE; - //发送下拨消息 - MainTask_Sendmsg(MSG_ID_BP_KEY,PICK_STATE_DOWN,0,0); } break; case PICK_FSM_STATE_UP: diff --git a/APP/app_log.c b/APP/app_log.c index 348715d..137d25e 100644 --- a/APP/app_log.c +++ b/APP/app_log.c @@ -264,6 +264,14 @@ uint8_t app_log_try_command(const char *cmd) app_log_flash_start(size); return 1U; } + if (strncmp(cmd, "flash erase chip", 16) == 0) { + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "FLASH_ERASE_BEGIN\n"); + LOG_I("FLASH", "W25Q128 chip erase start"); + W25Q128_Erase_Chip(); + LOG_I("FLASH", "W25Q128 chip erase done"); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "FLASH_ERASE_OK\n"); + return 1U; + } if (strncmp(cmd, "ui boot", 7) == 0) { LCD_BLK_Set(); LCD_WR_PIC_FROM_FLASH(0, 0, UI0902_BOOT_LOGO_W, UI0902_BOOT_LOGO_H, diff --git a/Global/Global.c b/Global/Global.c index 55b5826..a5a7210 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -25,8 +25,8 @@ GUI_SWITCH mGuiData[] = { }; GUI_SWITCH ParamGuiData[] = { - { 0, 0, 30, SONG_MODE_PARAM}, // 0 - { 0, 0, 30, EXPRESS_MODE_PARAM}, // 1 + { 0, 0, 30, SONG_MODE_PARAM}, // 0 节奏类型(1.bin,约 31 首) + { 0, 0, 0, EXPRESS_MODE_PARAM}, // 1 本地曲目(2.bin,先 1 首可扩) { 0, 0, 2, ALL_MODE_PARAM}, // 2 }; @@ -360,28 +360,49 @@ void Send_volume(uint8_t vol) SendMidiDataToDreamDSP(mMasterSysExVolume,12); } +void Accomp_UpdatePlayingChord(void) +{ + uint8_t midi_note_buff[4] = {0,}; + uint8_t note_cnt = 0; + uint8_t i; + + if (!StartFlag) + return; + if (KEY_ID_1629 == 0 || KEY_ID_1629 == 22 || KEY_ID_1629 == 23) + return; + + note_cnt = GetChordNotesByType(KEY_ID_1629, + chord_type_index_map[KEY_ID_1629].type, + midi_note_buff); + for (i = 0; i < note_cnt; i++) + AutoBandTop1_Note_On(midi_note_buff[i], 0x50); +} + void ADC_IN1_KEY_Handle(uint8_t key,bool on) { uint8_t midi_note_buff[4] = {0,}; uint8_t note_cnt = 0; + uint8_t is_preamble = 0; key_toggle[key] = !key_toggle[key]; if(key_toggle[key] == 1) { //ResetAutoPowerCount(); switch(key) { - case 3: AutoBandTop1_Preamble(0); break; - case 0: AutoBandTop1_Preamble(1); break; - case 1: AutoBandTop1_Postamble(0); break; - case 2: AutoBandTop1_Postamble(1); break; + case 3: AutoBandTop1_Preamble(0); is_preamble = 1; break; + case 0: AutoBandTop1_Preamble(1); is_preamble = 1; break; + case 1: AutoBandTop1_Postamble(0); break; + case 2: AutoBandTop1_Postamble(1); break; } //AutoBandTop1_Start(); AutoBandTop1_SyncStart(); wk_delay_ms(50); - //AutoBandTop1_Note_On(USE_MIDI.Midi_5, 0x50); - //AutoBandTop1_SetPiecePlayMode(1,0,1920); - //AutoBandTop1_StartWithNotes(midi_note_buff,GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff)); - //AutoBandTop1_SetLoopMode(0,1); + /* 音师:前奏起始 = 当前调一级和弦第一把位(index 1 + maj,再经移调) */ + if (is_preamble) + { + KEY_ID_1629 = 1; + chord_type_index_map[1].type = 0; + } note_cnt = GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff); for(uint8_t i = 0;i < note_cnt;i++) { @@ -880,6 +901,11 @@ void BT_Chord_offset_map(uint8_t pos_offset,uint8_t chord_offset) uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) { + uint8_t n = 0; + uint8_t i; + uint8_t root; + + buff[0] = buff[1] = buff[2] = buff[3] = 0; switch(MIDI_Index) { case 1:case 2:case 3: @@ -1109,49 +1135,41 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) break; } break; + default: + return 0; } - if(buff[3]!= 0) + + n = (buff[3] != 0) ? 4 : 3; + switch (chord_type_index_map[MIDI_Index].PitchOffset) { - switch(chord_type_index_map[MIDI_Index].PitchOffset) - { - case 1: - buff[0] += 0; - buff[1] += 0; - buff[2] += 0; - buff[3] += 0; - break; - case 2: - buff[0] += 1; - buff[1] += 1; - buff[2] += 1; - buff[3] += 1; - break; - } - buff[0] += mGuiData[GUI_TRANSPOSE].Current; - buff[1] += mGuiData[GUI_TRANSPOSE].Current; - buff[2] += mGuiData[GUI_TRANSPOSE].Current; - buff[3] += mGuiData[GUI_TRANSPOSE].Current; - return 4; - }else - { - switch(chord_type_index_map[MIDI_Index].PitchOffset) - { - case 1: - buff[0] += 0; - buff[1] += 0; - buff[2] += 0; - break; - case 2: - buff[0] += 1; - buff[1] += 1; - buff[2] += 1; - break; - } - buff[0] += mGuiData[GUI_TRANSPOSE].Current; - buff[1] += mGuiData[GUI_TRANSPOSE].Current; - buff[2] += mGuiData[GUI_TRANSPOSE].Current; - return 3; + case 2: /* # 升:整体 +1 半音 */ + for (i = 0; i < n; i++) + buff[i] += 1; + break; + default: + break; } + for (i = 0; i < n; i++) + buff[i] += mGuiData[GUI_TRANSPOSE].Current; + + /* 音师:所有调的 #四级 — 整组和弦低八度,音域压在一个八度内 */ + if (chord_type_index_map[MIDI_Index].PitchOffset == 2) + { + for (i = 0; i < n; i++) + { + if (buff[i] >= 12) + buff[i] -= 12; + } + root = buff[0]; + for (i = 1; i < n; i++) + { + while (buff[i] >= (uint8_t)(root + 12)) + buff[i] -= 12; + while (buff[i] < root) + buff[i] += 12; + } + } + return n; } void Read_Cur_Measure() diff --git a/Global/Global.h b/Global/Global.h index 368d4e1..62a6153 100644 --- a/Global/Global.h +++ b/Global/Global.h @@ -221,6 +221,8 @@ void Loop_Light_Handle(uint8_t measure); void BL_Set_led(uint8_t Led, uint8_t chord_type); uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff); +/* 伴奏已在播时仅更新和弦音,不 Restart(快转调) */ +void Accomp_UpdatePlayingChord(void); void BT_Chord_offset_map(uint8_t pos_offset,uint8_t chord_offset); void BT_Pitch_offset_map(uint8_t pos_offset,uint8_t pitch_offset); void Read_Cur_Measure(); diff --git a/UI/UI_Idle.c b/UI/UI_Idle.c index 4c83e32..4f3d7ae 100644 --- a/UI/UI_Idle.c +++ b/UI/UI_Idle.c @@ -113,7 +113,7 @@ void LoadConfig() { SendProgramChangeMidiEvent(0,Timbre_index[mGuiData[GUI_TIMBRE_SELECT].Current]); - uint8_t val_127 = (faders[0].val * 127) / 20; + uint8_t val_127 = (faders[0].val * 127) / 10; SendVolumeChangeMidiEvent(1,val_127); SendVolumeChangeMidiEvent(2,val_127); SendVolumeChangeMidiEvent(3,val_127); @@ -130,7 +130,7 @@ void LoadConfig() SendVolumeChangeMidiEvent(14,val_127); SendVolumeChangeMidiEvent(15,val_127); - SendVolumeChangeMidiEvent(0,faders[1].val); + SendVolumeChangeMidiEvent(0,(faders[1].val * 127) / 10); AW816_Set_Volume(MIC_VOL_MAP[faders[2].val]); @@ -147,25 +147,21 @@ void Touch_Mode_Select_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex) InModeFlag = true; /* 进入子页后抑制残留按下,否则易误触底栏 GUI_TAB_INDEX(id=5)→设置 */ app_touch_suppress(500); + /* 开机后再次确认弦音色(DSP 已就绪) */ + SendProgramChangeMidiEvent(0,Timbre_index[mGuiData[GUI_TIMBRE_SELECT].Current]); switch (ID) { - case 0: /* 万能 → 3.bin */ - if (mGuiData[GUI_AUTOBAND_SW].Current) - ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; - else - ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; + case 0: /* 万能 → 3.bin / 本地→2.bin */ + UI_ApplyToneAddress(); MenuPage_SongMode_Proc(); break; - case 1: /* 专业 → 1.bin */ - if (mGuiData[GUI_AUTOBAND_SW].Current) - ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; - else - ADDRESS = FLASH_ADDR_MODE_EXPERT; + case 1: /* 专业 → 1.bin / 本地→2.bin */ + UI_ApplyToneAddress(); MenuPage_ExpertMode_Proc(); break; case 2: /* 普通 → 1.bin */ - ADDRESS = FLASH_ADDR_MODE_NORMAL; + UI_ApplyToneAddress(); MenuPage_FreeMode_Proc(); break; default: diff --git a/UI/UI_SongMode.c b/UI/UI_SongMode.c index e596748..500471b 100644 --- a/UI/UI_SongMode.c +++ b/UI/UI_SongMode.c @@ -72,7 +72,6 @@ int8_t delta = 0; void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) { uint8_t last_value; - uint8_t midi_note_buff[4] = {0,}; NvmParam_Type * nvm = drv_nvm_param_ptr(); if(FLAG == 1 || FLAG == -1) { @@ -116,17 +115,9 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) } break; case 1: - { - AutoBandTop1_SetPiecePlayMode(1,0,480); // AutoBand 模块未加入工程 - AutoBandTop1_StartWithNotes(midi_note_buff,GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff)); - AutoBandTop1_SetLoopMode(0,1); - } - break; case 0: - { - AutoBandTop1_SetPiecePlayMode(1,0,480); // AutoBand 模块未加入工程 - AutoBandTop1_StartWithNotes(midi_note_buff,GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff)); - } + /* 音师:快转调只换和弦音,伴奏不重头 */ + Accomp_UpdatePlayingChord(); break; } } @@ -181,10 +172,12 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) case 0: case 1: if(mGuiData[GUI_AUTOBAND_SW].Current) { - AutoBandTop1_LoadPresetItemFromFlash(0); + UI_ApplyToneAddress(); + AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[EXPRESS_MODE_PARAM].Current); } else { + UI_ApplyToneAddress(); AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[SONG_MODE_PARAM].Current); } @@ -222,20 +215,7 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) if(ID == GUI_AUTOBAND_SW) { mGuiData[ID].Current = value; - - if(mGuiData[GUI_AUTOBAND_SW].Current) - { - ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; - }else - { - /* 关闭 AutoBand:回到当前 TAB 对应音色库 */ - switch (mGuiData[GUI_TAB_INDEX].Current) - { - case 1: ADDRESS = FLASH_ADDR_MODE_EXPERT; break; - case 2: ADDRESS = FLASH_ADDR_MODE_NORMAL; break; - default: ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; break; - } - } + UI_ReloadTonePreset(); if (!UI_SetFocus(GUI_MODE_PARAM)) label_ModeSelece_ui(&UI_Label[GUI_MODE_PARAM],&mGuiData[GUI_MODE_PARAM]); return; @@ -269,26 +249,20 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) app_tm1629_all_on(LED_COLOR_G); switch(mGuiData[ID].Current) { - case 0: /* 万能 → 3.bin */ + case 0: /* 万能 → 3.bin / 本地→2.bin */ if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) break; StartFlag = 0; AutoBandTop1_Stop(); - if(mGuiData[GUI_AUTOBAND_SW].Current) - ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; - else - ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; + UI_ApplyToneAddress(); break; - case 1: /* 专业 → 1.bin */ + case 1: /* 专业 → 1.bin / 本地→2.bin */ if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) break; StartFlag = 0; AutoBandTop1_Stop(); - if(mGuiData[GUI_AUTOBAND_SW].Current) - ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; - else - ADDRESS = FLASH_ADDR_MODE_EXPERT; + UI_ApplyToneAddress(); break; case 2: /* 普通 → 1.bin */ @@ -296,7 +270,7 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) break; StartFlag = 0; AutoBandTop1_Stop(); - ADDRESS = FLASH_ADDR_MODE_NORMAL; + UI_ApplyToneAddress(); break; } Refresh_TabSelect_ui(&UI_Label[ID], &mGuiData[ID]); @@ -325,7 +299,6 @@ void UI_SongMode_Init(void) void UI_SongMode_Process(DisplayTaskMessage_Type msg) { - uint8_t midi_note_buff[4] = {0,}; NvmParam_Type * nvm = drv_nvm_param_ptr(); switch(msg.MessageType) @@ -475,10 +448,8 @@ void UI_SongMode_Process(DisplayTaskMessage_Type msg) USE_MIDI.Midi_6 += delta; if(/*KEY_ID_1629 != 0 && */StartFlag && KEY_ID_1629 != 22 && KEY_ID_1629 != 23) { - AutoBandTop1_SetPiecePlayMode(1,0,480); - AutoBandTop1_StartWithNotes(midi_note_buff,GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff)); - AutoBandTop1_SetLoopMode(0,1); - AutoBandTop1_StartWithNotes(midi_note_buff,GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff)); + /* 音师:快转调只换和弦音,伴奏不重头 */ + Accomp_UpdatePlayingChord(); } ResetAutoPowerCount(); } diff --git a/UI/UI_global.c b/UI/UI_global.c index 30a914c..8ea1e29 100644 --- a/UI/UI_global.c +++ b/UI/UI_global.c @@ -109,6 +109,41 @@ void CallUI_RestoreSelect(void) { LOG_I("UI", "page Restore"); } +/* 音师:节奏类型→1.bin,本地曲目→2.bin,万能节奏→3.bin(不再用 AutoBand legacy) */ +void UI_ApplyToneAddress(void) +{ + if (mGuiData[GUI_AUTOBAND_SW].Current) + { + ADDRESS = FLASH_ADDR_SONG_HAITIAN; /* 本地曲目 → 2.bin */ + return; + } + switch (mGuiData[GUI_TAB_INDEX].Current) + { + case 1: ADDRESS = FLASH_ADDR_MODE_EXPERT; break; /* 专业 → 1.bin */ + case 2: ADDRESS = FLASH_ADDR_MODE_NORMAL; break; /* 普通 → 1.bin */ + default: ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; break; /* 万能 → 3.bin */ + } +} + +void UI_ReloadTonePreset(void) +{ + UI_ApplyToneAddress(); + if (mGuiData[GUI_AUTOBAND_SW].Current) + { + AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[EXPRESS_MODE_PARAM].Current); + return; + } + switch (mGuiData[GUI_TAB_INDEX].Current) + { + case 2: + AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); + break; + default: + AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[SONG_MODE_PARAM].Current); + break; + } +} + static uint8_t s_ui_focus_id = UI_FOCUS_NONE; static uint8_t UI_NormalizeFocusId(uint8_t widget_id) @@ -236,19 +271,15 @@ void UI_GotoCurrentModePage(void) switch (mGuiData[GUI_TAB_INDEX].Current) { case 0: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current - ? FLASH_ADDR_AUTOBAND_LEGACY - : FLASH_ADDR_MODE_UNIVERSAL; + UI_ApplyToneAddress(); MenuPage_SongMode_Proc(); break; case 1: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current - ? FLASH_ADDR_AUTOBAND_LEGACY - : FLASH_ADDR_MODE_EXPERT; + UI_ApplyToneAddress(); MenuPage_ExpertMode_Proc(); break; case 2: - ADDRESS = FLASH_ADDR_MODE_NORMAL; + UI_ApplyToneAddress(); MenuPage_FreeMode_Proc(); break; default: @@ -818,19 +849,15 @@ void UI_NavBar_Action(uint8_t value) switch (value) { case UI0902_NAV_UNIVERSAL: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current - ? FLASH_ADDR_AUTOBAND_LEGACY - : FLASH_ADDR_MODE_UNIVERSAL; + UI_ApplyToneAddress(); MenuPage_SongMode_Proc(); break; case UI0902_NAV_NORMAL: - ADDRESS = FLASH_ADDR_MODE_NORMAL; + UI_ApplyToneAddress(); MenuPage_FreeMode_Proc(); break; case UI0902_NAV_EXPERT: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current - ? FLASH_ADDR_AUTOBAND_LEGACY - : FLASH_ADDR_MODE_EXPERT; + UI_ApplyToneAddress(); MenuPage_ExpertMode_Proc(); break; case UI0902_NAV_SETTING: diff --git a/UI/UI_global.h b/UI/UI_global.h index 82a3cbe..f3c9a16 100644 --- a/UI/UI_global.h +++ b/UI/UI_global.h @@ -218,4 +218,7 @@ extern void RestoreSetting(); extern void Draw_System_Item_List(const LEBEL_UI *UI, GUI_SWITCH *Group); /* 子页顶栏:左返回 + 居中标题(调音台/系统设置) */ extern void UI_DrawSubPageHeader(const uint8_t *title_gbk); +/* 节奏类型→1.bin / 本地曲目→2.bin / 万能→3.bin */ +extern void UI_ApplyToneAddress(void); +extern void UI_ReloadTonePreset(void); #endif diff --git a/tools/erase_extflash_chip.py b/tools/erase_extflash_chip.py new file mode 100644 index 0000000..42b9dd1 --- /dev/null +++ b/tools/erase_extflash_chip.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Erase entire W25Q128 external Flash via RTT command: flash erase chip + +Requires firmware that implements the RTT command (APP/app_log.c). +Chip erase typically takes 30–120s; do not power-cycle mid-erase. +""" + +from __future__ import annotations + +import argparse +import time + +DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A") + + +def connect_jlink(device: str): + import pylink + + jlink = pylink.JLink() + jlink.open() + try: + jlink.exec_command("HideDeviceSelection = 1") + except Exception: + pass + jlink.set_tif(pylink.enums.JLinkInterfaces.SWD) + + last_error = None + for candidate in (device, *DEFAULT_DEVICES): + try: + try: + jlink.exec_command(f"Device = {candidate}") + except Exception: + pass + jlink.connect(candidate) + print(f"Connected as {candidate}") + return jlink + except Exception as exc: + last_error = exc + jlink.close() + raise SystemExit(f"Failed to connect J-Link: {last_error}") + + +def find_rtt_control_block(jlink, ram_base: int = 0x20000000, ram_size: int = 0x18000): + needle = b"SEGGER RTT" + chunk = 0x1000 + was_halted = jlink.halted() + if not was_halted: + jlink.halt() + try: + for off in range(0, ram_size, chunk): + data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off))) + idx = data.find(needle) + if idx >= 0: + return ram_base + off + idx + finally: + if not was_halted: + if hasattr(jlink, "restart"): + jlink.restart() + else: + jlink.go() + time.sleep(0.1) + return None + + +def rtt_read_text(jlink, timeout_s: float = 0.2) -> str: + deadline = time.time() + timeout_s + chunks: list[bytes] = [] + while time.time() < deadline: + try: + data = bytes(jlink.rtt_read(0, 512) or []) + except Exception: + data = b"" + if data: + chunks.append(data) + deadline = time.time() + timeout_s + else: + time.sleep(0.02) + return b"".join(chunks).decode("ascii", errors="replace") + + +def wait_for(jlink, marker: str, timeout_s: float) -> str: + deadline = time.time() + timeout_s + buf = "" + last_print = 0.0 + while time.time() < deadline: + chunk = rtt_read_text(jlink, 0.2) + if chunk: + buf += chunk + if marker in buf: + return buf + now = time.time() + if now - last_print >= 5.0: + elapsed = now - (deadline - timeout_s) + print(f" ... waiting erase ({elapsed:.0f}s)") + last_print = now + time.sleep(0.05) + raise SystemExit(f"Timeout waiting for {marker!r}\n--- RTT ---\n{buf[-1200:]}") + + +def send_bytes(jlink, payload: bytes) -> None: + off = 0 + while off < len(payload): + try: + n = jlink.rtt_write(0, list(payload[off : off + 64])) + except Exception: + time.sleep(0.05) + continue + if n is None or n <= 0: + time.sleep(0.02) + continue + off += n + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--device", default="Cortex-M4") + parser.add_argument("--no-reset", action="store_true") + parser.add_argument("--timeout", type=float, default=180.0, + help="seconds to wait for FLASH_ERASE_OK (default 180)") + args = parser.parse_args() + + jlink = connect_jlink(args.device) + try: + cb = find_rtt_control_block(jlink) + if cb is None: + raise SystemExit("RTT control block not found") + print(f"RTT CB @ 0x{cb:08X}") + jlink.rtt_start(cb) + time.sleep(0.3) + _ = rtt_read_text(jlink, 0.3) + + if not args.no_reset: + print("hardware reset...") + jlink.reset(halt=False) + time.sleep(3.5) + try: + jlink.rtt_stop() + except Exception: + pass + cb = find_rtt_control_block(jlink) + if cb is None: + raise SystemExit("RTT CB missing after reset") + print(f"RTT CB @ 0x{cb:08X}") + jlink.rtt_start(cb) + time.sleep(0.5) + _ = rtt_read_text(jlink, 0.5) + + print("Sending: flash erase chip") + send_bytes(jlink, b"flash erase chip\n") + log = wait_for(jlink, "FLASH_ERASE_BEGIN", 20.0) + print(log.strip().splitlines()[-1]) + print(f"Chip erase running (timeout {args.timeout:.0f}s)...") + log = wait_for(jlink, "FLASH_ERASE_OK", args.timeout) + print(log.strip().splitlines()[-1]) + print("ExtFlash chip erase done. Flash MCU + extflash_ALL*.res manually.") + finally: + try: + jlink.rtt_stop() + except Exception: + pass + jlink.close() + + +if __name__ == "__main__": + main()