智能灌溉控制器
组件和用品
| | × | 1 | |
| | × | 1 | |
| | × | 1 | |
关于这个项目
使用 Arduino 创建智能灌溉控制器
使用动态水循环智能灌溉您的院子。如果下雨或自上次浇水以来一直在下雨,请停止给院子浇水。使用光传感器检测日出时间并相应地自动调整水开始时间。如果太冷,请停止给院子浇水。
功能列表
- 外部温度传感器
- 室外雨量传感器
- 外部光线传感器
- 电池供电的实时时钟,用于每周安排
- 非易失性存储 - 不会因断电而丢失浇水
- 日出检测
- 智能浇水可节省水费
- 日出前浇水,以便有适当的浸泡时间
- 当外面太冷时停止浇水
- 减少真菌生长
- 轻松控制日程
构建智能灌溉控制器所需的部件 - IO 扩展器
- x2 1-Wire 转 I2C。
- 1-Wire 结
- 光连接器
- 分离器
- Arduino Nano。
- 4 通道 DC 5V 继电器。
- TSL2561 光传感器。
- DS18B20 防水温度传感器。
- 光学红外水位传感器。
- DS3231 AT24C32 IIC 精密实时时钟。
- I2C SSD1306 OLED 128x64 显示屏。
- 200x120x75mm 透明防水塑料外壳。
- 100x68x50mm 透明防水塑料外壳。
- ip68 pg7 防水尼龙电缆密封套。
- ip68 pg11 防水尼龙电缆密封套。
- RJ11 Keystone 螺丝端子插孔。
- 50 英尺 4C4P RJ11 线。
- 6 英尺 4C4P RJ11 线。
- 2.54 毫米接头线。
- x2 2 针 SPST 瞬时按钮微动开关
- 12VDC 1A 壁式适配器电源。
接线图
OLED 显示屏
按 Menu 按钮显示菜单,然后继续按该按钮循环显示所有菜单选项。菜单将在 30 秒不活动后自动删除。按选择按钮执行所需的菜单功能。
那么为什么要使用 IO 扩展器? - 设计更简单
- 现成的零件
- 无需编写 1-Wire 驱动程序
- 无需编写 DS3231 RTC 驱动程序
- 无需写入 EEPROM 驱动程序
- 无需编写 OLED 显示驱动程序
- 没有显示字体占用 Arduino 代码空间
- 无需编写温度传感器驱动程序
- 无需使用光学雨量传感器干燥器
- 节省Arduino上的代码空间;只有 12710 字节 (39%)
- 只需三天即可编写代码
- 使用标准 RJ11 电话线易于接线
- 没有传感器电缆长度问题
- 比类似的商业系统更便宜
- 易于更改以适应个性化需求
- 单电源
构建系统
将 Arduino Nano 连接到 IO 扩展器并使用以下代码对其进行编程。 6排针是软件串口调试口,最终安装不需要。
确保更改 ONEWIRE_TO_I2C_ROM1 和 ONEWIRE-TO_I2C_ROM2 定义的地址以匹配您的 1-Wire 到 I2C 地址。
/* IO Expander 草图优化
*
* Irrigation System v1.1
*
*/
#include
注意: 如果您使用 USB 端口对 Arduino Nano 进行编程,则必须将其与 IO 扩展器断开连接,因为它也使用相同的单个串行端口,如果您想调试,请使用 ICSP 端口对 ATmega328P 进行编程。要启用软件调试端口,取消对SERIAL_DEBUG 定义的注释。
分离器必须首先配置为将光学红外传感器数据线与 1-Wire 远程传感器线隔离。在 R2 处焊接一个零欧姆 0603 电阻器。
对于 PG7 和 PG11,在小外壳上钻一个 7/16" 孔,并在右侧较大外壳上钻一个 11/16" 孔。使用dremel 工具稍微扩大孔,直到压盖紧贴。 PG7 将馈入远程传感器,PG11 为 12VDC、24VAC、歧管线和 RJ11 远程传感器线供电。
连接 SPST 瞬时按钮微动开关并将其连接到 RJ11 螺钉端子。使用热缩管使触点绝缘。
连接所有电线并将所有部件组装/送入大外壳。用于远程传感器的 50 英尺 RJ11 线应正好穿过 PG11 压盖,而无需剪断它。
在光学红外水传感器的小外壳顶部钻一个 9/16" 孔。使用dremel 工具稍微扩大孔直到传感器适合。小型远程传感器外壳是紧密配合的,但如果内容物以推荐的方向铺设,它应该刚好合适。使 RJ11 电线尽可能短将有助于将其全部塞入较小的外壳中。组装后,建议在拧紧螺母之前在压盖螺母垫圈中添加一些船用胶水,以创造更好的密封。
将远程传感器外壳安装在室外,并将其安装在房屋东侧,光学红外水传感器和光传感器指向天空,没有障碍物。
在大外壳的顶部中间底部钻 1/4" 孔并安装按钮。使用 Dremel 工具稍微扩大孔,直到按钮适合。
测试系统并确保一切正常。要测试继电器和传感器,请断开 Arduino 与 IO 扩展器的连接,并将其直接连接到您的计算机以手动控制它。确认一切正常后,使用双面胶带和包装泡沫将所有部件组装到外壳中以固定您的电路板,并享受智能灌溉控制器的好处和节省。
操作视频
2019 年 9 月 12 日更新
发布了 v1.1,修复了系统断电数天后的启动问题。
2019/10/2 更新
当将 1-Wire 连接到 I2C 到 DS3231,然后连接到 SSD1306 OLED 屏幕时,您将在 SDA 和 SCL 线上总共有三个不同的上拉,如下图圆圈所示。这将有效地导致 4.7k / 3 =1.56k 上拉,它可能太强并导致随机屏幕损坏。
由于 DS3231 使用了其他线路使用的电阻包,因此请移除其他上拉电阻:
- 1-Wire 至 I2C R3 和 R4。
- SSD1306 OLED R6 和 R7。
代码
智能灌溉控制器C/C++
使用您的 Arduino 智能地为您的院子或花园浇水。/* IO Expander 草图优化 * * Irrigation System v1.1 * */#include #include // 文件位于 \Program文件 (x86)\Arduino\hardware\tools\avr\avr\include\time.h#include #include #include #include "IOExpander.h" h"#define FAHRENHEIT#define INIT_BOARD "g5w1;g11w1;g11d0,75;g12w1;g12d0,75;rsf"#define ONEWIRE_TO_I2C_ROM1 "i4scc"#define ONEWIRE_TO_I2C_ROM2 "i6s30000"#defines0tC_ROM2 "i6s0,75;rsf"定义 I2C_EEPROM "s4tf"#define I2C_OLED "s4t10"#define I2C_LIGHT "s3t9;sc0"#define OPTICAL_SENSOR "g5a"#define BUTTON1 "g11d"#define BUTTON2 "g12d"#define WATER_TIME_BEFORE_SURISE0#define WATER_TIME_BEFORE_SURISE0#define RAVEL6定义 DO_NOT_WATER_TEMP 4.4444 // 40F#define MAX_ZONES 4#define HOUR_IN_DAY 24L#define MIN_IN_HOUR 60L#define SEC_IN_MIN 60L#define SEC_IN_HOUR (MIN_IN_HOUR * SEC_IN_MIN_IN_SEC_DAY_IN_HOUR (MIN_IN_HOUR * SEC_IN_MIN_IN_HOUR)#define OUR)#define DAYS_IN_WEEK 7#define SEC_IN_WEEK (SEC_IN_DAY * DAYS_IN_WEEK)#define SUN 0x01#define MON 0x02#define TUE 0x04#define WED 0x08#define THR 0x10#define FRI 0x20x40#define FRI 0x20x40周一 |周二 |星期三 | THR |周五 | SAT)#define SUNRISE 0x80#define MENU_OPTIONS 9#define MENU_TIME 30#define OFF 0#define ON 1#define STATE_ON_OFF 0x01//#define SERIAL_DEBUG#ifdef SERIAL_DEBUGSoftwareSerial swSerial(8,7);#endif4char weekday[][] {"SUN","MON","TUE","WED","THU","FRI","SAT"};char menu[][13] ={"Next", "Water", "Reset" , "Clock Min +", "Clock Min-", "Clock Hour +", "Clock Hour-", "Sunrise", "ON/OFF"};enum { MENU_NEXT, MENU_WATER, MENU_RESET, MENU_CLOCK_MIN_PLUS, MENU_CLOCK_MIN_MINUS, MENU_CLOCK_HOUR_PLUS, MENU_CLOCK_HOUR_MINUS, MENU_SUNRISE, MENU_ON_OFF};typedef struct { char description[16]; uint8_t 中继;} 区域;typedef 结构 { uint8_t 区域; uint8_t 天; int8_t 小时; int8_t 分钟; uint8_t 持续时间;} 日程;typedef 结构 { time_t 日出时间; time_t last_water_time; uint8_t water_schedule; uint8_t water_duration; uint8_train[MAX_ZONES]; uint8_t 状态; uint8_t crc;} NVRAM;enum { ZONE1, ZONE2, ZONE3, ZONE4};enum { RELAY1 =1, RELAY2, RELAY3, RELAY4};ZONE zone[] ={ {"Front Right", RELAY1}, {"Front Left" , RELAY2}, {"Bushes", RELAY3}, {"Left Side", RELAY4},};SCHEDULE schedule[] ={ {ZONE1, SUNRISE | EVERYDAY, -1, 0, 4}, {ZONE2, EVERYDAY, 6, 15, 5}, {ZONE3, EVERYDAY, 6, 0, 10}, {ZONE4, EVERYDAY, 6, 10, 6},}; NVRAM nvram;bool update_nvram =false;uint8_t crc8(uint8_t* 数据, uint16_t 长度){ uint8_t crc =0;而(长度--){ crc =_crc8_ccitt_update(crc, *data++); } return crc;}int led =13;bool init_oled =true;bool update_oled =true;bool init_board =true;#ifdef FAHRENHEIT#define C2F(temp)CelsiusToFahrenheit(temp)floatCelsiusToFahrenheit(float celsius){ return ((celsius * 9) / 5) + 32;}#else#define C2F(temp) (temp)#endifvoid SerialPrint(const char* str, float decimal, char error){ Serial.print(str);如果(错误)Serial.print(F(“NA”)); else Serial.print(decimal, 1);}time_t NextScheduleTime(time_t last_time, uint8_t* next_schedule){ time_t next_time =-1; time_t clk_time; uint8_t i;时钟时钟; uint8_t 天; for (i =0; i SATURDAY) wday =SUNDAY; if (wday ==clk.tm_wday) 中断; // 只检查一周 } if (clk_time 0) { if (nvram.rain[i]> nvram.water_duration) nvram.water_duration =0;否则 nvram.water_duration -=nvram.rain[i]; nvram.rain[i] =0; }}void WaterScheduleTime(void) { uint8_t i; nvram.water_duration--; update_nvram =真; i =schedule[nvram.water_schedule].zone; if (i 0) Serial.println("o");否则 Serial.println("f"); SerialReadUntilDone(); }}void setup() { Serial.begin(115200);#ifdef SERIAL_DEBUG swSerial.begin(115200);#endif pinMode(led, OUTPUT); //延迟(1000); wdt_enable(WDTO_8S);}void loop() { static tm rtc; tm 时钟,sunrise_clk; time_t rtc_time; time_t clk_time;静态 time_t next_time;静态 uint8_t last_sec;静态 uint8_t last_min; bool error_rtc; bool error_light; bool error_temp;静态长勒克斯 =0;静态浮点温度,降雨;静态 uint8_t 日出计数器 =MIN_IN_HOUR; static bool check_sunrise =false; uint8_t i; static bool read_nvram =true;静态 time_t water_time;静态 uint8_t water_schedule; uint8_t sz; uint8_t 天;长 n;布尔按钮1,按钮2;静态 int8_t menu_select =-1;静态 time_t menu_time =0; Serial.println(); if (SerialReadUntilDone()) { if (init_board) { SerialCmdDone(INIT_BOARD); init_board =假; } if (init_oled) { if (SerialCmdNoError(ONEWIRE_TO_I2C_ROM1)) { SerialCmdDone(I2C_OLED ";si;sc;sd"); init_oled =假; } } if (SerialCmdDone(RTC_SENSOR)) { error_rtc =!SerialReadTime(&rtc); if (!error_rtc) { clk =rtc; // mktime() 可以改变 struct tm rtc_time =mktime(&clk); localtime_r(&rtc_time, &rtc); // 获取星期。 } if (read_nvram) { if (SerialCmdNoError(I2C_EEPROM)) { SerialReadEEPROM((uint8_t*)&nvram, 0, sizeof(nvram)); if (nvram.crc !=crc8((uint8_t*)&nvram, sizeof(nvram)-sizeof(uint8_t))) { //swSerial.println("CRC8 Failure!"); // 初始化 nvram memset(&nvram, 0, sizeof(nvram));时钟 =rtc; clk.tm_hour =6; clk.tm_min =0; clk.tm_sec =0; nvram.sunrise_time =mktime(&clk);如果(nvram.sunrise_time SEC_IN_WEEK) nvram.last_water_time =rtc_time - SEC_IN_WEEK; // 检查日出时间 if (rtc_time> nvram.sunrise_time) { localtime_r(&nvram.sunrise_time, &sunrise_clk);时钟 =rtc; clk.tm_hour =sunrise_clk.tm_hour; clk.tm_min =sunrise_clk.tm_min; clk.tm_sec =sunrise_clk.tm_sec; nvram.sunrise_time =mktime(&clk);如果(nvram.sunrise_time 0) sunrise_counter--; else check_sunrise =true; } else { if (sunrise_counter =MENU_OPTIONS) menu_select =0; } menu_time =rtc_time; update_oled =true; } if (menu_select>=0) { button2 =SerialReadButton(BUTTON2); if (button2) { clk_time =rtc_time; switch(menu_select) { case MENU_NEXT:case MENU_RESET:if (nvram.water_duration) { nvram.water_duration =1; WaterScheduleTime(); } water_time =NextScheduleTime((menu_select ==MENU_NEXT) ? water_time :rtc_time, &water_schedule);休息; case MENU_WATER:StartScheduleTime(water_time, water_schedule); WaterScheduleTime();休息; case MENU_CLOCK_MIN_PLUS:clk_time +=SEC_IN_MIN;休息; case MENU_CLOCK_MIN_MINUS:clk_time -=SEC_IN_MIN;休息; case MENU_CLOCK_HOUR_PLUS:clk_time +=SEC_IN_HOUR;休息; case MENU_CLOCK_HOUR_MINUS:clk_time -=SEC_IN_HOUR;休息; case MENU_ON_OFF:nvram.state ^=STATE_ON_OFF; update_nvram =true;休息; } if (clk_time !=rtc_time) { if (SerialCmdDone(RTC_SENSOR)) { localtime_r(&clk_time, &clk); SerialWriteTime(&clk); rtc_time =clk_time; } } menu_time =rtc_time; update_oled =true; } } if (menu_select>=0 &&rtc_time - menu_time> MENU_TIME) { menu_select =-1; update_oled =true; } if (update_oled) { if (SerialCmdNoError(ONEWIRE_TO_I2C_ROM1)) { Serial.print("st10;so1;sc;sf0;sa0;sd0,0,\""); if (nvram.water_duration) Serial.print(nvram.water_duration); else { if ((nvram.state &STATE_ON_OFF) ==OFF) Serial.print("OFF"); else if (rain <=RAIN_DETECT_LEVEL) Serial.print("Rain"); else if (temp <=DO_NOT_WATER_TEMP) Serial.print("Cold"); else Serial.print("v1.1"); } Serial.print("\";sf2;sa1;sd75,0,\""); if (menu_select ==7) { // Sunrise clk_time =nvram.sunrise_time; localtime_r(&clk_time, &clk); } else clk =rtc; Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min <10) Serial.print("0"); Serial.print(clk.tm_min); Serial.println("\""); SerialReadUntilDone(); Serial.print("sf1;sa0;sd79,8,\""); Serial.print((clk.tm_hour>12)?"PM":"AM"); Serial.print("\";sf0;sa1;sd127,1,\""); Serial.print(weekday[clk.tm_wday]); Serial.print("\";sd127,13,\""); Serial.print(clk.tm_mon+1); Serial.print("/"); Serial.print(clk.tm_mday); Serial.println("\""); SerialReadUntilDone(); Serial.print("sf0;sa0;sd1,36,\""); i =schedule[water_schedule].zone; if (i SEC_IN_DAY) { Serial.print("\";sa1;sd126,36,\""); Serial.print(clk.tm_mon+1); Serial.print("/"); Serial.print(clk.tm_mday); Serial.print(" "); Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min <10) Serial.print("0"); Serial.print(clk.tm_min); Serial.print(" "); } else { Serial.print("\";sf1;sa1;sd111,30,\""); Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min <10) Serial.print("0"); Serial.print(clk.tm_min); Serial.print("\";sf0;sd126,36,\""); } Serial.print((clk.tm_hour>12)?"PM":"AM"); if (nvram.water_duration) Serial.print("\";so2;sc0,29,128,19"); Serial.println(); SerialReadUntilDone(); if (menu_select ==-1) { //Serial.print("\";sa0;sd0,52,\""); //Serial.print(rain); SerialPrint("\";so1;sa2;sd63,52,\"", C2F(temp), error_temp); if (!error_temp) Serial.print("\",248,\"" #ifdef FAHRENHEIT "F" #else "C" #endif ); Serial.print(" / "); Serial.print(lux); } else { Serial.print("\";so0;sc0,51,128,14;sf0;sa2;sd63,52,\""); if (menu_select ==MENU_ON_OFF) { Serial.print((nvram.state &STATE_ON_OFF) ? "OFF" :"ON"); } else Serial.print(menu[menu_select]); } Serial.println("\";sd"); SerialReadUntilDone(); update_oled =false; } else init_oled =true; } if (update_nvram) { if (SerialCmdNoError(I2C_EEPROM)) { nvram.crc =crc8((uint8_t*)&nvram, sizeof(nvram)-sizeof(uint8_t)); //swSerial.println(nvram.crc, HEX); SerialWriteEEPROM((uint8_t*)&nvram, 0, sizeof(nvram)); update_nvram =false; } } delay(50); } else { digitalWrite(led, HIGH); delay(500); digitalWrite(led, LOW); delay(500); init_board =true; init_oled =true; } wdt_reset();}
示意图
Intelligently water your yard or garden