EtherCAT从站ssc V5.12源码研究与记录(二)

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),想要向从站写入一个文件。

逻辑流程:

  1. 检查文件名长度不超过 MAX_FILE_NAME_SIZE(16 字节),超过返回 ECAT_FOE_ERRCODE_DISKFULL
  2. 将邮箱中的文件名拷贝到本地 aFileName 缓冲区
  3. 重置 nFileWriteOffset = 0u32FileSize = 0,准备接收新文件
  4. 不校验文件名和密码 --- 示例代码接受任意文件名和密码
  5. 返回 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),想要读取从站中的某个文件。

逻辑流程:

  1. 检查文件名长度,超长返回 ECAT_FOE_ERRCODE_DISKFULL
  2. 将请求的文件名从邮箱拷贝到 aReadFileName 并补齐终止符
  3. 检查 u32FileSize == 0,如果从未写过文件则返回 ECAT_FOE_ERRCODE_NOTFOUND
  4. 逐字节比对文件名 ,不匹配返回 ECAT_FOE_ERRCODE_NOTFOUND
  5. 计算要发送的块大小:取 maxBlockSizeu32FileSize 的最小值
  6. aFileData 的前 N 字节拷贝到 pData
  7. 返回实际拷贝的字节数

关键点 : return sizeError 的值如果是 0 则表示文件传输完毕,非零表示本次传输的字节数。这是因为示例代码只存一个文件,且文件通常很小(嵌入式端 384 字节)。


4.4 FoE_ReadData() --- 文件读取续传(第 2~N 块)

c 复制代码
UINT16 FoE_ReadData(UINT32 offset, UINT16 maxBlockSize, UINT16 *pData)

触发时机: 主站确认收到前一个数据块后,请求文件的后续数据。

逻辑流程:

  1. 如果偏移量 offset 已超过文件总大小 u32FileSize,返回 0(传输结束)
  2. 计算剩余字节数 u32FileSize - offset
  3. 取剩余字节数和 maxBlockSize 的最小值作为本次传输量
  4. 通过偏移量 offset 定位到 aFileData 的正确位置,拷贝数据到 pData
  5. 返回实际拷贝的字节数

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.cbootloaderappl.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/Databootmode.c 中均为空实现,实际 Flash 写入逻辑由用户在 BL_Data() 中完成
16 位邮箱访问 MBX_16BIT_ACCESS 宏控制 aFileData 的访问粒度,确保与 ESC 的邮箱接口对齐

7. 实际产品开发建议

bootloaderappl.c 是一个示例框架,实际产品中需要修改的地方:

  1. bootmode.c 中的 BL_Data() --- 这是核心:需要实现将收到的数据写入 MCU Flash 的逻辑(擦除、编程、校验)
  2. BL_Start() --- 初始化 Flash 控制器、准备编程环境
  3. BL_Stop() --- 关闭 Flash 控制器、校验固件完整性(CRC)
  4. BL_StartDownload() --- 根据密码做安全校验,拒绝未授权的固件更新
  5. 移除 aFileData 缓冲区 --- 实际产品中数据直接写入 Flash,不需要在 RAM 中缓存整个文件,可以去掉这个数组以节省内存
  6. 固件校验 --- 在 FoE_WriteData 的最后一帧(bDataFollowing == FALSE)完成后,对写入的固件做 CRC/MD5 校验,如果失败则通过诊断消息上报
相关推荐
星恒讯工业路由器1 小时前
SDN:让网络变得更智能、更灵活、更可编程
网络·物联网·信息与通信·sdn
TOWE technology2 小时前
同为科技雷电防护产品,构筑全场景电气安全防线
网络·科技·安全·防雷产品·防雷工程·防雷施工·防雷设计
Sagittarius_A*2 小时前
H3CSE 高性能园区网:VLAN原理与MVRP协议
网络·计算机网络·h3cse
@insist1233 小时前
信息安全工程师-测评核心知识框架与关键流程(下篇)
网络·安全·软考·信息安全工程师·软件水平考试
JunLa3 小时前
L angGraph vs 链式调用
java·网络·数据库
DianSan_ERP3 小时前
抖店订单接口中消费者信息加密解密机制与安全履约全解析
前端·网络·数据库·后端·安全·团队开发·运维开发
難釋懷3 小时前
Redis网络模型-Redis是单线程的吗?为什么使用单线程
网络·数据库·redis
森旺电子3 小时前
嵌入式计算题 栈
网络
顶点多余3 小时前
传输层协议Tcp详解----上
网络·tcp/ip·udp