feat(boot): 1.1.0.22 CoE 跳回 APP + 连续崩溃门 + warm jump 根治
变更点: 1. CoE 0x2503「GD32 jump to app」 - 主站写 1 → requestJumpToApp:向量/CRC 校验后置 CHECK/AVAL/EN,process() 在 SDO 响应后跳 - 与 APP 0x2502 对称;crash_gate 只禁自动跳,人工跳仍允许 2. 连续 FWDGT 崩溃门(配置页 word9) - 复位 crash_count+1;达 OTA_LAYOUT_CRASH_STAY_MAX(3) 后关闭全部自动跳 APP - APP 喂狗稳定后再清 0(不在 init 立刻清) 3. warm jump 卡死 - bootEcatDeinit 去掉 LAN9252 writeReg32(INT_EN/IRQ_CFG):HBI 不返回会锁死 AHB - 跳转前有界等 USART TC,避免 handoff 半截 + APP 乱码 4. EtherCAT 邮箱空闲限速 5ms;delayMs 改 WFI,BOOT 驻留不空转烧 CPU 5. ota_layout.h vendored 进仓库(GD32E50X_BOOT/shared/),include 改 ../shared/ 6. .gitignore:firmware_keep/、评审 HTML、IDE 过程目录
This commit is contained in:
+13
-1
@@ -1,4 +1,4 @@
|
||||
# Prerequisites
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
@@ -85,3 +85,15 @@ uv2csolution.log
|
||||
|
||||
# legacy intermediate project file
|
||||
*.cprj
|
||||
# 本机固件留存与评审过程稿
|
||||
firmware_keep/
|
||||
SECURITY_AUDIT_REPORT.*
|
||||
*.html
|
||||
|
||||
# IDE / 过程产物
|
||||
.trae/
|
||||
.agents/
|
||||
.worktrees/
|
||||
.qoder/
|
||||
.zcode/
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
* 所以主站**只能靠 0x1008 的 "GD32E508 DCU BOOT" 字符串**辨认这是引导程序。
|
||||
* 0x2500 会话控制(REC of UINT32)
|
||||
* 0x2501 数据通道(ARR of UINT8,BitSize = 4096*8,写回调落 flash)
|
||||
* 0x2503 跳回 APP(VAR UINT32,写 1 触发;与 0x2502 对称,方向相反)
|
||||
*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
@@ -118,6 +119,23 @@ OBJCONST TSDOINFOENTRYDESC OBJMEM asDlChunkEntryDesc[] = {
|
||||
};
|
||||
static OBJCONST UCHAR aName0x2501[] = "GD32 firmware image chunk";
|
||||
|
||||
/*============================================================================
|
||||
* 0x2503 跳回 APP(主站 → BOOT:「让你跳回应用」)
|
||||
*
|
||||
* 与 APP 的 0x2502 对称:写 1 只置 pending,真正跳转由 process() 在
|
||||
* SDO 响应发出之后做,不会把 SM1 半包留在总线上。
|
||||
* VAR 对象只能写 :00;条目必须 ACCESS_READWRITE 才过 objdef 写权限门。
|
||||
*==========================================================================*/
|
||||
|
||||
static UINT32 s_jumpAppReq = 0U;
|
||||
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x2503[] = {
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE},
|
||||
};
|
||||
static OBJCONST UCHAR aName0x2503[] = "GD32 jump to app";
|
||||
|
||||
/* 回调实现在 dlLoadU32 之后(见下方 ObjJumpAppWrite)。 */
|
||||
|
||||
/*============================================================================
|
||||
* SDO 回调
|
||||
*==========================================================================*/
|
||||
@@ -177,6 +195,27 @@ static inline UINT32 dlLoadU32(const UINT16 MBXMEM *p)
|
||||
return v;
|
||||
}
|
||||
|
||||
/** @brief 0x2503 写 1 = 请求跳回 APP(挂 pending_app_jump,process() 排空)。 */
|
||||
static UINT8 ObjJumpAppWrite(UINT16 index, UINT8 subindex, UINT32 dataSize,
|
||||
UINT16 MBXMEM *pData, UINT8 bCompleteAccess)
|
||||
{
|
||||
(void)index;
|
||||
(void)dataSize;
|
||||
(void)bCompleteAccess;
|
||||
|
||||
if (subindex != 0U) {
|
||||
return ABORTIDX_SUBINDEX_NOT_EXISTING;
|
||||
}
|
||||
if (dlLoadU32(pData) != 1U) {
|
||||
return ABORTIDX_GENERAL_ERROR;
|
||||
}
|
||||
if (!ota.requestJumpToApp()) {
|
||||
return ABORTIDX_GENERAL_ERROR;
|
||||
}
|
||||
s_jumpAppReq = 1U;
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/** @brief 0x2500 写入:sub1 = 命令,sub2 = 镜像总长;sub3/sub4 只读。 */
|
||||
static UINT8 ObjFoeDlCtlWrite(UINT16 index, UINT8 subindex, UINT32 dataSize,
|
||||
UINT16 MBXMEM *pData, UINT8 bCompleteAccess)
|
||||
@@ -283,6 +322,10 @@ TOBJECT OBJMEM ApplicationObjDicList[] = {
|
||||
asDlChunkEntryDesc, aName0x2501, &s_dlChunk,
|
||||
NULL, ObjFoeDlWrite, 0x0000},
|
||||
|
||||
{NULL, NULL, 0x2503, {DEFTYPE_UNSIGNED32, 0 | (OBJCODE_VAR << 8)},
|
||||
sEntryDesc0x2503, aName0x2503, &s_jumpAppReq,
|
||||
NULL, ObjJumpAppWrite, 0x0000},
|
||||
|
||||
/* 终止条目 */
|
||||
{NULL, NULL, 0xFFFF, {0, 0}, NULL, NULL, NULL, NULL, NULL, 0x000}
|
||||
};
|
||||
@@ -363,7 +406,10 @@ static bool s_hwOk = false;
|
||||
namespace {
|
||||
/** @brief 与 main.cpp 的 1ms 主循环节拍对齐的时基判定(ECAT_TIMER_INT=0)。 */
|
||||
constexpr uint32_t kTickCycles = ECAT_TIMER_INC_P_MS; /* 180000 @180MHz = 1ms */
|
||||
uint32_t s_lastCheck = 0U;
|
||||
/** @brief 空闲时 MainLoop 最小间隔。FreeRun 每圈 csrRead 双读 = EXMC 20+ 线翻转,
|
||||
* 邮箱-only 在 5ms 节拍下仍够用;OTA/ECAT 会话期间不限速。 */
|
||||
constexpr uint32_t kEcatIdlePollMinMs = 5U;
|
||||
uint32_t s_lastPollCyc = 0U;
|
||||
}
|
||||
|
||||
/** @brief 上电调一次。硬件不通就整个 EtherCAT 栈不启,SPI OTA 照旧。 */
|
||||
@@ -383,19 +429,27 @@ extern "C" void ecatTaskStart(void)
|
||||
* APP 侧就是因为零调用者才让 s_alEventMask 影子停在 0xFFFF。 */
|
||||
(void)HW_Init();
|
||||
(void)MainInit();
|
||||
s_lastCheck = DWT->CYCCNT;
|
||||
s_lastPollCyc = DWT->CYCCNT;
|
||||
}
|
||||
|
||||
/** @brief 主循环每圈调。邮箱服务、AL 状态机、SDO 分段都在这条路上推进。 */
|
||||
/** @brief 主循环每圈调。邮箱服务、AL 状态机、SDO 分段都在这条路上推进。
|
||||
* 空闲时按 kEcatIdlePollMinMs 限速,降低 FreeRun 对 LAN9252 的 EXMC 翻转。 */
|
||||
extern "C" void ecatTaskPoll(void)
|
||||
{
|
||||
if (!s_started || !s_hwOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool busy = ota.isActive() || ota.ecatSessionActive();
|
||||
const uint32_t now = DWT->CYCCNT;
|
||||
if (!busy) {
|
||||
const uint32_t elapsed_ms = (now - s_lastPollCyc) / ECAT_TIMER_INC_P_MS;
|
||||
if (elapsed_ms < kEcatIdlePollMinMs) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
s_lastPollCyc = now;
|
||||
MainLoop();
|
||||
|
||||
/* ECAT_TIMER_INT=0:SSC 的超时计数靠 MainLoop 内部自己按 HW_GetTimerValue()
|
||||
* 判 1ms,这里不需要额外节拍。保留函数以便 Phase 2 挂进度上报。 */
|
||||
(void)kTickCycles;
|
||||
(void)s_lastCheck;
|
||||
}
|
||||
|
||||
@@ -495,24 +495,22 @@ void escResetForJump()
|
||||
gpio_input_bit_get(ESC_NRST_PORT, ESC_NRST_PIN) == SET ? 1 : 0);
|
||||
}
|
||||
|
||||
/** @brief 跳转 APP 前拆掉 BOOT 留下的 HBI 用户与引脚占用。 */
|
||||
/** @brief 跳转 APP 前拆掉 BOOT 留下的 HBI 用户与引脚占用。
|
||||
* ⚠ 跳转路径上**禁止**再对 LAN9252 做 writeReg32:HBI 若不返回会锁死 AHB,
|
||||
* 主循环卡在 deinit,SPI ISR 仍应答 HELLO(表现为 role=boot)而邮箱全死。
|
||||
* APP 1.6.40+ 放行门是 BYTE_TEST 往返,不再依赖「读复位默认 IDX_0」,
|
||||
* 清 INT_EN/IRQ_CFG 残值不是硬需求;宁可交给 APP 自己 init/RST#。 */
|
||||
extern "C" void bootEcatDeinit(void)
|
||||
{
|
||||
NVIC_DisableIRQ(ESC_IRQ_IRQn);
|
||||
NVIC_ClearPendingIRQ(ESC_IRQ_IRQn);
|
||||
exti_interrupt_disable(ESC_IRQ_EXTI);
|
||||
exti_flag_clear(ESC_IRQ_EXTI);
|
||||
writeReg32(LAN9252_REG_INT_EN, 0U); /* 免得 APP 起来后 PB1 还挂着 ESC 驱动的电平 */
|
||||
writeReg32(LAN9252_REG_IRQ_CFG, 0U);
|
||||
/** [WJ-TEST 1.1.0.18] 单变量裁决:暂时短路跳转前这发 RST#。
|
||||
* 板上实测(2026-09-16):刷完 warm jump 进不去 APP,但按一下 reset 走
|
||||
* cold jump 就正常起 —— 两条路唯一差异是 s_escBusUp 为真时下面这些真跑了,
|
||||
* 而 escResetForJump 释放 RST# 后只忙等 2ms 就交 CPU(对照 BOOT 自己起板
|
||||
* exmcInit 轮询 HBI_IDX_0==0x1234 上限 300ms)。
|
||||
* 预期代价=1.1.0.13 的原病回来(APP 自检「EXMC 未就绪/降级 ethercat」、
|
||||
* IDX_0 读到 0x0054);那是"能起但降级",与"起不来"形态不同,可分开判读。
|
||||
* 起来 ⇒ 元凶=这发复位的时序;仍起不来 ⇒ 转查 PD/PE 留在 AF 的 MCU 侧残留。 */
|
||||
/* escResetForJump(); */ /* 1.1.0.13: 交还总线前把芯片复位到上电默认态 */
|
||||
/** [WJ-TEST 1.1.0.18] escResetForJump 仍短路;1.1.0.21 再去掉两笔 HBI 写,
|
||||
* 根治 warm jump 时 jumpToApplication 卡死(SPI 活、ECAT 死、role=boot)。 */
|
||||
/* writeReg32(LAN9252_REG_INT_EN, 0U); */
|
||||
/* writeReg32(LAN9252_REG_IRQ_CFG, 0U); */
|
||||
/* escResetForJump(); */
|
||||
exmc_norsram_disable(EXMC_BANK0_NORSRAM_REGION0);
|
||||
rcu_periph_clock_disable(RCU_EXMC); /* PD/PE 回浮空,交还 APP 自己初始化 */
|
||||
DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk;
|
||||
|
||||
+53
-10
@@ -1,4 +1,4 @@
|
||||
#include "main.h"
|
||||
#include "main.h"
|
||||
#include "device/Can.h"
|
||||
#include "device/SpiOta.h"
|
||||
#include "sys/config.h"
|
||||
@@ -80,7 +80,7 @@ void BootloaderSystem::initHardware() {
|
||||
volatile u32 *cur0 = (volatile u32 *)n_BOOT_VERSION_ADDR0;
|
||||
volatile u32 *cur1 = (volatile u32 *)n_BOOT_VERSION_ADDR1;
|
||||
if (*cur0 != ver_word0 || *cur1 != ver_word1) {
|
||||
/** 版本变更:先读取整页 8 个配置字,再擦除整页后一次性回写。
|
||||
/** 版本变更:先读取整页全部配置字(含 arm/crash),再擦除整页后一次性回写。
|
||||
* 读取逻辑与 app_spi_command.cpp 中 BOOT 版本读取方式一致。 */
|
||||
u32 temp_val[OTA_CONFIG_NUM];
|
||||
volatile u32 *flash_ptr;
|
||||
@@ -139,6 +139,27 @@ void BootloaderSystem::initHardware() {
|
||||
/** @brief 心跳打印间隔(毫秒)。 */
|
||||
static constexpr uint32_t kHeartbeatPrintIntervalMs = 2000U;
|
||||
|
||||
/**
|
||||
* @brief 读连续 FWDGT 崩溃计数。
|
||||
* @note 擦除态 0xFFFFFFFF 视为 0(旧板/未写过)。
|
||||
*/
|
||||
static uint32_t otaCrashCount(void)
|
||||
{
|
||||
const uint32_t raw = *(volatile uint32_t *)n_BOOT_CRASH_COUNT_ADDR;
|
||||
return (raw == OTA_NOT_AVAL) ? 0U : raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FWDGT 复位时把崩溃计数 +1(Flash 友好:OTA_SET 内含擦页回写)。
|
||||
*/
|
||||
static void otaCrashBump(void)
|
||||
{
|
||||
const uint32_t cur = otaCrashCount();
|
||||
if (cur < 0xFFFFFFFFU) {
|
||||
(void)OTA_SET(n_BOOT_CRASH_COUNT_ADDR, cur + 1U);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief EtherCAT 邮箱栈:首次调用拉起,之后每圈推一次 SSC 状态机。
|
||||
*
|
||||
@@ -214,13 +235,14 @@ void BootloaderSystem::run() {
|
||||
uint32_t app_enable, app_check;
|
||||
|
||||
/** 检测 FWDGT 看门狗复位:APP 卡死导致看门狗超时。
|
||||
* 将 app_enable 标记为 NOT_AVAL,阻止 BOOT 再次跳入卡死的 APP,
|
||||
* 改为停留在 BOOT 等待 OTA 修复。 */
|
||||
* ① 崩溃计数 +1;② EN 标 NOT_AVAL 走 OTA 窗口。
|
||||
* 计数达 OTA_CRASH_STAY_MAX 后由下方 crash_gate 彻底禁止自动跳 APP。 */
|
||||
if (rcu_flag_get(RCU_FLAG_FWDGTRST) != RESET) {
|
||||
rcu_all_reset_flag_clear();
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] FWDGT reset detected, blocking app jump\r\n");
|
||||
printf("[BOOT] FWDGT reset detected, bump crash_count\r\n");
|
||||
#endif
|
||||
otaCrashBump();
|
||||
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL);
|
||||
}
|
||||
|
||||
@@ -231,17 +253,33 @@ void BootloaderSystem::run() {
|
||||
flash_ptr = (volatile uint32_t *)n_APP_LATEST_CHECK_ADDR;
|
||||
app_check = *flash_ptr;
|
||||
|
||||
/** 连续崩溃门:达阈值后强制走 wait_ota_window,并在超时/恢复路径上禁止跳 APP。
|
||||
* APP 起板成功会把计数清 0,故这不是永久砖,是「必须外部灌写才能再试」。 */
|
||||
const uint32_t crash_count = otaCrashCount();
|
||||
const bool crash_gate = (crash_count >= OTA_CRASH_STAY_MAX);
|
||||
if (crash_gate) {
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] app_enable=0x%08lX app_check=0x%08lX\r\n",
|
||||
printf("[BOOT] crash_count=%lu >= %lu, auto-jump DISABLED\r\n",
|
||||
(unsigned long)crash_count, (unsigned long)OTA_CRASH_STAY_MAX);
|
||||
#endif
|
||||
if (app_enable != OTA_NOT_AVAL) {
|
||||
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL);
|
||||
app_enable = OTA_NOT_AVAL;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] app_enable=0x%08lX app_check=0x%08lX crash=%lu\r\n",
|
||||
(unsigned long)app_enable,
|
||||
(unsigned long)app_check);
|
||||
(unsigned long)app_check,
|
||||
(unsigned long)crash_count);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 情况 1:应用已校验完成,且允许启动。
|
||||
* 增加 CRC32 完整性校验;若校验失败则标记 A区无效,fall through 到恢复逻辑。
|
||||
*/
|
||||
if (app_check == OTA_AVAL && app_enable == OTA_AVAL) {
|
||||
if (app_check == OTA_AVAL && app_enable == OTA_AVAL && !crash_gate) {
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] path=jump_latest_now\r\n");
|
||||
#endif
|
||||
@@ -336,6 +374,7 @@ void BootloaderSystem::run() {
|
||||
(unsigned long)ended_check, (unsigned long)ended_aval);
|
||||
#endif
|
||||
if (ended_check == OTA_AVAL && ended_aval == OTA_AVAL &&
|
||||
!crash_gate &&
|
||||
verifyAppCrc(APPLATEST_START_ADDR)) {
|
||||
ota.jumpToApplication();
|
||||
}
|
||||
@@ -354,7 +393,11 @@ void BootloaderSystem::run() {
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] ota_window_timeout_then_jump\r\n");
|
||||
#endif
|
||||
if (verifyAppCrc(APPLATEST_START_ADDR)) {
|
||||
if (crash_gate) {
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] crash_gate: timeout jump suppressed\r\n");
|
||||
#endif
|
||||
} else if (verifyAppCrc(APPLATEST_START_ADDR)) {
|
||||
ota.jumpToApplication();
|
||||
/** jumpToApplication() 正常不返回;若返回则继续 fall through。 */
|
||||
} else {
|
||||
@@ -384,7 +427,7 @@ void BootloaderSystem::run() {
|
||||
(unsigned long)back_check);
|
||||
#endif
|
||||
|
||||
if (back_check == OTA_AVAL && back_aval == OTA_AVAL) {
|
||||
if (back_check == OTA_AVAL && back_aval == OTA_AVAL && !crash_gate) {
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] path=restore_backup_then_jump\r\n");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @file ota_layout.h
|
||||
* @brief APP/BOOT 共用 OTA Flash 布局(唯一真源)。
|
||||
* @note 两侧 sys/config.h 都必须 include 本头,禁止再抄地址常量。
|
||||
* 相对路径:BOOT/APP 的 sys/config.h → ../../../shared/ota_layout.h
|
||||
*
|
||||
* 约定:0x00000000=AVAL(有效),0xFFFFFFFF=NOT_AVAL(擦除态/无效)。
|
||||
*/
|
||||
#ifndef SHARED_OTA_LAYOUT_H
|
||||
#define SHARED_OTA_LAYOUT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ---- 整片 Flash 区段(GD32E508VE 512KB) ---- */
|
||||
#define OTA_LAYOUT_BOOT_START 0x08000000U
|
||||
#define OTA_LAYOUT_BOOT_SIZE 0x00012000U /* 72KB */
|
||||
#define OTA_LAYOUT_APP_BACK_START 0x08012000U /* 216KB 备份区 */
|
||||
#define OTA_LAYOUT_APP_MAIN_START 0x08048000U /* 216KB 主应用区 = APP VTOR */
|
||||
#define OTA_LAYOUT_APP_MAX_SIZE 0x00035FFCU /* 216KB-4B,末尾 CRC32 */
|
||||
#define OTA_LAYOUT_CONFIG_PAGE 0x0807E000U /* OTA 状态配置页 2KB */
|
||||
#define OTA_LAYOUT_SLEEP_PAGE 0x0807E800U /* 睡眠配置页 2KB */
|
||||
#define OTA_LAYOUT_PAGE_SIZE 0x00000800U /* FMC 单页 2KB */
|
||||
|
||||
/* ---- OTA 配置页 word 布局(word N = PAGE + 4*N) ---- */
|
||||
#define OTA_LAYOUT_WORD_LATEST_AVAL 0U
|
||||
#define OTA_LAYOUT_WORD_BACKUP_AVAL 1U
|
||||
#define OTA_LAYOUT_WORD_LATEST_EN 2U
|
||||
#define OTA_LAYOUT_WORD_BACKUP_EN 3U
|
||||
#define OTA_LAYOUT_WORD_LATEST_CHECK 4U
|
||||
#define OTA_LAYOUT_WORD_BACKUP_CHECK 5U
|
||||
#define OTA_LAYOUT_WORD_BOOT_VER0 6U /* major|minor */
|
||||
#define OTA_LAYOUT_WORD_BOOT_VER1 7U /* patch|build */
|
||||
#define OTA_LAYOUT_WORD_ECAT_ARM 8U /* 0xACA70001 才允许 ECAT SDO 刷 APP */
|
||||
#define OTA_LAYOUT_WORD_CRASH_COUNT 9U /* 连续 FWDGT 崩溃次数;APP 起板成功清 0 */
|
||||
|
||||
#define OTA_LAYOUT_ADDR_LATEST_AVAL (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_LATEST_AVAL)
|
||||
#define OTA_LAYOUT_ADDR_BACKUP_AVAL (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BACKUP_AVAL)
|
||||
#define OTA_LAYOUT_ADDR_LATEST_EN (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_LATEST_EN)
|
||||
#define OTA_LAYOUT_ADDR_BACKUP_EN (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BACKUP_EN)
|
||||
#define OTA_LAYOUT_ADDR_LATEST_CHECK (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_LATEST_CHECK)
|
||||
#define OTA_LAYOUT_ADDR_BACKUP_CHECK (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BACKUP_CHECK)
|
||||
#define OTA_LAYOUT_ADDR_BOOT_VER0 (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BOOT_VER0)
|
||||
#define OTA_LAYOUT_ADDR_BOOT_VER1 (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BOOT_VER1)
|
||||
#define OTA_LAYOUT_ADDR_ECAT_ARM (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_ECAT_ARM)
|
||||
#define OTA_LAYOUT_ADDR_CRASH_COUNT (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_CRASH_COUNT)
|
||||
|
||||
/** 配置页有效 word 数(擦页回写必须覆盖到此);页 2KB,余量充足。 */
|
||||
#define OTA_LAYOUT_WORD_COUNT 10U
|
||||
|
||||
/** ECAT 下载 arm 令牌。避开擦除态与 AVAL。 */
|
||||
#define OTA_LAYOUT_ECAT_ARM_KEY 0xACA70001U
|
||||
|
||||
/** 连续 FWDGT 达到该次数后 BOOT 停止自动跳 APP,只接受外部灌写。 */
|
||||
#define OTA_LAYOUT_CRASH_STAY_MAX 3U
|
||||
|
||||
#define OTA_LAYOUT_AVAL 0x00000000U
|
||||
#define OTA_LAYOUT_NOT_AVAL 0xFFFFFFFFU
|
||||
|
||||
#endif /* SHARED_OTA_LAYOUT_H */
|
||||
@@ -177,6 +177,9 @@ u8 OTA_SET(u32 addr, u32 data) {
|
||||
* 表现为"刚 arm 完、一次标志更新就又不让刷了"。 */
|
||||
flash_ptr = (volatile u32 *)n_ECAT_OTA_ARM_ADDR;
|
||||
temp_val[8] = *flash_ptr;
|
||||
/** word 9 = 连续 FWDGT 崩溃计数。漏读会让「APP 刚清零 / BOOT 刚 +1」被误抹。 */
|
||||
flash_ptr = (volatile u32 *)n_BOOT_CRASH_COUNT_ADDR;
|
||||
temp_val[9] = *flash_ptr;
|
||||
|
||||
/** 整页擦除后重建配置页。 */
|
||||
fmc_unlock();
|
||||
@@ -609,6 +612,21 @@ return;
|
||||
(unsigned)BOOT_FW_VERSION_PATCH, (unsigned)BOOT_FW_VERSION_BUILD);
|
||||
/* 无缓冲的 microlib 下 fflush 是空操作,留着只为换回标准库时语义不变。 */
|
||||
fflush(stdout);
|
||||
/* putChar 只等 TBE(数据寄存器空),最后一字节可能还在移位寄存器。
|
||||
* 不等 TC 就 BX:APP SystemInit 会重配 USART/时钟,线上出现
|
||||
* "handoff" 后半截 + APP 乱码。DWT 已在 bootEcatDeinit 关掉,
|
||||
* 这里用有界轮询(~2ms @3Mbps 约需 60 bit 时间,留足余量)。 */
|
||||
{
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
const uint32_t t0 = DWT->CYCCNT;
|
||||
const uint32_t tmax = 2U * (SystemCoreClock / 1000U);
|
||||
while ((USART_STAT0(USART0) & USART_FLAG_TC) == 0U) {
|
||||
if ((DWT->CYCCNT - t0) > tmax) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -635,6 +653,31 @@ return;
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主站写 0x2503 请求跳回 APP。
|
||||
* @note 与 ecatCommit 成功尾巴逐字对齐:先置 CHECK/AVAL/EN 三面标志,
|
||||
* 再 pending_app_jump,由 process() 走同一套 delay+jumpToApplication。
|
||||
* 不要另起跳转实现。
|
||||
*/
|
||||
bool OTAHandler::requestJumpToApp()
|
||||
{
|
||||
if (ecat_session || ota_state != OTA_STATE_IDLE) {
|
||||
return false;
|
||||
}
|
||||
if (!validateApplication()) {
|
||||
return false;
|
||||
}
|
||||
if (!verifyAppCrc(APPLATEST_START_ADDR)) {
|
||||
return false;
|
||||
}
|
||||
/* 与 ecatCommit OTA_SET 三连相同;EN 原先可能是 NOT_AVAL(0x2502 进来的)。 */
|
||||
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_AVAL);
|
||||
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
|
||||
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
|
||||
pending_app_jump = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 擦除当前主应用区。
|
||||
*
|
||||
|
||||
@@ -165,6 +165,15 @@ class OTAHandler {
|
||||
/** @brief 放弃会话。已擦/已写的 A 区不恢复,靠 AVAL 未置守住。 */
|
||||
void ecatAbort();
|
||||
|
||||
/**
|
||||
* @brief 主站显式请求跳回 APP(CoE 0x2503:00=1)。
|
||||
* @note 不在 SDO 回调里跳:置 pending_app_jump,由 process() 排空。
|
||||
* 与 crash_gate 正交——门只禁自动跳,人工跳仍允许(否则 UI 无法退出 BOOT)。
|
||||
* @retval true 已挂起跳转(向量+CRC 均合法)
|
||||
* @retval false 会话中/镜像非法,未挂起
|
||||
*/
|
||||
bool requestJumpToApp();
|
||||
|
||||
/** @brief 已落字节数(0x2500:03 进度)。 */
|
||||
u32 ecatReceived() const { return ecat_recv; }
|
||||
/** @brief 会话是否活跃。 */
|
||||
|
||||
+47
-22
@@ -1,8 +1,9 @@
|
||||
#ifndef __CONFIG_H__
|
||||
#ifndef __CONFIG_H__
|
||||
#define __CONFIG_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gd32e50x.h"
|
||||
#include "../shared/ota_layout.h"
|
||||
|
||||
/** 项目内部统一使用的简写类型。 */
|
||||
typedef uint8_t u8;
|
||||
@@ -193,14 +194,34 @@ typedef uint32_t u32;
|
||||
* 注:本版与 1.1.0.15~17(关中断跳转实验、BKP 面包屑、EXTI15 清理)无关,
|
||||
* 那批改动未提交且已整体回退到 46adcb7,补丁存于
|
||||
* G:/Robot_v2/DCU/_revert_backup_20260916/。
|
||||
* v1.1.0.19: 连续 FWDGT 崩溃计数 + 共享 OTA 布局头。
|
||||
* ① 配置页新增 word9=crash_count:FWDGT 复位时 +1;APP 喂狗稳定满 30s 后清 0
|
||||
* (task_runner::maybeClearCrashCount,**不在 init 里立刻清**,否则主循环崩溃
|
||||
* 永远攒不到阈值)。达到 OTA_CRASH_STAY_MAX(3) 后 BOOT 停止一切自动跳 APP
|
||||
* (jump_latest_now / 窗口超时 / stay_in_ota 结束 / 备份恢复均被门控),
|
||||
* 只留在 BOOT 等外部灌写。
|
||||
* 动机:旧逻辑一次 FWDGT 就 EN=NOT_AVAL,10s 后 CRC 过仍自动再跳——崩溃镜像会
|
||||
* 以 10s 周期打转;同时“一次误触发就永远留守”过严。N 次阈值两边都收住。
|
||||
* ② 地址/字布局抽到 G:/Robot_v2/DCU/shared/ota_layout.h,APP/BOOT 共用,禁止再抄。
|
||||
* v1.1.0.20: CoE 0x2503「GD32 jump to app」——主站写 1 让 BOOT 跳回 APP。
|
||||
* 与 APP 0x2502 对称;回调只置 pending_app_jump,process() 在 SDO 响应后跳。
|
||||
* 主站 ecat op=jump_app / 前端「跳回应用」按钮配套。
|
||||
* v1.1.0.21: 修 warm jump 卡死。现象:写 0x2503 成功(ok=true)后 SPI 仍 role=boot、
|
||||
* EtherCAT 邮箱全死。根因:jumpToApplication → bootEcatDeinit 里两笔 writeReg32
|
||||
* (INT_EN/IRQ_CFG) 走 EXMC HBI,LAN9252 不返回时锁死 AHB,主循环卡住;SPI 由 ISR
|
||||
* 应答故看起来还活着。去掉这两笔(APP 1.6.40+ 用 BYTE_TEST,不依赖清残值)。
|
||||
* v1.1.0.22: [J6] 后串口乱码。putChar 只等 TBE,末字节仍在移位寄存器就 BX,
|
||||
* APP 重配 USART 后线上混成 "handoff"+乱码。跳转前有界等 USART TC(~2ms)。
|
||||
* requestJumpToApp 改为与 ecatCommit 成功尾巴一致:先置 CHECK/AVAL/EN 再 pending,
|
||||
* 仍走 process() 同一条 delay+jumpToApplication,不另起跳转。
|
||||
*/
|
||||
#define BOOT_FW_VERSION_MAJOR 1U
|
||||
#define BOOT_FW_VERSION_MINOR 1U
|
||||
#define BOOT_FW_VERSION_PATCH 0U
|
||||
#define BOOT_FW_VERSION_BUILD 18U /* 变更来由见上方注释块 */
|
||||
#define BOOT_FW_VERSION_BUILD 22U /* 变更来由见上方注释块 */
|
||||
|
||||
/**
|
||||
* Flash 布局说明(GD32E508VE,512KB)
|
||||
* Flash 布局说明(GD32E508VE,512KB)——唯一真源 shared/ota_layout.h
|
||||
* -----------------------------------
|
||||
* 0x08000000 ~ 0x08011FFF : Bootloader 区(72KB)
|
||||
* 0x08012000 ~ 0x08047FFF : 备份应用区(216KB)
|
||||
@@ -208,13 +229,13 @@ typedef uint32_t u32;
|
||||
* 0x0807E000 ~ 0x0807E7FF : OTA 状态配置页
|
||||
* 0x0807E800 ~ 0x0807EFFF : 睡眠配置页
|
||||
*/
|
||||
inline constexpr u32 BOOTLOADER_START_ADDR = 0x08000000U;
|
||||
inline constexpr u32 BOOTLOADER_SIZE = 0x00012000U; /**< 72KB */
|
||||
inline constexpr u32 APPBACK_START_ADDR = 0x08012000U; /**< 备份应用起始地址 */
|
||||
inline constexpr u32 APPLATEST_START_ADDR = 0x08048000U; /**< 当前有效应用起始地址 */
|
||||
inline constexpr u32 APP_MAX_SIZE = 0x00035FFCU; /**< 216KB - 4B,末尾预留 CRC32 */
|
||||
inline constexpr u32 FMC_PAGE_SIZE = 0x00000800U; /**< 2KB,GD32E50x 单页大小 */
|
||||
inline constexpr u32 OTA_CONFIG_PAGE_ADDR = 0x0807E000U; /**< OTA 元数据页 */
|
||||
inline constexpr u32 BOOTLOADER_START_ADDR = OTA_LAYOUT_BOOT_START;
|
||||
inline constexpr u32 BOOTLOADER_SIZE = OTA_LAYOUT_BOOT_SIZE;
|
||||
inline constexpr u32 APPBACK_START_ADDR = OTA_LAYOUT_APP_BACK_START;
|
||||
inline constexpr u32 APPLATEST_START_ADDR = OTA_LAYOUT_APP_MAIN_START;
|
||||
inline constexpr u32 APP_MAX_SIZE = OTA_LAYOUT_APP_MAX_SIZE;
|
||||
inline constexpr u32 FMC_PAGE_SIZE = OTA_LAYOUT_PAGE_SIZE;
|
||||
inline constexpr u32 OTA_CONFIG_PAGE_ADDR = OTA_LAYOUT_CONFIG_PAGE;
|
||||
inline constexpr u32 APP_CRC_SIZE = 4U; /**< 固件区末尾 CRC32 占用字节数 */
|
||||
inline constexpr u32 APP_FIRMWARE_SIZE = APP_MAX_SIZE - APP_CRC_SIZE; /**< 实际固件大小(不含 CRC) */
|
||||
inline constexpr u32 n_APP_LATEST_CRC_ADDR = APPLATEST_START_ADDR + APP_FIRMWARE_SIZE; /**< A区 CRC 存储地址 */
|
||||
@@ -228,24 +249,28 @@ inline constexpr u32 SLEEP_ENABLE = 0x11111111U;
|
||||
inline constexpr u32 SLEEP_DISABLE = 0x00000000U;
|
||||
|
||||
/**
|
||||
* OTA 配置页一共使用 9 个 word(页大小 2KB,余量充足):
|
||||
* OTA 配置页一共使用 10 个 word(页大小 2KB,余量充足):
|
||||
* word 0~5: latest_aval / backup_aval / latest_en / backup_en / latest_check / backup_check
|
||||
* word 6~7: BOOT 版本号(major|minor / patch|build)
|
||||
* word 8 : EtherCAT 下载 arm 令牌,值 == OTA_ECAT_ARM_KEY 才允许 SDO 刷 APP
|
||||
* word 9 : 连续 FWDGT 崩溃计数;APP 喂狗满 30s 清 0;达 OTA_CRASH_STAY_MAX 禁止自动跳
|
||||
*/
|
||||
inline constexpr u32 OTA_CONFIG_NUM = 9U;
|
||||
inline constexpr u32 n_APP_LATEST_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x00U;
|
||||
inline constexpr u32 n_APP_BACKUP_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x04U;
|
||||
inline constexpr u32 n_APP_LATEST_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x08U;
|
||||
inline constexpr u32 n_APP_BACKUP_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x0CU;
|
||||
inline constexpr u32 n_APP_LATEST_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x10U;
|
||||
inline constexpr u32 n_APP_BACKUP_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x14U;
|
||||
inline constexpr u32 n_BOOT_VERSION_ADDR0 = OTA_CONFIG_PAGE_ADDR + 0x18U; /**< BOOT版本 word0: major|minor */
|
||||
inline constexpr u32 n_BOOT_VERSION_ADDR1 = OTA_CONFIG_PAGE_ADDR + 0x1CU; /**< BOOT版本 word1: patch|build */
|
||||
inline constexpr u32 n_ECAT_OTA_ARM_ADDR = OTA_CONFIG_PAGE_ADDR + 0x20U; /**< EtherCAT 下载 arm 令牌 word8 */
|
||||
inline constexpr u32 OTA_CONFIG_NUM = OTA_LAYOUT_WORD_COUNT;
|
||||
inline constexpr u32 n_APP_LATEST_AVAL_ADDR = OTA_LAYOUT_ADDR_LATEST_AVAL;
|
||||
inline constexpr u32 n_APP_BACKUP_AVAL_ADDR = OTA_LAYOUT_ADDR_BACKUP_AVAL;
|
||||
inline constexpr u32 n_APP_LATEST_EN_ADDR = OTA_LAYOUT_ADDR_LATEST_EN;
|
||||
inline constexpr u32 n_APP_BACKUP_EN_ADDR = OTA_LAYOUT_ADDR_BACKUP_EN;
|
||||
inline constexpr u32 n_APP_LATEST_CHECK_ADDR = OTA_LAYOUT_ADDR_LATEST_CHECK;
|
||||
inline constexpr u32 n_APP_BACKUP_CHECK_ADDR = OTA_LAYOUT_ADDR_BACKUP_CHECK;
|
||||
inline constexpr u32 n_BOOT_VERSION_ADDR0 = OTA_LAYOUT_ADDR_BOOT_VER0;
|
||||
inline constexpr u32 n_BOOT_VERSION_ADDR1 = OTA_LAYOUT_ADDR_BOOT_VER1;
|
||||
inline constexpr u32 n_ECAT_OTA_ARM_ADDR = OTA_LAYOUT_ADDR_ECAT_ARM;
|
||||
inline constexpr u32 n_BOOT_CRASH_COUNT_ADDR = OTA_LAYOUT_ADDR_CRASH_COUNT;
|
||||
/** @brief arm 令牌值。刻意避开擦除态 0xFFFFFFFF 与 OTA_AVAL 0x00000000,
|
||||
* 所以没写过这一字的旧板子/新板子都天然读成「未 arm」。 */
|
||||
inline constexpr u32 OTA_ECAT_ARM_KEY = 0xACA70001U;
|
||||
inline constexpr u32 OTA_ECAT_ARM_KEY = OTA_LAYOUT_ECAT_ARM_KEY;
|
||||
/** @brief 连续崩溃达到该次数后,BOOT 停止自动跳 APP。 */
|
||||
inline constexpr u32 OTA_CRASH_STAY_MAX = OTA_LAYOUT_CRASH_STAY_MAX;
|
||||
|
||||
/**
|
||||
* 本项目把 0x00000000 视为“有效/可用”,
|
||||
|
||||
@@ -50,16 +50,20 @@ void SysTickTimer::delayUs(uint32_t count)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行毫秒级阻塞延时。
|
||||
* @brief 执行毫秒级阻塞延时(WFI 等 SysTick,不空转烧 CPU)。
|
||||
*
|
||||
* @param count 延时毫秒数。
|
||||
* @return 无。
|
||||
* @note 180MHz 下空 while 会让 die 迅速升温(BOOT 驻留实测 ~80°C)。
|
||||
* SysTick 已配 1ms 且优先级 0,WFI 睡到下一次滴答即可。
|
||||
* 禁止在 __disable_irq() 之后调用——滴答不进则永久睡死;
|
||||
* 那类路径(bootEcatDeinit 等)必须继续用 DWT 忙等。
|
||||
*/
|
||||
void SysTickTimer::delayMs(uint32_t count)
|
||||
{
|
||||
/** 设置倒计时,由 SysTick_Handler 每毫秒递减。 */
|
||||
s_delayCountdown = count;
|
||||
while (s_delayCountdown != 0U) {
|
||||
__WFI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user