3. 农业传感器原理与接口实践

农业远程监控系统的“感知层”由若干传感器构成,它们把土壤水分、空气温湿度、光照强度这些物理量转换成ESP32能够读取的电信号。本节聚焦三类最常用的农业传感器:电阻式土壤湿度传感器、DHT22数字温湿度传感器和光照传感器(以光敏电阻模块与BH1750数字光照传感器作对比),从电气特性、输出信号形式出发,完成与ESP32的硬件连接和基础采集代码。理解“信号是什么形态”是接口设计的前提——模拟量要走ADC,单总线数字量要走精确时序,I²C数字量要走总线协议,三者的接线与代码结构完全不同。

三类传感器的电气特性与输出信号

电阻式土壤湿度传感器通常由两片电极构成探头,土壤含水量越高,电极间导电性越好、等效电阻越小。常见模块(如YL-69配合LM393比较器)输出两路信号:AO为模拟电压,DO为比较器输出的数字高低电平。以3.3V供电时,干燥空气中AO输出接近供电电压,插入湿润土壤后AO电压下降。需要注意,这类传感器长期通电会产生电化学腐蚀,实践中建议“测量时供电、测完断电”,或选用电容式土壤湿度传感器(输出0~3.0V模拟电压,抗腐蚀性更好)。ESP32的ADC为12位,理论量程0~4095,但实际存在非线性,且Wi-Fi发射时ADC2通道会被占用,因此土壤湿度应接在ADC1通道(GPIO32~GPIO39)。

DHT22(AM2302)是数字式温湿度传感器,供电3.3~5.5V,湿度量程0~100%RH、精度约±2%RH,温度量程-40~80℃、精度约±0.5℃。它采用单总线协议:主机拉低总线至少1ms作为起始信号,随后释放总线,DHT22应答后连续输出40位数据——湿度高8位、湿度低8位、温度高8位、温度低8位、8位校验和。每一位以50μs低电平开始,高电平持续26~28μs表示“0”,持续70μs表示“1”。这意味着读取时序对微秒级延时敏感,且两次读取间隔不得小于2秒。数据引脚需接4.7kΩ~10kΩ上拉电阻,ESP32侧任选一个普通GPIO即可。

光照检测有两种典型方案。光敏电阻模块输出模拟电压,光照越强电阻越小,分压点电压随光照变化,适合做“白天/夜晚”或“遮阳/补光”的阈值判断,但线性度和一致性较差。BH1750是I²C数字光照传感器,直接输出以勒克斯(lx)为单位的光照度,量程1~65535lx,地址由ADDR引脚决定(接地为0x23,接VCC为0x5C),适合需要定量数据的场景。农业大棚中,光照度直接关联光合作用速率,定量值比“亮/暗”更有决策价值。

三类农业传感器与 ESP32 接口关系 ESP32 ADC1 / GPIO / I²C 3.3V 逻辑电平 12 位 ADC 土壤湿度传感器 输出:模拟电压 AO 湿→电压低,干→电压高 接 GPIO34(ADC1) 模拟量 DHT22 温湿度 输出:单总线 40 位数据 4.7kΩ 上拉,间隔≥2s 接 GPIO4 数字时序 光敏电阻模块 输出:模拟电压 AO 光强→电压变化 接 GPIO35(ADC1) 模拟量 BH1750 光照 输出:I²C 数字量(lx) 地址 0x23 / 0x5C SDA=21,SCL=22 I²C 总线 关键约束:模拟量必须走 ADC1(GPIO32~39);DHT22 两次读取间隔 ≥2s;I²C 设备共享 SDA/SCL 但地址不可冲突

硬件连接与供电注意事项

接线前先统一电平与供电。ESP32开发板的3.3V引脚可同时为DHT22和BH1750供电,但土壤湿度模块和光敏电阻模块的加热/分压电路耗流较大,若同时挂载多个模块,建议用外部5V电源经AMS1117稳压到3.3V单独供电,并与ESP32共地。所有模拟输出引脚不得超过3.3V,否则需加分压电阻。DHT22数据线接GPIO4并加4.7kΩ上拉到3.3V;BH1750的SDA接GPIO21、SCL接GPIO22,两根线上各加4.7kΩ上拉(多数模块已板载)。土壤湿度AO接GPIO34,光敏电阻AO接GPIO35,二者都属于ADC1,避开Wi-Fi冲突。

基础采集代码

下面给出一个可编译的Arduino框架示例,把三类传感器的读取封装成独立函数,主循环按不同周期调度:土壤湿度每2秒采一次并做多次平均,DHT22严格保持2秒以上间隔,BH1750每1秒读一次。代码中的阈值和换算系数属于示例值,实际使用前应通过标定替换。

#include <DHT.h>
#include <Wire.h>
#include <BH1750.h>

#define SOIL_PIN   34
#define LIGHT_PIN  35
#define DHT_PIN    4
#define DHT_TYPE   DHT22

DHT dht(DHT_PIN, DHT_TYPE);
BH1750 bh1750(0x23);

// 示例标定值:需按实际探头在空气/水中测得的ADC值替换
const int SOIL_DRY = 3200;   // 干燥空气 ADC 读数(示例)
const int SOIL_WET = 1300;   // 浸水 ADC 读数(示例)

int readSoilPercent() {
  long sum = 0;
  for (int i = 0; i < 8; i++) { sum += analogRead(SOIL_PIN); delay(5); }
  int raw = sum / 8;
  int pct = map(raw, SOIL_DRY, SOIL_WET, 0, 100);
  return constrain(pct, 0, 100);
}

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
  dht.begin();
  Wire.begin(21, 22);
  bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
}

void loop() {
  int soil = readSoilPercent();
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  float lux = bh1750.readLightLevel();

  if (isnan(t) || isnan(h)) {
    Serial.println("DHT22 读取失败,检查上拉与接线");
  } else {
    Serial.printf("土壤:%d%%  温度:%.1fC  湿度:%.1f%%  光照:%.0flx\n",
                  soil, t, h, lux);
  }
  delay(2000);   // 满足 DHT22 最小采样间隔
}

代码中有几个容易踩坑的点。第一,analogReadResolution(12)必须在读取前调用,否则默认可能是10位。第二,map()函数的两个端点顺序决定了输出方向:本例中干燥读数大、湿润读数小,所以把SOIL_DRY放在第一个参数位置,输出才会“越湿百分比越大”。第三,DHT22读取失败返回NaN,必须用isnan()判断,不能直接参与运算。第四,BH1750的CONTINUOUS_HIGH_RES_MODE分辨率1lx、测量周期约120ms,适合连续采集。

标定与结果判读

土壤湿度的百分比没有绝对标准,必须现场标定。做法是:把探头置于干燥空气中静置,串口记录稳定后的ADC读数作为“干端”;再把探头插入水中(不要淹没电路部分),记录稳定读数作为“湿端”。把这两个值填入SOIL_DRY和SOIL_WET,百分比才有意义。判断采集是否正常,可以看三条依据:数值是否在合理区间内波动(土壤湿度0~100%、温度与室温接近、光照白天明显高于夜晚);连续读取是否稳定(同一环境下波动不超过几个百分点);异常时是否有明确报错(DHT22返回NaN、BH1750返回负值说明总线通信失败)。若DHT22持续失败,优先检查上拉电阻和GPIO是否被其他功能占用;若BH1750读数为负,用I²C扫描确认地址是否为0x23。

建议在正式部署前做一次“三传感器联调记录”:在同一时刻记录土壤ADC原始值、温湿度、光照度,连续记录10组,观察各通道是否同步变化。这份记录既是标定依据,也是后续排查“数据跳变”问题的基线。

完成本节后,读者应能根据传感器输出信号类型选择正确的ESP32引脚与读取方式,独立完成接线、编写采集代码,并通过串口输出判断数据是否可信。这套“感知层”采集能力,是后续数据上传、阈值报警与远程控制的基础。