初始导入:PhoneRail 五路手机直供电源板
原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。 这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。 纳入: - 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本 - .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、 pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源) - 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾) - firmware/ 的 A1 骨架与 site/ 文档站源码 .gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、 site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。 .gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存, 不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
这个提交包含在:
@@ -0,0 +1,266 @@
|
||||
# PhoneRail 固件开发计划
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 版本 | v1.0(2026-09-06) |
|
||||
| 范围 | `需求文档.md` 的 **D5 / D5a** —— ESP32-S3 固件与双链路命令接口 |
|
||||
| 架构依据 | `需求文档.md` **§10 软件架构设计**(本计划不重复架构,只排施工顺序) |
|
||||
| 目标平台 | ESP32-S3-WROOM-1 / ESP-IDF v5.x / C |
|
||||
|
||||
> **这份计划是给「一次会话 ≤ 20% 上下文」这个约束量身排的。**
|
||||
> 它不是甘特图,是一串**自带入场清单与验收判据的任务包**:
|
||||
> 每包一次会话做完,做完时代码是绿的、交接是写好的,下一次会话不需要回头翻上一次的过程。
|
||||
|
||||
---
|
||||
|
||||
## 1. 决定这份计划形状的三个约束
|
||||
|
||||
| # | 约束 | 对计划的影响 |
|
||||
|---|---|---|
|
||||
| **C-a** | **硬件还没有。** PCB 仍在布线(`PROGRESS.md`:下板还欠 1044 条连接性) | 绝大部分固件必须能在**没有板子**的情况下写完并验完 ⇒ 先做模拟器,见 §4 |
|
||||
| **C-b** | **一次会话 ≤ 20 % 上下文**(按 200K 窗口 ≈ **40K token**) | 每个任务包必须**自带入场读取清单并给出上限**;源文件行数要控制;禁止整篇读大文档,见 §2 |
|
||||
| **C-c** | **保护不在软件里**(需求 SR-3 / SW-N1) | 固件写错的后果是「功能不好用」,不是「烧手机」⇒ 可以先把逻辑全部写完再上板调,不必等硬件边写边试 |
|
||||
|
||||
C-c 是最容易被忽略但最解放人的一条:**这块板子的固件不是安全关键件**,
|
||||
所以「离线写完 90 %、上板只调 10 %」这条路线是成立的。
|
||||
|
||||
---
|
||||
|
||||
## 2. 上下文预算 —— 每次会话怎么花那 40K
|
||||
|
||||
### 2.1 实测的「读一份文档要花多少」
|
||||
|
||||
| 文件 | 大小 | ≈ token | 占 20 % 预算 |
|
||||
|---|---|---|---|
|
||||
| `CLAUDE.md` | 3 KB | ~1K | 2.5 % |
|
||||
| `需求文档.md` **全文** | 47 KB | ~15K | **38 %** |
|
||||
| └ 单看 §10 软件架构设计 | 19 KB | ~6K | 15 % |
|
||||
| └ 单看 §9 固件需求 | 2 KB | ~0.7K | 2 % |
|
||||
| `PROGRESS.md` **全文** | 195 KB | ~60K | **150 % ⛔** |
|
||||
| `五路直供电源板设计.md` **全文** | 438 KB | ~140K | **350 % ⛔** |
|
||||
|
||||
> 换算口径:UTF-8 中文约 3 字节/字,中文约 1 token/字,所以 **字节 ÷ 3 ≈ token**。
|
||||
> 英文代码更省,约 4 字节/token。
|
||||
|
||||
### 2.2 一次会话的预算分配
|
||||
|
||||
| 段 | 预算 | 内容 |
|
||||
|---|---|---|
|
||||
| 入场 | **≤ 8 %**(16K) | `CLAUDE.md` + 本计划里**该任务包那一节** + 上一次交接 + 要改的 1~3 个源文件 |
|
||||
| 干活 | **≤ 8 %**(16K) | 写代码、跑构建与测试、读报错 |
|
||||
| 收尾 | **≤ 4 %**(8K) | 更新交接、提交、把新踩到的坑写回文档 |
|
||||
|
||||
### 2.3 硬规矩(违反其中任何一条,当次会话必然超预算)
|
||||
|
||||
1. **禁止整篇读 `五路直供电源板设计.md` 与 `PROGRESS.md`。** 需要什么就 `grep -n` 那个条目号
|
||||
再 `sed -n 'a,bp'` 取那一段。这两份文件单独任何一个都装不进一次会话。
|
||||
2. **禁止整篇读 `需求文档.md`。** 按需求 ID 取:`grep -n "SR-13\|SW-8" 需求文档.md`。
|
||||
要看架构就只读 §10 里对应的小节。
|
||||
3. **单个源文件控制在 ≈300 行以内。** 读一个 800 行的 C 文件≈8K token = 预算的 20 %。
|
||||
超了就拆文件 —— 这条是**为了让后续会话读得起**,不是代码洁癖。
|
||||
4. **构建与测试输出一律 `2>&1 | tail -40`。** ESP-IDF 全量构建日志有几千行。
|
||||
5. **长任务后台跑**(`run_in_background`)**只看摘要**,不要贴全量。
|
||||
6. **一次会话只碰一个任务包。** 顺手做掉相邻的一小块,通常就是下一次会话被迫回头重读的原因。
|
||||
7. **固件会话与 EasyEDA 会话分开。** `easyeda-sch-geometry` skill + `eda-parts.json` +
|
||||
截图验证那一套,本身就要吃掉一大半预算,和固件混在一起必然两头都做不完。
|
||||
|
||||
---
|
||||
|
||||
## 3. 会话协议
|
||||
|
||||
### 3.1 每次会话的固定动作
|
||||
|
||||
```
|
||||
开场:读 CLAUDE.md
|
||||
读 固件开发计划.md 里【本次任务包】那一节(只读那一节)
|
||||
读 firmware/HANDOFF.md(上一次的交接,控制在 60 行以内)
|
||||
按任务包的「入场读取」清单读文件,读完就动手
|
||||
|
||||
收尾:跑一次完整验收命令,贴结果
|
||||
更新 firmware/HANDOFF.md(覆盖,不追加 —— 它是「现状」不是「日志」)
|
||||
把新发现的坑写回 需求文档.md §10 或本计划,一两句话即可
|
||||
```
|
||||
|
||||
### 3.2 `firmware/HANDOFF.md` 模板(**≤ 60 行**,超了就是在写日记)
|
||||
|
||||
```markdown
|
||||
# 交接
|
||||
更新:<日期> 完成任务包 <编号>
|
||||
|
||||
## 一句话现状
|
||||
<现在能跑到哪一步>
|
||||
|
||||
## 下一步
|
||||
<下一个任务包编号 + 一句话>
|
||||
|
||||
## 本次新增/修改的文件
|
||||
- path — 干什么的
|
||||
|
||||
## 验收命令与结果
|
||||
$ <命令>
|
||||
<最后几行输出>
|
||||
|
||||
## 踩到的坑(只写会再犯的)
|
||||
- ...
|
||||
```
|
||||
|
||||
> **为什么覆盖而不是追加**:`PROGRESS.md` 现在 195 KB、读一次就是 1.5 倍会话预算,
|
||||
> 就是追加写出来的。交接文件的唯一读者是「下一次会话的开头 30 秒」,它只需要现状。
|
||||
> 历史留在 git log 里。
|
||||
|
||||
### 3.3 任务包的完成定义(DoD)
|
||||
|
||||
一个任务包算做完,必须同时满足:
|
||||
|
||||
- [ ] 宿主机测试全绿(`ctest` 或等价命令),**且新增了针对本包的测试**;
|
||||
- [ ] ESP-IDF 目标也能编过(`idf.py build`,不要求上板);
|
||||
- [ ] `HANDOFF.md` 已更新;
|
||||
- [ ] **没有跨会话的半成品** —— 宁可缩小本包范围,也不要留一个编不过的中间态。
|
||||
|
||||
---
|
||||
|
||||
## 4. 工程结构与两个关键决定
|
||||
|
||||
### 4.1 决定一:宿主机可编译 + 硬件抽象层
|
||||
|
||||
```
|
||||
firmware/
|
||||
├── main/ 应用层 —— 与硬件无关,宿主机上能编能跑
|
||||
│ ├── rail.c/.h 通道抽象、rail_set_mv()(§10.5)
|
||||
│ ├── safety.c SR-5 / SW-8 巡检判据(§10.3)
|
||||
│ ├── seq_*.c 三个执行器状态机(§10.6)
|
||||
│ ├── cmd.c 单一命令处理器(§10.8)
|
||||
│ ├── evlog.c 事件环形缓冲(§10.7)
|
||||
│ └── thermal.c 风扇闭环(§10.3)
|
||||
├── components/
|
||||
│ ├── hal/ i2c / gpio / pwm / time 的**接口**
|
||||
│ │ ├── esp/ 真实实现(ESP-IDF)
|
||||
│ │ └── sim/ 模拟实现(宿主机)
|
||||
│ └── drv/ ina226 / dac63204 / pca9555 / hc595 / lm75
|
||||
├── sim/ 假板子:五路电压模型 + 手机负载 + 可注入故障
|
||||
└── test/ 宿主机单元测试与场景测试
|
||||
```
|
||||
|
||||
**HAL 只抽象四样东西:I²C 事务、GPIO 电平、PWM/定时、单调时钟。**
|
||||
不要抽象得更多 —— 抽象层每多一个概念,后面每次会话都要多读一遍。
|
||||
|
||||
### 4.2 决定二:先写模拟器(A2 + A3 之前)
|
||||
|
||||
没有模拟器,每个任务包的「验收」都只能写成「等板子到了再说」,
|
||||
于是 C-a 会把整个计划堵死到打样之后。
|
||||
|
||||
模拟器要模拟的,只有**能让逻辑跑起来的最小集合**:
|
||||
|
||||
| 模拟 | 到什么程度 | 不模拟什么 |
|
||||
|---|---|---|
|
||||
| I²C 器件 | 寄存器读写 + 可编程返回值 + 可注入 NAK | 真实时序、时钟延展 |
|
||||
| 五路输出 | 一阶模型:`EN` 开 → 按 0.36 V/ms 斜坡到设定值;关 → 按 τ=22 ms 衰减 | 环路动态、纹波 |
|
||||
| 手机负载 | 一个可设定的电流值 + 「卡死」标志 | 真实功耗曲线 |
|
||||
| 故障注入 | 过压、负电流(反灌)、器件失联、PG 不来、风扇停转 | —— |
|
||||
|
||||
> ⚠️ **模拟器不是用来证明设计对的**,它只用来证明**代码逻辑对**。
|
||||
> 电压、余量、时序裕量那些结论全部来自设计文档的计算与首板实测,
|
||||
> 别拿模拟器跑出来的波形去推翻 §5 的任何一个数。
|
||||
|
||||
---
|
||||
|
||||
## 5. Track A —— 无硬件即可完成(**现在就能开始**)
|
||||
|
||||
每包 = 一次会话。「入场读」列的是**除 `CLAUDE.md` 与 `HANDOFF.md` 之外**要读的。
|
||||
|
||||
| 包 | 目标 | 入场读 | 验收 |
|
||||
|---|---|---|---|
|
||||
| **A1** | 工程骨架:CMake 双目标(idf + host)、空 `main`、能跑的 `ctest` | 本节 | `ctest` 通过(1 个假测试);`idf.py build` 通过 |
|
||||
| **A2** | HAL 接口 + `sim` 实现(i2c/gpio/pwm/time) | A1 产物 | 测试里能挂上一个假 I²C 器件并读回预置值 |
|
||||
| **A3** | INA226 驱动 + 六片地址表 | §10.4;设计文档 §8.2 那张地址表(`sed` 取 20 行) | 单测断言 `0x40/41/44/45/42/43`,**并有一条测试专门断言「通道号 ≠ 地址低位」** |
|
||||
| **A4** | DAC63204 驱动 + `rail_set_mv()` | §10.5 全节(约 1.5K token) | 断言 ①4020 mV 被拒(SR-13)②写更大的码值输出更低(反向关系)③3900 mV ↔ Vd 0.548 V |
|
||||
| **A5** | 74HC595 24 位链 + 影子寄存器单一写入点 | §10.6「影子寄存器」那段;设计文档 §17.2 的位表 | 断言 ①改 CH1 按键不动 CH2~5 的位 ②字节顺序 U33→U22→U21 |
|
||||
| **A6** | PCA9555 驱动 | §10.4 末段 | 断言**方向寄存器永远不会把 Port0[7] / Port1[7:5] 配成输出**(配错直接 abort) |
|
||||
| **A7** | 通道配置表 + 表驱动遍历 | §10.4 全节 | 启动自检:五路的地址/位号无重复、无越界 |
|
||||
| **A8** | 任务骨架与调度(5 个任务、两把锁) | §10.3 全节 | sim 时钟下 `t_safety` 周期 20 ms ±1 ms;I²C 占用打印出来与 §10.3 的 8 % 对得上 |
|
||||
| **A9** | 三个执行器状态机 + 互斥 | §10.6 全节 | 场景测试:硬重启走完 17 ms 上电时序;按键序列不重入;EDL 顺序固定 |
|
||||
| **A10** | 安全巡检(SR-5 / SW-8) | §10.2 + 需求 SR-5 / SW-8 两行 | 注入过压 → 20 ms 内关路并记事件;注入 −60 mA → 断 VBUS |
|
||||
| **A11** | 事件日志(环形缓冲 + 落盘) | §10.7 全节 | 写满 4096 条不丢最新;掉电重启后能读回 |
|
||||
| **A12** | 命令处理器(语义 + 序号判重 + 限幅) | §10.8 命令表 + 三条硬约束 | **同序号发两遍只执行一次**;越界命令被拒且记事件 |
|
||||
| **A13** | 传输适配器 B:USB CDC 分帧 | §10.8 第 4 条 | 宿主机上用管道灌「命令 + 日志混流」,命令仍能被正确切分 |
|
||||
| **A14** | 传输适配器 A:WiFi/MQTT 骨架 | §10.8 | 协议层用 sim 传输测通;**真连网留到 B 段** |
|
||||
| **A15** | 启动顺序 + 降级矩阵 | §10.9 + §10.10 | 逐个「拔掉」器件,行为与 §10.10 的表逐行对上;五路上电错开 100 ms |
|
||||
| **A16** | 风扇闭环 + TACH 采样窗口 | §10.3 `t_thermal` + 需求 TR-6 | PWM < 100 % 时不会把「读不到脉冲」误判成停转 |
|
||||
|
||||
**顺序**:A1 → A2 是硬前置;A3~A6 四个驱动包彼此独立(可换顺序);
|
||||
A7 依赖 A3~A6;A8 依赖 A2;A9~A11 依赖 A7+A8;A12 依赖 A9;A13/A14 依赖 A12;
|
||||
A15 依赖全部;A16 独立,随时可插。
|
||||
|
||||
**预计 16 次会话**。其中 A4 / A9 / A12 是三个「容易超预算」的包,
|
||||
如果开场发现读完入场清单已经用掉 12 %,就地拆成两包,别硬撑。
|
||||
|
||||
---
|
||||
|
||||
## 6. Track B —— 需要真板(板子到之前只能写脚本)
|
||||
|
||||
Track B 的验收判据**不是我定的,是 `需求文档.md` §12.1 那 17 步**。
|
||||
这里只做一件事:把那 17 步拆成能一次会话做完的包,并在板子到之前先把脚本写好。
|
||||
|
||||
| 包 | 对应 §12.1 | 说明 |
|
||||
|---|---|---|
|
||||
| **B0** | 全部 | 把 17 步做成一份**逐步 checklist 脚本**(提示 + 记录 + 存档),板子到之前就能写完 |
|
||||
| **B1** | 步 1~2 | 上电、3.3 V、I²C 扫到 10 个器件 |
|
||||
| **B2** | 步 3~4 | 单路 Buck 空载 3.90 V;DAC 扫全量程与公式误差 < 1 % |
|
||||
| **B3** | 步 5 | **人为断开 R2 → eFuse 断开** ⭐ 核心验证项,单独一次会话,不要和别的混 |
|
||||
| **B4** | 步 6 | 0→4 A 阶跃、无 6 kHz 振铃 ⭐ 另一个核心验证项 |
|
||||
| **B5** | 步 7~9 | 满载温升、断电重启时序、五路全装 |
|
||||
| **B6** | 步 12 | 复位安全:手动复位 ESP32,示波器确认全关、15 路按键无一误触发 |
|
||||
| **B7** | 步 13~15 | 母线泄放、USB 反灌(**不关充电**注入)、关充电后复测 |
|
||||
| **B8** | 步 10~11、16 | 接第一台手机、按键与组合键、有线 adb / Fastboot |
|
||||
| **B9** | 步 17 | 五台长稳 24 h + SW-12 的两条链路验收 |
|
||||
|
||||
> ⚠️ **B3 与 B4 不要和任何别的事放在同一次会话。** 它们是这块板子的两个核心验证项
|
||||
> (需求 §12.1 原话「不要跳过」),而且都要接示波器、都要人在场。
|
||||
> 会话里塞了第二件事,出问题时分不清是谁引起的。
|
||||
|
||||
---
|
||||
|
||||
## 7. Track C —— 收尾
|
||||
|
||||
| 包 | 内容 | 触发条件 |
|
||||
|---|---|---|
|
||||
| **C1** | WiFi 配网流程(USB 下发凭据写 NVS,IF-19) | A14 之后 |
|
||||
| **C2** | OTA:双分区 + 失败回滚 | B9 之后(§10.12 说了它是「省事」不是「救命」,别提前) |
|
||||
| **C3** | 出厂标定流程(§10.5 第 3 条,两点线性标定存 NVS) | B2 之后 |
|
||||
| **C4** | 文档回填:把实测值填回 `需求文档.md` §12.2,把踩到的坑写进 §10 | 随时 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 里程碑
|
||||
|
||||
| 里程碑 | 判据 | 依赖硬件 |
|
||||
|---|---|---|
|
||||
| **M1 逻辑跑通** | A1~A16 全绿;模拟器上能完成一次完整的「崩溃 → 软重启 → 硬重启」 | ❌ 不需要 |
|
||||
| **M2 板子活了** | §12.1 的 17 步全过,尤其 B3 / B4 | ✅ |
|
||||
| **M3 交付** | 五台 24 h 无重启无告警;双链路验收通过 | ✅ |
|
||||
|
||||
**M1 完全不依赖打样进度**,可以和 PCB 布线并行 —— 这是本计划最主要的排期收益。
|
||||
|
||||
---
|
||||
|
||||
## 9. 明确不做的事
|
||||
|
||||
| # | 不做 | 理由 |
|
||||
|---|---|---|
|
||||
| 1 | 不为「更好测试」去改硬件设计 | 硬件已定稿(需求 §0.1 的 13 条决策) |
|
||||
| 2 | 不在固件里做任何形式的过流保护环路 | SW-N1 |
|
||||
| 3 | 不做上位机 UI | N7;本项目只交付板侧协议与实现 |
|
||||
| 4 | 不追求模拟器的物理精度 | §4.2 的警告 |
|
||||
| 5 | 首版不做 FR-12 电量曲线 | §10.11:只留接口 |
|
||||
| 6 | 不写「跨会话的大重构」 | 违反 §3.3 的 DoD |
|
||||
|
||||
---
|
||||
|
||||
## 10. 待定项
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| ~~ESP-IDF 具体版本~~ | **A1 已定:v5.4**(`D:/Users/35138/esp/v5.4/esp-idf`,PATH 上的那个)。机器上另有 v5.3.1 / v5.5 / v5.5.1 / v6.0-rc1,别混。构建走 `firmware/build_idf.ps1`(内含关 ccache 的理由) |
|
||||
| MQTT broker 与 topic 约定 | 依赖农场既有系统,A14 之前需要对方给出 |
|
||||
| ~~单元测试框架~~ | **A1 已定:Unity**(从 IDF v5.4 原样复制进 `firmware/third_party/unity/`,与上板测试同一套)+ 宿主机 CMake/CTest,工具链用 cygwin 那套(见 `firmware/HANDOFF.md` 的坑)。测试放 `firmware/test/test_*.c`,被自动收成一条 ctest |
|
||||
| `input_suspend` 节点路径 | 与需求 V-8 是同一件事,首台机上确认 |
|
||||
在新工单中引用
屏蔽一个用户