SDO Expedited 传输详解
目录
概述
Expedited(快速)传输是 SDO 中最简单的数据传输方式,适用于 ≤ 4 字节 的数据。它的特点是:
- 一次请求 + 一次响应
- 数据直接嵌入 CAN 帧(字节 4-7)
- 无需分段和确认机制
适用场景:
- 读取设备类型(0x1000,4 字节)
- 读取错误寄存器(0x1001,1 字节)
- 写入配置参数(如心跳周期 0x1017,2 字节)
Expedited Upload 流程
1. 请求帧(Client -> Server)
COB-ID: 0x600 + node_id
DLC: 8
Byte 0: 0x40 | (n << 2) | 0x03
- Bit 7-5: CCS = 001 (Upload)
- Bit 4-2: n = 未传输字节数(0-3)
- Bit 1-0: s=1, e=1 (标记为 Expedited 且有数据)
Byte 1-2: Index(小端模式)
Byte 3: Sub-index
Byte 4-7: 0x00(填充)简化的请求字节 0:
对于 Expedited Upload,通常直接写 0x40,由服务端决定是否使用 Expedited 模式。
2. 响应帧(Server -> Client)
COB-ID: 0x580 + node_id
DLC: 8
Byte 0: 0x40 | (n << 2) | 0x03
- Bit 7-5: SCS = 011 (Upload Response)
- Bit 4-2: n = 未传输字节数
- Bit 1-0: s=1, e=1
Byte 1-2: Index(小端模式)
Byte 3: Sub-index
Byte 4-(7-n): 数据
Byte (8-n)-7: 0x00(填充)3. 逐字节解析示例
场景:读取节点 3 的设备类型(0x1000sub0)
请求:
candump vcan0
vcan0 603 [8] 40 00 10 00 00 00 00 00
│ │ │ │ │ │ └─ 填充字节 3
│ │ │ │ │ └──── 填充字节 2
│ │ │ │ └─────── 填充字节 1
│ │ │ └────────── 填充字节 0
│ │ └───────────── 子索引 0x00
│ └───────────────── 索引 0x1000(小端:00 10)
└────────────────────── CCS=001, Expedited 标记响应:
candump vcan0
vcan0 583 [8] 4B 00 10 00 92 01 01 00
│ │ │ │ │ │ │ └─ 数据字节 3 (0x00)
│ │ │ │ │ │ └──── 数据字节 2 (0x01)
│ │ │ │ │ └─────── 数据字节 1 (0x01)
│ │ │ │ └────────── 数据字节 0 (0x92)
│ │ │ └───────────── 子索引 0x00
│ │ └───────────────── 索引 0x1000
│ └───────────────────── SCS=011, n=00, s=1, e=1
└────────────────────────── COB-ID 0x583
解析字节 0: 0x4B = 0100 1011
Bit 7-5: 011 = SCS (Upload Response)
Bit 4-2: 00 = n (未传输字节数 = 0,即 4 字节数据)
Bit 1: 1 = s (有数据)
Bit 0: 1 = e (Expedited 模式)
数据: 0x00 0x01 0x01 0x92 (小端模式)
= 0x00010192 (大端读取值)
含义: 设备类型 = 0x00010192
- 0x92 = 146 (设备子类型)
- 0x01 = 运动控制设备
- 0x01 = 设备符合 CiA 402 行规
- 0x00 = 保留Expedited Download 流程
1. 请求帧(Client -> Server)
COB-ID: 0x600 + node_id
DLC: 8
Byte 0: 0x20 | (n << 1) | 0x03
- Bit 7-5: CCS = 000 (Download)
- Bit 4-2: n = 未传输字节数
- Bit 1-0: s=1, e=1
Byte 1-2: Index(小端模式)
Byte 3: Sub-index
Byte 4-(7-n): 数据
Byte (8-n)-7: 0x00(填充)2. 响应帧(Server -> Client)
COB-ID: 0x580 + node_id
DLC: 8
Byte 0: 0x60
- Bit 7-5: SCS = 010 (Download Response)
Byte 1-2: Index(小端模式)
Byte 3: Sub-index
Byte 4-7: 0x00(填充)3. 逐字节解析示例
场景:写入节点 3 的心跳周期(0x1017sub0)为 1000ms
请求:
cansend vcan0 603#2B171000E8030000
vcan0 603 [8] 2B 17 10 00 E8 03 00 00
│ │ │ │ │ │ └─ 填充字节 1
│ │ │ │ │ └──── 填充字节 0
│ │ │ │ └───────── 数据字节 1 (0x03)
│ │ │ └──────────── 数据字节 0 (0xE8)
│ │ └──────────────── 子索引 0x00
│ └──────────────────── 索引 0x1017
└───────────────────────── CCS=000, n=01 (2字节未传输), s=1, e=1
解析字节 0: 0x2B = 0010 1011
Bit 7-5: 000 = CCS (Download)
Bit 4-2: 010 = n = 2 (未传输字节数 = 2,即实际传输 2 字节)
Bit 1-0: 11 = s=1, e=1 (Expedited)
数据: 0xE8 0x03 (小端模式)
= 0x03E8 = 1000 (十进制)响应:
vcan0 583 [8] 60 17 10 00 00 00 00 00
│ │ │ │ └─ 填充
│ │ │ └───── 子索引 0x00
│ │ └───────── 索引 0x1017
│ └───────────── SCS=010 (Download Response)
└───────────────── COB-ID 0x583命令行实操
前置条件
# 创建虚拟 CAN 设备
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
# 启动监听(另一个终端)
candump vcan0读取设备类型(0x1000sub0)
# 发送 Expedited Upload 请求
cansend vcan0 603#4000100000000000
# 预期响应
# vcan0 583 [8] 4B 00 10 00 92 01 01 00读取错误寄存器(0x1001sub0)
# 1 字节数据
cansend vcan0 603#4001100000000000
# 预期响应
# vcan0 583 [8] 4F 01 10 00 00 00 00 00
# 0x4F = 0100 1111: SCS=011, n=11 (3字节未传输), s=1, e=1
# 数据: 0x00 (无错误)写入心跳周期(0x1017sub0)
# 2 字节数据: 1000ms = 0x03E8
cansend vcan0 603#2B171000E8030000
# 预期响应
# vcan0 583 [8] 60 17 10 00 00 00 00 00读取制造商设备名称(0x1008sub0)
这是一个 VISIBLE_STRING 类型,长度 > 4 字节,不能使用 Expedited 传输。必须使用 Segmented 传输。
代码实现
Expedited Upload(客户端)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int sdo_upload_expedited(int sock, const char *ifname, uint8_t node_id,
uint16_t index, uint8_t sub_index,
uint8_t *data, size_t *data_len) {
struct can_frame req, resp;
struct ifreq ifr;
struct sockaddr_can addr;
canid_t req_cobid = 0x600 + node_id;
canid_t resp_cobid = 0x580 + node_id;
/* 创建 CAN socket 并绑定(省略,见 can_socket.h) */
/* 构建请求帧 */
memset(&req, 0, sizeof(req));
req.can_id = req_cobid;
req.can_dlc = 8;
req.data[0] = 0x40; /* CCS=1 (Upload) */
req.data[1] = index & 0xFF; /* 索引低字节 */
req.data[2] = (index >> 8) & 0xFF; /* 索引高字节 */
req.data[3] = sub_index;
/* 发送请求 */
write(sock, &req, sizeof(req));
/* 接收响应 */
read(sock, &resp, sizeof(resp));
/* 验证响应 */
if (resp.can_id != resp_cobid) {
fprintf(stderr, "Wrong COB-ID: expected 0x%03X, got 0x%03X\n",
resp_cobid, resp.can_id);
return -1;
}
if (resp.data[0] == 0x80) {
/* Abort 响应 */
uint32_t abort_code = resp.data[4] | (resp.data[5] << 8) |
(resp.data[6] << 16) | (resp.data[7] << 24);
fprintf(stderr, "SDO Abort: 0x%08X\n", abort_code);
return -1;
}
/* 解析 Expedited 响应 */
if ((resp.data[0] & 0x03) == 0x03) { /* e=1, s=1 */
int n = (resp.data[0] >> 2) & 0x07; /* 未传输字节数 */
*data_len = 4 - n;
memcpy(data, &resp.data[4], *data_len);
}
return 0;
}Expedited Download(客户端)
int sdo_download_expedited(int sock, const char *ifname, uint8_t node_id,
uint16_t index, uint8_t sub_index,
const uint8_t *data, size_t data_len) {
struct can_frame req, resp;
int n = 4 - data_len; /* 未传输字节数 */
/* 构建请求帧 */
memset(&req, 0, sizeof(req));
req.can_id = 0x600 + node_id;
req.can_dlc = 8;
req.data[0] = 0x20 | (n << 1) | 0x03; /* CCS=0, Expedited, n */
req.data[1] = index & 0xFF;
req.data[2] = (index >> 8) & 0xFF;
req.data[3] = sub_index;
memcpy(&req.data[4], data, data_len);
/* 发送请求 */
write(sock, &req, sizeof(req));
/* 接收响应 */
read(sock, &resp, sizeof(resp));
/* 验证响应 */
if (resp.data[0] != 0x60) {
fprintf(stderr, "SDO Download failed, cmd=0x%02X\n", resp.data[0]);
return -1;
}
return 0;
}常见问题
Q: Expedited 传输的字节 0 中 n 位如何计算?
n 表示 未传输的字节数(即填充字节数)。对于 4 字节数据,n=0;对于 1 字节数据,n=3。
| 实际数据长度 | n 值 | 字节 0(Upload) | 字节 0(Download) |
|---|---|---|---|
| 1 字节 | 3 | 0x4F | 0x2F |
| 2 字节 | 2 | 0x4B | 0x2B |
| 3 字节 | 1 | 0x47 | 0x27 |
| 4 字节 | 0 | 0x43 | 0x23 |
Q: 服务端可以不使用 Expedited 模式吗?
是的。即使客户端发送 Expedited Upload 请求(0x40),服务端也可以选择使用 Segmented 模式响应。这通常发生在数据类型 > 4 字节时。
Q: 为什么小端模式很重要?
CANOpen 协议规定多字节数据使用小端模式传输。如果解析时使用大端模式,数据会被错误解读。例如 0x03E8(1000ms)在小端模式下传输为 E8 03,如果按大端读取会得到 0xE803(59395)。
总结
| 维度 | 说明 |
|---|---|
| 数据量 | ≤ 4 字节 |
| 通信 | 1 请求 + 1 响应 |
| 请求命令 | Upload=0x40, Download=0x20|n|s|e |
| 响应命令 | Upload=0x4B|n|s|e, Download=0x60 |
| Abort | 0x80 + 4 字节错误码 |
| 数据编码 | 小端模式 |
如果这篇文章对你有帮助,欢迎请我喝杯咖啡 ☕
支付宝
微信