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() 始终返回一个介于 065535 之间的无符号 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. 分压器

在电源轨和地之间串联的两个电阻构成一个分压器。它们之间的节点电压由两个电阻的比值决定:

一个分压器。顶部的 Vin 通过 R1 连接到一个引出为 V_out 的节点,该节点再通过 R2 连接到地。

分压器:串联的 R1R2Vin 按比例降低到 V_out

V_out = Vin × R2 / (R1 + R2)

相等的电阻给出电源轨电压的一半;R2 远小于 R1 时,引出点接近地;R2 远大得多时,引出点接近电源轨。

该公式假设没有其他元件从 V_out 抽取明显的电流。ADC 引脚是高阻抗的(兆欧、纳安级),很容易满足这一条件,因此给 ADC 供电的分压器的行为与公式预测的一致。

3.12.4. 电位器

电位器是一个单一的物理元件,本身就是一个分压器,带有一个在两端之间移动引出点的滑动触点(wiper)。转动旋钮会同时改变 R1R2,同时保持它们的总和(电位器的总电阻)不变。

一个接在 3.3 V 和地之间的电位器。触点引出到一个 ADC 引脚。

接成 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 范围的传感器输出——请在它到达引脚之前用一个固定分压器把它降下来:

一个将高 V_in 降低后供给 ADC 引脚的分压器。R1 从 V_in 向下接到一个结点,该结点水平引出到 ADC 引脚;R2 从该结点继续向下接到地。

将高压源缩放以适应 ADC:R1R2 构成一个固定分压器,其引出点供给 ADC 引脚。

选择 R1R2,使得在你预期的最高输入电压下,分压后的电压仍保持在 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 R2 = 10 。比值为 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 V_in = 12 V 时,约为 280 µA——通常可以忽略,但如果源是电池供电的,可考虑使用更大的电阻(100 kΩ 到 1 MΩ)以减少空闲耗电。

  • 电阻的容差(通常为 ±1 % 或 ±5 %)会直接影响测量精度。两个 ±5 % 的电阻在最坏情况下可能给恢复出的 V_in 带来约 ±10 % 的误差。

  • 分压器的源阻抗会与任何杂散电容结合,对输入进行低通滤波。对于快速变化的信号这一点很重要;但对于电池电压检测则无关紧要。