- 实现新的SPI同步帧格式,采用0xA5 0x5A起始符和CRC32校验,提升通信可靠性 - 添加GD32与ESP32间的握手机制,通过PB6开漏输出实现SPIready线与控制 - 实现异步OTA启动机制,将Flash操作延迟到主循环执行,避免SPI ISR阻塞 - 改进CAN反馈命令处理,提取通用payload构建函数支持主动推送模式 - 实现GD32主动按铃机制,循环推送CAN反馈数据到ESP32,减少轮询开销 - 添加SPI通信超时保护和死锁防护机制,确保系统稳定性
187 lines
5.6 KiB
C++
187 lines
5.6 KiB
C++
/**
|
||
* @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) {
|
||
}
|
||
}
|