EB引脚配置自动化方案|告别手动繁琐配PIN,Excel+Python批量生成ARXML实现一键配置

一、前言:传统EB引脚配置的痛点

近期在做国芯516pin芯片的EB配置开发工作,该芯片引脚数量多、PORT端口占用量大,引脚功能配置繁杂。在传统手动配置模式下,仅PORT引脚的参数配置工作就需要耗费大量时间和人力,同时存在两个核心痛点:

  1. 效率极低:数百个引脚的模式、方向、上下拉、功能复用等参数需要逐一点击配置,重复性操作多,耗时耗力;

  2. 出错率高:人工手动配置极易出现参数填错、引脚漏配、功能选错等问题,后续排查纠错成本极高。

为解决以上问题,我摸索出一套Excel+Python自动化EB引脚配置方案,通过表格统一管理引脚参数,脚本自动解析生成标准ARXML配置文件,再导入EB工具生成最终CFG配置文件,彻底替代手动配置,大幅提升开发效率、规避人工错误。

二、整体自动化实现思路

整套方案核心逻辑:标准化Excel引脚参数表格 → Python脚本批量解析 → 自动生成EB识别的ARXML文件 → EB导入文件一键生成CFG配置,全程无需手动逐个配置引脚参数。

整体落地流程分为四大步骤:制作ARXML模板、定位EB核心配置脚本、搭建标准化Excel配置表、编写Python自动生成脚本,最终完成EB导入生成。

三、详细落地实操流程

1、导出EB标准PORT配置ARXML模板

ARXML是EB工具的标准配置导入文件,想要实现自动配置,首先需要获取适配当前芯片、当前EB工程的标准ARXML模板,保证后续生成的文件格式、字段、参数完全兼容EB工具。

模板导出步骤:在EB工程的项目名称上右键,选择导入导出功能,根据工具指引,导出当前工程PORT模块的标准ARXML配置文件,作为后续脚本生成文件的基准模板。

2、定位EB底层配置参数文件(.m文件)

EB所有PORT引脚的配置参数、模式定义、属性规则,均由对应的.m配置文件定义,这是保障自动化配置参数精准有效的核心依据。

文件存放路径 :EB安装目录下的 plugins\Port_ARCH3DXXM10I0RX\generate 文件夹中

该文件定义了PORT引脚的全部可配置参数,包含引脚模式、端口方向、复用功能、上下拉模式等所有EB可视化配置选项,后续Excel表格的字段、Python脚本的参数映射,均需要严格对齐该文件的参数规则。

3、搭建标准化Excel引脚配置表

为了统一管理所有引脚参数,规避配置混乱,完全参照EB工具的原生配置选项,搭建专属Excel配置表格。所有引脚参数提前从芯片pinmux手册中筛选提取,统一录入表格,实现引脚配置标准化、可追溯、可批量修改。

Excel核心配置字段(完全对齐EB配置项)

  • ID:引脚唯一标识

  • PCR:引脚控制寄存器配置参数

  • DIR:引脚方向配置(输入/输出)

  • FUNCTION:引脚复用功能配置

  • PULLPUSH:引脚上下拉/推挽模式配置

  • 拓展字段:可根据EB实际配置项,新增速率、中断模式、默认电平参数等

所有引脚配置信息统一录入表格后,后续修改、新增、删除引脚配置,仅需编辑表格即可,无需操作EB工具。

4、Python脚本自动化生成ARXML文件

基于搭建好的Excel表格和标准ARXML模板,编写Python自动化解析生成脚本。脚本核心功能:

  1. 批量读取Excel表格中所有引脚的标准化配置参数;

  2. 按照EB的ARXML模板格式、.m文件参数规则,自动填充、替换模板中的引脚配置内容;

  3. 批量生成可被EB工具直接识别、导入的完整ARXML配置文件。

脚本运行后,无需人工干预,一键生成完整引脚配置文件,彻底解放双手。

5、EB导入配置,一键生成CFG文件

  1. 打开EB工程,通过导入功能,将Python脚本生成的ARXML文件导入工程;

  2. 导入完成后,直接点击EB生成按钮,即可自动生成对应的PORT配置CFG文件;

  3. 最终配置效果与手动逐一点击配置完全一致,且参数零错误、高效率。

四、方案核心优势

  1. 效率质变:数百个引脚配置从数小时人工操作,缩短至几十秒脚本自动生成,极大节省开发工时;

  2. 零人工误差:参数统一基于pinmux手册录入表格,脚本精准映射参数,彻底杜绝人工漏配、错配问题;

  3. 可维护性强:所有引脚配置集中在Excel表格,版本管理、参数修改、批量迭代极其方便;

  4. 通用性高:方案可适配同系列多款芯片,仅需更新Excel字段和对应ARXML模板即可复用。

五、方案使用限制与注意事项

该自动化方案稳定性依赖两大核心条件,也是方案最大的使用限制,实操过程中需要重点注意:

  1. MCU Pinmux手册必须稳定:芯片引脚功能、寄存器参数、复用定义不能频繁变更,若pinmux参数迭代更新,会导致表格参数与实际芯片定义不匹配,生成的配置文件存在隐患;

  2. EB插件配置规则固定:EB对应的PORT插件、.m底层配置文件参数规则不能改动。一旦EB插件更新、参数字段变更,会出现Excel参数、脚本映射规则与EB解析规则不匹配,导致ARXML配置失效、报错。

总结:只要芯片Pinmux定义、EB插件配置规则保持稳定,这套自动化方案可以完美落地,大幅提升PORT引脚配置效率,降低开发失误率。

六、资源开源说明

本文配套全部源文件已整理完毕,包含:标准化Excel引脚配置模板、Python自动生成脚本、EB标准ARXML模板文件,大家可以直接下载作为参考。

七、总结与展望

传统EB引脚手动配置模式,在多引脚芯片开发中存在明显短板,效率低、容错率差。而Excel+Python+ARXML的自动化配置方案,通过标准化、脚本化、批量化的思路,完美解决了多引脚PORT配置的痛点。

后续可继续优化脚本功能,增加参数校验、异常报错、配置比对功能,进一步提升方案的稳定性和智能化程度,适配更多型号芯片的EB自动化配置开发。

python代码如下:

复制代码
import xml.etree.ElementTree as ET
import pandas as pd
import re
import os

# ===================== 全局路径配置 =====================
EXCEL_PATH = r"C:/Users/ccore/Desktop/08PCSN_EB_Config.xlsx"
ORIGINAL_ARXML_PATH = r"C:/Users/ccore/Desktop/EPS_3008PCSN_HL/EB_TEST.arxml"
OUTPUT_ARXML_PATH = r"C:/Users/ccore/Desktop/EPS_3008PCSN_HL/EB_TEST_FIXED.arxml"
RESOURCE_FILE_PATH = r"D:/EB/plugins/Port_ARCH3DXXM10I0RX/generate/Matterhorn_Resource_Ccfc3008pcsn.m"

NS = {'ns': 'http://autosar.org/schema/r4.0'}
ET.register_namespace('', 'http://autosar.org/schema/r4.0')
ET.register_namespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance')


def bool_to_int(value):
    if pd.isna(value):
        return '0'
    val_str = str(value).strip()
    if val_str in ['是', 'true', 'True', '1']:
        return '1'
    return '0'


def normalize_mode_name(mode_name):
    if not mode_name:
        return mode_name
    name = mode_name.strip().upper()
    replacements = {
        "SENTO": "SENT0",   # 数字0容易写成字母O
        "SENT0_RX": "SENT0_RX",  # 已正确则不变
        "GTM_TOMO": "GTM_TOM",   # 修正可能缺少的 'M'
    }
    for old, new in replacements.items():
        if old in name:
            name = name.replace(old, new)
    return name

def parse_resource_pinmap(resource_path, keep_first=True, override_map=None, verbose=False):
    with open(resource_path, 'r', encoding='utf-8') as f:
        content = f.read()

    # 定位 $PortUseEIOMUX=1 分支
    start_marker = '[!IF "$PortUseEIOMUX=1"!]'
    end_marker = '[!ELSE!]'
    start_pos = content.find(start_marker)
    if start_pos == -1:
        print("警告:未找到 $PortUseEIOMUX=1 分支,将尝试提取所有 _PORT 条目。")
        pinmap_section = content
    else:
        end_pos = content.find(end_marker, start_pos)
        if end_pos == -1:
            end_pos = content.find('[!ENDIF!]', start_pos)
            if end_pos == -1:
                pinmap_section = content[start_pos + len(start_marker):]
            else:
                pinmap_section = content[start_pos + len(start_marker):end_pos]
        else:
            pinmap_section = content[start_pos + len(start_marker):end_pos]

    # 提取 PinMap 变量
    pinmap_pattern = re.compile(r'\[\!VAR "PinMap"\!\](.*?)\[\!ENDVAR\!\]', re.DOTALL)
    pinmap_match = pinmap_pattern.search(pinmap_section)
    if pinmap_match:
        pinmap_text = pinmap_match.group(1)
    else:
        print("警告:未找到 PinMap 变量,将尝试从当前块中提取所有 _PORT 条目。")
        pinmap_text = pinmap_section

    # 匹配所有 _PORT 条目
    pattern = re.compile(r'([A-Za-z0-9_]+)_PORT(\d+);(\d+)')
    matches = pattern.findall(pinmap_text)

    if not matches:
        print("警告:在 PortUseEIOMUX=1 分支中未找到任何 _PORT 条目。")
        return {}

    temp = {}
    for mode_name, pcr_str, idx_str in matches:
        pcr = int(pcr_str)
        idx = int(idx_str)
        if pcr not in temp:
            temp[pcr] = {}
        if idx in temp[pcr]:
            if keep_first:
                if verbose:
                    print(f"警告:PCR{pcr} 索引{idx} 重复定义,保留第一个 '{temp[pcr][idx]}',忽略 '{mode_name}'")
            else:
                if verbose:
                    print(f"警告:PCR{pcr} 索引{idx} 重复定义,覆盖为 '{mode_name}'")
                temp[pcr][idx] = mode_name
        else:
            temp[pcr][idx] = mode_name

    # 应用手动覆盖
    if override_map:
        for pcr, idx_dict in override_map.items():
            if pcr not in temp:
                temp[pcr] = {}
            for idx, mode in idx_dict.items():
                temp[pcr][idx] = mode
                print(f"手动修正:PCR{pcr} 索引{idx} 设置为 '{mode}'")

    # 转换为列表(缺失索引用 GPIO 补位)
    pcr_mode_map = {}
    for pcr, idx_dict in temp.items():
        max_idx = max(idx_dict.keys())
        mode_list = [None] * (max_idx + 1)
        for idx, mode in idx_dict.items():
            mode_list[idx] = mode
        mode_list = [m if m is not None else "PORT_PIN_MODE_GPIO" for m in mode_list]
        pcr_mode_map[pcr] = mode_list

    print(f"解析完成,共识别到 {len(pcr_mode_map)} 个 PCR 的映射。")
    return pcr_mode_map


def create_parameter_node(parent, param_type, def_ref, value):
    param = ET.SubElement(parent, param_type)
    def_node = ET.SubElement(param, "DEFINITION-REF")
    def_node.text = def_ref.strip()
    val_node = ET.SubElement(param, "VALUE")
    val_node.text = str(value).strip()
    return param


def create_pin_container(pin_name, pin_row, pcr_map):
    """单引脚 ECUC-CONTAINER-VALUE 生成,适配新 Excel 列名"""
    pin_container = ET.Element("ECUC-CONTAINER-VALUE")
    ET.SubElement(pin_container, "SHORT-NAME").text = pin_name
    def_ref = ET.SubElement(pin_container, "DEFINITION-REF")
    def_ref.text = "/PortConfig/Port/PortConfigSet/PortContainer/PortPin"
    param_root = ET.SubElement(pin_container, "PARAMETER-VALUES")

    # ---------- 布尔参数(新列名) ----------
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinWpde",
                          bool_to_int(pin_row.get("PinWpde", "否")))
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinWpue",
                          bool_to_int(pin_row.get("PinWpue", "否")))
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinSafeMode",
                          bool_to_int(pin_row.get("Pin Safe Mode", "否")))
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinWithReadBack",
                          bool_to_int(pin_row.get("PinWithReadBack", "否")))
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinHysteresisControl",
                          bool_to_int(pin_row.get("PinHysteresisControl", "否")))
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinModeChangeable",
                          bool_to_int(pin_row.get("Pin Mode Changeable", "否")))
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinDirectionChangeable",
                          bool_to_int(pin_row.get("Pin Direction Changeable", "否")))

    # ---------- 方向参数(将 'O'/'I' 转为完整枚举) ----------
    raw_dir = str(pin_row.get("Pin Direction", "I")).strip().upper()
    if raw_dir == "O":
        direction_val = "PORT_PIN_OUT"
    else:  # 默认输入
        direction_val = "PORT_PIN_IN"

    create_parameter_node(param_root, "ECUC-TEXTUAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinDirection",
                          direction_val)

    # ---------- 其他数值/文本参数(新列名) ----------
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinId",
                          pin_row.get("Pin Id", "0"))
    create_parameter_node(param_root, "ECUC-NUMERICAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinPcr",
                          pin_row.get("tPinPcr", "0"))

    # 注意:PortPin Initial Mode 已被删除,不再生成节点

    create_parameter_node(param_root, "ECUC-TEXTUAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinInputLevelSelection",
                          pin_row.get("PinInputLevelSelection", "AUTOMOTIVE"))
    create_parameter_node(param_root, "ECUC-TEXTUAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinLevelValue",
                          pin_row.get("Pin Level Value", "PORT_PIN_LEVEL_LOW"))
    create_parameter_node(param_root, "ECUC-TEXTUAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinOutputDriveControl",
                          pin_row.get("PinOutputDriveControl", "PORT_OUTPUT_BUFFER_DISABLED"))
    create_parameter_node(param_root, "ECUC-TEXTUAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinOutputEdgeRateControl",
                          pin_row.get("PinOutputEdgeRateControl", "WEAK_DRIVE"))

    # ---------- 核心:PortPinMode 处理 ----------
    # 优先使用 Excel 中手动填写的 PinMode
    custom_mode = str(pin_row.get("PinMode", "")).strip()
    if custom_mode and custom_mode != "":
        # 简单标准化(若需要可调用 normalize_mode_name)
        pin_mode = custom_mode
        print(f"使用自定义模式[{pin_name}]:{pin_mode}")
    else:
        # 否则根据方向和 FuncIndex 查找
        try:
            pcr_num = int(pin_row.get("tPinPcr", 0))
            func_idx = int(pin_row.get("FuncIndex", 0))
            direction = str(pin_row.get("Pin Direction", "I")).strip().upper()
            if direction == "O":
                actual_index = func_idx + 20
            else:  # 输入
                actual_index = func_idx
        except Exception as e:
            print(f"警告[{pin_name}]:数值异常,默认GPIO,err={e}")
            pin_mode = "PORT_PIN_MODE_GPIO"
        else:
            if pcr_num in pcr_map:
                mode_list = pcr_map[pcr_num]
                if 0 <= actual_index < len(mode_list):
                    pin_mode = mode_list[actual_index]
                    dir_desc = "OUT" if direction == "O" else "IN"
                    print(f"匹配[{pin_name}] PCR{pcr_num} {dir_desc} 索引{actual_index} → {pin_mode}")
                else:
                    print(f"警告[{pin_name}] 索引{actual_index}越界(PCR{pcr_num}长度{len(mode_list)}),使用GPIO")
                    pin_mode = "PORT_PIN_MODE_GPIO"
            else:
                print(f"警告[{pin_name}] PCR{pcr_num}未定义,使用GPIO")
                pin_mode = "PORT_PIN_MODE_GPIO"

    create_parameter_node(param_root, "ECUC-TEXTUAL-PARAM-VALUE",
                          "/PortConfig/Port/PortConfigSet/PortContainer/PortPin/PortPinMode",
                          pin_mode)

    ET.SubElement(pin_container, "SUB-CONTAINERS")
    return pin_container


def main():
    print("===== 开始解析 C*Core 资源文件(仅 PortUseEIOMUX=1 分支) =====")
    pcr_mode_map = parse_resource_pinmap(RESOURCE_FILE_PATH, keep_first=True)
    print(f"资源解析完成,PCR 列表:{sorted(pcr_mode_map.keys())}\n")

    print("===== 读取 Excel 引脚配置 =====")
    df = pd.read_excel(EXCEL_PATH)
    df.columns = df.columns.str.strip()  # 去除列名空格

    # 必要列(名称已更新)
    required_cols = ["名称", "分组名称", "PinPcr", "FuncIndex"]
    missing = [col for col in required_cols if col not in df.columns]
    if missing:
        print(f"错误:Excel 缺少必要列:{missing}")
        return

    df_valid = df.dropna(subset=required_cols).reset_index(drop=True)
    print(f"Excel 有效引脚行数:{len(df_valid)}")

    # 统计分组
    group_count = {}
    for _, row in df_valid.iterrows():
        g = row["分组名称"]
        group_count[g] = group_count.get(g, 0) + 1

    print("\n===== 加载原始 ARXML 文件 =====")
    if not os.path.exists(ORIGINAL_ARXML_PATH):
        print(f"错误:原始 ARXML 不存在 {ORIGINAL_ARXML_PATH}")
        return
    tree = ET.parse(ORIGINAL_ARXML_PATH)
    root = tree.getroot()

    port_cfg_set = root.find('.//ns:ECUC-CONTAINER-VALUE[ns:SHORT-NAME="PortConfigSet"]', NS)
    if port_cfg_set is None:
        print("ERROR:未找到 PortConfigSet 节点")
        return

    sub_root = port_cfg_set.find("ns:SUB-CONTAINERS", NS)
    if sub_root is None:
        sub_root = ET.SubElement(port_cfg_set, "SUB-CONTAINERS")
    else:
        old_groups = sub_root.findall("ns:ECUC-CONTAINER-VALUE", NS)
        for g in old_groups:
            sub_root.remove(g)
        print("已清空原有 Port 分组")

    group_dict = {}
    print("\n===== 创建 Port 分组容器 =====")
    for g_name, pin_total in group_count.items():
        group_node = ET.SubElement(sub_root, "ECUC-CONTAINER-VALUE")
        ET.SubElement(group_node, "SHORT-NAME").text = g_name
        def_ref = ET.SubElement(group_node, "DEFINITION-REF")
        def_ref.text = "/PortConfig/Port/PortConfigSet/PortContainer"

        param_grp = ET.SubElement(group_node, "PARAMETER-VALUES")
        num_param = ET.SubElement(param_grp, "ECUC-NUMERICAL-PARAM-VALUE")
        num_def = ET.SubElement(num_param, "DEFINITION-REF")
        num_def.text = "/PortConfig/Port/PortConfigSet/PortContainer/PortNumberOfPortPins"
        ET.SubElement(num_param, "VALUE").text = str(pin_total)
        ET.SubElement(group_node, "SUB-CONTAINERS")
        group_dict[g_name] = group_node
        print(f"创建分组【{g_name}】引脚总数:{pin_total}")

    print("\n===== 批量生成引脚配置 =====")
    for _, row in df_valid.iterrows():
        pin_name = row["名称"]
        g_name = row["分组名称"]
        group_node = group_dict.get(g_name)
        if group_node is None:
            print(f"跳过{pin_name}:分组{g_name}不存在")
            continue
        pin_parent = group_node.find("SUB-CONTAINERS")
        exist_pin = pin_parent.find(f'ECUC-CONTAINER-VALUE[SHORT-NAME="{pin_name}"]')
        if exist_pin is not None:
            print(f"跳过重复引脚 {g_name}/{pin_name}")
            continue
        pin_elem = create_pin_container(pin_name, row, pcr_mode_map)
        pin_parent.append(pin_elem)

    print(f"\n===== 输出修复后 ARXML:{OUTPUT_ARXML_PATH} =====")
    ET.indent(tree, space="  ")
    tree.write(OUTPUT_ARXML_PATH, encoding="utf-8", xml_declaration=True)
    print("✅ ARXML 生成完成。")


if __name__ == "__main__":
    main()
相关推荐
江华森1 小时前
jieba 分词 + 共现矩阵构建——《釜山行》人物关系提取(二)
python
用户298698530141 小时前
Python 实现 Excel 新旧格式互转:告别 Office 依赖的自动化方案
后端·python·excel
进击切图仔1 小时前
基于千问的白盒蒸馏 SOP
运维·服务器·人工智能·python·效率
2401_868534781 小时前
案例介绍-小型网络规划及搭
python·计算机网络
ONE_SIX_MIX2 小时前
lightweight-charts-onesixth v2.8.1 → v3.1.1 更新总览
java·前端·python
YUS云生2 小时前
Python学习笔记·第29天:Git进阶——分支操作与合并冲突
笔记·python·学习
奋斗的小方2 小时前
Java进阶篇1-3:集合框架(单列集合)
java·windows·python
乐以礼2 小时前
#100DaysOfAgent Day 5
python·ai
江华森2 小时前
Gephi 可视化 + NetworkX 网络分析——《釜山行》人物关系(三)
数据库·python