一、前言:传统EB引脚配置的痛点
近期在做国芯516pin芯片的EB配置开发工作,该芯片引脚数量多、PORT端口占用量大,引脚功能配置繁杂。在传统手动配置模式下,仅PORT引脚的参数配置工作就需要耗费大量时间和人力,同时存在两个核心痛点:
-
效率极低:数百个引脚的模式、方向、上下拉、功能复用等参数需要逐一点击配置,重复性操作多,耗时耗力;
-
出错率高:人工手动配置极易出现参数填错、引脚漏配、功能选错等问题,后续排查纠错成本极高。
为解决以上问题,我摸索出一套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自动化解析生成脚本。脚本核心功能:
-
批量读取Excel表格中所有引脚的标准化配置参数;
-
按照EB的ARXML模板格式、.m文件参数规则,自动填充、替换模板中的引脚配置内容;
-
批量生成可被EB工具直接识别、导入的完整ARXML配置文件。
脚本运行后,无需人工干预,一键生成完整引脚配置文件,彻底解放双手。


5、EB导入配置,一键生成CFG文件
-
打开EB工程,通过导入功能,将Python脚本生成的ARXML文件导入工程;
-
导入完成后,直接点击EB生成按钮,即可自动生成对应的PORT配置CFG文件;
-
最终配置效果与手动逐一点击配置完全一致,且参数零错误、高效率。
四、方案核心优势
-
效率质变:数百个引脚配置从数小时人工操作,缩短至几十秒脚本自动生成,极大节省开发工时;
-
零人工误差:参数统一基于pinmux手册录入表格,脚本精准映射参数,彻底杜绝人工漏配、错配问题;
-
可维护性强:所有引脚配置集中在Excel表格,版本管理、参数修改、批量迭代极其方便;
-
通用性高:方案可适配同系列多款芯片,仅需更新Excel字段和对应ARXML模板即可复用。
五、方案使用限制与注意事项
该自动化方案稳定性依赖两大核心条件,也是方案最大的使用限制,实操过程中需要重点注意:
-
MCU Pinmux手册必须稳定:芯片引脚功能、寄存器参数、复用定义不能频繁变更,若pinmux参数迭代更新,会导致表格参数与实际芯片定义不匹配,生成的配置文件存在隐患;
-
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()