feat(spi): 实现SPI同步通信协议和主动推送机制
- 实现新的SPI同步帧格式,采用0xA5 0x5A起始符和CRC32校验,提升通信可靠性 - 添加GD32与ESP32间的握手机制,通过PB6开漏输出实现SPIready线与控制 - 实现异步OTA启动机制,将Flash操作延迟到主循环执行,避免SPI ISR阻塞 - 改进CAN反馈命令处理,提取通用payload构建函数支持主动推送模式 - 实现GD32主动按铃机制,循环推送CAN反馈数据到ESP32,减少轮询开销 - 添加SPI通信超时保护和死锁防护机制,确保系统稳定性
This commit is contained in:
@@ -8,9 +8,14 @@
|
||||
* - 公共帧格式解析与应答构建
|
||||
* - 业务命令的注册与分发
|
||||
*
|
||||
* 具体命令处理逻辑已经拆到独立的 `app_spi_cmd_*.cpp`,
|
||||
* 通过 `registerHandler()` 注册进来,后续新增 SPI 业务时
|
||||
* 具体命令处理逻辑已经拆到独立的 app_spi_cmd_*.cpp,
|
||||
* 通过 registerHandler() 注册进来,后续新增 SPI 业务时
|
||||
* 只需要新增命令处理器,不再继续膨胀本文件。
|
||||
*
|
||||
* 握手架构(线与 SPIready,PB6<->ESP32 GPIO10 开漏单线):
|
||||
* - 命令处理在 onCsRelease() EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame),
|
||||
* 不再推迟到主循环,消除 PB6 拉高延迟。
|
||||
* - GD32 可通过 requestEsp32Read() 下拉 SPIready 主动按铃请求 ESP32 来读。
|
||||
*/
|
||||
|
||||
#include "device/spi/app_spi_command.h"
|
||||
@@ -62,12 +67,12 @@ static void startSpiTxDma(const uint8_t *buf, uint16_t 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)
|
||||
static bool isAllBytesValue(const uint8_t *data, uint16_t len, uint8_t value)
|
||||
{
|
||||
if (!data || len == 0U) {
|
||||
return false;
|
||||
}
|
||||
for (uint8_t i = 0U; i < len; ++i) {
|
||||
for (uint16_t i = 0U; i < len; ++i) {
|
||||
if (data[i] != value) {
|
||||
return false;
|
||||
}
|
||||
@@ -75,7 +80,7 @@ static bool isAllBytesValue(const uint8_t *data, uint8_t len, uint8_t value)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool shouldIgnoreTransaction(const uint8_t *data, uint8_t len)
|
||||
static bool shouldIgnoreTransaction(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (!data || len == 0U) {
|
||||
return true;
|
||||
@@ -110,40 +115,111 @@ static bool isFlashRelatedFrame(uint8_t frame_type)
|
||||
* CRC8 / 帧构建 / 帧解析(与 BOOT SpiOta 一致)
|
||||
* ============================================================ */
|
||||
|
||||
static uint8_t spi_crc8(const uint8_t *data, uint16_t len)
|
||||
static constexpr uint8_t kSyncSof0 = 0xA5U;
|
||||
static constexpr uint8_t kSyncSof1 = 0x5AU;
|
||||
static constexpr uint16_t kSyncHeaderLen = 6U;
|
||||
static constexpr uint16_t kSyncCrcLen = 4U;
|
||||
static constexpr uint16_t kSyncOverhead = (uint16_t)(kSyncHeaderLen + kSyncCrcLen);
|
||||
|
||||
/* GD32 APP 侧同步状态机(ISR 与主循环只读/原子写,避免临界区开销)。 */
|
||||
static volatile uint8_t g_localCredit = 8U;
|
||||
static volatile uint8_t g_remoteCredit = 8U;
|
||||
|
||||
static void putU16Le(uint8_t *pp, uint16_t v)
|
||||
{
|
||||
uint8_t crc = 0x00U;
|
||||
pp[0] = (uint8_t)(v & 0xFFU);
|
||||
pp[1] = (uint8_t)((v >> 8) & 0xFFU);
|
||||
}
|
||||
|
||||
static uint16_t getU16Le(const uint8_t *pp)
|
||||
{
|
||||
return (uint16_t)(pp[0] | ((uint16_t)pp[1] << 8));
|
||||
}
|
||||
|
||||
static void putU32Le(uint8_t *pp, uint32_t v)
|
||||
{
|
||||
pp[0] = (uint8_t)(v & 0xFFU);
|
||||
pp[1] = (uint8_t)((v >> 8) & 0xFFU);
|
||||
pp[2] = (uint8_t)((v >> 16) & 0xFFU);
|
||||
pp[3] = (uint8_t)((v >> 24) & 0xFFU);
|
||||
}
|
||||
|
||||
static uint32_t getU32Le(const uint8_t *pp)
|
||||
{
|
||||
return (uint32_t)pp[0] | ((uint32_t)pp[1] << 8) |
|
||||
((uint32_t)pp[2] << 16) | ((uint32_t)pp[3] << 24);
|
||||
}
|
||||
|
||||
static uint32_t spi_crc32(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFFU;
|
||||
for (uint16_t i = 0U; i < len; ++i) {
|
||||
crc ^= data[i];
|
||||
crc ^= (uint32_t)data[i] << 24;
|
||||
for (int b = 0; b < 8; ++b) {
|
||||
if (crc & 0x80000000U) {
|
||||
crc = (crc << 1) ^ 0x04C11DB7U;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 构建新同步帧;上层接口(type/payload)保持不变,handler 无需改动。
|
||||
* kFrameOverhead 旧常量仍保留供旧路径使用。 */
|
||||
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);
|
||||
uint16_t total = (uint16_t)(kSyncOverhead + 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);
|
||||
|
||||
out[0] = kSyncSof0;
|
||||
out[1] = kSyncSof1;
|
||||
out[2] = g_localCredit;
|
||||
out[3] = type;
|
||||
putU16Le(&out[4], payload_len);
|
||||
if (payload && payload_len > 0U) {
|
||||
memcpy(&out[4], payload, payload_len);
|
||||
memcpy(&out[kSyncHeaderLen], payload, payload_len);
|
||||
}
|
||||
out[4 + payload_len] = spi_crc8(out, (uint16_t)(4U + payload_len));
|
||||
uint32_t crc = spi_crc32(out, (uint16_t)(kSyncHeaderLen + payload_len));
|
||||
putU32Le(&out[kSyncHeaderLen + payload_len], crc);
|
||||
*out_len = total;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 解析:新同步帧优先;失败回退旧 0xA5 帧(含首字节偏移容错)。 */
|
||||
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。 */
|
||||
for (uint16_t s = 0U; s + kSyncOverhead <= frame_len; ++s) {
|
||||
if (frame[s] != kSyncSof0 || frame[s + 1U] != kSyncSof1) { continue; }
|
||||
uint16_t plen = getU16Le(&frame[s + 4U]);
|
||||
uint16_t total = (uint16_t)(kSyncOverhead + plen);
|
||||
if ((uint16_t)(frame_len - s) < total) { continue; }
|
||||
uint32_t expect = getU32Le(&frame[s + kSyncHeaderLen + plen]);
|
||||
uint32_t calc = spi_crc32(&frame[s], (uint16_t)(kSyncHeaderLen + plen));
|
||||
if (expect != calc) { continue; }
|
||||
|
||||
uint8_t credit = frame[s + 2U];
|
||||
*type = frame[s + 3U];
|
||||
*payload = (plen > 0U) ? &frame[s + kSyncHeaderLen] : nullptr;
|
||||
*payload_len = plen;
|
||||
g_remoteCredit = credit;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 旧 0xA5 帧兼容:首字节可能偏移,扫描 SOF。 */
|
||||
uint16_t s = 0U;
|
||||
while (s < frame_len && frame[s] != kSof) { ++s; }
|
||||
if (s > 0U && s < frame_len) {
|
||||
@@ -214,15 +290,11 @@ void AppSpiCommand::buildResponse(uint8_t *tx_buf, uint16_t tx_buf_size,
|
||||
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()
|
||||
@@ -238,13 +310,17 @@ void AppSpiCommand::configureGpio()
|
||||
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 复用为 SPI Ready 握手输出,默认 HIGH(就绪),允许主机立刻发起首帧 */
|
||||
gpio_init(CONFIG_SPI_READY_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_SPI_READY_PIN);
|
||||
/* PB6 复用为 SPI 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 {
|
||||
@@ -328,6 +404,9 @@ void AppSpiCommand::init()
|
||||
m_logPending = false;
|
||||
m_txActiveThisCs = false;
|
||||
m_commandCount = 0U;
|
||||
m_ringPending = false;
|
||||
m_ringDeadline = 0U;
|
||||
m_ringTxLen = 0U;
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
memset(m_logBuf, 0, sizeof(m_logBuf));
|
||||
memset(m_commands, 0, sizeof(m_commands));
|
||||
@@ -395,7 +474,7 @@ void AppSpiCommand::processCommand()
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
postLogFmt("[SPI-APP] 未知 type=0x%02X → ERROR", frame_type);
|
||||
postLogFmt("[SPI-APP] 未知 type=0x%02X -> ERROR", frame_type);
|
||||
uint8_t err = 0xEEU;
|
||||
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
||||
}
|
||||
@@ -406,13 +485,13 @@ void AppSpiCommand::processCommand()
|
||||
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
||||
}
|
||||
|
||||
m_txLen = (uint8_t)tx_len;
|
||||
m_txLen = 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");
|
||||
postLog("旧式0xB0 -> 进BOOT");
|
||||
|
||||
uint16_t tx_len_legacy = 0U;
|
||||
SpiCommandContext ctx{};
|
||||
@@ -434,28 +513,27 @@ void AppSpiCommand::processCommand()
|
||||
}
|
||||
|
||||
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;
|
||||
/* 兜底路径(handler 未注册时的死代码保护):
|
||||
* 同样使用异步路径,避免在 ISR 内执行 Flash 操作。
|
||||
* 先装 0xB5 ACK,再设 pending 标志。 */
|
||||
m_txBuf[0] = CONFIG_SPI_APP_OTA_BOOT_ACK_OK;
|
||||
m_txBuf[1] = CONFIG_SPI_APP_OTA_BOOT_CMD;
|
||||
m_txBuf[2] = 0x4FU;
|
||||
m_txBuf[3] = 0x54U;
|
||||
tx_len_legacy = 4U;
|
||||
if (ok) {
|
||||
TaskManager::set(1UL << TASK_HEARTBEAT);
|
||||
}
|
||||
OtaBootRequest::requestEnterBootAsync();
|
||||
TaskManager::set(1UL << TASK_HEARTBEAT);
|
||||
}
|
||||
|
||||
m_txLen = (uint8_t)tx_len_legacy;
|
||||
m_txLen = tx_len_legacy;
|
||||
m_txPos = 0U;
|
||||
} else {
|
||||
/* 无法识别 */
|
||||
postLogFmt("[SPI-APP] 非协议 0x%02X → ERROR", first_valid);
|
||||
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_txLen = tx_len;
|
||||
m_txPos = 0U;
|
||||
}
|
||||
}
|
||||
@@ -465,20 +543,65 @@ void AppSpiCommand::processCommand()
|
||||
TaskManager::set(1UL << TASK_SPI_LOG);
|
||||
}
|
||||
|
||||
/* 应答帧已就绪:若解析期间未再收到新帧(m_cmdPending 仍为 false),
|
||||
* 拉高 PB6 通知主机可发起下一笔事务;否则保持 LOW,等待下一轮
|
||||
* processCommand 完成后再翻高,避免主机在从机未就绪时抢跑。 */
|
||||
if (!m_cmdPending) {
|
||||
setSpiReady(true);
|
||||
/* 应答帧已就绪:拉高 PB6(释放 SPIready)通知主机可发起下一笔事务。
|
||||
* 命令处理已在 ISR 内同步完成,无需 m_cmdPending 推迟机制。 */
|
||||
|
||||
/* 有响应待发时,设置按铃标志阻止主循环 canFeedbackPushStep() 覆盖 m_txBuf。
|
||||
* ESP32 下次 CS assert 时 DMA 输出响应,onCsRelease 清除 m_ringPending。
|
||||
* 50ms 超时后 cancelRing() 清除,恢复 CAN 反馈推送。 */
|
||||
if (m_txLen > 0U) {
|
||||
m_ringPending = true;
|
||||
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||
}
|
||||
setSpiReady(true);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主循环轮询:打印延迟日志
|
||||
* GD32 主动按铃:下拉 SPIready 请求 ESP32 来读
|
||||
* ============================================================ */
|
||||
|
||||
void AppSpiCommand::cancelRing()
|
||||
{
|
||||
m_ringPending = false;
|
||||
m_ringTxLen = 0U;
|
||||
/* 清除待发响应,防止超时后残留帧在下一次 CS assert 时被错误输出 */
|
||||
m_txLen = 0U;
|
||||
m_txPos = 0U;
|
||||
/* 释放 SPIready(高阻),避免永久占用线 */
|
||||
setSpiReady(true);
|
||||
}
|
||||
|
||||
bool AppSpiCommand::requestEsp32Read(uint8_t frame_type, const uint8_t *payload, uint16_t payload_len)
|
||||
{
|
||||
/* 若上一次按铃尚未被 ESP32 读走,拒绝重复按铃 */
|
||||
if (m_ringPending) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 构建请求帧到 m_txBuf(ESP32 下次读事务时通过 DMA 输出到 MISO) */
|
||||
uint16_t built_len = 0U;
|
||||
if (!spi_build_frame(frame_type, payload, payload_len, m_txBuf, &built_len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ringTxLen = built_len;
|
||||
m_txLen = built_len;
|
||||
m_txPos = 0U;
|
||||
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||
m_ringPending = true;
|
||||
|
||||
/* 下拉 SPIready,通知 ESP32「我有请求帧要给你读」 */
|
||||
setSpiReady(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主循环轮询:打印延迟日志 + 按铃超时看护
|
||||
* ============================================================ */
|
||||
|
||||
void AppSpiCommand::poll()
|
||||
{
|
||||
/* 打印 ISR 推迟的日志 */
|
||||
char log_copy[kLogBufSize] = {};
|
||||
bool has_log = false;
|
||||
|
||||
@@ -493,15 +616,18 @@ void AppSpiCommand::poll()
|
||||
if (has_log) {
|
||||
printf("%s\r\n", log_copy);
|
||||
}
|
||||
}
|
||||
|
||||
void AppSpiCommand::processPendingCommand()
|
||||
{
|
||||
if (!m_cmdPending) {
|
||||
return;
|
||||
/* 按铃超时看护:ESP32 未在 kRingTimeoutMs 内来读则放弃本次按铃。
|
||||
* 防止 ESP32 不响应时永久占用 SPIready 线导致双方死锁。 */
|
||||
if (m_ringPending) {
|
||||
uint32_t save2 = RT_CRITICAL_SAVE();
|
||||
if (m_ringPending) {
|
||||
if ((int32_t)(SysTickTimer::getMs() - m_ringDeadline) >= 0) {
|
||||
cancelRing();
|
||||
}
|
||||
}
|
||||
RT_CRITICAL_RESTORE(save2);
|
||||
}
|
||||
m_cmdPending = false;
|
||||
processCommand();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -514,8 +640,8 @@ void AppSpiCommand::onCsAssert()
|
||||
return;
|
||||
}
|
||||
m_csActive = true;
|
||||
/* CS 下降沿立即拉低 PB6:表示从机即将忙碌,
|
||||
* 防止主机在 onCsRelease 拉低 PB6 前抢跑发起下一笔 CS。
|
||||
/* CS 下降沿立即拉低 PB6:表示从机即将忙碌,
|
||||
* 防止主机在 onCsRelease 准备好响应前抢跑发起下一笔 CS。
|
||||
* (若此帧被 shouldIgnoreTransaction 忽略,onCsRelease 会恢复 HIGH) */
|
||||
setSpiReady(false);
|
||||
m_rxLen = 0U;
|
||||
@@ -570,6 +696,48 @@ void AppSpiCommand::onCsRelease()
|
||||
}
|
||||
}
|
||||
|
||||
/* 若本次 CS 事务是 ESP32 响应 GD32 按铃来读请求帧,
|
||||
* 则 GD32 的请求帧已通过 DMA 输出到 MISO,取消按铃状态。
|
||||
*
|
||||
* 但需区分:ESP32 发的是 dummy 读(按铃响应)还是 0xB0 命令(OTA 请求)。
|
||||
* 若 GD32 正在按铃时 ESP32 发 0xB0,会因 m_ringPending=true 被误吞,
|
||||
* 导致 OTA 无法启动。检测到 0xB0 时取消按铃并走正常命令处理。 */
|
||||
if (m_ringPending) {
|
||||
if (m_rxLen >= 1U && m_rxBuf[0] == CONFIG_SPI_APP_OTA_BOOT_CMD) {
|
||||
/* ESP32 发的是 0xB0 Bootloader 请求,不是按铃响应。
|
||||
* 取消按铃,清空 TX 让 processCommand() 重新装载 0xB5 应答。
|
||||
* 不释放 SPIready,processCommand() 末尾会释放。 */
|
||||
m_ringPending = false;
|
||||
m_ringTxLen = 0U;
|
||||
m_txLen = 0U;
|
||||
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) {
|
||||
/* ESP32 发的是协议帧(如 PING/OTA 等),不是按铃响应。
|
||||
* 取消按铃,清空 TX 让 processCommand() 重新装载对应应答。
|
||||
* 不释放 SPIready,processCommand() 末尾会释放。 */
|
||||
m_ringPending = false;
|
||||
m_ringTxLen = 0U;
|
||||
m_txLen = 0U;
|
||||
m_txPos = 0U;
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
/* 继续走 shouldIgnoreTransaction + processCommand(),不 return */
|
||||
} else {
|
||||
/* 正常按铃响应:请求帧已被读走,取消按铃状态 */
|
||||
m_ringPending = false;
|
||||
m_ringTxLen = 0U;
|
||||
/* 请求帧已被读走,清空 TX 防止下次事务重复输出 */
|
||||
m_txLen = 0U;
|
||||
m_txPos = 0U;
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
/* 释放 SPIready,通知 ESP32 本帧完成 */
|
||||
setSpiReady(true);
|
||||
/* 上层若需要 ESP32 处理该请求帧,由 ESP32 侧读到的帧 TYPE 决定后续动作 */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIgnoreTransaction(m_rxBuf, m_rxLen)) {
|
||||
m_rxLen = 0U;
|
||||
if (m_txActiveThisCs) {
|
||||
@@ -578,8 +746,8 @@ void AppSpiCommand::onCsRelease()
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
}
|
||||
m_txActiveThisCs = false;
|
||||
/* 忽略事务(空轮询/残包)后恢复 PB6=HIGH,
|
||||
* 避免 PB6 卡 LOW 导致 ESP32 waitSpiReady 超时 */
|
||||
/* 忽略事务(空轮询/残包)后恢复 PB6=HIGH,
|
||||
* 避免 PB6 卡 LOW 导致 ESP32 等待上升沿超时 */
|
||||
setSpiReady(true);
|
||||
return;
|
||||
}
|
||||
@@ -588,10 +756,10 @@ void AppSpiCommand::onCsRelease()
|
||||
m_txPos = 0U;
|
||||
m_txActiveThisCs = false;
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
m_cmdPending = true;
|
||||
/* 拉低 PB6 通知主机:从机正忙,在主循环解析完前不要再发起 CS */
|
||||
setSpiReady(false);
|
||||
TaskManager::set(1UL << TASK_SPI_CMD_PROCESS);
|
||||
|
||||
/* 命令处理在 EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame 模式),
|
||||
* 消除主循环延迟。处理完成后 processCommand() 末尾释放 SPIready。 */
|
||||
processCommand();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
Reference in New Issue
Block a user