2b4c0e97c2
§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 未装,截图不入库)。
187 lines
6.7 KiB
Python
187 lines
6.7 KiB
Python
#!/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()
|