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 控制字),包含若干子对象。
| 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|
| Index | ushort | 只读 | 对象索引号 |
| Name | string | 只读 | 对象名称 |
| Datatype | ushort | 只读 | 数据类型标识 |
| Objectcode | ushort | 只读 | 对象代码(变量/数组/记录) |
| Count | int | 只读 | 子对象数量 |
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),是最终的数据读写节点。
| 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|
| Index | byte | 只读 | 子索引 |
| ODIndex | ushort | 只读 | 父对象索引 |
| Name | string | 只读 | 名称 |
| DataType | EcDataType | 只读 | 数据类型(决定 Value 的自动转换目标,见类型映射) |
| BitLength | uint | 只读 | 位长度 |
| ObjAccess | CoEObjectAccess | 只读 | 访问权限标志 |
EcDataType 枚举
EtherCAT 数据类型枚举(基于 ETG.1000.6),决定 Value 属性的自动转换目标类型。
| 枚举值 | 值 | C# 类型 | 说明 |
|---|---|---|---|
| Boolean | 0x0001 | bool | 布尔 |
| Integer8 | 0x0002 | sbyte | int8 |
| Integer16 | 0x0003 | short | int16 |
| Integer32 | 0x0004 | int | int32 |
| Unsigned8 | 0x0005 | byte | uint8 |
| Unsigned16 | 0x0006 | ushort | uint16 |
| Unsigned32 | 0x0007 | uint | uint32 |
| Real32 | 0x0008 | float | 单精度浮点 |
| VisibleString | 0x0009 | string | ASCII 字符串 |
| OctetString | 0x000A | byte[] | 字节串 |
| UnicodeString | 0x000B | string | Unicode 字符串 |
| Real64 | 0x0011 | double | 双精度浮点 |
| Integer64 | 0x0015 | long | int64 |
| Unsigned64 | 0x001B | ulong | uint64 |
| TimeOfDay | 0x000C | DateTime | 时间 |
| TimeDifference | 0x000D | TimeSpan | 时间差 |
| Domain | 0x000F | byte[] | 原始数据 |
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# 类型:bool、byte、sbyte、short、ushort、int、uint、long、ulong、float、double、string。
示例:
// 读取 — 根据 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);