SoE (Servo over EtherCAT)
SoE 协议将 SERCOS 伺服通信协议封装在 EtherCAT 邮箱中,适用于 SERCOS 兼容的伺服驱动器。
通过 slave.SoE 访问。从站不支持 SoE 时为 null。
大多数 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, μ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)
| 方法 | 返回类型 | 说明 |
|---|---|---|
| ReadInt16 | short? | 读取 16 位有符号整数 |
| ReadInt32 | int? | 读取 32 位有符号整数 |
| ReadUInt16 | ushort? | 读取 16 位无符号整数 |
| ReadUInt32 | uint? | 读取 32 位无符号整数 |
| ReadFloat | float? | 读取单精度浮点数 |
| ReadDouble | double? | 读取双精度浮点数 |
| ReadString | string? | 读取字符串 |
示例:
// 读取实际位置 (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。
| 方法 | 值类型 | 说明 |
|---|---|---|
| WriteInt16 | short | 写入 16 位有符号整数 |
| WriteInt32 | int | 写入 32 位有符号整数 |
| WriteUInt16 | ushort | 写入 16 位无符号整数 |
| WriteUInt32 | uint | 写入 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>— 驱动器编号到映射信息的字典
标准 IDN 常量
public static class StandardIDN
{
public const ushort IDNList = 0x0000; // IDN 列表 (S-0-0000)
public const ushort ATConfig = 0x0010; // AT 配置 - 输入映射 (S-0-0016)
public const ushort MDTConfig = 0x0018; // MDT 配置 - 输出映射 (S-0-0024)
public const ushort CycleTime = 0x0001; // 通信周期时间 (S-0-0001)
public const ushort DriveStatus = 0x0010; // 驱动器状态 (S-0-0016)
}
完整示例
参数扫描
if (slave.SoE)
{
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)
{
// 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");
}