# -*- coding: utf-8 -*- """Build K1 release zip: Boot + MCU APP + ExtFlash resources + SoundWalkerIAP + docs. External Flash has TWO regions that must both be present on a blank/erased chip: 0x00000000 tone pack (logo pad + Charg + 1/2/3.bin) -> *.tone.res / combined 0x00100000 UI0902 bitmaps (mode rows, boot logo, ...) -> *.ui0902.res / combined Prefer flashing the combined *.extflash.res @ 0x0 for colleague upgrades. Boot must also be updated so USB IAP wait screen can show UI0902_FLASH_MODE. """ 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" BOOT_BIN = ( PROJ / "AT32F403ARCT7_BOOT" / "project" / "IAR_V7.4" / "AT32F403ARCT7_BOOT" / "Exe" / "AT32F403ARCT7_BOOT.bin" ) TONE_BIN = PROJ / "tools" / "out" / "extflash_tone_0903.bin" UI0902_BIN = PROJ / "tools" / "out" / "ui0902_res.bin" MAP_TXT = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt" IAP_DIR = REPO / "升级" / "MCU主控升级" OUT_ROOT = REPO / "tools" / "out" FW_VER = "0.2.6" TONE_RES_VER = "0903" UI0902_RES_BASE = 0x00100000 BOOT_FLASH_ADDR = 0x08000000 APP_FLASH_ADDR = 0x08008000 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 build_combined_extflash(tone: bytes, ui: bytes) -> bytes: if len(tone) > UI0902_RES_BASE: raise SystemExit(f"tone pack {len(tone)} overflows into UI0902 @ 0x{UI0902_RES_BASE:X}") pad = UI0902_RES_BASE - len(tone) return tone + (b"\xFF" * pad) + ui def main() -> None: if not BOOT_BIN.is_file(): raise SystemExit(f"missing Boot bin: {BOOT_BIN}") if not EXE_BIN.is_file(): raise SystemExit(f"missing MCU bin: {EXE_BIN}") if not TONE_BIN.is_file(): raise SystemExit(f"missing tone pack: {TONE_BIN} (run pack_extflash_tone_0903.py)") if not UI0902_BIN.is_file(): raise SystemExit(f"missing UI0902 pack: {UI0902_BIN} (run gen_ui0902_assets.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}") 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) tone_data = TONE_BIN.read_bytes() ui_data = UI0902_BIN.read_bytes() combined = build_combined_extflash(tone_data, ui_data) boot_name = f"AT32F403ARCT7_BOOT_v{FW_VER}_{stamp}.bin" mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin" comb_name = f"extflash_ALL_tone0903_ui0902_{stamp}.res" boot_dst = pkg / boot_name mcu_dst = pkg / mcu_name comb_dst = pkg / comb_name shutil.copy2(BOOT_BIN, boot_dst) shutil.copy2(EXE_BIN, mcu_dst) comb_dst.write_bytes(combined) shutil.copy2(iap, pkg / "SoundWalkerIAP.exe") shutil.copy2(guide, pkg / "升级步骤.docx") if MAP_TXT.is_file(): shutil.copy2(MAP_TXT, pkg / "FLASH_MAP.txt") shutil.copy2(boot_dst, OUT_ROOT / boot_name) shutil.copy2(mcu_dst, OUT_ROOT / mcu_name) shutil.copy2(comb_dst, OUT_ROOT / "extflash_ALL_tone0903_ui0902.res") # Keep build intermediates under tools/out for local rebuilds; not shipped in package. (OUT_ROOT / "extflash_tone_0903.res").write_bytes(tone_data) (OUT_ROOT / "extflash_ui0902.res").write_bytes(ui_data) readme = pkg / "README.txt" readme.write_text( "\n".join( [ f"K1 release {base}", f"Built {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", f"Git HEAD {commit}", f"Git subject {subject}", f"FW version {FW_VER}", "", "==== 整机升级请刷这三项 ====", f" 1) {boot_name} -> Bootloader @ 0x{BOOT_FLASH_ADDR:08X}", f" 2) {mcu_name} -> MCU APP @ 0x{APP_FLASH_ADDR:08X}", f" 3) {comb_name} -> 外部 Flash 从 0x0 起整包", " (= 音色区 + 填充 + UI0902 图片区)", "", "建议步骤:", " - 先刷 Boot(否则 USB 烧录等待画面仍是旧红字「升级模式」)", " - 再刷 APP", " - 先整片擦除外部 Flash,再刷上述 ALL .res @ 0x00000000", " - 勿只刷音色区,否则模式选择页会花屏(UI 图在 0x100000)", "", "外部 Flash 分区:", " 0x00000000 音色/充电图 (toneRes)", " logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin(HKTK/2s) + 3.bin", " 0x00100000 UI0902 图片 (模式选择、开机 Logo、充电、烧录模式、设置/调音台等)", " 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图(Boot 读取)", "", f" {boot_name} size={boot_dst.stat().st_size} @ 0x{BOOT_FLASH_ADDR:08X}", f" sha256={sha256(boot_dst)}", f" {mcu_name} size={mcu_dst.stat().st_size} @ 0x{APP_FLASH_ADDR:08X}", f" sha256={sha256(mcu_dst)}", f" {comb_name} size={comb_dst.stat().st_size} @ 0x00000000", f" sha256={sha256(comb_dst)}", "", "说明:", " - Boot 进入 USB IAP 时显示 UI0902_FLASH_MODE;资源未烧录时白字黑底兜底。", " - 烧录模式图来源:K1标准界面图 0904/烧录模式.png。", " - 开机全屏 Logo、关机充电全屏画面在 UI0902,不在音色包里的 logo.bin/Charg.bin 占位。", " - AutoBand 0x9EB5F 本版未重排。", "", "附件: SoundWalkerIAP.exe / 升级步骤.docx / FLASH_MAP.txt", "", ] ), 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()