도망가는 알람시계 제작 방법

작품 소개

알람 시간을 설정해 두면 해당 시간에 알람(아기상어)이 울리면서 임의의 방향으로 방향을 바꾸면서 버튼을 누를 때까지 계속 도망다니는 로봇입니다.

본 작품은 무지개 지역 아동센터 교육 중 자유작품으로 아이들과 함께 기능을 구상하여 제작하였습니다.

주요 부품

1. RTC 모듈: 별도의 외부 전원 없이도 현재 시간을 저장하고 지속 갱신합니다.

2. 모터 드라이버: 바퀴 구동을 위한 DC 모터 2개의 방향 및 속도를 별도 제어합니다. 본 작품에서는 속도는 제어하지 않고 정지 및 최고 속도만 적용합니다.

3. 스위치: 로봇의 전체 전원을 켜거나 끕니다.

4. 스피커 모듈: 설정된 알람 시간이 되면 버튼을 누르기 전까지 아기상어 음악을 송출합니다.

5. 미니 브레드보드: 각 모듈에 전원이나 신호를 배분하기 위해 사용합니다.

6. 푸시버튼: 1개는 알람의 시간을 0~23까지 1시간 단위로 올립니다(23 다음은 다시 0으로). 나머지 1개는 알람의 분을 0~55까지 5분 단위로 올립니다(55 다음은 시간을 1 올리고 분은 다시 0으로).

7. LCD 모듈: 현재 시간을 1초에 1번 갱신하여 보여주고 세팅된 알람 시간도 보여줍니다.

8. 자동차 키트: 차체 및 모터 고정 부품, 수직 동력전달 기어박스 결함된 DC 모터 세트 x 2, 바퀴 x 2, 보조바퀴, 배터리 케이스, 기타 볼트 및 나사류

회로도

아두이노액츄에이터
VIN배터리 +전원 입력
GND배터리 -전원 연결
5V브레드보드 +전원 출력
GND브레드보드 -전원 연결
D01시간 버튼
D02분 버튼
D03스피커모듈_IN
D04모터드라이버_IN1
D05모터드라이버_IN2
D06모터드라이버_IN3
D07모터드라이버_IN4
D08RTC모듈_RST
D09RTC모듈_DAT
D10RTC모듈_CLK
SDALCD모듈_SDA
SCLLCD모듈_SCL

하드웨어 제작/조립 방법

자동차 키트의 모터를 아래와 같이 지지대와 함께 볼트와 너트로 고정 해 주세요. 모터를 다 조립했으면 마지막 사진처럼 바퀴까지 조립 해 주세요.

다음은 모터가 있는 차체 아랫 면에 서포터가 부착되도록 지지대 4개를 달아 보조 바퀴를 조립합니다.

배터리 홀더를 아랫 부분 양 쪽 나사 홈을 통해 고정 해 줍니다.

차체 윗 면에 나머지 부품들을 글루건을 이용 해 아래와 같이(혹은 자유롭게) 고정 시켜 줍니다. 이 때 아두이노 보드의 케이블 연결 부분에 간섭이 일어나지 않도록 주의 해야 합니다. 부품 고정이 완료된 후에는 회로도와 같이 선을 연결 해 주세요.

스위치는 아래와 같이 핀이 차체 아래로 나오도록 고정한 후 전선을 연결합니다(납땜 추천).

코드

아래 코드를 실행하기 위해서는 다음 라이브러리의 설치가 필요합니다.

1. Rtc by Makuna by Michael C. Miller : RTC 모듈의 제어를 위해 필요합니다.

2. LiquidCrystal I2C : LCD 모듈의 제어를 위해 필요합니다.

3. ArduinoThread by Ivan Seidel : 1초에 한 번 LCD에 현재 시간을 표현하는 함수를 Thread 프로그램 화 하기 위해 필요합니다.

#include <Thread.h>
#include <ThreeWire.h>  
#include <RtcDS1302.h>
#include <LiquidCrystal_I2C.h>

//-------------------------------------------------------------------------------------
//시계관련
Thread clThread = Thread();

ThreeWire myWire(9,10,8); // IO(DAT), SCLK, CE(RST)  디지털 연결 번호
RtcDS1302<ThreeWire> Rtc(myWire);
LiquidCrystal_I2C lcd(0x27,16,2);

#define countof(a) (sizeof(a) / sizeof(a[0]))   // 시계 모듈 년도, 달, 시간, 분, 초 받아오는 함수

RtcDateTime alm;
bool b_almState = true;
unsigned long int millTime = 0;

//-------------------------------------------------------------------------------------
//버튼관련
int BUTTON1 = 1;
int buttonState1, buttonState1_old;
int state1 = 0;
int BUTTON2 = 2;
int buttonState2;
int state2 = 0;

int alm_H = 0;
int alm_M = 0;
int alm_S = 0;

//--------------------------------------------------------------------------------------
//모터관련
int Flag_FWDBWD; // 0:전진, 1:후진
int Flag_DIRECT; // 0:정방향, 1:좌회전, 2:우회전
#define M_IN1 4 // 모터제어 입력 핀 IN1을 Digital4번핀으로 설정
#define M_IN2 5 // 모터제어 입력 핀 IN2을 Digital5번핀으로 설정
#define M_IN3 6 // 모터제어 입력 핀 IN3을 Digital13번핀으로 설정
#define M_IN4 7 // 모터제어 입력 핀 IN4을 Digital12번핀으로 설정

//-------------------------------------------------------------------------------------
//알람관련
  // 코드 설명 https://postpop.tistory.com/98
 
  // 12화음 이름 정의
  typedef enum {
    NOTE_C, NOTE_Cs, NOTE_D, NOTE_Eb, NOTE_E, NOTE_F, NOTE_Fs, NOTE_G, NOTE_Gs,
    NOTE_A, NOTE_Bb, NOTE_B, NOTE_MAX
  } note_t;
 
  // 화음별 피에조 부저 진동수 정의 및 옥타브에 따른 값 변경 함수
  double ledcWriteNote(note_t note, uint8_t octave){
    const uint16_t noteFrequencyBase[12] = {
    //  1      2      3      4      5      6     7       8     9       10    11      12
    //  C      C#     D      Eb     E      F     F#      G     G#      A     Bb      B
       4186,  4435,  4699,  4978,  5274,  5588,  5920,  6272,  6645,  7040,  7459,  7902
    };
    if(octave > 8 || note >= NOTE_MAX){
      return 0;
    }
    double noteFreq =  (double)noteFrequencyBase[note] / (double)(1 << (8-octave));
    return noteFreq;
  }
 
  // ---------------------------------------------------------------------- 아기상어
  uint8_t Tempo = 220;
  float tempoRatio = float(Tempo)/60.0;
  uint8_t KeySign = 0;
  uint8_t octaveTemp = 4;
  float beatTime = 4/4;
 
  const note_t Mnote[] PROGMEM= {
  NOTE_D,NOTE_E,NOTE_D,NOTE_E,NOTE_D,NOTE_E,NOTE_D,NOTE_E,NOTE_D,NOTE_E,NOTE_D,NOTE_E,NOTE_D,NOTE_E,NOTE_D,NOTE_E,
  NOTE_G,NOTE_F,NOTE_G,NOTE_A,NOTE_G,NOTE_F,NOTE_D,NOTE_F,NOTE_G,NOTE_F,NOTE_G,NOTE_A,NOTE_G,NOTE_F,NOTE_D,NOTE_F,NOTE_G,NOTE_F,NOTE_G,NOTE_A,NOTE_G,NOTE_F,NOTE_D,NOTE_F,NOTE_A,NOTE_MAX,NOTE_MAX,NOTE_D,NOTE_E,
  NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_F,NOTE_F,NOTE_MAX,NOTE_MAX,NOTE_D,NOTE_E,
  NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_F,NOTE_F,NOTE_MAX,NOTE_MAX,NOTE_D,NOTE_E,
  NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_F,NOTE_F,NOTE_MAX,NOTE_MAX,NOTE_D,NOTE_D,NOTE_E,
  NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_F,NOTE_F,NOTE_MAX,NOTE_MAX,NOTE_D,NOTE_E,
  NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_F,NOTE_F,NOTE_MAX,NOTE_MAX,NOTE_D,NOTE_E,
  NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_D,NOTE_E,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_MAX,NOTE_G,NOTE_G,NOTE_G,NOTE_G,NOTE_F,NOTE_F,NOTE_MAX,NOTE_MAX,NOTE_D,NOTE_F,
  NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_E,NOTE_F,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_E,NOTE_F,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_A,NOTE_G,NOTE_G,NOTE_MAX,NOTE_MAX,NOTE_E,NOTE_F,
  NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_E,NOTE_F,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_E,NOTE_F,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_A,NOTE_G,NOTE_MAX,NOTE_MAX,NOTE_MAX,NOTE_E,NOTE_F,
  NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_E,NOTE_F,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_E,NOTE_F,NOTE_A,NOTE_MAX,NOTE_A,NOTE_A,NOTE_B,NOTE_MAX,NOTE_B,NOTE_B,NOTE_B,NOTE_B,NOTE_A,NOTE_E,NOTE_F,NOTE_MAX,NOTE_E,NOTE_F,NOTE_A,NOTE_MAX,NOTE_MAX,
  };
 
  const uint8_t Moct[] PROGMEM= {
  3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,
  4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,4,
  4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,4,4,0,4,4,4,4,4,4,4,0,4,4,4,4,4,
  0,
  };
 
  const uint8_t localKey[] PROGMEM= {
  0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,
  0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,3,0,
  2,0,2,2,2,0,2,2,2,0,2,0,2,2,2,0,2,2,2,0,2,0,2,2,2,0,2,2,2,2,0,0,0,0,2,0,
  2,0,2,2,2,0,2,2,2,0,2,0,2,2,2,0,2,2,2,0,2,0,2,2,2,0,2,2,2,2,0,0,0,0,2,0,
  2,0,2,2,2,0,2,2,2,0,2,0,2,2,2,0,2,2,2,0,2,0,2,2,2,0,2,2,2,2,2,2,0,0,2,0,2,0,0,
  0,
  };
 
  const uint8_t Mdur[] PROGMEM= {
  16,
  8,8,8,8,4,4,4,4,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,4,4,4,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,4,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,4,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,2,2,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,4,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,4,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,4,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,4,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,4,4,2,2,
  2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,2,2,2,4,
  };
  // ----------------------------------------------------------------------
 
  uint8_t noteNum = 0;
 
  note_t keySignature(note_t note) {
    if (pgm_read_byte(&localKey[noteNum]) == 0) {
      if (KeySign == 1) {
        if (note == NOTE_B) note = NOTE_Bb;
      } else if (KeySign == 2) {
        if (note == NOTE_B) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_Eb;
      } else if (KeySign == 3) {
        if (note == NOTE_B) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Gs;
      } else if (KeySign == 4) {
        if (note == NOTE_B) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Cs;
      } else if (KeySign == 5) {
        if (note == NOTE_B) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Fs;
      } else if (KeySign == 6) {
        if (note == NOTE_B) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Fs;
        else if (note == NOTE_C) { note = NOTE_B; octaveTemp -= 1; }
      } else if (KeySign == 7) {
        if (note == NOTE_B) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Fs;
        else if (note == NOTE_C) { note = NOTE_B; octaveTemp -= 1; }
        else if (note == NOTE_F) note = NOTE_E;
      } else if (KeySign == 8) {
        if (note == NOTE_F) note = NOTE_Fs;
      } else if (KeySign == 9) {
        if (note == NOTE_F) note = NOTE_Fs;
        else if (note == NOTE_C) note = NOTE_Cs;
      } else if (KeySign == 10) {
        if (note == NOTE_F) note = NOTE_Fs;
        else if (note == NOTE_C) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Gs;
      } else if (KeySign == 11) {
        if (note == NOTE_F) note = NOTE_Fs;
        else if (note == NOTE_C) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Eb;
      } else if (KeySign == 12) {
        if (note == NOTE_F) note = NOTE_Fs;
        else if (note == NOTE_C) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Bb;
      } else if (KeySign == 13) {
        if (note == NOTE_F) note = NOTE_Fs;
        else if (note == NOTE_C) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_F;
      } else if (KeySign == 14) {
        if (note == NOTE_F) note = NOTE_Fs;
        else if (note == NOTE_C) note = NOTE_Cs;
        else if (note == NOTE_G) note = NOTE_Gs;
        else if (note == NOTE_D) note = NOTE_Eb;
        else if (note == NOTE_A) note = NOTE_Bb;
        else if (note == NOTE_E) note = NOTE_F;
        else if (note == NOTE_B) { note = NOTE_C; octaveTemp += 1; }
      }
    }
    else { // 1. natural
      if (pgm_read_byte(&localKey[noteNum]) == 2) { // 2. b
        if (note == NOTE_C) { note = NOTE_B; octaveTemp -= 1; }
        else if (note == NOTE_D) note = NOTE_Cs;
        else if (note == NOTE_E) note = NOTE_Eb;
        else if (note == NOTE_F) note = NOTE_E;
        else if (note == NOTE_G) note = NOTE_Fs;
        else if (note == NOTE_A) note = NOTE_Gs;
        else if (note == NOTE_B) note = NOTE_Bb;
      }
      else if (pgm_read_byte(&localKey[noteNum]) == 3) { // 2. #
        if (note == NOTE_C) note = NOTE_Cs;
        else if (note == NOTE_D) note = NOTE_Eb;
        else if (note == NOTE_E) note = NOTE_F;
        else if (note == NOTE_F) note = NOTE_Fs;
        else if (note == NOTE_G) note = NOTE_Gs;
        else if (note == NOTE_A) note = NOTE_Bb;
        else if (note == NOTE_B) { note = NOTE_C; octaveTemp += 1; }
      }
      else if (pgm_read_byte(&localKey[noteNum]) == 4) { // 4. bb
        if (note == NOTE_C) { note = NOTE_Bb; octaveTemp -= 1; }
        else if (note == NOTE_D) note = NOTE_C;
        else if (note == NOTE_E) note = NOTE_D;
        else if (note == NOTE_F) note = NOTE_Eb;
        else if (note == NOTE_G) note = NOTE_F;
        else if (note == NOTE_A) note = NOTE_G;
        else if (note == NOTE_B) note = NOTE_A;
      }
      else if (pgm_read_byte(&localKey[noteNum]) == 5) { // 5. ##
        if (note == NOTE_C) note = NOTE_D;
        else if (note == NOTE_D) note = NOTE_E;
        else if (note == NOTE_E) note = NOTE_Fs;
        else if (note == NOTE_F) note = NOTE_G;
        else if (note == NOTE_G) note = NOTE_A;
        else if (note == NOTE_A) note = NOTE_B;
        else if (note == NOTE_B) { note = NOTE_Cs; octaveTemp += 1; }
      }
    }
    return note;
  }
  // ----------------------------------------------------------------------
 
  int beepPin = 3;
  bool beepMelody = false;
  unsigned long int beepTime = 0;

//==========================================================================================================
void setup() { // 초기화

  //Serial.begin(9600);//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 
  //lcd 초기화
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0,0);

  Rtc.Begin();
  //Rtc.GetDateTime();
  //RTC 모듈 최초 실행 시 현재 시간을 세팅해 주어야 함. 이후에는 아래 line 주석 처리
  //RtcDateTime compiled = RtcDateTime(2024, 11, 23, 13, 27, 00);

  pinMode(BUTTON1, INPUT_PULLUP);
  pinMode(BUTTON2, INPUT_PULLUP);

  if (Rtc.GetIsWriteProtected())
  { //KHS: RTC 모듈 쓰기 금지 확인 후 해제
      lcd.println("RTC protected!");
      Rtc.SetIsWriteProtected(false);
  }

  if (!Rtc.IsDateTimeValid())
  {
      // Common Causes:
      //    1) first time you ran and the device wasn't running yet
      //    2) the battery on the device is low or even missing

      lcd.println("Lost DateTime!");
      //Rtc.SetDateTime(compiled);
  }

  if (!Rtc.GetIsRunning())
  { //KHS: RTC 모듈이 동작중이 아니라면 구동
      lcd.println("RTC activated!");
      Rtc.SetIsRunning(true);
  }

  pinMode(M_IN1, OUTPUT); // Digital4번핀 출력 핀 설정
  pinMode(M_IN2, OUTPUT); // Digital5번핀 출력 핀 설정  
  pinMode(M_IN3, OUTPUT); // Digital13번핀 출력 핀 설정
  pinMode(M_IN4, OUTPUT); // Digital12번핀 출력 핀 설정

  clThread.onRun(currentTime);
  clThread.setInterval(1000);
}
 
void loop() { // 무한루프

  //if(myThread.shouldRun())
  clThread.run();

  buttonState1 = digitalRead(BUTTON1);

  if(buttonState1 == HIGH)  //
  {
    if(state1 == 0)
    {
      state1 = 1;     
    }
  }
  else if(buttonState1 == LOW)  //스위치 누름 상태
  {
    if(state1 == 1)
    {
      if(beepMelody == 1)
      {
        if(alm_H == 0)alm_H = 23;
        else alm_H -= 1;
        stopAlarm();
      }
      else
      {      
        alm_H += 1;
        if(alm_H == 24)
        {
          alm_H = 0;        
        }  
        b_almState = 1;          
      }    
      alm = RtcDateTime(2024, 01, 01, alm_H, alm_M, alm_S);
      state1 = 0;  
      delay(100);   
    }    
  }

  buttonState2 = digitalRead(BUTTON2);
  if(buttonState2 == HIGH)
  {
    if(state2 == 0)
    {
      state2 = 1;         
    }
  }
  else if(buttonState2 == LOW)
  {
    if(state2 == 1)
    {      
      if(beepMelody == 1)
      {
        if(alm_H == 0)alm_H = 23;
        else alm_H -= 1;
        stopAlarm();      
      }
      else
      {
        alm_M += 5;
        if(alm_M == 60)
        {
          alm_H += 1;
          alm_M = 0;        
        }   
        b_almState = 1;              
      }
                 
      alm = RtcDateTime(2024, 01, 01, alm_H, alm_M, alm_S);
      state2 = 0;
      delay(100);
    }    
  }  

  if(state1 == 0 && state2 == 0)b_almState = 0; 

  if(b_almState == 0)lcdprintDateTime(2,alm);
  else lcdprintDateTime(1,alm);

  RtcDateTime ctm = Rtc.GetDateTime();
  int Delt_H = abs(ctm.Hour() - alm.Hour());
  int Delt_M = abs(ctm.Minute() - alm.Minute());
  int Delt_S = abs(ctm.Second() - alm.Second());

  if(Delt_H == 0 && Delt_M == 0 && Delt_S < 59)
  {
          
    beepMelody = 1;
    if (millis() - beepTime >= pgm_read_byte(&Mdur[noteNum])*(1000/(4*tempoRatio)))
    {
      beepTime = millis();
      noTone(beepPin);
      octaveTemp = pgm_read_byte(&Moct[noteNum]);
      note_t KeyNote = keySignature(pgm_read_word(&Mnote[noteNum]));
      tone(beepPin, ledcWriteNote(KeyNote, octaveTemp));  // ledcWriteNote(uint8_t channel, note_t note, uint8_t octaveTemp);
      noteNum++;
      if (noteNum == sizeof(Mdur))
      { // 초기화
        noTone(beepPin);  
        noteNum = 0;
        beepMelody = false;
      }
    }  
     
    if(millis() - millTime >= 2500)
    {
      Flag_FWDBWD = random(0,2);
      Flag_DIRECT = random(0,3);     
      millTime = millis();      
    }

    moveVehicle();

  }

  if(!beepMelody)stopAlarm();

}

void currentTime()
{
  RtcDateTime now = Rtc.GetDateTime();
  lcdprintDateTime(0,now);
  if (!now.IsValid())
  {
      // Common Causes:
      //    1) the battery on the device is low or even missing and the power line was disconnected
      lcd.println("Lost DateTime!");
  }  
}

void lcdprintDateTime(int iPos, const RtcDateTime &dt)
{
  char timeString[20] = {0,};

  if(iPos == 0)
  {
    snprintf_P(timeString,countof(timeString),PSTR("%02u/%02u %02u:%02u:%02u")
    ,dt.Month(),dt.Day(),dt.Hour(),dt.Minute(),dt.Second());
    lcd.setCursor(0,0);
    lcd.print(timeString);  
  }
  else if(iPos == 1)
  {
    snprintf_P(timeString,countof(timeString),PSTR("ALARM %02u:%02u:%02u")
    ,dt.Hour(),dt.Minute(),dt.Second());
    lcd.setCursor(0,1);
    lcd.print(timeString);
  }
  else 
  {
    snprintf_P(timeString,countof(timeString),PSTR("NO ALARM        "));    
    lcd.setCursor(0,1);
    lcd.print(timeString);
  }
}

void moveVehicle()
{
  if(Flag_FWDBWD == 0)
  {
    if(millis() - millTime < 500)
    {
      if(Flag_DIRECT == 0)  //직진
      {
        // 모터 A 정회전
        digitalWrite(M_IN1,1); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
        digitalWrite(M_IN2,0); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
        // 모터 B 정회전
        digitalWrite(M_IN3,0); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
        digitalWrite(M_IN4,1); // IN4번에 LOW(motorB_vector가 0이면 HIGH)
      }    
      else if(Flag_DIRECT == 1)  //죄회전
      {
        // 모터 A 역회전
        digitalWrite(M_IN1,0); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
        digitalWrite(M_IN2,1); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
        // 모터 B 정회전
        digitalWrite(M_IN3,0); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
        digitalWrite(M_IN4,1); // IN4번에 LOW(motorB_vector가 0이면 HIGH) 
      }
      else if(Flag_DIRECT == 2)  //우회전
      {
        // 모터 A 정회전
        digitalWrite(M_IN1,1); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
        digitalWrite(M_IN2,0); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
        // 모터 B 역회전
        digitalWrite(M_IN3,1); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
        digitalWrite(M_IN4,0); // IN4번에 LOW(motorB_vector가 0이면 HIGH)   
      }
    }
    else
    {
      // 모터 A 정회전
      digitalWrite(M_IN1,1); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
      digitalWrite(M_IN2,0); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
      // 모터 B 정회전
      digitalWrite(M_IN3,0); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
      digitalWrite(M_IN4,1); // IN4번에 LOW(motorB_vector가 0이면 HIGH)  
    }
  }
  else
  {
    if(millis() - millTime < 500)
    {
      if(Flag_DIRECT == 0)  //직진
      {
        // 모터 A 역회전
        digitalWrite(M_IN1,0); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
        digitalWrite(M_IN2,1); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
        // 모터 B 역회전
        digitalWrite(M_IN3,1); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
        digitalWrite(M_IN4,0); // IN4번에 LOW(motorB_vector가 0이면 HIGH) 
      } 
      else if(Flag_DIRECT == 1)  //죄회전
      {
        // 모터 A 정회전
        digitalWrite(M_IN1,1); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
        digitalWrite(M_IN2,0); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
        // 모터 B 역회전
        digitalWrite(M_IN3,1); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
        digitalWrite(M_IN4,0); // IN4번에 LOW(motorB_vector가 0이면 HIGH)  
      }
      else if(Flag_DIRECT == 2)  //우회전
      {
        // 모터 A 역회전
        digitalWrite(M_IN1,0); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
        digitalWrite(M_IN2,1); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
        // 모터 B 정회전
        digitalWrite(M_IN3,0); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
        digitalWrite(M_IN4,1); // IN4번에 LOW(motorB_vector가 0이면 HIGH)   
      }
    }
    else
    {
      // 모터 A 역회전
      digitalWrite(M_IN1,0); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
      digitalWrite(M_IN2,1); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
      // 모터 B 역회전
      digitalWrite(M_IN3,1); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
      digitalWrite(M_IN4,0); // IN4번에 LOW(motorB_vector가 0이면 HIGH)  
    } 
  } 
}

void stopAlarm()
{
  beepMelody = 0;
  noTone(beepPin); 

  // 모터 A 정지
  digitalWrite(M_IN1,0); // IN1번에 HIGH(motorA_vector가 0이면 LOW)
  digitalWrite(M_IN2,0); // IN2번에 LOW(motorA_vector가 0이면 HIGH)
  // 모터 B 정지
  digitalWrite(M_IN3,0); // IN3번에 HIGH(motorB_vector가 0이면 LOW)
  digitalWrite(M_IN4,0); // IN4번에 LOW(motorB_vector가 0이면 HIGH)  
}

트러블 슈팅 과정

업데이트 예정