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 命令 IDdata(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):
0NoTransmission — 无通知1Cyclic — 循环通知 - 按指定周期发送2OnChange — 变化通知 - 数据变化时发送3CyclicInDevice — 设备端循环通知4OnChangeInDevice — 设备端变化通知
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 协议的透明转发。
协议路由常量:
| 协议 | 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() 是 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;
}