跳到主要内容

CoE (CANopen over EtherCAT)

CoE 提供基于 CANopen 的 SDO(服务数据对象)访问,用于读写从站的对象字典参数。

CoE 可用性检查

通过 GetSlaveCoEDetails(master_index, slave) 返回值判断从站是否支持 CoE(非 0 表示支持)。

函数命名

SDO 邮箱读写在 C SDK 中以 dx_sdo_* 前缀提供。直接调用 dx_sdo_read(...)(无句柄、无函数指针表)。

C 特有语法糖

读 SDO 前常需获取从站 Vendor / Product 信息, C 可用 ec_vendor(mi, si) / ec_product(mi, si) inline 短名替代 GetSlaveVendorId / GetSlaveProductCode, 编译期完全展开, 零开销。 详见 inline 包装.

SDO 读写​

dx_sdo_read()​

char* dx_sdo_read(uint16_t master_index, uint16_t slave, uint16_t index,
uint8_t subindex, BOOL CA, int* out_byte_size);

读取 SDO 原始字节数据。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • index (uint16_t) — 对象索引
  • subindex (uint8_t) — 子索引
  • CA (BOOL) — Complete Access 模式
  • out_byte_size (int*) — 输出数据大小

返回值:

  • char* — 数据指针,需调用 FreeMemory() 释放。失败 / WKC=0 返回 NULL
  • 单次读取长度上限 64 KB:SDO 读取与 OD 枚举的应答数据超过 64 KB 时按读失败返回 NULL,超大对象请拆分读取
0x08000000 不是「对象不存在」

不要对 NULL 直接 memcpy。dx_sdo_read() 失败时 abort 可能是上一次残留。WKC=0 / 超时的一般错误是 0x08000000(GeneralError),不当对象不存在,也 不写入 NotPresent。只有确定性 abort(0x06010000 不支持访问 / 0x06010001 / 0x06010002 / 对象·子索引不存在类 0x06020000 / 0x06090011 / 0x06090012)才进 NotPresent 负缓存,判定用 sdo_abort_is_deterministic(code)。要 abort + 长度一起看,用下面的 DarraEcat_dx_sdo_read_ex()。

dx_sdo_write()​

uint8_t dx_sdo_write(uint16_t master_index, uint16_t slave, uint16_t index,
uint8_t subindex, BOOL CA, uint8_t* bytes, int length);

写入 SDO 数据。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • index (uint16_t) — 对象索引
  • subindex (uint8_t) — 子索引
  • CA (BOOL) — Complete Access 模式
  • bytes (uint8_t*) — 要写入的数据
  • length (int) — 数据长度

返回值:

  • uint8_t — 1 成功;0 下发失败(Abort / 超时);0xFF SDK 未就绪、未下发。0 与 0xFF 都算失败,以 != 1 判失败

示例:

/* 读取 VendorID (0x1018:01) — 失败 / WKC=0 返回 NULL,必须先判空再 memcpy */
int sdo_size = 0;
char* data = dx_sdo_read(master, 1, 0x1018, 0x01, FALSE, &sdo_size);
if (data == NULL) {
printf("SDO 读取失败 (WKC=0 / 超时可能是 GeneralError,不是对象不存在)\n");
} else {
uint32_t vendor_id = 0;
memcpy(&vendor_id, data, sdo_size > 4 ? 4 : sdo_size);
printf("Vendor ID: 0x%08X\n", vendor_id);
FreeMemory(data);
}

/* 写入 ControlWord (0x6040:00) — 返回 1 才是成功 */
uint16_t cw = 0x0006;
if (dx_sdo_write(master, 1, 0x6040, 0x00, FALSE, (uint8_t*)&cw, sizeof(cw)) != 1) {
printf("SDO 写入失败 (0 = Abort/超时, 0xFF = SDK 未就绪)\n");
}

DarraEcat_dx_sdo_read_ex()​

char* DarraEcat_dx_sdo_read_ex(uint16_t master_index, uint16_t slave, uint16_t index,
uint8_t subindex, BOOL CA, int* out_byte_size,
uint32_t* out_abort_code, int* out_wkc);

扩展版 SDO 读取,相对于 dx_sdo_read() 额外回填 CANopen Abort Code 与 WKC(失败诊断用)。普通版 dx_sdo_read() 仅通过 out_byte_size 负值或 NULL 指针报错;扩展版在 out_abort_code 输出标准的 4 字节 SDO 中止码(参见本页SDO 错误码表),便于上层精准定位失败原因。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • index (uint16_t) — 对象索引
  • subindex (uint8_t) — 子索引
  • CA (BOOL) — Complete Access 模式
  • out_byte_size (int*) — 输出数据大小
  • out_abort_code (uint32_t*) — [输出] SDO Abort Code,成功时为 0
  • out_wkc (int*) — [输出] 底层 WKC;<= 0 表示无应答,一律按失败(可传 NULL 不取)

返回值:

  • char* — 数据指针,需调用 FreeMemory() 释放。失败 / WKC=0 返回 NULL(此时 *out_wkc <= 0)。空成功时指针非空且 *out_byte_size == 0。
  • 单次读取长度上限 64 KB:应答数据超过 64 KB 时按读失败返回 NULL,超大对象请拆分读取
必须判空,不要直接 memcpy 成 uint32
  • dx_sdo_read 失败或 WKC=0 → NULL。先判空再转换。
  • 底层 LastSdoError = GeneralError (0x08000000) 是邮箱没回来(WKC=0 / 超时),不是「对象不存在」,也不写入 NotPresent。
  • 确定性 abort 才进 NotPresent 负缓存:0x06010000 / 0x06010001 / 0x06010002 / 0x06020000 对象不存在 / 0x06090011 子索引不存在 / 0x06090012,判定 sdo_abort_is_deterministic(code)。
  • 需要 abort code 时用 DarraEcat_dx_sdo_read_ex。失败时通过 out_abort_code 输出错误码。

DarraEcat_dx_sdo_write_ex()​

int32_t DarraEcat_dx_sdo_write_ex(uint16_t master_index, uint16_t slave, uint16_t index,
uint8_t subindex, BOOL CA, uint8_t* bytes, int length,
uint32_t* out_abort_code, int* out_wkc);

扩展版 SDO 写入,相对于 dx_sdo_write() 额外回填 CANopen Abort Code 与 WKC。失败时返回 0(入口不可用为 -1),同时能从 out_abort_code 读到具体的中止码(如 0x06090030 值超出范围、0x06010002 写只读对象等),无需再追加一次诊断调用。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • index (uint16_t) — 对象索引
  • subindex (uint8_t) — 子索引
  • CA (BOOL) — Complete Access 模式
  • bytes (uint8_t*) — 要写入的数据
  • length (int) — 数据长度
  • out_abort_code (uint32_t*) — [输出] SDO Abort Code,成功时为 0
  • out_wkc (int*) — [输出] 底层 WKC(可传 NULL 不取)

返回值:

  • int32_t — 1 成功(wkc>0 且无 abort);0 失败;-1 入口不可用(未下发)。非 1 一律按失败处理

示例:

/* 扩展版 SDO 读取,失败时能拿到 abort code */
int sdo_size = 0;
int wkc = 0;
uint32_t abort = 0;
char* data = DarraEcat_dx_sdo_read_ex(master, 1, 0x6041, 0x00, FALSE, &sdo_size, &abort, &wkc);
if (data) {
uint16_t status_word = 0;
memcpy(&status_word, data, sizeof(status_word));
printf("StatusWord: 0x%04X\n", status_word);
FreeMemory(data);
} else {
printf("SDO 读取失败, AbortCode=0x%08X\n", abort);
}

/* 扩展版 SDO 写入 — 1 = 成功 */
uint16_t cw = 0x000F;
abort = 0;
wkc = 0;
int32_t err = DarraEcat_dx_sdo_write_ex(master, 1, 0x6040, 0x00, FALSE,
(uint8_t*)&cw, sizeof(cw), &abort, &wkc);
if (err != 1) {
printf("SDO 写入失败, AbortCode=0x%08X\n", abort);
}
何时使用 _ex 版本
  • 需要区分"通信失败"与"从站拒绝"(如值超范围 vs 总线断链)
  • 调试新驱动器,把 abort code 写入日志便于回查
  • 应用层无需 abort code 时仍可继续用普通版,更简洁

类型化读写 (ethercat_advanced.h)​

ethercat_advanced.h 提供类型化 SDO 读写函数,自动处理类型转换。

类型化读取​

所有读取函数签名一致: sdo_read_xxx(master, slave, index, sub, out_ptr),返回 0 成功。

  • sdo_read_u8 (uint8_t*) — 读取 8 位无符号整数
  • sdo_read_u16 (uint16_t*) — 读取 16 位无符号整数
  • sdo_read_u32 (uint32_t*) — 读取 32 位无符号整数
  • sdo_read_i8 (int8_t*) — 读取 8 位有符号整数
  • sdo_read_i16 (int16_t*) — 读取 16 位有符号整数
  • sdo_read_i32 (int32_t*) — 读取 32 位有符号整数
  • sdo_read_f32 (float*) — 读取单精度浮点数
  • sdo_read_f64 (double*) — 读取双精度浮点数
  • sdo_read_string (char* buf, int buf_size) — 读取字符串

类型化写入​

所有写入函数签名一致: sdo_write_xxx(master, slave, index, sub, value),返回 0 成功。

  • sdo_write_u8 (uint8_t) — 写入 8 位无符号整数
  • sdo_write_u16 (uint16_t) — 写入 16 位无符号整数
  • sdo_write_u32 (uint32_t) — 写入 32 位无符号整数
  • sdo_write_i8 (int8_t) — 写入 8 位有符号整数
  • sdo_write_i16 (int16_t) — 写入 16 位有符号整数
  • sdo_write_i32 (int32_t) — 写入 32 位有符号整数
  • sdo_write_f32 (float) — 写入单精度浮点数
  • sdo_write_f64 (double) — 写入双精度浮点数

示例:

#include "ethercat_advanced.h"

/* 读取 StatusWord — 返回非 0 即失败 / WKC=0,不要直接打印 */
uint16_t sw;
if (sdo_read_u16(master, 1, 0x6041, 0, &sw) != 0) {
printf("SDO 读取失败 (GeneralError 0x08000000 不是对象不存在)\n");
} else {
printf("StatusWord: 0x%04X\n", sw);
}

/* 写入 ControlWord */
sdo_write_u16(master, 1, 0x6040, 0, 0x0006);

/* 读取设备名称 */
char name[64];
if (sdo_read_string(master, 1, 0x1008, 0, name, sizeof(name)) == 0)
printf("设备名称: %s\n", name);

SDO 负缓存(确定性 abort)​

从站明确拒绝的确定性 abort(0x06010000 不支持访问 / 0x06010001 / 0x06010002 / 对象·子索引不存在类 0x06020000 / 0x06090011 / 0x06090012)重试不会成功,sdo_read_u8…sdo_read_string 等读入口会记入负缓存,之后同一对象的读直接短路返回失败、不再打邮箱。热插拔 / 固件升级 / PDO 重扫前调用 sdo_not_present_cache_reset(m, si) 清缓存。写路径不查不写负缓存;CA 读不走负缓存。判定用 sdo_abort_is_deterministic(code)。判定 / 归一 / 负缓存本体的协议权威在 Core.dll(dx_sdo_abort_* / dx_sdo_notpresent_cache_*),C 侧 sdo_* 是转发壳。

#include "ethercat_advanced.h"

/* 判断 abort 是否为确定性拒绝 (1 = 重试无意义) */
int sdo_abort_is_deterministic(uint32_t abort_code);

/* 清单个从站全部负缓存条目 (热插拔 / 固件升级 / PDO 重扫前调用) */
void sdo_not_present_cache_reset(uint16_t master, uint16_t slave);

/* 负缓存查询 / 写入 (读入口内部已自动调用, 一般无需手动) */
int sdo_not_present_cache_lookup(uint16_t master, uint16_t slave,
uint16_t index, uint8_t sub, uint32_t* out_abort);
void sdo_not_present_cache_store(uint16_t master, uint16_t slave,
uint16_t index, uint8_t sub, uint32_t abort_code);

批量 SDO 读取​

C SDK 暂不支持批量 SDO 读取

SDOReadMultiple 批量读取接口当前版本未实现 / 未导出,调用无效。批量读取多个 SDO 条目请逐次调用 dx_sdo_read() / DarraEcat_dx_sdo_read_ex()。结构体 ec_sdo_entry_t / ec_sdo_read_result_t 在 slave/coe.h 中已预留供后续版本使用。

逐次读取多个参数:

/* 逐次读取多个参数 (替代不可用的批量接口) */
struct { uint16_t index; uint8_t subidx; } items[] = {
{ 0x6041, 0x00 }, /* StatusWord */
{ 0x6064, 0x00 }, /* 实际位置 */
{ 0x6077, 0x00 }, /* 实际转矩 */
};
for (int i = 0; i < 3; i++) {
int sz = 0;
char* data = dx_sdo_read(master, 1, items[i].index, items[i].subidx,
FALSE, &sz);
if (data == NULL) {
printf("0x%04X:%02X 失败 (WKC=0 / GeneralError 不是对象不存在)\n",
items[i].index, items[i].subidx);
continue;
}
printf("0x%04X:%02X = %d 字节\n", items[i].index, items[i].subidx, sz);
FreeMemory(data);
}

对象字典访问​

GetSlaveSDOList()​

void* GetSlaveSDOList(uint16_t master_index, uint16_t slave_index);

获取从站的完整对象字典列表(含子条目详情)。返回运行库分配的 od_list_t,需调用 DarraEcat_coe_free_odlist() 释放。公开路径真发 SDO Information。

空字典不是成功:指针为 NULL 或条目数为 0 都是失败(WKC=0 不当已加载)。PreOp 才适合走本接口完整加载子索引。

单次读取长度上限 64 KB:枚举应答数据超过 64 KB 时按读失败返回 NULL,超大字典请按分段方式读取。

参数:

  • master_index (uint16_t) — 主站索引
  • slave_index (uint16_t) — 从站索引

返回值:

  • void* — 对象字典列表(od_list_t*),需调用 DarraEcat_coe_free_odlist() 释放;失败 / WKC=0 返回 NULL

GetSlaveSDOListBasic()​

void* GetSlaveSDOListBasic(uint16_t master_index, uint16_t slave_index);

获取从站的基础对象字典列表(不含子条目,更快)。OP / SafeOp 只发 SDO Information Basic 0x01,不全量读对象描述 0x03,避免 OP 邮箱被打满后 WKC=0 被当成空字典。空字典同样不是成功。返回值同样需 DarraEcat_coe_free_odlist() 释放。

GetSlavePointer_SDO_Value()​

char* GetSlavePointer_SDO_Value(uint16_t master_index, uint16_t slave_index,
uint16_t OD_index, uint8_t OE_index, int* out_byte_size);

读取指定对象的子索引值。

参数:

  • OD_index (uint16_t) — 对象索引
  • OE_index (uint8_t) — 子索引
  • out_byte_size (int*) — 输出数据大小

返回值:

  • char* — 数据指针,需调用 FreeMemory() 释放

GetMultiSlaveSDOList()​

int  GetMultiSlaveSDOList(uint16_t master_index, uint16_t* slave_indices, int count, void** results);
void FreeMultiSlaveSDOList(void** results, int count);

批量读取多个从站的 SDO 对象字典(流水线并行,比逐个读取更快)。

参数:

  • slave_indices (uint16_t*) — 从站索引数组
  • count (int) — 从站数量
  • results (void**) — 输出结果数组

返回值:

  • int — 成功读取的从站数量

按需查询单个对象 (CoE-H2 SDO Information Services)​

GetSlaveSDOList* 是整表加载接口 (一次性把上千个对象描述拉回本地缓存); 适合 OD 浏览器场景。生产代码里通常只需要某一个对象 (如 0x6041 StatusWord) 的描述, 这时用 H2 单条查询接口避免拉整表。底层走标准 ETG.1000.6 §5.6.3.6 SDO Information Services。

C SDK 提供两层接口:

底层 — 薄壳导出 (darra_ethercat_shell.h, 经 ethercat.h 引入)

CoE-H2 原语由薄壳 DarraEtherCAT.dll 直接导出 (本层暂无同名 inline 转发,故按 DarraEcat_ 名调用), 无句柄、 无函数指针表; 返回的是不透明指针 (void*, 内部布局不公开):

void* DarraEcat_coe_get_od_list   (uint16_t mi, uint16_t si);
void* DarraEcat_coe_get_object_desc(uint16_t mi, uint16_t si, uint16_t index);
void* DarraEcat_coe_get_entry_desc (uint16_t mi, uint16_t si, uint16_t index, uint8_t subindex);
void DarraEcat_coe_free_odlist (void* p);
void DarraEcat_coe_free_oelist (void* p);

由于内部结构体不公开, 应用层不应直接解引用这些 void*。生产代码请用下面的高级封装。

高级 — 类型化封装 (ethercat_advanced.h)

ethercat_advanced.h 把上述原语包成填充调用者结构体的函数, 返回 1 成功 / 0 失败:

int coe_get_object_description(uint16_t master, uint16_t slave,
uint16_t index, od_object_t* out_obj);
int coe_get_entry_description (uint16_t master, uint16_t slave,
uint16_t index, uint8_t subindex,
od_entry_t* out_entry);
  • coe_get_object_description — 仅查询单个主索引的对象描述 (Object Code / DataType / MaxSubIndex / Name), 不展开子条目 (out_obj->entries 始终为 NULL)。适合判断"对象存在吗"或"是 RECORD 还是 VARIABLE"。
  • coe_get_entry_description — 查询单个 (index, subindex) 的子条目描述 (BitLength / ObjAccess / Name)。配合 DarraEcat_dx_sdo_read_ex 在不知道字段长度时按描述自适应解析。

这两个函数把结果写进调用者栈上的 od_object_t / od_entry_t (结构定义见本页 OD 树遍历 节), 不分配堆内存, 无需手动释放。若要整表加载, 用同文件的 od_load_via_coe_h2(master, slave) (返回 od_list_t*, 用 od_free 释放)。

示例:

#include "ethercat_advanced.h"

/* 查询 0x6040 是否存在 */
od_object_t obj;
if (coe_get_object_description(master, 1, 0x6040, &obj)) {
printf("0x6040 = %s, ObjectCode=0x%02X, MaxSub=%u\n",
obj.name, obj.object_code, obj.max_sub);
} else {
printf("0x6040 不存在或查询失败\n");
}

/* 查询 0x6041:00 子条目 */
od_entry_t entry;
if (coe_get_entry_description(master, 1, 0x6041, 0x00, &entry)) {
printf("0x6041:00 = %s, BitLen=%u, Access=0x%04X\n",
entry.name, entry.bit_length, entry.obj_access);
}
性能建议
  • 整表 GetSlaveSDOList / od_load_via_coe_h2 适合 OD 浏览器初始化, 一次性后驻留内存
  • 控制循环 / 在线参数调整应只查需要的索引, 走 coe_get_object_description / coe_get_entry_description
  • 这两个接口与 SDO 读写共用邮箱, 不要在 PDO 高频路径调用

OD 树遍历 (ethercat_advanced.h)​

高级功能,加载从站的完整对象字典树,支持索引查找和子索引遍历。

od_load()​

od_list_t* od_load(uint16_t master, uint16_t slave, BOOL load_entries);

加载从站的完整 OD 树,真发 SDO Information。空字典不是成功。load_entries=FALSE 对应 OP / SafeOp 只发 Basic 0x01。

参数:

  • master (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • load_entries (BOOL) — 是否加载子条目(FALSE 只加载主索引)

返回值:

  • od_list_t* — OD 列表,需调用 od_free() 释放

od_find()​

od_object_t* od_find(od_list_t* list, uint16_t index);

在 OD 树中查找指定索引的对象。

参数:

  • list (od_list_t*) — OD 列表
  • index (uint16_t) — 对象索引

返回值:

  • od_object_t* — 对象指针,未找到返回 NULL

od_free()​

void od_free(od_list_t* list);

释放 OD 树。

相关结构:

typedef struct {
uint16_t index; /* OD 索引 */
char name[42]; /* 名称 */
uint16_t datatype; /* 数据类型 */
uint8_t object_code; /* 对象类型 (0x07=VARIABLE, 0x09=RECORD) */
uint8_t max_sub; /* 最大子索引数 */
int entry_count; /* 实际条目数 */
od_entry_t* entries; /* 条目数组 */
} od_object_t;

typedef struct {
uint8_t subindex; /* 子索引 */
char name[42]; /* 名称 */
uint16_t datatype; /* 数据类型 (ec_datatype_t) */
uint16_t bit_length; /* 位长度 */
uint16_t obj_access; /* 访问权限 (ec_obj_access_t 位掩码) */
} od_entry_t;

示例:

#include "ethercat_advanced.h"

/* 加载完整 OD 树。空字典不是成功。OP 应 od_load(..., FALSE) 只发 Basic 0x01 */
od_list_t* od = od_load(master, 1, TRUE);
if (od == NULL || od->count == 0) {
printf("对象字典加载失败\n");
} else {
printf("对象数量: %d\n", od->count);

/* 遍历所有对象 */
for (int i = 0; i < od->count; i++) {
od_object_t* obj = &od->objects[i];
printf("0x%04X: %s (%d 子条目)\n", obj->index, obj->name, obj->entry_count);
for (int j = 0; j < obj->entry_count; j++) {
od_entry_t* entry = &obj->entries[j];
printf(" [%d] %s (类型=0x%04X, %d 位)\n",
entry->subindex, entry->name, entry->datatype, entry->bit_length);
}
}

/* 查找特定对象 */
od_object_t* cw = od_find(od, 0x6040);
if (cw) printf("找到 ControlWord: %s\n", cw->name);

od_free(od);
}

EMCY 紧急消息历史​

EmcyGetCount()​

int EmcyGetCount(uint16_t master, uint16_t slave);

获取从站的 EMCY 紧急消息数量。

参数:

  • master (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引

返回值:

  • int — 消息数量

EmcyGetHistory()​

int EmcyGetHistory(uint16_t master, uint16_t slave, ec_emcy_record_t* out, int max);

读取从站的 EMCY 紧急消息历史。

参数:

  • master (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • out (ec_emcy_record_t*) — 输出缓冲区
  • max (int) — 最大读取条数

返回值:

  • int — 实际读取的条数

EmcyClearHistory()​

void EmcyClearHistory(uint16_t master, uint16_t slave);

清除从站的 EMCY 紧急消息历史。

相关结构:

typedef struct {
uint16_t error_code; /* 错误码 */
uint8_t error_register; /* 错误寄存器 */
uint8_t data[5]; /* 厂商自定义数据 */
uint16_t slave_index; /* 从站索引 */
uint32_t timestamp_ms; /* 时间戳 (毫秒) */
} ec_emcy_record_t;

示例:

int count = EmcyGetCount(master, 1);
if (count > 0) {
ec_emcy_record_t records[16];
int n = EmcyGetHistory(master, 1, records, 16);
for (int i = 0; i < n; i++) {
printf("EMCY[%d]: 错误码=0x%04X, 寄存器=0x%02X, 时间=%ums\n",
i, records[i].error_code, records[i].error_register, records[i].timestamp_ms);
}
/* 处理完毕后清除历史 */
EmcyClearHistory(master, 1);
}

CoE 诊断历史 (ETG.1020 §16)​

从站对象字典 0x10F3 定义了标准的诊断消息历史 (Diagnosis History)。与 EMCY 不同, 诊断历史是从站主动压栈的结构化消息集合, 包含时间戳、严重级别、文本描述等, 最多保留 250 条。

诊断历史是从站对象字典中的标准对象, 应用层用通用 SDO 读写原语 (dx_sdo_read / dx_sdo_write) 按需轮询 / 读取 / 确认其各个子索引即可, 不需要专门的诊断历史 API。

与 EMCY 的区别
  • EMCY:驱动器异步推送的 1 帧 8 字节错误码, 由 SDK 侧历史缓冲捕获
  • Diagnosis History (0x10F3):从站内部环形缓冲的结构化消息, 主动调用 SDO 读取

0x10F3 子索引布局​

诊断历史对象按 ETG.1020 §16 定义子索引, 全部经标准 SDO 通道读写:

  • 0x10F3:01 (MaxMessages) — 缓冲区容量 (最多保留条数)
  • 0x10F3:02 (NewestMessage) — 最新消息编号
  • 0x10F3:03 (NewestAcknowledged) — 已确认的最新编号 (写入即标记已读)
  • 0x10F3:04 (NewMessageAvailable) — 是否有新消息 (布尔标志)
  • 0x10F3:05 (Flags) — Bit4 表示覆盖模式 (0=Overwrite 1=AckMode)
  • 0x10F3:06..FF (Diagnosis Message) — 各条诊断消息 (DiagCode + Flags + TextID + Timestamp + 参数, 见 ETG.1020 Table 49)

读取流程一般是: 读 0x10F3:04 判断有无新消息 → 读 0x10F3:02 拿最新编号 → 读 0x10F3:05 判断是否 AckMode → 读对应 0x10F3:06..FF 消息子索引 → (AckMode 从站) 向 0x10F3:03 写编号标记已读, 否则缓冲区不释放。

示例:

int n = 0;
int wkc = 0;
uint32_t abort = 0;

/* 读 0x10F3:04 判断是否有新消息 */
char* avail = dx_sdo_read(master, 1, 0x10F3, 0x04, FALSE, &n);
if (avail && n >= 1 && avail[0]) {
/* 读最新消息编号 0x10F3:02 */
char* newest_p = dx_sdo_read(master, 1, 0x10F3, 0x02, FALSE, &n);
uint8_t newest = (newest_p && n >= 1) ? (uint8_t)newest_p[0] : 0;
if (newest_p) FreeMemory(newest_p);

/* 读对应编号的诊断消息子索引 (从 0x06 起按编号定位) */
char* msg = DarraEcat_dx_sdo_read_ex(master, 1, 0x10F3, newest, FALSE, &n, &abort, &wkc);
if (msg && n >= 4) {
uint32_t diag_code = (uint32_t)(uint8_t)msg[0] | ((uint32_t)(uint8_t)msg[1] << 8)
| ((uint32_t)(uint8_t)msg[2] << 16) | ((uint32_t)(uint8_t)msg[3] << 24);
printf("诊断消息 #%u: DiagCode=0x%08X, %d 字节\n", newest, diag_code, n);
}
if (msg) FreeMemory(msg);

/* AckMode 从站: 向 0x10F3:03 写编号标记已读, 否则缓冲区不释放 */
dx_sdo_write(master, 1, 0x10F3, 0x03, FALSE, &newest, sizeof(newest));
}
if (avail) FreeMemory(avail);
线程安全
  • 诊断历史通过标准 SDO 通道, 与 dx_sdo_read / dx_sdo_write 共用邮箱, 不要在高频 PDO 线程调用
  • 建议由后台线程每 1 秒左右轮询一次 0x10F3:04 标志
  • DarraEcat_dx_sdo_read_ex 回填的 out_abort_code 非零时参考 SDO 错误码 表

SDO 错误码​

CANopen 标准 SDO 中止码(ETG.1020)。SDO 读写失败时,dx_sdo_read() 返回的 out_byte_size 负值编码了错误码(负值 = 中止码的相反数)。

错误码含义
0x00000000无错误
0x05030000Toggle 位未改变
0x05040000SDO 协议超时
0x05040001无效的命令标识符
0x05040005内存不足
0x06010000不支持的访问
0x06010001尝试读取只写对象
0x06010002尝试写入只读对象
0x06010003子索引不允许写入
0x06010004不支持完全访问
0x06010005对象长度超限
0x06010006对象已映射到 RxPDO
0x06020000对象不存在
0x06040041不可映射到 PDO
0x06040042超出 PDO 长度
0x06040043参数不兼容
0x06040047内部不兼容
0x06060000硬件访问错误
0x06070010数据类型不匹配
0x06070012数据类型长度过大
0x06070013数据类型长度过小
0x06090011子索引不存在
0x06090030值超出范围
0x06090031值过大
0x06090032值过小
0x06090033模块列表不匹配
0x06090036最大值小于最小值
0x08000000一般错误(WKC=0 / 超时,不是对象不存在,不进 NotPresent)
0x08000020数据传输错误
0x08000021本地控制错误
0x08000022设备状态错误
0x08000023字典错误
0xFFFFFFFF未知错误

完整示例​

#include "ethercat.h"
#include "ethercat_advanced.h"
#include <stdio.h>

int main(void)
{
uint16_t master = Initialize();
SetNetwork(master, "\\Device\\NPF_{...}", "");
SetStateSequence(master, EC_STATE_OPERATIONAL, 10000);
Start(master);

/* 类型化读取 StatusWord — 必须判返回值 */
uint16_t sw;
if (sdo_read_u16(master, 1, 0x6041, 0, &sw) != 0) {
printf("SDO 读取失败 (GeneralError 不是对象不存在)\n");
} else {
printf("StatusWord: 0x%04X\n", sw);
}

/* 遍历对象字典。空字典不是成功。OP 应改 GetSlaveSDOListBasic / od_load(..., FALSE) */
od_list_t* od = od_load(master, 1, TRUE);
if (od == NULL || od->count == 0) {
printf("对象字典加载失败\n");
} else {
for (int i = 0; i < od->count; i++) {
printf("0x%04X: %s\n", od->objects[i].index, od->objects[i].name);
}
od_free(od);
}

/* 检查 EMCY */
int emcy_count = EmcyGetCount(master, 1);
printf("EMCY 消息数: %d\n", emcy_count);

Stop(master);
Dispose(master);
return 0;
}