电路 (circuitikz)

circuitikz 是一个 LaTeX 宏包,源于 2007 年一位大学教师非常实际的需求:出习题和考卷。当时在米兰理工大学担任研究助理的 Massimo Redaelli,需要一件能排出课程讲义里电路图的工具,他写下的东西就是今天的这个宏包。它的语法出奇地直白——把导线写成一条路径,往里插入 to[R, l=$R_1$],一个电阻就出现了。但本页篇幅最长的一节并不讲语法,而是讲这样一件事:电阻符号在各国并不相同——锯齿还是矩形——而 circuitikz 的默认值多半不是你以为的那个。

\usepackage{circuitikz} 到底加载了什么

一行 \usepackage{circuitikz} 会自动加载 TikZ,还会附带 TikZ 库 calcarrows.metabendingfpu。所以 circuitikz 不是 TikZ 的替代品,而是在 TikZ 上加装了一套 电气符号词汇。路径与坐标的概念留给 TikZ 那一页;这里要记住的只有一点:circuitikz 的命令说到底全都是 TikZ 命令。在 pdfLaTeX、LuaLaTeX、XeLaTeX 下可直接使用;只有经由 DVI(pLaTeX、upLaTeX)时,才需要像用 TikZ 那样把驱动(通常是 dvipdfmx)写成类选项。

document.tex
\documentclass{article}   % one schematic only: \documentclass[border=3pt]{standalone}
\usepackage{circuitikz}
\begin{document}
\begin{circuitikz}
  \draw (0,0) to[R, l=$R_1$] (2,0);
\end{circuitikz}
\end{document}

「和 TikZ 一样」在这里不是比喻。在 circuitikz.sty 里,circuitikz 环境的定义只有一行:\newenvironment{circuitikz}{\begin{tikzpicture}}{\end{tikzpicture}}。它就是一个空壳别名。所以从 tikzpicture 一侧写 \tikz \draw (0,0) to[R=$R_1$] (2,0); 会得到完全相同的图。这个事实在实践中真的会咬人:TikZ 的 \tikzexternalize(把每张图缓存成独立 PDF 的机制)假定所有图都以 \end{tikzpicture} 结尾,因此使用 circuitikz 环境的文档会停在 ! Emergency stop.。手册的答复毫不客气——把所有 circuitikz 换成 tikzpicture,反正两者基本相同。若只想把一张电路图导出为图片,standalone 类最省事。

如何读懂 to[R, l=$R_1$]:把部件落进导线里

一个写法几乎就能解释整个 circuitikz。在 TikZ 路径操作 to 的方括号里写入部件键,就会在连接两点的导线 中途插入一个该部件\draw (0,0) to[R, l=$R_1$] (2,0); 读作「从 (0,0) 走到 (2,0),途中经过电阻 R₁」。这里 l= 是标签键——但 to[R=$R_1$] 效果相同,它正是 to[R, l=$R_1$] 的缩写。两种写法并排出现时常让人困惑;它们完全等价。长写法便于随后再补上 i=v=,所以熟悉之后不妨默认使用 to[R, l=…]

latex
\begin{circuitikz}
  \draw (0,0) to[R, l=$R_1$] (2,0)   % long form
              to[C=$C_1$] (2,-2)     % shorthand for the same thing
              to[short, -*] (0,-2);  % bare wire, connection dot at the end
\end{circuitikz}

画出来是一条门字形导线。想要导线而不放部件的地方,用 to[short]——它是「什么都不是的二端部件」,存在的意义就是让你能把布线继续写成 to 的链条。末尾的 -* 借用了 TikZ 的箭头端点写法,在路径终点打上连接点(实心圆);两端都要就写 *-*。坐标就是普通 TikZ,绝对坐标 (2,0)(默认单位为厘米)和相对坐标 ++(2,0)(从当前位置向右 2 cm)都可以。要走直角,可用 (a -| b)——a 的 x 坐标与 b 的 y 坐标的交点——以及路径运算符 -|。部件方向会自动跟随路径前进方向,所以纵向走线得到的就是纵向符号。

打错部件键时,报错乍看完全不像来自 circuitikz。写 to[resistorX] 会得到 ! Package pgfkeys Error: I do not know the key '/tikz/resistorX' and I am going to ignore it.。部件键本质上就是 TikZ 的键,所以抱怨未知键的是 pgfkeys。消息里原样引用了你输入的名字,顺着它去核对拼写即可。

二端部件的部件键:RCLD 与各种电源

能放进 to[…] 的是带两个端子的部件,即 二端部件(bipole)。多数键是便于记忆的首字母(电阻 R、电容 C、电感 L、二极管 D);像电源这类成员众多的族,还备有 vsourceisource 这样的拼写形式别名。短形式敲得快,拼写形式在几个月后重读代码时更友好——多人协作的文档里,倾向使用拼写形式能省下不少争论。

部件键部件写法
R电阻to[R, l=$R_1$]
C电容to[C, l=$C_1$]
L电感(线圈)to[L, l=$L_1$]
D二极管;leD(发光)、zD(齐纳)等属同族to[D]
V / vsource通用电压源to[V, l=$U_q$]
I / isource通用电流源to[I, l=$I_0$]
sV正弦电压源(交流)to[sV, l=$v_s$]
battery1 / battery电池(单节/多节)to[battery1]
short / open普通导线/开路(两端之间什么都没有)to[short]
closing switch / opening switch开关;闭合方向与断开方向的符号不同to[closing switch]

唯有电源值得多想一秒。V 是画成圆形的通用电压源,sV 在圆里加上正弦波表示交流,battery1 则是电池特有的长短线符号。直流电源该用圆还是电池符号,属于领域惯例问题,稳妥的做法是与读者手头的教材保持一致。开关分成 closing switchopening switch 也是同样的道理:符号记录的是这类开关往哪个方向动作,而不是它此刻是否闭合。

americaneuropean:电阻是锯齿还是矩形

电阻在美国习惯里画成锯齿,在 IEC(欧洲)标准里画成空心矩形。同一个部件、同一个电路,图形却不同。circuitikz 把这个差别摆到了 宏包选项 层面:\usepackage[american]{circuitikz}\usepackage[european]{circuitikz} 会成套切换——不只是电阻,还有电感、逻辑门,以及电压和电流箭头。用读者不熟悉的符号画出的电路图确实更难读,所以这是动笔之前就该定下的设置。

重点来了:什么都不指定时的默认值不是二者之一,而是混合。 翻开 circuitikz.sty 的默认选项表就会看到:电阻和逻辑门是美国式(americanresistorsamericanports),电压和电流箭头是欧洲式(europeanvoltageseuropeancurrents),电感则用了第三种流派 cuteinductors。结果是,未经配置的电路图 对美国读者和欧洲读者都「和自己的教材有点不一样」。而且差别相当明显:欧洲式电压是跨过部件的弧形箭头,美国式电压则是放在两端的 +,完全是两幅画。

设置的位置有三处,其中藏着一个小陷阱。宏包选项不带空格\usepackage[europeanresistors]{circuitikz}),而环境选项与 \ctikzset 带空格\begin{circuitikz}[european resistors])——两个名字只差一个字符。需要更细的粒度时可用键:\ctikzset{resistor=european}\ctikzset{voltage=american}。清晰的分工是:在导言区用一次 [european][american] 统一全文,只在某一张图需要破例时才用环境选项。

document.tex
% whole document in IEC style (note: no space in package options)
\usepackage[european]{circuitikz}

% ... but this one figure keeps the American zigzag
\begin{circuitikz}[american resistors]
  \draw (0,0) to[R, l=$R_1$] (2,0);
\end{circuitikz}

标签 l=、电压 v=、电流 i=、注记 a=

给部件加文字的键有四个,分工很清楚。l= 是名称(如 R₁);a= 是注记,在名称的另一侧再放一层信息,常用于实际数值。v= 画部件两端的电压,i= 画流过它的电流,各自带一个表示方向的箭头。这些键都可以在末尾加下划线来翻转位置或方向:l_= 把标签移到导线另一侧,i_=v_= 把电流和电压箭头反向。也有上标形式 l^=。由于导线横竖不同会让「上/下」的含义互换,把 ll_ 的取舍当成最后凭外观定夺的事,会轻松很多。

latex
\begin{circuitikz}
  \draw (0,0) to[V, l=$U_q$] (0,2)
              to[short] (2,2)
              to[R, l=$R_1$, a=$4.7\,\mathrm{k}\Omega$, i=$i_1$, v=$u_1$] (2,0)
              to[short] (0,0);
\end{circuitikz}

画出来是一个闭合回路:左边是电压源 Uq,上下两边是 to[short] 的普通导线,右边的电阻在导线两侧分别标出名称 R₁ 与实际值 4.7 kΩ,另外还带上电流 i₁ 与电压 u₁ 的箭头。上一节在这里发挥作用——v=$u_1$ 究竟怎么画,完全取决于当前生效的是 american voltages 还是 european voltages。什么都不指定时默认是欧洲式,于是得到跨过部件的弧形箭头。

要写带单位的数值,就用 \usepackage[siunitx]{circuitikz} 加载宏包。这会启用 circuitikz 自己的 数值<\单位> 记法——l=5<\ohm>a=3<\micro\farad>——其余交给 siunitx,由它排出正确的「5 Ω」和「3 µF」。数值与单位之间的间距、µ 的字形都不必自己操心,所以凡是要标注真实数值的电路图,一开始就打开它很划算。

晶体管、运放与接地要用 \node[…] 放置

三个或更多端子的部件放不进 to[…]to 是填充「两点之间」的操作,没有地方伸出第三条腿。晶体管、运算放大器、接地这类部件改为作为 TikZ 节点 放置,写成 \node[部件名] (名称) at (坐标) {};。即使内容为空,花括号 {} 也是必需的——忘掉它是初学者的经典卡点。放好的节点在每个端子处都有 锚点,用 节点名.引脚名 引用,例如 (oa.out),再接上导线。

  • 接地\node[ground] at (0,0) {};。电源轨有专门符号:正侧 vcc,负侧 vee
  • MOSFET\node[nmos] (q1) {};\node[pmos] {};。双极型晶体管为 npn/pnp。端子可用 q1.gateq1.drainq1.source 等引用。
  • 运算放大器\node[op amp] (oa) {};,引脚为 oa.+(同相输入)、oa.-(反相输入)、oa.out(输出)。
  • 放在路径中途 — 在坐标之后紧接着写 node[ground]{},即可在不打断导线的情况下插入节点。
latex
\begin{circuitikz}
  \draw (0,0) node[op amp] (oa) {};
  \draw (oa.-) to[R, l=$R_f$] ++(0,2) -| (oa.out);  % feedback around the amp
  \draw (oa.+) to[short] ++(-1,0) node[ground] {};
\end{circuitikz}

这正是锚点体现价值的地方。放一个运放,从反相输入 oa.- 向上引出反馈电阻 R_f,用 -| 折向右侧,再落到输出 oa.out——反馈环就闭合了。请注意,这里没有手工数出任何一个坐标:即使运放符号的尺寸变了,只要布线写在锚点上,它就会跟着走。同相输入 oa.+ 向左稍稍引出后接地。

\ctikzset 统一整张图,以及与 TikZ 混用

决定整张电路图外观的是 \ctikzset{…}。它是 circuitikz 一侧对应 TikZ \tikzset 的设置命令,可写在导言区,也可写在图的中途。实践中最见效的一条是 \ctikzset{bipoles/length=1cm}——让所有二端部件的绘制长度一致。保持默认时,各部件的长度略有差异,导线间距看起来会参差不齐;只要把长度固定下来,图往往立刻就整齐了。上一节的 resistor=americanvoltage=european 也属于同一个 \ctikzset 家族。

最后回到开头那句话:circuitikz 建立在 TikZ 之上。坐标计算的 calc、相对放置的 positioning、用作说明文字的普通 \node、颜色、虚线样式、样式定义——TikZ 能做的仍然都能用。电路越复杂,这种分工越站得住:用 TikZ 的方式搭建布局,把部件交给 circuitikz。当图变多、编译变慢时,external 库同样可以缓存它们——只是别忘了如前所述,先把 circuitikz 环境改写成 tikzpicture