Compare commits

...

9 Commits

Author SHA1 Message Date
yuquanjun c1b32c0351 Refresh git_user_fw_ver.h for 0.2.7 release commit.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 12:18:00 +08:00
yuquanjun bb6d9ae7f9 Release 0.2.7: refresh rhythm 1.bin pack with fixed BIN3 layout checks.
Update pack/publish to enforce 41KB song limit, verify 1/2/3.bin at fixed addresses, and emit .res aliases for the tone and ALL extflash images.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 12:17:42 +08:00
yuquanjun 99f7ed3e5a Pin BIN3 to fixed 0xA71AC and reject packs where 2.bin drift shifts it.
Variable 2.bin length previously moved 3.bin; firmware still read 0xA71AC and saw zeros. Pack now pads to the fixed slot and publish verifies DAB headers before shipping ALL.res.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 11:47:15 +08:00
yuquanjun cd21e4e45e Always enable loop on universal-mode pick so accompaniment can play.
Without SetLoopMode the piece path often produced no audible output when the chord pad was not held.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 11:35:14 +08:00
yuquanjun 92e5e32de3 Diagnose BIN3 LoadPreset failures and add flash bin3 tooling.
Log Flash magic on load failure, expose RTT flash bin3, and document the 0903 pack offset for the universal bank.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 11:32:40 +08:00
yuquanjun 015417dc3d Enlarge bottom nav touch zones to full-width 60x55 cells under the section buttons.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 11:01:40 +08:00
yuquanjun 6f2f7b6b75 Remove mode-select top stripe cover that left an extra line.
The ROW_BG full-width fill over the universal row top corners showed as a second separator under the status bar; source art no longer needs it.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 08:00:06 +08:00
yuquanjun ba110565f1 Use chevron asset for factory-reset row instead of solid triangle.
Matches 0902-07: restore is a drill-in row with thin > (ICON_CHEVRON), not the prev/next filled triangle.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 07:50:08 +08:00
yuquanjun ad0d990c55 Center indexed param/timbre labels as one block and nudge section digits.
Fixes the left-biased index+name layout, unifies index-name gap across rows, and shifts play-section numbers +2px per UI markup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 07:34:48 +08:00
12 changed files with 513 additions and 107 deletions

View File

@ -280,6 +280,12 @@ uint8_t app_log_try_command(const char *cmd)
"FLASH_HAITIAN_GO", "FLASH_HAITIAN_OK\n"); "FLASH_HAITIAN_GO", "FLASH_HAITIAN_OK\n");
return 1U; return 1U;
} }
if (strncmp(cmd, "flash bin3 ", 11) == 0) {
uint32_t size = (uint32_t)strtoul(cmd + 11, NULL, 10);
app_log_flash_start_at(EXTFLASH_BIN3_UNIVERSAL_ADDR, size,
"FLASH_BIN3_GO", "FLASH_BIN3_OK\n");
return 1U;
}
if (strncmp(cmd, "tone status", 11) == 0) { if (strncmp(cmd, "tone status", 11) == 0) {
uint8_t hdr[16]; uint8_t hdr[16];
uint8_t name[40]; uint8_t name[40];
@ -320,6 +326,16 @@ uint8_t app_log_try_command(const char *cmd)
(unsigned long)(cur_hdr[12] | (cur_hdr[13] << 8) | (unsigned long)(cur_hdr[12] | (cur_hdr[13] << 8) |
(cur_hdr[14] << 16) | (cur_hdr[15] << 24))); (cur_hdr[14] << 16) | (cur_hdr[15] << 24)));
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line); SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
{
uint8_t b3[16];
W25Q128_Read(b3, EXTFLASH_BIN3_UNIVERSAL_ADDR, 16);
snprintf(line, sizeof(line),
"TONE @BIN3 magic=%02X%02X%02X%02X cnt=%lu\n",
b3[0], b3[1], b3[2], b3[3],
(unsigned long)(b3[12] | (b3[13] << 8) |
(b3[14] << 16) | (b3[15] << 24)));
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
}
snprintf(line, sizeof(line), snprintf(line, sizeof(line),
"TONE @BIN2 magic=%02X%02X%02X%02X max=%lu cnt=%lu name=%.16s\n", "TONE @BIN2 magic=%02X%02X%02X%02X max=%lu cnt=%lu name=%.16s\n",
hdr[0], hdr[1], hdr[2], hdr[3], hdr[0], hdr[1], hdr[2], hdr[3],
@ -365,6 +381,20 @@ uint8_t app_log_try_command(const char *cmd)
return 1U; return 1U;
} }
if (strncmp(cmd, "tone pick", 9) == 0) { if (strncmp(cmd, "tone pick", 9) == 0) {
/* tone pick uni → 万能;默认仍测专业+本地曲目 */
if (cmd[9] == ' ' && (cmd[10] == 'u' || cmd[10] == 'U')) {
mGuiData[GUI_TAB_INDEX].Current = 0;
mGuiData[GUI_AUTOBAND_SW].Current = 0;
UI_ApplyToneAddress();
AutoBandTop1_Stop();
StartFlag = 0;
(void)AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[ALL_MODE_PARAM].Current);
KEY_ID_1629 = 0;
PressFlag = 0;
Pick_Handle();
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "TONE_PICK_UNI_DONE\n");
return 1U;
}
/* 模拟专业+本地曲目拨片(无和弦板) */ /* 模拟专业+本地曲目拨片(无和弦板) */
mGuiData[GUI_TAB_INDEX].Current = 1; mGuiData[GUI_TAB_INDEX].Current = 1;
mGuiData[GUI_AUTOBAND_SW].Current = 1; mGuiData[GUI_AUTOBAND_SW].Current = 1;

View File

@ -89,11 +89,11 @@ const Touch_AreaTypeDef Touch_Areas[] =
{ 126, 172, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 2, GUI_PLAY_SECTION, NULL }, { 126, 172, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 2, GUI_PLAY_SECTION, NULL },
{ 183, 229, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 3, GUI_PLAY_SECTION, NULL }, { 183, 229, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 3, GUI_PLAY_SECTION, NULL },
/* 底栏四键:万能 | 普通 | 专业 | 设置(触区中线对齐 cx 40/100/160/210 */ /* 底栏四键:万能 | 普通 | 专业 | 设置(60×55y265319 */
{ 0, 69, 280, 319, 0, UI0902_NAV_UNIVERSAL, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(0), UI0902_NAV_X1(0), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_UNIVERSAL, GUI_NAV_BAR, NULL },
{ 70, 129, 280, 319, 0, UI0902_NAV_NORMAL, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(1), UI0902_NAV_X1(1), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_NORMAL, GUI_NAV_BAR, NULL },
{ 130, 184, 280, 319, 0, UI0902_NAV_EXPERT, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(2), UI0902_NAV_X1(2), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_EXPERT, GUI_NAV_BAR, NULL },
{ 185, 239, 280, 319, 0, UI0902_NAV_SETTING, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(3), UI0902_NAV_X1(3), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_SETTING, GUI_NAV_BAR, NULL },
}; };
/* 推子 0~10 共 11 档档10 取 AW816 上限附近 */ /* 推子 0~10 共 11 档档10 取 AW816 上限附近 */
@ -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)); uint8_t TOUCH_AREA_NUM = (sizeof(Touch_Areas)/sizeof(Touch_AreaTypeDef));
const uint8_t Version[3] = {0,2,6}; /* major.minor.patch — 0.2.6: local song 2.bin from Doc/音色文件/0908/2ss_2.bin */ const uint8_t Version[3] = {0,2,7}; /* major.minor.patch — 0.2.7: 1.bin from Doc/音色文件/0909/31cs.bin; fixed BIN3 pack */
uint8_t Led = 8; uint8_t Led = 8;
bool PressFlag = 0; bool PressFlag = 0;
@ -783,15 +783,10 @@ void Pick_Handle()
AutoBandTop1_SetLoopMode(0,1); AutoBandTop1_SetLoopMode(0,1);
break; break;
case 0: case 0:
if(PressFlag) /* 设置页内沿用万能:与主路径一致,始终 loop */
{
AutoBandTop1_SetLoopMode(0,1);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
}else
{
AutoBandTop1_SetPiecePlayMode(1, 0, 480); AutoBandTop1_SetPiecePlayMode(1, 0, 480);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
} AutoBandTop1_SetLoopMode(0, 1);
StartFlag = 1; StartFlag = 1;
break; break;
} }
@ -842,17 +837,14 @@ void Pick_Handle()
} }
break; break;
case 0: case 0:
/* 万能:和弦走向用 piece 模式;须 SetLoopMode否则无循环时常听不到伴奏 */
StartFlag = 1; StartFlag = 1;
if(PressFlag)
{
AutoBandTop1_SetPiecePlayMode(1, 0, 480); AutoBandTop1_SetPiecePlayMode(1, 0, 480);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
AutoBandTop1_SetLoopMode(0, 1); AutoBandTop1_SetLoopMode(0, 1);
}else LOG_I("pick", "universal piece name=%s press=%u notes=%u chord=%u",
{ AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
AutoBandTop1_SetPiecePlayMode(1,0,480); (unsigned)PressFlag, (unsigned)note_cnt, (unsigned)chord_id);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
}
break; break;
} }
} }

View File

@ -148,8 +148,12 @@ void App_Auto_Init(void)
int ret = AutoBandTop1_LoadPresetItemFromFlash(0); int ret = AutoBandTop1_LoadPresetItemFromFlash(0);
if (ret != 0) if (ret != 0)
{ {
LOG_E("AUTO", "LoadPreset fail ret=%d addr=0x%08X map=%s", uint8_t hdr[16];
ret, (unsigned)ADDRESS, ToneFlashMapBank((uint32_t)ADDRESS)); W25Q128_Read(hdr, (uint32_t)ADDRESS, 16);
LOG_E("AUTO", "LoadPreset fail ret=%d addr=0x%08X map=%s magic=%02X%02X%02X%02X cnt=%lu",
ret, (unsigned)ADDRESS, ToneFlashMapBank((uint32_t)ADDRESS),
hdr[0], hdr[1], hdr[2], hdr[3],
(unsigned long)(hdr[12] | (hdr[13] << 8) | (hdr[14] << 16) | (hdr[15] << 24)));
} }
else else
{ {

View File

@ -43,12 +43,6 @@ static void UI_ModeSelect_DrawButtons(void)
LCD_WR_PIC_FROM_FLASH(UI0902_MODE_ROW_X, y, LCD_WR_PIC_FROM_FLASH(UI0902_MODE_ROW_X, y,
UI0902_MODE_ROW_W, UI0902_MODE_ROW_H, UI0902_MODE_ROW_W, UI0902_MODE_ROW_H,
s_mode_row_addr[i]); s_mode_row_addr[i]);
/* 万能模式素材顶部有残留近白横线(相对行内 y=7/8盖成行底色 */
if (i == 0) {
LCD_FillByColor(UI0902_MODE_ROW_X, (uint16_t)(y + 7),
(uint16_t)(UI0902_MODE_ROW_X + UI0902_MODE_ROW_W),
(uint16_t)(y + 9), UI0902_ROW_BG);
}
} }
} }

View File

@ -11,11 +11,11 @@ const Touch_AreaTypeDef Touch_SetingSelect_Areas[] = {
{ UI0902_HUB_CARD_X, (uint16_t)(UI0902_HUB_CARD_X + UI0902_HUB_CARD_W - 1), { UI0902_HUB_CARD_X, (uint16_t)(UI0902_HUB_CARD_X + UI0902_HUB_CARD_W - 1),
UI0902_HUB_CARD_Y1, (uint16_t)(UI0902_HUB_CARD_Y1 + UI0902_HUB_CARD_H - 1), UI0902_HUB_CARD_Y1, (uint16_t)(UI0902_HUB_CARD_Y1 + UI0902_HUB_CARD_H - 1),
0, 0, GUI_SETING_SELECT3, NULL }, 0, 0, GUI_SETING_SELECT3, NULL },
/* 底栏四键 */ /* 底栏四键:与 Touch_Areas 共用 UI0902_NAV_* 几何 */
{ 0, 69, 280, 319, 0, UI0902_NAV_UNIVERSAL, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(0), UI0902_NAV_X1(0), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_UNIVERSAL, GUI_NAV_BAR, NULL },
{ 70, 129, 280, 319, 0, UI0902_NAV_NORMAL, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(1), UI0902_NAV_X1(1), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_NORMAL, GUI_NAV_BAR, NULL },
{ 130, 184, 280, 319, 0, UI0902_NAV_EXPERT, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(2), UI0902_NAV_X1(2), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_EXPERT, GUI_NAV_BAR, NULL },
{ 185, 239, 280, 319, 0, UI0902_NAV_SETTING, GUI_NAV_BAR, NULL }, { UI0902_NAV_X0(3), UI0902_NAV_X1(3), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_SETTING, GUI_NAV_BAR, NULL },
}; };
#define TOUCH_AREA_SETING_SELECT_NUM (sizeof(Touch_SetingSelect_Areas)/sizeof(Touch_AreaTypeDef)) #define TOUCH_AREA_SETING_SELECT_NUM (sizeof(Touch_SetingSelect_Areas)/sizeof(Touch_AreaTypeDef))

View File

@ -168,10 +168,23 @@ void UI_ReloadTonePreset(void)
if (mGuiData[GUI_TAB_INDEX].Current == 0) if (mGuiData[GUI_TAB_INDEX].Current == 0)
{ {
int ret = AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); int ret = AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[ALL_MODE_PARAM].Current);
if (ret != 0)
{
uint8_t hdr[16];
W25Q128_Read(hdr, (uint32_t)ADDRESS, 16);
LOG_E("tone", "load universal idx=%d ret=%d magic=%02X%02X%02X%02X cnt=%lu addr=0x%08X",
ParamGuiData[ALL_MODE_PARAM].Current, ret,
hdr[0], hdr[1], hdr[2], hdr[3],
(unsigned long)(hdr[12] | (hdr[13] << 8) | (hdr[14] << 16) | (hdr[15] << 24)),
(unsigned)ADDRESS);
}
else
{
LOG_I("tone", "load universal idx=%d ret=%d name=%s addr=0x%08X map=BIN3@0x%08X", LOG_I("tone", "load universal idx=%d ret=%d name=%s addr=0x%08X map=BIN3@0x%08X",
ParamGuiData[ALL_MODE_PARAM].Current, ret, ParamGuiData[ALL_MODE_PARAM].Current, ret,
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)ADDRESS, (unsigned)EXTFLASH_BIN3_UNIVERSAL_ADDR); (unsigned)ADDRESS, (unsigned)EXTFLASH_BIN3_UNIVERSAL_ADDR);
}
return; return;
} }
/* 普通/专业:本地曲目或节奏类型 */ /* 普通/专业:本地曲目或节奏类型 */
@ -443,6 +456,58 @@ void Show_Battery_Icon(uint8_t flag)
} }
/* 中英文混排像素宽(与 LCD_GetMixedStringWidth 规则一致CJK=sizeyASCII=UI0902_ASCII_W */
static uint16_t UI_MixedTextWidth(const uint8_t *str, uint8_t sizey)
{
uint16_t width = 0;
uint8_t ascii_w = UI0902_ASCII_W(sizey);
if (str == NULL)
return 0;
while (*str != '\0') {
if (*str > 0x80) {
width = (uint16_t)(width + sizey);
str += 2;
} else {
width = (uint16_t)(width + ascii_w);
str += 1;
}
}
return width;
}
/* 曲目/音色共用序号与名称间距与「1.名称」拼接一致,保证两行视觉间距相同) */
#define UI0902_INDEX_NAME_GAP 0
/* 在 [x1,x2] 内整体居中绘制「N.」+ 名称无渐变show_Param 用) */
static void UI_DrawIndexedName_Mixed(uint16_t x1, uint16_t x2, uint16_t text_y,
const char *num, const char *name,
uint16_t fc, uint16_t bc, uint8_t sizey)
{
char line[48] = {0};
snprintf(line, sizeof(line), "%s%s", num, name);
LCD_ShowMixedString(x1, x2, text_y, (uint8_t *)line, LCD_ALIGN_CENTER,
fc, bc, sizey, 1);
}
/* 在 [x1,x2] 内整体居中绘制「N.」+ 中文名(渐变底,音色行用);间隙与曲目行一致 */
static void UI_DrawIndexedName_Grad(uint16_t x1, uint16_t x2, uint16_t text_y,
const char *num, const uint8_t *name,
uint16_t fc, uint16_t c_top, uint16_t c_bot, uint8_t sizey)
{
uint16_t num_w = (uint16_t)(strlen(num) * UI0902_ASCII_W(sizey));
uint16_t name_w = UI_MixedTextWidth(name, sizey);
uint16_t total_w = (uint16_t)(num_w + UI0902_INDEX_NAME_GAP + name_w);
uint16_t area_w = (x2 > x1) ? (uint16_t)(x2 - x1) : 0;
uint16_t start_x = (total_w >= area_w) ? x1 : (uint16_t)(x1 + (area_w - total_w) / 2);
uint16_t name_x = (uint16_t)(start_x + num_w + UI0902_INDEX_NAME_GAP);
LCD_ShowString_AutoAlign_Gradient(start_x, (uint16_t)(start_x + num_w), text_y,
(const uint8_t *)num, LCD_ALIGN_LEFT, fc, c_top, c_bot, sizey);
LCD_ShowChinese_AutoAlign_Gradient(name_x, x2, text_y, name, LCD_ALIGN_LEFT,
fc, c_top, c_bot, sizey);
}
void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group) void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group)
{ {
char Textstr_new[40] = {0}; char Textstr_new[40] = {0};
@ -453,6 +518,7 @@ void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group)
const uint16_t text_y = row_y + (row_h - UI->Text_size) / 2 + 1; const uint16_t text_y = row_y + (row_h - UI->Text_size) / 2 + 1;
uint8_t idx = Group->Current + 1; uint8_t idx = Group->Current + 1;
uint16_t c_top, c_bot; uint16_t c_top, c_bot;
uint16_t x1, x2;
UI_GetFocusGrad(GUI_MODE_PARAM, &c_top, &c_bot); UI_GetFocusGrad(GUI_MODE_PARAM, &c_top, &c_bot);
@ -464,18 +530,16 @@ void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group)
c_top, c_bot c_top, c_bot
); );
// 拼接序号 1. 2. 3. /* 序号+名称整体居中约等于标注「右移7px」的视觉效果 */
sprintf(NumBuf, "%d.", idx); sprintf(NumBuf, "%d.", idx);
uint16_t num_w = (uint16_t)(strlen(NumBuf) * UI0902_ASCII_W(UI->Text_size));
if (Group->Id == EXPRESS_MODE_PARAM && Group->Current < LOCAL_SONG_COUNT) if (Group->Id == EXPRESS_MODE_PARAM && Group->Current < LOCAL_SONG_COUNT)
strcpy(Textstr_new, LocalSongNameGbk[Group->Current]); strcpy(Textstr_new, LocalSongNameGbk[Group->Current]);
else else
GET_ParamNum_Str(Group->Current, 0, Textstr_new, Group->Id); GET_ParamNum_Str(Group->Current, 0, Textstr_new, Group->Id);
uint32_t string_start_x = LCD_ShowMixedString(UI->x+10, UI->x + UI->w, text_y, (uint8_t *)Textstr_new , LCD_ALIGN_CENTER, WHITE, BLACK, UI->Text_size, 1); x1 = (uint16_t)(UI->x + 10);
//序号X = 中文起点 - 数字宽度 - 5<EFBC8C>?像素 x2 = (uint16_t)(UI->x + UI->w - 10);
uint16_t num_x = string_start_x - num_w; UI_DrawIndexedName_Mixed(x1, x2, text_y, NumBuf, Textstr_new,
// 绘制左侧数字序号 WHITE, BLACK, UI->Text_size);
LCD_ShowString(num_x, text_y, (const uint8_t *)NumBuf, UI->Text_fc, UI->Text_bc, UI->Text_size, 1);
} }
@ -851,22 +915,17 @@ void label_TimbreSelece_ui(const LEBEL_UI *UI, GUI_SWITCH * Group)
c_top, c_bot c_top, c_bot
); );
sprintf(NumBuf, "%d.", idx); sprintf(NumBuf, "%d.", idx);
uint16_t num_w = (uint16_t)(strlen(NumBuf) * UI0902_ASCII_W(UI->Text_size));
GET_Num_Str(mGuiData[UI->Id].Current, 0, (char*)Textstr, UI->Id); GET_Num_Str(mGuiData[UI->Id].Current, 0, (char*)Textstr, UI->Id);
uint32_t chinese_start_x = LCD_ShowChinese_AutoAlign_Gradient( UI_DrawIndexedName_Grad(
UI->x + num_w, (uint16_t)(UI->x + 10),
UI->x + UI->w, (uint16_t)(UI->x + UI->w - 10),
text_y, text_y,
(const uint8_t*)Textstr, NumBuf,
UI->Text_Align, Textstr,
UI->Text_fc, UI->Text_fc,
c_top, c_bot, c_top, c_bot,
UI->Text_size UI->Text_size
); );
uint16_t num_x = chinese_start_x - num_w - 5;
LCD_ShowString_AutoAlign_Gradient(num_x, num_x + num_w, text_y,
(const uint8_t *)NumBuf, LCD_ALIGN_LEFT, UI->Text_fc,
c_top, c_bot, UI->Text_size);
switch(Group->Id) switch(Group->Id)
{ {
case GUI_TIMBRE_SELECT: case GUI_TIMBRE_SELECT:
@ -988,7 +1047,7 @@ void Draw_Tab_Section_Menu(const LEBEL_UI *UI, TAB_SECTION_INDEX select_tab)
const uint8_t pitch = UI0902_SECTION_PITCH; const uint8_t pitch = UI0902_SECTION_PITCH;
const uint16_t start_x = UI0902_MARGIN_X; /* 0902-0412/69/126/183 */ const uint16_t start_x = UI0902_MARGIN_X; /* 0902-0412/69/126/183 */
const uint16_t start_y = UI->y; const uint16_t start_y = UI->y;
/* 0819演奏段落数字与标题同为 13按钮内垂直居中视觉重心 +1 */ /* 0819演奏段落数字与标题同为 13按钮内垂直居中视觉重心 +1;稿面数字整体右移 2px */
const uint16_t text_y = start_y + (tab_h - UI0902_FONT_BODY) / 2 + 1; const uint16_t text_y = start_y + (tab_h - UI0902_FONT_BODY) / 2 + 1;
const char *nums[4] = {"1", "2", "3", "4"}; const char *nums[4] = {"1", "2", "3", "4"};
uint8_t i; uint8_t i;
@ -999,7 +1058,7 @@ void Draw_Tab_Section_Menu(const LEBEL_UI *UI, TAB_SECTION_INDEX select_tab)
uint16_t bg = (select_tab == i) ? COLOR_GRAD_MID : UI0902_ROW_BG; uint16_t bg = (select_tab == i) ? COLOR_GRAD_MID : UI0902_ROW_BG;
LCD_FillRoundRect(x, start_y, tab_w, tab_h, tab_r, bg); LCD_FillRoundRect(x, start_y, tab_w, tab_h, tab_r, bg);
LCD_ShowString_AutoAlign( LCD_ShowString_AutoAlign(
x, (uint16_t)(x + tab_w - 1), text_y, (uint16_t)(x + 2), (uint16_t)(x + tab_w - 1 + 2), text_y,
(const uint8_t*)nums[i], (const uint8_t*)nums[i],
UI->Text_Align, UI->Text_Align,
WHITE, WHITE,
@ -1269,8 +1328,12 @@ void Draw_System_Item_List(const LEBEL_UI *UI, GUI_SWITCH *Group)
(uint16_t)(UI->y + (UI->h - UI0902_ICON_SYS_RESTORE_H) / 2), (uint16_t)(UI->y + (UI->h - UI0902_ICON_SYS_RESTORE_H) / 2),
UI0902_ICON_SYS_RESTORE_W, UI0902_ICON_SYS_RESTORE_H, UI0902_ICON_SYS_RESTORE_W, UI0902_ICON_SYS_RESTORE_H,
UI0902_ICON_SYS_RESTORE_ADDR); UI0902_ICON_SYS_RESTORE_ADDR);
/* 右三角与蓝牙/自动关机同一右缘val_x2 */ /* 0902-07右侧为进入下级页的细 chevron设置图标-03勿用实心三角 */
UI_DrawTriRight((uint16_t)(val_x2 - UI0902_TRI_R_W), chev_cy); LCD_WR_PIC_FROM_FLASH_Trans(
(uint16_t)(val_x2 - UI0902_ICON_CHEVRON_W),
(uint16_t)(UI->y + (UI->h - UI0902_ICON_CHEVRON_H) / 2),
UI0902_ICON_CHEVRON_W, UI0902_ICON_CHEVRON_H,
UI0902_ICON_CHEVRON_ADDR);
break; break;
default: break; default: break;
} }

View File

@ -173,6 +173,12 @@ void CallUI_RestoreSelect(void);
#define UI0902_NAV_EXPERT 2 /* 底栏:专业模式 */ #define UI0902_NAV_EXPERT 2 /* 底栏:专业模式 */
#define UI0902_NAV_SETTING 3 /* 底栏:设置 */ #define UI0902_NAV_SETTING 3 /* 底栏:设置 */
#define UI0902_NAV_NONE 0xFF #define UI0902_NAV_NONE 0xFF
/* 底栏触区:段落钮底(y264)之下四等分全宽,各 60×55 */
#define UI0902_NAV_Y0 265
#define UI0902_NAV_Y1 319
#define UI0902_NAV_W 60
#define UI0902_NAV_X0(i) ((uint16_t)((i) * UI0902_NAV_W))
#define UI0902_NAV_X1(i) ((uint16_t)((i) * UI0902_NAV_W + UI0902_NAV_W - 1))
/* 兼容旧名(已废弃三键:设置|调音台|模式) */ /* 兼容旧名(已废弃三键:设置|调音台|模式) */
#define UI0902_NAV_MIXER UI0902_NAV_NORMAL #define UI0902_NAV_MIXER UI0902_NAV_NORMAL
#define UI0902_NAV_MODE UI0902_NAV_EXPERT #define UI0902_NAV_MODE UI0902_NAV_EXPERT

View File

@ -18,7 +18,7 @@
#define EXTFLASH_CHARGING_H 190 #define EXTFLASH_CHARGING_H 190
#define EXTFLASH_BIN1_RHYTHM_ADDR 0x0001B8F0UL #define EXTFLASH_BIN1_RHYTHM_ADDR 0x0001B8F0UL
#define EXTFLASH_BIN1_RHYTHM_SIZE 530317UL #define EXTFLASH_BIN1_RHYTHM_SIZE 530273UL
#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x0009D07DUL #define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x0009D07DUL
#define EXTFLASH_BIN2_SONG_HAITIAN_SIZE 41263UL #define EXTFLASH_BIN2_SONG_HAITIAN_SIZE 41263UL

View File

@ -1,16 +1,16 @@
/* Auto-generated by tools/gen_git_user_fw_ver.py — do not edit. /* Auto-generated by tools/gen_git_user_fw_ver.py — do not edit.
* git HEAD (dirty): 57448b3da8a3f912503e9d50313b5ec635888b69 * git HEAD (dirty): bb6d9ae7f9987ec6cfb3ab50579951d2b58ed888
* branch: develop develop * branch: develop develop
* 01 0C wire: develop_57448b* * 01 0C wire: develop_bb6d9a*
*/ */
#ifndef GIT_USER_FW_VER_H #ifndef GIT_USER_FW_VER_H
#define GIT_USER_FW_VER_H #define GIT_USER_FW_VER_H
#define GIT_COMMIT_ID_FULL "57448b3da8a3f912503e9d50313b5ec635888b69" #define GIT_COMMIT_ID_FULL "bb6d9ae7f9987ec6cfb3ab50579951d2b58ed888"
#define GIT_BRANCH_NAME "develop" #define GIT_BRANCH_NAME "develop"
#define GIT_COMMIT_SHORT6 "57448b" #define GIT_COMMIT_SHORT6 "bb6d9a"
#define GIT_DIRTY (1) #define GIT_DIRTY (1)
#define GIT_BUILD_ID "develop_57448b*" #define GIT_BUILD_ID "develop_bb6d9a*"
#define GIT_BUILD_ID_LEN 15u #define GIT_BUILD_ID_LEN 15u
#endif /* GIT_USER_FW_VER_H */ #endif /* GIT_USER_FW_VER_H */

View File

@ -0,0 +1,195 @@
#!/usr/bin/env python3
"""Flash Doc/音色文件/0903/3.bin to W25Q128 @ EXTFLASH_BIN3_UNIVERSAL_ADDR via RTT.
Requires firmware that accepts RTT command: flash bin3 <size>
"""
from __future__ import annotations
import argparse
import os
import time
DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A")
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
REPO = os.path.dirname(os.path.dirname(ROOT))
DEFAULT_BIN = os.path.join(REPO, "Doc", "音色文件", "0903", "3.bin")
EXTFLASH_BIN3_UNIVERSAL_ADDR = 0x000A71AC
DAB_MAGIC = bytes((0xAB, 0x44, 0x41, 0x42))
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 = 30.0) -> str:
deadline = time.time() + timeout_s
buf = ""
while time.time() < deadline:
buf += rtt_read_text(jlink, 0.15)
if marker in buf:
return buf
raise SystemExit(f"Timeout waiting for {marker!r}\n--- RTT ---\n{buf[-800:]}")
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("--bin", default=DEFAULT_BIN)
parser.add_argument("--device", default="Cortex-M4")
parser.add_argument("--no-reset", action="store_true")
args = parser.parse_args()
if not os.path.isfile(args.bin):
raise SystemExit(f"missing bin: {args.bin}")
data = open(args.bin, "rb").read()
size = len(data)
if size < 16 or data[:4] != DAB_MAGIC:
raise SystemExit(f"not a DAB bank (magic={data[:4].hex()})")
print(f"bin={args.bin} size={size} addr=0x{EXTFLASH_BIN3_UNIVERSAL_ADDR:08X}")
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)
cmd = f"flash bin3 {size}\n".encode("ascii")
send_bytes(jlink, cmd)
print("sent flash command, waiting GO...")
try:
log = wait_for(jlink, "FLASH_BIN3_GO", 20.0)
except SystemExit:
raise SystemExit(
"Firmware did not accept 'flash bin3'. Rebuild App with app_log.c "
"support, or temporarily flash via: concatenate 2.bin+3.bin and "
"'flash haitian <combined_size>'."
)
print(log.strip().splitlines()[-1])
time.sleep(0.2)
chunk = 256
sent = 0
t0 = time.time()
while sent < size:
end = min(sent + chunk, size)
send_bytes(jlink, data[sent:end])
sent = end
log = wait_for(jlink, "FLASH_ACK", 60.0)
if "FLASH_BIN3_OK" in log:
print(log.strip().splitlines()[-1])
print("Done. Universal bank (3.bin) updated.")
return
if sent % (4 * 1024) == 0 or sent == size:
elapsed = time.time() - t0
print(f" {sent}/{size} ({100.0 * sent / size:.1f}%) {elapsed:.1f}s")
log = wait_for(jlink, "FLASH_BIN3_OK", 60.0)
print(log.strip().splitlines()[-1])
print("Done. Universal bank (3.bin) updated.")
finally:
try:
jlink.rtt_stop()
except Exception:
pass
jlink.close()
if __name__ == "__main__":
main()

View File

@ -2,21 +2,23 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Pack external W25Q128 tone/logo image for K1 (0903). """Pack external W25Q128 tone/logo image for K1 (0903).
Layout (absolute W25Q128 offsets): Layout (absolute W25Q128 offsets BIN2/BIN3 are FIXED so FW addresses stay stable):
0x00000000 logo.bin (legacy pad; boot UI uses UI0902 full-screen logo) 0x00000000 logo.bin (legacy pad; boot UI uses UI0902 full-screen logo)
0x0000CB70 Charg.bin (legacy pad; charge UI uses UI0902_CHARGE_SCREEN full-screen) 0x0000CB70 Charg.bin (legacy pad; charge UI uses UI0902_CHARGE_SCREEN full-screen)
0x0001B8F0 1.bin 普通/专业 31 rhythms 0x0001B8F0 1.bin 普通/专业 31 rhythms
0x0009D07D 2.bin 海阔天空 0x0009D07D 2.bin 本地曲目变长尾部 0xFF 填到 BIN3<=41KB
0x000A8607 3.bin 万能模式 0x000A71AC 3.bin 万能模式固定勿随 2.bin 长度漂移
Outputs: Outputs:
tools/out/extflash_tone_0903.bin tools/out/extflash_tone_0903.bin
tools/out/extflash_tone_0903.res (same bytes, .res alias)
tools/out/extflash_ALL_tone0903_ui0902.res (tone + pad + ui0902, if ui pack present)
project/inc/ExtFlash_Tone_Addr.h project/inc/ExtFlash_Tone_Addr.h
Doc/音色文件/0903/FLASH_MAP.txt Doc/音色文件/0903/FLASH_MAP.txt
""" """
from __future__ import annotations from __future__ import annotations
import os import struct
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
@ -25,11 +27,25 @@ ROOT = Path(__file__).resolve().parents[1] # firmware project root
REPO = ROOT.parent.parent # 一诺国际吉他 REPO = ROOT.parent.parent # 一诺国际吉他
OUT_DIR = ROOT / "tools" / "out" OUT_DIR = ROOT / "tools" / "out"
OUT_BIN = OUT_DIR / "extflash_tone_0903.bin" OUT_BIN = OUT_DIR / "extflash_tone_0903.bin"
OUT_RES = OUT_DIR / "extflash_tone_0903.res"
OUT_ALL_RES = OUT_DIR / "extflash_ALL_tone0903_ui0902.res"
OUT_HDR = ROOT / "project" / "inc" / "ExtFlash_Tone_Addr.h" OUT_HDR = ROOT / "project" / "inc" / "ExtFlash_Tone_Addr.h"
OUT_MAP = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt" OUT_MAP = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
UI0902_BIN = OUT_DIR / "ui0902_res.bin"
TONE_DIR = REPO / "Doc" / "音色文件" / "0903" TONE_DIR = REPO / "Doc" / "音色文件" / "0903"
# Fixed map — must match project/inc/ExtFlash_Tone_Addr.h consumed by firmware.
OFF_LOGO = 0x00000000
OFF_CHARGING = 0x0000CB70
OFF_BIN1 = 0x0001B8F0
OFF_BIN2 = 0x0009D07D
OFF_BIN3 = 0x000A71AC # FIXED: never place 3.bin by concatenating after variable 2.bin
UI0902_RES_BASE = 0x00100000
DAB_MAGIC = b"\xABDAB"
MAX_SONG_BIN_BYTES = 41 * 1024 # 曲目文件(2.bin)硬上限 41KB
BIN2_SLOT_BYTES = OFF_BIN3 - OFF_BIN2 # 41263; must keep BIN3 fixed
def find_ziliao() -> Path: def find_ziliao() -> Path:
for p in REPO.iterdir(): for p in REPO.iterdir():
@ -38,29 +54,66 @@ def find_ziliao() -> Path:
raise FileNotFoundError("资料/logo.bin + Charg.bin not found under repo") raise FileNotFoundError("资料/logo.bin + Charg.bin not found under repo")
def dab_ok(blob: bytes, off: int, expect_cnt: int | None = None) -> None:
if off + 16 > len(blob):
raise SystemExit(f"DAB check @0x{off:X}: past end of pack ({len(blob)})")
magic = blob[off : off + 4]
cnt = struct.unpack_from("<I", blob, off + 12)[0]
if magic != DAB_MAGIC:
raise SystemExit(
f"DAB check @0x{off:X}: bad magic {magic.hex()} (want {DAB_MAGIC.hex()})"
)
if expect_cnt is not None and cnt != expect_cnt:
raise SystemExit(f"DAB check @0x{off:X}: cnt={cnt} want {expect_cnt}")
def require_region_equals(blob: bytes, off: int, src: bytes, label: str) -> None:
end = off + len(src)
if end > len(blob):
raise SystemExit(f"{label}: pack too short for region @0x{off:X}+{len(src)}")
if blob[off:end] != src:
raise SystemExit(f"{label}: bytes @0x{off:X} do not match source file")
def main() -> None: def main() -> None:
ziliao = find_ziliao() ziliao = find_ziliao()
bin1_path = TONE_DIR / "1.bin"
bin2_path = TONE_DIR / "2.bin"
bin3_path = TONE_DIR / "3.bin"
parts = [ parts = [
("LOGO", ziliao / "logo.bin", 0x00000000, "legacy pad; boot uses UI0902_BOOT_LOGO"), ("LOGO", ziliao / "logo.bin", OFF_LOGO, "legacy pad; boot uses UI0902_BOOT_LOGO"),
("CHARGING", ziliao / "Charg.bin", 0x0000CB70, "legacy pad; UI uses UI0902_CHARGE_SCREEN"), ("CHARGING", ziliao / "Charg.bin", OFF_CHARGING, "legacy pad; UI uses UI0902_CHARGE_SCREEN"),
("BIN1_RHYTHM", TONE_DIR / "1.bin", 0x0001B8F0, "普通/专业 31 条节奏"), ("BIN1_RHYTHM", bin1_path, OFF_BIN1, "普通/专业 31 条节奏"),
("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", None, "本地曲目 海阔天空"), ("BIN2_SONG_HAITIAN", bin2_path, OFF_BIN2, "本地曲目"),
("BIN3_UNIVERSAL", TONE_DIR / "3.bin", None, "万能模式"), ("BIN3_UNIVERSAL", bin3_path, OFF_BIN3, "万能模式(固定偏移)"),
] ]
blobs = [] bin2_data = bin2_path.read_bytes()
if len(bin2_data) > MAX_SONG_BIN_BYTES:
raise SystemExit(
f"2.bin (曲目) too large: {len(bin2_data)} bytes > {MAX_SONG_BIN_BYTES} (41KB)"
)
if len(bin2_data) > BIN2_SLOT_BYTES:
raise SystemExit(
f"2.bin too large for fixed BIN3 slot: {len(bin2_data)} > {BIN2_SLOT_BYTES} "
f"(would shift 3.bin past 0x{OFF_BIN3:X})"
)
blobs: list[bytes] = []
cursor = 0 cursor = 0
rows = [] rows: list[tuple[str, int, int, str]] = []
for name, path, force_off, note in parts: for name, path, force_off, note in parts:
data = path.read_bytes() data = path.read_bytes()
if force_off is not None:
if cursor > force_off: if cursor > force_off:
raise SystemExit(f"{name}: cursor 0x{cursor:X} past forced 0x{force_off:X}") raise SystemExit(
f"{name}: cursor 0x{cursor:X} past forced 0x{force_off:X} "
f"(previous blob too large; enlarge next fixed gap or shrink prior file)"
)
if cursor < force_off: if cursor < force_off:
pad = force_off - cursor pad = force_off - cursor
blobs.append(b"\xFF" * pad) blobs.append(b"\xFF" * pad)
rows.append(("(pad)", cursor, pad, "gap fill 0xFF"))
cursor = force_off cursor = force_off
rows.append((f"(pad)", force_off - pad, pad, "gap fill 0xFF"))
off = cursor off = cursor
blobs.append(data) blobs.append(data)
cursor += len(data) cursor += len(data)
@ -69,9 +122,25 @@ def main() -> None:
OUT_DIR.mkdir(parents=True, exist_ok=True) OUT_DIR.mkdir(parents=True, exist_ok=True)
packed = b"".join(blobs) packed = b"".join(blobs)
OUT_BIN.write_bytes(packed) OUT_BIN.write_bytes(packed)
OUT_RES.write_bytes(packed) # 总音色 .bin 同步为 .res
# named lookup
by_name = {r[0]: r for r in rows if not r[0].startswith("(")} by_name = {r[0]: r for r in rows if not r[0].startswith("(")}
bin1_data = bin1_path.read_bytes()
bin3_data = bin3_path.read_bytes()
# Hard integrity gates
dab_ok(packed, OFF_BIN1, expect_cnt=31)
dab_ok(packed, OFF_BIN2, expect_cnt=1)
dab_ok(packed, OFF_BIN3, expect_cnt=3)
if by_name["BIN1_RHYTHM"][1] != OFF_BIN1:
raise SystemExit(f"BIN1 placed @0x{by_name['BIN1_RHYTHM'][1]:X} want 0x{OFF_BIN1:X}")
if by_name["BIN2_SONG_HAITIAN"][1] != OFF_BIN2:
raise SystemExit(f"BIN2 placed @0x{by_name['BIN2_SONG_HAITIAN'][1]:X} want 0x{OFF_BIN2:X}")
if by_name["BIN3_UNIVERSAL"][1] != OFF_BIN3:
raise SystemExit(f"BIN3 placed @0x{by_name['BIN3_UNIVERSAL'][1]:X} want 0x{OFF_BIN3:X}")
require_region_equals(packed, OFF_BIN1, bin1_data, "BIN1/1.bin")
require_region_equals(packed, OFF_BIN2, bin2_data, "BIN2/2.bin")
require_region_equals(packed, OFF_BIN3, bin3_data, "BIN3/3.bin")
hdr = f"""#ifndef __EXTFLASH_TONE_ADDR_H hdr = f"""#ifndef __EXTFLASH_TONE_ADDR_H
#define __EXTFLASH_TONE_ADDR_H #define __EXTFLASH_TONE_ADDR_H
@ -92,13 +161,13 @@ def main() -> None:
#define EXTFLASH_CHARGING_W 160 #define EXTFLASH_CHARGING_W 160
#define EXTFLASH_CHARGING_H 190 #define EXTFLASH_CHARGING_H 190
#define EXTFLASH_BIN1_RHYTHM_ADDR 0x{by_name['BIN1_RHYTHM'][1]:08X}UL #define EXTFLASH_BIN1_RHYTHM_ADDR 0x{OFF_BIN1:08X}UL
#define EXTFLASH_BIN1_RHYTHM_SIZE {by_name['BIN1_RHYTHM'][2]}UL #define EXTFLASH_BIN1_RHYTHM_SIZE {by_name['BIN1_RHYTHM'][2]}UL
#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x{by_name['BIN2_SONG_HAITIAN'][1]:08X}UL #define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x{OFF_BIN2:08X}UL
#define EXTFLASH_BIN2_SONG_HAITIAN_SIZE {by_name['BIN2_SONG_HAITIAN'][2]}UL #define EXTFLASH_BIN2_SONG_HAITIAN_SIZE {by_name['BIN2_SONG_HAITIAN'][2]}UL
#define EXTFLASH_BIN3_UNIVERSAL_ADDR 0x{by_name['BIN3_UNIVERSAL'][1]:08X}UL #define EXTFLASH_BIN3_UNIVERSAL_ADDR 0x{OFF_BIN3:08X}UL
#define EXTFLASH_BIN3_UNIVERSAL_SIZE {by_name['BIN3_UNIVERSAL'][2]}UL #define EXTFLASH_BIN3_UNIVERSAL_SIZE {by_name['BIN3_UNIVERSAL'][2]}UL
/* Convenience aliases used by UI ADDRESS */ /* Convenience aliases used by UI ADDRESS */
@ -121,8 +190,11 @@ def main() -> None:
map_lines = [ map_lines = [
"K1 external Flash map — tone pack 0903", "K1 external Flash map — tone pack 0903",
f"Packed file: Code/.../tools/out/{OUT_BIN.name} ({len(packed)} bytes)", f"Packed file: Code/.../tools/out/{OUT_BIN.name} ({len(packed)} bytes)",
f"Also: {OUT_RES.name}; ALL.res when ui0902_res.bin present",
f"Ends at 0x{cursor:X}; UI0902_RES_BASE=0x00100000; free gap={0x100000 - cursor} bytes", f"Ends at 0x{cursor:X}; UI0902_RES_BASE=0x00100000; free gap={0x100000 - cursor} bytes",
"", "",
"NOTE: BIN3 is FIXED at 0x000A71AC. 2.bin must be <= 41KB and <= slot 41263 bytes.",
"",
f"{'Name':<22} {'Offset':>10} {'Size':>10} Note", f"{'Name':<22} {'Offset':>10} {'Size':>10} Note",
"-" * 72, "-" * 72,
] ]
@ -131,31 +203,52 @@ def main() -> None:
map_lines += [ map_lines += [
"", "",
"Firmware ADDRESS mapping:", "Firmware ADDRESS mapping:",
" 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin)", " 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin @ 0x1B8F0)",
" 海阔天空 -> FLASH_ADDR_SONG_HAITIAN (2.bin)", " 本地曲目 -> FLASH_ADDR_SONG_HAITIAN (2.bin @ 0x9D07D, max 41KB)",
" 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin)", " 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin @ 0xA71AC FIXED)",
" AutoBand -> FLASH_ADDR_AUTOBAND_LEGACY 0x9EB5F (unchanged; overlaps 2.bin region — do not enable until remapped)", " AutoBand -> FLASH_ADDR_AUTOBAND_LEGACY 0x9EB5F (unchanged; overlaps 2.bin region — do not enable until remapped)",
" Boot logo -> UI0902_BOOT_LOGO_ADDR (full-screen); packed logo.bin only pads 0x0..0xCB70", " Boot logo -> UI0902_BOOT_LOGO_ADDR (full-screen); packed logo.bin only pads 0x0..0xCB70",
" Charging -> EXTFLASH_CHARGING_ADDR", " Charging -> EXTFLASH_CHARGING_ADDR",
"", "",
"Add a local song:", "Add a local song:",
" 1. Pack the new preset into 2.bin", " 1. Pack the new preset into 2.bin (must stay <= 41KB / 41263 slot)",
" 2. Append a row to local_songs.csv (index,code,name)", " 2. Append a row to local_songs.csv (index,code,name)",
" 3. python tools/pack_extflash_tone_0903.py (also regenerates LocalSongNames.h)", " 3. python tools/pack_extflash_tone_0903.py (also regenerates LocalSongNames.h)",
" 4. Rebuild firmware and flash MCU + ExtFlash", " 4. Rebuild firmware and flash MCU + ExtFlash ALL.res",
] ]
OUT_MAP.write_text("\n".join(map_lines) + "\n", encoding="utf-8", newline="\n") OUT_MAP.write_text("\n".join(map_lines) + "\n", encoding="utf-8", newline="\n")
print(f"Wrote {OUT_BIN} ({len(packed)} bytes)") print(f"Wrote {OUT_BIN} ({len(packed)} bytes)")
print(f"Wrote {OUT_RES} ({len(packed)} bytes)")
print(f"Wrote {OUT_HDR}") print(f"Wrote {OUT_HDR}")
print(f"Wrote {OUT_MAP}") print(f"Wrote {OUT_MAP}")
for name, off, size, note in rows: for name, off, size, note in rows:
print(f" 0x{off:08X} {size:8d} {name} {note}") print(f" 0x{off:08X} {size:8d} {name} {note}")
if cursor > 0x00100000: if cursor > UI0902_RES_BASE:
raise SystemExit("ERROR: pack overflows into UI0902_RES_BASE") raise SystemExit("ERROR: pack overflows into UI0902_RES_BASE")
print(f"OK: {0x00100000 - cursor} bytes free before UI0902 @ 0x00100000") print(f"OK: {UI0902_RES_BASE - cursor} bytes free before UI0902 @ 0x{UI0902_RES_BASE:X}")
print("OK: BIN1/BIN2/BIN3 present at fixed addresses and match source files")
print(f"OK: 2.bin size {len(bin2_data)} <= 41KB ({MAX_SONG_BIN_BYTES})")
if UI0902_BIN.is_file():
ui = UI0902_BIN.read_bytes()
all_res = packed + (b"\xFF" * (UI0902_RES_BASE - len(packed))) + ui
require_region_equals(all_res, OFF_BIN1, bin1_data, "ALL.res BIN1")
require_region_equals(all_res, OFF_BIN2, bin2_data, "ALL.res BIN2")
require_region_equals(all_res, OFF_BIN3, bin3_data, "ALL.res BIN3")
dab_ok(all_res, OFF_BIN1, 31)
dab_ok(all_res, OFF_BIN2, 1)
dab_ok(all_res, OFF_BIN3, 3)
OUT_ALL_RES.write_bytes(all_res)
# Mirror under repo tools/out for publish/consumers
repo_out = REPO / "tools" / "out"
repo_out.mkdir(parents=True, exist_ok=True)
(repo_out / OUT_ALL_RES.name).write_bytes(all_res)
(repo_out / OUT_RES.name).write_bytes(packed)
print(f"Wrote {OUT_ALL_RES} ({len(all_res)} bytes) — verified 1/2/3.bin")
else:
print(f"WARN: {UI0902_BIN} missing; skipped ALL.res")
# Keep LocalSongNames.h in sync with Doc/.../local_songs.csv when packing tones.
gen = ROOT / "tools" / "gen_local_song_names.py" gen = ROOT / "tools" / "gen_local_song_names.py"
r = subprocess.run([sys.executable, str(gen)], check=False) r = subprocess.run([sys.executable, str(gen)], check=False)
if r.returncode != 0: if r.returncode != 0:

View File

@ -35,7 +35,7 @@ MAP_TXT = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
IAP_DIR = REPO / "升级" / "MCU主控升级" IAP_DIR = REPO / "升级" / "MCU主控升级"
OUT_ROOT = REPO / "tools" / "out" OUT_ROOT = REPO / "tools" / "out"
FW_VER = "0.2.6" FW_VER = "0.2.7"
TONE_RES_VER = "0903" TONE_RES_VER = "0903"
UI0902_RES_BASE = 0x00100000 UI0902_RES_BASE = 0x00100000
BOOT_FLASH_ADDR = 0x08000000 BOOT_FLASH_ADDR = 0x08000000
@ -69,6 +69,33 @@ def build_combined_extflash(tone: bytes, ui: bytes) -> bytes:
return tone + (b"\xFF" * pad) + ui return tone + (b"\xFF" * pad) + ui
def verify_tone_layout(tone: bytes, check_sources: bool = True) -> None:
"""Refuse to ship ALL.res if 1/2/3.bin missing, shifted, or content mismatch."""
dab = b"\xABDAB"
sources = {
"BIN1": (0x0001B8F0, 31, REPO / "Doc" / "音色文件" / "0903" / "1.bin"),
"BIN2": (0x0009D07D, 1, REPO / "Doc" / "音色文件" / "0903" / "2.bin"),
"BIN3": (0x000A71AC, 3, REPO / "Doc" / "音色文件" / "0903" / "3.bin"),
}
for name, (off, want_cnt, src_path) in sources.items():
if off + 16 > len(tone):
raise SystemExit(f"verify {name}: tone pack too short for 0x{off:X}")
magic = tone[off : off + 4]
cnt = int.from_bytes(tone[off + 12 : off + 16], "little")
if magic != dab or cnt != want_cnt:
raise SystemExit(
f"verify {name} @0x{off:X} failed: magic={magic.hex()} cnt={cnt} "
f"(want ABDAB/{want_cnt}). Re-run pack_extflash_tone_0903.py."
)
if check_sources and src_path.is_file():
src = src_path.read_bytes()
if name == "BIN2" and len(src) > 41 * 1024:
raise SystemExit(f"verify BIN2: source 2.bin {len(src)} > 41KB")
if tone[off : off + len(src)] != src:
raise SystemExit(f"verify {name}: content @0x{off:X} != {src_path}")
print("verify tone layout: BIN1/BIN2/BIN3 addresses + content OK")
def main() -> None: def main() -> None:
if not BOOT_BIN.is_file(): if not BOOT_BIN.is_file():
raise SystemExit(f"missing Boot bin: {BOOT_BIN}") raise SystemExit(f"missing Boot bin: {BOOT_BIN}")
@ -97,8 +124,10 @@ def main() -> None:
pkg.mkdir(parents=True) 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() ui_data = UI0902_BIN.read_bytes()
combined = build_combined_extflash(tone_data, ui_data) combined = build_combined_extflash(tone_data, ui_data)
verify_tone_layout(combined) # same offsets in ALL.res
boot_name = f"AT32F403ARCT7_BOOT_v{FW_VER}_{stamp}.bin" boot_name = f"AT32F403ARCT7_BOOT_v{FW_VER}_{stamp}.bin"
mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin" mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin"
@ -147,7 +176,7 @@ def main() -> None:
"", "",
"外部 Flash 分区:", "外部 Flash 分区:",
" 0x00000000 音色/充电图 (toneRes)", " 0x00000000 音色/充电图 (toneRes)",
" logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin(HKTK/2s) + 3.bin", " logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin@0x9D07D + 3.bin@0xA71AC(FIXED)",
" 0x00100000 UI0902 图片 (模式选择、开机 Logo、充电、烧录模式、设置/调音台等)", " 0x00100000 UI0902 图片 (模式选择、开机 Logo、充电、烧录模式、设置/调音台等)",
" 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图Boot 读取)", " 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图Boot 读取)",
"", "",