文件
phonerail/firmware/main/rail.h
T
zhou yang和Claude Opus 5 273c53347e A10:安全巡检的判据(SR-5 / SW-8)
判据全在 t_safety_step() 里、**放锁之后**、直接写引脚(§10.3 原话:不经任何
队列或抽象层):

  SR-5   |实测 − 该路设定值| > 100 mV **连着两圈** → 先断 VBUS 再关 eFuse
  SW-8   I < −50 mA,或 VBUS 期间电压被顶高 > +100 mV → **一圈就断 VBUS**

七条实现约定(详见需求文档 §10.2):

· 「持续 20 ms」数的是圈,不是墙钟毫秒;
· 反灌不设确认窗口,与过压那 20 ms 的不对称是有意的 —— 误断 VBUS 只丢一次
  USB 数据,漏判会把能量泵回 12V 母线连累另外四台(设计文档 §15.2);
· 判据只在「该路该有电 + 这一圈的值新鲜 + 过了 60 ms 缓启动窗口」时武装
  (对应 §10.10 的「盲跑 → 保持供电,禁用该路判据」);
· 比的是该路 DAC 设定值的影子,不是 3900 这个常数(SW-4 要单路微调);
· SR-5 关路关干净(不留「只靠 USB 供着电」的窗口),SW-8 只断 VBUS 不碰 eFuse;
· 断电之前先 t_actuator_abort(),断 VBUS 时只打断 EDL(只有它写 VBUS_EN);
· 动作在前、记录在后,事件走回调出去,A11 在那儿接环形缓冲。

判据一条 I²C 事务都不发(设定值、两根 EN、正在跑的序列全部走影子),
§10.3 那 7.8 % 的预算不变。顺带新增两处影子:rail_efuse_is_on()/rail_vbus_is_on()
与 rail_setpoint_mv(),都**只在写成功时更新**,两个方向都倒向安全的那一侧。

29 个用例;25 条注错逐个跑过,24 条变红。第一版把「持续 20 ms」按墙钟算,
测试一半红一半绿 —— 巡检的采样时刻带亚毫秒残差(一圈占 1.56 ms 总线,
而睡到下一拍按整毫秒算),相邻两次判据的时刻是 21/19 ms 交替,19 ms 那一半
要拖到第三圈 = 40 ms,超验收且间歇性。改成数圈之后一次不飘,
并留了一条跑 20 轮、两种相位都踩得到的回归拦网。

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-07 08:41:53 +08:00

96 行
4.3 KiB
C
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* 五路输出电压:mV ↔ DAC 码的**唯一**入口(需求 §10.5)。
⚠️ 这一节最容易写反的一条:**DAC 写得越大,手机看到的电压越低。**
写反了不会报错 —— 「调高电压」的命令会把手机调到 3.52 V,手机照样开机,
只是更容易在负载尖峰下掉电,而且要跑一阵子才看得出来。
所以:**除了本文件,任何地方都不许直接调 dac63204_write_code()。**
分压网络(设计文档 §4.2 / §4.3,R1 30.1k / R2 5.49k / Rinj 150k、Vref 0.6 V):
Vout = 0.6 × (1 + R1/R2 + R1/Rinj) − (R1/Rinj) × Vd
= 4.010 V − 0.2007 × Vd Vd ∈ [0, 2.424] V
DAC 满摆在输出端只值 486.4 mV —— 这是**有意的**:软件在物理上无法产生
3.524~4.010 V 以外的电压(设计文档 §4.1「本质安全」)。 */
#ifndef RAIL_H
#define RAIL_H
#include <stdbool.h>
#include <stdint.h>
#include "hal_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ---- 换算常量(全部 µV,整数运算,不引入浮点) ------------------------- */
/* Vd = 0 时的输出:0.6 V × (1 + 30100/5490 + 30100/150000) = 4.0100175 V。
取整到 µV 丢掉 0.5 µV,而一个码在输出端是 118.75 µV,可以忽略。 */
#define RAIL_VOUT0_UV 4010018
/* DAC 满量程 2.424 V 经 R1/Rinj = 30100/150000 折算到输出端:正好 486416 µV。
注意**是精确值不是近似**:2424000 × 30100 / 150000 = 486416。 */
#define RAIL_DAC_SPAN_UV 486416
/* 可设定范围。上限由 SR-13 拦(这是**第四道**限制,前三道在硬件里);
下限是 DAC 满码也到不了更低。 */
#define RAIL_MV_MAX 4010
#define RAIL_MV_MIN 3524
/* 上电安全值 = DAC 的 NVM 出厂默认(Vd 0.548 V),不依赖 ESP32 初始化成功。 */
#define RAIL_MV_SAFE 3900
/* ---- 纯换算,不碰总线 ---------------------------------------------------- */
bool rail_mv_in_range(int mv);
/* mv 必须先过 rail_mv_in_range(),越界直接 abort(那是调用方漏判)。
反向关系就在这里:mv 越小,返回的码越大。 */
uint16_t rail_mv_to_code(int mv);
int rail_code_to_mv(uint16_t code);
int32_t rail_code_to_vd_uv(uint16_t code); /* 码 → DAC 自己的输出 Vd(µV) */
/* ---- 器件操作 ------------------------------------------------------------ */
/* 认件 + 初始化两片 DAC,并把五路都置到 RAIL_MV_SAFE。
任何一片失败就返回它的错误码(降级矩阵在 A15 处理)。 */
hal_err_t rail_volt_init(void);
/* rail 是 **1~5**(不是 0~4),越界 → HAL_ERR_ARG。
mv > 4010 → HAL_ERR_ARG(SR-13:越界即拒,让操作者早点知道自己写错了)。
mv < 3524 → HAL_ERR_ARG(DAC 到不了)。
⚠️ **不要用它做保护** —— 异常处理是 EFUSE_EN 的活(需求 §10.5 规矩 2)。
TODO(A11):被拒的请求要记一条事件;事件环形缓冲还没有,先只返回错误码。 */
hal_err_t rail_set_mv(int rail, int mv);
/* 回读该路 DAC 里的码并换算成 mV。读的是**设定值**,不是实测输出 ——
实测要走该路的 INA226(A10 的巡检)。 */
hal_err_t rail_get_mv(int rail, int *mv);
/* 最后一次**成功**写进那一路 DAC 的设定值(mV)。不碰总线。
A10 的 SR-5 判据要的就是这个「设定值」(`|实测 − 设定| > 100 mV`)。
不用 `rail_get_mv()` 是因为它是一次 I²C 事务:巡检每 20 ms 跑一圈,
五路各多一次读就是每圈多 5 次事务,把 §10.3 那 7.8 % 的预算顶到 11 %,
而这个数是编译期就知道的。
上电默认是 `RAIL_MV_SAFE`(3900)—— 那不是猜的,是 DAC63204 **NVM 里的
出厂默认值**(rail.h 开头那段),所以在 `rail_volt_init()` 跑之前它也是真的。
写失败时不更新:那一次 DAC 里到底是什么值不知道,留着上一个成功的更可信。 */
int rail_setpoint_mv(int rail);
/* 把五路的设定值影子放回 `RAIL_MV_SAFE`,**不碰 DAC**。
给两处用:宿主机测试的 `setUp()`(`sim_hal_reset()` 够不到应用层的静态变量),
以及 A15 在「DAC 无应答」降级路径上声明「现在生效的是 NVM 默认值」。 */
void rail_setpoint_reset(void);
#ifdef __cplusplus
}
#endif
#endif /* RAIL_H */