3、SDO请求与响应机制:客户端/服务器模型、SDO请求报文格式、SDO响应报文格式、SDO超时机制

好,咱们今天聊聊SDO通信的核心——请求与响应机制。说白了,SDO就是CANopen网络里的“快递员”,专门负责把大件数据(比如配置参数、设备描述)从A点搬到B点。但怎么搬?谁来叫门?谁开门?搬的过程中出问题了怎么办?这就是咱们这节课要解决的问题。

3.1 客户端/服务器模型:谁主动,谁被动?

SDO通信用的是典型的客户端/服务器模型。我习惯这么记:客户端是“伸手党”,服务器是“资料库”。

  • 客户端(Client):通常是主站,或者任何发起SDO请求的节点。它负责发号施令:“喂,把对象字典里索引0x1017、子索引0x01的值给我读出来!”或者“我要把0x1234这个值写到你索引0x2000里去!”
  • 服务器(Server):通常是从站,负责响应请求。它收到命令后,去自己的对象字典里查数据,然后打包回复:“你要的数据是0x5678,拿好!”或者“写成功了,确认!”

关键点:一个SDO连接里,客户端和服务器是严格一一对应的。一个SDO请求,必然对应一个SDO响应。不存在“广播式”的SDO,这一点和PDO完全不同。

我在项目里遇到过一种情况:有人把SDO当PDO用,想用一个请求同时配置多个从站。结果呢?只有一个从站响应了,其他从站根本没理他。为什么?因为SDO的寻址是基于点对点的,你发出去的CAN-ID只对应一个特定的从站。你想想看,如果快递员把包裹往小区门口一扔,喊一嗓子“大家自己来拿”,那不乱套了吗?

3.2 SDO请求报文格式:客户端在说什么?

SDO请求报文,就是客户端发给服务器的“命令”。它固定占用8个字节,结构非常清晰。我把它拆成三部分来看:命令字、索引与子索引、数据。

字节偏移 内容 说明
Byte 0 命令字(CCS) 高4位表示命令类型,低4位是扩展位
Byte 1-2 索引(Index) 对象字典的索引,小端格式(低字节在前)
Byte 3 子索引(SubIndex) 对象字典的子索引
Byte 4-7 数据(Data) 要写入的数据,或上传请求时的占位符(通常为0)

命令字(Byte 0)的高4位决定了这次请求是“读”还是“写”,以及数据长度。常见的几种命令字:

  • 0x40:上传请求(读)。客户端说:“把数据给我!”
  • 0x21:下载请求(写),1字节数据。客户端说:“给你1个字节,存好!”
  • 0x22:下载请求,2字节数据。
  • 0x23:下载请求,4字节数据。
  • 0x2B:下载请求,3字节数据(不常用,但存在)。

我的小技巧:调试时,我习惯先看Byte 0的高4位。如果是0x4,那就是读请求;如果是0x2,那就是写请求。然后看Byte 4-7的数据长度,就能判断是几字节的写操作。这样一眼就能看出客户端想干嘛。

举个例子,我想把从站对象字典0x6000子索引0x01的值读出来。请求报文应该是:

CAN-ID: 0x600 + 节点ID (假设节点ID=1,则CAN-ID=0x601)
数据: 40 00 60 01 00 00 00 00
      ↑  ↑  ↑  ↑  ↑  ↑  ↑  ↑
      |  |  |  |  数据区(占位)
      |  |  |  子索引=0x01
      |  索引=0x6000 (小端: 00 60)
      命令字=0x40 (上传请求)

3.3 SDO响应报文格式:服务器在回答什么?

服务器收到请求后,必须给出响应。响应报文也是8个字节,结构类似,但命令字的含义不同。

字节偏移 内容 说明
Byte 0 命令字(SCS) 高4位表示响应类型,低4位是扩展位
Byte 1-2 索引(Index) 与请求报文中的索引一致
Byte 3 子索引(SubIndex) 与请求报文中的子索引一致
Byte 4-7 数据(Data) 上传响应时存放数据;下载响应时存放保留位或0

常见的响应命令字:

  • 0x40:上传响应(读成功)。服务器说:“数据给你,接着!”
  • 0x60:下载响应(写成功)。服务器说:“收到了,存好了!”
  • 0x80:中止响应(出错)。服务器说:“不行,出错了!”(后面会讲中止码)

接着上面的读请求例子,服务器应该回复:

CAN-ID: 0x580 + 节点ID (节点ID=1,则CAN-ID=0x581)
数据: 40 00 60 01 12 34 56 78
      ↑  ↑  ↑  ↑  ↑  ↑  ↑  ↑
      |  |  |  |  数据=0x12345678 (假设读出的值)
      |  |  |  子索引=0x01
      |  索引=0x6000
      命令字=0x40 (上传响应)

注意:如果服务器回复的是0x80(中止响应),那么Byte 4-7里存放的是一个4字节的“中止码”,用来告诉你为什么失败了。比如0x05040000表示“对象字典中不存在该对象”,0x06010000表示“试图读取一个只写对象”。我曾经被这个中止码坑过一次,调试了半天,最后发现是索引写错了。

3.4 SDO超时机制:等不到回复怎么办?

CAN总线不是百分百可靠的。有时候报文会丢失,或者从站正忙没来得及处理。这时候客户端不能一直傻等。SDO协议定义了一套超时机制,我称之为“耐心有限原则”。

具体来说:

  • 客户端超时:客户端发出请求后,启动一个定时器。如果在规定时间内(通常是1秒,但可配置)没收到响应,客户端认为这次通信失败。它会怎么做?重发请求,或者上报错误。
  • 服务器超时:服务器收到请求后,如果处理时间很长(比如写EEPROM),它可以在处理期间发送一个“分段响应”或“协议加速”的报文,告诉客户端“别急,我在处理”。如果服务器一直不吭声,客户端超时后就会重发。

避坑指南:我曾经在一个项目里,从站写EEPROM需要200ms,但客户端超时设的是100ms。结果客户端疯狂重发请求,从站每次收到新请求就重新开始写,永远写不完。后来我把客户端超时改成500ms,问题就解决了。所以,超时时间一定要大于服务器最坏情况下的处理时间。

超时后的处理策略,我一般建议这样:

  1. 第一次超时:重发一次请求。可能是总线冲突导致丢包。
  2. 第二次超时:再重发一次。如果还是超时,基本可以确定通信链路有问题。
  3. 第三次超时:停止重发,上报“SDO通信超时”错误,并记录日志。

为什么是三次?因为CAN总线上的错误通常是瞬时的,重发两三次大概率能成功。如果三次都失败,那肯定是硬件或配置问题了,再重发也没意义。

SDO请求与响应流程(含超时机制) 客户端(主站) 服务器(从站) ① SDO请求报文 ② SDO响应报文 等待响应 超时时间:T (默认1秒) 重发决策 重发次数 < 3? 是,重发请求 上报超时错误

我的经验:在实际调试中,我习惯在从站固件里加一个“SDO超时计数器”。如果连续出现多次超时,我会让从站主动进入“预操作状态”或“停止状态”,并点亮一个错误指示灯。这样现场工程师一眼就能看出哪个节点通信有问题,不用拿着CAN分析仪一根根线去测。

嗯,SDO的请求与响应机制,说白了就是“一问一答,有来有回”。客户端问得清楚,服务器答得明白,再加上一个“等不到就重试”的保底策略,这套机制在工业现场跑得非常稳。你只要记住报文格式里的命令字、索引、子索引、数据这四个要素,再配上一个合理的超时时间,SDO通信就不会出大乱子。