Files
STM32F4-Base/tools/arch_diagram/gen_adc_code_scale_svg.py
T
wandering1 2b4c0e97c2 docs: ADC 注入流程 §4 补码值刻度图
§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 未装,截图不入库)。
2026-10-27 17:25:51 +08:00

187 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 ('<text x="%s" y="%s" font-size="%g"%s%s fill="%s">%s</text>'
% (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 ('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%g"%s/>'
% (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('<rect x="0" y="0" width="%d" height="%d" fill="%s"/>' % (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('<circle cx="%.1f" cy="%.1f" r="5" fill="%s"/>' % (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 = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\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()