FSoE 功能安全
FSoE (Functional Safety over EtherCAT) 安全通信接口,在标准 PDO 通道上叠加安全协议层,实现 SIL3/PLe 等级的功能安全通信 (ETG.5100/5120)。
FSoE 设备在从站初始化时自动检测。检测过程:
- CoE 前置条件 — 从站必须支持 CoE 邮箱协议
- 0xF980:01 检测 — 尝试 SDO 读取设备级 FSoE 安全地址,成功则确认支持
- 0x9001:02 检测 — 若上步失败,尝试读取 MDP 连接参数(适用于多模块安全设备)
仅检查 CoE 支持是不够的,因为很多非安全设备(如普通伺服驱动器)也支持 CoE。FSoE 设备必须实现特定的安全对象索引才能被正确识别。
状态机
FSoE 在 EtherCAT PDO 通道上建立独立的安全连接。主站负责协议管理,从站负责安全逻辑。
状态机流程:
- Reset — 初始状态,等待主站发起会话
- Session — 会话建立,交换连接 ID
- Connection — 连接建立,验证安全地址
- Parameter — 参数下载阶段(SRA CRC 校验)
- Data — 正常安全数据交换
- Failsafe — 失效安全,从站输出安全值
FSoE 与普通 PDO 的区别
FSoE 安全数据不能像普通 PDO 那样直接映射。IOmap 中存放的是 FSoE 协议帧,用户的安全数据被包裹在帧内部,且需要经过校验。
SDK 的 FSoE 协议引擎负责:
- CRC 校验 — 验证输入帧完整性、计算输出帧 CRC
- 状态机管理 — Session -> Connection -> Parameter -> Data 自动推进
- 看门狗 — 监控通信超时,超时自动进入 Failsafe
- 序列号 — 检测帧丢失和重复
绕过 DLL 直接读写 IOmap 会破坏安全协议完整性,因此 FSoE 必须通过独立缓冲区访问。
安全数据结构体
/* EL19xx 数字量安全输入(2 字节安全数据) */
#pragma pack(push, 1)
typedef struct {
uint8_t InputBits; /* 安全输入状态位(每位对应一个通道) */
uint8_t DiagBits; /* 输入诊断状态 */
} safe_digital_input_t;
/* EL29xx 数字量安全输出(1 字节安全数据) */
typedef struct {
uint8_t OutputBits; /* 安全输出控制位 */
} safe_digital_output_t;
/* ETG.6100 安全驱动输入(10 字节安全数据) */
typedef struct {
uint16_t SafetyStatusWord; /* 安全状态字 */
int32_t SafeActualPosition; /* 安全实际位置 */
int32_t SafeActualVelocity; /* 安全实际速度 */
} safe_drive_input_t;
/* ETG.6100 安全驱动输出(2 字节安全数据) */
typedef struct {
uint16_t SafetyControlWord; /* 安全控制字 */
} safe_drive_output_t;
/* 安全编码器输入(6 字节安全数据) */
typedef struct {
uint32_t SafePosition; /* 安全位置值 */
uint16_t StatusWord; /* 状态字 */
} safe_encoder_input_t;
#pragma pack(pop)
SafeInputSize / SafeOutputSize 必须与结构体的 sizeof() 一致,否则读写会失败。
类型定义
fsoe_state_t
typedef enum {
FSOE_STATE_RESET = 0x100, /* 初始/重置状态 */
FSOE_STATE_SESSION = 0x101, /* 会话建立 */
FSOE_STATE_CONNECTION = 0x102, /* 连接建立 */
FSOE_STATE_PARAMETER = 0x103, /* 参数下载 */
FSOE_STATE_DATA = 0x104, /* 数据交换 */
FSOE_STATE_FAILSAFE = 0x105 /* 失效安全 */
} fsoe_state_t;
fsoe_error_t
typedef enum {
FSOE_ERROR_NONE = 0x0000, /* 无错误 */
FSOE_ERROR_WRONG_COMMAND = 0x0001, /* 错误的命令 */
FSOE_ERROR_UNKNOWN_COMMAND = 0x0002, /* 未知命令 */
FSOE_ERROR_WRONG_CONN_ID = 0x0003, /* 连接ID不匹配 */
FSOE_ERROR_CRC = 0x0004, /* CRC校验失败 */
FSOE_ERROR_WATCHDOG = 0x0005, /* 看门狗超时 */
FSOE_ERROR_WRONG_ADDRESS = 0x0006, /* 错误的FSoE地址 */
FSOE_ERROR_WRONG_DATA = 0x0007, /* 无效数据 */
FSOE_ERROR_COMM_PARAM_LENGTH = 0x0008, /* 通信参数长度错误 */
FSOE_ERROR_COMM_PARAM = 0x0009, /* 通信参数错误 */
FSOE_ERROR_APP_PARAM_LENGTH = 0x000A, /* 应用参数长度错误 */
FSOE_ERROR_APP_PARAM = 0x000B, /* 应用参数错误 */
FSOE_ERROR_UNEXPECTED_SESSION = 0x000C, /* 意外的会话命令 */
FSOE_ERROR_FAILSAFE_DATA = 0x000D, /* 收到失效安全数据 */
FSOE_ERROR_NOT_INITIALIZED = 0x0100, /* FSoE未初始化 (内部错误) */
FSOE_ERROR_MAX_CONNECTIONS = 0x0101, /* 达到最大连接数 (内部错误) */
FSOE_ERROR_INVALID_STATE = 0x0102 /* 无效状态转换 (内部错误) */
} fsoe_error_t;
fsoe_config_t
typedef struct {
uint16_t ConnectionId; /* 唯一连接标识符 */
uint16_t SafetyAddress; /* FSoE从站安全地址 */
uint32_t WatchdogTimeMs; /* 看门狗超时(毫秒) */
uint16_t SafeInputSize; /* 安全输入数据大小(字节) */
uint16_t SafeOutputSize; /* 安全输出数据大小(字节) */
uint32_t PdoInputOffset; /* IOmap中输入偏移 */
uint32_t PdoOutputOffset; /* IOmap中输出偏移 */
} fsoe_config_t;
fsoe_status_t
typedef struct {
fsoe_state_t State; /* 当前FSoE状态 */
fsoe_error_t LastError; /* 最后的错误代码 */
uint32_t ErrorCount; /* 总错误计数 */
uint32_t FramesSent; /* 发送帧数 */
uint32_t FramesReceived; /* 接收有效帧数 */
uint32_t CrcErrors; /* CRC错误计数 */
uint32_t WatchdogErrors; /* 看门狗超时计数 */
uint8_t WatchdogExpired; /* 当前看门狗状态 */
uint8_t InFailsafe; /* 是否处于失效安全模式 */
uint8_t ToggleBit; /* 当前切换位值 */
uint8_t Initialized; /* 连接是否已初始化 */
} fsoe_status_t;
fsoe_mdp_config_t
typedef struct {
uint16_t ConnectionId; /* 唯一连接标识符 */
uint16_t SafetyAddress; /* FSoE从站安全地址 */
uint32_t WatchdogTimeMs; /* 看门狗超时(毫秒) */
uint16_t SafeInputSize; /* 安全输入数据大小(字节) */
uint16_t SafeOutputSize; /* 安全输出数据大小(字节) */
uint32_t PdoInputOffset; /* IOmap中输入偏移 */
uint32_t PdoOutputOffset; /* IOmap中输出偏移 */
uint16_t ModuleNumber; /* 模块编号 (槽位号) */
uint16_t ModuleProfile; /* 模块配置文件 (190, 195, 290, 790, 6900) */
uint16_t AxisNumber; /* 轴编号 (仅用于 Drive Connection) */
uint16_t ConnectionType; /* 连接类型: 0=Master, 1=Slave */
} fsoe_mdp_config_t;
基本 FSoE 函数
FSoEIsSlaveCapable()
BOOL FSoEIsSlaveCapable(uint16_t master_index, uint16_t slave_index);
检查从站是否支持 FSoE。
返回值:
BOOL— 支持返回TRUE
FSoEInitConnection()
BOOL FSoEInitConnection(uint16_t master_index, uint16_t slave_index, fsoe_config_t* config);
初始化 FSoE 连接。
参数:
master_index(uint16_t) — 主站索引slave_index(uint16_t) — 从站索引config(fsoe_config_t*) — 连接配置
返回值:
BOOL— 成功返回TRUE
FSoECloseConnection()
BOOL FSoECloseConnection(uint16_t master_index, uint16_t slave_index);
关闭 FSoE 连接。
FSoEGetStatus()
BOOL FSoEGetStatus(uint16_t master_index, uint16_t slave_index, fsoe_status_t* status);
获取 FSoE 连接状态。
参数:
status(fsoe_status_t*) — 输出状态结构体
返回值:
BOOL— 成功返回TRUE
FSoERequestState()
BOOL FSoERequestState(uint16_t master_index, uint16_t slave_index, int target_state);
请求 FSoE 状态转换。
参数:
target_state(int) — 目标 FSoE 状态 (fsoe_state_t)
FSoEGetState()
int FSoEGetState(uint16_t master_index, uint16_t slave_index);
获取当前 FSoE 状态。
返回值:
int— 当前状态 (fsoe_state_t)
FSoEReset()
BOOL FSoEReset(uint16_t master_index, uint16_t slave_index);
重置 FSoE 连接。
FSoEWriteSafeOutput()
BOOL FSoEWriteSafeOutput(uint16_t master_index, uint16_t slave_index,
const uint8_t* data, uint32_t size);
写入安全输出数据。
参数:
data(const uint8_t*) — 安全输出数据size(uint32_t) — 数据大小
FSoEReadSafeInput()
BOOL FSoEReadSafeInput(uint16_t master_index, uint16_t slave_index,
uint8_t* data, uint32_t* size);
读取安全输入数据。
参数:
data(uint8_t*) — 输出安全输入数据缓冲区size(uint32_t*) — 输入/输出数据大小
FSoEDownloadParameters()
BOOL FSoEDownloadParameters(uint16_t master_index, uint16_t slave_index,
const uint8_t* param_data, uint32_t param_size, uint32_t* sra_crc);
下载安全参数。
参数:
param_data(const uint8_t*) — 参数数据param_size(uint32_t) — 参数大小sra_crc(uint32_t*) — 输出 SRA CRC 值
FSoESetFailsafeOutput()
BOOL FSoESetFailsafeOutput(uint16_t master_index, uint16_t slave_index,
const uint8_t* data, uint32_t size);
设置失效安全输出值。
FSoECheckWatchdog()
BOOL FSoECheckWatchdog(uint16_t master_index, uint16_t slave_index);
检查看门狗状态。
返回值:
BOOL— 看门狗正常返回TRUE
FSoEGetLastError()
int FSoEGetLastError(uint16_t master_index, uint16_t slave_index);
获取最后的错误代码。
返回值:
int— 错误代码 (fsoe_error_t)
FSoEClearError()
void FSoEClearError(uint16_t master_index, uint16_t slave_index);
清除错误状态。
FSoEGetConnectionCount()
uint16_t FSoEGetConnectionCount(uint16_t master_index);
获取当前活跃的 FSoE 连接数。
FSoEProcessCycle()
void FSoEProcessCycle(uint16_t master_index);
处理一个 FSoE 周期(在 PDO 回调中调用)。
FSoE MDP 多连接模式
MDP(模块化设备)从站可能包含多个 FSoE 安全连接。以下函数通过 connection_index 区分不同连接。
FSoEMdpDetectConnections()
uint16_t FSoEMdpDetectConnections(uint16_t master_index, uint16_t slave_index);
检测从站是否为 MDP 设备并返回支持的 FSoE 连接数。
返回值:
uint16_t— FSoE 连接数量
FSoEMdpGetSlaveConnectionCount()
uint16_t FSoEMdpGetSlaveConnectionCount(uint16_t master_index, uint16_t slave_index);
获取从站的 FSoE 连接数量。
FSoEMdpInitConnection()
BOOL FSoEMdpInitConnection(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index, fsoe_mdp_config_t* config);
初始化 MDP 从站的指定连接。
FSoEMdpCloseConnection()
BOOL FSoEMdpCloseConnection(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index);
关闭 MDP 从站的指定连接。
FSoEMdpGetStatus()
BOOL FSoEMdpGetStatus(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index, fsoe_status_t* status);
获取指定连接的状态。
FSoEMdpRequestState()
BOOL FSoEMdpRequestState(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index, int target_state);
请求指定连接的状态转换。
FSoEMdpGetState()
int FSoEMdpGetState(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index);
获取指定连接的当前状态。
FSoEMdpReset()
BOOL FSoEMdpReset(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index);
重置指定连接。
FSoEMdpWriteSafeOutput()
BOOL FSoEMdpWriteSafeOutput(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index, const uint8_t* data, uint32_t size);
写入指定连接的安全输出数据。
FSoEMdpReadSafeInput()
BOOL FSoEMdpReadSafeInput(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index, uint8_t* data, uint32_t* size);
读取指定连接的安全输入数据。
FSoEMdpDownloadParameters()
BOOL FSoEMdpDownloadParameters(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index, const uint8_t* param_data,
uint32_t param_size, uint32_t* sra_crc);
下载指定连接的安全参数。
FSoEMdpSetFailsafeOutput()
BOOL FSoEMdpSetFailsafeOutput(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index, const uint8_t* data, uint32_t size);
设置指定连接的失效安全输出值。
FSoEMdpCheckWatchdog()
BOOL FSoEMdpCheckWatchdog(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index);
检查指定连接的看门狗状态。
FSoEMdpGetLastError()
int FSoEMdpGetLastError(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index);
获取指定连接的最后错误。
FSoEMdpClearError()
void FSoEMdpClearError(uint16_t master_index, uint16_t slave_index,
uint16_t connection_index);
清除指定连接的错误。
FSoEMdpGetDeviceAddress()
BOOL FSoEMdpGetDeviceAddress(uint16_t master_index, uint16_t slave_index,
uint16_t* safety_address);
读取从站的设备级 FSoE 安全地址 (0xF980:01)。
FSoEMdpGetModuleCommParam()
BOOL FSoEMdpGetModuleCommParam(uint16_t master_index, uint16_t slave_index,
uint16_t module_number, uint8_t* param_data, uint32_t* param_size);
读取指定模块的 FSoE 通信参数 (0x9nn1)。
FSoEMdpGetModuleDiagnosis()
BOOL FSoEMdpGetModuleDiagnosis(uint16_t master_index, uint16_t slave_index,
uint16_t module_number, uint16_t* connection_state,
uint16_t* connection_diagnosis);
读取指定模块的 FSoE 诊断数据 (0xAnn0)。
ConnID 唯一性校验 (ETG.5120 §5.2.3)
FSoE ConnectionId 在主站内必须全局唯一, 且不允许为 0。上层配置向导在分配新 ConnID 之前应先校验冲突, 避免 FSoEInitConnection 因 ID 占用而失败。
FSoEValidateConnId()
BOOL FSoEValidateConnId(uint16_t conn_id);
校验指定 ConnID 是否可用 (未被任何活跃连接占用且非 0)。
参数:
conn_id(uint16_t) — 待校验的 ConnID
返回值:
BOOL—TRUE可用,FALSE已占用或为 0
示例:
/* 自动分配下一个可用 ConnID */
uint16_t pick_next_conn_id(dll_t* dll)
{
for (uint16_t id = 1; id < 0xFFFF; id++) {
if (dll->FSoEValidateConnId(id)) return id;
}
return 0; /* 已用满 */
}
uint16_t conn_id = pick_next_conn_id(&dll);
if (conn_id == 0) {
printf("无可用 ConnID\n");
return -1;
}
fsoe_config_t config = {0};
config.ConnectionId = conn_id;
/* ... 其他参数 */
dll.FSoEInitConnection(master, slave, &config);
FSoEValidateConnId 是无锁只读操作, 可在任意线程调用。但在并发分配场景下 "校验 → 使用" 之间的时间窗口仍可能出现冲突, 建议调用方加自己的互斥。
FSoE CRC16 (ethercat_advanced.h)
fsoe_crc16()
uint16_t fsoe_crc16(const uint8_t* data, uint32_t length);
计算 FSoE CRC16(CCITT-False, 多项式 0x1021, 初始值 0xFFFF)。
参数:
data(const uint8_t*) — 数据length(uint32_t) — 数据长度
返回值:
uint16_t— CRC16 值
fsoe_crc16_with_conn_id()
uint16_t fsoe_crc16_with_conn_id(const uint8_t* data, uint32_t length,
uint16_t connection_id);
带 ConnectionId 偏置的 CRC16 (ETG.5100 §5.5.1)。FSoE 标准 PDO 帧的每个 segment CRC 由 (payload + ConnId) 共同计算, 用于绑定特定连接, 防止跨连接帧串扰。
参数:
data(const uint8_t*) — segment 数据length(uint32_t) — 数据长度connection_id(uint16_t) — FSoE ConnectionId
返回值:
uint16_t— 带 ConnId 的 CRC16
FSoE PDO 帧 (ethercat_advanced.h)
直接构造或解析 FSoE PDO 帧字节流, 适用于:
- 离线日志回放分析 (录制 PDO bytes 后离线 dump)
- 仿真器 / 测试工装手动构造帧
- 自检脚本验证主站发出的帧格式是否符合 ETG.5100
fsoe_pdo_segment_t
typedef struct {
uint8_t cmd; /* FSoE Command 字节 */
uint8_t data[2]; /* Safe Data (2 字节/segment) */
uint16_t crc; /* Segment CRC16 */
} fsoe_pdo_segment_t;
fsoe_pdo_frame_t
typedef struct {
uint16_t connection_id;
int segment_count; /* 通常 1-7 */
fsoe_pdo_segment_t segments[16];
} fsoe_pdo_frame_t;
fsoe_pdo_frame_to_bytes()
int fsoe_pdo_frame_to_bytes(const fsoe_pdo_frame_t* frame,
uint8_t* out_buf, int buf_size);
序列化 FSoE PDO 帧到字节流。返回写入的字节数, 失败返回负数。
fsoe_pdo_frame_from_bytes()
int fsoe_pdo_frame_from_bytes(const uint8_t* bytes, int size,
fsoe_pdo_frame_t* out_frame);
从字节流解析为 PDO 帧结构。返回解析的 segment 数量, 失败返回负数。
fsoe_pdo_frame_validate_crc_segments()
BOOL fsoe_pdo_frame_validate_crc_segments(const fsoe_pdo_frame_t* frame);
逐 segment 校验 CRC (使用 fsoe_crc16_with_conn_id)。任一 segment CRC 不匹配返回 FALSE。
示例 (录制帧 + 离线校验):
#include "ethercat_advanced.h"
uint8_t recorded[64];
int n = read_log_bytes(recorded, sizeof(recorded));
fsoe_pdo_frame_t frame;
if (fsoe_pdo_frame_from_bytes(recorded, n, &frame) > 0) {
if (!fsoe_pdo_frame_validate_crc_segments(&frame)) {
printf("CRC 校验失败 (ConnId=0x%04X)\n", frame.connection_id);
} else {
printf("帧合法, %d segments\n", frame.segment_count);
}
}
FSoE 管理器 (ethercat_advanced.h)
高级多连接管理器,简化多 FSoE 连接的生命周期管理。
fsoe_conn_info_t
typedef struct {
uint16_t connection_id;
uint16_t safety_address;
int state; /* fsoe_state_t */
int last_error; /* fsoe_error_t */
uint32_t error_count;
uint32_t frames_sent;
uint32_t frames_received;
uint32_t crc_errors;
uint32_t watchdog_errors;
uint8_t watchdog_expired;
uint8_t in_failsafe;
} fsoe_conn_info_t;
fsoe_manager_create()
dx_fsoe_manager_t* fsoe_manager_create(dll_t* dll, uint16_t master, uint16_t slave);
创建 FSoE 管理器。
返回值:
dx_fsoe_manager_t*— 管理器实例,需调用fsoe_manager_destroy()释放
fsoe_manager_destroy()
void fsoe_manager_destroy(dx_fsoe_manager_t* mgr);
销毁 FSoE 管理器。
fsoe_manager_add_connection()
int fsoe_manager_add_connection(dx_fsoe_manager_t* mgr,
uint16_t conn_id, uint16_t safety_addr, uint32_t watchdog_ms,
uint16_t in_size, uint16_t out_size,
uint32_t pdo_in_offset, uint32_t pdo_out_offset);
添加 FSoE 连接。
返回值:
int— 0 成功,非 0 失败
fsoe_manager_close_all()
int fsoe_manager_close_all(dx_fsoe_manager_t* mgr);
关闭所有连接。
fsoe_manager_reset_all()
int fsoe_manager_reset_all(dx_fsoe_manager_t* mgr);
重置所有连接。
fsoe_manager_get_info()
int fsoe_manager_get_info(dx_fsoe_manager_t* mgr, int index, fsoe_conn_info_t* info);
获取指定连接的信息。
fsoe_manager_connection_count()
int fsoe_manager_connection_count(dx_fsoe_manager_t* mgr);
获取管理器中的连接数。
fsoe_manager_all_in_data()
BOOL fsoe_manager_all_in_data(dx_fsoe_manager_t* mgr);
检查是否所有连接都处于 DATA 状态。
fsoe_manager_any_in_failsafe()
BOOL fsoe_manager_any_in_failsafe(dx_fsoe_manager_t* mgr);
检查是否有连接处于 FAILSAFE 状态。
fsoe_manager_enter_all_failsafe()
int fsoe_manager_enter_all_failsafe(dx_fsoe_manager_t* mgr);
所有连接进入 FAILSAFE 状态。
完整示例
数字量安全 IO
#define DYNAMIC_LOAD
#include "ethercat.h"
#include "ethercat_advanced.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_OPERATIONAL, 10000);
dll.Start(master);
uint16_t slave = 1;
/* 检查 FSoE 支持 */
if (!dll.FSoEIsSlaveCapable(master, slave)) {
printf("从站不支持 FSoE\n");
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 1;
}
/* 初始化 FSoE 连接 */
fsoe_config_t config = {0};
config.ConnectionId = 1;
config.SafetyAddress = 0x0100;
config.WatchdogTimeMs = 100;
config.SafeInputSize = 2;
config.SafeOutputSize = 1;
config.PdoInputOffset = 0;
config.PdoOutputOffset = 0;
if (dll.FSoEInitConnection(master, slave, &config)) {
printf("FSoE 连接已初始化\n");
/* 设置失效安全输出(全零) */
uint8_t failsafe_out[] = {0x00};
dll.FSoESetFailsafeOutput(master, slave, failsafe_out, sizeof(failsafe_out));
/* 等待进入 DATA 状态 */
for (int i = 0; i < 100; i++) {
int state = dll.FSoEGetState(master, slave);
if (state == FSOE_STATE_DATA) {
printf("FSoE 进入 DATA 状态\n");
break;
}
dll.FSoEProcessCycle(master);
}
/* 周期性数据交换 */
for (int cycle = 0; cycle < 10000; cycle++) {
dll.FSoEProcessCycle(master);
/* 读取安全输入 */
safe_digital_input_t safe_in;
uint32_t in_size = sizeof(safe_in);
if (dll.FSoEReadSafeInput(master, slave, (uint8_t*)&safe_in, &in_size)) {
/* 根据输入控制输出 */
safe_digital_output_t safe_out;
safe_out.OutputBits = safe_in.InputBits;
dll.FSoEWriteSafeOutput(master, slave,
(const uint8_t*)&safe_out, sizeof(safe_out));
}
/* 检查看门狗 */
if (!dll.FSoECheckWatchdog(master, slave)) {
printf("FSoE 看门狗超时!\n");
break;
}
}
/* 获取连接状态 */
fsoe_status_t status;
if (dll.FSoEGetStatus(master, slave, &status)) {
printf("状态: %d, 错误计数: %u, CRC错误: %u\n",
status.State, status.ErrorCount, status.CrcErrors);
printf("发送帧: %u, 接收帧: %u, 看门狗: %s\n",
status.FramesSent, status.FramesReceived,
status.WatchdogExpired ? "过期" : "正常");
}
/* 关闭连接 */
dll.FSoECloseConnection(master, slave);
}
dll.Stop(master);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}
安全驱动器
#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_OPERATIONAL, 10000);
dll.Start(master);
uint16_t slave = 1;
/* 初始化安全驱动器连接(10 字节输入 + 2 字节输出) */
fsoe_config_t config = {0};
config.ConnectionId = 1;
config.SafetyAddress = 0x0100;
config.WatchdogTimeMs = 100;
config.SafeInputSize = sizeof(safe_drive_input_t); /* 10 字节 */
config.SafeOutputSize = sizeof(safe_drive_output_t); /* 2 字节 */
if (!dll.FSoEInitConnection(master, slave, &config)) {
printf("安全驱动器连接初始化失败\n");
goto cleanup;
}
/* 设置失效安全输出: STO(安全扭矩关闭) */
safe_drive_output_t failsafe = {0};
failsafe.SafetyControlWord = 0x0001; /* STO 请求 */
dll.FSoESetFailsafeOutput(master, slave,
(const uint8_t*)&failsafe, sizeof(failsafe));
/* 等待 DATA 状态 */
for (int i = 0; i < 200; i++) {
dll.FSoEProcessCycle(master);
if (dll.FSoEGetState(master, slave) == FSOE_STATE_DATA) break;
}
/* 安全驱动控制循环 */
for (int cycle = 0; cycle < 100000; cycle++) {
dll.FSoEProcessCycle(master);
safe_drive_input_t drive_in;
uint32_t in_size = sizeof(drive_in);
if (dll.FSoEReadSafeInput(master, slave, (uint8_t*)&drive_in, &in_size)) {
safe_drive_output_t drive_out = {0};
if (drive_in.SafetyStatusWord & 0x0100) {
/* Bit8: 安全故障 -> 确认故障 */
drive_out.SafetyControlWord = 0x0080;
printf("安全故障: STO, 位置=%d, 速度=%d\n",
drive_in.SafeActualPosition, drive_in.SafeActualVelocity);
} else {
/* 正常运行: 清除 STO 请求 */
drive_out.SafetyControlWord &= ~0x0001;
}
dll.FSoEWriteSafeOutput(master, slave,
(const uint8_t*)&drive_out, sizeof(drive_out));
}
/* 检查错误 */
int error = dll.FSoEGetLastError(master, slave);
if (error != FSOE_ERROR_NONE) {
printf("FSoE 错误: 0x%04X\n", error);
dll.FSoEClearError(master, slave);
}
}
dll.FSoECloseConnection(master, slave);
cleanup:
dll.Stop(master);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}
MDP 多连接安全 IO
#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();
/* ... 网络配置和启动省略 ... */
uint16_t slave = 1;
/* 探测 MDP 连接数 */
uint16_t conn_count = dll.FSoEMdpDetectConnections(master, slave);
printf("从站 %d 支持 %d 个 FSoE 连接\n", slave, conn_count);
/* 读取设备安全地址 */
uint16_t device_addr;
if (dll.FSoEMdpGetDeviceAddress(master, slave, &device_addr)) {
printf("设备安全地址: 0x%04X\n", device_addr);
}
/* 初始化各模块连接 */
for (uint16_t i = 0; i < conn_count; i++) {
fsoe_mdp_config_t mdp_config = {0};
mdp_config.ConnectionId = i + 1;
mdp_config.SafetyAddress = 0x0100 + i;
mdp_config.WatchdogTimeMs = 100;
mdp_config.SafeInputSize = 2;
mdp_config.SafeOutputSize = 1;
mdp_config.ModuleNumber = i;
mdp_config.ModuleProfile = 195; /* DigitalInOut */
if (dll.FSoEMdpInitConnection(master, slave, i, &mdp_config)) {
printf("MDP 连接 %d 已初始化\n", i);
}
}
/* 数据交换循环 */
for (int cycle = 0; cycle < 10000; cycle++) {
dll.FSoEProcessCycle(master);
for (uint16_t i = 0; i < conn_count; i++) {
/* 读取各模块安全输入 */
uint8_t safe_in[2];
uint32_t in_size = sizeof(safe_in);
if (dll.FSoEMdpReadSafeInput(master, slave, i, safe_in, &in_size)) {
/* 输入直通输出 */
uint8_t safe_out[] = { safe_in[0] };
dll.FSoEMdpWriteSafeOutput(master, slave, i, safe_out, 1);
}
}
}
/* 查询各连接状态 */
for (uint16_t i = 0; i < conn_count; i++) {
fsoe_status_t status;
if (dll.FSoEMdpGetStatus(master, slave, i, &status)) {
printf("连接 %d: 状态=%d, CRC错误=%u, 失效安全=%d\n",
i, status.State, status.CrcErrors, status.InFailsafe);
}
}
/* 模块诊断 */
for (uint16_t i = 0; i < conn_count; i++) {
uint16_t conn_state, conn_diag;
if (dll.FSoEMdpGetModuleDiagnosis(master, slave, i, &conn_state, &conn_diag)) {
printf("连接 %d: 诊断码=0x%04X\n", i, conn_diag);
}
}
/* 关闭所有连接 */
for (uint16_t i = 0; i < conn_count; i++) {
dll.FSoEMdpCloseConnection(master, slave, i);
}
dll.Stop(master);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}
使用高级管理器
#include "ethercat_advanced.h"
/* 创建 FSoE 管理器 */
dx_fsoe_manager_t* mgr = fsoe_manager_create(&dll, master, slave);
/* 添加连接 */
fsoe_manager_add_connection(mgr, 1, 0x0100, 100, 2, 1, 0, 0);
fsoe_manager_add_connection(mgr, 2, 0x0200, 100, 2, 0, 0, 0);
/* 检查状态 */
printf("连接数: %d\n", fsoe_manager_connection_count(mgr));
printf("全部 DATA: %s\n", fsoe_manager_all_in_data(mgr) ? "是" : "否");
printf("有失效安全: %s\n", fsoe_manager_any_in_failsafe(mgr) ? "是" : "否");
/* 获取连接信息 */
for (int i = 0; i < fsoe_manager_connection_count(mgr); i++) {
fsoe_conn_info_t info;
if (fsoe_manager_get_info(mgr, i, &info) == 0) {
printf("连接 %d: 状态=%d, 地址=0x%04X, CRC错误=%u\n",
i, info.state, info.safety_address, info.crc_errors);
}
}
/* 紧急: 所有连接进入失效安全 */
fsoe_manager_enter_all_failsafe(mgr);
/* 重置所有连接 */
fsoe_manager_reset_all(mgr);
/* 关闭并销毁 */
fsoe_manager_close_all(mgr);
fsoe_manager_destroy(mgr);