feat(protocol): 更新SPI Link协议至v2版本并优化CAN通信

- 将SPI OTA帧格式升级为SPI Link v2,采用新的帧格式:
  | SOF0 0xA5 | SOF1 0x5A | VER 0x02 | FLAGS | SESSION 4B LE |
  TX_SEQ | ACK_SEQ | CREDIT | TYPE | LEN 2B LE | RSV 2B | PAYLOAD |
  CRC32 4B LE |
- 新增V2_OVERHEAD=20字节开销,APP最大payload为236字节,BOOT为108字节
- 修改帧类型定义:ENTER_BOOT从0xB5合并至0xB0,新增CAN业务类型0xC0~C3
- 更新CAN协议文档,支持SPI Link v2-only模式,调整最大业务payload计算
- 扩展0xC1帧功能,支持双向通信和1Hz主动推送ID快照
- 添加0xC3完整ID快照帧类型,包含seq/rx_count等完整字段
- 更新协议文档中的错误码定义和反馈payload格式

refactor(main): 重构主程序入口并添加CAN反馈任务

- 在main.cpp中添加CAN反馈任务,更新启动流程注释
- 创建createCriticalTask辅助函数,统一关键任务创建逻辑
- 调整SPI初始化顺序,确保CAN反馈任务依赖正确
- 优化错误处理机制,关键任务失败时立即重启
- 将GD32版本探测移至ota_relay任务处理
- 更新版本号至0.3.48,调整编译时间和Git哈希

config: 更新SPI和BLE配置参数

- 添加SPI主机相关配置参数,包括CS GPIO、READY GPIO等引脚定义
- 配置SPI缓冲区大小:APP为256字节,BOOT为128字节
- 更新BLE滑动窗口参数,ACK窗口设为32,数据队列深度为72
- 调整SPI工作队列大小至5120字节,增加任务堆栈监控说明
This commit is contained in:
小龙虾
2026-07-16 23:00:15 +08:00
parent 51c89b3065
commit 5db3f66b8e
57 changed files with 4728 additions and 2098 deletions
+27 -15
View File
@@ -207,6 +207,14 @@ public:
*/
static QueueHandle_t getResultQueue();
/**
* @brief 探测 GD32 APP 存活并缓存版本(公开,供 OtaCoordinator / 启动探测共用)。
* @param timeout_ms appPing 超时(毫秒)。
* @param out_uptime 可选,输出 GD32 uptime(秒)。
* @return true 探测成功,false 失败。
*/
static bool probeAndCacheAppAlive(uint32_t timeout_ms, uint32_t *out_uptime = nullptr);
// ── 线程安全锁 ──
static void initMutex(); ///< 初始化状态互斥锁(taskEntry 首行调用)
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(50); ///< 锁获取超时时间
@@ -320,26 +328,39 @@ private:
/**
* @brief 准备单包:读 Flash + 组装 tx_buf + 加密 + CRC(doRelayPackets 子步骤)。
* @param seq 当前包序号。
* @param flash_offset 本包在 gd32_ota 分区中的起始偏移(不依赖 m_transferFlashOffset,便于预组下一包)。
* @param meta 固件元数据(用于计算末包 chunk_size)。
* @param tx_buf 输出缓冲区(64 字节)。
* @param out_chunk_size 输出本包有效字节数(用于 sendRelayPacketWithRetry 推进 offset)。
* @param out_chunk_size 输出本包有效字节数。
* @return OTA_RESULT_OK 成功,OTA_RESULT_ERR_STORAGE 读 Flash 失败,OTA_RESULT_ERR_AUTH 加密失败。
*/
ota_result_t prepareRelayPacket(uint32_t seq, const ota_meta_t &meta, uint8_t tx_buf[64], size_t *out_chunk_size);
ota_result_t prepareRelayPacket(uint32_t seq, uint32_t flash_offset, const ota_meta_t &meta,
uint8_t tx_buf[64], size_t *out_chunk_size);
/** 等 ACK 期间可选预组下一包(与 GD32 处理重叠,隐藏本端 Flash/AES 延迟)。 */
struct NextPacketPrep {
uint32_t next_seq = 0;
uint32_t next_flash_offset = 0;
const ota_meta_t *meta = nullptr;
uint8_t *buf = nullptr; ///< 64 字节
size_t *chunk_size = nullptr;
bool *ready = nullptr;
};
/**
* @brief 发送单包并等待 ACK(doRelayPackets 子步骤)。
*
* 内含重试循环,使用以下命名常量:
* kMaxResendOnNack=10(ACK 失败/轮询耗尽重发上限)
* kMaxResendOnStructError=3(结构化错误重发上限)
* 见 WAIT 后只 poll 不重发 DATA。可选 next_prep:在等 ACK 的 poll 间隙预组下一包。
*
* @param seq 当前包序号。
* @param tx_buf 已组装好的 64 字节发送缓冲区。
* @param chunk_size 本包有效字节数(成功时用于推进 m_transferFlashOffset)。
* @param[out] out_result 失败时写入错误码(OK 时不修改)。
* @param next_prep 可选;非空则在 poll 循环内至多预组一次下一包。
* @return true 应中止外层循环(result 已设);false 继续下一包。
*/
bool sendRelayPacketWithRetry(uint32_t seq, const uint8_t tx_buf[64], size_t chunk_size, ota_result_t *out_result);
bool sendRelayPacketWithRetry(uint32_t seq, const uint8_t tx_buf[64], size_t chunk_size,
ota_result_t *out_result, NextPacketPrep *next_prep = nullptr);
/**
* @brief 发送结束指令并验证 APP 存活(顺序调用 doSendFinishOta + doWaitForAppAlive).
* @return OTA_RESULT_OK 成功,负值表示错误。
@@ -361,20 +382,11 @@ private:
uint32_t m_transferTotalPackets = 0;
uint32_t m_transferCurrentSeq = 0;
uint32_t m_transferFlashOffset = 0;
uint32_t m_transferRetryCount = 0;
TickType_t m_transferDeadline = 0;
// ── 加密/工具方法(不变)──
static int aes128EcbEncryptBlockLocal(const uint8_t *key, const uint8_t *in, uint8_t *out);
static void logRespPayload(const char *stage, uint8_t resp_type, const uint8_t *payload, size_t len);
/**
* @brief 探测 GD32 APP 存活并缓存版本(统一 OtaRelay 内部探测逻辑).
* 成功打印版本号,失败打印 warning。
* @param timeout_ms appPing 超时(毫秒)。
* @param out_uptime 可选,输出 GD32 uptime(秒)。
* @return true 探测成功,false 失败。
*/
static bool probeAndCacheAppAlive(uint32_t timeout_ms, uint32_t *out_uptime = nullptr);
esp_err_t waitUntilNotWait(const char *stage, uint8_t *resp_type,
uint8_t *resp_payload, size_t resp_max,
size_t *resp_len, uint32_t timeout_ms,