SoE (Servo over EtherCAT)
SoE 协议将 SERCOS 伺服通信协议封装在 EtherCAT 邮箱中,适用于 SERCOS 兼容的伺服驱动器。
通过 slave.SoE() 访问。
大多数 EtherCAT 伺服驱动器使用 CoE + CiA 402 协议栈。 SoE 仅用于 SERCOS 兼容 的驱动器(如 Bosch Rexroth IndraDrive 系列)。 两者不能混用——从站支持哪种取决于硬件。
SERCOS IDN 体系
SoE 通过 IDN(Identification Number) 寻址驱动器参数。每个 IDN 是一个 16 位编号,代表一个参数(位置、速度、控制字等)。
IDN 命名规则:
| 前缀 | 范围 | 说明 |
|---|---|---|
| S-x-xxxx | 0x0000 – 0x7FFF | SERCOS 标准参数(所有厂商通用) |
| P-x-xxxx | 0x8000 – 0xBFFF | 产品特定参数 |
| — | 0xC000 – 0xFFFF | 厂商自定义参数 |
常用标准 IDN(伺服控制相关):
| IDN | SERCOS 名 | 说明 | 数据类型 |
|---|---|---|---|
| S-0-0001 (0x0001) | Cycle Time | 通信周期时间 | uint, us |
| S-0-0011 (0x000B) | Position Feedback 1 | 实际位置 | int, inc |
| S-0-0012 (0x000C) | Position Command | 目标位置 | int, inc |
| S-0-0013 (0x000D) | Velocity Feedback | 实际速度 | int, rpm |
| S-0-0014 (0x000E) | Velocity Command | 目标速度 | int, rpm |
| S-0-0016 (0x0010) | AT Config | 输入映射配置列表 | list |
| S-0-0019 (0x0013) | Drive Status | 驱动器状态字 | ushort |
| S-0-0024 (0x0018) | MDT Config | 输出映射配置列表 | list |
| S-0-0036 (0x0024) | Max Velocity | 最大速度限制 | uint, rpm |
| S-0-0040 (0x0028) | Homing Velocity | 回零速度 | uint, rpm |
| S-0-0064 (0x0040) | Drive Control Word | 驱动器控制字 | ushort |
| S-0-0071 (0x0047) | Drive Status Word | 驱动器状态字 | ushort |
元素标志(Element Flags)— 读取 IDN 的不同"层面":
| 标志 | 含义 |
|---|---|
| 0x01 | 数据状态 |
| 0x02 | 参数名称(字符串) |
| 0x04 | 属性(数据类型/长度/权限位域) |
| 0x08 | 单位(字符串) |
| 0x10 | 最小值 |
| 0x20 | 最大值 |
| 0x40 | 数据值(默认,最常用) |
| 0x80 | 默认值 |
基本读写
Read(short idn, byte elementFlags, int timeoutMs)
public byte[] Read(short idn)
public byte[] Read(short idn, byte elementFlags, int timeoutMs)
读取 IDN 参数原始字节。
参数:
idn(short) — IDN 编号elementFlags(byte) — 元素标志(默认 0x40 = 数据值)timeoutMs(int) — 超时时间(毫秒,默认 500)
返回值:
byte[]— 读取的数据,失败返回null
示例:
// 读取通信周期时间 (S-0-0001)
byte[] data = slave.SoE().Read((short) 0x0001);
if (data != null) {
int cycleTime = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8)
| ((data[2] & 0xFF) << 16) | ((data[3] & 0xFF) << 24);
System.out.printf("周期时间: %d us%n", cycleTime);
}
Write(short idn, byte[] data, byte elementFlags, int timeoutMs)
public boolean Write(short idn, byte[] data)
public boolean Write(short idn, byte[] data, byte elementFlags, int timeoutMs)
写入 IDN 参数。
参数:
idn(short) — IDN 编号data(byte[]) — 要写入的数据elementFlags(byte) — 元素标志(默认 0 = 数据值)timeoutMs(int) — 超时时间(毫秒,默认 500)
返回值:
boolean— 成功返回true
类型化读取
所有方法签名一致:ReadXxx(short idn) 或 ReadXxx(short idn, byte elementFlags, int timeoutMs)
| 方法 | 返回类型 | 说明 |
|---|---|---|
| ReadInt16 | Short | 读取 16 位有符号整数 |
| ReadInt32 | Integer | 读取 32 位有符号整数 |
| ReadUInt16 | Integer | 读取 16 位无符号整数 |
| ReadUInt32 | Long | 读取 32 位无符号整数 |
| ReadFloat | Float | 读取单精度浮点数 |
| ReadDouble | Double | 读取双精度浮点数 |
| ReadString | String | 读取字符串 |
示例:
SoE soe = slave.SoE();
// 读取实际位置 (S-0-0011)
Integer actualPos = soe.ReadInt32((short) 0x000B);
// 读取驱动器状态字 (S-0-0071)
Integer status = soe.ReadUInt16((short) 0x0047);
// 读取参数名称 (ElementFlag 0x02)
String name = soe.ReadString((short) 0x000B);
类型化写入
所有方法签名一致:WriteXxx(short idn, T value) 或带 elementFlags / timeoutMs 参数,返回 boolean。
| 方法 | 值类型 | 说明 |
|---|---|---|
| WriteInt16 | short | 写入 16 位有符号整数 |
| WriteInt32 | int | 写入 32 位有符号整数 |
示例:
SoE soe = slave.SoE();
// 写控制字 (S-0-0064)
soe.WriteInt16((short) 0x0040, (short) 0x0006);
// 设置目标速度 (S-0-0014)
soe.WriteInt32((short) 0x000E, 1000);
元数据读取
ReadName(short idn)
public String ReadName(short idn)
读取 IDN 参数名称(内部使用 ElementFlag 0x02)。
ReadUnit(short idn)
public String ReadUnit(short idn)
读取 IDN 参数单位(内部使用 ElementFlag 0x08)。
ReadMinValue / ReadMaxValue / ReadDefaultValue
public byte[] ReadMinValue(short idn)
public byte[] ReadMaxValue(short idn)
public byte[] ReadDefaultValue(short idn)
读取 IDN 参数的最小值(0x10)、最大值(0x20)、默认值(0x80)。
ReadDataState(short idn)
public Integer ReadDataState(short idn)
读取 IDN 的 Data State 元素(ElementFlag 0x01)。
示例:
SoE soe = slave.SoE();
String name = soe.ReadName((short) 0x000B);
String unit = soe.ReadUnit((short) 0x000B);
byte[] min = soe.ReadMinValue((short) 0x000B);
byte[] max = soe.ReadMaxValue((short) 0x000B);
System.out.printf("参数: %s (%s)%n", name, unit);
参数信息
GetAvailableIDNs()
public List<Short> GetAvailableIDNs()
获取从站支持的所有 IDN 列表。
返回值:
List<Short>— IDN 编号列表
GetParameterInfo(short idn)
public SoEParameter GetParameterInfo(short idn)
获取 IDN 参数的完整信息(名称、单位、当前值、最小/最大值等)。
返回值:
SoEParameter— 包含完整的参数信息
相关结构:
public static class SoEParameter {
public short IDN; // IDN 编号
public String Name; // 参数名称
public String Unit; // 单位
public byte[] Value; // 当前值
public byte[] DefaultValue; // 默认值
public byte[] MinValue; // 最小值
public byte[] MaxValue; // 最大值
public String Category(); // 类别("Standard"/"Product"/"Vendor")
public String SercosIDN(); // SERCOS IDN 格式(S-x-xxxx)
}
示例:
SoE soe = slave.SoE();
SoE.SoEParameter param = soe.GetParameterInfo((short) 0x000B);
System.out.printf("IDN: %s - %s%n", param.SercosIDN(), param.Name);
System.out.printf("单位: %s%n", param.Unit);
程序命令
ExecuteCommand(short idn, int timeoutMs, int pollIntervalMs)
public boolean ExecuteCommand(short idn)
public boolean ExecuteCommand(short idn, int timeoutMs, int pollIntervalMs)
执行 SoE 程序命令(SERCOS Procedure Command)。通过写入 DataState 元素(0x01)的 bit0 触发命令执行,然后轮询等待命令完成。
参数:
idn(short) — 要执行的命令 IDN 编号timeoutMs(int) — 等待命令完成的超时时间(毫秒,默认 5000)pollIntervalMs(int) — 轮询间隔(毫秒,默认 50)
返回值:
boolean— 命令执行成功返回true,超时或失败返回false
示例:
// 执行回零命令 (S-0-0148)
boolean success = slave.SoE().ExecuteCommand((short) 0x0094);
if (success)
System.out.println("回零完成");
else
System.out.println("回零超时或失败");
// 自定义超时(长时间命令)
boolean ok = slave.SoE().ExecuteCommand((short) 0x0094, 30000, 100);
AT/MDT 映射
SERCOS 使用 AT(Acknowledge Telegram)和 MDT(Master Data Telegram)传输周期数据:
- AT = 从站→主站的周期输入(实际位置、实际速度、状态字...)
- MDT = 主站→从站的周期输出(目标位置、目标速度、控制字...)
映射配置决定了哪些 IDN 参数被包含在周期数据帧中。
GetIDNMapping()
public SoEMappingInfo GetIDNMapping()
读取当前驱动器的 AT/MDT 映射配置。
返回值:
SoEMappingInfo— 映射信息对象
相关结构:
public static class SoEMappingInfo {
public List<SoEMappingEntry> ATMapping; // AT(输入)映射列表
public List<SoEMappingEntry> MDTMapping; // MDT(输出)映射列表
public int ATBitSize; // AT 总位数
public int MDTBitSize; // MDT 总位数
public int ATByteSize; // AT 总字节数
public int MDTByteSize; // MDT 总字节数
}
public static class SoEMappingEntry {
public short IDN; // IDN 编号
public String Name; // 参数名称
public int BitLength; // 数据长度(位)
public int ByteLength; // 数据长度(字节)
}
GetAllDriveMappings()
public Map<Byte, SoEMappingInfo> GetAllDriveMappings()
获取所有驱动器的映射信息(最多扫描 8 个驱动器)。
返回值:
Map<Byte, SoEMappingInfo>— 驱动器编号到映射信息的字典
示例:
SoE soe = slave.SoE();
SoE.SoEMappingInfo mapping = soe.GetIDNMapping();
System.out.println("AT 映射条目: " + mapping.ATMapping.size());
System.out.println("MDT 映射条目: " + mapping.MDTMapping.size());
// 所有驱动器映射
Map<Byte, SoE.SoEMappingInfo> allMappings = soe.GetAllDriveMappings();
参数变化通知
SoE Notification 功能允许监控 SERCOS 从站 IDN 参数的变化。当参数值发生改变时,通过监听器通知应用程序。
实现机制:
- 硬件通知:通过写入 S-0-0127(通知请求 IDN)尝试启用从站的原生通知
- 轮询检测:如果从站不支持硬件通知,自动回退到轮询模式
通知 API
addNotificationListener(listener)(void) — 注册通知回调监听器EnableNotification(idn)(boolean) — 启用 IDN 参数变化通知EnableNotification(idn, timeoutMs)(boolean) — 启用通知(自定义超时)DisableNotification(idn)(void) — 禁用指定 IDN 的通知DisableAllNotifications()(void) — 禁用所有通知PollNotifications()(int) — 轮询检测变化(默认 200ms 超时)PollNotifications(timeoutMs)(int) — 轮询检测变化(自定义超时)GetMonitoredIDNs()(List<Short>) — 获取当前监控的 IDN 列表
NotificationListener 接口
public interface NotificationListener {
void onNotification(SoENotificationEventArgs args);
}
// 与 C# SoENotificationListener 对齐的命名别名 (继承 NotificationListener)
public interface SoENotificationListener extends NotificationListener { }
SoENotificationEventArgs
public static class SoENotificationEventArgs {
public short SlaveIndex; // 从站索引
public byte DriveNumber; // 驱动器编号
public short IDN; // 变化的 IDN 编号
public byte ElementFlags; // SoE ElementFlags (bit0=Data bit1=Name ...)
public byte[] NewValue; // 新值
public byte[] OldValue; // 旧值
public String SercosIDN(); // 格式化的 SERCOS IDN 名称 (如 "S-0-0127")
}
示例:
SoE soe = slave.SoE();
// 注册通知监听器
soe.addNotificationListener((args) -> {
System.out.printf("参数变化: %s (IDN 0x%04X)%n", args.SercosIDN(), args.IDN);
});
// 启用监控实际位置 (S-0-0011)
boolean hwSupported = soe.EnableNotification((short) 0x000B);
System.out.println("硬件通知: " + (hwSupported ? "支持" : "回退到轮询"));
// 启用监控驱动器状态字 (S-0-0071)
soe.EnableNotification((short) 0x0047);
// 周期性轮询检测
int changed = soe.PollNotifications();
if (changed > 0)
System.out.printf("检测到 %d 个参数变化%n", changed);
// 查看已监控的 IDN
List<Short> monitored = soe.GetMonitoredIDNs();
// 停止监控
soe.DisableAllNotifications();
Emergency 事件
SoE Emergency 对应 DLL 回调中 OpCode=6 的硬件紧急帧,依据 ETG.1020 定义。从站在内部故障、硬件告警等场景下主动推送,ErrorCode 为 IEC 61491 Sercos 错误码。
Emergency API
addEmergencyListener(listener)(void) — 注册 Emergency 回调监听器removeEmergencyListener(listener)(void) — 移除指定 Emergency 监听器
SoEEmergencyListener 接口
public interface SoEEmergencyListener {
void onEmergency(SoEEmergencyEventArgs args);
}
SoEEmergencyEventArgs
public static class SoEEmergencyEventArgs {
public short SlaveIndex; // 从站索引
public byte DriveNumber; // 驱动器编号
public int ErrorCode; // IEC 61491 Sercos 错误码
public long TimestampUtcMs; // 事件时间戳 (UTC epoch 毫秒)
}
示例:
SoE soe = slave.SoE();
// 注册 Emergency 监听器 (OpCode=6 硬件帧触发)
soe.addEmergencyListener(args -> {
System.out.printf("SoE Emergency: slave=%d drive=%d code=0x%04X at %tF %<tT%n",
args.SlaveIndex, args.DriveNumber & 0xFF, args.ErrorCode,
args.TimestampUtcMs, args.TimestampUtcMs);
});
// 同时监听参数变化 Notification (OpCode=5)
soe.addNotificationListener(args -> {
System.out.printf("参数变化: %s (IDN 0x%04X, flags=0x%02X)%n",
args.SercosIDN(), args.IDN, args.ElementFlags & 0xFF);
});
- Java
SoENotificationListener/SoEEmergencyListener↔ C#SoENotificationListener/SoEEmergencyListener - Java
addNotificationListener / addEmergencyListener↔ C#NotificationReceived / EmergencyReceived事件订阅(+= handler) - 事件参数
SoENotificationEventArgs / SoEEmergencyEventArgs两语言字段一致,Java 追加ElementFlags字段暴露 DLL 上报的 ElementFlags
IDN 编解码工具
SoEElementFlags
elementFlags 位掩码常量,对齐 C# SoEElementFlags。
相关结构:
public static final class SoEElementFlags {
public static final byte DataState = 0x01; // 数据状态
public static final byte Name = 0x02; // 参数名称
public static final byte Attribute = 0x04; // 属性(数据类型/长度/权限)
public static final byte Unit = 0x08; // 单位
public static final byte Min = 0x10; // 最小值
public static final byte Max = 0x20; // 最大值
public static final byte Value = 0x40; // 数据值(默认)
public static final byte Default = (byte) 0x80; // 默认值
}
byte[] name = soe.Read((short) 0x000B, SoE.SoEElementFlags.Name, 500);
byte[] unit = soe.Read((short) 0x000B, SoE.SoEElementFlags.Unit, 500);
encodeIdn(boolean isStandard, byte parameterSet, short dataBlock)
public static short encodeIdn(boolean isStandard, byte parameterSet, short dataBlock)
将 SERCOS IDN 三元组编码为 16 位 IDN(标志位 + ParameterSet 3 bits + DataBlock 12 bits)。
decodeIdn(short idn)
public static IdnTriple decodeIdn(short idn)
将 16 位 IDN 解码为 IdnTriple 三元组。
IdnTriple
public static class IdnTriple {
public boolean isStandard; // true=S 标准 / false=P 产品
public byte parameterSet; // ParameterSet (0..7)
public short dataBlock; // DataBlock (0..4095)
}
示例:
// S-0-0011 (实际位置反馈) = standard, set=0, block=11
short idn = SoE.encodeIdn(true, (byte) 0, (short) 11);
SoE.IdnTriple t = SoE.decodeIdn(idn);
System.out.printf("%s-%d-%04d%n",
t.isStandard ? "S" : "P", t.parameterSet, t.dataBlock);
批量操作
ReadAllParameters()
public List<SoEParameter> ReadAllParameters()
批量读取从站所有可用参数。
返回值:
List<SoEParameter>— 参数列表
WriteParameter(short idn, byte[] data)
public boolean WriteParameter(short idn, byte[] data)
写入参数数据。
示例:
SoE soe = slave.SoE();
// 批量读取常见参数
List<SoE.SoEParameter> params = soe.ReadAllParameters();
for (SoE.SoEParameter p : params) {
System.out.println(p.SercosIDN() + " " + p.Name);
}
完整示例
参数扫描
SoE soe = slave.SoE();
List<Short> idns = soe.GetAvailableIDNs();
System.out.printf("从站支持 %d 个 IDN 参数%n%n", idns.size());
for (int i = 0; i < Math.min(10, idns.size()); i++) {
SoE.SoEParameter info = soe.GetParameterInfo(idns.get(i));
System.out.printf("%s: %s (%s)%n", info.SercosIDN(), info.Name, info.Unit);
}
伺服驱动器控制
SoE soe = slave.SoE();
// 1. 读取驱动器状态
Integer statusWord = soe.ReadUInt16((short) 0x0047);
System.out.printf("状态字: 0x%04X%n", statusWord);
// 2. 查看 AT/MDT 映射(了解周期数据布局)
SoE.SoEMappingInfo mapping = soe.GetIDNMapping();
System.out.printf("%nAT 映射(输入 %d 字节):%n", mapping.ATByteSize);
for (SoE.SoEMappingEntry entry : mapping.ATMapping)
System.out.printf(" IDN 0x%04X: %s (%dB)%n", entry.IDN, entry.Name, entry.ByteLength);
System.out.printf("%nMDT 映射(输出 %d 字节):%n", mapping.MDTByteSize);
for (SoE.SoEMappingEntry entry : mapping.MDTMapping)
System.out.printf(" IDN 0x%04X: %s (%dB)%n", entry.IDN, entry.Name, entry.ByteLength);
// 3. 写控制字使能驱动器 (S-0-0064)
soe.WriteInt16((short) 0x0040, (short) 0x0006);
// 4. 设置目标位置 (S-0-0012)
soe.WriteInt32((short) 0x000C, 50000);
// 5. 读取实际位置 (S-0-0011)
Integer actualPos = soe.ReadInt32((short) 0x000B);
System.out.printf("%n实际位置: %d inc%n", actualPos);