191 lines
7.3 KiB
Python
191 lines
7.3 KiB
Python
# -*- 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.4"
|
||
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()
|