ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

NI VeriStand中实现自定义CAN报文Checksum与Counter的实战解析

NI VeriStand中实现自定义CAN报文Checksum与Counter的实战解析 在自动驾驶相关的硬件在环HIL测试里我经常遇到一种看起来特别奇怪的失败仿真模型明明发了CAN报文下游控制器却像没收到一样不理会或者直接报出“帧校验异常”。排查半天问题往往不在波特率、ID或者周期而是绕开了校验——就是报文里那两个不起眼的字段Checksum和Counter。今天这篇东西就从NI VeriStand平台出发聊聊怎么给自定义CAN报文做一套靠谱的Checksum和Counter让仿真节点在真实控制器面前不掉链子。这篇内容适合三类人正在搭HIL台架、用VeriStand做实时仿真但被CAN通信校验问题卡住的工程师做Simulink模型在环开发、需要把E2E保护逻辑放进仿真链路里的同学以及单纯想搞明白“为什么真实ECU发的报文有CRC和Counter仿真模型发的就没有”的测试新人。我会从原理讲到实操最后附上踩坑记录尽量让大家拿着就能用。1. 为什么仿真报文会被“当作没收到”1.1 一次典型的HIL测试失败先说个我印象很深的现场。某个项目用PXI机箱跑VeriStand实时仿真模拟转向系统节点按10ms周期往CAN总线上发一帧转向角报文ID是0x183DLC是8。DUT是一台ADAS域控制器它要消费这帧数据来辅助车道保持。仿真端一切正常VeriStand界面里信号值都对角度也在动可DUT那边就是报功能不可用故障码里写着“E2E checksum error”和“alive counter timeout”。问题出在哪儿仿真模型里只填了物理量——把角度换算成原始值后塞进数据场就发出去了但真实ECU在发送这帧报文之前底层软件会自动做一件小事把当前帧的滚动计数器加一再把数据算一遍CRC填充进去。接收端DUT的E2E保护模块一拿到报文先检查Counter是否按预期递增再算一遍Checksum和发送端的对比对不上就判定报文无效。仿真模型把这两个字段落了DUT自然不认账。1.2 Checksum和Counter各管一摊很多刚接触CAN总线的同学会把这两个概念混在一起其实它们分工完全不同。Checksum在ECU通信语境里通常指的是CRC循环冗余校验它负责检测数据在传输过程中是否被篡改或受到干扰。发送方对报文数据场一般不含CRC字段本身做多项式计算把计算结果放进单独的字节接收方用完全相同的多项式重新算一遍比对不一致就说明数据在中间某处变了。在AUTOSAR的E2E保护规范里CRC字段就承担这个角色。Counter也叫Rolling Counter、Alive Counter是单调递增的计数器常见4bit或8bit。报文每发送一次数值加1溢出后回0。接收方记录上一帧的计数值如果当前帧的数值与上次“不连续”重复、跳变、原地不动就认为总线上出现了丢帧、错序或者通信中断。它和CRC一静一动一个管数据对不对一个管帧有没有丢两者配合起来接收端才能判断消息的“新鲜度”和完整性。可以这么理解CRC是快递包裹的防拆封条封条破损说明东西被动过Counter是快递单号流水号单号没连续递增说明中间有包裹丢了。仿真模型如果把这两个都省了等于给真实控制器寄了一箱没有封条、没有单号的快递。1.3 仿真模型和真实ECU在“校验”上的天然差异自动代码生成也好、快速原型也好我们习惯在Simulink里只关注物理量这个信号是多少、那个状态怎么变化。但真实ECU的底层软件栈里E2E保护是基础模块发送路径上自动完成CRC和Counter管理接收路径上自动做校验。到了VeriStand仿真环境如果直接把Simulink模型编译进来模型输出的往往是“裸数据”不会主动生成校验字段。这不是VeriStand的缺陷而是仿真建模的粒度问题。要解决它我们得手工把校验逻辑补进仿真链路里让仿真节点看起来和真实ECU一样“守规矩”。接下来就是实现方案的选择题。2. 在VeriStand里做Checksum和Counter有哪几条路2.1 四种主流方案的对比实现自定义CAN报文的校验字段业界常见做法有四种各有取舍我直接整理成表格方便对照。方案实现方式优点缺点适合场景Simulink模型内计算在模型里写CRC和Counter逻辑编译成VeriStand模型DLL与MBD流程统一逻辑可复用调试直观需要重新编译模型改参数要重新部署大多数HIL和快速原型场景推荐首选VeriStand自定义设备用C语言开发Custom Device挂在VeriStand里执行实时性最高可脱离Simulink独立运行适合大规模信号处理开发周期长需要掌握VeriStand Custom Device框架对微秒级时序要求极严苛的场景Python/PXI脚本动态改写通过VeriStand Python API在运行前/运行中修改信号值灵活适合测试用例临时注入错误实时性差不适合高频率、高确定性的周期帧故障注入、边界测试、自动化脚本外部CAN工具拦截转发用CANoe、PCAN等工具作网关在链路上修改报文可以在不碰仿真模型的情况下处理增加硬件成本和链路复杂度还有额外延迟临时排查问题、老台架改造从实际项目经验看多数HIL台架和快速原型项目都跑在Simulink VeriStand这条技术栈上第一种方案最稳维护成本也最低。2.2 为什么多数场景推荐Simulink模型方式我选Simulink模型内计算核心原因有三个。第一逻辑复用性。ECU端如果本身就用Simulink做E2E保护算法建模那套CRC查表逻辑、Counter管理逻辑可以直接搬进仿真模型里两边算法天然对齐。即使没有现成模型也能把AUTOSAR E2E库的算法描述翻译成仿真模型接收端DUT的验收标准是什么我们就在模型里实现什么。第二版本可追溯。模型文件放在版本控制里改一行CRC参数都留痕。相比之下直接在脚本里改值测试过了下个版本可能谁都不知道排查超时问题容易变成猜谜游戏。第三VeriStand对Simulink模型的集成最成熟。NI官方提供了模型接口工具链编译出来一个DLL和一个XML信息文件拖进VeriStand就能用模型输入输出自动变成端口配合操作界面可以直接在线调参比写C代码舒服得多。2.3 DBC信号布局先把“坑”填上在动手写模型之前我建议先在DBC文件里把报文信号定义清楚。很多人上来就写代码结果到VeriStand映射信号的时候发现信号定义跟模型端口对不上返工是最烦的。我习惯用一个标准模板。假设要模拟一个车辆状态报文名字叫AD_SYS_InfoCAN ID为标准帧0x183周期10msDLC为8信号布局如下Byte0ADAS_Statusuint8位址0Byte1~Byte2SteerAngle_Rawuint16小端模式精度0.1度/bit偏移量-780对应实际物理量范围大概-78.0度到26.0度Byte3RollingCounteruint80~255循环Byte4~Byte6保留字段或状态标志uint8Byte7Checksumuint8这个布局有一点很关键Counter放在数据场前部Checksum放在最后一个字节。CRC计算时通常把除了Checksum自己之外的前7个字节全部纳入计算范围这与常见ECU的E2E配置一致。当然不同厂商算法不同有的Counter放在高位有的Checksum会参与计算范围这些差异都要在DBC里先在注释中标注清楚免得后面对接DUT时扯皮。3. 实操从Simulink算法到VeriStand实时运行3.1 在Simulink里搭CRC和Counter计算逻辑模型整体思路是输入原始数据数组内部维护Counter状态输出带Checksum和Counter的完整数据帧。我用MATLAB Function块写核心算法Counter用Unit Delay块实现。先看Checksum计算函数以一个比较经典的CRC8实现为例多项式对应SAE J1850即0x1D初值0xFF结果异或0xFF输入输出不做反射。function crc calc_crc8(data) % data: uint8数组按DBC定义的信号顺序排列不包含Checksum自身 % 返回: CRC8校验值uint8类型 crc uint8(255); % 初值 0xFF for i 1:numel(data) crc bitxor(crc, data(i)); for j 1:8 if bitand(crc, uint8(128)) % 检查最高位 crc bitand(bitshift(crc, 1), 255); crc bitxor(crc, uint8(29)); % 0x1D else crc bitand(bitshift(crc, 1), 255); end end end crc bitxor(crc, uint8(255)); % 结果异或 0xFF end这段代码就是教科书里的位运算法逻辑直白适合在MATLAB Function块里直接跑。如果对性能有要求可以换成256字节的查表法但实时机上位运算法足够快一帧8字节算下来微秒级VeriStand完全扛得住。Counter更新逻辑我用Unit Delay加复位实现。核心思路很简单每个采样步进加1到达255后归零。function counterOut update_counter(counterIn) % 输入上一周期Counter值输出当前周期Counter值 if counterIn uint8(254) counterOut uint8(0); else counterOut counterIn uint8(1); end end然后在外层MATLAB Function块里把数据拼起来function packedData pack_frame(rawData, counterIn, counterOut) % rawData: 1x7 uint8数组对应Byte0~Byte6 % counterIn: 上一周期Counter % counterOut: 当前周期Counter packedData zeros(1, 8, uint8); packedData(1:7) rawData; % 原始数据放在前7字节 packedData(4) counterOut; % 按DBC定义Counter在Byte3 % 这里要注意CRC计算范围是除Checksum自身外的前7字节 % 也就是rawData里已包含Counter所在位置 packedData(8) calc_crc8(packedData(1:7)); end为什么Counter要放在计算范围里这是模拟真实ECU行为的关键。接收端算CRC时用的是实际收到的数据场Counter当然包含在内。如果发送端算CRC时不含Counter而接收端含两边永远对不上。3.2 模型编译成VeriStand可用DLL模型搭建好后要编译成VeriStand能加载的DLL这一步新手最容易卡。我把流程拆细一点。编译前置条件确保安装了NI VeriStand Model Interface Toolkit通常随着VeriStand安装包一起或者单独安装并在MATLAB里用veristand命令配置好环境。版本匹配非常重要Simulink的版本、NI VeriStand的版本、代码生成器版本三者最好保持一致差一个版本都有可能出现DLL加载不了的情况。在Simulink模型配置参考里做两处关键设置选择系统目标文件为NI VeriStand对应的.tlc文件比如niveristand_vx.tlc具体根据VeriStand版本选并且把积分算法设置成离散定步长步长通常设为CAN报文周期或更小比如1ms。然后把模型工作目录切到一个干净路径点击Build按钮。编译成功后会生成一个DLL文件和一个veristand_model_info.xml文件。这两个文件是VeriStand识别的核心不能动。如果模型里用到可调参数还会生成额外的参数文件一并保留。3.3 模型端口与CAN信号映射绑定打开VeriStand System Explorer操作界面和项目配置通常在NI VeriStand工程环境里新建一个System Definition然后按下面步骤配置在Targets列表下找到Controller右键Add Model浏览加载刚才编译的DLL。加载完成后Model的输入端口比如rawData数组的各个信号和输出端口packedData的8字节会出现在模型端口列表里。在Controller下添加CAN Interface这里选NI-XNET设备通道根据PXI板卡实际配置选择波特率要和DUT一致。在CAN Interface属性里导入DBC文件导入后VeriStand会识别出报文AD_SYS_Info以及里面的所有信号。把模型的输出端口映射到DBC信号的发送路径上。这一步在VeriStand的系统映射System Mapping里完成可以把模型端口和CAN信号拖拽绑定。映射完成后建议先在VeriStand的Stimulus面板里手动给模型输入赋一个固定值用CANalyzer或者VeriStand自带的XNET Monitor抓一下总线报文确认Counter在变、Checksum跟着数据变化。3.4 实时运行和在线观测我习惯用VeriStand的激励发生器来测试这套逻辑。新建一个Stimulus Profile给SteerAngle输入叠加一个正弦波周期设1Hz幅值适当偏置让角度值在工作范围内变化。然后启动实时运行观察两路信息一路是VeriStand界面里的模型端口值另一路是总线监视器抓到的CAN报文原始字节。正常情况下每10ms能看到一帧0x183Byte3的Counter每帧加1到255后归零Byte7的CRC随数据场变化而变化。如果DUT在接收端配置了E2E校验此时应该不再报checksum和alive counter错误。我还建议做两个反向用例来验证“假报文会被拒绝”一个用例是冻结Counter让它保持常数此时DUT应该报AliveCounter错误另一个用例是把CRC算法故意改成错误多项式DUT会报Checksum错误。这两个用例能证明仿真端的校验逻辑真的穿过了整条链路而不是靠运气。4. 核心细节和坑位盘点4.1 字节序与比特序最容易翻车的地方做CAN报文仿真绕不开Intel格式和Motorola格式。Intel格式是低字节在前、小端排列Motorola格式是高字节在前、大端排列。DBC文件里每个信号都有Start Bit定义同一个多字节信号用Intel和Motorola定义出来的字节顺序完全相反。如果模型里拼装数组的顺序和DBC信号定义不一致CRC和Counter算得再对接收端解析出来的数值也是错的。我踩过最深的坑是Motorola格式的跨字节信号。在CANdb里看起来比特位排布很规整但实际按字节数组填充时信号的高位在低字节必须先把所有信号按DBC定义的比特序转换成字节数组再给CRC计算模块。建议不要手算先在工具里布置好让工具导出数据场排列以抓包结果为准。4.2 CRC参数五项一个不对就全白干判断CRC是否正确的关键不只是多项式。完整的CRC算法参数一共有五项多项式、初值、结果异或值、输入是否反射、输出是否反射。这五项中任何一项和接收端不一致计算出来都是错的。算法名称多项式初值结果异或输入反射输出反射CRC8 SAE J18500x1D0xFF0xFF否否CRC8 AUTOSAR常见变体0x2F0xFF0xFF否否CRC16-CCITT0x10210xFFFF0x0000否否CRC16-IBM0x80050x00000x0000是是项目里最常见的麻烦是发送端用了CRC8 SAE J1850接收端DUT里用的却是AUTOSAR Profile的另一套参数两边看数据都“好像是CRC8”但结果永远对不上。解决办法只有一个拿到DUT的校验算法参数文档严格按文档对齐。没有文档就做一个数据样本交换测试用一个固定的8字节序列让DUT厂商给出期望的CRC值。4.3 Counter溢出和周期配合Counter出问题往往不是算法复杂而是更新节奏和报文发送节奏没对齐。举个例子VeriStand里模型调度周期设了1ms但CAN总线上报文是10ms发一次。如果Counter放在模型每个采样周期都加1那么一帧报文发出前Counter已经加了10次下一帧又是10次接收端看到的Counter跳跃式增长判定为乱序。解决办法有两种一是把Counter更新逻辑放到10ms的触发子系统里和报文发送节拍一致二是用VeriStand的定时器信号做使能每隔10个模型周期才允许Counter更新一次。还有一个常见细节4bit Counter会在0x0F处归零8bit Counter在0xFF处归零。如果DUT的E2E配置要求的是4bit我们模型里用了8bit虽然都能递增但溢出点完全不同接收端在接近满载时就会发现衔接不上。务必在DBC定义里确认Counter位宽和DUT ECU配置保持一致。4.4 别把CAN控制器硬件CRC和报文里的Checksum搞混这个坑也不少见总线上抓到的每帧CAN报文其实自带一个CAN数据链路层的帧校验CRC它由CAN控制器硬件自动生成和验证负责保证“这一帧在电气传输层面没有坏”。而我们在报文数据场里手动放的Checksum属于应用层校验通常是ECU软件里的E2E保护模块计算出来的负责保证“这一段业务数据在端到端通信中没有被篡改或丢失”。我见过有人把这两层CRC混为一谈认为硬件已经校验了应用层不需要再做“重复工作”。在真实ECU通信里上层ECU并不知道底层硬件校验是否生效所以应用层的CRC和Counter还得有。仿真时也一样VeriStand的CAN接口硬件会做物理层CRC但它不会帮你算应用层Checksum我们必须自己在模型里补。5. 故障排查速查表自留版下面这张表是我自己反复用到的排查清单直接抄即可。症状可能原因排查方法Counter不递增模型采样周期与CAN发送周期不匹配Counter更新逻辑被禁用端口映射没连接先在VeriStand界面看模型内部Counter值排除端口问题再用总线监视器确认帧周期Counter跳变、接收端报AliveCounter错误Counter更新节拍和发送节拍不一致把Counter更新放进10ms触发子系统或者用定时器使能信号控制CRC始终不对多项式/初值/异或/反射任一参数与DUT不一致字节序选错CRC计算范围包含了自己用固定样本序列发给DUT厂商核对检查DBC信号排列确认CRC计算数组不含Checksum自己数据值看起来对但DUT不处理Counter初始值不符合DUT预期接收端E2E配置里忽略第几帧的规则不同查看DUT配置文档确认Counter起始值通常从0或1开始总线上出现错误帧或总线关闭DLC定义错误报文实际长度和DBC不一致波特率配置错误多个节点同ID冲突用总线分析仪看错误帧类型确认所有节点CAN ID唯一核对DBC中的DLC模型加载DLL失败VeriStand版本和Simulink代码生成器不匹配目标文件选错用NI官方版本对照表重新生成查看VeriStand日志确认错误码CAN FD报文计算CRC时总不对DLC可变计算范围不固定CAN FD报文DLC可变CRC计算长度必须匹配实际数据场长度建议固定DLC或按实际动态取数问题定位时我习惯遵循一个顺序先看模型内部值再看总线上的原始字节最后才怀疑算法。VeriStand界面能看到模型端口值总线监视器能看到实际发出的字节两边一对比链路在哪个环节出偏差立刻现形。6. 写完再分享一点个人体会这套东西我前后在好几个项目里反复用过最大的感受是Checksum和Counter本身不复杂复杂的是和真实ECU的参数对齐。真正花时间的不是写算法而是确认DUT到底用哪套CRC参数、Counter从几开始、溢出位宽是多少、计算范围包括哪些字节。这些信息如果不提前谈清楚调试的时候只能一遍一遍地试极其消耗精力。一个小建议建DBC文件的时候就把Counter和Checksum定义成独立信号不要塞在“Reserved”字节里。信号独立之后不管是在VeriStand里映射还是在CANalyzer里分析都能一眼看到数值排查效率高很多。另外模型里加一个手动开关可以在运行中一键冻结Counter或者让CRC使用错误值这对做E2E故障注入用例非常有帮助不用每次改代码重新编译。最后再分享一个经验近几年的ECU里E2E保护已经从高端ADAS控制器蔓延到普通车身控制器了仿真台架如果不补上这套逻辑早晚会栽在环节校验上。提前把Checksum和Counter做成仿真模型的标准模板每个新项目直接套用能省下不少时间和反复沟通的成本。
返回列表