3.12. 使用 ADC 读取模拟量¶
到目前为止,摄像头一直在读取数字信号——某个引脚要么是 0 要么是 1,开关要么断开要么闭合。然而,来自真实世界传感器的大多数信号都是模拟信号:一个在某个范围内平滑变化的连续电压。光敏电阻会随着环境亮度的变化在电源轨之间扫过每一个电压值。温度传感器的输出会随着房间升温而漂移几毫伏。麦克风的输出会随着周围的声音起伏。
模数转换器(ADC)就是这两者之间的桥梁。它对引脚上的电压进行采样,并返回一个 Python 可以像读取其他任何值一样读取的整数。
3.12.1. 量化¶
数字值无法精确地表示连续电压。ADC 的工作就是量化——把每个采样捕捉到一组固定电平中最接近的那一个。N 位的 ADC 有 2^N 个电平;12 位转换器在其输入范围内分布有 4096 个电平。
量化:模拟信号(实线)的每个采样都被舍入到有限的一组数字电平之一(阶梯虚线)。¶
两个相邻电平之间的电压是 ADC 的步长;任何比这更小的量都会消失在舍入中。在 3.3 V 范围上的 12 位 ADC 步长约为 3.3 / 4096 ≈ 0.8 mV——精细到足以让大多数信号在软件中看起来实际上是连续的。
3.12.2. machine.ADC 类¶
machine.ADC 封装了一个模拟输入通道。用你要读取的引脚来构造它,然后调用 read_u16():
from machine import ADC
adc = ADC("P6")
value = adc.read_u16()
print(value)
read_u16() 始终返回一个介于 0 和 65535 之间的无符号 16 位整数。原生 ADC 分辨率因开发板而异(STM32 上为 12 位,其他端口各不相同);结果会左对齐到 16 位中,因此硬件细节不会泄露到 Python 中——无论芯片如何,65535 这个值都表示满量程。
参考电压——也就是对应满量程的输入——取决于开发板。请查看 OpenMV 开发板 以获取你的摄像头上的具体值。任何高于参考电压的输入都会读作满量程(如果超过绝对最大输入电压,还可能损坏引脚)。
3.12.2.1. 将计数转换为电压¶
从计数到电压的映射是线性的,满量程计数恰好映射到 Vref:
voltage = counts × Vref / 65535
代码如下:
VREF = 3.3 # cam-dependent; see the quickref
counts = adc.read_u16()
voltage = counts * VREF / 65535
print(voltage, "V")
3.12.3. 分压器¶
在电源轨和地之间串联的两个电阻构成一个分压器。它们之间的节点电压由两个电阻的比值决定:
分压器:串联的 R1 和 R2 将 Vin 按比例降低到 V_out。¶
V_out = Vin × R2 / (R1 + R2)
相等的电阻给出电源轨电压的一半;R2 远小于 R1 时,引出点接近地;R2 远大得多时,引出点接近电源轨。
该公式假设没有其他元件从 V_out 抽取明显的电流。ADC 引脚是高阻抗的(兆欧、纳安级),很容易满足这一条件,因此给 ADC 供电的分压器的行为与公式预测的一致。
3.12.4. 电位器¶
电位器是一个单一的物理元件,本身就是一个分压器,带有一个在两端之间移动引出点的滑动触点(wiper)。转动旋钮会同时改变 R1 和 R2,同时保持它们的总和(电位器的总电阻)不变。
接成 ADC 手动电压源的电位器:一端接 3.3 V,另一端接地,触点接到引脚。¶
电位器是试验 ADC 的典型输入设备。将一端接到 3.3 V,另一端接地,触点接到支持 ADC 的引脚;转动旋钮会让触点在电源轨之间扫过每一个电压值。
import time
from machine import ADC
pot = ADC("P6")
VREF = 3.3
while True:
counts = pot.read_u16()
voltage = counts * VREF / 65535
print(voltage, "V")
time.sleep_ms(100)
3.12.5. 用分压器读取更高的电压¶
高于 Vref 的电压会让 ADC 锁定在满量程,如果超过绝对最大额定值还可能损坏输入。要读取更高的源——电池、超出 Vref 范围的传感器输出——请在它到达引脚之前用一个固定分压器把它降下来:
将高压源缩放以适应 ADC:R1 和 R2 构成一个固定分压器,其引出点供给 ADC 引脚。¶
选择 R1 和 R2,使得在你预期的最高输入电压下,分压后的电压仍保持在 ADC 的范围内:
V_adc = V_in × R2 / (R1 + R2)
对于最大 V_in = 12 V 和 3.3 V 参考电压,比值 R2 / (R1 + R2) 最多为 3.3 / 12 ≈ 0.275。一个留有少量余量的常见选择是 R1 = 33 kΩ、R2 = 10 kΩ。比值为 10 / 43 ≈ 0.233,因此 V_adc 最高约为 12 × 0.233 ≈ 2.79 V——安全地低于 Vref。
要从 ADC 读数恢复出原始的 V_in,请反转分压公式:
V_in = V_adc × (R1 + R2) / R2
代码如下:
from machine import ADC
R1 = 33_000
R2 = 10_000
VREF = 3.3
adc = ADC("P6")
counts = adc.read_u16()
v_adc = counts * VREF / 65535
v_in = v_adc * (R1 + R2) / R2
print(v_in, "V")
几点实用说明:
分压器会持续抽取
V_in / (R1 + R2)的电流。当R1 + R2 = 43 kΩ且V_in = 12 V时,约为 280 µA——通常可以忽略,但如果源是电池供电的,可考虑使用更大的电阻(100 kΩ 到 1 MΩ)以减少空闲耗电。电阻的容差(通常为 ±1 % 或 ±5 %)会直接影响测量精度。两个 ±5 % 的电阻在最坏情况下可能给恢复出的
V_in带来约 ±10 % 的误差。分压器的源阻抗会与任何杂散电容结合,对输入进行低通滤波。对于快速变化的信号这一点很重要;但对于电池电压检测则无关紧要。