
大多数 Arduino 教程直接跳到代码。但微控制器周围的元件都是模拟的:让 LED 不被烧毁的电阻、把传感器变成电压的分压器、驱动电机的晶体管、把墙上适配器变成 5 V 的稳压器。其中任何一个出了问题,再多的代码也修不好。
Velxio 也模拟这一半。它运行 ngspice,这是开源的 SPICE 电路仿真器,被编译成 WebAssembly,因此电阻、电容、二极管、晶体管、运算放大器和稳压器在浏览器中的行为与真实器件一致。你可以用电压表、电流表和示波器测量它们,并在电路运行时更改任何数值。
本文介绍七个项目。前五个完全没有微控制器,只有元件。后两个把模拟电路与 Arduino 和 ESP32 结合起来。每个项目都在示例库中,下面的每个数值都是从模拟器中读取的,并对照解释它的公式进行了核验。
开始之前:三种仪器
- 没有开发板的电路一打开就会运行。 右键单击任何元件打开其检查器,在那里更改数值:电阻的阻值、电容的容值、传感器的光照水平或温度。电路会用新数值重新求解。
- 电压表和电流表和其他元件一样:电压表并联在你要测量的对象两端,电流表串联。在直流电路中它们显示一个数值。在交流电路中,它们显示有效值、峰值和直流平均值,在电路稳定后测量。
- 示波器显示电压随时间的变化。右键单击一根导线并选择 Add to scope;每个通道会自动缩放以适配信号。如果画布上有开发板,请先停止仿真:当草图运行时,画布会把点击留给元件。
需要知道一点:对于带交流源的电路,Velxio 从通电时刻起求解一小段时间窗口(信号的几个周期),并显示该窗口的示波器波形。当你更改参数时它会重新求解,因此画面保持最新,但它不是连续记录。
1. 一个 LED 和一个电阻:欧姆定律

每个初学者搭建的第一个电路,也是最常搭错的电路:LED 直接接电池会烧毁,因为没有任何东西限制它的电流。串联的电阻就是干这个的。
仪表说明了全部情况:
- 470 Ω 电阻两端的电压表读数为 7.136 V。
- 电流表读数为 15.18 mA。欧姆定律给出同样的结果:7.136 V / 470 Ω = 15.2 mA。
- 9 V 的其余部分降落在 LED 上:约 1.84 V,即红色 LED 的正向电压。
这就是选择电阻的公式:R = (Vsupply − VLED) / I。对于 9 V 电池提供 15 mA:(9 − 1.8) / 0.015 = 480 Ω,所以 470 Ω 是最接近的标准值。
试一试: 右键单击电阻,将其改为 1 kΩ。电流降到 7.2 mA,LED 变暗,而它自身的电压几乎不变(1.81 V)。LED 的电压几乎恒定,由电阻决定电流。
2. 一个 5 V 电源:整流器、电容和稳压器
经典的线性电源,也就是每个旧墙上适配器里的那个,分为四级:
- 变压器。 它把市电交流变成更安全、更低的交流电压。这里是一个中心抽头 12-0-12 V 次级,用两个反相的 12 V 峰值、50 Hz 信号发生器表示。
- 整流器。 两个 1N4007 二极管,每个对应次级的一半。每个二极管在自己的半周期导通,因此输出每 10 ms 得到一个正脉冲:全波整流。
- 储能电容。 220 µF 电容在每个峰值充电,并在其间为负载供电。它保持的电压并不平坦:峰值之间会下降。这个下降就是纹波。
- 稳压器。 7805 接收这个波动的电压,并将其输出稳定在恰好 5 V,只要其输入保持在输出以上至少约 2 V。
本文顶部的示波器画面显示了来自变压器的 12 V 交流以及电容两端的整流电压。电容在 10.1 V 和 11.8 V 之间摆动,在 50 mA 负载下约有 1.7 V 纹波,而输出端的电压表读数为稳定的 5.000 V。
纹波的快速估算为 ΔV ≈ I / (2·f·C):0.05 A / (2 × 50 Hz × 220 µF) = 2.3 V。该公式假设电容在整个半周期内为负载供电;实际上二极管会稍早一些给它充电,因此实测的 1.7 V 更低。
试一试: 右键单击电容,将其改为 47 µF。纹波增大到 6 V,谷值降到 5.9 V,低于 7805 所需,其输出在每个周期都会跌到 3.9 V。这就是电源使用大电容的原因。
3. 用 LDR 和两个晶体管做的夜灯

一个自动夜灯,没有微控制器,也没有代码。光敏电阻(LDR)测量光照,两个晶体管在天黑时点亮 LED。
光传感器模块是一个分压器:上面是 LDR,下面是 10 kΩ 电阻。在日光下 LDR 的阻值很低,模块的输出(AO)很高;在黑暗中它升到数百 kΩ,AO 降到接近 0 V。
- Q1 是传感器的开关。当 AO 高于约 0.6 V 时,Q1 导通,把 Q2 的基极拉到接近 0 V。
- Q2 驱动 LED。当 Q1 导通时,Q2 截止,LED 熄灭。当光线变暗时,Q1 停止导通,10 kΩ 电阻把 Q2 的基极拉高,Q2 导通,LED 点亮。
为什么要两个晶体管?因为单独一个会做相反的事:在明亮时点亮 LED。第一级反相了信号。
在仿真中,在 20 lux(昏暗的房间)时 AO 为 0.50 V,Q1 截止,LED 电流为 13 mA。在 30 lux 时 AO 为 0.67 V,Q1 导通,LED 熄灭。右键单击光传感器,在该范围内更改 Illumination (lux) 来切换它:

4. 晶体管放大器:共射极级

这就是小信号如何变成大信号:共射极放大器,每个分立音频放大器的基本构建模块。一个峰峰值为 0.4 V 的 1 kHz 正弦波通过耦合电容输入,从集电极输出时放大了约四倍半,并且反相。(9 V 电源是左上角的台式电源符号;信号发生器在它下方。)
有两点让它工作。
偏置。 晶体管只有在部分导通时才会放大。47 kΩ / 10 kΩ 分压器将基极设定在 1.54 V。发射极比它低一个二极管压降,为 0.89 V,因此 0.89 mA 的电流流过 1 kΩ 发射极电阻。同样的电流流过 4.7 kΩ 集电极电阻,产生 4.2 V 压降,使集电极保持在 4.83 V,接近电源的中点,两侧都有摆动空间。
增益。 基极的变化几乎原样出现在发射极上,因此它使电流变化 ΔV / Re。该电流变化流过 Rc,使集电极向相反方向移动 ΔV × Rc / Re。因此增益为 −Rc/Re = −4.7。模拟器测得 4.54:晶体管增加了一个约 26 mV / Ie ≈ 29 Ω 的小内部发射极电阻,而 4700 / 1029 = 4.57。
在示波器上,基极(绿色)在 1.5 V 附近摆动 0.4 V,集电极(红色)在 4.8 V 附近摆动 1.8 V,其峰值对应基极的谷值。
试一试: 右键单击信号发生器,将其幅度提高到 1 V。集电极将需要摆动 9 V,超过电源所能提供的范围,因此波形的顶部在 9 V 处被削平,底部在饱和处被削平:这就是削波,过驱动放大器的声音。
5. 用运放比较器实现的恒温器

一个在变暖时打开风扇的恒温器,只用一个 LM358 运放,无需代码。
传感器。 NTC 热敏电阻的阻值随温度升高而下降(25 °C 时为 10 kΩ,32 °C 时约为 7.4 kΩ)。模块将其置于一个固定的 10 kΩ 电阻下方,因此其输出电压随温度下降:25 °C 时为 2.50 V,32 °C 时为 2.12 V。
设定点。 电位器是第二个分压器。其滑动端位于 2.336 V,即热敏电阻在 28 °C 时给出的电压。电压表显示该值。
比较器。 没有反馈的运放增益极高,其输出会根据哪个输入更高而猛冲到某一电源轨。设定点接到 + 输入端,热敏电阻接到 − 输入端:低于 28 °C 时热敏电阻电压更高,输出为 0 V;高于 28 °C 时它更低,输出跳变到约 3.7 V,点亮 LED。27 °C 时 LED 熄灭,29 °C 时点亮。
为什么是 3.7 V 而不是 5 V?LM358 无法将其输出一直驱动到正电源;它会在约 1.3 V 处停下。项目 6 遇到了同样的限制。
真正的恒温器会加入迟滞,这样当温度在阈值附近徘徊时它不会反复开关。项目 7 用代码实现了这一点。
试一试: 右键单击热敏电阻并更改其温度,或右键单击电位器并更改其位置(0 到 1023)以移动设定点。
6. Arduino:将 PWM 转换为真正的模拟电压

Arduino Uno 没有 DAC:analogWrite() 不输出电压,它输出 PWM,即一个 490 Hz 的方波,其占空比决定平均值。LED 或电机不在乎,但许多电路需要真正的、稳定的电压。模拟滤波器可以产生一个。
- RC 低通滤波器。 10 kΩ 和 10 µF 对 PWM 求平均。其时间常数 τ = R·C = 0.1 s,是 PWM 周期的五十倍,因此 490 Hz 的纹波几乎被完全去除。代价是速度:变化后输出需要约 5τ = 0.5 s 才能稳定。
- 电压跟随器。 一个输出接到其 − 输入的 LM358 将滤波器的电压复制到其输出。它几乎不从滤波器抽取电流,因此你接下来连接的任何东西都不会把电压拉低。
- 反馈。 A0 读回结果,草图打印它请求的值和实际得到的值。
PWM 0 expected 0.00 V measured 0.02 V
PWM 64 expected 1.25 V measured 1.26 V
PWM 128 expected 2.51 V measured 2.51 V
PWM 192 expected 3.76 V measured 3.77 V
PWM 255 expected 5.00 V measured 4.03 V
直到 192,DAC 的精度为 10 mV。在 255 时它停在 4.03 V:滤波器确实达到了 5 V,但由 5 V 供电的 LM358 无法将其输出驱动到那么高。解决办法是使用轨到轨运放,例如 MCP6002,或者只使用 LM358 能达到的范围。
7. ESP32:一个开关风扇的温度计

又是恒温器,这次由微控制器来决定,因此它可以做比较器做不到的事情:以度数显示温度并加入迟滞。
读取温度。 NTC 模块的输出接到 GPIO34,这是 ESP32 第一个 ADC 上的仅输入引脚。草图对 16 次读数求平均,从分压器计算出热敏电阻的阻值(R = 10 kΩ × V / (3.3 V − V)),并用热敏电阻数据手册中的 Beta 方程将其转换为度数:1/T = 1/T₀ + ln(R/R₀)/β,其中 R₀ = 10 kΩ 在 T₀ = 25 °C 时,β = 3950。当热敏电阻设为 25 °C 时,串口监视器打印 25.0 °C,在 35 °C 时打印 35.0 °C。
开关负载。 GPIO 引脚只能提供几毫安,而不是风扇所需的数百毫安。GPIO25 改为通过一个 100 Ω 电阻驱动 2N7000 MOSFET 的栅极,MOSFET 将风扇(此处为绿色 LED)连接到地。在 35 °C 时漏极位于 0.06 V:MOSFET 完全导通。
迟滞。 风扇在 30 °C 时开启,仅在低于 28 °C 时关闭。没有这 2 °C 的间隙,在阈值附近徘徊的温度会每秒开关风扇一次。
对于真正的风扇,请使用额定电流适合风扇的逻辑电平 MOSFET(2N7000 仅适用于小负载),并在风扇两端并联一个二极管,以吸收电机关闭时的电压尖峰。
构建这些时我们修复了什么
撰写本文意味着测量每个电路,而测量发现了模拟器中的问题,全部在发布前修复:
- 在 PWM 值之后执行
analogWrite(pin, 0)或analogWrite(pin, 255)后,电路仍然看到旧的 PWM 电平。项目 6 的表格发现了它。 - 电压表和电流表将上电瞬态平均到其交流读数中:7805 的输出读数为 3.47 V 而不是 5 V。
- 示波器的模拟通道具有固定的 ±4 V 范围,因此 5 V 或 12 V 信号被画到屏幕外。它们现在会自行缩放。
- 电压稳压器不从其输入抽取任何电流,因此电源完全显示不出纹波。
如果你在 Velxio 中构建模拟电路而数值与公式不符,请在 GitHub 上告诉我们。这些就是这样被发现的。