3、传感器数据采集基础:使用Arduino/ESP32读取模拟传感器数据、ADC转换原理
好,咱们进入实战环节的第一个硬核知识点。
传感器数据采集,说白了就是让单片机“听懂”物理世界的声音。你想想看,温度、水位、浊度,这些物理量都是连续的模拟信号。但单片机只认0和1,怎么办?
这时候就需要ADC(模数转换器)出场了。我个人习惯把ADC比作一个“翻译官”——它把连续的电压值,翻译成单片机能处理的数字。
3.1 ADC转换原理:从电压到数字
ADC的核心原理其实不复杂。它内部有一个参考电压(比如ESP32的3.3V),然后通过比较器逐级逼近,把输入电压量化成二进制数。
关键公式:
数字值 = (输入电压 / 参考电压) × (2^分辨率 - 1)
举个例子:
- 参考电压3.3V
- 输入电压1.65V
- 10位ADC(分辨率1024)
- 数字值 = (1.65 / 3.3) × 1023 ≈ 511
嗯,这里要注意:分辨率越高,能区分的电压台阶就越细。10位ADC能区分1024个台阶,12位就是4096个。我在项目中遇到过用10位ADC测pH值,结果精度不够,后来换了12位才解决问题。
避坑指南:
我曾经在项目里直接用3.3V做参考电压,结果传感器输出只有0-1V,白白浪费了大部分ADC量程。后来我改用外部参考电压芯片,把量程匹配到0-1.1V,精度直接提升了3倍。
3.2 Arduino/ESP32的ADC硬件差异
Arduino Uno和ESP32的ADC,虽然原理一样,但脾气完全不同。
| 参数 | Arduino Uno (ATmega328P) | ESP32 |
|---|---|---|
| 分辨率 | 10位 (0-1023) | 12位 (0-4095) |
| 参考电压 | 默认5V (可改内部1.1V) | 默认3.3V (可衰减到0-1.1V) |
| 输入范围 | 0-5V | 0-3.3V (超过会烧) |
| 采样速率 | 约10kHz | 最高200kHz (但噪声大) |
| 线性度 | 较好 | 较差 (中间段有非线性) |
你看,ESP32虽然分辨率高,但线性度是个坑。我刚开始用ESP32做水位监测时,发现中间段的读数总跳变,后来查资料才知道是ADC的非线性问题。解决办法是:避开中间段,或者做软件校准。
我的经验:
如果你用ESP32做高精度测量,建议用analogReadMilliVolts()函数直接读毫伏值,而不是读原始ADC值。这个函数内部做了校准,能减少不少误差。
3.3 实战:读取模拟水位传感器
咱们拿一个最简单的模拟水位传感器来练手。这种传感器输出0-3.3V,电压越高表示水位越高。
硬件连接:
- 传感器VCC → ESP32 3.3V
- 传感器GND → ESP32 GND
- 传感器OUT → ESP32 GPIO34 (ADC1_CH6)
代码示例(ESP32):
// 水位传感器读取示例
#define WATER_SENSOR_PIN 34
void setup() {
Serial.begin(115200);
// ESP32的ADC默认12位,无需额外配置
analogReadResolution(12); // 显式设置12位分辨率
}
void loop() {
int rawValue = analogRead(WATER_SENSOR_PIN);
float voltage = rawValue * (3.3 / 4095.0);
// 简单映射到0-100%水位
int percent = map(rawValue, 0, 4095, 0, 100);
Serial.print("原始值: ");
Serial.print(rawValue);
Serial.print(" | 电压: ");
Serial.print(voltage, 3);
Serial.print("V | 水位: ");
Serial.print(percent);
Serial.println("%");
delay(500);
}
这段代码很简单,但有几个细节要注意:
analogReadResolution(12)这行我建议加上,虽然默认就是12位,但显式声明能避免后续代码移植时出问题。- 电压计算时,我用的是3.3V参考电压。如果你的传感器输出范围不同,记得调整。
map()函数做线性映射,但实际传感器往往不是线性的。我在项目中遇到过水位传感器在低水位时电压变化很小,高水位时变化剧烈。这时候需要做分段校准。
警告:
ESP32的ADC输入电压绝对不能超过3.3V!我曾经有一次不小心接了5V传感器,结果GPIO34直接烧了。如果你需要测0-5V信号,必须用分压电阻把电压降到3.3V以下。
3.4 提高采样精度的技巧
实际项目中,ADC读数往往有噪声。你想想看,电源纹波、电磁干扰、传感器自身抖动,都会让读数跳来跳去。
我常用的三种滤波方法:
- 多次采样取平均:最简单有效。我一般采10次去掉最大最小值,再取平均。
- 中值滤波:适合有突发噪声的场景。比如电机启动瞬间的干扰。
- 滑动平均:适合需要实时响应的场景。保留最近N个值,每次更新时去掉最旧的一个。
代码示例(滑动平均):
#define SAMPLE_SIZE 10
int samples[SAMPLE_SIZE];
int index = 0;
int readSmoothedADC() {
samples[index] = analogRead(WATER_SENSOR_PIN);
index = (index + 1) % SAMPLE_SIZE;
long sum = 0;
for(int i = 0; i < SAMPLE_SIZE; i++) {
sum += samples[i];
}
return sum / SAMPLE_SIZE;
}
嗯,这里要注意:滑动平均的缓冲区大小要选合适。太小了滤波效果差,太大了响应变慢。我一般根据传感器的更新频率来定,比如水位传感器每秒更新10次,我就取10个样本,这样延迟只有1秒。
3.5 避坑总结
最后,我把这些年踩过的坑总结一下:
- 参考电压不稳定:ESP32的3.3V本身就有纹波,如果你用电池供电,电压还会下降。建议用外部基准电压芯片(比如TL431)。
- 引脚选择:ESP32的ADC2引脚在WiFi开启时不能用。我建议只用ADC1的引脚(GPIO32-39)。
- 阻抗匹配:有些传感器输出阻抗很高,直接接ADC会导致读数偏低。这时候需要加一个运放做电压跟随器。
- 校准很重要:每个ADC都有个体差异。我习惯在量产前做一次两点校准(比如0%和100%水位),把校准系数写进EEPROM。
好了,传感器数据采集的基础就讲到这里。下一章咱们会把这些数据通过WiFi上传到云端,真正实现“数据上云”。
课后小练习:
用ESP32读取一个电位器(模拟量),通过串口打印出原始值和电压值。然后尝试用不同的滤波方法,观察读数的稳定性变化。