SoE (Servo over EtherCAT)
SoE 协议将 SERCOS 伺服通信协议封装在 EtherCAT 邮箱中,适用于 SERCOS 兼容的伺服驱动器。
大多数 EtherCAT 伺服驱动器使用 CoE + CiA 402 协议栈。
SoE 仅用于 SERCOS 兼容 的驱动器(如 Bosch Rexroth IndraDrive 系列)。
两者不能混用——从站支持哪种取决于硬件。通过 GetSlaveSoEDetails(master, slave) 返回值判断从站是否支持 SoE(非 0 表示支持)。
SERCOS IDN 体系
SoE 通过 IDN(Identification Number) 寻址驱动器参数。每个 IDN 是一个 16 位编号,代表一个参数(位置、速度、控制字等)。
IDN 命名规则:
| 前缀 | 范围 | 说明 |
|---|---|---|
| S-x-xxxx | 0x0000 – 0x7FFF | SERCOS 标准参数(所有厂商通用) |
| P-x-xxxx | 0x8000 – 0xBFFF | 产品特定参数 |
| — | 0xC000 – 0xFFFF | 厂商自定义参数 |
常用标准 IDN(伺服控制相关):
| IDN | SERCOS 名 | 说明 | 数据类型 |
|---|---|---|---|
| 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 常量宏
ethercat_advanced.h 提供以下宏,避免在调用 SoERead/Write 时硬编码 element_flags 参数:
#define SOE_ELEM_DATASTATE 0x01 /* 数据状态 */
#define SOE_ELEM_NAME 0x02 /* 参数名称 (字符串) */
#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.SoERead(master, 1, 0, SOE_ELEM_NAME, 0x000B, &name, &name_len, 5000);
/* 读取最大值 */
char* mx = NULL; int mx_sz = 0;
dll.SoERead(master, 1, 0, SOE_ELEM_MAX, 0x0024, &mx, &mx_sz, 5000);
IDN 编解码
SERCOS IDN 是一个 16 位编号, 由 4 个字段构成: Set (3 位) | Type (1 位, S=0/P=1) | Block (4 位) | SI (8 位)。ethercat_advanced.h 提供编解码工具, 减少手工位运算出错。
/* 将 (set, type, block, sub) 打包为 16 位 IDN */
uint16_t soe_idn_encode(uint8_t set, uint8_t type,
uint8_t block, uint8_t sub);
/* 解析 16 位 IDN 为各字段 */
void soe_idn_decode(uint16_t idn,
uint8_t* set, uint8_t* type,
uint8_t* block, uint8_t* sub);
参数:
set(uint8_t) — Parameter Set, 0-7type(uint8_t) — 0 = S (SERCOS 标准, 即S-x-xxxx), 1 = P (产品特定, 即P-x-xxxx)block(uint8_t) — Data Block, 0-15sub(uint8_t) — Structure Index, 0-255
示例:
#include "ethercat_advanced.h"
/* S-0-0011 (Position Feedback 1) */
uint16_t idn = soe_idn_encode(0, 0, 0, 11);
printf("IDN = 0x%04X\n", idn); /* 0x000B */
/* 反向解析 */
uint8_t s, t, b, si;
soe_idn_decode(0x000B, &s, &t, &b, &si);
printf("%c-%u-%04u\n", t == 0 ? 'S' : 'P', s, (b << 8) | si);
基本读写
SoERead()
BOOL SoERead(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
SoEWrite()
BOOL SoEWrite(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.SoERead(master, 1, 0, 0x40, 0x000B, &data, &size, 5000)) {
int32_t pos = *(int32_t*)data;
printf("位置: %d\n", pos);
dll.FreeMemory(data);
}
/* 写入目标速度 */
int32_t speed = 1000;
dll.SoEWrite(master, 1, 0, 0x00, 0x000E, (const char*)&speed, sizeof(speed), 5000);
属性读取
SoEReadAttributes()
BOOL SoEReadAttributes(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
SoEReadName()
BOOL SoEReadName(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
SoEReadUnit()
BOOL SoEReadUnit(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
SoEReadIDNList()
BOOL SoEReadIDNList(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
SoEReadDefault()
通过 SoERead() + SOE_ELEM_DEFAULT 元素位读取 IDN 参数出厂默认值。多用于参数复位 / 校验。
SoEReadMinMax()
BOOL SoEReadMinMax(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.SoEReadName(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.SoEReadMinMax(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.SoEReadIDNList(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);
}
参数信息
GetAvailableIDNs
通过 SoEReadIDNList() 获取从站支持的所有 IDN 编号列表,用于参数扫描和能力检测。
GetParameterInfo
组合调用 SoEReadName() / SoEReadUnit() / SoEReadAttributes() / SoEReadMinMax() 一次性获取 IDN 完整描述(名称、单位、属性、值域)。常用于参数管理 UI 自动生成。
AT/MDT 映射
GetIDNMapping
通过读取 IDN 列表 (S-0-0024 等映射相关参数) 获取从站当前的 AT (Acknowledge Telegram) / MDT (Master Data Telegram) PDO 映射,定位关键 IDN 在循环帧中的字节偏移。
GetAllDriveMappings
枚举多轴从站每个 drive_number 的 AT/MDT 映射,常用于多轴伺服的统一扫描。
程序命令
ExecuteCommand
SERCOS 程序命令 IDN(如 S-0-0099 复位、S-0-0148 回零)通过 SoEWrite() 写值 0x0003 启动,然后轮询读取数据状态字段 (元素 0x01) 等待完成。
ReadDataState
通过 SoERead() + SOE_ELEM_DATASTATE 元素位读取 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 参数的变化。当参数值发生改变时,通过回调通知应用程序。
实现机制:
- 硬件通知:通过写入 S-0-0127(通知请求 IDN)尝试启用从站的原生 SoE Notification(OpCode=5)
- 轮询检测:如果从站不支持硬件通知,自动回退到轮询模式,周期性读取参数值并与基线比较
通知结构与类型
/* 通知事件参数 */
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_value 和 old_value 内存。
标准 IDN 常量
| IDN | 名称 | 说明 |
|---|---|---|
| 0x0001 | S-0-0001 | 通信周期时间 |
| 0x000B | S-0-0011 | 实际位置 (Position Feedback 1) |
| 0x000C | S-0-0012 | 目标位置 (Position Command) |
| 0x000D | S-0-0013 | 实际速度 (Velocity Feedback) |
| 0x000E | S-0-0014 | 目标速度 (Velocity Command) |
| 0x0013 | S-0-0019 | 驱动器状态字 |
| 0x0024 | S-0-0036 | 最大速度限制 |
| 0x0028 | S-0-0040 | 回零速度 |
| 0x0040 | S-0-0064 | 驱动器控制字 |
| 0x0047 | S-0-0071 | 驱动器状态字 |
| 0x0063 | S-0-0099 | 复位命令 |
| 0x007F | S-0-0127 | 通知请求 IDN |
| 0x0094 | S-0-0148 | 回零命令 |
异步通知
NotificationReceived
通过 advanced.h 的 soe_notifier_* 系列轮询接收 SoE 通知事件,回调签名 void(const soe_notification_t*, void*)。详见上文 soe_poll_notifications()。
EmergencyReceived
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 的边界情况
通过 advanced.h 的 soe_notifier_* 系列轮询接收 SoE 通知事件。SDK 不在主表暴露 SoE 全局回调入口,推荐使用轮询式 Notifier。
通知事件中的 new_value / old_value 由 SDK 分配,使用完毕需调用 soe_notification_free 释放。
- 本节的
RegisterSoE*Callback是 DLL 全局回调, 适合需要硬件推送即时响应的场景 - advanced.h 的
soe_notifier_*是主动轮询包装, 适合通用兼容性场景 - 两者可同时使用, 互不冲突
完整示例
#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, "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;
/* 类型化读写 */
int32_t position;
if (soe_read_i32(&dll, master, slave, 0, 0x000B, &position) == 0) {
printf("实际位置: %d\n", position);
}
/* 读取参数属性 */
uint32_t attrs;
if (dll.SoEReadAttributes(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;
}