FoE (File over EtherCAT)
文件传输协议,支持固件更新、配置文件上传下载。
通过 GetSlaveFoEDetails(master, slave) 返回值判断从站是否支持 FoE(非 0 表示支持)。
基本读写
FOERead()
BOOL FOERead(uint16_t master_index, uint16_t slave, const char* filename,
uint32_t password, char** file_data, int* file_size, int timeout);
从从站读取文件。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引filename(const char*) — 文件名password(uint32_t) — 密码(无密码传 0)file_data(char**) — 输出文件数据指针,需调用FreeMemory()释放file_size(int*) — 输出文件大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
FOEWrite()
BOOL FOEWrite(uint16_t master_index, uint16_t slave, const char* filename,
uint32_t password, const char* file_data, int file_size, int timeout);
向从站写入文件。
参数:
master_index(uint16_t) — 主站索引slave(uint16_t) — 从站索引filename(const char*) — 文件名password(uint32_t) — 密码(无密码传 0)file_data(const char*) — 文件数据file_size(int) — 文件大小timeout(int) — 超时时间(微秒)
返回值:
BOOL— 成功返回TRUE
示例:
/* 读取文件 */
char* data = NULL;
int size = 0;
if (dll.FOERead(master, 1, "firmware.bin", 0, &data, &size, 30000000)) {
printf("读取 %d 字节\n", size);
dll.FreeMemory(data);
}
/* 写入文件 */
dll.FOEWrite(master, 1, "firmware.bin", 0, file_data, file_size, 30000000);
扩展版本(带 CRC)
FOEReadEx()
BOOL FOEReadEx(uint16_t master_index, uint16_t slave, const char* filename,
uint32_t password, char** file_data, int* file_size,
int timeout, ec_foe_options_t* options);
带 CRC 校验选项的文件读取。
参数:
- 基本参数同
FOERead() options(ec_foe_options_t*) — CRC 选项
返回值:
BOOL— 成功返回TRUE
FOEWriteEx()
BOOL FOEWriteEx(uint16_t master_index, uint16_t slave, const char* filename,
uint32_t password, const char* file_data, int file_size,
int timeout, ec_foe_options_t* options);
带 CRC 校验选项的文件写入。
返回值:
BOOL— 成功返回TRUE
ec_foe_options_t
typedef struct {
uint8_t enable_crc; /* 启用 CRC 校验 */
uint8_t strict_mode; /* 严格模式 (CRC 不匹配时失败) */
uint8_t auto_append_crc; /* 自动附加 CRC */
uint32_t expected_crc; /* 预期 CRC 值 */
void (*crc_progress_callback)(uint32_t bytes_processed, uint32_t total_bytes, void* userdata);
void* crc_callback_userdata;
uint32_t reserved[8];
} ec_foe_options_t;
示例:
/* CRC 计算进度回调 */
void on_crc_progress(uint32_t bytes_processed, uint32_t total_bytes, void* userdata) {
int percent = total_bytes > 0 ? (int)((uint64_t)bytes_processed * 100 / total_bytes) : 0;
printf("\rCRC 计算: %d%% (%u/%u 字节)", percent, bytes_processed, total_bytes);
fflush(stdout);
}
/* 带 CRC 校验的文件读取 */
ec_foe_options_t opts = {0};
opts.enable_crc = 1;
opts.strict_mode = 1;
opts.crc_progress_callback = on_crc_progress;
char* data = NULL;
int size = 0;
if (dll.FOEReadEx(master, 1, "firmware.bin", 0, &data, &size, 30000000, &opts)) {
printf("\n读取成功: %d 字节\n", size);
dll.FreeMemory(data);
}
传输进度
FOESetProgressHook()
BOOL FOESetProgressHook(uint16_t master_index, foe_progress_callback_t callback);
设置 FoE 传输进度回调。设置后,FOERead / FOEWrite / FOEReadEx / FOEWriteEx 操作会自动触发回调,每传输一个数据包调用一次。
参数:
master_index(uint16_t) — 主站索引callback— 进度回调函数
返回值:
BOOL— 成功返回TRUE
FOEClearProgressHook()
BOOL FOEClearProgressHook(uint16_t master_index);
清除 FoE 传输进度回调。
回调类型:
typedef void (*foe_progress_callback_t)(uint16_t slave, int packet_number, int data_size);
回调参数:
slave(uint16_t) — 从站编号packet_number(int) — 当前数据包序号(从 1 开始递增)data_size(int) — 当前包传输的数据大小(字节)
进度百分比计算
DLL 回调仅提供 packet_number 和 data_size,进度百分比需要应用层自行计算。可通过文件大小和邮箱大小估算总包数:
/* 估算总数据包数 */
int estimate_packet_count(int file_size, int mailbox_size) {
int per_packet = mailbox_size - 6; /* FoE 协议头占 6 字节 */
if (per_packet <= 0) return 0;
return (file_size + per_packet - 1) / per_packet;
}
BUSY 回调与传输取消
与 FOESetProgressHook (每包触发) 不同, BUSY 回调对应 ETG.1000.6 Table 93 的 Done/Entire/BusyText 进度语义, 由从站主动在擦除/烧写期间推送, 携带文本描述, 适合显示 Flash 烧写阶段的友好进度。
同时 DLL 提供了 Cancel 请求, 允许在 FOEWrite / FOERead 运行中请求中断, 下一次循环迭代起生效。
FoEBusyCallback
typedef void (*FoEBusyCallback)(
uint16_t slave, uint16_t done, uint16_t entire,
const char* text, int retry_idx);
回调参数:
slave(uint16_t) — 从站索引done(uint16_t) — 当前进度 (如擦除字节 / 块数)entire(uint16_t) — 总量text(const char*) — 从站返回的 BusyText 字符串 (仅回调期间有效, 需复制)retry_idx(int) — 当前重试计数
FOESetBusyHook()
BOOL FOESetBusyHook(uint16_t master_index, FoEBusyCallback callback);
设置 FoE BUSY 回调。传入 NULL 解除注册。
返回值:
BOOL— 成功返回TRUE
FOERequestCancel()
BOOL FOERequestCancel(uint16_t master_index, uint16_t slave);
请求取消正在进行的 FoE 传输。设置取消标志后, 下一次协议循环迭代 (下一帧) 开始生效, FOERead/FOEWrite 会以失败返回。
返回值:
BOOL— 标志已设置返回TRUE
FOEClearCancel()
BOOL FOEClearCancel(uint16_t master_index, uint16_t slave);
清除取消标志。通常无需手动调用, FOERead/FOEWrite 入口会自动清零。
示例 (固件升级 + 进度显示 + 用户中断):
static volatile int g_user_cancel = 0;
void on_foe_busy(uint16_t slave, uint16_t done, uint16_t entire,
const char* text, int retry_idx)
{
double pct = (entire > 0) ? (100.0 * done / entire) : 0.0;
printf("\r[slave %u] %.1f%% (%u/%u) %s [retry=%d]",
slave, pct, done, entire, text ? text : "", retry_idx);
fflush(stdout);
}
int firmware_update(dll_t* dll, uint16_t master, uint16_t slave,
const char* fname, const char* data, int size)
{
dll->FOESetBusyHook(master, on_foe_busy);
/* 后台线程监听用户取消键 */
/* ... 略 ... */
BOOL ok = dll->FOEWrite(master, slave, fname, 0, data, size, 60000000);
if (!ok && g_user_cancel) {
printf("\n用户取消\n");
} else if (!ok) {
printf("\n传输失败\n");
} else {
printf("\n完成\n");
}
/* 清理 hook */
dll->FOESetBusyHook(master, NULL);
return ok ? 0 : -1;
}
/* 用户按 Ctrl+C 或取消按钮 */
void on_user_cancel(dll_t* dll, uint16_t master, uint16_t slave)
{
g_user_cancel = 1;
dll->FOERequestCancel(master, slave);
}
- BUSY 回调在 DLL worker 线程,
text指针仅回调期间有效, 需立即复制 - 回调内禁止做长耗时操作或调用其他 FoE API
FOERequestCancel是软中断, 取消生效可能有 1-2 个邮箱周期延迟- 多从站并发烧写时,
FOERequestCancel只影响指定从站, 不会打断其他传输
默认超时与密码
C 是过程式 API, 通过进程内全局变量保存 FoE 默认参数 (对齐 C# FoEInstance.DefaultTimeoutMs / DefaultPassword)。FOERead / FOEWrite 在调用方未显式传超时/密码时由 advanced 层从此处读取, 修改默认值不影响已在飞行的请求。
int foe_get_default_timeout_ms(void);
void foe_set_default_timeout_ms(int timeout_ms);
int foe_get_default_timeout_us(void); /* DefaultTimeoutMs * 1000 */
void foe_set_default_timeout_us(int timeout_us);
uint32_t foe_get_default_password(void);
void foe_set_default_password(uint32_t password);
默认值: timeout_ms = 5000, password = 0。
示例:
/* 全局把 FoE 默认超时调到 30 秒 (大固件烧录) */
foe_set_default_timeout_ms(30000);
/* 给一组从站设统一密码 (Beckhoff EL6900 安全模块需要) */
foe_set_default_password(0xDEADBEEF);
/* 之后所有 FOERead/FOEWrite 默认带这两个值 */
dll.FOERead(master, 1, "fw.bin", foe_get_default_password(),
&data, &size, foe_get_default_timeout_ms());
FoE 错误码
FoE 协议定义的错误码,用于诊断传输失败原因。
foe_error_code_t
typedef enum {
FOE_ERR_NOT_DEFINED = 0x8000, /* 未定义错误 */
FOE_ERR_NOT_FOUND = 0x8001, /* 文件未找到 */
FOE_ERR_ACCESS_DENIED = 0x8002, /* 访问被拒绝 */
FOE_ERR_DISK_FULL = 0x8003, /* 磁盘已满 */
FOE_ERR_ILLEGAL = 0x8004, /* 非法操作 */
FOE_ERR_PACKET_NUMBER_WRONG = 0x8005, /* 数据包序号错误 */
FOE_ERR_ALREADY_EXISTS = 0x8006, /* 文件已存在 */
FOE_ERR_NO_USER = 0x8007, /* 无此用户 */
FOE_ERR_BOOTSTRAP_ONLY = 0x8008, /* 仅 Bootstrap 模式可用 */
FOE_ERR_NOT_BOOTSTRAP = 0x8009, /* 不在 Bootstrap 模式 */
FOE_ERR_NO_RIGHTS = 0x800A, /* 权限不足 */
FOE_ERR_PROGRAM_ERROR = 0x800B /* 程序错误 */
} foe_error_code_t;
完整示例
#define DYNAMIC_LOAD
#include "ethercat.h"
#include <stdio.h>
#include <stdlib.h>
/* 全局变量: 估算的总包数 (用于计算百分比) */
static int g_total_packets = 0;
/* 进度回调函数 */
void on_foe_progress(uint16_t slave, int packet_number, int data_size) {
int percent = 0;
if (g_total_packets > 0)
percent = (packet_number * 100) / g_total_packets;
if (percent > 100) percent = 100;
printf("\r从站 %d 传输进度: %d%% (包 %d/%d, 当前包 %d 字节)",
slave, percent, packet_number, g_total_packets, data_size);
fflush(stdout);
}
int estimate_packet_count(int file_size, int mailbox_size) {
int per_packet = mailbox_size - 6;
if (per_packet <= 0) return 0;
return (file_size + per_packet - 1) / per_packet;
}
int main(void)
{
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.dll");
uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{...}", "");
dll.SetStateSequence(master, EC_STATE_BOOT, 5000);
uint16_t slave = 1;
/* 加载固件文件 */
FILE* f = fopen("new_firmware.bin", "rb");
fseek(f, 0, SEEK_END);
int file_size = (int)ftell(f);
fseek(f, 0, SEEK_SET);
char* file_data = (char*)malloc(file_size);
fread(file_data, 1, file_size, f);
fclose(f);
/* 估算包数 (假设邮箱大小 512 字节) */
g_total_packets = estimate_packet_count(file_size, 512);
printf("文件大小: %d 字节, 预计 %d 个数据包\n", file_size, g_total_packets);
/* 启用进度回调 */
dll.FOESetProgressHook(master, on_foe_progress);
/* 执行固件上传 */
BOOL ok = dll.FOEWrite(master, slave, "firmware.bin", 0, file_data, file_size, 30000000);
printf("\n%s\n", ok ? "固件更新成功" : "固件更新失败");
/* 禁用进度回调 */
dll.FOEClearProgressHook(master);
free(file_data);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}