跳到主要内容

CoE (CANopen over EtherCAT)

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

CoE 可用性检查

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

SDO 读写

SDOread()

char* SDOread(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() 释放。失败返回 NULL

SDOwrite()

uint8_t SDOwrite(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 — 0 成功,非 0 失败

示例:

/* 读取 VendorID (0x1018:01) */
int sdo_size = 0;
char* data = dll.SDOread(master, 1, 0x1018, 0x01, FALSE, &sdo_size);
if (data && sdo_size > 0) {
uint32_t vendor_id = 0;
memcpy(&vendor_id, data, sdo_size > 4 ? 4 : sdo_size);
printf("Vendor ID: 0x%08X\n", vendor_id);
dll.FreeMemory(data);
}

/* 写入 ControlWord (0x6040:00) */
uint16_t cw = 0x0006;
dll.SDOwrite(master, 1, 0x6040, 0x00, FALSE, (uint8_t*)&cw, sizeof(cw));

SDOread_ex()

char* SDOread_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);

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

参数:

  • 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

返回值:

  • char* — 数据指针,需调用 FreeMemory() 释放。失败返回 NULL,并通过 out_abort_code 输出错误码

SDOwrite_ex()

uint8_t SDOwrite_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);

扩展版 SDO 写入,相对于 SDOwrite() 额外回填 CANopen Abort Code。失败时既能拿到非 0 返回值,也能从 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

返回值:

  • uint8_t — 0 成功,非 0 失败

示例:

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

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

类型化读写 (ethercat_advanced.h)

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

类型化读取

所有读取函数签名一致: sdo_read_xxx(dll, 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(dll, 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 */
uint16_t sw;
sdo_read_u16(&dll, master, 1, 0x6041, 0, &sw);
printf("StatusWord: 0x%04X\n", sw);

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

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

批量 SDO 读取

SDOReadMultiple()

int SDOReadMultiple(uint16_t master_index, uint16_t slave_index,
const ec_sdo_entry_t* entries, int count,
ec_sdo_read_result_t* results);

批量读取多个 SDO 条目(流水线并行,比逐个 SDOread() 更高效)。

参数:

  • master_index (uint16_t) — 主站索引
  • slave_index (uint16_t) — 从站索引
  • entries (const ec_sdo_entry_t*) — 请求条目数组
  • count (int) — 条目数量
  • results (ec_sdo_read_result_t*) — 输出结果数组(调用者分配,大小 >= count)

返回值:

  • int — 成功读取的条目数量

相关结构:

/* 批量读取请求条目 */
typedef struct {
uint16_t index; /* 对象索引 */
uint8_t subindex; /* 子索引 */
} ec_sdo_entry_t;

/* 批量读取结果条目 */
typedef struct {
uint16_t index; /* 对象索引 */
uint8_t subindex; /* 子索引 */
uint8_t* data; /* 数据指针 (NULL 表示失败, 需调用 FreeMemory 释放) */
int data_size; /* 数据大小 (字节) */
} ec_sdo_read_result_t;

示例:

/* 一次读取多个参数 */
ec_sdo_entry_t entries[] = {
{ 0x6041, 0x00 }, /* StatusWord */
{ 0x6064, 0x00 }, /* 实际位置 */
{ 0x6077, 0x00 }, /* 实际转矩 */
};
ec_sdo_read_result_t results[3];

int ok = dll.SDOReadMultiple(master, 1, entries, 3, results);
printf("成功读取: %d / 3\n", ok);

for (int i = 0; i < 3; i++) {
if (results[i].data) {
printf("0x%04X:%02X = %d 字节\n",
results[i].index, results[i].subindex, results[i].data_size);
dll.FreeMemory(results[i].data);
}
}
性能优势

批量读取内部使用流水线调度,减少邮箱往返次数,在读取多个参数时显著快于逐个调用 SDOread()

对象字典访问

GetSlaveSDOList()

void* GetSlaveSDOList(uint16_t master_index, uint16_t slave_index);

获取从站的完整对象字典列表(含子条目详情)。返回内部指针,无需释放。

参数:

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

返回值:

  • void* — 对象字典列表内部指针

GetSlaveSDOListBasic()

void* GetSlaveSDOListBasic(uint16_t master_index, uint16_t slave_index);

获取从站的基础对象字典列表(不含子条目,更快)。返回内部指针,无需释放。

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。

od_list_t* coe_get_od_list   (uint16_t master_index, uint16_t slave_index);
od_list_t* coe_get_object_desc(uint16_t master_index, uint16_t slave_index, uint16_t index);
oe_list_t* coe_get_entry_desc (uint16_t master_index, uint16_t slave_index,
uint16_t index, uint8_t subindex);
void coe_free_odlist (od_list_t* p);
void coe_free_oelist (oe_list_t* p);

coe_get_od_list — 等价 GetSlaveSDOList 但返回 malloc 内存 (跨运行时释放安全), 必须用 coe_free_odlist 释放。

coe_get_object_desc — 仅查询单个主索引的对象描述 (Object Code / DataType / MaxSubIndex / Name), 不展开子条目。适合判断"对象存在吗"或"是 RECORD 还是 VARIABLE"。

coe_get_entry_desc — 查询单个 (index, subindex) 的子条目描述 (BitLength / ObjAccess / Name)。配合 dx_sdo_read_ex 在不知道字段长度时按描述自适应解析。

返回内存释放规则:

  • coe_get_od_list / coe_get_object_desccoe_free_odlist
  • coe_get_entry_desccoe_free_oelist
  • 不要用 FreeMemory (这两个走 SDK 内部 malloc, 不是 DLL 共享堆)

示例:

/* 查询 0x6040 是否存在 */
od_list_t* obj = coe_get_object_desc(master, 1, 0x6040);
if (obj && obj->count > 0) {
printf("0x6040 = %s, ObjectCode=0x%02X, MaxSub=%u\n",
obj->objects[0].name, obj->objects[0].object_code,
obj->objects[0].max_sub);
}
coe_free_odlist(obj);

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

OD 树遍历 (ethercat_advanced.h)

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

od_load()

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

加载从站的完整 OD 树。

参数:

  • dll (dll_t*) — DLL 实例
  • 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 树 */
od_list_t* od = od_load(&dll, master, 1, TRUE);
if (od) {
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 = dll.EmcyGetCount(master, 1);
if (count > 0) {
ec_emcy_record_t records[16];
int n = dll.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);
}
/* 处理完毕后清除历史 */
dll.EmcyClearHistory(master, 1);
}

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

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

DLL 只提供底层 SDO 原语 (不起后台线程), 应用层按需轮询 / 读取 / 确认。

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

coe_diag_poll_new_available()

int coe_diag_poll_new_available(uint16_t master_index, uint16_t slave_index,
uint32_t* out_abort_code);

轮询从站是否有新的诊断消息 (读取 0x10F3:04 NewAvailable 标志)。

参数:

  • master_index (uint16_t) — 主站索引
  • slave_index (uint16_t) — 从站索引
  • out_abort_code (uint32_t*) — 输出 SDO 中止码 (成功时为 0)

返回值:

  • 1 — 有新消息
  • 0 — 无
  • -1 — 通信失败

coe_diag_read_meta()

int coe_diag_read_meta(uint16_t master_index, uint16_t slave_index,
uint8_t* max_msgs, uint8_t* newest,
uint8_t* acknowledged, uint16_t* flags16,
uint32_t* out_abort_code);

一次性读取诊断历史元数据:

  • 0x10F3:01 MaxMessages — 缓冲区容量
  • 0x10F3:02 NewestMessage — 最新消息编号
  • 0x10F3:03 NewestAcknowledged — 已确认的最新编号
  • 0x10F3:05 Flags — Bit4 表示覆盖模式 (0=Overwrite 1=AckMode)

返回值:

  • 1 — 成功
  • 0 — 失败 (查看 *out_abort_code)

coe_diag_read_message()

int coe_diag_read_message(uint16_t master_index, uint16_t slave_index,
uint8_t msg_subidx,
uint8_t* out_buf, int buf_cap,
int* out_len,
uint32_t* out_abort_code);

读取指定编号的诊断消息 (0x10F3:06..FF)。消息格式参考 ETG.1020 Table 49 (DiagCode + Flags + TextID + Timestamp + 参数)。

参数:

  • msg_subidx (uint8_t) — 消息子索引 (对应 newest / 遍历子索引)
  • out_buf (uint8_t*) — 输出缓冲区, 建议 ≥ 512 字节
  • buf_cap (int) — 缓冲区容量
  • out_len (int*) — 输出实际字节数

返回值:

  • 1 — 成功
  • 0 — 失败

coe_diag_acknowledge()

int coe_diag_acknowledge(uint16_t master_index, uint16_t slave_index,
uint8_t ack_subidx,
uint32_t* out_abort_code);

0x10F3:03 写入 ack_subidx, 标记该编号及之前的消息为已读。对 AckMode 从站是必需步骤, 否则缓冲区不释放。

返回值:

  • 1 — 成功
  • 0 — 失败
函数加载方式

以上 4 个 coe_diag_* 函数不在 dll_t 结构体字段中, 需通过 LOAD_FUNC() 按符号名延迟加载 (老 DLL 缺失时返回 NULL, 应用层需判空)。

示例:

/* 函数指针类型 */
typedef int (*pfn_coe_diag_poll_t)(uint16_t, uint16_t, uint32_t*);
typedef int (*pfn_coe_diag_meta_t)(uint16_t, uint16_t, uint8_t*, uint8_t*,
uint8_t*, uint16_t*, uint32_t*);
typedef int (*pfn_coe_diag_msg_t)(uint16_t, uint16_t, uint8_t,
uint8_t*, int, int*, uint32_t*);
typedef int (*pfn_coe_diag_ack_t)(uint16_t, uint16_t, uint8_t, uint32_t*);

/* 通过 LOAD_FUNC 按符号名加载 (建议初始化时一次性解析并缓存) */
pfn_coe_diag_poll_t fn_poll = LOAD_FUNC(&dll, pfn_coe_diag_poll_t, coe_diag_poll_new_available);
pfn_coe_diag_meta_t fn_meta = LOAD_FUNC(&dll, pfn_coe_diag_meta_t, coe_diag_read_meta);
pfn_coe_diag_msg_t fn_msg = LOAD_FUNC(&dll, pfn_coe_diag_msg_t, coe_diag_read_message);
pfn_coe_diag_ack_t fn_ack = LOAD_FUNC(&dll, pfn_coe_diag_ack_t, coe_diag_acknowledge);
if (!fn_poll || !fn_meta || !fn_msg || !fn_ack) {
printf("当前 DLL 不支持 CoE 诊断历史\n");
return;
}

uint32_t abort = 0;
if (fn_poll(master, 1, &abort) > 0) {
uint8_t max_msg, newest, acked;
uint16_t flags;
if (fn_meta(master, 1, &max_msg, &newest, &acked, &flags, &abort)) {
printf("诊断历史: 容量=%u, 最新=%u, 已确认=%u, Flags=0x%04X\n",
max_msg, newest, acked, flags);

/* 读最新一条消息 */
uint8_t buf[512];
int len = 0;
if (fn_msg(master, 1, newest, buf, sizeof(buf), &len, &abort)) {
uint32_t diag_code = (uint32_t)buf[0] | ((uint32_t)buf[1] << 8)
| ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);
printf("诊断消息 #%u: DiagCode=0x%08X, %d 字节\n", newest, diag_code, len);

/* 标记为已读 */
fn_ack(master, 1, newest, &abort);
}
}
}
线程安全
  • 诊断历史通过标准 SDO 通道, 与 SDOread/SDOwrite 共用邮箱, 不要在高频 PDO 线程调用
  • 建议由后台线程每 1 秒左右轮询一次 coe_diag_poll_new_available()
  • out_abort_code 非零时参考 SDO 错误码

SDO 错误码

CANopen 标准 SDO 中止码(ETG.1020)。SDO 读写失败时,SDOread() 返回的 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一般错误
0x08000020数据传输错误
0x08000021本地控制错误
0x08000022设备状态错误
0x08000023字典错误
0xFFFFFFFF未知错误

完整示例

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

int main(void)
{
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.dll");
uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{...}", "");
dll.SetStateSequence(master, EC_STATE_OPERATIONAL, 10000);
dll.Start(master);

/* 类型化读取 StatusWord */
uint16_t sw;
if (sdo_read_u16(&dll, master, 1, 0x6041, 0, &sw) == 0) {
printf("StatusWord: 0x%04X\n", sw);
}

/* 遍历对象字典 */
od_list_t* od = od_load(&dll, master, 1, TRUE);
if (od) {
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 = dll.EmcyGetCount(master, 1);
printf("EMCY 消息数: %d\n", emcy_count);

dll.Stop(master);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}