用 Schemdraw 把电路拓扑写成可验证的绘图程序
以 11 管复合放大电路为例:先建立网络契约,再用命名锚点、正交布线与机器断言生成可复现原理图。
复杂原理图最难的地方,并不是把晶体管符号放到画布上,而是让电气连接、版面关系和视觉语义同时正确。一张图能成功导出,只能证明程序运行了;它没有证明镜像管方向正确,也没有证明一条看似无连接点的线没有恰好落在器件端点上。
这篇笔记以一张 11 管复合放大电路为例,整理一套更接近软件工程的 Schemdraw 用法:把拓扑当作源数据,把绘图当作渲染,再为容易出错的结构写可执行的验收规则。示例材料使用 Schemdraw 0.23,完整产物包括 Python 源码、SVG、PNG、独立网络表和复盘记录。
先把“画图”改写成一条编译管线
如果直接从左到右堆元素,坐标会很快取代电路本身成为注意力中心。更稳妥的顺序,是先声明电路事实,再逐级降低抽象层次。
这个例子先被拆成五个功能段:Q1/Q2 是 NMOS 差分输入;Q4/Q5 是 PNP 电流镜有源负载;Q10/Q3 与 R1 形成尾电流镜;Q7、Q11/Q6 组成第二电压增益级及其负载;D1/D2 与 Q8/Q9 构成偏置和互补射极跟随输出。C1 从第二级输出回到第一级输出,承担密勒补偿。
器件清单还不够。真正能抵抗误连的是“端子—网络”契约。例如输出级先写成下面这张表,再决定它在页面右侧排成什么形状:
| 端子 | 必须连接 | 绝不能混入 |
|---|---|---|
Q8.C | +12 V | D1 节点、输出 |
Q8.B | D1 高偏置节点 | +12 V、输出 |
Q8.E | uout | 电源轨 |
Q9.C | −12 V | D2 节点、输出 |
Q9.B | D2 低偏置节点 | −12 V、输出 |
Q9.E | uout | 电源轨 |
这一步挡住了一个很常见的“漂亮错误”:为了让输出级紧凑,把两只管子的多个端子并到同一根竖线上。视觉上像一个整齐的输出柱,电气上却已经短接。
用命名锚点连接,不用目测坐标猜端子
一个轻量的 Builder 负责三件事:登记器件、保存每段路线、区分实心结点与空心外部端口。每个器件加入图后,都以编号作为名字保存;后面的连接只读取 gate、drain、source 或 base、collector、emitter 等锚点。
class Builder:
def device(self, ref, element):
element.color(BLUE).linewidth(1.65)
placed = self.d.add(element)
self.devices[ref] = placed
return placed
q1 = b.device("Q1", elm.NMos().right().at((7.0, 9.2)))
q2 = b.device("Q2", elm.NMos().right().reverse().at((11.0, 9.2)))
b.route([q1.source, (q1.source.x, tail[1]), tail], name="Q1 source to tail")
这里的坐标只负责版面,q1.source 才负责电气语义。即使以后移动 Q1,只要它的源极锚点仍然成立,连接代码就不会把“原来的某个点”误当成源极。
同时,每只关键器件都显式指定方向。材料中的一次失败说明:.at(...) 只确定位置,不一定重置 Schemdraw 前一个定向元素留下的绘制方向。差分管明明坐标正确,却可能横躺;在向下绘制二极管之后创建输出管,也可能继承错误方向。显式的 .right()、.reverse() 或 .flip(),是在消除隐藏状态。
布线是受约束的几何,而不是装饰
外部连线统一拆成水平或垂直线段,任何斜线都会在加入图纸前触发异常。密集模拟电路仍允许晶体管符号内部保留标准斜引脚,但不让外部路线为了“走捷径”穿过器件。
def segment(self, start, end, *, name="wire"):
a, b = self.xy(start), self.xy(end)
if not horizontal_or_vertical(a, b):
raise ValueError(f"Non-orthogonal route {name}: {a} -> {b}")
self.d.add(elm.Line().at(a).to(b))
self.routes.append((name, a, b))
结点也必须有明确语义:
- 实心点只表示真实的 T 形分支或多个内部端子共网;
- 空心圆只允许出现在
u+、u−、uout三个外部信号端口; - 普通折角和两段线的连续处不画点;
- “没有点”不能抵消端点落在线上的事实。一个器件端子若正好压在无关导线上,几何上仍然是短接风险。
这也是为什么输入端口最终被移到 −12 V 电源轨下方。早期版本虽然画了空心圆,但圆心恰好位于电源轨上;符号看起来像端口,坐标事实却意味着输入被短到负电源。
把容易回归的图形规则写成断言
机器检查适合验证数量、集合、方向和坐标关系。示例脚本在保存前确认 11 只晶体管全部存在,空心端口恰好有 3 个,全部外部路线正交,三组电流镜均向内相对,并核对输出级端子表。
required = {f"Q{i}" for i in range(1, 12)}
assert set(b.devices) == required
assert b.open_ports == {(5.0, input_y), (14.0, input_y), (25.0, 7.0)}
for left, right in ((q4, q5), (q10, q3), (q11, q6)):
assert left.base.x > left.collector.x
assert right.base.x < right.collector.x
器件数、端口白名单、正交线段、端子集合、镜像方向、电源轨终点、线路是否穿过器件包围盒。
电路级次是否易读、标签是否遮挡、留白是否均衡、符号朝向是否符合习惯、交叉关系是否可能被误解。
两道门不能互相替代。程序成功导出 SVG,只说明语法和断言通过;像素级没有重叠,也不代表读者能一眼理解信号流。最终验收必须回到真实 SVG/PNG,按功能级、器件方向、每个端子、结点和页面边缘逐项查看。
一次生成,保留四种证据
适合长期维护的交付不是单独一张 PNG,而是四件互相补强的产物:
- Python 源码:可以修改和重建;
- SVG:保留矢量质量,适合网页放大与排版;
- PNG:便于快速预览、批注和跨软件查看;
- 独立网络表与复盘记录:在坐标变化时保护拓扑,在再次失败时保留原因。
这套方法的边界也要写清楚:它能提高绘图拓扑和表达的一致性,但不会替代 SPICE 仿真,也不证明器件参数、环路稳定性、温漂或功率裕量。Schemdraw 负责让电路意图成为可审查、可再生的工程文档;电路本身是否工作,仍要交给模型、计算、仿真与实测。