
简介PCAN-UDS 是一套基于 PCAN 硬件接口实现 UDS统一诊断服务的完整开发包面向汽车电子工程师、嵌入式软件开发者以及 CAN 总线诊断入门者。它解决了在 Windows 环境下通过 PCAN 适配器连接车载 ECU、执行故障码读取、数据标定与内存读写等诊断任务的工程需求。压缩包共 31 个文件体积约 1.95MB包含 C/C、C#、VB、Pascal 等多语言头文件与源码示例以及 Win32/x64 双平台动态库和静态链接库还有英文版用户手册方便不同技术栈的开发者直接集成。已有 1147 人学习下载。通过学习这份资料可以掌握 UDS 协议在 PCAN 上的实现方式了解从 API 调用到具体服务执行的完整流程配套的 Samples 示例项目提供了客户端与服务端参考实现可快速移植到自己的诊断工具中节省底层协议调试时间。1. 从一条诊断指令到整车PCAN-UDS 到底解决了什么问题做车载诊断的工程师对这套组合拳不会陌生笔记本上跑着诊断工具USB 口插着一块 PCAN 适配器CAN 总线另一头连着 ECU。PCAN-UDS 这个名字看起来像一个专门的软件包其实它描述的是“用 PCAN 硬件作为 CAN 通道、按照 ISO 14229 标准跑 UDS 诊断服务”的完整工作方式。你可以在 CAPL 脚本里调用也可以在 Python 里通过 pcan 驱动收发报文再自己组 UDS 帧。标题里的 ZIP 后缀说明这通常是一个工具包或示例工程但真正值钱的不是压缩包本身而是它背后那套“请求-响应-超时-否定响应”的诊断交互模型。这里有个反直觉的事实UDS 诊断看起来只是发几帧 CAN 报文但 90% 的踩坑都发生在 CAN 层之下。波特率没对上、帧格式选错、ID 过滤配漏、物理寻址和功能寻址混用这些错误在报文层面根本不报错只会表现为 ECU 静默或者回 NRC。所以本文不打算给你讲某个现成工具怎么点按钮而是把 PCAN-UDS 从驱动到服务的完整链路拆开让你能徒手写脚本、手动拼报文、看懂 NRC并且在一台没有现成诊断仪的环境里也能把 UDS 服务跑通。对刚接触 UDS 诊断协议的嵌入式工程师以及对刷写流程只知其然不知其所以然的测试工程师这篇文章能帮你把碎片拼成一张完整的图。2. 搭起 PCAN-UDS 的最小可用环境驱动、固件与总线参数2.1 为什么选 PCAN 而不是其他 CAN 卡PCAN 在诊断开发中的角色基本等同于“老黄牛”。它不挑系统Windows 和 Linux 都有官方驱动它不挑工具链CAPL、Python、C、LabVIEW 都能通过统一的 API 访问更重要的是它的时间戳精度和报文缓冲策略在非实时操作系统上做得相对可靠这对分析诊断响应时间非常关键。相比之下有些 USB-CAN 卡在高速连续收发时容易丢帧而诊断服务恰恰对“发出去必须收到响应”有强依赖一帧丢失就可能让 ECU 进入异常状态。PCAN-UDS 这类脚本或工具包的核心价值是把 UDS 的会话层逻辑封装起来。你不需要手工计算 PCIProtocol Control Information字节不需要自己维护发送缓冲也不用为每个服务重新写一套超时重试。但封装之下的底层机制我们必须清楚否则遇到总线错误时根本不知道从哪排查。2.2 安装 pcan 驱动并验证通道在 Windows 上安装 PCAN 驱动后设备管理器里会出现“PCAN-PCI/PCIe/USB”系列设备。验证驱动是否正常工作的最快方法是用 PCAN-View它能直接看到总线上所有报文。命令行方式也支持用 pcaninfo 工具可以列出当前可用的通道、波特率和固件版本。# 列出所有 PCAN 通道及其配置 pcaninfo -v # 在 Linux 环境下加载驱动并查看内核日志 sudo modprobe pcan dmesg | grep -i pcan驱动安装完成只是第一步接下来必须在应用层把通道打开。以 Python 的 python-can 库为例子它通过 pcan 接口访问硬件下面的代码打开 USBBUS1 通道并发送一帧标准格式报文import can bus can.interface.Bus( interfacepcan, channelPCAN_USBBUS1, bitrate500000 ) msg can.Message( arbitration_id0x7DF, # 功能寻址OBD 广播 data[0x02, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], is_extended_idFalse ) bus.send(msg) print(fSent: {msg}) bus.shutdown()代码里有两个参数需要特别说明。channel必须写设备实际分配到的通道名Windows 下通常是 PCAN_USBBUS1 或 PCAN_USBBUS8取决于你插的物理端口和驱动分配顺序。bitrate必须与 ECU 所在网络的实际波特率一致常见的是 500 kbit/s 和 250 kbit/s错了表现为 ECU 完全无响应。发送前建议先用 PCAN-View 被动听总线报文确认总线上确实有数据流动再发主动请求。2.3 确认 CAN 帧格式和寻址方式UDS 运行在 CAN 层之上但 CAN 帧格式的选择直接影响诊断能否建立。绝大多数量产车型的诊断报文使用 11 位标准 ID但也有一小部分平台用 29 位扩展 ID。物理寻址Physical Addressing是一对一的请求 ID 和响应 ID 是固定的对应关系功能寻址Functional Addressing是一对多一个请求发到总线上所有支持该服务的 ECU 都会响应。PCAN-UDS 脚本里通常会有一个配置文件专门管理这些 ID。常见做法是把请求 ID 写在tx_id响应 ID 写在rx_id再配一个functional_tx_id做广播诊断。物理寻址时ECU 的响应 ID 是请求 ID 加 0x8例如请求 0x7E0响应 0x7E8。这个偏移规则在 ISO 15765-2 里有明确约定手动拼报文时非常有用。3. UDS 报文帧结构拆解PCI、SID 与 NRC 的对应关系3.1 单帧与多帧PCI 字节的规则UDS 走 ISO-TPISO 15765-2传输协议它把一长串诊断数据切成若干 CAN 帧。单帧SF最多承载 7 字节数据经典 CAN 标准帧格式下第一个字节的高四位是帧类型 0低四位是数据长度。多帧则分首帧FF、连续帧CF和流控帧FC首帧的第一个字节高四位固定为 1低四位和第二个字节组成 12 位总长度。这些看起来繁琐但 PCAN-UDS 脚本或你自己的封装库会自动处理你需要理解的只是在抓包分析时能识别一帧长响应被切成了几段。一个典型的多帧例子ECU 响应一个 19 服务读取故障码信息返回 20 字节数据。它在 CAN 总线上表现为首帧携带前 6 字节之后每帧连续帧携带 7 字节。每帧连续帧的 PCI 字节低四位是从 1 开始递增的序号用于接收方重组数据。你的 UDS 层代码需要按序号把数据拼回去丢弃重复帧处理丢帧后的超时。3.2 常用 SID 与 NRC 的对应关系UDS 服务分为六大类诊断会话控制、数据读取、数据写入、例程控制、上传下载和故障码相关。每个服务有唯一的 SID比如 0x10 是诊断会话控制0x22 按 DID 读数据0x2E 写数据0x31 例程控制0x34/0x36/0x37 配合做固件刷写0x19 和 0x14 负责故障码。NRCNegative Response Code是 ECU 说“不行”时的理由。最容易遇到的是 0x13请求长度错误或格式错误、0x22条件不满足、0x31请求超出范围、0x7F服务不支持。其中 0x22 和 0x31 经常被新手混淆0x22 表示当前状态下做不了这件事比如没进扩展会话就尝试写数据0x31 表示你给的值根本不在有效范围内比如 DID 不存在。遇到 NRC 时先确认当前会话、安全等级和前置条件不要急着怀疑总线问题。3.3 手动组装一帧 UDS 请求报文不依赖任何库用 Python 手动组一个 0x22 读数据请求有助于理解字节流的来龙去脉。假设要从 DID 0xF190 读取 VIN 码CAN 接收 ID 是 0x7E0import can # 0x22 ReadDataByIdentifierDID 为 0xF190两字节 # 单帧总长度 4 字节PCI SID DID_high DID_low payload [0x04, 0x22, 0xF1, 0x90] bus can.interface.Bus(interfacepcan, channelPCAN_USBBUS1, bitrate500000) msg can.Message( arbitration_id0x7E0, datapayload, is_extended_idFalse, is_fdFalse ) bus.send(msg) response bus.recv(timeout1.0) if response is None: print(Timeout: ECU did not respond) elif response.arbitration_id 0x7E8 and response.data[0] 0x06: # 0x7E8 是对应的物理响应 ID # data[0]0x06 表示单帧 6 字节有效data[1]0x62 是 0x22 的正响应 vin_bytes response.data[4:] print(VIN:, bytes(vin_bytes).decode(ascii)) elif response.data[0] 0x03 and response.data[2] 0x7F: # 3 字节的否定响应帧PCI 0x7F 请求SID NRC nrc response.data[3] print(fNRC: 0x{nrc:02X})请求帧里data[0] 0x04表示单帧且携带 4 字节有效数据包括 SID 本身。正响应的第一个字节是 0x06 加 0x62其中 0x62 是 0x22 的正响应 SID原 SID 0x40。否定响应的特征是第二个字节固定为 0x7F第三个字节是原请求 SID第四个字节才是 NRC。注意bus.recv(timeout)的取值——UDS 标准要求 ECU 在 50ms 内给出响应但考虑总线负载和调度延迟脚本里一般给 200ms 到 1s 比较稳妥。4. 动手跑通诊断服务会话切换、读写数据与例程控制4.1 诊断会话切换0x10 服务的正确姿势ECU 上电后通常处于默认会话Default Session只开放一部分服务。想读 VIN 可能默认会话就行但写数据、刷写固件、执行某些例程就必须先切到扩展会话或编程会话。0x10 服务的子功能包括 0x01 默认会话、0x02 编程会话、0x03 扩展会话有些 OEM 还有自定义子功能。用 PCAN-UDS 脚本切扩展会话的常见写法# 切换到扩展会话0x03 request [0x02, 0x10, 0x03] # 发送并等待响应 # 正响应应为 [0x03, 0x50, 0x03, P2_hi, P2_lo] # P2 是 ECU 在非默认会话下的响应时间参数单位是毫秒正响应里带的 P2 值值得留意。ECU 会用这两个字节告诉诊断仪“我后续服务的最大响应时间是多少”。比如返回 P2 0x00C8就是 200ms。如果你在扩展会话里发读写请求等待时间就要按这个值调整而不是用默认会话的 50ms。会话切换最常踩的坑是时间窗。很多 ECU 要求进入扩展会话后必须在规定时间内完成安全解锁否则退回默认会话。PCAN-UDS 脚本如果封了安全访问流程通常会处理这个时间窗手工发送时则要自己盯时间。4.2 安全访问0x27 服务的种子与密钥安全访问是写操作和刷写操作的前置门槛。0x27 服务通常包括两个子功能请求种子和发送密钥。常见流程是先发 0x27 0x01 请求种子ECU 返回 4 字节种子然后本地按算法计算密钥发 0x27 0x02 把密钥发给 ECU 验证。这里没有统一算法每个 OEM 甚至每个 ECU 的算法都不同种子也可能按时间变化。PCAN-UDS 的示例脚本里一般留一个security_algo函数接口你需要填入自己拿到的算法实现。下面是流程框架# Step 1: 请求种子 bus.send(can.Message(arbitration_id0x7E0, data[0x02, 0x27, 0x01])) # Step 2: 在响应里提取种子 # 假设响应数据为 [0x06, 0x67, 0x01, seed0, seed1, seed2, seed3] seed response.data[3:7] # Step 3: 执行算法生成密钥 key my_oem_security_algo(seed) # Step 4: 发送密钥 bus.send(can.Message(arbitration_id0x7E0, data[0x06, 0x27, 0x02] list(key)))安全访问失败的典型 NRC 是 0x35无效密钥和 0x36尝试次数超限。后者尤其危险连续失败多次后 ECU 会锁定安全访问功能一段时间有的长达 10 分钟。脚本里最好加一个计数器失败超过 3 次就自动停止并报警。4.3 读写数据的协议细节与 DID 表0x22 读数据和 0x2E 写数据都依赖 DIDData Identifier表。DID 是两字节的编号0xF190 通常是 VIN0xF187 可能是序列号0xF18C 可能是软件版本号。OEM 的规范文档里会列出全套 DID 表测试时需要交叉验证读到的值是否和实际硬件状态一致写的值在重启后是否还保留。读数据时响应里的数据长度是不确定的所以要按实际收到的帧解析。写数据的 0x2E 正响应比较简单回显 SID 加 0x40 即可不带数据。但有一个细节UDS 标准要求写数据的整个字节串必须在单个 CAN 帧内完成如果数据长度超过 7 字节经典 CAN就要走 ISO-TP 多帧。PCAN-UDS 脚本封装了 ISO-TP 后多帧对用户透明但抓包时会看到多个连续帧别误以为是多条诊断服务请求。4.4 用 0x31 例程控制跑通一个实际用例0x31 例程控制是最灵活的服务它的子功能包括 0x01 启动例程、0x02 停止例程、0x03 查询例程状态。0x31 的应用场景非常直观擦除 Flash、检查编程条件、计算校验和、复位某些外围器件。例程用两字节的 Routine Identifier 区分比如 0xFF00 可能是擦除 Flash0x0202 可能是检查编程电压。启动一个例程的完整 Python 示例# 请求启动例程 0xFF00假设是 Flash 擦除附带参数 0x0000 payload [0x05, 0x31, 0x01, 0xFF, 0x00, 0x00] bus.send(can.Message(arbitration_id0x7E0, datapayload)) # 正响应格式[0x06, 0x71, 0x01, 0xFF, 0x00, status_hi, status_lo] # 如果例程执行成功响应里会带结果状态如果失败会回 NRC执行例程时如果遇到 0x24例程执行超时或 0x31参数不合法先检查例程参数和当前会话、安全等级大多数情况下不是总线问题。例程执行期间总线上的周期性报文可能会暂时中断这是正常现象尤其是擦除 Flash 这类耗时操作ECU 忙于内部事务时可能停止发送应用报文。PCAN-UDS 脚本在这种场景下要给足超时时间不要按标准 P2 去等。5. 刷写场景下的 UDS 服务链34/36/37 与 31 服务的配合5.1 刷写流程的完整状态机固件刷写是 UDS 诊断协议里最复杂、也是 PCAN-UDS 这类工具最常见的应用场景。标准刷写流程是一个严格的线性状态机任何跳步都会触发 NRC。典型流程是10 02 进编程会话27 01/02 安全解锁31 01 FF00 擦除 Flash34 请求下载36 传输数据37 请求退出传输最后 31 01 FF01 校验并激活固件。每个状态都有前置条件。比如 34 服务RequestDownload必须在安全解锁且 Flash 已擦除的编程会话里才能发出36 服务的块序号必须从 1 开始递增不能跳号37 服务发出后就不能再发 36 了除非重新走 34 建立新的下载会话。PCAN-UDS 脚本做刷写时最宽松的封装也只是把这几个服务按顺序串起来但底层状态机必须由上层逻辑保证。5.2 34/36/37 服务的参数细节34 服务请求下载需要告诉 ECU你将要把数据写到哪个内存地址总长度是多少。地址和长度的编码格式由 0x34 服务的前两个字节指定常见的是[0x20, 0x00]表示地址 4 字节、长度 4 字节。例如要下载到地址 0x08010000长度 0x0001FF00# 34 服务参数SID 地址格式 长度格式 地址4字节 长度4字节 data [0x0E, 0x34, 0x20, 0x00, 0x08, 0x01, 0x00, 0x00, # address 0x08010000 0x00, 0x01, 0xFF, 0x00] # length 0x0001FF00 # 正响应会返回 maxNumberOfBlockLength每次 36 能带的最大字节数 # 以及一个 1 字节的 blockSequenceCounter 初始值通常是 1这里的data[0] 0x0E是单帧总数 14 字节符合 ISO-TP 单帧最多 7 字节的限制的话没问题。如果地址长度配置使整个请求超过 7 字节就需要走多帧。实际中 34 服务的地址和长度经常刚好卡在单帧边界附近所以很多 PCAN-UDS 封装会强制用 ISO-TP 发送所有 UDS 请求保证任何服务都能组帧。36 服务TransferData的核心是块序号和数据本身。块序号从 1 开始每发一帧递增。ECU 收到后正响应会回显当前块序号。如果收到 0x73错误块序号说明发送端和接收端的计数对不上了需要从 34 重新来。5.3 数据分块与流控不要一梭子发到底经典 CAN 标准帧一个报文最多带 7 字节 UDS 数据所以 36 服务的每次请求最多传输 7 字节。实际中 ECU 在 34 正响应里给的maxNumberOfBlockLength可能大于 7但那是给支持 CAN-FD 的链路的。经典 CAN 下每一次 36 请求都要等待 ECU 的正响应才能发下一次因为 ECU 内部 Flash 写入需要时间发送太快会触发 NRC 0x31 或直接丢帧。这里的经验值是收到上一个 36 的正响应后再发下一个。有些 PCAN-UDS 脚本会做流水线优化连续发几个 36 不等响应但前提是 ECU 的接收缓冲足够大且内部写入速度够快。量产刷写工具做得比较激进开发阶段调试建议保守一帧一确认慢但稳。整个固件文件几百 KB按每帧 7 字节算大约几万帧USB-CAN 卡在这种连续传输下长时间工作可能发热PCAN 相对稳定但也要注意总线错误计数器的增长。5.4 刷写后的校验与复位技巧37 服务RequestTransferExit发出后ECU 正响应里通常会带上校验信息比如编程后计算出的 CRC 值。这个值用于与电脑端计算的固件 CRC 做交叉验证。不要跳过这一步——很多刷写失败在早期没有症状直到跑完功能测试才发现固件跑飞了。最后一步通常是 31 服务启动一个检查例程Check Memory 或 Verify Application然后发 11 服务ECUReset复位 ECU。11 服务有子功能 0x01 硬复位、0x02 钥匙电复位、0x03 软复位。刷写完成后一般用 0x01 或 0x03复位后 ECU 会重新上电进入正常模式此时总线上的应用报文会重新出现。PCAN-UDS 脚本里可以监听应用报文的恢复状态来判断复位是否成功而不是只看复位响应的那一帧。一个容易忽略的细节是复位响应帧往往在路上就会断掉。ECU 执行复位动作太快诊断仪刚收到正响应总线就断开了。这种情况下脚本要容忍“发出复位请求后 ECU 不再响应但总线上其他报文开始恢复”的状态。抓包时看到复位响应缺失先别急着报故障观察一段时间如果应用报文陆续回来说明复位成功。5.5 刷写出错时的排查路径刷写过程中 NRC 的含义要比日常诊断复杂得多。0x72一般编程失败是刷写场景专用的表示 ECU 内部 Flash 编程操作失败比如扇区擦除超时、数据校验不匹配。遇到 0x72 基本可以判断是 ECU 端问题先查 Flash 驱动、电源稳定性再查刷写数据本身。如果 36 服务中途报 0x73则是块序号错乱检查脚本里的计数器是否有并发修改。如果 34 服务报 0x31先确认编程会话是否激活、安全是否解锁、地址和长度是否超出 Flash 地址空间——这三个条件挨个检查命中率极高。另外注意刷写期间的电源稳定性。刷写时 ECU 电流消耗明显上升尤其擦除和写入 Flash 的阶段电压跌落会直接导致刷写失败且 ECU 可能进入不可恢复的 boot 模式。PCAN-UDS 脚本如果配合可编程电源最好在刷写开始时记录电压值结束时再记录一次两个值偏差超过 0.5V 就要严肃对待。这个与协议无关但确实是刷写抓狂的头号元凶。本文还有配套的精品资源点击获取