扣1的来了,固件 v1.0 如下。Arduino IDE 直接编译烧录,板子选 ESP32 Dev Module,端口选 COM 口即可,不需要改库(只用标准库)。
接线速查:
- 湿度传感器1号(顶层小盆)→ A34
- 湿度传感器2号(地面大盆)→ A35
- 光照电阻(可选,不接恒按白天)→ A36
- 继电器 IN(水泵12V)→ GPIO26
- 蜂鸣器 → GPIO27,LED → GPIO28
- 浮球水位开关 → GPIO5(接GND,内部上拉)
两个校准项(烧录后串口看 boot 行):
1. 把两个传感器分别插湿土和悬空,把 ADC 值填进 soilCal1/soilCal2(格式 [湿土, 空气])
2. 光照传感器白天/黑夜各读一次,取中间值填 LIGHT_DAY
```cpp
/
* 封闭阳台自动浇花固件 v1.0
* ESP32-DevKitC v4 · 离线闭环 · 无 Wi-Fi 依赖
*
* 接线:
* 土壤湿度传感器1号(顶层小盆) A0 (VCC 5V, GND)
* 土壤湿度传感器2号(地面大盆) A1
* 光照电阻(可选) A2 (10kΩ分压, 接GND)
* 继电器 IN GPIO26 (水泵, 12V电源独立)
* 蜂鸣器 GPIO27 (有源, GND共地)
* LED GPIO28
* 浮球水位开关 GPIO5 (接GND, 内部上拉; 低水位=断开=HIGH)
*
* 逻辑:
* 每30分钟采样。
* 湿度1 < DRY_HARD 且 距上次>6h → 全轮 90s
* 湿度1 < DRY_MID 且 湿度2<DRY_MID → 半轮 45s
* 浮球低水位 / 泵堵 → 报警
/
// ===== 可调参数 =====
const uint8_t SOIL1_PIN = 34; // 顶层小盆(干得快)
const uint8_t SOIL2_PIN = 35; // 地面大盆(干得慢)
const uint8_t LIGHT_PIN = 36; // 光照(可选, 不接则恒白天)
const uint8_t PUMP_PIN = 26;
const uint8_t BUZZ_PIN = 27;
const uint8_t LED_PIN = 28;
const uint8_t WATER_PIN = 5;
const int DRY_HARD = 25; // 湿度百分比, 低于此 6h → 全轮
const int DRY_MID = 35; // 两路都低于此 → 半轮
const int DAY_ADJ = 5; // 白天阈值放宽(数字变大=更难触发)
const int NIGHT_ADJ = 0; // 夜间阈值收紧
const int LIGHT_DAY = 3200; // ADC>此值判白天(10k分压, 实测调)
const uint32_t CHECK_MS = 30ULL601000; // 30分钟采样
const uint32_t MIN_GAP = 6ULL36001000; // 两次浇水最小间隔6h
const uint16_t FULL_MS = 90; // 全轮秒数
const uint16_t HALF_MS = 45; // 半轮秒数
// ===== 状态 =====
uint32_t lastWater = 0;
uint8_t pumpFailCount = 0;
bool lowWater = false;
int soilCal1[2] = {0, 4095}; // 传感器校准: [湿土, 空气] 首跑后改
int soilCal2[2] = {0, 4095};
// ===== 工具 =====
int readADC(uint8_t pin) {
int s = 0;
for (int i = 0; i < 8; i ) { s = analogRead(pin); delay(2); }
return s / 8;
}
// 湿度百分比: 校准区间内线性, 未校准时用默认 0~4095
int soilPct(int adc, int cal[2]) {
int lo = cal[0], hi = cal[1];
if (hi - lo < 100) return 50;
int p = (hi - adc) * 100 / (hi - lo);
return constrain(p, 0, 100);
}
void beep(uint16_t n, uint16_t ms = 200) {
for (int i = 0; i < n; i ) {
digitalWrite(BUZZ_PIN, HIGH); delay(ms);
digitalWrite(BUZZ_PIN, LOW); delay(ms);
}
}
void pumpOn() { digitalWrite(PUMP_PIN, HIGH); }
void pumpOff() { digitalWrite(PUMP_PIN, LOW); }
// 水泵运行 N 秒, 每5秒闪一次LED
void runPump(uint16_t secs) {
pumpOn();
uint32_t t0 = millis();
while (millis() - t0 < (uint32_t)secs * 1000) {
digitalWrite(LED_PIN, (millis() / 500) % 2);
if (digitalRead(WATER_PIN) == HIGH) { // 中途缺水
pumpOff(); beep(5); lowWater = true;
return;
}
delay(50);
}
pumpOff();
digitalWrite(LED_PIN, LOW);
}
void setup() {
Serial.begin(115200);
pinMode(PUMP_PIN, OUTPUT); pinMode(PUMP_PIN, LOW);
pinMode(BUZZ_PIN, OUTPUT); pinMode(BUZZ_PIN, LOW);
pinMode(LED_PIN, OUTPUT); pinMode(LED_PIN, LOW);
pinMode(WATER_PIN, INPUT); digitalWrite(WATER_PIN, HIGH); // 上拉
analogReadResolution(12);
// 传感器预热(上电需稳定几秒)
delay(2000);
int a1 = soilPct(readADC(SOIL1_PIN), soilCal1);
int a2 = soilPct(readADC(SOIL2_PIN), soilCal2);
Serial.printf("boot: soil1=%d%% soil2=%d%%\n", a1, a2);
if (digitalRead(WATER_PIN) == HIGH) {
lowWater = true;
Serial.println("WARN: 储水低");
beep(3);
}
}
void loop() {
static uint32_t next = millis() 60000; // 首采1分钟后
if (millis() < next) { delay(200); return; }
next = millis() CHECK_MS;
int raw1 = readADC(SOIL1_PIN);
int raw2 = readADC(SOIL2_PIN);
int pct1 = soilPct(raw1, soilCal1);
int pct2 = soilPct(raw2, soilCal2);
bool day = (readADC(LIGHT_PIN) > LIGHT_DAY);
int adj = day ? DAY_ADJ : NIGHT_ADJ;
int hard = constrain(DRY_HARD adj, 0, 100);
int mid = constrain(DRY_MID adj, 0, 100);
Serial.printf("t=%lus soil1=%d%%(%d) soil2=%d%%(%d) %s gap=%lus\n",
millis()/1000, pct1, raw1, pct2, raw2,
day ? "day" : "night",
(lastWater ? (millis()-lastWater)/1000 : 0));
// 缺水检查
if (digitalRead(WATER_PIN) == HIGH) {
if (!lowWater) beep(3);
lowWater = true;
return;
}
lowWater = false;
uint32_t gap = lastWater ? (millis() - lastWater) : UINT32_MAX;
if (pct1 < hard