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读数往往有噪声。你想想看,电源纹波、电磁干扰、传感器自身抖动,都会让读数跳来跳去。

我常用的三种滤波方法:

  1. 多次采样取平均:最简单有效。我一般采10次去掉最大最小值,再取平均。
  2. 中值滤波:适合有突发噪声的场景。比如电机启动瞬间的干扰。
  3. 滑动平均:适合需要实时响应的场景。保留最近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读取一个电位器(模拟量),通过串口打印出原始值和电压值。然后尝试用不同的滤波方法,观察读数的稳定性变化。