feat(transport): 添加SPI Ready握手引脚支持并优化GPIO驱动依赖

添加ESP32-C3 GPIO驱动组件以支持SPI Ready握手机制,新增
SPI_READY_GPIO(10)引脚配置用于监测GD32 PB6就绪状态,避免SPI
通信时从机未准备好的丢帧问题。
This commit is contained in:
小龙虾
2026-07-06 19:14:13 +08:00
parent 2fe5393769
commit 069ce8e6a5
17 changed files with 251 additions and 76 deletions
+12 -1
View File
@@ -4,6 +4,7 @@
#include <string.h>
#include "canfd_bridge.h"
#include "spi_master.h"
#include "monitor/debug_log.h"
namespace {
@@ -39,7 +40,16 @@ static int sendCanFrameOverSpi(can_channel_t channel, uint32_t ext_id,
&resp_len,
timeout_ms);
if (ret != ESP_OK || resp_type != SPI_OTA_RSP_ACK) {
return -1;
if (ret != ESP_OK) {
DEBUG_WARN("CAN", "CAN_SEND SPI失败: ch=%u id=0x%08lX err=%s",
(unsigned)channel, (unsigned long)ext_id, esp_err_to_name(ret));
return -2; /* SPI 通信失败 */
}
/* resp_type != ACK: 读取 GD32 返回的错误码 */
uint8_t gd32_err = (resp_len > 0 && recv_buf) ? recv_buf[0] : 0xFF;
DEBUG_WARN("CAN", "CAN_SEND GD32拒绝: ch=%u id=0x%08lX resp=0x%02X err=0x%02X",
(unsigned)channel, (unsigned long)ext_id, resp_type, gd32_err);
return -3; /* GD32 返回 ERROR */
}
if (recv_buf == nullptr || recv_max == 0U) {
@@ -67,6 +77,7 @@ static inline uint32_t readU32Le(const uint8_t *p) {
/**
* @brief 发送一帧 CANFD 数据,可选等待并读取响应。
* 通过 SPI 协议帧发送给 GD32 APP,并读取 ACK/ERROR 响应。
* 返回值: >=0 成功(接收数据长度), -1 参数错误, -2 SPI通信失败, -3 GD32拒绝。
*/
int canFdSend(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
+61
View File
@@ -12,6 +12,7 @@
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "soc/spi_struct.h"
#include "esp_err.h"
#include "esp_log.h"
@@ -35,6 +36,44 @@ SpiMaster& SpiMaster::instance()
return inst;
}
/**
* @brief 等待 GD32 PB6 SPI Ready 握手引脚拉高(就绪)。
* GD32 从机在 CS 断言瞬间拉低 PB6(忙),在响应帧就绪后拉高(就绪)。
* 主机每次发起 CS 前调用,确保从机已准备好接收下一笔事务。
* @param timeout_ms 超时时间(毫秒),0 表示不等待直接返回。
* @return ESP_OK 表示从机就绪;ESP_ERR_TIMEOUT 表示超时(退化为继续发起)。
*/
esp_err_t SpiMaster::waitSpiReady(uint32_t timeout_ms)
{
if (timeout_ms == 0U) {
return ESP_OK;
}
const TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms);
for (;;) {
int level = gpio_get_level((gpio_num_t)SPI_READY_GPIO);
if (level == 1) {
/* 从机就绪:复位超时计数器,下次掉线时首次超时又能立即告警 */
m_spiReadyTimeoutCount = 0U;
return ESP_OK;
}
if (xTaskGetTickCount() >= deadline) {
/* 限频:首次超时立即告警,之后每 kSpiReadyWarnEvery 次打印一条,
* 避免 GD32 掉线/卡死时高频日志刷屏。计数器无锁:仅在持锁的
* transferClocked 路径调用,单任务访问,无需保护。 */
++m_spiReadyTimeoutCount;
if (m_spiReadyTimeoutCount == 1U ||
(m_spiReadyTimeoutCount % kSpiReadyWarnEvery) == 0U) {
DEBUG_WARN("SPI", "waitSpiReady 超时: GPIO%d 仍为 LOW(从机忙), timeout=%ums, 累计=%lu",
SPI_READY_GPIO, (unsigned)timeout_ms,
(unsigned long)m_spiReadyTimeoutCount);
}
return ESP_ERR_TIMEOUT;
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
// ============================================================
// 内部 SPI 传输
// ============================================================
@@ -69,6 +108,11 @@ esp_err_t SpiMaster::transferClocked(const uint8_t *tx_buf, size_t tx_len,
return ESP_ERR_INVALID_SIZE;
}
/* CS 断言前等待 GD32 PB6 拉高(就绪):从机在上一笔 onCsRelease/
* processCommand 末尾才会拉高 PB6,提前发起 CS 会因从机未装好
* DMA/响应帧而丢失字节。超时退化为继续发起,保留原有容错路径。 */
(void)waitSpiReady(SPI_READY_TIMEOUT_MS);
uint8_t tx_staging[kSpiTransferMaxBytes];
uint8_t rx_staging[kSpiTransferMaxBytes];
const uint8_t *tx_ptr = tx_buf;
@@ -336,6 +380,23 @@ esp_err_t SpiMaster::init()
DEBUG_INFO("SPI", "SPI2 总线初始化完成: MOSI=GPIO5, MISO=GPIO7, SCLK=GPIO6, CS=GPIO%d, CLK=%d Hz",
SPI_MASTER_CS_GPIO, SPI_CLOCK_HZ);
/* 配置 SPI Ready 握手输入引脚(GPIO10),监测 GD32 PB6 电平。
* GD32 在 CS 下降沿拉低 PB6(忙),在 onCsRelease/processCommand 末尾拉高(就绪)。
* 主机每次发起 CS 前须等待 PB6=HIGH,避免从机未就绪时抢跑丢帧。 */
gpio_config_t ready_cfg = {};
ready_cfg.pin_bit_mask = (1ULL << SPI_READY_GPIO);
ready_cfg.mode = GPIO_MODE_INPUT;
ready_cfg.pull_up_en = GPIO_PULLUP_ENABLE;
ready_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE;
ready_cfg.intr_type = GPIO_INTR_DISABLE;
esp_err_t gpio_ret = gpio_config(&ready_cfg);
if (gpio_ret != ESP_OK) {
DEBUG_WARN("SPI", "SPI Ready 握手引脚 GPIO%d 配置失败: %s", SPI_READY_GPIO, esp_err_to_name(gpio_ret));
/* 非致命:握手失败时退化为不等待,保持原有行为 */
} else {
DEBUG_INFO("SPI", "SPI Ready 握手输入已配置: GPIO%d (监测 GD32 PB6)", SPI_READY_GPIO);
}
return ESP_OK;
}
+8 -1
View File
@@ -1,4 +1,4 @@
/*
/*
* spi_master.h - SPI Master 驱动头文件
* CANFD 3通道发送接口 + 底层 SPI 总线
*
@@ -17,6 +17,8 @@
#define SPI_CLOCK_HZ (4 * 1000 * 1000) // 4MHz
#define SPI_MODE (0) // CPOL=0, CPHA=0
#define SPI_QUEUE_SIZE (1) ///< SPI 事务队列深度
#define SPI_READY_GPIO (10) // GD32 PB6 SPI Ready 握手输入引脚(HIGH=就绪,LOW=忙)
#define SPI_READY_TIMEOUT_MS (100) // waitSpiReady 单次超时(毫秒)
#define SPI_OTA_FRAME_MAX (133) ///< SPI OTA 单帧最大字节数
// GD32 SPI OTA 帧类型
@@ -153,6 +155,11 @@ private:
SemaphoreHandle_t m_lock;
};
/** @brief 等待 GD32 PB6 SPI Ready 握手引脚拉高(就绪),CS 断言前调用。 */
esp_err_t waitSpiReady(uint32_t timeout_ms = SPI_READY_TIMEOUT_MS);
// waitSpiReady 超时日志限频:每 kSpiReadyWarnEvery 次超时打印一条,避免 GD32 掉线时刷屏
static constexpr uint32_t kSpiReadyWarnEvery = 32U; ///< 超时日志打印间隔
uint32_t m_spiReadyTimeoutCount = 0U; ///< 累计超时次数(用于限频)
// 内部 SPI 传输
/**
* @brief 执行底层 SPI 时钟同步传输。