4. XCP协议入门

XCP 协议的核心,说白了就是主从设备之间来回倒腾数据包。你想想看,标定工程师通过上位机发一条指令,ECU 得能听懂,还得能回话。这个“听懂”和“回话”靠的就是三种数据包:CMD/CRM(命令/响应)、DAQ(数据采集)和 STIM(数据刺激)。

我刚开始接触 XCP 时,总觉得这些包格式很抽象。后来在台架上调一个发动机扭矩模型,上位机死活收不到标定值,查了两天才发现是 CMD 包的 PID 字段写错了。嗯,从那以后,我对每个字节都不敢马虎。

4.1 数据包通用头部

不管哪种包,XCP 都有一套统一的头部结构。就像快递单一样,收件人、寄件人、包裹类型都得写清楚。

字节偏移 字段名 长度(字节) 说明
0 PID 1 包标识符,区分 CMD/CRM/DAQ/STIM
1 填充/保留 0-1 对齐用,某些传输层需要
2+ 载荷 可变 具体命令或数据内容

这个 PID 是关键。它告诉接收方:“我这是个啥包”。比如 0xFF 是 CONNECT 命令,0xFE 是 DISCONNECT。我习惯把 PID 表打印出来贴在工位上,查起来快。

4.2 CMD/CRM 包:一问一答

CMD 包是主设备(上位机)发给从设备(ECU)的。CRM 包是从设备回给主设备的。它们俩成对出现,就像你问“今天天气咋样?”,对方回“还行,25度”。

4.2.1 CMD 包结构

+--------+--------+------------------+
|  PID   | 保留   |    参数/数据     |
+--------+--------+------------------+
  1 byte   0-1 byte   0-8 bytes
  • PID:命令标识符,比如 0xF0 表示 SHORT_UPLOAD(短上传)。
  • 保留:有些传输层要求对齐到 4 字节,这里就填 0。
  • 参数/数据:比如上传的地址、长度等。

举个例子,我想从 ECU 的 0x8000 地址读 4 个字节,CMD 包就是:

PID = 0xF0 (SHORT_UPLOAD)
保留 = 0x00
参数 = 0x00 0x80 0x00 0x04  // 地址高字节在前,长度在后
我的习惯:写代码构造 CMD 包时,先把 PID 写死,再填充参数。参数顺序一定要看协议文档,不同命令的排列方式不一样,踩过坑才知道疼。

4.2.2 CRM 包结构

+--------+--------+------------------+
|  PID   | 保留   |    响应数据      |
+--------+--------+------------------+
  1 byte   0-1 byte   0-8 bytes

CRM 包的 PID 和 CMD 的 PID 有对应关系。比如 CMD 是 0xF0,CRM 就是 0xFF(成功)或 0xFE(错误)。

响应数据里放的就是你要读的值。上面那个例子,ECU 会回:

PID = 0xFF (成功)
保留 = 0x00
数据 = 0x12 0x34 0x56 0x78  // 从 0x8000 读到的 4 个字节
注意:如果 ECU 返回 0xFE,后面会跟一个错误码。我曾经遇到过 ECU 返回 0xFE 0x10,查手册才知道是“命令不支持”。别慌,对照协议文档逐条排查。

4.3 DAQ 包:数据主动上报

DAQ 包和 CMD/CRM 不一样。它不是一问一答,而是 ECU 按照设定好的周期,主动往上位机“推”数据。这玩意儿在标定和测量时特别有用——你不需要一直发命令去读,ECU 自己会定时发。

4.3.1 DAQ 包结构

+--------+--------+------------------+------------------+
|  PID   | 保留   |   DAQ列表号      |   数据记录       |
+--------+--------+------------------+------------------+
  1 byte   0-1 byte     1 byte           N bytes
  • PID:固定为 0x00(DAQ 包标识)。
  • DAQ 列表号:标识这是哪个 DAQ 列表的数据。每个列表可以配置不同的采集周期和信号。
  • 数据记录:按照预先配置好的格式,把多个信号的值打包在一起。

我举个例子。假设你配置了 DAQ 列表 0,里面包含发动机转速和冷却液温度。ECU 每 10ms 发一次 DAQ 包:

PID = 0x00
保留 = 0x00
DAQ列表号 = 0x00
数据 = 0x0A 0x00 0x1E  // 转速 0x0A00(2560 rpm),温度 0x1E(30°C)
关键点:DAQ 包的数据格式是在标定阶段通过 GET_DAQ_LIST_INFO 等命令协商好的。上位机必须知道每个 DAQ 列表里信号的排列顺序和数据类型,才能正确解析。

4.2.2 DAQ 的两种模式

模式 说明 适用场景
周期模式 ECU 按固定时间间隔发送 DAQ 包 连续测量,比如监控发动机运行状态
事件模式 ECU 在特定事件触发时发送 DAQ 包 故障诊断,比如检测到爆震时上报数据

我个人更喜欢周期模式,简单可靠。事件模式虽然灵活,但调试时容易漏包,你想想看,如果事件触发条件没满足,数据就永远发不出来。

4.4 STIM 包:数据注入

STIM 包和 DAQ 包正好相反。DAQ 是 ECU 往外发数据,STIM 是上位机往 ECU 里写数据。这玩意儿在标定参数调整时特别有用——你可以实时修改 ECU 内部的标定量,比如喷油脉宽、点火提前角。

4.4.1 STIM 包结构

+--------+--------+------------------+------------------+
|  PID   | 保留   |   STIM列表号     |   数据记录       |
+--------+--------+------------------+------------------+
  1 byte   0-1 byte     1 byte           N bytes
  • PID:固定为 0x01(STIM 包标识)。
  • STIM 列表号:标识这是哪个 STIM 列表的数据。
  • 数据记录:要写入 ECU 的信号值。

比如你想把喷油脉宽从 2.5ms 改成 3.0ms,STIM 包就是:

PID = 0x01
保留 = 0x00
STIM列表号 = 0x00
数据 = 0x0A 0x00  // 假设喷油脉宽用 0.1ms 为单位,3.0ms = 30 = 0x001E
避坑指南:我曾经在标定一个柴油机 ECU 时,用 STIM 包修改了轨压标定量,结果没注意数据范围,直接写了个超限值。ECU 立马进入保护模式,发动机熄火。后来我养成了一个习惯:写 STIM 包之前,先读一遍当前值,确认范围再写。

4.5 三种数据包的关系

为了让你更直观地理解这三种包怎么配合工作,我画了一张图:

XCP 三种数据包交互关系 主设备(上位机) 从设备(ECU) CMD 包(命令) CRM 包(响应) DAQ 包(数据上报) 周期/事件触发,主动推送 STIM 包(数据注入) 上位机主动写入标定量 说明: • CMD/CRM:一问一答,适合配置、读取、写入操作 • DAQ:ECU 主动上报,适合连续测量和监控 • STIM:上位机主动注入,适合实时标定参数调整

4.6 数据包长度与对齐

XCP 协议对数据包长度有要求。不同的传输层(CAN、以太网、SPI)支持的包长度不一样。比如 CAN 传输层,一帧最多 8 字节数据;以太网传输层可以到 64KB 甚至更大。

传输层 最大包长(字节) 对齐要求
CAN 8 无
CAN FD 64 4 字节对齐
以太网(TCP/UDP) 65535 4 字节对齐
SPI 可变 2 字节对齐

我建议你在设计 DAQ 列表时,尽量让数据长度对齐到 4 字节。这样 ECU 处理起来快,上位机解析也方便。如果信号长度不对齐,比如一个 3 字节的信号,后面就得补 1 字节的填充。

小技巧:用 CAN 传输层时,如果 DAQ 包超过 8 字节,XCP 会自动拆包。但拆包会增加 ECU 的 CPU 负载。我一般把 DAQ 列表里的信号控制在 6-7 字节以内,留 1-2 字节给头部,这样一帧 CAN 就能搞定。

4.7 常见问题与排查思路

在实际项目中,数据包解析出问题是最常见的。我总结了几条排查思路:

  1. PID 对不上:检查上位机和 ECU 的协议版本是否一致。XCP 1.0 和 XCP 1.1 的 PID 定义有差异。
  2. 长度不匹配:用逻辑分析仪抓包,看实际收到的字节数和预期是否一致。我遇到过 ECU 多发了 2 字节填充,导致解析错位。
  3. DAQ 包没收到:先确认 DAQ 列表是否已经启动(用 GET_DAQ_CLOCK 命令检查)。再确认 ECU 的采集周期是否配置正确。
  4. STIM 包写不进去:检查 STIM 列表的访问权限。有些标定量是只读的,需要先解锁安全模式。

嗯,数据包格式这块,说白了就是“协议规定怎么写,你就怎么写”。但实际调试时,多留个心眼,多抓包看看,比什么都管用。