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:
@@ -18,7 +18,7 @@ constexpr const char *APP_BANNER_TEXT = "GD32E508 DCU task scheduler start";
|
||||
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
|
||||
constexpr uint32_t APP_FW_VERSION_MINOR = 2U;
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 3U;
|
||||
constexpr uint32_t APP_FW_VERSION_BUILD = 1U;
|
||||
constexpr uint32_t APP_FW_VERSION_BUILD = 2U;
|
||||
|
||||
|
||||
/** @brief USART0 默认波特率。 */
|
||||
@@ -109,7 +109,7 @@ constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ;
|
||||
#define CONFIG_CAN_FD_BRS_ENABLE 1U
|
||||
|
||||
/** @brief CAN0 通道使能开关:true=启用初始化/收发/自测,false=完全禁用。 */
|
||||
constexpr bool CONFIG_CAN_CH0_ENABLE = true;
|
||||
constexpr bool CONFIG_CAN_CH0_ENABLE = false;
|
||||
/** @brief CAN1 通道使能开关。 */
|
||||
constexpr bool CONFIG_CAN_CH1_ENABLE = false;
|
||||
/** @brief CAN2 通道使能开关。 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -28,14 +28,27 @@ public:
|
||||
* @brief 设置 Bootloader OTA 等待标志并安排软复位。
|
||||
* @retval true 标志写入成功,稍后会复位进入 Bootloader。
|
||||
* @retval false Flash 标志写入失败。
|
||||
* @note 此函数会执行 Flash 操作(可能擦页 20-30ms),不可在 ISR 内调用。
|
||||
*/
|
||||
static bool requestEnterBoot();
|
||||
|
||||
/**
|
||||
* @brief 异步请求进入 Bootloader(ISR 安全)。
|
||||
* @note 仅设置 pending 标志,Flash 操作和复位由 process() 在主循环执行。
|
||||
* 用于 SPI EXTI ISR 内的 0xB0 命令处理,避免 Flash 擦除阻塞 SPI 中断。
|
||||
*/
|
||||
static void requestEnterBootAsync();
|
||||
|
||||
/** @brief 若已确认进入 BOOT 请求,则延迟片刻后复位。 */
|
||||
static void process();
|
||||
|
||||
/** @brief 查询是否已收到进入 Bootloader 请求(等待复位中)。
|
||||
* 供主循环在复位前停止主动按铃等可能干扰 OTA 的操作。 */
|
||||
static bool isRebootPending() { return s_rebootPending; }
|
||||
|
||||
private:
|
||||
static volatile bool s_rebootPending;
|
||||
static volatile bool s_bootRequestPending; ///< 0xB0 异步请求标志,process() 中执行 Flash 操作
|
||||
};
|
||||
|
||||
#endif /* SYS_OTA_BOOT_REQUEST_H */
|
||||
|
||||
@@ -35,6 +35,13 @@ public:
|
||||
*/
|
||||
static void onInterrupt();
|
||||
|
||||
/**
|
||||
* @brief 获取当前系统毫秒节拍数。
|
||||
* @return 自启动以来的毫秒数(32 位回绕)。
|
||||
* @note 用于超时判定(如 SPI 按铃看护)。读取 volatile 原子。
|
||||
*/
|
||||
static uint32_t getMs() { return s_ms; }
|
||||
|
||||
private:
|
||||
static volatile uint32_t s_ms;
|
||||
};
|
||||
|
||||
@@ -331,7 +331,7 @@ void TaskRunner::taskDiagnosticPrint()
|
||||
*/
|
||||
void TaskRunner::taskSpiCmdProcess()
|
||||
{
|
||||
AppSpiCommandBus.processPendingCommand();
|
||||
AppSpiCommandBus.poll(); // 命令处理已移入 ISR,此处仅日志打印+按铃看护
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,6 +378,11 @@ void TaskRunner::runPending()
|
||||
{
|
||||
Watchdog::feed();
|
||||
|
||||
/* CAN 反馈主动推送:GD32 APP 循环推送 3 通道 × 2 页反馈帧到 ESP32,
|
||||
* 替代 ESP32 主动轮询。非阻塞:上一帧未读走时立即返回。
|
||||
* 每次主循环迭代调用一次,ESP32 读走一帧后 CS 释放中断唤醒主循环继续推送。 */
|
||||
canFeedbackPushStep();
|
||||
|
||||
/* 周期路径( SYNC0 ISR preempt=0 )用 s_periodicSeq 序列号通知主循环,
|
||||
* 不再走 task_flags,避免 BASEPRI 临界区与 preempt=0 的 RMW 竞态。
|
||||
* 优先于 takePending() 处理,确保周期数据及时消费。
|
||||
|
||||
Reference in New Issue
Block a user