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:
cbb
2026-09-20 22:58:14 +08:00
parent 3d1fcfc6fb
commit 77354fd2a8
9 changed files with 300 additions and 53 deletions
+60 -6
View File
@@ -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;
}