Compare commits

..

No commits in common. "c1b32c035110d4a6a1b8b30665bdb267a862180a" and "f1f9520637904606e839d7d2df794ac3e48e7956" have entirely different histories.

12 changed files with 107 additions and 513 deletions

View File

@ -280,12 +280,6 @@ uint8_t app_log_try_command(const char *cmd)
"FLASH_HAITIAN_GO", "FLASH_HAITIAN_OK\n");
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) {
uint8_t hdr[16];
uint8_t name[40];
@ -326,16 +320,6 @@ uint8_t app_log_try_command(const char *cmd)
(unsigned long)(cur_hdr[12] | (cur_hdr[13] << 8) |
(cur_hdr[14] << 16) | (cur_hdr[15] << 24)));
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),
"TONE @BIN2 magic=%02X%02X%02X%02X max=%lu cnt=%lu name=%.16s\n",
hdr[0], hdr[1], hdr[2], hdr[3],
@ -381,20 +365,6 @@ uint8_t app_log_try_command(const char *cmd)
return 1U;
}
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_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 },
{ 183, 229, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 3, GUI_PLAY_SECTION, NULL },
/* 底栏四键:万能 | 普通 | 专业 | 设置(60×55y265319 */
{ UI0902_NAV_X0(0), UI0902_NAV_X1(0), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_UNIVERSAL, 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 },
{ UI0902_NAV_X0(2), UI0902_NAV_X1(2), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_EXPERT, 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 },
/* 底栏四键:万能 | 普通 | 专业 | 设置(触区中线对齐 cx 40/100/160/210 */
{ 0, 69, 280, 319, 0, UI0902_NAV_UNIVERSAL, GUI_NAV_BAR, NULL },
{ 70, 129, 280, 319, 0, UI0902_NAV_NORMAL, GUI_NAV_BAR, NULL },
{ 130, 184, 280, 319, 0, UI0902_NAV_EXPERT, GUI_NAV_BAR, NULL },
{ 185, 239, 280, 319, 0, UI0902_NAV_SETTING, GUI_NAV_BAR, NULL },
};
/* 推子 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));
const uint8_t Version[3] = {0,2,7}; /* major.minor.patch — 0.2.7: 1.bin from Doc/音色文件/0909/31cs.bin; fixed BIN3 pack */
const uint8_t Version[3] = {0,2,6}; /* major.minor.patch — 0.2.6: local song 2.bin from Doc/音色文件/0908/2ss_2.bin */
uint8_t Led = 8;
bool PressFlag = 0;
@ -783,10 +783,15 @@ void Pick_Handle()
AutoBandTop1_SetLoopMode(0,1);
break;
case 0:
/* 设置页内沿用万能:与主路径一致,始终 loop */
AutoBandTop1_SetPiecePlayMode(1, 0, 480);
if(PressFlag)
{
AutoBandTop1_SetLoopMode(0,1);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
AutoBandTop1_SetLoopMode(0, 1);
}else
{
AutoBandTop1_SetPiecePlayMode(1,0,480);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
}
StartFlag = 1;
break;
}
@ -837,14 +842,17 @@ void Pick_Handle()
}
break;
case 0:
/* 万能:和弦走向用 piece 模式;须 SetLoopMode否则无循环时常听不到伴奏 */
StartFlag = 1;
AutoBandTop1_SetPiecePlayMode(1, 0, 480);
if(PressFlag)
{
AutoBandTop1_SetPiecePlayMode(1,0,480);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
AutoBandTop1_SetLoopMode(0, 1);
LOG_I("pick", "universal piece name=%s press=%u notes=%u chord=%u",
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)PressFlag, (unsigned)note_cnt, (unsigned)chord_id);
AutoBandTop1_SetLoopMode(0,1);
}else
{
AutoBandTop1_SetPiecePlayMode(1,0,480);
AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt);
}
break;
}
}

View File

@ -148,12 +148,8 @@ void App_Auto_Init(void)
int ret = AutoBandTop1_LoadPresetItemFromFlash(0);
if (ret != 0)
{
uint8_t hdr[16];
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)));
LOG_E("AUTO", "LoadPreset fail ret=%d addr=0x%08X map=%s",
ret, (unsigned)ADDRESS, ToneFlashMapBank((uint32_t)ADDRESS));
}
else
{

View File

@ -43,6 +43,12 @@ static void UI_ModeSelect_DrawButtons(void)
LCD_WR_PIC_FROM_FLASH(UI0902_MODE_ROW_X, y,
UI0902_MODE_ROW_W, UI0902_MODE_ROW_H,
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_Y1, (uint16_t)(UI0902_HUB_CARD_Y1 + UI0902_HUB_CARD_H - 1),
0, 0, GUI_SETING_SELECT3, NULL },
/* 底栏四键:与 Touch_Areas 共用 UI0902_NAV_* 几何 */
{ UI0902_NAV_X0(0), UI0902_NAV_X1(0), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_UNIVERSAL, 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 },
{ UI0902_NAV_X0(2), UI0902_NAV_X1(2), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_EXPERT, 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, 69, 280, 319, 0, UI0902_NAV_UNIVERSAL, GUI_NAV_BAR, NULL },
{ 70, 129, 280, 319, 0, UI0902_NAV_NORMAL, GUI_NAV_BAR, NULL },
{ 130, 184, 280, 319, 0, UI0902_NAV_EXPERT, GUI_NAV_BAR, NULL },
{ 185, 239, 280, 319, 0, UI0902_NAV_SETTING, GUI_NAV_BAR, NULL },
};
#define TOUCH_AREA_SETING_SELECT_NUM (sizeof(Touch_SetingSelect_Areas)/sizeof(Touch_AreaTypeDef))

View File

@ -168,23 +168,10 @@ void UI_ReloadTonePreset(void)
if (mGuiData[GUI_TAB_INDEX].Current == 0)
{
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",
ParamGuiData[ALL_MODE_PARAM].Current, ret,
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)ADDRESS, (unsigned)EXTFLASH_BIN3_UNIVERSAL_ADDR);
}
return;
}
/* 普通/专业:本地曲目或节奏类型 */
@ -456,58 +443,6 @@ 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)
{
char Textstr_new[40] = {0};
@ -518,7 +453,6 @@ void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group)
const uint16_t text_y = row_y + (row_h - UI->Text_size) / 2 + 1;
uint8_t idx = Group->Current + 1;
uint16_t c_top, c_bot;
uint16_t x1, x2;
UI_GetFocusGrad(GUI_MODE_PARAM, &c_top, &c_bot);
@ -530,16 +464,18 @@ void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group)
c_top, c_bot
);
/* 序号+名称整体居中约等于标注「右移7px」的视觉效果 */
// 拼接序号 1. 2. 3.
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)
strcpy(Textstr_new, LocalSongNameGbk[Group->Current]);
else
GET_ParamNum_Str(Group->Current, 0, Textstr_new, Group->Id);
x1 = (uint16_t)(UI->x + 10);
x2 = (uint16_t)(UI->x + UI->w - 10);
UI_DrawIndexedName_Mixed(x1, x2, text_y, NumBuf, Textstr_new,
WHITE, BLACK, UI->Text_size);
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);
//序号X = 中文起点 - 数字宽度 - 5<EFBC8C>?像素
uint16_t num_x = string_start_x - num_w;
// 绘制左侧数字序号
LCD_ShowString(num_x, text_y, (const uint8_t *)NumBuf, UI->Text_fc, UI->Text_bc, UI->Text_size, 1);
}
@ -915,17 +851,22 @@ void label_TimbreSelece_ui(const LEBEL_UI *UI, GUI_SWITCH * Group)
c_top, c_bot
);
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);
UI_DrawIndexedName_Grad(
(uint16_t)(UI->x + 10),
(uint16_t)(UI->x + UI->w - 10),
uint32_t chinese_start_x = LCD_ShowChinese_AutoAlign_Gradient(
UI->x + num_w,
UI->x + UI->w,
text_y,
NumBuf,
Textstr,
(const uint8_t*)Textstr,
UI->Text_Align,
UI->Text_fc,
c_top, c_bot,
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)
{
case GUI_TIMBRE_SELECT:
@ -1047,7 +988,7 @@ void Draw_Tab_Section_Menu(const LEBEL_UI *UI, TAB_SECTION_INDEX select_tab)
const uint8_t pitch = UI0902_SECTION_PITCH;
const uint16_t start_x = UI0902_MARGIN_X; /* 0902-0412/69/126/183 */
const uint16_t start_y = UI->y;
/* 0819演奏段落数字与标题同为 13按钮内垂直居中视觉重心 +1;稿面数字整体右移 2px */
/* 0819演奏段落数字与标题同为 13按钮内垂直居中视觉重心 +1 */
const uint16_t text_y = start_y + (tab_h - UI0902_FONT_BODY) / 2 + 1;
const char *nums[4] = {"1", "2", "3", "4"};
uint8_t i;
@ -1058,7 +999,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;
LCD_FillRoundRect(x, start_y, tab_w, tab_h, tab_r, bg);
LCD_ShowString_AutoAlign(
(uint16_t)(x + 2), (uint16_t)(x + tab_w - 1 + 2), text_y,
x, (uint16_t)(x + tab_w - 1), text_y,
(const uint8_t*)nums[i],
UI->Text_Align,
WHITE,
@ -1328,12 +1269,8 @@ void Draw_System_Item_List(const LEBEL_UI *UI, GUI_SWITCH *Group)
(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_ADDR);
/* 0902-07右侧为进入下级页的细 chevron设置图标-03勿用实心三角 */
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);
/* 右三角与蓝牙/自动关机同一右缘val_x2 */
UI_DrawTriRight((uint16_t)(val_x2 - UI0902_TRI_R_W), chev_cy);
break;
default: break;
}

View File

@ -173,12 +173,6 @@ void CallUI_RestoreSelect(void);
#define UI0902_NAV_EXPERT 2 /* 底栏:专业模式 */
#define UI0902_NAV_SETTING 3 /* 底栏:设置 */
#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_MODE UI0902_NAV_EXPERT

View File

@ -18,7 +18,7 @@
#define EXTFLASH_CHARGING_H 190
#define EXTFLASH_BIN1_RHYTHM_ADDR 0x0001B8F0UL
#define EXTFLASH_BIN1_RHYTHM_SIZE 530273UL
#define EXTFLASH_BIN1_RHYTHM_SIZE 530317UL
#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x0009D07DUL
#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.
* git HEAD (dirty): bb6d9ae7f9987ec6cfb3ab50579951d2b58ed888
* git HEAD (dirty): 57448b3da8a3f912503e9d50313b5ec635888b69
* branch: develop develop
* 01 0C wire: develop_bb6d9a*
* 01 0C wire: develop_57448b*
*/
#ifndef GIT_USER_FW_VER_H
#define GIT_USER_FW_VER_H
#define GIT_COMMIT_ID_FULL "bb6d9ae7f9987ec6cfb3ab50579951d2b58ed888"
#define GIT_COMMIT_ID_FULL "57448b3da8a3f912503e9d50313b5ec635888b69"
#define GIT_BRANCH_NAME "develop"
#define GIT_COMMIT_SHORT6 "bb6d9a"
#define GIT_COMMIT_SHORT6 "57448b"
#define GIT_DIRTY (1)
#define GIT_BUILD_ID "develop_bb6d9a*"
#define GIT_BUILD_ID "develop_57448b*"
#define GIT_BUILD_ID_LEN 15u
#endif /* GIT_USER_FW_VER_H */

View File

@ -1,195 +0,0 @@
#!/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,23 +2,21 @@
# -*- coding: utf-8 -*-
"""Pack external W25Q128 tone/logo image for K1 (0903).
Layout (absolute W25Q128 offsets BIN2/BIN3 are FIXED so FW addresses stay stable):
Layout (absolute W25Q128 offsets):
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)
0x0001B8F0 1.bin 普通/专业 31 rhythms
0x0009D07D 2.bin 本地曲目变长尾部 0xFF 填到 BIN3<=41KB
0x000A71AC 3.bin 万能模式固定勿随 2.bin 长度漂移
0x0009D07D 2.bin 海阔天空
0x000A8607 3.bin 万能模式
Outputs:
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
Doc/音色文件/0903/FLASH_MAP.txt
"""
from __future__ import annotations
import struct
import os
import subprocess
import sys
from pathlib import Path
@ -27,25 +25,11 @@ ROOT = Path(__file__).resolve().parents[1] # firmware project root
REPO = ROOT.parent.parent # 一诺国际吉他
OUT_DIR = ROOT / "tools" / "out"
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_MAP = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
UI0902_BIN = OUT_DIR / "ui0902_res.bin"
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:
for p in REPO.iterdir():
@ -54,66 +38,29 @@ def find_ziliao() -> Path:
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:
ziliao = find_ziliao()
bin1_path = TONE_DIR / "1.bin"
bin2_path = TONE_DIR / "2.bin"
bin3_path = TONE_DIR / "3.bin"
parts = [
("LOGO", ziliao / "logo.bin", OFF_LOGO, "legacy pad; boot uses UI0902_BOOT_LOGO"),
("CHARGING", ziliao / "Charg.bin", OFF_CHARGING, "legacy pad; UI uses UI0902_CHARGE_SCREEN"),
("BIN1_RHYTHM", bin1_path, OFF_BIN1, "普通/专业 31 条节奏"),
("BIN2_SONG_HAITIAN", bin2_path, OFF_BIN2, "本地曲目"),
("BIN3_UNIVERSAL", bin3_path, OFF_BIN3, "万能模式(固定偏移)"),
("LOGO", ziliao / "logo.bin", 0x00000000, "legacy pad; boot uses UI0902_BOOT_LOGO"),
("CHARGING", ziliao / "Charg.bin", 0x0000CB70, "legacy pad; UI uses UI0902_CHARGE_SCREEN"),
("BIN1_RHYTHM", TONE_DIR / "1.bin", 0x0001B8F0, "普通/专业 31 条节奏"),
("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", None, "本地曲目 海阔天空"),
("BIN3_UNIVERSAL", TONE_DIR / "3.bin", None, "万能模式"),
]
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] = []
blobs = []
cursor = 0
rows: list[tuple[str, int, int, str]] = []
rows = []
for name, path, force_off, note in parts:
data = path.read_bytes()
if force_off is not None:
if cursor > force_off:
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)"
)
raise SystemExit(f"{name}: cursor 0x{cursor:X} past forced 0x{force_off:X}")
if cursor < force_off:
pad = force_off - cursor
blobs.append(b"\xFF" * pad)
rows.append(("(pad)", cursor, pad, "gap fill 0xFF"))
cursor = force_off
rows.append((f"(pad)", force_off - pad, pad, "gap fill 0xFF"))
off = cursor
blobs.append(data)
cursor += len(data)
@ -122,25 +69,9 @@ def main() -> None:
OUT_DIR.mkdir(parents=True, exist_ok=True)
packed = b"".join(blobs)
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("(")}
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
#define __EXTFLASH_TONE_ADDR_H
@ -161,13 +92,13 @@ def main() -> None:
#define EXTFLASH_CHARGING_W 160
#define EXTFLASH_CHARGING_H 190
#define EXTFLASH_BIN1_RHYTHM_ADDR 0x{OFF_BIN1:08X}UL
#define EXTFLASH_BIN1_RHYTHM_ADDR 0x{by_name['BIN1_RHYTHM'][1]:08X}UL
#define EXTFLASH_BIN1_RHYTHM_SIZE {by_name['BIN1_RHYTHM'][2]}UL
#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x{OFF_BIN2:08X}UL
#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x{by_name['BIN2_SONG_HAITIAN'][1]:08X}UL
#define EXTFLASH_BIN2_SONG_HAITIAN_SIZE {by_name['BIN2_SONG_HAITIAN'][2]}UL
#define EXTFLASH_BIN3_UNIVERSAL_ADDR 0x{OFF_BIN3:08X}UL
#define EXTFLASH_BIN3_UNIVERSAL_ADDR 0x{by_name['BIN3_UNIVERSAL'][1]:08X}UL
#define EXTFLASH_BIN3_UNIVERSAL_SIZE {by_name['BIN3_UNIVERSAL'][2]}UL
/* Convenience aliases used by UI ADDRESS */
@ -190,11 +121,8 @@ def main() -> None:
map_lines = [
"K1 external Flash map — tone pack 0903",
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",
"",
"NOTE: BIN3 is FIXED at 0x000A71AC. 2.bin must be <= 41KB and <= slot 41263 bytes.",
"",
f"{'Name':<22} {'Offset':>10} {'Size':>10} Note",
"-" * 72,
]
@ -203,52 +131,31 @@ def main() -> None:
map_lines += [
"",
"Firmware ADDRESS mapping:",
" 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin @ 0x1B8F0)",
" 本地曲目 -> FLASH_ADDR_SONG_HAITIAN (2.bin @ 0x9D07D, max 41KB)",
" 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin @ 0xA71AC FIXED)",
" 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin)",
" 海阔天空 -> FLASH_ADDR_SONG_HAITIAN (2.bin)",
" 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin)",
" 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",
" Charging -> EXTFLASH_CHARGING_ADDR",
"",
"Add a local song:",
" 1. Pack the new preset into 2.bin (must stay <= 41KB / 41263 slot)",
" 1. Pack the new preset into 2.bin",
" 2. Append a row to local_songs.csv (index,code,name)",
" 3. python tools/pack_extflash_tone_0903.py (also regenerates LocalSongNames.h)",
" 4. Rebuild firmware and flash MCU + ExtFlash ALL.res",
" 4. Rebuild firmware and flash MCU + ExtFlash",
]
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_RES} ({len(packed)} bytes)")
print(f"Wrote {OUT_HDR}")
print(f"Wrote {OUT_MAP}")
for name, off, size, note in rows:
print(f" 0x{off:08X} {size:8d} {name} {note}")
if cursor > UI0902_RES_BASE:
if cursor > 0x00100000:
raise SystemExit("ERROR: pack overflows into UI0902_RES_BASE")
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")
print(f"OK: {0x00100000 - cursor} bytes free before UI0902 @ 0x00100000")
# Keep LocalSongNames.h in sync with Doc/.../local_songs.csv when packing tones.
gen = ROOT / "tools" / "gen_local_song_names.py"
r = subprocess.run([sys.executable, str(gen)], check=False)
if r.returncode != 0:

View File

@ -35,7 +35,7 @@ MAP_TXT = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
IAP_DIR = REPO / "升级" / "MCU主控升级"
OUT_ROOT = REPO / "tools" / "out"
FW_VER = "0.2.7"
FW_VER = "0.2.6"
TONE_RES_VER = "0903"
UI0902_RES_BASE = 0x00100000
BOOT_FLASH_ADDR = 0x08000000
@ -69,33 +69,6 @@ def build_combined_extflash(tone: bytes, ui: bytes) -> bytes:
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:
if not BOOT_BIN.is_file():
raise SystemExit(f"missing Boot bin: {BOOT_BIN}")
@ -124,10 +97,8 @@ def main() -> None:
pkg.mkdir(parents=True)
tone_data = TONE_BIN.read_bytes()
verify_tone_layout(tone_data)
ui_data = UI0902_BIN.read_bytes()
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"
mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin"
@ -176,7 +147,7 @@ def main() -> None:
"",
"外部 Flash 分区:",
" 0x00000000 音色/充电图 (toneRes)",
" logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin@0x9D07D + 3.bin@0xA71AC(FIXED)",
" logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin(HKTK/2s) + 3.bin",
" 0x00100000 UI0902 图片 (模式选择、开机 Logo、充电、烧录模式、设置/调音台等)",
" 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图Boot 读取)",
"",