初始化
强烈建议使用 Darra 配置工具导出的 DENI 文件进行初始化。
DENI 文件包含完整的网络配置(网口、从站、PDO 映射、DC 设置等),可确保配置的一致性和正确性。
使用 DENI 文件管理设备硬件设施将获得更好的兼容性与通用性。
创建主站
C SDK 直接调用同名函数:#include "ethercat.h" 之后即可用,无句柄、无函数指针表。主站索引 master_index 作为所有后续 API 的第一个参数。
Initialize()
uint16_t Initialize(void);
uint16_t InitializeSpecificMaster(uint16_t master_index);
int GetMaxMasterInstances(void);
Initialize() 创建一个新的主站实例并返回其索引(>0 成功,0 失败)。InitializeSpecificMaster() 初始化指定索引的主站(不允许索引 0)。GetMaxMasterInstances() 返回允许的最大主站实例数。
SetNetwork()
uint16_t SetNetwork(uint16_t master_index, const char* if_primary, const char* if_secondary);
绑定网卡。if_secondary 传空字符串 "" 表示单网卡模式。
参数:
master_index(uint16_t) — 主站索引if_primary(const char*) — 主网卡名称(Windows:\\Device\\NPF_{GUID},Linux:eth0)if_secondary(const char*) — 冗余网卡名称(""表示无冗余)
返回值:
uint16_t— 发现的从站数量,0 或 0xFFFF 表示失败
示例:
uint16_t master = Initialize();
uint16_t slave_count = SetNetwork(master, "\\Device\\NPF_{GUID}", "");
printf("发现 %d 个从站\n", slave_count);
资源释放
Dispose()
BOOL Dispose(uint16_t master_index);
void FreeMemory(void* ptr);
void EmergencyCloseNics(void);
Dispose()释放主站实例,返回是否成功。调用后master_index不可再使用。FreeMemory()释放 SDK 中标注「调用方 FreeMemory」的返回值(如GetRedundancyStatus()返回的状态结构)。GetSerialNumber()等只读借用字符串禁止用它释放(静态/缓存内存,释放会崩溃)。EmergencyCloseNics()紧急关闭所有网卡句柄,在进程异常退出(信号处理 /atexit)时调用。
多实例管理
每个主站实例管理一条独立的 EtherCAT 总线,使用不同的网卡。Initialize() 多次调用即可创建多个实例。
- 每个实例必须使用不同的网卡(同一网卡不能被多个实例同时使用)
- CPU 核心由 SDK 自动分配,不同实例占用不同核心
- Mailbox Gateway 端口自动按实例索引偏移
- 授权与日志在所有实例间共享
uint16_t master1 = Initialize();
SetNetwork(master1, "\\Device\\NPF_{GUID_A}", "");
uint16_t master2 = Initialize();
SetNetwork(master2, "\\Device\\NPF_{GUID_B}", "");
SetStateSequence(master1, EC_STATE_OPERATIONAL, 10000);
SetStateSequence(master2, EC_STATE_OPERATIONAL, 10000);
Dispose(master1);
Dispose(master2);
网络扫描
默认只列出已绑定 DarraRT_Eth 的物理有线口。虚拟网卡、WiFi、TAP 不进列表。ASIX 等 USB 有线网卡按物理有线口保留。未绑定口不要当作主口候选(条目中的 boundProtocol)。
GetNetworkInfo()
int GetNetworkInfo(BOOL is_redundant, BOOL need_slaves_num);
void* GetNetworksPointer(void);
GetNetworkInfo() 枚举网卡并返回数量。默认列表只含已绑定的物理有线口。need_slaves_num=TRUE 时同时为每张口填从站数;FALSE 时仅列举。GetNetworksPointer() 返回内部网卡列表指针,无需释放。每条记录含 boundProtocol(非 0 = 已绑定 DarraRT_Eth)。
QuickSlaveCount()
int QuickSlaveCount(const char* adapter_name);
int GetRingSlaveCount(void);
int QuickFindRedundantPairBatch(const char** adapter_names, int adapter_count,
int* primary_idx, int* secondary_idx,
int* out_ring_count, int* out_total_count);
QuickSlaveCount()— DENI / 下拉数量:不读 EEPROM,仅 BRD,返回从站个数。GetRingSlaveCount()— 上一次冗余扫描中环上的从站数。QuickFindRedundantPairBatch()— 在多张 NIC 中批量探测冗余对,输出主/副适配器索引 (primary_idx/secondary_idx) 以及环上从站数 (out_ring_count) 与总从站数 (out_total_count)。
- 快速计数:
QuickSlaveCount()(DENI)。 - 完整扫描(选用从站):
ScanSlaveInfo()+GetScannedSlaveInfo()。count > 0时不得当成 0 个从站。
ReadSlaveInfo()
int ReadSlaveInfo(const char* adapter_name);
int GetScannedSlaveCount(void);
BOOL GetScannedSlaveInfo(int index,
uint32_t* vendor_id, uint32_t* product_code,
uint32_t* revision, uint32_t* serial,
char* name, int name_size,
uint16_t* config_addr, uint16_t* alias_addr,
uint16_t* parent, uint8_t* topology, uint8_t* activeports,
uint8_t* entryport, uint8_t* parentport, uint8_t* ptype);
ReadSlaveInfo() 扫描指定网卡上所有从站并缓存详情,返回从站数量。配合 GetScannedSlaveCount() 与 GetScannedSlaveInfo() 按索引读取每个从站的厂商 ID / 产品代码 / 拓扑信息等(index 0-based)。
示例:
int count = ReadSlaveInfo("\\Device\\NPF_{GUID}");
for (int i = 0; i < count; i++) {
uint32_t vid, pid, rev, serial;
char name[128];
uint16_t cfg_addr, alias, parent;
uint8_t topo, ports, entry, pport, ptype;
if (GetScannedSlaveInfo(i, &vid, &pid, &rev, &serial,
name, sizeof(name), &cfg_addr, &alias,
&parent, &topo, &ports, &entry, &pport, &ptype)) {
printf("[%d] %s (VID=0x%08X, PID=0x%08X)\n", i + 1, name, vid, pid);
}
}
AbortScan()
void AbortScan(void);
void ResetScanAbort(void);
void AbortNetwork(void);
void ResetAbortNetwork(void);
中止/重置正在进行的网络扫描操作。AbortScan() 用于关闭 UI 窗口或取消操作时快速中断阻塞的扫描;AbortNetwork() 作用于网络层(绑定/通讯)的取消。
一步初始化 (EcInit)
EcInit()
uint16_t EcInit(const char* json_config);
uint16_t EcInitFromFile(const char* json_file_path);
void EcClose(uint16_t master_index);
EcInit() 通过 JSON 字符串一步完成 Initialize + SetNetwork + LoadConfig + SetStateSequence + Start。EcInitFromFile() 从文件读取 JSON。EcClose() 一步关闭并释放,配对使用。
JSON 配置:
{
"adapter": "\\Device\\NPF_{GUID}",
"redundant_adapter": "",
"cycle_time_us": 1000,
"dc_sync0_ns": 1000000,
"target_state": "OP",
"slaves": []
}
字段说明:
adapter(必选) — 主网卡名称redundant_adapter(可选) — 冗余网卡名称(空字符串=单网卡)cycle_time_us(可选) — PDO 周期,默认 1000dc_sync0_ns(可选) — DC SYNC0 周期,0=不配置 DCtarget_state(可选) — 目标状态("OP"/"SAFEOP"/"PREOP")slaves(可选) — 从站启动参数配置数组
ENI/DENI 配置
ethercat.h 自动包含 utils/xml.h。用户 API 是 deni_load / eni_load / xml_apply_config,以及把已解析结构写回文件的 deni_save / eni_save。保存配置 / 启动快照在主站自愈窗口内采用有限等待(秒级上限,短轮询),超时返回错误,不会长时间挂起。
SaveXML / save_xml_configuration / load_and_apply_xml_configuration 不是 C SDK 用户 API:ethercat.h 伞形头不导出,utils/xml.h 也不再声明。活主站导出仅 GUI 内部使用(internal/xml_live.h)。
deni_load()
int deni_load(const char* path,
DarraMasterXmlConfig* master_out,
DarraSlaveXmlConfig* slaves_out,
int max_slaves);
从 DENI 文件加载配置。根元素 DarraEtherCATConfig。
参数:
path(const char*) — DENI XML 文件路径master_out(DarraMasterXmlConfig*) — 主站配置输出(调用前清零)slaves_out(DarraSlaveXmlConfig*) — 从站配置数组max_slaves(int) — 数组容量
返回值:
int— ≥0 从站数量;-1 参数无效;-2 文件打开失败;-3 XML 解析失败;-4 根元素无效
eni_load()
int eni_load(const char* path,
DarraMasterXmlConfig* master_out,
DarraSlaveXmlConfig* slaves_out,
int max_slaves);
从标准 ENI 文件加载(根元素 EtherCATConfig)。签名与返回值同 deni_load。
xml_apply_config()
int xml_apply_config(const DarraMasterXmlConfig* master_config,
uint16_t master_index);
将 deni_load / eni_load 解析出的主站配置下发到主站。须在 SetNetwork 之前(INIT)调用。
参数:
master_config(const DarraMasterXmlConfig*) — 主站 XML 配置master_index(uint16_t) — 主站索引
返回值:
int— 0 成功;-1 参数无效;-2 主站状态不可读 / 薄壳入口不可用
下发项包括周期、CPU 亲和、冗余、UDP、IOmap 锁、VLAN/帧优先级、PDO 优化、状态超时、看门狗。
示例:
DarraMasterXmlConfig cfg = {0};
DarraSlaveXmlConfig slaves[XML_MAX_SLAVES];
int n = deni_load("C:\\EtherCAT\\MyProject.deni", &cfg, slaves, XML_MAX_SLAVES);
if (n < 0) { fprintf(stderr, "DENI 加载失败 %d\n", n); return 1; }
uint16_t master = Initialize();
if (xml_apply_config(&cfg, master) != 0) {
fprintf(stderr, "配置下发失败\n");
Dispose(master);
return 1;
}
SetNetwork(master, adapter, "");
deni_save() / eni_save()
int deni_save(const char* path,
const DarraMasterXmlConfig* master,
const DarraSlaveXmlConfig* slaves,
int slave_count);
int eni_save(const char* path,
const DarraMasterXmlConfig* master,
const DarraSlaveXmlConfig* slaves,
int slave_count);
把已解析的配置结构写回 DENI / ENI 文件。这是离线结构体序列化,不是把活主站 Dump 成 XML。
返回值:
int— 0 成功,负值失败
版本与授权
GetSerialNumber()
/* 返回只读借用字符串(内部静态/缓存),禁止 FreeMemory */
char* GetSerialNumber(void);
char* GetDeviceName(void);
char* GetUserEmail(void);
获取设备序列号 / 设备名称 / 用户邮箱。返回字符串为只读借用(内部静态 / 缓存单例),不要调用 FreeMemory() 释放(释放会崩溃),也不要改写。授权 API 已于 2026-09-16 从 SDK 移除。
示例:
char* serial = GetSerialNumber();
if (serial) {
printf("设备序列号: %s\n", serial); /* 只读借用,勿 FreeMemory */
}
完整示例
使用 ENI/DENI 文件(推荐)
#include "ethercat.h"
#include <stdio.h>
int main(void)
{
DarraMasterXmlConfig cfg = {0};
DarraSlaveXmlConfig slaves[XML_MAX_SLAVES];
int n = deni_load("C:\\EtherCAT\\MyProject.deni", &cfg, slaves, XML_MAX_SLAVES);
if (n < 0) { return 1; }
uint16_t master = Initialize();
if (master == 0) { return 1; }
if (xml_apply_config(&cfg, master) != 0) {
Dispose(master); return 1;
}
uint16_t ret = SetNetwork(master, "\\Device\\NPF_{GUID}", "");
if (ret == 0 || ret == 0xFFFF) {
Dispose(master); return 1;
}
printf("发现 %d 个从站\n", ret);
if (!SetStateSequence(master, EC_STATE_OPERATIONAL, 10000)) {
Dispose(master); return 1;
}
Start(master);
getchar();
Stop(master);
Dispose(master);
return 0;
}
动态扫描网口
int adapter_count = GetNetworkInfo(FALSE, TRUE);
printf("发现 %d 个网络适配器\n", adapter_count);
int slave_count = ReadSlaveInfo("\\Device\\NPF_{GUID}");
for (int i = 0; i < slave_count; i++) {
/* 通过 GetScannedSlaveInfo() 读取详情 */
}
uint16_t master = Initialize();
SetNetwork(master, "\\Device\\NPF_{GUID}", "");
SetStateSequence(master, EC_STATE_OPERATIONAL, 10000);
Start(master);
一步初始化
uint16_t master = EcInitFromFile("config.json");
if (master == 0) { return 1; }
/* ... 业务逻辑 ... */
EcClose(master);