Files
firmware-lowlevel-development/skills/firmware-lowlevel-bsp-development/scripts/bsp/build_pinmux_csv.py
T

101 lines
4.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate normalized pinmux data and write the final project CSV."""
import argparse
import csv
import json
import re
import sys
from pathlib import Path
HEADERS = ["引脚号", "功能名称", "引脚名称", "有效电平", "FMUX寄存器", "FMUX当前值", "FMUX默认值", "FMUX_list"]
INPUT_FIELDS = ["pin_number", "function_name", "pin_name", "is_gpio", "active_level", "fmux_register", "fmux_current", "fmux_default", "fmux_list"]
NUMBERED_MUX_RE = re.compile(r"(?:^|;\s*)([0-7])\s*:")
UNSAFE_FILENAME_RE = re.compile(r"[/\\:*?\"<>|]+")
def text(value):
return "null" if value is None else str(value).strip()
def safe_name(value):
value = UNSAFE_FILENAME_RE.sub("_", value.strip())
value = re.sub(r"\s+", "_", value)
value = re.sub(r"_+", "_", value).strip(" ._")
if not value:
raise ValueError("project_name 清理后为空")
return value
def normalize(raw, index, seen):
if not isinstance(raw, dict):
raise ValueError(f"rows[{index}] 必须是对象")
missing = [key for key in INPUT_FIELDS if key not in raw]
if missing:
raise ValueError(f"rows[{index}] 缺少字段: {', '.join(missing)}")
pin = text(raw["pin_number"])
if not pin or pin == "null":
raise ValueError(f"rows[{index}] pin_number 为空")
if pin in seen:
raise ValueError(f"rows[{index}] 引脚号重复: {pin}")
seen.add(pin)
if not isinstance(raw["is_gpio"], bool):
raise ValueError(f"rows[{index}] is_gpio 必须是布尔值")
active = text(raw["active_level"])
if raw["is_gpio"] and active not in {"高有效", "低有效", "待确认"}:
raise ValueError(f"rows[{index}] GPIO 有效电平无效")
if not raw["is_gpio"] and active != "null":
raise ValueError(f"rows[{index}] 非 GPIO 有效电平必须为 null")
mux_list = text(raw["fmux_list"])
mux_numbers = set(NUMBERED_MUX_RE.findall(mux_list))
current = text(raw["fmux_current"])
if current in set("01234567") and current not in mux_numbers:
raise ValueError(f"rows[{index}] 引脚 {pin}: FMUX当前值 {current} 未出现在 FMUX_list")
row = {
"引脚号": pin,
"功能名称": text(raw["function_name"]),
"引脚名称": text(raw["pin_name"]),
"有效电平": active,
"FMUX寄存器": text(raw["fmux_register"]),
"FMUX当前值": current,
"FMUX默认值": text(raw["fmux_default"]),
"FMUX_list": mux_list,
}
return row, len(mux_numbers)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input", required=True, type=Path)
parser.add_argument("--project-name")
parser.add_argument("--output-dir", type=Path, default=Path.cwd())
parser.add_argument("--output", type=Path)
args = parser.parse_args()
try:
with args.input.open(encoding="utf-8-sig") as handle:
payload = json.load(handle)
if not isinstance(payload, dict) or not isinstance(payload.get("rows"), list):
raise ValueError("JSON 顶层必须是对象并包含 rows 数组")
project = args.project_name or text(payload.get("project_name", ""))
rows, seen = [], set()
for index, raw in enumerate(payload["rows"]):
row, mux_count = normalize(raw, index, seen)
if mux_count >= 2:
rows.append(row)
output = args.output or args.output_dir / f"{safe_name(project)}_pinmux.csv"
output.parent.mkdir(parents=True, exist_ok=True)
with output.open("w", encoding="utf-8-sig", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=HEADERS)
writer.writeheader()
writer.writerows(rows)
print(f"已输出 {len(rows)} 行(输入 {len(payload['rows'])} 行): {output}")
return 0
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())