4. UDS会话层与寻址方式

在UDS诊断节点嵌入式开发中,应用层服务(如0x10会话控制、0x22读数据、0x2E写数据)本身并不直接操作CAN总线。真正把诊断请求和响应“搬”到总线上的,是会话层与传输层。理解物理寻址、功能寻址,以及ISO-TP的单帧、多帧、流控帧机制,是写出稳定诊断协议栈的前提。

一、核心原理:寻址方式决定“谁在听”

UDS over CAN使用29位扩展帧时,通常用地址信息区分诊断对象。两种最基础的寻址方式如下:

  • 物理寻址(Physical Addressing):诊断仪针对某一个具体ECU发送请求,只有该ECU会响应。请求ID和响应ID通常成对出现,例如请求0x18DA10F1,响应0x18DAF110。它用于读取故障码、刷写、标定等点对点操作。
  • 功能寻址(Functional Addressing):诊断仪向总线上所有ECU广播请求,例如ID 0x18DB33F1。多个ECU可以同时收到并各自决定是否响应。它常用于OBD排放相关读取、统一进入默认会话、统一清除故障码等场景。

功能寻址的响应仍然是物理寻址,即每个ECU用自己的响应ID回复,因此总线上可能出现多个响应帧。开发中必须处理“多响应竞争”和“响应超时”问题,不能假设功能寻址只有一个回复。

关键区别:物理寻址是“一对一”,功能寻址是“一对多”。功能寻址请求通常不要求所有ECU都回复,但支持的功能必须按规范响应。

二、ISO-TP传输机制:单帧、多帧与流控帧

CAN一帧经典数据最多8字节,而UDS请求或响应经常超过8字节,因此需要ISO 15765-2(ISO-TP)做分段与重组。ISO-TP定义了四种帧类型:

帧类型 PCI高4位 作用 典型场景
单帧 SF 0x0 一帧承载完整诊断数据 短请求、短响应,≤7字节
首帧 FF 0x1 声明总长度并携带第一段数据 长响应开始,如读取VIN
连续帧 CF 0x2 按序携带后续数据段 长数据分段传输
流控帧 FC 0x3 接收方控制发送节奏 允许继续、等待或溢出

单帧的PCI第一个字节为0x0N,其中N表示数据长度(1~7)。例如请求“02 10 03”表示进入扩展会话,长度2,服务0x10,子功能0x03。多帧传输时,发送方先发首帧,接收方回送流控帧,发送方再按流控参数发送连续帧。

三、方法流程:一次多帧响应的完整时序

以诊断仪读取VIN(服务0x22,DID 0xF190)为例,ECU返回的VIN有17字节,加上服务响应头,必然超过7字节,因此走多帧流程:

  1. 诊断仪发送物理寻址请求:03 22 F1 90(单帧)。
  2. ECU回复首帧:10 14 62 F1 90 ...,其中0x14表示总长度20字节。
  3. 诊断仪回复流控帧:30 00 00,表示允许发送、块大小0、最小间隔0。
  4. ECU连续发送连续帧:21 ...、22 ...,直到数据发完。
  5. 诊断仪重组所有段,得到完整响应62 F1 90 + VIN。

流控帧的三个关键参数是:FS(流状态)、BS(块大小)、STmin(最小间隔)。FS=0表示继续发送,FS=1表示等待,FS=2表示溢出。BS=0表示不再需要流控,可以连续发完;STmin则约束连续帧之间的最小时间间隔,嵌入式发送端必须遵守,否则接收方可能丢帧。

// 典型ISO-TP接收状态机伪代码
switch (pci_type) {
  case SINGLE_FRAME:
    handle_single_frame(data, len);
    break;
  case FIRST_FRAME:
    total_len = get_total_len(data);
    send_flow_control(FS_CTS, BS, STmin);
    state = WAIT_CF;
    break;
  case CONSECUTIVE_FRAME:
    if (seq_num == expected_seq) {
      append_data(data);
      expected_seq++;
    }
    if (received_len >= total_len) {
      deliver_to_uds();
      state = IDLE;
    }
    break;
  case FLOW_CONTROL:
    apply_fc_params(data);
    start_sending_cf();
    break;
}

四、案例与场景:功能寻址下的多响应处理

假设整车有发动机ECU、变速箱ECU、网关三个节点。诊断仪用功能寻址发送02 10 03请求进入扩展会话。三个ECU都可能回复06 50 03 00 32 01 F4。此时诊断仪不能只等一个响应就结束,而应设置一个收集窗口,例如100ms内收集所有物理响应,再分别处理。

嵌入式节点侧则要注意:如果本节点不支持该功能,可以选择不响应;如果支持,必须按规范在P2时间内回复。功能寻址请求通常不要求流控,因为请求本身很短;但功能寻址的响应如果很长,仍然可能触发多帧,此时每个ECU各自与诊断仪进行流控交互,总线上会出现多组并行的ISO-TP会话,对诊断仪的缓冲和调度能力要求较高。

实操建议:在CANoe或CANalyzer中观察功能寻址请求时,先过滤功能ID,再按响应ID分组统计。不要用“收到第一个响应就停止”的逻辑,否则会漏掉其他ECU的回复。

五、常见误区与复盘

  • 误区一:把功能寻址当物理寻址用。功能寻址广播后,多个ECU同时响应,若诊断仪只处理一个,会误判其他节点无响应。
  • 误区二:忽略STmin。连续帧发送过快,接收方来不及处理,表现为随机丢帧或重组失败。嵌入式发送端应在CF之间插入延时。
  • 误区三:流控帧BS=0理解错误。BS=0不是“不发送”,而是“不需要再等流控”,发送方可以连续发送剩余所有连续帧。
  • 误区四:单帧长度字段算错。单帧PCI中的长度只表示后续数据字节数,不包含PCI本身。写错会导致接收方解析越界。
  • 误区五:多帧超时未处理。ISO-TP有N_As、N_Bs、N_Cr等超时参数,嵌入式接收端若不在规定时间内收到下一帧,应终止重组并释放缓冲。

复盘时建议抓取完整CAN日志,按“请求ID → 首帧 → 流控 → 连续帧 → 响应ID”的顺序还原一次诊断交互。重点检查:寻址方式是否正确、PCI类型是否匹配、序列号是否连续、流控参数是否被遵守、总长度是否一致。只有这些环节全部对齐,UDS应用层服务才能稳定运行。

UDS over CAN:寻址方式与ISO-TP多帧传输时序 诊断仪 Tester 目标ECU Node CAN Bus 物理寻址:请求 0x18DA10F1 → 响应 0x18DAF110(一对一) 功能寻址:请求 0x18DB33F1 → 多ECU各自物理响应(一对多) ISO-TP 多帧响应时序(以读取VIN为例) ① 单帧请求:03 22 F1 90 ② 首帧 FF:10 14 62 F1 90 ... ③ 流控帧 FC:30 00 00(CTS, BS=0, STmin=0) ④ 连续帧 CF:21 ... 22 ... 23 ... ⑤ 重组完成:62 F1 90 + VIN(20字节) Tester ECU Tester ECU → ECU → Tester → ECU → Tester