跳到主要内容

MDP -- 模块化设备

Modular Device Profile (MDP, ETG.5001) 是 EtherCAT 中描述模块化设备的标准协议。

自动处理

内部会自动处理 MDP 设备的模块检测和 PDO 配置。 绝大多数场景下,用户无需直接操作 MDP。

关于热插拔

SDK 支持 MDP 模块热插拔再配置自修复。 但生产环境中几乎不会出现 MDP 模块热插拔的使用场景。 EtherCAT 从站的模块配置在设备上电后即固定,运行期间不会动态变更。 部署 MDP 模块化设备推荐使用 DENI; 如需自动配置,请先导入对应的 ESI 文件,再调用 AutoStartup()

模块索引间距

MDP 设备中各模块的对象字典地址按固定间距排列。例如模块 1 基地址为 0xF030,模块 2 基地址为 0xF040,间距为 0x10(16)。SDK 自动处理地址偏移计算。

MDP 检测

BOOL GetSlaveHasMDP(uint16_t master_index, uint16_t slave_index);

检查从站是否为 MDP 模块化设备。返回 TRUE 表示从站声明了 MDP 模块。

模块结构

typedef struct {
uint16_t slot_index; /* 槽位索引(从 1 开始) */
uint32_t module_ident; /* 模块标识码(32 位) */
char module_name[128]; /* 模块名称 */
uint32_t module_type; /* 模块设备类型码 */
char status[32]; /* "已配置" / "已检测" */
uint16_t object_dict_index; /* 对象字典基地址 */
char module_io_type[16]; /* "Input" / "Output" / "I/O" / "Unknown" */
} mdp_slot_info_t;

typedef struct {
uint16_t slot_index;
uint16_t module_profile;
uint16_t module_ident;
uint16_t pdo_input_offset;
uint16_t pdo_input_size;
uint16_t pdo_output_offset;
uint16_t pdo_output_size;
} mdp_module_t;

typedef struct {
int count;
mdp_module_t* modules;
} mdp_list_t;
ModuleIOType 判定逻辑

优先从 ESI 文件的模块定义中读取 TxPdo / RxPdo 配置:

  • 仅有 TxPdo → "Input"(从站发送到主站)
  • 仅有 RxPdo → "Output"(主站发送到从站)
  • 两者都有 → "I/O"

ESI 无法判断时回退到模块名称关键字匹配。

模块发现

/* ethercat_advanced.h */
mdp_list_t* mdp_discover(dll_t* dll, uint16_t master, uint16_t slave);
mdp_list_t* mdp_get_detected_modules(dll_t* dll, uint16_t master, uint16_t slave);
void mdp_free(mdp_list_t* list);
  • mdp_discover() 通过 CoE 读取已配置模块列表(索引 0xF030
  • mdp_get_detected_modules() 通过 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);
}

PDO 布局

typedef struct {
int slot_index;
uint32_t input_offset; /* 相对于 slave.Ioffset */
uint16_t input_size;
uint32_t output_offset; /* 相对于 slave.Ooffset */
uint16_t output_size;
} mdp_pdo_info_t;

typedef struct {
int count;
mdp_pdo_info_t* modules;
} mdp_pdo_layout_t;

mdp_pdo_layout_t* mdp_get_pdo_layout(dll_t* dll,
uint16_t master, uint16_t slave);
void mdp_free_pdo_layout(mdp_pdo_layout_t* layout);

获取各模块在从站 IOmap 中的 PDO 布局。通过 CoE SDORead 读取 PDO Assignment (0x1C12/0x1C13) 和 PDO Mapping 条目,累积计算各模块的字节偏移。

备注

需要从站已完成 DENI 配置(ConfigMap 后可用),不依赖 ESI 文件。CoE 不可用或未检测到模块时返回 NULL

自动配置

MDP 设备的自动配置需要先导入对应的 ESI 文件,由以下函数完成:

  • 导入 ESI(初始化时)SetEsiFile() / SetEsiFiles() 参见 初始化
  • 自动配置(初始化时)EnableAutoStartup() 参见 初始化
  • 自动配置(从站级)AutoStartup(master, slave) 参见 从站 API

完整示例

#define DYNAMIC_LOAD
#include "ethercat.h"
#include "ethercat_advanced.h"
#include <stdio.h>

int main(void) {
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.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;
}

/* 已配置模块 */
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);
}

/* PDO 布局 */
mdp_pdo_layout_t* layout = mdp_get_pdo_layout(&dll, master, slave);
if (layout) {
for (int i = 0; i < layout->count; i++) {
mdp_pdo_info_t* p = &layout->modules[i];
printf("Slot %d: In=%dB @%u, Out=%dB @%u\n",
p->slot_index, p->input_size, p->input_offset,
p->output_size, p->output_offset);
}
mdp_free_pdo_layout(layout);
}

dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}