CoE (CANopen over EtherCAT)
CoE 提供基于 CANopen 的 SDO(服务数据对象)访问,用于读写从站的对象字典参数。
通过 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);
}
- 需要区分"通信失败"与"从站拒绝"(如值超范围 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_desc→coe_free_odlistcoe_get_entry_desc→coe_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: 驱动器异步推送的 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:01MaxMessages — 缓冲区容量0x10F3:02NewestMessage — 最新消息编号0x10F3:03NewestAcknowledged — 已确认的最新编号0x10F3:05Flags — 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 | 无错误 |
| 0x05030000 | Toggle 位未改变 |
| 0x05040000 | SDO 协议超时 |
| 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;
}