Files
ESP32_COMM/.zcode/plans/plan-sess_a3047185-7ab8-4573-993b-07e65f6fed29.md
T
小龙虾 f9cbcc396b feat(spi): 增强 SPI master:性能剖析与执行耗时跟踪
- 新增 `lastWorkerExecUs()`:获取最近一次 worker 任务执行耗时(µs)。
- 引入 `SpiProfile` 结构体:事务级详细剖析,记录 SPI 事务各阶段的时间戳。
- 实现 `lastProfile()`:诊断用,获取最近一次事务剖析快照。
- SPI 时钟配置提升至 13.3 MHz,改善性能。
- 修改前端 JavaScript 轮询逻辑:新增 uptime 检查,更好地处理 ESP32 重启状态。
- SPI 测速 HTML 的计时单位由毫秒改为微秒显示。
- 新建 `spi_ring_cache.h`:共享 SPI ring push 缓存工具。
- 新增 `http_util.h`:共享 HTTP 处理器工具,含 JSON 拼接与查询参数解析。
- 引入 `json_parse.h`:轻量级 JSON 解析器,针对扁平 JSON 对象设计。
- 新增 Windows 环境下的项目构建批处理脚本。
- 重构 CAN 反馈处理:从推送式改为请求驱动架构,降低内存占用、提升效率。
2026-08-02 17:37:19 +08:00

81 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 方案:/can/feedback 改为请求驱动架构
### 目标
- ESP32 不再被动缓存 GD32 的 0xC1 推送,改为前端请求时主动向 GD32 查询
- GD32 停止主动推送 0xC1(降低 SPI 资源占用)
- 删除 `s_latest[3]`/`s_work[3]` 常驻 BSS(省 ~7.8KB)+ handler 栈上 `results[3]`(省 ~3.9KB 栈)
### 依据
- GD32 `handleFeedback` 已支持 0xC1 请求查询(`[channel, page]` → ACK + 页数据),无需新增协议
- ESP32 `transact(0xC1, ...)` 是现成请求/响应机制,`canFdSend`/`canFdSetRxMode` 已在用
- `canFdParseFeedbackPage` 可直接复用(只解析 payload 字节,不关心来源)
- 0xC4/0xC5/0xC6/0xD2 推送独立,不受影响
### 改动清单
#### 1. ESP32 侧 — `main/transport/canfd_bridge.cpp` / `.h`
新增 `canFdQueryFeedback(channel, page, can_feedback_result_t *out, uint8_t *entryIdx)`:
```cpp
int canFdQueryFeedback(uint8_t channel, uint8_t page,
can_feedback_result_t *out, uint8_t *entryIdx);
```
- 发 `transact(0xC1, {channel, page}, ...)`,超时 200ms
- 成功(resp_type==ACK)→ 调 `canFdParseFeedbackPage(resp_buf, resp_len, out, entryIdx)`
- 返回 0=成功,-1=SPI/参数失败,-2=GD32 拒绝(ERROR)
#### 2. ESP32 侧 — `main/server/http_handlers.cpp` `can_feedback_handler`
- 删除 `results[CAN_CHANNEL_MAX]` 栈数组 + `getLatestCanFeedback` 循环
- 改为:对每个使能通道,调 `canFdQueryFeedback(ch, 0, &result, &entryIdx)` + `canFdQueryFeedback(ch, 1, &result, &entryIdx)`,在栈上用单个 `can_feedback_result_t result`(1.3KB,用完即释放)
- lite 路径:只需 page 0(统计字段在 page0 header),1 次 SPI 查询即可
- full 路径:page 0 + page 1,2 次查询,然后序列化
- 查询失败 → 该通道 `valid=false`(与原来无快照时行为一致)
- `pauseCanFeedbackTask` 调用保留(send_batch 仍需独占 SPI)
#### 3. ESP32 侧 — `can_mode_handler` GET
- 原来 `getLatestCanFeedback(0, &fb)` 读缓存 mode → 改为 `canFdQueryFeedback(0, 0, &fb, &entryIdx)` 取 page0 的 mode 字段
- 原来的 fallback `canFdSetRxMode` 保留(查询也失败时兜底)
#### 4. ESP32 侧 — `main/monitor/can_feedback_task.cpp`
- **删除** `s_latest[3]`、`s_valid[3]`、`s_work[3]`、`s_workIdx[3]`、`s_lastLoggedError[3]`、`publish()`、`getLatestCanFeedback()`
- **删除** taskLoop 里的 0xC1 分支(`canFdParseFeedbackPage` + `publish`)
- **保留** 0xC4/0xC5/0xC6/0xD2 事件分发(这些仍走推送)
- **保留** `pauseCanFeedbackTask`(send_batch / OTA 仍需暂停事件消费)
- `s_mutex` 删除(仅保护 s_latest/s_valid,不再需要)
#### 5. ESP32 侧 — `main/monitor/can_feedback_task.h`
- 删除 `getLatestCanFeedback` 声明
- 保留 `startCanFeedbackTask`、`pauseCanFeedbackTask`
#### 6. GD32 侧 — `app_spi_cmd_can_feedback.cpp`
- `canFeedbackStartRound()`:改为空函数(或加 `s_pushEnabled=false` 永久禁用推送)
- `canFeedbackPushStep()`:开头加 `if (!s_pushEnabled) return;` 或直接 return
- **保留** `handleFeedback`(请求查询处理程序不动)
- **保留** `buildFeedbackPayload`(查询和推送共用)
#### 7. GD32 侧 — `task_runner.cpp`
- `canFeedbackStartRound()` 调用可保留(变成空函数无副作用)或删除
### 内存收益
| 项 | 改前 | 改后 | 省 |
|---|---|---|---|
| `s_latest[3]` BSS | 3.9KB | 0 | 3.9KB |
| `s_work[3]` BSS | 3.9KB | 0 | 3.9KB |
| handler 栈 `results[3]` | 3.9KB | 1.3KB(单通道) | 2.6KB 栈 |
| **合计常驻 BSS** | | | **~7.8KB** |
### 性能影响
- 原来读缓存:<1ms
- 改后查询:lite 路径 1 次 SPI 往返 ~2-4ms;full 路径 6 次往返(3ch×2page)~12-24ms
- 前端 1Hz 轮询,24ms 完全可接受(httpd 单线程最坏阻塞 <50ms)
- GD32 不再 1Hz 推送 6 帧,省下 SPI 带宽和 PB6 中断
### 风险与缓解
- **SPI 查询失败**:GD32 不响应时 `transact` 超时返回,handler 返回 valid=false,前端显示"--"(与原无快照行为一致)
- **OTA 期间查询**:send_batch/OTA 已 `pauseCanFeedbackTask`,且 SPI worker 串行化,查询不会与 OTA 数据流冲突
- **can_monitor.html full 路径延迟**:6 次 SPI 查询 ~24ms + JSON 序列化,总 <50ms,远小于 1s 轮询间隔
### 不改动的部分
- 0xC4 ADC、0xC5 motor state、0xC6 motor version、0xD2 motor OTA 推送机制不变
- `canFdParseFeedbackPage` 解析逻辑不变
- GD32 `handleFeedback` 查询处理程序不变
- 前端 JS 不变(API 响应格式不变)