跳到主要内容

ESI 文件管理

ESI (EtherCAT Slave Information) 文件包含从站完整配置信息: 设备标识、对象字典、PDO 映射、DC 配置、启动参数等。

C SDK 通过两套 API 处理 ESI:

  • esi_dll_* 系列 (utils/esi_ext.h) 在 DLL 上动态绑定 EcEsi_* 入口,完成"加载/绑定/启动参数注册/DC 模式查询"
  • utils/esi.hutils/eni.h 提供独立解析器,可在不依赖 DLL 的情况下解析 ESI/ENI XML
命名说明

utils/esi_ext.h 中 DLL 包装函数统一带 esi_dll_ 前缀,与 utils/esi.h 托管 XML 解析器的 esi_load_file() 区分(后者签名为 esi_load_file(const char*, DarraEsiDeviceInfo*),见下文 独立 ESI 解析)。

DLL 绑定

#include "utils/esi_ext.h"

esi_ctx_t esi = {0};
if (esi_init(&esi, &dll) == 0) {
/* DLL 未导出 EcEsi_* (老版 DLL) */
}

esi_init 在已加载的 dll_t 上绑定 EcEsi_* 函数指针。至少 LoadFile + BindToSlave 可用才算成功(返回 1=全部绑定成功,0=失败)。

ESI 加载

esi_dll_load_file()

int esi_dll_load_file(const esi_ctx_t* ctx, const char* file_path);

加载单个 ESI XML 文件,返回该文件中包含的 Device 数(0 表示失败)。

esi_dll_load_directory()

int esi_dll_load_directory(const esi_ctx_t* ctx, const char* dir_path);

加载目录下所有 .xml / .ESI 文件,返回累计 Device 数。

esi_dll_clear()

void esi_dll_clear(const esi_ctx_t* ctx);

清空 ESI 缓存。

esi_dll_get_loaded_count()

int esi_dll_get_loaded_count(const esi_ctx_t* ctx);

返回当前已加载的 ESI 文件数。

从站绑定

esi_dll_bind_to_slave()

int esi_dll_bind_to_slave(const esi_ctx_t* ctx,
uint16_t master_index, uint16_t slave_index,
const char* file_path);

将指定 ESI 文件绑定到从站。slave_index 1-based。返回 >0 成功,0 失败,<0 参数错误。

esi_dll_auto_match_all()

int esi_dll_auto_match_all(const esi_ctx_t* ctx, uint16_t master_index);

按从站身份(VendorId/ProductCode/Revision)自动匹配 ESI 仓库中的设备并绑定,返回成功匹配的从站数。

esi_dll_register_startup_parameters()

int esi_dll_register_startup_parameters(const esi_ctx_t* ctx,
uint16_t master_index, uint16_t slave_index);

读取从站绑定的 ESI 中 <InitCmd> 段,转换为启动参数并加入主站启动参数列表。需要在 PreOp 之前调用,返回注册的参数数量(<0 失败)。

esi_dll_apply_all_slaves()

int esi_dll_apply_all_slaves(const esi_ctx_t* ctx, uint16_t master_index);

对所有已绑定 ESI 的从站执行 esi_dll_register_startup_parameters + ApplyStartupParameters,一次完成扫描-绑定-注册-应用全流程。

ESI DC 同步模式查询

ESC 硬件 DC bit (GetSlaveHasDC()) 仅表示芯片支持 DC,不代表 ESI 声明该从站应启用 DC。EL6022 / EK1110 / GCAN-8200 等从站 ESC 报 DC capable 但 ESI 无 <Dc> OpMode,强行启用 DC 会让从站卡在 SafeOP→OP。启用 DC 前应同时检查 GetSlaveHasDC() 与本组接口。

esi_dll_get_device_dc_sync_mode()

int esi_dll_get_device_dc_sync_mode(const esi_ctx_t* ctx,
uint16_t master_index, uint16_t slave_index);

查询从站 ESI 声明的 DC 同步模式。返回值:

返回值含义
-2参数非法 / 从站未绑定 ESI Device(调用方应退化为 GetSlaveHasDC)
-1ESI 未声明 <Dc> 节点(从站不用 DC,应跑 FreeRun)
0ESI <Dc> 存在但无 OpMode → FreeRun
1SM-Sync(启用 SM 但 AssignActivate 无 0x100)
2DC-Sync0
3DC-Sync0 + Sync1

esi_dll_has_esi_dc_sync()

int esi_dll_has_esi_dc_sync(const esi_ctx_t* ctx, const dll_t* dll,
uint16_t master_index, uint16_t slave_index);

便捷判定: ESI 是否声明此从站使用 DC 同步。get_device_dc_sync_mode >= 2 返回 1,0/1/-1 返回 0,未绑定(-2)时退化为 GetSlaveHasDC 硬件位保守判定。返回 1=ESI 声明 DC 同步,0=未声明。

与 slave/dc.h 的关系

slave/dc.h 中的 slave_has_esi_dc_sync() 是同一语义的标准 API 封装,内部即走 EcEsi_GetDeviceDcSyncMode。详见 DC 同步

从站 ESI 查询

GetSlaveHasEsi()

BOOL GetSlaveHasEsi(uint16_t master_index, uint16_t slave_index);

检查从站是否已加载 ESI 文件。

SetSlaveEsiFile()

BOOL SetSlaveEsiFile(uint16_t master_index, uint16_t slave_index, const char* file_path);

为从站显式指定 ESI 文件。

GetSlaveEsiVersion()

BOOL GetSlaveEsiVersion(uint16_t master_index, uint16_t slave_index,
char* buf, int buf_size);

获取从站 ESI 版本字符串。

GetSlaveVendorName()

BOOL GetSlaveVendorName(uint16_t master_index, uint16_t slave_index,
char* buf, int buf_size);

获取从站厂商名称(从 ESI 文件中读取)。

GetSlaveHasMDP()

BOOL GetSlaveHasMDP(uint16_t master_index, uint16_t slave_index);

检查从站是否为 MDP 模块化设备。

独立 ESI 解析 (utils/esi.h)

不依赖 DLL,可在 SDK 任意层解析 ESI XML 文件:

esi_load_file (XML 解析版)

int esi_load_file(const char* filePath, DarraEsiDeviceInfo* info);

解析 ESI XML 文件,提取设备信息、SM/PDO/DC/邮箱配置到 DarraEsiDeviceInfo。返回 0 成功,-1 文件打开失败,-2 XML 解析失败,-3 未找到设备节点。

esi_apply_startup()

int esi_apply_startup(void* dll, uint16_t mi, uint16_t si, const char* filePath);

解析 ESI XML 中的 InitCmd 元素,转换为启动参数并通过 DLL 写入从站。返回成功写入的参数数量。

esi_match_revision()

bool esi_match_revision(uint32_t actual, uint32_t expected, int strategy);

ESI 版本号匹配。strategy 取值:

  • ESI_MATCH_EXACT (0) — 精确匹配 RevisionNo
  • ESI_MATCH_MAJOR (1) — 仅匹配高 16 位(主版本)
  • ESI_MATCH_IGNORE (2) — 忽略 RevisionNo

eeprom_crc()

uint8_t eeprom_crc(const uint8_t* data, int length);

计算 EEPROM 数据 CRC-8 校验值(ETG.2010 标准)。

eeprom_validate_crc()

bool eeprom_validate_crc(const uint8_t* data, int length);

验证 EEPROM 数据末尾的 CRC 字节是否匹配。length 包含 CRC 字节。

ESI 数据结构

typedef struct {
uint32_t vendorId;
uint32_t productId;
uint32_t revisionId;
char productName[128];
char vendorName[128];
char groupType[64];
DarraEsiCoEDetails coeDetails;
bool supportsCoE;
bool supportsSoE;
bool supportsFoE;
bool supportsEoE;
bool supportsVoE;
DarraEsiDcConfiguration dcConfig;
DarraEsiSmInfo syncManagers[8];
int smCount;
DarraEsiPdoConfiguration pdoConfig;
DarraEsiMailboxTimeout mailboxTimeout;
DarraEsiEepromConfiguration eepromConfig;
int ebusCurrent; /* E-Bus 电流 (mA) */
} DarraEsiDeviceInfo;

ENI 解析 (utils/eni.h)

ENI (EtherCAT Network Information) 是 ETG.2100 定义的整网拓扑/PDO/DC 配置 XML,可在调用 LoadENI() 之前先解析校验。

eni_load()

int eni_load(const char* filePath, DarraEniConfiguration* config);

解析 ETG.2100 ENI XML 到 DarraEniConfiguration。返回 0 成功,-1 文件不可读,-2 XML 格式错误,-3 根节点不是 EtherCATConfig。

eni_save()

int eni_save(const char* filePath, const DarraEniConfiguration* config);

将摘要结构序列化为 ETG.2100 兼容的 ENI XML。返回 0 成功,-1 输入参数错误,-2 写入失败。

typedef struct {
char name[128];
int cycleTimeUs;
int dcCycleTimeUs;
int expectedSlaveCount;
} DarraEniMasterConfig;

typedef struct {
int physicalAddress;
int autoIncAddress;
uint32_t vendorId;
uint32_t productCode;
uint32_t revisionNumber;
char name[128];
int dcAssignActivate;
int sm2Offset;
int sm2Length;
int sm3Offset;
int sm3Length;
} DarraEniSlaveConfig;

typedef struct {
DarraEniMasterConfig master;
DarraEniSlaveConfig slaves[256];
int slaveCount;
char version[16];
} DarraEniConfiguration;

完整示例

#include "ethercat.h"
#include "utils/esi_ext.h"

int main(void)
{
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.dll");

/* 1. 绑定 ESI 函数指针 */
esi_ctx_t esi = {0};
esi_init(&esi, &dll);

/* 2. 加载 ESI 库 */
int dev_count = esi_dll_load_directory(&esi, "C:/EtherCAT/ESI");
printf("已加载 %d 个 Device\n", dev_count);

/* 3. 创建主站 + 扫描 */
uint16_t mi = dll.Initialize();
dll.SetNetwork(mi, "\\Device\\NPF_{...}", "");

/* 4. 自动匹配 ESI 到从站 */
int matched = esi_dll_auto_match_all(&esi, mi);
printf("已匹配 %d 个从站的 ESI\n", matched);

/* 5. 注册并应用所有启动参数 */
int applied = esi_dll_apply_all_slaves(&esi, mi);
printf("应用了 %d 条启动参数\n", applied);

/* 6. 进入 OP */
dll.SetStateSequence(mi, EC_STATE_OPERATIONAL, 10000);
dll.Start(mi);

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