🧭 작동 요약

  1. 평상시
    • LCD 1행: 현재 시각
    • LCD 2행: 급이 시각
  2. 급이 시각 설정
    • * → 시 입력
    • 숫자 입력
    • # → 분 입력
    • 숫자 입력
    • # → 설정 완료
  3. 하루 1회만 자동 급이
#include <ThreeWire.h>
#include <RtcDS1302.h>
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

void handleKeypad();
void updateLCD(const RtcDateTime& now);
void checkFeedTime(const RtcDateTime& now);
void print2(int num);
void feedFish();

/* ================== RTC (DS1302) ================== */
#define DS1302_CLK 12
#define DS1302_DAT 11
#define DS1302_RST 10

ThreeWire myWire(DS1302_DAT, DS1302_CLK, DS1302_RST);
RtcDS1302<ThreeWire> Rtc(myWire);

/* ================== SERVO ================== */
Servo feederServo;
const int SERVO_PIN = 13;

const int REST_ANGLE = 0;
const int DROP_ANGLE = 180;

/* ================== LCD ================== */
LiquidCrystal_I2C lcd(0x27, 16, 2);

/* ================== KEYPAD ================== */
const byte ROWS = 4;
const byte COLS = 4;

char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

/* ================== FEED TIME ================== */
int feedHour   = 17;
int feedMinute = 13;

/* ================== STATE ================== */
int lastFeedMinute = -1;

enum Mode { NORMAL, SET_HOUR, SET_MIN };
Mode mode = NORMAL;

/* ================== SETUP ================== */
void setup() {
  Serial.begin(9600);

  feederServo.attach(SERVO_PIN);
  feederServo.write(REST_ANGLE);

  Rtc.Begin();
  if (!Rtc.GetIsRunning()) {
    Rtc.SetIsRunning(true);
  }

  // 최초 1회만 사용
  //Rtc.SetDateTime(RtcDateTime(__DATE__, __TIME__));

  lcd.init();
  lcd.backlight();
  lcd.clear();

  Serial.println("구피 자동 급이기 시작");
}

/* ================== LOOP ================== */
void loop() {
  RtcDateTime nowUTC = Rtc.GetDateTime();

  // UTC -> KST 변환
  int hourKST = nowUTC.Hour() + 9;
  int dayoffset = 0;
    
  if (hourKST >= 24) {
      hourKST -= 24;
      dayoffset = 1;
  }
    
  RtcDateTime nowKST(
    nowUTC.Year(),
    nowUTC.Month(),
    nowUTC.Day() + dayoffset,
    hourKST,
    nowUTC.Minute(),
    nowUTC.Second()
  );  
    
  handleKeypad();
  updateLCD(nowKST);
  checkFeedTime(nowKST);

  delay(500);   // 타이밍 안정성 확보
}

/* ================== FUNCTIONS ================== */

// 키패드 입력 처리
void handleKeypad() {
  char key = keypad.getKey();
  if (!key) return;

  if (mode == NORMAL && key == '*') {
    mode = SET_HOUR;
    feedHour = 0;
    lcd.clear();
  }
  else if (mode == SET_HOUR && isDigit(key)) {
    feedHour = feedHour * 10 + (key - '0');
    if (feedHour > 23) feedHour = 23;
  }
  else if (mode == SET_HOUR && key == '#') {
    mode = SET_MIN;
    feedMinute = 0;
    lcd.clear();
  }
  else if (mode == SET_MIN && isDigit(key)) {
    feedMinute = feedMinute * 10 + (key - '0');
    if (feedMinute > 59) feedMinute = 59;
  }
  else if (mode == SET_MIN && key == '#') {
    mode = NORMAL;
    lastFeedMinute = -1;   // 재설정 즉시 반영
    lcd.clear();
  }
}

// LCD 기본 화면 표시
void updateLCD(const RtcDateTime& now) {
  lcd.setCursor(0,0);
  lcd.print("Now ");
  print2(now.Hour());
  lcd.print(":");
  print2(now.Minute());
  lcd.print(":");
  print2(now.Second());

  lcd.setCursor(0,1);
  if (mode == SET_HOUR) {
    lcd.print("Set Hour: ");
    print2(feedHour);
  }
  else if (mode == SET_MIN) {
    lcd.print("Set Min : ");
    print2(feedMinute);
  }
  else {
    lcd.print("Feed ");
    print2(feedHour);
    lcd.print(":");
    print2(feedMinute);
  }
}

// 급이 시각 체크
void checkFeedTime(const RtcDateTime& now) {

  // 자정 지나면 하루 리셋
  if (now.Hour() == 0 && now.Minute() == 0) {
    lastFeedMinute = -1;
  }

  // 같은 분에 처음 진입했을 때만 급이
  if (now.Hour() == feedHour &&
      now.Minute() == feedMinute &&
      lastFeedMinute != now.Minute()) {

    feedFish();
    lastFeedMinute = now.Minute();
  }
}

// 실제 급이 + 성공 메시지 표시
void feedFish() {

  // 1. 급이 시작 표시
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Feeding...");

  // 2. 서보 동작
  const unsigned long FEED_HOLD_TIME = 1500; // 4초 (원하는 만큼 조절)

  feederServo.write(DROP_ANGLE);
  delay(FEED_HOLD_TIME);   // ← 급이량을 결정하는 핵심
  feederServo.write(REST_ANGLE);
  delay(1000);


  // 3. 급이 완료 표시
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Feed done");

  // 사람이 인지할 수 있도록 대기
  delay(2000);

  // 4. 화면 초기화 (다음 loop에서 기본 화면 표시)
  lcd.clear();
}

// 2자리 출력
void print2(int num) {
  if (num < 10) lcd.print("0");
  lcd.print(num);
}

 

반응형

+ Recent posts