跳到主要内容

SoE (Servo over EtherCAT)

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

通过 slave.SoE 访问。从站不支持 SoE 时为 null

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, μs
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(ushort idn, byte elementFlags = 0x40, int timeoutMs = 500)

public byte[]? Read(ushort idn, byte elementFlags = 0x40, int timeoutMs = 500)

读取 IDN 参数原始字节。非可用状态自动切换。

参数:

  • idn (ushort) — IDN 编号
  • elementFlags (byte) — 元素标志(默认 0x40 = 数据值)
  • timeoutMs (int) — 超时时间(毫秒)

返回值:

  • byte[]? — 读取的数据,失败返回 null

示例:

// 读取通信周期时间 (S-0-0001)
byte[]? data = slave.SoE.Read(0x0001, 0x40);
if (data != null)
{
uint cycleTime = BitConverter.ToUInt32(data, 0);
Console.WriteLine($"周期时间: {cycleTime} μs");
}

Write(ushort idn, byte[] data, byte elementFlags = 0, int timeoutMs = 500)

public bool Write(ushort idn, byte[] data, byte elementFlags = 0, int timeoutMs = 500)

写入 IDN 参数。非可用状态自动切换。

参数:

  • idn (ushort) — IDN 编号
  • data (byte[]) — 要写入的数据
  • elementFlags (byte) — 元素标志(默认 0 = 数据值)
  • timeoutMs (int) — 超时时间(毫秒)

返回值:

  • bool — 成功返回 true

示例:

// 设置目标位置 (S-0-0012)
byte[] position = BitConverter.GetBytes(100000);
bool success = slave.SoE.Write(0x000C, position);

类型化读取

所有方法签名一致:ReadXxx(ushort idn, byte elementFlags = 0x40, int timeoutMs = 500)

方法返回类型说明
ReadInt16short?读取 16 位有符号整数
ReadInt32int?读取 32 位有符号整数
ReadUInt16ushort?读取 16 位无符号整数
ReadUInt32uint?读取 32 位无符号整数
ReadFloatfloat?读取单精度浮点数
ReadDoubledouble?读取双精度浮点数
ReadStringstring?读取字符串

示例:

// 读取实际位置 (S-0-0011)
int? actualPos = slave.SoE.ReadInt32(0x000B);

// 读取驱动器状态字 (S-0-0071)
ushort? status = slave.SoE.ReadUInt16(0x0047);

// 读取参数名称
string? name = slave.SoE.ReadString(0x000B, elementFlags: 0x02);

类型化写入

所有方法签名一致:WriteXxx(ushort idn, T value, byte elementFlags = 0, int timeoutMs = 500),返回 bool

方法值类型说明
WriteInt16short写入 16 位有符号整数
WriteInt32int写入 32 位有符号整数
WriteUInt16ushort写入 16 位无符号整数
WriteUInt32uint写入 32 位无符号整数

示例:

// 设置目标位置 (S-0-0012)
slave.SoE.WriteInt32(0x000C, 50000);

// 写控制字 (S-0-0064)
slave.SoE.WriteUInt16(0x0040, 0x0006);

元数据读取

ReadName(ushort idn, int timeoutMs = 500)

public string? ReadName(ushort idn, int timeoutMs = 500)

读取 IDN 参数名称(内部使用 ElementFlag 0x02)。

ReadUnit(ushort idn, int timeoutMs = 500)

public string? ReadUnit(ushort idn, int timeoutMs = 500)

读取 IDN 参数单位(内部使用 ElementFlag 0x08)。

ReadMinValue / ReadMaxValue / ReadDefaultValue

public byte[]? ReadMinValue(ushort idn, int timeoutMs = 500)
public byte[]? ReadMaxValue(ushort idn, int timeoutMs = 500)
public byte[]? ReadDefaultValue(ushort idn, int timeoutMs = 500)

读取 IDN 参数的最小值(0x10)、最大值(0x20)、默认值(0x80)。

示例:

string? name = slave.SoE.ReadName(0x000B);
string? unit = slave.SoE.ReadUnit(0x000B);
byte[]? min = slave.SoE.ReadMinValue(0x000B);
byte[]? max = slave.SoE.ReadMaxValue(0x000B);

Console.WriteLine($"参数: {name} ({unit})");
if (min != null && max != null)
Console.WriteLine($"范围: {BitConverter.ToInt32(min, 0)} - {BitConverter.ToInt32(max, 0)}");

参数信息

GetAvailableIDNs(int timeoutMs = 5000)

public List<ushort> GetAvailableIDNs(int timeoutMs = 5000)

获取从站支持的所有 IDN 列表。

返回值:

  • List<ushort> — IDN 编号列表

GetParameterInfo(ushort idn, int timeoutMs = 5000)

public SoEParameter GetParameterInfo(ushort idn, int timeoutMs = 5000)

获取 IDN 参数的完整信息(名称、单位、属性、当前值、最小/最大值等)。

返回值:

  • SoEParameter — 包含完整的参数信息

相关结构:

public class SoEParameter
{
public ushort IDN { get; } // IDN 编号
public string Name { get; } // 参数名称
public string Unit { get; } // 单位
public SoEAttributes Attributes { get; } // 属性(数据类型、长度等)
public byte[] Value { get; } // 当前值
public byte[] DefaultValue { get; } // 默认值
public byte[] MinValue { get; } // 最小值
public byte[] MaxValue { get; } // 最大值
public string SercosIDN { get; } // SERCOS IDN 格式(如 "S-0-0001")
public string Category { get; } // 类别("Standard" / "Product" / "Vendor")
public bool IsReadOnly { get; } // 是否只读
public string AccessMode { get; } // 访问权限("RO" / "RW" / "RW*")
public string FormattedValue { get; } // 格式化的当前值
public string FormattedMinValue { get; } // 格式化的最小值
public string FormattedMaxValue { get; } // 格式化的最大值
}
public struct SoEAttributes
{
public ushort EvaluationFactor; // 评价因子
public byte Length; // 数据长度
public bool IsList; // 是否为列表
public bool IsCommand; // 是否为命令
public SoEDataType DataType; // 数据类型
public byte Decimals; // 小数位数
public bool WriteProtectedPreOp; // PreOp 状态写保护
public bool WriteProtectedSafeOp; // SafeOp 状态写保护
public bool WriteProtectedOp; // Op 状态写保护
}
public enum SoEDataType
{
Binary = 0, // 二进制
UInt = 1, // 无符号整数
Int = 2, // 有符号整数
Hexadecimal = 3, // 十六进制
String = 4, // 字符串
IDN = 5, // IDN 引用
Float = 6, // 浮点数
Parameter = 7 // 参数
}

示例:

var param = slave.SoE.GetParameterInfo(0x000B);
Console.WriteLine($"IDN: {param.SercosIDN} - {param.Name}");
Console.WriteLine($"单位: {param.Unit}, 访问: {param.AccessMode}");
Console.WriteLine($"当前值: {param.FormattedValue}");
Console.WriteLine($"范围: {param.FormattedMinValue} - {param.FormattedMaxValue}");

AT/MDT 映射

SERCOS 使用 AT(Acknowledge Telegram)和 MDT(Master Data Telegram)传输周期数据:

  • AT = 从站→主站的周期输入(实际位置、实际速度、状态字…)
  • MDT = 主站→从站的周期输出(目标位置、目标速度、控制字…)

映射配置决定了哪些 IDN 参数被包含在周期数据帧中。

GetIDNMapping(int timeoutMs = 5000)

public SoEMappingInfo GetIDNMapping(int timeoutMs = 5000)

读取当前驱动器的 AT/MDT 映射配置。

返回值:

  • SoEMappingInfo — 映射信息对象

相关结构:

public class SoEMappingInfo
{
public List<SoEMappingEntry> ATMapping { get; } // AT(输入)映射列表
public List<SoEMappingEntry> MDTMapping { get; } // MDT(输出)映射列表
public int ATBitSize { get; } // AT 总位数
public int MDTBitSize { get; } // MDT 总位数
public int ATByteSize { get; } // AT 总字节数
public int MDTByteSize { get; } // MDT 总字节数
}

public class SoEMappingEntry
{
public ushort IDN { get; } // IDN 编号
public string Name { get; } // 参数名称
public int BitLength { get; } // 数据长度(位)
public int ByteLength { get; } // 数据长度(字节)
}

GetAllDriveMappings(int timeoutMs = 5000)

public Dictionary<byte, SoEMappingInfo> GetAllDriveMappings(int timeoutMs = 5000)

获取所有驱动器的映射信息(最多扫描 8 个驱动器)。

返回值:

  • Dictionary<byte, SoEMappingInfo> — 驱动器编号到映射信息的字典

参数变化通知

SoE Notification 功能允许监控 SERCOS 从站 IDN 参数的变化。当参数值发生改变时,通过事件通知应用程序。

实现机制:

  1. 硬件通知:通过写入 S-0-0127(通知请求 IDN)尝试启用从站的原生 SoE Notification(OpCode=5)
  2. 轮询检测:如果从站不支持硬件通知,自动回退到轮询模式,周期性读取参数值并与基线比较

NotificationReceived 事件

public event Action<SoENotificationEventArgs>? NotificationReceived;

当监控的 IDN 参数值发生变化时触发。

EnableNotification(ushort idn, int timeoutMs = 500)

public bool EnableNotification(ushort idn, int timeoutMs = 500)

启用指定 IDN 的参数变化通知。首先尝试通过 S-0-0127 启用硬件通知,无论是否成功都会将 IDN 加入轮询监控列表。

参数:

  • idn (ushort) — 要监控的 IDN 编号
  • timeoutMs (int) — 超时时间(毫秒,默认 500)

返回值:

  • booltrue 表示从站确认支持硬件通知;false 表示回退到轮询模式

DisableNotification(ushort idn)

public void DisableNotification(ushort idn)

禁用指定 IDN 的参数变化通知,从监控列表中移除。

DisableAllNotifications()

public void DisableAllNotifications()

禁用所有已启用的通知,清空监控列表。

PollNotifications(int timeoutMs = 200)

public int PollNotifications(int timeoutMs = 200)

轮询检测所有已监控 IDN 的参数变化。逐个读取当前值并与上次记录值比较,发现变化时触发 NotificationReceived 事件。适合在定时器或后台线程中周期性调用。

参数:

  • timeoutMs (int) — 每个 IDN 读取的超时时间(毫秒,默认 200)

返回值:

  • int — 本次轮询检测到变化的 IDN 数量

SoENotificationEventArgs

public class SoENotificationEventArgs : EventArgs
{
public ushort SlaveIndex { get; } // 从站索引
public byte DriveNumber { get; } // 驱动器编号
public ushort IDN { get; } // 变化的 IDN 编号
public byte[] NewValue { get; } // 新值
public byte[] OldValue { get; } // 旧值
public string SercosIDN { get; } // 格式化的 SERCOS IDN 名称 (如 "S-0-0127")
}

示例:

if (slave.SoE != null)
{
// 注册通知事件
slave.SoE.NotificationReceived += (args) =>
{
Console.WriteLine($"参数变化: {args.SercosIDN} (IDN 0x{args.IDN:X4})");
Console.WriteLine($" 旧值: {BitConverter.ToString(args.OldValue)}");
Console.WriteLine($" 新值: {BitConverter.ToString(args.NewValue)}");
};

// 启用监控实际位置 (S-0-0011)
bool hwSupported = slave.SoE.EnableNotification(0x000B);
Console.WriteLine($"硬件通知: {(hwSupported ? "支持" : "回退到轮询")}");

// 启用监控驱动器状态字 (S-0-0071)
slave.SoE.EnableNotification(0x0047);

// 在定时器中周期性轮询
while (running)
{
int changed = slave.SoE.PollNotifications();
if (changed > 0)
Console.WriteLine($"检测到 {changed} 个参数变化");
Thread.Sleep(100);
}

// 停止监控
slave.SoE.DisableAllNotifications();
}

标准 IDN 常量

public static class StandardIDN
{
public const ushort IDNList = 0x0000; // IDN 列表 (S-0-0000)
public const ushort CycleTime = 0x0001; // 通信周期时间 (S-0-0001)
public const ushort ATConfig = 0x0010; // AT 配置 - 输入映射 (S-0-0016)
public const ushort MDTConfig = 0x0018; // MDT 配置 - 输出映射 (S-0-0024)
public const ushort NotificationRequest = 0x007F; // 通知请求 (S-0-0127)
}

完整示例

参数扫描

if (slave.SoE != null)
{
var idns = slave.SoE.GetAvailableIDNs();
Console.WriteLine($"从站支持 {idns.Count} 个 IDN 参数\n");

foreach (var idn in idns.Take(10))
{
var info = slave.SoE.GetParameterInfo(idn);
Console.WriteLine($"{info.SercosIDN}: {info.Name} = {info.FormattedValue} {info.Unit} [{info.AccessMode}]");
}
}

伺服驱动器控制

if (slave.SoE != null)
{
// 1. 读取驱动器状态
ushort? statusWord = slave.SoE.ReadUInt16(0x0047);
Console.WriteLine($"状态字: 0x{statusWord:X4}");

// 2. 查看 AT/MDT 映射(了解周期数据布局)
var mapping = slave.SoE.GetIDNMapping();
Console.WriteLine($"\nAT 映射(输入 {mapping.ATByteSize} 字节):");
foreach (var entry in mapping.ATMapping)
Console.WriteLine($" IDN 0x{entry.IDN:X4}: {entry.Name} ({entry.ByteLength}B)");

Console.WriteLine($"\nMDT 映射(输出 {mapping.MDTByteSize} 字节):");
foreach (var entry in mapping.MDTMapping)
Console.WriteLine($" IDN 0x{entry.IDN:X4}: {entry.Name} ({entry.ByteLength}B)");

// 3. 写控制字使能驱动器 (S-0-0064)
slave.SoE.WriteUInt16(0x0040, 0x0006);

// 4. 设置目标位置 (S-0-0012)
slave.SoE.WriteInt32(0x000C, 50000);

// 5. 读取实际位置 (S-0-0011)
int? actualPos = slave.SoE.ReadInt32(0x000B);
Console.WriteLine($"\n实际位置: {actualPos} inc");
}

程序命令

ExecuteCommand(ushort idn, int timeoutMs = 5000, int pollIntervalMs = 50)

public bool ExecuteCommand(ushort idn, int timeoutMs = 5000, int pollIntervalMs = 50)

执行 SoE 程序命令(SERCOS Procedure Command)。通过写入 DataState 元素(0x01)的 bit0 触发命令执行,然后轮询等待命令完成。

参数:

  • idn (ushort) — 要执行的命令 IDN 编号
  • timeoutMs (int) — 等待命令完成的超时时间(毫秒,默认 5000)
  • pollIntervalMs (int) — 轮询间隔(毫秒,默认 50)

返回值:

  • bool — 命令执行成功返回 true,超时或失败返回 false

示例:

// 执行回零命令 (S-0-0148)
bool success = slave.SoE.ExecuteCommand(0x0094);
if (success)
Console.WriteLine("回零完成");
else
Console.WriteLine("回零超时或失败");

// 自定义超时(长时间命令)
bool ok = slave.SoE.ExecuteCommand(0x0094, timeoutMs: 30000, pollIntervalMs: 100);

ReadDataState(ushort idn, int timeoutMs = 500)

public byte[]? ReadDataState(ushort idn, int timeoutMs = 500)

读取 IDN 的 Data State 元素(ElementFlag 0x01)。Data State 包含命令状态、数据有效性等信息。

参数:

  • idn (ushort) — IDN 编号
  • timeoutMs (int) — 超时时间(毫秒,默认 500)

返回值:

  • byte[]? — Data State 字节数据,失败返回 null

示例:

// 读取 IDN 的 Data State
byte[]? state = slave.SoE.ReadDataState(0x0094);
if (state != null)
{
bool commandActive = (state[0] & 0x01) != 0;
bool commandError = (state[0] & 0x02) != 0;
Console.WriteLine($"命令激活: {commandActive}, 命令错误: {commandError}");
}

运行时异步通知 (Notification / Emergency)

除了同步的 Read/Write API, SoE 还支持两类由从站侧异步推送的消息, SDK 将其表面化为两个事件:

事件SERCOS OpCode用途
NotificationReceived5IDN 参数值变化通知 (热更新监听)
EmergencyReceived6驱动器 Sercos Emergency (紧急停止 / 故障)

典型用例:

  • 伺服参数热变化监听 — HMI 改写 S-0-0036 Max Velocity 后, 其他监控端点通过 Notification 实时收到新值
  • 紧急停止监听 — 驱动器硬件级 Emergency (过流/过温/编码器丢失) 主动推送, 应用层立刻切换到安全分支
  • 多端调参一致性 — 多个工程师通过不同上位机调参时, 参数变化可广播到所有监听端

NotificationReceived 事件

public event Action<SoENotificationEventArgs>? NotificationReceived;

当监控的 IDN 参数值发生变化时触发。SDK 首先尝试通过 S-0-0127 启用硬件通知 (OpCode=5), 若从站不支持则回退到周期性读取 + 本地比较的轮询模式。两种模式对应用层透明, 订阅者收到的事件结构一致。

事件参数:

public class SoENotificationEventArgs : EventArgs
{
public ushort SlaveIndex { get; } // 从站索引 (1-based)
public byte DriveNumber { get; } // 驱动器编号 (多轴驱动用)
public ushort IDN { get; } // 发生变化的 IDN 编号
public SoEElementFlags ElementFlags { get; } // 触发通知的 Element 位掩码
public byte[]? NewValue { get; } // 变化后的值 (可能 null 表示读取失败)
public byte[]? OldValue { get; } // 变化前的值 (首次通知时为 null)
public string SercosIDN { get; } // SERCOS 格式, 如 "S-0-0036"
}

EmergencyReceived 事件

public event Action<SoEEmergencyEventArgs>? EmergencyReceived;

驱动器推送 Sercos Emergency (OpCode=6) 时触发。与 CoE 的 EMCY 消息不同, Sercos Emergency 是伺服专属, 错误码遵循 SERCOS 定义 (非 CiA 301)。

事件参数:

public class SoEEmergencyEventArgs : EventArgs
{
public ushort SlaveIndex { get; } // 来源从站索引 (1-based)
public byte DriveNumber { get; } // 驱动器编号 (SoEHeader driveNo 位段, 3 bit)
public ushort ErrorCode { get; } // Sercos Emergency Error Code (2 字节)
public DateTime Timestamp { get; } // 接收时间戳 (UTC)
}

线程模型

回调线程

这两个事件在 DLL worker 线程 上触发, 不是 UI 线程。若需在 WinForms / WPF UI 上更新控件, 必须 Invoke / Dispatcher.Invoke 切回 UI 线程:

// WinForms
slave.SoE.EmergencyReceived += (args) =>
{
if (mainForm.InvokeRequired)
mainForm.BeginInvoke(new Action(() => HandleEmergency(args)));
else
HandleEmergency(args);
};

回调内禁止做长时间阻塞 (> 几毫秒), 避免拖慢 SoE 邮箱处理; 耗时工作丢到 Task.Run 异步执行。

完整示例 — 订阅 + 取消 + UI 切换

if (slave.SoE == null) return;

// 1. 订阅 Notification (参数热变化)
Action<SoENotificationEventArgs> onNotification = args =>
{
string newStr = args.NewValue != null ? BitConverter.ToString(args.NewValue) : "N/A";
string oldStr = args.OldValue != null ? BitConverter.ToString(args.OldValue) : "(首次)";
Console.WriteLine(
$"[SoE Notify] 从站={args.SlaveIndex} 驱动器={args.DriveNumber} " +
$"{args.SercosIDN} (0x{args.IDN:X4}) Elements=0x{(byte)args.ElementFlags:X2} " +
$"旧=[{oldStr}] → 新=[{newStr}]");
};

// 2. 订阅 Emergency (紧急故障)
Action<SoEEmergencyEventArgs> onEmergency = args =>
{
Console.WriteLine(
$"[SoE EMCY] 从站={args.SlaveIndex} 驱动器={args.DriveNumber} " +
$"ErrorCode=0x{args.ErrorCode:X4} @ {args.Timestamp:HH:mm:ss.fff}");

// 典型响应: 切换 UI 到故障分支, 停止运动轴
if (mainForm.InvokeRequired)
mainForm.BeginInvoke(new Action(() => mainForm.ShowEmergencyBanner(args.ErrorCode)));
};

slave.SoE.NotificationReceived += onNotification;
slave.SoE.EmergencyReceived += onEmergency;

// 3. 启用需要监控的 IDN (支持多个)
slave.SoE.EnableNotification(0x0024); // Max Velocity
slave.SoE.EnableNotification(0x0047); // Drive Status Word

// 4. 应用退出时取消订阅, 避免悬挂引用
try
{
// ... 应用运行 ...
}
finally
{
slave.SoE.DisableAllNotifications();
slave.SoE.NotificationReceived -= onNotification;
slave.SoE.EmergencyReceived -= onEmergency;
}

注意事项

  • Notification 的首次触发 OldValue 可能为 null (基线刚建立, 无历史值)
  • 从站不支持硬件 Notification 时, SDK 按 PollNotifications() 周期 (默认 100ms) 检测, 变化延迟取决于轮询频率
  • Emergency 回调内不要再调用阻塞的 SDO/SoE 读写, 容易在故障状态下堆积请求
  • 订阅事件不需要从站处于 OP 状态, PreOp/SafeOp 也能收到 (与邮箱可用性一致)