HPM CIA402工程融合FOE

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 验证与固件烧录步骤

  1. 将修改后的 ECAT_CIA402.xml 放置到 C:\TwinCAT\3.1\Config\Io\EtherCAT\ 并点击 Reload Device Descriptions。
  2. 重新扫描从站设备(Scan),右键点击该从站选择 Online 选项卡。
  3. 将设备切换到 Bootstrap (BOOT) 或 PreOP 状态。
  4. 点击 Download... 按钮:
    ○ File Name: 输入 app(与代码中 strcmp("app", write_file_name) 一致)。
    ○ Password: 输入十六进制 0x87654321(或十进制 2271560481)。
    ○ 选择编译好的 .bin 固件文件并点击确认。
  5. 传输完成后,TwinCAT 会将从站切回 Init 状态,设备自动触发软件复位并完成固件升级。
相关推荐
嵌入式科普2 天前
嵌入式科普(57) 各类MCU/MPU的以太网功能详细对比
以太网·ethercat·profinet·gmac
企鹅的蚂蚁5 天前
LubanCat RK3588 实时 Linux 开发实战(十四):EtherCAT 主站方案选择与 Full SDK 准备
rk3588·lubancat·嵌入式linux·ethercat·full sdk
星源~6 天前
MCU-OTA升级的flash分散加载应用
单片机·嵌入式·ota·flash分散加载
@嵌入式扫地僧10 天前
国产 RISC-V 机器人关节 MCU 量产落地解析:硬件 EtherCAT 支持与进口替代完整指南
机器人·risc-v·ethercat·国产化替代·机器人关节mcu
北京盟通科技官方账号11 天前
高通 IQ-9075 部署 EC-Master 实战指南:环境构建与实时 Linux (RT) 系统烧录
人工智能·机器人·yocto·ethercat·ecmaster·盟通科技·rt kernel
hlpinghcg24 天前
HPM Twincat Inputs全为0解决方法
ethercat·hpm
方芯半导体1 个月前
EtherCAT 从站方案中 MAC、PHY 与 MII 的层级关系与实现解析
网络·人工智能·单片机·macos·ethercat
明如正午1 个月前
OTA、SOTA与FOTA:一篇讲透汽车远程升级
ota·sota·fota
hlpinghcg1 个月前
HPM(先辑) ESC相关配置
ethercat·hpm·先辑