4、ESP32模拟信号采集:ADC原理、ESP32 ADC使用、电压采集、光敏电阻实战

好,咱们进入第四章。这一章聊的是模拟信号采集,说白了就是让ESP32去“感受”真实世界的电压变化。

你想想看,数字世界只有0和1,但现实世界是连续的。温度、光照、声音,这些都是模拟量。要让ESP32理解它们,就得靠ADC——模数转换器。

4.1 ADC原理:从模拟到数字的桥梁

ADC,全称Analog-to-Digital Converter。它的任务很简单:把连续的电压值,转换成离散的数字值。

怎么转的呢?我打个比方。你有一把尺子,量程是0到10厘米,上面有1024个刻度。你量一个物体,看它落在哪个刻度上,就得到了一个数字。ADC也是这个道理。

ESP32的ADC是12位的。12位意味着什么?2的12次方,也就是4096个刻度。参考电压通常是3.3V。所以,它能分辨的最小电压变化是:

3.3V / 4096 ≈ 0.8mV

嗯,0.8毫伏的精度,对于大多数传感器来说,够用了。

核心公式:

测量电压 = (ADC读数 / 4096) × 参考电压

举个例子:ADC读数是2048,那电压就是 (2048/4096) × 3.3V = 1.65V

我在项目中遇到过一个问题:ADC读数跳得厉害,明明电压没变,数值却上下乱窜。后来发现是参考电压不稳定。所以,稳定的参考电压是ADC精度的前提

4.2 ESP32 ADC硬件特性

ESP32有两个ADC模块:ADC1和ADC2。它们各有8个通道,但用法不太一样。

特性 ADC1 ADC2
通道数 8 (GPIO32-GPIO39) 8 (GPIO0, 2, 4, 12-15, 25-27)
WiFi影响 不受影响 WiFi开启时不可用
推荐使用 ✅ 优先使用 ❌ 尽量避开

⚠️ 重要提醒:

ADC2在WiFi开启时会被占用。如果你同时用WiFi和ADC,ADC2的读数会乱掉。我曾经在这个坑里爬了整整一天。

所以,我的建议是:所有ADC采集,一律用ADC1的通道。省心。

另外,ESP32的ADC输入电压范围是0到3.3V。千万别超了,否则芯片会烧。如果你要测更高的电压,得用分压电阻。

4.3 实战:用ESP32读取电压

好,理论说完了,咱们直接上手。先写一个最简单的电压读取程序。

4.3.1 硬件连接

你需要:

  • 一个电位器(10kΩ就行)
  • 几根杜邦线

接线方式:

  • 电位器中间脚 → GPIO34 (ADC1_CH6)
  • 电位器一脚 → 3.3V
  • 电位器另一脚 → GND

4.3.2 代码实现

#include <driver/adc.h>

void setup() {
  Serial.begin(115200);
  
  // 配置ADC1的通道6
  adc1_config_width(ADC_WIDTH_BIT_12);  // 12位精度
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);  // 0-3.3V范围
}

void loop() {
  int adc_value = adc1_get_raw(ADC1_CHANNEL_6);
  float voltage = adc_value * (3.3 / 4095.0);
  
  Serial.print("ADC值: ");
  Serial.print(adc_value);
  Serial.print("  电压: ");
  Serial.print(voltage);
  Serial.println(" V");
  
  delay(500);
}

💡 个人经验:

注意我用了 ADC_ATTEN_DB_11 这个衰减参数。它表示输入范围是0-3.3V。如果你用 ADC_ATTEN_DB_0,范围只有0-1.1V。这个细节很容易忽略,我刚开始就吃过亏。

上传代码,打开串口监视器。转动电位器,你会看到ADC值和电压值跟着变化。嗯,就是这么简单。

4.4 光敏电阻实战:感知光照强度

光敏电阻,也叫光敏电阻器。它的阻值随光照强度变化:光越强,阻值越小;光越暗,阻值越大。

怎么用ADC读它呢?很简单,搭一个分压电路。

4.4.1 电路原理

把光敏电阻和一个固定电阻(比如10kΩ)串联,中间点接到ADC引脚。这样,光照变化会引起分压点电压变化,ADC就能读到了。

电路连接:

  • 3.3V → 光敏电阻 → GPIO34 → 10kΩ电阻 → GND

4.4.2 代码实现

#define LIGHT_SENSOR_PIN 34

void setup() {
  Serial.begin(115200);
  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
}

void loop() {
  int light_value = adc1_get_raw(ADC1_CHANNEL_6);
  
  // 将ADC值映射到0-100的百分比
  int light_percent = map(light_value, 0, 4095, 0, 100);
  
  Serial.print("光照强度: ");
  Serial.print(light_percent);
  Serial.println("%");
  
  if (light_percent < 20) {
    Serial.println("环境很暗,建议开灯");
  } else if (light_percent < 60) {
    Serial.println("环境正常");
  } else {
    Serial.println("环境很亮");
  }
  
  delay(1000);
}

📊 实际测试数据(我实测的):

环境 ADC值 百分比
手电筒直射 3800 92%
室内日光灯 2000 48%
阴天窗边 1200 29%
抽屉里(全黑) 100 2%

你看,数据很直观。用手遮住光敏电阻,数值会迅速下降。这就是模拟信号采集的魅力——你能真实地“触摸”到环境的变化。

4.5 避坑指南与优化技巧

做ADC采集,有几个坑是绕不开的。我一个个说。

4.5.1 噪声问题

ADC读数跳动,这是最常见的。原因可能是:

  • 电源纹波太大
  • 信号线太长,引入了干扰
  • 参考电压不稳定

解决办法:

  • 在ADC引脚对地加一个0.1μF的电容,滤除高频噪声
  • 软件上做多次采样取平均
// 多次采样取平均,消除噪声
int read_adc_average(int channel, int samples) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += adc1_get_raw(channel);
    delay(1);  // 每次采样间隔1ms
  }
  return sum / samples;
}

// 使用示例
int value = read_adc_average(ADC1_CHANNEL_6, 10);

💡 我的习惯:

我一般取10次平均,采样间隔1ms。这样既消除了噪声,又不会太慢。如果你对实时性要求高,可以减到5次。

4.5.2 非线性问题

ESP32的ADC在接近0V和接近3.3V时,线性度会变差。说白了,就是两端不准。

我曾经做过一个项目,要求测量0.1V到3.2V的电压。结果发现,低于0.2V时,ADC读数几乎不变。后来我查了手册才知道,ESP32的ADC在两端确实有非线性区。

解决办法:

  • 避开两端,只使用中间80%的量程
  • 或者做软件校准,用已知电压反推校正曲线

4.5.3 输入阻抗问题

ADC采样时,会从信号源吸取一点电流。如果信号源内阻太大,电压会被拉低,导致读数不准。

光敏电阻的阻值变化范围很大(几kΩ到几MΩ),这个问题尤其明显。我的建议是:

  • 分压电阻选10kΩ左右,不要太大
  • 如果信号源内阻很高,加一个运放做电压跟随器

4.6 本章小结

这一章我们干了三件事:

  1. 理解了ADC的原理——把模拟电压变成数字值
  2. 学会了ESP32的ADC配置——用ADC1,别用ADC2
  3. 实战了光敏电阻采集——搭电路、写代码、看数据

模拟信号采集是IoT项目的基础。你想想看,温度、湿度、光照、气压,哪个不是模拟量?掌握了ADC,就等于打开了感知世界的大门。

下一章,我们会把这些数据通过WiFi发到腾讯云上。到时候,你就能在手机上看实时数据了。嗯,那才是真正的“上云”。

📌 本章核心要点:

  • ADC1优先,ADC2在WiFi下不能用
  • 12位精度,0-4095对应0-3.3V
  • 衰减参数用ADC_ATTEN_DB_11
  • 多次采样取平均,消除噪声
  • 光敏电阻用分压电路,简单可靠