CoE (CANopen over EtherCAT)
通过 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 EcODList 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;
LastSdoError
public SDOError LastSdoError { get; }
最近一次 SDO 操作的错误代码。每次 SDORead / SDOWrite 调用后自动更新。成功时为 SDOError.NoError,失败时为对应的 Abort Code。
示例:
var data = slave.CoE.SDORead(0x6040, 0);
if (data == null)
{
Console.WriteLine($"SDO 读取失败: {slave.CoE.LastSdoError}");
}
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;
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)}");
}
相关结构:
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 标准错误代码高字节返回分类描述,如 "电流错误"、"温度错误"、"通信错误" 等。
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();
完整示例
遍历对象字典
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}");
}
诊断历史 0x10F3 (Diagnosis History)
CiA 301 / ETG.1020 §16 规定了对象 0x10F3 (Diagnosis History) 作为从站向主站报告运行期故障、事件、警告的标准通道。与 EMCY 的"瞬时紧急消息"不同, 0x10F3 是一段持久化的环形缓冲区, 存在从站里最近 N 条诊断记录, 主站可随时翻看 / 确认 / 清理。
典型应用:
- 设备故障排查 — 从站温度过高、力矩过载、编码器断线等故障码完整保留
- 从站自报告故障 — 硬件层自动生成诊断条目, 上位机无需主动轮询每个对象
- 按 FIFO / Ring 语义消费 — 主站消费后
Acknowledge让从站清理, 避免缓冲区溢出
- 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. 直通 DLL SDOread_ex, 调用方可区分 "对象不存在 (0x06020000)" / "数据类型不匹配 (0x06070010)" / "值超范围 (0x06090030)" 等具体中止码.
参数:
index(ushort) — 对象索引subindex(byte) — 子索引completeAccess(bool) — Complete Access (ETG.1000.6 §5.6.4), 启用时 subindex 必须为0或1
返回值:
SdoReadResult— 含Ok/Data/AbortCode/Wkc字段
相关结构:
public readonly struct SdoReadResult
{
public bool Ok { get; } // 操作是否成功
public byte[]? Data { get; } // 读到的数据 (失败时为 null)
public uint AbortCode { get; }// CoE Abort 码 (成功时为 0)
public int Wkc { get; } // 返回 WKC
}
示例:
var r = slave.CoE.SDOReadEx(0x6041, 0);
if (r.Ok)
ushort statusword = BitConverter.ToUInt16(r.Data!, 0);
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[]) — 写入数据 (不可为空)completeAccess(bool) — Complete Access, 启用时 subindex 必须为0或1
返回值:
SdoWriteResult— 含Ok/AbortCode/Wkc字段
示例:
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; set; }
public ushort DataType { get; set; }
public byte ObjectCode { get; set; }
public byte MaxSubIndex { get; set; }
public string Name { get; set; }
}
示例:
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; set; }
public byte SubIndex { get; set; }
public byte ValueInfo { get; set; }
public ushort DataType { get; set; }
public ushort BitLength { get; set; }
public CoEObjectAccess ObjectAccess { get; set; }
public string Name { get; set; }
}
示例:
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 ObjectAccess { get; }
}
CoE 对象访问被拒时由 SDO 写/读路径主动抛出. 通常意味着 PDO 映射对象在 OP 状态下被尝试改写, 或 ObjectAccess 标记禁止当前状态访问. 调用方可选择捕获后回退到 PreOp 重试.
示例:
try
{
slave.CoE.SDOwrite(0x1C12, 0, BitConverter.GetBytes((byte)0));
}
catch (CoE_AccessDeniedException ex)
{
Console.WriteLine($"对象 0x{ex.Index:X4}:{ex.SubIndex:X2} 当前不可写");
master.SetState(EcState.PreOp);
}