Arduino Nano 기반 스마트 관수 컨트롤러 - 날씨·조도 센서로 자동 급수
부품 및 소모품
Arduino Nano R3
1
IO Expander
1
IO Expander Bundle
1
앱 및 온라인 서비스
프로젝트 소개
Arduino로 스마트 관수 컨트롤러 만들기
동적 급수 주기로 정원을 지능적으로 관리하세요. 비가 오거나 이전 급수 이후 비가 온 경우 급수를 중단합니다. 조도 센서로 일출 시간을 감지해 자동으로 급수 시작 시간을 조정합니다. 너무 추울 때도 급수를 멈춥니다.
주요 기능
외부 온도 센서
외부 강우 센서
외부 조도 센서
주간 스케줄용 배터리 백업 실시간 시계(RTC)
비휘발성 저장소 - 정전 시 급수 일정 보존
일출 감지
스마트 급수로 수도 요금 절약
일출 전 급수로 충분한 침투 시간 확보
너무 추우면 급수 중단
곰팡이 성장 억제
간편한 스케줄 제어
필요 부품 상세
IO Expander
1-Wire to I2C 변환기 2개
1-Wire 분기점
광 커넥터, 스플리터
Arduino Nano
4채널 5V 릴레이
TSL2561 조도 센서
DS18B20 방수 온도 센서
광학 적외선 수위 센서
DS3231 AT24C32 IIC 고정밀 RTC
I2C SSD1306 OLED 128x64 디스플레이
방수 플라스틱 인클로저 (대/소)
IP68 PG7/PG11 방수 케이블 글랜드
RJ11 키스톤 스크류 터미널 잭
RJ11 케이블 (50ft, 6ft)
2.54mm 헤더 와이어
SPST 순간 푸시 버튼 마이크로 스위치 2개
12VDC 1A 월 어댑터 전원 공급장치
배선도
OLED 디스플레이
메뉴 버튼을 눌러 메뉴를 표시하고, 계속 눌러 옵션을 순환합니다. 30초간 조작 없으면 자동으로 메뉴가 사라집니다. 선택 버튼으로 원하는 기능을 실행하세요.
IO Expander를 사용하는 이유
설계 단순화
기성 부품 활용
1-Wire 드라이버 작성 불필요
DS3231 RTC 드라이버 불필요
EEPROM 드라이버 불필요
OLED 디스플레이 드라이버 불필요
디스플레이 폰트로 인한 코드 공간 절약
온도 센서 드라이버 불필요
광학 강우 센서 드라이버 불필요
Arduino 코드 공간 절약 (12710 바이트, 39%만 사용)
코드 작성 3일 만에 완료
표준 RJ11 전화선으로 간편 배선
센서 케이블 길이 문제 없음
유사 상용 시스템보다 저렴
개별 요구사항에 맞게 쉽게 수정 가능
단일 전원 공급
시스템 구축
Arduino Nano를 IO Expander에 연결하고 아래 코드를 업로드하세요. 6핀 헤더는 소프트웨어 시리얼 디버그 포트로 최종 설치 시에는 필요 없습니다.
중요: ONEWIRE_TO_I2C_ROM1과 ONEWIRE_TO_I2C_ROM2 정의 주소를 본인의 1-Wire to I2C 주소에 맞게 변경하세요.
전체 코드 (C/C++)
/* IO Expander 스케치 최적화
* 관수 시스템 v1.1 */
#include <math.h>
#include <time.h>
#include <util/crc16.h>
#include <avr/wdt.h>
#include <SoftwareSerial.h>
#include "IOExpander.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 "i6s8f"
#define ONEWIRE_TEMPERATURE "t6s0300"
#define RTC_SENSOR "s4te"
#define 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_SUNRISE 60
#define SUNRISE_LUX 100
#define RAIN_DETECT_LEVEL 4.0
#define DO_NOT_WATER_TEMP 4.4444 // 40℉
#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)
#define SEC_IN_DAY (HOUR_IN_DAY * SEC_IN_HOUR)
#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 0x20
#define SAT 0x40
#define EVERYDAY (SUN | MON | TUE | WED | THR | FRI | 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_DEBUG
SoftwareSerial swSerial(8,7);
#endif
char weekday[][4] = {"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 relay; } ZONE;
typedef struct { uint8_t zone; uint8_t days; int8_t hour; int8_t min; uint8_t duration; } SCHEDULE;
typedef struct { time_t sunrise_time; time_t last_water_time; uint8_t water_schedule; uint8_t water_duration; uint8_t rain[MAX_ZONES]; uint8_t state; 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* data, uint16_t length) {
uint8_t crc = 0;
while (length--) crc = _crc8_ccitt_update(crc, *data++);
return crc;
}
int led = 13;
bool init_oled = true, update_oled = true, init_board = true;
#ifdef FAHRENHEIT
#define C2F(temp) CelsiusToFahrenheit(temp)
float CelsiusToFahrenheit(float celsius) { return ((celsius * 9) / 5) + 32; }
#else
#define C2F(temp) (temp)
#endif
void SerialPrint(const char* str, float decimal, char error) {
Serial.print(str);
if (error) Serial.print(F("NA")); else Serial.print(decimal, 1);
}
// ... (이하 루프, 스케줄링, 버튼 처리, OLED 업데이트, NVRAM 저장 등 전체 로직 생략 - 원본 참조)
참고: USB 포트로 Arduino Nano를 프로그래밍할 때는 IO Expander에서 분리해야 합니다(동일 시리얼 포트 공유). 디버깅 시에는 ICSP 포트로 ATmega328P를 프로그래밍하세요. 소프트웨어 디버그 포트를 활성화하려면 SERIAL_DEBUG 정의를 주석 해제하세요.
스플리터는 광학 적외선 센서 데이터 라인을 1-Wire 원격 센서 라인에서 격리하도록 설정해야 합니다. R2에 0옴 0603 저항을 납땜하세요.
작은 인클로저에 7/16인치, 큰 인클로저 오른쪽 측면에 11/16인치 구멍을 뚫고 PG7, PG11 글랜드를 장착하세요. 드레멜 도구로 구멍을 살짝 확대해 꽉 끼게 만드세요. PG7은 원격 센서용, PG11은 12VDC, 24VAC, 매니폴드 와이어, RJ11 원격 센서 와이어용입니다.
SPST 순간 푸시 버튼 마이크로 스위치를 배선하고 RJ11 스크류 터미널에 연결하세요. 열수축 튜브로 접점을 절연하세요.
모든 와이어를 연결하고 부품을 대형 인클로저에 조립/수납하세요. 원격 센서용 50ft RJ11 와이어는 자르지 않고 PG11 글랜드를 통과할 수 있어야 합니다.
작은 인클로저 상단에 9/16인치 구멍을 뚫고 광학 적외선 수위 센서를 장착하세요. 드레멜로 구멍을 조정해 센서가 딱 맞게 하세요. 작은 원격 센서 인클로저는 공간이 타이트하지만 권장 방향으로 배치하면 들어갑니다. RJ11 와이어를 최대한 짧게 하면 수납이 쉽습니다. 조립 후 글랜드 너트 와셔에 마린 글루를 발라 밀봉하세요.
원격 센서 인클로저를 집 동쪽 높은 곳에 설치하고, 광학 적외선 수위 센서와 조도 센서가 하늘을 향하고 장애물이 없도록 하세요.
대형 인클로저 상단/중간/하단에 1/4인치 구멍을 뚫고 버튼을 장착하세요.
시스템을 테스트하고 모든 것이 정상 작동하는지 확인하세요. 릴레이와 센서 테스트 시 Arduino를 IO Expander에서 분리해 컴퓨터에 직접 연결해 수동 제어하세요. 검증 완료 후 양면 테이프와 포장 폼으로 보드를 고정해 인클로저에 모두 조립하면 스마트 관수 컨트롤러의 혜택과 절약 효과를 누릴 수 있습니다.
작동 영상
업데이트 내역
2019/09/12: v1.1 릴리스 - 며칠간 정전 시 시작 문제 수정.
2019/10/02: 1-Wire to I2C를 DS3231과 SSD1306 OLED에 연결하면 SDA/SCL 라인에 총 3개의 풀업 저항이 병렬로 연결되어 4.7k/3 = 1.56k 풀업이 되어 너무 강해지고 화면 깨짐이 발생할 수 있습니다. DS3231은 다른 라인에도 쓰이는 저항 팩을 사용하므로 다음 풀업 저항을 제거하세요:
1-Wire to I2C: R3, R4
SSD1306 OLED: R6, R7
회로도