Files
DigiRadio/Software/tools/extract_si4684_blob.py
T
micheleandCursor 81d404a1df Add Slice 4 tuner stack with AGENTS-compliant core types and HTTP API.
Introduces Si4684/ADAU1701 drivers, TunerService, FrequencyKHz,
SeekDirection, /api/tuner routes without file-scope globals, host tests,
and firmware blob tooling (binaries remain gitignored).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:17:56 +02:00

89 lines
3.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""extract_si4684_blob.py — convert Si4684 C arrays or copies to .bin files.
DigiRadio firmware — https://github.com/manvalan/DigiRadio
Copyright 2026 Michele Bigi
SPDX-License-Identifier: Apache-2.0
Usage:
# From a SigmaStudio-style C header (PE5PVB firmware.h, Si468xROM.h):
tools/extract_si4684_blob.py --header src/firmware.h --array firmware \\
--out Firmware/Si4684-Firmware/dab_firmware.bin
# Copy an existing Skyworks .bin from an eval-board folder:
tools/extract_si4684_blob.py --copy ~/si46xx_firmware/fm_radio_5_1_0.bin \\
--out Firmware/Si4684-Firmware/fm_firmware.bin
"""
from __future__ import annotations
import argparse
import re
import shutil
import sys
from pathlib import Path
def extract_c_array(header: Path, array_name: str) -> bytes:
text = header.read_text(encoding="utf-8", errors="replace")
pattern = rf"{re.escape(array_name)}\s*\[\s*\]\s*=\s*\{{(.*?)\}};"
match = re.search(pattern, text, re.S)
if not match:
raise SystemExit(f"array {array_name!r} not found in {header}")
values = re.findall(r"0x[0-9a-fA-F]+", match.group(1))
if not values:
raise SystemExit(f"array {array_name!r} is empty in {header}")
return bytes(int(v, 16) for v in values)
def extract_flash_region(dump: Path, offset: int, size: int) -> bytes:
data = dump.read_bytes()
end = offset + size
if end > len(data):
raise SystemExit(
f"flash dump too small: need {end} bytes, have {len(data)}"
)
return data[offset:end]
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--out", type=Path, required=True, help="output .bin path")
src = ap.add_mutually_exclusive_group(required=True)
src.add_argument("--header", type=Path, help="C header with byte array")
src.add_argument("--copy", type=Path, help="existing firmware .bin to copy")
src.add_argument("--flash-dump", type=Path, help="full SPI flash dump")
ap.add_argument("--array", help="array name when using --header")
ap.add_argument(
"--offset",
type=lambda s: int(s, 0),
default=0x106000,
help="byte offset for --flash-dump (default: FM region from dirb.tech)",
)
ap.add_argument(
"--size",
type=lambda s: int(s, 0),
default=0x81721,
help="byte length for --flash-dump (default: FM V5.1.0 size)",
)
args = ap.parse_args()
if args.header is not None:
if not args.array:
ap.error("--array is required with --header")
payload = extract_c_array(args.header, args.array)
elif args.copy is not None:
payload = args.copy.read_bytes()
else:
payload = extract_flash_region(args.flash_dump, args.offset, args.size)
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_bytes(payload)
print(f"wrote {args.out} ({len(payload)} bytes)")
return 0
if __name__ == "__main__":
raise SystemExit(main())