MDP — 模块化设备
Modular Device Profile (MDP, ETG.5001) 是 EtherCAT 中描述模块化设备的标准协议。一个 MDP 从站 (如 GCAN-8200、Beckhoff EK 耦合器) 由若干可插拔模块组成,每个模块在对象字典中占用一段地址区。
进入 OP 时 SDK 会自动编排 MDP 从站的模块配置 (检测物理模块 → 比对已配置列表 → 必要时回写)。GCAN-8200 等设备只需导入 ESI 或使用 DENI,绝大多数场景下无需手动操作 MDP API。
模块级 API
Rust SDK 的 MDP 功能以 darra_ethercat::slave::mdp 模块的自由函数形式提供,不是从站对象上的方法。所有函数以 master_index + slave_index (1-based) 定位从站。
use darra_ethercat::slave::mdp::{
mdp_discover, mdp_discover_safety, mdp_is_mdp_device,
read_configured_address_list, read_detected_address_list,
read_module_profile_list, get_module_pdo_layout,
is_module_config_consistent, check_module_match,
auto_enumerate, auto_configure_from_detected_modules,
MdpModule, MdpModuleClass, MdpModuleProfile, MdpModulePdoInfo,
};
检测设备
mdp_is_mdp_device()
pub fn mdp_is_mdp_device(master_index: u16, slave_index: u16) -> bool
通过 0xF000:00 的可读性判断从站是否为 MDP 模块化设备。
发现模块
mdp_discover()
pub fn mdp_discover(master_index: u16, slave_index: u16) -> Result<Vec<MdpModule>>
读取 0xF050 (模块描述列表) 发现从站所有 MDP 模块。需要从站处于 PreOp 及以上状态。
pub struct MdpModule {
pub module_number: u8, // 模块编号 (1-based, 对应 0xF050 子索引)
pub vendor_id: u32, // 厂商 ID
pub product_code: u32, // 产品代码
pub revision_no: u32, // 修订号
pub module_class: MdpModuleClass, // 模块分类 (枚举)
pub raw_ident: u32, // 原始模块标识符 (0xF050:sub 完整 u32 值)
}
impl MdpModule {
pub fn is_safety(&self) -> bool; // 是否为 FSoE 功能安全模块
pub fn is_drive(&self) -> bool; // 是否为驱动器轴模块
}
模块分类为枚举类型 MdpModuleClass,从 raw_ident 高 16 位解析:
| 枚举值 | 原始码 | 含义 |
|---|---|---|
DigitalInput | 0x0001 | 数字量输入 |
DigitalOutput | 0x0002 | 数字量输出 |
AnalogInput | 0x0003 | 模拟量输入 |
AnalogOutput | 0x0004 | 模拟量输出 |
CounterInput | 0x0005 | 计数器输入 |
DriveAxis | 0x0006 | 驱动器轴 |
FunctionalSafety | 0x0007 | 功能安全 (FSoE) |
EncoderInterface | 0x0008 | 编码器接口 |
CommunicationBridge | 0x0009 | 通信桥接 |
Unknown | 0xFFFF | 未知分类 |
MdpModule 与 MdpModuleClass 均实现 Display,可直接格式化输出。
mdp_discover_safety()
pub fn mdp_discover_safety(master_index: u16, slave_index: u16) -> Result<Vec<MdpModule>>
仅返回支持 FSoE 的功能安全模块 (mdp_discover() 结果按 is_safety() 过滤)。
示例:
use darra_ethercat::slave::mdp::{mdp_discover, mdp_is_mdp_device};
if mdp_is_mdp_device(master.index(), 1) {
let modules = mdp_discover(master.index(), 1)?;
for m in &modules {
// Display: "模块[01] 分类:数字量输入 VID:0x... PC:0x... Rev:0x..."
println!("{}", m);
}
}
模块地址列表
read_configured_address_list() / read_detected_address_list()
pub fn read_configured_address_list(master_index: u16, slave_index: u16) -> Result<Vec<u32>>
pub fn read_detected_address_list(master_index: u16, slave_index: u16) -> Result<Vec<u32>>
返回各槽位的模块标识 (ident) 列表:
- configured — 已配置模块列表 (
0xF030,转调 nativeMDPGetConfigModuleList) - detected — 已检测模块列表,反映物理上实际插入的模块 (
0xF050,转调 nativeMDPGetDetectedModuleList)
read_module_profile_list()
pub fn read_module_profile_list(master_index: u16, slave_index: u16) -> Result<Vec<MdpModuleProfile>>
读取模块 Profile 列表 (0xF010),同时从 0xF050 读取对应的模块标识码。需要从站处于 PreOp 及以上状态。
pub struct MdpModuleProfile {
pub slot_index: u8, // 槽位索引 (子索引, 1-based)
pub profile_number: u32, // Profile Number (0xF010:sub 的 u32 值)
pub module_ident: u32, // 模块标识码 (从 0xF050 读取的对应值)
}
模块一致性校验
is_module_config_consistent()
pub fn is_module_config_consistent(master_index: u16, slave_index: u16) -> bool
检查已配置模块与已检测模块是否一致 (转调 native MDPCheckModuleMatch)。等价于 ETG.5001 ModuleIdListMismatch (AL 0x0035) 校验语义。
check_module_match()
pub fn check_module_match(master_index: u16, slave_index: u16) -> (bool, i32)
校验模块列表,返回 (is_match, first_mismatch_slot)。不匹配时 first_mismatch_slot 为 0-based 槽位索引。
模块 PDO 布局
get_module_pdo_layout()
pub fn get_module_pdo_layout(master_index: u16, slave_index: u16) -> Result<Vec<MdpModulePdoInfo>>
获取各模块在从站 IOmap 中的 PDO 布局。通过 CoE 读取 PDO Assignment (0x1C12/0x1C13) 与 PDO Mapping 条目,累积计算各模块字节偏移。返回值中的偏移相对于 slave.Ioffset / slave.Ooffset。
pub struct MdpModulePdoInfo {
pub slot_index: u8, // 槽位索引
pub input_offset: u32, // 输入 PDO 偏移
pub input_size: u16, // 输入 PDO 字节数
pub output_offset: u32, // 输出 PDO 偏移
pub output_size: u16, // 输出 PDO 字节数
}
示例:
use darra_ethercat::slave::mdp::get_module_pdo_layout;
let layout = get_module_pdo_layout(master.index(), 1)?;
for m in &layout {
println!("Slot {}: In={}B @{}, Out={}B @{}",
m.slot_index, m.input_size, m.input_offset,
m.output_size, m.output_offset);
}
自动编排与配置
auto_enumerate()
pub fn auto_enumerate(master_index: u16) -> i32
自动枚举该 master 下所有 MDP 从站的模块 (转调 native MDPAutoEnumerate)。返回成功枚举的从站/模块数,负值表示失败。
auto_configure_from_detected_modules()
pub fn auto_configure_from_detected_modules(master_index: u16, slave_index: u16) -> Result<u8>
根据检测到的模块自动配置:读取 0xF050 (已检测模块),逐个写入 0xF030 (已配置模块),使已配置列表与已检测列表保持一致。返回成功写入的模块数。需要从站处于 PreOp 状态且支持 0xF030 写入。
MDP 设备的配置推荐通过 DENI 文件 (Darra 配置工具一键导出) 或导入对应 ESI 文件 完成:
- 导入 ESI —
EsiManager::load_path()/EsiManager::add_file()(进程级 ESI 仓库静态方法, 见 ESI 文件管理) - 主站自动启动参数 —
EtherCATMaster::builder().enable_auto_startup()
进入 OP 时 SDK 自动编排 MDP 模块,auto_enumerate() / auto_configure_from_detected_modules() 仅用于工具类应用或诊断脚本中的显式控制。
SDK 支持 MDP 模块热插拔再配置自修复。但生产环境几乎不会出现 MDP 模块热插拔场景 —— EtherCAT 从站的模块配置在设备上电后即固定,运行期间不会动态变更。
完整示例
use darra_ethercat::{EtherCATMaster, EcState};
use darra_ethercat::utils::esi::EsiManager;
use darra_ethercat::slave::mdp::{
mdp_is_mdp_device, mdp_discover,
read_configured_address_list, read_detected_address_list,
get_module_pdo_layout, check_module_match, auto_enumerate,
};
fn main() -> darra_ethercat::Result<()> {
// 加载 ESI 文件 (MDP 设备需要 ESI 描述模块布局)
// EsiManager 是进程级 ESI 仓库, 静态方法 load_path 加载整个目录
EsiManager::load_path(&EsiManager::default_path());
// build() 返回 BuildResult (只实现 Deref, 无 DerefMut)。
// set_state 需要 &mut self, 须用 .master 取出 owned EtherCATMaster 并 let mut。
let mut master = EtherCATMaster::builder()
.set_eni(r"C:\EtherCAT\MyProject.deni")
.build()?
.master;
master.set_state(EcState::PreOp)?;
// 进 OP 前先让 MDP 从站完成模块自动枚举
let enumerated = auto_enumerate(master.index());
println!("MDP 自动枚举: {} 项", enumerated);
let slave_count = master.slave_count();
for i in 1..=slave_count {
if !mdp_is_mdp_device(master.index(), i) {
continue;
}
println!("从站 {} 是 MDP 设备", i);
// 发现模块
for m in &mdp_discover(master.index(), i)? {
println!(" {}", m);
}
// 已配置 / 已检测对比
let configured = read_configured_address_list(master.index(), i)?;
let detected = read_detected_address_list(master.index(), i)?;
println!(" 已配置 {} 个, 已检测 {} 个", configured.len(), detected.len());
let (is_match, first_mismatch) = check_module_match(master.index(), i);
if !is_match {
println!(" ⚠ 模块列表不一致, 首个不匹配槽位: {}", first_mismatch);
}
// PDO 布局
for m in &get_module_pdo_layout(master.index(), i)? {
println!(" PDO: Slot {} In={}B @{}, Out={}B @{}",
m.slot_index, m.input_size, m.input_offset,
m.output_size, m.output_offset);
}
}
master.set_state(EcState::Operational)?;
Ok(())
}