文件
zhou yang和Claude Opus 5 2bfe308b30 初始导入: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
2026-09-06 18:18:14 +08:00

14 KiB

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 行,超了就是在写日记)

# 交接
更新:<日期> 完成任务包 <编号>

## 一句话现状
<现在能跑到哪一步>

## 下一步
<下一个任务包编号 + 一句话>

## 本次新增/修改的文件
- 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 是硬前置;A3A6 四个驱动包彼此独立(可换顺序); A7 依赖 A3A6;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 具体版本 本计划按 v5.x 写;A1 落地时钉死一个小版本并写进 HANDOFF.md(IDF 小版本之间 CMake 与 menuconfig 有差异,换版本 = 一次白花的会话)
MQTT broker 与 topic 约定 依赖农场既有系统,A14 之前需要对方给出
单元测试框架 建议 Unity(IDF 自带)+ 宿主机 CMake/CTest;A1 定
input_suspend 节点路径 与需求 V-8 是同一件事,首台机上确认