跳到主要内容

FSoE 功能安全

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

通过 slave.FSoE 访问。从站不支持 FSoE 时为 null

FSoE 设备检测

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

  1. CoE 前置条件 — 从站必须支持 CoE 邮箱协议
  2. 0xF980:01 检测 — 尝试 SDO 读取设备级 FSoE 安全地址,成功则确认支持
  3. 0x9001:02 检测 — 若上步失败,尝试读取 MDP 连接参数(适用于多模块安全设备)

仅检查 CoE 支持是不够的,因为很多非安全设备(如普通伺服驱动器)也支持 CoE。FSoE 设备必须实现特定的安全对象索引才能被正确识别。

快速开始

FSoE 工作流程

FSoE 在 EtherCAT PDO 通道上建立独立的安全连接。主站负责协议管理,从站负责安全逻辑。

状态机流程:

  • Reset — 初始状态,等待主站发起会话
  • Session — 会话建立,交换连接 ID
  • Connection — 连接建立,验证安全地址
  • Parameter — 参数下载阶段(SRA CRC 校验)
  • Data — 正常安全数据交换
  • Failsafe — 失效安全,从站输出安全值
自动状态推进

BindSafeIO 一行完成初始化 + 启动数据交换,中间状态(Session → Connection → Parameter)由 DLL 自动推进。

推荐方式

使用 Darra EtherCAT Master Tools 的代码导出功能,可一键生成安全数据结构体和绑定代码。

最简示例(数字量安全 IO)

// 安全数据结构体(通过 Darra 配置工具一键导出)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDigitalInput // EL19xx: 2 字节
{
public byte InputBits; // 安全输入状态位(每位对应一个通道)
public byte DiagBits; // 输入诊断状态
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDigitalOutput // EL29xx: 1 字节
{
public byte OutputBits; // 安全输出控制位
}
var slave = master.Slaves[0];  // EL1904 安全输入 + EL2904 安全输出

// 1. 一行绑定 + exchange 回调:自动推断数据大小、启动数据交换
// exchange 每个 PDO 周期自动调用,以 ref 方式传入安全输入/输出
slave.FSoE.BindSafeIO<SafeDigitalInput, SafeDigitalOutput>(
safetyAddress: 0x0100,
exchange: (ref SafeDigitalInput input, ref SafeDigitalOutput output) =>
{
bool ch0 = (input.InputBits & 0x01) != 0;
output.OutputBits = ch0 ? (byte)0x01 : (byte)0x00;
});

// 2. 订阅事件(自动触发,无需手动调用)
slave.FSoE.ErrorOccurred += (s, e) =>
Console.WriteLine($"FSoE 错误: {e.Description}");
PDO 周期时序

每个 PDO 周期,系统自动执行:

  1. 周期开始 — 刷新所有 FSoE 从站的安全输入缓冲区、检查状态变化并触发事件
  2. DataExchange 回调 — 从站级别回调,读取 ref 输入(已是最新数据),修改 ref 输出
  3. ProcessDataCyclicSync 回调 — 主站级别回调(普通 PDO 读写)
  4. 周期结束 — 提交所有 FSoE 从站的安全输出缓冲区到 DLL

FSoE 与普通 PDO 的区别

FSoE 安全数据不能像普通 PDO 那样直接用 InputsMapping<T>() 映射。原因在于协议层的差异:

使用体验对比

尽管底层机制不同,BindSafeIO + DataExchange 的使用体验已经非常接近普通 PDO:

普通 PDO

IOmap 中的数据就是用户数据,结构体直接叠加到内存指针即可

FSoE PDO

IOmap 中存放的是 FSoE 协议帧,用户的安全数据被包裹在帧内部,且需要经过校验

SDK 的 FSoE 协议引擎负责:

  • CRC 校验 — 验证输入帧完整性、计算输出帧 CRC
  • 状态机管理 — Session → Connection → Parameter → Data 自动推进
  • 看门狗 — 监控通信超时,超时自动进入 Failsafe
  • 序列号 — 检测帧丢失和重复

绕过 DLL 直接读写 IOmap 会破坏安全协议完整性,因此 FSoE 必须通过独立缓冲区访问。

简化绑定

BindSafeIO<TIn, TOut>()

public bool BindSafeIO<TIn, TOut>(ushort safetyAddress,
SafeDataExchangeHandler<TIn, TOut>? exchange = null,
uint watchdogMs = 100, ushort connectionId = 0)
where TIn : struct where TOut : struct

一行完成 FSoE 初始化 + 启动数据交换。自动从结构体推断 SafeInputSize / SafeOutputSize,使用从站的 Ioffset / Ooffset 作为 PDO 偏移,connectionId 为 0 时自动分配。

exchange 回调每个 PDO 周期自动调用,以 ref 方式传入安全输入/输出,零拷贝。

参数:

  • safetyAddress (ushort) — 从站安全地址(硬件拨码)
  • exchange (SafeDataExchangeHandler<TIn, TOut>?) — 数据交换回调(可选)
  • watchdogMs (uint) — 看门狗超时(毫秒),默认 100
  • connectionId (ushort) — 连接 ID,0 则自动分配

返回值:

  • bool — 成功返回 true

示例:

slave.FSoE.BindSafeIO<SafeDigitalInput, SafeDigitalOutput>(
safetyAddress: 0x0100,
exchange: (ref SafeDigitalInput input, ref SafeDigitalOutput output) =>
{
output.OutputBits = input.InputBits;
});

BindSafeInput<TIn>()

public bool BindSafeInput<TIn>(ushort safetyAddress,
SafeInputExchangeHandler<TIn>? exchange = null,
uint watchdogMs = 100, ushort connectionId = 0)
where TIn : struct

仅绑定安全输入(SafeOutputSize = 0),适用于纯安全输入设备(如 EL1904)。

BindSafeOutput<TOut>()

public bool BindSafeOutput<TOut>(ushort safetyAddress,
SafeOutputExchangeHandler<TOut>? exchange = null,
uint watchdogMs = 100, ushort connectionId = 0)
where TOut : struct

仅绑定安全输出(SafeInputSize = 0),适用于纯安全输出设备(如 EL2904)。

BindMdpSafeIO<TIn, TOut>()

// 自动偏移(从 DENI PDO 配置自动计算)
public bool BindMdpSafeIO<TIn, TOut>(ushort safetyAddress,
SafeDataExchangeHandler<TIn, TOut>? exchange = null, uint watchdogMs = 100)
where TIn : struct where TOut : struct

// 显式偏移(用户指定模块 PDO 偏移)
public bool BindMdpSafeIO<TIn, TOut>(ushort safetyAddress,
uint pdoInputOffset, uint pdoOutputOffset,
SafeDataExchangeHandler<TIn, TOut>? exchange = null, uint watchdogMs = 100)
where TIn : struct where TOut : struct

MDP 多连接绑定。每次调用添加一个独立 FSoE 连接,自动从结构体推断安全数据大小。所有连接在首个 PDO 周期自动启动,无需手动调用。

自动偏移版本通过 slave.MDP.GetModulePdoLayout() 从 DENI 的 PDO Assignment/Mapping 配置自动计算每个模块在 IOmap 中的偏移,按调用顺序依次分配(对应槽位顺序)。需要从站已完成 DENI 配置(ConfigMap 后)且 MDP 已检测到模块。

示例:

// 自动偏移 + exchange 回调(推荐)
slave.FSoE.BindMdpSafeIO<SafeDigitalInput, SafeDigitalOutput>(
safetyAddress: 0x0100,
exchange: (ref SafeDigitalInput input, ref SafeDigitalOutput output) =>
{
output.OutputBits = input.InputBits;
});
slave.FSoE.BindMdpSafeIO<SafeDigitalInput, SafeDigitalOutput>(safetyAddress: 0x0200);

// 显式偏移 — 用户指定模块 PDO 偏移
slave.FSoE.BindMdpSafeIO<SafeDigitalInput, SafeDigitalOutput>(
safetyAddress: 0x0100, pdoInputOffset: slave.Ioffset, pdoOutputOffset: slave.Ooffset);
slave.FSoE.BindMdpSafeIO<SafeDigitalInput, SafeDigitalOutput>(
safetyAddress: 0x0200, pdoInputOffset: slave.Ioffset + 7, pdoOutputOffset: slave.Ooffset + 6);
PDO 偏移自动计算

自动偏移依赖 slave.MDP.GetModulePdoLayout(),该方法通过 CoE SDORead 读取 PDO Assignment (0x1C12/0x1C13) 和 PDO Mapping 条目,累加各 Entry 的 BitLen 计算模块大小,再按槽位顺序累积为字节偏移(相对于slave.Ioffset/slave.Ooffset)。

需要从站已完成 DENI 配置(ConfigMap 后),不依赖 ESI 文件。如果 MDP 模块未检测到或 CoE 不可用,自动偏移将回退到从站基础偏移,此时应使用显式偏移版本。

BindMdpSafeInput<TIn>() / BindMdpSafeOutput<TOut>()

public bool BindMdpSafeInput<TIn>(ushort safetyAddress,
SafeInputExchangeHandler<TIn>? exchange = null, uint watchdogMs = 100)
where TIn : struct
public bool BindMdpSafeOutput<TOut>(ushort safetyAddress,
SafeOutputExchangeHandler<TOut>? exchange = null, uint watchdogMs = 100)
where TOut : struct

MDP 多连接绑定(仅安全输入 / 仅安全输出),自动偏移。

BindMdpDriveAxis<TIn, TOut>()

// 自动偏移
public bool BindMdpDriveAxis<TIn, TOut>(int axisNumber, ushort safetyAddress,
SafeDataExchangeHandler<TIn, TOut>? exchange = null, uint watchdogMs = 100)
where TIn : struct where TOut : struct

// 显式偏移
public bool BindMdpDriveAxis<TIn, TOut>(int axisNumber, ushort safetyAddress,
uint pdoInputOffset, uint pdoOutputOffset,
SafeDataExchangeHandler<TIn, TOut>? exchange = null, uint watchdogMs = 100)
where TIn : struct where TOut : struct

MDP 驱动轴安全连接绑定。每轴一个独立 FSoE 连接。首个 PDO 周期自动启动。

示例:

slave.FSoE.BindMdpDriveAxis<SafeDriveInput, SafeDriveOutput>(
axisNumber: 0, safetyAddress: 0x0100,
exchange: (ref SafeDriveInput input, ref SafeDriveOutput output) =>
{
if ((input.SafetyStatusWord & 0x0100) != 0)
output.SafetyControlWord |= 0x0080; // 确认故障
});
slave.FSoE.BindMdpDriveAxis<SafeDriveInput, SafeDriveOutput>(
axisNumber: 1, safetyAddress: 0x0200);

DataExchange 事件

public event Action? DataExchange

从站级别的安全数据交换回调。每个 PDO 周期中,在安全输入刷新后、安全输出提交前自动触发。

推荐使用 exchange 回调

BindSafeIO / BindMdpSafeIOexchange 参数会自动注册到 DataExchange 事件,并以类型化的 ref 方式传入安全数据。直接使用 DataExchange 事件适合需要手动管理多个从站数据的场景。

时序:

PDO 周期回调
├─ FSoE 前处理
│ └─ 对每个 FSoE 从站:
│ ├─ 刷新安全输入缓冲区 ← 输入已刷新
│ ├─ 检查状态变化和错误事件
│ └─ DataExchange 回调 ← 【在此读写 ref】
├─ ProcessDataCyclicSync ← 普通 PDO 读写
└─ FSoE 后处理
└─ 提交安全输出缓冲区 ← 自动提交输出

属性

属性类型说明
IsInitializedbool是否已初始化
StateFSoEState当前 FSoE 状态
InFailsafebool是否处于失效安全模式
WatchdogExpiredbool看门狗是否过期
LastErrorFSoEError最后的错误代码
ConfigFSoEConnectionConfig?当前连接配置
StatusFSoEConnectionStatus连接状态详情(通信统计)
IsMdpModebool是否为 MDP 多连接模式
ConnectionCountint连接数量(单连接模式为 1,多连接模式为模块数)
ManagerFSoEManager?MDP 多连接管理器(非 MDP 模式为 null)
FSoEState 枚举
public enum FSoEState : int
{
Reset = 0x100, // 初始/重置状态
Session = 0x101, // 会话建立
Connection = 0x102, // 连接建立
Parameter = 0x103, // 参数下载
Data = 0x104, // 数据交换(正常工作)
Failsafe = 0x105 // 失效安全
}
FSoEError 枚举
public enum FSoEError : int
{
None = 0x0000, // 无错误
WrongCommand = 0x0001, // 错误的命令
UnknownCommand = 0x0002, // 未知命令
WrongConnectionId = 0x0003, // 连接ID不匹配
CrcError = 0x0004, // CRC校验失败
Watchdog = 0x0005, // 看门狗超时
WrongAddress = 0x0006, // 错误的FSoE地址
WrongData = 0x0007, // 无效数据
CommParamLength = 0x0008, // 通信参数长度错误
CommParam = 0x0009, // 通信参数错误
AppParamLength = 0x000A, // 应用参数长度错误
AppParam = 0x000B, // 应用参数错误
UnexpectedSession = 0x000C, // 意外的会话命令
FailsafeData = 0x000D, // 收到失效安全数据
NotInitialized = 0x0100, // FSoE未初始化(内部错误)
MaxConnections = 0x0101, // 达到最大连接数(内部错误)
InvalidStateTransition = 0x0102 // 无效状态转换(内部错误)
}
FSoEConnectionStatus
属性类型说明
StateFSoEState当前 FSoE 状态
LastErrorFSoEError最后的错误代码
ErrorCountuint总错误计数
FramesSentuint发送帧数
FramesReceiveduint接收有效帧数
CrcErrorsuintCRC 错误计数
WatchdogErrorsuint看门狗超时计数
WatchdogExpiredbool看门狗是否过期
InFailsafebool是否处于失效安全模式
IsInitializedbool连接是否已初始化
FSoEConnectionConfig
属性类型说明
ConnectionIdushort唯一连接标识符
SafetyAddressushortFSoE 从站安全地址(拨码开关设定)
WatchdogTimeMsuint看门狗超时(毫秒),默认 100
SafeInputSizeushort安全输入数据大小(字节),由设备决定
SafeOutputSizeushort安全输出数据大小(字节),由设备决定
PdoInputOffsetuintIOmap 中输入偏移(通常用 slave.Ioffset)
PdoOutputOffsetuintIOmap 中输出偏移(通常用 slave.Ooffset)

安全数据结构

推荐方式

使用 Darra EtherCAT Master Tools 的代码导出功能,可一键生成安全数据结构体和绑定代码。 导出结果包含正确的结构体大小、字段布局和 FSoE 绑定调用,避免手动定义出错。

结构体定义

安全数据结构体示例:

// EL19xx 数字量安全输入(2 字节安全数据)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDigitalInput
{
public byte InputBits; // 安全输入状态位(每位对应一个通道)
public byte DiagBits; // 输入诊断状态
}

// EL29xx 数字量安全输出(1 字节安全数据)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDigitalOutput
{
public byte OutputBits; // 安全输出控制位
}

// ETG.6100 安全驱动输入(10 字节安全数据)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDriveInput
{
public ushort SafetyStatusWord; // 安全状态字
public int SafeActualPosition; // 安全实际位置
public int SafeActualVelocity; // 安全实际速度
}

// ETG.6100 安全驱动输出(2 字节安全数据)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDriveOutput
{
public ushort SafetyControlWord; // 安全控制字
}

// 安全编码器输入(6 字节安全数据)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeEncoderInput
{
public uint SafePosition; // 安全位置值
public ushort StatusWord; // 状态字
}
结构体大小必须匹配

SafeInputSize / SafeOutputSize 必须与结构体的 Marshal.SizeOf<T>() 一致,否则读写会失败。

状态控制

RequestState(FSoEState targetState)

public bool RequestState(FSoEState targetState)

请求 FSoE 状态转换。自动验证状态转换有效性。

参数:

返回值:

  • bool — 成功返回 true

EnterFailsafe()

public bool EnterFailsafe()

主动进入失效安全模式。从站将输出安全值(由 SetFailsafeOutput 预设)。

Reset()

public bool Reset()

重置 FSoE 连接到初始状态,重新开始状态机。

Close()

public bool Close()

关闭 FSoE 连接,释放资源。

参数和配置

DownloadParameters(byte[] parameterData, out uint sraCrc)

public bool DownloadParameters(byte[] parameterData, out uint sraCrc)

下载安全参数。

备注

仅在 Parameter 状态下可调用。参数内容由安全配置工具生成。

参数:

  • parameterData (byte[]) — 参数数据
  • sraCrc (out uint) — 输出 SRA CRC32 校验值

返回值:

  • bool — 成功返回 true

SetFailsafeOutput(byte[] data) / SetFailsafeOutput<T>(T data)

public bool SetFailsafeOutput(byte[] data)
public bool SetFailsafeOutput<T>(T data) where T : struct

预设失效安全时的输出值。当 FSoE 进入 Failsafe 状态时,从站自动切换到此值。

示例:

// 失效安全时所有输出关闭
slave.FSoE.SetFailsafeOutput(new byte[] { 0x00 });

// 或使用结构体
var failsafe = new SafeDriveOutput();
failsafe.SafetyControlWord |= 0x0001; // 请求 STO(安全扭矩关闭)
slave.FSoE.SetFailsafeOutput(failsafe);

事件

事件在每个 PDO 周期自动检测并触发。

StateChanged

public event EventHandler<FSoEStateChangedEventArgs>? StateChanged;

FSoE 状态变化事件。连接状态机每次转换时触发。

相关结构:

public class FSoEStateChangedEventArgs
{
public ushort SlaveIndex { get; } // 从站索引
public int ConnectionIndex { get; } // 连接索引(MDP 模式下区分连接)
public FSoEState OldState { get; } // 变化前的状态
public FSoEState NewState { get; } // 变化后的状态
public DateTime Timestamp { get; } // 时间戳
}

示例:

slave.FSoE.StateChanged += (sender, e) =>
{
Console.WriteLine($"FSoE 从站{e.SlaveIndex} 连接{e.ConnectionIndex}: " +
$"{e.OldState}{e.NewState}");
};

ErrorOccurred

public event EventHandler<FSoEErrorEventArgs>? ErrorOccurred;

FSoE 错误事件。检测到新错误时触发,包含错误代码和自动生成的中文描述。

相关结构:

public class FSoEErrorEventArgs
{
public ushort SlaveIndex { get; } // 从站索引
public int ConnectionIndex { get; } // 连接索引
public FSoEError Error { get; } // 错误代码
public string Description { get; } // 错误描述(中文,自动生成)
public FSoEState CurrentState { get; } // 发生错误时的状态
public DateTime Timestamp { get; } // 时间戳
}

示例:

slave.FSoE.ErrorOccurred += (sender, e) =>
{
Console.WriteLine($"FSoE 错误: 从站{e.SlaveIndex}, " +
$"代码=0x{(int)e.Error:X4}, {e.Description}, " +
$"当前状态={e.CurrentState}");

if (e.Error == FSoEError.CrcError)
slave.FSoE.ClearError();
};

FailsafeTriggered

public event EventHandler<FSoEFailsafeEventArgs>? FailsafeTriggered;

失效安全事件。进入或退出失效安全模式时触发。

相关结构:

public class FSoEFailsafeEventArgs
{
public ushort SlaveIndex { get; } // 从站索引
public int ConnectionIndex { get; } // 连接索引
public FSoEFailsafeReason Reason { get; } // 触发原因
public bool EnteringFailsafe { get; } // true=进入失效安全,false=恢复到数据交换
public DateTime Timestamp { get; } // 时间戳
}
public enum FSoEFailsafeReason
{
WatchdogTimeout, // 看门狗超时
CrcError, // CRC错误
CommunicationError, // 通信错误
ApplicationRequest, // 应用主动请求
SlaveRequest, // 从站请求
MasterRequest, // 主站请求
RecoveryToData // 恢复到数据模式(退出失效安全)
}

示例:

slave.FSoE.FailsafeTriggered += (sender, e) =>
{
if (e.EnteringFailsafe)
{
Console.WriteLine($"进入失效安全! 原因: {e.Reason}");
// 应用层安全处理:停止运动、切断气源等
}
else
{
Console.WriteLine($"恢复数据交换");
}
};

诊断

ClearError()

public void ClearError()

清除 FSoE 错误。清除后可尝试重新建立连接。

MDP 多连接模式

MDP 设备(安全 IO 耦合器、多轴驱动器等)包含多个安全模块。多连接模式为每个模块创建独立的 FSoE 连接,各连接有独立的状态机、看门狗和安全数据缓冲区。

选择指南
  • 单连接 (BindSafeIO) — 简单设备、单轴驱动器
  • 多连接 (BindMdpSafeIO) — 模块需要独立监控/恢复时(如多轴驱动器各轴独立安全连接)

GetConnection(int index)

public FSoEConnection? GetConnection(int index)

获取指定索引的 MDP 连接。也可通过索引器 slave.FSoE[index] 访问。

参数:

  • index (int) — 连接索引(0-based)

返回值:

  • FSoEConnection? — 连接实例,索引无效返回 null

FindConnectionByAddress(ushort safetyAddress)

public FSoEConnection? FindConnectionByAddress(ushort safetyAddress)

通过安全地址查找 MDP 连接。

参数:

  • safetyAddress (ushort) — FSoE 安全地址

返回值:

  • FSoEConnection? — 连接实例,未找到返回 null

FSoEManager 类

MDP 模式下的连接管理器,通过 slave.FSoE.Manager 访问。

属性类型说明
ConnectionCountint连接数量
ConnectionModeFSoEConnectionMode连接模式
ConnectionsIReadOnlyList<FSoEConnection>所有连接列表
PrimaryConnectionFSoEConnection?主连接(索引 0)
FSoEConnectionMode 枚举
public enum FSoEConnectionMode
{
Single, // 单连接模式 - 所有模块共用一个 FSoE 连接
Multiple // 多连接模式 - 每个模块独立 FSoE 连接
}
FSoEModuleProfile 枚举
public enum FSoEModuleProfile : ushort
{
DigitalInput = 190, // FSoE 数字量输入
DigitalInOut = 195, // FSoE 数字量输入/输出
DigitalOutput = 290, // FSoE 数字量输出
DriveConnection = 790, // FSoE 驱动连接 (CiA402)
Master = 6900 // FSoE 主站模块
}

AllInDataState()

public bool AllInDataState()

检查是否所有连接都处于数据交换状态。

AnyInFailsafe()

public bool AnyInFailsafe()

检查是否有任何连接处于失效安全状态。

EnterAllFailsafe()

public bool EnterAllFailsafe()

将所有连接进入失效安全模式。

ResetAll()

public bool ResetAll()

重置所有连接。

GetStatusSummary()

public string GetStatusSummary()

获取所有连接的状态摘要(诊断用)。

FSoEConnection 类

MDP 模式下的单个 FSoE 连接实例。每个 FSoEConnection 拥有独立的状态机、看门狗和安全数据缓冲区。

通过 slave.FSoE[index]slave.FSoE.GetConnection(index)slave.FSoE.Manager.Connections[index] 访问。

属性类型说明
Indexint连接索引(0-based)
IsInitializedbool是否已初始化
StateFSoEState当前 FSoE 状态
InFailsafebool是否处于失效安全模式
WatchdogExpiredbool看门狗是否过期
LastErrorFSoEError最后的错误代码
ConfigFSoEConnectionConfig?连接配置
ModuleProfileFSoEModuleProfile模块配置文件类型
AxisNumberint轴编号(仅驱动连接)

RequestState / EnterFailsafe / Reset / ClearError / Close

public bool RequestState(FSoEState targetState)
public bool EnterFailsafe()
public bool Reset()
public void ClearError()
public bool Close()

连接级别的状态控制和错误处理。每个连接可独立控制。

DownloadParameters(byte[] parameterData, out uint sraCrc)

public bool DownloadParameters(byte[] parameterData, out uint sraCrc)

下载该连接的安全参数。

SetFailsafeOutput(byte[] data)

public bool SetFailsafeOutput(byte[] data)

设置该连接的失效安全输出值。

完整示例

数字量安全 IO

var safeInput = master.Slaves[0];   // EL1904 安全输入
var safeOutput = master.Slaves[1]; // EL2904 安全输出

// 一行绑定 + exchange 回调
SafeDigitalInput lastInput = default;
safeInput.FSoE.BindSafeInput<SafeDigitalInput>(
safetyAddress: 0x0100,
exchange: (ref SafeDigitalInput input) =>
{
lastInput = input; // 捕获最新输入
});

safeOutput.FSoE.BindSafeOutput<SafeDigitalOutput>(
safetyAddress: 0x0200,
exchange: (ref SafeDigitalOutput output) =>
{
output.OutputBits = lastInput.InputBits; // 输入直通输出
});

// 事件(自动触发)
safeInput.FSoE.FailsafeTriggered += (s, e) =>
Console.WriteLine($"安全输入 失效安全: {e.Reason}");

safeOutput.FSoE.ErrorOccurred += (s, e) =>
Console.WriteLine($"安全输出 错误: {e.Description}");

// 预设失效安全输出
safeOutput.FSoE.SetFailsafeOutput(new byte[] { 0x00 });

安全驱动器

var drive = master.Slaves[0];  // 带 FSoE 的伺服驱动器

// 一行绑定 + exchange 回调
drive.FSoE.BindSafeIO<SafeDriveInput, SafeDriveOutput>(
safetyAddress: 0x0100,
exchange: (ref SafeDriveInput driveIn, ref SafeDriveOutput driveOut) =>
{
if ((driveIn.SafetyStatusWord & 0x0100) != 0) // Bit8: 安全故障
{
bool sto = (driveIn.SafetyStatusWord & 0x0001) != 0;
Console.WriteLine($"安全故障: STO={sto}, 位置={driveIn.SafeActualPosition}");
driveOut.SafetyControlWord |= 0x0080; // 确认故障
}
else
{
driveOut.SafetyControlWord &= unchecked((ushort)~0x0001); // 清除 STO 请求
driveOut.SafetyControlWord &= unchecked((ushort)~0x0080); // 清除故障确认
}
});

drive.FSoE.FailsafeTriggered += (s, e) =>
{
if (e.EnteringFailsafe)
Console.WriteLine($"驱动器安全停止! 原因: {e.Reason}");
};

多连接 IO(MDP 模式)

每个模块有独立 FSoE 连接,可独立监控状态和错误。PDO 偏移从 DENI PDO 配置自动计算:

// 安全数据结构体(通过 Darra 配置工具一键导出)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDigitalInput // 2 字节
{
public byte InputBits; // 安全输入状态位
public byte DiagBits; // 输入诊断状态
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDigitalOutput // 1 字节
{
public byte OutputBits; // 安全输出控制位
}
var coupler = master.Slaves[0];  // 安全 IO 耦合器,2 个安全模块

// 绑定 2 个独立连接 + exchange 回调(自动计算 PDO 偏移)
coupler.FSoE.BindMdpSafeIO<SafeDigitalInput, SafeDigitalOutput>(
safetyAddress: 0x0100,
exchange: (ref SafeDigitalInput input, ref SafeDigitalOutput output) =>
{
output.OutputBits = input.InputBits;
});
coupler.FSoE.BindMdpSafeIO<SafeDigitalInput, SafeDigitalOutput>(
safetyAddress: 0x0200,
exchange: (ref SafeDigitalInput input, ref SafeDigitalOutput output) =>
{
output.OutputBits = input.InputBits;
});
// 首个 PDO 周期自动启动所有连接

// 每个模块独立监控状态
coupler.FSoE.StateChanged += (s, e) =>
Console.WriteLine($"模块{e.ConnectionIndex}: {e.OldState}{e.NewState}");

多轴安全驱动器(MDP 模式)

// 安全数据结构体(通过 Darra 配置工具一键导出)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDriveInput // 10 字节 (ETG.6100)
{
public ushort SafetyStatusWord; // 安全状态字
public int SafeActualPosition; // 安全实际位置
public int SafeActualVelocity; // 安全实际速度
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SafeDriveOutput // 2 字节 (ETG.6100)
{
public ushort SafetyControlWord; // 安全控制字
}
var drive = master.Slaves[0];  // 双轴伺服驱动器,每轴独立 FSoE 连接

// 绑定 2 个轴 + exchange 回调(自动计算 PDO 偏移)
SafeDataExchangeHandler<SafeDriveInput, SafeDriveOutput> driveExchange =
(ref SafeDriveInput input, ref SafeDriveOutput output) =>
{
if ((input.SafetyStatusWord & 0x0100) != 0) // 安全故障
output.SafetyControlWord |= 0x0080; // 确认故障
else
output.SafetyControlWord &= unchecked((ushort)~0x0001); // 清除 STO
};

drive.FSoE.BindMdpDriveAxis<SafeDriveInput, SafeDriveOutput>(
axisNumber: 0, safetyAddress: 0x0100, exchange: driveExchange);
drive.FSoE.BindMdpDriveAxis<SafeDriveInput, SafeDriveOutput>(
axisNumber: 1, safetyAddress: 0x0200, exchange: driveExchange);
// 首个 PDO 周期自动启动

// 每轴独立监控
drive.FSoE.FailsafeTriggered += (s, e) =>
Console.WriteLine($"轴{e.ConnectionIndex} 失效安全: {e.Reason}");