跳到主要内容

AoE (ADS over EtherCAT)

AoE 协议实现了 ADS (Automation Device Specification) over EtherCAT 通信,支持 Beckhoff TwinCAT 和类似设备的 ADS 通信。

AoE 配置

AOESetConfig()

BOOL AOESetConfig(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

AOEGetConfig()

BOOL AOEGetConfig(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.AOESetConfig(master, 1, target_net_id, 851, source_net_id, 32768);

/* 获取 AoE 路由配置 */
uint8_t tgt[6], src[6];
uint16_t tgt_port, src_port;
dll.AOEGetConfig(master, 1, tgt, &tgt_port, src, &src_port);
printf("目标端口: %d, 源端口: %d\n", tgt_port, src_port);

数据读写

AOESendCommand()

BOOL AOESendCommand(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

AOEReadWrite()

BOOL AOEReadWrite(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

设备信息与状态

AOEReadDeviceInfo()

BOOL AOEReadDeviceInfo(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

AOEReadState()

BOOL AOEReadState(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

AOEWriteControl()

BOOL AOEWriteControl(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

通知

AOEAddNotification()

BOOL AOEAddNotification(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 — 设备端变化通知

AOEDelNotification()

BOOL AOEDelNotification(uint16_t master_index, uint16_t slave,
uint32_t handle, int timeout);

删除 ADS 通知。

参数:

  • handle (uint32_t) — 通知句柄
  • timeout (int) — 超时时间(微秒)

返回值:

  • BOOL — 成功返回 TRUE

AOEStartNotificationListener()

BOOL AOEStartNotificationListener(uint16_t master_index);

启动通知监听器线程。

AOEStopNotificationListener()

BOOL AOEStopNotificationListener(void);

停止通知监听器线程。

AOEIsNotificationListening()

BOOL AOEIsNotificationListening(void);

查询通知监听器是否正在运行。

AOERegisterNotification()

int AOERegisterNotification(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) — 通知句柄(由 AOEAddNotification 返回)
  • index_group (uint32_t) — 索引组
  • index_offset (uint32_t) — 索引偏移
  • data_length (uint32_t) — 数据长度
  • callback — 回调函数
  • user_data — 用户数据指针

返回值:

  • int — 订阅索引(用于取消注册)

AOEUnregisterNotification()

BOOL AOEUnregisterNotification(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.AOEReadWrite(), 不在 dll_t 结构体字段中。也可直接使用 dll.AOEReadWrite() 自行按 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 便捷读写 (ethercat_advanced.h)

AOEReadWrite 同时承载读写两个方向, 调用复杂。advanced.h 提供单向便捷函数和结果码枚举。

aoe_result_code_t

typedef enum {
AOE_OK = 0x00000000, /* 成功 */
AOE_ERR_INTERNAL = 0x00000001, /* 内部错误 */
AOE_ERR_NO_RTIME = 0x00000002, /* 无实时时间 */
AOE_ERR_LOCKED_MEM = 0x00000003, /* 内存被锁 */
AOE_ERR_MAILBOX = 0x00000004, /* 邮箱错误 */
AOE_ERR_WRONG_HMSG = 0x00000005, /* 错误的 HMSG */
AOE_ERR_TARGET_PORT = 0x00000006, /* 目标端口未找到 */
AOE_ERR_TARGET_MACHINE = 0x00000007, /* 目标机不可达 */
AOE_ERR_UNKNOWN_CMD = 0x00000008, /* 未知命令 */
AOE_ERR_BAD_TASK = 0x00000009, /* 错误任务 ID */
AOE_ERR_NO_IO = 0x0000000A, /* 无 IO */
AOE_ERR_UNKNOWN_AMS = 0x0000000B, /* 未知 AMS 命令 */
AOE_ERR_TIMEOUT = 0x00000745, /* ADS 超时 */
AOE_ERR_NOT_FOUND = 0x00000702, /* 对象/服务未找到 */
AOE_ERR_INVALID_PARM = 0x00000706, /* 无效参数 */
AOE_ERR_OUT_OF_RANGE = 0x00000709 /* 索引越界 */
} aoe_result_code_t;

完整错误码列表见 Beckhoff ADS Return Codes (Information System)。aoe_read/write 返回的 int 即对应该枚举。

aoe_read()

int aoe_read(dll_t* dll, uint16_t master, uint16_t slave,
uint32_t index_group, uint32_t index_offset,
uint32_t read_length,
uint8_t** read_data, uint32_t* bytes_read,
int timeout_us);

ADS 单向读取 (IndexGroup + IndexOffset)。返回 0 (AOE_OK) 成功。

参数:

  • dll (dll_t*) — DLL 实例
  • master (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • index_group (uint32_t) — 索引组
  • index_offset (uint32_t) — 索引偏移
  • read_length (uint32_t) — 读取长度
  • read_data (uint8_t**) — 输出数据指针, 需调用 FreeMemory() 释放
  • bytes_read (uint32_t*) — 输出实际读取字节数
  • timeout_us (int) — 超时 (微秒)

aoe_write()

int aoe_write(dll_t* dll, uint16_t master, uint16_t slave,
uint32_t index_group, uint32_t index_offset,
const uint8_t* data, uint32_t data_len,
int timeout_us);

ADS 单向写入。返回 0 (AOE_OK) 成功。

示例:

#include "ethercat_advanced.h"

uint8_t* buf = NULL;
uint32_t got = 0;
int rc = aoe_read(&dll, master, 1, 0x4020, 0, 4, &buf, &got, 500000);
if (rc == AOE_OK) {
printf("读到 %u 字节\n", got);
dll.FreeMemory(buf);
} else if (rc == AOE_ERR_TIMEOUT) {
printf("超时\n");
} else {
printf("ADS 错误: 0x%08X\n", (uint32_t)rc);
}

uint16_t cmd = 0x0001;
aoe_write(&dll, 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, "Darra.Core.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.AOESetConfig(master, slave, target_net_id, 851, source_net_id, 32768);

/* 读取设备信息 */
uint8_t major, minor;
uint16_t build;
char name[64];
if (dll.AOEReadDeviceInfo(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.AOEReadState(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;
}