ESP32 + 传感器:手把手教你做土壤监测终端

上一篇给了硬件清单,这篇直接上代码。从 GPIO 初始化到传感器读数,再到 MQTT 上发,最后低功耗优化——每一行都有注释,复制到 Arduino IDE 里就能跑。


前置准备

软件环境:

  • Arduino IDE 2.x(装好 ESP32 开发板支持)
  • 库:WiFi PubSubClient DHT sensor library OneWire DallasTemperature BH1750 ModbusMaster

硬件接线: 按上一篇的引脚表接好,USB 线插电脑。


第一步:基础采集——五个传感器一把梭

#include <WiFi.h>
#include <DHT.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <BH1750.h>
#include <Wire.h>
#include <ModbusMaster.h>

// ==================== 引脚定义 ====================
#define DHT_PIN        25
#define ONE_WIRE_PIN   4
#define RS485_DE_RE    18    // MAX485 方向控制引脚
#define SOIL_SENSOR_1  32    // ADC1 读模拟量土壤湿度(备选方案)

// ==================== 传感器对象 ====================
DHT dht(DHT_PIN, DHT22);
OneWire oneWire(ONE_WIRE_PIN);
DallasTemperature soilTempSensor(&oneWire);
BH1750 lightMeter;
ModbusMaster modbus;

// ==================== 数据结构 ====================
struct SensorData {
  float airTemp;       // 空气温度 ℃
  float airHumidity;   // 空气湿度 %
  float soilTemp;      // 土壤温度 ℃
  float soilMoisture;  // 土壤湿度 %(RS485 读数)
  float lightLux;      // 光照强度 Lux
};

// ==================== 初始化 ====================
void setup() {
  Serial.begin(115200);
  
  // DHT22
  dht.begin();
  
  // DS18B20 土壤温度
  soilTempSensor.begin();
  
  // BH1750 光照
  Wire.begin(21, 22);  // SDA=21, SCL=22
  lightMeter.begin();
  
  // RS485 (Serial2: RX=16, TX=17)
  Serial2.begin(9600, SERIAL_8N1, 16, 17);
  modbus.begin(1, Serial2);  // 从机地址 = 1
  pinMode(RS485_DE_RE, OUTPUT);
  
  Serial.println("=== 土壤监测终端启动 ===");
}

// ==================== RS485 读土壤湿度 ====================
float readSoilMoistureRS485() {
  // 切换到发送模式
  digitalWrite(RS485_DE_RE, HIGH);
  delay(10);
  
  // 读保持寄存器 0x0000(湿度值),读 1 个寄存器
  uint8_t result = modbus.readHoldingRegisters(0x0000, 1);
  
  // 切回接收模式
  digitalWrite(RS485_DE_RE, LOW);
  
  if (result == modbus.ku8MBSuccess) {
    return modbus.getResponseBuffer(0) / 10.0;  // 传感器返回 0-1000,除以 10
  }
  return -1.0;  // 读取失败
}

// ==================== 读土壤温度(DS18B20) ====================
float readSoilTemp() {
  soilTempSensor.requestTemperatures();
  float temp = soilTempSensor.getTempCByIndex(0);
  // DS18B20 在 -127 时表示读取异常
  if (temp == DEVICE_DISCONNECTED_C) return -127;
  return temp;
}

// ==================== 采集全部数据 ====================
SensorData collectAll() {
  SensorData data;
  
  // DHT22 空气温湿度
  data.airTemp = dht.readTemperature();
  data.airHumidity = dht.readHumidity();
  
  // DS18B20 土壤温度
  data.soilTemp = readSoilTemp();
  
  // RS485 土壤湿度
  data.soilMoisture = readSoilMoistureRS485();
  
  // BH1750 光照
  data.lightLux = lightMeter.readLightLevel();
  
  return data;
}

void loop() {
  SensorData data = collectAll();
  
  Serial.println("════════════════════════════");
  Serial.printf("空气温度: %.1f ℃\n", data.airTemp);
  Serial.printf("空气湿度: %.1f %%\n", data.airHumidity);
  Serial.printf("土壤温度: %.1f ℃\n", data.soilTemp);
  Serial.printf("土壤湿度: %.1f %%\n", data.soilMoisture);
  Serial.printf("光照强度: %.0f Lux\n", data.lightLux);
  
  delay(5000);  // 调试阶段 5s 一次,实际部署改 5min
}

这段代码用 Arduino IDE 编译下载到 ESP32,打开串口监视器(115200 波特率),你应该能看到每隔 5 秒打印一组传感器读数。


第二步:接上 MQTT,数据上云端

光串口打印没意义,数据得上云。MQTT 部分只需要在 setup() 里连 WiFi + 连 EMQX,在 loop() 里把采集到的数据 publish 出去。

#include <PubSubClient.h>

// ==================== WiFi & MQTT 配置 ====================
const char* WIFI_SSID = "你的WiFi名";
const char* WIFI_PASS = "你的WiFi密码";
const char* MQTT_BROKER = "你的服务器IP";
const int   MQTT_PORT = 1883;
const char* MQTT_CLIENT_ID = "esp32_greenhouse_01";
const char* MQTT_TOPIC = "farm/greenhouse/01/data";

WiFiClient espClient;
PubSubClient mqtt(espClient);

// 连接 WiFi
void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print("连接 WiFi");
  int retry = 0;
  while (WiFi.status() != WL_CONNECTED && retry < 40) {
    delay(500);
    Serial.print(".");
    retry++;
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi 已连接");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
  }
}

// 连接 MQTT
void connectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("连接 MQTT...");
    if (mqtt.connect(MQTT_CLIENT_ID)) {
      Serial.println("成功");
    } else {
      Serial.print("失败, rc=");
      Serial.print(mqtt.state());
      Serial.println(" 5 秒后重试");
      delay(5000);
    }
  }
}

// 发送 JSON 数据(不用 ArduinoJson 库也行,手动拼)
void publishData(SensorData data) {
  char payload[256];
  snprintf(payload, sizeof(payload),
    "{\"t\":\"%.1f\",\"h\":\"%.1f\",\"st\":\"%.1f\",\"sm\":\"%.1f\",\"l\":\"%.0f\"}",
    data.airTemp, data.airHumidity, data.soilTemp, data.soilMoisture, data.lightLux
  );
  
  if (mqtt.publish(MQTT_TOPIC, payload)) {
    Serial.println("✅ 数据已上发");
  } else {
    Serial.println("❌ 发送失败");
  }
}

void setup() {
  Serial.begin(115200);
  // ... 传感器初始化(同上,省略)...
  
  connectWiFi();
  mqtt.setServer(MQTT_BROKER, MQTT_PORT);
  connectMQTT();
}

void loop() {
  if (!mqtt.connected()) connectMQTT();
  mqtt.loop();
  
  SensorData data = collectAll();
  publishData(data);
  
  delay(300000);  // 5 分钟上报一次
}

到这里,你的 ESP32 已经能做到:每 5 分钟采集一组数据 → JSON 格式化 → MQTT 上发到 EMQX。


第三步:低功耗优化——一节电池用半年

ESP32 全速跑起来大约 150-200mA,18650 一节 3400mAh 撑不到 24 小时。必须做低功耗优化。

思路

ESP32 大部分时间在睡觉(深度睡眠模式,电流 ~10μA),定时醒来(RTC 闹钟),花 5 秒采数据 + 连 WiFi + 发 MQTT,然后倒头接着睡。

时间线:
┌───── 睡眠(295s) ─────┬─ 唤醒(5s) ─┐
│     约 10μA           │  WiFi+采集+发送 │
└───────────────────────┴──────────────┘
                          平均电流 ≈ (295*0.01 + 5*150) / 300 = 2.5mA
                          一节 3400mAh → 约 56 天

加太阳能板 + TP4056 充电,白天充电晚上耗电,基本无限续航。

#define uS_TO_S_FACTOR 1000000ULL
#define SLEEP_SECONDS  300       // 5 分钟

void setup() {
  // ... 前面初始化 ...
  
  // 采集一次并上报
  SensorData data = collectAll();
  publishData(data);
  
  // 断开 WiFi(省电)
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  
  // 配置唤醒源:定时器
  esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * uS_TO_S_FACTOR);
  
  Serial.println("进入深度睡眠...");
  esp_deep_sleep_start();
}

void loop() {
  // 深度睡眠模式下永远不会执行到这里
  // setup() 执行完后 ESP32 自动关机,RTC 闹钟触发后重启 → 再次进 setup()
}

注意: 用了深度睡眠后,setup() 就是你的主循环——每次醒来跑一遍,跑完就睡。loop() 成了摆设。

进一步省电技巧

  • 去掉串口打印:Serial.begin() 和 Serial.print() 在部署时全删掉,能省 5-10mA
  • WiFi 快速连接:提前存 WiFi 信道,连接时间从 3 秒降到 1 秒
  • 静态 IP:DHCP 握手省 0.5 秒
  • MQTT QoS 0:不等待服务器确认,发完就走

优化后平均电流可以压到 1.5mA 以下,一节 18650 撑 90+ 天。


常见问题

Q: RS485 读数总是 -1(失败)?
A: 检查 DE/RE 引脚电平——发送时拉高、接收时拉低。时序:拉高后要 delay(10) 再发指令。另外确认 MAX485 的 RO/DI 交叉接对。

Q: DHT22 偶尔返回 NaN?
A: DHT22 的单总线时序很敏感,读取间隔不能短于 2 秒。如果连续读太快,加 delay(2500) 或者换 DHT22 库里带超时处理的版本。

Q: 太阳能板阴天能充进去吗?
A: 能,但很慢。5V 1W 的多晶硅在阴天输出只有 0.1-0.3W。如果你在四川、贵州这种阴天多的地区,建议上 2W 的板子或者两片并联。


下一篇:《MQTT 物联网通信:100 个传感器的数据怎么传到云端?》——部署 EMQX、设计 topic 规范、设备上下线监控、断网重连机制。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐