Files
DCU_APP/GD32E508_DCU_APP/sys/ota_boot_request.cpp
T
cbb 33037bc5a3 feat(spi): 实现SPI同步通信协议和主动推送机制
- 实现新的SPI同步帧格式,采用0xA5 0x5A起始符和CRC32校验,提升通信可靠性
- 添加GD32与ESP32间的握手机制,通过PB6开漏输出实现SPIready线与控制
- 实现异步OTA启动机制,将Flash操作延迟到主循环执行,避免SPI ISR阻塞
- 改进CAN反馈命令处理,提取通用payload构建函数支持主动推送模式
- 实现GD32主动按铃机制,循环推送CAN反馈数据到ESP32,减少轮询开销
- 添加SPI通信超时保护和死锁防护机制,确保系统稳定性
2026-07-08 02:51:24 +08:00

187 lines
5.6 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;
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;
/**
* @brief 初始化进入 Bootloader 请求模块的运行态。
*/
void OtaBootRequest::init()
{
s_rebootPending = false;
s_bootRequestPending = false;
}
/**
* @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;
}
return ok;
}
/**
* @brief 异步请求进入 Bootloader(ISR 安全)。
* @note 仅设置 pending 标志,Flash 操作和复位由 process() 在主循环执行。
* 用于 SPI EXTI ISR 内的 0xB0 命令处理,避免 Flash 擦除阻塞 SPI 中断。
*/
void OtaBootRequest::requestEnterBootAsync()
{
s_bootRequestPending = 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 ISR */
if(s_bootRequestPending) {
s_bootRequestPending = false;
const bool ok = requestEnterBoot();
if(ok) {
s_rebootPending = true;
}
}
if(!s_rebootPending) {
return;
}
s_rebootPending = false;
/* 方案A: 拉低 PB6 通知 ESP32 "即将复位"。
* ESP32 通过 LOW→HIGH 序列识别 Bootloader 就绪:
* APP 拉低 → 复位(μs级HIGH) → Bootloader极早拉低 → init完成释放HIGH
* 20ms 延时确保 ESP32 5ms 轮询能捕获到 LOW 信号后再复位。 */
gpio_bit_reset(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
SysTickTimer::delayMs(kRebootDelayMs);
__disable_irq();
NVIC_SystemReset();
while(1) {
}
}