From 01382a3fc151785778e151f550b376321f4629d2 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Sat, 13 Jun 2026 17:07:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(canfd):=20=E6=B7=BB=E5=8A=A0=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=8F=91=E9=80=81CANFD=E5=B8=A7=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E7=94=A8=E4=BA=8ESPI=E6=A1=A5=E6=8E=A5=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CanFd::transmitFrame() 方法,支持绕过软件队列直接调用 硬件发送CANFD扩展帧 - 实现参数验证,包括通道索引、数据指针和DLC长度检查 - 集成GD32E508特定的FD标志和波特率切换设置 - 更新诊断统计信息,记录发送计数、最后ID、通道和负载长度 feat(spi-command): 添加SPI到CAN总线桥接发送命令类型 - 引入 kTypeCanSend (0xC0) 命令类型用于SPI-CAN桥接 - 实现CAN_SEND命令处理逻辑,解析载荷中的通道、ID和数据 - 支持3个CAN通道(0/1/2),验证通道有效性防止越界访问 - 添加详细的错误处理和日志记录机制 refactor(config): 更新SPI命令ACK确认码和构建版本号 - 将CONFIG_SPI_APP_OTA_BOOT_ACK_OK从0xA5U改为0xB5U以避免冲突 - 提升APP_FW_VERSION_BUILD从1到2以反映新功能集成 --- GD32E508_DCU_APP/device/CANFD.cpp | 43 +++++++++++++++ GD32E508_DCU_APP/device/app_spi_command.cpp | 43 +++++++++++++++ GD32E508_DCU_APP/device/app_spi_command.h.bak | 55 ------------------- GD32E508_DCU_APP/device/canfd.h | 12 ++++ GD32E508_DCU_APP/sys/config.h | 4 +- 5 files changed, 100 insertions(+), 57 deletions(-) delete mode 100644 GD32E508_DCU_APP/device/app_spi_command.h.bak diff --git a/GD32E508_DCU_APP/device/CANFD.cpp b/GD32E508_DCU_APP/device/CANFD.cpp index 9f6863f..7b2dea4 100644 --- a/GD32E508_DCU_APP/device/CANFD.cpp +++ b/GD32E508_DCU_APP/device/CANFD.cpp @@ -432,6 +432,49 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t return true; } +/** + * @brief 直接向指定 CAN 通道发送一帧任意数据报文(扩展帧)。 + * @param[in] channelIndex 目标 CAN 通道号(0/1/2)。 + * @param[in] ext_id 扩展帧 ID(29 位)。 + * @param[in] data 数据负载指针。 + * @param[in] dlc 数据长度(1~64)。 + * @retval bool true 表示成功入队硬件邮箱,false 表示参数无效或邮箱已满。 + * @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。 + */ +bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, + const uint8_t *data, uint8_t dlc) +{ + if (channelIndex >= kChannelCount || data == nullptr || dlc == 0U || dlc > 64U) { + return false; + } + + can_trasnmit_message_struct tx_msg{}; + can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg); + tx_msg.tx_efid = ext_id; + tx_msg.tx_ff = CAN_FF_EXTENDED; + tx_msg.tx_ft = CAN_FT_DATA; + tx_msg.tx_dlen = dlc; +#ifdef GD32E508 + tx_msg.fd_flag = (dlc > 8U) ? ENABLE : DISABLE; + tx_msg.fd_brs = (dlc > 8U) ? ENABLE : DISABLE; + tx_msg.fd_esi = CAN_ESI_DOMINANT; +#endif + for (uint8_t i = 0U; i < dlc; ++i) { + tx_msg.tx_data[i] = data[i]; + } + + const uint8_t mailbox = can_message_transmit(kChannels[channelIndex].canPeriph, &tx_msg); + if (mailbox != CAN_NOMAILBOX) { + s_diag.tx_count++; + s_diag.last_tx_id = ext_id; + s_diag.last_tx_channel = channelIndex; + s_diag.last_payload_length = dlc; + return true; + } + s_diag.error_count++; + return false; +} + /** * @brief 获取 CANFD 诊断统计信息只读指针。 * @param[in] 无。 diff --git a/GD32E508_DCU_APP/device/app_spi_command.cpp b/GD32E508_DCU_APP/device/app_spi_command.cpp index 1761621..c5ecdeb 100644 --- a/GD32E508_DCU_APP/device/app_spi_command.cpp +++ b/GD32E508_DCU_APP/device/app_spi_command.cpp @@ -12,6 +12,7 @@ */ #include "device/app_spi_command.h" +#include "device/canfd.h" extern "C" { #include "gd32e50x_exti.h" @@ -34,6 +35,7 @@ extern "C" { * ============================================================ */ static constexpr uint8_t kSof = 0xA5U; static constexpr uint8_t kTypeBootReq = 0xB0U; +static constexpr uint8_t kTypeCanSend = 0xC0U; static constexpr uint8_t kTypeOtaReq = 0x01U; static constexpr uint8_t kTypeAppPing = 0xA1U; static constexpr uint8_t kTypeOtaData = 0x02U; @@ -359,6 +361,47 @@ void AppSpiCommand::processCommand() spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len); } break; + + 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(payload[1]) << 24) + | (static_cast(payload[2]) << 16) + | (static_cast(payload[3]) << 8) + | static_cast(payload[4]); + const uint8_t *can_data = &payload[5]; + uint8_t can_dlc = static_cast(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; } s_txLen = (uint8_t)tx_len; s_txPos = 0U; diff --git a/GD32E508_DCU_APP/device/app_spi_command.h.bak b/GD32E508_DCU_APP/device/app_spi_command.h.bak deleted file mode 100644 index 6c80865..0000000 --- a/GD32E508_DCU_APP/device/app_spi_command.h.bak +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef DEVICE_APP_SPI_COMMAND_H -#define DEVICE_APP_SPI_COMMAND_H - -#include - -/** - * @brief APP 运行期 SPI 命令从机模块。 - * - * 复用 BOOT 的 SpiOta 协议帧格式(SOF+TYPE+LEN+PAYLOAD+CRC): - * - TYPE=0xB0 → 请求进入 Bootloader,回复 ACK 帧 - * - TYPE=0x01/0x02/0x03(OTA 命令)→ 回复 WAIT 帧(不在 BOOT 模式) - * - 旧式单字节 0xB0 命令仍兼容 - * - * 调试日志采用延迟输出:processCommand() 只写缓冲+置标志, - * poll() 中再 printf,避免在帧处理路径上耗时。 - */ -class AppSpiCommand { -public: - /** @brief 初始化 SPI0 从机命令通道。 */ - static void init(); - - /** @brief 主循环轮询:打印延迟日志。 */ - static void poll(); - - /** @brief SPI0 中断处理入口。 */ - static void onInterrupt(); - - /** @brief CS 边沿中断处理入口。 */ - static void onCsExtiInterrupt(); - -private: - static constexpr uint16_t kRxBufSize = 128U; - static constexpr uint16_t kTxBufSize = 64U; - static constexpr uint16_t kLogBufSize = 128U; - - static volatile bool s_csActive; - static volatile uint8_t s_rxLen; - static volatile uint8_t s_txLen; - static volatile uint8_t s_txPos; - static volatile bool s_logPending; - static uint8_t s_rxBuf[kRxBufSize]; - static uint8_t s_txBuf[kTxBufSize]; - static char s_logBuf[kLogBufSize]; - - static void configureGpio(); - static void configureSpi0(); - static void configureExti(); - static void onCsAssert(); - static void onCsRelease(); - static void processCommand(); - static void logDeferred(const char *msg); - static void logDeferredFmt(const char *fmt, uint8_t val); -}; - -#endif /* DEVICE_APP_SPI_COMMAND_H */ diff --git a/GD32E508_DCU_APP/device/canfd.h b/GD32E508_DCU_APP/device/canfd.h index c29d3eb..67ba852 100644 --- a/GD32E508_DCU_APP/device/canfd.h +++ b/GD32E508_DCU_APP/device/canfd.h @@ -111,6 +111,18 @@ public: */ static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr); + /** + * @brief 直接向指定 CAN 通道发送一帧任意数据报文(扩展帧)。 + * @param[in] channelIndex 目标 CAN 通道号(0/1/2)。 + * @param[in] ext_id 扩展帧 ID(29 位)。 + * @param[in] data 数据负载指针。 + * @param[in] dlc 数据长度(1~64)。 + * @retval bool true 表示成功入队硬件邮箱,false 表示参数无效或邮箱已满。 + * @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。 + */ + static bool transmitFrame(uint8_t channelIndex, uint32_t ext_id, + const uint8_t *data, uint8_t dlc); + /** * @brief 获取 CANFD 诊断统计信息只读指针。 * @param[in] 无。 diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index e2b273f..e0170f9 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -57,7 +57,7 @@ /** @brief ESP32 在 APP 运行期通过 SPI 发送的进入 Bootloader OTA 命令。 */ #define CONFIG_SPI_APP_OTA_BOOT_CMD 0xB0U /** @brief SPI 进入 Bootloader OTA 命令确认状态:成功。 */ -#define CONFIG_SPI_APP_OTA_BOOT_ACK_OK 0xA5U +#define CONFIG_SPI_APP_OTA_BOOT_ACK_OK 0xB5U /** @brief SPI 进入 Bootloader OTA 命令确认状态:失败。 */ #define CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR 0xE2U /** @brief CAN 接收过滤器目标标准帧 ID。 */ @@ -127,7 +127,7 @@ /** @brief APP 补丁版本号。 */ #define APP_FW_VERSION_PATCH 0U /** @brief APP 构建编号。 */ -#define APP_FW_VERSION_BUILD 1U +#define APP_FW_VERSION_BUILD 2U /* ============================================================ * BOOT 版本在 OTA 配置页中的 Flash 存储地址