简介

去年五一之前给三只小龟做了个自动喂食器:赶在五一之前给乌龟做个自动喂食器,刚好一年过去了(我这拖延症),升级到了 2.0 版本,主要是增加了自动水循环系统。

update:乌龟自动喂食器再次升级

结构设计

在结构上增加了一个循环水箱,一个过滤水箱。水从循环水箱里抽出来,经过过滤水箱后给乌龟们洗澡喝水,再回流到了循环水箱里面。画了一个示意图:
schematic
在经过一段时间的循环后,水必然会减少,除了乌龟们用的,还有蒸发的,所以还得往循环水箱里面加水。既然自动化了,当然不能让我每天盯着水少了就加啊。于是我就增加了一个检测水位的装置:
Water-Level2
检测到了低水位后,水泵就打开,从水桶里往循环水箱里加水,一直加到高水位后水泵就停止了。
你问桶里没水了怎么办?当然是我手动加了,我可舍不得钱买一个能电动控制的水龙头,一个星期加一桶水,顺便清晰一下几个水箱,更换一下过滤棉。

硬件设计

主控还是 ESP8266 模块,除了自动喂食器的电机驱动,增加了两个水泵的电源控制继电器,再用两个 IO 来检测水位。
一个水泵是循环抽水的,可以通过云端设置开关的时间间隔,另一个水泵是加水的,当检测到低水位时,该水泵就打开,水位到最高点时自动关闭水泵。
监测水位的我用的是干簧管和磁铁,在防水上没什么问题,也不用多余的工作。
Water-Level1
中间的白色浮子里面嵌入了一个小磁铁,可以飘浮在水中,随着水位升降。当水面下降时,磁铁会靠近底下的干簧管,干簧管短路,IO 电平由低变高。水位上升的时候磁铁会离开底下的干簧管,干簧管开路,IO 由高变低,当靠近上面的干簧管后,IO由低变高,说明水面到了高水位。
开始本来的想法是用水的导电性来设计水位传感器的,淘宝上卖的也基本都是这类,因为水箱离主控板比较远,线缆一长,ADC 转换的时候容易出错,所以我就改成了用 IO 电平变化来监测水位了。
电路很简单,也没考虑很多,能用就行,仅供 DIY 用。
PCB

软件设计

软件设计还是 ESP8266 模块作为 Arduino 板联网控制,通过 Arduino IoT Cloud 连接设备。
Arduino Cloud IoT 使用入门指南

Arduino 代码很简单,注释我也写得很详细,流程图如下:
flowchart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323


















#include "thingProperties.h"


const char* softwareVersion = "2.1";

const int pwm1Pin = 12;
const int pwm2Pin = 14;
const int pumpPin = 13;
const int pumpINPin = 16;
const int pumpLowPin = 5;
const int pumpHighPin = 4;


bool systemFault = false;


bool pumpINRunning = false;
unsigned long pumpINStartTime = 0;
const unsigned long PUMP_TIMEOUT_MS = 2 * 60 * 1000;


unsigned long lastLowPinDebounceTime = 0;
unsigned long lastHighPinDebounceTime = 0;
const long DEBOUNCE_DELAY_MS = 300;

int debouncedLowPinState = LOW;
int debouncedHighPinState = LOW;
int lastLowReading = -1;
int lastHighReading = -1;







int debounceRead(int pin, int& lastStableState, unsigned long& lastDebounceTime,int& lastReading) {
int reading = digitalRead(pin);


if (reading != lastReading) {
lastDebounceTime = millis();
}


if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY_MS) {
if (reading != lastStableState){
lastStableState = reading;
}
}
lastReading = reading;
return lastStableState;
}



bool motorRunning = false;
unsigned long motorStartTime = 0;
const unsigned long motorRunDurationMillis = 25 * 1000;


unsigned long motorCycleStartTime = 0;
unsigned long motorCycleDurationMillis = 0;


bool pumpRunning = false;
bool pumpOnPeriod = false;
unsigned long lastPumpToggleTime = 0;
unsigned long pumpOnDurationMillis = 0;
unsigned long pumpOffDurationMillis = 0;


void startMotor();
void stopMotor();
void setPumpState(bool state);
void stopPump();


void setup() {

Serial.begin(115200);

Serial.println("Software Version: " + String(softwareVersion));
delay(100);


initProperties();


ArduinoCloud.begin(ArduinoIoTPreferredConnection);

pinMode(pwm1Pin, OUTPUT);
pinMode(pwm2Pin, OUTPUT);
pinMode(pumpPin, OUTPUT);
pinMode(pumpINPin, OUTPUT);
pinMode(pumpHighPin, INPUT);
pinMode(pumpLowPin, INPUT);


analogWrite(pwm1Pin, 0);
analogWrite(pwm2Pin, 0);
digitalWrite(pumpPin, LOW);
digitalWrite(pumpINPin, LOW);



motorCycleStartTime = millis();
lastPumpToggleTime = millis();


Serial.println("Arduino Cloud Connected!");







setDebugMessageLevel(2);
ArduinoCloud.printDebugInfo();
}

void loop() {
ArduinoCloud.update();



debouncedLowPinState = debounceRead(pumpLowPin, debouncedLowPinState, lastLowPinDebounceTime, lastLowReading);
debouncedHighPinState = debounceRead(pumpHighPin, debouncedHighPinState, lastHighPinDebounceTime, lastHighReading);

Serial.printf("实时状态 -> 低水位: %d, 高水位: %d, 水泵运行中: %d\n",
debouncedLowPinState, debouncedHighPinState, pumpINRunning);


if ((debouncedHighPinState == HIGH && debouncedLowPinState == LOW) && pumpINRunning == true) {
digitalWrite(pumpINPin, LOW);
Serial.printf("水泵状态: 关闭\n");

pumpINRunning = false;
}


if ((debouncedLowPinState == HIGH && debouncedHighPinState == LOW) && pumpINRunning == false && !systemFault) {
digitalWrite(pumpINPin, HIGH);
Serial.printf("水泵状态: 开启\n");
pumpINStartTime = millis();

pumpINRunning = true;
}


if (pumpINRunning == true) {
if (millis() - pumpINStartTime >= PUMP_TIMEOUT_MS) {
digitalWrite(pumpINPin, LOW);
Serial.printf("进水泵状态: 关闭 (超时 - 系统可能故障!)\n");
pumpINRunning = false;
systemFault = true;
}
}




motorCycleDurationMillis = (unsigned long)motorCloudCycleHours * 3600UL * 1000UL;
if (motorCloudControl && !motorRunning && motorCycleDurationMillis > 0 && (millis() - motorCycleStartTime >= motorCycleDurationMillis)) {
Serial.println("Motor triggered by automatic cycle.");
startMotor();
motorStartTime = millis();
motorRunning = true;
motorCycleStartTime = millis();
}


if (motorRunning && (millis() - motorStartTime >= motorRunDurationMillis)) {
Serial.println("Motor run duration finished.");
stopMotor();
motorRunning = false;


}



if (pumpCloudControl && !systemFault) {

pumpOnDurationMillis = (unsigned long)pumpCloudOnSecond * 1000UL;
pumpOffDurationMillis = (unsigned long)pumpCloudOffSecond * 1000UL;


if (pumpOnPeriod) {
if (millis() - lastPumpToggleTime >= pumpOnDurationMillis && pumpOnDurationMillis > 0) {
Serial.println("Pump ON period finished, switching to OFF.");
pumpOnPeriod = false;
lastPumpToggleTime = millis();
setPumpState(pumpOnPeriod);
}
} else {
if (millis() - lastPumpToggleTime >= pumpOffDurationMillis && pumpOffDurationMillis > 0) {
Serial.println("Pump OFF period finished, switching to ON.");
pumpOnPeriod = true;
lastPumpToggleTime = millis();
setPumpState(pumpOnPeriod);
}
}
pumpRunning = true;
setPumpState(pumpOnPeriod);
} else {

if(pumpRunning) {
Serial.println("Pump Cloud control turned OFF, stopping pump cycle.");
stopPump();
pumpRunning = false;
pumpOnPeriod = false;
}
}
delay(50);
}

void startMotor() {
digitalWrite(pwm1Pin, HIGH);
digitalWrite(pwm2Pin, LOW);
Serial.println("Motor started.");
}

void stopMotor() {
digitalWrite(pwm1Pin, LOW);
digitalWrite(pwm2Pin, LOW);
Serial.println("Motor stopped.");
}

void setPumpState(bool state) {
digitalWrite(pumpPin, state ? HIGH : LOW);

}

void stopPump() {
setPumpState(false);
}






void onPumpCloudControlChange() {
Serial.printf("pumpCloudControl changed to: %s\n", pumpCloudControl ? "true" : "false");
if (pumpCloudControl) {

Serial.println("Starting pump cycle from Cloud.");
pumpRunning = true;
pumpOnPeriod = true;
lastPumpToggleTime = millis();
setPumpState(pumpOnPeriod);
} else {

Serial.println("Stopping pump cycle from Cloud.");
stopPump();
pumpRunning = false;
pumpOnPeriod = false;
}
}





void onMotorCloudControlChange() {
Serial.printf("motorCloudControl changed to: %s\n", motorCloudControl ? "true" : "false");
if (motorCloudControl) {

startMotor();
motorStartTime = millis();
motorRunning = true;
} else {

stopMotor();
motorRunning = false;
}
}





void onMotorCloudCycleHoursChange() {
motorCycleStartTime = millis();
}






void onPumpCloudOffSecondChange() {

}





void onPumpCloudOnSecondChange() {

}

可以看到上面的代码是 V2.1 版本,因为 V2.0 开始用的第一天,从乌龟槽流到循环水箱的下水管堵住了,导致水没法循环了,所以循环水箱里的水一直减少,加水的泵一直就加,一满桶水很快没了。我赶紧关闭了电源进行清理。
结果没过两天,早上五点多被嗡嗡声吵醒了,我一看,循环水箱没水了,水桶也没水了,导致加水泵一直开着,还不知道干烧了多久。我还在想昨晚桶里水不少,为什么这样,仔细一看,水桶底漏了一个洞。靠,刚烧了固件就连续出现两个意外,促使我完善了代码。
于是我就增加了加水泵保护的代码,持续工作时长超过两分钟自动停止,并报故障,直到系统重启。

下载 Arduino APP,登录,可以看到设备上线了,点击按键控制,因为 Arduino IoT Cloud 对免费版的 Cloud Variables 总数量有限制,所以开关项就这么几个了。
phone

以上所有原始文件在此,仅供参考,有问题自己搜索或问 AI:
https://github.com/harry10086/Autofeed