Map tone Flash banks for pack 0903 and bump firmware to 0.2.0.

Point normal/expert/universal ADDRESS at 1.bin/3.bin bases, add ExtFlash_Tone_Addr.h plus pack/publish helpers for paired MCU+.res releases.
This commit is contained in:
yuquanjun 2026-09-04 11:12:22 +08:00
parent 8f0d0a3a52
commit c05301d8b9
8 changed files with 373 additions and 37 deletions

View File

@ -100,7 +100,7 @@ int16_t MIC_VOL_MAP[10]= {-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,1,11}; const uint8_t Version[3] = {0,2,0}; /* major.minor.patch — 0.2.0: tone pack 0903 addr remap */
uint8_t Led = 8; uint8_t Led = 8;
bool PressFlag = 0; bool PressFlag = 0;
@ -304,8 +304,7 @@ void Power_Key_Scan()
void (*CurrUIProcress)(DisplayTaskMessage_Type xEvent); void (*CurrUIProcress)(DisplayTaskMessage_Type xEvent);
/* UI 公共全局变量 */ /* UI 公共全局变量 */
//volatile uint32_t ADDRESS = 0x0001b8F0; volatile uint32_t ADDRESS = FLASH_ADDR_MODE_UNIVERSAL;
volatile uint32_t ADDRESS = 0x00000000;
uint8_t KEY_ID_1629 = 0; uint8_t KEY_ID_1629 = 0;
bool StartFlag = false; bool StartFlag = false;
bool InModeFlag = false; bool InModeFlag = false;

View File

@ -150,22 +150,22 @@ void Touch_Mode_Select_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex)
switch (ID) switch (ID)
{ {
case 0: /* 万能 */ case 0: /* 万能 → 3.bin */
if (mGuiData[GUI_AUTOBAND_SW].Current) if (mGuiData[GUI_AUTOBAND_SW].Current)
ADDRESS = 0x0009EB5F; ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY;
else else
ADDRESS = 0x0001B8F0; ADDRESS = FLASH_ADDR_MODE_UNIVERSAL;
MenuPage_SongMode_Proc(); MenuPage_SongMode_Proc();
break; break;
case 1: /* 专业 */ case 1: /* 专业 → 1.bin */
if (mGuiData[GUI_AUTOBAND_SW].Current) if (mGuiData[GUI_AUTOBAND_SW].Current)
ADDRESS = 0x0009EB5F; ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY;
else else
ADDRESS = 0x00000000; ADDRESS = FLASH_ADDR_MODE_EXPERT;
MenuPage_ExpertMode_Proc(); MenuPage_ExpertMode_Proc();
break; break;
case 2: /* 普通 */ case 2: /* 普通 → 1.bin */
ADDRESS = 0x0009598F; ADDRESS = FLASH_ADDR_MODE_NORMAL;
MenuPage_FreeMode_Proc(); MenuPage_FreeMode_Proc();
break; break;
default: default:
@ -266,7 +266,8 @@ void IdleProcess(DisplayTaskMessage_Type msg)
BSP_MainPowerEnable(1); BSP_MainPowerEnable(1);
LCD_WR_REG(0x29); //Display on LCD_WR_REG(0x29); //Display on
LCD_WR_PIC_FROM_FLASH(40, 50, 160, 190, 0x0000CB70); LCD_WR_PIC_FROM_FLASH(40, 50, EXTFLASH_CHARGING_W, EXTFLASH_CHARGING_H,
EXTFLASH_CHARGING_ADDR);
LCD_BLK_Set(); LCD_BLK_Set();
flag = 1; flag = 1;
} }

View File

@ -225,10 +225,16 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value)
if(mGuiData[GUI_AUTOBAND_SW].Current) if(mGuiData[GUI_AUTOBAND_SW].Current)
{ {
ADDRESS = 0x0009EB5B; ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY;
}else }else
{ {
ADDRESS = 0x00000000; /* 关闭 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;
}
} }
if (!UI_SetFocus(GUI_MODE_PARAM)) if (!UI_SetFocus(GUI_MODE_PARAM))
label_ModeSelece_ui(&UI_Label[GUI_MODE_PARAM],&mGuiData[GUI_MODE_PARAM]); label_ModeSelece_ui(&UI_Label[GUI_MODE_PARAM],&mGuiData[GUI_MODE_PARAM]);
@ -263,42 +269,34 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value)
app_tm1629_all_on(LED_COLOR_G); app_tm1629_all_on(LED_COLOR_G);
switch(mGuiData[ID].Current) switch(mGuiData[ID].Current)
{ {
case 0: case 0: /* 万能 → 3.bin */
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current)
break; break;
StartFlag = 0; StartFlag = 0;
AutoBandTop1_Stop(); AutoBandTop1_Stop();
if(mGuiData[GUI_AUTOBAND_SW].Current) if(mGuiData[GUI_AUTOBAND_SW].Current)
{ ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY;
ADDRESS = 0x0009EB5F;
}
else else
{ ADDRESS = FLASH_ADDR_MODE_UNIVERSAL;
ADDRESS = 0x00000000;
}
break; break;
case 1: case 1: /* 专业 → 1.bin */
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current)
break; break;
StartFlag = 0; StartFlag = 0;
AutoBandTop1_Stop(); AutoBandTop1_Stop();
if(mGuiData[GUI_AUTOBAND_SW].Current) if(mGuiData[GUI_AUTOBAND_SW].Current)
{ ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY;
ADDRESS = 0x0009EB5F;
}
else else
{ ADDRESS = FLASH_ADDR_MODE_EXPERT;
ADDRESS = 0x00000000;
}
break; break;
case 2: case 2: /* 普通 → 1.bin */
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current)
break; break;
StartFlag = 0; StartFlag = 0;
AutoBandTop1_Stop(); AutoBandTop1_Stop();
ADDRESS = 0x0009598F; ADDRESS = FLASH_ADDR_MODE_NORMAL;
break; break;
} }
Refresh_TabSelect_ui(&UI_Label[ID], &mGuiData[ID]); Refresh_TabSelect_ui(&UI_Label[ID], &mGuiData[ID]);

View File

@ -236,15 +236,19 @@ void UI_GotoCurrentModePage(void)
switch (mGuiData[GUI_TAB_INDEX].Current) switch (mGuiData[GUI_TAB_INDEX].Current)
{ {
case 0: case 0:
ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x0001B8F0; ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current
? FLASH_ADDR_AUTOBAND_LEGACY
: FLASH_ADDR_MODE_UNIVERSAL;
MenuPage_SongMode_Proc(); MenuPage_SongMode_Proc();
break; break;
case 1: case 1:
ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x00000000; ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current
? FLASH_ADDR_AUTOBAND_LEGACY
: FLASH_ADDR_MODE_EXPERT;
MenuPage_ExpertMode_Proc(); MenuPage_ExpertMode_Proc();
break; break;
case 2: case 2:
ADDRESS = 0x0009598F; ADDRESS = FLASH_ADDR_MODE_NORMAL;
MenuPage_FreeMode_Proc(); MenuPage_FreeMode_Proc();
break; break;
default: default:
@ -803,15 +807,19 @@ void UI_NavBar_Action(uint8_t value)
switch (value) switch (value)
{ {
case UI0902_NAV_UNIVERSAL: case UI0902_NAV_UNIVERSAL:
ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x0001B8F0; ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current
? FLASH_ADDR_AUTOBAND_LEGACY
: FLASH_ADDR_MODE_UNIVERSAL;
MenuPage_SongMode_Proc(); MenuPage_SongMode_Proc();
break; break;
case UI0902_NAV_NORMAL: case UI0902_NAV_NORMAL:
ADDRESS = 0x0009598F; ADDRESS = FLASH_ADDR_MODE_NORMAL;
MenuPage_FreeMode_Proc(); MenuPage_FreeMode_Proc();
break; break;
case UI0902_NAV_EXPERT: case UI0902_NAV_EXPERT:
ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x00000000; ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current
? FLASH_ADDR_AUTOBAND_LEGACY
: FLASH_ADDR_MODE_EXPERT;
MenuPage_ExpertMode_Proc(); MenuPage_ExpertMode_Proc();
break; break;
case UI0902_NAV_SETTING: case UI0902_NAV_SETTING:

View File

@ -0,0 +1,42 @@
#ifndef __EXTFLASH_TONE_ADDR_H
#define __EXTFLASH_TONE_ADDR_H
/* Auto-generated by tools/pack_extflash_tone_0903.py — do not hand-edit. */
/* W25Q128 absolute offsets. Boot logo display uses UI0902_BOOT_LOGO_ADDR. */
/* Tone resource pack has no in-bin version field; release tag = EXTFLASH_TONE_RES_VER. */
#define EXTFLASH_TONE_RES_VER "0903"
#define EXTFLASH_TONE_RES_VER_MAJOR 0
#define EXTFLASH_TONE_RES_VER_MINOR 9
#define EXTFLASH_TONE_RES_VER_PATCH 3
#define EXTFLASH_LOGO_ADDR 0x00000000UL
#define EXTFLASH_LOGO_SIZE 52080UL
#define EXTFLASH_CHARGING_ADDR 0x0000CB70UL
#define EXTFLASH_CHARGING_SIZE 60800UL
#define EXTFLASH_CHARGING_W 160
#define EXTFLASH_CHARGING_H 190
#define EXTFLASH_BIN1_RHYTHM_ADDR 0x0001B8F0UL
#define EXTFLASH_BIN1_RHYTHM_SIZE 530317UL
#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x0009D07DUL
#define EXTFLASH_BIN2_SONG_HAITIAN_SIZE 46474UL
#define EXTFLASH_BIN3_UNIVERSAL_ADDR 0x000A8607UL
#define EXTFLASH_BIN3_UNIVERSAL_SIZE 31854UL
/* Convenience aliases used by UI ADDRESS */
#define FLASH_ADDR_MODE_NORMAL EXTFLASH_BIN1_RHYTHM_ADDR /* 普通 */
#define FLASH_ADDR_MODE_EXPERT EXTFLASH_BIN1_RHYTHM_ADDR /* 专业 */
#define FLASH_ADDR_SONG_HAITIAN EXTFLASH_BIN2_SONG_HAITIAN_ADDR
#define FLASH_ADDR_MODE_UNIVERSAL EXTFLASH_BIN3_UNIVERSAL_ADDR /* 万能 */
/* Legacy AutoBand bank — not repacked in 0903; left unchanged in firmware */
#define FLASH_ADDR_AUTOBAND_LEGACY 0x0009EB5FUL
#define EXTFLASH_TONE_PACK_END 0x000B0275UL
#define EXTFLASH_TONE_PACK_SIZE 721525UL
#define UI0902_RES_BASE_SAFE_GAP (0x00100000UL - EXTFLASH_TONE_PACK_END)
#endif

View File

@ -37,6 +37,7 @@
#include "Drv_ILI9341_Lcd_Image.h" #include "Drv_ILI9341_Lcd_Image.h"
#include "Drv_ILI9341_Lcd_Image_UI0902.h" #include "Drv_ILI9341_Lcd_Image_UI0902.h"
#include "UI0902_Res_Addr.h" #include "UI0902_Res_Addr.h"
#include "ExtFlash_Tone_Addr.h"
#include "Drv_ILI9341_Lcd_Dump.h" #include "Drv_ILI9341_Lcd_Dump.h"
#include "Drv_XPT2046_Touch.h" #include "Drv_XPT2046_Touch.h"

View File

@ -0,0 +1,152 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Pack external W25Q128 tone/logo image for K1 (0903).
Layout (absolute W25Q128 offsets):
0x00000000 logo.bin (legacy pad; boot UI uses UI0902 full-screen logo)
0x0000CB70 Charg.bin (160x190 RGB565 charging art)
0x0001B8F0 1.bin 普通/专业 31 rhythms
0x0009D07D 2.bin 海阔天空
0x000A8607 3.bin 万能模式
Outputs:
tools/out/extflash_tone_0903.bin
project/inc/ExtFlash_Tone_Addr.h
Doc/音色文件/0903/FLASH_MAP.txt
"""
from __future__ import annotations
import os
from pathlib import Path
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_HDR = ROOT / "project" / "inc" / "ExtFlash_Tone_Addr.h"
OUT_MAP = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
TONE_DIR = REPO / "Doc" / "音色文件" / "0903"
def find_ziliao() -> Path:
for p in REPO.iterdir():
if p.is_dir() and (p / "logo.bin").is_file() and (p / "Charg.bin").is_file():
return p
raise FileNotFoundError("资料/logo.bin + Charg.bin not found under repo")
def main() -> None:
ziliao = find_ziliao()
parts = [
("LOGO", ziliao / "logo.bin", 0x00000000, "legacy pad; boot uses UI0902_BOOT_LOGO"),
("CHARGING", ziliao / "Charg.bin", 0x0000CB70, "160x190 RGB565"),
("BIN1_RHYTHM", TONE_DIR / "1.bin", 0x0001B8F0, "普通/专业 31 条节奏"),
("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", None, "本地曲目 海阔天空"),
("BIN3_UNIVERSAL", TONE_DIR / "3.bin", None, "万能模式"),
]
blobs = []
cursor = 0
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}")
if cursor < force_off:
pad = force_off - cursor
blobs.append(b"\xFF" * pad)
cursor = force_off
rows.append((f"(pad)", force_off - pad, pad, "gap fill 0xFF"))
off = cursor
blobs.append(data)
cursor += len(data)
rows.append((name, off, len(data), f"{note} <- {path.name}"))
OUT_DIR.mkdir(parents=True, exist_ok=True)
packed = b"".join(blobs)
OUT_BIN.write_bytes(packed)
# named lookup
by_name = {r[0]: r for r in rows if not r[0].startswith("(")}
hdr = f"""#ifndef __EXTFLASH_TONE_ADDR_H
#define __EXTFLASH_TONE_ADDR_H
/* Auto-generated by tools/pack_extflash_tone_0903.py do not hand-edit. */
/* W25Q128 absolute offsets. Boot logo display uses UI0902_BOOT_LOGO_ADDR. */
/* Tone resource pack has no in-bin version field; release tag = EXTFLASH_TONE_RES_VER. */
#define EXTFLASH_TONE_RES_VER "0903"
#define EXTFLASH_TONE_RES_VER_MAJOR 0
#define EXTFLASH_TONE_RES_VER_MINOR 9
#define EXTFLASH_TONE_RES_VER_PATCH 3
#define EXTFLASH_LOGO_ADDR 0x{by_name['LOGO'][1]:08X}UL
#define EXTFLASH_LOGO_SIZE {by_name['LOGO'][2]}UL
#define EXTFLASH_CHARGING_ADDR 0x{by_name['CHARGING'][1]:08X}UL
#define EXTFLASH_CHARGING_SIZE {by_name['CHARGING'][2]}UL
#define EXTFLASH_CHARGING_W 160
#define EXTFLASH_CHARGING_H 190
#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{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{by_name['BIN3_UNIVERSAL'][1]:08X}UL
#define EXTFLASH_BIN3_UNIVERSAL_SIZE {by_name['BIN3_UNIVERSAL'][2]}UL
/* Convenience aliases used by UI ADDRESS */
#define FLASH_ADDR_MODE_NORMAL EXTFLASH_BIN1_RHYTHM_ADDR /* 普通 */
#define FLASH_ADDR_MODE_EXPERT EXTFLASH_BIN1_RHYTHM_ADDR /* 专业 */
#define FLASH_ADDR_SONG_HAITIAN EXTFLASH_BIN2_SONG_HAITIAN_ADDR
#define FLASH_ADDR_MODE_UNIVERSAL EXTFLASH_BIN3_UNIVERSAL_ADDR /* 万能 */
/* Legacy AutoBand bank not repacked in 0903; left unchanged in firmware */
#define FLASH_ADDR_AUTOBAND_LEGACY 0x0009EB5FUL
#define EXTFLASH_TONE_PACK_END 0x{cursor:08X}UL
#define EXTFLASH_TONE_PACK_SIZE {cursor}UL
#define UI0902_RES_BASE_SAFE_GAP (0x00100000UL - EXTFLASH_TONE_PACK_END)
#endif
"""
OUT_HDR.write_text(hdr, encoding="utf-8", newline="\n")
map_lines = [
"K1 external Flash map — tone pack 0903",
f"Packed file: Code/.../tools/out/{OUT_BIN.name} ({len(packed)} bytes)",
f"Ends at 0x{cursor:X}; UI0902_RES_BASE=0x00100000; free gap={0x100000 - cursor} bytes",
"",
f"{'Name':<22} {'Offset':>10} {'Size':>10} Note",
"-" * 72,
]
for name, off, size, note in rows:
map_lines.append(f"{name:<22} 0x{off:08X} {size:10d} {note}")
map_lines += [
"",
"Firmware ADDRESS mapping:",
" 普通/专业 -> 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",
]
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_HDR}")
print(f"Wrote {OUT_MAP}")
for name, off, size, note in rows:
print(f" 0x{off:08X} {size:8d} {name} {note}")
if cursor > 0x00100000:
raise SystemExit("ERROR: pack overflows into UI0902_RES_BASE")
print(f"OK: {0x00100000 - cursor} bytes free before UI0902 @ 0x00100000")
if __name__ == "__main__":
main()

135
tools/publish_k1_release.py Normal file
View File

@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
"""Build K1 release zip: MCU .bin + tone .res + SoundWalkerIAP + docs.
No RTT flashing. Version stays Firmware Version[] (currently 0.2.0).
"""
from __future__ import annotations
import hashlib
import shutil
import subprocess
import zipfile
from datetime import datetime
from pathlib import Path
REPO = Path(r"C:\Users\qjyu\Documents\SoundWalker\一诺国际吉他")
PROJ = REPO / "Code" / "YNGJ-GT1-M - AT32F403ARCT7"
EXE_BIN = PROJ / "project" / "IAR_V7.4" / "YNGJ-GT1-M" / "Exe" / "YNGJ-GT1-M.bin"
TONE_BIN = PROJ / "tools" / "out" / "extflash_tone_0903.bin"
MAP_TXT = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
IAP_DIR = REPO / "升级" / "MCU主控升级"
OUT_ROOT = REPO / "tools" / "out"
FW_VER = "0.2.0"
TONE_RES_VER = "0903"
def git_info(cwd: Path) -> tuple[str, str]:
def run(args: list[str]) -> str:
r = subprocess.run(
args, cwd=str(cwd), capture_output=True, text=True, encoding="utf-8", errors="replace"
)
return (r.stdout or "").strip()
commit = run(["git", "rev-parse", "--short=7", "HEAD"]) or "nogit"
subject = run(["git", "log", "-1", "--pretty=%s"]) or ""
return commit, subject
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def main() -> None:
if not EXE_BIN.is_file():
raise SystemExit(f"missing MCU bin: {EXE_BIN}")
if not TONE_BIN.is_file():
raise SystemExit(f"missing tone pack: {TONE_BIN} (run pack_extflash_tone_0903.py)")
iap = IAP_DIR / "SoundWalkerIAP.exe"
guide = IAP_DIR / "升级步骤.docx"
if not iap.is_file():
raise SystemExit(f"missing IAP tool: {iap}")
if not guide.is_file():
raise SystemExit(f"missing guide: {guide}")
# date + commit only (no minutes, no dirty tag)
stamp = datetime.now().strftime("%Y%m%d")
commit, subject = git_info(PROJ)
base = f"K1_MCU_v{FW_VER}_toneRes_v{TONE_RES_VER}_{stamp}_{commit}"
OUT_ROOT.mkdir(parents=True, exist_ok=True)
pkg = OUT_ROOT / base
if pkg.exists():
shutil.rmtree(pkg)
pkg.mkdir(parents=True)
mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin"
res_name = f"extflash_toneRes_v{TONE_RES_VER}_{stamp}.res"
mcu_dst = pkg / mcu_name
res_dst = pkg / res_name
shutil.copy2(EXE_BIN, mcu_dst)
shutil.copy2(TONE_BIN, res_dst)
shutil.copy2(iap, pkg / "SoundWalkerIAP.exe")
shutil.copy2(guide, pkg / "升级步骤.docx")
if MAP_TXT.is_file():
shutil.copy2(MAP_TXT, pkg / "FLASH_MAP.txt")
# flat convenience copies
shutil.copy2(res_dst, OUT_ROOT / "extflash_tone_0903.res")
shutil.copy2(mcu_dst, OUT_ROOT / mcu_name)
readme = pkg / "README.txt"
readme.write_text(
"\n".join(
[
f"K1 release {base}",
f"Built {datetime.now().strftime('%Y-%m-%d %H:%M:%S %z')}",
f"Git HEAD {commit}",
f"Git subject {subject}",
f"FW version {FW_VER} (settings UI Version[])",
f"Tone res toneRes_v{TONE_RES_VER} (no in-bin version; tag in filename)",
"",
"Contents:",
f" {mcu_name}",
f" size={mcu_dst.stat().st_size} sha256={sha256(mcu_dst)}",
f" flash with SoundWalkerIAP.exe (USB IAP)",
f" {res_name}",
f" size={res_dst.stat().st_size} sha256={sha256(res_dst)}",
" W25Q128 layout: logo@0 + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin + 3.bin",
" Must pair with MCU v0.2.0 (address map changed).",
" SoundWalkerIAP.exe",
" 升级步骤.docx",
" FLASH_MAP.txt",
"",
"Notes:",
" - Boot logo uses UI0902 full-screen asset; packed logo.bin only pads 0x0..0xCB70.",
" - AutoBand legacy 0x9EB5F not remapped in this release.",
" - Flash MCU firmware AND tone .res together.",
"",
]
),
encoding="utf-8",
newline="\n",
)
zip_path = OUT_ROOT / f"{base}.zip"
if zip_path.exists():
zip_path.unlink()
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for f in sorted(pkg.rglob("*")):
if f.is_file():
zf.write(f, arcname=f"{base}/{f.relative_to(pkg).as_posix()}")
print(f"PKG {pkg}")
print(f"ZIP {zip_path} ({zip_path.stat().st_size} bytes)")
for f in sorted(pkg.iterdir()):
print(f" {f.name} {f.stat().st_size}")
if __name__ == "__main__":
main()