From 2b4c0e97c2d40f705d3885f78feaa4cd366266d1 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Tue, 27 Oct 2026 17:25:51 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20ADC=20=E6=B3=A8=E5=85=A5=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=20=C2=A74=20=E8=A1=A5=E7=A0=81=E5=80=BC=E5=88=BB?= =?UTF-8?q?=E5=BA=A6=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §4 的「码」定义表只有文字与数字,台架现场容易把 TPAFE 的 152.59 µV 与 STM32 的 805.66 µV 混着用。新增一张刻度尺对照 图(docs/images/ADC码值刻度.svg),上下两把尺子分别标出 11430 码 ≈ 1.744 V 与 1000 码 ≈ 0.806 V,把"同一整数在两片 转换器上代表不同电压"变成可见的;正文补一句两把尺子差 5.3 倍、§9 每格必须写明所属通道。 生成器 tools/arch_diagram/gen_adc_code_scale_svg.py 沿用图形绘制 流程 §3.2 约定(纯标准库 / html.escape / newline="\n" / ElementTree 自检 + 文字溢出预检);码制与 LSB 数据集中在 PANELS 常量,换芯片才重画。已登记到 tools/arch_diagram/README.md 与 docs/架构设计/图形绘制流程.md 的生成器表 + 变更记录。 目检用无头 Chrome 截图完成(cairosvg 未装,截图不入库)。 --- docs/images/ADC码值刻度.svg | 58 ++++++ docs/架构设计/图形绘制流程.md | 2 + docs/测试说明/ADC前端注入验证流程.md | 9 + tools/arch_diagram/README.md | 1 + tools/arch_diagram/gen_adc_code_scale_svg.py | 186 +++++++++++++++++++ 5 files changed, 256 insertions(+) create mode 100644 docs/images/ADC码值刻度.svg create mode 100644 tools/arch_diagram/gen_adc_code_scale_svg.py diff --git a/docs/images/ADC码值刻度.svg b/docs/images/ADC码值刻度.svg new file mode 100644 index 00000000..d5876c4c --- /dev/null +++ b/docs/images/ADC码值刻度.svg @@ -0,0 +1,58 @@ + + +「码」是 ADC 的最小刻度 —— 两片转换器的刻度大小不同 +ADC 输出的是整数(码),不是电压;电压 = 码 × LSB,所以要先知道「1 码值多少伏」 +TPAFE5160:16 位双极性,65536 码 = 10 V 跨度 · STM32 ADC1:12 位单极性,4096 码 = 3.3 V 跨度 +A · TPAFE5160(16 位双极性) + +码范围 −32768 ~ +32767 · 每 1 码 = 152.59 µV + + + + + + + + + + + + + + + + + + + + + + +−32768 ++32767 +0 + + +11430 码 ≈ 1.744 V +B · STM32 ADC1(12 位单极性) + +码范围 0 ~ 4095 · 每 1 码 = 805.66 µV + + + + + + + + + + +0 +4095 + + +1000 码 ≈ 0.806 V + +刻度疏密仅示意位数差(16 位比 12 位细),不是逐码绘制;1 码的电压当量见各行右侧标注。 +两个换算值都来自台架实测码值:11430 与 cal: 行的 1743 mV 自洽,说明「码 × LSB」这条链是通的。 + diff --git a/docs/架构设计/图形绘制流程.md b/docs/架构设计/图形绘制流程.md index a61cb5a5..0d9820ae 100644 --- a/docs/架构设计/图形绘制流程.md +++ b/docs/架构设计/图形绘制流程.md @@ -50,6 +50,7 @@ | `gen_memory_svg.py` | `docs/images/内存布局_*.svg` | RAM / FLASH 布局 | | `gen_watchdog_svg.py` | `docs/images/看门狗心跳模型.svg` | `TASKS` / `REQ_BITS` / `POLL_STEPS` / `TIMELINE` | | `gen_adc_frontend_svg.py` | `docs/images/ADC前端电路.svg` | `SUB1` / `NOTE_*` 等文案常量 + 面板坐标(电路拓扑固定,网表变了才重画) | +| `gen_adc_code_scale_svg.py` | `docs/images/ADC码值刻度.svg` | `PANELS` 列表(码范围 / LSB / 实测码标记位置)+ `FOOT`(换芯片才重画) | --- @@ -144,3 +145,4 @@ sequenceDiagram | 2026-09-11 | 存量 ASCII 框图清理:`STM32F4-Base存储架构说明.md` 两图(存储栈分层 / 物理分区布局)重绘为 SVG(生成器家族新增 `gen_storage_stack_svg.py` / `gen_partition_svg.py`);`RemoDispBus协议与接收设计说明.md` 删 ASCII 帧图(SVG 帧格式图已在,信息无损失);`测试说明/CH395F_Test_Guide.md` 4 图(2 组件架构 + 2 会话时序)转 Mermaid | | 2026-10-22 | 澄清 §派生物一句:`simulator/tests/ref/` 下作为像素黄金参考的 `.bin` 已入库,只有派生的 `.png` 目检图仍不入库(原表述把两者混为"参考图不入库",会诱导后来者把整个目录继续忽略掉) | | 2026-10-27 | 新增 `gen_adc_frontend_svg.py` → `docs/images/ADC前端电路.svg`:把 `docs/硬件平台/ADC通道映射说明.md` §7.1 与 `docs/测试说明/ADC前端注入验证流程.md` §2 的 ASCII 拓扑图换成 SVG。ASCII 版把滤波支路画成挂在信号节点下的一根竖线,读起来像 2:1 分压——恰是要否掉的结论;SVG 把 `ADC7` / `ADC7_GND` 画成两个节点并加 STM32 侧对照面板。生成器沿用 §3.2 约定(纯标准库 / `html.escape` / `newline="\n"` / `ElementTree` 自检 + 文字溢出预检);目检用无头 Chrome 截图完成(`cairosvg` 未装,截图不入库) | +| 2026-10-27 | 新增 `gen_adc_code_scale_svg.py` → `docs/images/ADC码值刻度.svg`:给 `docs/测试说明/ADC前端注入验证流程.md` §4 的「码」定义配图。两张刻度尺上下对照,标出 `11430 码 ≈ 1.744 V`(TPAFE)与 `1000 码 ≈ 0.806 V`(STM32),把"同一整数在两片转换器上代表不同电压"变成可见的,避免台架抄数时混用两套 LSB | diff --git a/docs/测试说明/ADC前端注入验证流程.md b/docs/测试说明/ADC前端注入验证流程.md index 1e69841d..039240c4 100644 --- a/docs/测试说明/ADC前端注入验证流程.md +++ b/docs/测试说明/ADC前端注入验证流程.md @@ -87,6 +87,14 @@ | TPAFE5160 | 16 位双极性 | −32768 ~ +32767(共 65536 码 = 10 V 跨度) | 152.59 µV | 11430 码 × 152.59 µV ≈ **1.744 V** | | STM32 ADC1 | 12 位单极性 | 0 ~ 4095(共 4096 码 = 3.3 V 跨度) | 805.66 µV | 1000 码 × 805.66 µV ≈ **0.806 V** | +同一个整数落在两把刻度尺上代表不同电压(图由 `tools/arch_diagram/gen_adc_code_scale_svg.py` +生成,改图请改脚本重跑,勿手改 SVG): + +![两把刻度尺:TPAFE5160 16 位双极性 −32768~+32767、1 码 152.59 µV、11430 码≈1.744 V;STM32 ADC1 12 位单极性 0~4095、1 码 805.66 µV、1000 码≈0.806 V](../images/ADC码值刻度.svg) + +**两把尺子不能混用**:同样是"1000",在 TPAFE 上是 0.153 V,在 STM32 上是 0.806 V,差 5.3 倍。 +所以 §9 记录表里每一格都必须写明是哪一路(UA/UB/… 属 TPAFE,T 属 STM32),抄错一行全盘结论作废。 + 本文一律用"码"而不是"伏",有两个理由:① 固件 `adc:` 行打印的就是码,抄数不用心算; ② **标定还没做,把码换成伏正是本次要验证的事**——先换算再用,等于默认了尚未验证的系数。 判据里的"码值绝对值 ≤ 10"= 短接后读数离 0 不超过 10 个刻度,即 TPAFE 侧 ≈1.5 mV、 @@ -274,3 +282,4 @@ TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性, | 2026-10-27 | **§5.1 步骤 0 判据更正**:原写"端子侧应量到 1 kΩ 量级(串阻 + 转换器输入阻抗**并联**)"两处错——① 端子到 AGND 是**串联**通路(1k + 芯片 1 MΩ + 1k ≈ 1.002 MΩ),1 kΩ 只在**串阻两端**成立;② 芯片输入阻抗是 **1 MΩ 恒定**(`docs/芯片手册/TPAFE5160.md` "Analog Input Impedance" 节),不是可与 1 kΩ 并联出 1 kΩ 的量级。改为三行分测表(串阻两端 / TPAFE 侧对 AGND / STM32 侧对 AGND),并写明本步**只排除短路、不验收正确性**;§8 已知坑补对应一行。`docs/硬件平台/ADC通道映射说明.md` §7"前端偏置"行同步补 1 MΩ 与源阻抗匹配依据 | | 2026-10-27 | **§5.2 表格可读性修正**:原表单元格里写了 `\|码\|`(未转义竖线),被 GFM 当成列分隔符,整表渲染成乱码;改为"码值绝对值 ≤ 10 / ≤ 5"(§6 判据汇总同病同修)。同时 §5.2 补"做什么"四步(断电短接 → 上电读 `adc:` → 抄进 §9.1 → 断电换下一路),并明确**本节只给判据、要填的表在 §9.1**;§9.1 记录表新增"空载码值(浮空)"列与填法说明,使空载与短接两个数都被记下来 | | 2026-10-27 | **§4 补「码」的定义**:用户反馈"码这个单位第一次见"。补 16 位双极性(−32768~+32767、1 码 152.59 µV)与 12 位单极性(0~4095、1 码 805.66 µV)两个刻度对照表、换算示例(11430 码 ≈ 1.744 V,与 `cal:` 行的 1743 mV 自洽),并写明本文为何一律用码而不用伏:固件打印的就是码,且**把码换成伏正是本次要验证的事** | +| 2026-10-27 | §4 的「码」定义表配刻度尺图(`docs/images/ADC码值刻度.svg`,生成器 `tools/arch_diagram/gen_adc_code_scale_svg.py`):上下两张刻度尺分别标出 `11430 码 ≈ 1.744 V` 与 `1000 码 ≈ 0.806 V`,把"同一个整数在两片转换器上代表不同电压"画出来;正文补一句两把尺子差 5.3 倍、§9 每格必须写明所属通道,防止台架抄数时混用两套 LSB | diff --git a/tools/arch_diagram/README.md b/tools/arch_diagram/README.md index 7263ec9a..ffe40c2f 100644 --- a/tools/arch_diagram/README.md +++ b/tools/arch_diagram/README.md @@ -17,6 +17,7 @@ | `gen_partition_svg.py` | 分区图 | 存储分区 | | `gen_watchdog_svg.py` | 看门狗心跳模型图 | IWDG + 任务心跳位图(置位→判定→喂狗 + 时间轴) | | `gen_adc_frontend_svg.py` | ADC 前端电路图 | 端子 → 串阻 → 转换节点 → 转换器引脚;含差分负端与 STM32 侧对照 | +| `gen_adc_code_scale_svg.py` | ADC 码值刻度图 | 「码」的直观定义:两片转换器的刻度尺对照(16 位双极性 vs 12 位单极性) | ## 使用方式 diff --git a/tools/arch_diagram/gen_adc_code_scale_svg.py b/tools/arch_diagram/gen_adc_code_scale_svg.py new file mode 100644 index 00000000..206c929d --- /dev/null +++ b/tools/arch_diagram/gen_adc_code_scale_svg.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""生成 docs/images/ADC码值刻度.svg —— 解释「码」是什么:ADC 输出的整数是刻度数, +电压 = 码 × LSB,而两片转换器的 LSB 不同(纯标准库)。 + +为什么不用 Mermaid:这是**两条刻度尺的对照**,要让人一眼看出"同一个整数在两片转换器上 +代表不同电压",靠的是刻度疏密与标注对齐,Mermaid 的流程图语义完全表达不了。 + +用法(仓库根目录,任意 python3): + python tools/arch_diagram/gen_adc_code_scale_svg.py +输出: + docs/images/ADC码值刻度.svg +改内容:码制与 LSB 由转换器决定,换芯片才重画;调坐标 / 文案编辑下方常量后重跑。 +""" +import html +import os +import xml.etree.ElementTree as ET + +ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +OUT = os.path.join(ROOT, "docs", "images", "ADC码值刻度.svg") + +# ----------------------------- 画布与尺寸 ----------------------------- +W = 980 +H = 512 +PAD = 60 +RIGHT = 920 +AX0, AX1 = 100, 900 + +FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif" + +TITLE_FONT = 20 +SUB_FONT = 13 +HEAD_FONT = 14 +MARK_FONT = 13 +NOTE_FONT = 12 +TINY_FONT = 11 + +INK = "#0f172a" +LINE = "#334155" +MUTED = "#64748b" +FAINT = "#94a3b8" +RULE = "#cbd5e1" +WHITE = "#ffffff" + +C_BLUE = "#2563eb" +C_TEAL = "#0d9488" + +# ----------------------------- 数据(改这里) ----------------------------- +TITLE = "「码」是 ADC 的最小刻度 —— 两片转换器的刻度大小不同" +SUB1 = "ADC 输出的是整数(码),不是电压;电压 = 码 × LSB,所以要先知道「1 码值多少伏」" +SUB2 = "TPAFE5160:16 位双极性,65536 码 = 10 V 跨度 · STM32 ADC1:12 位单极性,4096 码 = 3.3 V 跨度" + +# (面板标题, 副标题, 左端标, 中间标, 右端标, 刻度段数, 标记码, 标记位置比, 标记文案, 颜色) +PANELS = [ + dict(head="A · TPAFE5160(16 位双极性)", + note="码范围 −32768 ~ +32767 · 每 1 码 = 152.59 µV", + left="−32768", mid="0", right="+32767", + n_ticks=20, + mark_frac=(11430 + 32768) / 65536.0, + mark_text="11430 码 ≈ 1.744 V", + color=C_BLUE), + dict(head="B · STM32 ADC1(12 位单极性)", + note="码范围 0 ~ 4095 · 每 1 码 = 805.66 µV", + left="0", mid=None, right="4095", + n_ticks=8, + mark_frac=1000 / 4095.0, + mark_text="1000 码 ≈ 0.806 V", + color=C_TEAL), +] + +FOOT = ("刻度疏密仅示意位数差(16 位比 12 位细),不是逐码绘制;1 码的电压当量见各行右侧标注。", + "两个换算值都来自台架实测码值:11430 与 cal: 行的 1743 mV 自洽,说明「码 × LSB」这条链是通的。") + + +# ----------------------------- 工具函数 ----------------------------- +def esc(s): + return html.escape(s, quote=True) + + +def text_w(s, fs): + """粗略估算文本像素宽:CJK/全角 ≈ 1.0em,其余 ≈ 0.58em(宁可偏大)。""" + return sum(fs * (1.0 if ord(ch) >= 0x2E80 else 0.58) for ch in s) + + +OVERFLOW = [] + + +def check_fit(s, fs, avail, where): + if text_w(s, fs) > avail: + OVERFLOW.append("%s: %.0f > %.0f [%s]" % (where, text_w(s, fs), avail, s)) + + +def txt(x, y, s, fs, fill, weight=400, anchor=None): + a = "" if anchor is None else ' text-anchor="%s"' % anchor + w = "" if weight == 400 else ' font-weight="%d"' % weight + return ('%s' + % (x, y, fs, a, w, fill, esc(s))) + + +def line(x1, y1, x2, y2, color=LINE, width=2.0, dash=None): + d = "" if dash is None else ' stroke-dasharray="%s"' % dash + return ('' + % (x1, y1, x2, y2, color, width, d)) + + +def panel_head(y, s): + return [txt(PAD, y, s, HEAD_FONT, INK, weight=700), + line(PAD, y + 12, RIGHT, y + 12, RULE, 1.0)] + + +# ----------------------------- 生成 ----------------------------- +def build(): + out = [] + add = out.append + + avail = RIGHT - PAD + for s, fs, where in ((TITLE, TITLE_FONT, "title"), (SUB1, SUB_FONT, "sub1"), + (SUB2, SUB_FONT, "sub2")): + check_fit(s, fs, avail, where) + for i, s in enumerate(FOOT): + check_fit(s, NOTE_FONT, avail, "foot %d" % (i + 1)) + for i, p in enumerate(PANELS): + check_fit(p["head"], HEAD_FONT, avail, "panel %d head" % (i + 1)) + check_fit(p["note"], NOTE_FONT, avail, "panel %d note" % (i + 1)) + check_fit(p["mark_text"], MARK_FONT, 220, "panel %d mark" % (i + 1)) + if OVERFLOW: + for o in OVERFLOW: + print("OVERFLOW WARN " + o) + + add('' % (W, H, WHITE)) + + # ---- 标题区 ---- + add(txt(PAD, 44, TITLE, TITLE_FONT, INK, weight=700)) + add(txt(PAD, 72, SUB1, SUB_FONT, MUTED)) + add(txt(PAD, 92, SUB2, SUB_FONT, MUTED)) + + ys = [(150, 238, 260), (328, 416, 438)] # (head_y, axis_y, label_y) + + for i, p in enumerate(PANELS): + hy, ay, ly = ys[i] + for e in panel_head(hy, p["head"]): + add(e) + add(txt(PAD, hy + 30, p["note"], NOTE_FONT, MUTED)) + + # 轴 + add(line(AX0, ay, AX1, ay, LINE, 1.8)) + # 刻度 + n = p["n_ticks"] + for k in range(n + 1): + x = AX0 + (AX1 - AX0) * k / float(n) + add(line(x, ay - 6, x, ay + 6, FAINT, 1.0)) + + # 端点 / 中点标 + add(txt(AX0, ly, p["left"], TINY_FONT, FAINT, anchor="middle")) + add(txt(AX1, ly, p["right"], TINY_FONT, FAINT, anchor="middle")) + if p["mid"] is not None: + add(txt((AX0 + AX1) / 2.0, ly, p["mid"], TINY_FONT, MUTED, anchor="middle")) + + # 实测码值标记 + mx = AX0 + (AX1 - AX0) * p["mark_frac"] + add(line(mx, ay - 6, mx, ay - 28, p["color"], 1.0, dash="3 3")) + add('' % (mx, ay, p["color"])) + add(txt(mx, ay - 36, p["mark_text"], MARK_FONT, p["color"], weight=700, anchor="middle")) + + # ---- 底部说明 ---- + add(line(PAD, 452, RIGHT, 452, RULE, 1.0)) + for i, s in enumerate(FOOT): + add(txt(PAD, 470 + i * 18, s, NOTE_FONT, MUTED)) + + svg = ('\n%s\n\n' + % (W, H, W, H, FONT, "\n".join(out))) + + d = os.path.dirname(OUT) + if not os.path.isdir(d): + os.makedirs(d) + with open(OUT, "w", encoding="utf-8", newline="\n") as fp: + fp.write(svg) + ET.fromstring(svg) + print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, H, OUT)) + if OVERFLOW: + print("!! %d 处文字可能溢出,见上方 WARN" % len(OVERFLOW)) + + +if __name__ == "__main__": + build()