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:
+74
-230
@@ -1,39 +1,39 @@
|
||||
# CAN 通信协议文档
|
||||
|
||||
> 更新日期:2026-06-29
|
||||
> 涉及固件:GD32 DCU_APP + ESP32 ESP32_COMM
|
||||
> SPI 时钟:4 MHz | 应答帧最大 payload:123 字节(kTxBufSize=128 - overhead=5)
|
||||
> 更新日期:2026-07-16
|
||||
> 涉及固件:GD32 DCU_APP + ESP32 ESP32_COMM
|
||||
> SPI:**Link v2 only** · 时钟 4 MHz
|
||||
> APP 最大业务 payload:`kTxBufSize(256) - V2_OVERHEAD(20) = 236`
|
||||
> BOOT 最大业务 payload:`kTxBufSize(128) - V2_OVERHEAD(20) = 108`
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
ESP32 通过 SPI 协议与 GD32 通信,实现三路 CAN/CANFD 的收发控制。CAN 相关 SPI 命令占用 `0xC0` ~ `0xC2` 帧类型。
|
||||
ESP32 通过 SPI Link v2 与 GD32 通信,实现三路 CAN/CANFD 的收发控制。CAN 相关 SPI 命令占用 `0xC0` ~ `0xC3`。
|
||||
|
||||
| 帧类型 | 名称 | 方向 | 用途 |
|
||||
|--------|------|------|------|
|
||||
| `0xC0` | kTypeCanSend | ESP32 → GD32 | 发送一帧 CAN 数据 |
|
||||
| `0xC1` | kTypeCanFeedback | ESP32 → GD32 | 拉取不重复 ID 快照表(分页) |
|
||||
| `0xC1` | kTypeCanFeedback | 双向 | 通道统计 + ID 快照页;GD32 亦可 1Hz 主动推送 |
|
||||
| `0xC2` | kTypeCanSetMode | ESP32 → GD32 | 切换三路 CAN 接收过滤模式 |
|
||||
| `0xC3` | kTypeIdSnapshot | ESP32 → GD32 | 完整 ID 条目(含 seq/rx_count) |
|
||||
|
||||
---
|
||||
|
||||
## 2. SPI 协议帧格式
|
||||
## 2. SPI 协议帧格式(v2)
|
||||
|
||||
所有 SPI 帧遵循统一格式:
|
||||
所有 SPI 帧遵循 SPI Link v2(`spi_link/spi_link_v2.h`):
|
||||
|
||||
```
|
||||
[SOF:1B][TYPE:1B][LEN_LO:1B][LEN_HI:1B][PAYLOAD:NB][CRC8:1B]
|
||||
[SOF0 A5][SOF1 5A][VER 02][FLAGS][SESSION 4LE][TX_SEQ][ACK_SEQ][CREDIT][TYPE][LEN 2LE][RSV 2][PAYLOAD][CRC32 4LE]
|
||||
```
|
||||
|
||||
- SOF = `0xA5`
|
||||
- LEN = payload 长度(小端 16 位)
|
||||
- CRC8 = 对 SOF+TYPE+LEN+PAYLOAD 的校验
|
||||
- 开销 `V2_OVERHEAD = 20`
|
||||
- CRC32 MPEG-2(`0x04C11DB7`,无反射)
|
||||
- **无** v1 单字节 SOF + CRC8 路径
|
||||
|
||||
应答帧 TYPE:
|
||||
- `0x80` = ACK(成功)
|
||||
- `0x81` = WAIT(OTA 中间态)
|
||||
- `0x82` = ERROR
|
||||
应答 TYPE 常用:`0x80` ACK · `0x81` WAIT · `0x82` ERROR
|
||||
|
||||
---
|
||||
|
||||
@@ -44,253 +44,97 @@ ESP32 通过 SPI 协议与 GD32 通信,实现三路 CAN/CANFD 的收发控制
|
||||
```
|
||||
[0] channel 0/1/2
|
||||
[1..4] ext_id 扩展帧 ID(Big-Endian,29 位)
|
||||
[5..N] data CAN 数据负载(1~64 字节)
|
||||
[5..N] data CAN 数据负载(0~64 字节;可无,对应 DLC=0)
|
||||
```
|
||||
|
||||
最短 payload 为 **5** 字节(仅 ch+id,DLC=0,例如 `CANFD_BOOT`)。
|
||||
|
||||
### 应答 payload
|
||||
|
||||
```
|
||||
[0] 0x00(ACK)或错误码
|
||||
[0] 0x00(ACK)或错误码 E0~E5
|
||||
```
|
||||
|
||||
| 码 | 含义 |
|
||||
|----|------|
|
||||
| E0 | payload 过短(< 5 字节) |
|
||||
| E1 | 通道非法 |
|
||||
| E2 | BUS_OFF |
|
||||
| E3 | TEC 过高(>96,对端 ACK 失败率高) |
|
||||
| E4 | 邮箱满 |
|
||||
| E5 | 参数无效 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 0xC1 — 不重复 ID 快照表(核心协议)
|
||||
## 4. 0xC1 — 通道统计 + ID 快照(核心)
|
||||
|
||||
每秒 ESP32 对每通道发 2 次请求(page=0 和 page=1),拉取该通道所有**不重复 CAN ID** 的最新帧快照。
|
||||
GD32 可 1Hz 主动 `requestEsp32Read(0xC1)` 推送;ESP32 亦支持被动查询。
|
||||
打包上限:`kFeedbackPayloadLen = kTxBufSize - V2_OVERHEAD = 236`。
|
||||
|
||||
### 请求 payload
|
||||
### 请求 payload(被动)
|
||||
|
||||
```
|
||||
[0] channel 0/1/2
|
||||
[1] page 页号:0 = 条目 0~7,1 = 条目 8~15
|
||||
[1] page 0 = 条目 0~7,1 = 条目 8~15
|
||||
```
|
||||
|
||||
### 应答 payload(变长,最大 123 字节)
|
||||
### 应答 / 推送 payload(变长)
|
||||
|
||||
固定头 **28** 字节 + 条目区:
|
||||
|
||||
```
|
||||
[0] channel 回显通道号
|
||||
[1] page 回显页号
|
||||
[2] total_count 该通道实际 ID 条目总数(0~16)
|
||||
[3] entry_count 本页实际条目数
|
||||
[4] mode 当前 CAN 接收模式(0=全收, 1=过滤)
|
||||
[5..8] snapshot_seq 快照序号(LE,每秒 +1)
|
||||
[9..12] rx_count 通道累计接收帧数(LE)
|
||||
[13..16] tx_count 通道累计发送帧数(LE)
|
||||
[17..20] rx_drop_count 通道累计丢帧(LE)
|
||||
[21..24] error_count 通道累计错误(LE)
|
||||
[25..] 条目逐条拼接(见下)
|
||||
[0] channel
|
||||
[1] page
|
||||
[2] total_count
|
||||
[3] entry_count
|
||||
[4] mode 0=全收 1=过滤 2=桥接
|
||||
[5..8] snapshot_seq LE
|
||||
[9..12] rx_count LE
|
||||
[13..16] tx_count LE
|
||||
[17..20] rx_drop_count LE
|
||||
[21..24] error_count LE
|
||||
[25] tec
|
||||
[26] rec
|
||||
[27] bus_state bit0=init … bit3=bus_off bit4=EW
|
||||
[28..] 条目
|
||||
```
|
||||
|
||||
### 单条目格式(变长)
|
||||
### 单条目(8 + dlc)
|
||||
|
||||
```
|
||||
[+0..+3] id CAN 报文 ID(LE)
|
||||
[+4] dlc 数据长度(1~64)
|
||||
[+5] flags bit0=FD, bit1=BRS, bit2=ESI
|
||||
[+6..+7] reserved 保留(0x00)
|
||||
[+8..+N] data 报文数据(dlc 字节)
|
||||
```
|
||||
|
||||
条目总大小 = 8 + dlc 字节。
|
||||
- 标准 CAN(dlc=8):14 字节/条 → 每页最多 8 条
|
||||
- CANFD(dlc=64):72 字节/条 → 每页最多 1 条
|
||||
|
||||
### 拉取时序
|
||||
|
||||
```
|
||||
ESP32 GD32
|
||||
|--- 0xC1 [ch=0, page=0] --->|
|
||||
|<-- ACK [统计+条目0~7] -----|
|
||||
|--- 0xC1 [ch=0, page=1] --->|
|
||||
|<-- ACK [条目8~15] ---------|
|
||||
|--- 0xC1 [ch=1, page=0] --->|
|
||||
|<-- ACK [统计+条目0~7] -----|
|
||||
|--- 0xC1 [ch=1, page=1] --->|
|
||||
|<-- ACK [条目8~15] ---------|
|
||||
|--- 0xC1 [ch=2, page=0] --->|
|
||||
|<-- ACK [统计+条目0~7] -----|
|
||||
|--- 0xC1 [ch=2, page=1] --->|
|
||||
|<-- ACK [条目8~15] ---------|
|
||||
| (1 秒后重复)
|
||||
```
|
||||
|
||||
每轮 6 次 SPI 事务,SPI 总线占用 ~5ms(0.5%)。
|
||||
|
||||
### total_count 与 entry_count 的关系
|
||||
|
||||
- `total_count` = 该通道当前所有不重复 ID 的总数(0~16)
|
||||
- `entry_count` = 本页实际返回的条目数(≤8)
|
||||
- 当 `total_count ≤ 8` 时,page=1 的 `entry_count=0`,ESP32 不再拉第二页
|
||||
- 当 `total_count = 0` 时,表示该通道未收到任何 CAN 帧
|
||||
|
||||
---
|
||||
|
||||
## 5. 0xC2 — 切换接收过滤模式
|
||||
|
||||
### 请求 payload
|
||||
|
||||
```
|
||||
[0] mode 0 = 全部接收(kModeAllPass)
|
||||
1 = 按 ID 过滤(kModeFilterId)
|
||||
```
|
||||
|
||||
### 应答 payload
|
||||
|
||||
```
|
||||
[0] actual_mode GD32 实际生效的模式
|
||||
```
|
||||
|
||||
模式切换影响三路 CAN 通道(全局生效,不是单通道)。
|
||||
|
||||
---
|
||||
|
||||
## 6. GD32 端实现要点
|
||||
|
||||
### 不重复 ID 快照表(canfd.h)
|
||||
|
||||
```cpp
|
||||
struct IdSnapshotEntry {
|
||||
uint32_t id;
|
||||
uint32_t seq; // 更新序号
|
||||
uint32_t rx_count; // 该 ID 累计接收
|
||||
uint8_t dlc, fd_flag, brs, esi;
|
||||
uint8_t data[64];
|
||||
bool occupied;
|
||||
};
|
||||
|
||||
struct IdSnapshotTable {
|
||||
IdSnapshotEntry entries[16]; // CANFD_ID_SNAPSHOT_MAX
|
||||
uint8_t count;
|
||||
uint32_t seq;
|
||||
};
|
||||
```
|
||||
|
||||
### 中断更新逻辑(canfd.cpp::handleRxInterrupt)
|
||||
|
||||
每收到一帧 CAN:
|
||||
1. 遍历 `s_idSnapshot[channel].entries[0..count-1]`,查找 ID 匹配的条目
|
||||
2. 找到 → 更新 data/dlc/flags/rx_count++
|
||||
3. 未找到且 count < 16 → 新建条目
|
||||
4. 未找到且 count = 16 → FIFO 淘汰最旧条目(index 0),整体左移,新条目插入末尾
|
||||
|
||||
### SPI 处理器(app_spi_cmd_can_feedback.cpp)
|
||||
|
||||
- `kFeedbackPayloadLen = 123`(kTxBufSize 128 - overhead 5)
|
||||
- 固定头 25 字节 + 变长条目逐条拼接
|
||||
- 条目不足时 `entry_count < kPageEntries`,ESP32 侧据此判断是否需要拉下一页
|
||||
|
||||
---
|
||||
|
||||
## 7. ESP32 端实现要点
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
GD32 CAN 中断
|
||||
→ s_idSnapshot[ch].entries[匹配ID]
|
||||
↓ (1Hz 快照刷新)
|
||||
refreshFeedbackSnapshot → seq++
|
||||
↓ (ESP32 1Hz 轮询)
|
||||
0xC1 × 3通道 × 2页 = 6次 SPI/s
|
||||
↓
|
||||
can_feedback_task 缓存到 s_latest[ch]
|
||||
↓
|
||||
/can/feedback HTTP → JSON
|
||||
↓
|
||||
can_monitor.js 表格渲染
|
||||
```
|
||||
|
||||
### SPI 轮询(can_feedback_task.cpp)
|
||||
|
||||
- 固定 1Hz 循环,每通道发 2 次 0xC1(page=0 和 page=1)
|
||||
- OTA 进行中时暂停(`s_paused` 标志,由 OtaCoordinator 和 OtaRelay 设置)
|
||||
- 结果缓存在 `s_latest[3]`(`can_feedback_result_t`),线程安全(互斥锁)
|
||||
|
||||
### HTTP 接口
|
||||
|
||||
| 端点 | 方法 | 用途 |
|
||||
|------|------|------|
|
||||
| `/can/feedback` | GET | 三路 CAN 通道统计 + ID 快照表 |
|
||||
| `/can/mode` | GET | 查询当前接收模式 |
|
||||
| `/can/mode` | POST | 切换模式 `{"mode":0}` 或 `{"mode":1}` |
|
||||
| `/can/send` | POST | 发送一帧 CAN 数据 |
|
||||
|
||||
### /can/feedback 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": [
|
||||
{
|
||||
"ch": 0,
|
||||
"valid": true,
|
||||
"mode": 0,
|
||||
"seq": 1234,
|
||||
"rx": 5678,
|
||||
"tx": 0,
|
||||
"drop": 0,
|
||||
"err": 0,
|
||||
"count": 5,
|
||||
"entries": [
|
||||
{
|
||||
"id": "0x18DA00F1",
|
||||
"dlc": 8,
|
||||
"fd": 0,
|
||||
"brs": 0,
|
||||
"esi": 0,
|
||||
"data": "0102030405060708"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "ch": 1, "valid": false },
|
||||
{ "ch": 2, "valid": false }
|
||||
]
|
||||
}
|
||||
[+0..+3] id LE
|
||||
[+4] dlc
|
||||
[+5] flags bit0=FD bit1=BRS bit2=ESI
|
||||
[+6..+7] reserved
|
||||
[+8..] data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 前端(tools/can_monitor.html)
|
||||
## 5. 0xC2 — 模式切换
|
||||
|
||||
### 页面结构
|
||||
|
||||
1. **顶部状态栏**:连接状态、当前模式、轮询状态
|
||||
2. **三路通道面板**(CH0/CH1/CH2):
|
||||
- 状态(活跃/无帧)
|
||||
- rx/tx/drop/err 统计
|
||||
- seq(活性指示)
|
||||
- 当前 ID 数量
|
||||
3. **每 ID 快照表**:
|
||||
- 通道下拉选择
|
||||
- 每行一个不重复 ID
|
||||
- 列:CAN ID | DLC | FD | BRS | ESI | 数据 (HEX)
|
||||
- 1Hz 自动刷新
|
||||
|
||||
### 导航
|
||||
|
||||
- OTA 控制台 (`index.html`) → "CAN 接收监控" 按钮
|
||||
- CAN 发送控制台 (`can.html`) → header "CAN 接收监控" 链接
|
||||
- CAN 接收监控 (`can_monitor.html`) → header 可跳回 CAN 发送或 OTA 控制台
|
||||
```
|
||||
req[0] mode 0=全收 1=过滤 2=CAN0→1/2 桥接
|
||||
rsp[0] actual 生效 mode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 资源消耗
|
||||
## 6. 0xC3 — ID 快照(完整字段)
|
||||
|
||||
| 资源 | 消耗 |
|
||||
|------|------|
|
||||
| GD32 SRAM(快照表) | 3.8KB / 128KB = 3% |
|
||||
| GD32 中断开销(16 条遍历) | < 1μs |
|
||||
| SPI 事务/秒 | 6 次(3 通道 × 2 页) |
|
||||
| SPI 总线占用 | ~5ms/s = 0.5% |
|
||||
| ESP32 RAM(缓存) | ~4KB |
|
||||
| OTA 期间 CAN 轮询 | 自动暂停 |
|
||||
固定头 4B + 条目 `(16 + dlc)`(含 id/seq/rx_count/dlc/flags/rsv/data)。
|
||||
上限同 APP:`256 - V2_OVERHEAD = 236`。
|
||||
|
||||
---
|
||||
|
||||
## 10. 已废弃
|
||||
## 7. 实现索引
|
||||
|
||||
| 项目 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `0xC3 kTypeIdSnapshot` | 保留定义但不使用 | 功能已合并到 0xC1 |
|
||||
| `/can/idsnap` | 已移除 | 数据由 `/can/feedback` 统一提供 |
|
||||
| `canFdPullIdSnapshot()` | 已移除 | 改用 `canFdPollFeedback()` 分页拉取 |
|
||||
| 端 | 文件 |
|
||||
|----|------|
|
||||
| GD32 0xC1/C2 | `DCU_APP/.../app_spi_cmd_can_feedback.cpp` |
|
||||
| GD32 0xC3 | `app_spi_cmd_id_snapshot.cpp` |
|
||||
| GD32 0xC0 | `app_spi_cmd_can_bridge.cpp` |
|
||||
| ESP32 解析/桥 | `ESP32_COMM/main/transport/canfd_bridge.*` |
|
||||
| ESP32 读预算 | `spi_master.cpp` → `spiRespReadBudget()` |
|
||||
| 帧格式 | `spi_link/spi_link_v2.h` |
|
||||
| 图示文档 | `SPEC/communication_ids.html` · `SPEC/spi_flow_diagram.html` |
|
||||
|
||||
Reference in New Issue
Block a user