feat(canfd): 添加直接发送CANFD帧功能用于SPI桥接场景
- 新增 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以反映新功能集成
This commit is contained in:
@@ -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] 无。
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
s_txLen = (uint8_t)tx_len;
|
||||
s_txPos = 0U;
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#ifndef DEVICE_APP_SPI_COMMAND_H
|
||||
#define DEVICE_APP_SPI_COMMAND_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @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 */
|
||||
@@ -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] 无。
|
||||
|
||||
@@ -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 存储地址
|
||||
|
||||
Reference in New Issue
Block a user