十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

新能源控制器应用层开发规范:从AUTOSAR分层到标定落地

新能源控制器应用层开发规范:从AUTOSAR分层到标定落地 简介面向新能源汽车软件工程师的AUTOSAR开发设计规范适用于整车厂、供应商及嵌入式软件团队完善软件架构与编码标准。文档以分层解耦为主线详细说明应用层、运行时环境RTE与基础软件层BSW的职责划分其中BSW又细分为微控制器抽象层MCAL、ECU抽象层、服务层和复杂驱动帮助开发者理解标准化的基础软件如何支撑上层应用。应用层部分覆盖Unit单元设计、Component模块设计、System系统设计、变量管理、Simulink工程目录与配置以及转换标定变量文件、a2l标定文件等定制工具开发兼顾模型与手写代码两种场景。此外还给出软件编程规范的命名规则、建模规则与C语言编程规则并梳理了岗位职责和开发流程。资源包共1个PDF文件大小1.53MB结构清晰已有912人学习可作为团队规范建设或个人系统学习的参考资料。1. 为什么新能源控制器应用层开发要先定规范接触过 VCU、BMS 或氢堆控制器的人都有体会应用层模型如果只按功能堆 Unit三个月后连写代码的人自己都理不清信号从哪来、标定量为什么叫这个名字。更现实的问题是Simulink 模型生成的 Global.c 里标定量地址是随机的不重新映射段区间烧进单片机后 flash 规划直接被打乱A2L 文件如果不与 map 文件合并CANape 连变量地址都看不到。这套规范解决的就是这类工程落地问题——从 AUTOSAR 分层里应用层的职责边界到 Unit、Component、System 的三级粒度划分再到变量管理表格、Simulink 工程配置、A2L 合并流程每一环都直接关系到最后能不能顺利标定、集成和排查故障。适合正在搭应用层开发流程的嵌入式软件工程师也适合刚接手商用车控制器项目的朋友对照检查自己的工程目录。2. 从 AUTOSAR 分层看应用层职责边界Unit、Component 与 System 的粒度划分2.1 四层体系里应用层该管什么AUTOSAR 架构把软件分成应用层、RTE、基础软件层和微控制器四层。应用层位于 RTE 之上OEM 在这里做有竞争力的控制策略RTE 负责隔离让应用层不直接碰寄存器基础软件层里的 MCAL 管硬件驱动ECU 抽象层统一外设访问接口服务层提供操作系统、网络管理和内存服务。复杂驱动比较特殊它跨在硬件和 RTE 之间处理喷油控制、电磁阀这类对时间要求苛刻的非标准功能。实际做新能源控制器时我的经验是应用层只需保证整体架构满足 AUTOSAR 分层要求不必纠结底层怎么实现。真正要下功夫的是应用层内部的 Unit、Component、System 三级架构因为这三个层级的划分方式决定了模型能不能并行开发、能不能复用、以及故障定位的粒度。2.2 Unit 设计要交代清楚哪些信号Unit 是最小子系统定义是“具备一个完整且明确功能定义的子系统”比如车速计算、纯电上下电、氢堆上下电。每个 Unit 设计时至少要三步明确需求、确认策略、确认信号的输入输出和标定量。第三步最容易被忽视。规范里要求列一张信号表包含信号名称、功能、类型输入/输出/标定、信号源、取值范围、数据类型和分辨率。这张表就是后续变量管理的原始依据。我一般会让软件功能规范里直接带这张表评审时先查信号表再查模型能省掉大量返工。2.3 Component 分组与 System 归类的参考做法Component 是把同类 Unit 归组。比如电源管理模块包含纯电上下电和氢堆上下电驱动管理模块包含回馈制动、巡航控制、蠕行。Component 层还要额外分出输入信号模块、输出信号模块、任务调度模块、故障诊断模块和故障处理模块。这个分法和很多人习惯的“按功能分模型”不同——输入输出单独成模块是为了在集成时能统一处理总线信号和 IO 信号的转换、滤波、有效性检查。模块内部加载 Unit 时Simulink 里推荐用 Model Reference而不是把单元直接复制到上级模型中。这样每个 Unit 可以独立编译、独立做单元测试生成代码时也能保留模块边界。System 层则只负责归类输入信号处理归一类逻辑控制处理归一类输出信号处理归一类让顶层模型看起来足够干净。2.4 信号属性定义表参考以下表格是 Unit 设计阶段常用的信号描述格式建议直接写进软件功能规范信号名称功能描述类型信号源取值范围数据类型分辨率VehSpd_Valid车速信号有效标志输入CAN来自 BMS[0,1]UInt81VehSpd_Act实际车速输入CAN来自 TCU[0,250]UInt160.1TorqReq_Out扭矩请求输出输出驱动管理模块[-1000,1000]Int160.5BrkPedal_Max制动踏板最大开度标定标定量N/A[0,100]UInt80.1表格里“类型”要区分模型输入、模型输出还是内部标定量“信号源”写来自哪个 Unit 或总线“分辨率”要提前约定否则合并 A2L 时会出现换算不一致的问题。这四列是后续变量管理脚本能否正确生成 Simulink 对象的关键。3. Simulink 工程里的变量管理与模型配置从 xlsx 到 workspace 再到全局 C 文件3.1 Signal_Param.xlsx 的表结构约定变量管理是整个应用层模型能否自动化的核心。规范里把输入输出信号放在 Signal sheet标定量放在 Parameter sheet然后通过一个 DataDictionaryImport.m 脚本把它们加载到 workspace。这里有一个容易踩的坑sheet 名字必须改成 Signal 和 Parameter而且第一行必须是表头否则 xlsread 读出来的数据错位。Signal sheet 至少要有四列Owner所属 Unit、Name变量名、Datatype数据类型、Storage Class存储类型再加一列 mark 说明信号用途。Parameter sheet 则需要额外一列 Initial Value。举个例子Owner | Name | Datatype | Storage Class | Initial Value | mark Unit1 | Param1 | Uint8 | ExportedGlobal | 1 | 标定量 Unit1 | Signal1 | Uint8 | ImportedExtern | | 模型输入 Unit1 | Signal2 | Int16 | ExportedGlobal | | 模型输出 Unit1 | Signal3 | fixdt(1,16,2^-7,0)| ExportedGlobal | | 中间信号注意fixdt(1,16,2^-7,0)这种定点数写法在 Simulink.Signal 里能直接使用。Storage Class 用ImportedExtern表示信号由外部 C 代码定义用ExportedGlobal表示由模型生成全局变量。这两种存储类型决定了最终生成代码里变量是extern声明还是const定义。3.2 DataDictionaryImport 脚本做了什么脚本核心逻辑是用xlsread读两个 sheet然后遍历每一行创建Simulink.Signal或Simulink.Parameter对象并通过assignin(base, ...)放进 workspace。最后调用Simulink.saveVars把 workspace 里的对象保存成.m文件方便下次加载。在 MATLAB Command 窗口执行DataDictionaryImport(Signal_Param.xlsx, Application.slx)这段命令的含义是读取当前目录下的Signal_Param.xlsx将 Signal 和 Parameter 两个 sheet 中的变量创建为 Simulink 数据对象然后打开Application.slx模型并把模型里同名 Inport/Outport 的数据类型自动改成表格里对应的类型。注意第二个参数传入的是模型名脚本内部会通过find_mdlrefs递归查找到所有引用的子模型然后把端口类型统一改掉。这里有个细节脚本里的setupModelInterfacePorts会把模型引用的所有子模型的 Inport/Outport 的数据类型强制改成 Signal sheet 里定义的 Name 对应的类型。所以表格里的 Name 必须和模型端口名完全一致大小写都不能差。建议在模型搭建阶段就把端口名和表格一起维护否则跑完脚本后很容易出现端口类型被改错的情况。3.3 工程目录与 open_model.m 的用法规范推荐的目录结构分四层P1_Specification 放功能规范P2_Model 放单元模型和系统模型P3_Code 放生成的代码和加载脚本P4_DataManagement 放变量管理表格和 DataDictionaryImport.m。我习惯在 P3_Code 下放一个open_model.m内容大致如下%% 将工程根目录加入 MATLAB 路径 addpath(genpath(../../), -frozen); %% 加载变量管理表格并更新模型端口类型 DataDictionaryImport(Signal_Param.xlsx, Application.slx); %% 打开系统应用模型 open_system(Application.slx);为什么用相对路径../../而不是绝对路径因为工程经常在多人之间同步用相对路径可以保证换一台电脑、换一个盘符后不用改脚本。-frozen参数可以避免反复添加路径时把重复目录再扫一遍能节省大型模型加载时间。3.4 模型级配置的五个关键点模型配置里最容易漏掉的是 ASAP2 interface 勾选和求解器设置。规范里点名了几项我结合实践补充说明配置项设置值作用与坑Solver TypeFixed-Step控制模型生成代码时不会出现变步长逻辑Solverdiscrete (no continuous states)避免生成连续状态求解代码减少 flash 占用Tasking and sample time optionsEnsure sample time independent防止不同周期的任务被强制对齐影响调度Interface - ASAP2 interface勾选生成 A2L 文件漏了后面标定会很麻烦Code Generation - Global variables指定文件名如 global.c将所有全局变量集中生成到指定文件求解器选discrete (no continuous states)意味着你不在模型里用连续积分器所有状态都用离散 Unit Delay 或 Discrete-Time Integrator。实际做整车控制策略时几乎不会用到连续状态所以这个设置很合理。Ensure sample time independent则让每个模块可以有自己的采样时间不受顶层任务周期约束方便在不同控制器之间复用同一套模型。3.5 单元模型里 C Caller 的处理如果 Unit 模型里用到了 C Caller 模块调用手写 C 函数比如查表算法或自定义校验那么对应的.c和.h文件必须放在和单元模型相同的目录下并且要在模型的 Configuration Parameters 里填写文件名。否则代码生成时会提示找不到函数声明。这个坑在集成多个 Unit 时特别容易出现因为 Model Reference 生成的代码会各自引用 C Caller 里的函数名一旦头文件路径不对编译直接报 undefined reference。4. 标定文件落地的两个硬骨头Global.c 段地址与 A2L 合并4.1 为什么模型生成的 Global.c 不能直接烧模型编译后会生成一个 Global.c 文件里面放着所有 Storage Class 为ExportedGlobal的变量定义。问题是这些变量在链接时才分配地址编译器随机排布不满足单片机 flash 的区间规划。项目早期通常会把 flash 划成 boot、APP、标定段、EEPROM 模拟段等区域标定变量必须放在专门的标定段里否则标定时没法通过固定地址读写。解决思路是在 Global.c 里给标定量加段属性。规范里给了两种宏定义#define CALIBRATION_SEG __attribute__ ((section(.cal_ram_section))) #define CALIBRATION_SEG_R __attribute__ ((section(.cal_rom_section)))cal_ram_section用于需要掉电保存但运行时要读写的标定量cal_rom_section用于只读标定量。在常量定义前加上宏就能让变量进入指定段const CALIBRATION_SEG_R boolean_T MD5_OR_AS18;这里要注意const变量如果同时加__attribute__((section))链接脚本里必须保证目标段是只读属性且 flash 编程接口允许直接写入。很多工程师把const去掉改成普通全局变量也能放到cal_ram_section但那样 RAM 占用会变大。实际项目中我一般把这类标定量统一放在cal_rom_section而把需要运行时动态修正的标定量放在cal_ram_section并在下电前做 EEPROM 备份。4.2 手动修改 Global.c 的替换规则模型重新生成代码后Global.c 会被覆盖因此不能每次都手动加宏。常见做法是写一个小脚本在代码生成后自动替换。可以用 MATLAB 的parse正则或者直接用文本替换工具。我的做法是准备一个模板文件保留宏定义和#include部分脚本只替换变量定义块。简单示例import re # 读取生成的 Global.c with open(Global.c, r) as f: content f.read() # 在 const 和变量类型之间插入段属性宏 pattern re.compile(rconst\s(boolean_T|uint8_T|int16_T|uint16_T|real32_T)\s(\w)) content pattern.sub(rconst CALIBRATION_SEG_R \1 \2, content) # 对非 const 的全局变量插入 ram 段属性 pattern2 re.compile(r^(?!const\s)(boolean_T|uint8_T|int16_T|uint16_T|real32_T)\s(\w), re.M) content pattern2.sub(rCALIBRATION_SEG \1 \2, content) with open(Global.c, w) as f: f.write(content)逻辑说明第一步给所有 const 变量加上CALIBRATION_SEG_R第二步给非 const 的全局变量加上CALIBRATION_SEG。注意正则会匹配到函数内部的变量所以建议先把函数代码剔除或者只处理变量定义区域。更稳妥的方式是让模型生成代码时设置“变量定义集中存放”然后把这段脚本作为代码后处理步骤接在编译前。4.3 A2L 合并与 map 文件地址回填A2L 是 CANape 用来描述变量和标定地址的文件。模型生成的每个 Unit 子模型都会自带一个 A2L但这些 A2L 里没有 flash 地址因为地址是链接后才确定的。所以标定文件的制作要分两步先把多个 A2L 合并成一个再用 map 文件更新地址。合并用 MATLAB 自带命令rtw.asap2MergeMdlRefs(Application, merged.a2l)前半部分Application是系统应用模型名后半部分是输出文件名。这个命令会遍历模型引用的所有子模型把它们的接口定义和记录都合并到一个文件中。合并后的 A2L 里变量地址为 0接下来用 CANape 打开 merged.a2l再手动导入编译生成的 map 文件。CANape 会根据变量名和 map 文件里的符号地址一一匹配然后保存回 Application.a2l。实际执行时需要注意map 文件里的符号名和 A2L 里的变量名必须完全一致。如果模型里有局部静态变量map 文件里可能带后缀需要先做符号重映射。另外合并前要确认各 Unit 模型生成的 A2L 里不包含重复的变量名否则asap2MergeMdlRefs会报错或丢弃其中一个。建议在变量管理阶段就做全局唯一性检查。4.4 一个完整的标定文件生成流程# 1. 编译所有模型生成各 Unit 的 A2L 和 Global.c make all # 2. 合并 A2L在 MATLAB 中执行 # rtw.asap2MergeMdlRefs(Application, merged.a2l) # 3. 用后处理脚本修改 Global.c 的段属性 python add_calibration_section.py Global.c # 4. 编译整个 ECU 工程生成 map 文件 make -C ecu_project # 5. 在 CANape 中导入 map 文件并保存最终 A2L每一步之间都有依赖不能跳过。步骤 3 必须在步骤 4 之前因为段属性要体现在最终目标文件的符号地址上。如果你用的是 Tasking 或 GCC__attribute__((section))的语法略有差异但原理相同。注意不同编译器对 section 名字的修饰可能加前缀比如 GCC 会生成.cal_rom_section而 Tasking 可能生成.cal_rom_section也没问题关键看链接脚本里怎么定义。5. 命名规范之外的坑从前缀到 C 编程规则的实际校验5.1 命名规则解析作用域前缀与模块缩写规范的命名规则很有工程味每个变量名由作用域前缀、单元模型名缩写、含义标识、数组/结构体后缀、单位后缀组成。作用域前缀三个Gv全局变量、Cv常量、Lv局部变量。单元模型名缩写是固定表比如aipt表示模拟信号输入cipt表示 CAN 输入copt表示 CAN 输出。我强烈建议在模型里用同样的规则给信号命名。比如一个来自 CAN 的车速信号可以叫Gv_cipt_VehSpd_kmh其中Gv表示全局变量cipt表示 CAN 输入VehSpd是含义_kmh是单位。在 Simulink 中端口名和信号线名都按这个规则走生成代码时变量名可读性会很好。更重要的是A2L 合并后在 CANape 里按前缀过滤变量非常方便Gv_一搜就是全局量Cv_一搜就是标定量。规范里提到下划线原则上不超过三个目的是避免过长。我见过有人把整个信号源都塞进名字里结果变量名比注释还长。实际上保留作用域前缀 来源 含义 单位就足够了更多的信息放进Signal_Param.xlsx的备注列。5.2 建模规则和 C 语言规则怎么落地检查建模规则方面规范要求可读性、可靠性和风格一致性。实际执行时我建议在开发环境里做两层检查第一层是静态检查工具比如 Simulink Model Advisor 或行业的 MAB 规范检查脚本第二层是人工 code review重点看有没有高亮橙色或红色的代数环、有没有连续状态块混入离散模型、有没有直接使用 Goto/From 跨层跳转。Goto/From 是建模里最影响可读性的用法能避免就避免用信号线和 Model Reference 端口替代。C 语言编程规则部分主要针对 C Caller 和手写算法。除了缩进和注释更关键的是变量作用域控制——禁止在头文件里定义变量禁止在函数内使用未初始化指针禁止在中断函数里调用可能阻塞的库函数。这些规则没有自动化工具能完全覆盖所以要求代码走查时对照 MISRA C 的条目逐条过。5.3 一个快速自查脚本思路如果你管理的是一个有很多 Unit 的工程可以写个 Python 脚本扫描所有模型端口名和表格里的 Name 是否匹配。参考思路import re def check_naming(port_names, excel_names): errors [] for p in port_names: if p not in excel_names: errors.append(f端口 {p} 在 Signal_Param.xlsx 中未定义) elif not re.match(r^(Gv|Cv|Lv)_[a-z]\_, p): errors.append(f端口 {p} 不符合命名规则) return errors这个检查放在模型保存回调里或提交前跑一遍能快速发现拼写错误和漏定义。注意正则里的[a-z]只覆盖了单元缩写如果你们的缩写含数字需要按实际列表调整。比较理想的是把规范里的缩写表直接做成 JSON脚本读取后逐一匹配这样新增缩写时只要维护 JSON不需要改脚本。实际项目里命名和建模规范的效果要等三个月后才能看出来。当你要在十几个 Unit 里找一个信号的来源或者要把某个 Unit 从 A 项目搬到 B 项目时规范的收益才会真正放大。所以在搭建应用层模型的时候花一小时把信号表和命名规则定下来比后期补任何工具都值。本文还有配套的精品资源点击获取
返回列表