2026-05-30 00:24:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @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"
|
2026-06-13 17:07:25 +08:00
|
|
|
|
#include "device/canfd.h"
|
2026-05-30 00:24:31 +08:00
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
#include "gd32e50x_exti.h"
|
|
|
|
|
|
}
|
|
|
|
|
|
#include "gd32e50x_gpio.h"
|
|
|
|
|
|
#include "gd32e50x_misc.h"
|
|
|
|
|
|
#include "gd32e50x_rcu.h"
|
2026-06-03 03:13:49 +08:00
|
|
|
|
#include "gd32e50x_dma.h"
|
2026-05-30 00:24:31 +08:00
|
|
|
|
#include "gd32e50x_spi.h"
|
|
|
|
|
|
#include "sys/config.h"
|
|
|
|
|
|
#include "sys/ota_boot_request.h"
|
|
|
|
|
|
#include "sys/task_manager.h"
|
2026-06-03 03:13:49 +08:00
|
|
|
|
#include "sys/systick.h"
|
2026-05-30 00:24:31 +08:00
|
|
|
|
#include "main.h"
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 协议常量(与 BOOT SpiOta 一致)
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
static constexpr uint8_t kSof = 0xA5U;
|
|
|
|
|
|
static constexpr uint8_t kTypeBootReq = 0xB0U;
|
2026-06-13 17:07:25 +08:00
|
|
|
|
static constexpr uint8_t kTypeCanSend = 0xC0U;
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-06-03 03:13:49 +08:00
|
|
|
|
/* 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-30 20:03:31 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 03:13:49 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-30 00:24:31 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 静态成员
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
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);
|
2026-06-03 03:13:49 +08:00
|
|
|
|
(void)spi_i2s_data_receive(SPI0);
|
|
|
|
|
|
spi_i2s_data_transmit(SPI0, 0xFFU);
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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);
|
2026-06-03 03:13:49 +08:00
|
|
|
|
nvic_irq_enable(SPI0_IRQn, 2U, 0U);
|
2026-05-30 00:24:31 +08:00
|
|
|
|
spi_enable(SPI0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 03:13:49 +08:00
|
|
|
|
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)s_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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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();
|
2026-06-03 03:13:49 +08:00
|
|
|
|
configureDma();
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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:
|
2026-06-03 03:13:49 +08:00
|
|
|
|
/* APP 存活探测 — 回复 ACK 携带带标签的版本号
|
|
|
|
|
|
*
|
|
|
|
|
|
* 因 SPI 从机首字节延迟导致帧偏移不稳定,
|
|
|
|
|
|
* 采用“标签+值”格式,ESP32 通过扫描标签定位字段。
|
|
|
|
|
|
*
|
|
|
|
|
|
* payload 布局 (19B):
|
|
|
|
|
|
* [0] = 0xA1 魔术字
|
|
|
|
|
|
* [1] = 0x55 APP 版本标签
|
|
|
|
|
|
* [2..5] = APP 版本 (4 x uint8: major, minor, patch, build)
|
|
|
|
|
|
* [6] = 0xAA BOOT 版本标签
|
|
|
|
|
|
* [7..10]= BOOT 版本 (4 x uint8: major, minor, patch, build)
|
|
|
|
|
|
* [11..14]= uptime (uint32_t LE)
|
|
|
|
|
|
* [15..18]= 填充 0 */
|
2026-05-30 00:24:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
extern volatile uint32_t g_app_uptime_sec;
|
2026-06-03 03:13:49 +08:00
|
|
|
|
uint8_t alive_payload[19];
|
|
|
|
|
|
memset(alive_payload, 0, sizeof(alive_payload));
|
|
|
|
|
|
alive_payload[0] = 0xA1U; /* magic */
|
|
|
|
|
|
alive_payload[1] = 0x55U; /* APP version tag */
|
|
|
|
|
|
alive_payload[2] = (uint8_t)APP_FW_VERSION_MAJOR;
|
|
|
|
|
|
alive_payload[3] = (uint8_t)APP_FW_VERSION_MINOR;
|
|
|
|
|
|
alive_payload[4] = (uint8_t)APP_FW_VERSION_PATCH;
|
|
|
|
|
|
alive_payload[5] = (uint8_t)APP_FW_VERSION_BUILD;
|
|
|
|
|
|
alive_payload[6] = 0xAAU; /* BOOT version tag */
|
|
|
|
|
|
|
|
|
|
|
|
/* BOOT 版本 (从 Flash OTA 配置页 word6/word7 读取) */
|
|
|
|
|
|
const volatile uint32_t *bv0 = (const volatile uint32_t *)CONFIG_BOOT_VERSION_ADDR0;
|
|
|
|
|
|
const volatile uint32_t *bv1 = (const volatile uint32_t *)CONFIG_BOOT_VERSION_ADDR1;
|
|
|
|
|
|
uint32_t bw0 = *bv0;
|
|
|
|
|
|
uint32_t bw1 = *bv1;
|
|
|
|
|
|
alive_payload[7] = (uint8_t)(bw0 & 0xFFU); /* boot major */
|
|
|
|
|
|
alive_payload[8] = (uint8_t)((bw0 >> 16) & 0xFFU); /* boot minor */
|
|
|
|
|
|
alive_payload[9] = (uint8_t)(bw1 & 0xFFU); /* boot patch */
|
|
|
|
|
|
alive_payload[10] = (uint8_t)((bw1 >> 16) & 0xFFU); /* boot build */
|
|
|
|
|
|
|
|
|
|
|
|
/* uptime (uint32_t LE) */
|
|
|
|
|
|
alive_payload[11] = (uint8_t)(g_app_uptime_sec & 0xFFU);
|
|
|
|
|
|
alive_payload[12] = (uint8_t)((g_app_uptime_sec >> 8) & 0xFFU);
|
|
|
|
|
|
alive_payload[13] = (uint8_t)((g_app_uptime_sec >> 16) & 0xFFU);
|
|
|
|
|
|
alive_payload[14] = (uint8_t)((g_app_uptime_sec >> 24) & 0xFFU);
|
|
|
|
|
|
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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;
|
2026-06-13 17:07:25 +08:00
|
|
|
|
|
|
|
|
|
|
case kTypeCanSend:
|
|
|
|
|
|
/* SPI→CAN 桥接:解析载荷并通过 CanFd 直接发送到 CAN 总线
|
|
|
|
|
|
*
|
|
|
|
|
|
* payload 布局 (6~69B):
|
|
|
|
|
|
* [0] = channel (0/1/2)
|
|
|
|
|
|
* [1..4] = ext_id (32-bit Big-Endian)
|
|
|
|
|
|
* [5..] = CAN data (1~64 bytes, 实际长度 = payload_len - 5)
|
|
|
|
|
|
*/
|
|
|
|
|
|
if (payload_len < 6U) {
|
|
|
|
|
|
/* 载荷太短:至少需要 1B channel + 4B ext_id + 1B data */
|
|
|
|
|
|
logDeferred("[SPI-APP] CAN_SEND 载荷太短 → ERROR");
|
|
|
|
|
|
uint8_t err = 0xE0U;
|
|
|
|
|
|
spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uint8_t can_ch = payload[0];
|
|
|
|
|
|
uint32_t can_id = (static_cast<uint32_t>(payload[1]) << 24)
|
|
|
|
|
|
| (static_cast<uint32_t>(payload[2]) << 16)
|
|
|
|
|
|
| (static_cast<uint32_t>(payload[3]) << 8)
|
|
|
|
|
|
| static_cast<uint32_t>(payload[4]);
|
|
|
|
|
|
const uint8_t *can_data = &payload[5];
|
|
|
|
|
|
uint8_t can_dlc = static_cast<uint8_t>(payload_len - 5U);
|
|
|
|
|
|
|
|
|
|
|
|
if (can_ch >= 3U) {
|
|
|
|
|
|
logDeferredFmt("[SPI-APP] CAN_SEND 通道非法: CH%d → ERROR", can_ch);
|
|
|
|
|
|
uint8_t err = 0xE1U;
|
|
|
|
|
|
spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bool ok = CanFd::transmitFrame(can_ch, can_id, can_data, can_dlc);
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
logDeferredFmt("[SPI-APP] CAN_SEND CH%d → OK", can_ch);
|
|
|
|
|
|
uint8_t ack = 0x00U; /* 成功 */
|
|
|
|
|
|
spi_build_frame(kRspAck, &ack, 1U, s_txBuf, &tx_len);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logDeferredFmt("[SPI-APP] CAN_SEND CH%d FIFO满 → ERROR", can_ch);
|
|
|
|
|
|
uint8_t err = 0xE2U; /* FIFO 满 */
|
|
|
|
|
|
spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2026-05-30 00:24:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
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()
|
|
|
|
|
|
{
|
2026-05-30 20:03:31 +08:00
|
|
|
|
char log_copy[kLogBufSize] = {};
|
|
|
|
|
|
bool has_log = false;
|
|
|
|
|
|
|
|
|
|
|
|
__disable_irq();
|
2026-05-30 00:24:31 +08:00
|
|
|
|
if (s_logPending) {
|
2026-05-30 20:03:31 +08:00
|
|
|
|
memcpy(log_copy, s_logBuf, sizeof(log_copy));
|
2026-05-30 00:24:31 +08:00
|
|
|
|
s_logPending = false;
|
2026-05-30 20:03:31 +08:00
|
|
|
|
has_log = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
__enable_irq();
|
|
|
|
|
|
|
|
|
|
|
|
if (has_log) {
|
|
|
|
|
|
printf("%s\r\n", log_copy);
|
2026-05-30 00:24:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* CS 边沿处理
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
void AppSpiCommand::onCsAssert()
|
|
|
|
|
|
{
|
2026-06-03 03:13:49 +08:00
|
|
|
|
if (s_csActive) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-30 00:24:31 +08:00
|
|
|
|
s_csActive = true;
|
|
|
|
|
|
s_rxLen = 0U;
|
|
|
|
|
|
s_txPos = 0U;
|
2026-06-03 03:13:49 +08:00
|
|
|
|
|
|
|
|
|
|
/* 停止 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 完全一致) */
|
2026-05-30 00:24:31 +08:00
|
|
|
|
if (s_txLen > 0U) {
|
2026-06-03 03:13:49 +08:00
|
|
|
|
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
|
|
|
|
|
startSpiTxDma(s_txBuf, s_txLen);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
/* 无响应时发送全 0xFF 填充 */
|
|
|
|
|
|
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
|
|
|
|
|
startSpiTxDma(s_txBuf, kTxBufSize);
|
2026-05-30 00:24:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void AppSpiCommand::onCsRelease()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!s_csActive) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
s_csActive = false;
|
2026-06-03 03:13:49 +08:00
|
|
|
|
|
|
|
|
|
|
/* 停止 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 (s_rxLen < sizeof(s_rxBuf)) {
|
|
|
|
|
|
s_rxBuf[s_rxLen++] = last_byte;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldIgnoreTransaction(s_rxBuf, s_rxLen)) {
|
2026-05-30 20:03:31 +08:00
|
|
|
|
s_rxLen = 0U;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 03:13:49 +08:00
|
|
|
|
/* TBE 已由 DMA 替代,不再处理 */
|
2026-05-30 00:24:31 +08:00
|
|
|
|
|
|
|
|
|
|
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();
|
2026-05-30 20:03:31 +08:00
|
|
|
|
}
|