Files
DCU_APP/GD32E508_DCU_APP/sys/ota_boot_request.cpp
T
cbb a52d94687b refactor(SPI): 实现全双工DMA传输以提升通信可靠性
- 配置DMA0_CH1用于SPI0_RX接收,替代原有的RBNE中断逐字节处理
- 添加startSpiRxDma、stopSpiDmas、drainSpiRxHw等DMA控制函数
- 在CS空闲期预装下一笔DMA传输,避免EXTI CS下降沿相对于SCK偏晚导致收0字节
- 与BOOT工程SpiOta的DMA配置对齐,确保通信稳定性

refactor(SPI): 优化CS边沿处理逻辑和命令解析时机

- 移除原有的EXTI5_9_IRQHandler用于SPI Ready引脚处理
- 将命令处理从异步任务调度改为在CS释放中断中同步完成
- 重构onCsAssert和onCsRelease逻辑,简化DMA启停流程
- 删除TASK_SPI_CMD_PROCESS任务类型,减少不必要的任务调度

feat(CANFD): 修改transmitFrame返回类型为枚举值以提供详细错误信息

- 将CanFd::transmitFrame返回类型从bool改为CanTxResult枚举
- 新增CanTxResult枚举定义,包含kOk、kErrBusOff、kErrTecHigh、kErrMailbox、kErrInvalid
- 更新SPI桥接命令处理中对transmitFrame返回值的处理逻辑
- 枚举值设计与SPI错误响应码对齐,便于直接透传错误信息

refactor(SPI): 实现全双工DMA传输以提升通信可靠性

- 配置DMA0_CH1用于SPI0_RX接收,替代原有的RBNE中断逐字节处理
- 添加startSpiRxDma、stopSpiDmas、drainSpiRxHw等DMA控制函数
- 在CS空闲期预装下一笔DMA传输,避免EXTI CS下降沿相对于SCK偏晚导致收0字节
- 与BOOT工程SpiOta的DMA配置对齐,确保通信稳定性

refactor(SPI): 优化CS边沿处理逻辑和命令解析时机

- 移除原有的EXTI5_9_IRQHandler用于SPI Ready引脚处理
- 将命令处理从异步任务调度改为在CS释放中断中同步完成
- 重构onCsAssert和onCsRelease逻辑,简化DMA启停流程
- 删除TASK_SPI_CMD_PROCESS任务类型,减少不必要的任务调度

feat(OTA): 添加BOOT版本号读取功能并优化版本信息获取

- 在ota_boot_request模块中添加readBootVersion函数
- 从OTA配置页Flash读取BOOT版本号信息
- 在SPI PING命令响应中使用新的版本读取接口
- 统一版本号获取方式,避免裸指针访问Flash地址

refactor(task): 调整诊断打印任务调度周期为5秒间隔

- 新增div_diag_print调度分频计数器用于诊断打印
- 将诊断打印任务从心跳任务中分离,独立控制执行周期
- 修改诊断打印函数注释,明确其由5秒节拍触发
- 优化任务调度逻辑,提高系统运行效率
2026-07-15 00:34:56 +08:00

225 lines
7.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file ota_boot_request.cpp
* @brief DCU_APP 进入 Bootloader OTA 窗口控制实现。
*/
#include "sys/ota_boot_request.h"
#include "gd32e50x.h"
#include "sys/config.h"
#include "sys/systick.h"
namespace {
/** Bootloader OTA 配置页地址,与 BOOT 工程 sys/config.h 保持一致。 */
constexpr uint32_t kOtaConfigPageAddr = 0x0807E000U;
constexpr uint32_t kAppLatestAvalAddr = kOtaConfigPageAddr + 0x00U;
constexpr uint32_t kAppLatestEnAddr = kOtaConfigPageAddr + 0x08U;
constexpr uint32_t kAppLatestCheckAddr = kOtaConfigPageAddr + 0x10U;
/** BOOT 版本号在 OTA 配置页中的存储地址:word6=major|minor, word7=patch|build。 */
constexpr uint32_t kBootVersionAddr0 = kOtaConfigPageAddr + 0x18U;
constexpr uint32_t kBootVersionAddr1 = kOtaConfigPageAddr + 0x1CU;
constexpr uint32_t kOtaAval = 0x00000000U;
constexpr uint32_t kOtaNotAval = 0xFFFFFFFFU;
constexpr uint32_t kFmcPageSize = 0x00000800U;
constexpr uint32_t kRebootDelayMs = 20U;
static bool writeWord(uint32_t addr, uint32_t value)
{
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(addr, value);
fmc_lock();
return status == FMC_READY;
}
/**
* @brief 按 Bootloader OTA_SET 语义更新配置页中单个 word。
* @note Flash 只能 1->0;需要写回 0xFFFFFFFF 时必须擦整页再恢复其他字段。
*/
static bool otaConfigSet(uint32_t targetAddr, uint32_t value)
{
if(targetAddr < kOtaConfigPageAddr || targetAddr >= (kOtaConfigPageAddr + kFmcPageSize)) {
return false;
}
const uint32_t current = *((volatile uint32_t *)targetAddr);
if(current == value) {
return true;
}
if(value != kOtaNotAval) {
return writeWord(targetAddr, value);
}
uint32_t words[CONFIG_OTA_CONFIG_WORD_COUNT];
for(uint32_t i = 0U; i < CONFIG_OTA_CONFIG_WORD_COUNT; ++i) {
words[i] = *((volatile uint32_t *)(kOtaConfigPageAddr + i * 4U));
}
words[(targetAddr - kOtaConfigPageAddr) / 4U] = value;
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_page_erase(kOtaConfigPageAddr);
if(status != FMC_READY) {
fmc_lock();
return false;
}
bool ok = true;
for(uint32_t i = 0U; i < CONFIG_OTA_CONFIG_WORD_COUNT; ++i) {
if(words[i] == kOtaNotAval) {
continue;
}
status = fmc_word_program(kOtaConfigPageAddr + i * 4U, words[i]);
if(status != FMC_READY) {
ok = false;
break;
}
}
fmc_lock();
return ok;
}
static bool requestMagicValid(const uint8_t *data, uint8_t length)
{
return data != nullptr && length >= 4U &&
data[0] == CONFIG_CAN_OTA_BOOT_MAGIC0 &&
data[1] == CONFIG_CAN_OTA_BOOT_MAGIC1 &&
data[2] == CONFIG_CAN_OTA_BOOT_MAGIC2 &&
data[3] == CONFIG_CAN_OTA_BOOT_MAGIC3;
}
} // namespace
/** @brief 已写入 OTA 标志并等待软复位的挂起标志。 */
volatile bool OtaBootRequest::s_rebootPending = false;
volatile bool OtaBootRequest::s_bootRequestPending = false;
volatile bool OtaBootRequest::s_spiFrozenForBoot = false;
/**
* @brief 初始化进入 Bootloader 请求模块的运行态。
*/
void OtaBootRequest::init()
{
s_rebootPending = false;
s_bootRequestPending = false;
s_spiFrozenForBoot = false;
}
BootVersion OtaBootRequest::readBootVersion()
{
/* BOOT 启动时将自身版本写入 OTA 配置页 word6/word7,
* 字布局:word6=[15:0]major|[31:16]minor, word7=[15:0]patch|[31:16]build。
* Flash 未写入(擦除态 0xFFFFFFFF)时各字段返回 0xFF,调用方可据此判断有效性。 */
const volatile uint32_t *bv0 = (const volatile uint32_t *)kBootVersionAddr0;
const volatile uint32_t *bv1 = (const volatile uint32_t *)kBootVersionAddr1;
const uint32_t w0 = *bv0;
const uint32_t w1 = *bv1;
return BootVersion{
(uint8_t)(w0 & 0xFFU),
(uint8_t)((w0 >> 16) & 0xFFU),
(uint8_t)(w1 & 0xFFU),
(uint8_t)((w1 >> 16) & 0xFFU)
};
}
/**
* @brief 写入 Bootloader OTA 等待标志,并安排稍后软复位。
* @return true 表示标志写入成功;false 表示 Flash 配置页写入失败。
*/
bool OtaBootRequest::requestEnterBoot()
{
bool ok = true;
ok = otaConfigSet(kAppLatestCheckAddr, kOtaAval) && ok;
ok = otaConfigSet(kAppLatestAvalAddr, kOtaAval) && ok;
if(ok) {
ok = otaConfigSet(kAppLatestEnAddr, kOtaNotAval);
}
if(ok) {
s_rebootPending = true;
} else {
/* 写配置页失败则永远停在 APP,ESP32 会看到持续 role=1 */
/* 无 printf 依赖:由调用方 process() 恢复 SPI 后继续运行 */
}
return ok;
}
/**
* @brief 异步请求进入 Bootloader(ISR 安全)。
* @note 仅设置 pending 标志,Flash 操作和复位由 process() 在主循环执行。
* 用于 SPI EXTI ISR 内的 0xB0 命令处理,避免 Flash 擦除阻塞 SPI 中断。
*/
void OtaBootRequest::requestEnterBootAsync()
{
s_bootRequestPending = true;
/* 立即冻结 SPI 业务:不必等 process()(原依赖 1Hz 心跳,最坏拖 ~1s)。
* 否则 ESP32 紧跟着 HELLO/0xC1 会继续进 processCommand,干扰擦页/复位。 */
s_spiFrozenForBoot = true;
}
/**
* @brief 处理备用 CAN 入口的进入 Bootloader 请求。
* @note 当前主路径是 SPI 命令;CAN 入口只负责识别魔术字并触发同一套标志写入逻辑。
*/
bool OtaBootRequest::handleCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t length)
{
if(id != CONFIG_CAN_OTA_BOOT_REQ_ID) {
return false;
}
if(!requestMagicValid(data, length)) {
return true;
}
(void)channel;
const bool ok = requestEnterBoot();
(void)ok;
return true;
}
/**
* @brief 在主循环任务中执行异步 Flash 操作和短延迟软复位。
* @note 不在 SPI/CAN 中断里直接复位,避免应答还没有发出就重启。
* 异步路径:SPI ISR 收到 0xB0 后只设标志,Flash 操作(可能擦页 20-30ms)
* 在此主循环执行,避免阻塞 SPI 中断导致 ESP32 ACK 轮询丢字节。
*/
void OtaBootRequest::process()
{
/* 异步处理 0xB0:主循环写 Flash(可能擦页 20–30ms)。
* 期间冻结 SPI 业务处理,避免 ESP32 紧跟着 HELLO/按铃读 打断擦写。 */
if(s_bootRequestPending) {
s_bootRequestPending = false;
s_spiFrozenForBoot = true;
nvic_irq_disable(SPI0_IRQn);
nvic_irq_disable(EXTI10_15_IRQn);
const bool ok = requestEnterBoot();
if(!ok) {
s_spiFrozenForBoot = false;
nvic_irq_enable(SPI0_IRQn, 2U, 0U);
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
/* requestEnterBoot 失败时 s_rebootPending 仍为 false */
return;
}
s_rebootPending = true;
}
if(!s_rebootPending) {
return;
}
s_rebootPending = false;
s_spiFrozenForBoot = true;
nvic_irq_disable(SPI0_IRQn);
nvic_irq_disable(EXTI10_15_IRQn);
/* v2 DATA_READY: Boot 切换不再用 PB6 通知对端,
* BOOT 起机后再由 HELLO role=boot 确认。 */
/* SysTick 中断仍开,delayMs 可用;SPI 相关 IRQ 已关。 */
SysTickTimer::delayMs(kRebootDelayMs);
__disable_irq();
NVIC_SystemReset();
while(1) {
}
}