VoE (Vendor over EtherCAT)
VoE 协议允许设备厂商在 EtherCAT 邮箱中传输自定义私有数据,用于实现非标准的厂商特定功能(Mailbox Type 0x0F)。
函数命名
VoE 邮箱收发在 C SDK 中以 dx_voe_* 前缀提供。动态加载模式经 dll.dx_voe_send(...) 调用,静态链接模式直接调 dx_voe_send(...)。
功能检测
dx_voe_is_supported()
BOOL dx_voe_is_supported(uint16_t master_index, uint16_t slave);
检查从站是否支持 VoE 协议。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引
返回值:
BOOL— 支持返回TRUE
结构化收发
dx_voe_send()
BOOL dx_voe_send(uint16_t master_index, uint16_t slave, uint32_t vendor_id,
uint16_t vendor_type, const uint8_t* data, int size, int timeout);
发送带有厂商标识的 VoE 数据包。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引vendor_id(uint32_t) — 厂商 IDvendor_type(uint16_t) — 厂商自定义类型data(const uint8_t*) — 数据size(int) — 数据大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
dx_voe_receive()
BOOL dx_voe_receive(uint16_t master_index, uint16_t slave, uint32_t* vendor_id,
uint16_t* vendor_type, uint8_t** data, int* size, int timeout);
接收带有厂商标识的 VoE 数据包。
参数:
vendor_id(uint32_t*) — 输出厂商 IDvendor_type(uint16_t*) — 输出厂商自定义类型data(uint8_t**) — 输出数据指针,需调用FreeMemory()释放size(int*) — 输出数据大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
原始收发
dx_voe_send_raw()
BOOL dx_voe_send_raw(uint16_t master_index, uint16_t slave,
const uint8_t* data, int size, int timeout);
发送原始 VoE 数据(不含厂商头部)。
参数:
data(const uint8_t*) — 帧数据size(int) — 帧大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
dx_voe_receive_raw()
BOOL dx_voe_receive_raw(uint16_t master_index, uint16_t slave,
uint8_t** data, int* size, int timeout);
接收原始 VoE 数据(不含厂商头部)。
参数:
data(uint8_t**) — 输出数据指针,需调用FreeMemory()释放size(int*) — 输出数据大小
返回值:
BOOL— 成功返回TRUE
异步通知 (slave/voe.h)
VoE 不像 CoE 那样有标准的事件机制, 但厂商常需要"从站主动推送" → "应用回调" 的语义。slave/voe.h 通过后台监听线程实现, 应用层只需注册订阅就能在数据到达时收到通知。对应底层接口 dx_voe_start_notification_listener / dx_voe_register_notification 等由 advanced 层封装, 应用层无需直接调用。
voe_notification_cb_t
typedef void (*voe_notification_cb_t)(uint16_t slave,
uint32_t vendor_id,
uint16_t vendor_type,
const uint8_t* data,
uint32_t data_size,
void* user_data);
回调参数:
slave(uint16_t) — 从站索引vendor_id(uint32_t) — 厂商 IDvendor_type(uint16_t) — 厂商自定义类型data(const uint8_t*) — 收到的 VoE 数据 (仅回调期间有效)data_size(uint32_t) — 数据大小user_data(void*) — 注册时传入的上下文指针
voe_start_notification_listener()
int voe_start_notification_listener(uint16_t master_index);
启动指定主站的 VoE 通知监听线程。返回 1=成功, 0=FFI 未就绪 / 失败。
voe_register_notification()
int voe_register_notification(uint16_t master_index, uint16_t slave,
uint32_t vendor_id, uint16_t vendor_type,
voe_notification_cb_t cb, void* user_data);
注册一个 VoE 通知订阅 (按 vendor_id / vendor_type 过滤)。返回 >= 0 的订阅索引, 负值表示失败。
voe_unregister_notification()
int voe_unregister_notification(int subscription_index);
按订阅索引注销订阅。
voe_stop_notification_listener()
int voe_stop_notification_listener(void);
停止监听线程。
voe_is_notification_listening()
int voe_is_notification_listening(void);
查询监听线程是否在运行 (1=运行中)。
示例:
#include "slave/voe.h"
static void on_voe_data(uint16_t si, uint32_t vid, uint16_t vtype,
const uint8_t* data, uint32_t size, void* user)
{
printf("VoE 推送: slave=%u, VendorID=0x%08X, Type=0x%04X, %u 字节\n",
si, vid, vtype, size);
/* data 仅回调期间有效, 需要持久化请立即复制 */
}
int main(void)
{
dll_t dll;
LOAD_DLL(&dll, "DarraEtherCAT.dll");
uint16_t master = dll.Initialize();
/* ... 初始化省略 ... */
/* 启动监听线程 */
voe_start_notification_listener(master);
/* 注册从站 1 / 2 的订阅 */
int sub1 = voe_register_notification(master, 1, 0x12345678, 0x0001, on_voe_data, NULL);
int sub2 = voe_register_notification(master, 2, 0x12345678, 0x0001, on_voe_data, NULL);
/* ... 业务循环 ... */
/* 停止并清理 */
voe_unregister_notification(sub1);
voe_unregister_notification(sub2);
voe_stop_notification_listener();
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}
回调线程
回调在 advanced 层内部的监听线程上调用, 与主程序异步。访问共享数据请使用互斥锁; 不要在回调内调用阻塞式 dx_voe_send / dx_voe_receive, 会与监听自身竞争邮箱。
完整示例
#define DYNAMIC_LOAD
#include "ethercat.h"
#include <stdio.h>
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;
/* 检查 VoE 支持 */
if (!dll.dx_voe_is_supported(master, slave)) {
printf("从站不支持 VoE\n");
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 1;
}
/* 发送结构化数据 */
uint8_t cmd[] = {0x01, 0x02, 0x03, 0x04};
if (dll.dx_voe_send(master, slave, 0x12345678, 0x0001, cmd, sizeof(cmd), 5000000)) {
printf("VoE 发送成功\n");
}
/* 接收响应 */
uint32_t vid;
uint16_t vtype;
uint8_t* resp = NULL;
int resp_size = 0;
if (dll.dx_voe_receive(master, slave, &vid, &vtype, &resp, &resp_size, 5000000)) {
printf("VoE 响应: VendorID=0x%08X, Type=0x%04X, %d 字节\n", vid, vtype, resp_size);
for (int i = 0; i < resp_size; i++)
printf("%02X ", resp[i]);
printf("\n");
dll.FreeMemory(resp);
}
/* 原始帧收发 */
uint8_t raw[] = {0xAA, 0xBB, 0xCC};
dll.dx_voe_send_raw(master, slave, raw, sizeof(raw), 5000000);
uint8_t* raw_resp = NULL;
int raw_size = 0;
if (dll.dx_voe_receive_raw(master, slave, &raw_resp, &raw_size, 5000000)) {
printf("原始响应: %d 字节\n", raw_size);
dll.FreeMemory(raw_resp);
}
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}