文件
phonerail/firmware/main/rail.c
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

124 行
3.8 KiB
C
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* 五路输出电压的换算与设定。与硬件无关,宿主机与 ESP32-S3 上编的是同一份。 */
#include "rail.h"
#include <assert.h>
#include <stddef.h>
#include "dac63204.h"
#include "phonerail.h"
/* 五路数与 DAC 侧的映射表必须是同一个数 —— 分家的那天就是错一路的那天。 */
_Static_assert(PHONERAIL_RAIL_COUNT == DAC63204_RAIL_COUNT, "五路数对不上");
bool rail_mv_in_range(int mv)
{
return mv >= RAIL_MV_MIN && mv <= RAIL_MV_MAX;
}
uint16_t rail_mv_to_code(int mv)
{
assert(rail_mv_in_range(mv)); /* 越界要在 rail_set_mv() 里被拒掉,不该走到这儿 */
/* code = (Vout0 − Vout) / 486416 µV × 4096,四舍五入。
**减法在这里**:Vout 越低,差越大,码越大 —— 反向关系只此一处。 */
int64_t diff_uv = (int64_t)RAIL_VOUT0_UV - (int64_t)mv * 1000;
int64_t code = (diff_uv * DAC63204_CODE_SPAN + RAIL_DAC_SPAN_UV / 2) / RAIL_DAC_SPAN_UV;
if (code < 0) {
code = 0;
}
if (code > DAC63204_CODE_MAX) {
code = DAC63204_CODE_MAX;
}
return (uint16_t)code;
}
int rail_code_to_mv(uint16_t code)
{
int64_t drop_uv = ((int64_t)code * RAIL_DAC_SPAN_UV + DAC63204_CODE_SPAN / 2)
/ DAC63204_CODE_SPAN;
int64_t uv = (int64_t)RAIL_VOUT0_UV - drop_uv;
return (int)((uv + 500) / 1000);
}
int32_t rail_code_to_vd_uv(uint16_t code)
{
return dac63204_code_to_uv(code);
}
hal_err_t rail_volt_init(void)
{
const uint16_t safe = rail_mv_to_code(RAIL_MV_SAFE);
for (int i = 0; i < DAC63204_COUNT; i++) {
uint8_t addr = dac63204_all_addr[i];
hal_err_t err = dac63204_probe(addr);
if (err != HAL_OK) {
return err;
}
/* init 自己就把用到的通道写成 safe 了(上电之前写,见 dac63204_init 的 ③)。 */
err = dac63204_init(addr, safe);
if (err != HAL_OK) {
return err;
}
}
return HAL_OK;
}
/* 五路设定值的影子(契约见 rail.h)。初值就是 DAC 的 NVM 出厂默认,
所以 `rail_volt_init()` 跑之前它也说的是实话。 */
static int s_set_mv[PHONERAIL_RAIL_COUNT] = {
RAIL_MV_SAFE, RAIL_MV_SAFE, RAIL_MV_SAFE, RAIL_MV_SAFE, RAIL_MV_SAFE,
};
_Static_assert(PHONERAIL_RAIL_COUNT == 5, "s_set_mv 的初值列表要跟着改");
int rail_setpoint_mv(int rail)
{
if (rail < 1 || rail > PHONERAIL_RAIL_COUNT) {
return RAIL_MV_SAFE;
}
return s_set_mv[rail - 1];
}
void rail_setpoint_reset(void)
{
for (int i = 0; i < PHONERAIL_RAIL_COUNT; i++) {
s_set_mv[i] = RAIL_MV_SAFE;
}
}
hal_err_t rail_set_mv(int rail, int mv)
{
if (rail < 1 || rail > PHONERAIL_RAIL_COUNT) {
return HAL_ERR_ARG;
}
if (!rail_mv_in_range(mv)) {
return HAL_ERR_ARG; /* SR-13。TODO(A11):这里要记一条事件 */
}
const hal_err_t err = dac63204_write_code(dac63204_rail_addr(rail),
dac63204_rail_ch(rail),
rail_mv_to_code(mv));
if (err == HAL_OK) {
/* 记的是**请求的 mV**,不是 `rail_code_to_mv(码)` —— 两者差不到一个码
(118.75 µV),但 A10 的判据是拿它当「设定值」用的,回读换算会让
「设成 3900」在判据眼里变成 3899,白白吃掉 1 mV 的余量。 */
s_set_mv[rail - 1] = mv;
}
return err;
}
hal_err_t rail_get_mv(int rail, int *mv)
{
if (rail < 1 || rail > PHONERAIL_RAIL_COUNT || mv == NULL) {
return HAL_ERR_ARG;
}
uint16_t code = 0;
hal_err_t err = dac63204_read_code(dac63204_rail_addr(rail), dac63204_rail_ch(rail), &code);
if (err != HAL_OK) {
return err;
}
*mv = rail_code_to_mv(code);
return HAL_OK;
}