跳到主要内容

FSoE 功能安全

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

通过 slave.FSoE() 访问。

FSoE 设备检测

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

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

SDK 提供三个层级的 FSoE 支持:

  • slave.FSoE() — 基础接口,通过从站访问,提供单连接 FSoE 操作
  • MDP — 多连接管理,支持单从站上的多个 FSoE 连接
  • FSoEManager — 多从站多连接协调器,提供 BindSafeIO 一步初始化

快速开始

FSoE 工作流程

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

状态机流程:

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

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

最简示例

FSoE fsoe = slave.FSoE();

// 1. 检查能力
if (!fsoe.IsCapable()) return;

// 2. 配置连接
FSoE.FSoEConnectionConfig config = new FSoE.FSoEConnectionConfig();
config.ConnectionId = 0x0100;
config.SafetyAddress = 0x0100;
config.WatchdogTimeMs = 100;
config.SafeInputSize = 2;
config.SafeOutputSize = 1;

// 3. 初始化
fsoe.Initialize(config);

// 4. 启动数据交换
fsoe.RequestData();

// 5. 周期性读写安全数据
byte[] safeInput = fsoe.SafeInputData();
fsoe.SetSafeOutputData(new byte[]{0x01});

FSoE 与普通 PDO 的区别

FSoE 安全数据不能像普通 PDO 那样直接映射。IOmap 中存放的是 FSoE 协议帧,用户的安全数据被包裹在帧内部,且需要经过校验。

SDK 的 FSoE 协议引擎负责:

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

能力检测

IsCapable()

public boolean IsCapable()

检测从站是否支持 FSoE 功能安全协议。

返回值:

  • boolean — 支持返回 true

属性

属性类型说明
IsInitialized()boolean是否已初始化
ConnectionState()FSoEState当前 FSoE 状态
ConnectionId()int当前连接 ID
InFailsafe()boolean是否处于失效安全模式
WatchdogExpired()boolean看门狗是否过期
LastError()FSoEError最后的错误代码
ConnectionCount()int连接数量
CrcErrorCount()intCRC 错误计数
WatchdogErrorCount()int看门狗超时计数
FramesSent()int发送帧数
FramesReceived()int接收有效帧数

连接管理

Initialize(FSoEConnectionConfig config)

public boolean Initialize(FSoEConnectionConfig config)

初始化 FSoE 连接。

参数:

返回值:

  • boolean — 成功返回 true

相关结构:

public static class FSoEConnectionConfig {
public short ConnectionId; // 唯一连接标识符
public short SafetyAddress; // FSoE 从站安全地址(拨码开关设定)
public int WatchdogTimeMs; // 看门狗超时(毫秒),默认 100
public short SafeInputSize; // 安全输入数据大小(字节)
public short SafeOutputSize; // 安全输出数据大小(字节)
public int PdoInputOffset; // IOmap 中输入偏移
public int PdoOutputOffset; // IOmap 中输出偏移
}

示例:

FSoE fsoe = slave.FSoE();

FSoE.FSoEConnectionConfig config = new FSoE.FSoEConnectionConfig();
config.ConnectionId = 0x0100;
config.SafetyAddress = 0x0100;
config.WatchdogTimeMs = 100;
config.SafeInputSize = 2;
config.SafeOutputSize = 1;

boolean ok = fsoe.Initialize(config);

Close()

public boolean Close()

关闭 FSoE 连接,释放资源。

Dispose()

public void Dispose()

释放所有资源。

安全数据交换

SafeInputData(int bufferSize)

public byte[] SafeInputData(int bufferSize)
public byte[] SafeInputData()

读取安全输入数据。

参数:

  • bufferSize (int) — 缓冲区大小(可选,默认 64 字节)

返回值:

  • byte[] — 安全输入数据,失败返回 null

SetSafeOutputData(byte[] data)

public boolean SetSafeOutputData(byte[] data)

设置安全输出数据。

参数:

  • data (byte[]) — 安全输出数据

返回值:

  • boolean — 成功返回 true

示例:

FSoE fsoe = slave.FSoE();

// 读取安全输入
byte[] safeInput = fsoe.SafeInputData();
if (safeInput != null) {
boolean ch0 = (safeInput[0] & 0x01) != 0;
System.out.println("通道0: " + (ch0 ? "安全" : "触发"));
}

// 写入安全输出
fsoe.SetSafeOutputData(new byte[]{0x01});

WriteOutputFrame(byte[] frameData)

public boolean WriteOutputFrame(byte[] frameData)

直接写入 FSoE 输出帧数据。

状态控制

RequestState(FSoEState targetState)

public boolean RequestState(FSoEState targetState)

请求 FSoE 状态转换。

参数:

返回值:

  • boolean — 成功返回 true

RequestData()

public boolean RequestData()

请求进入数据交换状态(DATA)。

RequestFailsafe()

public boolean RequestFailsafe()

主动进入失效安全模式。

ResetConnection()

public boolean ResetConnection()

重置 FSoE 连接到初始状态。

CheckWatchdog()

public boolean CheckWatchdog()

检查看门狗状态。

ClearError()

public void ClearError()

清除错误状态。

示例:

FSoE fsoe = slave.FSoE();

fsoe.RequestState(FSoE.FSoEState.DATA);
fsoe.RequestData();
fsoe.RequestFailsafe();
fsoe.ResetConnection();

统计信息

Status()

public FSoEConnectionStatus Status()

获取连接状态详情。

返回值:

相关结构:

public static class FSoEConnectionStatus {
public FSoEState State; // 当前 FSoE 状态
public FSoEError LastError; // 最后的错误代码
public int ErrorCount; // 总错误计数
public int FramesSent; // 发送帧数
public int FramesReceived; // 接收有效帧数
public int CrcErrors; // CRC 错误计数
public int WatchdogErrors; // 看门狗超时计数
public boolean WatchdogExpired; // 看门狗是否过期
public boolean InFailsafe; // 是否处于失效安全模式
public boolean IsInitialized; // 连接是否已初始化
}

示例:

FSoE.FSoEConnectionStatus status = slave.FSoE().Status();
System.out.println("状态: " + status.State);
System.out.println("CRC 错误: " + status.CrcErrors);
System.out.println("发送帧: " + status.FramesSent);
System.out.println("接收帧: " + status.FramesReceived);
System.out.println("看门狗: " + (status.WatchdogExpired ? "过期" : "正常"));

MDP 多连接

MDP(Modular Device Profile)支持单个从站包含多个独立的 FSoE 安全连接,每个模块有自己的安全地址和数据通道。

InitializeMdp(FSoEModuleConfig[] moduleConfigs)

public boolean InitializeMdp(FSoEModuleConfig[] moduleConfigs)

初始化 MDP 多连接模式。

参数:

返回值:

  • boolean — 成功返回 true

相关结构:

public static class FSoEModuleConfig {
public int SlotIndex; // 模块槽位索引
public int AxisNumber; // 轴编号
public FSoEModuleProfile Profile;// 模块配置文件
public short SafetyAddress; // 安全地址
public short ConnectionId; // 连接 ID
public FSoEConnectionType ConnectionType; // 连接类型
public short SafeInputSize; // 安全输入数据大小
public short SafeOutputSize; // 安全输出数据大小
public int PdoInputOffset; // PDO 输入偏移
public int PdoOutputOffset; // PDO 输出偏移
public int WatchdogTimeMs; // 看门狗超时(毫秒)

public FSoEConnectionConfig toConnectionConfig(); // 转换为连接配置
}

MDP 数据读写

// 读取指定连接的安全输入
byte[] mdpInput = fsoe.MdpSafeInputData(connectionIndex);

// 写入指定连接的安全输出
fsoe.SetMdpSafeOutputData(connectionIndex, outputData);

// 查询指定连接的状态
FSoE.FSoEState mdpState = fsoe.MdpConnectionState(connectionIndex);

示例:

FSoE fsoe = slave.FSoE();

int connCount = fsoe.ConnectionCount();
for (int i = 0; i < connCount; i++) {
FSoE.FSoEState state = fsoe.MdpConnectionState(i);
byte[] input = fsoe.MdpSafeInputData(i);
System.out.printf("连接 %d: 状态=%s, 输入=%s%n", i, state,
input != null ? String.format("%d 字节", input.length) : "null");
}

MDP 设备配置

FSoEMdpDeviceConfig

用于配置 MDP 多连接设备的完整配置结构。

public static class FSoEMdpDeviceConfig {
public FSoEConnectionMode ConnectionMode; // 连接模式
public short DeviceSafetyAddress; // 设备级安全地址
public boolean IsDrive; // 是否为驱动设备
public int AxisCount; // 轴数量
public List<FSoEModuleConfig> Modules; // 模块列表

public FSoEModuleConfig addModule(FSoEModuleProfile profile, short safetyAddress);
public FSoEModuleConfig addDriveAxis(int axisNumber, short safetyAddress);
public int getConnectionCount();
}

示例:

FSoE.FSoEMdpDeviceConfig devConfig = new FSoE.FSoEMdpDeviceConfig();
devConfig.ConnectionMode = FSoE.FSoEConnectionMode.MULTIPLE;

// 添加数字量安全 IO 模块
devConfig.addModule(FSoE.FSoEModuleProfile.DIGITAL_IN_OUT, (short) 0x0100);
devConfig.addModule(FSoE.FSoEModuleProfile.DIGITAL_INPUT, (short) 0x0200);

// 添加驱动轴
devConfig.addDriveAxis(0, (short) 0x0300);

事件监听

FSoE 提供多种事件监听器接口,用于监控安全连接状态。

事件接口

// 状态变化事件
@FunctionalInterface
public interface OnStateChanged {
void onStateChanged(FSoEStateChangedEventArgs args);
}

// 错误事件
@FunctionalInterface
public interface OnErrorOccurred {
void onErrorOccurred(FSoEErrorEventArgs args);
}

// 失效安全事件
@FunctionalInterface
public interface OnFailsafeTriggered {
void onFailsafeTriggered(FSoEFailsafeEventArgs args);
}

// 安全数据更新事件
@FunctionalInterface
public interface OnSafeDataUpdated {
void onSafeDataUpdated(FSoEDataUpdatedEventArgs args);
}

事件参数结构:

public static class FSoEStateChangedEventArgs {
public int SlaveIndex; // 从站索引
public int ConnectionIndex; // 连接索引
public FSoEState OldState; // 旧状态
public FSoEState NewState; // 新状态
}

public static class FSoEErrorEventArgs {
public int SlaveIndex; // 从站索引
public int ConnectionIndex; // 连接索引
public FSoEError Error; // 错误代码
public FSoEState CurrentState; // 当前状态
public String getDescription(); // 错误描述
}

public static class FSoEFailsafeEventArgs {
public int SlaveIndex; // 从站索引
public int ConnectionIndex; // 连接索引
public FSoEFailsafeReason Reason; // 触发原因
public boolean EnteringFailsafe; // 是否进入失效安全
}

安全数据结构体

安全数据使用 Structure 映射,确保内存布局与设备匹配:

// EL19xx 数字量安全输入(2 字节安全数据)
public static class SafeDigitalInput extends Structure {
public byte inputBits; // 安全输入状态位(每位对应一个通道)
public byte diagBits; // 输入诊断状态

@Override
protected List<String> getFieldOrder() {
return Arrays.asList("inputBits", "diagBits");
}
}

// EL29xx 数字量安全输出(1 字节安全数据)
public static class SafeDigitalOutput extends Structure {
public byte outputBits; // 安全输出控制位

@Override
protected List<String> getFieldOrder() {
return Arrays.asList("outputBits");
}
}

// ETG.6100 安全驱动输入(10 字节安全数据)
public static class SafeDriveInput extends Structure {
public short safetyStatusWord; // 安全状态字
public int safeActualPosition; // 安全实际位置
public int safeActualVelocity; // 安全实际速度

@Override
protected List<String> getFieldOrder() {
return Arrays.asList("safetyStatusWord", "safeActualPosition", "safeActualVelocity");
}
}

// ETG.6100 安全驱动输出(2 字节安全数据)
public static class SafeDriveOutput extends Structure {
public short safetyControlWord; // 安全控制字

@Override
protected List<String> getFieldOrder() {
return Arrays.asList("safetyControlWord");
}
}
结构体大小必须匹配

SafeInputSize / SafeOutputSize 必须与结构体的实际大小一致,否则读写会失败。

FSoE 管理器

FSoEManager

多连接管理器,管理多个 FSoE 连接的生命周期。

public static class FSoEManager {
public boolean addConnection(short slaveIndex, int connectionId,
short safetyAddress, int watchdogMs,
short safeInputSize, short safeOutputSize,
int pdoInputOffset, int pdoOutputOffset);
public boolean removeConnection(int slaveIndex, int connectionId);
public void processCycle();
public void closeAll();
public int getConnectionCount();
public FSoEMdp FindByAddress(short safetyAddress);
public FSoEMdp FindByConnectionId(int connectionId);
public boolean BindSafeIO(short slaveIndex, int safeInputOffset, int safeInputSize,
int safeOutputOffset, int safeOutputSize,
short safetyAddress, int watchdogMs);
public boolean BindMdpDriveAxis(short slaveIndex, int axisIndex,
short safetyAddress, int watchdogMs,
short safeInputSize, short safeOutputSize,
int pdoInputOffset, int pdoOutputOffset);
public String GetStatusSummary();
}

BindSafeIO()

public boolean BindSafeIO(short slaveIndex, int safeInputOffset, int safeInputSize,
int safeOutputOffset, int safeOutputSize,
short safetyAddress, int watchdogMs)

一步绑定安全 IO。自动完成连接初始化 + 状态推进到 DATA。

参数:

  • slaveIndex (short) — 从站索引(1-based)
  • safeInputOffset (int) — 输入 PDO 中安全数据的字节偏移
  • safeInputSize (int) — 安全输入数据大小(字节)
  • safeOutputOffset (int) — 输出 PDO 中安全数据的字节偏移
  • safeOutputSize (int) — 安全输出数据大小(字节)
  • safetyAddress (short) — 安全地址(硬件拨码)
  • watchdogMs (int) — 看门狗超时(毫秒)

示例:

FSoE.FSoEManager mgr = new FSoE.FSoEManager(dll, masterIndex);

// 一步绑定(自动 Session -> Connection -> Parameter -> Data)
mgr.BindSafeIO((short) 1, 0, 2, 0, 1, (short) 0x0100, 100);

BindMdpDriveAxis()

public boolean BindMdpDriveAxis(short slaveIndex, int axisIndex,
short safetyAddress, int watchdogMs,
short safeInputSize, short safeOutputSize,
int pdoInputOffset, int pdoOutputOffset)

MDP 驱动轴安全连接绑定。每轴一个独立 FSoE 连接。

示例:

// 双轴安全驱动器
mgr.BindMdpDriveAxis((short) 1, 0, (short) 0x0100, 100,
(short) 10, (short) 2, 0, 0); // 轴 0
mgr.BindMdpDriveAxis((short) 1, 1, (short) 0x0200, 100,
(short) 10, (short) 2, 0, 0); // 轴 1

批量操作

// 检查所有连接是否在 DATA 状态
// (通过遍历连接状态判断)

// 获取状态摘要
System.out.println(mgr.GetStatusSummary());

// 关闭所有连接
mgr.closeAll();

SafetyManager — MDP 模块化绑定

SafetyManagerFSoE 内的多连接管理器。FSoEManager 是其历史别名(API 等价)。下面三个 MDP 专用方法适配 ETG.5001 模块化设备协议(如 Beckhoff EL69x0、安全 IO 模块),通过 moduleNumber / axisIndex 自动映射 PDO 偏移,无需手算 safeInputOffset / safeOutputOffset

BindMdpSafeInput()

public boolean BindMdpSafeInput(short slaveIndex, short safetyAddress,
short inputSize, int moduleNumber, int watchdogMs)

绑定一个只输入的 MDP 安全模块(典型场景:紧急停止按钮 / 光幕反馈)。

参数:

  • moduleNumber (int) — MDP 模块编号(0-based),SDK 自动从该模块 SI 描述推导 PDO 字节偏移
  • 其余参数同 BindSafeIO

BindMdpSafeOutput()

public boolean BindMdpSafeOutput(short slaveIndex, short safetyAddress,
short outputSize, int moduleNumber, int watchdogMs)

绑定一个只输出的 MDP 安全模块(典型:安全继电器输出 / 安全使能信号)。

BindMdpDriveAxis() — 推荐

public boolean BindMdpDriveAxis(short slaveIndex, int axisIndex,
short safetyAddress, int watchdogMs)

绑定 MDP 驱动轴的 STO/SS1/SS2 安全连接。输入/输出 size 由 SDK 从 MDP 模块描述自动发现,无需手填。每轴独立 ConnID = axisIndex + 1

示例(双轴 MDP 安全驱动器 + 一个 STO 输入):

FSoE.SafetyManager mgr = new FSoE.SafetyManager(dll, masterIndex);

// 紧急停止按钮 (输入)
mgr.BindMdpSafeInput((short) 2, (short) 0x0001, (short) 2, 0, 100);

// 双轴驱动器
mgr.BindMdpDriveAxis((short) 1, 0, (short) 0x0100, 100); // 轴 0
mgr.BindMdpDriveAxis((short) 1, 1, (short) 0x0200, 100); // 轴 1

System.out.println(mgr.GetStatusSummary());
与 BindSafeIO 的差异
  • BindSafeIO — 必须手填 safeInputOffset / safeOutputOffset,适合非 MDP 安全从站(裸 FSoE 帧)
  • BindMdpSafeInput / Output / DriveAxis — SDK 从 MDP 0xF050/0xF030 模块列表自动定位 PDO 区域,参数更少

Connection ID 校验

isConnectionIdAvailable(int connId)

public static boolean isConnectionIdAvailable(int connId)

检查指定的 FSoE Connection ID 是否可用(未被当前 Master 占用)。静态方法,无需实例化 FSoE 即可调用,供配置工具在分配新 ConnID 前做冲突校验,避免 Initialize 因重复而失败。

参数:

  • connId (int) — 候选 Connection ID,有效范围 1..65535

返回值:

  • booleantrue 可用(未被占用且非 0);false 已被占用,或为 0(保留值)
ETG.5120 §5.2.3 约束
  • ConnID 必须 >= 10 为保留值
  • ConnID 在同一 Master 内必须全局唯一

示例:

int myConnId = 0x1234;

if (!FSoE.isConnectionIdAvailable(myConnId)) {
System.out.println("ConnID 0x" + Integer.toHexString(myConnId) + " 已被占用");
return;
}

// 冲突校验通过后再 Initialize
FSoE.FSoEConnectionConfig config = new FSoE.FSoEConnectionConfig();
config.ConnectionId = (short) myConnId;
config.SafetyAddress = 0x0100;
config.WatchdogTimeMs = 100;
config.SafeInputSize = 2;
config.SafeOutputSize = 1;
slave.FSoE().Initialize(config);
与 C# 的对齐

Java 的静态方法 FSoE.isConnectionIdAvailable(int) 对应 C# 的 FSoE.IsConnectionIdAvailable(ushort),语义一致,仅遵循 Java camelCase 命名惯例。

CRC 计算

fsoeCrc16(byte[] data)

public static int fsoeCrc16(byte[] data)

计算 FSoE CRC16 校验值(CCITT-False 算法)。

fsoeCrc16Fast(byte[] data)

public static int fsoeCrc16Fast(byte[] data)

使用查找表加速计算 FSoE CRC16。

FSoECrc16 类

可配置的 CRC-16 实现类(默认 CRC-16/CCITT-FALSE)。

public static class FSoECrc16 {
public FSoECrc16(); // 默认参数
public FSoECrc16(int polynomial, int initial, int xorOut); // 自定义参数
public int compute(byte[] data); // 计算 CRC
}

FSoEState 枚举

public enum FSoEState {
RESET(0x100), // 初始/重置状态
SESSION(0x101), // 会话建立
CONNECTION(0x102), // 连接建立
PARAMETER(0x103), // 参数下载
DATA(0x104), // 数据交换(正常工作)
FAILSAFE(0x105); // 失效安全
}

FSoEError 枚举

public enum FSoEError {
NONE(0x0000), // 无错误
WRONG_COMMAND(0x0001), // 错误的命令
UNKNOWN_COMMAND(0x0002), // 未知命令
WRONG_CONNECTION_ID(0x0003), // 连接 ID 不匹配
CRC_ERROR(0x0004), // CRC 校验失败
WATCHDOG(0x0005), // 看门狗超时
WRONG_ADDRESS(0x0006), // 错误的 FSoE 地址
WRONG_DATA(0x0007), // 无效数据
COMM_PARAM_LENGTH(0x0008), // 通信参数长度错误
COMM_PARAM(0x0009), // 通信参数错误
APP_PARAM_LENGTH(0x000A), // 应用参数长度错误
APP_PARAM(0x000B), // 应用参数错误
UNEXPECTED_SESSION(0x000C), // 意外的会话命令
FAILSAFE_DATA(0x000D), // 收到失效安全数据
NOT_INITIALIZED(0x0100), // FSoE 未初始化
MAX_CONNECTIONS(0x0101), // 达到最大连接数
INVALID_STATE_TRANSITION(0x0102) // 无效状态转换
}

其他枚举

FSoECommand

public enum FSoECommand {
RESET((byte) 0x00), // 重置命令
SESSION((byte) 0x01), // 会话请求/响应
CONNECTION((byte) 0x02), // 连接请求/响应
PARAMETER((byte) 0x03), // 参数下载
FAILSAFE((byte) 0x04), // 失效安全模式
DATA((byte) 0x05); // 正常数据交换
}

FSoEFailsafeReason

public enum FSoEFailsafeReason {
WATCHDOG_TIMEOUT(0), // 看门狗超时
CRC_ERROR(1), // CRC 错误
COMMUNICATION_ERROR(2), // 通信错误
APPLICATION_REQUEST(3), // 应用请求
SLAVE_REQUEST(4), // 从站请求
MASTER_REQUEST(5), // 主站请求
RECOVERY_TO_DATA(6); // 恢复到数据模式
}

FSoEModuleProfile

public enum FSoEModuleProfile {
DIGITAL_INPUT(190), // FSoE 数字输入
DIGITAL_IN_OUT(195), // FSoE 数字输入/输出
DIGITAL_OUTPUT(290), // FSoE 数字输出
DRIVE_CONNECTION(790), // FSoE MDP 驱动连接
MASTER(6900); // FSoE 主站
}

完整示例

数字量安全 IO

FSoE fsoe = slave.FSoE();

if (fsoe.IsCapable()) {
// 初始化连接
FSoE.FSoEConnectionConfig config = new FSoE.FSoEConnectionConfig();
config.ConnectionId = 0x0100;
config.SafetyAddress = 0x0100;
config.WatchdogTimeMs = 100;
config.SafeInputSize = 2;
config.SafeOutputSize = 1;
fsoe.Initialize(config);

// 请求数据交换
fsoe.RequestData();

// 注册事件
fsoe.addOnErrorOccurred(args -> {
System.out.printf("FSoE 错误: 0x%04X, %s%n",
args.Error.Value(), args.getDescription());
});
fsoe.addOnFailsafeTriggered(args -> {
if (args.EnteringFailsafe) {
System.out.println("进入失效安全! 原因: " + args.Reason);
}
});

// 周期性数据交换
while (running) {
byte[] safeInput = fsoe.SafeInputData();
if (safeInput != null) {
boolean ch0 = (safeInput[0] & 0x01) != 0;
fsoe.SetSafeOutputData(new byte[]{ ch0 ? (byte) 0x01 : (byte) 0x00 });
}

// 检查状态
if (fsoe.InFailsafe()) {
System.out.println("安全模式已触发!");
break;
}

Thread.sleep(1);
}

// 查看统计
FSoE.FSoEConnectionStatus status = fsoe.Status();
System.out.printf("发送: %d, 接收: %d, CRC错误: %d%n",
status.FramesSent, status.FramesReceived, status.CrcErrors);

fsoe.Close();
}

FSoEManager 一步绑定

FSoE.FSoEManager mgr = new FSoE.FSoEManager(dll, masterIndex);

// 一步绑定(自动 Session -> Connection -> Parameter -> Data)
mgr.BindSafeIO((short) 1, 0, 2, 0, 1, (short) 0x0100, 100);

// 检查状态
System.out.println(mgr.GetStatusSummary());

// PDO 回调中读写安全数据
master.addOnPdoCyclicSync(mi -> {
mgr.processCycle();
// 读取安全输入
byte[] data = slave.FSoE().SafeInputData();
if (data != null) {
boolean ch0 = (data[0] & 0x01) != 0;
slave.FSoE().SetSafeOutputData(new byte[]{ ch0 ? (byte) 0x01 : (byte) 0x00 });
}
});

// ... 运行后关闭
mgr.closeAll();

安全驱动器

FSoE fsoe = slave.FSoE();

if (fsoe.IsCapable()) {
// 初始化安全驱动器连接(10 字节输入 + 2 字节输出)
FSoE.FSoEConnectionConfig config = new FSoE.FSoEConnectionConfig();
config.ConnectionId = 0x0100;
config.SafetyAddress = 0x0100;
config.WatchdogTimeMs = 100;
config.SafeInputSize = 10; // SafeDriveInput: u16 + i32 + i32
config.SafeOutputSize = 2; // SafeDriveOutput: u16
fsoe.Initialize(config);
fsoe.RequestData();

// 失效安全事件
fsoe.addOnFailsafeTriggered(args -> {
if (args.EnteringFailsafe) {
System.out.println("驱动器安全停止! 原因: " + args.Reason);
}
});

// 周期性安全驱动控制
while (running) {
byte[] safeInput = fsoe.SafeInputData();
if (safeInput != null && safeInput.length >= 10) {
// 解析安全状态字 (前2字节, 小端)
int safetyStatus = (safeInput[0] & 0xFF) | ((safeInput[1] & 0xFF) << 8);

byte[] output = new byte[2];
if ((safetyStatus & 0x0100) != 0) {
// Bit8: 安全故障 -> 确认故障
output[0] = (byte) 0x80;
output[1] = 0x00;
// 读取安全实际位置 (字节2-5)
int position = (safeInput[2] & 0xFF)
| ((safeInput[3] & 0xFF) << 8)
| ((safeInput[4] & 0xFF) << 16)
| ((safeInput[5] & 0xFF) << 24);
System.out.printf("安全故障: 位置=%d%n", position);
} else {
// 正常运行: 清除 STO 请求
output[0] = 0x00;
output[1] = 0x00;
}
fsoe.SetSafeOutputData(output);
}
Thread.sleep(1);
}

fsoe.Close();
}

MDP 多连接模式

FSoE fsoe = slave.FSoE();

if (fsoe.IsCapable()) {
// 配置多个模块
FSoE.FSoEModuleConfig[] modules = new FSoE.FSoEModuleConfig[2];

modules[0] = new FSoE.FSoEModuleConfig();
modules[0].SafetyAddress = 0x0100;
modules[0].ConnectionId = 0x0001;
modules[0].SafeInputSize = 2;
modules[0].SafeOutputSize = 1;
modules[0].WatchdogTimeMs = 100;
modules[0].Profile = FSoE.FSoEModuleProfile.DIGITAL_IN_OUT;

modules[1] = new FSoE.FSoEModuleConfig();
modules[1].SafetyAddress = 0x0200;
modules[1].ConnectionId = 0x0002;
modules[1].SafeInputSize = 2;
modules[1].SafeOutputSize = 0;
modules[1].WatchdogTimeMs = 100;
modules[1].Profile = FSoE.FSoEModuleProfile.DIGITAL_INPUT;

fsoe.InitializeMdp(modules);

// 按连接索引访问数据
int connCount = fsoe.ConnectionCount();
for (int i = 0; i < connCount; i++) {
FSoE.FSoEState state = fsoe.MdpConnectionState(i);
byte[] input = fsoe.MdpSafeInputData(i);
System.out.printf("连接 %d: %s%n", i, state);
}
}

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

FSoE.FSoEManager mgr = new FSoE.FSoEManager(dll, masterIndex);

// 绑定 2 个轴(每轴独立 FSoE 连接)
mgr.BindMdpDriveAxis((short) 1, 0, (short) 0x0100, 100,
(short) 10, (short) 2, 0, 0); // 轴 0
mgr.BindMdpDriveAxis((short) 1, 1, (short) 0x0200, 100,
(short) 10, (short) 2, 0, 0); // 轴 1

System.out.println(mgr.GetStatusSummary());

// PDO 回调中处理各轴安全数据
master.addOnPdoCyclicSync(mi -> {
mgr.processCycle();
// 各轴独立读写安全数据...
});

// 事件监控: 每轴独立监控失效安全
slave.FSoE().addOnFailsafeTriggered(args -> {
System.out.printf("轴%d 失效安全: %s%n",
args.ConnectionIndex, args.Reason);
});

// 运行后关闭
mgr.closeAll();