Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5d8c9798a | ||
|
|
1223be9ca9 |
+101
-63
@@ -186,7 +186,7 @@ void SpiOta::sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
|
|||||||
u8 frame_buf[kTxBufSize] = {};
|
u8 frame_buf[kTxBufSize] = {};
|
||||||
u16 built_len = 0U;
|
u16 built_len = 0U;
|
||||||
if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) {
|
if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) {
|
||||||
queueTxFrame(frame_buf, built_len);
|
submitResponse(frame_buf, built_len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ static u16 stopSpiDmas(void) {
|
|||||||
return got;
|
return got;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
void SpiOta::submitResponse(const u8 *frame, u16 len) {
|
||||||
if (!frame || len == 0U || len > kTxBufSize) { return; }
|
if (!frame || len == 0U || len > kTxBufSize) { return; }
|
||||||
uint32_t primask = enterCritical();
|
uint32_t primask = enterCritical();
|
||||||
memcpy(s_txBuf, frame, len);
|
memcpy(s_txBuf, frame, len);
|
||||||
@@ -297,7 +297,7 @@ void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
|||||||
/* ★ 不在此处启动 DMA。
|
/* ★ 不在此处启动 DMA。
|
||||||
*
|
*
|
||||||
* 本函数可从 RBNE ISR 中调用(isrProcessFrame → handleLinkMessage
|
* 本函数可从 RBNE ISR 中调用(isrProcessFrame → handleLinkMessage
|
||||||
* → send_structured_ack → sender → queueTxFrame),此时 CS 仍为 LOW,
|
* → send_structured_ack → sender → submitResponse),此时 CS 仍为 LOW,
|
||||||
* DMA 正在发送上一次 onCsAssert() 装载的旧响应。
|
* DMA 正在发送上一次 onCsAssert() 装载的旧响应。
|
||||||
*
|
*
|
||||||
* 若在此处 startSpiTxDma(),GD32 DMA 控制器会先 CHEN=0 再 CHEN=1,
|
* 若在此处 startSpiTxDma(),GD32 DMA 控制器会先 CHEN=0 再 CHEN=1,
|
||||||
@@ -358,7 +358,7 @@ void SpiOta::isrProcessFrame() {
|
|||||||
(uint8_t)g_localCredit,
|
(uint8_t)g_localCredit,
|
||||||
(uint8_t)s_txSeq, hdr.tx_seq,
|
(uint8_t)s_txSeq, hdr.tx_seq,
|
||||||
hello_ack, &built)) {
|
hello_ack, &built)) {
|
||||||
queueTxFrame(hello_ack, (u16)built);
|
submitResponse(hello_ack, (u16)built);
|
||||||
/* 缓存 HELLO_ACK 以应对 ESP32 重试同一 HELLO。 */
|
/* 缓存 HELLO_ACK 以应对 ESP32 重试同一 HELLO。 */
|
||||||
memcpy(s_lastRespBuf, hello_ack, built);
|
memcpy(s_lastRespBuf, hello_ack, built);
|
||||||
s_lastRespLen = (u16)built;
|
s_lastRespLen = (u16)built;
|
||||||
@@ -380,7 +380,7 @@ void SpiOta::isrProcessFrame() {
|
|||||||
(uint8_t)g_localCredit, V2_ERR_SESSION,
|
(uint8_t)g_localCredit, V2_ERR_SESSION,
|
||||||
(uint8_t)s_expectedRxSeq,
|
(uint8_t)s_expectedRxSeq,
|
||||||
err_frame, &built)) {
|
err_frame, &built)) {
|
||||||
queueTxFrame(err_frame, (u16)built);
|
submitResponse(err_frame, (u16)built);
|
||||||
}
|
}
|
||||||
isrLogFrameEx("[SES-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
isrLogFrameEx("[SES-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
@@ -394,12 +394,19 @@ void SpiOta::isrProcessFrame() {
|
|||||||
s_expectedRxSeq = (u8)(s_expectedRxSeq + 1U);
|
s_expectedRxSeq = (u8)(s_expectedRxSeq + 1U);
|
||||||
} else if (dist >= 128U) {
|
} else if (dist >= 128U) {
|
||||||
/* 重复请求(rx_seq 落后于 expected):重发上一条缓存响应,
|
/* 重复请求(rx_seq 落后于 expected):重发上一条缓存响应,
|
||||||
* 不推进序号。若 DATA 仍在主循环排队,回 WAIT 让 ESP32 继续 poll。 */
|
* 不推进序号。若 DATA 仍在主循环排队,回 WAIT 让 ESP32 继续 poll。
|
||||||
|
*
|
||||||
|
* 注意:缓存若仍是 ISR 即时 WAIT,而主循环已处理完并写过 ACK,
|
||||||
|
* 应优先回放 ACK(type=0x80),避免 ESP32 长时间只见 WAIT → 断续。 */
|
||||||
if (s_hasCachedResp && s_lastRespLen > 0U) {
|
if (s_hasCachedResp && s_lastRespLen > 0U) {
|
||||||
queueTxFrame(s_lastRespBuf, s_lastRespLen);
|
submitResponse(s_lastRespBuf, s_lastRespLen);
|
||||||
} else if (s_otaDataCnt > 0U) {
|
} else if (s_otaDataCnt > 0U) {
|
||||||
u8 wait_hint = 0x03U;
|
u8 wait_hint = 0x03U;
|
||||||
sender(OTA_LINK_TX_WAIT, 1U, &wait_hint);
|
sender(OTA_LINK_TX_WAIT, 1U, &wait_hint);
|
||||||
|
} else if (s_otaHandler != nullptr && s_otaHandler->isActive()) {
|
||||||
|
/* 无缓存但会话仍活跃:回 WAIT 促使 ESP32 继续 poll,勿静默丢弃 */
|
||||||
|
u8 wait_hint = 0x03U;
|
||||||
|
sender(OTA_LINK_TX_WAIT, 1U, &wait_hint);
|
||||||
}
|
}
|
||||||
isrLogFrameEx("[DUP-SEQ]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
isrLogFrameEx("[DUP-SEQ]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
@@ -412,7 +419,7 @@ void SpiOta::isrProcessFrame() {
|
|||||||
(uint8_t)g_localCredit, V2_ERR_SEQ_JUMP,
|
(uint8_t)g_localCredit, V2_ERR_SEQ_JUMP,
|
||||||
(uint8_t)s_expectedRxSeq,
|
(uint8_t)s_expectedRxSeq,
|
||||||
err_frame, &built)) {
|
err_frame, &built)) {
|
||||||
queueTxFrame(err_frame, (u16)built);
|
submitResponse(err_frame, (u16)built);
|
||||||
}
|
}
|
||||||
isrLogFrameEx("[SEQ-JMP]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
isrLogFrameEx("[SEQ-JMP]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
@@ -425,7 +432,7 @@ void SpiOta::isrProcessFrame() {
|
|||||||
u8 ack_frame[kTxBufSize] = {};
|
u8 ack_frame[kTxBufSize] = {};
|
||||||
u16 built = 0U;
|
u16 built = 0U;
|
||||||
if (buildFrame(SPI_OTA_RSP_ACK, nullptr, 0U, ack_frame, &built)) {
|
if (buildFrame(SPI_OTA_RSP_ACK, nullptr, 0U, ack_frame, &built)) {
|
||||||
queueTxFrame(ack_frame, built);
|
submitResponse(ack_frame, built);
|
||||||
/* 缓存 ACK 响应以应对重复的 0xB0。 */
|
/* 缓存 ACK 响应以应对重复的 0xB0。 */
|
||||||
memcpy(s_lastRespBuf, ack_frame, built);
|
memcpy(s_lastRespBuf, ack_frame, built);
|
||||||
s_lastRespLen = built;
|
s_lastRespLen = built;
|
||||||
@@ -455,7 +462,7 @@ void SpiOta::isrProcessFrame() {
|
|||||||
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);
|
submitResponse(err_frame, built);
|
||||||
}
|
}
|
||||||
isrLogFrameEx("[UNK-TYPE]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
isrLogFrameEx("[UNK-TYPE]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
@@ -575,20 +582,41 @@ bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len)
|
|||||||
if (s_ringPending) {
|
if (s_ringPending) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
/* 已有 OTA ACK/WAIT 待发时禁止 CREDIT_UPDATE 等按铃覆盖 s_txBuf,
|
||||||
|
* 否则 ESP32 读到 EVENT 而非 ACK → 超时重发 → DUP-SEQ 断续。
|
||||||
|
* CREDIT 仍随下一笔 RESPONSE 帧头更新。 */
|
||||||
|
if (s_txPending && s_txLen > 0U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!s_linkState.canRingNow()) {
|
if (!s_linkState.canRingNow()) {
|
||||||
/* ESP32 尚未完成上一帧读取(PB6 未出现上升沿),拒绝发起按铃,
|
/* ESP32 尚未完成上一帧读取(PB6 未出现上升沿),拒绝发起按铃,
|
||||||
* 避免下降沿被 ESP32 ISR 在非 IDLE 态丢弃。 */
|
* 避免下降沿被 ESP32 ISR 在非 IDLE 态丢弃。 */
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (payload_len > kBootMaxPayload) {
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* L5:主动上报必须用 EVENT(非 RESPONSE),与 App requestEsp32Read 对齐。 */
|
||||||
|
u8 frame_buf[kTxBufSize];
|
||||||
|
size_t built = 0U;
|
||||||
|
if (!v2_build_frame(frame_type, V2_FLAG_EVENT,
|
||||||
|
(uint32_t)s_sessionId,
|
||||||
|
(uint8_t)s_txSeq, 0U, (uint8_t)g_localCredit,
|
||||||
|
payload, (size_t)payload_len,
|
||||||
|
frame_buf, &built)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (built > kTxBufSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u16 built_len = (u16)built;
|
||||||
|
if (s_v2Active) {
|
||||||
|
s_txSeq = (u8)(s_txSeq + 1U);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t primask = enterCritical();
|
uint32_t primask = enterCritical();
|
||||||
queueTxFrame(frame_buf, built_len);
|
submitResponse(frame_buf, built_len);
|
||||||
s_ringTxLen = built_len;
|
s_ringTxLen = built_len;
|
||||||
s_ringPending = true;
|
s_ringPending = true;
|
||||||
s_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
s_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||||
@@ -598,6 +626,17 @@ bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpiOta::notifyCreditRestore() {
|
||||||
|
static u8 s_prevCredit = 8U;
|
||||||
|
const u8 cur = g_localCredit;
|
||||||
|
if (s_prevCredit == 0U && cur > 0U) {
|
||||||
|
const u8 pl[1] = { cur };
|
||||||
|
/* 队列忙时失败可接受:下一笔响应帧头仍带 CREDIT,ESP32 会更新 */
|
||||||
|
(void)requestEsp32Read(V2_TYPE_CREDIT_UPDATE, pl, 1U);
|
||||||
|
}
|
||||||
|
s_prevCredit = cur;
|
||||||
|
}
|
||||||
|
|
||||||
void SpiOta::cancelRing() {
|
void SpiOta::cancelRing() {
|
||||||
if (!s_ringPending) {
|
if (!s_ringPending) {
|
||||||
return;
|
return;
|
||||||
@@ -703,27 +742,6 @@ void SpiOta::configureExti() {
|
|||||||
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
|
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpiOta::configureSpiReadyExti() {
|
|
||||||
/* EXTI6 上升沿:ESP32 releaseReady 通知 BOOT "读取完成"。
|
|
||||||
* EXTI5_9_IRQn 已被 BootSleepManager 以 (0,0) 占用(PB8 深睡唤醒)。
|
|
||||||
* 此处显式使能 EXTI5_9 NVIC 并设置抢占优先级 (3,1),低于 CS 中断 (1,1)
|
|
||||||
* 与 SPI0 RBNE (2,0),确保 Ready 上升沿不会打断正在进行的帧处理。
|
|
||||||
* 中断入口在 EXTI5_9_IRQHandler 中分发到 onSpiReadyExti()。 */
|
|
||||||
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_6);
|
|
||||||
exti_init(EXTI_6, EXTI_INTERRUPT, EXTI_TRIG_RISING);
|
|
||||||
exti_interrupt_enable(EXTI_6);
|
|
||||||
exti_interrupt_flag_clear(EXTI_6);
|
|
||||||
nvic_irq_enable(EXTI5_9_IRQn, 3U, 1U);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpiOta::onSpiReadyExti() {
|
|
||||||
/* v2 DATA_READY: ESP32 不再操作 PB6,EXTI6 上升沿不再有意义。
|
|
||||||
* 仅清理可能残留的挂起标志,避免 NVIC 重复进入。 */
|
|
||||||
if (exti_interrupt_flag_get(EXTI_6) != RESET) {
|
|
||||||
exti_interrupt_flag_clear(EXTI_6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpiOta::init() {
|
void SpiOta::init() {
|
||||||
s_csActive = false;
|
s_csActive = false;
|
||||||
s_rxStreamLen = 0U;
|
s_rxStreamLen = 0U;
|
||||||
@@ -749,7 +767,6 @@ void SpiOta::init() {
|
|||||||
configureDma();
|
configureDma();
|
||||||
configureSpi0();
|
configureSpi0();
|
||||||
configureExti();
|
configureExti();
|
||||||
configureSpiReadyExti();
|
|
||||||
s_linkState.reset();
|
s_linkState.reset();
|
||||||
/* 首笔 CS 到来前预装 DMA,避免 EXTI 晚于首 SCK */
|
/* 首笔 CS 到来前预装 DMA,避免 EXTI 晚于首 SCK */
|
||||||
armSpiDmasForNextCs();
|
armSpiDmasForNextCs();
|
||||||
@@ -797,14 +814,35 @@ void SpiOta::onCsRelease() {
|
|||||||
* armSpiDmasForNextCs() 装载后并不清除 s_txPending,若此处不标记交付,
|
* armSpiDmasForNextCs() 装载后并不清除 s_txPending,若此处不标记交付,
|
||||||
* pending 会永远为 true → updateDataReady() 把 PB6 永久拉低 →
|
* pending 会永远为 true → updateDataReady() 把 PB6 永久拉低 →
|
||||||
* ESP32 waitSpiReady(等 HIGH) 每包空等 SPI_READY_TIMEOUT_MS,
|
* ESP32 waitSpiReady(等 HIGH) 每包空等 SPI_READY_TIMEOUT_MS,
|
||||||
* 1116 包 OTA 表现为“卡在 1/1116 十几分钟”。
|
* 1116 包 OTA 表现为"卡在 1/1116 十几分钟"。
|
||||||
* 先清交付态;随后 isrProcessFrame/queueTxFrame 可再置 pending。
|
* 先清交付态;随后 isrProcessFrame/submitResponse 可再置 pending。
|
||||||
* 重复帧缓存在 s_lastRespBuf,不依赖 s_txPending 保活。 */
|
* 重复帧缓存在 s_lastRespBuf,不依赖 s_txPending 保活。
|
||||||
|
*
|
||||||
|
* ★ 修复 ACK 丢失死锁(2026-07-14):
|
||||||
|
* 旧代码无条件清 s_txPending,ESP32 poll 盲读(0xFF dummy)也会清掉
|
||||||
|
* GD32 待发 ACK。如果 ESP32 读到 ACK 但 CRC 解析失败,ACK 永久丢失,
|
||||||
|
* ESP32 永远等不到该 ACK → 卡死。
|
||||||
|
* 修复:只在 ESP32 发送了有效 v2 请求帧(含 SOF0/SOF1)时才清交付态;
|
||||||
|
* 纯 dummy poll(全 0xFF / 无 SOF)保留 s_txPending,让 ESP32 下次
|
||||||
|
* poll 能重新读到同一个 ACK。 */
|
||||||
|
bool has_v2_request = false;
|
||||||
|
if (s_rxStreamLen >= 2U) {
|
||||||
|
const u16 chk = (s_rxStreamLen < 16U) ? (u16)(s_rxStreamLen - 1U) : 16U;
|
||||||
|
for (u16 i = 0U; i < chk; ++i) {
|
||||||
|
if (s_rxStream[i] == V2_SOF0 && s_rxStream[i + 1U] == V2_SOF1) {
|
||||||
|
has_v2_request = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_v2_request) {
|
||||||
if (s_txPending && s_txLen > 0U) {
|
if (s_txPending && s_txLen > 0U) {
|
||||||
s_txPending = false;
|
s_txPending = false;
|
||||||
s_txLen = 0U;
|
s_txLen = 0U;
|
||||||
s_txStreamPos = 0U;
|
s_txStreamPos = 0U;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
/* dummy poll:保留 s_txPending,ACK 可被下次 CS 重新读走 */
|
||||||
|
|
||||||
/* 按铃中若 ESP32 发的是 v2 协议帧(HELLO/OTA 等),不能当 dummy 读吞掉 */
|
/* 按铃中若 ESP32 发的是 v2 协议帧(HELLO/OTA 等),不能当 dummy 读吞掉 */
|
||||||
if (s_ringPending) {
|
if (s_ringPending) {
|
||||||
@@ -917,9 +955,24 @@ void SpiOta::poll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (s_otaHandler != nullptr) {
|
if (s_otaHandler != nullptr) {
|
||||||
/* 先消化 ISR 入队的 64B DATA(AES+入 flash 槽),再 process 擦除/drain。 */
|
/* 先 process 排空 flash_buf,再消化 ISR 入队的 64B DATA。
|
||||||
|
* 旧顺序先 handleLinkMessage 再 process,当 flash_buf 满时
|
||||||
|
* handleLinkMessage 会丢弃当前包(ISR 已推进 seq),导致永久 DUP-SEQ。 */
|
||||||
|
/* 先消化 ISR DATA 队列并尽快回 ACK,再 process 写 Flash。
|
||||||
|
* 旧顺序先 drainFlash 会把 ACK 推迟到整批 fmc_word_program 之后,
|
||||||
|
* ESP32 侧易超时重发 → DUP-SEQ 断续。flash 满时 handleLinkMessage
|
||||||
|
* 内部仍会 drainFlashBuffer 腾槽,不会永久丢包。 */
|
||||||
u8 data_pkt[64];
|
u8 data_pkt[64];
|
||||||
u8 drained = 0U;
|
u8 drained = 0U;
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
/* 限频:满速 OTA 时每包 printf 会拖慢主循环、放大断续 */
|
||||||
|
static u32 s_poll_log_n = 0U;
|
||||||
|
if (s_otaDataCnt > 0U && ((++s_poll_log_n % 32U) == 1U)) {
|
||||||
|
printf("[POLL] otaData=%u txPending=%d txLen=%u credit=%u\r\n",
|
||||||
|
(unsigned)s_otaDataCnt, (int)s_txPending, (unsigned)s_txLen,
|
||||||
|
(unsigned)g_localCredit);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
while (drained < kOtaDataSlots && dequeueOtaData(data_pkt)) {
|
while (drained < kOtaDataSlots && dequeueOtaData(data_pkt)) {
|
||||||
s_otaHandler->setTransportSender(sender);
|
s_otaHandler->setTransportSender(sender);
|
||||||
s_otaHandler->handleLinkMessage(OTA_LINK_MSG_DATA, data_pkt, 64U);
|
s_otaHandler->handleLinkMessage(OTA_LINK_MSG_DATA, data_pkt, 64U);
|
||||||
@@ -930,6 +983,11 @@ void SpiOta::poll() {
|
|||||||
}
|
}
|
||||||
drained++;
|
drained++;
|
||||||
}
|
}
|
||||||
|
/* ACK 已入队:CS 空闲则立刻 re-arm + 拉低 PB6,再去做慢速 Flash */
|
||||||
|
if (!s_csActive && s_txPending && s_txLen > 0U) {
|
||||||
|
armSpiDmasForNextCs();
|
||||||
|
updateDataReady();
|
||||||
|
}
|
||||||
s_otaHandler->process();
|
s_otaHandler->process();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -942,30 +1000,12 @@ void SpiOta::poll() {
|
|||||||
|
|
||||||
void SpiOta::setSender(OTAHandler *handler) { s_otaHandler = handler; }
|
void SpiOta::setSender(OTAHandler *handler) { s_otaHandler = handler; }
|
||||||
|
|
||||||
void SpiOta::feedRxBytes(const u8 *data, u16 len) {
|
|
||||||
if (!data || len == 0U || !s_csActive) { return; }
|
|
||||||
if (len > kRxBufSize) { data += (len - kRxBufSize); len = kRxBufSize; }
|
|
||||||
memcpy(s_rxStream, data, len);
|
|
||||||
s_rxStreamLen = len;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SpiOta::hasTxFrame() { return s_txPending; }
|
bool SpiOta::hasTxFrame() { return s_txPending; }
|
||||||
|
|
||||||
bool SpiOta::hasActiveSession() { return s_otaSessionActive; }
|
|
||||||
|
|
||||||
u16 SpiOta::copyTxFrame(u8 *out, u16 max_len) {
|
u8 SpiOta::pendingDataCount() {
|
||||||
if (!out || max_len == 0U) { return 0U; }
|
return s_otaDataCnt;
|
||||||
uint32_t primask = enterCritical();
|
|
||||||
if (!s_txPending || s_txLen == 0U) { exitCritical(primask); return 0U; }
|
|
||||||
u16 copy_len = (s_txLen < max_len) ? s_txLen : max_len;
|
|
||||||
memcpy(out, s_txBuf, copy_len);
|
|
||||||
s_txPending = false;
|
|
||||||
s_txLen = 0U;
|
|
||||||
s_txStreamPos = 0U;
|
|
||||||
exitCritical(primask);
|
|
||||||
return copy_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* C 风格兼容包装
|
* C 风格兼容包装
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
@@ -981,9 +1021,7 @@ void spi_ota_spi0_init(void) { SpiOta::configureSpi0(); }
|
|||||||
void spi_ota_exti_init(void) { SpiOta::configureExti(); }
|
void spi_ota_exti_init(void) { SpiOta::configureExti(); }
|
||||||
void spi_ota_irq_handler(void) { SpiOta::onSpiInterrupt(); }
|
void spi_ota_irq_handler(void) { SpiOta::onSpiInterrupt(); }
|
||||||
void spi_ota_cs_exti_handler(void) { SpiOta::onCsExtiInterrupt(); }
|
void spi_ota_cs_exti_handler(void) { SpiOta::onCsExtiInterrupt(); }
|
||||||
void spi_ota_feed_rx_bytes(const u8 *data, u16 len) { SpiOta::feedRxBytes(data, len); }
|
|
||||||
bool spi_ota_has_tx_frame(void) { return SpiOta::hasTxFrame(); }
|
bool spi_ota_has_tx_frame(void) { return SpiOta::hasTxFrame(); }
|
||||||
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len) { return SpiOta::copyTxFrame(out, max_len); }
|
|
||||||
|
|
||||||
extern "C" void SPI0_IRQHandler(void) {
|
extern "C" void SPI0_IRQHandler(void) {
|
||||||
SpiOta::onSpiInterrupt();
|
SpiOta::onSpiInterrupt();
|
||||||
|
|||||||
@@ -59,16 +59,7 @@ public:
|
|||||||
static void onSpiInterrupt();
|
static void onSpiInterrupt();
|
||||||
static void onCsExtiInterrupt();
|
static void onCsExtiInterrupt();
|
||||||
|
|
||||||
/** @brief EXTI6(PB6 SPIready) 上升沿 ISR 入口,通知 SpiLinkState ESP32 已完成读取。
|
|
||||||
* @note 必须为 public,供 extern "C" EXTI5_9_IRQHandler 调用。 */
|
|
||||||
static void onSpiReadyExti();
|
|
||||||
|
|
||||||
/** @brief 配置 EXTI6 上升沿中断(SPIready/PB6)。 */
|
|
||||||
static void configureSpiReadyExti();
|
|
||||||
static void feedRxBytes(const u8 *data, u16 len);
|
|
||||||
static bool hasTxFrame();
|
static bool hasTxFrame();
|
||||||
static bool hasActiveSession();
|
|
||||||
static u16 copyTxFrame(u8 *out, u16 max_len);
|
|
||||||
|
|
||||||
/** @brief BOOT 主动按铃:构建请求帧并拉低 SPIready,通知 ESP32 来读。
|
/** @brief BOOT 主动按铃:构建请求帧并拉低 SPIready,通知 ESP32 来读。
|
||||||
* @param[in] frame_type 请求帧类型。
|
* @param[in] frame_type 请求帧类型。
|
||||||
@@ -82,6 +73,12 @@ public:
|
|||||||
/** @brief 取消当前按铃请求,释放 SPIready。 */
|
/** @brief 取消当前按铃请求,释放 SPIready。 */
|
||||||
static void cancelRing();
|
static void cancelRing();
|
||||||
|
|
||||||
|
/** @brief CREDIT 从 0 恢复为 >0 时发送 CREDIT_UPDATE 事件并拉低 DATA_READY。 */
|
||||||
|
static void notifyCreditRestore();
|
||||||
|
|
||||||
|
/** @brief ISR DATA 队列中待处理包数(供 OTA.cpp 计算 CREDIT 用)。 */
|
||||||
|
static u8 pendingDataCount();
|
||||||
|
|
||||||
/** @brief 主循环入队 TX 后装载 DMA + 更新 DATA_READY(擦除中途刷 WAIT 用)。 */
|
/** @brief 主循环入队 TX 后装载 DMA + 更新 DATA_READY(擦除中途刷 WAIT 用)。 */
|
||||||
static void servicePendingTx();
|
static void servicePendingTx();
|
||||||
|
|
||||||
@@ -118,7 +115,7 @@ private:
|
|||||||
const u8 **payload,
|
const u8 **payload,
|
||||||
u16 *payload_len);
|
u16 *payload_len);
|
||||||
static ota_link_msg_type_t mapMsgType(spi_ota_frame_type_t type);
|
static ota_link_msg_type_t mapMsgType(spi_ota_frame_type_t type);
|
||||||
static void queueTxFrame(const u8 *frame, u16 len);
|
static void submitResponse(const u8 *frame, u16 len);
|
||||||
static uint32_t enterCritical();
|
static uint32_t enterCritical();
|
||||||
static void exitCritical(uint32_t primask);
|
static void exitCritical(uint32_t primask);
|
||||||
|
|
||||||
@@ -143,9 +140,7 @@ void spi_ota_spi0_init(void);
|
|||||||
void spi_ota_exti_init(void);
|
void spi_ota_exti_init(void);
|
||||||
void spi_ota_irq_handler(void);
|
void spi_ota_irq_handler(void);
|
||||||
void spi_ota_cs_exti_handler(void);
|
void spi_ota_cs_exti_handler(void);
|
||||||
void spi_ota_feed_rx_bytes(const u8 *data, u16 len);
|
|
||||||
bool spi_ota_has_tx_frame(void);
|
bool spi_ota_has_tx_frame(void);
|
||||||
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
+83
-41
@@ -85,7 +85,7 @@ static void ota_spi_sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
|
|||||||
* 关键修复:这里不能是空函数!
|
* 关键修复:这里不能是空函数!
|
||||||
*
|
*
|
||||||
* 之前是占位实现,因为 ISR 内的回包由 SpiOta::isrProcessFrame() 直接调用
|
* 之前是占位实现,因为 ISR 内的回包由 SpiOta::isrProcessFrame() 直接调用
|
||||||
* SpiOta::sender() → queueTxFrame() → DMA 推送。
|
* SpiOta::sender() → submitResponse() → DMA 推送。
|
||||||
*
|
*
|
||||||
* 但 process() 主循环中也会发送 ACK(如擦除完成后的 ACK(seq=0)),
|
* 但 process() 主循环中也会发送 ACK(如擦除完成后的 ACK(seq=0)),
|
||||||
* 这些调用走的是 transport_send = ota_spi_sender。
|
* 这些调用走的是 transport_send = ota_spi_sender。
|
||||||
@@ -243,7 +243,7 @@ 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 DEBUG_PRINT
|
||||||
if (seq_or_flag <= 5U || seq_or_flag == 0xF0F0F0F0U) {
|
if (seq_or_flag <= 20U || seq_or_flag == 0xF0F0F0F0U) {
|
||||||
printf("[OTA] send_structured_ack seq=%u\r\n", (unsigned)seq_or_flag);
|
printf("[OTA] send_structured_ack seq=%u\r\n", (unsigned)seq_or_flag);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -317,7 +317,11 @@ void OTAHandler::process() {
|
|||||||
send_structured_ack(0U);
|
send_structured_ack(0U);
|
||||||
SpiOta::servicePendingTx();
|
SpiOta::servicePendingTx();
|
||||||
}
|
}
|
||||||
|
if (pending_finish_ack || pending_finish_ota) {
|
||||||
drainFlashBuffer();
|
drainFlashBuffer();
|
||||||
|
} else {
|
||||||
|
drainFlashBufferOne();
|
||||||
|
}
|
||||||
|
|
||||||
if (pending_finish_ack && flash_buf_cnt == 0U) {
|
if (pending_finish_ack && flash_buf_cnt == 0U) {
|
||||||
pending_finish_ack = false;
|
pending_finish_ack = false;
|
||||||
@@ -631,6 +635,31 @@ void OTAHandler::eraseApplicationArea() {
|
|||||||
SpiOta::servicePendingTx();
|
SpiOta::servicePendingTx();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 关键修复:CRC 地址 (n_APP_LATEST_CRC_ADDR = APP_FIRMWARE_SIZE 末尾) 所在的 page
|
||||||
|
* 必须擦除,否则 writeAppCrc 调用 fmc_word_program 时会因目标 word 非擦除态
|
||||||
|
* (上次 OTA 残留的旧 CRC) 触发 FMC_PGERR (status=2),导致 CRC 写入失败 →
|
||||||
|
* verifyAppCrc FAIL → abort jump → GD32 卡在 BOOT,ESP32 探测 APP 超时。
|
||||||
|
*
|
||||||
|
* 当固件较小时(如 53KB),固件 pages 不覆盖 CRC page(0x0807D800~0x0807DFFF),
|
||||||
|
* 需要额外擦除这一页。固件较大时(>0x35800)固件 pages 已覆盖 CRC page,跳过。
|
||||||
|
* 仅多擦 1 page (~30ms),不破坏 eraseApplicationArea 的速度优化。 */
|
||||||
|
const u32 crc_page_start = n_APP_LATEST_CRC_ADDR & ~(FMC_PAGE_SIZE - 1U);
|
||||||
|
if (erase_end <= crc_page_start) {
|
||||||
|
__disable_irq();
|
||||||
|
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
|
||||||
|
fmc_state_enum crc_status = fmc_page_erase(crc_page_start);
|
||||||
|
__enable_irq();
|
||||||
|
if (crc_status != FMC_READY) {
|
||||||
|
ota_error = OTA_ERROR_FLASH_ERROR;
|
||||||
|
ota_bind_spi_transport();
|
||||||
|
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
|
||||||
|
}
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
printf("[FW] E crc_page=0x%08lX status=%u\r\n",
|
||||||
|
(unsigned long)crc_page_start, (unsigned)crc_status);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
fmc_lock();
|
fmc_lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,56 +673,42 @@ void OTAHandler::eraseApplicationArea() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
void OTAHandler::drainFlashBuffer() {
|
bool OTAHandler::drainFlashBufferOne() {
|
||||||
#if DEBUG_PRINT
|
if (flash_buf_cnt == 0U) { return false; }
|
||||||
u32 drain_cnt = 0;
|
|
||||||
#endif
|
|
||||||
if (flash_buf_cnt == 0U) { return; }
|
|
||||||
/* v2 DATA_READY: Flash 写入不再用 PB6 背压,改由 CREDIT=0 通知 ESP32 停止发送。
|
|
||||||
* fmc_word_program 会让 CPU 总线停顿,但 ESP32 在 CREDIT=0 后改为 POLL 等待,
|
|
||||||
* 不会盲目发新事务导致 RX-OVR。 */
|
|
||||||
while (1) {
|
|
||||||
FlashSlot slot = {};
|
|
||||||
bool has_slot = false;
|
|
||||||
|
|
||||||
/* 先原子出队,再慢速写 Flash。
|
FlashSlot slot = {};
|
||||||
* 这样即使 ISR 中途 resetToIdle() 清空了环形缓冲计数,
|
|
||||||
* 这里也不会在写完后再把 0 继续 -- 成 255。 */
|
|
||||||
__disable_irq();
|
__disable_irq();
|
||||||
if (flash_buf_cnt > 0U) {
|
if (flash_buf_cnt > 0U) {
|
||||||
u8 ri = flash_buf_ri;
|
u8 ri = flash_buf_ri;
|
||||||
slot = flash_buf[ri];
|
slot = flash_buf[ri];
|
||||||
flash_buf_ri = (ri + 1U) % kFlashBufSlots;
|
flash_buf_ri = (ri + 1U) % kFlashBufSlots;
|
||||||
flash_buf_cnt--;
|
flash_buf_cnt--;
|
||||||
has_slot = true;
|
} else {
|
||||||
|
__enable_irq();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
__enable_irq();
|
__enable_irq();
|
||||||
/* 缓冲被消费,恢复 CREDIT 通知 ESP32 可继续发数据。 */
|
|
||||||
g_localCredit = (u8)(kFlashBufSlots - flash_buf_cnt);
|
|
||||||
|
|
||||||
if (!has_slot) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeApplicationData(slot.offset, slot.data, OTA_PACKET_SIZE);
|
writeApplicationData(slot.offset, slot.data, OTA_PACKET_SIZE);
|
||||||
|
|
||||||
|
{ u8 pending = SpiOta::pendingDataCount();
|
||||||
|
g_localCredit = (flash_buf_cnt + pending >= kFlashBufSlots) ? 0U
|
||||||
|
: (u8)(kFlashBufSlots - flash_buf_cnt - pending); }
|
||||||
|
SpiOta::notifyCreditRestore();
|
||||||
|
|
||||||
#if DEBUG_PRINT
|
#if DEBUG_PRINT
|
||||||
drain_cnt++;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
/* Flash 写入完成,恢复 CREDIT 通知 ESP32 可继续发数据。
|
|
||||||
* v2 DATA_READY: 不再操作 PB6,PB6 由队列状态自动维护。 */
|
|
||||||
g_localCredit = (u8)(kFlashBufSlots - flash_buf_cnt);
|
|
||||||
#if DEBUG_PRINT
|
|
||||||
/* 每累计 10 包打一次,避免 OTA 过程刷屏。 */
|
|
||||||
static u32 s_drain_since_log = 0U;
|
static u32 s_drain_since_log = 0U;
|
||||||
if (drain_cnt > 0U) {
|
s_drain_since_log++;
|
||||||
s_drain_since_log += drain_cnt;
|
|
||||||
if (s_drain_since_log >= 10U) {
|
if (s_drain_since_log >= 10U) {
|
||||||
printf("[FW] drain %u pkts\r\n", (unsigned)s_drain_since_log);
|
printf("[FW] drain %u pkts\r\n", (unsigned)s_drain_since_log);
|
||||||
s_drain_since_log = 0U;
|
s_drain_since_log = 0U;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OTAHandler::drainFlashBuffer() {
|
||||||
|
while (drainFlashBufferOne()) {}
|
||||||
}
|
}
|
||||||
void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
|
void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
|
||||||
/**
|
/**
|
||||||
@@ -705,12 +720,26 @@ void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
|
|||||||
fmc_unlock();
|
fmc_unlock();
|
||||||
u32 i = 0;
|
u32 i = 0;
|
||||||
for (; i + 4U <= size; i += 4U) {
|
for (; i + 4U <= size; i += 4U) {
|
||||||
(void)fmc_word_program(write_addr + i, *(u32 *)&data[i]);
|
fmc_state_enum st = fmc_word_program(write_addr + i, *(u32 *)&data[i]);
|
||||||
|
if (st != FMC_READY) {
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
printf("[FW] write err 0x%08lX st=%u\r\n", (unsigned long)(write_addr + i), (unsigned)st);
|
||||||
|
#endif
|
||||||
|
fmc_lock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (i < size) {
|
if (i < size) {
|
||||||
u32 word = 0xFFFFFFFFU;
|
u32 word = 0xFFFFFFFFU;
|
||||||
memcpy(&word, &data[i], size - i);
|
memcpy(&word, &data[i], size - i);
|
||||||
(void)fmc_word_program(write_addr + i, word);
|
fmc_state_enum st = fmc_word_program(write_addr + i, word);
|
||||||
|
if (st != FMC_READY) {
|
||||||
|
#if DEBUG_PRINT
|
||||||
|
printf("[FW] write tail err 0x%08lX st=%u\r\n", (unsigned long)(write_addr + i), (unsigned)st);
|
||||||
|
#endif
|
||||||
|
fmc_lock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fmc_lock();
|
fmc_lock();
|
||||||
}
|
}
|
||||||
@@ -786,7 +815,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
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 DEBUG_PRINT
|
||||||
if (receive_packet_num <= 5U) {
|
if (receive_packet_num <= 20U) {
|
||||||
printf("[OTA] DATA in: seq=%u next=%u flash_cnt=%u\r\n",
|
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);
|
(unsigned)receive_packet_num, (unsigned)next_expected_packet, (unsigned)flash_buf_cnt);
|
||||||
}
|
}
|
||||||
@@ -798,7 +827,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
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 DEBUG_PRINT
|
||||||
if (receive_packet_num <= 5U) {
|
if (receive_packet_num <= 20U) {
|
||||||
printf("[OTA] DATA dup seq=%u (next=%u) -> ack %u\r\n",
|
printf("[OTA] DATA dup seq=%u (next=%u) -> ack %u\r\n",
|
||||||
(unsigned)receive_packet_num, (unsigned)next_expected_packet, (unsigned)request_packet);
|
(unsigned)receive_packet_num, (unsigned)next_expected_packet, (unsigned)request_packet);
|
||||||
}
|
}
|
||||||
@@ -831,11 +860,20 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_SIZE);
|
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_SIZE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (flash_buf_cnt >= kFlashBufSlots) {
|
||||||
|
/* 缓冲满:同步排空已写入的槽位,腾出空间后再入队。
|
||||||
|
* 旧实现在此直接 return 丢弃当前包,但 ISR 已推进
|
||||||
|
* s_expectedRxSeq,导致 ESP32 重发被判为 DUP-SEQ,
|
||||||
|
* 永远只能读到缓存的 WAIT,包永久丢失。 */
|
||||||
|
/* 只排空一个包腾槽,避免一次 drain 8 包阻塞主循环。
|
||||||
|
* 剩余包由 process() 的增量 drain 逐步消费。 */
|
||||||
|
drainFlashBufferOne();
|
||||||
if (flash_buf_cnt >= kFlashBufSlots) {
|
if (flash_buf_cnt >= kFlashBufSlots) {
|
||||||
g_localCredit = 0U;
|
g_localCredit = 0U;
|
||||||
send_wait_frame(1, (const u8 *)"\x03");
|
send_wait_frame(1, (const u8 *)"\x03");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
u8 wi = flash_buf_wi;
|
u8 wi = flash_buf_wi;
|
||||||
flash_buf[wi].offset = offset;
|
flash_buf[wi].offset = offset;
|
||||||
memcpy(flash_buf[wi].data, decrypted_data, OTA_PACKET_SIZE);
|
memcpy(flash_buf[wi].data, decrypted_data, OTA_PACKET_SIZE);
|
||||||
@@ -845,11 +883,15 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
|||||||
__enable_irq();
|
__enable_irq();
|
||||||
/* 更新 CREDIT:剩余可接收槽位。缓冲将满时置 0,
|
/* 更新 CREDIT:剩余可接收槽位。缓冲将满时置 0,
|
||||||
* ESP32 收到 CREDIT=0 后会改为 POLL 等待而非盲目重发。 */
|
* ESP32 收到 CREDIT=0 后会改为 POLL 等待而非盲目重发。 */
|
||||||
g_localCredit = (flash_buf_cnt >= kFlashBufSlots) ? 0U
|
/* CREDIT 需同时减去 ISR DATA 队列待处理数,
|
||||||
: (u8)(kFlashBufSlots - flash_buf_cnt);
|
* 否则 ESP32 看到 credit>0 但 flash_buf 即将满。 */
|
||||||
|
{ u8 pending = SpiOta::pendingDataCount();
|
||||||
|
u8 avail = (flash_buf_cnt + pending >= kFlashBufSlots) ? 0U
|
||||||
|
: (u8)(kFlashBufSlots - flash_buf_cnt - pending);
|
||||||
|
g_localCredit = avail; }
|
||||||
next_expected_packet = receive_packet_num + 1U;
|
next_expected_packet = receive_packet_num + 1U;
|
||||||
#if DEBUG_PRINT
|
#if DEBUG_PRINT
|
||||||
if (receive_packet_num <= 5U) {
|
if (receive_packet_num <= 20U) {
|
||||||
printf("[OTA] DATA ok seq=%u -> ack %u\r\n",
|
printf("[OTA] DATA ok seq=%u -> ack %u\r\n",
|
||||||
(unsigned)receive_packet_num, (unsigned)next_expected_packet);
|
(unsigned)receive_packet_num, (unsigned)next_expected_packet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ class OTAHandler {
|
|||||||
u32 next_expected_packet = 0U; /**< 下一个期望收到的包号,用于去重 */
|
u32 next_expected_packet = 0U; /**< 下一个期望收到的包号,用于去重 */
|
||||||
|
|
||||||
void drainFlashBuffer();
|
void drainFlashBuffer();
|
||||||
|
bool drainFlashBufferOne(); /**< 排空一个 flash_buf 槽位,返回 true 表示有写入 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief OTA 流程状态定义。
|
* @brief OTA 流程状态定义。
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ typedef uint32_t u32;
|
|||||||
/* BOOT 固件版本号 */
|
/* BOOT 固件版本号 */
|
||||||
#define BOOT_FW_VERSION_MAJOR 1U
|
#define BOOT_FW_VERSION_MAJOR 1U
|
||||||
#define BOOT_FW_VERSION_MINOR 0U
|
#define BOOT_FW_VERSION_MINOR 0U
|
||||||
#define BOOT_FW_VERSION_PATCH 1U
|
#define BOOT_FW_VERSION_PATCH 2U
|
||||||
#define BOOT_FW_VERSION_BUILD 1U
|
#define BOOT_FW_VERSION_BUILD 1U
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -267,9 +267,6 @@ void BootSleepManager::onExtiInterrupt(void)
|
|||||||
system_soft_reset();
|
system_soft_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** EXTI_6 (PB6 SPIready) 上升沿:通知 SpiOta 状态机 ESP32 已完成读取。 */
|
|
||||||
SpiOta::onSpiReadyExti();
|
|
||||||
|
|
||||||
/* 注意:SPI OTA 的 CS 边沿信号 PA15 映射到 EXTI_15,属于 EXTI10_15_IRQn,
|
/* 注意:SPI OTA 的 CS 边沿信号 PA15 映射到 EXTI_15,属于 EXTI10_15_IRQn,
|
||||||
* 由 EXTI10_15_IRQHandler 直接分发到 SpiOta::onCsExtiInterrupt(),
|
* 由 EXTI10_15_IRQHandler 直接分发到 SpiOta::onCsExtiInterrupt(),
|
||||||
* 不应在此处(EXTI5_9_Handler)调用 spi_ota_cs_exti_handler()。
|
* 不应在此处(EXTI5_9_Handler)调用 spi_ota_cs_exti_handler()。
|
||||||
|
|||||||
Reference in New Issue
Block a user