💡 划重点

  • 主控:ESP32-S3,兼具 Wi-Fi 与 BLE,满足 MQTT 联网与未来 OTA 需求。
  • 核心监测:电阻式土壤湿度传感器模拟输出,配合 DHT22 温湿度补偿,提升浇水判断准确率。
  • 交互与反馈:0.96 寸 OLED 实时显示数据,本地灯光/蜂鸣器提醒,MQTT 推送至手机。
  • 低功耗策略:非浇水期进入深度睡眠,每小时唤醒一次采集并上报,单节 18650 可续航约两个月。
  • 成本控制:总物料费控制在 60 元以内,全部使用常见模块,可自行焊接组装。

需求洞察

目标用户:阳台面积有限、喜欢种植香草、花卉或小番茄的都市园艺爱好者,尤其是经常出差或忘记浇水的上班族。

真实痛点

  1. 浇水全凭感觉:盆土表面干了但底部仍湿,反复过量浇水导致烂根,是阳台盆栽死亡的第一大原因。
  2. 时间碎片化:用户无法每天定时巡查每一盆植物,出差或假期时只能拜托邻居,结果往往不理想。
  3. 缺乏长期数据:不知道盆土湿度在一天中的变化规律,难以优化浇水时间和水量,无法积累种植经验。
  4. 市售方案太贵:成品智能花盆或灌溉套件均价 150 元以上,且多为封闭系统,无法按自己需求扩展。

为什么值得做

方案设计

感知层

控制层

执行层

日志/联网层

整体工作流程:每小时唤醒→依次开启传感器与 OLED 供电→采集 3 次取中值→OLED 显示并判断是否需要浇水→连接 Wi-Fi→MQTT 上报→关闭外设供电→进入深度睡眠。

物料清单

物料名称 数量 用途 估算单价(元) 小计(元)
ESP32-S3 开发板 (Mini N16R8) 1 主控与无线通信 19.90 19.90
电阻式土壤湿度传感器 1 测量盆土容积含水率 3.50 3.50
DHT22 温湿度模块 1 环境温湿度采集 5.80 5.80
0.96 寸 OLED (I²C, SSD1306) 1 本地数据显示 8.90 8.90
18650 电池盒 + 保护板 1 供电与电池保护 3.00 3.00
TP4056 充电模块 1 USB 锂电池充电 1.80 1.80
AO3401 PMOS 模块 + 电阻 1 外设电源控制 0.80 0.80
洞洞板、排针、杜邦线、热缩管 若干 结构连接 4.00 4.00
3D 打印外壳(或防水盒改装) 1 IP54 级防护 6.00 6.00
合计 53.70

总成本 53.70 元,预留 6.30 元用于运费或价格浮动,确保在 60 元目标内。

接线与结构

依据给定建议并微调后的接线如下:

结构说明

核心代码

以下为基于 Arduino 框架的可编译骨架,使用 PlatformIO 环境,开发板选择 esp32-s3-devkitc-1


#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <U8g2lib.h>

// ---------- 引脚与常量 ----------
#define PIN_MOISTURE 34        // ADC1_CH6
#define PIN_DHT      15
#define PIN_PERI_EN  7         // 外设供电使能
#define PIN_BUZZER   5
#define PIN_LED      2

#define DHT_TYPE DHT22
DHT dht(PIN_DHT, DHT_TYPE);
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// WiFi 与 MQTT
const char* ssid = "Your_SSID";
const char* password = "Your_PASSWORD";
const char* mqtt_server = "192.168.1.100";
WiFiClient espClient;

// 湿度范围参数(需根据探头在空气中/水中的读数标定)
const int DRY_AIR   = 3200;   // 空气中 ADC 原始值
const int WET_WATER = 1400;   // 水中 ADC 原始值

// 唤醒间隔(秒)
const uint64_t SLEEP_SEC = 3600; // 每小时

void setup() {
  Serial.begin(115200);
  pinMode(PIN_PERI_EN, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);
  pinMode(PIN_LED, OUTPUT);

  // 上电开启外设供电
  delay(100);   // 等待电源稳定

  dht.begin();
  u8g2.begin();
  u8g2.clearBuffer();
  u8g2.sendBuffer();

  // 采集并处理数据
  float moisture = readMoisture();
  float temp = dht.readTemperature();
  float hum  = dht.readHumidity();

  // 显示
  displayData(moisture, temp);

  // 判断是否需要本地告警
  if (moisture < 30.0) {
    digitalWrite(PIN_LED, HIGH);
    tone(PIN_BUZZER, 1000, 300);
    delay(300);
    digitalWrite(PIN_LED, LOW);
  }

  // 联网上报(若电池电压较低可跳过)
  if (connectWiFi()) {
    if (client.connect("BalconyPlanter")) {
      String payload = "{\"moisture\":" + String(moisture, 1) +
                       ",\"temp\":" + String(temp, 1) +
                       ",\"humidity\":" + String(hum, 1) + "}";
      client.publish("planter/data", payload.c_str());
      client.disconnect();
    }
    WiFi.disconnect(true);
  }

  // 关闭外设供电,准备休眠
  esp_sleep_enable_timer_wakeup(SLEEP_SEC * 1000000ULL);
  esp_deep_sleep_start();
}

void loop() {
  // 主逻辑均在 setup 中完成,深睡后重启执行
}

// ---------- 读取土壤湿度百分比 ----------
float readMoisture() {
  const int samples = 3;
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(PIN_MOISTURE);
    delay(10);
  }
  int avg = sum / samples;
  // 反向线性映射:干=0%,湿=100%
  float percent = map(avg, DRY_AIR, WET_WATER, 0, 100);
  if (percent < 0) percent = 0;
  if (percent > 100) percent = 100;
  return percent;
}

// ---------- OLED 显示 ----------
void displayData(float moisture, float temp) {
  u8g2.clearBuffer();
  u8g2.drawStr(0, 10, ("Soil: " + String(moisture, 0) + "%").c_str());
  u8g2.drawStr(0, 25, ("Temp: " + String(temp, 1) + " C").c_str());
  if (moisture < 30) {
    u8g2.drawStr(0, 45, ">>> Water me! <<<");
  } else {
  }
  u8g2.sendBuffer();
}

// ---------- 连接 Wi-Fi(带超时) ----------
bool connectWiFi() {
  WiFi.begin(ssid, password);
  int retries = 0;
  while (WiFi.status() != WL_CONNECTED && retries < 20) {
    delay(500);
    retries++;
  }
  return (WiFi.status() == WL_CONNECTED);
}

代码说明

  • 未使用 RTOS,所有任务在 setup 中顺序执行,结束时进入深度睡眠,醒来后芯片复位重新运行 setup,结构简单可靠。
  • ADC 反向映射为 0~100% 湿度,实际阈值需在烧录后首次调试标定。
  • MQTT 采用简单 JSON 字符串拼接,避免引入 ArduinoJson 库,减小固件体积。

调试步骤

  1. 供电检查:未焊接探头前,仅给系统供电,测量 PMOS 输出 V_PERI 是否为稳定的 3.3 V,按下按钮能正常唤醒。
  2. 传感器校准:将湿度探头完全浸入水中,读取串口打印的 ADC 值(约 1400~1600);擦拭后放置干燥纸巾中,读取空气值(约 3000~3300)。将这两个值替换到 DRY_AIR 和 WET_WATER 宏定义里。
  3. OLED 测试:先单独测试 SSD1306 示例程序,确认 I²C 地址为 0x3C,显示正常无残缺。
  4. Wi-Fi 与 MQTT 验证:设置正确的 SSID 和密码,在电脑上运行 MQTT 客户端订阅 planter/data,观察串口是否打印连接成功,消息是否送达。
  5. 低功耗验证:在 PMOS 关闭前,用万用表 μA 档串联在电池总回路,观察 deep sleep 电流,应小于 2 mA。若偏高,检查 IO 状态和上拉电阻。
  6. 联调:将探头插入花盆正常浇水后的土壤中,静置 1 小时后观察湿度百分比是否在经验“湿润”区间(如 60%~85%),待土壤自然干燥至 30% 以下时验证 LED 和蜂鸣器动作。

设计图

阳台盆栽土壤湿度守护结构flow
1
感知输入
2
控制处理
3
执行反馈
4
数据上报