ESP32-C3FH4 × GD32E508 双 MCU 架构:WiFi / BLE 双通道 OTA 中继 · SPI Link v2 高速桥 · 三路 CANFD 电机总线控制
ESP32_COMM 是机器人 DCU(Domain Control Unit,域控制器)板上的 ESP32-C3 固件,承担三类职责:
① OTA 中继枢纽:上位机通过 WiFi(HTTP)或 BLE(GATT)把固件交给 ESP32;ESP32 自身固件写入 AB 双分区升级, GD32 固件缓存到本地 Flash 后经 SPI 安全通道转发烧写,电机节点固件经 GD32 的 CAN 总线二次中继。
② SPI Link v2 主站:以 13.33 MHz 五线 SPI(含 DATA_READY 通知线)与 GD32E508 建立带会话、序号、 信用流控和 CRC32 校验的可靠链路,是所有跨芯片业务的唯一通道。
③ 电机总线控制面:把 HTTP/BLE 请求翻译成 GD32 上三路 CAN/CANFD 收发(单帧、批量、跨通道同步突发、 只听模式),并周期回收电机状态、ADC、版本等遥测,通过网页控制台呈现。
| 项 | 值 | 说明 |
|---|---|---|
| 固件版本 | v0.5.2(FW_VERSION_MAJOR/MINOR/PATCH) | main/include/version.h 手动维护;构建时间由构建系统自动生成(fwBuildTime()) |
| Git 提交 | 770ae14 | 手动填入 version.h |
| 平台 / 框架 | ESP32-C3FH4 · ESP-IDF v6.0 · FreeRTOS | RISC-V 单核 160MHz,4MB Flash |
| 代码规模 | 约 19,000 行 C++(main/ 下 30 个源文件) | 另含三工程共享协议 spi_link/spi_link_v2.h(兄弟目录) |
| 对端固件 | GD32 Bootloader + GD32 DCU_APP + 电机节点 | 本仓只含 ESP32 侧;协议真源 SPEC/spi_link_v2.md 与 GD32 仓共享 |
| 功能域 | 能力 | 状态 | 详见 |
|---|---|---|---|
| OTA 中继 | GD32 固件 WiFi/BLE 上传 → SPI 加密转发(六阶段握手/协商/转发/验证) | 已实现 | §7 |
| ESP32 自身 AB 分区 OTA + SHA-256 回读校验 + 回滚保护 | 已实现 | §7.5 | |
| 电机节点固件 CAN 二次中继(0xD0/D1/D2) | 已实现 | §11 | |
| GD32 固件包级防呆(Cortex-M 向量表身份校验,禁止 BOOT 包刷入 APP 区) | 已实现 | §7.6 | |
| 传输链路 | SPI Link v2:会话 / 序号 / 信用流控 / CRC32 / HELLO 重同步 / 延迟一拍协议 | 已实现 | §5 |
| AES-128 会话密钥挑战-应答握手(GD32 OTA 数据面) | 已实现 | §6 | |
| 链路熔断(3 连败×2s 冷却)、透明重试(kTxRetryMax=3)、撕裂诊断 ring_steal_diag | 已实现 | §5.8/§13 | |
| CAN 控制 | 0xC0 单帧 / 0xC7 跨通道同步突发(多电机同发 <1ms 偏差) | 已实现 | §10 |
| 0xC1 统计+ID 快照分页查询 / 0xC2 收模式(全收/过滤/桥接)/ 0xC3 完整快照 | 已实现 | §10.2 | |
| 0xC8 收发器 Silent 只听不发(TCAN1057AV S 脚) | 已实现 | §10.4 | |
| 0xC1 GD32 1Hz 主动推送路径 | 保留但默认请求驱动 | §12.2 | |
| 接入通道 | WiFi STA/AP 自动降级 + HTTP 22 端点 | 已实现 | §9 |
| BLE GATT 窗口 ACK(64)+ 乱序重排 + 负 ACK 续传 | 已实现 | §8 | |
| BLE/WiFi 双通道互斥与射频共存(软挂起) | 已实现 | §8.5 | |
| 监控诊断 | 健康监控(温度/RSSI/堆,4 级状态,OTA 门控) | 已实现 | §12.1 |
| 日志三路输出(UART 3M / UDP / 环形缓冲 → /logs)+ UART0 RX 泵 | 已实现 | §12.3 | |
| GD32 ADC 遥测(0xC4)/ 电机状态(0xC5×14 页)/ 电机版本(0xC6)/ 初始化状态(0xA3) | 已实现 | §12.2 | |
| CPU 占用 / 任务栈水位 / 堆低水位快照 / 启动自检位图 | 已实现 | §4/§12 | |
| 网页控制台 | OTA 控制台 / 电机控制台 / CAN 工具 / CAN 监控 四页面 | 已实现 | §17 |
| DBC 报文编辑、波形显示、启动自检面板、堆/任务抽屉 | 已实现 | §17 | |
| HTTP 接口认证(token) | 未实现 | §19 |
debug_log.h:34),115200 仅为 sdkconfig 控制台项。| 项目 | 规格 | 备注 |
|---|---|---|
| 芯片 | ESP32-C3FH4(RISC-V 单核 160MHz,Wi-Fi 4 + BLE 5) | 所有任务固定核 0 |
| Flash | 4MB,DIO 模式 | 曾试 QIO 致 OTA 读回 SHA 必错,已锁定 DIO(sdkconfig 注释警告) |
| SPI | MOSI=GPIO5 · MISO=GPIO7 · SCLK=GPIO6 · CS=GPIO4 · DATA_READY=GPIO10 | 13.33MHz(CONFIG_DCU_SPI_CLOCK_HZ=13333333)Mode 0;GD32 PB6 开漏驱动 DATA_READY |
| 调试 UART0 | TX=GPIO21 · RX=GPIO20 · 3,000,000 baud | 非阻塞直写 FIFO;RX 泵把外部串口行汇入 /logs |
| 欠压检测 | LVL7(约 2.8V) | 长断电后 CHIP_EN RC 充电慢,LVL5 会停在半复位状态 |
| GD32 侧存储 | BOOT 0x08000000~0x08011FFF(72KB)· APP 0x08048000~0x0807DFFB(216KB)· SRAM 0x20000000~0x2001FFFF | 固件身份校验的判据区间(§7.6) |
config/partitions.csv)gd32_ota 分区,靠互斥串行化)关键设计约束(线上事故沉淀):UART 日志直写曾阻塞 ota_relay(23)/spi_worker(6) 数毫秒,导致 50ms ACK 窗口拖爆、12/777 包重发——因此日志改非阻塞 FIFO 写、BLE worker 提到 prio 12 尽快排空队列、spi_worker 提到 prio 8(高于 motor_feed 的 7,低于状态机任务)。
SPI Link v2 是三个工程(ESP32_COMM / GD32 DCU_APP / GD32 Bootloader)共享的链路协议,真源 spi_link/spi_link_v2.h,PC 端 golden-vector 自测交叉锁定。它解决了裸 SPI 没有「帧界、会话、背压、应答归属」的问题。
| 机制 | 规则 |
|---|---|
| seq 推进 | 发送成功且非陈旧读 → m_txSeq++;poll 读到 ACK/WAIT/ERROR 且 ack_seq==lastSent → 推进;WAIT 也必须推进(否则同 seq 重发被判重,feedTask 卡首 chunk) |
| 迟到响应偷读 | poll 超时放弃后 GD32 迟到响应拉低 PB6 被 worker 按铃读走(帧已上时钟无法退回)→ 同样必须推进 seq(0.4.2 修复,否则下条命令复用旧序号被 GD32 判重吞掉,业务永远读到旧 0x82) |
| 失配帧处理 | CRC+session 合法的 RESPONSE 一律交调用方(帧已被读走、GD32 已清 pending,丢弃会死等),由 m_lastRxAckMatched 标记归属,业务以结构化 payload 终判 |
| session 失配 | EVENT 帧放行;session=0 的响应放行(GD32 重启语义)并置重同步;其余拒收且不更新 credit |
| 撕裂诊断 | ring_steal_diag:偷读计数/最后类型/ack 归属/payload 签名 [rxLen, rb0, rb1](0xE8 撕裂时 rb0=A5 rb1=5A 为期望值,255=未找到 SOF);0x82 归因 rsp_err_last_req/p0..p5,全部进 /system/status |
GD32 固件转发采用预共享主密钥 + 挑战-应答派生会话密钥,会话内数据 AES-128-ECB 加密。实现于 OtaRelay::doChallengeAndAuth() 与 Protocol(nonce 搅拌生成 / 会话密钥管理)。
SPI_LINK_V2_SELFTEST)交叉锁定帧字节。单例 + FreeRTOS 互斥(100ms 锁超时),是 WiFi/BLE 传输层到 OTA 执行体的唯一权威入口;同时承担忙态看门狗自愈、epoch 会话保护、BLE 射频共存通知、CAN 反馈暂停与电机 OTA 互斥。
独立任务(栈 2560B)+ dispatch/result 队列(深度 4)。收到调度信号先 setSuppressRingService(true)(防 SPI worker 把 OTA 响应当 EVENT 偷读——线上事故根因),全程 RAII 持有抑制引用。
| 步骤 | 实现 | 要点 |
|---|---|---|
| begin | 选非当前启动分区(优先 ota_0)· esp_ota_begin(OTA_WITH_SEQUENTIAL_WRITES) 边写边擦 | 整区预擦 1.25MB 会堵死 HTTP TCP 窗口(进度卡开头);begin 同时 setSpiPaused(true)——写 Flash 时 cache 反复失效会拖慢 SPI worker 甚至 Cache error;使能回滚时先 invalidate_inactive_ota_data_slot() |
| write | esp_ota_write 循环 + 喂狗 | 不做增量 SHA;httpd 侧 4KB RAII 接收缓冲(会话内 malloc),堆紧时 pacing(<8KB 减速) |
| finalize | 分区读回算 SHA-256(1024B 静态缓冲,每 16 块喂狗)→ 与前端 SHA 大小写不敏感比对 → esp_ota_end → set_boot_partition | 不匹配返回 ESP_ERR_INVALID_VERSION 并回传设备 SHA 供前端核对;m_ended 标志防 end 后误 abort |
| 回滚保护 | 任务启动完成后 esp_ota_mark_app_valid_cancel_rollback() | 新固件标记前崩溃 → bootloader 自动回退上一有效分区 |
| 能力 | 实现细节 |
|---|---|
| 按需擦除(水位) | beginWriteSession(false) 复位水位;ensureErasedForWrite 仅从水位向高地址顺序擦 4KB 扇区(跳空一并擦),禁止重擦已写扇区;每扇区擦后喂狗+yield 让 WiFi/LWIP 有机会运行 |
| 整区强制擦除 | 电机 OTA 上传 beginWriteSession(true) 全擦 252KB——NOR「只能 1→0」与残留固件叠加会造成静默单 bit 翻转(实测 0x19→0x18 根因) |
| NVS 元数据 | 命名空间 ota_meta:total_size / stored_size / last_seq / checksum(blob 32B);读-改-写全程持锁防丢失更新;仅 ACK checkpoint 持久化(NVS commit 毫秒级,不能每包一次) |
| SHA-256 | 收流收尾统一 metaFinalizeChecksum;relay 门控:checksum 非全零则信任跳过二次全盘 SHA;全零则兜底 metaVerifyChecksum(全零不容忍,防 meta 被清零掩盖损坏) |
| 对齐缓冲 | OTA 会话 begin 时 calloc(4KB+8),end/abort 释放——消除每非对齐块 malloc/free 堆抖动,非 OTA 期零占用 |
validateStoredImageIdentity() 读分区头 8B Cortex-M 向量表:初始 MSP 必须落在 GD32 SRAM(0x20000000~0x2001FFFF);复位向量(清 Thumb 位)落在 APP 区 → kApp 放行;落在 BOOT 区(0x08000000~0x08011FFF)→ kBootloader 拒绝;判据与 GD32 BOOT 侧 validateApplication() 完全一致(两侧需同步修改)。/ota/relay 与 BLE 0xF7 都强制 kApp 才放行(BLE 错误码 0x87 ERR_FW_IDENTITY)。| UUID | 特征 | 属性 | 用途 / 格式 |
|---|---|---|---|
| 0xFFFF | OTA Service | Primary | 服务主入口(设备名 ESP32-OTA-RELAY · MTU 512 · 不做 bonding) |
| 0x0001 | CTRL | Write | 命令字:0xF7 触发 GD32 中继(≥35B:SHA32+session2)· 0xF8 ESP32 收尾(≥33B:SHA32)· 0xFA 重启 · 0xFB 中止(RELAYING/FINALIZING 拒绝)· 0xF9 SHA 预检仅定义未实现 |
| 0x0002 | DATA_IN | Write + WriteNoRsp | 固件包:cmd 1B(0x00 GD32/0x01 ESP32)+ seq u16 LE + total_size u32 LE(仅 seq=0 有效)+ payload ≤480B;无 CRC(BLE 链路层已有) |
| 0x0003 | DATA_OUT | Notify | 0xA1 窗口 ACK 8B(cmd+seq+completed_bytes)· 0xB1 FINISH 6B(stage+value)· 0xC1 SHA-256 35B |
| 0x0004 | VERSION | Read | 固件版本 + 构建时间字符串 |
FINISH stage:0x02 DONE · 0x07 REBOOT_NOW · 0x10 RELAY_START · 0x11 RELAY_DONE · 0x20 ABORTED;错误码 0x80 BUSY / 0x81 ERR_FINALIZE / 0x83 ERR_MUTEX / 0x84 ERR_GD32_SIZE / 0x85 ERR_SHA_MISMATCH / 0x86 ERR_GD32_RELAY / 0x87 ERR_FW_IDENTITY。连接参数:高速分支 7.5~10ms 间隔 + 2M PHY 请求,Windows 兼容回退 10~12.5ms。
| 方向 | 机制 |
|---|---|
| WiFi OTA 开始 → 挂起 BLE | 软挂起 suspendStack:停广播 → 断连接(等 30ms)→ CTRL/DATA_IN 拒写(ATT 错误)→ 清数据队列;不 deinit 协议栈(硬 deinit 会打挂共存导致上传失联);挂起期 CONNECT/ADV_COMPLETE 被拒绝/抑制 |
| OTA 中继结束/中止 → 恢复 BLE | resumeStack:重新开广播;仅曾 hard-deinit 才完整 serverStart。/ota/relay 失败路径也会 resume |
| BLE OTA 期间 → WiFi 让步 | 保持 WIFI_PS_NONE,仅暂停 RSSI 轮询(射频活动干扰 BLE 连接事件);标志防重入;断连按系统状态清理会话(GD32_RELAYING/REBOOTING 保留等自然结束,WiFi 通道会话不误杀) |
httpd 端口 80 · httpd 栈 7168B · max_uri_handlers 48(22 条 handler 路由 / 21 个唯一路径,另为每个 path 注册 OPTIONS CORS)· max_open_sockets 6(约束 ≤ LWIP 12 − 内部 3)· LRU 踢除 · recv/send 5s 超时 + keep-alive 探针。无任何认证(见 §19);CORS 全放开(前端本地托管跨源调用)。错误响应统一 {"status":"error","code":"snake_case","message":...}。
| 方法 | 路径 | 功能 |
|---|---|---|
| POST | /ota/upload | 固件上传(JSON 头 {"type":"esp32"|"gd32","total_size":N} + 二进制流式);GD32 路径流式写 gd32_ota + NVS meta + 收尾算 SHA;chunked 拒绝 |
| POST | /ota/update | ESP32 OTA 收尾 {"sha256","verify_only"};SHA 不匹配回 error 并带 device SHA;成功后 100ms 重启 |
| POST | /ota/relay | 触发 GD32 中继:校验前端 SHA-256 + 身份 kApp → 投递 dispatch;失败恢复 BLE |
| POST | /restart | 重启 ESP32 |
| GET | /logs | 日志环 JSON chunked 流式(512B 栈缓冲攒块,替代 4KB malloc;堆 <4KB 返回 []) |
| GET | /system/status | 全量状态(?lite=1 轻量、?layout=1 加内存布局);堆极紧自动降级简包 |
| GET | /system/probe | 单次 GD32 APP 存活探测(HELLO/PING),返回版本/uptime |
| GET | /system/heap_low_log | 最近一条「堆创新低」快照(alloc hook 最后一击归因) |
| POST | /can/send_batch | CAN 发送唯一入口,1~128 帧,512B 滚动缓冲流式解析;单帧 1200ms / 多帧 80ms/帧;SPI 失败或 0xE2/0xE3 致命中止并报 skipped |
| POST | /can/send_burst | 0xC7 跨通道突发(≤24 帧,按容量分块背靠背;id=(node<<16)|suffix) |
| GET | /can/feedback | 0xC1 按需查询三路统计+ID 快照(?lite=1 轻量) |
| GET/POST | /can/mode | 查询/切换接收模式 0 全收 / 1 过滤 / 2 CAN0→1/2 桥接 |
| POST | /can/silent | 0xC8 通道收发器 Silent 只听不发(OTA/send_batch 独占 SPI 期间拒绝) |
| POST | /spi/send | 原始 SPI 帧透传 {"type","data","timeout_ms"} |
| GET | /motor/state | 电机状态缓存(0xC5;?bin=1 二进制协议 12B 头 + 92B/节点,省 66% 体积) |
| GET | /motor/version | 电机版本缓存(0xC6 事件推送) |
| POST | /motor/ota | 电机固件上传(?ch=N;整区强制擦除;SHA 随响应返回) |
| POST | /motor/ota/relay | 触发电机 OTA(SPI 0xD0 START + feedTask) |
| GET | /motor/ota/status | 电机 OTA 状态机进度(0xD2 缓存) |
| POST | /motor/ota/cancel | 取消(异步 cancel task,先 resetCredit 再 CANCEL) |
| GET | /spi/speedtest | SPI 性能测试(0xA1 PING 探针,RTT 直方图/5 段细分;临时功能标记可删) |
ESP32 不直接接触 CAN 总线:HTTP/BLE 请求翻译为 SPI 命令,GD32 驱动三路 TCAN1057AV 收发器。帧约定 [通道 1B][扩展 ID 4B 大端][数据 0~64B]。
| TYPE | 名称 | 载荷 / 行为 |
|---|---|---|
| 0xC0 | CAN 单帧发送 | req = ch + id(4B BE) + data(最短 5B,DLC=0);GD32 回 ACK 或错误码 E0~E7(payload[0]) |
| 0xC1 | 统计 + ID 快照页 | req=[ch,page];resp = 28B 固定头(含 mode/snapshot_seq/rx/tx/rx_drop/error/tec/rec/bus_state)+ 条目 (8+dlc)=id 4LE+dlc+flags(FD/BRS/ESI)+rsv+data;快照上限 16 条,page0=条目 0~7、page1=8~15;GD32 亦可 1Hz 主动推送(现默认请求驱动,§12.2) |
| 0xC2 | 设置接收模式 | 0=全收 · 1=按 ID/掩码过滤 · 2=CAN0→CAN1/CAN2 桥接(每帧按原格式自动转发);回实际生效模式 |
| 0xC3 | ID 快照完整版 | 固定头 4B + 条目 (16+dlc),含 seq/rx_count;响应可长,不进短响应读预算白名单 |
| 0xC7 | 跨通道突发发送 | 多电机同步:4B 头 [count][dlc][suffix_hi][suffix_lo] + count×(chnode(1B=(ch<<4)|node)+data);单次 ≤16 帧(容量 = min(16,(236−4)/(1+dlc))) |
| 0xC8 | 收发器 Silent | req=[ch, silent 0/1];回 [ch 回显][actual][ch_en];错误 E0 过短/E1 通道非法/E2 S 脚回读不符(标志已回滚) |
| 码 | 含义 | ESP32/HTTP 处理 |
|---|---|---|
| E0 | payload 过短(<5B) | 参数错误 |
| E1 | 通道非法 | 参数错误 |
| E2 | BUS_OFF | fatal:批量/突发中止剩余帧 |
| E3 | TEC>96(对端 ACK 失败率高) | fatal:同上 |
| E4 | 邮箱满 | 按帧透传,前端冷却重试 |
| E5 | 参数无效 / 电机 OTA 会话残留(0xD0 语境) | motorOtaStart 收到 E5 自动 CANCEL→resetCredit→重试 START 自愈 |
| E6 | 通道处于 Silent(入口即拒) | 不计错误计数 |
| E7 | 通道被电机 OTA 独占(跨总线排他) | OTA 结束自动恢复,不计错误计数 |
POST /can/silent {"ch":0,"on":1}电机节点固件(≤48KB)经 GD32 的 CAN 总线二次烧写。两段式:前端先上传到 gd32_ota 分区(与 GD32 固件共用,上传前 abortCurrent + 清 meta + 整区强制擦除),再触发 relay 由 feedTask 经 SPI 喂给 GD32。
| 数据源 | SPI 命令 | 机制 | 内容 |
|---|---|---|---|
| 电机状态 | 0xC5 | ESP32 每 250ms 逐页查询 ×14 页(page0 头部 active_count&0x7F 在线数、bit7 boot_active) | 9 节点 × 3 通道 = 27 槽(node×ch 双键);每条目 92B:STATUS data[48](字节 32~35=InitStatus)+ CONFIG data[24] + rx/seq 计数 |
| GD32 ADC | 0xC4 | ESP32 每 1s 查询 | 20B:temp×100 / vdda_mV / vin_mV / raw 等;VCC = vin_adc × (47k+4.7k)/4.7k = ×11 分压反推;5s 内算 fresh |
| 电机版本 | 0xC6 | GD32 事件推送(收到电机 0x00FF 应答后按铃)→ can_fb 任务 takeEvent 消费 | 29B/条目 ×9 节点 |
| 电机 OTA 进度 | 0xD2 | GD32 ring push 事件 → 同上消费 | 12B 进度帧 |
| GD32 初始化状态 | 0xA3 | 身份块 [15] 惰性查询,10s 失败退避 | 13 模块各 1B 故障码 + module_mask/critical_fail_mask |
pauseCanFeedbackTask 暂停,电机 OTA 期间也自动暂停避让 0xD1。0xC1 CAN 反馈则完全由 HTTP 请求时按需查询(APP 查询 0 时保持静默)。| 模块 | 功能 |
|---|---|
| cpu_stats | 1Hz FreeRTOS runtime stats:总占用率 + 每任务 delta 占用(≤20 任务);httpd 只读静态快照,不在 httpd 线程遍历任务表 |
| task_stack | 10 项任务栈水位表(创建大小/high watermark/峰值);栈溢出 hook 硬写 UART「STACK OVERFLOW」后 abort(OTA 末尾溢出若不 abort 前端会停在 100%) |
| gd32_adc_feedback | 0xC4 快照缓存(单快照覆盖写 + recv_ms 年龄);互斥懒创建,取锁 20ms 超时不阻塞查询节奏 |
| motor_state / motor_version | 同上缓存模型;motor_state 整表拷贝在锁内一次完成,读方永远拿到一致快照(防撕裂) |
| spi_speed_test | GET /spi/speedtest:0xA1 PING 探针走真实 worker 路径,RTT 直方图 7 档 + 单帧 5 段细分(提交/发送/等应答/读响应/唤醒)+ 等效吞吐;文件头注明删除步骤 |
| build_info | 构建系统自动生成编译时间(fwBuildTime()),旧手写 BUILD_TIME 曾被误判为 OTA 回退 |
| 层级 | 机制 | 参数 | 针对的问题(事故沉淀) |
|---|---|---|---|
| SPI 链路 | 透明重试 | kTxRetryMax=3,同 TX_SEQ,间隔 3ms | 瞬时无响应;DUP-SEQ 去重保安全 |
| 链路熔断 | 3 连败 → 离线,2s 冷却放一笔探针 | SPI 不通时周期查询死循环阻塞/耗尽 | |
| HELLO 重同步 | session=0 触发 + 5s 冷却 | GD32 重启后全命令超时死循环 | |
| credit 自愈 | PB6 高强制置 1;resetCredit() | credit=0 锁存死锁(§11) | |
| 撕裂防护 | CS 间隔 ≥1ms · 快速类型禁盲读 · 读预算收紧 | RX DMA mid-pulse 重装 → 0x82 0xE8 → 偷读风暴 | |
| OTA 协调 | 忙态看门狗 | 150s(>中继预算 120s) | 异常退出导致系统永久「忙」 |
| epoch 会话保护 | begin 递增,清理校验 epoch | 旧上传失败清理误杀新会话 | |
| 半会话清理 | abort 必发 SPI CANCEL 0x03 | GD32 停在 WAITING_* 半会话 | |
| 数据完整性 | SHA-256 双端比对 | 前端算 / 设备读回算 / meta 存 | 传输错误静默落盘 |
| 包身份校验 | 向量表区间判定 kApp | BOOT 包刷进 APP 区(§7.6) | |
| NOR 全擦 | 电机 OTA 整区预擦 | 残留位叠加单 bit 翻转 | |
| 资源保护 | 池化/会话内分配 | BLE 池 4KB · 上传 4KB RAII · 对齐缓冲 OTA 期 calloc | BLE 收尾 0xF8 OOM(曾 32KB 池) |
| 栈水位收敛 | S ≈ 峰值 + 1.5~2KB | OTA 末尾 httpd 栈溢出假死 |
| 类别 | 常量 = 值 |
|---|---|
| SPI | CONFIG_DCU_SPI_CLOCK_HZ=13333333 · Mode 0 · 队列 4 · SPI_OTA_FRAME_MAX=256 · BOOT/APP TX 缓冲 128/256 · V2_MAX_PAYLOAD=236 · 读预算 96(短响应)· credit 8/8 · kTxRetryMax=3 · 熔断 3 败/2s · CS 间隔 1ms · poll backoff 1ms |
| GD32 OTA | OTA_MAX_FIRMWARE_SIZE=216KB · 包载荷 48B(帧 64B)· OTA_BLOCK_SIZE=4096(RAII)· 每 32 包健康检查 · 预算 10s+25ms/包 ≤120s · 卡死 10s · 协调器看门狗 150s |
| 电机 OTA | MOTOR_OTA_MAX_FW_SIZE=48KB(0xC000) · 帧 ≤236B(4B offset + ≤232B data)· feed 120s 整体超时 · 8 败/150 WAIT → QUERY · 退出轮询 8s/150ms |
| BLE | MTU 512 · 载荷 480B · ACK 窗口 64 · reorder 64 槽 · DATA_IN 池 8×512B · 队列 144 · 高速连接 7.5~10ms(回退 10~12.5ms)· 中继等待 120s · 负 ACK 节流 64 包/200ms |
| WiFi/HTTP | STA 超时 30s · 重试 5(冷启动退避 2s×retry≤3,否则 1s)· AP 192.168.31.1/6/4 台 · 端口 80 · httpd 栈 7168 · sockets 6/LWIP 12 · TCP_WND 8192 / SND 5760 · RSSI 采样 10s |
| 健康 | 温度 80/100°C · RSSI -80/-85/-88dBm(15s 时效)· 堆 10KB · 栈告警 20% · 巡检 5s · 温度采样 1s |
| 日志 | UART0 3Mbps(TX21/RX20) · UDP 192.168.31.3:6666 · 环 64→16 槽 ×128B · 门控 6KB/10KB/4KB · CONFIG_SPI_DEBUG_LOG_LEVEL=2(编译期裁 SPI DBG/INFO) |
| 任务栈 | http_server 7168 / wifi_ota 3072 / ota_relay 2560 / spi_worker 5120 / ble_ota 2560 / ble_worker 2560(p12) / health_mon 3072 / cpu_stats 3072 / wifi_reconnect 2560 / can_fb 4096 / motor_feed 4096 / ota_cancel 3072 / uart_rx 3072 |
| 欠压/Flash | Brownout LVL7(~2.8V) · Flash DIO(禁 QIO)· FreeRTOS runtime stats 开启 |
ESP32_COMM/
├── CMakeLists.txt # project(ESP32_COMM)
├── sdkconfig(.defaults) # SPI 时钟/欠压 LVL7/LWIP/NimBLE/FreeRTOS stats 等
├── config/partitions.csv # 4MB 分区表(§2.2)
├── main/
│ ├── include/ # app_config.h(统一常量)/ version.h(0.5.2)/ build_info.h
│ ├── core/ # main.cpp 启动编排 · system_boot.cpp 16 位自检 · build_info
│ ├── server/ # wifi_ota(STA/AP+路由表)· http_handlers(22 端点)· ble_ota*(GATT/窗口/reorder/池)
│ ├── ota/ # ota_coordinator(9 态单例)· esp32_ota_core(本机 OTA)
│ ├── relay/ # ota_relay(7 态事件状态机 + 安全握手 + 逐包转发)
│ ├── storage/ # gd32_flash_store(分区读写/擦除水位/NVS meta/SHA/身份校验)
│ ├── transport/ # spi_master(Link v2 链路+worker)· protocol(AES/nonce)· canfd_bridge(0xC0~C8 桥)
│ └── monitor/ # health_monitor · debug_log(3 路+RX泵)· can_feedback_task(0xC4/C5 周期查询)
│ # motor_ota_relay(电机 OTA feed)· motor_state/motor_version/gd32_adc_feedback
│ # cpu_stats · task_stack · spi_speed_test · spi_ring_cache.h
├── components/transport/ # 组件封装(CMakeLists)
├── docs/ # can_protocol.md · frontend_architecture.md · 架构评审 · 本文件
├── spi_link/(兄弟目录) # 三工程共享 SPI Link v2 协议真源 + golden-vector 自测(../../../spi_link/)
└── tools/(已移出仓库) # 网页控制台;快照在 .cache/ws_era_frontend_backup/(§17)
# 设置工具链环境(IDF_TOOLS_PATH / IDF_PATH / PATH,见仓库历史 README 或 .devcontainer)
Set-Location "G:\Robot_v2\DCU\ESP32_COMM"
python "F:\.espressif\v6.0\esp-idf\tools\idf.py" build
# 产物:build/ESP32_COMM.bin + bootloader + partition-table(零 error/warning 基线)
版本发布流程:手动递增 main/include/version.h 的 MAJOR/MINOR/PATCH 与 FW_VERSION_STRING(并把变更摘要写进该文件注释),GIT_HASH 填 git rev-parse --short HEAD;编译时间自动生成。
| 路径 | 流程 |
|---|---|
| 串口(首次/恢复) | idf.py -p COM15 flash monitor;固件反复重启等严重故障时唯一恢复手段 |
| WiFi OTA(ESP32 自升级) | 网页控制台选 build/ESP32_COMM.bin → 前端算 SHA → /ota/upload → /ota/update(SHA 比对→set_boot→重启)→ 前端按 banner「启动」正则判定恢复(uptime<120s 判重启完成) |
| BLE OTA | Web Bluetooth(Chrome/Edge):DATA_IN 480B 包 ×64 窗口 → 0xF8+SHA 收尾重启;0xFB 可中止 |
| GD32 中继 | 上传 .bin/.hex 到 gd32_ota → /ota/relay(或 BLE 0xF7)→ 六阶段转发 → PING 确认 APP 存活;可重复触发(STORED/COMPLETE 均可) |
| 电机节点 | /motor/ota?ch=N 上传 → /motor/ota/relay → 0xD0/0xD1 经 GD32 CAN 烧写 → /motor/ota/status 轮询 |
托管方式:Web 资源不嵌入固件,由 tools/serve.bat(python -m http.server 8080)本地托管,浏览器跨源调用 ESP32 REST API——改网页无需重编固件。零构建原生 ES Modules(ES module 有 file:// CORS 限制,必须经 http 访问)。tools/ 目录已移出本仓库(历史快照在 .cache/ws_era_frontend_backup/),约定文档见 docs/frontend_architecture.md。
| 页面 | 功能 |
|---|---|
| index.html(OTA 控制台) | WiFi/BLE 双通道切换 · GD32/ESP32 目标选择(GD32 支持 .hex 自动转换)· 上传/SHA/阶段流程条驱动 · 设备状态(含启动自检面板 = SystemBoot 位图)· 运行指标(ESP32 温度/GD32 温度 0xC4/输入 VCC 分压反推/GD32 APP·BOOT 版本)· 日志面板(/logs 轮询 + 级别过滤)· 「堆与任务栈」抽屉(CPU 条形/水位/任务表)· CAN/电机页入口 |
| motor.html(电机控制台) | 独立 seed 主题;节点总览 1Hz · CMD 控制/周期重发 · CONFIG/PID · 版本查询 · NodeID 设置 · 电机 OTA relay(进度 0xD2)· 波形显示;三条轮询链路互不阻塞 |
| can.html / can_monitor.html | DBC 导入解析 · 报文/信号编辑 · 单发/批量发送 · 发送历史与会话持久化 · 接收监控(独立页/抽屉,0xC1 快照分页) |
关键约定:JS import 不带 ?v=(版本戳只放 HTML 标签);统一 data-action 事件委托禁内联 onclick;js/ble/*(Web Bluetooth 时序)与 js/ota/*(OTA 状态机/window.* 总线)为红线区;重启完成判定 = banner 正则 + uptime<120s。
| 项 | 状态 | 说明 |
|---|---|---|
| BLE CTRL 0xF9(SHA 预检) | 未实现 | 仅 app_config.h 定义常量,CTRL 回调无处理分支;WiFi 侧 /ota/update 有 verify_only 等价功能 |
| 0xC3 ID 快照完整版(ESP32 侧) | 协议就绪/桥未主动调用 | canfd_bridge.h 有常量与条目布局定义;GD32 侧已实现;ESP32 HTTP 无独立端点 |
| 0xC1 GD32 1Hz 主动推送 | 保留 | 协议支持但现架构默认请求驱动(§12.2) |
| /spi/speedtest | 临时功能 | 性能剖析用,代码标注可整行删除(3 处挂接点) |
| HTTP 认证 / 凭据治理 | 未实现 | 见 §19 |
| mDNS | 不存在 | 历史 README 讹误;仅 lwIP 编译项支持 mDNS 解析 |
其他工程性限制:SPI 13.33MHz 为实测上限(≥16MHz 全双工不可行;12M 与 13.33M 重发率相当,时钟非重发主因);Flash 必须 DIO(QIO 读回 SHA 必错);CANFD 数据字段 64B 不可压缩(用户明确使用长帧);GD32 包修复依赖三工程协议同步(spi_link/ 兄弟目录 + 两侧判据成对修改)。
| 版本 | 要点 |
|---|---|
| 0.3.81~0.3.85 | GD32 OTA 包级防呆(向量表身份)· UART0 RX 泵(/logs 汇聚外部串口)· GD32 APP 初始化状态可视化(0xA3)· 模块级故障码 hex · CAN 反馈请求驱动改造(0xC5/0xC4 改周期查询) |
| 0.4.2 | serviceRing 偷读迟到 RESPONSE 的 seq 失配修复(「一直读到 0x82」根因) |
| 0.4.3~0.4.8 | 0x82 量化归因插桩 · 快速类型禁盲读 · CS 事务间隔 1ms · ring_steal_diag 撕裂形态签名 |
| 0.4.9 | tx_total/tx_retry/tx_fail 计数进 /system/status(透明重试不再静默) |
| 0.5.0 | SPI 链路稳健性 + OTA 内存(上传 4KB RAII)+ 状态诊断 |
| 0.5.1 | SPI 调试日志编译期门 CONFIG_SPI_DEBUG_LOG_LEVEL(84 条 DBG/INFO 裁剪) |
| 0.5.2 | SPI 0xC8 通道收发器 Silent + POST /can/silent · 0xC1 bus_state 新增 bit5=tx_silent · 静默通道发送回 0xE6 |
本文件(docs/project_introduction.html)是项目的完整介绍白皮书:算法细节、协议字节级定义、状态机与事故沉淀。根目录 README.md 是它的浓缩版(快速上手 + 速查表),两者内容同源。
main/include/version.h 注释,文档头部版本行随之更新;
③ HTML 偏重原理与完整参数,README 偏重速查与上手,细节以 HTML 为准;
④ 两文件顶部均带「文档同步版本」(当前 0.5.2 / 2026-10-11),修改后同步递增日期。