/** * @file app_spi_command.cpp * @brief APP 运行期 SPI 命令从机实现。 * * 复用 BOOT 的 SpiOta 协议帧格式(SOF+TYPE+LEN+PAYLOAD+CRC), * APP 只处理进入 Bootloader 命令(TYPE=0xB0), * 收到 OTA 命令帧(TYPE=0x01/0x02/0x03)时回复 WAIT 帧告知 * ESP32 "当前不在 BOOT 模式"。 * * 调试日志采用延迟输出:processCommand() 只写 s_logBuf + 置 s_logPending, * 同时置位 TASK_SPI_LOG 唤醒主循环,poll() 中立刻 printf。 */ #include "device/app_spi_command.h" extern "C" { #include "gd32e50x_exti.h" } #include "gd32e50x_gpio.h" #include "gd32e50x_misc.h" #include "gd32e50x_rcu.h" #include "gd32e50x_spi.h" #include "sys/config.h" #include "sys/ota_boot_request.h" #include "sys/task_manager.h" #include "main.h" #include #include /* ============================================================ * 协议常量(与 BOOT SpiOta 一致) * ============================================================ */ static constexpr uint8_t kSof = 0xA5U; static constexpr uint8_t kTypeBootReq = 0xB0U; static constexpr uint8_t kTypeOtaReq = 0x01U; static constexpr uint8_t kTypeAppPing = 0xA1U; static constexpr uint8_t kTypeOtaData = 0x02U; static constexpr uint8_t kTypeOtaCancel = 0x03U; static constexpr uint8_t kRspAck = 0x80U; static constexpr uint8_t kRspWait = 0x81U; static constexpr uint8_t kRspError = 0x82U; static constexpr uint16_t kFrameOverhead = 5U; static constexpr uint16_t kRxBufSize = 128U; static constexpr uint16_t kTxBufSize = 64U; static constexpr uint16_t kLogBufSize = 128U; /* ============================================================ * 静态成员 * ============================================================ */ volatile bool AppSpiCommand::s_csActive = false; volatile uint8_t AppSpiCommand::s_rxLen = 0U; volatile uint8_t AppSpiCommand::s_txLen = 0U; volatile uint8_t AppSpiCommand::s_txPos = 0U; volatile bool AppSpiCommand::s_logPending = false; uint8_t AppSpiCommand::s_rxBuf[kRxBufSize] = {}; uint8_t AppSpiCommand::s_txBuf[kTxBufSize] = {}; char AppSpiCommand::s_logBuf[kLogBufSize] = {}; /* ============================================================ * 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 > 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 || 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 (expected != frame_len) { return false; } if (spi_crc8(frame, (uint16_t)(4U + len)) != frame[4U + len]) { return false; } if (type) { *type = frame[1]; } if (payload) { *payload = &frame[4]; } if (payload_len) { *payload_len = len; } return true; } /* ============================================================ * 延迟日志:写入缓冲(ISR 安全,不调用 printf) * ============================================================ */ void AppSpiCommand::logDeferred(const char *msg) { if (s_logPending) { return; } snprintf(s_logBuf, kLogBufSize, "[SPI-APP] %s", msg); s_logPending = true; } void AppSpiCommand::logDeferredFmt(const char *fmt, uint8_t val) { if (s_logPending) { return; } snprintf(s_logBuf, kLogBufSize, fmt, val); s_logPending = true; } /* ============================================================ * 硬件配置(与 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); 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, 1U, 0U); spi_enable(SPI0); } 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); } /* ============================================================ * 初始化 * ============================================================ */ void AppSpiCommand::init() { s_csActive = false; s_rxLen = 0U; s_txLen = 0U; s_txPos = 0U; s_logPending = false; memset(s_rxBuf, 0, sizeof(s_rxBuf)); memset(s_txBuf, 0xFF, sizeof(s_txBuf)); memset(s_logBuf, 0, sizeof(s_logBuf)); configureGpio(); configureSpi0(); configureExti(); } /* ============================================================ * 帧处理:CS 释放后解析帧并准备回复(不调用 printf) * ============================================================ */ void AppSpiCommand::processCommand() { if (s_rxLen == 0U) { return; } /* 尝试按协议帧解析 */ uint8_t frame_type = 0U; const uint8_t *payload = nullptr; uint16_t payload_len = 0U; if (spi_try_parse_frame(s_rxBuf, s_rxLen, &frame_type, &payload, &payload_len)) { /* ★ 合法帧:按 TYPE 分发 */ uint16_t tx_len = 0U; switch (frame_type) { case kTypeBootReq: /* 请求进入 Bootloader */ logDeferred("BOOT_REQ → 进BOOT"); { const bool ok = OtaBootRequest::requestEnterBoot(); uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK : CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR; spi_build_frame(kRspAck, &ack, 1U, s_txBuf, &tx_len); if (ok) { TaskManager::set(1UL << TASK_HEARTBEAT); } } break; case kTypeOtaReq: case kTypeOtaData: case kTypeOtaCancel: /* OTA 命令帧 — APP 不处理,回复 WAIT */ logDeferredFmt("[SPI-APP] OTA type=0x%02X → WAIT", frame_type); { uint8_t hint = 0x01U; spi_build_frame(kRspWait, &hint, 1U, s_txBuf, &tx_len); } break; case kTypeAppPing: /* APP 存活探测 — 回复 ACK 携带状态摘要 */ logDeferred("[SPI-APP] PING → APP_ALIVE"); { /* payload: [0]=0xA1(魔术) [1]=运行秒数低8位 [2]=运行秒数高8位 [3]=任务位图低8位 */ extern volatile uint32_t g_app_uptime_sec; uint8_t alive_payload[4] = { 0xA1U, (uint8_t)(g_app_uptime_sec & 0xFFU), (uint8_t)((g_app_uptime_sec >> 8) & 0xFFU), (uint8_t)(TaskManager::state().task_flags & 0xFFU) }; spi_build_frame(kRspAck, alive_payload, sizeof(alive_payload), s_txBuf, &tx_len); } break; default: /* 未知帧类型 */ logDeferredFmt("[SPI-APP] 未知 type=0x%02X → ERROR", frame_type); { uint8_t err = 0xEEU; spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len); } break; } s_txLen = (uint8_t)tx_len; s_txPos = 0U; } else { /* ★ 不符合协议帧 — 兼容旧逻辑 */ if (s_rxBuf[0] == CONFIG_SPI_APP_OTA_BOOT_CMD) { /* 旧式单字节 0xB0 */ logDeferred("旧式0xB0 → 进BOOT"); const bool ok = OtaBootRequest::requestEnterBoot(); uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK : CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR; s_txBuf[0] = CONFIG_SPI_APP_OTA_BOOT_CMD; s_txBuf[1] = ack; s_txBuf[2] = 0x4FU; s_txBuf[3] = 0x54U; s_txLen = 4U; s_txPos = 0U; if (ok) { TaskManager::set(1UL << TASK_HEARTBEAT); } } else { /* 无法识别 */ logDeferredFmt("[SPI-APP] 非协议 0x%02X → ERROR", s_rxBuf[0]); uint16_t tx_len = 0U; uint8_t err = 0xEEU; spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len); s_txLen = (uint8_t)tx_len; s_txPos = 0U; } } /* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */ TaskManager::set(1UL << TASK_SPI_LOG); } /* ============================================================ * 主循环轮询:打印延迟日志 * ============================================================ */ void AppSpiCommand::poll() { if (s_logPending) { s_logPending = false; printf("%s\r\n", s_logBuf); } } /* ============================================================ * CS 边沿处理 * ============================================================ */ void AppSpiCommand::onCsAssert() { s_csActive = true; s_rxLen = 0U; s_txPos = 0U; if (s_txLen > 0U) { spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_TBE); } } void AppSpiCommand::onCsRelease() { if (!s_csActive) { return; } s_csActive = false; spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE); processCommand(); } /* ============================================================ * SPI0 / EXTI 中断处理 * ============================================================ */ void AppSpiCommand::onInterrupt() { if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) { if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); } uint8_t byte = (uint8_t)(spi_i2s_data_receive(SPI0) & 0xFFU); if (s_csActive && s_rxLen < sizeof(s_rxBuf)) { s_rxBuf[s_rxLen++] = byte; } } if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_TBE) != RESET) { if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); } uint8_t tx_byte = 0xFFU; if (s_csActive && s_txPos < s_txLen) { tx_byte = s_txBuf[s_txPos++]; if (s_txPos >= s_txLen) { s_txLen = 0U; s_txPos = 0U; spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE); } } else { spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE); } spi_i2s_data_transmit(SPI0, tx_byte); } if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) { (void)spi_i2s_data_receive(SPI0); s_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) { AppSpiCommand::onInterrupt(); } extern "C" void EXTI10_15_IRQHandler(void) { AppSpiCommand::onCsExtiInterrupt(); }