- 新增 `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 反馈处理:从推送式改为请求驱动架构,降低内存占用、提升效率。
4.4 KiB
4.4 KiB
方案:/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):
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 响应格式不变)