跳到主要内容

SoE (Servo over EtherCAT)

SoE 协议将 SERCOS 伺服通信协议封装在 EtherCAT 邮箱中,适用于 SERCOS 兼容的伺服驱动器。

SoE vs CoE

大多数 EtherCAT 伺服驱动器使用 CoE + CiA 402 协议栈。 SoE 仅用于 SERCOS 兼容 的驱动器(如 Bosch Rexroth IndraDrive 系列)。 两者不能混用——从站支持哪种取决于硬件。通过 GetSlaveSoEDetails(master_index, slave) 返回值判断从站是否支持 SoE(非 0 表示支持)。

SERCOS IDN 体系

SoE 通过 IDN(Identification Number) 寻址驱动器参数。每个 IDN 是一个 16 位编号,代表一个参数(位置、速度、控制字等)。

IDN 命名规则:

前缀范围说明
S-x-xxxx0x0000 – 0x7FFFSERCOS 标准参数(所有厂商通用)
P-x-xxxx0x8000 – 0xBFFF产品特定参数
0xC000 – 0xFFFF厂商自定义参数

常用标准 IDN(伺服控制相关):

IDNSERCOS 名说明数据类型
S-0-0001 (0x0001)Cycle Time通信周期时间uint, μs
S-0-0011 (0x000B)Position Feedback 1实际位置int, inc
S-0-0012 (0x000C)Position Command目标位置int, inc
S-0-0013 (0x000D)Velocity Feedback实际速度int, rpm
S-0-0014 (0x000E)Velocity Command目标速度int, rpm
S-0-0019 (0x0013)Drive Status驱动器状态字ushort
S-0-0036 (0x0024)Max Velocity最大速度限制uint, rpm
S-0-0040 (0x0028)Homing Velocity回零速度uint, rpm
S-0-0064 (0x0040)Drive Control Word驱动器控制字ushort
S-0-0071 (0x0047)Drive Status Word驱动器状态字ushort

元素标志(Element Flags)— 读取 IDN 的不同"层面":

标志含义
0x01数据状态
0x02参数名称(字符串)
0x04属性(数据类型/长度/权限位域)
0x08单位(字符串)
0x10最小值
0x20最大值
0x40数据值(默认,最常用)
0x80默认值

Element Flag 常量宏

slave/soe.h 提供以下宏,避免在调用 dx_soe_read / dx_soe_write 时硬编码 element_flags 参数(ETG.1000.6 §5.11,7 位位掩码,可 OR 组合):

#define SOE_ELEM_NONE         0x00  /* 无 */
#define SOE_ELEM_DATA_STATUS 0x01 /* 数据状态字 (valid/invalid/busy) */
#define SOE_ELEM_NAME 0x02 /* IDN 名称字符串 */
#define SOE_ELEM_ATTRIBUTE 0x04 /* 数据属性 (数据类型/长度/权限位域) */
#define SOE_ELEM_UNIT 0x08 /* 单位字符串 */
#define SOE_ELEM_MIN 0x10 /* 最小值 */
#define SOE_ELEM_MAX 0x20 /* 最大值 */
#define SOE_ELEM_VALUE 0x40 /* 操作数据值 (最常用) */
#define SOE_ELEM_DEFAULT 0x80 /* 默认值 */

示例:

/* 读取参数名称 (避免硬编码 0x02) */
char* name = NULL;
int name_len = 0;
dll.dx_soe_read(master, 1, 0, SOE_ELEM_NAME, 0x000B, &name, &name_len, 5000);

/* 读取最大值 */
char* mx = NULL; int mx_sz = 0;
dll.dx_soe_read(master, 1, 0, SOE_ELEM_MAX, 0x0024, &mx, &mx_sz, 5000);

IDN 编解码

SERCOS IDN 是一个 16 位编号, 位布局为: bit15 = 标准位 (0 = S 标准 / 1 = P 厂商) | bit14..12 = 参数集 Set (3 位, 0-7) | bit11..0 = 数据块 Block (12 位, 0-4095)。slave/soe.h 提供 static inline 编解码工具(纯位运算,零运行时成本,与 DLL dx_soe_idn_encode/dx_soe_idn_decode 输出完全一致),减少手工位运算出错。

/* 将 (is_standard, parameter_set, data_block) 打包为 16 位 IDN */
uint16_t soe_idn_encode(int is_standard, uint8_t parameter_set, uint16_t data_block);

/* 解析 16 位 IDN 为各字段 (任一输出指针可为 NULL) */
void soe_idn_decode(uint16_t idn,
int* is_standard, uint8_t* parameter_set, uint16_t* data_block);

参数:

  • is_standard (int) — 非 0 = S-x-xxxx (SERCOS 标准 IDN); 0 = P-x-xxxx (厂商 IDN)
  • parameter_set (uint8_t) — 参数集, 0-7 (3 位)
  • data_block (uint16_t) — 数据块编号, 0-4095 (12 位)

示例:

#include "slave/soe.h"

/* S-0-0011 (Position Feedback 1): 标准 IDN, Set=0, Block=11 */
uint16_t idn = soe_idn_encode(1, 0, 11);
printf("IDN = 0x%04X\n", idn); /* 0x000B */

/* 反向解析 */
int is_std;
uint8_t set;
uint16_t block;
soe_idn_decode(0x000B, &is_std, &set, &block);
printf("%c-%u-%04u\n", is_std ? 'S' : 'P', set, block);

基本读写

函数命名

SoE 邮箱读写在 C SDK 中以 dx_soe_* 前缀提供。动态加载模式经 dll.dx_soe_read(...) 调用,静态链接模式直接调 dx_soe_read(...)

dx_soe_read()

BOOL dx_soe_read(uint16_t master_index, uint16_t slave, uint8_t drive_no,
uint8_t element_flags, uint16_t idn, char** data, int* data_size, int timeout);

读取 IDN 参数原始字节。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • drive_no (uint8_t) — 驱动器编号(多轴从站为 0, 1, 2...)
  • element_flags (uint8_t) — 元素标志(默认 0x40 = 数据值)
  • idn (uint16_t) — IDN 编号
  • data (char**) — 输出数据指针,需调用 FreeMemory() 释放
  • data_size (int*) — 输出数据大小
  • timeout (int) — 超时时间(微秒)

返回值:

  • BOOL — 成功返回 TRUE

dx_soe_write()

BOOL dx_soe_write(uint16_t master_index, uint16_t slave, uint8_t drive_no,
uint8_t element_flags, uint16_t idn, const char* data, int data_size, int timeout);

写入 IDN 参数。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • drive_no (uint8_t) — 驱动器编号
  • element_flags (uint8_t) — 元素标志
  • idn (uint16_t) — IDN 编号
  • data (const char*) — 要写入的数据
  • data_size (int) — 数据大小
  • timeout (int) — 超时时间(微秒)

返回值:

  • BOOL — 成功返回 TRUE

示例:

/* 读取实际位置 (S-0-0011) */
char* data = NULL;
int size = 0;
if (dll.dx_soe_read(master, 1, 0, SOE_ELEM_VALUE, 0x000B, &data, &size, 5000)) {
int32_t pos = *(int32_t*)data;
printf("位置: %d\n", pos);
dll.FreeMemory(data);
}

/* 写入目标速度 */
int32_t speed = 1000;
dll.dx_soe_write(master, 1, 0, SOE_ELEM_VALUE, 0x000E, (const char*)&speed, sizeof(speed), 5000);

属性读取

dx_soe_read_attributes()

BOOL dx_soe_read_attributes(uint16_t master_index, uint16_t slave, uint8_t drive_no,
uint16_t idn, uint32_t* attributes, int timeout);

读取 IDN 参数的属性信息(数据类型、长度、访问权限等位域)。

参数:

  • master_index (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • drive_no (uint8_t) — 驱动器编号
  • idn (uint16_t) — IDN 编号
  • attributes (uint32_t*) — 输出属性值
  • timeout (int) — 超时时间(微秒)

返回值:

  • BOOL — 成功返回 TRUE

dx_soe_read_name()

BOOL dx_soe_read_name(uint16_t master_index, uint16_t slave, uint8_t drive_no,
uint16_t idn, char** name, int* name_length, int timeout);

读取 IDN 参数的名称字符串。

参数:

  • name (char**) — 输出名称指针,需调用 FreeMemory() 释放
  • name_length (int*) — 输出名称长度

返回值:

  • BOOL — 成功返回 TRUE

dx_soe_read_unit()

BOOL dx_soe_read_unit(uint16_t master_index, uint16_t slave, uint8_t drive_no,
uint16_t idn, char** unit, int* unit_length, int timeout);

读取 IDN 参数的单位字符串。

参数:

  • unit (char**) — 输出单位指针,需调用 FreeMemory() 释放
  • unit_length (int*) — 输出单位长度

返回值:

  • BOOL — 成功返回 TRUE

dx_soe_read_idn_list()

BOOL dx_soe_read_idn_list(uint16_t master_index, uint16_t slave, uint8_t drive_no,
uint16_t** idn_list, int* list_count, int timeout);

读取从站支持的所有 IDN 列表。

参数:

  • idn_list (uint16_t**) — 输出 IDN 列表指针,需调用 FreeMemory() 释放
  • list_count (int*) — 输出 IDN 数量

返回值:

  • BOOL — 成功返回 TRUE

SoE 默认值读取

通过 dx_soe_read() + SOE_ELEM_DEFAULT 元素位读取 IDN 参数出厂默认值。多用于参数复位 / 校验。

dx_soe_read_min_max()

BOOL dx_soe_read_min_max(uint16_t master_index, uint16_t slave, uint8_t drive_no,
uint16_t idn, char** min_value, int* min_size,
char** max_value, int* max_size, int timeout);

读取 IDN 参数的最小值和最大值。

参数:

  • min_value (char**) — 输出最小值指针,需调用 FreeMemory() 释放
  • min_size (int*) — 输出最小值大小
  • max_value (char**) — 输出最大值指针,需调用 FreeMemory() 释放
  • max_size (int*) — 输出最大值大小

返回值:

  • BOOL — 成功返回 TRUE

示例:

/* 读取参数名称 */
char* name = NULL;
int name_len = 0;
if (dll.dx_soe_read_name(master, 1, 0, 0x000B, &name, &name_len, 5000)) {
printf("名称: %.*s\n", name_len, name);
dll.FreeMemory(name);
}

/* 读取参数范围 */
char *min_val = NULL, *max_val = NULL;
int min_sz = 0, max_sz = 0;
if (dll.dx_soe_read_min_max(master, 1, 0, 0x0024, &min_val, &min_sz, &max_val, &max_sz, 5000)) {
printf("最大速度范围: %d ~ %d\n", *(int32_t*)min_val, *(int32_t*)max_val);
dll.FreeMemory(min_val);
dll.FreeMemory(max_val);
}

/* 读取 IDN 列表 */
uint16_t* idn_list = NULL;
int idn_count = 0;
if (dll.dx_soe_read_idn_list(master, 1, 0, &idn_list, &idn_count, 5000)) {
printf("支持 %d 个 IDN\n", idn_count);
for (int i = 0; i < idn_count; i++)
printf(" IDN 0x%04X\n", idn_list[i]);
dll.FreeMemory(idn_list);
}

参数信息

可用 IDN 枚举

通过 dx_soe_read_idn_list() 获取从站支持的所有 IDN 编号列表,用于参数扫描和能力检测。

参数完整描述

组合调用 dx_soe_read_name() / dx_soe_read_unit() / dx_soe_read_attributes() / dx_soe_read_min_max() 一次性获取 IDN 完整描述(名称、单位、属性、值域)。常用于参数管理 UI 自动生成。

AT/MDT 映射

IDN 映射查询

通过读取 IDN 列表 (S-0-0024 等映射相关参数) 获取从站当前的 AT (Acknowledge Telegram) / MDT (Master Data Telegram) PDO 映射,定位关键 IDN 在循环帧中的字节偏移。

多轴映射枚举

枚举多轴从站每个 drive_number 的 AT/MDT 映射,常用于多轴伺服的统一扫描。

程序命令

命令执行

SERCOS 程序命令 IDN(如 S-0-0099 复位、S-0-0148 回零)通过 dx_soe_write() 写值 0x0003 启动,然后轮询读取数据状态字段 (元素 0x01) 等待完成。

数据状态读取

通过 dx_soe_read() + SOE_ELEM_DATA_STATUS 元素位读取 IDN 数据状态(命令是否完成、出错等)。

类型化读写 (ethercat_advanced.h)

ethercat_advanced.h 提供便捷的类型化 SoE 读写函数,自动处理类型转换。

类型化读取

所有读取函数签名一致: soe_read_xxx(dll, master, slave, drive, idn, out_ptr),返回 0 成功。

  • soe_read_i16 (int16_t*) — 读取 16 位有符号整数
  • soe_read_i32 (int32_t*) — 读取 32 位有符号整数
  • soe_read_u16 (uint16_t*) — 读取 16 位无符号整数
  • soe_read_u32 (uint32_t*) — 读取 32 位无符号整数
  • soe_read_i64 (int64_t*) — 读取 64 位有符号整数
  • soe_read_u64 (uint64_t*) — 读取 64 位无符号整数
  • soe_read_f32 (float*) — 读取单精度浮点数
  • soe_read_f64 (double*) — 读取双精度浮点数
  • soe_read_string (char* buf, int buf_size) — 读取字符串

类型化写入

所有写入函数签名一致: soe_write_xxx(dll, master, slave, drive, idn, value),返回 0 成功。

  • soe_write_i16 (int16_t) — 写入 16 位有符号整数
  • soe_write_i32 (int32_t) — 写入 32 位有符号整数
  • soe_write_u16 (uint16_t) — 写入 16 位无符号整数
  • soe_write_u32 (uint32_t) — 写入 32 位无符号整数
  • soe_write_f32 (float) — 写入单精度浮点数
  • soe_write_f64 (double) — 写入双精度浮点数

示例:

#include "ethercat_advanced.h"

/* 读取实际位置 */
int32_t position;
if (soe_read_i32(&dll, master, 1, 0, 0x000B, &position) == 0) {
printf("实际位置: %d\n", position);
}

/* 写入目标速度 */
soe_write_i32(&dll, master, 1, 0, 0x000E, 5000);

/* 读取驱动器名称 */
char name[64];
soe_read_string(&dll, master, 1, 0, 0x000B, name, sizeof(name));
printf("参数名: %s\n", name);

参数变化通知 (ethercat_advanced.h)

SoE Notification 功能允许监控 SERCOS 从站 IDN 参数的变化。当参数值发生改变时,通过回调通知应用程序。

实现机制:

  1. 硬件通知:通过写入 S-0-0127(通知请求 IDN)尝试启用从站的原生 SoE Notification(OpCode=5)
  2. 轮询检测:如果从站不支持硬件通知,自动回退到轮询模式,周期性读取参数值并与基线比较

通知结构与类型

/* 通知事件参数 */
typedef struct {
uint16_t slave_index; /* 从站索引 */
uint8_t drive_number; /* 驱动器编号 */
uint16_t idn; /* 变化的 IDN 编号 */
uint8_t* new_value; /* 新值 (需调用 free 释放) */
int new_value_size; /* 新值大小 */
uint8_t* old_value; /* 旧值 (需调用 free 释放) */
int old_value_size; /* 旧值大小 */
} soe_notification_t;

/* 通知回调类型 */
typedef void (*soe_notification_callback_t)(const soe_notification_t* event, void* user_data);

soe_notifier_create()

soe_notifier_t* soe_notifier_create(dll_t* dll, uint16_t master,
uint16_t slave, uint8_t drive);

创建通知管理器实例。

参数:

  • dll (dll_t*) — DLL 实例
  • master (uint16_t) — 主站索引
  • slave (uint16_t) — 从站索引
  • drive (uint8_t) — 驱动器编号

返回值:

  • soe_notifier_t* — 通知管理器实例,需调用 soe_notifier_destroy() 释放

soe_notifier_destroy()

void soe_notifier_destroy(soe_notifier_t* notifier);

销毁通知管理器,释放资源。

soe_enable_notification()

int soe_enable_notification(soe_notifier_t* notifier, uint16_t idn, int timeout_ms);

启用指定 IDN 的参数变化通知。首先尝试通过 S-0-0127 启用硬件通知,无论是否成功都会将 IDN 加入轮询监控列表。

参数:

  • notifier — 通知管理器实例
  • idn (uint16_t) — 要监控的 IDN 编号
  • timeout_ms (int) — 超时时间(毫秒)

返回值:

  • 1 — 从站确认支持硬件通知
  • 0 — 回退到轮询模式

soe_disable_notification()

void soe_disable_notification(soe_notifier_t* notifier, uint16_t idn);

禁用指定 IDN 的参数变化通知,从监控列表中移除。

soe_disable_all_notifications()

void soe_disable_all_notifications(soe_notifier_t* notifier);

禁用所有已启用的通知,清空监控列表。

soe_poll_notifications()

int soe_poll_notifications(soe_notifier_t* notifier, int timeout_ms,
soe_notification_callback_t callback, void* user_data);

轮询检测所有已监控 IDN 的参数变化。逐个读取当前值并与上次记录值比较,发现变化时触发回调。适合在定时器或后台线程中周期性调用。

参数:

  • notifier — 通知管理器实例
  • timeout_ms (int) — 每个 IDN 读取的超时时间(毫秒)
  • callback — 参数变化回调函数
  • user_data — 用户数据指针,传递给回调

返回值:

  • int — 本次轮询检测到变化的 IDN 数量

soe_notification_free()

void soe_notification_free(soe_notification_t* event);

释放通知事件中动态分配的 new_valueold_value 内存。

标准 IDN 常量

IDN名称说明
0x0001S-0-0001通信周期时间
0x000BS-0-0011实际位置 (Position Feedback 1)
0x000CS-0-0012目标位置 (Position Command)
0x000DS-0-0013实际速度 (Velocity Feedback)
0x000ES-0-0014目标速度 (Velocity Command)
0x0013S-0-0019驱动器状态字
0x0024S-0-0036最大速度限制
0x0028S-0-0040回零速度
0x0040S-0-0064驱动器控制字
0x0047S-0-0071驱动器状态字
0x0063S-0-0099复位命令
0x007FS-0-0127通知请求 IDN
0x0094S-0-0148回零命令

异步通知

通知事件接收

通过 advanced.h 的 soe_notifier_* 系列轮询接收 SoE 通知事件,回调签名 void(const soe_notification_t*, void*)。详见上文 soe_poll_notifications()

紧急消息接收

SoE 邮箱可携带 SERCOS Emergency 帧(驱动器报警),SDK 自动解析并通过 RegisterEmergency 全局回调统一上报,与 CoE EMCY 同入口。详见 事件 - RegisterEmergency

线程模型

soe_poll_notifications() 在调用线程内同步执行(一次轮询遍历所有已注册 IDN)。建议在专用后台线程或定时器中调用,不要塞进 PDO 周期回调。回调中触发的事件值生命周期由 SDK 管理,必须调用 soe_notification_free()

注意事项

  • 硬件通知 (S-0-0127) 在大多数厂商的从站上行为不一致, SDK 默认双轨工作 (硬件通知 + 轮询兜底)
  • 多 drive 从站需为每个 drive_number 创建独立 Notifier 实例
  • 通知不保证单调时序, 应用层需自行处理 old_value 与 new_value 的边界情况

C SDK 通过 advanced.h 的 soe_notifier_* 系列轮询接收 SoE 通知事件。SDK 不在 dll_t 主表暴露 SoE 全局回调入口(C# DLL 的 RegisterSoENotificationCallback 等在 C SDK 未绑定),统一使用轮询式 Notifier。

数据生命周期

通知事件中的 new_value / old_value 由 SDK 分配,使用完毕需调用 soe_notification_free 释放。

SoE 通知在 C SDK 的唯一入口

C SDK 仅提供 advanced.hsoe_notifier_* 主动轮询包装。它内部会先尝试 S-0-0127 硬件通知, 失败自动回退轮询, 兼顾即时性与通用兼容性, 无需另行注册全局回调。

完整示例

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

/* 通知回调函数 */
void on_soe_change(const soe_notification_t* event, void* user_data) {
printf("参数变化: IDN 0x%04X, 从站 %d, 驱动器 %d\n",
event->idn, event->slave_index, event->drive_number);
printf(" 旧值 (%d 字节): ", event->old_value_size);
for (int i = 0; i < event->old_value_size; i++)
printf("%02X ", event->old_value[i]);
printf("\n 新值 (%d 字节): ", event->new_value_size);
for (int i = 0; i < event->new_value_size; i++)
printf("%02X ", event->new_value[i]);
printf("\n");
}

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;

/* 类型化读写 */
int32_t position;
if (soe_read_i32(&dll, master, slave, 0, 0x000B, &position) == 0) {
printf("实际位置: %d\n", position);
}

/* 读取参数属性 */
uint32_t attrs;
if (dll.dx_soe_read_attributes(master, slave, 0, 0x000B, &attrs, 5000)) {
printf("属性: 0x%08X\n", attrs);
}

/* 创建通知管理器 */
soe_notifier_t* notifier = soe_notifier_create(&dll, master, slave, 0);

/* 启用监控实际位置 (S-0-0011) */
int hw = soe_enable_notification(notifier, 0x000B, 500);
printf("硬件通知: %s\n", hw ? "支持" : "回退到轮询");

/* 启用监控驱动器状态字 (S-0-0071) */
soe_enable_notification(notifier, 0x0047, 500);

/* 在定时器或循环中周期性轮询 */
for (int i = 0; i < 100; i++) {
int changed = soe_poll_notifications(notifier, 200, on_soe_change, NULL);
if (changed > 0)
printf("检测到 %d 个参数变化\n", changed);
}

/* 停止监控并清理 */
soe_disable_all_notifications(notifier);
soe_notifier_destroy(notifier);

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