refactor(SPI): 实现全双工DMA传输以提升通信可靠性

- 配置DMA0_CH1用于SPI0_RX接收,替代原有的RBNE中断逐字节处理
- 添加startSpiRxDma、stopSpiDmas、drainSpiRxHw等DMA控制函数
- 在CS空闲期预装下一笔DMA传输,避免EXTI CS下降沿相对于SCK偏晚导致收0字节
- 与BOOT工程SpiOta的DMA配置对齐,确保通信稳定性

refactor(SPI): 优化CS边沿处理逻辑和命令解析时机

- 移除原有的EXTI5_9_IRQHandler用于SPI Ready引脚处理
- 将命令处理从异步任务调度改为在CS释放中断中同步完成
- 重构onCsAssert和onCsRelease逻辑,简化DMA启停流程
- 删除TASK_SPI_CMD_PROCESS任务类型,减少不必要的任务调度

feat(CANFD): 修改transmitFrame返回类型为枚举值以提供详细错误信息

- 将CanFd::transmitFrame返回类型从bool改为CanTxResult枚举
- 新增CanTxResult枚举定义,包含kOk、kErrBusOff、kErrTecHigh、kErrMailbox、kErrInvalid
- 更新SPI桥接命令处理中对transmitFrame返回值的处理逻辑
- 枚举值设计与SPI错误响应码对齐,便于直接透传错误信息

refactor(SPI): 实现全双工DMA传输以提升通信可靠性

- 配置DMA0_CH1用于SPI0_RX接收,替代原有的RBNE中断逐字节处理
- 添加startSpiRxDma、stopSpiDmas、drainSpiRxHw等DMA控制函数
- 在CS空闲期预装下一笔DMA传输,避免EXTI CS下降沿相对于SCK偏晚导致收0字节
- 与BOOT工程SpiOta的DMA配置对齐,确保通信稳定性

refactor(SPI): 优化CS边沿处理逻辑和命令解析时机

- 移除原有的EXTI5_9_IRQHandler用于SPI Ready引脚处理
- 将命令处理从异步任务调度改为在CS释放中断中同步完成
- 重构onCsAssert和onCsRelease逻辑,简化DMA启停流程
- 删除TASK_SPI_CMD_PROCESS任务类型,减少不必要的任务调度

feat(OTA): 添加BOOT版本号读取功能并优化版本信息获取

- 在ota_boot_request模块中添加readBootVersion函数
- 从OTA配置页Flash读取BOOT版本号信息
- 在SPI PING命令响应中使用新的版本读取接口
- 统一版本号获取方式,避免裸指针访问Flash地址

refactor(task): 调整诊断打印任务调度周期为5秒间隔

- 新增div_diag_print调度分频计数器用于诊断打印
- 将诊断打印任务从心跳任务中分离,独立控制执行周期
- 修改诊断打印函数注释,明确其由5秒节拍触发
- 优化任务调度逻辑,提高系统运行效率
This commit is contained in:
cbb
2026-07-15 00:34:56 +08:00
parent 8bc01dd654
commit a52d94687b
13 changed files with 254 additions and 148 deletions
+148 -92
View File
@@ -55,9 +55,13 @@ static constexpr uint8_t kRspAck = SpiDefs::kRspAck;
static constexpr uint8_t kRspWait = SpiDefs::kRspWait;
static constexpr uint8_t kRspError = SpiDefs::kRspError;
/* DMA 配置(与 BOOT SpiOta 一致:DMA0_CH2 用于 SPI0 TX) */
/* DMA 全双工配置(与 BOOT SpiOta 一致):
* DMA0_CH1: SPI0_RX DMA0_CH2: SPI0_TX
* RX 走 DMA 后禁用 RBNE 逐字节中断,避免 256B 事务 RX-OVR/SOF 丢失。 */
#define SPI_TX_DMA_PERIPH DMA0
#define SPI_TX_DMA_CH DMA_CH2
#define SPI_RX_DMA_PERIPH DMA0
#define SPI_RX_DMA_CH DMA_CH1
static void startSpiTxDma(const uint8_t *buf, uint16_t len)
{
@@ -68,6 +72,45 @@ static void startSpiTxDma(const uint8_t *buf, uint16_t len)
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) |= DMA_CHXCTL_CHEN;
}
/** @brief 启动 DMA 接收 SPI0_DATA → m_rxBuf(容量 kRxBufSize)。
* 每次 CS 空闲期预装,下一笔 CS 上升沿时由 stopSpiDmas() 读 CHCNT 反算实际长度。 */
static void startSpiRxDma(uint8_t *rx_buf, uint16_t rx_cap)
{
DMA_CHCTL(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
DMA_INTC(SPI_RX_DMA_PERIPH) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, SPI_RX_DMA_CH);
DMA_CHMADDR(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH) = (uint32_t)rx_buf;
DMA_CHCNT(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH) = rx_cap;
DMA_CHCTL(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH) |= DMA_CHXCTL_CHEN;
}
/** @brief 清空 SPI 接收通路残留(含 OVR),对齐 BOOT drainSpiRxHw。 */
static void drainSpiRxHw(void)
{
while (spi_i2s_flag_get(SPI0, SPI_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);
}
}
/** @brief 停止双 DMA;返回本 CS 实际 RX 字节数(必须在 disable 前读 CHCNT)。
* 对齐 BOOT stopSpiDmas:先读 remaining 再 disable,否则计数器被清。 */
static uint16_t stopSpiDmas(uint16_t rx_cap)
{
const uint16_t remaining = (uint16_t)DMA_CHCNT(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH);
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
DMA_CHCTL(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
spi_dma_disable(SPI0, SPI_DMA_RECEIVE);
uint16_t got = 0U;
if (remaining <= rx_cap) {
got = (uint16_t)(rx_cap - remaining);
}
return got;
}
static bool isAllBytesValue(const uint8_t *data, uint16_t len, uint8_t value)
{
if (!data || len == 0U) {
@@ -264,6 +307,7 @@ void AppSpiCommand::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);
@@ -276,11 +320,14 @@ void AppSpiCommand::configureSpi0()
spi_init_struct.prescale = SPI_PSC_8;
spi_init(SPI0, &spi_init_struct);
/* 内部 NSS 常低:SPI 从机始终就绪,不依赖 EXTI ISR 切换 internal NSS。
* PA15 仅作 EXTI 事务边界信号,用于 onCsAssert/onCsRelease 控制 DMA 装载。 */
spi_nss_internal_low(SPI0);
(void)spi_i2s_data_receive(SPI0);
spi_i2s_data_transmit(SPI0, 0xFFU);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
/* RX 改走 DMA0_CH1:禁用 RBNE 逐字节中断,避免 256B 事务 RX-OVR/SOF 丢失。 */
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_RBNE);
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_ERR);
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
nvic_irq_enable(SPI0_IRQn, 2U, 0U);
spi_enable(SPI0);
@@ -289,8 +336,9 @@ void AppSpiCommand::configureSpi0()
void AppSpiCommand::configureDma()
{
rcu_periph_clock_enable(RCU_DMA0);
dma_deinit(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
/* TX: memory → SPI0_DATA */
dma_deinit(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
dma_parameter_struct dma_cfg;
dma_struct_para_init(&dma_cfg);
dma_cfg.periph_addr = (uint32_t)(SPI0 + 0x0CU); /* SPI0_DATA 寄存器 */
@@ -305,6 +353,23 @@ void AppSpiCommand::configureDma()
dma_init(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH, &dma_cfg);
dma_circulation_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
dma_channel_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
/* RX: SPI0_DATA → m_rxBuf(替代旧 RBNE 逐字节累积)。
* 与 BOOT SpiOta 对齐:DMA0_CH1 固定映射 SPI0_RX。 */
dma_deinit(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH);
dma_struct_para_init(&dma_cfg);
dma_cfg.periph_addr = (uint32_t)(SPI0 + 0x0CU);
dma_cfg.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_cfg.memory_addr = (uint32_t)m_rxBuf;
dma_cfg.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_cfg.number = kRxBufSize;
dma_cfg.priority = DMA_PRIORITY_HIGH;
dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_cfg.direction = DMA_PERIPHERAL_TO_MEMORY;
dma_init(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH, &dma_cfg);
dma_circulation_disable(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH);
dma_channel_disable(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH);
}
void AppSpiCommand::configureExti()
@@ -316,26 +381,6 @@ void AppSpiCommand::configureExti()
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
}
void AppSpiCommand::configureSpiReadyExti()
{
/* PB6 已在 configureGpio() 配置为开漏输出,此处保留 EXTI6 中断使能。
* v2 DATA_READY: ESP32 不再操作 PB6,EXTI6 上升沿无业务含义,
* 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, CONFIG_IRQ_PREEMPT_SPI_USART, 1U);
}
void AppSpiCommand::onSpiReadyExti()
{
/* v2 DATA_READY: ESP32 不再操作 PB6,EXTI6 上升沿不再有意义。
* 清除中断标志避免反复触发。 */
if (exti_interrupt_flag_get(EXTI_6) != RESET) {
exti_interrupt_flag_clear(EXTI_6);
}
}
/* ============================================================
@@ -373,10 +418,55 @@ void AppSpiCommand::init()
registerSpiIdSnapshotCommand(AppSpiCommandBus);
configureGpio();
configureSpi0();
configureDma();
configureSpi0();
configureExti();
configureSpiReadyExti();
/* 首笔 CS 到来前预装 DMA,避免 EXTI 晚于首 SCK。
* 必须在 configureSpi0/configureDma 之后调用。 */
armSpiDmasForNextCs();
}
/** @brief 在 CS 空闲期预装下一笔全双工 DMA。
*
* 必须在首个 SCK 之前完成:EXTI CS 下降相对 ESP32 时钟经常偏晚,
* 若在 onCsAssert 里才 start DMA,整笔事务会收 0 字节 → 永不 HELLO_ACK → MISO 全 0xFF。
*
* TX 始终按 kTxBufSize 装载:响应帧在头部,其余 0xFF。
* 若只 DMA 实际帧长(~26B),主机打 96 时钟时 TX underrun,后续 MISO 不定。
*
* 对齐 BOOT SpiOta::armSpiDmasForNextCs。
*/
void AppSpiCommand::armSpiDmasForNextCs()
{
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
DMA_CHCTL(SPI_RX_DMA_PERIPH, SPI_RX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
spi_dma_disable(SPI0, SPI_DMA_RECEIVE);
drainSpiRxHw();
/* 预填 TX:有挂起响应用响应帧(头部)+ 0xFF 填充到满缓冲 */
if (m_txLen > 0U && m_txLen <= kTxBufSize) {
if (m_txLen < kTxBufSize) {
memset(&m_txBuf[m_txLen], 0xFF, (size_t)(kTxBufSize - m_txLen));
}
} else {
m_txLen = 0U;
memset(m_txBuf, 0xFF, kTxBufSize);
}
/* RX 缓冲清零(便于诊断时区分有效字节与残留) */
memset(m_rxBuf, 0x00, kRxBufSize);
m_rxLen = 0U;
/* 预装 SPI_DATA,避免 DMA 启动前首拍 TBE 吐旧值 */
spi_i2s_data_transmit(SPI0, m_txBuf[0]);
/* 先使能 SPI DMA 请求,再开 DMA 通道,保证首字节即可搬运 */
spi_dma_enable(SPI0, SPI_DMA_RECEIVE);
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
startSpiRxDma(m_rxBuf, kRxBufSize);
/* TX 永远满缓冲长度,与主机 96~256B 事务匹配,避免 underrun */
startSpiTxDma(m_txBuf, kTxBufSize);
}
/* ============================================================
@@ -715,59 +805,36 @@ void AppSpiCommand::onCsAssert()
return;
}
m_csActive = true;
/* v2 DATA_READY: CS 本身即互斥,PB6 不再表示「忙碌」,不在 CS 下降沿拉低。
* PB6 仅由 updateDataReady() 依据待发数据驱动,避免与队列语义冲突。 */
m_rxLen = 0U;
/* CS 下降:DMA 已在空闲期由 armSpiDmasForNextCs() 预装,此处只标记忙碌。
* v2 DATA_READY: CS 本身即互斥,PB6 不再表示「忙碌」,不在 CS 下降沿拉低。
* 切勿在这里 stop/restart DMA,否则赶不上 ESP32 已发出的 SCK(对齐 BOOT)。 */
m_txPos = 0U;
m_txActiveThisCs = false;
/* 停止 DMA 并清理旧状态 */
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
/* 与 BOOT 一致:清除 SPI 双缓冲中的残留数据 */
spi_i2s_data_transmit(SPI0, 0xFFU);
if (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) != RESET) {
spi_i2s_data_transmit(SPI0, 0xFFU);
}
/* 清除错误标志 */
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);
}
/* 启动 DMA 发送响应帧(与 BOOT 完全一致) */
if (m_txLen > 0U) {
m_txActiveThisCs = true;
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
startSpiTxDma(m_txBuf, m_txLen);
} else {
/* 无响应时发送全 0xFF 填充 */
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
startSpiTxDma(m_txBuf, kTxBufSize);
}
}
void AppSpiCommand::onCsRelease()
{
if (!m_csActive) {
/* EXTI 丢失下降沿时:仍尝试收尾并 re-arm,避免永久失步(对齐 BOOT) */
m_rxLen = stopSpiDmas(kRxBufSize);
drainSpiRxHw();
if (m_txLen > 0U) {
m_txLen = 0U;
m_txPos = 0U;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
}
if (m_rxLen >= V2_OVERHEAD) {
processCommand();
}
armSpiDmasForNextCs();
updateDataReady();
return;
}
m_csActive = false;
/* 停止 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) {
uint8_t last_byte = (uint8_t)(spi_i2s_data_receive(SPI0) & 0xFFU);
if (m_rxLen < sizeof(m_rxBuf)) {
m_rxBuf[m_rxLen++] = last_byte;
}
}
/* 停 DMA 并取本笔 RX 长度(先读 CHCNT 再 disable,否则计数器被清) */
m_rxLen = stopSpiDmas(kRxBufSize);
drainSpiRxHw();
/* 若本次 CS 事务是 ESP32 响应 GD32 按铃来读请求帧,
* 则 GD32 的请求帧已通过 DMA 输出到 MISO,取消按铃状态。
@@ -810,8 +877,9 @@ void AppSpiCommand::onCsRelease()
/* v2 DATA_READY: 已清 m_txLen=0 与 m_ringPending=false,
* updateDataReady() 释放为高;ESP32 完成 CS 即视为已读走确认。 */
m_linkState.onEsp32Complete();
/* 重新预装下一笔 DMA,保证下一 CS 首字节即可收发 */
armSpiDmasForNextCs();
updateDataReady();
/* 上层若需要 ESP32 处理该请求帧,由 ESP32 侧读到的帧 TYPE 决定后续动作 */
return;
}
}
@@ -826,6 +894,7 @@ void AppSpiCommand::onCsRelease()
m_txActiveThisCs = false;
/* v2 DATA_READY: shouldIgnoreTransaction 路径已清 m_txLen=0,
* updateDataReady() 释放为高,避免 PB6 卡 LOW。 */
armSpiDmasForNextCs();
updateDataReady();
return;
}
@@ -836,8 +905,12 @@ void AppSpiCommand::onCsRelease()
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
/* 命令处理在 EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame 模式),
* 消除主循环延迟。处理完成后 processCommand() 末尾释放 SPIready。 */
* 消除主循环延迟。处理完成后 processCommand() 末尾再 re-arm DMA + 释放 SPIready。 */
processCommand();
/* 处理完立即预装下一笔,保证下一 CS 首字节即可收发 */
armSpiDmasForNextCs();
updateDataReady();
}
/* ============================================================
@@ -846,26 +919,14 @@ void AppSpiCommand::onCsRelease()
void AppSpiCommand::onInterrupt()
{
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
if (!m_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
onCsAssert();
}
uint8_t byte = (uint8_t)(spi_i2s_data_receive(SPI0) & 0xFFU);
if (m_csActive && m_rxLen < sizeof(m_rxBuf)) {
m_rxBuf[m_rxLen++] = byte;
}
}
/* TBE 已由 DMA 替代,不再处理 */
/* RX 已改走 DMA0_CH1:RBNE 逐字节中断已禁用,本 ISR 只保留错误标志清理。
* CS 边沿处理全部在 onCsExtiInterrupt 的 EXTI15 ISR 里完成。 */
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
/* 读取并丢弃 overrun 字节以清除标志,但保留已收到的帧数据。
* 丢失的字节会导致 CRC 校验失败,由 spi_try_parse_frame 优雅处理
* (返回 false -> processCommand 构建 ERROR 响应),
* 而不是重置 m_rxLen 导致整帧丢失。 */
/* RXORERR 理论上不应再出现(RX 走 DMA),但兜底清理避免标志卡死。
* 读 SPI_DATA + SPI_STAT 清除 OVR 标志。 */
(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);
}
@@ -892,8 +953,3 @@ extern "C" void EXTI10_15_IRQHandler(void)
{
AppSpiCommandBus.onCsExtiInterrupt();
}
extern "C" void EXTI5_9_IRQHandler(void)
{
AppSpiCommandBus.onSpiReadyExti();
}