跳到主要内容

CoE (CANopen over EtherCAT)

CoE 协议实现了 CANopen over EtherCAT 通信,提供对象字典管理和 SDO 读写功能。

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

读写方式

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

slave.CoE                        → CoEInstance(对象字典集合)
└─ [0x6040] → ObjectDictionary(一个对象)
└─ [0] → ObjectEntry(一个子对象)
├─ .Value → 类型化读写(自动转换,推荐)
└─ .Bytes → 原始字节读写

示例:

// 读取 — 自动类型转换
ushort status = slave.CoE[0x6041][0].Value;
int position = slave.CoE[0x6064][0].Value;
string name = slave.CoE[0x1008][0].Value;

// 写入 — 需显式指定类型
slave.CoE[0x6040][0].Value = (ushort)0x0006;
slave.CoE[0x607A][0].Value = 100000;

// 按字节写入
slave.CoE[0x6040][0].Bytes = BitConverter.GetBytes((ushort)0x0006);

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 od = 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)

异步加载对象字典结构,不阻塞调用线程。

行为:

  • 已加载成功 → 直接返回 true
  • 正在加载中 → 不重复启动,返回 false
  • 加载过程中 ODList 逐步填充,可边加载边遍历已加载部分
  • 支持通过 CancellationToken 取消,已加载的部分仍保留

示例:

// 异步加载,不阻塞主线程
bool loaded = await slave.CoE.LoadODListAsync();
if (loaded)
{
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 CoEInstance 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[]原始数据
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);写入时需显式指定目标类型,自动转换为字节写入。

支持隐式转换的 C# 类型:boolbytesbyteshortushortintuintlongulongfloatdoublestring

示例:

// 读取 — 根据 DataType 自动转换
ushort status = slave.CoE[0x6041][0].Value; // Unsigned16 → ushort
int position = slave.CoE[0x6064][0].Value; // Integer32 → int
string name = slave.CoE[0x1008][0].Value; // VisibleString → string

// 写入 — 需显式指定类型,自动转换为对应字节
slave.CoE[0x6040][0].Value = (ushort)0x0006; // ushort → 2字节
slave.CoE[0x607A][0].Value = 100000; // int → 4字节

Bytes

public byte[] Bytes { get; set; }

原始字节读写。

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) — 权限描述文字

示例:

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

SDO 直接读写

SDORead()

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

读取 SDO 原始字节数据。

示例:

byte[] data = slave.CoE.SDORead(0x1000, 0);
uint typeValue = BitConverter.ToUInt32(data, 0);
便捷访问

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

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

ReadSDOAsync() / ReadSDOAsync<T>()

public async Task<byte[]> ReadSDOAsync(ushort index, byte subindex, int timeoutMs = 2000)
public async Task<T> ReadSDOAsync<T>(ushort index, byte subindex, int timeoutMs = 2000)

异步读取 SDO 数据。

示例:

int velocity = await slave.ReadSDOAsync<int>(0x606C, 0);

WriteSDOAsync() / WriteSDOAsync<T>()

public async Task WriteSDOAsync(ushort index, byte subindex, byte[] data, int timeoutMs = 2000)
public async Task WriteSDOAsync<T>(ushort index, byte subindex, T value, int timeoutMs = 2000)

异步写入 SDO 数据。

示例:

await slave.WriteSDOAsync<int>(0x60FF, 0, 1000);

相关结构:

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 // 未知错误
}

完整示例

遍历对象字典

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

异步批量读取

var status   = slave.ReadSDOAsync<ushort>(0x6041, 0);
var position = slave.ReadSDOAsync<int>(0x6064, 0);
var velocity = slave.ReadSDOAsync<int>(0x606C, 0);
await Task.WhenAll(status, position, velocity);