CoE (CANopen over EtherCAT)
通过 slave.GetCoE() 获取 CoE& 引用。从站不支持 CoE 时相关方法返回空值或 false。
快速开始
auto& coe = master.GetSlave(1).GetCoE();
// 读取 SDO (原始字节) — 失败 / WKC=0 返回空 vector,必须先判空再 memcpy
auto data = coe.SDORead(0x6041, 0);
if (data.empty()) {
auto err = coe.last_sdo_error();
printf("SDO 失败: 0x%08X\n", static_cast<uint32_t>(err)); // GeneralError ≠ 对象不存在
return;
}
uint16_t status_word;
std::memcpy(&status_word, data.data(), 2);
printf("StatusWord: 0x%04X\n", status_word);
// 写入 SDO (原始字节)
uint16_t control_word = 0x0006;
coe.SDOWrite(0x6040, 0,
reinterpret_cast<const uint8_t*>(&control_word),
sizeof(control_word));
不要对空 vector 直接 memcpy / 当 uint32_t 解析。last_sdo_error() == SDOError::GeneralError 是邮箱没回来(WKC=0 / 超时),不是「对象不存在」。确定性 abort(不支持访问、对象/子索引不存在:ObjectDoesNotExist 0x06020000 / SubindexDoesNotExist 0x06090011 / 0x06090012)才进 NotPresent 缓存。
SDO 读写
SDORead()
std::vector<uint8_t> SDORead(uint16_t index, uint8_t subindex = 0,
bool completeAccess = false) const;
读取 SDO 原始字节数据。失败 / WKC=0 返回空 vector。空成功(空 VISIBLE_STRING)也是空 vector,用 last_sdo_error() 区分:NoError = 空字符串成功,GeneralError = WKC=0 / 超时。
单次读取长度上限 64 KB:SDO 读取与 OD 枚举的应答数据超过 64 KB 时按读失败返回(空 std::vector,last_sdo_error() 如实记录),超大对象请拆分读取。
- 不要对返回值直接
memcpy成uint32_t SDOError::GeneralError (0x08000000)不是「对象不存在」,不写入 NotPresent- 确定性 abort 才进 NotPresent(不支持访问、对象/子索引不存在):
ObjectDoesNotExist/SubindexDoesNotExist/0x06090012
SDOReadEx()
SDOReadExResult SDOReadEx(uint16_t index, uint8_t subindex = 0,
bool completeAccess = false) const;
返回 ok / data / abort_code / wkc 四个字段(wkc 为底层真值,失败路径为 0;仅缓存短路与底层库不可用回落两条路径按 ok 合成 1/0)。dx_sdo_read_ex 不可用时回落 SDORead。热插拔后调 ResetNotPresentCache();ClearSdoCaches() 与其等价(C++ 层无邮箱值缓存)。应答数据超过 64 KB 时按失败返回(ok=false,data 为空),超大对象请拆分读取。
SDOWrite()
bool SDOWrite(uint16_t index, uint8_t subindex,
const uint8_t* data, int dataLen,
bool completeAccess = false) const;
bool SDOWrite(uint16_t index, uint8_t subindex,
const std::vector<uint8_t>& data,
bool completeAccess = false) const;
写入 SDO 数据。支持原始指针和 std::vector 两种方式。
类型化 SDO 读取
所有类型化读取方法返回 std::optional,失败时为 std::nullopt:
auto& coe = master.GetSlave(1).GetCoE();
// 读取各类型
auto sw = coe.SDOReadU16(0x6041, 0); // std::optional<uint16_t>
auto pos = coe.SDOReadI32(0x6064, 0); // std::optional<int32_t>
auto vid = coe.SDOReadU32(0x1018, 1); // std::optional<uint32_t>
auto temp = coe.SDOReadFloat(0x6078, 0); // std::optional<float>
auto name = coe.SDOReadString(0x1008, 0); // std::optional<std::string>
if (sw) printf("StatusWord: 0x%04X\n", *sw);
if (pos) printf("位置: %d\n", *pos);
| 类别 | 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 类型化 SDO 读取 | SDOReadU8(index, sub) | std::optional<uint8_t> | 只读 | 无符号 8 位 |
| SDOReadU16(index, sub) | std::optional<uint16_t> | 只读 | 无符号 16 位 | |
| SDOReadU32(index, sub) | std::optional<uint32_t> | 只读 | 无符号 32 位 | |
| SDOReadI16(index, sub) | std::optional<int16_t> | 只读 | 有符号 16 位 | |
| SDOReadI32(index, sub) | std::optional<int32_t> | 只读 | 有符号 32 位 | |
| SDOReadFloat(index, sub) | std::optional<float> | 只读 | 32 位浮点 | |
| SDOReadString(index, sub) | std::optional<std::string> | 只读 | 字符串 |
类型化 SDO 写入
auto& coe = master.GetSlave(1).GetCoE();
// 类型化写入
coe.SDOWriteU16(0x6040, 0, 0x0006); // ControlWord
coe.SDOWriteU32(0x607A, 0, 100000); // TargetPosition (uint32)
coe.SDOWriteI32(0x607A, 0, 100000); // TargetPosition (int32)
coe.SDOWriteU8(0x6060, 0, 8); // 操作模式 CSP
| 类别 | 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 类型化 SDO 写入 | SDOWriteU8(index, sub, val) | uint8_t | 只写 | 无符号 8 位 |
| SDOWriteU16(index, sub, val) | uint16_t | 只写 | 无符号 16 位 | |
| SDOWriteU32(index, sub, val) | uint32_t | 只写 | 无符号 32 位 | |
| SDOWriteI16(index, sub, val) | int16_t | 只写 | 有符号 16 位 | |
| SDOWriteI32(index, sub, val) | int32_t | 只写 | 有符号 32 位 |
类型化 SDO 补充
以下类型化方法同样可用:
| 类别 | 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 类型化 SDO 补充 | SDOReadU64(index, sub) | std::optional<uint64_t> | 只读 | 无符号 64 位 |
| SDOReadDouble(index, sub) | std::optional<double> | 只读 | 64 位浮点 | |
| SDOWriteU64(index, sub, val) | uint64_t | 只写 | 无符号 64 位 | |
| SDOWriteFloat(index, sub, val) | float | 只写 | 32 位浮点 | |
| SDOWriteDouble(index, sub, val) | double | 只写 | 64 位浮点 |
属性
| 类别 | 属性 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 诊断 | last_sdo_error() | SDOError | 只读 | 最后一次 SDO 操作的错误码 |
last_sdo_error()
SDOError last_sdo_error() const;
C++ SDK 的方法名是 last_sdo_error()(snake_case),返回 SDOError 枚举(注意 SDO 三字母全大写)。
最后一次 SDO 操作的错误码。当 SDORead() / SDOWrite() 失败时,可通过此方法获取 SDO 中止码 (Abort Code)。成功操作后自动重置为 SDOError::NoError。
GeneralError 不是「对象不存在」SDOError::GeneralError (0x08000000)— WKC=0 / 邮箱超时 / 通信未回。abort 可能是上一次残留,不当对象不存在,也 不写入 NotPresent。- 确定性 abort 才进 NotPresent(不支持访问、对象/子索引不存在):
ObjectDoesNotExist (0x06020000)、SubindexDoesNotExist (0x06090011)、0x06090012。
缓存语义:
CoE 实例内部维护 last_sdo_error_ 成员(声明为 mutable,对调用方仍是 const 接口):
SDORead()/SDOWrite()/SDOReadXxx()/SDOWriteXxx()等所有 SDO 路径在返回前都会更新该缓存- 成功 → 缓存被清为
SDOError::NoError - 失败 → 缓存被设为底层映射的 SDO Abort Code(没有 Abort Code 时退化为
SDOError::GeneralError) - 缓存按 每个
CoE实例 隔离(即每个从站独立),不会被其他从站的 SDO 失败覆盖 - 缓存被下次同实例 SDO 调用覆盖, 不支持多线程并发读取 (调用方需在 SDO 调用与 last_sdo_error() 读取之间保持单线程顺序)
示例:
auto data = coe.SDORead(0x6041, 0);
if (data.empty()) {
auto error = coe.last_sdo_error();
printf("SDO 读取失败: 0x%08X\n", static_cast<uint32_t>(error));
}
SDOError 枚举
SDO 操作错误代码(CANopen 标准 / ETG.1020)。
| 名称 | 值 | 说明 |
|---|---|---|
| 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 | 未知错误 |
对象字典
auto& coe = master.GetSlave(1).GetCoE();
// 加载对象字典列表(真发 SDO Information)
// 空字典不是成功:指针空或 Count()==0 都是失败(WKC=0 不当已加载)
EcODList* odList = coe.LoadODList();
if (!odList || odList->Count() == 0) {
printf("对象字典加载失败: 0x%08X\n", static_cast<uint32_t>(coe.last_sdo_error()));
return;
}
printf("对象数量: %d\n", odList->Count());
// 异步加载。OP / SafeOp 只发 SDO Information Basic 0x01(GetSlaveSDOListBasic),
// 不全量读对象描述 0x03。PreOp 才完整加载子索引。
auto future = coe.LoadODListAsync();
EcODList* result = future.get();
if (!result || result->Count() == 0) {
printf("异步加载失败\n");
return;
}
EcODList 字段与方法:
| 类别 | 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| EcODList | Count() | int | 只读 | 对象数量 |
| Get(uint16_t idx) | const ObjectDictionary* | 只读 | 按索引获取 | |
| Get(const std::string& key) | const ObjectDictionary* | 只读 | 按名称获取 | |
| GetByPosition(int pos) | const ObjectDictionary* | 只读 | 按位置获取 | |
| ContainsKey(uint16_t idx) | bool | 只读 | 检查索引是否存在 | |
| ContainsKey(const std::string& key) | bool | 只读 | 检查名称是否存在 | |
| Copy() | EcODList | 只读 | 创建副本 |
支持 range-based for 循环遍历:
auto* odList = coe.LoadODList();
if (!odList || odList->Count() == 0) {
printf("对象字典加载失败: 0x%08X\n", static_cast<uint32_t>(coe.last_sdo_error()));
return;
}
for (auto& od : *odList) {
printf("0x%04X %s (%d 条目)\n", od.index, od.name.c_str(), od.Count());
}
ObjectDictionary 字段与方法:
| 类别 | 字段/方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| ObjectDictionary | index | uint16_t | 只读 | 对象索引 |
| name | std::string | 只读 | 对象名称 | |
| datatype | uint16_t | 只读 | 数据类型 | |
| object_code | uint8_t | 只读 | 对象代码 | |
| max_sub | uint8_t | 只读 | 最大子索引 | |
| entries | std::vector<ObjectEntry> | 只读 | 子索引条目 | |
| Count() | int | 只读 | 子索引数量 | |
| IsVar() | bool | 只读 | 是否为 VAR 类型 | |
| IsArray() | bool | 只读 | 是否为 ARRAY 类型 | |
| IsRecord() | bool | 只读 | 是否为 RECORD 类型 | |
| Get(uint8_t sub) | const ObjectEntry* | 只读 | 按子索引获取条目 | |
| ContainsKey(uint8_t sub) | bool | 只读 | 检查子索引是否存在 |
ObjectEntry 字段与方法:
| 类别 | 字段/方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| ObjectEntry | od_index | uint16_t | 只读 | 父对象索引 |
| sub_index | uint8_t | 只读 | 子索引 | |
| name | std::string | 只读 | 条目名称 | |
| data_type | uint16_t | 只读 | 数据类型 | |
| bit_length | uint16_t | 只读 | 位长度 | |
| obj_access | uint16_t | 只读 | 访问权限 | |
| ByteLength() | int | 只读 | 字节长度 | |
| CanRead() | bool | 只读 | 是否可读 | |
| CanWrite() | bool | 只读 | 是否可写 | |
| IsReadOnly() | bool | 只读 | 是否只读 | |
| AccessDescription() | std::string | 只读 | 访问权限中文描述 |
批量 SDO 读取
ReadMultiple()
std::vector<SDOReadResult> ReadMultiple(const std::vector<SDOReadEntry>& entries) const;
批量 SDO 读取 — 一次调用读取多个对象。同步版本,依次读取所有条目。
参数:
entries— 要读取的(index, subindex)列表
返回值:
struct SDOReadEntry {
uint16_t index; // 对象索引
uint8_t subindex; // 子索引
};
struct SDOReadResult {
uint16_t index; // 对象索引
uint8_t subindex; // 子索引
bool success; // 是否成功
std::vector<uint8_t> data; // 读取的数据 (失败时为空)
};
示例:
auto& coe = master.GetSlave(1).GetCoE();
// 批量读取多个对象
std::vector<CoE::SDOReadEntry> entries = {
{0x1018, 1}, // VendorID
{0x1018, 2}, // ProductCode
{0x6041, 0}, // StatusWord
{0x6064, 0}, // ActualPosition
};
auto results = coe.ReadMultiple(entries);
for (auto& r : results) {
if (r.success)
printf("0x%04X:%02X 读取成功 (%zu 字节)\n", r.index, r.subindex, r.data.size());
else
printf("0x%04X:%02X 读取失败\n", r.index, r.subindex);
}
ReadMultipleAsync()
std::future<std::vector<SDOReadResult>> ReadMultipleAsync(
const std::vector<SDOReadEntry>& entries) const;
批量 SDO 读取的异步版本,在后台线程中执行,不阻塞调用线程。
示例:
auto future = coe.ReadMultipleAsync(entries);
// ... 执行其他操作 ...
auto results = future.get(); // 等待完成
诊断消息 (ETG.1510)
auto messages = coe.ReadDiagnosticMessages();
for (auto& msg : messages) {
printf("诊断码: 0x%08X, 标志: 0x%04X\n", msg.diag_code, msg.flags);
}
跨线程上报诊断时, 可先 auto snaps = darra::sugar::slaves_snapshot(master); 拿 POD 快照再发到 worker 线程, 避免持有 dll_t&. 详见 数据快照.
设备协议检测
uint16_t profile = coe.GetDeviceProfile(); // 读取 0x1000 低 16 位
bool is402 = coe.IsCiA402(); // 是否为 CiA 402 伺服
bool is401 = coe.IsCiA401(); // 是否为 CiA 401 I/O
紧急消息历史
C++ wrapper 提供两套接口读取 EMCY:
CoE::GetEmergencyHistory()/ClearEmergencyHistory()— 通过 SDO 读写0x1003(CANopen Pre-defined Error Field), 按子索引逐条取得从站本地缓存的历史错误帧。darra::ethercat::slave_emcy命名空间下的自由函数 — 读取运行时实时捕获的 EMCY 事件缓存, 不触发 SDO.
GetEmergencyHistory()
std::vector<std::vector<uint8_t>> GetEmergencyHistory() const;
SDO 读取 0x1003. 返回的每一项为一帧原始 EMCY 字节 (按 ETG/CiA DS301 §7.2.8.1 定义, 至少包含 2 字节 Error Code + 1 字节 Error Register + 5 字节 Manufacturer-specific).
ClearEmergencyHistory()
bool ClearEmergencyHistory() const;
写入 0x1003:00 = 0 清空从站历史错误区。 成功返回 true.
slave_emcy 自由函数
namespace darra::ethercat::slave_emcy {
int emcy_count(dll_t& dll, uint16_t mi, uint16_t si);
std::vector<ec_emcy_record_t>
emcy_history(dll_t& dll, uint16_t mi, uint16_t si);
void clear_emcy(dll_t& dll, uint16_t mi, uint16_t si);
}
读取运行时实时 EMCY 环形缓冲, 返回 ec_emcy_record_t 数组。 不会触发额外 SDO 流量。 每条记录字段如下:
timestamp_ms(uint32_t) — 捕获时间戳 (毫秒)slave_index(uint16_t) — 从站索引error_code(uint16_t) — EMCY 错误码error_register(uint8_t) — 错误寄存器data(uint8_t[5]) — 厂商自定义字节
示例 (SDO 0x1003 方式):
auto& coe = master.GetSlave(1).GetCoE();
auto history = coe.GetEmergencyHistory();
printf("0x1003 历史条目: %zu\n", history.size());
for (auto& frame : history) {
if (frame.size() >= 3) {
uint16_t err_code;
std::memcpy(&err_code, frame.data(), 2);
uint8_t err_reg = frame[2];
printf("EMCY 错误码=0x%04X 寄存器=0x%02X\n", err_code, err_reg);
}
}
coe.ClearEmergencyHistory();
示例 (实时 EMCY 缓存方式):
using namespace darra::ethercat;
int n = slave_emcy::emcy_count(dll, mi, si);
auto records = slave_emcy::emcy_history(dll, mi, si);
for (auto& r : records) {
printf("EMCY t=%u ms slave=%u code=0x%04X reg=0x%02X\n",
r.timestamp_ms, r.slave_index, r.error_code, r.error_register);
}
slave_emcy::clear_emcy(dll, mi, si);
EcDataType 枚举
CoE 对象字典中使用的 EtherCAT 数据类型标识符。
| 名称 | 值 | C++ 类型 | 说明 |
|---|---|---|---|
| BOOLEAN | 0x0001 | bool | 布尔 |
| INTEGER8 | 0x0002 | int8_t | 有符号 8 位 |
| INTEGER16 | 0x0003 | int16_t | 有符号 16 位 |
| INTEGER32 | 0x0004 | int32_t | 有符号 32 位 |
| UNSIGNED8 | 0x0005 | uint8_t | 无符号 8 位 |
| UNSIGNED16 | 0x0006 | uint16_t | 无符号 16 位 |
| UNSIGNED32 | 0x0007 | uint32_t | 无符号 32 位 |
| REAL32 | 0x0008 | float | 单精度浮点 |
| VISIBLE_STRING | 0x0009 | std::string | 可见字符串 |
| OCTET_STRING | 0x000A | std::vector<uint8_t> | 字节数组 |
| UNICODE_STRING | 0x000B | std::string | Unicode 字符串 |
| TIME_OF_DAY | 0x000C | std::vector<uint8_t> | 时间(原始字节) |
| TIME_DIFF | 0x000D | std::vector<uint8_t> | 时间差(原始字节) |
| DOMAIN | 0x000F | std::vector<uint8_t> | 原始数据 |
| REAL64 | 0x0011 | double | 双精度浮点 |
| INTEGER64 | 0x0015 | int64_t | 有符号 64 位 |
| UNSIGNED64 | 0x001B | uint64_t | 无符号 64 位 |
对象字典遍历
auto* odList = coe.LoadODList();
if (!odList || odList->Count() == 0) {
printf("对象字典加载失败: 0x%08X\n", static_cast<uint32_t>(coe.last_sdo_error()));
return;
}
for (auto& od : *odList) {
printf("0x%04X %s (子索引数=%d)\n", od.index, od.name.c_str(), od.Count());
for (auto& entry : od.entries) {
printf(" [%d] %s (类型=0x%04X, %d位, %s)\n",
entry.sub_index, entry.name.c_str(),
entry.data_type, entry.bit_length,
entry.AccessDescription().c_str());
}
}
完整示例
#include "ethercat.hpp"
using namespace darra;
int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
master.SetState(EcState::OP);
master.Start();
auto& coe = master.GetSlave(1).GetCoE();
// 读取 Identity Object
auto vid = coe.SDOReadU32(0x1018, 1);
auto pid = coe.SDOReadU32(0x1018, 2);
if (vid && pid)
printf("VID=0x%08X PID=0x%08X\n", *vid, *pid);
// 读取 StatusWord
auto sw = coe.SDOReadU16(0x6041, 0);
if (sw) printf("StatusWord: 0x%04X\n", *sw);
// 写入 ControlWord
coe.SDOWriteU16(0x6040, 0, 0x0006);
// 设置操作模式为 CSP
coe.SDOWriteU8(0x6060, 0, 8);
// 完全访问模式读取 PDO 映射
auto pdo_map = coe.SDORead(0x1C12, 0, true);
if (!pdo_map.empty())
printf("PDO 映射数据: %zu 字节\n", pdo_map.size());
// 读取设备名称
auto name = coe.SDOReadString(0x1008, 0);
if (name) printf("设备名称: %s\n", name->c_str());
return 0;
}
CoE 诊断历史 (0x10F3, ETG.1020 §16)
从站在 0x10F3 对象下维护一个诊断消息环形缓冲区, 记录硬件故障、通信警告、参数超限等事件。 C++ wrapper 通过一次调用 ReadDiagnosticMessages() 拉取当前缓冲区内最多 20 条消息 (SDO 逐子索引读取)。如需细粒度的"是否有新消息"轻量轮询或逐条确认接口, 请直接使用 SDORead(0x10F3, ...) 按 ETG.1020 自行封装。
ReadDiagnosticMessages()
std::vector<DiagnosticMessage> ReadDiagnosticMessages() const;
读取 0x10F3 诊断历史, 返回 DiagnosticMessage 列表 (最多 20 条). 从站不支持或尚无消息时返回空 vector.
相关结构:
struct DiagnosticMessage {
uint8_t sub_index; // 子索引
uint32_t diag_code; // 诊断码 (ETG.1020 Table 65)
uint16_t flags; // 标志位
uint16_t text_index; // 文本索引
std::vector<uint8_t> raw_data; // 原始字节 (含上述头 + 参数)
};
示例:
auto& coe = master.GetSlave(1).GetCoE();
auto messages = coe.ReadDiagnosticMessages();
for (auto& msg : messages) {
printf("[Sub=%u] 诊断码: 0x%08X, Flags: 0x%04X, TextIdx: 0x%04X\n",
msg.sub_index, msg.diag_code, msg.flags, msg.text_index);
}
ETG.1020 §16 诊断历史对象; CANopen CiA 301 DS301 §7.5.3.
0x10F3 诊断历史 (轻量轮询 + 逐条确认)
ReadDiagnosticMessages() 一次性把 0x10F3 子索引拉一遍, 适合"导出全部诊断"的离线场景。 实时监控如果想避免反复读取, 应使用 ETG.1020 §16 的轻量轮询接口: 先 PollHasNewDiagnostic() 看有没有新消息, 有再读 ReadDiagnosticMeta() 拿元数据 (newest 编号 / overrun 标志), 按编号 ReadDiagnosticMessage() 取一条, 处理完写 AcknowledgeDiagnostic() 通知从站删除。
四个 API 由 SDK 在首次调用时按需绑定底层运行时入口并缓存。 当前运行时不支持该轻量轮询接口时, 所有调用安静返回 false / nullopt / -1, 不抛异常。 失败原因可读 LastDiagAbortCode() 拿 SDO Abort Code.
DiagMeta 结构
struct DiagMeta {
uint8_t maxMessages; // 0x10F3:01 缓冲区最大消息数
uint8_t newestMessage; // 0x10F3:02 最新消息编号 (环形写入下一个位置)
uint8_t newestAcknowledged; // 0x10F3:03 最后一个已确认编号
uint16_t flags; // 0x10F3:05 Bit4=RingBuffer, Bit5=Overrun
bool isRingBuffer() const; // Bit4
bool hasOverrun() const; // Bit5 (从消息超过缓冲容量, 旧消息已被覆盖)
};
PollHasNewDiagnostic()
int PollHasNewDiagnostic();
轻量轮询 — 不消耗邮箱, 只查询 newestMessage > newestAcknowledged. 适合周期性检查。
返回值:
>0— 有新诊断消息0— 无新消息<0— 当前运行时不支持该接口或通信失败
ReadDiagnosticMeta()
std::optional<DiagMeta> ReadDiagnosticMeta();
读取 0x10F3:01..05 元数据。 失败返回 std::nullopt.
ReadDiagnosticMessage()
std::vector<uint8_t> ReadDiagnosticMessage(uint8_t msgIdx, size_t bufSize = 1024);
读取指定编号的诊断消息字节 (msgIdx 通常 6..255). bufSize 默认 1024, ETG.1020 推荐。 失败返回空 vector, 具体错误见 LastDiagAbortCode().
AcknowledgeDiagnostic()
bool AcknowledgeDiagnostic(uint8_t msgIdx);
把 msgIdx 写到 0x10F3:03, 通知从站可以从环形缓冲清除该条及之前的消息。
LastDiagAbortCode()
uint32_t LastDiagAbortCode() const;
最近一次 4 个诊断 API 的 SDO Abort Code, 0 表示无错误。
端到端示例
auto& coe = master.GetSlave(1).GetCoE();
while (running) {
int n = coe.PollHasNewDiagnostic();
if (n <= 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
auto meta = coe.ReadDiagnosticMeta();
if (!meta) {
printf("读元数据失败, AbortCode=0x%08X\n", coe.LastDiagAbortCode());
continue;
}
if (meta->hasOverrun()) {
printf("[警告] 诊断历史溢出, 部分旧消息丢失\n");
}
// 从最后已确认的下一条开始, 读到最新
for (uint8_t i = static_cast<uint8_t>(meta->newestAcknowledged + 1);
i != static_cast<uint8_t>(meta->newestMessage + 1); ++i) {
auto bytes = coe.ReadDiagnosticMessage(i);
if (bytes.size() < 8) continue;
uint32_t diagCode;
uint16_t flags, textIdx;
std::memcpy(&diagCode, bytes.data(), 4);
std::memcpy(&flags, bytes.data() + 4, 2);
std::memcpy(&textIdx, bytes.data() + 6, 2);
printf("Diag[%u] code=0x%08X flags=0x%04X textIdx=0x%04X\n",
i, diagCode, flags, textIdx);
coe.AcknowledgeDiagnostic(i);
}
}
ReadDiagnosticMessages()— 一次拉满最多 20 条, 不区分新旧, 适合离线导出- 4 个轻量 API — 增量轮询 + 逐条确认, 适合 7×24 实时监控, 避免重复读取与缓冲溢出
- 不要混用。 一旦走轻量 API, 由应用维护
newestAcknowledged进度, 不要再调ReadDiagnosticMessages()干扰从站环形缓冲
ETG.1020 §16 Table 65; ETG.1510 §6.2 Diagnosis history.