AoE (ADS over EtherCAT)
AoE 协议实现了 ADS (Automation Device Specification) over EtherCAT 通信,支持与实现 ADS 协议的设备进行通信。
AoE 邮箱函数在 C SDK 中以 dx_aoe_* 前缀提供。直接调用 dx_aoe_set_config(...)(无句柄、无函数指针表)。
AoE 配置
dx_aoe_set_config()
BOOL dx_aoe_set_config(uint16_t master_index, uint16_t slave,
const uint8_t* target_net_id, uint16_t target_port,
const uint8_t* source_net_id, uint16_t source_port);
设置 AoE 路由配置(Net ID 和端口)。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引target_net_id(const uint8_t*) — 目标 AMS Net ID(6 字节)target_port(uint16_t) — 目标 AMS 端口source_net_id(const uint8_t*) — 源 AMS Net ID(6 字节)source_port(uint16_t) — 源 AMS 端口
返回值:
BOOL— 成功返回TRUE
dx_aoe_get_config()
BOOL dx_aoe_get_config(uint16_t master_index, uint16_t slave,
uint8_t* target_net_id, uint16_t* target_port,
uint8_t* source_net_id, uint16_t* source_port);
获取当前 AoE 路由配置。
参数:
target_net_id(uint8_t*) — 输出目标 AMS Net ID(6 字节缓冲区)target_port(uint16_t*) — 输出目标 AMS 端口source_net_id(uint8_t*) — 输出源 AMS Net ID(6 字节缓冲区)source_port(uint16_t*) — 输出源 AMS 端口
返回值:
BOOL— 成功返回TRUE
示例:
/* 设置 AoE 路由配置 */
uint8_t target_net_id[] = { 5, 80, 187, 177, 1, 1 };
uint8_t source_net_id[] = { 192, 168, 1, 100, 1, 1 };
dx_aoe_set_config(master, 1, target_net_id, 851, source_net_id, 32768);
/* 获取 AoE 路由配置 */
uint8_t tgt[6], src[6];
uint16_t tgt_port, src_port;
dx_aoe_get_config(master, 1, tgt, &tgt_port, src, &src_port);
printf("目标端口: %d, 源端口: %d\n", tgt_port, src_port);
数据读写
dx_aoe_send_command()
BOOL dx_aoe_send_command(uint16_t master_index, uint16_t slave, uint16_t target_port,
uint16_t command_id, const uint8_t* data, uint32_t size,
uint8_t** response, uint32_t* response_size, int timeout);
发送 ADS 命令并接收响应。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引target_port(uint16_t) — 目标 AMS 端口command_id(uint16_t) — ADS 命令 IDdata(const uint8_t*) — 请求数据size(uint32_t) — 请求数据大小response(uint8_t**) — 输出响应数据指针,需调用FreeMemory()释放response_size(uint32_t*) — 输出响应数据大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
dx_aoe_read_write()
BOOL dx_aoe_read_write(uint16_t master_index, uint16_t slave,
uint32_t index_group, uint32_t index_offset,
uint32_t read_len, uint32_t write_len, const uint8_t* write_data,
uint8_t** read_data, uint32_t* bytes_read, int timeout);
ADS ReadWrite 命令,同时读写数据。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引index_group(uint32_t) — 索引组index_offset(uint32_t) — 索引偏移read_len(uint32_t) — 读取长度write_len(uint32_t) — 写入长度write_data(const uint8_t*) — 写入数据read_data(uint8_t**) — 输出读取数据指针,需调用FreeMemory()释放bytes_read(uint32_t*) — 输出实际读取字节数timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
设备信息与状态
dx_aoe_read_device_info()
BOOL dx_aoe_read_device_info(uint16_t master_index, uint16_t slave,
uint8_t* major, uint8_t* minor, uint16_t* build,
char* name, int name_size, int timeout);
读取 ADS 设备信息。
参数:
major(uint8_t*) — 输出主版本号minor(uint8_t*) — 输出次版本号build(uint16_t*) — 输出构建号name(char*) — 输出设备名称缓冲区name_size(int) — 缓冲区大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
dx_aoe_read_state()
BOOL dx_aoe_read_state(uint16_t master_index, uint16_t slave,
uint16_t* ads_state, uint16_t* device_state, int timeout);
读取 ADS 设备状态。
参数:
ads_state(uint16_t*) — 输出 ADS 状态device_state(uint16_t*) — 输出设备状态timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
dx_aoe_write_control()
BOOL dx_aoe_write_control(uint16_t master_index, uint16_t slave,
uint16_t ads_state, uint16_t device_state,
const uint8_t* data, int data_size, int timeout);
写入 ADS 控制命令(状态转换)。
参数:
ads_state(uint16_t) — 目标 ADS 状态device_state(uint16_t) — 目标设备状态data(const uint8_t*) — 附加数据(可为 NULL)data_size(int) — 附加数据大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
通知
dx_aoe_add_notification()
BOOL dx_aoe_add_notification(uint16_t master_index, uint16_t slave,
uint32_t index_group, uint32_t index_offset, uint32_t length,
uint32_t trans_mode, uint32_t max_delay, uint32_t cycle_time,
uint32_t* handle, int timeout);
添加 ADS 通知。
参数:
index_group(uint32_t) — 索引组index_offset(uint32_t) — 索引偏移length(uint32_t) — 数据长度trans_mode(uint32_t) — 传输模式max_delay(uint32_t) — 最大延迟(100ns 为单位)cycle_time(uint32_t) — 周期时间(100ns 为单位)handle(uint32_t*) — 输出通知句柄timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
通知传输模式 (trans_mode):
0NoTransmission — 无通知1Cyclic — 循环通知 - 按指定周期发送2OnChange — 变化通知 - 数据变化时发送3CyclicInDevice — 设备端循环通知4OnChangeInDevice — 设备端变化通知
dx_aoe_del_notification()
BOOL dx_aoe_del_notification(uint16_t master_index, uint16_t slave,
uint32_t handle, int timeout);
删除 ADS 通知。
参数:
handle(uint32_t) — 通知句柄timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
dx_aoe_start_notification_listener()
BOOL dx_aoe_start_notification_listener(uint16_t master_index);
启动通知监听器线程。
dx_aoe_stop_notification_listener()
BOOL dx_aoe_stop_notification_listener(void);
停止通知监听器线程。
dx_aoe_is_notification_listening()
BOOL dx_aoe_is_notification_listening(void);
查询通知监听器是否正在运行。
dx_aoe_register_notification()
int dx_aoe_register_notification(uint16_t slave, uint32_t handle,
uint32_t index_group, uint32_t index_offset,
uint32_t data_length,
aoe_notification_callback_t callback, void* user_data);
注册通知回调。
参数:
slave(uint16_t) — 从站索引handle(uint32_t) — 通知句柄(由dx_aoe_add_notification返回)index_group(uint32_t) — 索引组index_offset(uint32_t) — 索引偏移data_length(uint32_t) — 数据长度callback— 回调函数user_data— 用户数据指针
返回值:
int— 订阅索引(用于取消注册)
dx_aoe_unregister_notification()
BOOL dx_aoe_unregister_notification(int subscription_index);
取消注册通知回调。
跨协议网关
通过 AoE 路由访问其他邮箱协议(ETG.1020),支持 CoE 和 SoE 协议的透明转发。
协议路由常量:
| 协议 | IndexGroup | IndexOffset 编码 |
|---|---|---|
| CoE (CANopen over EtherCAT) | 0xF302 | (index << 16) | subindex |
| SoE (Servo over EtherCAT) | 0xF420 | IDN 编号 |
aoe_read_coe() / aoe_write_coe() / aoe_read_soe() / aoe_write_soe() 不在 C SDK 出货面:薄壳 DarraEtherCAT.dll 没有对应导出,原型会让调用方在链接期直接 LNK2019。CoE 请直接调用 dx_sdo_read() / dx_sdo_write(),SoE 请直接调用 dx_soe_read() / dx_soe_write();确需经 AoE 路由时,用 dx_aoe_read_write() 按上表自行组装帧头。
示例 (同一个 CoE 对象, 直接走 SDO 原语):
#include "ethercat.h"
/* 读 CoE 对象 0x6041:00(状态字) */
int sdo_size = 0;
char* data = dx_sdo_read(master, 1, 0x6041, 0x00, FALSE, &sdo_size);
if (data) {
uint16_t statusWord = *(uint16_t*)data;
printf("状态字: 0x%04X\n", statusWord);
FreeMemory(data);
}
/* 写 CoE 对象 0x6040:00(控制字) */
uint16_t ctrlWord = 0x000F;
dx_sdo_write(master, 1, 0x6040, 0x00, FALSE, (uint8_t*)&ctrlWord, sizeof(ctrlWord));
AoE 读写 (slave/aoe.h)
dx_aoe_read_write 同时承载读写两个方向, 调用复杂。slave/aoe.h 提供单向 static __inline 包装 aoe_read() / aoe_write(), 内部透传到 dx_aoe_read_write。这两个是头文件内联函数 (不是 DLL 导出, 无需句柄)。
aoe_read() / aoe_write() 定义在 slave/aoe.h, 内部直接调用 dx_aoe_read_write / FreeMemory 裸符号, 首参是主站索引。需要自行组装单向读写帧时, 直接调用 dx_aoe_read_write(...) 按 ETG.1020 组装即可。
aoe_result_code_t
ADS 结果错误码枚举 (slave/aoe.h, ETG.1020 Table 16)。数值布局: 高字节 = 错误类 (0x0700 段 = ADS 设备错误, 0x0740 段 = 客户端错误), 低字节 = 具体码。
typedef enum {
AOE_RC_NO_ERROR = 0x0000, /* 无错误 */
AOE_RC_INTERNAL_ERROR = 0x0001, /* 内部错误 */
AOE_RC_NO_REALTIME = 0x0002, /* 无实时 */
AOE_RC_INSERT_MAILBOX_ERROR = 0x0004, /* 邮箱满 */
AOE_RC_TARGET_PORT_NOT_FOUND = 0x0006, /* 目标端口未找到 */
AOE_RC_TARGET_MACHINE_NOT_FOUND = 0x0007, /* 目标设备未找到 */
AOE_RC_AMS_SYNC_TIMEOUT = 0x0015, /* AMS 同步超时 */
/* ADS device error 类 (0x0700 段) */
AOE_RC_DEVICE_INVALID_GROUP = 0x0700, /* 设备无效组 */
AOE_RC_DEVICE_INVALID_ACCESS = 0x0702, /* 设备无效访问 */
AOE_RC_DEVICE_BUSY = 0x0706, /* 设备忙 */
AOE_RC_DEVICE_NOT_FOUND = 0x0708, /* 设备不存在 */
AOE_RC_DEVICE_TIMEOUT = 0x0712, /* 设备超时 */
AOE_RC_DEVICE_ACCESS_DENIED = 0x071C, /* 设备访问被拒绝 */
/* ADS client error 类 (0x0740 段) */
AOE_RC_CLIENT_ERROR = 0x0740, /* 客户端错误 */
AOE_RC_CLIENT_INVOKE_TIMEOUT = 0x0744, /* 客户端调用超时 */
AOE_RC_CLIENT_PORT_NOT_OPEN = 0x0745 /* 客户端端口未打开 */
/* ... 完整枚举见 slave/aoe.h, 共 50+ 项 */
} aoe_result_code_t;
完整错误码列表见 slave/aoe.h 与 Beckhoff ADS Return Codes (Information System)。失败时错误码语义见该枚举, 与 dx_aoe_read_write 的 BOOL 返回值配合判断。
aoe_read()
/* slave/aoe.h — static __inline 包装, 透传 dx_aoe_read_write (write_len=0) */
BOOL aoe_read(uint16_t master_index, uint16_t slave,
uint32_t index_group, uint32_t index_offset,
uint32_t read_len, int timeout,
uint8_t** out_data, uint32_t* out_bytes_read);
ADS 单向读取 (IndexGroup + IndexOffset)。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引index_group(uint32_t) — 索引组index_offset(uint32_t) — 索引偏移read_len(uint32_t) — 读取长度timeout(int) — 超时 (微秒)out_data(uint8_t**) — 输出数据指针, 需调用FreeMemory()释放out_bytes_read(uint32_t*) — 输出实际读取字节数
返回值:
BOOL— 成功返回TRUE
aoe_write()
/* slave/aoe.h — static __inline 包装, 透传 dx_aoe_read_write (read_len=0) */
BOOL aoe_write(uint16_t master_index, uint16_t slave,
uint32_t index_group, uint32_t index_offset,
const uint8_t* data, uint32_t data_len, int timeout);
ADS 单向写入。
返回值:
BOOL— 成功返回TRUE
示例:
#include "ethercat.h" /* aoe_read / aoe_write 声明在 slave/aoe.h, 经伞形头引入 */
uint8_t* buf = NULL;
uint32_t got = 0;
if (aoe_read(master, 1, 0x4020, 0, 4, 500000, &buf, &got)) {
printf("读到 %u 字节\n", got);
FreeMemory(buf); /* 直接调用裸符号, 无需句柄 */
} else {
/* 失败时错误码语义见 slave/aoe.h 的 aoe_result_code_t 枚举 */
printf("AoE 读取失败\n");
}
uint16_t cmd = 0x0001;
aoe_write(master, 1, 0x4020, 0, (uint8_t*)&cmd, sizeof(cmd), 500000);
AoE 订阅管理器 (ethercat_advanced.h)
aoe_sub_create()
aoe_sub_mgr_t* aoe_sub_create(uint16_t master, uint16_t slave);
创建 AoE 订阅管理器。
返回值:
aoe_sub_mgr_t*— 管理器实例,需调用aoe_sub_destroy()释放
aoe_sub_destroy()
void aoe_sub_destroy(aoe_sub_mgr_t* mgr);
销毁管理器(自动移除所有订阅)。
aoe_sub_add()
int aoe_sub_add(aoe_sub_mgr_t* mgr, uint32_t index_group, uint32_t index_offset,
uint32_t length, uint32_t trans_mode, uint32_t max_delay, uint32_t cycle_time,
aoe_data_callback_t callback, void* user_data);
添加订阅。
返回值:
int— 订阅 ID(小于 0 表示失败)
aoe_sub_remove()
int aoe_sub_remove(aoe_sub_mgr_t* mgr, int subscription_id);
移除指定订阅。
aoe_sub_remove_all()
int aoe_sub_remove_all(aoe_sub_mgr_t* mgr);
移除所有订阅。
aoe_sub_count()
int aoe_sub_count(aoe_sub_mgr_t* mgr);
获取活跃订阅数量。
完整示例
#include "ethercat.h"
#include "ethercat_advanced.h"
#include <stdio.h>
/* 数据变化回调 */
void on_data_changed(uint32_t handle, uint32_t ig, uint32_t io,
const uint8_t* data, uint32_t size, void* user) {
printf("数据变化: handle=%u, size=%u\n", handle, size);
}
int main(void)
{
uint16_t master = Initialize();
SetNetwork(master, "\\Device\\NPF_{...}", "");
SetStateSequence(master, EC_STATE_PRE_OP, 5000);
uint16_t slave = 1;
/* 配置 AoE 路由 */
uint8_t target_net_id[] = { 5, 80, 187, 177, 1, 1 };
uint8_t source_net_id[] = { 192, 168, 1, 100, 1, 1 };
dx_aoe_set_config(master, slave, target_net_id, 851, source_net_id, 32768);
/* 读取设备信息 */
uint8_t major, minor;
uint16_t build;
char name[64];
if (dx_aoe_read_device_info(master, slave, &major, &minor, &build, name, sizeof(name), 500000)) {
printf("设备: %s v%d.%d.%d\n", name, major, minor, build);
}
/* 读取设备状态 */
uint16_t ads_state, dev_state;
if (dx_aoe_read_state(master, slave, &ads_state, &dev_state, 500000)) {
printf("ADS 状态: %d, 设备状态: %d\n", ads_state, dev_state);
}
/* 读取 CoE 对象 0x6041:00(状态字) */
int sdo_size = 0;
char* read_data = dx_sdo_read(master, slave, 0x6041, 0x00, FALSE, &sdo_size);
if (read_data) {
printf("状态字: 0x%04X\n", *(uint16_t*)read_data);
FreeMemory(read_data);
}
/* 使用订阅管理器 */
aoe_sub_mgr_t* mgr = aoe_sub_create(master, slave);
int sub_id = aoe_sub_add(mgr, 0x4020, 0, 4,
2 /* OnChange */, 0, 100000, on_data_changed, NULL);
printf("活跃订阅: %d\n", aoe_sub_count(mgr));
/* 清理 */
aoe_sub_destroy(mgr);
Dispose(master);
return 0;
}