HPM CIA402工程融合FOE
- [一、 XML 描述文件修改 (ECAT_CIA402.xml)](#一、 XML 描述文件修改 (ECAT_CIA402.xml))
-
- [1. 使能 邮箱协议](#1. 使能 邮箱协议)
- [2. 添加 Bootstrap 邮箱配置 (<BootStrap>)](#2. 添加 Bootstrap 邮箱配置 (
))
- [二、 协议栈宏定义修改 (ecat_def.h)](#二、 协议栈宏定义修改 (ecat_def.h))
- [三、 底层应用回调函数集成 (cia402appl.c / main.c)](#三、 底层应用回调函数集成 (cia402appl.c / main.c))
-
- [1. 声明与注册 FoE 回调函数](#1. 声明与注册 FoE 回调函数)
- [2. 在从站状态机停止时触发复位](#2. 在从站状态机停止时触发复位)
- [3. 主循环轮询处理系统复位](#3. 主循环轮询处理系统复位)
- [四、 Flash 存储与分区配置 (ecat_foe_support.h)](#四、 Flash 存储与分区配置 (ecat_foe_support.h))
- [五、 将FOE相关源代码拷贝并整合源代码](#五、 将FOE相关源代码拷贝并整合源代码)
-
- [5.1 拷贝bootmode、ecatfoe、foeappl、ecat_foe_support、applInterface](#5.1 拷贝bootmode、ecatfoe、foeappl、ecat_foe_support、applInterface)
- [5.2 修改ecatslv.c](#5.2 修改ecatslv.c)
- [5.3 修改mailbox.c](#5.3 修改mailbox.c)
- 六、工程配置
-
- [6.1 Ecat/Src增加bootmode.c、ecatfoe.c、foeappl.c](#6.1 Ecat/Src增加bootmode.c、ecatfoe.c、foeappl.c)
- [6.2 Ecat/port增加ecat_foe_support.c、hpm_ecat_foe.c](#6.2 Ecat/port增加ecat_foe_support.c、hpm_ecat_foe.c)
- [七、 TwinCAT 3 验证与固件烧录步骤](#七、 TwinCAT 3 验证与固件烧录步骤)
在 ECAT_CIA402 伺服工程中添加 FoE (File over EtherCAT) 功能,主要涉及 XML 描述文件配置、SSC 协议栈宏开关、Flash 擦写与回调绑定 以及 主循环复位处理。
一、 XML 描述文件修改 (ECAT_CIA402.xml)
在 ECAT_CIA402.xml 中,需要向 节点声明 FoE 协议,并在 节点补充 Bootstrap(引导)模式的邮箱配置。
1. 使能 邮箱协议
找到 节点,在 <CoE .../> 下方添加 :
xml
<Mailbox DataLinkLayer="true">
<CoE SdoInfo="true" PdoAssign="true" PdoConfig="false" CompleteAccess="true" SegmentedSdo="true" />
<!-- 新增:声明支持 FoE 协议 -->
<FoE />
</Mailbox>
2. 添加 Bootstrap 邮箱配置 ()
在 节点中添加 标签(配置 Bootstrap 模式下 SM0/SM1 邮箱起始地址与长度):
xml
<Eeprom>
<ByteSize>2048</ByteSize>
<ConfigData>800C8166000000001234</ConfigData>
<!-- 新增:Bootstrap 模式邮箱定义 (SM0: 0x1000/128B, SM1: 0x1080/128B) -->
<BootStrap>0010800080108000</BootStrap>
</Eeprom>
二、 协议栈宏定义修改 (ecat_def.h)
在 SSC 工具生成的 ecat_def.h 中,开启 FoE 与 Bootstrap 支持:
c
/* 开启 FoE 协议支持 */
#ifndef FOE_SUPPORTED
#define FOE_SUPPORTED 1
#endif
/* 开启 Bootstrap 引导状态机支持 (固件在线升级必需) */
#ifndef BOOTSTRAPMODE_SUPPORTED
#define BOOTSTRAPMODE_SUPPORTED 1
#endif
三、 底层应用回调函数集成 (cia402appl.c / main.c)
1. 声明与注册 FoE 回调函数
在主程序初始化流程(main.c)中,初始化 Flash 并将底层读写接口挂载到 SSC 回调指针:
c
#include "hpm_ecat_foe.h"
#include "ecat_foe_support.h"
extern bool foe_reset_request;
void ecat_init_foe_service(void);
void ecat_init_foe_service(void)
{
hpm_stat_t stat;
/* 1. 初始化 Flash 控制器与 FoE 底层存储接口 */
stat = foe_support_init();
if (stat != status_success) {
printf("FOE support init failed!\n");
return;
}
/* 2. 挂载 SSC 协议栈 FoE 回调函数指针 */
pAPPL_FoeRead = foe_read;
pAPPL_FoeReadData = foe_read_data;
pAPPL_FoeWrite = foe_write;
pAPPL_FoeWriteData = foe_write_data;
}
2. 在从站状态机停止时触发复位
在 cia402appl.c(或 ecatapp.c)的 APPL_StopMailboxHandler() 中,当校验通过并退回 INIT 状态时触发软件复位:
c
extern bool foe_data_verification_pass;
extern bool foe_reset_request;
UINT16 APPL_StopMailboxHandler(void)
{
/* 当 FoE 传输完成且数据校验通过,主站请求退回 INIT 时触发系统软复位 */
if (foe_data_verification_pass) {
foe_reset_request = true;
}
return ALSTATUSCODE_NOERROR;
}
3. 主循环轮询处理系统复位
在 main() 函数的主循环中轮询 foe_reset_request:
c
int main(void)
{
/* 板级与 EtherCAT 硬件初始化 */
board_init();
board_init_ethercat(HPM_ESC0);
ecat_hardware_init(HPM_ESC0);
MainInit(); /* SSC 协议栈初始化 */
ecat_init_foe_service(); /* 初始化并挂载 FoE */
bRunApplication = TRUE;
while (bRunApplication == TRUE) {
/* 检测到固件更新完成信号后执行系统复位 */
if (foe_reset_request) {
printf("FoE update complete, system resetting...\n");
foe_support_soc_reset();
}
MainLoop();
}
return 0;
}
四、 Flash 存储与分区配置 (ecat_foe_support.h)
在 ecat_foe_support.h 中,明确区分 App 升级暂存区与 Flash 存储地址:
c
/*
* Copyright (c) 2024 HPMicro
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
/* this file for eeprom emulation component */
#ifndef FOE_SUPPORT_H
#define FOE_SUPPORT_H
#include "board.h"
#include "ecat_def.h"
#include "ecat_config.h"
/* 依据 4.0ms 周期重新计算 Tick 阈值 */
#define OTA_PACKET_TIMEOUT_TICKS (2500) /* 2500 * 4ms = 10 秒空闲超时 */
#define OTA_ACK_DELAY_TICKS (25) /* 25 * 4ms = 100 毫秒延时等待 ACK 发出 */
//#define FLASH_BOOTLOADER_ADDR (0x00000000UL) /* 64 KB 引导区 */
//#define FLASH_EEPROM_ADDR (0x000E0000UL) /* 64 KB EEPROM 模拟区 */
/* Flash 基地址与扇区定义 */
#define FLASH_ADDR_BASE BOARD_FLASH_BASE_ADDRESS
#define FLASH_SECTOR_SIZE_BYTE (0x1000UL) /* 4KB */
#define FLASH_PAGE_SIZE_BYTE (256UL)
/* Flash 物理分区相对偏移 (匹配 1MB Flash 布局) */
#define FLASH_APP_A_ADDR (0x00000000UL) /* 覆盖物理首地址的运行区 (480 KB) */
#define FLASH_FILE_ADDR (0x00078000UL) /* Sector 120: Bank B 升级暂存区 (416 KB) */
#define FLASH_APP_MAX_SIZE (0x00068000UL) /* 416 KB */
#define FLASH_FILE_SIZE FLASH_APP_MAX_SIZE
#define FLASH_EEPROM_ADDR (0x000E0000UL) /* 64 KB EEPROM 模拟区 */
#define FLASH_OTA_META_ADDR (0x000F2000UL) /* Sector 242 (4KB) OTA 元数据区 */
#define FOE_FILE_MAX_SIZE FLASH_FILE_SIZE
#define FOE_FILE_ADDR (FLASH_ADDR_BASE + FLASH_FILE_ADDR)
#define FOE_FILE_TOTAL_SIZE (FLASH_ADDR_BASE + FLASH_FILE_ADDR + FLASH_FILE_SIZE + 4) /* last 4 byte store file size */
/* 认证信息 */
#define FOE_FILE_NAME "app"
#define FOE_PASSWORD (0x87654321)
/* 官方 HPM APP Header 标准定义 */
#define HPM_APP_FILE_FLAG_MAGIC (0xbeaf5aa5UL)
#define HPM_APP_FILE_TOUCH_ID (0xa5a55a5aUL)
#define OTA_MAGIC_CODE (0x48504D43UL) /* 'H''P''M''C' */
typedef enum {
OTA_STATUS_IDLE = 0x00,
OTA_STATUS_UPGRADED = 0xA5,
OTA_STATUS_VERIFY_OK = 0x5A,
OTA_STATUS_ROLLBACK = 0xFF
} OtaStatus_e;
typedef struct {
uint32_t magic; /* 0x48504D43 */
uint32_t fw_size; /* 固件字节长度 */
uint32_t fw_crc32; /* 全镜像 CRC32 */
uint8_t upgrade_flag; /* OtaStatus_e */
uint8_t boot_trials;
uint8_t reserved[2];
} OtaMetadata_t;
#ifdef __cplusplus
extern "C" {
#endif
int8_t foe_support_init(void);
void foe_support_soc_reset(void);
uint32_t foe_support_get_file_addr(uint16_t MBXMEM *name, uint16_t name_size);
uint32_t foe_support_get_file_size(uint16_t MBXMEM *name, uint16_t name_size);
int8_t foe_support_prepare_data_space(uint16_t MBXMEM *name, uint16_t name_size);
int8_t foe_support_store_data(uint32_t offset, uint16_t MBXMEM *data, uint16_t size, uint8_t data_following);
void foe_timeout_monitor_100ms(void);
/* 后台 4.0ms 任务轮询接口 */
void ota_background_fsm_poll(void);
#ifdef __cplusplus
}
#endif
#endif /* FOE_SUPPORT_H */
在 ecat_foe_support.c 中
c
/*
* Copyright (c) 2024-2026 HPMicro
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "board.h"
#include "hpm_romapi.h"
#include "hpm_ppor_drv.h"
#include "ecat_foe_support.h"
#include "hpm_l1c_drv.h"
#include <math.h>
#include <string.h>
#include "../App/global.h"
#include "hpm_ewdg_drv.h"
/* 状态机状态定义 */
typedef enum {
OTA_FSM_IDLE = 0,
OTA_FSM_RECEIVING, /* 正在分片接收并写入 Bank B */
OTA_FSM_WAIT_LAST_ACK, /* 接收完成,延时等待 ESC 硬件发出最后一包 FoE ACK */
OTA_FSM_VERIFY_IMAGE, /* Flash 物理全量回读与完整性强校验 */
OTA_FSM_SAFE_PREPARE, /* 电机强行去使能、封锁硬件 PWM */
OTA_FSM_TRIGGER_RAM_COPY,/* 驻留 SRAM 执行覆盖搬运与软复位 */
OTA_FSM_ERROR_ABORT /* 异常回退终止 */
} OtaFsmState_t;
/* 异常错误原因枚举 */
typedef enum {
OTA_ERR_NONE = 0,
OTA_ERR_TIMEOUT, /* 通信超时中断(断线) */
OTA_ERR_OVERSIZE, /* 固件超出分区容量 */
OTA_ERR_FLASH_WRITE, /* 底层 Flash 擦写硬件错误 */
OTA_ERR_CRC_MISMATCH, /* Flash 物理回读校验未通过 */
OTA_ERR_HEADER_INVALID /* 固件头非法(非先楫有效镜像) */
} OtaErrorCode_t;
/* 先楫官方 ROM 标志寄存器物理地址 */
#define SOC_ROM_FLAG_REG (*(volatile uint32_t *)(0xF40D4000UL + 8U))
static volatile OtaFsmState_t s_ota_fsm = OTA_FSM_IDLE;
static volatile OtaErrorCode_t s_ota_err = OTA_ERR_NONE;
static volatile uint32_t s_ota_target_fw_size = 0;
static volatile uint32_t s_ota_target_fw_crc = 0;
static volatile uint16_t s_ota_packet_idle_cnt = 0;
static volatile uint16_t s_ota_ack_delay_cnt = 0;
static volatile uint32_t s_ota_real_dst_addr = FLASH_APP_A_ADDR;
/* 保存旧中断状态,杜绝嵌套中断踩踏 MEPC */
#define ENTER_CRITICAL() uint32_t irq_state = disable_global_irq(CSR_MSTATUS_MIE_MASK)
#define EXIT_CRITICAL() do { if (irq_state & CSR_MSTATUS_MIE_MASK) enable_global_irq(CSR_MSTATUS_MIE_MASK); } while(0)
bool foe_reset_request = false;
extern bool foe_data_verification_pass;
static xpi_nor_config_t s_xpi_nor_config;
static uint32_t s_current_erased_offset = 0;
static uint32_t s_accumulated_crc = 0xFFFFFFFFUL;
static uint16_t s_foe_comm_timeout_cnt = 0;
static bool s_foe_transfer_active = false;
/* 4KB 扇区缓存移至 BSS 段(SRAM),消除栈溢出 */
static uint32_t s_ram_sector_buf[FLASH_SECTOR_SIZE_BYTE / 4];
/* 声明定义在 hpm_ecat_foe.c 中的全局函数指针 */
extern uint32_t (*foe_get_file_addr)(uint16_t MBXMEM * name, uint16_t name_size);
extern uint32_t (*foe_get_file_size)(uint16_t MBXMEM * name, uint16_t name_size);
extern int8_t (*foe_prepare_data_space)(uint16_t MBXMEM * name, uint16_t name_size);
extern int8_t (*foe_store_data)(uint32_t offset, uint16_t MBXMEM * data, uint16_t size, uint8_t data_following);
/* 快速查表法 CRC32 发生器 */
static uint32_t Calc_CRC32_Update(uint32_t crc, const uint8_t *data, size_t len)
{
static const uint32_t table[16] = {
0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC,
0x76DC4190, 0x6B6B51F4, 0x4DB26158, 0x5005713C,
0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C,
0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C
};
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
crc = (crc >> 4) ^ table[crc & 0x0F];
crc = (crc >> 4) ^ table[crc & 0x0F];
}
return crc;
}
uint32_t foe_support_get_file_addr(uint16_t MBXMEM * name, uint16_t name_size)
{
(void) name;
(void) name_size;
return (FLASH_ADDR_BASE + FLASH_FILE_ADDR);
}
/* 统一加上 MBXMEM 修饰符 */
uint32_t foe_support_get_file_size(uint16_t MBXMEM *name, uint16_t name_size)
{
(void)name;
(void)name_size;
/* 读取前强制使元数据区的 Cache 失效,确保从物理 Flash 读出最新值 */
uint32_t meta_addr = FLASH_ADDR_BASE + FLASH_OTA_META_ADDR;
l1c_dc_invalidate(HPM_L1C_CACHELINE_ALIGN_DOWN(meta_addr),
HPM_L1C_CACHELINE_ALIGN_UP(sizeof(OtaMetadata_t)));
OtaMetadata_t *meta = (OtaMetadata_t *)meta_addr;
if (meta->magic == OTA_MAGIC_CODE && meta->fw_size <= FLASH_FILE_SIZE) {
return meta->fw_size;
}
return 0;
}
/**
* @brief FoE WRQ 准备:安全检查并【仅擦除首个 4KB 扇区】
*/
int8_t foe_support_prepare_data_space(uint16_t MBXMEM * name, uint16_t name_size)
{
hpm_stat_t status;
(void) name;
(void) name_size;
/* 1. 安全联锁检查:电机运行中坚决禁止升级 */
if (gSysCtrl.enable || fabsf(gEncoder.obsv.fSpdFb) > 10.0f) {
return -1;
}
/* 2. 重置传输与校验上下文 */
s_current_erased_offset = 0;
s_accumulated_crc = 0xFFFFFFFFUL;
s_foe_comm_timeout_cnt = 0;
s_foe_transfer_active = true;
foe_data_verification_pass = false;
/* 3. 核心修改:仅擦除首个 4KB 扇区 (耗时约 20ms,杜绝超时与越界破坏 EEPROM) */
ENTER_CRITICAL();
status = rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config, FLASH_FILE_ADDR, FLASH_SECTOR_SIZE_BYTE);
EXIT_CRITICAL();
if (status != status_success) {
return -1;
}
/* 标记当前已擦除完首个扇区边界 (4096 字节) */
s_current_erased_offset = FLASH_SECTOR_SIZE_BYTE;
return 0;
}
/**
* @brief 数据流接收写入:边写边按需擦除 + 在线累加 CRC32
*/
int8_t foe_support_store_data(uint32_t offset, uint16_t MBXMEM *data, uint16_t size, uint8_t data_following)
{
/* 1. 动态喂狗:只要主站发来有效数据包,重置空闲计数 */
s_ota_packet_idle_cnt = 0;
s_ota_fsm = OTA_FSM_RECEIVING;
/* 容量越界保护 */
if (offset + size > FLASH_FILE_SIZE) {
s_ota_err = OTA_ERR_OVERSIZE;
s_ota_fsm = OTA_FSM_ERROR_ABORT;
return -1;
}
/* 2. 跨扇区即时擦除 */
while ((offset + size) > s_current_erased_offset) {
ENTER_CRITICAL();
hpm_stat_t erase_stat = rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config,
FLASH_FILE_ADDR + s_current_erased_offset,
FLASH_SECTOR_SIZE_BYTE);
EXIT_CRITICAL();
if (erase_stat != status_success) {
s_ota_err = OTA_ERR_FLASH_WRITE;
s_ota_fsm = OTA_FSM_ERROR_ABORT;
return -1;
}
s_current_erased_offset += FLASH_SECTOR_SIZE_BYTE;
}
/* 3. 编程写入 Bank B Flash */
if (size > 0) {
uint32_t written = 0;
while (written < size) {
uint32_t curr_flash_addr = FLASH_FILE_ADDR + offset + written;
// 计算当前地址距离当前 256 字节页尾的剩余空间
uint32_t page_offset = curr_flash_addr % FLASH_PAGE_SIZE_BYTE;
uint32_t page_remain = FLASH_PAGE_SIZE_BYTE - page_offset;
uint32_t write_len = (size - written > page_remain) ? page_remain : (size - written);
ENTER_CRITICAL();
hpm_stat_t prog_stat = rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config,
(uint32_t *)((uint8_t *)data + written),
curr_flash_addr, write_len);
EXIT_CRITICAL();
if (prog_stat != status_success) {
s_ota_err = OTA_ERR_FLASH_WRITE;
s_ota_fsm = OTA_FSM_ERROR_ABORT;
return -1;
}
written += write_len;
}
/* 增量累计网络接收端的 CRC32 */
s_accumulated_crc = Calc_CRC32_Update(s_accumulated_crc, (const uint8_t *)data, size);
}
/* 4. 最后一包处理:暂不搬运,保存参数并切入确认等待 */
if (!data_following) {
s_foe_transfer_active = false;
uint32_t file_size = offset + size;
uint32_t final_crc = s_accumulated_crc ^ 0xFFFFFFFFUL;
/* 写入 OTA 元数据 */
OtaMetadata_t meta;
meta.magic = OTA_MAGIC_CODE;
meta.fw_size = file_size;
meta.fw_crc32 = final_crc;
meta.upgrade_flag = OTA_STATUS_UPGRADED;
meta.boot_trials = 0;
ENTER_CRITICAL();
rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config, FLASH_OTA_META_ADDR, FLASH_SECTOR_SIZE_BYTE);
rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config, (uint32_t *)&meta, FLASH_OTA_META_ADDR, sizeof(OtaMetadata_t));
EXIT_CRITICAL();
l1c_dc_invalidate(HPM_L1C_CACHELINE_ALIGN_DOWN(FLASH_ADDR_BASE + FLASH_OTA_META_ADDR),
HPM_L1C_CACHELINE_ALIGN_UP(FLASH_SECTOR_SIZE_BYTE));
foe_data_verification_pass = true;
/* 记录参数并交由后台 4ms 任务去延时触发搬运 */
s_ota_target_fw_size = file_size;
s_ota_target_fw_crc = final_crc;
s_ota_ack_delay_cnt = 0;
s_ota_fsm = OTA_FSM_WAIT_LAST_ACK;
/* 必须返回 0!让 SSC 顺利调用 FOE_Ack() 发送最后一包确认 */
return 0;
}
return 0;
}
void foe_support_soc_reset(void)
{
ENTER_CRITICAL();
SOC_ROM_FLAG_REG = 0;
ppor_sw_reset(HPM_PPOR, 1000);
while (1) {
}
}
int8_t foe_support_init(void)
{
hpm_stat_t status;
xpi_nor_config_option_t option;
option.header.U = BOARD_APP_XPI_NOR_CFG_OPT_HDR;
option.option0.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT0;
option.option1.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT1;
ENTER_CRITICAL();
status = rom_xpi_nor_auto_config(BOARD_APP_XPI_NOR_XPI_BASE, &s_xpi_nor_config, &option);
EXIT_CRITICAL();
if (status != status_success) {
return -1;
}
foe_get_file_addr = foe_support_get_file_addr;
foe_get_file_size = foe_support_get_file_size;
foe_prepare_data_space = foe_support_prepare_data_space;
foe_store_data = foe_support_store_data;
foe_reset_request = false;
foe_data_verification_pass = false;
return 0;
}
///* 强制驻留 SRAM 运行的搬运核心函数 */
//ATTR_RAMFUNC static void ota_execute_copy_in_sram(uint32_t src_offset, uint32_t dst_offset, uint32_t size)
//{
// // 1. 彻底关闭全局中断,防止任何外设中断触发 XIP 取指崩溃
// disable_global_irq(CSR_MSTATUS_MIE_MASK);
// /* 2. 依照官方规范彻底关闭 Cache 与刷新总线,杜绝 AHB 冲突挂死 */
// l1c_dc_invalidate_all();
// //l1c_dc_disable();
// //l1c_ic_disable();
// fencei();
// uint32_t offset = 0;
// // 2. 逐个扇区由 Bank B 搬运覆盖到 Bank A
// while (offset < size) {
// uint32_t chunk = (size - offset > FLASH_SECTOR_SIZE_BYTE) ? FLASH_SECTOR_SIZE_BYTE : (size - offset);
// //// a. 从 Bank B 读出数据到 RAM 缓存
// //rom_xpi_nor_read(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
// // &s_xpi_nor_config, s_ram_sector_buf, src_offset + offset, chunk);
// // a. 从 Bank B 物理内存映射直接读出至 SRAM 缓存(纯寄存器循环,绝不跳外部函数)
// volatile uint32_t *p_src = (volatile uint32_t *)(FLASH_ADDR_BASE + src_offset + offset);
// for (uint32_t i = 0; i < (chunk / 4); i++) {
// s_ram_sector_buf[i] = p_src[i];
// }
// // b. 擦除 Bank A 对应扇区
// rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
// &s_xpi_nor_config, dst_offset + offset, FLASH_SECTOR_SIZE_BYTE);
// //// c. 将 RAM 缓存编程写入 Bank A
// //rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
// // &s_xpi_nor_config, s_ram_sector_buf, dst_offset + offset, chunk);
// // c. 严格拆分为 256 字节页写入,杜绝 XPI FIFO 溢出
// for (uint32_t page_off = 0; page_off < chunk; page_off += FLASH_PAGE_SIZE_BYTE) {
// uint32_t write_size = (chunk - page_off > FLASH_PAGE_SIZE_BYTE) ? FLASH_PAGE_SIZE_BYTE : (chunk - page_off);
// rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
// &s_xpi_nor_config,
// (uint32_t *)((uint8_t *)s_ram_sector_buf + page_off),
// dst_offset + offset + page_off, write_size);
// }
// offset += chunk;
// }
// /* 4. 搬运完成,清空先楫 ROM 复位标志寄存器 */
// //SOC_ROM_FLAG_REG = 0;
// // 4. 触发芯片软件复位,10s 后以新固件重启
// ppor_sw_reset(HPM_PPOR, 10000);
// while (1) {
// }
//}
ATTR_RAMFUNC static void ota_execute_copy_in_sram(uint32_t src_offset, uint32_t dst_offset, uint32_t size)
{
// 1. 彻底关闭全局中断,防止任何外设中断触发 XIP 取指崩溃
disable_global_irq(CSR_MSTATUS_MIE_MASK);
// 2. 清理 D-Cache 脏数据并保持开启
l1c_dc_invalidate_all();
fencei();
uint32_t src_addr = src_offset;
uint32_t dst_addr = dst_offset;
uint32_t bytes_left = size;
uint32_t last_erased_sector = 0xFFFFFFFFUL;
// 3. 🌟 页流式搬运:自适应任何起始偏移(如 0x00000400),并在跨扇区时自动即时擦除
while (bytes_left > 0) {
// a. 计算当前目标地址所属的 4KB 扇区物理基地址
uint32_t current_sector = dst_addr & ~(FLASH_SECTOR_SIZE_BYTE - 1);
if (current_sector != last_erased_sector) {
rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config, current_sector, FLASH_SECTOR_SIZE_BYTE);
last_erased_sector = current_sector;
}
// b. 单次读取并写入 1 个 256 字节页
uint32_t chunk = (bytes_left > FLASH_PAGE_SIZE_BYTE) ? FLASH_PAGE_SIZE_BYTE : bytes_left;
volatile uint32_t *p_src = (volatile uint32_t *)(FLASH_ADDR_BASE + src_addr);
for (uint32_t i = 0; i < (chunk + 3) / 4; i++) {
s_ram_sector_buf[i] = p_src[i];
}
// c. 写入目标页
rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config,
s_ram_sector_buf,
dst_addr, chunk);
src_addr += chunk;
dst_addr += chunk;
bytes_left -= chunk;
}
// 4. 清空先楫 ROM 标志寄存器,刷新总线
SOC_ROM_FLAG_REG = 0;
//l1c_dc_invalidate_all();
//fencei();
// 5. 触发芯片软件复位,10ms 后以新固件重启
ppor_sw_reset(HPM_PPOR, 10000);
while (1) {
}
}
/**
* @brief 周期调用(建议每 100ms 执行一次),全面覆盖超时、异常终止与安全搬运
*/
void ota_background_fsm_poll(void)
{
switch (s_ota_fsm) {
case OTA_FSM_IDLE:
break;
case OTA_FSM_RECEIVING:
/* 通信包间空闲看门狗累加 */
s_ota_packet_idle_cnt++;
if (s_ota_packet_idle_cnt >= OTA_PACKET_TIMEOUT_TICKS) {
/* 连续 10 秒未收到任何后续分片,判定为主站断网或异常中止 */
s_ota_err = OTA_ERR_TIMEOUT;
s_ota_fsm = OTA_FSM_ERROR_ABORT;
}
break;
case OTA_FSM_WAIT_LAST_ACK:
/* 延迟 100ms,确保物理层网卡将最后一个 FoE ACK 数据包完整推向总线 */
s_ota_ack_delay_cnt++;
if (s_ota_ack_delay_cnt >= OTA_ACK_DELAY_TICKS) {
s_ota_fsm = OTA_FSM_VERIFY_IMAGE;
}
break;
case OTA_FSM_VERIFY_IMAGE: {
/* 核心安全防线:物理回读 Bank B 强校验 */
//uint32_t aligned_start = HPM_L1C_CACHELINE_ALIGN_DOWN(FLASH_ADDR_BASE + FLASH_FILE_ADDR);
//uint32_t aligned_size = HPM_L1C_CACHELINE_ALIGN_UP(s_ota_target_fw_size);
//l1c_dc_invalidate(aligned_start, aligned_size); // 强制刷掉 D-Cache,读回 Flash 物理值
uint8_t *p_flash_img = (uint8_t *)(FLASH_ADDR_BASE + FLASH_FILE_ADDR);
// a. 校验先楫 BootHeader Tag (0xFCF90002) BootHeader Tag 位于 0x400 偏移处,不是 0 偏移!
uint32_t tag = *(uint32_t *)(p_flash_img);
if (tag != 0xFCF90002) {
s_ota_err = OTA_ERR_HEADER_INVALID;
s_ota_fsm = OTA_FSM_ERROR_ABORT;
break;
}
// b. 重新全量计算物理 Flash 内容的 CRC32
uint32_t verify_crc = 0xFFFFFFFFUL;
verify_crc = Calc_CRC32_Update(verify_crc, p_flash_img, s_ota_target_fw_size) ^ 0xFFFFFFFFUL;
if (verify_crc != s_ota_target_fw_crc) {
s_ota_err = OTA_ERR_CRC_MISMATCH;
s_ota_fsm = OTA_FSM_ERROR_ABORT;
break;
}
/* 校验彻底通过,进入安全准备阶段 */
s_ota_fsm = OTA_FSM_SAFE_PREPARE;
break;
}
case OTA_FSM_SAFE_PREPARE:
/* 强制联锁封锁 */
gSysCtrl.enable = false;
// 关闭底层三相逆变器 PWM 输出,进入高阻安全态
//Bsp_Pwm_EnableOutput(false);
s_ota_fsm = OTA_FSM_TRIGGER_RAM_COPY;
break;
case OTA_FSM_TRIGGER_RAM_COPY:
/* 切入 SRAM 执行全量覆盖与复位 */ /* 切入 SRAM 执行覆盖搬运与复位 (源: Bank B 0x00078000 -> 目标: Bank A 0x00000000) */
ota_execute_copy_in_sram(FLASH_FILE_ADDR, FLASH_APP_A_ADDR+0x400, s_ota_target_fw_size);
break;
case OTA_FSM_ERROR_ABORT:{
/* 异常回退清理 */
s_foe_transfer_active = false;
s_current_erased_offset = 0;
s_accumulated_crc = 0xFFFFFFFFUL;
s_ota_packet_idle_cnt = 0;
foe_data_verification_pass = false;
// 擦除元数据区,使损坏的暂存固件失效
ENTER_CRITICAL();
rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto,
&s_xpi_nor_config, FLASH_OTA_META_ADDR, FLASH_SECTOR_SIZE_BYTE);
EXIT_CRITICAL();
// 复位回初始状态,等待下一次主站重新下发
s_ota_fsm = OTA_FSM_IDLE;
}
break;
}
}
五、 将FOE相关源代码拷贝并整合源代码
5.1 拷贝bootmode、ecatfoe、foeappl、ecat_foe_support、applInterface



5.2 修改ecatslv.c






5.3 修改mailbox.c





六、工程配置
6.1 Ecat/Src增加bootmode.c、ecatfoe.c、foeappl.c

注意:不需要foe.c
6.2 Ecat/port增加ecat_foe_support.c、hpm_ecat_foe.c

七、 TwinCAT 3 验证与固件烧录步骤
- 将修改后的 ECAT_CIA402.xml 放置到 C:\TwinCAT\3.1\Config\Io\EtherCAT\ 并点击 Reload Device Descriptions。
- 重新扫描从站设备(Scan),右键点击该从站选择 Online 选项卡。
- 将设备切换到 Bootstrap (BOOT) 或 PreOP 状态。
- 点击 Download... 按钮:
○ File Name: 输入 app(与代码中 strcmp("app", write_file_name) 一致)。
○ Password: 输入十六进制 0x87654321(或十进制 2271560481)。
○ 选择编译好的 .bin 固件文件并点击确认。 - 传输完成后,TwinCAT 会将从站切回 Init 状态,设备自动触发软件复位并完成固件升级。






