- 根因:IDF 5.4 的 i2c_master_transmit / receive / transmit_receive 在从机 NACK / SCL 超时时返回 ESP_ERR_INVALID_STATE,HAL 照翻成 HAL_ERR_STATE(= 没 init);命令层 from_hal 又把 STATE 当成 「执行器忙」⇒ PCA9555 不应答时 power off 回 busy(eFuse 其实已关、意图已清),I2C_LOST 错误码是 5 - hal_i2c_esp.c:传输函数用 map_xfer_err(INVALID_STATE → NAK);总线没建起来仍单独报 STATE - cmd_exec.c:新增 from_bus,set_voltage / vbus / power 的器件失败一律 io;from_hal 只留给执行器 - sim:sim_i2c_inject_err_ch() 可指定注入的错误码;test_cmd 加 STATE 注入 ⇒ 必须 io 不是 busy (改回 from_hal 时这段确实红)。26 个 ctest 全绿 - 板 B(台架镜像,CH1 强制在位,未提交):power off 回 io、CH1 关掉;I2C_LOST 错误码 5 → 2。现跑 0.1.1-c3 Co-Authored-By: Claude Opus 5.5 <[email protected]>
8.7 KiB
8.7 KiB
交接
更新:2026-09-27 0.1.1-c3:修了 I²C 不应答被报成 STATE / 关电回 busy(下面「0.1.1」);0.1.0-c3:重启不断电
一句话现状
OTA 换固件 / 超时回滚现在走 warm_restart()(main/warm_esp.c):目标固件认接管 ⇒ 手机不断电;
崩溃 / 看门狗 / 上电 / 兜底回滚仍全关(SR-6)。26 个 ctest、448 个用例全绿。板 B 跑 0.1.1-c3(经 OTA 推上去的)。
定稿写在需求 §10.9「重启不断电」。C2 OTA 的说明在下面。
0.1.1:I²C 不应答被报成 STATE(真板独有,sim 测不出)
- 根因:IDF 5.4 的
i2c_master_transmit/receive/transmit_receive在从机 NACK / SCL 超时时返回ESP_ERR_INVALID_STATE, HAL 照翻成HAL_ERR_STATE(= 没 init)。于是:I2C_LOST 事件的错误码是 5;命令层from_hal又把 STATE 当成「执行器忙」⇒ PCA9555 不应答时power off回busy(eFuse 其实已经关了、意图也清了),上位机会以为没执行。 - 修:
hal_i2c_esp.c传输函数用map_xfer_err(INVALID_STATE → NAK);cmd_exec.c新增from_bus, I²C / GPIO 路径(set_voltage / vbus / power)失败一律io,from_hal只留给执行器。 - 测试:sim 加
sim_i2c_inject_err_ch()(指定注入的错误码);test_cmd加一段 STATE 注入 ⇒ 必须是 io 不是 busy (把关电那行改回 from_hal 时这段确实红,回应就是台架上看到的{"err":"busy"})。 - 上板(台架镜像,CH1 强制在位):
power off回io、CH1 关掉;I2C_LOST 的错误码 5 → 2。
重启不断电(c3)
- 三道闸:RTC 标记(软件复位 + 魔数 / 反码,一次性)· 目标镜像能力标记(第 288 字节
PRCAPS1,老固件不认就不锁)· 起来后读 EN 真电平核对。 rail_en_init_keep(adopt)五根一律放开 hold:崩溃时上一条命的锁可能还在。pca9555_init_keep():方向寄存器是我们写的才留 VBUS;键 / EDL 清零。- 实测:台架镜像(空槽强制 CH1 在位,没提交)开 CH1 → OTA 带重启 → 事件
18/7 mv=1(新固件读到 IO9 仍为高、接管)。 ⚠️ 没测到的:示波器上看 EN 在重启期间有没有毛刺(warm_take读到高只能说明「起来时还是高」); 「锁着 + 非计划复位」这条路径只有宿主机测试(真板上造不出来)。插上刀片 + 手机后要看一次手机有没有掉电重启。 - 第一次从 c2 升到 c3 仍会断电一次:锁 EN 的是正在跑的固件,c2 不会锁。
下一步(没人定,候选)
- 示波器验证:插一片刀片,CH 开着,OTA 带重启,抓 EN / 输出电压(
scope_*工具)。 - 服务器 fw 目录不会自动清理旧镜像(每个 ~1 MB,问题不大)。
踩到的坑(c3)
.rodata_custom_desc没人引用会被--gc-sections丢掉 ⇒main/CMakeLists.txt加-u g_phonerail_caps。PROJECT_VER在 CMake 配置时读 ⇒ 改phonerail.h不会重配,镜像头版本会是旧的 ⇒ 顶层加了CMAKE_CONFIGURE_DEPENDS。- 撤掉台架补丁后要重新编译再推:
build/phonerail.bin还是台架版(这次推错了一次,已换回)。 - Git Bash heredoc 里的 Python 写
\0又变成真 NUL 了(这次在 rack/firmware.py)—— 含反斜杠的一律写 .py 文件。
怎么推固件
python tools/ota_push.py b81f3fc9c8bc # 推 build/phonerail.bin:只下载 + 校验 + 切分区,下次复位生效
python tools/ota_push.py b81f3fc9c8bc --reboot # 校验过就立刻重启 —— ⚠️ 五台手机硬断电一次
python tools/ota_push.py b81f3fc9c8bc --status
它把镜像传到服务器 /root/phonerail/fw/<sha 前 16 位>.bin(nginx 7765,只放行这种文件名,不过门户),
再在服务器上用后端账号经 1884 发命令、盯遥测。
rack 管理界面「固件」页也能推(2026-09-27 加,上传 → 选板 → 可选立刻重启并二次确认;见 rack/README.md「推固件」)。
C2 改了什么
partitions.csv重排(16 MB):nvs 0x9000 没动;ota_0 / ota_1 各 3 MB;otadata 0x610000;evlog 挪到 0x612000(历史事件清空了一次)。sdkconfig.defaults+sdkconfig:flash 16 MB(之前误写 8 MB);CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y(bootloader 配置,只有 USB 整片烧才生效)。main/ota.{h,c}新(宿主机可测):URL / 哈希校验、判定(busy / 待验证拒 / 同镜像不重下)、健康判据。main/ota_esp.{h,c}新:t_ota任务(esp_http_client + mbedtls sha256 +OTA_WITH_SEQUENTIAL_WRITES,每块让一个 tick)、 核 project_name、切分区;t_link里判健康 / 到点重启(先等 2 s 让回应发出去、事件落盘);esp_timer 150 s 兜底回滚。cmd新命令ota=13(cmd_net.c的do_ota);status 多ota一块;evlog新事件LOG_EV_OTA=18。- 顶层
CMakeLists.txt:PROJECT_VER取PHONERAIL_FW_VERSION(否则镜像头版本是 git describe,回滚后ota.bad报79c9f8b-dirty)。 test/test_ota.c新(13 例);test_cmd/test_evlog的冻结数字表各加一行。tools/ota_push.py新;rack/deploy/nginx-phonerail-fw.conf新(已部署);rack/backend/prrack/protocol.py加事件 18 / 命令 13(已部署)。
上板实测(板 B,五槽空)
正常:c2 → c2-t1 经 MQTT --reboot:下载 ≈8 s,推送到自证健康 19 s;事件 18/1 → 18/2 → 18/6 → BOOT → 18/4
哈希错:err "hash"、got 996064、固件不变;下载 20 s 里 t_safety skipped 0
回滚:服务器删掉板子 broker 账号 → USB 发同镜像 reboot:true → 起在 c2-rb(pend true)→ 120 075 ms 记 18/5 → 回到 c2-t1、bad 报版本
(账号已原样恢复,备份已删)
收尾:c2-t1 → 正式 0.1.0-c2 经 MQTT --reboot 成功
踩到的坑(只写会再犯的)
- 路由器不是整机 DMZ:新开的 7765 公网 TCP 都连不上,用户手动加了转发才通(本机测公网要
curl --noproxy '*', 否则 7890 代理回的 502 会被当成服务器的)。 - 本机 PowerShell
Set-Content -Encoding ascii改phonerail.h把中文注释写成??(已git checkout还原)—— 改它一律用 sed。 - Git Bash heredoc 里的 Python 写
'\0'会变成真 NUL 字节进源码;含反斜杠的替换写成 .py 文件再跑。 - 宿主机
bash host/test.sh用-j 4在 cygwin 上偶发「link: Subprocess aborted」,cmake --build build -j 1就过。
其它挂着的事
- 风扇还没下单购买(2026-09-27):板 B 两路报
rpm 0+ 停转是预期的。到货插上后看status.fan.rpm;在转却仍是 0 再查 Q9/Q10 与 PCNT。 - 风扇闭环四个阈值 + 占空比下限 + PPR 都是「待上板实测」(t_thermal.h),手机上架跑满载之后再定。
- A15 留下的:I²C 总线挂死恢复(HAL 层);PD 上限的降载动作(§8.3);CH224A 读到 100 W 与早上记的 20V/3A 不一致,待核。
本次新增/修改的文件
main/ntc.{h,c}新(宿主机可测)— mV → Ω → 查 Murata NCP18XH103F03RB 的 R-T 表(−40~125 °C、5 °C 一格、格内线性插值); 贴 +3V3 / 贴地 / 表外一律「读不到」(⇒ t_thermal 满速)main/thermal_esp.{h,c}新(只进 IDF)— ADC1 oneshot + 曲线拟合校准(8 次平均,引脚反查通道);两个 PCNT 单元数 TACH 上升沿(累计计数、1 µs 毛刺滤波)main/app_main.c—task_thermal任务体(每 10 s 打一行温度 / mV / 占空比 / 转速);thermal_esp_init()+t_thermal_init()放在上电恢复之前;cmd_env.thermal = &s_thermmain/CMakeLists.txtREQUIRES 加esp_adc esp_driver_pcnt;host/CMakeLists.txt加ntc.ctest/test_ntc.c新(4 例:表点、⭐方向「越热电压越低」、⭐开路/短路不是温度、比例式);版本0.1.0-a16
上板实测(板 B,五槽空,风扇状态未知)
status: "temp":[38249,34027] 分压端 1266 / 1384 mV ← 两路 NTC 读数合理(NTC1 在电感区更热)
"fan":{"duty":300,"en":true,"rpm":[0,0],"stall":[true,true]}
events: [31, 14318, 0, 11(FAN_STALL), 0] [32, …, 11, 1] ← 启动宽限 5 s + 一个窗口之后各报一次,不刷屏
踩到的坑(只写会再犯的)
- 比例式 NTC 的电源误差估算:+3V3 偏 1 % 在 25 °C 附近是 ~0.6 °C(中点处电阻误差是电源误差的 2 倍、NTC 每度 ~4 %),不是 0.3 °C。
- PowerShell 5.1 的
Set-Content -Encoding utf8会写 BOM —— 改phonerail.h这种文件用 sed,别用它。 python在 Git Bash 里是 Windows Store 占位程序,会挂住;一律用/c/Python314/python。