MDP -- 模块化设备
Modular Device Profile (MDP, ETG.5001) 是 EtherCAT 中描述模块化设备的标准协议。
内部会自动处理 MDP 设备的模块检测和 PDO 配置。 绝大多数场景下,用户无需直接操作 MDP。
SDK 支持 MDP 模块热插拔再配置自修复。
但生产环境中几乎不会出现 MDP 模块热插拔的使用场景。
EtherCAT 从站的模块配置在设备上电后即固定,运行期间不会动态变更。
部署 MDP 模块化设备推荐使用 DENI;
如需自动配置,请先用 esi_dll_load_directory() 导入对应的 ESI 文件,再让主站正常走 INIT → PreOp → OP 状态链,自动编排会在 PreOp 阶段完成(见下文 MDP 自动编排)。
MDP 设备中各模块的对象字典地址按固定间距排列。例如模块 1 基地址为 0xF030,模块 2 基地址为 0xF040,间距为 0x10(16)。SDK 自动处理地址偏移计算。
MDP 检测
BOOL GetSlaveHasMDP(uint16_t master_index, uint16_t slave_index);
检查从站是否为 MDP 模块化设备。返回 TRUE 表示从站声明了 MDP 模块。
模块结构
/* ethercat_advanced.h */
typedef struct {
uint16_t slot_index; /* 槽位号 */
uint16_t module_profile; /* 模块配置文件 (190/195/290/790/6900) */
uint16_t module_ident; /* 模块标识 */
uint16_t pdo_input_offset; /* PDO 输入偏移 */
uint16_t pdo_input_size; /* PDO 输入大小 */
uint16_t pdo_output_offset; /* PDO 输出偏移 */
uint16_t pdo_output_size; /* PDO 输出大小 */
} mdp_module_t;
typedef struct {
int count;
mdp_module_t* modules;
} mdp_list_t;
模块发现 (ethercat_advanced.h)
/* ethercat_advanced.h */
mdp_list_t* mdp_discover(dll_t* dll, uint16_t master, uint16_t slave);
void mdp_free(mdp_list_t* list);
mdp_discover()通过 CoE 读取从站已检测模块列表(对象字典0xF050,反映物理上实际插入的模块)- 返回值需调用
mdp_free()释放,失败返回NULL
示例:
mdp_list_t* modules = mdp_discover(&dll, master, 1);
if (modules) {
for (int i = 0; i < modules->count; i++) {
mdp_module_t* m = &modules->modules[i];
printf("Slot %d: Ident=0x%04X, In=%dB Out=%dB\n",
m->slot_index, m->module_ident,
m->pdo_input_size, m->pdo_output_size);
}
mdp_free(modules);
}
模块标识列表直读 (slave/mdp.h)
slave/mdp.h 提供轻量 static __inline 包装,直读 ETG.5001 标准模块标识数组(uint32_t 模块标识码),不走 SDO 缓存路径。对应 native 导出 MDPGetConfigModuleList / MDPGetDetectedModuleList / MDPCheckModuleMatch。
/* slave/mdp.h — 第一个参数为运行时库的 HMODULE 句柄 */
int mdp_get_config_module_list(void* dll_handle, uint16_t master, uint16_t slave,
uint32_t* out_idents, int max);
int mdp_get_detected_module_list(void* dll_handle, uint16_t master, uint16_t slave,
uint32_t* out_idents, int max);
int mdp_check_module_match(void* dll_handle, uint16_t master, uint16_t slave,
int* out_first_mismatch);
mdp_get_config_module_list()— 读0xF030ConfigModuleIdent[](已配置模块标识)。返回写入数量,<=0失败。mdp_get_detected_module_list()— 读0xF050DetectedModuleIdent[](已检测模块标识)。返回写入数量,<=0失败。mdp_check_module_match()— 比对0xF030与0xF050。返回1=完全匹配,0=不匹配(out_first_mismatch输出 0-based 槽位),<0=未支持/失败。
这组包装第一个参数是运行时库的模块句柄(动态加载模式下即 dll.handle),不是 dll_t*。对应函数缺失时安全返回 -1。
MDP 自动编排 (ETG.5001)
MDP 模块化设备的自动配置 = 读 0xF050(已检测)→ 匹配 ESI <Modules> → 生成 0xF030 写序列 + 主站 PDO Assignment + 模块 SdoInit。该编排在主站 INIT → PreOp 后由底层运行时自动执行,SDK 通常无需任何编排代码。
MDPAutoEnumerate()
int MDPAutoEnumerate(uint16_t master_index);
对主站下所有 MDP 从站执行模块枚举(读取 0xF050 检测模块列表并刷新内部模块表)。返回处理的从站数量,<0 表示失败。绑定在 dll_t 主表中,可直接 dll.MDPAutoEnumerate(master) 调用。
推荐使用 DENI 文件部署(一行加载 ESI/PDO/模块配置全套)。如需运行时自动配置,先用 esi_dll_load_directory() 导入对应 ESI 文件,再让主站正常走 INIT → PreOp → OP 状态链,自动编排会在 PreOp 阶段完成。
完整示例
#define DYNAMIC_LOAD
#include "ethercat.h"
#include "ethercat_advanced.h"
#include "slave/mdp.h"
#include <stdio.h>
int main(void) {
dll_t dll;
LOAD_DLL(&dll, "DarraEtherCAT.dll");
uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{GUID}", "");
dll.SetStateSequence(master, EC_STATE_PRE_OP, 5000);
uint16_t slave = 1;
if (!dll.GetSlaveHasMDP(master, slave)) {
printf("从站不是 MDP 设备\n");
dll.Dispose(master); UNLOAD_DLL(&dll);
return 1;
}
/* 已检测模块 (含 PDO 偏移/大小) */
mdp_list_t* modules = mdp_discover(&dll, master, slave);
if (modules) {
for (int i = 0; i < modules->count; i++) {
mdp_module_t* m = &modules->modules[i];
printf("Slot %d: Profile=%d Ident=0x%04X In=%dB@%u Out=%dB@%u\n",
m->slot_index, m->module_profile, m->module_ident,
m->pdo_input_size, m->pdo_input_offset,
m->pdo_output_size, m->pdo_output_offset);
}
mdp_free(modules);
}
/* 模块标识列表校验: 已配置 (0xF030) vs 已检测 (0xF050) */
int first_mismatch = -1;
int match = mdp_check_module_match(dll.handle, master, slave, &first_mismatch);
if (match == 1) {
printf("模块列表匹配\n");
} else if (match == 0) {
printf("模块不匹配, 首个差异槽位: %d\n", first_mismatch);
}
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}