跳到主要内容

SoE (Servo over EtherCAT)

SoE 协议将 SERCOS 伺服通信协议封装在 EtherCAT 邮箱中,适用于 SERCOS 兼容的伺服驱动器。

通过 slave.SoE() 访问。

SoE vs CoE

大多数 EtherCAT 伺服驱动器使用 CoE + CiA 402 协议栈。 SoE 仅用于 SERCOS 兼容 的驱动器(如 Bosch Rexroth IndraDrive 系列)。 两者不能混用——从站支持哪种取决于硬件。

SERCOS IDN 体系

SoE 通过 IDN(Identification Number) 寻址驱动器参数。每个 IDN 是一个 16 位编号,代表一个参数(位置、速度、控制字等)。

IDN 命名规则:

前缀范围说明
S-x-xxxx0x0000 – 0x7FFFSERCOS 标准参数(所有厂商通用)
P-x-xxxx0x8000 – 0xBFFF产品特定参数
0xC000 – 0xFFFF厂商自定义参数

常用标准 IDN(伺服控制相关):

IDNSERCOS 名说明数据类型
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)

方法返回类型说明
ReadInt16Short读取 16 位有符号整数
ReadInt32Integer读取 32 位有符号整数
ReadUInt16Integer读取 16 位无符号整数
ReadUInt32Long读取 32 位无符号整数
ReadFloatFloat读取单精度浮点数
ReadDoubleDouble读取双精度浮点数
ReadStringString读取字符串

示例:

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

方法值类型说明
WriteInt16short写入 16 位有符号整数
WriteInt32int写入 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 参数的完整信息(名称、单位、当前值、最小/最大值等)。

返回值:

相关结构:

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 映射配置。

返回值:

相关结构:

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 参数的变化。当参数值发生改变时,通过监听器通知应用程序。

实现机制:

  1. 硬件通知:通过写入 S-0-0127(通知请求 IDN)尝试启用从站的原生通知
  2. 轮询检测:如果从站不支持硬件通知,自动回退到轮询模式

通知 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);
});
与 C# 的对齐
  • 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);