feat(spi): 实现SPI同步通信协议和主动推送机制

- 实现新的SPI同步帧格式,采用0xA5 0x5A起始符和CRC32校验,提升通信可靠性
- 添加GD32与ESP32间的握手机制,通过PB6开漏输出实现SPIready线与控制
- 实现异步OTA启动机制,将Flash操作延迟到主循环执行,避免SPI ISR阻塞
- 改进CAN反馈命令处理,提取通用payload构建函数支持主动推送模式
- 实现GD32主动按铃机制,循环推送CAN反馈数据到ESP32,减少轮询开销
- 添加SPI通信超时保护和死锁防护机制,确保系统稳定性
This commit is contained in:
cbb
2026-07-08 02:51:24 +08:00
parent b78e36ff37
commit 33037bc5a3
9 changed files with 509 additions and 163 deletions
+32 -5
View File
@@ -89,6 +89,7 @@ static bool requestMagicValid(const uint8_t *data, uint8_t length)
/** @brief 已写入 OTA 标志并等待软复位的挂起标志。 */
volatile bool OtaBootRequest::s_rebootPending = false;
volatile bool OtaBootRequest::s_bootRequestPending = false;
/**
* @brief 初始化进入 Bootloader 请求模块的运行态。
@@ -96,6 +97,7 @@ volatile bool OtaBootRequest::s_rebootPending = false;
void OtaBootRequest::init()
{
s_rebootPending = false;
s_bootRequestPending = false;
}
/**
@@ -117,6 +119,16 @@ bool OtaBootRequest::requestEnterBoot()
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 入口只负责识别魔术字并触发同一套标志写入逻辑。
@@ -138,19 +150,34 @@ bool OtaBootRequest::handleCanFrame(uint8_t channel, uint32_t id, const uint8_t
}
/**
* @brief 在主循环任务中执行短延迟软复位。
* @note 不在 SPI/CAN 中断里直接复位,避免应答还没发出就重启。
* 这里使用毫秒级短延时,而不是 1s 心跳计数;否则一次 SPI 请求
* 实际会拖到 2~3 秒后才复位,ESP32 第二次 OTA 时很容易在 APP 还没
* 真正重启进入 BOOT 之前就发出 HANDSHAKE_REQ。
* @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();