跳到主要内容

FSoE 功能安全

FSoE (Functional Safety over EtherCAT) 安全通信接口,在标准 PDO 通道上叠加安全协议层,实现 SIL3/PLe 等级的功能安全通信 (ETG.5100/5120)。

FSoE 设备检测

FSoE 设备在从站初始化时自动检测。检测过程:

  1. CoE 前置条件 — 从站必须支持 CoE 邮箱协议
  2. 0xF980:01 检测 — 尝试 SDO 读取设备级 FSoE 安全地址,成功则确认支持
  3. 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

返回值:

  • BOOLTRUE 可用, 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);