拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CAN与UDS诊断开发实战:从位时间配置到服务调测

1. 先把 CAN 和 UDS 的关系理顺做车载通信开发这几年我发现一个特别常见的误区很多新人把 CAN 和 UDS 当成两个独立的东西学今天啃 CAN 报文格式明天背 UDS 服务表结果一到实际项目里全对不上号。其实这俩在车载电子电气架构里是标准的“底层承载 上层应用”关系——CAN 负责把 0 和 1 可靠地从一个节点搬到另一个节点UDS 则在这条“数据管道”上定义了一套谁来讲、讲什么、怎么回答的规矩。打个比方CAN 是高速公路UDS 就是上面跑着的救护车有固定的鸣笛节奏请求/响应格式和出诊流程诊断服务序列但车能不能跑起来最终还是看路修得怎么样。这篇内容我打算按实际开发的顺序来拆先讲清楚 CAN 底层物理层和链路层那些容易踩坑的点比如位时间配置、时钟误差、采样点选择再往上走看 UDS 协议栈怎么在这条总线上落地重点分析 19、27、34/36/37 这几个高频服务到底在干什么最后聊开发和测试阶段的排查手段包括波形怎么看、错误帧怎么抓、模拟器怎么搭。适合正在做车载测试、BSP 开发、诊断协议栈集成或者准备转车载方向的嵌入式工程师参考。我默认你已经能看懂 CAN 报文的基本构成ID、DLC、数据场如果这层还不熟建议先翻一遍 ISO 11898 再回来。说到底这份文档最大的价值不是罗列协议条文而是把“协议怎么用”和“为什么这么设计”串起来。比如同样一个 27 服务解锁为什么 OEM 要求种子和密钥必须分两帧发为什么 19 服务读 DTC 要按子功能拆那么多类这些设计背后都有真实的工程考量理解了它们你写代码、调测试用例的时候才能少走弯路。2. 底层 CAN把位时间掰碎了看2.1 位时间结构为什么 500kbps 和 500kbps 也会对不上CAN 总线是异步串行通信收发双方没有独立的时钟线全靠每个节点自己的晶振去“猜”对方什么时候发送了一个位。为了保证大家猜的节奏一致协议把每一个位的传输时间切成四段同步段、传播段、相位缓冲段 1 和相位缓冲段 2。这四段加起来就是一个完整的位时间而采样点就落在相位缓冲段 1 结束、相位缓冲段 2 开始的那个边界上。实际配置时我们操作的不是纳秒而是“时间量子”TQ。一个位时间由若干个 TQ 组成TQ 又由 CAN 控制器外设时钟分频得到。比如 STM32 的 bxCAN 或者 S32K 的 FlexCAN外设时钟 40MHz目标波特率 500kbps那一位时间是 2000ns如果每个位时间设 20 个 TQTQ 就是 100ns分频系数就是 40MHz / (1/100ns) 4。这个换算看着简单但项目里经常出问题的地方在于很多人只算了波特率没算采样点位置。采样点位置的工程经验值是 75% 到 85%。为什么不能太靠前因为 CAN 的信号传输有延迟包括收发器延时、总线线缆传播延时、控制器内部处理延时。如果采样点太早发送端刚把电平拉起来接收端还没来得及看到稳定的电平就采样了容易误判。如果太靠后留给相位缓冲段 2 做重同步补偿的余量就不够了总线在有干扰或者节点时钟偏差较大时更容易报错。我在实际项目里一般按 80% 来配比特率 500kbps、采样点 80%在 20 个 TQ 的位时间里同步段 1 TQ、传播段 2 TQ、相位缓冲段 1 取 13 TQ相位缓冲段 2 取 4 TQ这样采样点正好在 (1213)/20 80%。不同 MCU 的寄存器字段可能把传播段和相位缓冲段 1 合并成一个字段配置时注意看一下参考手册别把字段含义搞混了。2.2 时钟误差和重同步错误帧是怎么冒出来的CAN 的容错能力很强但前提是每个节点的实际位时间都接近理想值。晶振是有精度误差的常规晶振 ±100ppm 甚至 ±500ppm 都很常见如果两个节点的晶振偏差方向相反并且波特率又比较高累计误差就会超过协议允许的范围导致接收节点在采样点拿到的电平和预期不符然后就会主动发错误帧。为了对抗这种时钟偏差CAN 控制器内置了重同步机制。简单说当接收节点发现边沿到来的时间和预期有偏差时会自动调整相位缓冲段的长度把采样点往后推或者往前拉。但重同步的调整范围是有限的不能无限补偿所以位时间配置里相位缓冲段 2 的长度就特别重要它决定了最大补偿能力。我实测过一个案例两块开发板一块用内部 RC 振荡器精度大约 ±1%另一块用外部 8MHz 晶振两边都配 500kbps结果总线上一会儿就出一帧错误帧。用示波器看波形边沿的位置在抖动明显是时钟偏差超出了补偿范围。后来把内部 RC 那块的波特率降到 250kbps同时把采样点从 80% 调到 75%错误帧就消失了。所以排查这类问题不要一上来就怀疑收发器或者线束先确认两边的时钟源精度和位时间参数是不是匹配。2.3 验收滤波器和 ID 规划别让 MCU 被无用报文淹死CAN 控制器都有一个验收滤波器作用是在硬件层面把不关心的报文直接丢掉不进接收 FIFO这样 CPU 就不用浪费时间处理。这个功能在节点多的总线上特别有用。比如一个车身控制器可能挂在动力 CAN 上但只关心发动机转速、车速和挡位那几路信号其他几十路报文全是噪音。配置滤波器时CAN 2.0 的 ID 有标准帧 11 位和扩展帧 29 位之分滤波器分掩码模式和列表模式。掩码模式适合过滤“一段连续的 ID 范围”列表模式适合精确匹配“几个指定的 ID”。我用过的一个快捷配置是列表模式配 4 个关键 ID掩码模式再兜底放行一个范围。这样既能保证关键报文不丢又不会把 CPU 的中断频率拉得太高。这里有个坑ID 规划如果做得不好滤波器怎么配都不顺手。我见过一个项目各模块的报文 ID 随手定义没有按功能域分组结果整车联调时发现某个 ECU 的滤波器列表越加越长硬件资源不够用。后来重新按“源地址 功能域”来规划 ID 段比如动力域报文统一落在 0x100–0x1FF车身域落在 0x200–0x2FF问题立刻缓解。所以开发早期花半天时间把 ID 规划好后面能省几个星期的联调时间。3. 上层 UDS诊断服务不是对着表格抄就行3.1 UDS 的定位与会话/安全等级UDSUnified Diagnostic Services是 ISO 14229 定义的一套应用层诊断协议它不关心底下跑的是 CAN、CAN FD 还是以太网只定义“请求-响应”的语义。在 CAN 上承载时一般用 ISO 15765-2也就是常说的 TP 层做传输层解决“一条 CAN 报文最多 8 字节诊断数据可能几百字节”的拆包和重组问题。上电后 ECU 默认处于默认会话只能做最基本的读故障码、读数据这类操作。要执行刷写、配置、解锁等敏感操作得先切换到扩展会话10 03再通过 27 服务做安全解锁。这个分层设计是为了防止误操作和非法访问。比如车辆的 OBD 诊断口任何外接设备都能发报文如果 ECU 在默认会话就能改配置那随便一个手持诊断仪都能把车刷坏这显然不行。实际操作中会话切换的时序要特别注意。有些 ECU 规定从默认会话切到扩展会话必须在特定时间内完成超时会被拒绝有些则要求在扩展会话下执行完操作后主动切回默认会话否则会一直占着资源。做测试用例的时候这些时序边界是必须覆盖的。3.2 19 服务读 DTC 没那么简单19 服务ReadDTCInformation是诊断开发里最常打交道的服务之一但它的子功能特别多容易记混。最常用的是 02 子功能按状态掩码读取当前已存储的 DTC还有 01 子功能返回 DTC 数量以及 04 子功能清除故障码。每一个子功能对应的响应报文格式都不一样有的还需要按 DTC 格式字节、状态可用性掩码来解析。举个实际开发例子0x19 02 请求的响应里每条 DTC 记录包含 DTC 的高字节、中字节、低字节和一个状态字节。状态字节的每一位代表一个状态位比如 bit0 表示测试失败bit1 表示当前有故障bit6 表示上一次测试失败。很多新手拿到响应数据后直接按三相 DTC 编号去查手册却忽略了状态掩码的过滤条件结果明明有故障码却没查出来——因为 0x19 02 的请求里带了状态掩码只返回符合条件的 DTC。另外149 服务ReadPeriodicDataIdentifier和 22 服务ReadDataByIdentifier也经常一起出现。22 服务是立即读取某个数据 ID 的值比如当前车速、发动机水温149 服务则是周期性上报一组数据 ID适合需要持续观察波形参数的测试场景。在做诊断仪开发的时候一定要看一下 DTC 的状态字节怎么解析不然客户会投诉“明明仪表亮了故障灯诊断仪却读不到故障码”。3.3 27 服务种子-密钥流程里的时序陷阱27 服务SecurityAccess的目的是解锁 ECU 的受保护功能比如刷写、配置、特殊测试。流程很经典诊断仪先发 27 01 请求种子ECU 返回一个种子值诊断仪用这个种子做某种算法运算得到密钥然后发 27 02 把密钥发回给 ECUECU 校验通过后解锁。这个流程看起来不复杂但项目里踩坑极多。首先是种子和密钥的字节序不同 ECU 的种子可能是大端也可能是小端算法计算的输入如果字节序反了密钥永远对不上。其次是延时要求很多 ECU 规定从发送 27 01 到收到 27 02 必须在某个时间窗口内完成比如 500ms否则认为非法操作直接拒绝。我遇到过一个案例诊断仪和 ECU 之间的 TP 层传输延迟偏大每次解锁都失败最后抓总线看时间戳才定位到是响应太慢。还有一个容易忽略的点是失败计数。很多 ECU 会记录安全解锁的失败次数比如连续失败 5 次就锁定一段时间期间即使密钥正确也不接受。这种机制是为了防暴力破解。因此做自动化测试时解锁失败后一定要等锁定超时或者复位 ECU不然会卡住整个测试序列。测试脚本里建议把重试次数和失败延时做成参数别写死。3.4 34/36/37 服务刷写流程的“三段式”刷写是 UDS 服务里最复杂也最容易出问题的场景核心是 34RequestDownload、36TransferData、37RequestTransferExit三个服务的组合。34 服务先告诉 ECU 要下载的数据长度和地址ECU 返回一个允许的最大块长度36 服务按块发送实际数据每发一块 ECU 都要回一个肯定响应全部发完后用 37 服务告诉 ECU 传输结束。为什么不能一把梭直接把几百 KB 的数据塞进一条报文因为底层 CAN 报文最多 8 字节TP 层虽然能拆包重组但一次传输的数据量太大会占用大量缓冲区。同时ECU 端的 Flash 擦写是需要时间的如果不按块发送、逐块确认ECU 根本来不及处理数据就会丢。这个设计本质上是一个简单流控机制。块大小的选择有讲究。我在一个项目里遇到 ECU 最大接收块长度是 64 字节但诊断仪侧一帧发送缓冲区只有 32 字节结果每次发到 32 字节就断了刷写速度慢得离谱。后来把 TP 层的发送缓冲加大到 128 字节实测刷写时间缩短了近一半。另外36 服务里的块计数blockSequenceCounter是从 1 开始递增的范围 1–0xFF到 0xFF 后回绕到 0 再继续。有些 ECU 实现不严谨回绕时会误判为重复块这也是联调时的一个典型问题。4. 实操过程从零搭一套 CANUDS 联调环境4.1 硬件选型和环境准备做 CANUDS 开发手边至少要有一套带 CAN 收发器的开发板和一个 USB-CAN 分析仪。开发板选型上STM32F4/F7/H7 系列自带 bxCAN 或 FDCAN资源丰富、资料多适合起步。USB-CAN 分析仪我用过周立功的 USBCAN-II 和 PCAN 的 USB 设备前者便宜、国内资料多后者抓包软件更顺手。无论选哪个都要确认它支持你要用的波特率和 CAN FD 功能。环境准备包括三部分硬件接线、驱动安装、软件工具。CAN 总线两端必须接 120Ω 终端电阻很多人第一次联调没有接导致波形振铃严重通信时好时坏。终端电阻不需要每个节点都接只需在总线物理两端各接一个保证总线阻抗匹配。软件方面CAN 分析仪自带的工具一般都能收发报文但要做会话层和 TP 层解析推荐装一个 Vector CANoe如果有授权或者开源的 Wireshark 配合 CAN 接口插件可以解析 UDS 层并过滤请求/响应。4.2 最小可用的 UDS 请求/响应流程我习惯把联调流程做成一个最小验证集按顺序跑通后再扩展功能。第一步测试仪发送 10 03 切换到扩展会话ECU 应返回 50 03 表示肯定。第二步发送 27 01 请求种子ECU 返回 67 01 加种子数据。第三步根据算法计算密钥发送 27 02ECU 返回 67 02。第四步发送 22 F1 90 之类的读数据请求确认能读到 VIN 码或固件版本。如果这四步都能跑通说明 CAN 底层、TP 层、UDS 会话管理、安全解锁、数据读取这些基础链路都已经正常。这个流程里最容易出问题的是 TP 层的连续帧处理。比如 27 01 请求种子ECU 回复的种子如果超过 6 字节TP 层就会拆成多个连续帧。诊断仪端如果只处理了首帧没正确拼接连续帧拿到的种子就是残缺的解锁必失败。排查这类问题最好的方法就是在分析仪上抓取完整的总线数据看 TP 层的帧类型、帧序号和 payload 拼接。4.3 用波形判断 CAN 通信质量判断一条 CAN 总线好不好示波器是最直接的证据。抓波形时重点看四件事显性电平幅值是否足够一般要求大于 2V隐性电平是否稳定接近 2.5V边沿是否陡峭以及有没有振铃和台阶。显性电平偏低的常见原因是收发器驱动能力不足、终端电阻配置错误或总线线缆过长。边沿不陡峭则可能和线缆电容、收发器上升率设置有关。另外波形上的毛刺很容易被误判为错误帧。有一次我在实验室调一个 2m 长的 CAN 总线用示波器探头夹在 ECU 端的 CAN_H 和 CAN_L 上看到一堆毛刺当时以为总线被干扰了后来才发现是探头接地线太长形成了天线效应。换成短地线弹簧探头后波形干净得很。所以排查波形问题时先排除测量手段本身引入的噪声。4.4 常见错误帧排查速查表错误帧是 CAN 通信中最烦人的问题之一。根据我的经验把错误帧的类型和可能原因整理成一个速查表排查起来会快很多。错误帧现象排查方向节点发送一次报文就出一次错误帧检查该节点位时间配置和采样点确认波特率偏差是否过大特定两块板之间通信出错检查两端时钟源精度、收发器是否同一型号、CAN_H/CAN_L是否接反总线负载高时偶发错误帧检查是否缺少终端电阻、线缆是否过长、连接器是否氧化接触不良上电瞬间大量错误帧检查节点上电时序多个节点同时初始化可能造成总线竞争CAN FD 报错确认仲裁段和数据段波特率是否配置一致数据段波特率过高时检查收发器是否支持错误帧周期性和发动机转速相关检查是否来自点火系统或高压线缆的电磁干扰考虑双绞屏蔽线报文发送成功但接收端收不到检查验收滤波器是否配置错误ID 是否被硬件过滤掉了这里面我特别想强调一下“CAN_H 和 CAN_L 接反”。很多新手觉得差分信号接反了也能通信因为 CAN_H 和 CAN_L 是互补的电平关系。但实际上接反后显性电平会变成负压很多收发器根本不识别表现就是毫无反应或者错误帧不断。排查这个问题最快的方法是量静态电压正常情况下 CAN_H 和 CAN_L 的对地电压都是 2.5V 左右隐性时如果 CAN_H 量出来是 1.5V、CAN_L 是 3.5V那就基本可以断定是接反了。5. 工具链和测试手段文档之外的实战装备5.1 从 CANoe 到开源方案的工具选择大厂项目里 CANoe 几乎是人手一份它的强大之处在于不仅能抓包还能做仿真节点、CAPL 脚本自动化、诊断控制台。但 CANoe 价格不便宜个人学习或者小团队用起来有点心疼。开源方案里cantact 配合 can-utils 是很好的入门组合再加上 Wireshark 的 CAN 解析插件已经能覆盖大部分开发和调试需求。我在个人项目里常用的是 PCAN-USB Python 的 python-can 库。python-can 是一个比较好用的库能直接发 CAN 报文也能用 can-utils 的 candump 抓数据。配合 cantools 库还能直接加载 DBC 文件解析信号比手动看十六进制报文效率高得多。写自动化测试脚本时我通常把“发送请求-等待响应-校验响应”封装成一个公共函数错误码、响应超时时间做成可配置项这样换 ECU 型号时改动很小。5.2 如何高效构造 UDS 测试用例UDS 测试用例的设计要覆盖正常流程、异常流程和边界条件。正常流程很简单就是按协议规范发请求验证肯定响应。异常流程要覆盖超时无响应、格式错误、服务不支持、条件不满足比如未解锁就执行刷写这些场景。边界条件包括数据长度恰好等于单帧最大长度、TP 层连续帧达到最大数量、块计数回绕、请求种子后又重复发 27 01 等。我见过很多测试团队只测正常流程结果量产前联调时被各种异常场景打懵。实际上UDS 诊断开发的大部分 Bug 都藏在异常分支里尤其是响应码处理。比如某个服务在 ECU 处于错误状态时应该返回 0x22条件不正确但代码实现里可能直接返回了 0x7F 22 78响应待定然后没后续了。这类问题只有靠充分覆盖异常分支才能发现。5.3 CAN FD 来了UDS 怎么办CAN FDCAN with Flexible Data-rate是 CAN 2.0 的升级版把数据场长度从 8 字节扩展到最多 64 字节数据段波特率也能提高到 5Mbps 甚至更高。UDS 跑在 CAN FD 上时TP 层的拆包次数大大减少刷写速度提升非常明显。但要注意CAN FD 的仲裁段数据段波特率可以不同而且不是所有收发器都支持选型时得确认硬件规格。对开发者的影响主要在两个层面一是测试工具必须支持 CAN FD老旧的 USB-CAN 设备可能只支持经典 CAN二是 DBC 或 ARXML 描述文件里要正确区分仲裁段和数据段波特率不然报文解析会错位。我在一个项目里遇到过工具显示波特率对但抓不到包的情况最后发现是工具默认没开启 FD 模式报文被当成了错误帧丢弃。6. 把这些经验落到你的项目里从我个人的体会来说车载 CAN 和 UDS 诊断这块最容易拉开差距的不是背了多少协议条文而是遇到问题时的排查思路。协议条文是固定的网上随便都能查到但“总线波形有毛刺是测量问题还是干扰”“解锁失败是时序问题还是算法问题”“错误帧是时钟偏差还是收发器问题”这些判断必须靠实际调试一点点积累。最后分享一个我最近在用的习惯每接到一个新项目先花时间把诊断规范里所有服务的请求和响应格式整理成一张速查表标注好字节序、长度、子功能枚举然后把这张表导入到测试脚本的配置文件里。后续写用例、做联调、写报告全部从这张表取数既不会漏项也能保证多个工程师之间口径一致。这比每次翻几百页的 PDF 规范高效得多也能减少因为手势不统一导致的低级错误。如果你正在入门或者转岗车载方向我建议你找一块带 CAN 的开发板搭一套最小环境的优先级要高过啃协议文本。把 10、22、27、19、34/36/37 这些服务在真实环境里跑一遍比读十遍规范都管用。等你能把 EC U 的故障码读出来、解锁成功、再刷进一段自己的小代码恭喜你你已经迈进车载诊断开发的门槛了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门