A4:DAC63204 驱动 + rail_set_mv()
调压是**反向**的(DAC 码越大输出越低),所以整块换算只在 rail_mv_to_code() 里出现一次那个减法,除 rail.c 外任何地方都不许碰 dac63204_write_code()。 范围 3524~4010 mV,越界即拒(SR-13,第四道限制),4010 = Vd 0。 通道映射按 §26.18 重排过,五路里四路对不上「前三路在 U6、通道号 = rail−1」: CH1→U6.OUT2 / CH2→U6.OUT0 / CH3→U7.OUT0 / CH4→U6.OUT1 / CH5→U7.OUT1。 四通道电气对等 ⇒ 接错了板子照样正常工作,只是调错了路。设计文档 §17.2 那张 过期的通道表一并改掉了。 驱动只经 hal_i2c_*:认件(DEVICE-ID=01h,53204/43204 当 BUS 错拒掉)、 开内部基准 → 写增益 2× → 写安全码 → 才给用到的通道上电(COMMON-CONFIG 因此写两次: 复位码 0 就是输出上限 4.010 V),未用通道 FBx 悬空一律保持 Hi-Z。 三个「一定会被写出来的错」逐个改进去验过测试拦得住:反向关系写反 → test_rail 红; 忘了码左对齐、通道按 rail−1 算 → test_dac63204 红。 留给下次的两条:越界请求还没记事件(TODO(A11));两点标定还没做, 现在走理论公式,将来顶替的是 RAIL_VOUT0_UV / RAIL_DAC_SPAN_UV 两个常量。 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
这个提交包含在:
+45
-38
@@ -1,56 +1,63 @@
|
||||
# 交接
|
||||
更新:2026-09-06 完成任务包 **A3**(INA226 驱动 + 六片地址表)
|
||||
更新:2026-09-06 完成任务包 **A4**(DAC63204 驱动 + `rail_set_mv()`)
|
||||
|
||||
## 一句话现状
|
||||
六片 INA226 的地址表钉死在代码里(`0x40/41/44/45/42` + 输入侧 `0x43`),
|
||||
驱动能认件(MFG/DIE ID)、写 Config+CAL 并回读校验、读出 V / I / 分流 / 功率,
|
||||
电流**有符号**(反灌为负)。4 条 ctest(34 个用例)全绿,`idf.py build` 通过。
|
||||
五路输出电压能设了:`rail_set_mv(rail, mv)` 是唯一入口(rail 是 **1~5**),
|
||||
内部走 mV → Vd → 12 位码,**码越大电压越低**;4010 mV 以上、3524 mV 以下一律拒。
|
||||
两片 DAC 认件(DEVICE-ID=01h)、开内部基准、增益 2×、只给用到的通道上电,
|
||||
上电即 3.90 V。6 条 ctest(46 个用例)全绿,`idf.py build` 通过。
|
||||
|
||||
## 下一步
|
||||
**A4**:DAC63204 驱动 + `rail_set_mv()`(需求 §10.5 全节)。
|
||||
三条断言:①4020 mV 被拒(SR-13)②**写更大的码值输出更低**(反向关系)③3900 mV ↔ Vd 0.548 V。
|
||||
地址 `0x4A`(U6:CH1~3)/ `0x4B`(U7:CH4~5),同样查表不要算。
|
||||
**A5**:74HC595 24 位链 + 影子寄存器单一写入点(需求 §10.6「影子寄存器」那段 +
|
||||
设计文档 §17.2 的位表)。两条断言:①改 CH1 按键不动 CH2~5 的位 ②字节顺序 U33→U22→U21。
|
||||
⚠️ 位表也被 §26.18 重排过(`.claude/pinmap.py` 的 `P3B` / `P6` 两节是真表),
|
||||
和这次的 DAC 通道一样,**别照 §17.2 的旧表写**。
|
||||
|
||||
## 本次新增/修改的文件
|
||||
- `components/drv/include/ina226.h` — 寄存器、量程常量、地址表、采样结构(唯一对外契约)
|
||||
- `components/drv/ina226.c` — 驱动本体(≈170 行,只经 `hal_i2c_*`)
|
||||
- `components/drv/CMakeLists.txt` — 新 IDF 组件 `drv`,`REQUIRES phonerail_hal`
|
||||
- `test/test_ina226.c` — 17 个用例
|
||||
- `host/CMakeLists.txt` — 加 `PHONERAIL_DRV_SRCS` 与 `drv/include`
|
||||
- `main/CMakeLists.txt` — `REQUIRES phonerail_hal drv`
|
||||
- `components/drv/include/dac63204.h` — 寄存器、位域、五路→(片,通道)映射(唯一对外契约)
|
||||
- `components/drv/dac63204.c` — 驱动本体(≈180 行,只经 `hal_i2c_*`)
|
||||
- `main/rail.h` / `main/rail.c` — mV ↔ 码的**唯一**换算与 `rail_set_mv()` / `rail_get_mv()`
|
||||
- `test/test_dac63204.c`(21 个用例)/ `test/test_rail.c`(12 个用例)
|
||||
- 三处 CMakeLists + `main/phonerail.h`(版本 `0.1.0-a4`)
|
||||
- `../需求文档.md` §10.5、`../五路直供电源板设计.md` §17.2 — 记落地 + **修掉过期的通道分配表**
|
||||
|
||||
## 验收命令与结果
|
||||
```
|
||||
$ bash host/test.sh
|
||||
100% tests passed, 0 tests failed out of 4 # +test_ina226(17 个用例)
|
||||
100% tests passed, 0 tests failed out of 6 # +test_dac63204(21) +test_rail(12)
|
||||
|
||||
$ .\build_idf.ps1 # PowerShell
|
||||
phonerail.bin binary size 0x32550 bytes. ... 0xcdab0 bytes (80%) free.
|
||||
Project build complete. # build/esp-idf/drv/.../ina226.c.obj 确认编到了
|
||||
Project build complete. # dac63204.c.obj 与 rail.c.obj 都在;bin 与 A3 同样大是正常的
|
||||
#(还没人调它们,链接器把符号丢了)
|
||||
|
||||
# 三个「一定会被写出来的错」逐个改进去跑过,确认测试拦得住:
|
||||
# 反向关系写反 -> test_rail 红;忘了左对齐 / 通道按 rail-1 算 -> test_dac63204 红
|
||||
```
|
||||
|
||||
## 踩到的坑(只写会再犯的)
|
||||
- **别依赖 I²C 寄存器指针自增**:sim 的假器件**会**自增,INA226 按 datasheet **不会**。
|
||||
「一次 write_read 连读 0x01~0x04」在宿主机上全绿,到板上四个量会读成同一个。
|
||||
驱动每次读之前重写指针,`test_pointer_is_rewritten_before_every_read` 盯着这条。
|
||||
(指针不自增这条**未在实物上验过**,B1 上板时确认。)
|
||||
- **`write_verify()` 之后 `last_write` 是那次回读的指针字节(len 1),不是刚写的三字节。**
|
||||
按「最后一次写就是我写的数据」写断言会红一次 —— 改看 `n_write`/`n_read` 计数。
|
||||
- cygwin 并行链接偶发 `Error running link command: Subprocess aborted`(这次是 test_version)。
|
||||
**重跑一次就好**,不是代码问题,别去查。
|
||||
- **12 位码在 `DAC-X-DATA` 里左对齐**(bit15-4,低 4 位 don't care)。忘了 `<< 4`
|
||||
不报错,只让输出变成 1/16 —— 而在本板上那是「五路都 4.010 V 满电」,看不出异常。
|
||||
- **两种步长**:`DAC-X-VOUT-CMP-CONFIG` 每通道跨 6(03/09/0F/15),`DAC-X-DATA` 连续(19~1C)。
|
||||
- **上电顺序**:`EN-INT-REF` 要先写(datasheet 明说增益设定之前必须已置位),
|
||||
安全码要在通道上电之前写(复位码 0 = Vd 0 V = 输出上限 4.010 V)。
|
||||
所以 `COMMON-CONFIG` 写两次。sim 只留寄存器终值看不到中间态,
|
||||
顺序是靠「`CC_REF_ONLY` 里四路输出全按住」+「`n_read` 计数」两条间接钉的。
|
||||
- **未用通道不许上电**:FBx 悬空时电压输出是开环运放(datasheet 表 5-1)。
|
||||
U6 用 OUT0/1/2、U7 只用 OUT0/1,所以 `COMMON-CONFIG` 是每片一个值,不是常量。
|
||||
- **cygwin 的偶发 `Subprocess aborted` 不止发生在链接**:这次 ctest 跑测试时
|
||||
`test_hal_sim` / `test_ina226` 同时挂了一次,重跑就全绿。**别去查代码。**
|
||||
|
||||
## 这个驱动的几条约定(写 A7 / A10 之前看一眼)
|
||||
- **地址查表**:`ina226_rail_addr(rail)`,rail 是 **1~5**(不是 0~4),越界 abort。
|
||||
`ina226_all_addr[6]` 供扫描用;`ina226_addr_table_ok()` 做启动自检(含与
|
||||
DAC 0x4A/0x4B、PCA9555 0x20、LM75 0x48 的撞车检查)。
|
||||
- **单位**:`bus_mv` / `shunt_uv` / `current_ua` / `power_mw`,全是 `int32_t`。
|
||||
电流 LSB 0.5 mA ⇒ `current_ua` 一定是 500 的整数倍;−60 mA(SW-8 判据)= `-60000`。
|
||||
- **巡检走 `ina226_read_vi()`**(只读母线电压 + 电流,两次事务)——
|
||||
§10.3 那 8 % 的 I²C 占用是按「每片每 20 ms 两次事务」算的,读四个量会翻倍。
|
||||
- **`HAL_ERR_BUS` 在这个驱动里有特定含义**:器件应答了但内容不对
|
||||
(probe 的 ID 不匹配 = 贴错件/地址跳线焊错;init 的回读不一致;check 的 CAL 丢了)。
|
||||
和「没这个器件」的 `HAL_ERR_NAK` 是两回事,降级矩阵(A15)要分开处理。
|
||||
- **CAL 丢了不会报错,只会让 Current/Power 恒读 0**,看上去像「这台手机不耗电」。
|
||||
所以有 `ina226_check()`,巡检里定期查一次、不一致就 `ina226_init()` 重来并记事件。
|
||||
- Config 定为 **0x4327**(AVG=4、VBUSCT=VSHCT=1.1 ms、连续测量分流与母线),
|
||||
转换周期 8.8 ms < t_safety 的 20 ms。改这个值前先算一遍这个不等式。
|
||||
## 这个驱动 / 这一层的几条约定(写 A7 / A10 / A12 之前看一眼)
|
||||
- **除 `rail.c` 外,任何地方都不许调 `dac63204_write_code()`。** 反向关系只在
|
||||
`rail_mv_to_code()` 里出现一次(那个减法),多一处就多一处写反的机会。
|
||||
- **查表不要算**:`dac63204_rail_addr()` / `dac63204_rail_ch()`,rail 是 1~5,越界 abort。
|
||||
CH1→U6.OUT2 / CH2→U6.OUT0 / CH3→**U7**.OUT0 / CH4→U6.OUT1 / CH5→U7.OUT1(§26.18)。
|
||||
四通道电气对等 ⇒ **接错了板子照样正常工作,只是调错了路**,功能测试测不出来。
|
||||
- **单位**:`rail_set_mv/rail_get_mv` 用 mV,`rail_code_to_vd_uv()` 用 µV。一个码在输出端
|
||||
只值 **0.119 mV**,往返误差 ≤ 1 mV 正常。`rail_get_mv()` 读的是**设定值不是实测**(实测走 INA226)。
|
||||
- **不要用 `rail_set_mv()` 做保护**(需求 §10.5 规矩 2):异常处理是 `EFUSE_EN` 的活。
|
||||
- **越界请求现在只返回 `HAL_ERR_ARG`,还没记事件** —— SR-13 要求记,`rail.c` 里
|
||||
留了 `TODO(A11)`,事件环形缓冲做完要回来补。
|
||||
- **标定还没做**(需求 §10.5 规矩 3):现在走理论公式,误差约 ±40 mV。
|
||||
将来两点标定要顶替的是 `RAIL_VOUT0_UV`(4010018) 与 `RAIL_DAC_SPAN_UV`(486416) 这两个常量。
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
# 驱动只准依赖 phonerail_hal,不准直接包含 IDF 的头。
|
||||
idf_component_register(
|
||||
SRCS "ina226.c"
|
||||
"dac63204.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES phonerail_hal)
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/* DAC63204 驱动。只经 hal_i2c_*,不碰任何平台头文件。 */
|
||||
#include "dac63204.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "hal_i2c.h"
|
||||
|
||||
/* 设计文档 §8.2(两片地址)+ §26.18(按 PCB 几何重排过的通道分配)。
|
||||
⚠️ 不是「前三路在 U6、通道号 = rail−1」—— 五路里有四路对不上那个算式。 */
|
||||
static const uint8_t s_rail_addr[DAC63204_RAIL_COUNT] = {
|
||||
DAC63204_ADDR_U6, /* CH1 */
|
||||
DAC63204_ADDR_U6, /* CH2 */
|
||||
DAC63204_ADDR_U7, /* CH3 ← 在 U7 上,不在 U6 */
|
||||
DAC63204_ADDR_U6, /* CH4 */
|
||||
DAC63204_ADDR_U7, /* CH5 */
|
||||
};
|
||||
static const uint8_t s_rail_ch[DAC63204_RAIL_COUNT] = { 2, 0, 0, 1, 1 };
|
||||
|
||||
const uint8_t dac63204_all_addr[DAC63204_COUNT] = { DAC63204_ADDR_U6, DAC63204_ADDR_U7 };
|
||||
|
||||
uint8_t dac63204_rail_addr(int rail)
|
||||
{
|
||||
assert(rail >= 1 && rail <= DAC63204_RAIL_COUNT); /* 越界是代码写错,不许悄悄返回一片 */
|
||||
return s_rail_addr[rail - 1];
|
||||
}
|
||||
|
||||
uint8_t dac63204_rail_ch(int rail)
|
||||
{
|
||||
assert(rail >= 1 && rail <= DAC63204_RAIL_COUNT);
|
||||
return s_rail_ch[rail - 1];
|
||||
}
|
||||
|
||||
uint8_t dac63204_used_mask(uint8_t addr)
|
||||
{
|
||||
uint8_t mask = 0;
|
||||
for (int rail = 1; rail <= DAC63204_RAIL_COUNT; rail++) {
|
||||
if (s_rail_addr[rail - 1] == addr) {
|
||||
mask |= (uint8_t)(1u << s_rail_ch[rail - 1]);
|
||||
}
|
||||
}
|
||||
return mask; /* 不是这两片中的一片 -> 0,init 时会被 HAL_ERR_ARG 拦下 */
|
||||
}
|
||||
|
||||
bool dac63204_map_ok(void)
|
||||
{
|
||||
/* 同一条 I²C 上的其它器件(§8.2):撞上任何一个都是硬件表被改坏了。 */
|
||||
static const uint8_t others[] = { 0x40, 0x41, 0x44, 0x45, 0x42, 0x43, 0x20, 0x48 };
|
||||
|
||||
if (dac63204_all_addr[0] == dac63204_all_addr[1]) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < DAC63204_COUNT; i++) {
|
||||
if (dac63204_all_addr[i] > 0x7f) {
|
||||
return false;
|
||||
}
|
||||
for (size_t k = 0; k < sizeof(others); k++) {
|
||||
if (dac63204_all_addr[i] == others[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int a = 1; a <= DAC63204_RAIL_COUNT; a++) {
|
||||
uint8_t addr = s_rail_addr[a - 1];
|
||||
uint8_t ch = s_rail_ch[a - 1];
|
||||
if (ch > 3) {
|
||||
return false;
|
||||
}
|
||||
if (addr != dac63204_all_addr[0] && addr != dac63204_all_addr[1]) {
|
||||
return false;
|
||||
}
|
||||
/* 一个 (片, 通道) 只能被一路占用 —— 两路撞在一起的现象是「调这路那路也动」。 */
|
||||
for (int b = a + 1; b <= DAC63204_RAIL_COUNT; b++) {
|
||||
if (addr == s_rail_addr[b - 1] && ch == s_rail_ch[b - 1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((dac63204_used_mask(addr) & (1u << ch)) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---- 寄存器读写 ---------------------------------------------------------
|
||||
和 INA226 那份一样:**每次读之前重写寄存器指针**,不依赖指针自增。
|
||||
sim 的假器件会自增,真器件的行为没在实物上验过(B1 上板时确认)。 */
|
||||
|
||||
static hal_err_t read_reg(uint8_t addr, uint8_t reg, uint16_t *raw)
|
||||
{
|
||||
uint8_t rd[2];
|
||||
hal_err_t err = hal_i2c_write_read(addr, ®, 1, rd, sizeof(rd));
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
*raw = (uint16_t)(((uint16_t)rd[0] << 8) | rd[1]); /* 大端 */
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
static hal_err_t write_reg(uint8_t addr, uint8_t reg, uint16_t val)
|
||||
{
|
||||
const uint8_t wr[3] = { reg, (uint8_t)(val >> 8), (uint8_t)(val & 0xff) };
|
||||
return hal_i2c_write(addr, wr, sizeof(wr));
|
||||
}
|
||||
|
||||
static hal_err_t write_verify(uint8_t addr, uint8_t reg, uint16_t val)
|
||||
{
|
||||
hal_err_t err = write_reg(addr, reg, val);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
uint16_t back = 0;
|
||||
err = read_reg(addr, reg, &back);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
return back == val ? HAL_OK : HAL_ERR_BUS;
|
||||
}
|
||||
|
||||
/* ---- COMMON-CONFIG 的两个形态 ------------------------------------------- */
|
||||
|
||||
/* ② 把 used 里的通道上电,其余保持 Hi-Z。电流输出一律掉电(本板只用电压输出)。 */
|
||||
uint16_t dac63204_cc_run(uint8_t addr)
|
||||
{
|
||||
uint8_t used = dac63204_used_mask(addr);
|
||||
uint16_t v = DAC63204_CC_EN_INT_REF | DAC63204_CC_IOUT_PDN_ALL;
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
if ((used & (1u << ch)) == 0) {
|
||||
v |= DAC63204_CC_VOUT_PDN(ch); /* 没用到:FBx 悬空,绝不能上电 */
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* ---- 对外 ---------------------------------------------------------------- */
|
||||
|
||||
hal_err_t dac63204_probe(uint8_t addr)
|
||||
{
|
||||
uint16_t st = 0;
|
||||
hal_err_t err = read_reg(addr, DAC63204_REG_GENERAL_STATUS, &st);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
/* 应答了但 ID 不对:多半贴成了 DAC53204(02h)/DAC43204(03h),
|
||||
那两颗只有 10 位 / 8 位,码值左对齐的位数不一样,装上去电压全错。 */
|
||||
return DAC63204_STATUS_ID(st) == DAC63204_DEVICE_ID ? HAL_OK : HAL_ERR_BUS;
|
||||
}
|
||||
|
||||
hal_err_t dac63204_init(uint8_t addr, uint16_t safe_code)
|
||||
{
|
||||
uint8_t used = dac63204_used_mask(addr);
|
||||
if (used == 0 || safe_code > DAC63204_CODE_MAX) {
|
||||
return HAL_ERR_ARG;
|
||||
}
|
||||
|
||||
/* ① 先开内部基准(输出还全部掉电着) */
|
||||
hal_err_t err = write_verify(addr, DAC63204_REG_COMMON_CONFIG, DAC63204_CC_REF_ONLY);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
/* ② 增益 + ③ 安全码。**都要在上电之前**:复位码是 0,而 Vd=0 对应输出 4.010 V。 */
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
if ((used & (1u << ch)) == 0) {
|
||||
continue;
|
||||
}
|
||||
err = write_verify(addr, DAC63204_REG_VOUT_CMP_CONFIG(ch), DAC63204_CFG_GAIN_2X_INT);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
err = write_verify(addr, DAC63204_REG_DATA(ch), DAC63204_CODE_TO_REG(safe_code));
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
/* ④ 用到的通道上电 */
|
||||
return write_verify(addr, DAC63204_REG_COMMON_CONFIG, dac63204_cc_run(addr));
|
||||
}
|
||||
|
||||
hal_err_t dac63204_check(uint8_t addr)
|
||||
{
|
||||
uint8_t used = dac63204_used_mask(addr);
|
||||
if (used == 0) {
|
||||
return HAL_ERR_ARG;
|
||||
}
|
||||
uint16_t cc = 0;
|
||||
hal_err_t err = read_reg(addr, DAC63204_REG_COMMON_CONFIG, &cc);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
if (cc != dac63204_cc_run(addr)) {
|
||||
return HAL_ERR_BUS;
|
||||
}
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
if ((used & (1u << ch)) == 0) {
|
||||
continue;
|
||||
}
|
||||
uint16_t cfg = 0;
|
||||
err = read_reg(addr, DAC63204_REG_VOUT_CMP_CONFIG(ch), &cfg);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
if (cfg != DAC63204_CFG_GAIN_2X_INT) {
|
||||
return HAL_ERR_BUS;
|
||||
}
|
||||
}
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
hal_err_t dac63204_write_code(uint8_t addr, uint8_t ch, uint16_t code)
|
||||
{
|
||||
if (ch > 3 || code > DAC63204_CODE_MAX) {
|
||||
return HAL_ERR_ARG;
|
||||
}
|
||||
return write_reg(addr, DAC63204_REG_DATA(ch), DAC63204_CODE_TO_REG(code));
|
||||
}
|
||||
|
||||
hal_err_t dac63204_read_code(uint8_t addr, uint8_t ch, uint16_t *code)
|
||||
{
|
||||
if (ch > 3 || code == NULL) {
|
||||
return HAL_ERR_ARG;
|
||||
}
|
||||
uint16_t raw = 0;
|
||||
hal_err_t err = read_reg(addr, DAC63204_REG_DATA(ch), &raw);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
*code = DAC63204_REG_TO_CODE(raw);
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
int32_t dac63204_code_to_uv(uint16_t code)
|
||||
{
|
||||
/* Vd = code / 2^12 × 2.424 V(datasheet 式 (1),增益 2× 内部基准)。 */
|
||||
int64_t num = (int64_t)code * DAC63204_FULLSCALE_UV + DAC63204_CODE_SPAN / 2;
|
||||
return (int32_t)(num / DAC63204_CODE_SPAN);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/* DAC63204 —— 板上 2 片(U6 = 0x4A、U7 = 0x4B),5 路调压用 5 个通道。
|
||||
|
||||
这颗 DAC **不做全范围调压**,只在固定中心点上做 ±0.24 V 的微调(设计文档 §4.1),
|
||||
而且是**反向**的:写进去的码越大,手机看到的电压越低。换算一律走 `rail.h` 的
|
||||
`rail_set_mv()`,本文件只负责「把一个 12 位码写进某片某通道」。
|
||||
|
||||
本驱动只通过 hal_i2c_* 访问总线,宿主机上用 sim 的假器件即可全部测完。
|
||||
|
||||
寄存器与位定义出处:TI SLASEX3A(DAC63204/53204/43204)表 7-21、7-26、7-32、7-33。 */
|
||||
#ifndef DAC63204_H
|
||||
#define DAC63204_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "hal_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 寄存器(全部 16 位、大端) ---------------------------------------- */
|
||||
|
||||
/* 每通道 6 个寄存器一组:03h/09h/0Fh/15h。 */
|
||||
#define DAC63204_REG_VOUT_CMP_CONFIG(ch) ((uint8_t)(0x03 + 0x06 * (ch)))
|
||||
/* 数据寄存器却是**连续**的:19h/1Ah/1Bh/1Ch。两种步长别记混。 */
|
||||
#define DAC63204_REG_DATA(ch) ((uint8_t)(0x19 + (ch)))
|
||||
|
||||
#define DAC63204_REG_COMMON_CONFIG 0x1F
|
||||
#define DAC63204_REG_COMMON_TRIGGER 0x20
|
||||
#define DAC63204_REG_GENERAL_STATUS 0x22
|
||||
|
||||
/* ---- 位域 --------------------------------------------------------------- */
|
||||
|
||||
/* DAC-X-VOUT-CMP-CONFIG bit12-10 = VOUT-GAIN-X。
|
||||
011b = 增益 2×、用内部基准 —— 这一路的满量程 1.212 V × 2 = **2.424 V**
|
||||
(不是 2.5 V,设计文档 §4.5 早期写错过一次)。 */
|
||||
#define DAC63204_VOUT_GAIN_SHIFT 10
|
||||
#define DAC63204_VOUT_GAIN_2X_INT 0x3
|
||||
#define DAC63204_CFG_GAIN_2X_INT ((uint16_t)(DAC63204_VOUT_GAIN_2X_INT << DAC63204_VOUT_GAIN_SHIFT))
|
||||
|
||||
/* COMMON-CONFIG(1Fh,复位值 0FFFh = 八路输出全部掉电)。
|
||||
bit12 EN-INT-REF —— 用内部基准增益之前**必须先置 1**(datasheet 原话)
|
||||
bit11-10 / 8-7 / 5-4 / 2-1 VOUT-PDN-3/2/1/0:00 = 上电,11 = Hi-Z 掉电
|
||||
bit9 / 6 / 3 / 0 IOUT-PDN-3/2/1/0:1 = 掉电(本板不用电流输出) */
|
||||
#define DAC63204_CC_EN_INT_REF ((uint16_t)(1u << 12))
|
||||
#define DAC63204_CC_IOUT_PDN_ALL ((uint16_t)0x0249) /* bit 0/3/6/9 */
|
||||
/* 某通道的 VOUT-PDN 两位全 1 = Hi-Z 掉电。ch: 0~3。 */
|
||||
#define DAC63204_CC_VOUT_PDN(ch) ((uint16_t)(0x3u << (1 + 3 * (ch))))
|
||||
|
||||
/* `dac63204_init()` 第 ① 步写进去的值:**只开内部基准,八路输出仍全部 Hi-Z**。
|
||||
必须先有这一步,安全码才来得及在上电之前写进数据寄存器。 */
|
||||
#define DAC63204_CC_REF_ONLY ((uint16_t)0x1FFF)
|
||||
|
||||
/* GENERAL-STATUS(22h)bit7-2 = DEVICE-ID。DAC63204 是 01h
|
||||
(DAC53204 = 02h、DAC43204 = 03h —— 贴错料就是这里露馅)。 */
|
||||
#define DAC63204_DEVICE_ID 0x01
|
||||
#define DAC63204_STATUS_ID(reg) ((uint8_t)(((reg) >> 2) & 0x3f))
|
||||
#define DAC63204_STATUS_NVM_BUSY ((uint16_t)(1u << 8))
|
||||
|
||||
/* ---- 分辨率与量程 ------------------------------------------------------- */
|
||||
|
||||
#define DAC63204_BITS 12
|
||||
#define DAC63204_CODE_MAX 4095
|
||||
#define DAC63204_CODE_SPAN 4096 /* VOUT = code / 2^12 × VREF × GAIN,见 datasheet 式 (1) */
|
||||
|
||||
/* 内部基准 1.212 V × 增益 2 = 满量程 2.424 V。µV 为单位,整数运算用。 */
|
||||
#define DAC63204_FULLSCALE_UV 2424000
|
||||
|
||||
/* ⚠️ **12 位码在 DAC-X-DATA 里是左对齐的**(bit15-4,低 4 位 don't care)。
|
||||
忘了 `<< 4` 不会报错,只会让输出变成期望值的 1/16 —— 五路全部顶到 4.010 V,
|
||||
而这在本板上是「正常满电」,看不出异常(设计文档 §4.4)。 */
|
||||
#define DAC63204_DATA_SHIFT 4
|
||||
#define DAC63204_CODE_TO_REG(code) ((uint16_t)((uint16_t)(code) << DAC63204_DATA_SHIFT))
|
||||
#define DAC63204_REG_TO_CODE(reg) ((uint16_t)((reg) >> DAC63204_DATA_SHIFT))
|
||||
|
||||
/* ---- 两片的地址与「哪一路挂在哪片哪通道」(设计文档 §8.2 + §26.18) ----- */
|
||||
|
||||
#define DAC63204_COUNT 2
|
||||
#define DAC63204_RAIL_COUNT 5
|
||||
#define DAC63204_ADDR_U6 0x4A /* A0 接 SDA */
|
||||
#define DAC63204_ADDR_U7 0x4B /* A0 接 SCL */
|
||||
|
||||
extern const uint8_t dac63204_all_addr[DAC63204_COUNT];
|
||||
|
||||
/* rail 是 1~5,越界直接 abort。
|
||||
⚠️ **「前三路在 U6、通道号 = rail−1」是错的**:2026-09-06 按 PCB 几何重排过
|
||||
(§26.18),五路里有四路对不上那个算式。真表是
|
||||
|
||||
CH1 → U6.OUT2 CH2 → U6.OUT0 CH3 → **U7**.OUT0 CH4 → U6.OUT1 CH5 → U7.OUT1
|
||||
|
||||
四个通道在电气上完全对等,重排只改固件通道号,一根线都没动 ——
|
||||
所以**接错了板子照样正常工作,只是调 CH1 的命令去调了 CH2**。查表,不要算。 */
|
||||
uint8_t dac63204_rail_addr(int rail);
|
||||
uint8_t dac63204_rail_ch(int rail);
|
||||
|
||||
/* 某片上被真正用到的通道位图(bit0..bit3)。没用到的通道 FBx 悬空,
|
||||
**不许上电**:电压输出模式下 FBx 是运放的反馈脚,悬空上电就是开环
|
||||
(datasheet 表 5-1 要求 FBx 接到 OUTx)。 */
|
||||
uint8_t dac63204_used_mask(uint8_t addr);
|
||||
|
||||
/* 该片跑起来之后 COMMON-CONFIG 应该是什么:内部基准开、用到的通道上电、
|
||||
其余 Hi-Z、四路电流输出全掉电。`dac63204_check()` 拿它对账。 */
|
||||
uint16_t dac63204_cc_run(uint8_t addr);
|
||||
|
||||
/* 两片地址不重复、不与 INA226 六片 / PCA9555 / LM75 撞车,
|
||||
且五路映射覆盖到的 (片, 通道) 互不重复、都在 used_mask 里。
|
||||
启动自检调一次;返回 false = 表被改坏了。 */
|
||||
bool dac63204_map_ok(void);
|
||||
|
||||
/* ---- 器件操作 ----------------------------------------------------------- */
|
||||
|
||||
/* 读 GENERAL-STATUS 认件。
|
||||
不应答 → HAL_ERR_NAK;**应答了但 DEVICE-ID 不是 01h → HAL_ERR_BUS**
|
||||
(这个地址上有东西,但不是 DAC63204:多半贴成了 53204/43204,或地址脚焊错)。 */
|
||||
hal_err_t dac63204_probe(uint8_t addr);
|
||||
|
||||
/* 初始化一片。顺序是有讲究的,四步不能换:
|
||||
① COMMON-CONFIG 先只开 EN-INT-REF,**输出仍全部掉电**
|
||||
(datasheet:用内部基准增益之前这一位必须已经是 1)
|
||||
② 逐个用到的通道写 VOUT-CMP-CONFIG = 增益 2× 内部基准
|
||||
③ 逐个用到的通道写 `safe_code`(上电安全值),**在上电之前写**
|
||||
—— 否则输出会先在复位码 0 上停一下,那是 Vd = 0 V = 输出 4.010 V 的上限
|
||||
④ COMMON-CONFIG 再写一次,把**用到的**通道上电(没用到的保持 Hi-Z)
|
||||
每一步都回读校验,不一致 → HAL_ERR_BUS。 */
|
||||
hal_err_t dac63204_init(uint8_t addr, uint16_t safe_code);
|
||||
|
||||
/* 只校验 COMMON-CONFIG 与各通道增益还在(巡检用)。
|
||||
这两样掉了不会报错,只会让输出悄悄跑到别的量程上。 */
|
||||
hal_err_t dac63204_check(uint8_t addr);
|
||||
|
||||
/* 写 / 读某通道的 12 位码。code > 4095 → HAL_ERR_ARG;ch 越界 → HAL_ERR_ARG。 */
|
||||
hal_err_t dac63204_write_code(uint8_t addr, uint8_t ch, uint16_t code);
|
||||
hal_err_t dac63204_read_code(uint8_t addr, uint8_t ch, uint16_t *code);
|
||||
|
||||
/* 码 → DAC 输出电压 Vd(µV)。四舍五入。 */
|
||||
int32_t dac63204_code_to_uv(uint16_t code);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DAC63204_H */
|
||||
@@ -22,6 +22,7 @@ set(FW_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
# ---- 被测代码 -------------------------------------------------------------
|
||||
set(PHONERAIL_CORE_SRCS
|
||||
${FW_ROOT}/main/version.c
|
||||
${FW_ROOT}/main/rail.c
|
||||
)
|
||||
|
||||
# HAL:接口在 include/,宿主机链 sim/ 的实现(esp/ 那份只有 IDF 目标编)。
|
||||
@@ -39,6 +40,7 @@ set(PHONERAIL_HAL_SIM_SRCS
|
||||
set(PHONERAIL_DRV_DIR ${FW_ROOT}/components/drv)
|
||||
set(PHONERAIL_DRV_SRCS
|
||||
${PHONERAIL_DRV_DIR}/ina226.c
|
||||
${PHONERAIL_DRV_DIR}/dac63204.c
|
||||
)
|
||||
|
||||
add_library(phonerail_core STATIC
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
idf_component_register(
|
||||
SRCS "app_main.c"
|
||||
"version.c"
|
||||
"rail.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES phonerail_hal drv)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define PHONERAIL_H
|
||||
|
||||
/* 固件版本。规则:<主>.<次>.<修订>-<最后完成的任务包> */
|
||||
#define PHONERAIL_FW_VERSION "0.1.0-a2"
|
||||
#define PHONERAIL_FW_VERSION "0.1.0-a4"
|
||||
|
||||
/* 通道数。五路直供,见设计文档 §2。 */
|
||||
#define PHONERAIL_RAIL_COUNT 5
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/* 五路输出电压的换算与设定。与硬件无关,宿主机与 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;
|
||||
}
|
||||
|
||||
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):这里要记一条事件 */
|
||||
}
|
||||
return dac63204_write_code(dac63204_rail_addr(rail), dac63204_rail_ch(rail),
|
||||
rail_mv_to_code(mv));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/* 五路输出电压: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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RAIL_H */
|
||||
@@ -0,0 +1,293 @@
|
||||
/* A4 之一:DAC63204 驱动。
|
||||
两个验收主项在 test_rail.c(反向关系、3900 mV ↔ Vd 0.548 V);
|
||||
这里盯的是器件层的三条:**通道映射不是算出来的**、**码是左对齐的**、
|
||||
**没用到的通道不许上电**。 */
|
||||
#include "dac63204.h"
|
||||
#include "sim_hal.h"
|
||||
#include "unity.h"
|
||||
|
||||
void setUp(void) { sim_hal_reset(); }
|
||||
void tearDown(void) {}
|
||||
|
||||
/* 挂一片假 DAC63204 并预置 DEVICE-ID。 */
|
||||
static uint8_t mount(uint8_t addr)
|
||||
{
|
||||
sim_i2c_add(addr, 2);
|
||||
sim_i2c_set_reg(addr, DAC63204_REG_GENERAL_STATUS,
|
||||
(uint16_t)(DAC63204_DEVICE_ID << 2)); /* bit7-2 = ID,低两位是版本 */
|
||||
sim_i2c_set_reg(addr, DAC63204_REG_COMMON_CONFIG, 0x0FFF); /* 复位值:八路全掉电 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL));
|
||||
return addr;
|
||||
}
|
||||
|
||||
static void mount_both(void)
|
||||
{
|
||||
mount(DAC63204_ADDR_U6);
|
||||
mount(DAC63204_ADDR_U7);
|
||||
}
|
||||
|
||||
/* ---- 通道映射 ------------------------------------------------------------ */
|
||||
|
||||
/* ⭐ 设计文档 §26.18 重排之后的真表,逐路钉死。 */
|
||||
static void test_rail_map_is_the_one_from_26_18(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(DAC63204_ADDR_U6, dac63204_rail_addr(1));
|
||||
TEST_ASSERT_EQUAL_UINT8(2, dac63204_rail_ch(1));
|
||||
TEST_ASSERT_EQUAL_HEX8(DAC63204_ADDR_U6, dac63204_rail_addr(2));
|
||||
TEST_ASSERT_EQUAL_UINT8(0, dac63204_rail_ch(2));
|
||||
TEST_ASSERT_EQUAL_HEX8(DAC63204_ADDR_U7, dac63204_rail_addr(3)); /* ← 不在 U6 上 */
|
||||
TEST_ASSERT_EQUAL_UINT8(0, dac63204_rail_ch(3));
|
||||
TEST_ASSERT_EQUAL_HEX8(DAC63204_ADDR_U6, dac63204_rail_addr(4));
|
||||
TEST_ASSERT_EQUAL_UINT8(1, dac63204_rail_ch(4));
|
||||
TEST_ASSERT_EQUAL_HEX8(DAC63204_ADDR_U7, dac63204_rail_addr(5));
|
||||
TEST_ASSERT_EQUAL_UINT8(1, dac63204_rail_ch(5));
|
||||
}
|
||||
|
||||
/* ⭐ 「前三路在 U6、通道号 = rail−1」这行代码一定会被写出来(需求 §10.4 同款)。
|
||||
这条测试拦它 —— 五路里只有 CH5 碰巧对,其余四路全错。
|
||||
⚠️ 四个通道电气上完全对等,接错了板子照样正常工作,只是调 CH1 调到了 CH2。 */
|
||||
static void test_rail_map_is_not_the_naive_formula(void)
|
||||
{
|
||||
int mismatches = 0;
|
||||
for (int rail = 1; rail <= DAC63204_RAIL_COUNT; rail++) {
|
||||
uint8_t naive_addr = rail <= 3 ? DAC63204_ADDR_U6 : DAC63204_ADDR_U7;
|
||||
uint8_t naive_ch = (uint8_t)((rail - 1) % 3);
|
||||
if (dac63204_rail_addr(rail) != naive_addr || dac63204_rail_ch(rail) != naive_ch) {
|
||||
mismatches++;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(4, mismatches, "CH1~CH4 四路必须与顺排不同");
|
||||
}
|
||||
|
||||
static void test_map_has_no_collisions(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(dac63204_map_ok()); /* 含与 INA226 六片 / PCA9555 / LM75 的撞车检查 */
|
||||
}
|
||||
|
||||
/* U6 用 OUT0/1/2,U7 只用 OUT0/1。 */
|
||||
static void test_used_mask_matches_the_board(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(0x07, dac63204_used_mask(DAC63204_ADDR_U6));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x03, dac63204_used_mask(DAC63204_ADDR_U7));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x00, dac63204_used_mask(0x4C)); /* 不是这两片 */
|
||||
}
|
||||
|
||||
/* ---- 寄存器地址 ---------------------------------------------------------- */
|
||||
|
||||
/* 两种步长:配置寄存器每通道跨 6(03/09/0F/15),数据寄存器却是连续的(19~1C)。 */
|
||||
static void test_register_addresses(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(0x03, DAC63204_REG_VOUT_CMP_CONFIG(0));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x09, DAC63204_REG_VOUT_CMP_CONFIG(1));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x0F, DAC63204_REG_VOUT_CMP_CONFIG(2));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x15, DAC63204_REG_VOUT_CMP_CONFIG(3));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x19, DAC63204_REG_DATA(0));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x1A, DAC63204_REG_DATA(1));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x1B, DAC63204_REG_DATA(2));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x1C, DAC63204_REG_DATA(3));
|
||||
}
|
||||
|
||||
/* ---- 认件 ---------------------------------------------------------------- */
|
||||
|
||||
static void test_probe_accepts_real_dac63204(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_probe(mount(DAC63204_ADDR_U6)));
|
||||
}
|
||||
|
||||
/* 贴成 DAC53204(10 位,ID 02h):应答了,但码值左对齐的位数不一样,
|
||||
装上去五路电压全错 —— 必须当成 BUS 错,不能当「没这个器件」混过去。 */
|
||||
static void test_probe_rejects_the_10bit_sibling(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U7);
|
||||
sim_i2c_set_reg(a, DAC63204_REG_GENERAL_STATUS, (uint16_t)(0x02 << 2));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_BUS, dac63204_probe(a));
|
||||
}
|
||||
|
||||
static void test_probe_absent_device_naks(void)
|
||||
{
|
||||
mount(DAC63204_ADDR_U6);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, dac63204_probe(DAC63204_ADDR_U7));
|
||||
}
|
||||
|
||||
/* ---- 初始化 -------------------------------------------------------------- */
|
||||
|
||||
/* ⭐ 内部基准要先开、增益要 2×、**没用到的通道保持 Hi-Z**。 */
|
||||
static void test_init_enables_ref_and_powers_only_used_channels(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U7); /* 只用 OUT0/OUT1 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_init(a, 926));
|
||||
|
||||
uint16_t cc = sim_i2c_get_reg(a, DAC63204_REG_COMMON_CONFIG);
|
||||
TEST_ASSERT_TRUE_MESSAGE(cc & DAC63204_CC_EN_INT_REF, "内部基准没开,增益设定无效");
|
||||
TEST_ASSERT_EQUAL_HEX16_MESSAGE(0, cc & DAC63204_CC_VOUT_PDN(0), "OUT0 该上电");
|
||||
TEST_ASSERT_EQUAL_HEX16_MESSAGE(0, cc & DAC63204_CC_VOUT_PDN(1), "OUT1 该上电");
|
||||
/* OUT2/OUT3 的 FBx 悬空,上电就是开环运放。 */
|
||||
TEST_ASSERT_EQUAL_HEX16_MESSAGE(DAC63204_CC_VOUT_PDN(2), cc & DAC63204_CC_VOUT_PDN(2),
|
||||
"OUT2 没接 FB,必须保持 Hi-Z");
|
||||
TEST_ASSERT_EQUAL_HEX16_MESSAGE(DAC63204_CC_VOUT_PDN(3), cc & DAC63204_CC_VOUT_PDN(3),
|
||||
"OUT3 没接 FB,必须保持 Hi-Z");
|
||||
/* 电流输出四路一律掉电 */
|
||||
TEST_ASSERT_EQUAL_HEX16(DAC63204_CC_IOUT_PDN_ALL, cc & DAC63204_CC_IOUT_PDN_ALL);
|
||||
|
||||
TEST_ASSERT_EQUAL_HEX16(DAC63204_CFG_GAIN_2X_INT,
|
||||
sim_i2c_get_reg(a, DAC63204_REG_VOUT_CMP_CONFIG(0)));
|
||||
TEST_ASSERT_EQUAL_HEX16(DAC63204_CFG_GAIN_2X_INT,
|
||||
sim_i2c_get_reg(a, DAC63204_REG_VOUT_CMP_CONFIG(1)));
|
||||
/* 没用到的通道连增益都不该被碰 */
|
||||
TEST_ASSERT_EQUAL_HEX16(0, sim_i2c_get_reg(a, DAC63204_REG_VOUT_CMP_CONFIG(2)));
|
||||
}
|
||||
|
||||
/* 安全码必须在**上电之前**就写进去:复位码是 0,而 Vd=0 = 输出 4.010 V 的上限。
|
||||
这条盯两件事:①第一次写进 COMMON-CONFIG 的值把八路输出全按住(`CC_REF_ONLY`);
|
||||
②COMMON-CONFIG 确实写了**两次**(写一次 = 上电和写码挤在一起了)。
|
||||
⚠️ sim 只留得下寄存器终值,看不到中间态,所以顺序是靠这两条间接钉的。 */
|
||||
static void test_init_writes_safe_code_before_powering_up(void)
|
||||
{
|
||||
/* ① 的那个值本身:基准已开,但四路输出一个都没上电。 */
|
||||
TEST_ASSERT_TRUE(DAC63204_CC_REF_ONLY & DAC63204_CC_EN_INT_REF);
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
TEST_ASSERT_EQUAL_HEX16_MESSAGE(DAC63204_CC_VOUT_PDN(ch),
|
||||
DAC63204_CC_REF_ONLY & DAC63204_CC_VOUT_PDN(ch),
|
||||
"第一步就不许有通道上电");
|
||||
}
|
||||
|
||||
uint8_t a = mount(DAC63204_ADDR_U6);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_init(a, 926));
|
||||
for (uint8_t ch = 0; ch < 3; ch++) {
|
||||
TEST_ASSERT_EQUAL_HEX16(DAC63204_CODE_TO_REG(926),
|
||||
sim_i2c_get_reg(a, DAC63204_REG_DATA(ch)));
|
||||
}
|
||||
/* 八次「写 + 回读」:①ref-only、②③三个通道各 gain+data、④run。
|
||||
每次回读是一次 read,所以 n_read 就是 write_verify 的次数。 */
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(1 + 3 * 2 + 1, sim_i2c_dev(a)->n_read,
|
||||
"COMMON-CONFIG 必须写两次:先按住,写完码再上电");
|
||||
}
|
||||
|
||||
/* 跑起来之后 COMMON-CONFIG 该是什么,两片各钉一遍。 */
|
||||
static void test_cc_run_values(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX16(0x1E49, dac63204_cc_run(DAC63204_ADDR_U6)); /* 只 OUT3 掉电 */
|
||||
TEST_ASSERT_EQUAL_HEX16(0x1FC9, dac63204_cc_run(DAC63204_ADDR_U7)); /* OUT2/OUT3 掉电 */
|
||||
}
|
||||
|
||||
static void test_init_rejects_unknown_chip_and_bad_code(void)
|
||||
{
|
||||
mount(DAC63204_ADDR_U6);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, dac63204_init(0x4C, 926)); /* 不是本板的片 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, dac63204_init(DAC63204_ADDR_U6, 4096)); /* 码越界 */
|
||||
}
|
||||
|
||||
static void test_check_catches_lost_gain(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U6);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_init(a, 926));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_check(a));
|
||||
sim_i2c_set_reg(a, DAC63204_REG_VOUT_CMP_CONFIG(1), 0); /* 增益掉回 1×、外部基准 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_BUS, dac63204_check(a));
|
||||
}
|
||||
|
||||
static void test_check_catches_lost_common_config(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U7);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_init(a, 926));
|
||||
sim_i2c_set_reg(a, DAC63204_REG_COMMON_CONFIG, 0x0FFF); /* 掉回复位值 = 输出全掉电 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_BUS, dac63204_check(a));
|
||||
}
|
||||
|
||||
/* ---- 码的写读 ------------------------------------------------------------ */
|
||||
|
||||
/* ⭐ 12 位码在 DAC-X-DATA 里是**左对齐**的(bit15-4)。
|
||||
忘了 `<< 4` 输出只有期望的 1/16,而那在本板上表现为「满电 4.010 V」,看不出异常。 */
|
||||
static void test_code_is_left_aligned_in_the_data_register(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U6);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_write_code(a, 2, 0x123));
|
||||
TEST_ASSERT_EQUAL_HEX16_MESSAGE(0x1230, sim_i2c_get_reg(a, DAC63204_REG_DATA(2)),
|
||||
"码必须左移 4 位");
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_write_code(a, 2, DAC63204_CODE_MAX));
|
||||
TEST_ASSERT_EQUAL_HEX16(0xFFF0, sim_i2c_get_reg(a, DAC63204_REG_DATA(2)));
|
||||
}
|
||||
|
||||
static void test_read_code_round_trips(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U7);
|
||||
uint16_t back = 0;
|
||||
for (uint16_t code = 0; code <= DAC63204_CODE_MAX; code += 273) {
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_write_code(a, 1, code));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_read_code(a, 1, &back));
|
||||
TEST_ASSERT_EQUAL_UINT16(code, back);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_write_code_rejects_bad_args(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U6);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, dac63204_write_code(a, 4, 100));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, dac63204_write_code(a, 0, 4096));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, dac63204_read_code(a, 0, NULL));
|
||||
}
|
||||
|
||||
/* 每次读之前重写指针 —— sim 的假器件会自增,真器件不一定(和 INA226 同一条坑)。 */
|
||||
static void test_pointer_is_rewritten_before_every_read(void)
|
||||
{
|
||||
uint8_t a = mount(DAC63204_ADDR_U6);
|
||||
sim_i2c_set_reg(a, DAC63204_REG_DATA(0), 0x1110);
|
||||
sim_i2c_set_reg(a, DAC63204_REG_DATA(1), 0x2220);
|
||||
uint16_t c0 = 0, c1 = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_read_code(a, 0, &c0));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, dac63204_read_code(a, 1, &c1));
|
||||
TEST_ASSERT_EQUAL_HEX16(0x111, c0);
|
||||
TEST_ASSERT_EQUAL_HEX16(0x222, c1); /* 靠自增的话这里会读成 0x111 的下一格 */
|
||||
}
|
||||
|
||||
/* ---- 量程 ---------------------------------------------------------------- */
|
||||
|
||||
/* 满量程是 1.212 V × 2 = 2.424 V,不是 2.5 V(设计文档 §4.5 早期写错过)。 */
|
||||
static void test_fullscale_is_2424_mv_not_2500(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT32(0, dac63204_code_to_uv(0));
|
||||
TEST_ASSERT_INT32_WITHIN(1, 2423408, dac63204_code_to_uv(DAC63204_CODE_MAX));
|
||||
TEST_ASSERT_INT32_WITHIN(1, 1212000, dac63204_code_to_uv(2048));
|
||||
}
|
||||
|
||||
static void test_bus_not_initialised_propagates(void)
|
||||
{
|
||||
/* 不调 hal_i2c_init():驱动不许把「总线没起来」吞掉。 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, dac63204_probe(DAC63204_ADDR_U6));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, dac63204_write_code(DAC63204_ADDR_U6, 0, 100));
|
||||
}
|
||||
|
||||
static void test_nak_propagates(void)
|
||||
{
|
||||
mount_both();
|
||||
sim_i2c_inject_nak(DAC63204_ADDR_U6, -1);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, dac63204_init(DAC63204_ADDR_U6, 926));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, dac63204_check(DAC63204_ADDR_U6));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_rail_map_is_the_one_from_26_18);
|
||||
RUN_TEST(test_rail_map_is_not_the_naive_formula);
|
||||
RUN_TEST(test_map_has_no_collisions);
|
||||
RUN_TEST(test_used_mask_matches_the_board);
|
||||
RUN_TEST(test_register_addresses);
|
||||
RUN_TEST(test_probe_accepts_real_dac63204);
|
||||
RUN_TEST(test_probe_rejects_the_10bit_sibling);
|
||||
RUN_TEST(test_probe_absent_device_naks);
|
||||
RUN_TEST(test_init_enables_ref_and_powers_only_used_channels);
|
||||
RUN_TEST(test_init_writes_safe_code_before_powering_up);
|
||||
RUN_TEST(test_cc_run_values);
|
||||
RUN_TEST(test_init_rejects_unknown_chip_and_bad_code);
|
||||
RUN_TEST(test_check_catches_lost_gain);
|
||||
RUN_TEST(test_check_catches_lost_common_config);
|
||||
RUN_TEST(test_code_is_left_aligned_in_the_data_register);
|
||||
RUN_TEST(test_read_code_round_trips);
|
||||
RUN_TEST(test_write_code_rejects_bad_args);
|
||||
RUN_TEST(test_pointer_is_rewritten_before_every_read);
|
||||
RUN_TEST(test_fullscale_is_2424_mv_not_2500);
|
||||
RUN_TEST(test_bus_not_initialised_propagates);
|
||||
RUN_TEST(test_nak_propagates);
|
||||
return UNITY_END();
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/* A4 之二:`rail_set_mv()` —— 三个验收主项(固件开发计划 §5 A4 那一行):
|
||||
① 4020 mV 被拒(SR-13)
|
||||
② **写更大的码值,输出更低**(反向关系)
|
||||
③ 3900 mV ↔ Vd 0.548 V
|
||||
都在下面打了 ⭐。 */
|
||||
#include "dac63204.h"
|
||||
#include "phonerail.h"
|
||||
#include "rail.h"
|
||||
#include "sim_hal.h"
|
||||
#include "unity.h"
|
||||
|
||||
void setUp(void) { sim_hal_reset(); }
|
||||
void tearDown(void) {}
|
||||
|
||||
/* 两片假 DAC 都挂上,并预置 DEVICE-ID 与 COMMON-CONFIG 的复位值。 */
|
||||
static void mount_both(void)
|
||||
{
|
||||
for (int i = 0; i < DAC63204_COUNT; i++) {
|
||||
uint8_t a = dac63204_all_addr[i];
|
||||
sim_i2c_add(a, 2);
|
||||
sim_i2c_set_reg(a, DAC63204_REG_GENERAL_STATUS, (uint16_t)(DAC63204_DEVICE_ID << 2));
|
||||
sim_i2c_set_reg(a, DAC63204_REG_COMMON_CONFIG, 0x0FFF);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL));
|
||||
}
|
||||
|
||||
/* 直接从假器件的寄存器里把某一路的码抠出来(绕开被测代码自己的读路径)。 */
|
||||
static uint16_t raw_code(int rail)
|
||||
{
|
||||
uint16_t reg = sim_i2c_get_reg(dac63204_rail_addr(rail),
|
||||
DAC63204_REG_DATA(dac63204_rail_ch(rail)));
|
||||
return DAC63204_REG_TO_CODE(reg);
|
||||
}
|
||||
|
||||
/* ---- ① 越界即拒(SR-13) --------------------------------------------------- */
|
||||
|
||||
/* ⭐ 验收主项一:4020 mV 必须被拒,而且**一个字节都不许发出去**。
|
||||
这是第四道限制,前三道在硬件里(设计文档 §4.1)—— 它的作用是让操作者早点
|
||||
知道自己写错了,不是靠它保安全。 */
|
||||
static void test_4020_mv_is_rejected(void)
|
||||
{
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
|
||||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||||
uint16_t before = raw_code(rail);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(HAL_ERR_ARG, rail_set_mv(rail, 4020), "4020 mV 必须被拒");
|
||||
TEST_ASSERT_EQUAL_UINT16_MESSAGE(before, raw_code(rail), "被拒的请求不许改到 DAC");
|
||||
}
|
||||
TEST_ASSERT_FALSE(rail_mv_in_range(4011));
|
||||
TEST_ASSERT_TRUE(rail_mv_in_range(RAIL_MV_MAX));
|
||||
}
|
||||
|
||||
/* 下限同样拒:DAC 满码也到不了 3523 mV,悄悄夹到 3524 就成了「设了但没设到」。 */
|
||||
static void test_below_range_is_rejected(void)
|
||||
{
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_set_mv(1, RAIL_MV_MIN - 1));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_set_mv(1, 0));
|
||||
TEST_ASSERT_FALSE(rail_mv_in_range(3523));
|
||||
TEST_ASSERT_TRUE(rail_mv_in_range(RAIL_MV_MIN));
|
||||
}
|
||||
|
||||
static void test_rail_index_is_one_based(void)
|
||||
{
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_set_mv(0, RAIL_MV_SAFE));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_set_mv(6, RAIL_MV_SAFE));
|
||||
int mv = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_get_mv(0, &mv));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_get_mv(1, NULL));
|
||||
}
|
||||
|
||||
/* ---- ② 反向关系 ---------------------------------------------------------- */
|
||||
|
||||
/* ⭐ 验收主项二:**码越大,输出越低。**
|
||||
写反了不报错 —— 「调高电压」的命令把手机调到 3.52 V,手机照样开机,
|
||||
要跑一阵子才在负载尖峰下掉电。所以这里既查换算,也查真写进 DAC 的那个数。 */
|
||||
static void test_higher_code_means_lower_voltage(void)
|
||||
{
|
||||
/* 换算层:单调**递减**,端点钉死。 */
|
||||
TEST_ASSERT_EQUAL_UINT16(0, rail_mv_to_code(RAIL_MV_MAX));
|
||||
TEST_ASSERT_TRUE(rail_mv_to_code(RAIL_MV_MIN) > rail_mv_to_code(RAIL_MV_MAX));
|
||||
TEST_ASSERT_TRUE(rail_mv_to_code(3600) > rail_mv_to_code(4000));
|
||||
|
||||
uint16_t prev = rail_mv_to_code(RAIL_MV_MIN);
|
||||
for (int mv = RAIL_MV_MIN + 1; mv <= RAIL_MV_MAX; mv++) {
|
||||
uint16_t code = rail_mv_to_code(mv);
|
||||
TEST_ASSERT_TRUE_MESSAGE(code <= prev, "mv 变大,码必须变小或持平");
|
||||
prev = code;
|
||||
}
|
||||
|
||||
/* 总线层:同一路先设高再设低,DAC 里的数必须变大。 */
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_set_mv(1, 4000));
|
||||
uint16_t at_4000 = raw_code(1);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_set_mv(1, 3600));
|
||||
uint16_t at_3600 = raw_code(1);
|
||||
TEST_ASSERT_TRUE_MESSAGE(at_3600 > at_4000, "3.60 V 的码必须比 4.00 V 的大");
|
||||
}
|
||||
|
||||
/* ---- ③ 3900 mV ↔ Vd 0.548 V --------------------------------------------- */
|
||||
|
||||
/* ⭐ 验收主项三:NVM 出厂默认那一点(设计文档 §4.3 / §10.5 的表)。 */
|
||||
static void test_3900_mv_is_vd_548_mv(void)
|
||||
{
|
||||
uint16_t code = rail_mv_to_code(3900);
|
||||
TEST_ASSERT_EQUAL_UINT16(926, code);
|
||||
/* Vd = 926 / 4096 × 2.424 V = 548.0 mV */
|
||||
TEST_ASSERT_INT32_WITHIN_MESSAGE(500, 548000, rail_code_to_vd_uv(code),
|
||||
"3900 mV 对应的 Vd 必须是 0.548 V");
|
||||
TEST_ASSERT_EQUAL_INT(3900, rail_code_to_mv(code));
|
||||
}
|
||||
|
||||
/* 设计文档 §4.3 那三个角点,一个不落。 */
|
||||
static void test_transfer_curve_endpoints(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT(RAIL_MV_MAX, rail_code_to_mv(0)); /* Vd 0 V -> 4.010 V */
|
||||
TEST_ASSERT_EQUAL_INT(3900, rail_code_to_mv(926)); /* Vd 0.548 -> 3.900 V */
|
||||
TEST_ASSERT_EQUAL_INT(3524, rail_code_to_mv(DAC63204_CODE_MAX)); /* 满码 -> 3.524 V */
|
||||
|
||||
/* 上下限都必须落在 12 位码里 —— 越界说明 RAIL_MV_MIN/MAX 和电阻值对不上了。 */
|
||||
TEST_ASSERT_TRUE(rail_mv_to_code(RAIL_MV_MIN) <= DAC63204_CODE_MAX);
|
||||
TEST_ASSERT_TRUE(rail_mv_to_code(RAIL_MV_MAX) <= DAC63204_CODE_MAX);
|
||||
}
|
||||
|
||||
/* 全量程往返:设进去再读回来,误差不超过 1 mV(一个码在输出端 0.119 mV)。 */
|
||||
static void test_set_get_round_trip(void)
|
||||
{
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||||
for (int mv = RAIL_MV_MIN; mv <= RAIL_MV_MAX; mv += 7) {
|
||||
int back = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_set_mv(rail, mv));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_get_mv(rail, &back));
|
||||
TEST_ASSERT_INT_WITHIN(1, mv, back);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 五路互不串扰 --------------------------------------------------------- */
|
||||
|
||||
/* 改一路不许动到别的四路。CH1 与 CH2 在同一片(U6)上,是最容易串的一对。 */
|
||||
static void test_setting_one_rail_leaves_the_others_alone(void)
|
||||
{
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
|
||||
uint16_t before[PHONERAIL_RAIL_COUNT + 1];
|
||||
for (int r = 1; r <= PHONERAIL_RAIL_COUNT; r++) {
|
||||
before[r] = raw_code(r);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_set_mv(1, 3600));
|
||||
for (int r = 2; r <= PHONERAIL_RAIL_COUNT; r++) {
|
||||
TEST_ASSERT_EQUAL_UINT16_MESSAGE(before[r], raw_code(r), "只该动 CH1");
|
||||
}
|
||||
TEST_ASSERT_TRUE(raw_code(1) != before[1]);
|
||||
}
|
||||
|
||||
/* 五路各写一个不同的值,逐路读回来必须各是各的 —— 映射表串了这条就红。 */
|
||||
static void test_each_rail_keeps_its_own_value(void)
|
||||
{
|
||||
static const int want[PHONERAIL_RAIL_COUNT] = { 3600, 3700, 3800, 3900, 4000 };
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
for (int r = 1; r <= PHONERAIL_RAIL_COUNT; r++) {
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_set_mv(r, want[r - 1]));
|
||||
}
|
||||
for (int r = 1; r <= PHONERAIL_RAIL_COUNT; r++) {
|
||||
int back = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_get_mv(r, &back));
|
||||
TEST_ASSERT_INT_WITHIN(1, want[r - 1], back);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 初始化 -------------------------------------------------------------- */
|
||||
|
||||
/* 上电即安全值:init 完五路都应该在 3.90 V 上,不依赖后续有人来设。 */
|
||||
static void test_init_leaves_every_rail_at_the_safe_value(void)
|
||||
{
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
for (int r = 1; r <= PHONERAIL_RAIL_COUNT; r++) {
|
||||
int mv = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_get_mv(r, &mv));
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(RAIL_MV_SAFE, mv, "上电安全值必须是 3900 mV");
|
||||
}
|
||||
}
|
||||
|
||||
/* 少一片 DAC:init 必须如实失败(降级留给 A15,这里不许悄悄当成功)。 */
|
||||
static void test_init_fails_when_a_chip_is_missing(void)
|
||||
{
|
||||
sim_i2c_add(DAC63204_ADDR_U6, 2);
|
||||
sim_i2c_set_reg(DAC63204_ADDR_U6, DAC63204_REG_GENERAL_STATUS,
|
||||
(uint16_t)(DAC63204_DEVICE_ID << 2));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, rail_volt_init()); /* U7 没挂 */
|
||||
}
|
||||
|
||||
static void test_set_propagates_bus_errors(void)
|
||||
{
|
||||
mount_both();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_volt_init());
|
||||
sim_i2c_set_present(dac63204_rail_addr(3), false);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, rail_set_mv(3, 3800));
|
||||
int mv = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, rail_get_mv(3, &mv));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_4020_mv_is_rejected);
|
||||
RUN_TEST(test_below_range_is_rejected);
|
||||
RUN_TEST(test_rail_index_is_one_based);
|
||||
RUN_TEST(test_higher_code_means_lower_voltage);
|
||||
RUN_TEST(test_3900_mv_is_vd_548_mv);
|
||||
RUN_TEST(test_transfer_curve_endpoints);
|
||||
RUN_TEST(test_set_get_round_trip);
|
||||
RUN_TEST(test_setting_one_rail_leaves_the_others_alone);
|
||||
RUN_TEST(test_each_rail_keeps_its_own_value);
|
||||
RUN_TEST(test_init_leaves_every_rail_at_the_safe_value);
|
||||
RUN_TEST(test_init_fails_when_a_chip_is_missing);
|
||||
RUN_TEST(test_set_propagates_bus_errors);
|
||||
return UNITY_END();
|
||||
}
|
||||
+18
-1
@@ -2236,9 +2236,26 @@ ESP32 复位期间 GPIO 高阻,595 输出随之高阻,配合 P2 上每个栅
|
||||
100nF 太小。
|
||||
3. **VREF 不用外部基准时,要 100 nF 到地 + 上拉电阻到 VDD**。
|
||||
|
||||
通道分配:U6 的 OUT0/OUT1/OUT2 = VDAC1/2/3,U7 的 OUT0/OUT1 = VDAC4/5;
|
||||
通道分配(**2026-09-06 按 PCB 几何重排,取代 09-03 那一版**,§26.18):
|
||||
|
||||
| 路 | 片 | 通道 |
|
||||
|---|---|---|
|
||||
| `VDAC1` | U6(0x4A) | **OUT2** |
|
||||
| `VDAC2` | U6 | **OUT0** |
|
||||
| `VDAC3` | **U7(0x4B)** | OUT0 |
|
||||
| `VDAC4` | U6 | **OUT1** |
|
||||
| `VDAC5` | U7 | OUT1 |
|
||||
|
||||
U6.OUT3、U7.OUT2/OUT3 未用(FB 也不接,datasheet 说未用通道保持 FB 悬空)。
|
||||
|
||||
> ⚠️ **「前三路在 U6、通道号 = rail−1」是旧版**,五路里有四路对不上。
|
||||
> 四个通道电气上完全对等,所以接错了**板子照样正常工作,只是调 CH1 的命令去调了 CH2**,
|
||||
> 功能测试完全测不出来。固件里查表不要算:`dac63204_rail_addr()` / `dac63204_rail_ch()`
|
||||
> (A4 已落地,`test_rail_map_is_not_the_naive_formula` 专门拦这一手)。
|
||||
>
|
||||
> ⚠️ **未用通道不许上电**:FBx 悬空时电压输出模式是开环运放。
|
||||
> `COMMON-CONFIG` 里 U6 = `0x1E49`(OUT3 保持 Hi-Z)、U7 = `0x1FC9`(OUT2/OUT3 保持 Hi-Z)。
|
||||
|
||||
> **内部基准是 1.212 V(1.1979/1.212/1.224),不是 1.25 V。**
|
||||
> 增益 2× → 满量程 **2.424 V**,不是 2.5 V。§4 的公式按 0~2.5V 推的,
|
||||
> 实际 Vd 上限 2.424V,所以输出下限是 **3.524 V** 而不是 3.508 V(更安全,见 §4.4)。
|
||||
|
||||
@@ -434,6 +434,21 @@ typedef struct {
|
||||
3. **出厂标定**:分压网络与 DAC 基准都有 ±1 % 级的容差,用该路 INA226 读回实际输出,
|
||||
做一次两点线性标定存进 NVS。标定值缺失时退回理论公式(误差约 ±40 mV,可用)。
|
||||
|
||||
> **A4 已落地**:换算与设定在 `firmware/main/rail.c`,唯一入口 `rail_set_mv(rail, mv)`
|
||||
> (rail 是 **1~5**)。三个验收点各有一条测试:4020 mV 被拒、码大电压低、3900 mV ↔ Vd 0.548 V。
|
||||
> 标定(本节规矩 3)**还没做**,现在走的是理论公式;`RAIL_VOUT0_UV` / `RAIL_DAC_SPAN_UV`
|
||||
> 两个常量就是将来要被标定值顶替的地方。
|
||||
>
|
||||
> 同处记下三条 DAC63204 的实现约定(出处 SLASEX3A):
|
||||
> - **12 位码在 `DAC-X-DATA`(19h~1Ch) 里是左对齐的**(bit15-4)。忘了 `<< 4` 不报错,
|
||||
> 只让输出变成 1/16 —— 而在本板上那表现为「五路都是 4.010 V 满电」,看不出异常。
|
||||
> - **`EN-INT-REF` 要先于增益设定写**,且**上电安全码要写在通道上电之前**:
|
||||
> 复位码是 0,而 Vd = 0 就是输出上限 4.010 V。`dac63204_init()` 因此把
|
||||
> `COMMON-CONFIG` 写两次(先按住输出,写完码再上电)。
|
||||
> - **通道分配不是 `rail−1`**(设计文档 §26.18 重排过,五路里四路对不上):
|
||||
> CH1→U6.OUT2 / CH2→U6.OUT0 / CH3→**U7**.OUT0 / CH4→U6.OUT1 / CH5→U7.OUT1。
|
||||
> 四通道电气对等 ⇒ 接错了照样工作,只是调错路,功能测试测不出来。
|
||||
|
||||
### 10.6 执行器:三个状态机 + 一个影子寄存器
|
||||
|
||||
**每路三个序列,彼此互斥、不可重入**(同一路上同时只能跑一个):
|
||||
|
||||
在新工单中引用
屏蔽一个用户