refactor(spi): 优化CAN反馈推送的节流机制

将CAN反馈推送改为1Hz节流,由taskHeartbeat每秒调用
canFeedbackStartRound激活新轮次,避免与CANFD刷新频率不匹配。

添加s_roundActive标志控制推送节奏,确保每轮完成后等待下一秒
再开始新的推送循环。

---

feat(spi): 实现SPI就绪状态检测和链路状态机

新增SpiLinkState类管理SPI链路状态,通过EXTI6检测PB6引脚
的上升沿来判断ESP32是否完成上一帧处理。

配置SPI Ready引脚的外部中断,实现可靠的ESP32完成信号检测,
避免在ESP32未就绪时过早发起新的SPI通信。

修复SPI接收溢出处理,改为丢弃溢出字节而非重置整个接收缓冲区,
提高SPI通信稳定性。
This commit is contained in:
cbb
2026-07-09 02:47:22 +08:00
parent 33037bc5a3
commit 3691cf2aba
6 changed files with 151 additions and 10 deletions
@@ -121,6 +121,18 @@ static constexpr uint16_t kSyncHeaderLen = 6U;
static constexpr uint16_t kSyncCrcLen = 4U;
static constexpr uint16_t kSyncOverhead = (uint16_t)(kSyncHeaderLen + kSyncCrcLen);
/* 扫描前 16 字节查找同步帧头 SOF0 SOF1,容忍 RXORERR 导致的首字节偏移。
* 与 spi_try_parse_frame 的偏移扫描逻辑对齐。 */
static bool containsSyncSof(const uint8_t *data, uint16_t len)
{
if (!data || len < 2U) { return false; }
uint16_t last = (len < 16U) ? (uint16_t)(len - 1U) : 16U;
for (uint16_t i = 0U; i < last; ++i) {
if (data[i] == kSyncSof0 && data[i + 1U] == kSyncSof1) { return true; }
}
return false;
}
/* GD32 APP 侧同步状态机(ISR 与主循环只读/原子写,避免临界区开销)。 */
static volatile uint8_t g_localCredit = 8U;
static volatile uint8_t g_remoteCredit = 8U;
@@ -384,6 +396,28 @@ void AppSpiCommand::configureExti()
nvic_irq_enable(EXTI10_15_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 1U);
}
void AppSpiCommand::configureSpiReadyExti()
{
/* PB6 已在 configureGpio() 配置为开漏输出,此处启用 EXTI6 上升沿检测。
* EXTI 检测引脚实际电平(IDR),与 GPIO 输出模式兼容。
* 用于检测 ESP32 releaseReady 产生的上升沿(线从忙碌→空闲),
* 作为 ESP32 完成上一帧处理的可靠信号。 */
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()
{
if (exti_interrupt_flag_get(EXTI_6) != RESET) {
exti_interrupt_flag_clear(EXTI_6);
/* 线升高(空闲):ESP32 已 releaseReady,上一帧处理完 */
m_linkState.onEsp32Complete();
}
}
/* ============================================================
* 默认命令注册入口(由各 app_spi_cmd_*.cpp 提供)
@@ -407,6 +441,7 @@ void AppSpiCommand::init()
m_ringPending = false;
m_ringDeadline = 0U;
m_ringTxLen = 0U;
m_linkState.reset();
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
memset(m_logBuf, 0, sizeof(m_logBuf));
memset(m_commands, 0, sizeof(m_commands));
@@ -422,6 +457,7 @@ void AppSpiCommand::init()
configureSpi0();
configureDma();
configureExti();
configureSpiReadyExti();
}
/* ============================================================
@@ -567,6 +603,8 @@ void AppSpiCommand::cancelRing()
/* 清除待发响应,防止超时后残留帧在下一次 CS assert 时被错误输出 */
m_txLen = 0U;
m_txPos = 0U;
/* 超时后允许重试,不依赖 ESP32 releaseReady 上升沿 */
m_linkState.onRingTimeout();
/* 释放 SPIready(高阻),避免永久占用线 */
setSpiReady(true);
}
@@ -589,6 +627,8 @@ bool AppSpiCommand::requestEsp32Read(uint8_t frame_type, const uint8_t *payload,
m_txPos = 0U;
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
m_ringPending = true;
/* 消费完成 flag,等 ESP32 releaseReady 触发 EXTI6 上升沿 */
m_linkState.onRingStart();
/* 下拉 SPIready,通知 ESP32「我有请求帧要给你读」 */
setSpiReady(false);
@@ -623,6 +663,7 @@ void AppSpiCommand::poll()
uint32_t save2 = RT_CRITICAL_SAVE();
if (m_ringPending) {
if ((int32_t)(SysTickTimer::getMs() - m_ringDeadline) >= 0) {
printf("[SPI-APP] 按铃超时取消 (ESP32 未响应, 释放 SPIready 重试)\r\n");
cancelRing();
}
}
@@ -713,7 +754,7 @@ void AppSpiCommand::onCsRelease()
m_txPos = 0U;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
/* 继续走 shouldIgnoreTransaction + processCommand(),不 return */
} else if (m_rxLen >= 2U && m_rxBuf[0] == kSyncSof0 && m_rxBuf[1] == kSyncSof1) {
} else if (containsSyncSof(m_rxBuf, m_rxLen)) {
/* ESP32 发的是协议帧(如 PING/OTA 等),不是按铃响应。
* 取消按铃,清空 TX 让 processCommand() 重新装载对应应答。
* 不释放 SPIready,processCommand() 末尾会释放。 */
@@ -781,8 +822,11 @@ void AppSpiCommand::onInterrupt()
/* TBE 已由 DMA 替代,不再处理 */
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
/* 读取并丢弃 overrun 字节以清除标志,但保留已收到的帧数据。
* 丢失的字节会导致 CRC 校验失败,由 spi_try_parse_frame 优雅处理
* (返回 false -> processCommand 构建 ERROR 响应),
* 而不是重置 m_rxLen 导致整帧丢失。 */
(void)spi_i2s_data_receive(SPI0);
m_rxLen = 0U;
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
@@ -811,3 +855,8 @@ extern "C" void EXTI10_15_IRQHandler(void)
{
AppSpiCommandBus.onCsExtiInterrupt();
}
extern "C" void EXTI5_9_IRQHandler(void)
{
AppSpiCommandBus.onSpiReadyExti();
}