#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成 docs/images/ADC前端电路.svg —— UA 通道前端拓扑(端子 → R31 → ADC7 → TPAFE),
并对照 STM32 侧(UB)拓扑,回答"这个前端到底是不是分压器"(纯标准库)。
为什么不用 Mermaid:本图要读的是"ADC7 节点上到底挂了几个器件"——Mermaid 会自动加
有向边、按文本自适应盒宽,恰好把"有没有对地分流电阻"这个唯一要看的东西抹掉。
用法(仓库根目录,任意 python3):
python tools/arch_diagram/gen_adc_frontend_svg.py
输出:
docs/images/ADC前端电路.svg
改内容:本图是固定电路拓扑(依据 docs/硬件平台/V0.1.tel 第 151 / 241 / 245 / 248 行),
网表变了才重画;调坐标 / 文案编辑下方常量后重跑。
"""
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 = 776
PAD = 60
RIGHT = 940
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
TITLE_FONT = 20
SUB_FONT = 13
HEAD_FONT = 14
BOX_FONT = 14
PIN_FONT = 13
NOTE_FONT = 12
TINY_FONT = 11
INK = "#0f172a"
LINE = "#334155"
MUTED = "#64748b"
FAINT = "#94a3b8"
RULE = "#cbd5e1"
WHITE = "#ffffff"
C_BLUE, BG_BLUE = "#2563eb", "#eff6ff"
C_TEAL, BG_TEAL = "#0d9488", "#f0fdfa"
C_GRAY, BG_GRAY = "#475569", "#f1f5f9"
C_AMBER, BG_AMBER = "#d97706", "#fffbeb"
# ----------------------------- 数据(改这里) -----------------------------
TITLE = "ADC 前端注入通路 —— 本板不是分压器"
SUB1 = ("网表 V0.1.tel 第 241 行:ADC7 ; C44.2 R31.2 U3.61 —— 该节点只有三个连接,"
"没有任何对地元件")
SUB2 = ("所以端子侧与引脚侧斜率相同(TPAFE 6553.6 码/V、STM32 1241.2 码/V),"
"注入点优先选端子侧焊盘")
HEAD_A = "A · UA 通道(TPAFE5160 侧)"
HEAD_B = "B · STM32 侧(以 UB 为例)—— 更简单,连接地电阻都没有"
NOTE_A1 = "R31 的 1 脚在端子侧、2 脚在芯片侧 —— 现场找焊盘按此定位"
NOTE_A2 = "R31 + C44 构成差分 RC 低通;C44 另一端是 TPAFE 负输入脚,不是地"
NOTE_TAG = "无对地分流电阻"
# ----------------------------- 工具函数 -----------------------------
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 rect(x, y, w, h, fill, stroke=None, rx=10, sw=1.6):
s = "" if stroke is None else ' stroke="%s" stroke-width="%g"' % (stroke, sw)
return '' % (
x, y, w, h, rx, fill, s)
def dot(x, y, r=5.5):
return '' % (x, y, r, INK)
def res(x, cy, w, h, label):
"""电阻:白底灰框 + 上方标号。x = 左边界,cy = 中心线。"""
out = [rect(x, cy - h / 2.0, w, h, WHITE, LINE, rx=3, sw=1.6)]
out.append(txt(x + w / 2.0, cy - h / 2.0 - 14, label, NOTE_FONT, MUTED, anchor="middle"))
return out
def cap(x, cy, half=20, gap=16):
"""电容:两条平行板(信号线在 x 上竖直走)。"""
return [line(x - half, cy, x + half, cy, INK, 2.4),
line(x - half, cy + gap, x + half, cy + gap, INK, 2.4)]
def gnd(x, y, label_y, label="AGND"):
"""接地符号:y = 接线点。"""
out = [line(x, y, x, y + 16, LINE, 2.0)]
for i, half in enumerate((28, 20, 12)):
out.append(line(x - half, y + 16 + i * 8, x + half, y + 16 + i * 8, LINE, 2.0))
out.append(txt(x, label_y, label, NOTE_FONT, MUTED, anchor="middle"))
return out
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"), (HEAD_A, HEAD_FONT, "head A"),
(HEAD_B, HEAD_FONT, "head B"),
(NOTE_A1, NOTE_FONT, "note A1"), (NOTE_A2, NOTE_FONT, "note A2")):
check_fit(s, fs, avail, where)
check_fit(NOTE_TAG, NOTE_FONT, 110, "note tag")
if OVERFLOW:
for o in OVERFLOW:
print("OVERFLOW WARN " + o)
add(rect(0, 0, W, H, WHITE, rx=0))
# ---- 标题区 ----
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))
# ================= 面板 A:UA 通道 =================
for e in panel_head(138, HEAD_A):
add(e)
AY = 250 # 主线(正端信号)
GY = 390 # 负端参考线
# 端子
add(rect(PAD, AY - 36, 130, 72, BG_BLUE, C_BLUE))
add(txt(PAD + 65, AY - 8, "端子侧", BOX_FONT, C_BLUE, weight=700, anchor="middle"))
add(txt(PAD + 65, AY + 14, "U5.A3 · AIN1", NOTE_FONT, C_BLUE, anchor="middle"))
# 端子 → R31 → ADC7
add(line(PAD + 130, AY, 250, AY))
for e in res(250, AY, 90, 20, "R31 · 1 kΩ"):
add(e)
add(txt(257, AY + 28, "1", TINY_FONT, FAINT))
add(txt(333, AY + 28, "2", TINY_FONT, FAINT, anchor="end"))
add(line(340, AY, 600, AY))
# ADC7 节点
add(dot(600, AY))
add(txt(600, AY - 36, "ADC7", BOX_FONT, INK, weight=700, anchor="middle"))
add(txt(600, AY - 18, NOTE_TAG, NOTE_FONT, C_AMBER, weight=700, anchor="middle"))
add(line(600, AY, 720, AY))
# C44:ADC7 → ADC7_GND
add(line(600, AY, 600, 290))
for e in cap(600, 290):
add(e)
add(txt(630, 300, "C44", NOTE_FONT, MUTED))
add(line(600, 306, 600, GY))
# ADC7_GND 节点
add(dot(600, GY))
add(txt(600, GY + 28, "ADC7_GND", PIN_FONT, INK, weight=700, anchor="middle"))
add(txt(600, GY + 46, "(差分负端)", TINY_FONT, MUTED, anchor="middle"))
add(line(600, GY, 720, GY))
# ADC7_GND → R32 → AGND
add(line(600, GY, 520, GY))
for e in res(430, GY, 90, 20, "R32 · 1 kΩ"):
add(e)
add(line(430, GY, 380, GY))
for e in gnd(380, GY, GY + 50):
add(e)
# TPAFE 芯片
add(rect(720, 196, 200, 244, BG_TEAL, C_TEAL, rx=12))
add(txt(820, 222, "TPAFE5160 · U3", BOX_FONT, C_TEAL, weight=700, anchor="middle"))
add(txt(820, 316, "±5 V 满量程 · 152.59 µV/LSB", NOTE_FONT, FAINT, anchor="middle"))
add(txt(736, AY, "61", TINY_FONT, FAINT))
add(txt(762, AY, "AIN_7P", PIN_FONT, INK, weight=700))
add(txt(736, GY, "62", TINY_FONT, FAINT))
add(txt(762, GY, "AIN_7GND", PIN_FONT, INK, weight=700))
# 面板 A 的两条注记(写在左侧空白区)
add(txt(PAD, 306, NOTE_A1, NOTE_FONT, MUTED))
add(txt(PAD, 330, NOTE_A2, NOTE_FONT, MUTED))
# ================= 面板 B:STM32 侧 =================
for e in panel_head(500, HEAD_B):
add(e)
BY = 590
add(rect(PAD, BY - 36, 130, 72, BG_BLUE, C_BLUE))
add(txt(PAD + 65, BY - 8, "端子侧", BOX_FONT, C_BLUE, weight=700, anchor="middle"))
add(txt(PAD + 65, BY + 14, "U5.B7 · AIN2", NOTE_FONT, C_BLUE, anchor="middle"))
add(line(PAD + 130, BY, 250, BY))
for e in res(250, BY, 90, 20, "R52 · 1 kΩ"):
add(e)
add(line(340, BY, 720, BY))
add(dot(720, BY))
add(txt(720, BY - 28, "ADC17", BOX_FONT, INK, weight=700, anchor="middle"))
add(line(720, BY, 800, BY))
# C111:ADC17 → AGND(直落,无串阻)
add(line(720, BY, 720, 650))
for e in cap(720, 650):
add(e)
add(txt(750, 660, "C111", NOTE_FONT, MUTED))
add(line(720, 666, 720, 690))
for e in gnd(720, 690, 742):
add(e)
# STM32 芯片
add(rect(800, 530, 140, 120, BG_GRAY, C_GRAY, rx=12))
add(txt(870, 552, "STM32 ADC1", PIN_FONT, C_GRAY, weight=700, anchor="middle"))
add(txt(870, 622, "0~3.3 V 单极性", TINY_FONT, FAINT, anchor="middle"))
add(txt(816, BY, "44", TINY_FONT, FAINT))
add(txt(840, BY, "PC4", PIN_FONT, INK, weight=700))
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()