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,问题就解决了。所以,超时时间一定要大于服务器最坏情况下的处理时间。
超时后的处理策略,我一般建议这样:
- 第一次超时:重发一次请求。可能是总线冲突导致丢包。
- 第二次超时:再重发一次。如果还是超时,基本可以确定通信链路有问题。
- 第三次超时:停止重发,上报“SDO通信超时”错误,并记录日志。
为什么是三次?因为CAN总线上的错误通常是瞬时的,重发两三次大概率能成功。如果三次都失败,那肯定是硬件或配置问题了,再重发也没意义。
我的经验:在实际调试中,我习惯在从站固件里加一个“SDO超时计数器”。如果连续出现多次超时,我会让从站主动进入“预操作状态”或“停止状态”,并点亮一个错误指示灯。这样现场工程师一眼就能看出哪个节点通信有问题,不用拿着CAN分析仪一根根线去测。
嗯,SDO的请求与响应机制,说白了就是“一问一答,有来有回”。客户端问得清楚,服务器答得明白,再加上一个“等不到就重试”的保底策略,这套机制在工业现场跑得非常稳。你只要记住报文格式里的命令字、索引、子索引、数据这四个要素,再配上一个合理的超时时间,SDO通信就不会出大乱子。