feat(SpiOta): 实现SPI OTA同步协议和主动按铃机制
- 添加SPI OTA同步协议,使用新的帧格式包含信用控制和CRC32校验 - 实现BOOT主动按铃功能,通过SPI Ready线通知ESP32读取数据 - 添加按铃超时保护机制,防止死锁情况发生 - 优化SPI中断处理,统一在CS释放时处理帧避免RX溢出错误 - 修改GPIO配置为开漏输出模式,实现硬件握手信号 - 添加同步帧解析功能,支持帧头偏移扫描和完整性校验 - 实现信用控制机制,当flash缓冲区满时阻止ESP32继续发送数据 - 修复BOOT命令响应逻辑,确保一致性处理裸字节和帧式命令 - 将OTA完成验证移到主循环处理,避免SPI ready线阻塞过久 - 添加详细的调试日志输出便于问题排查 refactor(SpiOta): 重构SPI OTA协议栈 - 使用CRC32替代CRC8进行帧校验,提高数据完整性 - 重命名帧相关常量以匹配新的同步协议格式 - 移除旧的帧处理方法,采用新的同步帧解析流程 - 添加大小端转换辅助函数用于数据编码解码 - 优化内存布局减少RAM使用 - 统一错误处理流程,添加帧校验失败日志记录
This commit is contained in:
+209
-75
@@ -6,6 +6,7 @@
|
|||||||
#include "gd32e50x_misc.h"
|
#include "gd32e50x_misc.h"
|
||||||
#include "gd32e50x_spi.h"
|
#include "gd32e50x_spi.h"
|
||||||
#include "gd32e50x_dma.h"
|
#include "gd32e50x_dma.h"
|
||||||
|
#include "sys/systick.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -23,6 +24,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* SPI0_TX = DMA0_CH2 (GD32E50x 固定映射) */
|
/* SPI0_TX = DMA0_CH2 (GD32E50x 固定映射) */
|
||||||
|
|
||||||
|
/* 主动按铃超时时间(毫秒) */
|
||||||
|
static constexpr u32 kRingTimeoutMs = 500U;
|
||||||
#define SPI_TX_DMA_PERIPH DMA0
|
#define SPI_TX_DMA_PERIPH DMA0
|
||||||
#define SPI_TX_DMA_CH DMA_CH2
|
#define SPI_TX_DMA_CH DMA_CH2
|
||||||
|
|
||||||
@@ -47,28 +51,70 @@ volatile u16 SpiOta::s_txLen = 0U;
|
|||||||
volatile bool SpiOta::s_txPending = false;
|
volatile bool SpiOta::s_txPending = false;
|
||||||
volatile bool SpiOta::s_isrResponded = false;
|
volatile bool SpiOta::s_isrResponded = false;
|
||||||
volatile bool SpiOta::s_otaSessionActive = false;
|
volatile bool SpiOta::s_otaSessionActive = false;
|
||||||
|
volatile bool SpiOta::s_ringPending = false;
|
||||||
|
volatile u16 SpiOta::s_ringTxLen = 0U;
|
||||||
|
volatile u32 SpiOta::s_ringDeadline = 0U;
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* CRC8 / 帧构建 / 帧解析
|
* CRC8 / 帧构建 / 帧解析
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
u8 SpiOta::crc8(const u8 *data, u16 len) {
|
static constexpr u8 kSyncSof0 = 0xA5U;
|
||||||
u8 crc = 0x00U;
|
static constexpr u8 kSyncSof1 = 0x5AU;
|
||||||
for (u16 i = 0; i < len; ++i) { crc ^= data[i]; }
|
static constexpr u16 kSyncHeaderLen = 6U;
|
||||||
|
static constexpr u16 kSyncCrcLen = 4U;
|
||||||
|
static constexpr u16 kSyncOverhead = (u16)(kSyncHeaderLen + kSyncCrcLen);
|
||||||
|
|
||||||
|
/* BOOT 侧同步状态机(ISR 内只读/原子写)。 */
|
||||||
|
volatile u8 g_localCredit = 8U; /* 非 static:供 OTA.cpp extern 引用 */
|
||||||
|
static volatile u8 g_remoteCredit = 8U;
|
||||||
|
|
||||||
|
static void putU16Le(u8 *pp, u16 v) {
|
||||||
|
pp[0] = (u8)(v & 0xFFU);
|
||||||
|
pp[1] = (u8)((v >> 8) & 0xFFU);
|
||||||
|
}
|
||||||
|
static u16 getU16Le(const u8 *pp) {
|
||||||
|
return (u16)(pp[0] | ((u16)pp[1] << 8));
|
||||||
|
}
|
||||||
|
static void putU32Le(u8 *pp, u32 v) {
|
||||||
|
pp[0] = (u8)(v & 0xFFU);
|
||||||
|
pp[1] = (u8)((v >> 8) & 0xFFU);
|
||||||
|
pp[2] = (u8)((v >> 16) & 0xFFU);
|
||||||
|
pp[3] = (u8)((v >> 24) & 0xFFU);
|
||||||
|
}
|
||||||
|
static u32 getU32Le(const u8 *pp) {
|
||||||
|
return (u32)pp[0] | ((u32)pp[1] << 8) |
|
||||||
|
((u32)pp[2] << 16) | ((u32)pp[3] << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 spiCrc32(const u8 *data, u16 len) {
|
||||||
|
u32 crc = 0xFFFFFFFFU;
|
||||||
|
for (u16 i = 0U; i < len; ++i) {
|
||||||
|
crc ^= (u32)data[i] << 24;
|
||||||
|
for (int b = 0; b < 8; ++b) {
|
||||||
|
if (crc & 0x80000000U) {
|
||||||
|
crc = (crc << 1) ^ 0x04C11DB7U;
|
||||||
|
} else {
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpiOta::buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
|
bool SpiOta::buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
|
||||||
u8 *out, u16 *out_len) {
|
u8 *out, u16 *out_len) {
|
||||||
if (!out || !out_len) { return false; }
|
if (!out || !out_len) { return false; }
|
||||||
u16 total_len = (u16)(kFrameOverhead + payload_len);
|
u16 total_len = (u16)(kSyncOverhead + payload_len);
|
||||||
if (total_len > kTxBufSize) { return false; }
|
if (total_len > kTxBufSize) { return false; }
|
||||||
out[0] = kSof;
|
out[0] = kSyncSof0;
|
||||||
out[1] = (u8)type;
|
out[1] = kSyncSof1;
|
||||||
out[2] = (u8)(payload_len & 0xFFU);
|
out[2] = g_localCredit;
|
||||||
out[3] = (u8)((payload_len >> 8) & 0xFFU);
|
out[3] = (u8)type;
|
||||||
if (payload && payload_len > 0U) { memcpy(&out[4], payload, payload_len); }
|
putU16Le(&out[4], payload_len);
|
||||||
out[4 + payload_len] = crc8(out, (u16)(4U + payload_len));
|
if (payload && payload_len > 0U) { memcpy(&out[kSyncHeaderLen], payload, payload_len); }
|
||||||
|
u32 crc = spiCrc32(out, (u16)(kSyncHeaderLen + payload_len));
|
||||||
|
putU32Le(&out[kSyncHeaderLen + payload_len], crc);
|
||||||
*out_len = total_len;
|
*out_len = total_len;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -77,16 +123,28 @@ bool SpiOta::tryParseFrame(const u8 *frame, u16 frame_len,
|
|||||||
spi_ota_frame_type_t *type,
|
spi_ota_frame_type_t *type,
|
||||||
const u8 **payload,
|
const u8 **payload,
|
||||||
u16 *payload_len) {
|
u16 *payload_len) {
|
||||||
if (!frame || frame_len < kFrameOverhead) { return false; }
|
if (!frame || frame_len < kSyncOverhead) {
|
||||||
if (frame[0] != kSof) { return false; }
|
return false;
|
||||||
u16 len = (u16)(frame[2] | ((u16)frame[3] << 8));
|
}
|
||||||
u16 expected = (u16)(kFrameOverhead + len);
|
|
||||||
if (expected != frame_len) { return false; }
|
/* 同步帧:允许首字节偏移,扫描 SOF0 SOF1。 */
|
||||||
if (crc8(frame, (u16)(4U + len)) != frame[4U + len]) { return false; }
|
for (u16 s = 0U; s + kSyncOverhead <= frame_len; ++s) {
|
||||||
if (type) { *type = (spi_ota_frame_type_t)frame[1]; }
|
if (frame[s] != kSyncSof0 || frame[s + 1U] != kSyncSof1) { continue; }
|
||||||
if (payload) { *payload = &frame[4]; }
|
u16 plen = getU16Le(&frame[s + 4U]);
|
||||||
if (payload_len){ *payload_len = len; }
|
u16 total = (u16)(kSyncOverhead + plen);
|
||||||
return true;
|
if ((u16)(frame_len - s) < total) { continue; }
|
||||||
|
u32 expect = getU32Le(&frame[s + kSyncHeaderLen + plen]);
|
||||||
|
u32 calc = spiCrc32(&frame[s], (u16)(kSyncHeaderLen + plen));
|
||||||
|
if (expect != calc) { continue; }
|
||||||
|
|
||||||
|
u8 credit = frame[s + 2U];
|
||||||
|
if (type) { *type = (spi_ota_frame_type_t)frame[s + 3U]; }
|
||||||
|
if (payload) { *payload = (plen > 0U) ? &frame[s + kSyncHeaderLen] : nullptr; }
|
||||||
|
if (payload_len){ *payload_len = plen; }
|
||||||
|
g_remoteCredit = credit;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ota_link_msg_type_t SpiOta::mapMsgType(spi_ota_frame_type_t type) {
|
ota_link_msg_type_t SpiOta::mapMsgType(spi_ota_frame_type_t type) {
|
||||||
@@ -139,30 +197,18 @@ void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
|||||||
s_txStreamPos = 0U;
|
s_txStreamPos = 0U;
|
||||||
if (len < kTxBufSize) { memset(&s_txBuf[len], 0xFF, kTxBufSize - len); }
|
if (len < kTxBufSize) { memset(&s_txBuf[len], 0xFF, kTxBufSize - len); }
|
||||||
exitCritical(primask);
|
exitCritical(primask);
|
||||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
/* ★ 不在此处启动 DMA。
|
||||||
startSpiTxDma(s_txBuf, s_txLen);
|
*
|
||||||
}
|
* 本函数可从 RBNE ISR 中调用(isrProcessFrame → handleLinkMessage
|
||||||
|
* → send_structured_ack → sender → queueTxFrame),此时 CS 仍为 LOW,
|
||||||
/* ============================================================
|
* DMA 正在发送上一次 onCsAssert() 装载的旧响应。
|
||||||
* RBNE 中断内即时帧检测与处理
|
*
|
||||||
* ============================================================ */
|
* 若在此处 startSpiTxDma(),GD32 DMA 控制器会先 CHEN=0 再 CHEN=1,
|
||||||
|
* 导致 DMA 计数器清零并从 s_txBuf[0] 重新开始,MISO 输出被破坏
|
||||||
void SpiOta::tryProcessCompleteFrame() {
|
* (旧响应尾部 + 新响应头部拼接),ESP32 的 otaFindFrame() 无法解析。
|
||||||
u16 rx_len = s_rxStreamLen;
|
*
|
||||||
if (rx_len == 0U) { return; }
|
* 正确做法:只缓冲响应到 s_txBuf,由下一次 onCsAssert() 检测
|
||||||
|
* s_txPending==true 后统一装载 DMA。这遵循"延迟一拍"协议模型。 */
|
||||||
/* 单字节 BOOT 命令留到事务结束后再判定。 */
|
|
||||||
if (s_rxStream[0] == SPI_OTA_CMD_BOOT) { return; }
|
|
||||||
|
|
||||||
if (s_rxStream[0] != kSof) { return; }
|
|
||||||
if (rx_len < kFrameOverhead) { return; }
|
|
||||||
|
|
||||||
u16 payload_len = (u16)(s_rxStream[2] | ((u16)s_rxStream[3] << 8));
|
|
||||||
u16 expected = (u16)(kFrameOverhead + payload_len);
|
|
||||||
if (expected > kRxBufSize) { return; }
|
|
||||||
if (rx_len < expected) { return; }
|
|
||||||
|
|
||||||
isrProcessFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -177,12 +223,11 @@ void SpiOta::isrProcessFrame() {
|
|||||||
s_isrResponded = true;
|
s_isrResponded = true;
|
||||||
|
|
||||||
if (rx_len == 1U && s_rxStream[0] == SPI_OTA_CMD_BOOT) {
|
if (rx_len == 1U && s_rxStream[0] == SPI_OTA_CMD_BOOT) {
|
||||||
if (s_otaHandler != nullptr && s_otaHandler->isActive()) {
|
/* 0xB0 始终回复 0xB5 并重置 OTA 状态机。
|
||||||
s_rxStreamLen = 0U;
|
* 旧代码在 isActive() 时静默忽略,导致上次 OTA 失败后
|
||||||
return;
|
* 所有后续 0xB0 都被吞掉,ESP32 永远收不到 0xB5。 */
|
||||||
}
|
u8 ack = 0xB5U;
|
||||||
u8 ack[4] = {0xB0U, 0xA5U, 0x4FU, 0x54U};
|
queueTxFrame(&ack, 1U);
|
||||||
queueTxFrame(ack, sizeof(ack));
|
|
||||||
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
|
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
|
||||||
s_otaSessionActive = false;
|
s_otaSessionActive = false;
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
@@ -190,23 +235,29 @@ void SpiOta::isrProcessFrame() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_rxStream[0] != kSof) {
|
/* 新同步帧校验。 */
|
||||||
|
if (rx_len < kSyncOverhead ||
|
||||||
|
s_rxStream[0] != kSyncSof0 || s_rxStream[1] != kSyncSof1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rx_len < kFrameOverhead) { return; }
|
|
||||||
|
|
||||||
u16 payload_len = (u16)(s_rxStream[2] | ((u16)s_rxStream[3] << 8));
|
u16 payload_len = getU16Le(&s_rxStream[4U]);
|
||||||
u16 expected = (u16)(kFrameOverhead + payload_len);
|
u16 expected = (u16)(kSyncOverhead + payload_len);
|
||||||
if (expected > rx_len) { return; }
|
if (expected > rx_len) { return; }
|
||||||
|
|
||||||
if (crc8(s_rxStream, (u16)(4U + payload_len)) != s_rxStream[4U + payload_len]) {
|
/* 帧收齐日志:确认 BOOT 收到了完整帧 */
|
||||||
|
isrLogFrameEx("[RX-FRM]", s_rxStream, (expected < 18U) ? expected : 18U);
|
||||||
|
|
||||||
|
u32 expect = getU32Le(&s_rxStream[kSyncHeaderLen + payload_len]);
|
||||||
|
u32 calc = spiCrc32(s_rxStream, (u16)(kSyncHeaderLen + payload_len));
|
||||||
|
if (expect != calc) {
|
||||||
u8 err = 0xEEU;
|
u8 err = 0xEEU;
|
||||||
u8 err_frame[kTxBufSize] = {};
|
u8 err_frame[kTxBufSize] = {};
|
||||||
u16 built = 0U;
|
u16 built = 0U;
|
||||||
if (buildFrame(SPI_OTA_RSP_ERROR, &err, 1U, err_frame, &built)) {
|
if (buildFrame(SPI_OTA_RSP_ERROR, &err, 1U, err_frame, &built)) {
|
||||||
queueTxFrame(err_frame, built);
|
queueTxFrame(err_frame, built);
|
||||||
}
|
}
|
||||||
isrLogFrameEx("[CRC-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
isrLogFrameEx("[CRC-ERR]", s_rxStream, (rx_len < 18U) ? rx_len : 18U);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,22 +265,26 @@ void SpiOta::isrProcessFrame() {
|
|||||||
const u8 *payload = nullptr;
|
const u8 *payload = nullptr;
|
||||||
u16 p_len = 0U;
|
u16 p_len = 0U;
|
||||||
if (!tryParseFrame(s_rxStream, expected, &frame_type, &payload, &p_len)) {
|
if (!tryParseFrame(s_rxStream, expected, &frame_type, &payload, &p_len)) {
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
static volatile u8 s_parseFailCnt = 0U;
|
||||||
|
if (s_parseFailCnt < 10U) {
|
||||||
|
s_parseFailCnt++;
|
||||||
|
printf("[SPI] parse fail rx=%u expected=%u sof=0x%02X type=0x%02X plen=%u\r\n",
|
||||||
|
rx_len, expected, s_rxStream[0], s_rxStream[1],
|
||||||
|
(u16)(s_rxStream[2] | ((u16)s_rxStream[3] << 8)));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
isrLogFrameEx("[PARSE-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
isrLogFrameEx("[PARSE-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame_type == SPI_OTA_CMD_BOOT) {
|
if (frame_type == SPI_OTA_CMD_BOOT) {
|
||||||
if (s_otaHandler != nullptr && s_otaHandler->isActive()) {
|
/* 帧式 0xB0 与裸字节 0xB0 行为一致:始终回裸字节 0xB5 + resetToIdle()。
|
||||||
s_rxStreamLen = 0U;
|
* 旧代码在 isActive() 时静默吞帧且回 0xA5 帧式 ACK,违反
|
||||||
s_isrResponded = true;
|
* "0xB0 始终回 0xB5" 约束,且与裸字节路径不一致(0xA5 vs 0xB5)。
|
||||||
return;
|
* ESP32 appRequestBootloader() 只读首字节期望 0xB5,帧式 ACK 永不匹配。 */
|
||||||
}
|
u8 ack = 0xB5U;
|
||||||
u8 ack_ok = 0xA5U;
|
queueTxFrame(&ack, 1U);
|
||||||
u8 ack_frame[kTxBufSize] = {};
|
|
||||||
u16 ack_len = 0U;
|
|
||||||
if (buildFrame(SPI_OTA_RSP_ACK, &ack_ok, 1U, ack_frame, &ack_len)) {
|
|
||||||
queueTxFrame(ack_frame, ack_len);
|
|
||||||
}
|
|
||||||
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
|
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
|
||||||
s_otaSessionActive = false;
|
s_otaSessionActive = false;
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
@@ -308,14 +363,21 @@ void SpiOta::configureGpio() {
|
|||||||
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
|
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
|
||||||
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
|
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
|
||||||
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
|
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
|
||||||
/* PB6 复用为 SPI Ready 握手输出,默认 HIGH(就绪),允许主机立刻发起首帧。
|
/* PB6 复用为 SPI Ready 握手线(开漏输出,线与)。
|
||||||
* 与 DCU_APP app_spi_command.cpp configureGpio 行为一致,
|
* 开漏模式:写 BRR = 下拉驱动低;写 BSRR = 高阻释放,靠外部/对端上拉拉高。
|
||||||
* 适配 ESP32 GPIO10 输入监测 PB6 电平的硬件握手时序。 */
|
* 默认释放(HIGH),允许主机立刻发起首帧。
|
||||||
gpio_init(CONFIG_SPI_READY_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_SPI_READY_PIN);
|
* 与 DCU_APP app_spi_command.cpp configureGpio 行为一致。
|
||||||
|
*
|
||||||
|
* 方案A: 此处 PB6 从 main() 极早的推挽 LOW 切换为开漏 HIGH,
|
||||||
|
* 产生 LOW→HIGH 上升沿 = "Bootloader 就绪"信号,
|
||||||
|
* ESP32 waitForBootloaderReady() 据此唤醒并发起握手。 */
|
||||||
|
gpio_init(CONFIG_SPI_READY_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, CONFIG_SPI_READY_PIN);
|
||||||
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpiOta::setSpiReady(bool ready) {
|
void SpiOta::setSpiReady(bool ready) {
|
||||||
|
/* 开漏线与:ready=true 释放(BSRR,高阻,靠上拉拉高);
|
||||||
|
* ready=false 下拉(BRR,驱动低)。 */
|
||||||
if (ready) {
|
if (ready) {
|
||||||
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
||||||
} else {
|
} else {
|
||||||
@@ -323,6 +385,46 @@ void SpiOta::setSpiReady(bool ready) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
* BOOT 主动按铃:下拉 SPIready 请求 ESP32 来读
|
||||||
|
* ============================================================ */
|
||||||
|
|
||||||
|
bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len) {
|
||||||
|
if (s_ringPending) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 frame_buf[kTxBufSize];
|
||||||
|
u16 built_len = 0U;
|
||||||
|
if (!buildFrame((spi_ota_frame_type_t)frame_type, payload, payload_len, frame_buf, &built_len)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t primask = enterCritical();
|
||||||
|
queueTxFrame(frame_buf, built_len);
|
||||||
|
s_ringTxLen = built_len;
|
||||||
|
s_ringPending = true;
|
||||||
|
s_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||||
|
setSpiReady(false);
|
||||||
|
exitCritical(primask);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpiOta::cancelRing() {
|
||||||
|
if (!s_ringPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t primask = enterCritical();
|
||||||
|
s_ringPending = false;
|
||||||
|
s_ringTxLen = 0U;
|
||||||
|
s_txPending = false;
|
||||||
|
s_txLen = 0U;
|
||||||
|
s_txStreamPos = 0U;
|
||||||
|
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
|
||||||
|
setSpiReady(true);
|
||||||
|
exitCritical(primask);
|
||||||
|
}
|
||||||
|
|
||||||
void SpiOta::configureSpi0() {
|
void SpiOta::configureSpi0() {
|
||||||
rcu_periph_clock_enable(RCU_SPI0);
|
rcu_periph_clock_enable(RCU_SPI0);
|
||||||
spi_i2s_deinit(SPI0);
|
spi_i2s_deinit(SPI0);
|
||||||
@@ -477,15 +579,33 @@ void SpiOta::onCsRelease() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 如果这是 ESP32 响应 BOOT 主动按铃来读请求帧,直接清空按铃状态并释放 ready。
|
||||||
|
* 按铃帧已在 CS 期间通过 DMA 输出到 MISO,不需要再解析本次接收到的 dummy 字节。 */
|
||||||
|
if (s_ringPending) {
|
||||||
|
s_ringPending = false;
|
||||||
|
s_ringTxLen = 0U;
|
||||||
|
s_txPending = false;
|
||||||
|
s_txLen = 0U;
|
||||||
|
s_txStreamPos = 0U;
|
||||||
|
s_rxStreamLen = 0U;
|
||||||
|
s_isrResponded = true;
|
||||||
|
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
|
||||||
|
setSpiReady(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (s_rxStreamLen > 0U && !s_isrResponded) {
|
if (s_rxStreamLen > 0U && !s_isrResponded) {
|
||||||
isrProcessFrame();
|
isrProcessFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* BOOT 在 ISR 内同步完成帧处理,响应帧已就绪(或为空轮询/残包无需响应),
|
/* BOOT 在 ISR 内同步完成帧处理,响应帧已就绪(或为空轮询/残包无需响应),
|
||||||
* 拉高 PB6 通知主机可发起下一笔事务。
|
* 拉高 PB6 通知主机可发起下一笔事务。
|
||||||
* 与 DCU_APP processCommand() 末尾 setSpiReady(true) 行为一致;
|
* flash_buf 满(g_localCredit==0)时不拉高,等 drainFlashBuffer 写完 Flash
|
||||||
* 空轮询事务同样恢复 HIGH,避免 PB6 卡 LOW 导致 ESP32 waitSpiReady 超时。 */
|
* 再拉高,形成硬件级背压,避免 ESP32 疯狂重发导致 overrun。 */
|
||||||
setSpiReady(true);
|
if (g_localCredit > 0U) {
|
||||||
|
setSpiReady(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -503,12 +623,15 @@ void SpiOta::onSpiInterrupt() {
|
|||||||
if (s_rxStreamLen < kRxBufSize) {
|
if (s_rxStreamLen < kRxBufSize) {
|
||||||
s_rxStream[s_rxStreamLen++] = byte;
|
s_rxStream[s_rxStreamLen++] = byte;
|
||||||
}
|
}
|
||||||
tryProcessCompleteFrame();
|
/* 帧处理统一在 onCsRelease() (EXTI CS 释放中断) 里执行,
|
||||||
|
* 不在 RBNE 中断内调 isrProcessFrame(),避免 CRC32+AES+buildFrame
|
||||||
|
* 重操作(10-20us)期间 SPI 外设继续收字节导致 RXORERR。 */
|
||||||
}
|
}
|
||||||
/* TBE 已由 DMA 替代,不再处理 */
|
/* TBE 已由 DMA 替代,不再处理 */
|
||||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||||
(void)spi_i2s_data_receive(SPI0);
|
(void)spi_i2s_data_receive(SPI0);
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
|
isrLogFrameEx("[RX-OVR]", NULL, 0U);
|
||||||
}
|
}
|
||||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||||
@@ -537,7 +660,18 @@ void SpiOta::poll() {
|
|||||||
s_logReadIdx = (s_logReadIdx + 1U) % kLogSlotCount;
|
s_logReadIdx = (s_logReadIdx + 1U) % kLogSlotCount;
|
||||||
s_logCount--;
|
s_logCount--;
|
||||||
__enable_irq();
|
__enable_irq();
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
printf("[SPI] %s\r\n", (const char*)s_logSlots[s_logReadIdx]);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
/* 按铃超时看护:ESP32 未在 kRingTimeoutMs 内来读则放弃本次按铃,
|
||||||
|
* 防止永久占用 SPIready 线导致双方死锁。 */
|
||||||
|
if (s_ringPending) {
|
||||||
|
if ((int32_t)(SysTickTimer::getMs() - s_ringDeadline) >= 0) {
|
||||||
|
cancelRing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
|
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ typedef enum {
|
|||||||
|
|
||||||
class SpiOta {
|
class SpiOta {
|
||||||
public:
|
public:
|
||||||
static constexpr u8 kSof = 0xA5U;
|
|
||||||
static constexpr u16 kRxBufSize = 256U;
|
static constexpr u16 kRxBufSize = 256U;
|
||||||
static constexpr u16 kTxBufSize = 128U;
|
static constexpr u16 kTxBufSize = 128U;
|
||||||
static constexpr u16 kFrameOverhead = 5U;
|
|
||||||
static constexpr u16 kIsrLogBufSize = 128U;
|
static constexpr u16 kIsrLogBufSize = 128U;
|
||||||
|
|
||||||
static void init();
|
static void init();
|
||||||
@@ -46,6 +44,18 @@ public:
|
|||||||
static bool hasActiveSession();
|
static bool hasActiveSession();
|
||||||
static u16 copyTxFrame(u8 *out, u16 max_len);
|
static u16 copyTxFrame(u8 *out, u16 max_len);
|
||||||
|
|
||||||
|
/** @brief BOOT 主动按铃:构建请求帧并拉低 SPIready,通知 ESP32 来读。
|
||||||
|
* @param[in] frame_type 请求帧类型。
|
||||||
|
* @param[in] payload 请求帧负载。
|
||||||
|
* @param[in] payload_len 负载长度。
|
||||||
|
* @return true 按铃成功,false 当前已有按铃未处理或帧构建失败。
|
||||||
|
* @note 与 DCU_APP AppSpiCommand::requestEsp32Read 对齐。
|
||||||
|
*/
|
||||||
|
static bool requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len);
|
||||||
|
|
||||||
|
/** @brief 取消当前按铃请求,释放 SPIready。 */
|
||||||
|
static void cancelRing();
|
||||||
|
|
||||||
/** @brief 翻转 SPI Ready 握手引脚(PB6)电平。
|
/** @brief 翻转 SPI Ready 握手引脚(PB6)电平。
|
||||||
* @param[in] ready true=就绪(HIGH), false=忙(LOW)。
|
* @param[in] ready true=就绪(HIGH), false=忙(LOW)。
|
||||||
* @note GPIO BSRR/BRR 写入原子,无需临界区;可由 ISR 与主循环共同调用。
|
* @note GPIO BSRR/BRR 写入原子,无需临界区;可由 ISR 与主循环共同调用。
|
||||||
@@ -53,7 +63,6 @@ public:
|
|||||||
static void setSpiReady(bool ready);
|
static void setSpiReady(bool ready);
|
||||||
|
|
||||||
static void isrProcessFrame();
|
static void isrProcessFrame();
|
||||||
static void tryProcessCompleteFrame();
|
|
||||||
static void isrLogFrame(const char *tag, const u8 *data, u16 len);
|
static void isrLogFrame(const char *tag, const u8 *data, u16 len);
|
||||||
|
|
||||||
static OTAHandler *s_otaHandler;
|
static OTAHandler *s_otaHandler;
|
||||||
@@ -66,11 +75,13 @@ public:
|
|||||||
static volatile bool s_txPending;
|
static volatile bool s_txPending;
|
||||||
static volatile bool s_isrResponded;
|
static volatile bool s_isrResponded;
|
||||||
static volatile bool s_otaSessionActive;
|
static volatile bool s_otaSessionActive;
|
||||||
|
static volatile bool s_ringPending; ///< 是否有未读走的主动按铃请求
|
||||||
|
static volatile u16 s_ringTxLen; ///< 按铃帧长度
|
||||||
|
static volatile u32 s_ringDeadline; ///< 按铃超时截止时间(ms)
|
||||||
|
|
||||||
static void sender(ota_link_tx_type_t type, u8 len, const u8 *data);
|
static void sender(ota_link_tx_type_t type, u8 len, const u8 *data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static u8 crc8(const u8 *data, u16 len);
|
|
||||||
static bool buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
|
static bool buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
|
||||||
u8 *out, u16 *out_len);
|
u8 *out, u16 *out_len);
|
||||||
static bool tryParseFrame(const u8 *frame, u16 frame_len,
|
static bool tryParseFrame(const u8 *frame, u16 frame_len,
|
||||||
@@ -83,6 +94,9 @@ private:
|
|||||||
static void exitCritical(uint32_t primask);
|
static void exitCritical(uint32_t primask);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* OTA.cpp 通过此变量更新 SPI 响应帧的 CREDIT 字段 */
|
||||||
|
extern volatile u8 g_localCredit;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+10
-1
@@ -1,4 +1,4 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "device/Can.h"
|
#include "device/Can.h"
|
||||||
#include "device/SpiOta.h"
|
#include "device/SpiOta.h"
|
||||||
#include "sys/config.h"
|
#include "sys/config.h"
|
||||||
@@ -333,6 +333,15 @@ void BootloaderSystem::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
/* 方案A: 极早夺回 PB6 控制权,拉低 SPIready 线。
|
||||||
|
* 复位后 GPIO 高阻,外部上拉会短暂拉高 PB6 (μs级);
|
||||||
|
* 此处尽快配为推挽输出 LOW,让 ESP32 能通过 LOW→HIGH 序列
|
||||||
|
* 识别 "Bootloader 已接管" → "Bootloader 就绪"。
|
||||||
|
* 后续 SpiOta::configureGpio() 会重配为开漏并释放 HIGH。 */
|
||||||
|
rcu_periph_clock_enable(RCU_GPIOB);
|
||||||
|
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
|
||||||
|
gpio_bit_reset(GPIOB, GPIO_PIN_6);
|
||||||
|
|
||||||
sys.initHardware();
|
sys.initHardware();
|
||||||
sys.run();
|
sys.run();
|
||||||
|
|
||||||
|
|||||||
+89
-31
@@ -1,4 +1,4 @@
|
|||||||
#include "OTA.h"
|
#include "OTA.h"
|
||||||
#include "Can.h"
|
#include "Can.h"
|
||||||
#include "SpiOta.h"
|
#include "SpiOta.h"
|
||||||
#include "aes128.h"
|
#include "aes128.h"
|
||||||
@@ -242,6 +242,11 @@ static inline void write_u32_to_4bytes(uint8_t *array, uint32_t value) {
|
|||||||
*/
|
*/
|
||||||
static inline void send_structured_ack(uint32_t seq_or_flag) {
|
static inline void send_structured_ack(uint32_t seq_or_flag) {
|
||||||
u8 send_data[6] = {0xBA, 0xC0, 0x00, 0x00, 0x00, 0x00};
|
u8 send_data[6] = {0xBA, 0xC0, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
if (seq_or_flag <= 5U || seq_or_flag == 0xF0F0F0F0U) {
|
||||||
|
printf("[OTA] send_structured_ack seq=%u\r\n", (unsigned)seq_or_flag);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
write_u32_to_4bytes(&send_data[2], seq_or_flag);
|
write_u32_to_4bytes(&send_data[2], seq_or_flag);
|
||||||
send_ack_frame(6, send_data);
|
send_ack_frame(6, send_data);
|
||||||
}
|
}
|
||||||
@@ -317,6 +322,39 @@ void OTAHandler::process() {
|
|||||||
pending_finish_ack = false;
|
pending_finish_ack = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** FINISH_OTA 已在 ISR 里回 ACK,等 Flash 缓冲 drain 完再做校验/CRC/写标志/跳转,
|
||||||
|
* 避免在 CS 释放 ISR 里长时间阻塞 SPIready。 */
|
||||||
|
if (pending_finish_ota && flash_buf_cnt == 0U) {
|
||||||
|
pending_finish_ota = false;
|
||||||
|
|
||||||
|
if (!validateApplication()) {
|
||||||
|
ota_error = OTA_ERROR_FLASH_ERROR;
|
||||||
|
ota_state = OTA_STATE_IDLE;
|
||||||
|
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
|
||||||
|
} else {
|
||||||
|
/** 计算并写入 A区固件 CRC32,供后续启动时完整性校验。 */
|
||||||
|
u32 latest_crc = calculateAppCrc(APPLATEST_START_ADDR, APP_FIRMWARE_SIZE / 4U);
|
||||||
|
writeAppCrc(APPLATEST_START_ADDR, latest_crc);
|
||||||
|
|
||||||
|
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_AVAL);
|
||||||
|
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
|
||||||
|
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
|
||||||
|
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
/* OTA 完成后立即 dump Reset_Handler 地址 */
|
||||||
|
{
|
||||||
|
uint32_t rh_addr = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U) & ~1U;
|
||||||
|
printf("[OTA-DONE2] RH@0x%08lX: ", (unsigned long)rh_addr);
|
||||||
|
const uint8_t *p = (const uint8_t *)rh_addr;
|
||||||
|
for (int i = 0; i < 16; i++) printf("%02X ", p[i]);
|
||||||
|
printf("\r\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pending_app_jump = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (pending_app_jump) {
|
if (pending_app_jump) {
|
||||||
pending_app_jump = false;
|
pending_app_jump = false;
|
||||||
delay_1ms(20);
|
delay_1ms(20);
|
||||||
@@ -580,6 +618,11 @@ void OTAHandler::drainFlashBuffer() {
|
|||||||
#if DEBUG_PRINT
|
#if DEBUG_PRINT
|
||||||
u32 drain_cnt = 0;
|
u32 drain_cnt = 0;
|
||||||
#endif
|
#endif
|
||||||
|
if (flash_buf_cnt == 0U) { return; }
|
||||||
|
/* Flash 写入期间拉低 PB6,阻止 ESP32 发起新 SPI 事务。
|
||||||
|
* fmc_word_program 会让 CPU 总线停顿,SPI RBNE ISR 来不及响应 → RX-OVR。
|
||||||
|
* 写完 drain 后恢复 HIGH,ESP32 的 PB6 等待循环会自动放行。 */
|
||||||
|
SpiOta::setSpiReady(false);
|
||||||
while (1) {
|
while (1) {
|
||||||
FlashSlot slot = {};
|
FlashSlot slot = {};
|
||||||
bool has_slot = false;
|
bool has_slot = false;
|
||||||
@@ -596,6 +639,8 @@ void OTAHandler::drainFlashBuffer() {
|
|||||||
has_slot = true;
|
has_slot = true;
|
||||||
}
|
}
|
||||||
__enable_irq();
|
__enable_irq();
|
||||||
|
/* 缓冲被消费,恢复 CREDIT 通知 ESP32 可继续发数据。 */
|
||||||
|
g_localCredit = (u8)(kFlashBufSlots - flash_buf_cnt);
|
||||||
|
|
||||||
if (!has_slot) {
|
if (!has_slot) {
|
||||||
break;
|
break;
|
||||||
@@ -606,6 +651,10 @@ void OTAHandler::drainFlashBuffer() {
|
|||||||
drain_cnt++;
|
drain_cnt++;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
/* Flash 写入完成,恢复 CREDIT 并释放 PB6。
|
||||||
|
* ESP32 检测到 PB6 拉高后会继续发数据包。 */
|
||||||
|
g_localCredit = (u8)(kFlashBufSlots - flash_buf_cnt);
|
||||||
|
SpiOta::setSpiReady(true);
|
||||||
#if DEBUG_PRINT
|
#if DEBUG_PRINT
|
||||||
if (drain_cnt > 0U) {
|
if (drain_cnt > 0U) {
|
||||||
printf("[FW] drain %u pkts\r\n", (unsigned)drain_cnt);
|
printf("[FW] drain %u pkts\r\n", (unsigned)drain_cnt);
|
||||||
@@ -694,15 +743,32 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
*/
|
*/
|
||||||
if (len == 64U && ota_state == OTA_STATE_RECEIVING_FIRMWARE) {
|
if (len == 64U && ota_state == OTA_STATE_RECEIVING_FIRMWARE) {
|
||||||
u32 receive_session_num = extract_u32_from_4bytes(data);
|
u32 receive_session_num = extract_u32_from_4bytes(data);
|
||||||
(void)receive_session_num;
|
u32 expected_session_num = extract_u32_from_4bytes(nonce_server);
|
||||||
|
if (receive_session_num != expected_session_num) {
|
||||||
|
ota_error = OTA_ERROR_INVALID_PACKET;
|
||||||
|
send_error_frame(extract_u32_from_4bytes(data + 4), OTA_ERROR_INVALID_PACKET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
u32 receive_packet_num = extract_u32_from_4bytes(data + 4);
|
u32 receive_packet_num = extract_u32_from_4bytes(data + 4);
|
||||||
u32 receive_IFEND = extract_u32_from_4bytes(data + 56);
|
u32 receive_IFEND = extract_u32_from_4bytes(data + 56);
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
if (receive_packet_num <= 5U) {
|
||||||
|
printf("[OTA] DATA in: seq=%u next=%u flash_cnt=%u\r\n",
|
||||||
|
(unsigned)receive_packet_num, (unsigned)next_expected_packet, (unsigned)flash_buf_cnt);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
const u8 *receive_encrypted_data = data + 8;
|
const u8 *receive_encrypted_data = data + 8;
|
||||||
const u8 *receive_dataCRC32 = data + 60;
|
const u8 *receive_dataCRC32 = data + 60;
|
||||||
|
|
||||||
/** 去重:跳过已经确认接收过的包号(ESP32 超时重传)。 */
|
/** 去重:跳过已经确认接收过的包号(ESP32 超时重传)。 */
|
||||||
if (receive_packet_num < next_expected_packet) {
|
if (receive_packet_num < next_expected_packet) {
|
||||||
u32 request_packet = receive_packet_num + 1U;
|
u32 request_packet = receive_packet_num + 1U;
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
if (receive_packet_num <= 5U) {
|
||||||
|
printf("[OTA] DATA dup seq=%u (next=%u) -> ack %u\r\n",
|
||||||
|
(unsigned)receive_packet_num, (unsigned)next_expected_packet, (unsigned)request_packet);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
send_structured_ack(request_packet);
|
send_structured_ack(request_packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -717,6 +783,9 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
/** 校验分包 CRC32(密钥参与 CRC,防止简单重放/篡改)。 */
|
/** 校验分包 CRC32(密钥参与 CRC,防止简单重放/篡改)。 */
|
||||||
calculate_crc32_with_key(current_K_session, decrypted_data, calculated_dataCRC32);
|
calculate_crc32_with_key(current_K_session, decrypted_data, calculated_dataCRC32);
|
||||||
if (memcmp(receive_dataCRC32, calculated_dataCRC32, SIZE_U32) != 0) {
|
if (memcmp(receive_dataCRC32, calculated_dataCRC32, SIZE_U32) != 0) {
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
printf("[OTA] DATA crc err seq=%u\r\n", (unsigned)receive_packet_num);
|
||||||
|
#endif
|
||||||
ota_error = OTA_ERROR_INVALID_CRC;
|
ota_error = OTA_ERROR_INVALID_CRC;
|
||||||
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_CRC);
|
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_CRC);
|
||||||
return;
|
return;
|
||||||
@@ -729,6 +798,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (flash_buf_cnt >= kFlashBufSlots) {
|
if (flash_buf_cnt >= kFlashBufSlots) {
|
||||||
|
g_localCredit = 0U;
|
||||||
send_wait_frame(1, (const u8 *)"\x03");
|
send_wait_frame(1, (const u8 *)"\x03");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -739,7 +809,17 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
__disable_irq();
|
__disable_irq();
|
||||||
flash_buf_cnt++;
|
flash_buf_cnt++;
|
||||||
__enable_irq();
|
__enable_irq();
|
||||||
|
/* 更新 CREDIT:剩余可接收槽位。缓冲将满时置 0,
|
||||||
|
* ESP32 收到 CREDIT=0 后会改为 POLL 等待而非盲目重发。 */
|
||||||
|
g_localCredit = (flash_buf_cnt >= kFlashBufSlots) ? 0U
|
||||||
|
: (u8)(kFlashBufSlots - flash_buf_cnt);
|
||||||
next_expected_packet = receive_packet_num + 1U;
|
next_expected_packet = receive_packet_num + 1U;
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
if (receive_packet_num <= 5U) {
|
||||||
|
printf("[OTA] DATA ok seq=%u -> ack %u\r\n",
|
||||||
|
(unsigned)receive_packet_num, (unsigned)next_expected_packet);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -788,35 +868,13 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
/** 结束态下要求显式收到 0x0A AC,确认可以切换到 APP。 */
|
/** 结束态下要求显式收到 0x0A AC,确认可以切换到 APP。 */
|
||||||
else if (len == 2U && ota_state == OTA_STATE_FINISHED) {
|
else if (len == 2U && ota_state == OTA_STATE_FINISHED) {
|
||||||
if (data[0] == 0x0AU && data[1] == 0xACU) {
|
if (data[0] == 0x0AU && data[1] == 0xACU) {
|
||||||
if (!validateApplication()) {
|
|
||||||
ota_error = OTA_ERROR_FLASH_ERROR;
|
|
||||||
ota_state = OTA_STATE_IDLE;
|
|
||||||
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 计算并写入 A区固件 CRC32,供后续启动时完整性校验。 */
|
|
||||||
u32 latest_crc = calculateAppCrc(APPLATEST_START_ADDR, APP_FIRMWARE_SIZE / 4U);
|
|
||||||
writeAppCrc(APPLATEST_START_ADDR, latest_crc);
|
|
||||||
|
|
||||||
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_AVAL);
|
|
||||||
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
|
|
||||||
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
|
|
||||||
|
|
||||||
#if DEBUG_PRINT
|
|
||||||
/* OTA 完成后立即 dump Reset_Handler 地址 */
|
|
||||||
{
|
|
||||||
uint32_t rh_addr = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U) & ~1U;
|
|
||||||
printf("[OTA-DONE2] RH@0x%08lX: ", (unsigned long)rh_addr);
|
|
||||||
const uint8_t *p = (const uint8_t *)rh_addr;
|
|
||||||
for (int i = 0; i < 16; i++) printf("%02X ", p[i]);
|
|
||||||
printf("\r\n");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
u8 finish_ack[2] = {0x0A, 0xAC};
|
u8 finish_ack[2] = {0x0A, 0xAC};
|
||||||
send_ack_frame(2, finish_ack);
|
send_ack_frame(2, finish_ack);
|
||||||
pending_app_jump = true;
|
/** 重活放到主循环 process() 里做,ISR 只回 ACK 并置标志,
|
||||||
|
* 避免 SPIready 被拉低太久导致 ESP32 超时。 */
|
||||||
|
if (!pending_finish_ota) {
|
||||||
|
pending_finish_ota = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,8 +894,8 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
else {
|
else {
|
||||||
ota_error = OTA_ERROR_INVALID_PACKET;
|
ota_error = OTA_ERROR_INVALID_PACKET;
|
||||||
#if DEBUG_PRINT
|
#if DEBUG_PRINT
|
||||||
printf("[OTA] INV_PKT: l=%u s=%u\r\n",
|
printf("[OTA] DATA ignored/INV_PKT: type=%u len=%u state=%u\r\n",
|
||||||
(unsigned)len, (unsigned)ota_state);
|
(unsigned)type, (unsigned)len, (unsigned)ota_state);
|
||||||
#endif
|
#endif
|
||||||
ota_state = OTA_STATE_IDLE;
|
ota_state = OTA_STATE_IDLE;
|
||||||
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET);
|
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET);
|
||||||
|
|||||||
@@ -328,9 +328,13 @@ extern "C" void EXTI5_9_IRQHandler(void)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief External interrupt handler for EXTI line group 10 to 15.
|
* @brief External interrupt handler for EXTI line group 10 to 15.
|
||||||
* @return none
|
*
|
||||||
|
* PA15 (SPI CS from ESP32) maps to EXTI_15, which fires this handler.
|
||||||
|
* Must directly dispatch to the SPI OTA CS handler — NOT through
|
||||||
|
* BootSleepManager::onExtiInterrupt(), which only handles EXTI_8
|
||||||
|
* (PB8 deep-sleep wake) and knows nothing about EXTI_15.
|
||||||
*/
|
*/
|
||||||
extern "C" void EXTI10_15_IRQHandler(void)
|
extern "C" void EXTI10_15_IRQHandler(void)
|
||||||
{
|
{
|
||||||
BootSleepManager::onExtiInterrupt();
|
SpiOta::onCsExtiInterrupt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
volatile uint32_t SysTickTimer::s_delayCountdown = 0U;
|
volatile uint32_t SysTickTimer::s_delayCountdown = 0U;
|
||||||
|
volatile uint32_t SysTickTimer::s_msTicks = 0U;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 配置 SysTick 为 1ms 周期中断。
|
* @brief 配置 SysTick 为 1ms 周期中断。
|
||||||
@@ -87,6 +88,7 @@ void SysTickTimer::decrement()
|
|||||||
if (s_delayCountdown != 0U) {
|
if (s_delayCountdown != 0U) {
|
||||||
s_delayCountdown--;
|
s_delayCountdown--;
|
||||||
}
|
}
|
||||||
|
s_msTicks++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,6 +117,16 @@ void SysTickTimer::softReset()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取上电以来的毫秒数。
|
||||||
|
*
|
||||||
|
* @return 毫秒计数。
|
||||||
|
*/
|
||||||
|
uint32_t SysTickTimer::getMs()
|
||||||
|
{
|
||||||
|
return s_msTicks;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Configure SysTick via the timer helper.
|
* @brief Configure SysTick via the timer helper.
|
||||||
* @return none
|
* @return none
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#ifndef SYSTICK_H
|
#ifndef SYSTICK_H
|
||||||
#define SYSTICK_H
|
#define SYSTICK_H
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -28,9 +28,13 @@ public:
|
|||||||
static void onInterrupt();
|
static void onInterrupt();
|
||||||
/** @brief 触发软件复位。 */
|
/** @brief 触发软件复位。 */
|
||||||
static void softReset();
|
static void softReset();
|
||||||
|
/** @brief 获取上电以来的毫秒数。
|
||||||
|
* @note 供按铃超时等非阻塞超时场景使用,不用于精确阻塞延时。 */
|
||||||
|
static uint32_t getMs();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static volatile uint32_t s_delayCountdown;
|
static volatile uint32_t s_delayCountdown;
|
||||||
|
static volatile uint32_t s_msTicks;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user