ESP32芯片的模数转换器在物联网项目中经常用来读取传感器模拟量,但许多开发者发现在启用Wi-Fi后,ADC读数出现严重跳动,甚至无法获取有效电压。这个现象并非代码逻辑错误,而是与芯片内部ADC分组和射频模块的资源竞争有关。本文将解析冲突的硬件根源,通过实测数据对比ADC1与ADC2在Wi-Fi开启前后的差异,并提供多种可以在MicroPython中直接使用的解决方案。

冲突根源:ADC2与Wi-Fi射频共享内部资源
ESP32的ADC分为两组,ADC1支持8个通道,对应GPIO32至GPIO39;ADC2支持10个通道,对应GPIO0、2、4、12至15以及25至27等。这两组ADC的内部模拟前端并不完全相同。ADC1拥有独立的采样保持电路和比较器,而ADC2的部分模拟通路与Wi-Fi射频校准共用。当Wi-Fi模块处于激活状态时,它会周期性占用ADC2进行内部功率检测和信道状态评估,导致用户发起的ADC2采样请求被抢占或受到射频干扰。
从ESP-IDF官方文档可以明确看到:使用ADC2时若同时开启Wi-Fi,API会返回错误或读取值不可靠。这并不是驱动bug,而是硬件设计上的限制。具体来说,Wi-Fi在进行信道扫描、连接和保活时会周期性占用ADC2,这个占用过程对用户代码完全透明,无法通过普通配置取消。在MicroPython中,我们虽然无法直接看到底层冲突,但通过实验可以验证:在Wi-Fi关闭时读取ADC2正常,Wi-Fi连接后读数偏差甚至超过300mV。
实测对比:ADC1与ADC2在Wi-Fi开启前后的读数差异
为了量化冲突的影响,可以设计一个简单实验:使用两块分压电阻将3.3V电压分压到约1.65V,分别连接到ADC1的GPIO34和ADC2的GPIO14。编写MicroPython脚本,先关闭Wi-Fi读取100次,再开启Wi-Fi并连接热点后读取100次,计算平均值、最大值、最小值和标准差。
from machine import ADC, Pin
import network
import time
adc1 = ADC(Pin(34))
adc1.atten(ADC.ATTN_11DB)
adc2 = ADC(Pin(14))
adc2.atten(ADC.ATTN_11DB)
def measure(adc, n=100):
total = 0
max_v = 0
min_v = 4095
for _ in range(n):
val = adc.read()
total += val
if val > max_v:
max_v = val
if val < min_v:
min_v = val
time.sleep_ms(2)
avg = total / n
return avg, min_v, max_v
print('Wi-Fi关闭时:')
avg1, min1, max1 = measure(adc1)
avg2, min2, max2 = measure(adc2)
print('ADC1 平均:{:.2f} 最小:{} 最大:{}'.format(avg1, min1, max1))
print('ADC2 平均:{:.2f} 最小:{} 最大:{}'.format(avg2, min2, max2))
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('your_ssid', 'your_password')
while not wlan.isconnected():
time.sleep_ms(500)
print('Wi-Fi已连接')
print('Wi-Fi开启时:')
avg1, min1, max1 = measure(adc1)
avg2, min2, max2 = measure(adc2)
print('ADC1 平均:{:.2f} 最小:{} 最大:{}'.format(avg1, min1, max1))
print('ADC2 平均:{:.2f} 最小:{} 最大:{}'.format(avg2, min2, max2))
实验结果显示,ADC1的均值变化通常小于5mV,标准差也很小;ADC2在Wi-Fi开启后均值可能漂移数十到数百毫伏,标准差显著增大。这说明ADC2在Wi-Fi启用时已经无法用于精确测量。值得注意的是,即使没有实际数据传输,只要Wi-Fi模块处于激活状态,ADC2的干扰就会存在。因此单纯降低数据发送频率并不能完全解决问题。
三种解决方案:硬件选型、分时复用与外部ADC
方案一是优先使用ADC1。在硬件设计阶段就将模拟传感器连接到ADC1对应的GPIO引脚,例如GPIO32、GPIO33、GPIO34、GPIO35、GPIO36、GPIO37、GPIO38、GPIO39。ADC1与Wi-Fi没有资源冲突,读数稳定可靠。同时注意输入电压不能超过3.3V,必要时在ADC初始化时设置衰减参数,例如adc.atten(ADC.ATTN_11DB),以支持0至3.3V的输入范围。
方案二是分时复用ADC2。如果项目必须使用ADC2的引脚,例如ADC1已被其他外设占用,可以在采样前临时关闭Wi-Fi,采样完成后再恢复。关闭Wi-Fi使用wlan.active(False),恢复使用wlan.active(True)。这种方法会断开网络连接,只适合允许短暂断网的场景,频繁开关会增加重连开销和功耗。
import network
import time
from machine import ADC, Pin
adc2 = ADC(Pin(14))
adc2.atten(ADC.ATTN_11DB)
wlan = network.WLAN(network.STA_IF)
def read_adc2_offline():
wlan.active(False)
time.sleep_ms(100)
value = adc2.read()
wlan.active(True)
return value
方案三是采用外部ADC芯片,如ADS1115、ADS1015等通过I2C接口通信,彻底避开ESP32内部ADC的限制。这样即使Wi-Fi全速运行也不影响模拟量采集,且分辨率和精度通常更高。该方案需要额外硬件成本,但适合对稳定性要求高的工业采集场景。
MicroPython自动适配示例:检测Wi-Fi状态并选择ADC
在实际项目中,可以通过软件逻辑在开发阶段帮助识别潜在的ADC2冲突。下面这个函数会在Wi-Fi开启且目标引脚属于ADC2时输出警告,提醒开发者切换到ADC1或采用其他方案。虽然它不能自动修复硬件限制,但可以作为初始化阶段的检查工具。
from machine import ADC, Pin
import network
ADC2_PINS = {0, 2, 4, 12, 13, 14, 15, 25, 26, 27}
def read_adc_safe(pin_num):
if network.WLAN(network.STA_IF).active() and pin_num in ADC2_PINS:
print('警告:Wi-Fi开启时ADC2通道读数不可靠')
adc = ADC(Pin(pin_num))
adc.atten(ADC.ATTN_11DB)
return adc.read()
更稳妥的做法是在系统初始化阶段就根据Wi-Fi使能状态分配引脚。例如定义一个引脚映射表,如果Wi-Fi启用则只使用ADC1对应的引脚,否则允许使用全部ADC引脚。这样可以从源头上避免冲突,而不是等到运行时才发现读数异常。
常见误区与调试建议
第一个误区是认为ADC2读数跳动是因为电源噪声,于是加大滤波电容、使用独立稳压器,结果改善有限。因为根源是射频共享而不是电源纹波。当然电源去耦仍然重要,但不能解决ADC2被Wi-Fi占用的问题。第二个误区是在Wi-Fi开启时仅使用软件滤波,比如多次采样取平均,期望消除干扰。软件滤波可以平滑数据,但无法纠正硬件冲突导致的系统性偏移,平均值可能稳定地错误。
调试时建议先确认芯片型号。ESP32经典款(如ESP32-D0WD)的ADC2与Wi-Fi冲突明显;ESP32-S2、S3等较新型号中,ADC2与Wi-Fi的冲突已大幅改善甚至不存在。可以通过MicroPython中的import esp然后执行esp.chip_id()查看芯片ID辅助判断,但最可靠的方法还是实测对比。编写测试脚本时,应分别连接固定电压源到ADC1和ADC2,记录Wi-Fi开关前后的数据。如果ADC2在无Wi-Fi时正常,说明引脚和ADC初始化没有问题,问题来自射频干扰。避免在Wi-Fi信道扫描和连接过程中进行采样,因为此时射频活动最密集。若必须使用ADC2,可以等待wlan.isconnected()返回True后延迟几秒再采样,但效果依然有限。
ESP32MicroPythonADC修改时间:2026-08-30 07:01:42