从CAN底层到UDS诊断:车载通信与刷写流程全解析
发布时间:2026/9/5 10:37:11 作者:尧图编辑部 阅读量:1,286

1. 项目背景与整体方案设计思路市面上讲 CAN 总线基础的文章很多讲 UDS 诊断应用的也不少但把“底层 CAN 通信”和“上层 UDS 诊断协议”串成一条完整开发链路的系统性资料却不多见。很多新人入行时往往陷入两个极端要么停留在收发报文层面会看 DBC、会抓波形但不知道这些报文怎么支撑起一套诊断流程要么直接上手 UDS 服务对着 19 服务、27 服务的交互流程一顿操作底层一旦出现总线错误、时钟漂移、采样点偏差导致的通信不稳定就完全无从下手。这个项目文档的核心目标就是把这条链路彻底打通从 CAN 物理层和链路层的底层机制讲起逐步过渡到 UDS 网络层与诊断应用层的服务定义再落到刷写、例行诊断、安全检查点这类真实车载场景。它在解决一个实际问题当一块新控制器装上台架上位机发诊断请求没响应你怎么从 CAN 底层一路排查到应用层最终定位是波特率配置不对、采样点不在最佳窗口、地址模式匹配不上、还是服务参数填错。这篇内容适合三类读者刚入门想做车载测试与嵌入式开发的应届生需要从头建立 CANUDS 知识体系从应用层转到底层驱动的软件工程师需要补齐总线和诊断协议栈的底层细节以及做台架集成与产线测试的测试工程师需要一套可直接参照的刷写、诊断验证流程和故障排查思路。需要先说明的是本文涉及的代码片段和参数配置来自我参与过的多个车载控制器项目的工程实践具体数值因芯片和 PHY 芯片而异但排查思路和协议逻辑是通用的。2. CAN 底层通信机制解析与工程配置要点2.1 位定时与时钟误差CAN 通信稳定性的第一道门槛CAN 底层开发里最容易被轻视、却最容易引发间歇性故障的就是位定时配置。CAN 协议本身是异步串行通信没有独立的时钟线收发双方依靠每一位的跳变沿来同步。波特率时钟由控制器的主时钟分频而来一颗晶振在常温下精度可能只有 ±20 ppm 甚至 ±50 ppm温度拉高、老化之后误差还会变大。如果接收方采样点相对于发送方的位时序偏差累计过多就会出现“偶尔一帧错误、重启后正常、温度一变又复现”这类难查的问题。配置位定时的核心参数有三个预分频器、时间段 1TSEG1、时间段 2TSEG2三者共同决定一个位时间由多少个 time quantumtq组成。CAN 2.0 规范把一个位时间划分为同步段、传播段、相位缓冲段 1、相位缓冲段 2其中同步段固定为 1 tq传播段加相位缓冲段 1 对应 TSEG1相位缓冲段 2 对应 TSEG2。采样点就落在 TSEG1 结束、TSEG2 开始的位置。假设系统时钟 80 MHz目标波特率 500 kbps每个位时间需要 160 tq 显然不现实——CAN 控制器通常限制每个位时间在 8 到 25 tq 之间。所以要先定 tq 数再反推预分频器。比如每个位时间选 16 tq预分频器就是 80 MHz / (500 kbps × 16) 10TSEG1 设为 13TSEG2 设为 2同步段 1采样点 (1 13) / 16 87.5%。这是高速 CAN 常用的采样点位置。低速容错 CAN 或某些对线缆长度敏感的场景可以适当把采样点后移到 80%~85%给传播延迟更多余量。注意不能只算波特率采样点位置同样关键。我自己踩过的坑是同一套代码把 250 kbps 的配置直接拿来跑 500 kbps只改了波特率宏定义结果总线上两台设备互发正常一旦接入第三节点就频繁出错误帧。后来定位才发现 TSEG1/TSEG2 的分频逻辑在不同波特率下没有同步调整采样点漂到了 60% 附近总线信号畸变时根本没机会纠正。2.2 CAN 时钟误差与重同步机制光配置好标称位时序还不够必须理解 CAN 控制器的重同步机制才能解释清楚为什么时钟误差在一定范围内通信正常、突破某个阈值就全线崩溃。CAN 的同步分为硬同步和重同步。总线从空闲转为起始位时所有节点执行硬同步把当前位时间重新对齐到起始沿。之后每一位的跳变沿如果与本地预期采样点有偏差控制器会通过延长 TSEG1 或缩短 TSEG2 来微调这个过程就是重同步。重同步的补偿能力是有限的限制条件就是同步跳转宽度SJW。SJW 必须大于等于 1且不能超过 TSEG2 的宽度。它的物理意义是单个位时间内最多能纠正多少误差。比较关键的一个工程结论是当总线波特率误差超过一定边界时即使有重同步也救不回来。以 500 kbps、位时间 16 tq、SJW 为 1 tq 为例每位最多纠正 1 tq如果两个节点的时钟误差每秒累积起来超过这个量级就会产生位填充错误或 CRC 错误。所以在项目里我会对同一总线上的所有 ECU 做一次晶振精度摸底误差超过 ±0.5% 的建议直接更换晶振而不是去调大 SJW 硬扛。调大 SJW 虽然能提升容错但会让采样点对噪声更敏感属于拆东墙补西墙。2.3 总线仲裁与报文过滤CAN 是载波监听多路访问/冲突检测的仲裁机制总线空闲时任何节点都能发起发送多个节点同时发送时靠标识符的显性位优先仲裁。标识符数值越小优先级越高。这个机制决定了整车网络里电源管理、碰撞安全这类高优先级报文必须分配较小的 ID而多媒体、诊断这类低实时性报文可以用大 ID。诊断报文通常是低于大部分周期报文的优先级所以高负载时诊断响应被延迟是正常现象上位机超时时间的设定必须考虑这一点。对底层开发来说报文接收过滤设计同样重要。现代 CAN 控制器都带硬件过滤比如 STM32 的 bxCAN 有 28 个过滤器组可以配置成标识符列表模式或掩码模式。掩码模式适合接收一组 ID列表模式适合精确匹配。实际项目里我习惯把诊断报文如 0x7E0/0x7E8用列表模式精确过滤把网络管理报文用掩码模式过滤避免 CPU 频繁被无关中断打断。有个容易忽略的细节如果开了 FIFO 满中断大量周期报文灌进来时处理不及时会导致诊断报文被覆盖。这时候要么提高接收中断优先级要么把诊断报文放到独立的 FIFO 和过滤器组里从硬件层面隔离开。3. UDS 诊断协议分层结构与核心服务解析3.1 从 CAN 帧到 UDS 数据网络层与传输层的拆装UDSUnified Diagnostic Services跑在 CAN 之上遵循 ISO 14229 和 ISO 15765-2 的分层结构。CAN 数据链路层一帧最多 8 字节而一条 UDS 消息可能很长比如 34 服务下载数据动辄几百 KB这就需要在网络层做拆包和重组。ISO 15765-2 定义了单帧、首帧、连续帧和流控帧四种帧类型。单帧用于不超过 7 字节的用户数据CAN FD 下可能更长首帧携带总长度信息连续帧按顺序发送数据流控帧则由接收方决定发送方能否继续发以及每帧间隔多少毫秒。这个机制很像两台机器之间的滑动窗口协议。工程上最容易出问题的地方是流控帧的 BS块大小和 STmin最小间隔时间参数配合不当。BS 表示连续发多少帧后要等一个流控帧STmin 表示连续帧之间的最小间隔。如果接收端处理能力弱BS 可以设成 0 表示不限制但 STmin 拉大给应用层留出处理时间。我见过一个典型案例刷写工具发送连续帧的节奏过快ECU 的接收缓冲区溢出导致数据段丢失刷写反复失败。排查半天最后发现是 Bootloader 里的流控参数初始化有误STmin 设成了 0但接收缓冲区根本没有为连续帧风暴预留足够的深度。这个问题在实车上会被温度、总线上其他报文干扰放大表现成十次刷写偶尔失败一两次特别难查。3.2 10 会话控制与 27 安全访问诊断流程的两道门UDS 诊断会话有默认会话、编程会话和扩展会话三种状态。10 服务负责切换会话不同会话决定哪些诊断服务能被允许执行。默认会话下通常只能做读取类操作比如 22 读数据、19 读故障码扩展会话允许写入类的配置操作比如 2E 写数据、31 例程控制编程会话则专供刷写 Bootloader 或应用程序使用。会话切换本身看起来只是发一条 10 01、10 02 或 10 03但工程上的麻烦在于会话超时管理。ECU 通常有 P2 和 S3 两个超时时间P2 是服务器响应请求的最大等待时间S3 是会话保持时间。如果上位机在 S3 时间内没有发任何诊断请求ECU 会自动回落到默认会话。这时候刷写流程就断了必须重新走一遍会话切换。我看过不少测试人员写的刷写工具没有在长数据段传输过程中周期发送保持会话的请求结果一遇到 Debug 断点或网络抖动会话就掉了只能从头再来。27 服务是安全访问通常流程是上位机发送种子请求子功能 01ECU 返回一串随机种子上位机用固定算法计算出密钥发送比对请求子功能 02一致则解锁之后才能执行写入擦除类操作。这个机制的本质是防止非授权设备乱写 ECU。工程开发时要注意安全等级是分级的有些 ECU 有多个安全等级比如一级解锁只能做常规配置二级解锁才能刷 Bootloader。密钥算法一般由供应商保密开发阶段甲方会提供一个动态库或算法文档测试时用现成的上位机算好密钥再发送。注意27 服务的种子和密钥不能通过诊断仪或抓包工具直接明文比对这在量产项目里是严重的安全漏洞。有的供应商会在特定条件下限制尝试次数比如连续三次密钥错误后ECU 要在 10 秒后才重新允许获取种子。这是故意的防暴力破解设计测试时不要误判成故障。踩过坑的朋友应该都知道拿一个没解锁的 ECU 反复尝试错误密钥后面等的那段时间特别焦躁。3.3 19 读取故障码子功能与状态掩码的深度理解19 服务是诊断中最常用的服务之一用于读取故障码DTC。它定义了多个子功能常见的有 01 按状态掩码读取、02 按严重度读取、04 读取快照、06 读取扩展数据。实际项目中 01 用得最多它与状态掩码配合可以精确筛选出当前存在、历史存在或已确认的故障。DTC 在 UDS 中由三个字节组成高字节、中字节和低字节。比如 0xC00101高字节 0xC0 表示动力系统中字节 0x01 表示具体子系统低字节 0x01 是故障类型。状态掩码的位定义更是细节中的细节bit0 表示测试失败、bit1 表示当前故障、bit2 表示历史故障、bit3 表示确认故障、bit4 表示未确认故障等。需要特别注意的一个坑很多初学诊断的人以为 DTC 的“当前故障”就是读出来的状态字节 bit1 为 1但实际工程里还有一个“确认故障”的概念它要满足连续多个驾驶循环都检测到同一故障才会被置位。所以当你看到一条 DTC 报当前故障但未确认说明这个故障是本次上电周期才出现的可能还没达到确认阈值不能直接判定为“稳定复现”。19 服务的响应报文格式也容易搞混0x59 是服务 ID后面跟的 DTC 格式指示符、状态可用性掩码、DTC 列表及其状态字节。状态可用性掩码表示 ECU 支持的故障状态位集合通常固定为 0xFF表示所有位都支持。解析响应时一定要先判断格式指示符不同格式下 DTC 字节长度不同直接按固定长度解析会错位。3.4 34/36/37 数据传输服务与刷写流程刷写是整车厂和供应商之间联调最多的诊断场景也是 34请求下载、36传输数据、37请求退出传输三个服务集中发力的地方。完整刷写流程大致是进入编程会话10 03安全解锁27 02 带密钥写入刷写相关参数2E 写 VIN、写刷写次数等用 31 服务例程控制做擦除操作比如擦除应用程序区用 34 服务请求下载告诉 ECU“我准备写多少字节进哪个地址”用 36 服务分块传输实际数据用 37 服务结束传输让 ECU 做校验用 31 服务执行检查或跳转程序最后复位 ECU11 服务。34 服务的请求格式是34 数据格式标识符 地址和长度格式标识符 内存地址 内存大小。地址和长度格式标识符决定地址和大小用几个字节表示比如 0x24 表示地址 4 字节、大小 2 字节。这个格式标识符必须和 Bootloader 约定的地址范围匹配常见错误是请求地址落在 ROM 保护区或者大小不是对齐单位ECU 会返回 NRC 0x31请求超出范围。36 服务的块大小必须和 Bootloader 的内部缓冲区匹配。比如 ECU 内部 Flash 编程缓冲区是 256 字节那上位机每帧传 256 字节是比较高效的传多了 Buffer 装不下传少了效率低。某些 Bootloader 还要求最后一块数据必须补齐到块大小不足的部分填 0xFF 或 0x00具体查规范。37 服务结束传输后ECU 会做 CRC 或累加和校验。整包刷写数据在传输前先算好校验值Bootloader 烧完后核对。如果校验失败通常返回 NRC 0x72通用编程失败。这块我在实际项目里踩过一个坑上位机对 S19 文件解析后地址段不是连续的中间有空洞但传输时没有单独处理空洞直接把数据按连续地址段切块发下去结果 Bootloader 往空洞地址写数据返回 0x31刷写直接终止。正确做法是按 Bootloader 支持的地址区间分包空洞部分既不要传也不要参与校验。3.5 31 例程控制与检查点流程31 服务用于启动、停止或请求例程执行结果刷写流程中的擦除、校验、跳转都可以用它实现。例程控制的核心是“例程标识符”RID每个 RID 对应一段固定逻辑。整车厂通常会定义一套例程编号规范比如擦除例程、编程依赖检查例程、应用程序有效性检查例程等。检查点流程其实是 31 服务的一个典型变体用于多步操作时确认每一步执行成功。比如刷写过程中完成擦除后建议用 31 服务读取擦除结果确认无误再进入下一阶段。这个“每步确认”的机制在量产产线上尤其重要能有效避免因某一步静默失败导致整包数据异常到最后一刻才发现返工成本极高。4. 工程实操从抓报文到完成一次完整刷写4.1 抓取 CAN 总线数据与波形判断开发阶段第一步是确认物理层有没有问题。用一个带 CAN 接口的示波器或逻辑分析仪抓波形。正常的高速 CAN 显性电平差分电压约 2 V隐性电平差分电压接近 0 V波形边沿应该干净利落没有明显的回勾或台阶。判断通信好坏有几个实际经验第一显性电平持续时间抖动不超过位时间的 10%比如 500 kbps 下每位 2 μs抖动应该小于 200 ns第二CAN_H 和 CAN_L 的共模电压在正常范围内不应有持续漂移第三连续抓取多帧数据没有 CRC 错误或填充错误计数增长。如果波形边沿存在缓慢爬升或回勾首先要怀疑终端电阻。高速 CAN 要求在总线两端各接一个 120 Ω 终端电阻测量 CAN_H 与 CAN_L 之间的直流电阻正常应为 60 Ω。如果测出 120 Ω说明有一端终端电阻缺失信号反射会明显增加长线缆场景下必现通信异常。如果测出接近 0 Ω则是终端电阻短路总线直接不可用。这一步是台架调试第一课也是排查所有上层问题的前提。4.2 CANoe/CANalyzer 环境下的 DBC 加载与诊断发送用 Vector 工具链做诊断是最主流的路径。新建工程后第一步加载 DBC 文件这样报文 ID、信号定义、字节序、缩放因子就都进了数据库。第二步用 Diagnostic Console 直接发送 UDS 请求也可以写 CAPL 脚本自动化实现多步骤流程。有个常见困惑为什么手动发送诊断报文没响应大部分原因在于 CAN 数据库里诊断报文的地址模式、DLC 长度和实际发送的数据不匹配。UDS 诊断请求通常用标准帧 0x7E0、响应 0x7E8但有的 OEM 会定义扩展帧或不同的功能寻址 ID不按客户规范来肯定收不到响应。还有一个小细节发送诊断请求前确认目标 ECU 是否处于正常通信状态。有些 ECU 在 Bootloader 模式下不再发应用报文但诊断报文仍然响应有些 ECU 需要先收到应用层报文才激活通信。如果目标 ECU 被其他工具连接占用了也可能一直无响应——CAN 总线上同一时刻两个诊断仪同时连同一个 ECU后连接的那个通常会因为会话被抢占而失败。4.3 完整刷写流程的参数设计与验证以一台 500 kbps 的台架 ECU 为例假设应用固件大小为 512 KBFlash 页大小为 4 KBBootloader 的接收缓冲区大小为 256 字节。上位机刷写参数可以做如下设计34 服务请求地址 0x08010000长度 0x00080000512 KB36 服务数据块大小选择 256 字节这样 34 服务一次最多可以传输吗注意 34 服务只是请求下载并携带总长度实际数据通过多条 36 服务连续帧发送每条 36 服务最多可以携带 256 字节有效数据加上服务 ID 和块序号CAN 帧需要拆包成多条连续帧。CAN 2.0 每帧数据最多 8 字节其中首帧最多 6 字节有效数据连续帧最多 7 字节有效数据。所以 256 字节数据约需拆成 1 个首帧 37 个连续帧共 38 帧。这里就回到我们第一节说的流控参数的重要性了。如果 Bootloader 设置的 STmin 是 0上位机也可以按总线节奏猛发连续帧但 ECU 的接收中断处理不过来时就会丢帧。实际工程中上位机建议支持两种模式一种是严格遵守流控帧的 BS/STmin另一种是上位机主动限速比如每帧间隔 1 ms~2 ms。低速模式刷写确实慢但稳定。完整刷一遍 512 KB 固件在 500 kbps 下理论速率约为 45 KB/s有效数据率受协议开销和流控影响实际测下来 15~20 KB/s 是常态。配上会话管理、擦除、校验时间整体刷完大约 40~50 秒。如果超过 2 分钟就说明流控参数或数据块大小配置不合理。4.4 检查点流程的 CAPL 脚本化诊断流程验证时我习惯用 CAPL 脚本把关键检查点自动化。大致逻辑如下发送 10 03等待 50 03 响应发送 27 01等待 67 01 4 字节种子用 CAPL 里集成的算法计算密钥发送 27 02发送 31 01 擦除例程等待 71 01 例程结果确认擦除完成发送 34 请求下载校验响应中的长度和数据格式标识符是否与请求一致逐块发送 36 服务每 256 字节一块验证块序号回显正确发送 37 结束传输确认响应发送 31 03 校验例程等待校验结果发送 11 01 复位观察应用报文是否重新出现。CAPL 脚本和 C 语言类似但每个等待响应都要设超时不要用死等。我在脚本里统一设 500 ms 的响应超时超时则输出错误、记录当前步骤并停止流程。这样做的好处是失败时能快速定位到具体步骤而不是整条流程跑完才发现刷写失败非常省时间。5. 常见问题与排查技巧实录5.1 诊断请求无响应的排查顺序遇到“发诊断请求没响应”先别急着怀疑协议栈代码按以下顺序排查第一确认物理层。用示波器看波形、测终端电阻排除总线物理异常第二确认节点的 CAN 控制器是否进入 BusOff 状态。如果 ECU 频繁发送错误帧控制器会自动离线需要查询总线状态寄存器第三确认诊断 ID 是否匹配。物理寻址还是功能寻址标准帧还是扩展帧0x7E0 是不是对该 ECU 有效第四确认是否处于正确会话。默认会话下某些服务会被禁用先发 10 03 试试能否响应第五确认时序参数没有越界。有些 ECU 对请求间隔敏感连续发送太快可能被判定为总线攻击而静默。5.2 CAN 时钟误差偏大导致的偶发错误帧故障现象台架上两节点通信正常整车总线挂满后偶发错误帧且跟温度强相关。用 CANoe 的 Error Frame 统计功能能看到错误帧集中在某些 ID 上。排查过程先用 CANoe 的 Bus Statistics 查看 bus load 和 error frame 计数然后用示波器测各节点实际发出的波特率与标称值对比。注意不能只看 ECU 的晶振规格还要看单片机的 PLL 配置。AMBA 总线的外设时钟分频因子经常被忽略配置不当会把最终 CAN 时钟偏到 1% 以上。解决办法有硬件上换精度的晶振软件上重新计算预分频器和 SJW并做高温老化测试全程抓错误帧确保误差在可控范围内。5.3 UDS 刷写失败的常见 NRC 语义速查表刷写失败时 ECU 返回的否定响应码NRC是定位问题的关键线索。我把常见的 NRC 整理成表新人完全可以对照着排查NRC含义常见原因与排查方向0x10一般拒绝请求不被接受检查服务是否在该会话被禁用0x11服务不支持该服务未实现检查 Bootloader 与应用层支持的服务列表0x12子功能不支持常见于 19 服务子功能号超出范围0x13报文长度或格式错误请求长度和规范不一致检查 DLC 和填充字节0x22条件不满足典型于 34 服务前置条件不满足比如没有进编程会话或未解锁0x24请求序列错误上位机跳过了某步骤比如没发 34 直接发 360x31请求超出范围地址或大小越界检查内存地址是否在可编程区域0x33安全访问被拒绝未解锁或密钥错误重新走 27 服务流程0x72通用编程失败烧写校验失败、Flash 操作出错查 Flash 驱动和文件数据还有一个高级点的排查技巧不要只盯着第一个 NRC很多 ECU 在上一条服务执行失败后后续服务都会返回条件不满足或一般拒绝。所以排查时先回到最前面的失败点逐条清理而不是改最后一个响应。5.4 总线高负载下的诊断超时问题整车运行时总线负载可能到 60% 以上诊断报文的优先级偏低响应延迟变大。遇到此类问题时我建议上位机超时时间不要固定写死 500 ms而是根据当前总线的负载情况动态调整。如果持续超时也不要盲目加大超时时间而是查一下是不是有其他工具在周期性占用诊断会话。笔者曾遇到一个项目中控娱乐系统启动后每 100 ms 轮询一次故障码导致诊断仪刷写时频繁收到 0x24 请求序列错误。最后和客户协调统一了诊断会话占用策略才把刷写流程稳定下来。这一点对做量产产线的朋友尤其重要同一台 ECU 同时被产线自动化工具和诊断仪连接两边会话冲突是高频故障。6. 从底层到上层的项目实践心得这个项目做下来我最深的一个体会是底层 CAN 通信和上层 UDS 诊断从来不是割裂的两个方向。很多测试人员用 CANoe 发送诊断请求时觉得“能通就行”一旦出现偶发失败便归咎于工具不稳定。实际上大部分偶发失败都能追溯到底层参数配置或者总线物理状态的劣化。反过来底层 CAN 波形抓得再漂亮如果对 UDS 的服务状态机不熟遇到 0x24、0x31 这类 NRC 照样一头雾水。真正的车载开发能力是把这两层串起来看遇到问题能从上到下、从下到上双向排查。具体到这个项目下一步值得扩展的方向有三个第一把 CAN 2.0 迁移到 CAN FD物理层位时序逻辑变了但 UDS 上层服务不需要大改只是单帧数据更长、刷写速率能明显提升第二移植到车载以太网 DoIP 做诊断上层应用逻辑基本沿用 UDS只是底层传输从 CAN 帧变成了 TCP/UDP 流地址和流控机制完全不同第三引入更自动化的诊断测试框架把 CAPL 脚本化检查点整合进 CI实现诊断回归自动化。这些方向都能用这套“底层通信 上层诊断”的双层思维去切入项目价值也能再上一个台阶。