跳到主要内容

VoE (Vendor over EtherCAT)

VoE 协议允许设备厂商在 EtherCAT 邮箱中传输自定义私有数据,用于实现非标准的厂商特定功能(Mailbox Type 0x0F)。

功能检测

VOEIsSupported()

BOOL VOEIsSupported(uint16_t master_index, uint16_t slave);

检查从站是否支持 VoE 协议。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引

返回值:

  • BOOL — 支持返回 TRUE

结构化收发

VOESend()

BOOL VOESend(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) — 厂商 ID
  • vendor_type (uint16_t) — 厂商自定义类型
  • data (const uint8_t*) — 数据
  • size (int) — 数据大小
  • timeout (int) — 超时时间(微秒)

返回值:

  • BOOL — 成功返回 TRUE

VOEReceive()

BOOL VOEReceive(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*) — 输出厂商 ID
  • vendor_type (uint16_t*) — 输出厂商自定义类型
  • data (uint8_t**) — 输出数据指针,需调用 FreeMemory() 释放
  • size (int*) — 输出数据大小
  • timeout (int) — 超时时间(微秒)

返回值:

  • BOOL — 成功返回 TRUE

原始收发

VOESendRaw()

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

VOEReceiveRaw()

BOOL VOEReceiveRaw(uint16_t master_index, uint16_t slave,
uint8_t** data, int* size, int timeout);

接收原始 VoE 数据(不含厂商头部)。

参数:

  • data (uint8_t**) — 输出数据指针,需调用 FreeMemory() 释放
  • size (int*) — 输出数据大小

返回值:

  • BOOL — 成功返回 TRUE

异步通知 (ethercat_advanced.h)

VoE 不像 CoE 那样有标准的事件机制, 但厂商常需要"从站主动推送" → "应用回调" 的语义。SDK 通过周期性轮询 VOEReceiveRaw 实现, 应用层只需注册回调就能在数据到达时收到通知。

voe_notification_cb_t

typedef void (*voe_notification_cb_t)(
uint16_t master, uint16_t slave,
const uint8_t* data, int size,
void* user_data);

回调参数:

  • master (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • data (const uint8_t*) — 收到的 VoE 原始数据 (仅回调期间有效)
  • size (int) — 数据大小
  • user_data (void*) — 注册时传入的上下文指针

voe_notification_register()

int voe_notification_register(dll_t* dll,
uint16_t master, uint16_t slave,
voe_notification_cb_t callback, void* user_data);

为指定从站注册 VoE 数据到达回调。同一从站重复注册会覆盖旧回调。

返回值:

  • int — 0 成功, 非 0 失败

voe_notification_unregister()

int voe_notification_unregister(dll_t* dll,
uint16_t master, uint16_t slave);

取消注册指定从站的 VoE 回调。

voe_notification_set_poll_interval()

void voe_notification_set_poll_interval(uint32_t interval_ms);

设置后台轮询周期 (默认 100ms)。值越小响应越快, 但邮箱占用越高。

voe_notification_start() / voe_notification_stop()

int voe_notification_start(dll_t* dll, uint16_t master);
int voe_notification_stop(dll_t* dll, uint16_t master);

启动/停止后台轮询线程。Register 后必须调用 Start 才会有回调; Unregister 后建议在所有从站都已取消时调用 Stop 释放线程。

示例:

#include "ethercat_advanced.h"

static void on_voe_data(uint16_t mi, uint16_t si,
const uint8_t* data, int size, void* user)
{
printf("VoE 推送: slave=%u, %d 字节\n", si, size);
/* data 仅回调期间有效, 需要持久化请立即复制 */
}

int main(void)
{
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.dll");
uint16_t master = dll.Initialize();
/* ... 初始化省略 ... */

/* 设 50ms 轮询 */
voe_notification_set_poll_interval(50);

/* 监听从站 1 / 2 */
voe_notification_register(&dll, master, 1, on_voe_data, NULL);
voe_notification_register(&dll, master, 2, on_voe_data, NULL);

/* 启动轮询线程 */
voe_notification_start(&dll, master);

/* ... 业务循环 ... */

/* 停止并清理 */
voe_notification_unregister(&dll, master, 1);
voe_notification_unregister(&dll, master, 2);
voe_notification_stop(&dll, master);

dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}
回调线程

回调在 advanced.h 内部的轮询线程上调用, 与主程序异步。访问共享数据请使用互斥锁; 不要在回调内调用阻塞式 VOESend/Receive, 会与轮询自身竞争邮箱。

完整示例

#define DYNAMIC_LOAD
#include "ethercat.h"
#include <stdio.h>

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;

/* 检查 VoE 支持 */
if (!dll.VOEIsSupported(master, slave)) {
printf("从站不支持 VoE\n");
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 1;
}

/* 发送结构化数据 */
uint8_t cmd[] = {0x01, 0x02, 0x03, 0x04};
if (dll.VOESend(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.VOEReceive(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.VOESendRaw(master, slave, raw, sizeof(raw), 5000000);

uint8_t* raw_resp = NULL;
int raw_size = 0;
if (dll.VOEReceiveRaw(master, slave, &raw_resp, &raw_size, 5000000)) {
printf("原始响应: %d 字节\n", raw_size);
dll.FreeMemory(raw_resp);
}

dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}