2. 传感器数据采集:温湿度传感器(DHT22)、光照传感器(BH1750)、风速风向传感器、雨量传感器,通过ESP32采集数据

好,咱们进入实战的第二关。

传感器选好了,板子也定了,接下来就是真刀真枪地让ESP32把数据读上来。这一章我会带着你,把四个典型的农业传感器挨个搞定。DHT22、BH1750、风速风向、雨量传感器,一个都不能少。

我个人习惯,先把每个传感器的脾气摸透,再写代码。你想想看,传感器就像人一样,有的用单总线通信,有的走I2C,有的就是个简单的开关量。不了解清楚,代码写得再漂亮也白搭。

2.1 温湿度传感器 DHT22

DHT22,也叫AM2302。精度比DHT11高不少,温度±0.5°C,湿度±2%RH。农业大棚里用,完全够用。

它用的是单总线协议,说白了就是一根数据线既发命令又收数据。时序要求比较严格,我建议直接用现成的库,别自己折腾时序——我在项目中吃过这个亏,自己写时序,结果不同批次的DHT22时序有细微差异,折腾了两天才发现是库的问题。

关键参数:
  • 供电电压:3.3V ~ 5.5V
  • 测量范围:温度 -40~80°C,湿度 0~100%RH
  • 采样周期:≥2秒

接线很简单:VCC接3.3V,GND接地,DATA接一个GPIO(比如GPIO4)。注意,DATA引脚需要上拉一个4.7kΩ电阻到VCC。嗯,这里要注意,有些开发板内部有上拉,但为了稳定,我建议还是外接一个。

#include <DHT.h>

#define DHTPIN 4
#define DHTTYPE DHT22

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  dht.begin();
}

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(h) || isnan(t)) {
    Serial.println("读取DHT22失败!");
    return;
  }

  Serial.print("湿度: ");
  Serial.print(h);
  Serial.print(" %\t");
  Serial.print("温度: ");
  Serial.print(t);
  Serial.println(" *C");

  delay(2000);
}
避坑指南: 我曾经遇到过DHT22读数全是NaN的情况。排查了半天,发现是传感器离ESP32太远,线长了信号衰减。解决办法:线长控制在20cm以内,或者加一个电平转换芯片。

2.2 光照传感器 BH1750

BH1750是个数字光照传感器,直接输出lux值。它用I2C通信,地址默认是0x23(ADDR引脚接地),也可以改成0x5C(ADDR接VCC)。

我特别喜欢这个传感器,因为它不需要校准,拿来就用。不像某些模拟光照传感器,还得自己算公式,麻烦得很。

接线方式:
  • VCC → 3.3V
  • GND → GND
  • SCL → GPIO22 (ESP32默认I2C时钟)
  • SDA → GPIO21 (ESP32默认I2C数据)
  • ADDR → GND (地址0x23)
#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  lightMeter.begin();
}

void loop() {
  float lux = lightMeter.readLightLevel();
  Serial.print("光照强度: ");
  Serial.print(lux);
  Serial.println(" lx");
  delay(1000);
}

这里有个小细节:BH1750有几种测量模式,连续高分辨率模式最常用,精度1lx。如果你需要快速响应,可以用连续低分辨率模式,精度4lx,但测量时间从120ms降到16ms。农业场景下,我建议用高分辨率模式,数据更平滑。

2.3 风速风向传感器

风速风向传感器,市面上常见的有两种:一种是模拟输出的,一种是脉冲输出的。

模拟输出的风速传感器,输出0-5V电压,对应0-30m/s。风向传感器通常是8个或16个方向,也是模拟电压输出。ESP32的ADC是0-3.3V,所以需要分压电阻或者用ADS1115外部ADC。

注意: ESP32的ADC线性度不太好,尤其是靠近0V和3.3V的时候。我建议用外部ADC芯片,比如ADS1115,16位精度,I2C接口,省心很多。

脉冲输出的风速传感器,每转一圈输出一个脉冲。比如三杯风速计,每转一圈对应一个脉冲频率。这种传感器,用ESP32的脉冲计数功能(PCNT)或者外部中断来测量频率,再换算成风速。

// 脉冲式风速传感器示例
// 假设每转一圈产生一个脉冲,风速 = 频率 * 0.1 (m/s)

volatile unsigned long pulseCount = 0;
unsigned long lastTime = 0;

void IRAM_ATTR pulseCounter() {
  pulseCount++;
}

void setup() {
  Serial.begin(115200);
  pinMode(5, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(5), pulseCounter, RISING);
}

void loop() {
  if (millis() - lastTime >= 1000) {
    noInterrupts();
    unsigned long count = pulseCount;
    pulseCount = 0;
    interrupts();

    float freq = count; // 每秒脉冲数
    float windSpeed = freq * 0.1; // 换算系数根据传感器手册调整

    Serial.print("风速: ");
    Serial.print(windSpeed);
    Serial.println(" m/s");

    lastTime = millis();
  }
}
经验之谈: 我曾经在野外测试时,发现风速数据偶尔会跳变。排查后发现是风杯转动时产生的抖动干扰。解决办法:在中断里加一个20ms的去抖延时,或者用PCNT硬件滤波。

2.4 雨量传感器

雨量传感器,农业上常用的是翻斗式雨量计。原理很简单:雨水积满一个小斗,翻倒一次,产生一个脉冲。每个脉冲代表0.5mm或1mm的降雨量。

还有一种电容式雨量传感器,输出模拟电压,但精度不如翻斗式。我个人更推荐翻斗式,机械结构简单,不容易坏。

接线:
  • 翻斗式雨量计:两根线,一根接GPIO,一根接GND
  • 内部是干簧管,需要上拉电阻到3.3V
  • 每个脉冲代表0.5mm降雨量(具体看传感器规格)
// 翻斗式雨量计示例
// 每个脉冲代表0.5mm降雨量

volatile unsigned long rainCount = 0;
unsigned long lastRainTime = 0;

void IRAM_ATTR rainDetected() {
  // 去抖处理,防止机械抖动
  if (millis() - lastRainTime > 100) {
    rainCount++;
    lastRainTime = millis();
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(14, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(14), rainDetected, FALLING);
}

void loop() {
  // 每10秒上报一次累计雨量
  static unsigned long lastReport = 0;
  if (millis() - lastReport >= 10000) {
    noInterrupts();
    unsigned long count = rainCount;
    rainCount = 0;
    interrupts();

    float rainfall = count * 0.5; // 单位mm
    Serial.print("10秒内降雨量: ");
    Serial.print(rainfall);
    Serial.println(" mm");

    lastReport = millis();
  }
}
重要提醒: 翻斗式雨量计需要水平安装,否则翻斗动作不准确。我在一个项目里,因为安装时没注意水平,导致雨量数据偏差了20%。后来用水平仪重新调整,数据才恢复正常。

2.5 多传感器整合采集

四个传感器都单独调通了,接下来就是整合。我建议用定时器,每2秒采集一次DHT22和BH1750,每1秒采集一次风速,雨量用中断累计,每10分钟上报一次累计值。

// 整合采集框架
void collectAllSensors() {
  // 1. 采集温湿度
  float temp = dht.readTemperature();
  float hum = dht.readHumidity();

  // 2. 采集光照
  float lux = lightMeter.readLightLevel();

  // 3. 采集风速(从中断累计值计算)
  float windSpeed = getWindSpeed();

  // 4. 雨量累计(从中断累计值计算)
  float rainfall = getRainfall();

  // 5. 打包成JSON或结构体,准备上传
  // 这部分下一章会详细讲
}

这里有个坑:DHT22的读取间隔必须大于2秒,否则会出错。BH1750连续模式没问题,但如果你用单次模式,也要注意间隔。我建议把DHT22放在主循环里,每2.5秒读一次,其他传感器可以更频繁。

我的习惯: 在采集函数里加一个超时保护。如果某个传感器连续3次读取失败,就标记为故障,并记录错误日志。这样后期排查问题会方便很多。

好了,四个传感器的数据采集,咱们都过了一遍。下一章,我会讲怎么把这些数据打包成标准格式,通过WiFi或者4G模块上传到云端。到时候,你就能在手机或电脑上实时看到大棚里的环境数据了。

记住,传感器采集是物联网项目的地基。地基不稳,上层建筑再漂亮也没用。多花点时间在传感器调试上,后面会省很多事。