FSoE 功能安全
FSoE (Functional Safety over EtherCAT) 安全通信接口,在标准 PDO 通道上叠加安全协议层,实现 SIL3/PLe 等级的功能安全通信。
通过 slave.FSoE 访问。从站不支持 FSoE 时为 null。
slave.FSoE 在从站初始化时自动检测。检测过程:
- CoE 前置条件 — 从站必须支持 CoE 邮箱协议
- 0xF980:01 检测 — 尝试 SDO 读取设备级 FSoE 安全地址,成功则确认支持
- 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 周期,系统自动执行:
- 周期开始 — 刷新所有 FSoE 从站的安全输入缓冲区、检查状态变化并触发事件
DataExchange回调 — 从站级别回调,读取 ref 输入(已是最新数据),修改 ref 输出ProcessDataCyclicSync回调 — 主站级别回调(普通 PDO 读写)- 周期结束 — 提交所有 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) — 看门狗超时(毫秒),默认 100connectionId(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);
自动偏移依赖 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 周期中,在安全输入刷新后、安全输出提交前自动触发。
BindSafeIO / BindMdpSafeIO 的 exchange 参数会自动注册到 DataExchange 事件,并以类型化的 ref 方式传入安全数据。直接使用 DataExchange 事件适合需要手动管理多个从站数据的场景。
时序:
PDO 周期回调
├─ FSoE 前处理
│ └─ 对每个 FSoE 从站:
│ ├─ 刷新安全输入缓冲区 ← 输入已刷新
│ ├─ 检查状态变化和错误事件
│ └─ DataExchange 回调 ← 【在此读写 ref】
├─ ProcessDataCyclicSync ← 普通 PDO 读写
└─ FSoE 后处理
└─ 提交安全输出缓冲区 ← 自动提交输出
属性
| 属性 | 类型 | 说明 |
|---|---|---|
| IsInitialized | bool | 是否已初始化 |
| State | FSoEState | 当前 FSoE 状态 |
| InFailsafe | bool | 是否处于失效安全模式 |
| WatchdogExpired | bool | 看门狗是否过期 |
| LastError | FSoEError | 最后的错误代码 |
| Config | FSoEConnectionConfig? | 当前连接配置 |
| Status | FSoEConnectionStatus | 连接状态详情(通信统计) |
| IsMdpMode | bool | 是否为 MDP 多连接模式 |
| ConnectionCount | int | 连接数量(单连接模式为 1,多连接模式为模块数) |
| Manager | FSoEManager? | 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
FSoEConnectionConfig
| 属性 | 类型 | 说明 |
|---|---|---|
| ConnectionId | ushort | 唯一连接标识符 |
| SafetyAddress | ushort | FSoE 从站安全地址(拨码开关设定) |
| WatchdogTimeMs | uint | 看门狗超时(毫秒),默认 100 |
| SafeInputSize | ushort | 安全输入数据大小(字节),由设备决定 |
| SafeOutputSize | ushort | 安全输出数据大小(字节),由设备决定 |
| PdoInputOffset | uint | IOmap 中输入偏移(通常用 slave.Ioffset) |
| PdoOutputOffset | uint | IOmap 中输出偏移(通常用 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 状态转换。自动验证状态转换有效性。
参数:
targetState(FSoEState) — 目标状态
返回值:
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 访问。
| 属性 | 类型 | 说明 |
|---|---|---|
| ConnectionCount | int | 连接数量 |
| ConnectionMode | FSoEConnectionMode | 连接模式 |
| Connections | IReadOnlyList<FSoEConnection> | 所有连接列表 |
| PrimaryConnection | FSoEConnection? | 主连接(索引 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] 访问。
| 属性 | 类型 | 说明 |
|---|---|---|
| Index | int | 连接索引(0-based) |
| IsInitialized | bool | 是否已初始化 |
| State | FSoEState | 当前 FSoE 状态 |
| InFailsafe | bool | 是否处于失效安全模式 |
| WatchdogExpired | bool | 看门狗是否过期 |
| LastError | FSoEError | 最后的错误代码 |
| Config | FSoEConnectionConfig? | 连接配置 |
| ModuleProfile | FSoEModuleProfile | 模块配置文件类型 |
| AxisNumber | int | 轴编号(仅驱动连接) |
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}");