ARTICLE DETAIL

资讯详情

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

LabVIEW+CAN卡实现UDS刷写工具:从ISO-TP到ECU升级全流程实战

LabVIEW+CAN卡实现UDS刷写工具:从ISO-TP到ECU升级全流程实战 最近在给一个纯电动项目的VCU升级工具做方案选型客户指定要用LabVIEW配合CAN卡来实现ECU的UDS刷写。折腾了小两周从一穷二白到最终把Bootloader到App的整条刷写链路跑通踩了不少坑也积累了一些实打实的经验。这篇文章就是把这套基于图莫斯CAN卡的LabVIEW版UDS升级上位机的搭建过程完整复盘一遍从硬件接线、CAN驱动封装到ISO-TP组包、诊断服务流程再到HEX文件解析和刷写状态机尽量把关键细节和常见问题都讲透。这套工具最终实现的功能很简单通过CAN总线给ECU刷写固件支持UDS标准诊断服务能读取固件、校验数据、查看NRC错误码带基本的进度和错误日志界面。但越是看起来简单的工具真正做起来越容易在时序、组包、状态切换这些地方翻车。这篇文章适合正在做或者准备做ECU刷写工具的工程师看也适合刚接触CAN和UDS协议、想搞明白刷写全流程的新手。废话不多说下面按我实际搭建的顺序来讲。1. 项目背景与方案选型1.1 为什么选LabVIEW做刷写工具有人可能会问现在市面上做上位机的方案太多了C#、Python、Qt都能干为什么要用LabVIEW这个问题我在项目启动前也纠结过。核心原因有三个。第一是现场的调试习惯我们团队很多人用LabVIEW做测试台架和产线设备有一套现成的UI控件库和报表工具刷写工具需要做成一个供产线工人操作的傻瓜界面LabVIEW的界面拖拽式开发比C#写XAML快太多了。第二是LabVIEW对NI硬件和各种第三方CAN卡的支持还不错图莫斯这类国产CAN卡基本上都提供LabVIEW调用的DLL不需要自己去写驱动程序。第三是调试周期短LabVIEW图形化开发不用编译改完直接跑在量产前调试阶段能省不少时间。当然LabVIEW也有它的短板比如对复杂状态机的管理容易乱、代码复用性不如文本语言、版本兼容性问题多这些在我后面做协议层封装时都遇到了标准做法是用队列状态机的架构来管理收发包避免界面卡死和数据丢失。1.2 整体架构与数据流这个工具的整体架构用一句话概括就是LabVIEW界面层 状态机逻辑层 CAN驱动封装层 图莫斯硬件层。最上层是人机交互界面负责显示刷写进度、当前诊断服务、NRC错误码、文件信息等中间是核心逻辑层跑一个UDS刷写状态机按顺序执行诊断会话切换、安全解锁、请求下载、数据传输、退出传输、复位等步骤再往下是CAN驱动封装层也就是把图莫斯CAN卡的DLL接口封装成LV里好用的收发函数最底层就是CAN卡和总线物理链路。数据流也很好理解上位机要发送的UDS请求经过ISO-TP层组包变成CAN帧通过CAN卡发到总线上ECU的响应帧同样经过CAN卡收回来拆包成完整的UDS响应再交给状态机判断下一步动作。注意这里有个特别容易踩坑的地方就是ISO-TP的多帧重组和流控时序很多刷写失败都是这一层出了问题。1.3 硬件选型与接线准备硬件方面我用的CAN卡是图莫斯的USBCAN系列这个系列在市面上很常见稳定性对于刷写工具来说是够用的。接到电脑上会自动识别为一个USB转CAN设备驱动装好后在设备管理器里能看到对应的COM口或者设备节点。接线就三根CAN_H、CAN_L、CAN_GND。CAN_H接CAN_HCAN_L接CAN_LGND最好也接上否则共模电压可能会带来一些奇怪的通信问题。总线两端各接一个120Ω终端电阻这是CAN总线的基本要求我记得有个项目没接终端电阻结果远端的一台ECU怎么都通信不上后来发现就是阻抗不匹配导致的反射问题。还要确认一件事ECU的CAN波特率。市面上绝大多数车规ECU用的都是500 kbps但有的老平台是250 kbps或者125 kbps配置不对连总线都上不去。所以第一步一定要跟BMS/VCU的供应商确认好波特率别等写了一半代码才发现总线压根没通。2. CAN通信底层搭建2.1 图莫斯CAN卡初始化与通道配置图莫斯CAN卡在LabVIEW里的调用方式本质上就是调用它的DLL接口。打开驱动库后能看到几个核心函数打开设备、初始化CAN通道、发送报文、接收报文、关闭设备。在LabVIEW里我用的是“调用库函数节点”的方式把DLL里的接口一个个导入然后封装成自己的子VI。这里有一个很重要的经验DLL导入时参数类型一定要核对清楚。图莫斯的接口很多参数是UINT类型LabVIEW默认导入时可能会把它识别成I32虽然大多数情况下不影响但在某些边界值上会出问题。稳妥的做法是每个DLL参数都手动确认一下数据类型。初始化通道的代码逻辑大致是这样的打开设备后对指定通道设置波特率、工作模式正常模式还是环回模式、滤波方式。我习惯第一次调试时先用环回模式验证收发链路确认驱动封装没有问题了再切到正常模式。环回模式下发的报文自己不经过总线能快速定位问题是出在驱动层还是ECU侧。2.2 CAN报文收发与ID规划CAN通讯的核心就是报文ID和数据场。刷写UDS时用的CAN ID一般是标准帧的11位ID常见配置是物理请求ID为0x7E0、物理响应ID为0x7E8功能寻址ID为0x7DF。这个不是固定的具体要看ECU的CAN矩阵定义有的项目用扩展帧29位ID有的自定义了更复杂的ID分配。总之拿到项目后第一件事就是找诊断规范确认物理寻址和功能寻址的ID。在LabVIEW实现收发的时候我设计了一个生产者-消费者架构。生产循环里开了一个接收线程不停从CAN卡驱动读取总线上的帧然后按ID做过滤把属于本工具的响应帧丢进队列消费者循环从队列里取数据传给上层协议解析。这样做的目的是防止UI线程阻塞导致CAN接收缓冲区溢出丢帧尤其是刷写过程中大量连续帧涌入的时候。发送侧要注意CAN卡发送函数的返回不要忽略。图莫斯的发送接口会返回发送是否成功如果总线上有其他节点持续占用仲裁发送就会失败。我起初没做这个判断导致刷写跑到一半停下来查了半天才发现在某些总线负载高的情况下会有偶发发送失败。2.3 发送超时与总线错误处理CAN总线和普通串口不一样它本身有完善的错误检测机制但作为上位机我们还是得处理两类异常一类是发送超时CAN卡内部有发送缓冲区如果缓冲区满了或者总线错误发送接口会返回超时另一类是总线错误帧ECU侧如果上电不正常或者波特率不匹配总线上会出现大量错误帧。我在工具里加了一个简单的总线状态监控模块。每次收发完帧后查询一下CAN卡驱动的错误计数器和状态寄存器如果连续多次查询都发现总线处于Bus-Off状态就弹窗提示用户检查ECU供电和波特率配置同时在日志面板输出当前的错误状态码。这一类看似不起眼的功能在实际产线和现场调试时非常实用。有一次现场怎么都进不了刷写流程我打开总线状态面板发现错误计数器一直在增长排查后发现是CAN_L线出现了接触不良导线压到了端子屏蔽层换了一根线就好了。如果没有这个监控模块估计得拿着示波器在总线上测老半天。3. UDS协议与ISO-TP传输实现3.1 ISO-TP单帧与多帧组包UDS诊断协议本身跑在ISO-TP传输层上LabVIEW里做UDS第一个硬骨头就是ISO-TP的组包和拆包。ISO-TP的帧类型有四种单帧SF、首帧FF、连续帧CF和流控帧FC。单帧最多携带7个字节的数据如果UDS请求或响应的数据长度超过7字节就要拆成首帧加连续帧发送接收方回复流控帧来控制后续连续帧的发送节奏。举个例子发送34 00 00 00 00 00 10 00 10 00这个下载请求数据长度是10字节超过了单帧能力。首帧的第一个字节就是0x10加上后续总长度格式为首帧的第一个字节高4位是0x1低4位是总数据的长度高4位第二个字节存放的是长度低8位从第三个字节开始放实际数据。后续所有字节都放到连续帧里连续帧的第一个字节高4位是0x2低4位是帧序号从1开始循环到0xF后再回到0。在LabVIEW里我把ISO-TP的收发逻辑封装成了两个子VI一个叫ISO-TP_Send输入UDS请求字节数组输出发送的结果和实际发出的CAN帧数另一个叫ISO-TP_Receive输入接收缓冲区输出重组后的UDS响应字节数组。这两个VI内部用循环和移位寄存器处理多帧的状态重点是把流控帧的BS块大小、STmin最小间隔时间两个参数处理好。BS表示允许发送方在收到下一个流控帧之前最多发送的连续帧数量STmin表示两个连续帧之间的最小时间间隔。这两个参数的取值直接影响刷写速度如果ECU设置的STmin是0毫秒那上位机就可以全速发帧刷写速度会快很多如果ECU比较保守STmin是10毫秒甚至更久那整个刷写过程就会非常慢。实际解码ECU返回的流控帧后上位机必须严格按照STmin来发送连续帧否则ECU那边会因为数据接收不及时而回复NRC 0x73。3.2 常用诊断服务与NRC应答UDS协议里和刷写相关的常用服务我在工具里都做了封装用表格列一下服务ID服务名称用途0x10诊断会话控制切换默认、编程、扩展会话0x27安全访问Seed Key解锁0x34请求下载告知ECU要下载的地址和长度0x36数据传输分块发送固件数据0x37请求传输退出结束下载0x31例程控制执行擦除、校验等例程0x22按ID读数据读取ECU版本信息0x11ECU复位刷写完成后复位ECU每一个服务都有相应的正响应和负响应。负响应的格式是固定的第一个字节是0x7F第二个字节是请求的服务ID第三个字节是NRC码。NRC码是刷写调试中最需要熟悉的东西常见的几个0x11表示服务不支持通常是这个ECU型号没有实现当前服务0x12是子功能不支持比如0x27服务里的解锁参数不对0x22是条件不满足常见的场景是没进编程会话就尝试擦除Flash0x31是请求超出范围比如地址越界、长度不合法0x33是安全访问被拒绝说明Seed或Key算错了0x36的0x73是块序列号错误大概率是上位机发送的帧序号和ECU期望的不一致。我把这些NRC码整理成了一个枚举常量在做状态机的时候收到负响应就查表输出对应的描述信息同时把原始请求和响应都记录到日志里问题定位效率提高了不少。3.3 定时参数与超时策略刷写UDS还有一个特别容易忽略但特别关键的地方是定时参数。ISO 14229里定义了P2、P3等多个定时参数P2是ECU对请求给出响应的最大等待时间通常是25毫秒到50毫秒P2*是服务器需要额外时间处理时的扩展时间最多可达5秒P3是会话层请求之间的最小间隔通常为5秒。在LabVIEW里这些定时参数如果做成固定延时效率会非常低。最好的做法是做一个超时定时器发送请求后记录当前时间戳然后在循环里轮询接收队列如果在P2时间内收到了正响应就继续下一步如果收到了0x78响应待处理的P2*指示就切换到P3等待时间一直等到最终响应到达。实际调试时我遇到过一个和定时相关的问题刷写过程中ECU擦除Flash需要大概4秒钟这段时间ECU会先回复0x78响应待处理然后才返回真正的正响应。第一次做的时候我把超时时间写成了P2的50毫秒结果就是刷写永远在第一步超时失败。后来看了半天规范才发现0x78的存在。4. 刷写流程与核心逻辑4.1 刷写前置流程完整的一次UDS刷写从ECU的角度看大致有这几个阶段建立通信、切换到编程会话、安全解锁、擦除Flash通过例程控制、请求下载、传输数据、退出传输、复位ECU。在LabVIEW里我用一个枚举定义了这个状态机的各个状态每一个状态对应一个UDS服务请求发送完请求后根据响应决定是跳转到下一个状态还是停留在当前状态重试。状态机的好处是逻辑清晰出现错误时可以随时回滚到上一个状态。建立通信阶段首先要发送一个0x10 02进入编程会话这是一个功能寻址请求。进入编程会话后ECU可能还会要求先执行一个会话切换等待时间有些ECU需要等一小段时间才能开始安全解锁这时候需要在状态机里加一个延时太急的话ECU会回复0x22条件不满足。安全解锁是刷写流程里最容易出问题的环节。0x27服务的流程是上位机发送0x27 01请求SeedECU返回Seed上位机根据Seed用特定算法计算出Key通过0x27 02发送给ECUECU验证Key正确后返回正响应解锁成功。问题在于不同ECU厂商的Seed-Key算法五花八门有些是简单的异或叠加有些是查表变换有些加入了实时计数器防止重放攻击。我们的做法是把Seed-Key算法做成一个单独的子VI每个项目只需要改这个子VI即可。第一个版本接的VCU算法是一个用了LFSR线性反馈移位寄存器的变种我拿到算法文档后花了半天时间在LabVIEW里用移位数组实现了LFSR的计算调试的时候还遇到了字节序的问题文档里写的是大端序ECU实际跑的是小端序导致算出来的Key总是对不上。刷写前置流程补充例程控制擦除安全解锁成功后一般需要先执行擦除操作。这个是通过0x31例程控制服务的01子功能实现的例程ID通常是FF00擦除Flash。擦除期间ECU会忙活一段时间典型的响应序列是先回复0x7F 31 78响应待处理过几秒钟后再返回0x71 01 FF 00的正响应。我在状态机里专门针对这种情况做了处理收到0x78后进入一个独立的长等待子状态每100毫秒轮询一次接收队列直到收到最终响应或超时。超时时间我通常设置成10秒对于大容量Flash可能需要更久。4.2 HEX文件解析与固件数据准备刷写用的固件文件常见格式有HEX和S19两种我们项目用的是Intel HEX格式。HEX文件每一行都是一个记录结构是冒号开头紧接着是数据长度、地址、类型、数据、校验和。解析HEX文件的几个关键点一是记录类型判断0x00是数据记录0x01是文件结束记录0x04是扩展线性地址记录二是地址计算当碰到扩展线性地址记录时需要把地址的高16位更新后续的数据记录地址需要和这个扩展地址组合成完整的32位地址三是校验和计算每一行的校验和是前面所有字节长度、地址、类型、数据累加后取反再加1也就是对整体按字节求和结果为0。LabVIEW里做文件解析比文本语言繁琐一些我用的方法是用“读取分隔符文件”把整个文件按行读成一个字符串数组然后对每一行用“字符串截取”和“十六进制字符串至数值转换”依次解析。解析完成后把固件数据按地址顺序存进一个二维数组或者簇数组数组元素包括地址和数据长度。在刷写前工具会做一次简单的完整性校验检查文件结束记录是否存在、总数据长度是否超过ECU的Flash容量、HEX文件里是否存在地址空洞。地址空洞是指文件里某些区域没有数据这在HEX里是合法的但对于目标Flash来说可能意味着风险我通常的做法是提示用户确认而不是直接自动填充0xFF。4.3 34/36/37下载流程实现进入了具体的下载流程第一件事是发送0x34请求下载。请求的数据参数包括数据格式标识符、地址长度格式标识符、内存地址、内存大小。以我们的VCU为例地址长度是4字节大小是4字节所以0x34请求的组成是34 [数据格式标识符] [地址长度格式: 0x44表示4字节地址4字节大小] [4字节地址] [4字节大小] [可选加密算法标识符]数据格式标识符是一个很有意思的参数它本身由低半字节和高半字节组成。很多国产ECU会在0x34请求里加上压缩和加密标识对于不加密不压缩的普通刷写这个字节通常直接填0x00。0x34的正响应里会返回最大传输块长度表示ECU每次通过0x36最多能接收多少字节。这个值直接决定了后面0x36分块的块大小。比如ECU返回的最大块长度是4096字节那么每次0x36请求最多可以发4096字节的数据。然而注意ISO-TP层的连续帧虽然可以传输4095字节但CAN底层每帧只有8字节数据场。4096字节的数据在CAN总线上意味着要拆成几百帧连续帧来发。所以我做了一个自适应分块逻辑优先用0x34返回的最大块长度但是如果ECU的流控帧里STmin比较大为了不超时我会适当减小块大小保证整个0x36请求在P3时间内能完成。发送0x36数据时最关键的是块序列号。块序列号从1开始每发一个0x36请求加1到0xFF后回到0循环。ECU会检查正在接收的数据块的序列号是否和期望值一致不一致就回复NRC 0x73。我碰到过一种情况一个0x36请求里包含多个连续帧图莫斯CAN卡发送本身没有异常但LabVIEW状态机里处理响应的时候偶尔会把上一次的0x36正响应误判成当前0x36的正响应导致序列号提前自增ECU那边就报0x73了。后来我在每个0x36请求前先清空接收队列才彻底解决这个问题。所有数据发送完后发送0x37请求传输退出。ECU收到后会做一次内部校验返回正响应后执行0x31例程控制做Flash校验最后发0x11 ECU复位结束整个刷写流程。下面我给出一段关键的刷写状态机伪代码便于理解整个逻辑当前状态 初始化 循环直到 刷写完成 或 错误发生 根据 当前状态 发送对应的UDS请求 等待响应处理0x78 如果 收到正响应 更新状态到下一步 更新进度条 否则如果 收到负响应 记录NRC码 按策略决定重试还是放弃 否则 超时 记录超时 重试当前状态最多3次在LabVIEW里这就是一个事件结构状态机搭配的实现界面上的按钮触发刷写开始然后状态循环不断驱动刷写进度。4.4 读回与备份功能除了刷写我还顺手在工具里加了一个读回备份功能。操作流程和刷写相反先进入编程会话并完成安全解锁再发送0x23按地址读数据或者0x31例程控制配合0x34/0x36/0x37实现读FlashECU返回的数据通过ISO-TP拆包组包逐段读出最终拼成完整的固件文件保存在本机。这个功能看起来简单但实用价值极高。有一次客户给的固件版本和新ECU不匹配需要确认旧ECU里实际跑的到底是哪个版本就是用这个功能把Flash内容读回来和HEX文件对比后立刻确定了差异区域省去了拆壳用编程器读芯片的麻烦。5. 实操踩坑与问题排查5.1 高频问题速查表因为是真实项目总结我整理了刷写工具调试中遇到的几个高频问题做成速查表方便大家参考现象可能原因排查方向CAN总线通信不上波特率不匹配、终端电阻缺失确认波特率检查120Ω电阻0x10会话切换失败未按ECU要求先进入扩展会话先发0x10 03再切0x10 020x27安全解锁拒绝Seed-Key算法错误、字节序不对核对算法文档打印Seed和Key0x34请求下载NRC 0x31地址或长度越界核对Flash地址映射表0x36传输NRC 0x73序列号错乱、接收队列残留清空队列检查序号处理逻辑刷写过程中超时P2/P3定时设置不当、0x78处理不完善检查定时参数和0x78分支速度很慢STmin太大或BS为0调整流控参数优化发送节奏5.2 时序和流控的坑前文提到过0x78响应待处理的问题这里再展开讲一下。在UDS中如果ECU处理时间超过P2它会在P2时间内先回复一个0x7F xx 78表示还在处理然后在P2*时间内给出最终响应。这个机制在擦除Flash时几乎必然触发。我在LabVIEW里的处理是发送请求后进入一个超时循环循环内部先等待50毫秒P2如果收到0x78就切到等待最终响应的模式超时上限设为5000毫秒P2*如果P2内没收到任何响应直接报超时。注意0x78本身不是一个正响应它只是一个中间状态不能把它当作刷写成功信号去推进状态机。另外流控帧的处理有一个非常隐蔽的坑如果ECU回复的流控帧里STmin是0xF0或者更大的值表示的是毫秒级还是微秒级要根据协议标定来换算有的ECU供应商在这个参数的实现上不太规范会出现标称和实际不符的情况。遇到这种情况我一般直接用逻辑分析仪抓一次完整的多帧交互看ECU实际有没有按标称的STmin接收数据然后微调上位机的发送间隔。5.3 文件解析与固件准备的坑HEX文件解析的坑主要在地址空洞和地址重叠上。地址空洞有时候是编译器生成的有时候是文件被截断导致的。我在工具里加了一个统计功能解析完HEX后显示有效数据的起始地址、结束地址、总长度、区块条数如果地址不连续会标黄提示。刷写前用0x34请求时的内存大小我直接按最后一个有效地址加块大小来算而不是文件里所有数据的累计长度这样能避免少写尾部数据。还有一个数据对齐的问题。有些ECU要求0x34请求的内存地址必须按4字节或者8字节对齐如果HEX文件里的起始地址不对齐就要在上位机里做偏移处理。我当时遇到的情况是HEX文件在某一次编译后起始地址变成了0x08008000而ECU要求对齐到4字节本身倒是对齐的但如果重新编译后起始地址变了就可能不对齐。所以我在文件解析时增加了一个对齐检查不对齐就自动在数据前面补0xFF。5.4 LabVIEW版本与CAN驱动兼容性最后说说LabVIEW环境本身的坑。图莫斯提供的DLL通常支持32位和64位LabVIEW也有32位和64位版本。如果LabVIEW是64位的但DLL只有32位版本调用就会失败。我的经验是直接用32位LabVIEW 32位DLL这一套最稳中间遇到的“调用库函数节点加载DLL失败”问题基本都跟位数不匹配有关。LabVIEW版本兼容性也是一个隐患我用的是LabVIEW 2018图莫斯的DLL接口是基于C语言封装的在2018上导入没有问题。但是如果换到LabVIEW新版本建议先在属性里检查DLL导入路径是否有效有些情况下需要重新导入一次接口。另外LabVIEW的安装路径千万不要带中文否则调用DLL时容易报错找不到模块这一点很多人会忽略。装LabVIEW时也建议选择自定义安装把NI-CAN、VISA这些模块一并装好后续做CAN通信调试会有帮助。6. 一些个人的实操体会这套工具从零开始搭建到最终稳定用于产线前后改动最大的部分其实不是协议逻辑而是状态机的健壮性和日志的记录完整度。早期版本刷写失败后只能看到一个NRC码要花很长时间去复现和分析后期我把每一次请求、每一次响应、每一个时间戳都记录到一个文本日志文件里工程师只需把日志发给我就能定位问题出在哪一步。如果让我现在重新做一遍我依然会选择LabVIEW但会在一开始就把状态机、ISO-TP收发、文件解析这三个模块彻底分开各自内部保持独立千万不要都堆在一个大VI里。LabVIEW虽然上手容易但项目复杂后模块划分不清的话维护成本会急剧上升。另外一个小建议不论用LabVIEW做什么工具界面上的错误提示一定要做得清晰直白给产线操作人员看的信息不能是0x7F 0x36 0x73这种十六进制而是数据块序号错误请重新刷写这类可以理解的中文描述。工具最好用的状态是让一个完全不了解CAN和UDS的人也能顺利操作这才是工程工具的最终价值。
返回列表