跳到主要内容

CoE (CANopen over EtherCAT)

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

快速开始​

CoE 对象字典是三层嵌套结构,通过索引器层层访问:

slave.CoE                        → CoEInstance(对象字典集合)
└─ [0x6040] → ObjectDictionary(一个对象)
└─ [0] → ObjectEntry(一个子对象)
├─ .Value → 类型化读取(自动转换,推荐用于读)
├─ .Bytes → 原始字节读写(写入用此)
└─ .SDOWrite(...) → 字节写入方法(返回成功标志)

示例:

// 原始字节读取 — 失败 / WKC=0 返回 null,必须先判空再 ToUInt16 / ToUInt32
byte[] swData = slave.CoE.SDORead(0x6041, 0);
if (swData == null)
{
Console.WriteLine($"SDO 失败: {slave.CoE.LastSdoError}"); // GeneralError ≠ 对象不存在
return;
}
ushort status = BitConverter.ToUInt16(swData, 0);

byte[] posData = slave.CoE.SDORead(0x6064, 0);
if (posData == null) return;
int position = BitConverter.ToInt32(posData, 0);

// 写入 — 赋值字节数组(Bytes 属性 setter 自动处理状态切换与权限检查)
slave.CoE[0x6040][0].Bytes = BitConverter.GetBytes((ushort)0x0006);
slave.CoE[0x607A][0].Bytes = BitConverter.GetBytes(100000);

// 写入 — 或使用 SDOWrite 方法(返回 bool 表示成功,可指定 Complete Access)
slave.CoE[0x6040][0].SDOWrite(BitConverter.GetBytes((ushort)0x0006));
slave.CoE.SDOWrite(0x607A, 0, BitConverter.GetBytes(100000));
SDORead 失败 / WKC=0 返回 null

不要对 SDORead 的返回值直接 BitConverter.ToUInt32。LastSdoError = GeneralError 是邮箱没回来(WKC=0 / 超时),不是「对象不存在」。真 abort(0x06020000 / 0x06090011 / 0x06090012)才进 NotPresent 缓存。详见 SDORead。

CoEInstance(第一层)​

对象字典集合,首次访问时自动加载结构并缓存。

this[]​

public ObjectDictionary this[ushort index] { get; }
public ObjectDictionary this[int index] { get; }
public ObjectDictionary this[string key] { get; }

按索引或名称获取 ObjectDictionary。

参数:

  • index (ushort/int) — 对象索引(int >= 0x1000 按对象索引,否则按位置)
  • key (string) — 十六进制字符串 "0x1000"、十进制字符串 "4096" 或对象名称 "Device Type"

示例:

var od = slave.CoE[0x6040];
var odByName = slave.CoE["Device Type"];

foreach (var item in slave.CoE)
Console.WriteLine($"0x{item.Key:X4}: {item.Value.Name}");

Count​

public int Count { get; }

对象字典中的对象数量。

ContainsKey()​

public bool ContainsKey(ushort index)
public bool ContainsKey(string key)

检查对象索引或名称是否存在。

IsODListLoading​

public bool IsODListLoading { get; }

对象字典是否正在异步加载中。

LoadODListAsync()​

public Task<bool> LoadODListAsync(CancellationToken ct = default)

异步加载对象字典结构,不阻塞调用线程。真发 SDO Information(不走 ESI 缓存)。

参数:

  • ct (CancellationToken) — 取消令牌

行为:

  • 已加载成功(Count > 0 且有真实名称)→ 直接返回 true
  • 正在加载中 → 不重复启动,返回 false
  • 空字典不是成功:SDO Information / piggyback WKC=0 扫出 0 个对象时 loaded=False,返回 false,不会把空表当已加载
  • OP / SafeOp 只发 SDO Information Basic 0x01(GetSlaveSDOListBasic,仅对象索引列表),不全量读对象描述 0x03,避免 OP 邮箱被打满后 WKC=0 被当成空字典
  • PreOp 才完整加载子索引(OEList)
  • 加载过程中 ODList 逐步填充;支持 CancellationToken 取消,已加载的部分仍保留

示例:

bool loaded = await slave.CoE.LoadODListAsync();
if (!loaded || slave.CoE.Count == 0)
{
Console.WriteLine($"对象字典加载失败: {slave.CoE.LastSdoError}");
return;
}
foreach (var item in slave.CoE)
Console.WriteLine($"0x{item.Key:X4}: {item.Value.Name}");

// 支持取消
var cts = new CancellationTokenSource();
var task = slave.CoE.LoadODListAsync(cts.Token);
cts.Cancel(); // 中途取消

Copy()​

public EcODList Copy()

创建对象字典的缓存副本(结构 + 值的快照)。副本中的值为快照,不再实时读取。

ObjectDictionary(第二层)​

代表一个对象字典对象(如 0x6040 控制字),包含若干子对象。

类别属性类型读写说明
对象字典Indexushort只读对象索引号
Namestring只读对象名称
Datatypeushort只读数据类型标识
Objectcodeushort只读对象代码(变量/数组/记录)
Countint只读子对象数量

this[]​

public ObjectEntry this[byte subindex] { get; }
public ObjectEntry this[int subindex] { get; }
public ObjectEntry this[string key] { get; }

按子索引或名称获取 ObjectEntry。

OEList​

public OEDictionary OEList { get; }

子对象集合,支持 foreach 遍历和索引器访问。

示例:

var od = slave.CoE[0x1018];
var vendorId = od[1].Value;
var productCode = od["Product Code"].Value;

foreach (var entry in od.OEList)
Console.WriteLine($" [{entry.Index}] {entry.Name}: {entry.Value}");

SDOWrite()​

public bool SDOWrite(byte[] data, bool useCompleteAccess = false)
public bool SDOWrite(byte subIndex, byte[] data, bool useCompleteAccess = false)

对整个对象或指定子索引进行 SDO 写入。

ReadAll()​

public Dictionary<byte, byte[]> ReadAll()

批量读取所有子索引的原始字节数据。读取失败的条目自动跳过。

ObjectEntry(第三层)​

代表一个子对象(如 0x6040:00),是最终的数据读写节点。

类别属性类型读写说明
对象条目Indexbyte只读子索引
ODIndexushort只读父对象索引
Namestring只读名称
DataTypeEcDataType只读数据类型(决定 Value 的自动转换目标,见类型映射)
BitLengthuint只读位长度
ObjAccessCoEObjectAccess只读访问权限标志
EcDataType 枚举

EtherCAT 数据类型枚举(基于 ETG.1000.6),决定 Value 属性的自动转换目标类型。

枚举值值C# 类型说明
Boolean0x0001bool布尔
Integer80x0002sbyteint8
Integer160x0003shortint16
Integer320x0004intint32
Unsigned80x0005byteuint8
Unsigned160x0006ushortuint16
Unsigned320x0007uintuint32
Real320x0008float单精度浮点
VisibleString0x0009stringASCII 字符串
OctetString0x000Abyte[]字节串
UnicodeString0x000BstringUnicode 字符串
Real640x0011double双精度浮点
Integer640x0015longint64
Unsigned640x001Bulonguint64
TimeOfDay0x000CDateTime时间
TimeDifference0x000DTimeSpan时间差
Domain0x000Fbyte[]原始数据
None0x0000—无类型(位宽 0)
Integer240x0010int24 位有符号
Integer400x0012byte[]40 位有符号;Value 原样返回字节
Integer480x0013byte[]48 位有符号;同上
Integer560x0014byte[]56 位有符号;同上
Unsigned240x0016uint24 位无符号
Unsigned400x0018byte[]40 位无符号;Value 原样返回字节
Unsigned480x0019byte[]48 位无符号;同上
Unsigned560x001Abyte[]56 位无符号;同上
Bit1 – Bit80x0030 – 0x0037bool位型(1 bit)
Unknown0xFFFFbyte[]解析不了 / 不认识的类型码,原样返回字节

上表 C# 类型为 Value / GetValue<T>() 的自动转换目标。其中 Integer8 (sbyte)、TimeOfDay (DateTime)、TimeDifference (TimeSpan)、OctetString / Domain / Unknown (byte[]) 及 Integer40/48/56、Unsigned40/48/56 没有 ValueWrapper 隐式转换,赋值给变量请用 GetValue<T>() / Value.As<T>()(或 Bytes)。

CoEObjectAccess 枚举
[Flags]
public enum CoEObjectAccess : ushort
{
None = 0x00,
ReadPreOp = 0x01, // PreOp 可读
ReadSafeOp = 0x02, // SafeOp 可读
ReadOp = 0x04, // OP 可读
WritePreOp = 0x08, // PreOp 可写
WriteSafeOp = 0x10, // SafeOp 可写
WriteOp = 0x20, // OP 可写
ReadAny = ReadPreOp | ReadSafeOp | ReadOp,
WriteAny = WritePreOp | WriteSafeOp | WriteOp,
ReadWriteAll = ReadAny | WriteAny,
}

Value​

public ValueWrapper Value { get; set; }

类型化读取。读取时根据 DataType 自动将字节转换为 C# 类型(见 EcDataType)。返回的 ValueWrapper 可隐式转换为目标类型,赋值给对应的 C# 变量即可。

支持隐式转换的 C# 类型:bool、byte、short、ushort、int、uint、long、ulong、float、double、string。

常规写入不走 Value。 ValueWrapper 只定义了 ValueWrapper → C# 类型 的隐式转换,没有 C# 类型 → ValueWrapper 的反向转换,entry.Value = 5; 这类直接赋 C# 标量无法编译(setter 只接受 ValueWrapper)。写入请用 Bytes 属性或 SDOWrite() 方法。

示例:

// 推荐:SDORead 判空后再转换(失败 / WKC=0 为 null)
byte[] swRaw = slave.CoE.SDORead(0x6041, 0);
if (swRaw == null) { Console.WriteLine(slave.CoE.LastSdoError); return; }
ushort status = BitConverter.ToUInt16(swRaw, 0);

byte[] posRaw = slave.CoE.SDORead(0x6064, 0);
if (posRaw == null) return;
int position = BitConverter.ToInt32(posRaw, 0);

// 写入 — 通过 Bytes 属性赋值字节数组
slave.CoE[0x6040][0].Bytes = BitConverter.GetBytes((ushort)0x0006); // ushort → 2字节
slave.CoE[0x607A][0].Bytes = BitConverter.GetBytes(100000); // int → 4字节

Bytes​

public byte[] Bytes { get; set; }

原始字节读写。

  • 读:每次访问实时 SDO 读取。读失败(NotPresent 命中 / SDO abort / 通信失败)折叠为 byte[0],与「从站真的返回空字符串」(空 VISIBLE_STRING 成功)不可区分。需要区分成功 / 失败请用 TryGetBytes() 或读 LastBytesReadOk。
  • 写:setter 执行 SDO 写并自动状态切换与权限检查;写入 null 或空数组抛 ArgumentException。

GetValue<T>()​

public T GetValue<T>()

泛型读取,将值转换为指定类型。

SDOWrite()​

public bool SDOWrite(byte[] data, bool useCompleteAccess = false)

对子对象进行 SDO 写入。

相关属性:

  • CanRead (bool) — 是否可读
  • CanWrite (bool) — 是否可写
  • IsReadOnly (bool) — 是否只读
  • CanWritePreOp (bool) — PreOp 状态下可写
  • CanWriteSafeOp (bool) — SafeOp 状态下可写
  • CanWriteOp (bool) — OP 状态下可写
  • AccessDescription (string) — 权限描述文字
  • LastBytesReadOk (bool) — 上次 Bytes 读取是否真成功(Bytes 读失败折叠 byte[0],此标志负责区分)
  • TryGetBytes(out byte[] data, out SDOError error) (bool) — 读取原始字节并给出失败错误码,不折叠失败

示例:

var entry = slave.CoE[0x6040][0];
if (entry.CanWrite)
entry.SDOWrite(BitConverter.GetBytes((ushort)0x0006));

LastSdoError​

public SDOError LastSdoError { get; }

最近一次 SDO 操作的错误代码。每次 SDORead / SDOWrite 调用后自动更新。成功时为 SDOError.NoError,失败时为对应的 Abort Code。

GeneralError 不是「对象不存在」
  • SDOError.GeneralError (0x08000000) — WKC=0 / 邮箱超时 / 通信未回。abort 可能是上一次残留,不当对象不存在,也 不写入 NotPresent 缓存。
  • 真 abort 才进 NotPresent(后续同 index:sub 直接返 null、不再发 SDO):ObjectDoesNotExist (0x06020000)、SubindexDoesNotExist (0x06090011)、0x06090012。
  • 空 byte[0] + NoError 是空字符串成功(如 0x1009),与失败 null 不同。

示例:

var data = slave.CoE.SDORead(0x6040, 0);
if (data == null)
{
Console.WriteLine($"SDO 读取失败: {slave.CoE.LastSdoError}");
if (slave.CoE.LastSdoError == SDOError.GeneralError)
Console.WriteLine("WKC=0 / 超时,不是对象不存在");
}

SDO 直接读写​

SDORead()​

public byte[] SDORead(ushort index, byte subindex, bool completeAccess = false)

读取 SDO 原始字节数据。

返回值:

  • 成功:byte[](WKC > 0 且 abort=0;长度为 0 表示空 VISIBLE_STRING 成功,如 0x1009 常空)
  • 失败 / WKC=0:返回 null。邮箱没回来不是「已成功的空对象」,禁止对返回值直接 BitConverter.ToUInt32
  • 单次读取上限 64 KB:应答数据超过 64 KB 时按读失败返回 null(LastSdoError 如实记录),超大对象请拆分读取
必须判空
  • LastSdoError = GeneralError 不是「对象不存在」
  • 真 abort(ObjectDoesNotExist / SubindexDoesNotExist / 0x06090012)才进 NotPresent 缓存
  • WKC=0 的 GeneralError 不写入 NotPresent,避免把「没收到回复」一直当成空对象

示例:

byte[] data = slave.CoE.SDORead(0x1000, 0);
if (data == null)
{
Console.WriteLine($"SDO 读取失败: {slave.CoE.LastSdoError}");
return;
}
uint typeValue = BitConverter.ToUInt32(data, 0);
访问

通过对象字典可直接获取类型化的值,无需手动转换:

ushort statusWord = slave.CoE[0x6041][0].Value;

ReadMultipleAsync()​

public async Task<Dictionary<(ushort index, byte subindex), byte[]>> ReadMultipleAsync(
IEnumerable<(ushort index, byte subindex)> entries,
CancellationToken cancellationToken = default)

批量 SDO 读取 — 一次调用读取多个对象。异步执行,支持取消。读取失败的条目值为 null。

参数:

  • entries (IEnumerable<(ushort, byte)>) — 要读取的 (index, subindex) 列表
  • cancellationToken (CancellationToken) — 取消令牌

返回值:

  • Dictionary<(ushort, byte), byte[]> — 结果字典,key=(index, subindex),value=数据(失败为 null)

示例:

var entries = new[] {
((ushort)0x1018, (byte)1), // VendorID
((ushort)0x1018, (byte)2), // ProductCode
((ushort)0x6041, (byte)0), // StatusWord
};
var results = await slave.CoE.ReadMultipleAsync(entries, cancellationToken);
foreach (var (key, data) in results)
{
if (data != null)
Console.WriteLine($"0x{key.index:X4}:{key.subindex:X2} = {BitConverter.ToString(data)}");
}

SDOError 枚举​

SDO 操作错误代码(CANopen 标准 / ETG.1020)。

public enum SDOError : uint
{
NoError = 0x00000000, // 无错误
ToggleBitNotChanged = 0x05030000, // Toggle 位未改变
SDOProtocolTimeout = 0x05040000, // SDO 协议超时
InvalidCommandSpecifier = 0x05040001, // 无效的命令标识符
OutOfMemory = 0x05040005, // 内存不足
UnsupportedAccess = 0x06010000, // 不支持的访问
ReadWriteOnlyError = 0x06010001, // 尝试读取只写对象
WriteReadOnlyError = 0x06010002, // 尝试写入只读对象
SubindexWriteError = 0x06010003, // 子索引不允许写入
CompleteAccessNotSupported = 0x06010004, // 不支持完全访问
ObjectLengthExceeded = 0x06010005, // 对象长度超限
ObjectMappedToRxPDO = 0x06010006, // 对象已映射到 RxPDO
ObjectDoesNotExist = 0x06020000, // 对象不存在
CannotBeMappedToPDO = 0x06040041, // 不可映射到 PDO
ExceedsPDOLength = 0x06040042, // 超出 PDO 长度
ParameterIncompatibility = 0x06040043, // 参数不兼容
InternalIncompatibility = 0x06040047, // 内部不兼容
HardwareAccessError = 0x06060000, // 硬件访问错误
DataTypeMismatch = 0x06070010, // 数据类型不匹配
DataTypeTooHigh = 0x06070012, // 数据类型长度过大
DataTypeTooLow = 0x06070013, // 数据类型长度过小
SubindexDoesNotExist = 0x06090011, // 子索引不存在
ValueRangeExceeded = 0x06090030, // 值超出范围
ValueTooHigh = 0x06090031, // 值过大
ValueTooLow = 0x06090032, // 值过小
ModuleIdentListMismatch = 0x06090033, // 模块列表不匹配
MaxLessThanMin = 0x06090036, // 最大值小于最小值
GeneralError = 0x08000000, // 一般错误
DataTransferError = 0x08000020, // 数据传输错误
LocalControlError = 0x08000021, // 本地控制错误
DeviceStateError = 0x08000022, // 设备状态错误
DictionaryError = 0x08000023, // 字典错误
UnknownError = 0xFFFFFFFF // 未知错误
}

EMCY 紧急消息​

CoE 支持 CiA 301 紧急消息 (Emergency) 记录。每个从站的 CoE 实例自动收集该从站的 EMCY 消息。

MaxEmcyHistorySize​

public int MaxEmcyHistorySize { get; set; }

EMCY 历史记录最大容量(默认 256)。超出容量时自动丢弃最旧的消息。最小值为 1。

GetEmergencyHistory()​

public List<EmergencyMessage> GetEmergencyHistory()

获取该从站的 EMCY 紧急消息历史记录(返回副本)。最多保留最近 MaxEmcyHistorySize 条。

相关结构:

public struct EmergencyMessage
{
public ushort ErrorCode; // 紧急错误代码 (CiA 301 Table 24)
public byte ErrorRegister; // 错误寄存器 (对象 0x1001)
public byte[] Data; // 厂商特定数据 (5 字节)
public int SlaveIndex; // 来源从站编号
public DateTime Timestamp; // 接收时间

// 获取错误代码的文本描述 (CiA 301 标准错误分类)
public string GetErrorDescription();
}

GetErrorDescription() 返回错误代码的文本描述(CiA 301 + ETG.1000.6 标准错误分类,16 位错误码精确查表,未命中时按错误分类回退),如 "电流错误"、"温度错误"、"通信错误" 等。

ClearEmergencyHistory()​

public void ClearEmergencyHistory()

清除该从站的 EMCY 历史记录。

示例:

// 读取 EMCY 历史
var history = slave.CoE.GetEmergencyHistory();
foreach (var msg in history)
{
Console.WriteLine($"EMCY 0x{msg.ErrorCode:X4}: {msg.GetErrorDescription()} " +
$"寄存器=0x{msg.ErrorRegister:X2} 时间={msg.Timestamp:HH:mm:ss.fff}");
}

// 调整历史容量
slave.CoE.MaxEmcyHistorySize = 100;

// 清除历史
slave.CoE.ClearEmergencyHistory();

完整示例​

遍历对象字典​

bool loaded = await slave.CoE.LoadODListAsync();
if (!loaded || slave.CoE.Count == 0)
{
Console.WriteLine($"对象字典加载失败: {slave.CoE.LastSdoError}");
return;
}
foreach (var item in slave.CoE)
{
Console.WriteLine($"0x{item.Key:X4}: {item.Value.Name}");
foreach (var entry in item.Value.OEList)
{
byte[] raw = slave.CoE.SDORead(item.Key, entry.Index);
if (raw == null)
{
Console.WriteLine($" [{entry.Index}] {entry.Name}: 读失败 {slave.CoE.LastSdoError}");
continue;
}
Console.WriteLine($" [{entry.Index}] {entry.Name}: {raw.Length} 字节");
}
}

诊断历史 0x10F3 (Diagnosis History)​

CiA 301 / ETG.1020 §16 规定了对象 0x10F3 (Diagnosis History) 作为从站向主站报告运行期故障、事件、警告的标准通道。与 EMCY 的"瞬时紧急消息"不同, 0x10F3 是一段持久化的环形缓冲区, 存在从站里最近 N 条诊断记录, 主站可随时翻看 / 确认 / 清理。

典型应用:

  • 设备故障排查 — 从站温度过高、力矩过载、编码器断线等故障码完整保留
  • 从站自报告故障 — 硬件层自动生成诊断条目, 上位机无需主动轮询每个对象
  • 按 FIFO / Ring 语义消费 — 主站消费后 Acknowledge 让从站清理, 避免缓冲区溢出
与 EMCY 的区别
  • EMCY (0x80 CAN ID) — 事件到达瞬间触发, 不保证补发, 适合 RT 通知
  • 0x10F3 — 从站持久化, 宕机重连后仍能读到, 适合事后分析

PollHasNewDiagnostic()​

public bool PollHasNewDiagnostic()

快速轮询 0x10F3:04 NewMessagesAvailable 标志, 不触发完整的 SDO 历史拉取, 适合高频应用层轮询。

返回值:

  • bool — true 表示有新消息, false 表示无消息或通信失败

ReadDiagnosticMeta()​

public DiagMeta? ReadDiagnosticMeta()

读取诊断历史元数据, 包括最大容量、最新消息编号、已确认编号、标志位。

返回值:

  • DiagMeta? — 元数据对象, 通信失败返回 null

相关结构:

public class DiagMeta
{
public byte MaxMessages { get; } // 0x10F3:01 从站支持的最大消息数 (通常 <= 250)
public byte NewestMessage { get; } // 0x10F3:02 最新消息所在 subindex
public byte NewestAcknowledged { get; } // 0x10F3:03 用户已确认的最新 subindex
public ushort Flags { get; } // 0x10F3:05 标志位
}

Flags 按位解读:

  • bit 4 — Ring/Linear 模式 (1=Ring, 0=Linear)
  • bit 5 — Overrun 标志 (1=缓冲区曾溢出丢帧)

AcknowledgeDiagnostic(byte subIndex)​

public bool AcknowledgeDiagnostic(byte subIndex)

写入 0x10F3:03 NewestAcknowledged, 告诉从站"到 subIndex 为止的消息都已处理"。Ring 模式下从站按此清理可回收空间。

参数:

  • subIndex (byte) — 已处理的最后一个 subindex (有效范围 6..255)

返回值:

  • bool — 成功返回 true

DiagnosticMessage​

public struct DiagnosticMessage
{
public byte SubIndex; // 0x10F3 子索引 (6..n)
public uint DiagCode; // 4 字节诊断编码 (厂商定义)
public ushort Flags; // 标志位 (严重度 / 类型)
public ushort TextIndex; // 文本索引 (对应 0x1000 字符串表)
public byte[] RawData; // 原始消息字节 (含 8 字节头 + 可变参数)
public DateTime Timestamp; // SDK 接收 / 轮询时刻 (UTC)
public int SlaveIndex; // 来源从站编号

public override string ToString();
}

ETG.1510 诊断消息结构。Flags 高字节 = 严重度 (0=Info, 1=Warning, 2=Error),低字节 = 类型;TextIndex 可在 0x1000 区域字符串表中查到中文化描述。RawData 保留原始字节供需要厂商扩展解析的场景。

示例:

foreach (var msg in slave.CoE.ReadDiagnosticMessages())
{
string severity = (msg.Flags & 0x00FF) switch
{
0 => "Info",
1 => "Warning",
2 => "Error",
_ => "Unknown"
};
Console.WriteLine(
$"[Slave {msg.SlaveIndex}] SI={msg.SubIndex} {severity} " +
$"Code=0x{msg.DiagCode:X8} TextIndex=0x{msg.TextIndex:X4} " +
$"@ {msg.Timestamp:HH:mm:ss.fff}");
}

完整示例 — 轮询并消费诊断历史​

if (slave.CoE == null) return;

// 1. 订阅订阅事件 — EMCY 到达时 SDK 会自动拉取增量消息
slave.CoE.DiagnosticMessageReceived += (sender, e) =>
{
foreach (var msg in e.NewMessages)
{
Console.WriteLine(
$"[从站 {msg.SlaveIndex}] 诊断 SubIndex={msg.SubIndex} " +
$"Code=0x{msg.DiagCode:X8} Flags=0x{msg.Flags:X4} " +
$"Text=0x{msg.TextIndex:X4} @ {msg.Timestamp:HH:mm:ss.fff}");
}
};

// 2. 应用层定时轮询 (低频, 1s 一次即可)
while (running)
{
if (slave.CoE.PollHasNewDiagnostic())
{
var meta = slave.CoE.ReadDiagnosticMeta();
if (meta != null)
{
Console.WriteLine(
$"诊断元数据: 最大 {meta.MaxMessages} 条, " +
$"最新={meta.NewestMessage}, 已确认={meta.NewestAcknowledged}, " +
$"Ring={((meta.Flags & 0x10) != 0)}, Overrun={((meta.Flags & 0x20) != 0)}");

// 3. 增量拉取
var messages = slave.CoE.ReadDiagnosticMessages();

// 4. 处理并确认
if (meta.NewestMessage > meta.NewestAcknowledged)
{
slave.CoE.AcknowledgeDiagnostic(meta.NewestMessage);
}
}
}
Thread.Sleep(1000);
}

注意事项​

  • 0x10F3 是可选对象, 从站若不实现则 ReadDiagnosticMeta() 返回 null, 应用层需做 fallback 到 EMCY 监听
  • Ring 模式下超出 MaxMessages 时最旧消息被覆盖, Overrun 位会被置 1
  • 首次读取建议一次性读完所有未确认消息再 Acknowledge, 避免遗漏
  • SDK 内部的 ReadDiagnosticMessages() 已做去重与本地缓存 (默认保留最近 256 条), 多次调用不会重复入库

SDO 扩展接口​

SDOReadEx(ushort index, byte subindex, bool completeAccess = false)​

public SdoReadResult SDOReadEx(ushort index, byte subindex, bool completeAccess = false)

SDO 读取扩展版 (CoE-H1): 显式返回 abort code + WKC. 直通底层库, 调用方可区分 "对象不存在 (0x06020000)" / "数据类型不匹配 (0x06070010)" / "值超范围 (0x06090030)" 等具体中止码。

参数:

  • index (ushort) — 对象索引
  • subindex (byte) — 子索引
  • completeAccess (bool) — Complete Access (ETG.1000.6 §5.6.4), 启用时 subindex 必须为 0 或 1

返回值:

  • SdoReadResult — 含 Ok / Data / AbortCode / Error / Wkc / ExpectedWkc 字段
  • 应答数据超过 64 KB 时按失败返回(Ok=false,Data=null),超大对象请拆分读取

相关结构:

public readonly struct SdoReadResult
{
public bool Ok { get; } // 操作是否成功
public byte[]? Data { get; } // 读到的数据 (失败时为 null)
public uint AbortCode { get; } // CoE Abort 码 (成功时为 0)
public SDOError Error { get; } // AbortCode 对应的 SDOError 枚举值 (便于 switch)
public int Wkc { get; } // 返回 WKC
public int ExpectedWkc { get; } // 期望 WKC (SDO 邮箱单从站寻址 = 1)
}

示例:

var r = slave.CoE.SDOReadEx(0x6041, 0);
if (r.Ok)
{
ushort statusword = BitConverter.ToUInt16(r.Data!, 0);
Console.WriteLine($"状态字: 0x{statusword:X4}");
}
else
{
Console.WriteLine($"读失败 abort=0x{r.AbortCode:X8} wkc={r.Wkc}");
}

SDOWriteEx(ushort index, byte subindex, byte[] data, bool completeAccess = false)​

public SdoWriteResult SDOWriteEx(ushort index, byte subindex, byte[] data, bool completeAccess = false)

SDO 写入扩展版 (CoE-H6): 显式返回 abort code + WKC.

参数:

  • index (ushort) — 对象索引
  • subindex (byte) — 子索引
  • data (byte[]) — 写入数据(空数组 / null 直接返回 Ok=false,不抛异常)
  • completeAccess (bool) — Complete Access, 启用时 subindex 必须为 0 或 1

返回值:

  • SdoWriteResult — 含 Ok / AbortCode / Error / Wkc / ExpectedWkc 字段(Error 为 AbortCode 对应的 SDOError 枚举值)

示例:

var r = slave.CoE.SDOWriteEx(0x6040, 0, BitConverter.GetBytes((ushort)0x0F));
if (!r.Ok)
Console.WriteLine($"写失败 abort=0x{r.AbortCode:X8}");

OD (对象字典) 描述符查询​

按需查询单个对象/子索引的元信息, 比 LoadODListAsync 强制加载整表轻量, 适合 ESI vs 实测对比 / 诊断 UI 单条查询。

GetObjectDictionaryIndices()​

public ushort[] GetObjectDictionaryIndices()

读取对象索引清单 (ETG.1000.6 Table 34, SDO Info Service 0x01). 不读描述, 仅返回索引数组, 速度远快于 LoadODListAsync.

返回值:

  • ushort[] — 对象索引数组 (按从站返回顺序), 失败返回空数组

示例:

ushort[] indices = slave.CoE.GetObjectDictionaryIndices();
Console.WriteLine($"OD 共 {indices.Length} 项");

GetObjectDescription(ushort index)​

public CoeObjectDescription? GetObjectDescription(ushort index)

读取单个对象的描述 (Table 35, SDO Info Service 0x03). 返回 DataType / MaxSubIndex / Name. 对象不存在或从站不支持 SDO Info 时返回 null.

参数:

  • index (ushort) — 对象索引

返回值:

  • CoeObjectDescription? — 对象描述

相关结构:

public class CoeObjectDescription
{
public ushort Index { get; init; }
public ushort DataType { get; init; }
public byte ObjectCode { get; init; }
public byte MaxSubIndex { get; init; }
public string Name { get; init; }
}

示例:

var desc = slave.CoE.GetObjectDescription(0x6041);
if (desc != null)
Console.WriteLine($"{desc.Name} type=0x{desc.DataType:X4} subs={desc.MaxSubIndex}");

GetEntryDescription(ushort index, byte subindex)​

public CoeEntryDescription? GetEntryDescription(ushort index, byte subindex)

读取单个子索引的描述 (Table 36, SDO Info Service 0x05). 返回 DataType / BitLength / ObjectAccess / Name.

返回值:

  • CoeEntryDescription? — 子索引描述

相关结构:

public class CoeEntryDescription
{
public ushort Index { get; init; }
public byte SubIndex { get; init; }
public byte ValueInfo { get; init; }
public ushort DataType { get; init; }
public ushort BitLength { get; init; }
public CoEObjectAccess ObjectAccess { get; init; }
public string Name { get; init; }
}

示例:

var entry = slave.CoE.GetEntryDescription(0x1A00, 1);
if (entry != null)
Console.WriteLine($"sub1: {entry.Name}, bits={entry.BitLength}");

异常类型​

CoE_AccessDeniedException​

public class CoE_AccessDeniedException : Exception
{
public ushort Index { get; }
public byte SubIndex { get; }
public string EntryName { get; }
public bool IsReadOperation { get; }
public ushort ObjAccess { get; }
}

CoE 对象访问被拒时由 SDO 写/读路径主动抛出。 通常意味着 PDO 映射对象在 OP 状态下被尝试改写, 或条目明确只读(OD 表只在明确只读时才拦, 残缺 OD 缓存放行、由从站 SDO abort 当真相源,ECM-SDDOC1)。 SDO 写门槛为 PreOp 及以上(Init 拒)。 调用方可选择捕获后回退到 PreOp 重试。

示例:

try
{
slave.CoE.SDOWrite(0x1C12, 0, BitConverter.GetBytes((ushort)0));
}
catch (CoE_AccessDeniedException ex)
{
Console.WriteLine($"对象 0x{ex.Index:X4}:{ex.SubIndex:X2} 当前不可写");
master.SetState(EcState.PreOp);
}

异步邮箱事务 (低层 API)​

DarraEtherCAT_Master.Abstractions 命名空间提供一个协议无关的低层邮箱事务类 MailboxTransaction,底层对接内核的异步邮箱 Worker。它直接收发完整邮箱帧(含 6 字节 MbxHeader),适用于自定义协议封装、批量邮箱操作、可取消的长事务等高级场景。

优先用高层 API

普通的 SDO 读写请用 slave.CoE.SDORead / SDOWrite 等高层 API——它们已封装帧构造和错误处理。MailboxTransaction 仅在需要直接控制原始邮箱帧时使用。

MailboxTransaction 实现 IMailboxTransaction 接口,CoE/FoE/SoE/AoE/EoE/VoE/FSoE 全部共用同一套状态码 MailboxStatus。

主要成员:

类别成员类型读写说明
配置MasterIndexint读写主站索引(0-based)
配置SlaveIndexint读写从站索引(1-based)
配置ProtocolTypebyte读写邮箱协议类型:CoE=0x03, FoE=0x04, SoE=0x05, AoE=0x01, EoE=0x02, VoE=0x0F, FSoE=0x08
配置TimeoutMsint读写事务超时(毫秒),默认 5000
配置RequestFramebyte[]?读写请求邮箱帧(完整帧,含 MbxHeader)
配置ResponseCapacityint读写响应缓冲容量(字节),默认 1500
结果StatusMailboxStatus只读事务最终状态
结果ErrorCodeuint只读协议层错误码(0 = 无)
结果ResponseFramebyte[]?只读响应邮箱帧(成功后填充)
结果ResponseLengthushort只读响应帧实际长度(字节,含 MbxHeader)
结果ResponseCounterbyte只读响应帧邮箱计数器(低 3 位,调试用途)
结果SubmitTime / CompleteTimeDateTime?只读提交 / 完成时刻(UTC)

方法:

public MailboxStatus Execute()
public Task<MailboxStatus> ExecuteAsync(CancellationToken ct = default)
  • Execute() — 同步执行:投递事务并阻塞等待完成。
  • ExecuteAsync(ct) — 异步执行:支持细粒度取消。CancellationToken 触发时立即通知 C 层 Worker 取消,无需等待 TimeoutMs 超时。

示例:

using DarraEtherCAT_Master.Abstractions;

// 构造一个 CoE 邮箱事务 (RequestFrame 为完整邮箱帧)
var txn = new MailboxTransaction
{
MasterIndex = 0,
SlaveIndex = 1,
ProtocolType = 0x03, // CoE
TimeoutMs = 3000,
RequestFrame = coeRequestFrame, // 含 6 字节 MbxHeader 的完整帧
};

// 同步执行
MailboxStatus status = txn.Execute();
if (status == MailboxStatus.Success)
{
Console.WriteLine($"响应 {txn.ResponseLength} 字节, 错误码=0x{txn.ErrorCode:X8}");
byte[] response = txn.ResponseFrame!;
}
else
{
Console.WriteLine($"事务失败: {status}");
}

// 异步执行 + 可取消
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
MailboxStatus asyncStatus = await txn.ExecuteAsync(cts.Token);

MailboxStatus 枚举​

所有邮箱协议共用的统一状态码(数值与底层内核邮箱状态码一致)。

public enum MailboxStatus
{
Pending = 0, // 待处理(事务尚未发起或仍在进行中)
Success = 1, // 成功完成
Timeout = -1, // 超时(对端未在规定时间内响应)
Cancelled = -2, // 已取消(通过 CancellationToken 或主动取消)
MailboxError = -3, // 邮箱错误帧(对端返回 MBX ERROR / Abort)
ProtoMismatch = -4, // 响应协议类型不匹配
CounterFail = -5, // 响应邮箱计数器校验失败
WkcFail = -6, // 底层工作计数器 (WKC) 不符合预期
NoMemory = -7, // 内存不足
InvalidArg = -8, // 非法参数
NotImplemented = -9 // 未实现 / 不支持
}