跳到主要内容

FSoE 功能安全

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

通过 FsoeManager(单/多连接管理)和 FsoeMdp(MDP 多模块连接)管理连接。

FSoE 设备检测

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

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

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

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

  • FsoeManager — 多从站多连接协调器,提供 bind_safe_io 一步初始化
  • FsoeMdp — MDP 多连接管理器,支持单从站上的多个 FSoE 连接
  • FsoeCrc16 — CRC16 校验工具

快速开始

FSoE 工作流程

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

状态机流程:

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

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

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

use ethercat::{EtherCatMaster, EcState, FsoeManager, FsoeConfig};

// 安全数据结构体
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
struct SafeDigitalInput {
input_bits: u8, // 安全输入状态位(每位对应一个通道)
diag_bits: u8, // 输入诊断状态
}

#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
struct SafeDigitalOutput {
output_bits: u8, // 安全输出控制位
}

let master = EtherCatMaster::new()?;
master.set_eni("config.deni")?;
master.build()?;
master.set_state(EcState::Operational)?;

// 一步绑定安全 IO
let mut fsoe_mgr = FsoeManager::new(master.index());
fsoe_mgr.bind_safe_io(
1, // slave_index
0, 2, // safe_input_offset, safe_input_size
0, 1, // safe_output_offset, safe_output_size
0x0100, // safety_address
100, // watchdog_ms
)?;

// PDO 周期中处理
let events = master.events();
events.on_pdo_cyclic_sync(move |_| {
fsoe_mgr.process_cycle();

// 读取安全输入
let inputs = fsoe_mgr.all_in_data();
if let Some(data) = inputs.get(&1) {
let safe_input = data[0];
let safe_output = if (safe_input & 0x01) != 0 { 0x01u8 } else { 0x00u8 };
let _ = fsoe_mgr.write_safe_output(1, &[safe_output]);
}
});

FSoE 与普通 PDO 的区别

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

普通 PDO

IOmap 中的数据就是用户数据,可以直接读写内存。

FSoE PDO

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

SDK 的 FSoE 协议引擎负责:

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

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

安全数据结构体

安全数据结构体定义,使用 #[repr(C, packed)] 确保内存布局与设备匹配:

// EL19xx 数字量安全输入(2 字节安全数据)
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
pub struct SafeDigitalInput {
pub input_bits: u8, // 安全输入状态位(每位对应一个通道)
pub diag_bits: u8, // 输入诊断状态
}

// EL29xx 数字量安全输出(1 字节安全数据)
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
pub struct SafeDigitalOutput {
pub output_bits: u8, // 安全输出控制位
}

// ETG.6100 安全驱动输入(10 字节安全数据)
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
pub struct SafeDriveInput {
pub safety_status_word: u16, // 安全状态字
pub safe_actual_position: i32, // 安全实际位置
pub safe_actual_velocity: i32, // 安全实际速度
}

// ETG.6100 安全驱动输出(2 字节安全数据)
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
pub struct SafeDriveOutput {
pub safety_control_word: u16, // 安全控制字
}

// 安全编码器输入(6 字节安全数据)
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
pub struct SafeEncoderInput {
pub safe_position: u32, // 安全位置值
pub status_word: u16, // 状态字
}
结构体大小必须匹配

safe_input_size / safe_output_size 必须与结构体的 std::mem::size_of::<T>() 一致,否则读写会失败。

FsoeManager — 多从站连接管理器

创建管理器

pub fn new(master_index: u16) -> Self

创建新的 FSoE 多连接管理器,统一管理一个主站上的所有 FSoE 连接。

let mut fsoe_mgr = FsoeManager::new(master.index());

bind_safe_io()

pub fn bind_safe_io(&mut self, slave_index: u16,
safe_input_offset: u32, safe_input_size: u16,
safe_output_offset: u32, safe_output_size: u16,
safety_address: u16, watchdog_ms: u32) -> Result<()>

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

参数:

  • slave_index — 从站索引(1-based)
  • safe_input_offset — 输入 PDO 中安全数据的字节偏移
  • safe_input_size — 安全输入数据大小(字节)
  • safe_output_offset — 输出 PDO 中安全数据的字节偏移
  • safe_output_size — 安全输出数据大小(字节)
  • safety_address — 安全地址(硬件拨码)
  • watchdog_ms — 看门狗超时(毫秒)

示例:

fsoe_mgr.bind_safe_io(
1, // slave_index
0, std::mem::size_of::<SafeDigitalInput>() as u16,
0, std::mem::size_of::<SafeDigitalOutput>() as u16,
0x0100, 100,
)?;

bind_safe_input() / bind_safe_output()

pub fn bind_safe_input(&mut self, slave_index: u16, safety_address: u16,
input_size: u16, watchdog_ms: u32) -> Result<()>
pub fn bind_safe_output(&mut self, slave_index: u16, safety_address: u16,
output_size: u16, watchdog_ms: u32) -> Result<()>

仅绑定安全输入 / 仅绑定安全输出,适用于纯安全输入设备(如 EL1904)或纯安全输出设备(如 EL2904)。

bind_mdp_drive_axis()

pub fn bind_mdp_drive_axis(&mut self, slave_index: u16, axis_index: u16,
safety_address: u16, watchdog_ms: u32) -> Result<()>

MDP 驱动轴安全连接绑定。每轴一个独立 FSoE 连接,安全数据大小由 DLL 从 MDP 模块信息自动发现。

参数:

  • slave_index — 从站索引(1-based)
  • axis_index — MDP 轴/模块索引
  • safety_address — 安全地址
  • watchdog_ms — 看门狗超时(毫秒)

示例:

// 双轴安全驱动器
fsoe_mgr.bind_mdp_drive_axis(1, 0, 0x0100, 100)?; // 轴 0
fsoe_mgr.bind_mdp_drive_axis(1, 1, 0x0200, 100)?; // 轴 1
Rust SDK 仅暴露 bind_mdp_drive_axis

C# 上的 BindMdpSafeIo / BindMdpSafeInput / BindMdpSafeOutput 三种 MDP 模块绑定变体, Rust 端只保留语义最完整的 bind_mdp_drive_axis (走 SafeMdp::init_connection + module_profile=790 FSoE Drive Connection)。

如需"输入 only"或"输出 only"绑定, 当前做法:

  1. 使用 bind_safe_input / bind_safe_output 显式指定 PDO 偏移和大小, 不依赖 MDP 自动发现;
  2. 或者通过 FsoeMdp 子对象自行调 init_connection, 在 SafeMdpConfig 里只填 safe_input_sizesafe_output_size 一侧。

add_connection()

pub fn add_connection(&mut self, slave_index: u16, config: FsoeConfig) -> Result<()>

初始化指定从站的 FSoE 连接并加入管理(底层 API)。

参数:

  • slave_index — 从站索引(1-based)
  • config — FSoE 连接配置

相关结构:

pub struct FsoeConfig {
pub connection_id: u16, // 连接 ID(0 则自动分配)
pub safety_address: u16, // 安全地址(硬件拨码)
pub watchdog_time_ms: u32, // 看门狗超时(毫秒)
pub safe_input_size: u16, // 安全输入数据大小(字节)
pub safe_output_size: u16, // 安全输出数据大小(字节)
pub pdo_input_offset: u32, // PDO 输入偏移
pub pdo_output_offset: u32, // PDO 输出偏移
}

示例:

let config = FsoeConfig {
connection_id: 1,
safety_address: 0x0100,
watchdog_time_ms: 100,
safe_input_size: 2,
safe_output_size: 1,
pdo_input_offset: 0,
pdo_output_offset: 0,
};
fsoe_mgr.add_connection(1, config)?;

remove_connection()

pub fn remove_connection(&mut self, slave_index: u16) -> Result<()>

关闭并移除指定从站的 FSoE 连接。

refresh_all_status()

pub fn refresh_all_status(&mut self) -> HashMap<u16, FsoeStatus>

刷新并返回所有连接的状态快照。

返回值:

  • HashMap<u16, FsoeStatus> — 从站索引到状态的映射

write_safe_output() / write_output_frame()

pub fn write_safe_output(&self, slave_index: u16, data: &[u8]) -> Result<()>
pub fn write_output_frame(&self, slave_index: u16, frame_data: &[u8]) -> bool

向指定从站写入安全输出数据 / 原始 FSoE 输出帧。

all_in_data() / read_input_frame()

pub fn all_in_data(&self) -> HashMap<u16, Vec<u8>>
pub fn read_input_frame(&self, slave_index: u16, buffer_size: usize) -> Option<Vec<u8>>

获取所有连接的安全输入数据 / 读取指定从站的原始输入帧。

any_in_failsafe()

pub fn any_in_failsafe(&mut self) -> bool

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

enter_all_failsafe()

pub fn enter_all_failsafe(&self) -> Vec<(u16, Result<()>)>

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

返回值:

  • Vec<(u16, Result<()>)> — 每个连接的操作结果

process_cycle()

pub fn process_cycle(&self)

执行一次 FSoE 帧处理周期(由 PDO 循环回调调用)。

download_parameters()

pub fn download_parameters(&self, slave_index: u16, params: &[u8]) -> Result<u32>

下载安全参数到指定从站,返回 SRA CRC32 校验值。仅在 PARAMETER 状态调用。

参数:

  • slave_index — 从站索引(1-based)
  • params — 安全参数数据

返回值:

  • Result<u32> — SRA CRC32 校验值

reset_connection() / clear_error()

pub fn reset_connection(&self, slave_index: u16) -> Result<()>
pub fn clear_error(&self, slave_index: u16)

重置 FSoE 连接 / 清除错误。

find_by_address() / find_by_connection_id()

pub fn find_by_address(&self, safety_address: u16) -> Option<u16>
pub fn find_by_connection_id(&self, connection_id: u16) -> Option<u16>

按安全地址 / 连接 ID 查找从站索引。

state()

pub fn state(&self, slave_index: u16) -> Option<FsoeState>

获取指定从站的当前 FSoE 状态。

connection_count() / is_slave_capable()

pub fn connection_count(&self) -> usize
pub fn is_slave_capable(&self, slave_index: u16) -> bool

获取当前管理的连接数量 / 检查指定从站是否支持 FSoE。

status_summary()

pub fn status_summary(&self) -> String

返回所有连接的状态摘要字符串(诊断用)。

FsoeMdp — MDP 多模块连接

MDP 设备支持在单个从站上承载多个独立的 FSoE 连接(模块),典型场景如安全 I/O 模块、安全驱动器。

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

创建实例

pub fn new(master_index: u16, slave_index: u16) -> Self

示例:

let mdp = FsoeMdp::new(master.index(), 1);

detect_connections() / slave_connection_count() / device_address()

pub fn detect_connections(&self) -> u16
pub fn slave_connection_count(&self) -> u16
pub fn device_address(&self) -> Result<u16>

detect_connections() 探测从站支持的 FSoE MDP 连接数量; slave_connection_count() 返回从站已配置的 MDP 连接数; device_address() 读取从站设备级 FSoE 安全地址。

init_connection() / close_connection()

pub fn init_connection(&self, connection_index: u16, config: FsoeMdpConfig) -> Result<()>
pub fn close_connection(&self, connection_index: u16) -> Result<()>

初始化 / 关闭指定模块的 FSoE MDP 连接。

参数:

  • connection_index — 连接索引(从 0 开始)
  • config — MDP 连接配置

相关结构:

pub struct FsoeMdpConfig {
pub connection_id: u16, // 唯一连接标识符
pub safety_address: u16, // FSoE 从站安全地址
pub watchdog_time_ms: u32, // 看门狗超时(毫秒)
pub safe_input_size: u16, // 安全输入数据大小(字节)
pub safe_output_size: u16, // 安全输出数据大小(字节)
pub pdo_input_offset: u32, // IOmap 中输入偏移
pub pdo_output_offset: u32, // IOmap 中输出偏移
pub module_number: u16, // 模块编号(槽位号)
pub module_profile: u16, // 模块配置文件 (190, 195, 290, 790, 6900)
pub axis_number: u16, // 轴编号(仅用于 Drive Connection)
pub connection_type: u16, // 连接类型: 0=Master, 1=Slave
}

status() / check_watchdog() / last_error() / clear_error()

pub fn status(&self, connection_index: u16) -> Result<FsoeStatus>
pub fn check_watchdog(&self, connection_index: u16) -> bool
pub fn last_error(&self, connection_index: u16) -> i32
pub fn clear_error(&self, connection_index: u16)

status() 返回该连接的完整运行状态; check_watchdog() 检查看门狗 (true=正常); last_error() / clear_error() 获取与清除该连接的错误。

相关结构:

pub struct FsoeStatus {
pub state: FsoeState, // 当前 FSoE 状态
pub last_error: FsoeError, // 最后的错误代码
pub error_count: u32, // 总错误计数
pub frames_sent: u32, // 发送帧数
pub frames_received: u32, // 接收有效帧数
pub crc_errors: u32, // CRC 错误计数
pub watchdog_errors: u32, // 看门狗超时计数
pub watchdog_expired: u8, // 当前看门狗状态
pub in_failsafe: u8, // 是否处于失效安全模式
pub toggle_bit: u8, // 当前切换位值
pub initialized: u8, // 连接是否已初始化
}

request_state() / state() / reset()

pub fn request_state(&self, connection_index: u16, target_state: FsoeState) -> Result<()>
pub fn state(&self, connection_index: u16) -> Option<FsoeState>
pub fn reset(&self, connection_index: u16) -> Result<()>

request_state() 请求指定连接的状态转换; state() 返回当前状态; reset() 重置指定连接 (清错误计数、回 Reset 状态)。

write_safe_output() / read_safe_input()

pub fn write_safe_output(&self, connection_index: u16, data: &[u8]) -> Result<()>
pub fn read_safe_input(&self, connection_index: u16, max_size: usize) -> Result<Vec<u8>>

写入 / 读取指定连接的安全输出 / 输入数据。

download_parameters() / set_failsafe_output()

pub fn download_parameters(&self, connection_index: u16, params: &[u8]) -> Result<u32>
pub fn set_failsafe_output(&self, connection_index: u16, data: &[u8]) -> Result<()>

download_parameters() 下载安全参数到指定连接, 返回 SRA CRC; set_failsafe_output() 设置指定连接的失效安全输出值。

module_comm_param() / module_diagnosis()

pub fn module_comm_param(&self, module_number: u16) -> Result<Vec<u8>>
pub fn module_diagnosis(&self, module_number: u16) -> Result<(u16, u16)>

module_comm_param() 读取指定模块的 FSoE 通信参数; module_diagnosis() 返回 (连接状态, 连接诊断)

FsoeCrc16 — CRC16 校验

符合 ETG.5001.4 第 7.2 节规范的 CCITT-False CRC16 计算器(多项式 0x1021, 初始值 0xFFFF)。

use ethercat::FsoeCrc16;

// 计算 CRC16
let crc = FsoeCrc16::compute(&[0x01, 0x02, 0x03]);

// 验证数据末尾附带的 CRC16 (小端序)
let valid = FsoeCrc16::verify(&data_with_crc);

// 在数据末尾追加 CRC16
let data_with_crc = FsoeCrc16::append(&data);

// 增量更新
let crc = FsoeCrc16::update(0xFFFF, &chunk1);
let crc = FsoeCrc16::update(crc, &chunk2);

FSoE 状态与错误

FsoeState

#[repr(i32)]
pub enum FsoeState {
Reset = 0x100, // 初始/重置状态
Session = 0x101, // 会话建立
Connection = 0x102, // 连接建立
Parameter = 0x103, // 参数下载
Data = 0x104, // 数据交换(正常工作)
Failsafe = 0x105, // 失效安全 (从站输出安全值)
}

FsoeError

#[repr(i32)]
pub enum FsoeError {
None = 0x0000, // 无错误
WrongCommand = 0x0001, // 错误的命令
UnknownCommand = 0x0002, // 未知命令
WrongConnectionId = 0x0003, // 连接 ID 不匹配
CrcError = 0x0004, // CRC 校验失败
Watchdog = 0x0005, // 看门狗超时
WrongAddress = 0x0006, // 安全地址错误
WrongData = 0x0007, // 数据错误
CommParamLength = 0x0008, // 通信参数长度错误
CommParam = 0x0009, // 通信参数错误
AppParamLength = 0x000A, // 应用参数长度错误
AppParam = 0x000B, // 应用参数错误
UnexpectedSession = 0x000C, // 意外的会话命令
FailsafeData = 0x000D, // 收到失效安全数据
NotInitialized = 0x0100, // FSoE 未初始化(内部错误)
MaxConnections = 0x0101, // 达到最大连接数(内部错误)
}

FsoeCommand

#[repr(u8)]
pub enum FsoeCommand {
Reset = 0x00, // 重置命令
Session = 0x01, // 会话请求/响应
Connection = 0x02, // 连接请求/响应
Parameter = 0x03, // 参数下载
Failsafe = 0x04, // 失效安全模式
Data = 0x05, // 正常数据交换
}

FsoeFailsafeReason

pub enum FsoeFailsafeReason {
WatchdogTimeout, // 看门狗超时
CrcError, // CRC 错误
CommunicationError, // 通信错误
ApplicationRequest, // 应用请求
SlaveRequest, // 从站请求
MasterRequest, // 主站请求
RecoveryToData, // 恢复到数据模式
}

FsoeModuleProfile

pub enum FsoeModuleProfile {
DigitalInput = 190, // FSoE 数字量输入
DigitalInOut = 195, // FSoE 数字量输入/输出
DigitalOutput = 290, // FSoE 数字量输出
DriveConnection = 790, // FSoE 驱动连接 (CiA402)
Master = 6900, // FSoE 主站模块
}

FsoeConnectionInfo

pub struct FsoeConnectionInfo {
pub slave_index: u16, // 从站索引
pub config: FsoeConfig, // 连接配置(拷贝)
pub last_status: Option<FsoeStatus>, // 最近一次状态快照
}

事件

FSoE 事件通过主站事件系统注册,在 PDO 周期中自动检测并触发。

on_fsoe_state_changed()

events.on_fsoe_state_changed(|args| {
println!("FSoE 从站{} 连接{}: {:?} -> {:?}",
args.slave_index, args.connection_index,
args.old_state, args.new_state);
});

参数结构:

pub struct FsoeStateChangedEventArgs {
pub slave_index: u16, // 从站索引
pub connection_index: i32, // 连接索引(MDP 模式下区分连接)
pub old_state: FsoeState, // 变化前的状态
pub new_state: FsoeState, // 变化后的状态
}

on_fsoe_error()

events.on_fsoe_error(|args| {
println!("FSoE 错误: 从站{}, 代码=0x{:04X}, 当前状态={:?}",
args.slave_index, args.error as i32, args.current_state);
});

参数结构:

pub struct FsoeErrorEventArgs {
pub slave_index: u16, // 从站索引
pub connection_index: i32, // 连接索引
pub error: FsoeError, // 错误代码
pub current_state: FsoeState, // 发生错误时的状态
}

on_fsoe_failsafe()

events.on_fsoe_failsafe(|args| {
if args.entering_failsafe {
println!("进入失效安全! 原因: {:?}", args.reason);
// 应用层安全处理:停止运动、切断气源等
} else {
println!("恢复数据交换");
}
});

参数结构:

pub struct FsoeFailsafeEventArgs {
pub slave_index: u16, // 从站索引
pub connection_index: i32, // 连接索引
pub reason: FsoeFailsafeReason, // 触发原因
pub entering_failsafe: bool, // true=进入失效安全,false=恢复
}

on_fsoe_safe_data_updated()

events.on_fsoe_safe_data_updated(|args| {
let direction = if args.is_input { "输入" } else { "输出" };
println!("安全数据更新: 从站{} {} {}字节",
args.slave_index, direction, args.data.len());
});

参数结构:

pub struct FsoeDataUpdatedEventArgs {
pub slave_index: u16, // 从站索引
pub connection_index: i32, // 连接索引
pub is_input: bool, // 是否为输入数据
pub data: Vec<u8>, // 安全数据
}

完整示例

单连接安全 IO(bind_safe_io 一步初始化)

use ethercat::{EtherCatMaster, EcState, FsoeManager};

let master = EtherCatMaster::new()?;
master.set_eni("config.deni")?;
master.build()?;
master.set_state(EcState::Operational)?;

let mut fsoe_mgr = FsoeManager::new(master.index());

// 一步绑定(自动 Session -> Connection -> Parameter -> Data)
fsoe_mgr.bind_safe_io(
1, // slave_index: EL1904+EL2904
0, 2, // safe_input_offset=0, safe_input_size=2
0, 1, // safe_output_offset=0, safe_output_size=1
0x0100, // safety_address
100, // watchdog_ms
)?;

// PDO 周期中处理
let events = master.events();
events.on_pdo_cyclic_sync(move |_| {
fsoe_mgr.process_cycle();

// 读取安全输入
let inputs = fsoe_mgr.all_in_data();
if let Some(data) = inputs.get(&1) {
let safe_input = data[0];
// 根据安全输入决定安全输出
let safe_output = if (safe_input & 0x01) != 0 { 0x01u8 } else { 0x00u8 };
let _ = fsoe_mgr.write_safe_output(1, &[safe_output]);
}
});

安全驱动器

use ethercat::{FsoeManager, FsoeConfig};

let mut fsoe_mgr = FsoeManager::new(master.index());

// 绑定安全驱动器 IO(10 字节输入 + 2 字节输出)
let config = FsoeConfig {
connection_id: 1,
safety_address: 0x0100,
watchdog_time_ms: 100,
safe_input_size: 10, // SafeDriveInput: u16 + i32 + i32
safe_output_size: 2, // SafeDriveOutput: u16
pdo_input_offset: 0,
pdo_output_offset: 0,
};
fsoe_mgr.add_connection(1, config)?;

// PDO 周期中处理安全驱动
let events = master.events();
events.on_pdo_cyclic_sync(move |_| {
fsoe_mgr.process_cycle();

if let Some(data) = fsoe_mgr.read_input_frame(1, 10) {
// 解析安全状态字 (前2字节, 小端)
let safety_status = u16::from_le_bytes([data[0], data[1]]);

let mut ctrl_word: u16 = 0;
if (safety_status & 0x0100) != 0 {
// Bit8: 安全故障 -> 确认故障
ctrl_word |= 0x0080;
println!("安全故障: STO, 位置={}", i32::from_le_bytes([
data[2], data[3], data[4], data[5]
]));
} else {
// 正常运行: 清除 STO 请求
ctrl_word &= !0x0001;
}
let _ = fsoe_mgr.write_safe_output(1, &ctrl_word.to_le_bytes());
}
});

// 事件监控
let events = master.events();
events.on_fsoe_failsafe(|args| {
if args.entering_failsafe {
println!("驱动器安全停止! 原因: {:?}", args.reason);
}
});

MDP 多模块安全设备

use ethercat::{FsoeMdp, FsoeMdpConfig};

let mdp = FsoeMdp::new(master.index(), 1);

// 探测支持的连接数
let conn_count = mdp.detect_connections();
println!("从站 1 支持 {} 个 FSoE 连接", conn_count);

// 获取设备安全地址
if let Ok(addr) = mdp.device_address() {
println!("设备安全地址: 0x{:04X}", addr);
}

// 初始化各模块连接
for i in 0..conn_count {
let config = FsoeMdpConfig {
connection_id: i + 1,
safety_address: 0x0100 + i,
watchdog_time_ms: 100,
safe_input_size: 2,
safe_output_size: 1,
pdo_input_offset: 0,
pdo_output_offset: 0,
module_number: i,
module_profile: 195, // DigitalInOut
axis_number: 0,
connection_type: 0,
};
mdp.init_connection(i, config)?;
}

// 读取各模块安全输入
for i in 0..conn_count {
let input = mdp.read_safe_input(i, 16)?;
println!("模块 {} 安全输入: {:02X?}", i, input);
}

// 查询各连接状态
for i in 0..conn_count {
if let Ok(status) = mdp.status(i) {
println!("连接 {}: 状态={:?}, 失效安全={}, CRC错误={}",
i, status.state, status.in_failsafe != 0, status.crc_errors);
}
}

// 模块诊断
for i in 0..conn_count {
if let Ok((state, diagnosis)) = mdp.module_diagnosis(i) {
println!("连接 {}: 诊断码=0x{:04X}", i, diagnosis);
}
}

// 关闭所有连接
for i in 0..conn_count {
let _ = mdp.close_connection(i);
}

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

use ethercat::FsoeManager;

let mut fsoe_mgr = FsoeManager::new(master.index());

// 绑定 2 个轴的安全连接
fsoe_mgr.bind_mdp_drive_axis(1, 0, 0x0100, 100)?; // 轴 0
fsoe_mgr.bind_mdp_drive_axis(1, 1, 0x0200, 100)?; // 轴 1

// PDO 周期中处理
let events = master.events();
events.on_pdo_cyclic_sync(move |_| {
fsoe_mgr.process_cycle();
// 各轴独立读写安全数据
});

// 每轴独立监控
let events2 = master.events();
events2.on_fsoe_failsafe(|args| {
println!("轴{} 失效安全: {:?}", args.connection_index, args.reason);
});

状态监控

// 刷新所有连接状态
let status_map = fsoe_mgr.refresh_all_status();
for (slave_idx, status) in &status_map {
println!("从站 {}: 状态={:?}, 错误计数={}, CRC错误={}, 帧发送={}, 帧接收={}",
slave_idx, status.state,
status.error_count, status.crc_errors,
status.frames_sent, status.frames_received);
if status.in_failsafe != 0 {
println!(" 从站 {} 处于失效安全模式", slave_idx);
}
}

// 状态摘要
println!("{}", fsoe_mgr.status_summary());

// 检查是否有连接处于失效安全
if fsoe_mgr.any_in_failsafe() {
println!("警告: 存在失效安全连接");
}

// 紧急情况:所有连接进入失效安全
let results = fsoe_mgr.enter_all_failsafe();
for (idx, result) in results {
match result {
Ok(()) => println!("从站 {} 已进入失效安全", idx),
Err(e) => println!("从站 {} 失效安全失败: {:?}", idx, e),
}
}
备注

FsoeManager 实现了 Drop trait,销毁时自动关闭所有连接。

ConnID 校验 (validate_conn_id)

在建立新 FSoE 连接前, 需保证所选 connection_id 在本主站所有活动连接中唯一 (ETG.5120 §5.2.3). SDK 在模块级 (crate::slave::fsoe) 提供 validate_conn_id / is_connection_id_available 两个 等价函数, 直接调用 DLL FSoEValidateConnId.

is_connection_id_available()

pub fn is_connection_id_available(conn_id: u16) -> bool
pub fn validate_conn_id(conn_id: u16) -> bool // 等价别名

校验指定 ConnID 是否未被占用。conn_id == 0 总是非法; 非 0 且未被任何活动连接占用时返回 true。两个函数语义完全一致, 命名不同以适配不同代码风格。

批量分配示例

use ethercat::slave::fsoe::{validate_conn_id, is_connection_id_available};

// 从 1 起递增找第一个可用 ID
let mut conn_id = 1u16;
while !validate_conn_id(conn_id) {
conn_id = conn_id.checked_add(1).expect("ConnID 耗尽");
}
println!("分配 ConnID = {}", conn_id);

// 别名写法 (语义相同)
assert!(is_connection_id_available(conn_id));

// 应用到 FsoeConfig
let config = FsoeConfig {
connection_id: conn_id,
safety_address: 0x0100,
watchdog_time_ms: 100,
safe_input_size: 2,
safe_output_size: 1,
pdo_input_offset: 0,
pdo_output_offset: 0,
};
fsoe_mgr.add_connection(1, config)?;
参考
  • ETG.5120 §5.2.3 FSoE Connection Identifier 唯一性要求