Files
DCU_APP/GD32E508_DCU_APP/device/spi/app_spi_command.cpp
T
cbb 76da7a3dd6 Refactor and clean up codebase
- Removed unused constants and type aliases in app_spi_command_defs.h and temp_sensor.h.
- Simplified ISpiCommandHandler destructor to default.
- Eliminated redundant methods and comments in temp_sensor and usart0 files.
- Consolidated writeU32Le function to app_spi_command_defs.h for reuse.
- Updated comments for clarity and accuracy in task_manager.h and task_runner.cpp.
- Removed hardfault.h as it contained no declarations.
- Cleaned up CANFD and watchdog implementations by removing dead code and unnecessary comments.
- Updated version numbers in config.h to reflect recent changes.
- Enhanced error handling and diagnostics in hardfault handling.
2026-07-15 02:49:37 +08:00

922 lines
35 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file app_spi_command.cpp
* @brief APP 运行期 SPI 链路与命令路由实现。
*
* 本模块现在只负责:
* - SPI0 从机链路初始化(GPIO/SPI/DMA/EXTI)
* - CS、DMA、中断驱动的帧接收与发送
* - 公共帧格式解析与应答构建
* - 业务命令的注册与分发
*
* 具体命令处理逻辑已经拆到独立的 app_spi_cmd_*.cpp,
* 通过 registerHandler() 注册进来,后续新增 SPI 业务时
* 只需要新增命令处理器,不再继续膨胀本文件。
*
* 握手架构(v2 DATA_READY,PB6<->ESP32 GPIO10 开漏单线):
* - PB6 低 = 有待发数据(m_txLen>0 或 m_ringPending),高 = 队列空。
* - 命令处理在 onCsRelease() EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame),
* 完成后由 updateDataReady() 依据待发数据驱动 PB6,不再延迟拉高。
* - GD32 可通过 requestEsp32Read() 设 m_ringPending 并由 updateDataReady()
* 拉低 PB6 主动按铃,请求 ESP32 来读。
*/
#include "device/spi/app_spi_command.h"
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command_defs.h"
#include "spi_link_v2.h"
#include "sys/ota_boot_request.h"
extern "C" {
#include "gd32e50x_exti.h"
}
#include "gd32e50x_gpio.h"
#include "gd32e50x_misc.h"
#include "gd32e50x_rcu.h"
#include "gd32e50x_dma.h"
#include "gd32e50x_spi.h"
#include "sys/config.h"
#include "sys/critical.h"
#include "sys/task_manager.h"
#include "sys/systick.h"
#include "main.h"
#include <cstdio>
#include <cstring>
/* ============================================================
* 本地别名:保持当前实现文件内部风格不变
* ============================================================ */
static constexpr uint8_t kTypeBootReq = SpiDefs::kTypeBootReq;
static constexpr uint8_t kTypeOtaReq = SpiDefs::kTypeOtaReq;
static constexpr uint8_t kTypeOtaData = SpiDefs::kTypeOtaData;
static constexpr uint8_t kTypeOtaCancel = SpiDefs::kTypeOtaCancel;
static constexpr uint8_t kRspError = SpiDefs::kRspError;
/* 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)
{
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;
}
/** @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) {
return false;
}
for (uint16_t i = 0U; i < len; ++i) {
if (data[i] != value) {
return false;
}
}
return true;
}
static bool shouldIgnoreTransaction(const uint8_t *data, uint16_t len)
{
if (!data || len == 0U) {
return true;
}
if (isAllBytesValue(data, len, 0xFFU) || isAllBytesValue(data, len, 0x00U)) {
return true;
}
/* v2 协议帧最小长度为 V2_OVERHEAD(20B)。
* 短于该长度的事务只能是启动切换期的残包,直接忽略。
* (0xB0 进 BOOT 现已走标准 v2 帧 handler,不再有裸字节通道。) */
return (len < V2_OVERHEAD);
}
static bool isFlashRelatedFrame(uint8_t frame_type)
{
switch (frame_type) {
case kTypeOtaReq:
case kTypeOtaData:
case kTypeOtaCancel:
case kTypeBootReq:
return true;
default:
return false;
}
}
/* GD32 APP 侧同步状态机(ISR 与主循环只读/原子写,避免临界区开销)。 */
static volatile uint8_t g_localCredit = 8U;
static volatile uint8_t g_remoteCredit = 8U;
/* SPI Link v2 会话/序号状态机(ISR 写、主循环读写,均原子,无需临界区)。 */
static volatile uint32_t g_sessionId = 0; /* v2 会话 ID(0=未建立) */
static volatile uint8_t g_txSeq = 0; /* GD32 发送序号(事件/响应帧) */
static volatile uint8_t g_expectedRxSeq = 0; /* 期望收到的 ESP32 请求序号 */
static volatile bool g_v2Active = false; /* v2 session 是否激活 */
/* 当前请求的 TX_SEQ,写入响应 ACK_SEQ,供 ESP32 做响应归属匹配(延迟一拍)。 */
static volatile uint8_t g_pendingAckSeq = 0;
/* 响应缓存:用于重复请求重发同一响应(避免业务 handler 重复执行)。 */
static uint8_t s_lastReqSeq = 0xFF;
static uint8_t s_lastRespBuf[256];
static uint16_t s_lastRespLen = 0;
static bool s_hasCachedResp = false;
/* ============================================================
* v2 帧构建 / 帧解析(委托 spi_link_v2.h,三工程共用)
* ============================================================ */
/* 构建响应帧;上层接口(type/payload)保持不变,handler 无需改动。
* 内部调用 v2_build_frame,FLAGS 默认 V2_FLAG_RESPONSE,session/seq/credit
* 从 v2 状态全局变量取。session 未建立时 session_id 填 0。
* ACK_SEQ = g_pendingAckSeq(对端请求的 TX_SEQ),不能写死 0,否则 ESP32
* otaFindFrame 在 m_v2Active 时会把合法延迟响应当成 stale 丢弃。 */
static bool spi_build_frame(uint8_t type, const uint8_t *payload, uint16_t payload_len,
uint8_t *out, uint16_t *out_len)
{
if (!out || !out_len) { return false; }
if ((uint32_t)V2_OVERHEAD + payload_len > AppSpiCommand::kTxBufSize) { return false; }
const uint32_t sess = g_v2Active ? g_sessionId : 0U;
size_t built = 0U;
const bool ok = v2_build_frame(type, V2_FLAG_RESPONSE, sess,
g_txSeq, g_pendingAckSeq, (uint8_t)g_localCredit,
payload, payload_len, out, &built);
if (!ok) { return false; }
*out_len = (uint16_t)built;
return true;
}
/* 解析 v2 帧;委托 v2_parse_frame,扫描 SOF 并校验 VERSION/CRC32。
* 旧的 0xA5 CRC8 帧兼容路径已移除(v2 不再向后兼容 v1)。 */
static bool spi_try_parse_frame(const uint8_t *frame, uint16_t frame_len,
uint8_t *type, const uint8_t **payload, uint16_t *payload_len,
v2_header_t *out_hdr = nullptr)
{
if (!frame || !type || !payload || !payload_len) { return false; }
v2_header_t hdr;
const uint8_t *pload = nullptr;
const bool ok = v2_parse_frame(frame, frame_len, &hdr, &pload);
if (!ok) { return false; }
g_remoteCredit = hdr.credit;
*type = hdr.type;
*payload = pload;
*payload_len = hdr.payload_len;
if (out_hdr) { *out_hdr = hdr; }
return true;
}
/* ============================================================
* 调试日志辅助
* ============================================================ */
void AppSpiCommand::postLog(const char *msg)
{
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
if (!msg) return;
snprintf(m_logBuf, kLogBufSize, "[SPI-APP] %s", msg);
m_logPending = true;
#else
(void)msg;
#endif
}
void AppSpiCommand::postLogFmt(const char *fmt, uint8_t val)
{
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
if (!fmt) return;
snprintf(m_logBuf, kLogBufSize, fmt, val);
m_logPending = true;
#else
(void)fmt;
(void)val;
#endif
}
void AppSpiCommand::buildResponse(uint8_t *tx_buf, uint16_t tx_buf_size,
uint16_t *tx_len, uint8_t rsp_type,
const uint8_t *payload, uint16_t payload_len)
{
if (!tx_buf || !tx_len || tx_buf_size == 0U) {
return;
}
uint16_t built_len = 0U;
const bool ok = spi_build_frame(rsp_type, payload, payload_len, tx_buf, &built_len);
if (ok) {
*tx_len = built_len;
}
}
/* ============================================================
* 硬件配置
* ============================================================ */
void AppSpiCommand::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(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
/* PB6 复用为 DATA_READY 握手线(开漏输出,线与)。
* 开漏模式:写 BRR = 下拉驱动低;写 BSRR = 高阻释放,靠外部/对端上拉拉高。
* 语义:低 = 有待发数据,高 = 队列空;默认释放(HIGH)。 */
gpio_init(CONFIG_SPI_READY_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, CONFIG_SPI_READY_PIN);
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
}
void AppSpiCommand::setSpiReady(bool ready)
{
/* 开漏线与:ready=true 释放(BSRR,高阻,靠上拉拉高);
* ready=false 下拉(BRR,驱动低)。 */
if (ready) {
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
} else {
gpio_bit_reset(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
}
}
/* v2 DATA_READY: PB6 低 = 有待发数据,高 = 队列空。
* L5:同步 g_localCredit(单缓冲 0/1)。 */
void AppSpiCommand::updateDataReady()
{
const bool has_data = (m_txLen > 0U) || m_ringPending;
setSpiReady(!has_data);
g_localCredit = has_data ? 0U : 1U;
}
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);
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_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 常低: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);
/* 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);
}
void AppSpiCommand::configureDma()
{
rcu_periph_clock_enable(RCU_DMA0);
/* 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 寄存器 */
dma_cfg.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_cfg.memory_addr = (uint32_t)m_txBuf;
dma_cfg.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_cfg.number = 0U;
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_MEMORY_TO_PERIPHERAL;
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()
{
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);
}
/* ============================================================
* 默认命令注册入口(由各 app_spi_cmd_*.cpp 提供)
* ============================================================ */
extern void registerSpiBootRequestCommand(AppSpiCommand &bus);
extern void registerSpiOtaWaitCommand(AppSpiCommand &bus);
extern void registerSpiAppPingCommand(AppSpiCommand &bus);
extern void registerSpiCanBridgeCommand(AppSpiCommand &bus);
extern void registerSpiCanFeedbackCommand(AppSpiCommand &bus);
extern void registerSpiIdSnapshotCommand(AppSpiCommand &bus);
AppSpiCommand AppSpiCommandBus;
void AppSpiCommand::init()
{
m_csActive = false;
m_rxLen = 0U;
m_txLen = 0U;
m_logPending = false;
m_txActiveThisCs = false;
m_commandCount = 0U;
m_ringPending = false;
m_ringDeadline = 0U;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
memset(m_logBuf, 0, sizeof(m_logBuf));
memset(m_commands, 0, sizeof(m_commands));
registerSpiBootRequestCommand(AppSpiCommandBus);
registerSpiOtaWaitCommand(AppSpiCommandBus);
registerSpiAppPingCommand(AppSpiCommandBus);
registerSpiCanBridgeCommand(AppSpiCommandBus);
registerSpiCanFeedbackCommand(AppSpiCommandBus);
registerSpiIdSnapshotCommand(AppSpiCommandBus);
configureGpio();
configureDma();
configureSpi0();
configureExti();
/* 首笔 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);
}
/* ============================================================
* 命令路由
* ============================================================ */
void AppSpiCommand::registerHandler(ISpiCommandHandler *handler)
{
if (!handler || m_commandCount >= kMaxCommands) {
return;
}
m_commands[m_commandCount++] = handler;
}
void AppSpiCommand::processCommand()
{
if (m_rxLen == 0U) {
return;
}
/* 0xB0 已受理、正在写 OTA 配置页 / 即将复位:不再处理业务帧,避免打断 Flash。 */
if (OtaBootRequest::isSpiFrozenForBoot() || OtaBootRequest::isRebootPending()) {
m_txLen = 0U;
updateDataReady();
return;
}
/* 尝试按 v2 协议帧解析;同时取回完整帧头用于 session/seq 校验 */
uint8_t frame_type = 0U;
const uint8_t *payload = nullptr;
uint16_t payload_len = 0U;
v2_header_t hdr;
if (!spi_try_parse_frame(m_rxBuf, m_rxLen, &frame_type, &payload, &payload_len, &hdr)) {
/* 无法解析为合法 v2 帧:构建 ERROR 响应 */
postLogFmt("[SPI-APP] 非v2帧 0x%02X -> ERROR", m_rxBuf[0]);
uint16_t tx_len = 0U;
uint8_t err = 0xEEU;
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
m_txLen = tx_len;
if (m_logPending) {
TaskManager::set(1UL << TASK_SPI_LOG);
}
if (m_txLen > 0U) {
armRingPending();
}
updateDataReady();
return;
}
if (CONFIG_SPI_APP_LOG_ENABLE != 0 && !isFlashRelatedFrame(frame_type)) {
postLogFmt("[SPI-APP] 收到非刷写 SPI type=0x%02X", frame_type);
}
/* 响应归属:ACK_SEQ 必须回显请求 TX_SEQ(延迟一拍 + ESP32 otaFindFrame 匹配)。 */
g_pendingAckSeq = hdr.tx_seq;
/* ---------- HELLO 建链路处理(在 session 校验之前) ---------- */
if (frame_type == V2_TYPE_LINK_HELLO) {
/* 接受 ESP32 提供的 session_id,重置本端序号状态机 */
g_sessionId = hdr.session_id;
g_txSeq = 0;
g_expectedRxSeq = 0;
g_v2Active = true;
s_hasCachedResp = false;
/* 构建 HELLO_ACK 响应帧(回显 session_id + 角色 + credit_max) */
size_t built_len = 0U;
v2_build_hello_ack_frame(g_sessionId, V2_ROLE_APP, (uint8_t)g_localCredit,
0, hdr.tx_seq, m_txBuf, &built_len);
m_txLen = (uint16_t)built_len;
/* 缓存 HELLO_ACK 便于重复 HELLO 重发 */
s_lastReqSeq = hdr.tx_seq;
memcpy(s_lastRespBuf, m_txBuf, m_txLen);
s_lastRespLen = m_txLen;
s_hasCachedResp = true;
if (m_logPending) {
TaskManager::set(1UL << TASK_SPI_LOG);
}
if (m_txLen > 0U) {
armRingPending();
}
updateDataReady();
return;
}
/* ---------- SESSION / SEQ 校验(仅在 v2 session 激活后生效) ---------- */
if (g_v2Active) {
/* session_id 不匹配:构建 LINK_ERROR(V2_ERR_SESSION),跳过 handler */
if (hdr.session_id != g_sessionId) {
postLogFmt("[SPI-APP] session 不匹配 0x%02X -> LINK_ERROR",
(uint8_t)(hdr.session_id & 0xFFU));
size_t built_len = 0U;
v2_build_error_frame(g_sessionId, g_txSeq, hdr.tx_seq,
(uint8_t)g_localCredit, V2_ERR_SESSION,
(uint8_t)g_expectedRxSeq, m_txBuf, &built_len);
m_txLen = (uint16_t)built_len;
if (m_logPending) {
TaskManager::set(1UL << TASK_SPI_LOG);
}
if (m_txLen > 0U) {
armRingPending();
}
updateDataReady();
return;
}
/* 序号回绕距离判定 */
const uint8_t dist = v2_seq_distance(hdr.tx_seq, g_expectedRxSeq);
if (dist == 0U) {
/* 正常:序号匹配,推进期望序号 */
g_expectedRxSeq = (uint8_t)(g_expectedRxSeq + 1U);
} else if (dist >= 128U) {
/* 重复请求:重发缓存的响应帧,跳过 handler */
if (s_hasCachedResp && hdr.tx_seq == s_lastReqSeq) {
postLogFmt("[SPI-APP] 重复请求 seq=0x%02X -> 重发缓存", hdr.tx_seq);
memcpy(m_txBuf, s_lastRespBuf, s_lastRespLen);
m_txLen = s_lastRespLen;
if (m_logPending) {
TaskManager::set(1UL << TASK_SPI_LOG);
}
if (m_txLen > 0U) {
armRingPending();
}
updateDataReady();
return;
}
/* 无缓存可用:按正常请求处理 */
} else {
/* dist 1-127:序号跳变。对齐期望序号后:
* - 普通业务:回 LINK_ERROR 并丢弃(避免副作用)
* - 0xB0 ENTER_BOOT:控制面命令,仍必须执行,否则永远进不了 BOOT */
postLogFmt("[SPI-APP] SEQ jump rx=0x%02X", hdr.tx_seq);
g_expectedRxSeq = (uint8_t)(hdr.tx_seq + 1U);
if (frame_type != kTypeBootReq) {
size_t built_len = 0U;
v2_build_error_frame(g_sessionId, g_txSeq, hdr.tx_seq,
(uint8_t)g_localCredit, V2_ERR_SEQ_JUMP,
(uint8_t)g_expectedRxSeq, m_txBuf, &built_len);
m_txLen = (uint16_t)built_len;
if (m_logPending) {
TaskManager::set(1UL << TASK_SPI_LOG);
}
if (m_txLen > 0U) {
armRingPending();
}
updateDataReady();
return;
}
postLog("SEQ jump but BOOT_REQ still handled");
/* fall through → 业务分发 */
}
}
/* ---------- 业务命令分发 ---------- */
uint16_t tx_len = 0U;
bool handled = false;
SpiCommandContext ctx{};
ctx.frame_type = frame_type;
ctx.payload = payload;
ctx.payload_len = payload_len;
ctx.tx_buf = m_txBuf;
ctx.tx_buf_size = kTxBufSize;
ctx.tx_len = &tx_len;
for (uint8_t i = 0U; i < m_commandCount; ++i) {
ISpiCommandHandler *handler = m_commands[i];
if (handler && handler->canHandle(frame_type)) {
handler->handle(AppSpiCommandBus, ctx);
handled = true;
break;
}
}
if (!handled) {
postLogFmt("[SPI-APP] 未知 type=0x%02X -> ERROR", frame_type);
uint8_t err = 0xEEU;
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
}
if (tx_len == 0U) {
/* 兜底:避免残留上一次应答帧 */
uint8_t err = 0xEEU;
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
}
m_txLen = tx_len;
/* 缓存本次响应,便于重复请求时重发(避免 handler 重复执行副作用) */
if (g_v2Active) {
s_lastReqSeq = hdr.tx_seq;
memcpy(s_lastRespBuf, m_txBuf, m_txLen);
s_lastRespLen = m_txLen;
s_hasCachedResp = true;
/* 推进本端发送序号(响应帧占用一个 tx_seq 槽位) */
g_txSeq = (uint8_t)(g_txSeq + 1U);
}
if (m_logPending) {
/* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */
TaskManager::set(1UL << TASK_SPI_LOG);
}
/* 应答帧已就绪:由 updateDataReady() 依据 m_txLen 驱动 PB6(有待发=低)。
* 命令处理已在 ISR 内同步完成,无需 m_cmdPending 推迟机制。 */
/* 有响应待发时,设置按铃标志阻止主循环 canFeedbackPushStep() 覆盖 m_txBuf。
* ESP32 下次 CS assert 时 DMA 输出响应,onCsRelease 清除 m_ringPending。
* 50ms 超时后 cancelRing() 清除,恢复 CAN 反馈推送。 */
if (m_txLen > 0U) {
armRingPending();
}
updateDataReady();
}
/* ============================================================
* GD32 主动按铃:设 m_ringPending + updateDataReady() 拉低 PB6 请求 ESP32 来读
* ============================================================ */
void AppSpiCommand::armRingPending()
{
m_ringPending = true;
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
}
void AppSpiCommand::cancelRing()
{
m_ringPending = false;
/* 清除待发响应,防止超时后残留帧在下一次 CS assert 时被错误输出 */
m_txLen = 0U;
/* 释放 SPIready(高阻),避免永久占用线。
* cancelRing 已清 m_txLen 和 m_ringPending,updateDataReady() 会释放为高。 */
updateDataReady();
}
bool AppSpiCommand::requestEsp32Read(uint8_t frame_type, const uint8_t *payload, uint16_t payload_len)
{
/* 若上一次按铃尚未被 ESP32 读走,拒绝重复按铃 */
if (m_ringPending) {
return false;
}
/* 构建 v2 EVENT 帧到 m_txBuf(ESP32 下次读事务时通过 DMA 输出到 MISO)。
* 主动按铃帧使用 V2_FLAG_EVENT,session/seq 从 v2 状态全局变量取;
* session 未建立时 session_id 填 0。 */
const uint32_t sess = g_v2Active ? g_sessionId : 0U;
/* L5:单缓冲 — 装载前 credit 报 1,装载后 updateDataReady 置 0 */
g_localCredit = 1U;
size_t built_len = 0U;
if (!v2_build_frame(frame_type, V2_FLAG_EVENT, sess,
g_txSeq, 0U, (uint8_t)g_localCredit,
payload, payload_len, m_txBuf, &built_len)) {
return false;
}
/* EVENT 帧占用一个发送序号槽位 */
if (g_v2Active) {
g_txSeq = (uint8_t)(g_txSeq + 1U);
}
m_txLen = (uint16_t)built_len;
armRingPending();
/* v2 DATA_READY: 已设 m_ringPending=true 且 m_txLen>0,
* updateDataReady() 据此拉低 PB6,并置 g_localCredit=0。 */
updateDataReady();
return true;
}
/* ============================================================
* 主循环轮询:打印延迟日志 + 按铃超时看护
* ============================================================ */
void AppSpiCommand::poll()
{
/* 打印 ISR 推迟的日志 */
char log_copy[kLogBufSize] = {};
bool has_log = false;
uint32_t save = RT_CRITICAL_SAVE();
if (m_logPending) {
memcpy(log_copy, m_logBuf, sizeof(log_copy));
m_logPending = false;
has_log = true;
}
RT_CRITICAL_RESTORE(save);
if (has_log) {
printf("%s\r\n", log_copy);
}
/* 按铃超时看护:ESP32 未在 kRingTimeoutMs 内来读则放弃本次按铃。
* 防止 ESP32 不响应时永久占用 SPIready 线导致双方死锁。 */
if (m_ringPending) {
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();
}
}
RT_CRITICAL_RESTORE(save2);
}
}
/* ============================================================
* CS 边沿处理
* ============================================================ */
void AppSpiCommand::onCsAssert()
{
if (m_csActive) {
return;
}
m_csActive = true;
/* CS 下降:DMA 已在空闲期由 armSpiDmasForNextCs() 预装,此处只标记忙碌。
* v2 DATA_READY: CS 本身即互斥,PB6 不再表示「忙碌」,不在 CS 下降沿拉低。
* 切勿在这里 stop/restart DMA,否则赶不上 ESP32 已发出的 SCK(对齐 BOOT)。 */
m_txActiveThisCs = false;
}
void AppSpiCommand::onCsRelease()
{
if (!m_csActive) {
/* EXTI 丢失下降沿时:仍尝试收尾并 re-arm,避免永久失步(对齐 BOOT) */
m_rxLen = stopSpiDmas(kRxBufSize);
drainSpiRxHw();
if (m_txLen > 0U) {
m_txLen = 0U;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
}
if (m_rxLen >= V2_OVERHEAD) {
processCommand();
}
armSpiDmasForNextCs();
updateDataReady();
return;
}
m_csActive = false;
/* 停 DMA 并取本笔 RX 长度(先读 CHCNT 再 disable,否则计数器被清) */
m_rxLen = stopSpiDmas(kRxBufSize);
drainSpiRxHw();
/* 若本次 CS 事务是 ESP32 响应 GD32 按铃来读请求帧,
* 则 GD32 的请求帧已通过 DMA 输出到 MISO,取消按铃状态。
*
* 但需区分:ESP32 发的是 dummy 读(按铃响应)还是 v2 协议帧(如 PING/OTA/0xB0)。
* 若 GD32 正在按铃时 ESP32 发协议帧,会因 m_ringPending=true 被误吞,
* 导致命令无法处理。检测到 v2 SOF 时取消按铃并走正常命令处理。 */
if (m_ringPending) {
/* 判断本次收到的数据是否为 v2 协议帧(扫描 SOF0 SOF1)。
* 取代旧 containsSyncSof,直接用 V2_SOF0/V2_SOF1。
* 0xB0 进 BOOT 现已走标准 v2 帧 handler,不再单独检测裸字节。 */
bool is_v2_frame = false;
if (m_rxLen >= 2U) {
const uint16_t last = (m_rxLen < 16U) ? (uint16_t)(m_rxLen - 1U) : 16U;
for (uint16_t i = 0U; i < last; ++i) {
if (m_rxBuf[i] == V2_SOF0 && m_rxBuf[i + 1U] == V2_SOF1) {
is_v2_frame = true;
break;
}
}
}
if (is_v2_frame) {
/* ESP32 发的是 v2 协议帧(如 PING/OTA/0xB0 等),不是按铃响应。
* 取消按铃,清空 TX 让 processCommand() 重新装载对应应答。
* processCommand() 末尾由 updateDataReady() 依据新 m_txLen 驱动 PB6。 */
m_ringPending = false;
m_txLen = 0U;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
/* 继续走 shouldIgnoreTransaction + processCommand(),不 return */
} else {
/* 正常按铃响应:请求帧已被读走,取消按铃状态 */
m_ringPending = false;
/* 请求帧已被读走,清空 TX 防止下次事务重复输出 */
m_txLen = 0U;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
/* 重新预装下一笔 DMA,保证下一 CS 首字节即可收发 */
armSpiDmasForNextCs();
updateDataReady();
return;
}
}
if (shouldIgnoreTransaction(m_rxBuf, m_rxLen)) {
m_rxLen = 0U;
if (m_txActiveThisCs) {
m_txLen = 0U;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
}
m_txActiveThisCs = false;
/* v2 DATA_READY: shouldIgnoreTransaction 路径已清 m_txLen=0,
* updateDataReady() 释放为高,避免 PB6 卡 LOW。 */
armSpiDmasForNextCs();
updateDataReady();
return;
}
m_txLen = 0U;
m_txActiveThisCs = false;
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
/* 命令处理在 EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame 模式),
* 消除主循环延迟。处理完成后 processCommand() 末尾再 re-arm DMA + 释放 SPIready。 */
processCommand();
/* 处理完立即预装下一笔,保证下一 CS 首字节即可收发 */
armSpiDmasForNextCs();
updateDataReady();
}
/* ============================================================
* SPI0 / EXTI 中断处理
* ============================================================ */
void AppSpiCommand::onInterrupt()
{
/* RX 已改走 DMA0_CH1:RBNE 逐字节中断已禁用,本 ISR 只保留错误标志清理。
* CS 边沿处理全部在 onCsExtiInterrupt 的 EXTI15 ISR 里完成。 */
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
/* 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);
}
}
void AppSpiCommand::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();
}
}
}
extern "C" void SPI0_IRQHandler(void)
{
AppSpiCommandBus.onInterrupt();
}
extern "C" void EXTI10_15_IRQHandler(void)
{
AppSpiCommandBus.onCsExtiInterrupt();
}