/** * @file app_spi_command.cpp * @brief APP 运行期 SPI 链路与命令路由实现。 * * 本模块现在只负责: * - SPI0 从机链路初始化(GPIO/SPI/DMA/EXTI) * - CS、DMA、中断驱动的帧接收与发送 * - 公共帧格式解析与应答构建 * - 业务命令的注册与分发 * * 具体命令处理逻辑已经拆到独立的 `app_spi_cmd_*.cpp`, * 通过 `registerHandler()` 注册进来,后续新增 SPI 业务时 * 只需要新增命令处理器,不再继续膨胀本文件。 */ #include "device/spi/app_spi_command.h" #include "device/spi/spi_command_handler.h" #include "device/spi/app_spi_command_defs.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/ota_boot_request.h" #include "sys/systick.h" #include "main.h" #include #include /* ============================================================ * 本地别名:保持当前实现文件内部风格不变 * ============================================================ */ static constexpr uint8_t kSof = SpiDefs::kSof; static constexpr uint8_t kTypeBootReq = SpiDefs::kTypeBootReq; static constexpr uint8_t kTypeCanSend = SpiDefs::kTypeCanSend; static constexpr uint8_t kTypeOtaReq = SpiDefs::kTypeOtaReq; static constexpr uint8_t kTypeAppPing = SpiDefs::kTypeAppPing; static constexpr uint8_t kTypeOtaData = SpiDefs::kTypeOtaData; static constexpr uint8_t kTypeOtaCancel = SpiDefs::kTypeOtaCancel; static constexpr uint8_t kRspAck = SpiDefs::kRspAck; static constexpr uint8_t kRspWait = SpiDefs::kRspWait; static constexpr uint8_t kRspError = SpiDefs::kRspError; static constexpr uint16_t kFrameOverhead = SpiDefs::kFrameOverhead; /* DMA 配置(与 BOOT SpiOta 一致:DMA0_CH2 用于 SPI0 TX) */ #define SPI_TX_DMA_PERIPH DMA0 #define SPI_TX_DMA_CH DMA_CH2 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; } static bool isAllBytesValue(const uint8_t *data, uint8_t len, uint8_t value) { if (!data || len == 0U) { return false; } for (uint8_t i = 0U; i < len; ++i) { if (data[i] != value) { return false; } } return true; } static bool shouldIgnoreTransaction(const uint8_t *data, uint8_t len) { if (!data || len == 0U) { return true; } if (isAllBytesValue(data, len, 0xFFU) || isAllBytesValue(data, len, 0x00U)) { return true; } /* APP 侧只接受两类事务: * 1) 旧式单字节 0xB0 * 2) 完整协议帧(至少 5 字节) * 短于 5 字节且不是 0xB0 的事务只能是启动切换期的残包,直接忽略。 */ return (len < kFrameOverhead) && !(len == 1U && data[0] == CONFIG_SPI_APP_OTA_BOOT_CMD); } static bool isFlashRelatedFrame(uint8_t frame_type) { switch (frame_type) { case kTypeOtaReq: case kTypeOtaData: case kTypeOtaCancel: case kTypeBootReq: return true; default: return false; } } /* ============================================================ * CRC8 / 帧构建 / 帧解析(与 BOOT SpiOta 一致) * ============================================================ */ static uint8_t spi_crc8(const uint8_t *data, uint16_t len) { uint8_t crc = 0x00U; for (uint16_t i = 0U; i < len; ++i) { crc ^= data[i]; } return crc; } 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; } uint16_t total = (uint16_t)(kFrameOverhead + payload_len); if (total > AppSpiCommand::kTxBufSize) { return false; } out[0] = kSof; out[1] = type; out[2] = (uint8_t)(payload_len & 0xFFU); out[3] = (uint8_t)((payload_len >> 8) & 0xFFU); if (payload && payload_len > 0U) { memcpy(&out[4], payload, payload_len); } out[4 + payload_len] = spi_crc8(out, (uint16_t)(4U + payload_len)); *out_len = total; return true; } 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) { if (!frame || !type || !payload || !payload_len) { return false; } /* 兼容首字节延迟:在帧头前面可能出现若干 0xFF / 0x00 残留字节, * 搜索到第一个 kSof 后尝试按合法帧长度校验 CRC。 */ uint16_t s = 0U; while (s < frame_len && frame[s] != kSof) { ++s; } if (s > 0U && s < frame_len) { const uint16_t shifted_len = (uint16_t)(frame_len - s); if (shifted_len >= kFrameOverhead) { const uint16_t len_field = (uint16_t)frame[s + 2U] | ((uint16_t)frame[s + 3U] << 8); const uint16_t expected = (uint16_t)(kFrameOverhead + len_field); if (shifted_len >= expected && spi_crc8(&frame[s], (uint16_t)(4U + len_field)) == frame[s + 4U + len_field]) { *type = frame[s + 1U]; *payload = &frame[s + 4U]; *payload_len = len_field; return true; } } } if (frame_len < kFrameOverhead) { return false; } if (frame[0] != kSof) { return false; } uint16_t len = (uint16_t)frame[2] | ((uint16_t)frame[3] << 8); uint16_t expected = (uint16_t)(kFrameOverhead + len); if (frame_len < expected) { return false; } if (spi_crc8(frame, (uint16_t)(4U + len)) != frame[4U + len]) { return false; } *type = frame[1]; *payload = &frame[4]; *payload_len = len; 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; } else { uint8_t err = 0xEEU; (void)spi_build_frame(kRspError, &err, 1U, tx_buf, &built_len); *tx_len = built_len; } } /* ============================================================ * 硬件配置(与 BOOT 完全一致) * ============================================================ */ 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); } void AppSpiCommand::configureSpi0() { rcu_periph_clock_enable(RCU_SPI0); spi_i2s_deinit(SPI0); 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); 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); spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE); nvic_irq_enable(SPI0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U); spi_enable(SPI0); } void AppSpiCommand::configureDma() { rcu_periph_clock_enable(RCU_DMA0); 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); } 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, CONFIG_IRQ_PREEMPT_SPI_USART, 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_txPos = 0U; m_logPending = false; m_commandCount = 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(); configureSpi0(); configureDma(); configureExti(); } /* ============================================================ * 命令路由 * ============================================================ */ void AppSpiCommand::registerHandler(ISpiCommandHandler *handler) { if (!handler || m_commandCount >= kMaxCommands) { return; } m_commands[m_commandCount++] = handler; } void AppSpiCommand::processCommand() { if (m_rxLen == 0U) { return; } /* 尝试按协议帧解析 */ uint8_t frame_type = 0U; const uint8_t *payload = nullptr; uint16_t payload_len = 0U; if (spi_try_parse_frame(m_rxBuf, m_rxLen, &frame_type, &payload, &payload_len)) { if (CONFIG_SPI_APP_LOG_ENABLE != 0 && !isFlashRelatedFrame(frame_type)) { postLogFmt("[SPI-APP] 收到非刷写 SPI type=0x%02X", frame_type); } /* 合法帧:按 TYPE 分发给已注册命令处理器 */ 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 = (uint8_t)tx_len; m_txPos = 0U; } else { /* 非标准帧:保留旧式单字节 0xB0 通道,后续新命令建议走注册式帧协议 */ const uint8_t first_valid = m_rxBuf[0U]; if (first_valid == CONFIG_SPI_APP_OTA_BOOT_CMD) { postLog("旧式0xB0 → 进BOOT"); uint16_t tx_len_legacy = 0U; SpiCommandContext ctx{}; ctx.frame_type = first_valid; ctx.payload = nullptr; ctx.payload_len = 0U; ctx.tx_buf = m_txBuf; ctx.tx_buf_size = kTxBufSize; ctx.tx_len = &tx_len_legacy; bool handled = false; for (uint8_t i = 0U; i < m_commandCount; ++i) { ISpiCommandHandler *handler = m_commands[i]; if (handler && handler->canHandle(first_valid)) { handler->handle(AppSpiCommandBus, ctx); handled = true; break; } } if (!handled) { const bool ok = OtaBootRequest::requestEnterBoot(); uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK : CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR; m_txBuf[0] = CONFIG_SPI_APP_OTA_BOOT_CMD; m_txBuf[1] = ack; m_txBuf[2] = 0x4FU; m_txBuf[3] = 0x54U; tx_len_legacy = 4U; if (ok) { TaskManager::set(1UL << TASK_HEARTBEAT); } } m_txLen = (uint8_t)tx_len_legacy; m_txPos = 0U; } else { /* 无法识别 */ postLogFmt("[SPI-APP] 非协议 0x%02X → ERROR", first_valid); uint16_t tx_len = 0U; uint8_t err = 0xEEU; spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len); m_txLen = (uint8_t)tx_len; m_txPos = 0U; } } if (m_logPending) { /* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */ TaskManager::set(1UL << TASK_SPI_LOG); } } /* ============================================================ * 主循环轮询:打印延迟日志 * ============================================================ */ void AppSpiCommand::poll() { 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); } } void AppSpiCommand::processPendingCommand() { if (!m_cmdPending) { return; } m_cmdPending = false; processCommand(); } /* ============================================================ * CS 边沿处理 * ============================================================ */ void AppSpiCommand::onCsAssert() { if (m_csActive) { return; } m_csActive = true; m_rxLen = 0U; m_txPos = 0U; /* 停止 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) { 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) { 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; } } if (shouldIgnoreTransaction(m_rxBuf, m_rxLen)) { m_rxLen = 0U; return; } m_cmdPending = true; TaskManager::set(1UL << TASK_SPI_CMD_PROCESS); } /* ============================================================ * SPI0 / EXTI 中断处理 * ============================================================ */ 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 替代,不再处理 */ if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) { (void)spi_i2s_data_receive(SPI0); m_rxLen = 0U; } 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(); }