3、ESP32模拟信号采集:ADC模拟输入、电压读取、光敏电阻与电位器实战
好,咱们今天来聊聊ESP32的ADC。说白了,就是让ESP32能“听懂”模拟世界的语言。
数字世界只有0和1,但现实世界是连续的电压、温度、光照。ADC(模数转换器)就是连接这两个世界的桥梁。ESP32内部集成了两个12位的ADC,精度还不错,能分辨出4096个不同的电压等级。
我个人习惯把ADC比作一把尺子。这把尺子量程是0到3.3V,刻度有4096格。你给它一个电压,它告诉你对应的数字是多少。反过来,我们也能通过数字反推电压值。
3.1 ESP32的ADC硬件特性
先说说ESP32的ADC有哪些特点,免得你踩坑。
- 分辨率:12位,也就是0-4095的数值范围
- 输入电压范围:0 ~ 3.3V(注意!不能超过3.3V,否则会烧引脚)
- 可用通道:ADC1有8个通道(GPIO32-GPIO39),ADC2有10个通道
- 注意:ADC2的通道在WiFi开启时会被占用,所以我建议你优先使用ADC1的引脚
3.2 基础代码:读取ADC值
先来个最简单的例子,读取电位器(可变电阻)的电压值。
// 定义ADC引脚
const int adcPin = 34; // GPIO34,属于ADC1
void setup() {
Serial.begin(115200);
// 不需要额外配置,analogRead直接可用
}
void loop() {
int adcValue = analogRead(adcPin);
float voltage = adcValue * (3.3 / 4095.0);
Serial.print("ADC值: ");
Serial.print(adcValue);
Serial.print(" 电压: ");
Serial.print(voltage);
Serial.println(" V");
delay(500);
}
这段代码很简单,但有个细节我想提一下。计算电压时,我用了 3.3 / 4095.0 而不是 3.3 / 4096。为什么?因为ADC的读数范围是0到4095,总共4096个值。但实际测量中,0对应0V,4095对应3.3V,所以分母用4095更准确。嗯,这个小细节当年让我困惑了好一阵子。
3.3 实战一:光敏电阻测光照
光敏电阻(LDR)的阻值会随光照强度变化。光线越强,阻值越小。我们把它和一个固定电阻串联,中间抽头接到ESP32的ADC引脚,就能读出光照强度了。
电路连接很简单:
- 3.3V → 10kΩ电阻 → A点 → 光敏电阻 → GND
- A点 → GPIO34
代码实现:
const int ldrPin = 34;
void setup() {
Serial.begin(115200);
}
void loop() {
int ldrValue = analogRead(ldrPin);
// 将ADC值映射到0-100的百分比
// 注意:这个映射不是线性的,只是做个演示
int lightPercent = map(ldrValue, 0, 4095, 0, 100);
Serial.print("光照强度: ");
Serial.print(lightPercent);
Serial.println("%");
// 简单的判断
if (lightPercent < 20) {
Serial.println("环境很暗,建议开灯");
} else if (lightPercent < 60) {
Serial.println("环境正常");
} else {
Serial.println("环境很亮");
}
delay(1000);
}
3.4 实战二:电位器控制LED亮度
这个实验很经典,用电位器控制LED的亮度。ADC读取电位器的电压,然后通过PWM控制LED。
电路连接:
- 电位器中间脚 → GPIO34
- 电位器另外两脚 → 3.3V 和 GND
- LED正极(长脚) → GPIO2(通过220Ω电阻)
- LED负极(短脚) → GND
const int potPin = 34;
const int ledPin = 2;
void setup() {
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
}
void loop() {
int potValue = analogRead(potPin);
// 将ADC值(0-4095)映射到PWM值(0-255)
int pwmValue = map(potValue, 0, 4095, 0, 255);
analogWrite(ledPin, pwmValue);
Serial.print("电位器值: ");
Serial.print(potValue);
Serial.print(" PWM值: ");
Serial.println(pwmValue);
delay(50); // 快速响应
}
这里有个小技巧。我习惯把 delay(50) 而不是 delay(500)。为什么?因为电位器是手动调节的,响应要快才自然。你转一下旋钮,灯要立刻跟着变,不能有半秒延迟,否则体验很差。
3.5 ADC精度与校准
说实话,ESP32的ADC精度不算顶尖。官方文档也承认,在3.3V附近有±6%的误差。这在实际项目中可能是个问题。
我分享一个简单的校准方法:
- 用万用表测量一个稳定的电压(比如1.65V,即3.3V的一半)
- 用ESP32读取这个电压的ADC值
- 计算实际电压和测量电压的比例关系
- 把这个比例写入代码中
// 校准系数,需要根据实际测量调整
float calibrationFactor = 1.05; // 假设实际值比测量值大5%
void loop() {
int adcValue = analogRead(adcPin);
float rawVoltage = adcValue * (3.3 / 4095.0);
float calibratedVoltage = rawVoltage * calibrationFactor;
Serial.print("校准前: ");
Serial.print(rawVoltage);
Serial.print("V 校准后: ");
Serial.print(calibratedVoltage);
Serial.println("V");
delay(1000);
}
| 实际电压(V) | 理论ADC值 | 典型实测值 | 误差 |
|---|---|---|---|
| 0.0 | 0 | 0-5 | 较小 |
| 1.65 | 2048 | 2000-2100 | ±2% |
| 3.0 | 3724 | 3500-3700 | ±5% |
| 3.3 | 4095 | 3900-4095 | ±6% |
3.6 避坑指南
做ADC项目时,有几个坑我踩过,分享给你:
- 引脚电压不能超过3.3V:哪怕一瞬间超过,都可能烧坏引脚。如果需要测量更高电压,必须用分压电阻
- ADC2与WiFi冲突:如果你同时用WiFi和ADC,记得只用ADC1的引脚(GPIO32-GPIO39)
- 电源噪声影响:ESP32的ADC对电源噪声很敏感。我建议在ADC引脚附近加一个0.1μF的电容到GND,能有效滤除高频噪声
- 多次采样取平均:单次采样可能不稳定,我习惯连续读10次取平均,能提高稳定性
// 多次采样取平均的示例
int readAverage(int pin, int samples) {
long sum = 0;
for (int i = 0; i < samples; i++) {
sum += analogRead(pin);
delay(1); // 短暂延时,让ADC稳定
}
return sum / samples;
}
好了,关于ESP32的ADC模拟输入,核心内容就这些。从硬件特性到基础代码,从光敏电阻到电位器控制,再到精度校准和避坑技巧,你应该能上手实战了。记住,实践出真知,拿起你的ESP32和几个元件,动手试试吧。