教程 · 入门 · 30 分钟
读取模拟电压:ADC 参考电压与分压电路
analogRead 返回的是比值,不是电压。本文讲如何选一个不会跌落的参考电压、如何配分压电阻,以及如何做一个读数可信的电池监测器。
引言
analogRead() 是 Arduino 上最友好的函数之一,同时也是最安静地骗人的函数之一。它返回一个 0 到 1023 之间的数,所有人都把这个数当成电压,而绝大多数时候并不会出事。
在机器人上它就会出事,因为机器人有电机,而电机恰好会挪动这个数的参考基准。
analogRead 返回的是比值
ADC 并不测量电压。它把输入与一个参考作比较,然后报告这个比值:
reading = 1023 × V_in / V_ref
在 Arduino Uno 上,V_ref 默认取 AVCC,也就是板子的 5 V 轨。所以 analogRead() 真正回答的问题是“这是 5 V 轨的几分之几”,乘以 5/1023 换算成电压,只有在轨压恰好是 5 V 时才成立。
而电机一跑,你就知道它并不是。
参考电压才是问题的根源
把电池经分压器接到 A0 上,然后在电机启动时盯着这个数看。
建立在第一种设计上的低电截止 做的恰恰是与目的相反的事:机器人越使劲干,电池看起来越健康。而且这个故障是安静的,因为每一次单独的读数都是一个完全有效的比值。
解法是换一个不会动的参考。ATmega328P 板载一个 1.1 V 带隙参考:
analogReference(INTERNAL); // 1.1 V on an Uno; INTERNAL1V1 on a Mega
analogRead(A0); // discard — the reference needs a moment to settle
切换参考之后第一次读数一定要扔掉。内部参考和输入电容都需要时间,第一个采样是在它们还没稳定时取到的。
分压器怎么配
现在参考是 1.1 V,而电池包是 8.4 V,所以分压器必须把量程顶端压到参考之下:
V_adc = V_pack × R2 / (R1 + R2)
取 R1 = 68 kΩ、R2 = 10 kΩ,比值是 0.128,满电的 8.4 V 电池包送到 ADC 的是 1.077 V——刚好在参考之下,几乎用满了整个量程。任何你设计的分压器都值得核对这三个数:
| 核对项 | 这组分压器 | 为什么 |
|---|---|---|
| 量程顶端与参考之比 | 1.077 V 对 1.1 V | 超过参考就会削平 |
| 静态电流 | 108 µA | 一天耗 2.6 mAh——相对 2000 mAh 可以忽略 |
| 输出阻抗(R1∥R2) | 8.7 kΩ | 保持在约 10 kΩ 以下,见下文 |
把它和朴素版本比一比:20 kΩ/10 kΩ 分压器配 5 V 轨参考。满电电池包会送出 2.80 V,占 1023 中的 573 个计数;而在电池包整个 6.0–8.4 V 范围内,它只用到 164 个计数,更好的设计用到 286 个。分辨率几乎翻倍,代价是同样两个电阻和一行代码。
分辨率不等于精度
10 位配 5 V 参考是每步 4.88 mV;配 1.1 V 参考是每步 1.07 mV。这两个数都不是你的精度。
真正限制你的是别的东西:
- 参考容差。带隙标称 1.1 V,芯片之间的差异有几个百分点。这是一个固定的标度误差,所以每块板子量一次:喂进一个已知电压,读出计数,反解出真实的参考值,把这个常数存下来。这是标定步骤,不是可有可无的润色。
- **噪声。**机器人上的电源是嘈杂的,而平均是最便宜的解法。平均 N 个采样把随机噪声降低 √N——16 个采样安静 4 倍,白赚一位可用分辨率。它对标度误差毫无作用。
- **源阻抗。**ADC 通过外接的那个东西给一小电容(采样保持电容)充电。高阻抗的源在允许的时间里充不满它,而上一个通道残留的电压还留在里面。
最后这一项会产生一个值得单独命名的典型故障。
读起来像邻居的通道
用高阻抗源轮流读 A0 和 A1,每次读数都会被拉向前一个的值。在 A0 上扫动电位器,会看到 A1 也跟着动。并没有接错线。
有两个修法,可以同时用:
int readSettled(uint8_t pin) {
analogRead(pin); // charge the sample-and-hold on this channel
return analogRead(pin); // then take the one you keep
}
读两遍并扔掉第一遍,等于给电容第二个采样窗口去跟上新电压。另一个修法是降低源阻抗——这正是上面那组分压器用 68 kΩ/10 kΩ 而不是看起来更整齐、却给出 50 kΩ 输出阻抗的 100 kΩ/100 kΩ 的原因。
这在机器人上出现在哪
- 电池监控,如上——它也是让低电压截止值得信赖、足以保护锂电包的原因。
- 模拟循迹传感器。红外反射阵列为每个传感器给出一个连续值,这正是循迹能算出平滑位置而不是一个左右标志位的原因。这些传感器读的正是电机压塌的那条轨,这也是循迹机器人的标定会随电池电量下降而漂移的原因之一。
- 电流传感,分流器或霍尔传感器,输出是一个电压。
- 电位器当作调参旋钮,这是无需重新烧录就调整 PID 增益最友好的办法。
- 不适用于 3.3 V 板子上的 5 V 传感器。ESP32或树莓派需要这组分压器是为了保护,不只是为了量程。
出问题时
| 现象 | 通常原因 |
|---|---|
| 电机一启动电池读数就涨 | 参考取自会塌的轨压;改用 INTERNAL |
| 读数钉在 1023 | 输入超过参考——分压比太大 |
| 某个通道复读上一个通道的值 | 源阻抗太高;读两遍并扔掉第一遍 |
| 读数几十个计数地乱跳 | 电源噪声;做平均,并检查电源轨 |
| 电压持续偏几个百分点 | 内部参考没标定——量一次 |
analogReference() 之后第一次读数是错的 |
正常;扔掉 |
| 电池越空循迹标定漂得越多 | 在下沉的轨压上做轨压参考读数 |
| 只用到 15% 量程 | 分压器按 5 V 设计,却配 1.1 V 参考读 |
能挡掉大部分这类问题的原则是:**在做其他任何决定之前,先决定你的参考是什么。**ADC 给你的每一个数都是相对于它的,参考一动,每个读数就同时在讲两件事。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读