ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AUTOSAR代码生成全解析:从ARXML到RTE与NvM的工程实践

AUTOSAR代码生成全解析:从ARXML到RTE与NvM的工程实践 简介面向汽车开放系统架构AUTOSAR与自适应平台AUTOSAR AP的开发者这份代码工程包适用于正在学习AUTOSAR模块化设计或从事汽车ECU软件开发的工程师可用于理解从基础软件BSW、运行时环境RTE到应用软件组件的完整层次。压缩包共1229个文件、2.01MB以C/C源码为主体546个头文件、406个C源文件辅以34个ARXML配置描述、92个mk与28个makefile构建脚本、23个LDF链接描述文件以及少量DBC通信矩阵、Shell脚本等覆盖ECU抽象、软件组件接口定义、模块间通信与构建链路。目前已有187人学习浏览其中ARXML示例涉及多种MCU板级工程如MPC55xx、TMS570、STM32等可作为从零搭建实验环境的起点。借助源码与配置能看清软件组件如何通过RTE交互、自适应平台服务导向架构如何落地以及网络安全机制如何在AP中配置适合需要对照示例快速上手AUTOSAR AP开发的读者。1. 从“点一下生成”到“凌晨排查 RTE”AUTOSAR 代码生成到底在做什么凌晨两点交付前一周我只是在 SWC 接口里加了一个 Data Element点下 Generate编译直接挂掉——Rte_Call 找不到定义。这是每个 AUTOSAR 工程师都经历过的瞬间。标题里的 auto、autosar、code 三个词连起来就是这一行每天的工作用 AUTOSAR 工具链把配置变成 C 代码再把 C 代码连同 BSW 一起烧进 ECU。它不是 IDE 的“一键生成”而是一条从 ARXML 出发、经过 ECU 配置、落到 RTE 与 SWC 桩代码的完整链路。这篇笔记写给正在入门的嵌入式工程师、从 Simulink 模型转 AUTOSAR 的同事以及被 RTE 生成折磨的 ECU 集成人员目标是让你在改接口、配存储、烧写之前先知道每一步生成的代码会出现在哪里又会在哪里埋雷。2. AUTOSAR 代码生成的全貌ARXML、工具链和三层产物2.1 工具链选型DaVinci、EB tresos 与 Simulink 的边界AUTOSAR 代码生成不是只有一个工具。我在实际项目里见过三类角色分工完全不同。第一类是 ECU 配置工具最常用的是 Vector 的 DaVinci Configurator 和 EB 的 tresos Studio。它们负责把 AUTOSAR 的 ECU 配置ECUC描述文件转成 BSW 代码、RTE 代码和 SWC 模板代码。区别在于DaVinci Configurator 配合 DaVinci Developer 使用前者管 ECUC后者管 SWC 的接口与 Runnable 映射EB tresos 更偏向底层 BSW 的深度配置在加密、存储、通信模块上更灵活。选型主要看公司已有的许可证和 OEM 的强制要求没有绝对好坏。第二类是系统级配置工具比如 PREEvision 或 SystemDesk它们生成的是系统描述System Description和 ECU Extract不是直接生成代码。很多团队把这个环节放在上游由系统组维护ECU 组只接一个裁剪好的 ARXML 包。第三类是模型工具链典型的是 Simulink AUTOSAR Blockset 配合 Embedded Coder。它适合控制策略团队你在 Simulink 里建模型导出的 ARXML 和 C 代码可以直接喂给 DaVinci 做 RTE 集成。热词里那个“simulink autosar”问的正是这个环节我后面会提反导时的坑。工具在链路中的角色输出物典型使用人群DaVinci DeveloperSWC 设计Port/Runnable 建模SWC ARXML 描述软件架构工程师DaVinci ConfiguratorECUC 配置模块参数NvM/Com/OsBSW 代码、RTE 代码、Cfg 头文件ECU 集成工程师EB tresos StudioECUC 配置偏底层 BSWBSW 代码、RTE 代码底层驱动工程师Simulink AUTOSAR Blockset模型生成 ARXML 与代码模型级 ARXML、SWC 代码算法工程师2.2 ARXML 是什么三个文件类型把配置和代码串起来ARXMLAUTOSAR XML是 AUTOSAR 方法论的“图纸”。很多人以为配置就是点界面其实界面操作最终都在生成和消费 ARXML。做代码生成时你至少要认识三类文件。第一类叫 ECU ExtractECU 抽取描述从系统描述里裁剪出当前 ECU 需要的内容包括信号、PDU、通信矩阵里跟本 ECU 相关的部分。它决定通信模块生成哪些收发函数。第二类叫 SWC 描述由 DaVinci Developer 或 SystemDesk 生成描述 Component Type、Port、Interface、Runnable 和 Internal Behavior。RTE 生成器靠它决定生成哪些 Read/Write/Call API。第三类叫 ECUC 配置描述这是你在 DaVinci Configurator 里配置的结果涵盖 NvM、Com、Os、EcuM 等所有 BSW 模块参数。它决定 BSW 代码的宏、缓冲区大小、任务周期和优先级。我见过最混乱的团队是把这三类文件全部塞进同一个 SVN 目录谁改都不记录。正确做法是按“系统级 / ECU 级 / 生成物”分三个目录ARXML 入库生成物不入库每次 build 前从配置重新生成。2.3 生成链路全貌ECUC → RTE → SWC 桩代码AUTOSAR 代码生成的标准链路可以拆成四步每一步的输入输出要钉死。第一步是导入 ARXML。在 DaVinci Configurator 里导入 ECU Extract 和 SWC 描述工具会做一次一致性校验检查引用的 Interface、Data Type 是否存在。这个校验不通过时后续生成全是废的。第二步是配置 BSW 模块。在 ECUC 编辑器里配 NvM、Fee、Com、Os、EcuM。这里配的每一个参数最后都会变成生成的 C 代码里的宏、结构体或初始化表。比如你在 NvM 里配了三个 Block生成代码里就有三个 NvM 管理块描述符。第三步是生成 RTE。RTE 生成器读取 SWC 描述和 ECUC 里的 Os 配置生成 Rte_ .c/h、Rte_Type.h、Rte_ _Data.c 等文件。RTE 是 SWC 和 BSW 之间的胶水它把 SWC 的 Runnable 函数和 BSW 模块 API 连接起来。第四步是生成并编译 BSW。BSW 代码生成后连同 RTE、手写的 SWC 实现通常在 Rte_ .c 里填函数体一起交给编译脚本。很多人卡在第四步核心原因是前两步的配置和生成物对不上比如 Runnable 没映射到任务、Data Type 宽度不一致。3. 用 DaVinci Configurator 配一个 SWC 接口从 Port 到 RTE 生成3.1 创建 Data Type 与 Port先定类型再画接口在 DaVinci Developer 里配 SWC 接口顺序不能反。常见的新手翻车是先在 Port 上随手拖一个 Interface回头发现 Data Type 的 Implementation 宽度不对RTE 生成的 API 参数类型和手写代码对不上编译报一堆 warning 或隐式转换。我一般按三步走。第一步定义 Data Type。进入 Data Type 面板新建一个名为 VehicleSpeed 的 Application Data Type底层映射到 Integer 类型Implementation 选 uint16取值范围 0 到 32000。这里必须注意 Implementation 里选的是 uint16 还是 uint8它决定 RTE 生成的 API 参数类型。第二步定义 Interface。新建 Sender-Receiver Interface加入一个 Data Element命名为 VehicleSpeed类型挂到刚建的 VehicleSpeed 上。第三步创建 Port。在 SWC 内部新建一个 PortPort 方向选 ProvideInterface 挂到刚才建的 Interface 上。这样 RTE 生成时就会为这个 SWC 生成 Rte_Write_xxx_VehicleSpeed() 的发送 API。3.2 配置 Runnable 并挂事件把函数体挂到调度上SWC 的逻辑不是直接写在 Port 上的而是写在 Runnable 里。Runnable 是 RTE 对调度实体的抽象最终会生成一个带固定签名的 C 函数。我在实际项目里的习惯是一个 Runnable 只干一件事要么周期读取信号要么周期写入结果避免把逻辑揉成一团。在 DaVinci Developer 里新建一个 Runnable命名 RE_10ms_VehicleSpeed 之类的可读名字然后把它的 Invocation Event 设为 TimingEvent周期 10ms。这里的关键是事件类型决定 RTE 生成函数的触发方式。TimingEvent 对应 Os 的周期任务InitEvent 对应系统启动时调用一次DataReceiveEvent 对应收到消息时触发。生成出来的 Runnable 函数签名一般是这样的/* 在 RTE 生成的 Rte_SwcName.c 中 */ void RE_10ms_VehicleSpeed(void) { /* 用户代码区BEGIN */ uint16 speed; speed Rte_IRead_xxx_VehicleSpeed(); /* 用户代码区END */ }这段代码的逻辑说明RE_10ms_VehicleSpeed是我们在配置里注册的 Runnable 名RTE 生成器把它做成一个无参数无返回值的函数由 RTE 调度。Rte_IRead_xxx_VehicleSpeed()是内部读取接口用于读取接收到的数据。用户代码要写在上下的用户代码区标记之间因为在下次生成时RTE 会保留这两个标记之间的内容标记以外的部分全量覆盖。参数说明函数名由“Runnable 名”决定改名字要在 DaVinci Developer 里改再重新生成不要手改 C 文件里的函数名。事件周期在 ECUC 的 Os 任务配置里定义如果任务周期在 Os 里设成 10ms这里就是 10ms两者不同步是调度错乱的根源。3.3 生成 RTE看输出文件与 API配置做完进入 DaVinci Configurator 点 Generate RTE。一个典型的最小配置会生成以下几种输出文件Rte_SwcName.c 是 SWC 的 RTE 实体Rte_SwcName.h 是 API 声明Rte_Type.h 是跨模块共享类型Rte_SwcName_Data.c 是数据缓冲区的定义Rte_Internal.h 是内部接口声明。把这几个文件加入编译工程即可。生成后先在生成目录里搜一下你期望的 API不要急着编译。比如我在 3.1 节配置的 VehicleSpeed应当能在 Rte_SwcName.h 里找到类似的声明/* 发送接口向 VehicleSpeed 信号写入数值 */ extern Std_ReturnType Rte_Write_VehicleSpeed_VehicleSpeed(uint16 data);这段代码的逻辑说明Std_ReturnType是 AUTOSAR 标准返回值类型Rte_Write是写接口的前缀后面第一段是 Port 名第二段是 Data Element 名参数类型正是我们在 Data Type 里选定的 uint16。如果这里生成的参数是 uint8说明 Data Type 的 Implementation 配置错了需要回到 3.1 修正。参数说明接口名由“Rte_Write Port 名 Data Element 名”拼接中间用下划线分隔。所以 Port 和 Data Element 的命名要尽量简短、稳定一旦生成代码再改名所有手写逻辑里的引用都要跟着改这是踩坑高频区。4. 打通 NvM 存储链路从 SWC 到闪存的 AUTOSAR 配置4.1 NvM、Fee、Flash 驱动这条链路的四个角色AUTOSAR 的非易失存储链路是 SWC 应用最常见的存储方案热词里“nvm的autosar的模块链路”指的就是这条链路。一次完整存储要经过四层。SWC 层是最上层应用代码调用 RTE 提供的 NvM 写接口。RTE 层做数据缓冲和请求转发它把 SWC 对 NvM 的服务调用映射到 BSW 的 NvM 模块 API。NvM 模块是存储管家负责块管理、校验和、请求排队它把上层请求转换成对 Fee 的读、写、擦除操作。Fee 模块是 Flash EEPROM Emulation它把逻辑块地址映射到物理 Flash 扇区做磨损均衡和掉电保护。最底下是 Flash Driver直接操作硬件寄存器。我在实际项目里见过两种失败模式一种是不了解 Fee 的扇区布局把 NvM 块大小配得太大一个块跨越多个扇区写一次就触发擦除性能奇差另一种是 NvM 块配置了校验和但 SWC 层写入时只写数据不更新 CRC结果每次启动读回时都校验失败。4.2 NvM Block 配置参数大小、CRC 和掉电保护在 DaVinci Configurator 的 NvM 模块里每个存储块Block需要配置的参数直接影响生成代码的行为。NvMBlockSize 是最基本的参数定义这个块存储的数据字节数。它必须与 SWC 层读写的结构体大小一致多一字节或少一字节都会导致后续 CRC 校验或数据错位。NvMBlockUseCrc 决定是否启用 CRC 校验建议默认打开代价是生成代码多算几个周期收益是能立刻发现数据损坏。NvMNrOfDataBytes 用于定义管理数据之外的用户数据区长度。NvMResistantToPowerLoss 是关键参数开启后RTE 会在写入时调用一次“写后校验”来确保数据落盘。参数作用常见错误NvMBlockSize块的数据区字节数与结构体大小不一致NvMBlockUseCrc启用 CRC16 校验开启后上层不更新 CRC 导致读回失败NvMResistantToPowerLoss掉电保护写策略开启后写时间变长任务超时NvMBlockNum同类块的冗余数量配多导致 Fee 空间不足NvMDataSetListDataSet 与 Block 的绑定绑错导致读到旧数据4.3 SWC 里读写 NvM 的代码方式配置完成后RTE 会为 SWC 生成两个常用的 NvM 访问接口Rte_Write_NvM 和 Rte_Read_NvM。这两个接口内部会调用 NvM_Write 和 NvM_Read 请求。下面是我在 SWC 里读写一个校准参数块的典型代码/* 写入校准参数块 */ Std_ReturnType ret; CalibData_t data; data.vehicleSpeedThreshold 60; data.engineSpeedLimit 4000; ret Rte_Write_CalibPort_CalibData(data); if (ret ! E_OK) { /* 写请求排队失败稍后重试或置故障标志 */ }这段代码的逻辑说明CalibData_t是我们在 Data Type 里定义的结构体类型它的大小必须与 NvMBlockSize 一致。Rte_Write_CalibPort_CalibData是 RTE 生成的写接口它会先把数据拷贝到 RTE 缓冲区再调用 NvM 模块的写请求。返回值E_OK表示请求被 NvM 接受不表示已经写进 Flash——真正落盘由 NvM 的后台任务完成。参数说明函数名中的 CalibPort 是 SWC 上 Port 的名字CalibData 是 Data Element 的名字对应 NvM 配置的那个 Block。如果这个接口写的是数组指针而不是结构体指针说明你在配置 Interface 时把 Data Type 定义成了 Array这时传参方式是Rte_Write_CalibPort_CalibData(data[0])坑点在于数组长度必须在两端保持一致。5. AUTOSAR 配置生成避坑五个让我加班到凌晨的翻车现场5.1 编译报 undefined referenceRTE 接口生成了但没有实现现象在 RTE 生成后编译链接阶段出现大量undefined reference to Rte_Call_xxx而且只报引用不报定义。原因这类问题九成是因为 SWC 描述里声明了 Client-Server 调用的 Runnable但对应的 Server Runnable 没有挂到具体 SWC 上RTE 只生成了 Request 端 API没有生成 Response 端实现。另一种常见原因是 Runnable 的事件映射缺失生成器不知道这个函数该谁调用。解决回到 DaVinci Developer检查所有 Runnable 的 Invocation Event。凡是 Client-Server 调用服务端 Runnable 必须在某个 SWC 的 Internal Behavior 里存在并且挂到一个事件上。我用一个笨办法快速定位在生成目录里 grep 这个函数名如果只在 Rte_XX.h 里搜得到声明在 .c 里搜不到定义就说明定义侧没配全。5.2 信号值错位Data Type 宽度不一致RTE 悄悄截断现象从 CAN 总线收到的车速显示为 0或者在某些工况下突然跳变但用 CANoe 看总线上的报文是正确的。原因这是一个典型的黑匣子问题。发端 SWC 配置了 uint16接收端 Signal 对应的 Implementation 配成了 uint8RTE 生成时把数据从宽类型往窄类型截断高位直接丢弃。配置里所有“本地 Data Type、信号长度、PDU 位的位置”三者必须对齐。解决检查 ARXML 里的 SWC Implementation 数据宽度和 DBC 文件里对应 Signal 的位宽是否一致。我在排这类问题时的最有效手段是给 Data Type 的 Implementation 层起一个严格的名字比如Impl_VehicleSpeed_u16在跨模块评审时一眼就能看出接口宽度。5.3 NvM 写入后一复位就丢数据现象程序运行中写入校准值返回 E_OK单步调试能看到数据在 RAM 里更新了但重新上电后读回的是旧数据。原因这条踩坑我有血泪经验。常见原因有三条。NvM 块配置里打开了 CRC 校验但应用层写入只更新数据区、没有同步更新 CRC 字段读回时校验失败后 NvM 判定数据无效。Fee 层没有配置正确的扇区映射导致写操作没有真正落到 Flash。NvM 的后台处理任务没有运行写请求只进了队列没有执行写 Flash 的操作。解决先检查 EcuM 启动流程里是否调用了 NvM_Init并确认 NvM 的主处理函数 NvM_MainFunction 在 Os 任务里周期性调度。然后在应用层写入后加一个短延时再调用 NvM_Read 读回验证看返回值和缓存是否一致。如果 CRC 开启一定要让写入端把校验字段一起填好或者在配置里改用 NvM 自动计算 CRC。5.4 周期性任务不触发TimingEvent 配了但函数没跑现象Runnable 里的断点永远不命中RTE 也生成了函数任务调度器看起来正常但周期逻辑就是不走。原因这种问题最容易出现在 Os 和 RTE 的事件映射上。在 DaVinci Configurator 里RTE 的 TimingEvent 背后会落到一个 Os 任务上如果这个 Os 任务的激活方式Schedule配成了单次执行或者任务的优先级低于某个占用 CPU 的自旋任务周期性调用就永远轮不到。解决检查 ECUC 里 Os Task 的配置确认 AUtostart 选项已启用Priority 设置合理并且任务的周期 Act 参数正确。我在排查时会先在 Os 的 Hook 里打印任务切换日志确认任务本身有没有被调度任务调了就查 RTE 事件映射任务没调就查 Os 配置。5.5 手改生成代码下次生成被覆盖现象在 Rte_XX.c 里直接改了业务逻辑保存再次生成 RTE 后改动全部消失。原因这是对生成器行为的理解问题。RTE 生成器以 ARXML 为唯一事实源每次生成都是全量写文件任何不在用户代码区的修改都会被覆盖。这也是最让人后悔的操作。解决永远不要在生成文件的非用户代码区写业务逻辑。需要加自定义逻辑时把代码放进 RTE 生成的用户代码区标记之间或者在配置里把逻辑放到一个独立的手写 SWC 文件中再通过 Client-Server 接口由 RTE 调用。我在团队里立过一条规矩生成目录不允许手工编辑所有手写文件必须放在专门的“Manual”目录并以Swc_前缀命名。5.6 容易忽略的生成顺序先改配置再生成还是先生成再改配置现象修改了 NvM 的 BlockSize但生成的 RTE 数据接口类型没变编译报结构体大小冲突。原因DaVinci Configurator 的 ECUC 和 DaVinci Developer 的 SWC 描述是两个输入源前者改了后者没重新导入生成器拿到的还是旧的 SWC 描述。解决每次改完 ECUC 或 SWC 描述后先执行一次“导入”操作让两边都看到最新状态再点生成。我习惯把“导入 ARXML → 生成 RTE → 生成 BSW”三步固定为一个 bat 脚本避免漏掉中间环节。这也是为什么很多项目要求生成过程必须可重复配完就出代码不允许手工修补。6. 生成结果怎么验证代码快照 diff 与 RTE Contract 对照先做一个最实用的习惯每次重生成代码之前把上一轮的生成目录完整复制到gen_snapshot_prev/生成完用 diff 工具对比。RTE 生成的文件往往带时间戳注释直接 diff 会产生大量噪音。我一般先把时间戳行过滤掉再比较。#!/bin/bash # 对比两次 RTE 生成结果忽略时间戳与空行差异 diff -r -B -w \ --exclude*.map \ --exclude*.lst \ gen_prev/ Rte_Gen/ \ | grep -v ^[0-9-]* [0-9:]*$ | grep -v Generated at这段脚本的逻辑说明-r递归比较目录-B忽略空行差异-w忽略行内空格差异--exclude跳过编译产物。grep 过滤掉包含时间戳的行这样 diff 结果里只会出现真正的代码变化。我每次改接口前都会先跑一次看到变化只有新增的函数和对应调用才继续往里填逻辑。另一个验证手段是 RTE Contract。AUTOSAR 规范里 RTE 支持生成 Contract 头文件它记录接口的数据类型、端口方向、最大数据长度等契约信息。在两个 SWC 对接时把双方的 Contract 文件拿到一起比对能提前发现数据类型不匹配。我在集成第三方提供的 SWC 时都会索要这个文件省去拿 Simulink 模型反查 ARXML 的时间。最后说一个反导技巧如果你拿到的是别人生成好的代码和 ARXML想确认它当初是不是用 DaVinci 生成的可以看生成文件头部注释的生成器版本号再看Rte_Type.h里的类型命名风格。AUTOSAR 工具链生成的代码带明显的调度表和数据结构特征Simulink 生成的则带模型名后缀。我做集成时先辨别风格再决定用哪套配置去对齐能少走很多弯路。我之前改接口前不做快照被覆盖过整整一个下午的工作后来养成了生成前打包gen_prev的习惯再没丢过代码。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表