bootloaderappl.c 源码分析
文件路径 : SSC_V5i12/SSC_V5i12/SlaveFiles/src/bootloaderappl.c
版本 : V5.12(该版本新增)
编译条件 : BOOTLOADER_SAMPLE 宏为 1 时才编译
依赖 : bootmode.c(Boot 状态处理)、FoE 相关模块
1. 文件概述
bootloaderappl.c 是 Beckhoff EtherCAT SSC 提供的 Bootloader 示例应用。它演示了如何利用 EtherCAT 的 Boot 状态 + FoE 协议(File over EtherCAT)实现固件更新功能。
核心思路:从站上电后先进入 Bootloader,通过 FoE 协议从主站接收新固件,存到数据缓冲区,然后跳转到主应用执行。
2. 编译期配置
c
// 只有 FOE_SUPPORTED 为 1 时,以下逻辑才生效
#if FOE_SUPPORTED
#define MAX_FILE_NAME_SIZE 16 // 最大文件名长度
// 最大文件大小因平台而异:
#if FC1100_HW
#define MAX_FILE_SIZE 0x6400000 // FC1100: 100MB(PCI 卡,内存充裕)
#else
#define MAX_FILE_SIZE 0x180 // 嵌入式平台: 仅 384 字节(展示用)
#endif
#endif
FC1100 平台有 100MB 空间可以存真实固件,而嵌入式平台仅 384 字节作为概念验证。
3. 全局变量
| 变量 | 类型 | 作用 |
|---|---|---|
nFileWriteOffset |
UINT32 |
当前文件写入偏移量(字节) |
aFileName[16] |
CHAR[] |
存储已写入的文件名 |
aFileData[] |
UINT8[] 或 UINT16[] |
固件数据缓冲区,大小为 MAX_FILE_SIZE |
u32FileSize |
UINT32 |
已存储文件的实际大小 |
其中 aFileData 的访问宽度和邮箱访问宽度一致(MBX_16BIT_ACCESS 决定),16 位平台使用 UINT16 数组。
4. 核心函数详解
4.1 FoE_Write() --- 文件写入请求处理
c
UINT16 FoE_Write(UINT16 MBXMEM *pName, UINT16 nameSize, UINT32 password)
触发时机: 主站发起 FoE 写请求(WRQ),想要向从站写入一个文件。
逻辑流程:
- 检查文件名长度不超过
MAX_FILE_NAME_SIZE(16 字节),超过返回ECAT_FOE_ERRCODE_DISKFULL - 将邮箱中的文件名拷贝到本地
aFileName缓冲区 - 重置
nFileWriteOffset = 0和u32FileSize = 0,准备接收新文件 - 不校验文件名和密码 --- 示例代码接受任意文件名和密码
- 返回 0,表示接受本次写入请求
关键点 : 这个函数只是准备写入 ,不实际接收数据。真正的文件内容通过后续的 FoE_WriteData() 接收。
4.2 FoE_WriteData() --- 文件数据块写入
c
UINT16 FoE_WriteData(UINT16 MBXMEM *pData, UINT16 Size, BOOL bDataFollowing)
触发时机: 主站发来 FoE Data 帧,包含文件的实际内容数据。
逻辑流程:
┌─ bBootMode == TRUE? ───────────────────┐
│ 是 → 调用 BL_Data(pData, Size) │
│ 将数据交给实际的 Bootloader │
│ 写入 Flash/ROM │
│ │
│ 否 → 检查 nFileWriteOffset + Size │
│ 是否超过 MAX_FILE_SIZE │
│ 超过 → 返回 DISKFULL 错误 │
│ 未超过 → MEMCPY 拷贝到 │
│ aFileData 缓冲区 │
└────────────────────────────────────────┘
↓
bDataFollowing == TRUE?
是 → nFileWriteOffset += Size(累加偏移,等待后续数据)
否 → u32FileSize = 最终大小,nFileWriteOffset = 0(文件接收完成)
关键设计:
bBootMode标志(在ecatslv.c中定义)区分了 Boot 模式(真正的固件更新,数据交给BL_Data()写入 Flash)和测试模式(仅存到aFileData缓冲区)bDataFollowing是 FoE 协议的分段标志:TRUE 表示还有后续数据帧,FALSE 表示这是最后一帧- 每次数据块到达时累加偏移量,直到最后一帧把累计大小赋值给
u32FileSize
4.3 FoE_Read() --- 文件读取请求处理(第一块)
c
UINT16 FoE_Read(UINT16 MBXMEM *pName, UINT16 nameSize, UINT32 password, UINT16 maxBlockSize, UINT16 *pData)
触发时机: 主站发起 FoE 读请求(RRQ),想要读取从站中的某个文件。
逻辑流程:
- 检查文件名长度,超长返回
ECAT_FOE_ERRCODE_DISKFULL - 将请求的文件名从邮箱拷贝到
aReadFileName并补齐终止符 - 检查
u32FileSize == 0,如果从未写过文件则返回ECAT_FOE_ERRCODE_NOTFOUND - 逐字节比对文件名 ,不匹配返回
ECAT_FOE_ERRCODE_NOTFOUND - 计算要发送的块大小:取
maxBlockSize和u32FileSize的最小值 - 将
aFileData的前 N 字节拷贝到pData - 返回实际拷贝的字节数
关键点 : return sizeError 的值如果是 0 则表示文件传输完毕,非零表示本次传输的字节数。这是因为示例代码只存一个文件,且文件通常很小(嵌入式端 384 字节)。
4.4 FoE_ReadData() --- 文件读取续传(第 2~N 块)
c
UINT16 FoE_ReadData(UINT32 offset, UINT16 maxBlockSize, UINT16 *pData)
触发时机: 主站确认收到前一个数据块后,请求文件的后续数据。
逻辑流程:
- 如果偏移量
offset已超过文件总大小u32FileSize,返回 0(传输结束) - 计算剩余字节数
u32FileSize - offset - 取剩余字节数和
maxBlockSize的最小值作为本次传输量 - 通过偏移量
offset定位到aFileData的正确位置,拷贝数据到pData - 返回实际拷贝的字节数
与 FoE_Read 的区别 : FoE_Read 是 FoE 协议的第一次读取 (需要验证文件名和密码),FoE_ReadData 是后续续传(已知文件,直接按偏移量取数据)。
4.5 APPL_StartMailboxHandler() --- 状态迁移确认
c
UINT16 APPL_StartMailboxHandler(void)
触发时机: ESM 状态机从 INIT 迁移到 BOOT 或 PreOP 时。
逻辑:
if (bBootMode)
return ALSTATUSCODE_NOERROR; // Boot 模式:允许迁移
else
return ALSTATUSCODE_NOVALIDFIRMWARE; // 非 Boot 模式:拒绝进入 PreOP
// 因为没有有效固件,需要先通过 Boot 加载
设计意图 : 这个逻辑保证了从站上电后只能进入 Boot 状态进行固件更新。如果 bBootMode 为 FALSE(表示主固件不存在或已损坏),则返回错误码 0x0024(无有效固件),阻止进入 PreOP,从而告诉主站"我需要先更新固件"。
4.6 APPL_StopMailboxHandler() --- 退出邮箱处理
c
UINT16 APPL_StopMailboxHandler(void)
从 BOOT 退出到 INIT 时调用,直接返回 ALSTATUSCODE_NOERROR,不做任何拒绝。
4.7 APPL_AckErrorInd() --- 错误确认回调
c
void APPL_AckErrorInd(UINT16 stateTrans)
主站确认错误状态后回调。示例代码为空 --- 实际产品中可以在这里做错误恢复或日志记录。
4.8 APPL_Application() --- 应用层主循环回调
c
void APPL_Application(void)
SSC 每次主循环后会调用此函数。示例为空 --- 实际产品可以在这里执行 Bootloader 自己的后台任务。
4.9 main() --- 主函数
c
void main(void)
流程:
HW_Init() // 1. 硬件初始化(初始化 ESC、PDI 接口等)
↓
MainInit() // 2. 协议栈初始化(SSC 核心初始化)
↓
pAPPL_FoeRead = FoE_Read; // 3. 注册 FoE 回调函数
pAPPL_FoeWriteData = FoE_WriteData;
pAPPL_FoeReadData = FoE_ReadData;
pAPPL_FoeWrite = FoE_Write;
↓
bRunApplication = TRUE;
do {
MainLoop(); // 4. 主循环 --- 处理状态机、邮箱、FoE 传输
} while (bRunApplication == TRUE);
↓
HW_Release(); // 5. 释放硬件资源
5. 完整固件更新时序
结合 bootmode.c 和 bootloaderappl.c,一次完整的固件更新流程如下:
主站 (TwinCAT) 从站 (SSC)
│ │
│ ── AL Control = INIT ──→ │ 上电后自动进入 INIT
│ │
│ ── AL Control = BOOT ──→ │ APPL_StartMailboxHandler()
│ │ bBootMode==TRUE → 允许进入 BOOT
│ │
│ ── FoE WRQ (文件名) ──→ │ FoE_Write() → 接受写入
│ ←── ACK ── │
│ │
│ ── FoE Data (第 1 块) ──→ │ FoE_WriteData()
│ ←── ACK ── │ bBootMode==TRUE → BL_Data()
│ │ 写入 Flash
│ ── FoE Data (第 2 块) ──→ │ FoE_WriteData()
│ ←── ACK ── │ ...
│ ... │ ...
│ ── FoE Data (最后块) ──→ │ FoE_WriteData()
│ bDataFollowing = FALSE │ bDataFollowing==FALSE
│ ←── ACK ── │ 固件写入完成
│ │
│ ── AL Control = INIT ──→ │ APPL_StopMailboxHandler()
│ │ 退出 BOOT
│ │
│ ── 设备复位/重启 ──→ │ 重新上电,加载新固件
6. 关键设计点总结
| 设计点 | 说明 |
|---|---|
| 双模式设计 | bBootMode 标志区分真实固件更新(FLASH 写入)与内存加载(测试模式) |
| FoE 分段传输 | 通过 bDataFollowing 标志和 nFileWriteOffset 偏移量实现大文件分段接收 |
| 状态机保护 | APPL_StartMailboxHandler 在非 Boot 模式下返回 NOVALIDFIRMWARE,确保无有效固件时只进入 Boot 状态 |
| 平台适配 | FC1100 平台支持 100MB 固件文件,嵌入式平台限制为 384 字节(概念演示) |
| 空函数占位 | BL_Start/Load/Data 在 bootmode.c 中均为空实现,实际 Flash 写入逻辑由用户在 BL_Data() 中完成 |
| 16 位邮箱访问 | MBX_16BIT_ACCESS 宏控制 aFileData 的访问粒度,确保与 ESC 的邮箱接口对齐 |
7. 实际产品开发建议
bootloaderappl.c 是一个示例框架,实际产品中需要修改的地方:
bootmode.c中的BL_Data()--- 这是核心:需要实现将收到的数据写入 MCU Flash 的逻辑(擦除、编程、校验)BL_Start()--- 初始化 Flash 控制器、准备编程环境BL_Stop()--- 关闭 Flash 控制器、校验固件完整性(CRC)BL_StartDownload()--- 根据密码做安全校验,拒绝未授权的固件更新- 移除
aFileData缓冲区 --- 实际产品中数据直接写入 Flash,不需要在 RAM 中缓存整个文件,可以去掉这个数组以节省内存 - 固件校验 --- 在
FoE_WriteData的最后一帧(bDataFollowing == FALSE)完成后,对写入的固件做 CRC/MD5 校验,如果失败则通过诊断消息上报