跳到主要内容

AoE (ADS over EtherCAT)

AoE 协议实现了 ADS (Automation Device Specification) over EtherCAT 通信,支持与实现 ADS 协议的设备进行通信。

函数命名

AoE 邮箱函数在 C SDK 中以 dx_aoe_* 前缀提供。动态加载模式经 dll.dx_aoe_set_config(...) 调用,静态链接模式直接调 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 };
dll.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;
dll.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 命令 ID
  • data (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):

  • 0 NoTransmission — 无通知
  • 1 Cyclic — 循环通知 - 按指定周期发送
  • 2 OnChange — 变化通知 - 数据变化时发送
  • 3 CyclicInDevice — 设备端循环通知
  • 4 OnChangeInDevice — 设备端变化通知

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 协议的透明转发。

协议路由常量:

协议IndexGroupIndexOffset 编码
CoE (CANopen over EtherCAT)0xF302(index << 16) | subindex
SoE (Servo over EtherCAT)0xF420IDN 编号
便捷 wrapper, 非 DLL 导出

以下 aoe_read_coe() / aoe_write_coe() / aoe_read_soe() / aoe_write_soe() 是 SDK 在 ethercat_advanced.h 中提供的便捷 wrapper, 内部组装 AoE 帧后调用 dll.dx_aoe_read_write(), 不在 dll_t 结构体字段中(它们是普通 C 函数, 不经函数指针)。也可直接使用 dll.dx_aoe_read_write() 自行按 ETG.1020 组装帧头。

aoe_read_coe()

int aoe_read_coe(dll_t* dll, uint16_t master, uint16_t slave,
uint16_t index, uint8_t subindex, uint32_t read_length,
uint8_t** read_data, uint32_t* bytes_read, int timeout_us);

通过 AoE 路由读取 CoE 对象(IndexGroup=0xF302)。

参数:

  • dll (dll_t*) — DLL 实例
  • index (uint16_t) — CoE 对象索引
  • subindex (uint8_t) — CoE 子索引
  • read_length (uint32_t) — 读取长度
  • read_data (uint8_t**) — 输出数据指针,需调用 FreeMemory() 释放
  • bytes_read (uint32_t*) — 输出实际读取字节数
  • timeout_us (int) — 超时时间(微秒)

返回值:

  • int — 0 成功,非 0 失败

aoe_write_coe()

int aoe_write_coe(dll_t* dll, uint16_t master, uint16_t slave,
uint16_t index, uint8_t subindex,
const uint8_t* data, uint32_t data_len, int timeout_us);

通过 AoE 路由写入 CoE 对象(IndexGroup=0xF302)。

返回值:

  • int — 0 成功,非 0 失败

aoe_read_soe()

int aoe_read_soe(dll_t* dll, uint16_t master, uint16_t slave,
uint32_t idn, uint32_t read_length,
uint8_t** read_data, uint32_t* bytes_read, int timeout_us);

通过 AoE 路由读取 SoE IDN(IndexGroup=0xF420)。

返回值:

  • int — 0 成功,非 0 失败

aoe_write_soe()

int aoe_write_soe(dll_t* dll, uint16_t master, uint16_t slave,
uint32_t idn, const uint8_t* data, uint32_t data_len, int timeout_us);

通过 AoE 路由写入 SoE IDN(IndexGroup=0xF420)。

返回值:

  • int — 0 成功,非 0 失败

示例 (便捷函数):

#include "ethercat_advanced.h"

/* 通过 AoE 网关读取 CoE 对象 0x6041:0(状态字) */
uint8_t* data = NULL;
uint32_t bytes_read = 0;
if (aoe_read_coe(&dll, master, 1, 0x6041, 0, 2, &data, &bytes_read, 500000) == 0) {
uint16_t statusWord = *(uint16_t*)data;
printf("状态字: 0x%04X\n", statusWord);
dll.FreeMemory(data);
}

/* 通过 AoE 网关写入 CoE 对象 0x6040:0(控制字) */
uint16_t ctrlWord = 0x000F;
aoe_write_coe(&dll, master, 1, 0x6040, 0,
(const uint8_t*)&ctrlWord, sizeof(ctrlWord), 500000);

AoE 便捷读写 (slave/aoe.h)

dx_aoe_read_write 同时承载读写两个方向, 调用复杂。slave/aoe.h 提供单向 static __inline 便捷包装 aoe_read() / aoe_write(), 内部透传到 dx_aoe_read_write。这两个是头文件内联函数 (不是 DLL 导出, 不在 dll_t 字段中)。

仅静态链接模式可用

aoe_read() / aoe_write() 定义在 slave/aoe.h#ifndef DYNAMIC_LOAD 块内, 内部直接调用 dx_aoe_read_write / FreeMemory 裸符号, 因此仅静态链接模式 (!DYNAMIC_LOAD) 可用, 首参不带 dll_t*。动态加载模式下请直接调用 dll.dx_aoe_read_write(...) 自行按 ETG.1020 组装单向读写帧。

aoe_result_code_t

ADS 结果错误码枚举 (slave/aoe.h, 对齐 C# AoEResultCode, ETG.1020 Table 16)。数值与 C# 一致: 高字节 = 错误类 (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_get_last_error 返回值, 与 dx_aoe_read_writeBOOL 返回值配合使用。

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

示例 (静态链接模式 — 不定义 DYNAMIC_LOAD):

#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); /* 静态模式直接调裸符号, 无 dll. 前缀 */
} else {
/* 失败时可读 dx_aoe_get_last_error 取 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(dll_t* dll, 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

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

获取活跃订阅数量。

完整示例

#define DYNAMIC_LOAD
#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)
{
dll_t dll;
LOAD_DLL(&dll, "DarraEtherCAT.dll");
uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{...}", "");
dll.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 };
dll.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 (dll.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 (dll.dx_aoe_read_state(master, slave, &ads_state, &dev_state, 500000)) {
printf("ADS 状态: %d, 设备状态: %d\n", ads_state, dev_state);
}

/* 通过 AoE 网关读取 CoE 对象 */
uint8_t* read_data = NULL;
uint32_t bytes_read = 0;
if (aoe_read_coe(&dll, master, slave, 0x6041, 0, 2, &read_data, &bytes_read, 500000) == 0) {
printf("状态字: 0x%04X\n", *(uint16_t*)read_data);
dll.FreeMemory(read_data);
}

/* 使用订阅管理器 */
aoe_sub_mgr_t* mgr = aoe_sub_create(&dll, 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);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}