模拟输入
本节将介绍 ADC(模数转换器)的基本概念,并通过读取电位器的电压值,讲解如何在 ESP32 MicroPython 环境中读取模拟信号。
1. 模拟信号
模拟信号是一种在一定范围内可以连续变化的信号。
比如调光灯旋钮,可以平滑地调节灯的亮度,从完全熄灭到最亮,中间有无数个亮度级别。这个亮度调节的过程就是模拟的。而数字信号就像普通电灯开关,只有“开”和“关”。
现实世界中,像温度、光线强度、声音大小等许多物理量都是模拟的,它们的变化是连续而平滑的。
对于 ESP32,若要测量连续变化的信号(例如读取电位器、传感器的数值),仅用 1 (High) 或 0 (Low) 两种数字状态是无法实现的。此时,就需要使用 ADC。
-
ADC(Analog-to-Digital Converter, 模数转换器):一种能将连续的模拟电压信号(例如 0V ~ 3.3V)转换为 ESP32 可处理的数字值的设备。
简单来说,ADC 就像一把将 0~3.3V 之间的电压切分成很多“刻度”的尺子,让每个电压范围都对应一个具体的数字。ADC 能将电压细分成多少个等级,这个能力就叫做分辨率。分辨率越高,能检测的电压变化就越细致。
ESP32 的 ADC 通常是 12 位的,也就是一共可以分成 2¹²(= 4096) 个等级。因此,ADC 的读数范围是 0 ~ 4095。
- 输入电压为 0V,ADC 读数约等于 0。
- 当输入电压从 0V 连续变化到 3.3V 时,ADC 读数会相应地从 0 连续变化到 4095。

这样,MicroPython 程序就可以通过 adc.read() 得到 0~4095 之间的整数,这个数值直接对应了输入引脚上的电压大小。
2. ADC 引脚
并不是所有 ESP32 引脚都支持模拟输入。需要查询对应开发板的引脚图或者芯片的手册,查找标有“ADC”的引脚作为模拟输入使用。
建议优先选择 ADC1 通道的引脚,以避免与其他功能冲突。
| 芯片型号 | ADC 通道 1 (建议使用) | ADC 通道 2 | 参考文档 |
|---|---|---|---|
| ESP32 | GPIO32 - GPIO39 | GPIO0, 2, 4, 12-15, 25-27 | ESP32 技术规格书 2.2 节 |
| ESP32-C3 | GPIO0 - GPIO5 | - | ESP32-C3 技术规格书 2.3.2 节 |
| ESP32-C6 | GPIO0 - GPIO6 | - | ESP32-C6 技术规格书 2.3.3 节 |
| ESP32-C5 | GPIO1 - GPIO6 | - | ESP32-C5 技术规格书 2.3.3 节 |
| ESP32-S3 | GPIO1 - GPIO10 | GPIO11 - GPIO20 | ESP32-S3 技术规格书 2.3.3 节 |
| ESP32-P4 | GPIO16 - GPIO23 | GPIO49 - GPIO54 | ESP32-P4 技术规格书 2.3.3 节 |
| 其它 | - | - | 乐鑫文档中心(CDP) |
3. 搭建电路
需要使用的器件有:
- 电位器 * 1
- 面包板 * 1
- 导线
- ESP32 开发板
ESP32-S3-Zero 引脚图


电路解析
让我们理解一下这个模拟信号读取电路是如何工作的:
-
电位器的连接:
- VCC 引脚:连接到 ESP32 的 3.3V,提供电位器工作电压。
- GND 引脚:连接到 ESP32 的 GND,形成电路回路。
- 信号引脚(中间):连接到 ESP32 的 GPIO7(ADC 引脚),输出 0V~3.3V 之间的模拟电压。
-
电位器工作原理:
- 电位器内部是一个可变电阻,通过旋转可以改变阻值。
- 旋转电位器旋钮时,信号引脚的输出电压会在 0V 到 3.3V 之间连续变化。
- 完全逆时针:输出接近 0V。
- 完全顺时针:输出接近 3.3V。
4. 代码
4.1 REPL 交互
在编写完整程序前,可通过 REPL 熟悉 ADC 相关函数。
在 Shell 中逐行输入以下命令并观察现象:
from machine import Pin, ADC
pot = ADC(Pin(7)) # 在 GPIO7 上创建 ADC 对象
pot.read() # 直接读取,此时数值应反映当前电位器位置 (0-4095)
pot.read() # 再次读取
pot.read_uv() # 读取电压值,单位微伏 (uV)
4.2 完整代码示例
在 Thonny IDE 中新建文件,输入并运行以下代码。此代码将循环读取电压值,并按特定格式输出,以便配合 Thonny 的“绘图仪”功能使用。
import time
from machine import Pin, ADC
# 定义电位器连接的引脚 (GPIO 7)
POT_PIN = 7
# 初始化 ADC
# 1. 创建 ADC 对象关联引脚
pot = ADC(Pin(POT_PIN))
while True:
# 读取原始模拟值 (0 - 4095)
adc_value = pot.read()
# 读取电压值 (单位:微伏 uV),并转换为毫伏 (mV)
voltage_uv = pot.read_uv()
voltage_mv = voltage_uv / 1000
# 格式化输出,方便在绘图仪中观察
# 格式:Label:Value
print("ADC:", adc_value, ",Voltage_mV:", voltage_mv)
# 延时 0.1 秒
time.sleep(0.1)
运行结果:
将代码运行在 ESP32 开发板上后,Shell 窗口将持续输出数值。
在 Thonny IDE 中,点击菜单栏的 “视图 (View)” -> “绘图仪 (Plotter)”,即可看到右侧出现实时的波形图。转动电位器,曲线会随之升降。

可能会观察到,ADC 的读数在电压未达到 3.3V 时就已饱和(达到 4095),或在两端(接近 0V 和 3.3V)表现出非线性。
此为 ESP32 ADC 的设计特性之一。其内部核心电路能直接处理的电压范围有限,因此使用名为衰减器 (Attenuator) 的内部模块扩展可测量电压范围。
在 MicroPython 环境下,固件默认会启用一个能测量最高电压的衰减选项。根据官方文档,此设置下 ESP32 S3 ADC 可靠测量上限约为 3.1V。(注意:不同芯片在不同的衰减选项下,可测量的输入电压范围是不同的。查看 此表。)
因此,当输入电压超过 3.1V 时,读数就会“饱和”,即保持在最大值 4095。
因此,read_uv() 是更推荐的方式,它会利用出厂校准数据将原始读数转换为较为准确的电压值(微伏),在一定程度上补偿了非线性和参考电压的误差。
代码解析
-
pot = ADC(Pin(POT_PIN))创建一个 ADC 对象来控制指定的 GPIO 引脚。
-
pot.read()读取 ADC 的原始数值。对于 ESP32,这通常是一个 12 位的数值,范围是 0 ~ 4095。
-
pot.read_uv()直接返回以微伏 (μV) 为单位的校准电压值。这是一个非常实用的函数,不需要手动进行
(读数 / 4095) * 3300的数学运算,且通常经过了出厂校准,精度更高。 -
print("ADC:", adc_value, ",Voltage_mV:", voltage_mv)Thonny 绘图仪格式说明: Thonny 的绘图仪通过识别 Shell 中的打印输出来绘制曲线。为了让绘图仪正确显示数据,建议遵循以下格式:
- 纯数值:每行打印一个或多个数值(用逗号或空格分隔)。
- 键值对(推荐):使用
名称:数值的格式。例如print("ADC:", value)。
这种格式不仅能绘制曲线,还能在图例中显示每条曲线的名称,方便区分多组数据。
5. 拓展:减少噪声
当停止转动电位器时,可能会观察到 ADC 读数并未稳定停留在某一个值,而是在小范围内持续跳动,有时甚至出现较大毛刺。

这种现象通常是由噪声引起的。ESP32 的 ADC 对电源噪声和外部环境的电磁干扰比较敏感。
要减少噪声的影响,通常有两种方法:
-
硬件滤波:在 ADC 输入引脚和 GND 之间并联一个 0.1µF (100nF) 的陶瓷电容,滤除高频干扰。
-
软件滤波:通过算法对多次采样结果进行处理。最简单有效的方法是均值滤波。
代码示例:去极值均值滤波
以下代码演示了如何连续采样多次,去除最大值和最小值后取平均,从而获得平滑的读数。
import time
from machine import Pin, ADC
POT_PIN = 7
pot = ADC(Pin(POT_PIN))
def read_average_adc(adc_obj, times=10):
"""
连续读取多次 ADC 值,去掉最大值和最小值后计算平均数
:param adc_obj: ADC 对象
:param times: 采样次数,默认为 10 次
:return: 平均后的整数值
"""
val_list = []
for _ in range(times):
val_list.append(adc_obj.read())
time.sleep_ms(1) # 采样间隔
# 去掉最大值和最小值,计算剩余数据的平均值
if len(val_list) > 2:
val_list.remove(min(val_list))
val_list.remove(max(val_list))
return int(sum(val_list) / len(val_list))
while True:
# 获取 20 次采样的平均值
smooth_value = read_average_adc(pot, 20)
print("Raw:", pot.read(), "Smooth:", smooth_value)
time.sleep(0.1)
应用软件滤波后,可以看到波形中的毛刺减少,曲线更加平滑。
