refactor(device): 移除未使用的CAN DMA初始化函数和SPI中断处理
移除了CAN模块中未启用的DMA初始化相关代码,包括can_dma_init函数 和相关的调用。同时移除了SPI OTA模块中已废弃的中断处理函数和C风格 兼容包装函数,这些功能已被DMA方式替代。 BREAKING CHANGE: 移除了can_dma_init函数和spi_ota_irq_handler等C风格API。
This commit is contained in:
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* CSOLUTION generated file: DO NOT EDIT!
|
||||
* Generated by: csolution version 2.13.0+p3-g40b6564e
|
||||
*
|
||||
* Project: 'GD32E50X_BOOT.Debug+GD32E508VE'
|
||||
* Target: 'Debug+GD32E508VE'
|
||||
*/
|
||||
|
||||
#ifndef RTE_COMPONENTS_H
|
||||
#define RTE_COMPONENTS_H
|
||||
|
||||
|
||||
/*
|
||||
* Define the Device Header File:
|
||||
*/
|
||||
#define CMSIS_device_header "gd32e50x.h"
|
||||
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:ADC@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_ADC
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:CAN@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_CAN
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:CRC@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_CRC
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:DBG@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_DBG
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:DMA@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_DMA
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:EXTI@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_EXTI
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:FMC@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_FMC
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:FWDGT@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_FWDGT
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:GPIO@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_GPIO
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:MISC@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_MISC
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:PMU@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_PMU
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:RCU@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_RCU
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:TIMER@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_TIMER
|
||||
/* GigaDevice::Device:GD32E50x_StdPeripherals:USART@1.6.0 */
|
||||
#define RTE_DEVICE_STDPERIPHERALS_USART
|
||||
|
||||
|
||||
#endif /* RTE_COMPONENTS_H */
|
||||
@@ -53,18 +53,6 @@ u8 can_gpio_init(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CAN DMA 初始化占位函数。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void can_dma_init(void) {
|
||||
/**
|
||||
* 预留接口:当前接收路径直接从 FIFO 取数,
|
||||
* 这样实现更简单,也避免 DMA 与中断抢数据的时序问题。
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置 CAN-FD 控制器、滤波器和中断。
|
||||
*
|
||||
@@ -77,7 +65,6 @@ void canfd_config(void) {
|
||||
can_filter_parameter_struct can_filter;
|
||||
|
||||
can_gpio_init();
|
||||
can_dma_init();
|
||||
can_deinit(CAN0);
|
||||
|
||||
/**
|
||||
@@ -303,28 +290,6 @@ u8 CAN_transmiter::CANFD_AsyncSend(u32 id, u8 length, u8 *data) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送带 4 字节业务数据的 OTA ACK。
|
||||
*
|
||||
* @param packet_num 分包序号,当前实现未使用。
|
||||
* @param data 指向业务数据缓冲区的指针。
|
||||
* @return 1 表示发送成功或成功入队,0 表示失败。
|
||||
*/
|
||||
u8 CAN_transmiter::sendDATA_ACK(u32 packet_num, u8 *data) {
|
||||
/**
|
||||
* 兼容旧协议格式:
|
||||
* 前两个字节固定为 0xBA 0xC0,后四字节带上包号/状态信息。
|
||||
*/
|
||||
(void)packet_num;
|
||||
memset(transmit_data, 0, sizeof(transmit_data));
|
||||
transmit_data[0] = 0xBA;
|
||||
transmit_data[1] = 0xC0;
|
||||
for (int i = 2; i < 6; ++i) {
|
||||
transmit_data[i] = data[i - 2];
|
||||
}
|
||||
return CANFD_AsyncSend(CANFD_OTA_ACK_ID, 6, transmit_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送备份恢复提示帧。
|
||||
*
|
||||
@@ -338,28 +303,6 @@ u8 CAN_transmiter::sendBackup() {
|
||||
return CANFD_AsyncSend(CANFD_OTA_ACK_ID, 0, transmit_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送简化 OTA ACK。
|
||||
*
|
||||
* @param packet_num 分包序号,当前实现未使用。
|
||||
* @param if_success 是否成功。
|
||||
* @return 1 表示发送成功或成功入队,0 表示失败。
|
||||
*/
|
||||
u8 CAN_transmiter::sendDATA_ACK(u32 packet_num, bool if_success) {
|
||||
/**
|
||||
* 简化 ACK 版本:
|
||||
* if_success 为真时写入 0x0001;
|
||||
* 为假则发送全 0,保留给对端区分处理。
|
||||
*/
|
||||
(void)packet_num;
|
||||
memset(transmit_data, 0, 4);
|
||||
if (if_success) {
|
||||
transmit_data[0] = 0x01;
|
||||
transmit_data[1] = 0x00;
|
||||
}
|
||||
return CANFD_AsyncSend(CANFD_OTA_ACK_ID, 4, transmit_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CAN RX0 中断实际处理逻辑。
|
||||
*
|
||||
|
||||
@@ -32,15 +32,6 @@ u8 can_gpio_init(void);
|
||||
*/
|
||||
void canfd_config(void);
|
||||
|
||||
/**
|
||||
* @brief 初始化 CAN DMA 通道。
|
||||
*
|
||||
* @note 当前实现未启用 DMA,该函数作为保留占位接口。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void can_dma_init(void);
|
||||
|
||||
/**
|
||||
* @brief CAN0 FIFO0 接收中断服务函数。
|
||||
*
|
||||
@@ -118,22 +109,6 @@ class CAN_transmiter {
|
||||
*/
|
||||
u8 CANFD_AsyncSend(u32 ID, u8 length, u8 *data);
|
||||
|
||||
/** @brief 发送简化 OTA ACK 帧。
|
||||
*
|
||||
* @param packet_num 分包序号,当前实现未实际使用。
|
||||
* @param if_success 是否成功,true 表示成功。
|
||||
* @return 1 表示发送成功或成功入队,0 表示失败。
|
||||
*/
|
||||
u8 sendDATA_ACK(u32 packet_num, bool if_success);
|
||||
|
||||
/** @brief 发送带 4 字节有效载荷的 OTA ACK 帧。
|
||||
*
|
||||
* @param packet_num 分包序号,当前实现未实际使用。
|
||||
* @param data 指向 4 字节业务数据的缓冲区指针。
|
||||
* @return 1 表示发送成功或成功入队,0 表示失败。
|
||||
*/
|
||||
u8 sendDATA_ACK(u32 packet_num, u8 *data);
|
||||
|
||||
/** @brief 发送「备份恢复」提示帧。
|
||||
*
|
||||
* @return 1 表示发送成功或成功入队,0 表示失败。
|
||||
|
||||
@@ -895,24 +895,6 @@ void SpiOta::onCsRelease() {
|
||||
updateDataReady();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* SPI0 中断处理 — RX 已改 DMA,保留空壳兼容旧 IRQ 向量
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::onSpiInterrupt() {
|
||||
/* 遗留 ERR 标志清理(RBNE 已禁用) */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
(void)SPI_STAT(SPI0);
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||
}
|
||||
}
|
||||
|
||||
void SpiOta::onCsExtiInterrupt() {
|
||||
if (exti_interrupt_flag_get(EXTI_15) != RESET) {
|
||||
exti_interrupt_flag_clear(EXTI_15);
|
||||
@@ -1006,23 +988,3 @@ bool SpiOta::hasTxFrame() { return s_txPending; }
|
||||
u8 SpiOta::pendingDataCount() {
|
||||
return s_otaDataCnt;
|
||||
}
|
||||
/* ============================================================
|
||||
* C 风格兼容包装
|
||||
* ============================================================ */
|
||||
|
||||
void spi_ota_init(void) { SpiOta::init(); }
|
||||
void spi_ota_poll(void) { SpiOta::poll(); }
|
||||
void spi_ota_service_pending_tx(void) { SpiOta::servicePendingTx(); }
|
||||
void spi_ota_set_sender(OTAHandler *handler) { SpiOta::setSender(handler); }
|
||||
void spi_ota_on_cs_assert(void) { SpiOta::onCsAssert(); }
|
||||
void spi_ota_on_cs_release(void) { SpiOta::onCsRelease(); }
|
||||
void spi_ota_gpio_init(void) { SpiOta::configureGpio(); }
|
||||
void spi_ota_spi0_init(void) { SpiOta::configureSpi0(); }
|
||||
void spi_ota_exti_init(void) { SpiOta::configureExti(); }
|
||||
void spi_ota_irq_handler(void) { SpiOta::onSpiInterrupt(); }
|
||||
void spi_ota_cs_exti_handler(void) { SpiOta::onCsExtiInterrupt(); }
|
||||
bool spi_ota_has_tx_frame(void) { return SpiOta::hasTxFrame(); }
|
||||
|
||||
extern "C" void SPI0_IRQHandler(void) {
|
||||
SpiOta::onSpiInterrupt();
|
||||
}
|
||||
|
||||
@@ -1,608 +0,0 @@
|
||||
#include "SpiOta.h"
|
||||
#include "core_cm33.h"
|
||||
#include "gd32e50x_gpio.h"
|
||||
#include "gd32e50x_exti.h"
|
||||
#include "gd32e50x_rcu.h"
|
||||
#include "gd32e50x_misc.h"
|
||||
#include "gd32e50x_spi.h"
|
||||
#include "gd32e50x_dma.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @file SpiOta.cpp
|
||||
* @brief Bootloader SPI OTA 从机模块实现(DMA 发送版)。
|
||||
*
|
||||
* CS 释放后,EXTI ISR 直接完成帧校验+处理+响应准备,
|
||||
* 响应帧写入 s_txBuf,DMA0_CH2 自动推到 SPI0 TX 寄存器。
|
||||
* 硬件 DMA 替代 TBE 中断逐字节发送,消除字节错位问题。
|
||||
*
|
||||
* GD32E50x DMA 通道映射(固定):
|
||||
* DMA0_CH0: ADC0, DMA0_CH1: SPI0_RX, DMA0_CH2: SPI0_TX
|
||||
* DMA0_CH3: SPI1_RX, DMA0_CH4: SPI1_TX
|
||||
*/
|
||||
|
||||
/* SPI0_TX = DMA0_CH2 (GD32E50x 固定映射) */
|
||||
#define SPI_TX_DMA_PERIPH DMA0
|
||||
#define SPI_TX_DMA_CH DMA_CH2
|
||||
|
||||
/* 调试:多缓冲日志(8 条 ISR 日志缓冲区,避免关键事件丢失) */
|
||||
static constexpr u16 kLogSlotCount = 8U;
|
||||
static constexpr u16 kLogSlotSize = 80U;
|
||||
static u8 s_logSlots[kLogSlotCount][kLogSlotSize] = {};
|
||||
static u16 s_logSlotLens[kLogSlotCount] = {};
|
||||
static volatile u8 s_logWriteIdx = 0U;
|
||||
static volatile u8 s_logReadIdx = 0U;
|
||||
static volatile u8 s_logCount = 0U;
|
||||
|
||||
static void isrLogFrameEx(const char *tag, const u8 *data, u16 len);
|
||||
|
||||
|
||||
OTAHandler *SpiOta::s_otaHandler = nullptr;
|
||||
volatile bool SpiOta::s_csActive = false;
|
||||
volatile u16 SpiOta::s_rxStreamLen = 0U;
|
||||
volatile u16 SpiOta::s_txStreamPos = 0U;
|
||||
u8 SpiOta::s_rxStream[SpiOta::kRxBufSize] = {};
|
||||
u8 SpiOta::s_txBuf[SpiOta::kTxBufSize] = {};
|
||||
volatile u16 SpiOta::s_txLen = 0U;
|
||||
volatile bool SpiOta::s_txPending = false;
|
||||
volatile bool SpiOta::s_isrResponded = false;
|
||||
volatile bool SpiOta::s_isrLogPending = false;
|
||||
volatile bool SpiOta::s_otaSessionActive = false;
|
||||
u8 SpiOta::s_isrLogBuf[kIsrLogBufSize] = {};
|
||||
u16 SpiOta::s_isrLogLen = 0U;
|
||||
|
||||
/* ============================================================
|
||||
* CRC8 / 帧构建 / 帧解析
|
||||
* ============================================================ */
|
||||
|
||||
u8 SpiOta::crc8(const u8 *data, u16 len) {
|
||||
u8 crc = 0x00U;
|
||||
for (u16 i = 0; i < len; ++i) { crc ^= data[i]; }
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SpiOta::buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
|
||||
u8 *out, u16 *out_len) {
|
||||
if (!out || !out_len) { return false; }
|
||||
u16 total_len = (u16)(kFrameOverhead + payload_len);
|
||||
if (total_len > kTxBufSize) { return false; }
|
||||
out[0] = kSof;
|
||||
out[1] = (u8)type;
|
||||
out[2] = (u8)(payload_len & 0xFFU);
|
||||
out[3] = (u8)((payload_len >> 8) & 0xFFU);
|
||||
if (payload && payload_len > 0U) { memcpy(&out[4], payload, payload_len); }
|
||||
out[4 + payload_len] = crc8(out, (u16)(4U + payload_len));
|
||||
*out_len = total_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpiOta::tryParseFrame(const u8 *frame, u16 frame_len,
|
||||
spi_ota_frame_type_t *type,
|
||||
const u8 **payload,
|
||||
u16 *payload_len) {
|
||||
if (!frame || frame_len < kFrameOverhead) { return false; }
|
||||
if (frame[0] != kSof) { return false; }
|
||||
u16 len = (u16)(frame[2] | ((u16)frame[3] << 8));
|
||||
u16 expected = (u16)(kFrameOverhead + len);
|
||||
if (expected != frame_len) { return false; }
|
||||
if (crc8(frame, (u16)(4U + len)) != frame[4U + len]) { return false; }
|
||||
if (type) { *type = (spi_ota_frame_type_t)frame[1]; }
|
||||
if (payload) { *payload = &frame[4]; }
|
||||
if (payload_len){ *payload_len = len; }
|
||||
return true;
|
||||
}
|
||||
|
||||
ota_link_msg_type_t SpiOta::mapMsgType(spi_ota_frame_type_t type) {
|
||||
switch (type) {
|
||||
case SPI_OTA_CMD_REQ: return OTA_LINK_MSG_REQ;
|
||||
case SPI_OTA_CMD_DATA: return OTA_LINK_MSG_DATA;
|
||||
default: return OTA_LINK_MSG_CANCEL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 回包发送适配器(ISR 安全)
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
|
||||
spi_ota_frame_type_t rsp_type = SPI_OTA_RSP_ACK;
|
||||
if (type == OTA_LINK_TX_WAIT) { rsp_type = SPI_OTA_RSP_WAIT; }
|
||||
u8 frame_buf[kTxBufSize] = {};
|
||||
u16 built_len = 0U;
|
||||
if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) {
|
||||
queueTxFrame(frame_buf, built_len);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SpiOta::enterCritical() {
|
||||
uint32_t primask = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
return primask;
|
||||
}
|
||||
|
||||
void SpiOta::exitCritical(uint32_t primask) {
|
||||
__set_PRIMASK(primask);
|
||||
}
|
||||
|
||||
/** @brief 启动 DMA 传输 s_txBuf → SPI0_DATA */
|
||||
static void startSpiTxDma(const u8 *buf, u16 len) {
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
DMA_INTC(SPI_TX_DMA_PERIPH) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, SPI_TX_DMA_CH);
|
||||
DMA_CHMADDR(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = (uint32_t)buf;
|
||||
DMA_CHCNT(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = len;
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) |= DMA_CHXCTL_CHEN;
|
||||
}
|
||||
|
||||
void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
||||
if (!frame || len == 0U || len > kTxBufSize) { return; }
|
||||
uint32_t primask = enterCritical();
|
||||
memcpy(s_txBuf, frame, len);
|
||||
s_txLen = len;
|
||||
s_txPending = true;
|
||||
s_txStreamPos = 0U;
|
||||
if (len < kTxBufSize) { memset(&s_txBuf[len], 0xFF, kTxBufSize - len); }
|
||||
exitCritical(primask);
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* RBNE 中断内即时帧检测与处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::tryProcessCompleteFrame() {
|
||||
u16 rx_len = s_rxStreamLen;
|
||||
if (rx_len == 0U) { return; }
|
||||
|
||||
if (s_rxStream[0] == SPI_OTA_CMD_BOOT) {
|
||||
isrProcessFrame();
|
||||
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();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* CS 释放后 ISR 直接帧处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::isrProcessFrame() {
|
||||
u16 rx_len = s_rxStreamLen;
|
||||
if (rx_len == 0U) { return; }
|
||||
|
||||
if (rx_len >= 1U && s_rxStream[0] == SPI_OTA_CMD_BOOT) {
|
||||
u8 ack[4] = {0xB0U, 0xA5U, 0x4FU, 0x54U};
|
||||
queueTxFrame(ack, sizeof(ack));
|
||||
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
|
||||
isrLogFrameEx("[BOOT-CMD]", s_txBuf, s_txLen);
|
||||
s_isrResponded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_rxStream[0] != kSof) {
|
||||
isrLogFrameEx("[NO-SOF]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
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 > rx_len) { return; }
|
||||
|
||||
if (crc8(s_rxStream, (u16)(4U + payload_len)) != s_rxStream[4U + payload_len]) {
|
||||
u8 err = 0xEEU;
|
||||
u8 err_frame[kTxBufSize] = {};
|
||||
u16 built = 0U;
|
||||
if (buildFrame(SPI_OTA_RSP_ERROR, &err, 1U, err_frame, &built)) {
|
||||
queueTxFrame(err_frame, built);
|
||||
}
|
||||
isrLogFrameEx("[CRC-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
return;
|
||||
}
|
||||
|
||||
spi_ota_frame_type_t frame_type;
|
||||
const u8 *payload = nullptr;
|
||||
u16 p_len = 0U;
|
||||
if (!tryParseFrame(s_rxStream, expected, &frame_type, &payload, &p_len)) {
|
||||
isrLogFrameEx("[PARSE-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame_type == SPI_OTA_CMD_BOOT) {
|
||||
u8 ack_ok = 0xA5U;
|
||||
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(); }
|
||||
isrLogFrameEx("[BOOT-FRAME]", s_txBuf, s_txLen);
|
||||
s_isrResponded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_otaHandler != nullptr) {
|
||||
s_otaSessionActive = true;
|
||||
s_otaHandler->setTransportSender(sender);
|
||||
ota_link_msg_type_t msg_type = mapMsgType(frame_type);
|
||||
isrLogFrameEx("[OTA-MSG]", (const u8*)&frame_type, 1);
|
||||
s_otaHandler->handleLinkMessage(msg_type, const_cast<u8 *>(payload), (u8)p_len);
|
||||
}
|
||||
|
||||
isrLogFrameEx("[ISR-DONE]", s_txBuf, (s_txLen < 16U) ? s_txLen : 16U);
|
||||
s_isrResponded = true;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* ISR 内延迟日志
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::isrLogFrame(const char *tag, const u8 *data, u16 len) {
|
||||
isrLogFrameEx(tag, data, len);
|
||||
}
|
||||
|
||||
static void isrLogFrameEx(const char *tag, const u8 *data, u16 len) {
|
||||
if (s_logCount >= kLogSlotCount) { return; }
|
||||
u8 *buf = s_logSlots[s_logWriteIdx];
|
||||
u16 pos = 0U;
|
||||
if (tag) {
|
||||
while (*tag && pos < kLogSlotSize - 3U) { buf[pos++] = (u8)*tag++; }
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
const u16 preview = (len < 24U) ? len : 24U;
|
||||
for (u16 i = 0U; i < preview && pos + 2U < kLogSlotSize; ++i) {
|
||||
static const char hx[] = "0123456789ABCDEF";
|
||||
buf[pos++] = (u8)hx[data[i] >> 4];
|
||||
buf[pos++] = (u8)hx[data[i] & 0x0FU];
|
||||
}
|
||||
buf[pos] = 0U;
|
||||
s_logSlotLens[s_logWriteIdx] = pos;
|
||||
s_logWriteIdx = (s_logWriteIdx + 1U) % kLogSlotCount;
|
||||
s_logCount++;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 硬件初始化
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::configureGpio() {
|
||||
rcu_periph_clock_enable(RCU_GPIOA);
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
gpio_pin_remap_config(GPIO_SPI0_REMAP, ENABLE);
|
||||
gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
|
||||
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
|
||||
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_6 | GPIO_PIN_7);
|
||||
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
|
||||
}
|
||||
|
||||
void SpiOta::configureSpi0() {
|
||||
rcu_periph_clock_enable(RCU_SPI0);
|
||||
spi_i2s_deinit(SPI0);
|
||||
(void)spi_i2s_data_receive(SPI0); /* 清空 SPI_DATA 残留值 */
|
||||
spi_parameter_struct spi_init_struct;
|
||||
spi_struct_para_init(&spi_init_struct);
|
||||
spi_init_struct.device_mode = SPI_SLAVE;
|
||||
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
|
||||
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
|
||||
spi_init_struct.nss = SPI_NSS_SOFT;
|
||||
spi_init_struct.endian = SPI_ENDIAN_MSB;
|
||||
/* SPI NSS 由 ESP32 的 CS 线 (GPIO4→PA15) 控制。\外部 CS 高时:
|
||||
* spi_nss_internal_high(SPI0) 让 SPI 外设进入未选中状态,
|
||||
* 这样 SPI0_DATA 输出 0xFF(而非 0x00),
|
||||
* DMA 只在 CS 低时才被 SPI 外设允许推数据到 MISO。 */
|
||||
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
|
||||
spi_init_struct.prescale = SPI_PSC_8;
|
||||
spi_init(SPI0, &spi_init_struct);
|
||||
/* 关键修正:内部 NSS 始终保持 LOW,使 SPI0 从机一直处于“已选中”状态。
|
||||
*
|
||||
* APP 侧的稳定实现就是这样做的:PA15 只作为 EXTI 事务边界信号,
|
||||
* 不再依赖 EXTI ISR 去“赶在首个 SCK 之前”切 internal NSS。
|
||||
*
|
||||
* 之前 BOOT 的问题正出在这里:
|
||||
* - 空闲时 spi_nss_internal_high(SPI0)
|
||||
* - CS↓ 后靠 EXTI ISR 再切 internal_low
|
||||
* - 但 ESP32 在 CS↓ 后几乎立刻出时钟
|
||||
* - 于是 GD32 常常在 internal NSS 真正拉低前丢掉首字节/首几位
|
||||
* → 请求帧首字节 0xA5 被吃掉,看到 0x80 / 0xFF / 随机 0x07
|
||||
*
|
||||
* 现在改为 internal NSS 常低:
|
||||
* - SPI 从机始终就绪,不会因为 EXTI 延迟丢首字节
|
||||
* - PA15 仍用于 onCsAssert/onCsRelease 做缓冲区清零和 DMA 重装
|
||||
*/
|
||||
spi_nss_internal_low(SPI0);
|
||||
/* 初始化时清空 SPI_DATA 寄存器,防止残留值影响首次传输 */
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
|
||||
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
|
||||
/* SPI0 RBNE 中断优先级设为 (2, 0),低于 EXTI CS 中断 (1, 1)。
|
||||
* 这确保 CS 下降沿的 onCsAssert() 在任何 RBNE 之前执行,
|
||||
* 否则 s_csActive 仍为 false,RBNE 中断丢弃接收字节。 */
|
||||
nvic_irq_enable(SPI0_IRQn, 2U, 0U);
|
||||
spi_enable(SPI0);
|
||||
}
|
||||
|
||||
void SpiOta::configureDma() {
|
||||
rcu_periph_clock_enable(RCU_DMA0);
|
||||
dma_deinit(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
dma_parameter_struct dma_init_struct;
|
||||
dma_struct_para_init(&dma_init_struct);
|
||||
dma_init_struct.periph_addr = (uint32_t)(SPI0 + 0x0CU);
|
||||
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
|
||||
dma_init_struct.memory_addr = (uint32_t)s_txBuf;
|
||||
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
|
||||
dma_init_struct.number = 0U;
|
||||
dma_init_struct.priority = DMA_PRIORITY_HIGH;
|
||||
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
|
||||
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
|
||||
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
|
||||
dma_init(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH, &dma_init_struct);
|
||||
dma_circulation_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
dma_channel_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
}
|
||||
|
||||
void SpiOta::configureExti() {
|
||||
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_15);
|
||||
exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
|
||||
exti_interrupt_enable(EXTI_15);
|
||||
exti_interrupt_flag_clear(EXTI_15);
|
||||
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
|
||||
}
|
||||
|
||||
void SpiOta::init() {
|
||||
s_csActive = false;
|
||||
s_rxStreamLen = 0U;
|
||||
s_txStreamPos = 0U;
|
||||
s_txLen = 0U;
|
||||
s_txPending = false;
|
||||
s_isrLogPending = false;
|
||||
memset(s_rxStream, 0, sizeof(s_rxStream));
|
||||
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
|
||||
configureGpio();
|
||||
configureDma();
|
||||
configureSpi0();
|
||||
configureExti();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* CS 边沿处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::onCsAssert() {
|
||||
if (s_csActive) { return; }
|
||||
s_csActive = true;
|
||||
s_rxStreamLen = 0U;
|
||||
s_txStreamPos = 0U;
|
||||
s_isrResponded = false;
|
||||
|
||||
/* 内部 NSS 已在初始化时常置 LOW,这里不再依赖 EXTI 时序切 NSS。
|
||||
* PA15 仅作为“事务开始”的边界信号,用于清接收缓冲和重装 DMA。 */
|
||||
|
||||
/* 关键修复:先彻底停止 DMA,再重新配置并启动。
|
||||
*
|
||||
* 上一版预填 SPI_DATA 方案失败原因:
|
||||
* spi_i2s_data_transmit() 写入的字节进入 TBE 发送缓冲区。
|
||||
* 当 ESP32 第一个 SCK 边沿到来时,TBE 的预填字节被移入移位
|
||||
* 寄存器推到 MISO,同时移位寄存器中的 MOSI 数据被推入 RBNE。
|
||||
* 但 GD32E50x SPI 的 RBNE 标志在 SPI_DATA 写入后可能被误触发,
|
||||
* 导致 isrLogFrame 报出预填值(如 0x80)而非 MOSI 真实数据。
|
||||
*
|
||||
* 正确方案:不预填 SPI_DATA,而是确保 DMA 在 CS↓ 后、ESP32
|
||||
* 第一个 SCK 边沿之前就绪。DMA 第一拍自动把 s_txBuf[0] 写入
|
||||
* SPI_DATA,不会产生 RBNE 误触发。
|
||||
*
|
||||
* 操作顺序:
|
||||
* 1. 禁 DMA 通道 + 禁 SPI DMA 请求(彻底停止旧传输)
|
||||
* 2. 清 RBNE / RXORERR / FERR 等残留标志
|
||||
* 3. 配置并启动 DMA(此时 RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断)
|
||||
* 4. 重新使能 RBNE 中断,开始接收 MOSI 数据
|
||||
*/
|
||||
|
||||
/* 1. 停止旧 DMA */
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
|
||||
|
||||
/* 2. 清残留错误标志
|
||||
*
|
||||
* 关键:这里绝不能在 CS↓ 时“顺手读掉 RBNE”。
|
||||
*
|
||||
* 因为 internal NSS 现在常 LOW,ESP32 在 CS↓ 后很快就会发出首字节 A5。
|
||||
* 如果 EXTI ISR 略晚于 SPI 接收,A5 可能已经先进了 RX 缓冲并置位 RBNE。
|
||||
* 这时若在 onCsAssert() 里执行 spi_i2s_data_receive(),就会把真实首字节 A5
|
||||
* 当成“残留垃圾”吞掉,导致后续看到的首字节变成 0x01。
|
||||
*
|
||||
* 所以这里仅清真正的错误标志,不主动消费 RBNE。
|
||||
* 已到达的首字节留给 onSpiInterrupt() 正常读走。 */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||
}
|
||||
|
||||
/* 3. 配置并启动 DMA(RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断) */
|
||||
if (s_txPending && s_txLen > 0U) {
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
} else {
|
||||
s_txPending = false;
|
||||
s_txLen = kTxBufSize;
|
||||
memset(s_txBuf, 0xFF, kTxBufSize);
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
}
|
||||
|
||||
/* 4. 重新使能 RBNE 中断,开始接收 MOSI 数据 */
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
|
||||
}
|
||||
void SpiOta::onCsRelease() {
|
||||
if (!s_csActive) { return; }
|
||||
s_csActive = false;
|
||||
|
||||
/* 内部 NSS 保持常 LOW,不在 CS↑ 时切回 HIGH。
|
||||
* 这样 SPI 从机始终处于就绪态,下一次 CS↓ 不会因 EXTI 延迟丢首字节。
|
||||
*
|
||||
* 这里只停止 DMA,避免 CS 高期间继续空转消耗 s_txBuf 内容。
|
||||
* s_txPending 保持不变——如果 ISR 已准备好 ACK 帧,
|
||||
* 下次 CS↓ 时 onCsAssert 会重启 DMA 推出。 */
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
|
||||
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
|
||||
u8 last_byte = (u8)(spi_i2s_data_receive(SPI0) & 0xFFU);
|
||||
if (s_rxStreamLen < kRxBufSize) {
|
||||
s_rxStream[s_rxStreamLen++] = last_byte;
|
||||
}
|
||||
}
|
||||
|
||||
/* 关键调试日志:记录本次 CS 周期结束时到底收到了多少字节。
|
||||
* 这样能直接判断:
|
||||
* 1) 是不是完整收到了 ESP32 发来的整帧
|
||||
* 2) 还是只收到了首字节 A5 / 少数字节,导致永远无法解析回包 */
|
||||
if (s_rxStreamLen > 0U) {
|
||||
isrLogFrame("[CS-RX]", s_rxStream, (s_rxStreamLen < 12U) ? s_rxStreamLen : 12U);
|
||||
}
|
||||
|
||||
if (s_rxStreamLen > 0U && !s_isrResponded) {
|
||||
isrProcessFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* SPI0 中断处理 — 只处理 RBNE(接收)和错误,TBE 已由 DMA 替代
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::onSpiInterrupt() {
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
|
||||
/* 与 APP 侧稳定实现保持一致:
|
||||
* 如果 EXTI 下降沿因为中断时序略晚到达,但此时 PA15 已经为低,
|
||||
* 则在首个 RBNE 到来时立即补做 onCsAssert()。
|
||||
*
|
||||
* 关键限制:只能在“本事务尚未收过任何字节”时补做。
|
||||
* 否则若 EXTI / SPI 时序偶发抖动,重复 onCsAssert() 会把
|
||||
* s_rxStreamLen 清零,导致已经收到的半帧被冲掉,最终永远拼不成完整帧。 */
|
||||
if (!s_csActive && s_rxStreamLen == 0U && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
|
||||
onCsAssert();
|
||||
}
|
||||
|
||||
u8 byte = (u8)(spi_i2s_data_receive(SPI0) & 0xFFU);
|
||||
if (s_rxStreamLen < kRxBufSize) {
|
||||
s_rxStream[s_rxStreamLen++] = byte;
|
||||
}
|
||||
/* 调试阶段先关闭 RBNE 首字节日志。
|
||||
* 原因:ISR 日志缓冲是单槽的,RBNE 一旦先占位,
|
||||
* onCsRelease() 里的 [CS-RX] 摘要日志就会被丢弃。
|
||||
* 目前排查重点是“本次 CS 周期总共收到了哪些字节”,
|
||||
* 所以优先保留 [CS-RX],暂时关闭 [RBNE]。 */
|
||||
tryProcessCompleteFrame();
|
||||
}
|
||||
/* TBE 已由 DMA 替代,不再处理 */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
s_rxStreamLen = 0U;
|
||||
isrLogFrame("[RXOR]", (const u8*)"OVR", 3);
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||
}
|
||||
}
|
||||
|
||||
void SpiOta::onCsExtiInterrupt() {
|
||||
if (exti_interrupt_flag_get(EXTI_15) != RESET) {
|
||||
exti_interrupt_flag_clear(EXTI_15);
|
||||
if (gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); }
|
||||
else { onCsRelease(); }
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主循环接口
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::poll() {
|
||||
if (s_isrResponded) {
|
||||
s_isrResponded = false;
|
||||
}
|
||||
/* 打印 ISR 日志缓冲区 */
|
||||
while (s_logCount > 0U) {
|
||||
__disable_irq();
|
||||
if (s_logCount == 0U) { __enable_irq(); break; }
|
||||
u8 *buf = s_logSlots[s_logReadIdx];
|
||||
u8 nextRead = (s_logReadIdx + 1U) % kLogSlotCount;
|
||||
s_logReadIdx = nextRead;
|
||||
s_logCount--;
|
||||
__enable_irq();
|
||||
printf("[SPI] %s\r\n", buf);
|
||||
}
|
||||
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
|
||||
|
||||
/* 调试:定期打印 SPI 从机状态 */
|
||||
static u32 dbg_tick = 0;
|
||||
dbg_tick++;
|
||||
if ((dbg_tick % 5000U) == 0U) {
|
||||
printf("[SPI] csActive=%u rxLen=%u txPend=%u txLen=%u isrResp=%u otaState=%u\r\n",
|
||||
(unsigned)s_csActive, (unsigned)s_rxStreamLen,
|
||||
(unsigned)s_txPending, (unsigned)s_txLen,
|
||||
(unsigned)s_isrResponded,
|
||||
(unsigned)(s_otaHandler ? s_otaHandler->getState() : 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
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::hasActiveSession() { return s_otaSessionActive; }
|
||||
|
||||
u16 SpiOta::copyTxFrame(u8 *out, u16 max_len) {
|
||||
if (!out || max_len == 0U) { return 0U; }
|
||||
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 风格兼容包装
|
||||
* ============================================================ */
|
||||
|
||||
void spi_ota_init(void) { SpiOta::init(); }
|
||||
void spi_ota_poll(void) { SpiOta::poll(); }
|
||||
void spi_ota_set_sender(OTAHandler *handler) { SpiOta::setSender(handler); }
|
||||
void spi_ota_on_cs_assert(void) { SpiOta::onCsAssert(); }
|
||||
void spi_ota_on_cs_release(void) { SpiOta::onCsRelease(); }
|
||||
void spi_ota_gpio_init(void) { SpiOta::configureGpio(); }
|
||||
void spi_ota_spi0_init(void) { SpiOta::configureSpi0(); }
|
||||
void spi_ota_exti_init(void) { SpiOta::configureExti(); }
|
||||
void spi_ota_irq_handler(void) { SpiOta::onSpiInterrupt(); }
|
||||
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(); }
|
||||
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len) { return SpiOta::copyTxFrame(out, max_len); }
|
||||
|
||||
extern "C" void SPI0_IRQHandler(void) {
|
||||
SpiOta::onSpiInterrupt();
|
||||
}
|
||||
@@ -1,626 +0,0 @@
|
||||
#include "SpiOta.h"
|
||||
#include "core_cm33.h"
|
||||
#include "gd32e50x_gpio.h"
|
||||
#include "gd32e50x_exti.h"
|
||||
#include "gd32e50x_rcu.h"
|
||||
#include "gd32e50x_misc.h"
|
||||
#include "gd32e50x_spi.h"
|
||||
#include "gd32e50x_dma.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @file SpiOta.cpp
|
||||
* @brief Bootloader SPI OTA 从机模块实现(DMA 发送版)。
|
||||
*
|
||||
* CS 释放后,EXTI ISR 直接完成帧校验+处理+响应准备,
|
||||
* 响应帧写入 s_txBuf,DMA0_CH2 自动推到 SPI0 TX 寄存器。
|
||||
* 硬件 DMA 替代 TBE 中断逐字节发送,消除字节错位问题。
|
||||
*
|
||||
* GD32E50x DMA 通道映射(固定):
|
||||
* DMA0_CH0: ADC0, DMA0_CH1: SPI0_RX, DMA0_CH2: SPI0_TX
|
||||
* DMA0_CH3: SPI1_RX, DMA0_CH4: SPI1_TX
|
||||
*/
|
||||
|
||||
/* SPI0_TX = DMA0_CH2 (GD32E50x 固定映射) */
|
||||
#define SPI_TX_DMA_PERIPH DMA0
|
||||
#define SPI_TX_DMA_CH DMA_CH2
|
||||
|
||||
/* 调试:多缓冲日志(8 条 ISR 日志缓冲区,避免关键事件丢失) */
|
||||
static constexpr u16 kLogSlotCount = 8U;
|
||||
static constexpr u16 kLogSlotSize = 80U;
|
||||
static u8 s_logSlots[kLogSlotCount][kLogSlotSize] = {};
|
||||
static u16 s_logSlotLens[kLogSlotCount] = {};
|
||||
static volatile u8 s_logWriteIdx = 0U;
|
||||
static volatile u8 s_logReadIdx = 0U;
|
||||
static volatile u8 s_logCount = 0U;
|
||||
|
||||
static void isrLogFrameEx(const char *tag, const u8 *data, u16 len);
|
||||
|
||||
|
||||
OTAHandler *SpiOta::s_otaHandler = nullptr;
|
||||
volatile bool SpiOta::s_csActive = false;
|
||||
volatile u16 SpiOta::s_rxStreamLen = 0U;
|
||||
volatile u16 SpiOta::s_txStreamPos = 0U;
|
||||
u8 SpiOta::s_rxStream[SpiOta::kRxBufSize] = {};
|
||||
u8 SpiOta::s_txBuf[SpiOta::kTxBufSize] = {};
|
||||
volatile u16 SpiOta::s_txLen = 0U;
|
||||
volatile bool SpiOta::s_txPending = false;
|
||||
volatile bool SpiOta::s_isrResponded = false;
|
||||
volatile bool SpiOta::s_isrLogPending = false;
|
||||
volatile bool SpiOta::s_otaSessionActive = false;
|
||||
u8 SpiOta::s_isrLogBuf[kIsrLogBufSize] = {};
|
||||
u16 SpiOta::s_isrLogLen = 0U;
|
||||
|
||||
/* ============================================================
|
||||
* CRC8 / 帧构建 / 帧解析
|
||||
* ============================================================ */
|
||||
|
||||
u8 SpiOta::crc8(const u8 *data, u16 len) {
|
||||
u8 crc = 0x00U;
|
||||
for (u16 i = 0; i < len; ++i) { crc ^= data[i]; }
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SpiOta::buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
|
||||
u8 *out, u16 *out_len) {
|
||||
if (!out || !out_len) { return false; }
|
||||
u16 total_len = (u16)(kFrameOverhead + payload_len);
|
||||
if (total_len > kTxBufSize) { return false; }
|
||||
out[0] = kSof;
|
||||
out[1] = (u8)type;
|
||||
out[2] = (u8)(payload_len & 0xFFU);
|
||||
out[3] = (u8)((payload_len >> 8) & 0xFFU);
|
||||
if (payload && payload_len > 0U) { memcpy(&out[4], payload, payload_len); }
|
||||
out[4 + payload_len] = crc8(out, (u16)(4U + payload_len));
|
||||
*out_len = total_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpiOta::tryParseFrame(const u8 *frame, u16 frame_len,
|
||||
spi_ota_frame_type_t *type,
|
||||
const u8 **payload,
|
||||
u16 *payload_len) {
|
||||
if (!frame || frame_len < kFrameOverhead) { return false; }
|
||||
if (frame[0] != kSof) { return false; }
|
||||
u16 len = (u16)(frame[2] | ((u16)frame[3] << 8));
|
||||
u16 expected = (u16)(kFrameOverhead + len);
|
||||
if (expected != frame_len) { return false; }
|
||||
if (crc8(frame, (u16)(4U + len)) != frame[4U + len]) { return false; }
|
||||
if (type) { *type = (spi_ota_frame_type_t)frame[1]; }
|
||||
if (payload) { *payload = &frame[4]; }
|
||||
if (payload_len){ *payload_len = len; }
|
||||
return true;
|
||||
}
|
||||
|
||||
ota_link_msg_type_t SpiOta::mapMsgType(spi_ota_frame_type_t type) {
|
||||
switch (type) {
|
||||
case SPI_OTA_CMD_REQ: return OTA_LINK_MSG_REQ;
|
||||
case SPI_OTA_CMD_DATA: return OTA_LINK_MSG_DATA;
|
||||
default: return OTA_LINK_MSG_CANCEL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 回包发送适配器(ISR 安全)
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
|
||||
spi_ota_frame_type_t rsp_type = SPI_OTA_RSP_ACK;
|
||||
if (type == OTA_LINK_TX_WAIT) { rsp_type = SPI_OTA_RSP_WAIT; }
|
||||
u8 frame_buf[kTxBufSize] = {};
|
||||
u16 built_len = 0U;
|
||||
if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) {
|
||||
queueTxFrame(frame_buf, built_len);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SpiOta::enterCritical() {
|
||||
uint32_t primask = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
return primask;
|
||||
}
|
||||
|
||||
void SpiOta::exitCritical(uint32_t primask) {
|
||||
__set_PRIMASK(primask);
|
||||
}
|
||||
|
||||
/** @brief 启动 DMA 传输 s_txBuf → SPI0_DATA */
|
||||
static void startSpiTxDma(const u8 *buf, u16 len) {
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
DMA_INTC(SPI_TX_DMA_PERIPH) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, SPI_TX_DMA_CH);
|
||||
DMA_CHMADDR(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = (uint32_t)buf;
|
||||
DMA_CHCNT(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = len;
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) |= DMA_CHXCTL_CHEN;
|
||||
}
|
||||
|
||||
void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
||||
if (!frame || len == 0U || len > kTxBufSize) { return; }
|
||||
uint32_t primask = enterCritical();
|
||||
memcpy(s_txBuf, frame, len);
|
||||
s_txLen = len;
|
||||
s_txPending = true;
|
||||
s_txStreamPos = 0U;
|
||||
if (len < kTxBufSize) { memset(&s_txBuf[len], 0xFF, kTxBufSize - len); }
|
||||
exitCritical(primask);
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* RBNE 中断内即时帧检测与处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::tryProcessCompleteFrame() {
|
||||
u16 rx_len = s_rxStreamLen;
|
||||
if (rx_len == 0U) { return; }
|
||||
|
||||
if (s_rxStream[0] == SPI_OTA_CMD_BOOT) {
|
||||
isrProcessFrame();
|
||||
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();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* CS 释放后 ISR 直接帧处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::isrProcessFrame() {
|
||||
u16 rx_len = s_rxStreamLen;
|
||||
if (rx_len == 0U) { return; }
|
||||
|
||||
/* 关键修复:一旦开始处理本次 CS 周期中的完整帧,立刻标记“已响应/已处理”。
|
||||
*
|
||||
* 否则会出现这样的竞态:
|
||||
* 1) SPI0 RBNE IRQ 在收到最后一个字节后调用 isrProcessFrame()
|
||||
* 2) 还没执行到函数尾部的 s_isrResponded = true
|
||||
* 3) CS 已经上升,优先级更高的 EXTI IRQ 抢占进来执行 onCsRelease()
|
||||
* 4) onCsRelease() 看到 s_isrResponded 仍为 false,于是再次调用 isrProcessFrame()
|
||||
* 5) 同一帧被重复处理一次,状态机被错误推进(如 challenge 又被当 confirm)
|
||||
*
|
||||
* 所以必须在函数入口就先置位,防止 REQ/DATA 被双重分发。 */
|
||||
s_isrResponded = true;
|
||||
|
||||
if (rx_len >= 1U && s_rxStream[0] == SPI_OTA_CMD_BOOT) {
|
||||
u8 ack[4] = {0xB0U, 0xA5U, 0x4FU, 0x54U};
|
||||
queueTxFrame(ack, sizeof(ack));
|
||||
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
|
||||
isrLogFrameEx("[BOOT-CMD]", s_txBuf, s_txLen);
|
||||
s_isrResponded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_rxStream[0] != kSof) {
|
||||
isrLogFrameEx("[NO-SOF]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
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 > rx_len) { return; }
|
||||
|
||||
if (crc8(s_rxStream, (u16)(4U + payload_len)) != s_rxStream[4U + payload_len]) {
|
||||
u8 err = 0xEEU;
|
||||
u8 err_frame[kTxBufSize] = {};
|
||||
u16 built = 0U;
|
||||
if (buildFrame(SPI_OTA_RSP_ERROR, &err, 1U, err_frame, &built)) {
|
||||
queueTxFrame(err_frame, built);
|
||||
}
|
||||
isrLogFrameEx("[CRC-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
return;
|
||||
}
|
||||
|
||||
spi_ota_frame_type_t frame_type;
|
||||
const u8 *payload = nullptr;
|
||||
u16 p_len = 0U;
|
||||
if (!tryParseFrame(s_rxStream, expected, &frame_type, &payload, &p_len)) {
|
||||
isrLogFrameEx("[PARSE-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame_type == SPI_OTA_CMD_BOOT) {
|
||||
u8 ack_ok = 0xA5U;
|
||||
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(); }
|
||||
isrLogFrameEx("[BOOT-FRAME]", s_txBuf, s_txLen);
|
||||
s_isrResponded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_otaHandler != nullptr) {
|
||||
s_otaSessionActive = true;
|
||||
s_otaHandler->setTransportSender(sender);
|
||||
ota_link_msg_type_t msg_type = mapMsgType(frame_type);
|
||||
isrLogFrameEx("[OTA-MSG]", (const u8*)&frame_type, 1);
|
||||
/* 诊断: 记录 OTA 状态机当前状态和帧信息 */
|
||||
{
|
||||
u8 diag[4] = {(u8)frame_type, (u8)p_len, (u8)(p_len >> 8),
|
||||
(u8)(s_otaHandler ? s_otaHandler->getState() : 0xFFU)};
|
||||
isrLogFrameEx("[OTA-DIAG]", diag, 4);
|
||||
}
|
||||
s_otaHandler->handleLinkMessage(msg_type, const_cast<u8 *>(payload), (u8)p_len);
|
||||
}
|
||||
|
||||
isrLogFrameEx("[ISR-DONE]", s_txBuf, (s_txLen < 16U) ? s_txLen : 16U);
|
||||
s_isrResponded = true;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* ISR 内延迟日志
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::isrLogFrame(const char *tag, const u8 *data, u16 len) {
|
||||
isrLogFrameEx(tag, data, len);
|
||||
}
|
||||
|
||||
static void isrLogFrameEx(const char *tag, const u8 *data, u16 len) {
|
||||
if (s_logCount >= kLogSlotCount) { return; }
|
||||
u8 *buf = s_logSlots[s_logWriteIdx];
|
||||
u16 pos = 0U;
|
||||
if (tag) {
|
||||
while (*tag && pos < kLogSlotSize - 3U) { buf[pos++] = (u8)*tag++; }
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
const u16 preview = (len < 24U) ? len : 24U;
|
||||
for (u16 i = 0U; i < preview && pos + 2U < kLogSlotSize; ++i) {
|
||||
static const char hx[] = "0123456789ABCDEF";
|
||||
buf[pos++] = (u8)hx[data[i] >> 4];
|
||||
buf[pos++] = (u8)hx[data[i] & 0x0FU];
|
||||
}
|
||||
buf[pos] = 0U;
|
||||
s_logSlotLens[s_logWriteIdx] = pos;
|
||||
s_logWriteIdx = (s_logWriteIdx + 1U) % kLogSlotCount;
|
||||
s_logCount++;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 硬件初始化
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::configureGpio() {
|
||||
rcu_periph_clock_enable(RCU_GPIOA);
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
gpio_pin_remap_config(GPIO_SPI0_REMAP, ENABLE);
|
||||
gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
|
||||
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
|
||||
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_6 | GPIO_PIN_7);
|
||||
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
|
||||
}
|
||||
|
||||
void SpiOta::configureSpi0() {
|
||||
rcu_periph_clock_enable(RCU_SPI0);
|
||||
spi_i2s_deinit(SPI0);
|
||||
(void)spi_i2s_data_receive(SPI0); /* 清空 SPI_DATA 残留值 */
|
||||
spi_parameter_struct spi_init_struct;
|
||||
spi_struct_para_init(&spi_init_struct);
|
||||
spi_init_struct.device_mode = SPI_SLAVE;
|
||||
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
|
||||
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
|
||||
spi_init_struct.nss = SPI_NSS_SOFT;
|
||||
spi_init_struct.endian = SPI_ENDIAN_MSB;
|
||||
/* SPI NSS 由 ESP32 的 CS 线 (GPIO4→PA15) 控制。\外部 CS 高时:
|
||||
* spi_nss_internal_high(SPI0) 让 SPI 外设进入未选中状态,
|
||||
* 这样 SPI0_DATA 输出 0xFF(而非 0x00),
|
||||
* DMA 只在 CS 低时才被 SPI 外设允许推数据到 MISO。 */
|
||||
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
|
||||
spi_init_struct.prescale = SPI_PSC_8;
|
||||
spi_init(SPI0, &spi_init_struct);
|
||||
/* 关键修正:内部 NSS 始终保持 LOW,使 SPI0 从机一直处于“已选中”状态。
|
||||
*
|
||||
* APP 侧的稳定实现就是这样做的:PA15 只作为 EXTI 事务边界信号,
|
||||
* 不再依赖 EXTI ISR 去“赶在首个 SCK 之前”切 internal NSS。
|
||||
*
|
||||
* 之前 BOOT 的问题正出在这里:
|
||||
* - 空闲时 spi_nss_internal_high(SPI0)
|
||||
* - CS↓ 后靠 EXTI ISR 再切 internal_low
|
||||
* - 但 ESP32 在 CS↓ 后几乎立刻出时钟
|
||||
* - 于是 GD32 常常在 internal NSS 真正拉低前丢掉首字节/首几位
|
||||
* → 请求帧首字节 0xA5 被吃掉,看到 0x80 / 0xFF / 随机 0x07
|
||||
*
|
||||
* 现在改为 internal NSS 常低:
|
||||
* - SPI 从机始终就绪,不会因为 EXTI 延迟丢首字节
|
||||
* - PA15 仍用于 onCsAssert/onCsRelease 做缓冲区清零和 DMA 重装
|
||||
*/
|
||||
spi_nss_internal_low(SPI0);
|
||||
/* 初始化时清空 SPI_DATA 寄存器,防止残留值影响首次传输 */
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
|
||||
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
|
||||
/* SPI0 RBNE 中断优先级设为 (2, 0),低于 EXTI CS 中断 (1, 1)。
|
||||
* 这确保 CS 下降沿的 onCsAssert() 在任何 RBNE 之前执行,
|
||||
* 否则 s_csActive 仍为 false,RBNE 中断丢弃接收字节。 */
|
||||
nvic_irq_enable(SPI0_IRQn, 2U, 0U);
|
||||
spi_enable(SPI0);
|
||||
}
|
||||
|
||||
void SpiOta::configureDma() {
|
||||
rcu_periph_clock_enable(RCU_DMA0);
|
||||
dma_deinit(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
dma_parameter_struct dma_init_struct;
|
||||
dma_struct_para_init(&dma_init_struct);
|
||||
dma_init_struct.periph_addr = (uint32_t)(SPI0 + 0x0CU);
|
||||
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
|
||||
dma_init_struct.memory_addr = (uint32_t)s_txBuf;
|
||||
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
|
||||
dma_init_struct.number = 0U;
|
||||
dma_init_struct.priority = DMA_PRIORITY_HIGH;
|
||||
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
|
||||
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
|
||||
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
|
||||
dma_init(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH, &dma_init_struct);
|
||||
dma_circulation_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
dma_channel_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
}
|
||||
|
||||
void SpiOta::configureExti() {
|
||||
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_15);
|
||||
exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
|
||||
exti_interrupt_enable(EXTI_15);
|
||||
exti_interrupt_flag_clear(EXTI_15);
|
||||
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
|
||||
}
|
||||
|
||||
void SpiOta::init() {
|
||||
s_csActive = false;
|
||||
s_rxStreamLen = 0U;
|
||||
s_txStreamPos = 0U;
|
||||
s_txLen = 0U;
|
||||
s_txPending = false;
|
||||
s_isrLogPending = false;
|
||||
memset(s_rxStream, 0, sizeof(s_rxStream));
|
||||
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
|
||||
configureGpio();
|
||||
configureDma();
|
||||
configureSpi0();
|
||||
configureExti();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* CS 边沿处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::onCsAssert() {
|
||||
if (s_csActive) { return; }
|
||||
s_csActive = true;
|
||||
s_rxStreamLen = 0U;
|
||||
s_txStreamPos = 0U;
|
||||
s_isrResponded = false;
|
||||
|
||||
/* 内部 NSS 已在初始化时常置 LOW,这里不再依赖 EXTI 时序切 NSS。
|
||||
* PA15 仅作为“事务开始”的边界信号,用于清接收缓冲和重装 DMA。 */
|
||||
|
||||
/* 关键修复:先彻底停止 DMA,再重新配置并启动。
|
||||
*
|
||||
* 上一版预填 SPI_DATA 方案失败原因:
|
||||
* spi_i2s_data_transmit() 写入的字节进入 TBE 发送缓冲区。
|
||||
* 当 ESP32 第一个 SCK 边沿到来时,TBE 的预填字节被移入移位
|
||||
* 寄存器推到 MISO,同时移位寄存器中的 MOSI 数据被推入 RBNE。
|
||||
* 但 GD32E50x SPI 的 RBNE 标志在 SPI_DATA 写入后可能被误触发,
|
||||
* 导致 isrLogFrame 报出预填值(如 0x80)而非 MOSI 真实数据。
|
||||
*
|
||||
* 正确方案:不预填 SPI_DATA,而是确保 DMA 在 CS↓ 后、ESP32
|
||||
* 第一个 SCK 边沿之前就绪。DMA 第一拍自动把 s_txBuf[0] 写入
|
||||
* SPI_DATA,不会产生 RBNE 误触发。
|
||||
*
|
||||
* 操作顺序:
|
||||
* 1. 禁 DMA 通道 + 禁 SPI DMA 请求(彻底停止旧传输)
|
||||
* 2. 清 RBNE / RXORERR / FERR 等残留标志
|
||||
* 3. 配置并启动 DMA(此时 RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断)
|
||||
* 4. 重新使能 RBNE 中断,开始接收 MOSI 数据
|
||||
*/
|
||||
|
||||
/* 1. 停止旧 DMA */
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
|
||||
|
||||
/* 2. 清残留错误标志
|
||||
*
|
||||
* 关键:这里绝不能在 CS↓ 时“顺手读掉 RBNE”。
|
||||
*
|
||||
* 因为 internal NSS 现在常 LOW,ESP32 在 CS↓ 后很快就会发出首字节 A5。
|
||||
* 如果 EXTI ISR 略晚于 SPI 接收,A5 可能已经先进了 RX 缓冲并置位 RBNE。
|
||||
* 这时若在 onCsAssert() 里执行 spi_i2s_data_receive(),就会把真实首字节 A5
|
||||
* 当成“残留垃圾”吞掉,导致后续看到的首字节变成 0x01。
|
||||
*
|
||||
* 所以这里仅清真正的错误标志,不主动消费 RBNE。
|
||||
* 已到达的首字节留给 onSpiInterrupt() 正常读走。 */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||
}
|
||||
|
||||
/* 3. 配置并启动 DMA(RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断) */
|
||||
if (s_txPending && s_txLen > 0U) {
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
} else {
|
||||
s_txPending = false;
|
||||
s_txLen = kTxBufSize;
|
||||
memset(s_txBuf, 0xFF, kTxBufSize);
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
}
|
||||
|
||||
/* 4. 重新使能 RBNE 中断,开始接收 MOSI 数据 */
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
|
||||
}
|
||||
void SpiOta::onCsRelease() {
|
||||
if (!s_csActive) { return; }
|
||||
s_csActive = false;
|
||||
|
||||
/* 内部 NSS 保持常 LOW,不在 CS↑ 时切回 HIGH。
|
||||
* 这样 SPI 从机始终处于就绪态,下一次 CS↓ 不会因 EXTI 延迟丢首字节。
|
||||
*
|
||||
* 这里只停止 DMA,避免 CS 高期间继续空转消耗 s_txBuf 内容。
|
||||
* s_txPending 保持不变——如果 ISR 已准备好 ACK 帧,
|
||||
* 下次 CS↓ 时 onCsAssert 会重启 DMA 推出。 */
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
|
||||
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
|
||||
u8 last_byte = (u8)(spi_i2s_data_receive(SPI0) & 0xFFU);
|
||||
if (s_rxStreamLen < kRxBufSize) {
|
||||
s_rxStream[s_rxStreamLen++] = last_byte;
|
||||
}
|
||||
}
|
||||
|
||||
/* 关键调试日志:记录本次 CS 周期结束时到底收到了多少字节。
|
||||
* 这样能直接判断:
|
||||
* 1) 是不是完整收到了 ESP32 发来的整帧
|
||||
* 2) 还是只收到了首字节 A5 / 少数字节,导致永远无法解析回包 */
|
||||
if (s_rxStreamLen > 0U) {
|
||||
isrLogFrame("[CS-RX]", s_rxStream, (s_rxStreamLen < 12U) ? s_rxStreamLen : 12U);
|
||||
}
|
||||
|
||||
if (s_rxStreamLen > 0U && !s_isrResponded) {
|
||||
isrProcessFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* SPI0 中断处理 — 只处理 RBNE(接收)和错误,TBE 已由 DMA 替代
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::onSpiInterrupt() {
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
|
||||
/* 与 APP 侧稳定实现保持一致:
|
||||
* 如果 EXTI 下降沿因为中断时序略晚到达,但此时 PA15 已经为低,
|
||||
* 则在首个 RBNE 到来时立即补做 onCsAssert()。
|
||||
*
|
||||
* 关键限制:只能在“本事务尚未收过任何字节”时补做。
|
||||
* 否则若 EXTI / SPI 时序偶发抖动,重复 onCsAssert() 会把
|
||||
* s_rxStreamLen 清零,导致已经收到的半帧被冲掉,最终永远拼不成完整帧。 */
|
||||
if (!s_csActive && s_rxStreamLen == 0U && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
|
||||
onCsAssert();
|
||||
}
|
||||
|
||||
u8 byte = (u8)(spi_i2s_data_receive(SPI0) & 0xFFU);
|
||||
if (s_rxStreamLen < kRxBufSize) {
|
||||
s_rxStream[s_rxStreamLen++] = byte;
|
||||
}
|
||||
/* 调试阶段先关闭 RBNE 首字节日志。
|
||||
* 原因:ISR 日志缓冲是单槽的,RBNE 一旦先占位,
|
||||
* onCsRelease() 里的 [CS-RX] 摘要日志就会被丢弃。
|
||||
* 目前排查重点是“本次 CS 周期总共收到了哪些字节”,
|
||||
* 所以优先保留 [CS-RX],暂时关闭 [RBNE]。 */
|
||||
tryProcessCompleteFrame();
|
||||
}
|
||||
/* TBE 已由 DMA 替代,不再处理 */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
s_rxStreamLen = 0U;
|
||||
isrLogFrame("[RXOR]", (const u8*)"OVR", 3);
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||
}
|
||||
}
|
||||
|
||||
void SpiOta::onCsExtiInterrupt() {
|
||||
if (exti_interrupt_flag_get(EXTI_15) != RESET) {
|
||||
exti_interrupt_flag_clear(EXTI_15);
|
||||
if (gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); }
|
||||
else { onCsRelease(); }
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主循环接口
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::poll() {
|
||||
if (s_isrResponded) {
|
||||
s_isrResponded = false;
|
||||
}
|
||||
/* 打印 ISR 日志缓冲区 */
|
||||
while (s_logCount > 0U) {
|
||||
__disable_irq();
|
||||
if (s_logCount == 0U) { __enable_irq(); break; }
|
||||
u8 *buf = s_logSlots[s_logReadIdx];
|
||||
u8 nextRead = (s_logReadIdx + 1U) % kLogSlotCount;
|
||||
s_logReadIdx = nextRead;
|
||||
s_logCount--;
|
||||
__enable_irq();
|
||||
printf("[SPI] %s\r\n", buf);
|
||||
}
|
||||
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
|
||||
|
||||
/* 调试:定期打印 SPI 从机状态 */
|
||||
static u32 dbg_tick = 0;
|
||||
dbg_tick++;
|
||||
if ((dbg_tick % 5000U) == 0U) {
|
||||
printf("[SPI] csActive=%u rxLen=%u txPend=%u txLen=%u isrResp=%u otaState=%u\r\n",
|
||||
(unsigned)s_csActive, (unsigned)s_rxStreamLen,
|
||||
(unsigned)s_txPending, (unsigned)s_txLen,
|
||||
(unsigned)s_isrResponded,
|
||||
(unsigned)(s_otaHandler ? s_otaHandler->getState() : 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
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::hasActiveSession() { return s_otaSessionActive; }
|
||||
|
||||
u16 SpiOta::copyTxFrame(u8 *out, u16 max_len) {
|
||||
if (!out || max_len == 0U) { return 0U; }
|
||||
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 风格兼容包装
|
||||
* ============================================================ */
|
||||
|
||||
void spi_ota_init(void) { SpiOta::init(); }
|
||||
void spi_ota_poll(void) { SpiOta::poll(); }
|
||||
void spi_ota_set_sender(OTAHandler *handler) { SpiOta::setSender(handler); }
|
||||
void spi_ota_on_cs_assert(void) { SpiOta::onCsAssert(); }
|
||||
void spi_ota_on_cs_release(void) { SpiOta::onCsRelease(); }
|
||||
void spi_ota_gpio_init(void) { SpiOta::configureGpio(); }
|
||||
void spi_ota_spi0_init(void) { SpiOta::configureSpi0(); }
|
||||
void spi_ota_exti_init(void) { SpiOta::configureExti(); }
|
||||
void spi_ota_irq_handler(void) { SpiOta::onSpiInterrupt(); }
|
||||
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(); }
|
||||
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len) { return SpiOta::copyTxFrame(out, max_len); }
|
||||
|
||||
extern "C" void SPI0_IRQHandler(void) {
|
||||
SpiOta::onSpiInterrupt();
|
||||
}
|
||||
@@ -56,7 +56,6 @@ public:
|
||||
static void configureSpi0();
|
||||
static void configureDma();
|
||||
static void configureExti();
|
||||
static void onSpiInterrupt();
|
||||
static void onCsExtiInterrupt();
|
||||
|
||||
static bool hasTxFrame();
|
||||
@@ -125,25 +124,4 @@ private:
|
||||
/* OTA.cpp 通过此变量更新 SPI 响应帧的 CREDIT 字段 */
|
||||
extern volatile u8 g_localCredit;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void spi_ota_init(void);
|
||||
void spi_ota_poll(void);
|
||||
void spi_ota_service_pending_tx(void);
|
||||
void spi_ota_set_sender(OTAHandler *handler);
|
||||
void spi_ota_on_cs_assert(void);
|
||||
void spi_ota_on_cs_release(void);
|
||||
void spi_ota_gpio_init(void);
|
||||
void spi_ota_spi0_init(void);
|
||||
void spi_ota_exti_init(void);
|
||||
void spi_ota_irq_handler(void);
|
||||
void spi_ota_cs_exti_handler(void);
|
||||
bool spi_ota_has_tx_frame(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1060,25 +1060,6 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 兼容旧接口:按 CAN ID 分发 OTA 消息。
|
||||
*
|
||||
* @param id CAN 扩展帧 ID。
|
||||
* @param data 指向消息数据缓冲区的指针。
|
||||
* @param len 消息数据长度。
|
||||
* @return 无。
|
||||
*/
|
||||
void OTAHandler::handleOTAMessage(u32 id, u8 *data, u8 len) {
|
||||
/** 兼容旧的"按 CAN ID 分发"调用方式。 */
|
||||
if (id == CANFD_OTA_CANCEL_ID) {
|
||||
handleLinkMessage(OTA_LINK_MSG_CANCEL, data, len);
|
||||
} else if (id == CANFD_OTA_DATA_ID) {
|
||||
handleLinkMessage(OTA_LINK_MSG_DATA, data, len);
|
||||
} else if (id == CANFD_OTA_REQ_ID) {
|
||||
handleLinkMessage(OTA_LINK_MSG_REQ, data, len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送 OTA ACK 帧。
|
||||
*
|
||||
|
||||
+1
-12
@@ -1,4 +1,4 @@
|
||||
#ifndef __OTA_H
|
||||
#ifndef __OTA_H
|
||||
#define __OTA_H
|
||||
|
||||
#include "device/Can.h"
|
||||
@@ -46,7 +46,6 @@ typedef void (*ota_send_fn_t)(ota_link_tx_type_t type, u8 len, const u8 *data);
|
||||
*/
|
||||
class OTAHandler {
|
||||
public:
|
||||
u32 ota_stateota_time; /**< 历史遗留字段,当前实现未实际使用。 */
|
||||
u32 received_size; /**< 已接收总字节数。 */
|
||||
u32 expected_size; /**< 预期固件总字节数。 */
|
||||
|
||||
@@ -142,16 +141,6 @@ class OTAHandler {
|
||||
*/
|
||||
void handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len);
|
||||
|
||||
/**
|
||||
* @brief 兼容旧接口:按 CAN ID 处理 OTA 消息。
|
||||
*
|
||||
* @param id CAN 扩展帧 ID。
|
||||
* @param data 指向消息数据缓冲区的指针。
|
||||
* @param len 消息数据长度。
|
||||
* @return 无。
|
||||
*/
|
||||
void handleOTAMessage(u32 id, u8 *data, u8 len);
|
||||
|
||||
/**
|
||||
* @brief 关闭 Bootloader 运行环境并跳转执行主应用。
|
||||
*
|
||||
|
||||
@@ -14,9 +14,9 @@ typedef uint32_t u32;
|
||||
|
||||
/* BOOT 固件版本号 */
|
||||
#define BOOT_FW_VERSION_MAJOR 1U
|
||||
#define BOOT_FW_VERSION_MINOR 0U
|
||||
#define BOOT_FW_VERSION_PATCH 2U
|
||||
#define BOOT_FW_VERSION_BUILD 1U
|
||||
#define BOOT_FW_VERSION_MINOR 1U
|
||||
#define BOOT_FW_VERSION_PATCH 0U
|
||||
#define BOOT_FW_VERSION_BUILD 0U
|
||||
|
||||
/**
|
||||
* Flash 布局说明(GD32E508VE,512KB)
|
||||
|
||||
@@ -62,89 +62,6 @@ int BootDebugUart::putChar(int ch, FILE *f)
|
||||
*/
|
||||
int fputc(int ch, FILE *f) throw() { return BootDebugUart::putChar(ch, f); }
|
||||
|
||||
/**
|
||||
* @brief Initialize USART0 through the boot debug UART helper.
|
||||
* @return 1 when initialization succeeds.
|
||||
*/
|
||||
u8 USART0_init(void) { return BootDebugUart::init(); }
|
||||
|
||||
/**
|
||||
* @brief 获取复位原因。
|
||||
*
|
||||
* @return 复位原因编码。
|
||||
*/
|
||||
u16 BootPlatform::getResetCause(void)
|
||||
{
|
||||
/** 预留:如需区分上电/看门狗/软复位等原因,可在此补齐。 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get reset cause through the platform helper.
|
||||
* @return Reset-cause code.
|
||||
*/
|
||||
u16 get_reset_cause(void) { return BootPlatform::getResetCause(); }
|
||||
|
||||
/**
|
||||
* @brief 初始化未使用引脚。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void BootPlatform::initUnusedPins(void)
|
||||
{
|
||||
/**
|
||||
* 预留:如果需要降低悬空功耗,可把未使用 GPIO 统一初始化。
|
||||
* 当前只保证时钟可用,未进一步配置具体引脚。
|
||||
*/
|
||||
rcu_periph_clock_enable(RCU_GPIOA);
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
rcu_periph_clock_enable(RCU_GPIOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize unused pins through the platform helper.
|
||||
* @return none
|
||||
*/
|
||||
void unused_pin_init(void) { BootPlatform::initUnusedPins(); }
|
||||
|
||||
/**
|
||||
* @brief 打印系统时钟信息。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void BootPlatform::printSystemClockInfo(void)
|
||||
{
|
||||
/** 预留:可在此打印系统时钟源与频率。 */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print system clock info through the platform helper.
|
||||
* @return none
|
||||
*/
|
||||
void print_system_clock_info(void) { BootPlatform::printSystemClockInfo(); }
|
||||
|
||||
/**
|
||||
* @brief 扫描主应用区 Flash 内容。
|
||||
*
|
||||
* @param print_content 是否打印内容。
|
||||
* @param page_count 需要扫描的页数。
|
||||
* @return 无。
|
||||
*/
|
||||
void BootPlatform::scanAppFlashContent(int print_content, u32 page_count)
|
||||
{
|
||||
/** 预留:调试用,后续可打印主应用区内容。 */
|
||||
(void)print_content;
|
||||
(void)page_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Scan application flash content through the platform helper.
|
||||
* @param print_content Whether to print flash payload.
|
||||
* @param page_count Number of pages to scan.
|
||||
* @return none
|
||||
*/
|
||||
void scanAppFlashContent(int print_content, u32 page_count) { BootPlatform::scanAppFlashContent(print_content, page_count); }
|
||||
|
||||
/**
|
||||
* @brief 向指定 Flash 地址写入一个 32 位字。
|
||||
*
|
||||
@@ -268,9 +185,8 @@ void BootSleepManager::onExtiInterrupt(void)
|
||||
}
|
||||
|
||||
/* 注意:SPI OTA 的 CS 边沿信号 PA15 映射到 EXTI_15,属于 EXTI10_15_IRQn,
|
||||
* 由 EXTI10_15_IRQHandler 直接分发到 SpiOta::onCsExtiInterrupt(),
|
||||
* 不应在此处(EXTI5_9_Handler)调用 spi_ota_cs_exti_handler()。
|
||||
* 该调用属于死代码,会导致 PA15 边沿在错误的 ISR 中被重复处理。 */
|
||||
* 由 EXTI10_15_IRQHandler 直接分发到 SpiOta::onCsExtiInterrupt()。
|
||||
* 本函数(EXTI5_9)只处理 PB8 深睡唤醒,不处理 SPI OTA CS 信号。 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,18 +223,6 @@ int BootSleepManager::check(void)
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enter deep sleep through the sleep manager.
|
||||
* @return none
|
||||
*/
|
||||
void enter_deep_sleep(void) { BootSleepManager::enterDeepSleep(); }
|
||||
|
||||
/**
|
||||
* @brief Check sleep configuration through the sleep manager.
|
||||
* @return Status code from BootSleepManager::check().
|
||||
*/
|
||||
int check_sleep(void) { return BootSleepManager::check(); }
|
||||
|
||||
/**
|
||||
* @brief External interrupt handler for EXTI line group 5 to 9.
|
||||
* @return none
|
||||
|
||||
@@ -42,19 +42,6 @@ private:
|
||||
static fmc_state_enum writeAddress(u32 address, u32 data);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Bootloader 平台辅助模块。
|
||||
*
|
||||
* 负责复位原因读取、未使用引脚初始化、系统时钟信息打印与应用区扫描等平台杂项。
|
||||
*/
|
||||
class BootPlatform {
|
||||
public:
|
||||
static u16 getResetCause();
|
||||
static void initUnusedPins();
|
||||
static void printSystemClockInfo();
|
||||
static void scanAppFlashContent(int print_content, u32 page_count);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief printf 重定向输出函数。
|
||||
*
|
||||
@@ -64,52 +51,6 @@ public:
|
||||
*/
|
||||
int fputc(int ch, FILE *f) throw();
|
||||
|
||||
/**
|
||||
* @brief 初始化 USART0 调试串口。
|
||||
*
|
||||
* @return 1 表示初始化完成。
|
||||
*/
|
||||
u8 USART0_init(void);
|
||||
|
||||
/**
|
||||
* @brief 获取复位原因。
|
||||
*
|
||||
* @note 当前实现为占位实现。
|
||||
*
|
||||
* @return 复位原因编码。
|
||||
*/
|
||||
u16 get_reset_cause(void);
|
||||
|
||||
/**
|
||||
* @brief 初始化未使用引脚。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void unused_pin_init(void);
|
||||
|
||||
/**
|
||||
* @brief 打印系统时钟信息。
|
||||
*
|
||||
* @note 当前实现为占位实现。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void print_system_clock_info(void);
|
||||
|
||||
/**
|
||||
* @brief 进入深度睡眠模式。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void enter_deep_sleep(void);
|
||||
|
||||
/**
|
||||
* @brief 检查睡眠配置并决定是否进入深睡。
|
||||
*
|
||||
* @return 0 表示已有有效配置;1 表示写默认配置失败;2 表示已写入默认禁用配置。
|
||||
*/
|
||||
int check_sleep(void);
|
||||
|
||||
/**
|
||||
* @brief EXTI5_9 中断服务函数。
|
||||
*
|
||||
@@ -123,15 +64,6 @@ void EXTI5_9_IRQHandler(void);
|
||||
*/
|
||||
void EXTI10_15_IRQHandler(void);
|
||||
|
||||
/**
|
||||
* @brief 扫描主应用区 Flash 内容。
|
||||
*
|
||||
* @param print_content 是否打印内容。
|
||||
* @param page_count 需要扫描的页数。
|
||||
* @return 无。
|
||||
*/
|
||||
void scanAppFlashContent(int print_content, u32 page_count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -127,12 +127,6 @@ uint32_t SysTickTimer::getMs()
|
||||
return s_msTicks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure SysTick via the timer helper.
|
||||
* @return none
|
||||
*/
|
||||
void systick_config(void) { SysTickTimer::init(); }
|
||||
|
||||
/**
|
||||
* @brief Delay for microseconds via the timer helper.
|
||||
* @param count Delay time in microseconds.
|
||||
@@ -147,19 +141,6 @@ void delay_1us(uint32_t count) { SysTickTimer::delayUs(count); }
|
||||
*/
|
||||
void delay_1ms(uint32_t count) { SysTickTimer::delayMs(count); }
|
||||
|
||||
/**
|
||||
* @brief Delay for seconds via the timer helper.
|
||||
* @param count Delay time in seconds.
|
||||
* @return none
|
||||
*/
|
||||
void delay_1s(uint32_t count) { SysTickTimer::delaySeconds(count); }
|
||||
|
||||
/**
|
||||
* @brief Decrement the global millisecond delay counter.
|
||||
* @return none
|
||||
*/
|
||||
void delay_decrement(void) { SysTickTimer::decrement(); }
|
||||
|
||||
/**
|
||||
* @brief Trigger a software reset via the timer helper.
|
||||
* @return none
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef SYSTICK_H
|
||||
#ifndef SYSTICK_H
|
||||
#define SYSTICK_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -37,13 +37,6 @@ private:
|
||||
static volatile uint32_t s_msTicks;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置 SysTick 为 1ms 周期中断。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void systick_config(void);
|
||||
|
||||
/**
|
||||
* @brief 执行毫秒级阻塞延时。
|
||||
*
|
||||
@@ -60,21 +53,6 @@ void delay_1ms(uint32_t count);
|
||||
*/
|
||||
void delay_1us(uint32_t count);
|
||||
|
||||
/**
|
||||
* @brief 执行秒级阻塞延时。
|
||||
*
|
||||
* @param count 延时秒数。
|
||||
* @return 无。
|
||||
*/
|
||||
void delay_1s(uint32_t count);
|
||||
|
||||
/**
|
||||
* @brief 递减 SysTick 维护的毫秒倒计时。
|
||||
*
|
||||
* @return 无。
|
||||
*/
|
||||
void delay_decrement(void);
|
||||
|
||||
/**
|
||||
* @brief 触发 MCU 软件复位。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user