이전 글 "아두이노 말하는 알람시계 예제 - DFPlayer"에서는 말하는 알람시계의 시간 및 날짜 설정을 시리얼 모니터를 통해 서 해주었다. 하지만 외부 전원을 사용하고 컴퓨터와 연결이 안 된 상태라면 설정을 해줄 수가 없게 되는데, 아두이노에 블루투스를 연결하고 안드로이드 앱을 통해 시간 및 날짜를 설정하고 DFPlayer의 사운드 파일도 제어해 보자. 

 

우선 앞선 글에서 미처 발견하지 못했던 사운드 파일 재생 중 시간 출력 시 간헐적으로 오전/오후 음성을 건너뛰고 재생하는 오류를 수정하고 반영한 스케치를 기준으로 진행하겠다.

arduino_clock_dfplayer_millis_option_final_revised.ino
0.02MB

더보기
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display

#include <SoftwareSerial.h> 
#define DF_rxPin 2 // DFplayer RX -> arduino 2
#define DF_txPin 3 // DFplayer TX -> arduino 3 
SoftwareSerial dfSerial(DF_txPin, DF_rxPin); // (RX, TX)

// DFplayer command 변수
uint8_t df_command[] = {0x7E, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0xEF};

void send_command(uint8_t cmd, uint8_t ack, uint8_t msb, uint8_t lsb) {
  df_command[3] = cmd;
  df_command[4] = ack;
  df_command[5] = msb;
  df_command[6] = lsb;
  for(int i = 0; i < sizeof(df_command); i++){
    dfSerial.write(df_command[i]);
  }
}

int h = 12; // initial Time display is 12:59:45 PM
int m = 59;
uint8_t s = 45;
bool meridian = true;
uint8_t hm;
bool now_am = true; //AM
uint16_t yy = 2019;
uint8_t mm = 1;
uint8_t dd = 1;
uint8_t week_num = 1;
String week_day = "SUN";
bool adjust = false;

bool display_t = false;
unsigned long int start_time = 0;
uint8_t volume = 20;

bool receive_status = false; // 질의 코드 0x42수신 여부 플래그
uint8_t status_count = 0;   // 질의값 저장 인덱스용 카운터
uint8_t status_val[7];         // 질의값 저장 배열
bool played = false;         // 질의결과 플래그 (재생중인지 아닌지)
bool status_check = false; // 질의 수신 완료 플래그

unsigned long int count_time = 0;
bool delay_set = false;
uint8_t sequence = 0;

bool speak = false; // 현재 상태 질의 시작 및 음성출력 시작 플래그
bool short_sentence = false;
bool sharp = false;

int am;
int time_sharp;
int hour_a;
int min_w;
int min_s;

uint8_t alarm_track_folder = 2;
uint8_t alarm_track = 2;

bool alarm_on = false;
int alarm_h;
int alarm_m;
unsigned long int alarm_delay = 0;
bool alarm_sentence = false;
uint8_t alarm_count = 0;
bool alarm_time = false;

unsigned long int alarm_sequence_delay = 0;
uint8_t alarm_sequence = 0;
bool alarm_send_f = false;
bool alarm_stop = false; 

bool no_speak_sharp = false;
uint8_t no_speak_s = 0;
uint8_t no_speak_f = 0;

uint8_t time_volume = 30; // 음성출력 및 알람 볼륨

uint8_t pin = 7;

void setup() {
  Serial.begin(9600);
  dfSerial.begin(9600); // DFplayer Serial
  pinMode(pin, INPUT_PULLUP);  // pullup 저항 이용 - 버튼쪽에 저항 없음 OFF - HIHG, ON - LOW
  send_command(0x06, 0, 0, volume); // 볼륨 
  lcd.begin();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0,0);  // row, col의 좌표로 커서를 위치 col: 0 ~ 15 row: 0, 1
  lcd.print("Arduino clock");
}

void loop() {
  cal_time();
  adjust_time();
  speak_time();
  delayCount();
  alarm_warning();
  alarm_send();
  if (dfSerial.available()) {   // 42 02 01
    uint8_t temp = dfSerial.read();
    if (temp == 0x42) receive_status = true; 
    if (receive_status == true) {
      status_val[status_count] = temp;
      status_count++; 
      if (temp == 0xEF) {
        status_check = true;
        receive_status = false;  
        status_count = 0; 
        if (status_val[2] == 2 && status_val[3] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
        else if (status_val[2] == 2 && status_val[3] == 0) played = false; // 인덱스 3 = 0 정지중
      }
    }
  }
  if (digitalRead(pin) == LOW) { // LOW - ON
    if (alarm_time == true) {
      alarm_stop = true;
      alarm_time = false;
      alarm_sentence = false;
      alarm_count = 0;
      alarm_send_f = true;
    }
    else {
      speak_option();
    }
  }
}

void adjust_time() {
    if (Serial.available() > 0) {
    String temp = Serial.readStringUntil('\n');
    if (temp == "dtime") display_time();
    else if (temp == "s") { s = 0; start_time = millis(); display_t = true; }
    else if (temp == "mm") { m++; display_t = true; }
    else if (temp == "m") { m--; display_t = true; }
    else if (temp == "hh") { h++; display_t = true; }
    else if (temp == "h") { h--; display_t = true; }
    else if (temp == "24") { meridian = !meridian; display_t = true; }
    else if (temp == "yy") { yy++; display_t = true; }
    else if (temp == "y") { yy--; display_t = true; }
    else if (temp == "mon") { mm++; adjust = true; display_t = true; }
    else if (temp == "mon-") { mm--; adjust = true; display_t = true; }
    else if (temp == "dd") { dd++; adjust = true; display_t = true; }
    else if (temp == "d") { dd--; adjust = true; display_t = true; }
    else if (temp == "ww") { week_num++; adjust = true; display_t = true; }
    else if (temp == "w") { week_num--; adjust = true; display_t = true; }
    else if (temp == "nsp") no_speak_sharp = !no_speak_sharp;
    else if (temp.startsWith("tset")) {
      String set = temp.substring(4, 6);
      h = set.toInt();
      set = temp.substring(6, 8);
      m = set.toInt();
      set = temp.substring(8, 10);
      s = set.toInt();
      start_time = millis();
      adjust = true;
      display_t = true;
    }
    else if (temp.startsWith("dset")) {
      String set = temp.substring(4, 8);
      yy = set.toInt();
      set = temp.substring(8, 10);
      mm = set.toInt();
      set = temp.substring(10, 12);
      dd = set.toInt();
      adjust = true;
      display_t = true;
    }
    else if (temp == "alarm") { 
      alarm_on = !alarm_on;
      if (alarm_on) Serial.println("alarm on");
      else Serial.println("alarm off");
    }
    else if (temp.startsWith("aset")) {
      String set = temp.substring(4, 6);
      alarm_h = set.toInt();
      set = temp.substring(6, 8);
      alarm_m = set.toInt();
      Serial.print("alarm set: "); Serial.print(alarm_h); Serial.print(":"); Serial.println(alarm_m);
    }
    else if (temp.startsWith("setnsp")) {
      String set = temp.substring(6, 8);
      no_speak_s = set.toInt();
      set = temp.substring(8, 10);
      no_speak_f = set.toInt();
      Serial.print("no speak set: "); Serial.print(no_speak_s); Serial.print("~"); Serial.println(no_speak_f);
    }
    else if (temp == "test") speak_option();
    else if (temp == "stop") { 
      alarm_stop = true;
      alarm_time = false;
      alarm_sentence = false;
      alarm_count = 0;
      alarm_send_f = true;
    }
    else if (temp == "play") send_command(0x0F, 0, 2, 6);
    else if (temp == "pause") send_command(0x0E, 1, 0, 0);
    else if (temp == "previous") send_command(0x02, 1, 0, 0);
    else if (temp == "next") send_command(0x01, 1, 0, 0);
    else if (temp == "vv") { if(volume < 30) volume++; send_command(0x06, 0, 0, volume); }
    else if (temp == "v") { if(volume > 0) volume--; send_command(0x06, 0, 0, volume); }
    else if (temp == "reset") send_command(0x0C, 0, 0, 0);
    if (m >= 60) m = 0;
    else if (m < 0) m = 59;
    if (h >= 24) h = 0; 
    else if (h < 0) h = 23; 
    if (h < 12) now_am = true;
    else now_am = false; 
    hm = h; 
    if(meridian == true && hm == 12) { now_am = false; }
    else if (meridian == true && hm > 12) { hm = hm - 12; }
    cal_dd();
    if (display_t == true) { display_time(); display_t = false; }
  }
}

bool speak_finished = true;

void speak_option() {
  if (m == 0 && s != 0) {
    sharp = true;
    if (h < 13) time_sharp = 2200 + h;
    else time_sharp = 2200 + (h - 12);
  }
  else {
    sharp = false;
    if (h < 13) hour_a = 1000 + h;
    else hour_a = 1000 + (h - 12);
    if (h < 12) am = 1301;
    else am = 1302;
    int temp = m%10;
    if (temp == 0 || m < 10) {
      short_sentence = true;
      min_s = 1200 + m;
    }
    else {
      short_sentence = false;
      min_w = 1100 + (m - temp);
      min_s = 1200 + temp;
    }
  }
  speak = true;
  speak_finished = false;
}

uint8_t am_s = 4;
uint8_t vol_s = 6;
uint8_t hour_a_s = 21;
uint8_t long_min_w_s = 44;
uint8_t long_min_s_s = 65;
uint8_t long_min_s_end = 94;
uint8_t short_min_s_s = 44;
uint8_t short_min_s_end = 74;
uint8_t time_sharp_s = 4;
uint8_t time_sharp_end = 64;

void speak_time() {
  if (speak == true) {
    if (sequence == 0 && status_check == false) {
      send_command(0x42, 0, 0, 0);
      sequence++;
    }
    else if (sequence == 1 && status_check == true) {
      if (played == false) {
        send_command(0x0F, 0, 2, 255);
        sequence++; // 2
        delay_set = true;
      }
      else {
        delay_set = true;
        sequence = 3;
      }
    }
    else if (sequence >= am_s && sharp == false) {
      if (sequence == am_s) { advert(am); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == hour_a_s) { advert(hour_a); speak = false; }
      if (short_sentence == false) {
        if (sequence == long_min_w_s) { advert(min_w); speak = false; }
        else if (sequence == long_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == long_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
        }
      }
      else {
        if (sequence == short_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
        else if (sequence == short_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
        }
      }
    }
    else if (sequence >= time_sharp_s && sharp == true) {
      if (sequence == time_sharp_s) { advert(time_sharp); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == time_sharp_end) {
        send_command(0x06, 0, 0, volume);
        speak_finished = true;
        speak = false;
        status_check = false;
        sequence = 0;
        time_sharp_end = 64;
      }
    }
  }
}

void advert(int ad_num) {
  uint8_t ad_lsb = ad_num;
  uint8_t ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
}

void delayCount() {
  if (delay_set == true) {
    if (millis() - count_time >= 50) { // 시간 간격: 밀리초
      count_time = millis();
      sequence++;
      if (sharp == false) { 
        if (sequence == am_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == hour_a_s) speak = true;
        if (short_sentence == false) {
          if (sequence == long_min_w_s) speak = true;
          else if (sequence == long_min_s_s) speak = true;
          else if (sequence == long_min_s_end) { speak = true; delay_set = false; }
        }
        else {
          if (sequence == short_min_s_s) speak = true;
          else if (sequence == short_min_s_end) { speak = true; delay_set = false; }
        }
      }
      else {
        if (sequence == time_sharp_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == time_sharp_end)  { speak = true; delay_set = false; }
      }
    }
  }
}

bool change_millis = false; 
uint16_t millis_sec = 994;

void cal_time() {
  if (millis() - start_time >= millis_sec) { // 시간 간격: 밀리초
    start_time = millis(); // 상기 조건을 만족할때의 밀리초를 다시 start_time에 저장하여 조건 초기화
    change_millis = ! change_millis;
    if (change_millis == true)  millis_sec = 995;
    else millis_sec = 994;
    s++;
    if (s == 60) {
      s = 0;
      m++;
    }
    if(m == 60) { m = 0; h++; }
    if(h == 12) { now_am = false;}
    else if (h == 24) { h = 0; now_am = true; dd++; week_num++; cal_dd(); }
    hm = h;
    if(meridian == true && hm == 12) { now_am = false; }
    else if (meridian == true && hm > 12) { hm = hm - 12; }
    else now_am = true;
    display_time();
    if (no_speak_sharp == true) {
      if (no_speak_s == no_speak_f) no_speak_sharp = false;
    }
    if (alarm_on) {
      if (alarm_h == h && alarm_m == m && s == 0){
        alarm_time = true;
        alarm_send_f = true;
      }
    }
    if (alarm_time != true) {
      if (m == 0 && s == 0) {
        if (no_speak_sharp == false) {
          speak_sharp();
        }
        else {
          if (no_speak_s > no_speak_f) {
            if (h < no_speak_s && h > no_speak_f) {
              speak_sharp();
            }
          }
          else if (no_speak_s < no_speak_f) {
            if (h > no_speak_s && h < no_speak_f) {
              speak_sharp();
            }
          }
        }
      }
    }
  }
}

void speak_sharp() {
  if (speak_finished == true) {
    sharp = true;
    if (h < 13) time_sharp = 2100 + h;
    else time_sharp = 2100 + (h - 12);
    time_sharp_end = 48;
    speak = true;   
  }
}

void alarm_send() {
  if (alarm_send_f == true) {
    if (millis() - alarm_sequence_delay >= 50) { // 시간 간격: 밀리초
      alarm_sequence_delay = millis();
      alarm_sequence++;
      if (alarm_stop == true) {
        if (alarm_sequence == 1) send_command(0x06, 0, 0, volume);
        else if (alarm_sequence == 2) send_command(0x16, 0, 0, 0); 
        else if (alarm_sequence == 3) {
          send_command(0x15, 0, 0, 0); 
          alarm_send_f = false;
          alarm_sequence = 0;
          alarm_stop = false;
        }
      }
      else {
        if (alarm_sequence == 1) send_command(0x16, 0, 0, 0);
        else if (alarm_sequence == 2) send_command(0x06, 0, 0, 30); // 볼륨 30
        else if (alarm_sequence == 3) send_command(0x0F, 0, alarm_track_folder, alarm_track); // 폴더2 002번 사운드 파일
        else if (alarm_sequence == 8) {
          send_command(0x19, 0, 0, 0); // 반복재생
          alarm_sentence = true;
          alarm_count = 0;
          alarm_send_f = false;
          alarm_sequence = 0;
        }
      }
    }
  }
}

void alarm_warning() {
  if (alarm_sentence == true) {
    if (millis() - alarm_delay >= 1000) { // 시간 간격: 밀리초
      alarm_delay = millis();
      alarm_count++;
      if (alarm_count == 4) {
        advert(1401); 
      }
      else if (alarm_count == 12) {
        advert(1402);
      }
      else if (alarm_count == 25) {
        advert(1403);
      }
      else if (alarm_count == 70) {
        advert(1404);
        alarm_sentence = false;
        alarm_count = 0;
      }
    }
  }
}

void cal_dd() {
  uint8_t m_dds;
  switch (mm) {
    case 1:   m_dds = 31; break;
    case 2:   m_dds = 28; break;
    case 3:   m_dds = 31; break;
    case 4:   m_dds = 30; break;
    case 5:   m_dds = 31; break;
    case 6:   m_dds = 30; break;
    case 7:   m_dds = 31; break;
    case 8:   m_dds = 31; break;
    case 9:   m_dds = 30; break;
    case 10:  m_dds = 31; break;
    case 11:  m_dds = 30; break;
    case 12:  m_dds = 31; break;
  }
  if (adjust == false) {
    if (dd > m_dds) { dd = 1; mm++; }
    if (mm > 12) { mm = 1; yy++; }
    if (week_num > 7) week_num = 1;
    week_day_converter();
  }
  else {
    if (dd > m_dds) dd = 1;
    else if (dd < 1) dd = m_dds;
    if (mm > 12) mm = 1;
    else if (mm < 1) mm = 12;
    if (week_num > 7) week_num = 1;
    else if (week_num < 1) week_num = 7; 
    week_day_converter();
    adjust = false;
  }
}

void week_day_converter() {
  switch (week_num) {
    case 1:   week_day = "SUN"; break;
    case 2:   week_day = "MON"; break;
    case 3:   week_day = "TUE"; break;
    case 4:   week_day = "WED"; break;
    case 5:   week_day = "THU"; break;
    case 6:   week_day = "FRI"; break;
    case 7:   week_day = "SAT"; break;
  }
}

void display_time() {
  Serial.print(yy); Serial.print("."); 
  if (mm < 10) { Serial.print("0"); Serial.print(mm); Serial.print("."); }
  else { Serial.print(mm); Serial.print("."); }
  if (dd < 10) { Serial.print("0"); Serial.print(dd); Serial.print(". "); }
  else { Serial.print(dd); Serial.print(". "); }
  Serial.print(week_day); Serial.print(" ");  
  if (meridian == true) {
    if (now_am == true) Serial.print("AM ");
    else Serial.print("PM ");
    if (hm < 10) { Serial.print("0"); Serial.print(hm); Serial.print(":"); }
    else { Serial.print(hm); Serial.print(":"); }
  }
  else {
    if (h < 10) { Serial.print("0"); Serial.print(h); Serial.print(":"); }
    else { Serial.print(h); Serial.print(":"); }
  }
  if (m < 10) { Serial.print("0"); Serial.print(m); Serial.print(":"); }
  else { Serial.print(m); Serial.print(":"); }
  if (s < 10) { Serial.print("0"); Serial.println(s); }
  else Serial.println(s);
  lcd.setCursor(0,0); 
  lcd.print(yy); lcd.print("."); 
  if (mm < 10) { lcd.print("0"); lcd.print(mm); lcd.print("."); }
  else { lcd.print(mm); lcd.print("."); }
  if (dd < 10) { lcd.print("0"); lcd.print(dd); lcd.print(". "); }
  else { lcd.print(dd); lcd.print(". "); }
  lcd.print(week_day); lcd.print(" ");  
  lcd.setCursor(0,1);
  if (meridian == true) {
    if (now_am == true) lcd.print("AM ");
    else lcd.print("PM ");
    if (hm < 10) { lcd.print("0"); lcd.print(hm); lcd.print(":"); }
    else { lcd.print(hm); lcd.print(":"); }
  }
  else {
    if (h < 10) { lcd.print("0"); lcd.print(h); lcd.print(":"); }
    else { lcd.print(h); lcd.print(":"); }
  }
  if (m < 10) { lcd.print("0"); lcd.print(m); lcd.print(":"); }
  else { lcd.print(m); lcd.print(":"); }
  if (s < 10) { lcd.print("0"); lcd.print(s); lcd.print("     "); }
  else { lcd.print(s); lcd.print("     "); }  // 크리스탈 LCD에는 pritln 명령어 없음
}

이전 글에서는 EEPROM을 이용하여 말하는 알람시계의 현재 설정을 저장하는 코드를 작성하지 않았었다. 따라서 전원을 껐다 켜거나 리셋을 시키면 초기화가 되어 알람 사용 유무나 24시 모드 등을 다시 설정해줘야만 했다. EEPROM 라이브러리를 이용하여 필요한 세팅값을 저장시키고 아두이노 리셋 또는 재기 동시 설정값을 불러와 다시 설정하지 않아도 되도록 해보자.

우선, 아래의 항목들을 EEPROM에 저장시키기로 정했다. 

1. 묵음 사용여부

2. 알람 ON/OFF

3. 24시 모드 사용 여부 (오전/오후)

4. 묵음 시작 시간

5. 묵음 종료 시간

6. 알람 시간의 시

7. 알람 시간의 분

8. 알람용 트랙의 폴더 번호

9. 알람용 트랙의 번호

10. 알람 볼륨 값

 

EEPROM 사용 방법은 라이브러리를 불러오고 전체 사이즈를(byte) 정해준 뒤 setup() 함수에서 한 번만 저장하고자 하는 변수의 초기값들을 EEPROM에 저장한 다음 loop() 함수에서 변숫값들이 변경될 때 이미 저장된 EEPROM 값에 덮어 씌우기를 하고 이후 기동시에는 setup() 함수에서 EEPROM에 저장된 값을 읽어서 해당 변수에 덮어 씌우기를 하면 loop() 함수에서는 이전에 저장된 함수의 세팅 상태로 작동을 하게 된다. 여기서 주의할 부분은 EEPROM에 변수의 초기값을 한 번만 저장시키도록 하는 방법이다. 이를 구현하기 위해 EEPROM 인덱스 0번을 한 번만 저장하기 위한 플래그로써 이용하게 된다. EEPROM은 저장공간이 1byte 기준이며, 각 인덱스 별 초기값은 0xFF이다. 따라서 아래 조건(특정 인덱스 값이 1이 아닌 경우)을 적용시킬 수 있게 된다. 초기값 0xFF 때문에 처음 실행 시 실행 코드 1로 진입하게 되고, 여기에서 EEPROM 특정 인덱스의 값을 1로 변경을 해주면 차후 기동시부터는 특정 인덱스의 값 1 때문에 실행 코드 1은 더 이상 실행되지 않고 실행 코드 2만 계속 실행 되게 된다.   

if(EEPROM.read(0) != 1) { 실행 코드 1 }
else ( 실행 코드 2 )

EEPROM의 쓰기와 읽기는 아래처럼 사용한다. 

EEPROM.write(인덱스 번호, 저장할 값);
EEPROM.read(인덱스 번호);

위에서 저장시킬 항목의 변수 타입은 bool 또는 uin8_t 로서 모두 1byte로 처리가 되므로 EEPROM에 필요한 저장 공간은 10byte가 되는데, 여기에 한 번만 저장시키기 위한 플래그를 포함시키면 전체 11byte EEPROM 저장공간이 필요하게 된다.  이에 따라 아래 코드를 작성해 주었다. 

 

 

#include <EEPROM.h>
#define EEPROM_SIZE 11 // 11 byte 

// setup() 함수 추가 코드
if(EEPROM.read(0) != 1) { 
  EEPROM.write(0, 1); // eeprom 저장 플래그 
  EEPROM.write(1, no_speak_sharp); // 묵음 플래그 
  EEPROM.write(2, alarm_on); // 알람 플래그 
  EEPROM.write(3, meridian); // 24 플래그 
  EEPROM.write(4, no_speak_s); // 묵음 시작 
  EEPROM.write(5, no_speak_f); // 묵음 종료 
  EEPROM.write(6, alarm_h); // 알람 시 
  EEPROM.write(7, alarm_m); // 알람 분 
  EEPROM.write(8, alarm_track_folder); // 알람 폴더 
  EEPROM.write(9, alarm_track); // 알람 트랙 
  EEPROM.write(10, time_volume); // 알람 볼륨 
}  
else { 
  no_speak_sharp = EEPROM.read(1); 
  alarm_on = EEPROM.read(2); 
  meridian = EEPROM.read(3); 
  no_speak_s = EEPROM.read(4); 
  no_speak_f = EEPROM.read(5); 
  alarm_h = EEPROM.read(6); 
  alarm_m = EEPROM.read(7); 
  alarm_track_folder = EEPROM.read(8); 
  alarm_track = EEPROM.read(9); 
  time_volume = EEPROM.read(10); 
}

또한 loop() 함수에서 각 변수 값이 변경되는 곳에서 EEPROM의 해당 인덱스에 그 값을 저장하는 코드도 추가해 주어야 한다. adjust_time() 함수의 해당 코드를 변경해 주었다. 

else if (temp == "24") { meridian = !meridian; EEPROM.write(3, meridian); display_t = true; }
else if (temp == "nsp") { no_speak_sharp = !no_speak_sharp; EEPROM.write(1, no_speak_sharp); }
else if (temp.startsWith("atrack")) { // 알람 트랙 번호 설정
  String set = temp.substring(6, temp.indexOf(","));
  alarm_track_folder = set.toInt();
  set = temp.substring(temp.indexOf(",") + 1, temp.length());
  alarm_track = set.toInt();
  EEPROM.write(8, alarm_track_folder); // 알람 폴더
  EEPROM.write(9, alarm_track); // 알람 트랙
  Serial.print("Folder: "); Serial.print(alarm_track_folder); Serial.print(" ");
  Serial.print("Track: "); Serial.println(alarm_track); 
}
else if (temp.startsWith("avol")) { // 알람 볼륨 설정
  temp.remove(0, 4);
  time_volume = temp.toInt();
  EEPROM.write(10, time_volume); // 알람 볼륨
  Serial.print("alarm volume: "); Serial.println(time_volume); 
}
else if (temp == "alarm") { // 알람 ON/OFF
  alarm_on = !alarm_on;
  EEPROM.write(2, alarm_on); // 알람 플래그
  if (alarm_on) Serial.println("alarm on");
  else Serial.println("alarm off");
}
else if (temp.startsWith("aset")) {  // 알람 시간 설정
  String set = temp.substring(4, 6);
  alarm_h = set.toInt();
  set = temp.substring(6, 8);
  alarm_m = set.toInt();
  EEPROM.write(6, alarm_h); // 알람 시
  EEPROM.write(7, alarm_m); // 알람 분
  Serial.print("alarm set: "); Serial.print(alarm_h); Serial.print(":"); Serial.println(alarm_m);
}
else if (temp.startsWith("setnsp")) { // 묵음 시간 설정
  String set = temp.substring(6, 8);
  no_speak_s = set.toInt();
  set = temp.substring(8, 10);
  no_speak_f = set.toInt();
  EEPROM.write(4, no_speak_s); // 묵음 시작
  EEPROM.write(5, no_speak_f); // 묵음 종료
  Serial.print("no speak set: "); Serial.print(no_speak_s); Serial.print("~"); Serial.println(no_speak_f);
}

텍스트 명령어 추가

- 알람 트랙 설정

"atrack폴더,파일넘버" -> "atrack2,2" -> 2번 폴더, 2번 파일

else if (temp.startsWith("atrack")) {
  String set = temp.substring(6, temp.indexOf(","));
  alarm_track_folder = set.toInt();
  set = temp.substring(temp.indexOf(",") + 1, temp.length());
  alarm_track = set.toInt();
  EEPROM.write(8, alarm_track_folder); // 알람 폴더
  EEPROM.write(9, alarm_track); // 알람 트랙
  Serial.print("Folder: "); Serial.print(alarm_track_folder); Serial.print(" ");
  Serial.print("Track: "); Serial.println(alarm_track); 
}

- 알람 볼륨 설정

"avol알람볼륨" -> "avol25" -> 알람 볼륨 25

else if (temp.startsWith("avol")) {
  temp.remove(0, 4);
  time_volume = temp.toInt();
  EEPROM.write(10, time_volume); // 알람 볼륨
  Serial.print("alarm volume: "); Serial.println(time_volume); 
}

- 묵음 시간 설정

"setnsp시작시간~종료시간" -> "setnsp23~6" -> 묵음 시작: 23시 ~ 묵음 종료: 6시

else if (temp.startsWith("setnsp")) {
  String set = temp.substring(6, temp.indexOf("~"));
  no_speak_s = set.toInt();
  set = temp.substring(temp.indexOf("~") + 1, temp.length());
  no_speak_f = set.toInt();
  EEPROM.write(4, no_speak_s); // 묵음 시작
  EEPROM.write(5, no_speak_f); // 묵음 종료
  Serial.print("no speak set: "); Serial.print(no_speak_s); Serial.print("-"); Serial.println(no_speak_f); 
}

arduino_clock_dfplayer_millis_option_final_revised_eeprom.ino
0.02MB

더보기
#include <EEPROM.h>
#define EEPROM_SIZE 11 // 11 byte

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display

#include <SoftwareSerial.h> 
#define DF_rxPin 2 // DFplayer RX -> arduino 2
#define DF_txPin 3 // DFplayer TX -> arduino 3 
SoftwareSerial dfSerial(DF_txPin, DF_rxPin); // (RX, TX)

// DFplayer command 변수
uint8_t df_command[] = {0x7E, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0xEF};

void send_command(uint8_t cmd, uint8_t ack, uint8_t msb, uint8_t lsb) {
  df_command[3] = cmd;
  df_command[4] = ack;
  df_command[5] = msb;
  df_command[6] = lsb;
  for(int i = 0; i < sizeof(df_command); i++){
    dfSerial.write(df_command[i]);
  }
}

int h = 12; // initial Time display is 12:59:45 PM
int m = 59;
uint8_t s = 45;
bool meridian = true;
uint8_t hm;
bool now_am = true; //AM
uint16_t yy = 2019;
uint8_t mm = 1;
uint8_t dd = 1;
uint8_t week_num = 1;
String week_day = "SUN";
bool adjust = false;

bool display_t = false;
unsigned long int start_time = 0;
uint8_t volume = 20;

bool receive_status = false; // 질의 코드 0x42수신 여부 플래그
uint8_t status_count = 0;   // 질의값 저장 인덱스용 카운터
uint8_t status_val[7];         // 질의값 저장 배열
bool played = false;         // 질의결과 플래그 (재생중인지 아닌지)
bool status_check = false; // 질의 수신 완료 플래그

unsigned long int count_time = 0;
bool delay_set = false;
uint8_t sequence = 0;

bool speak = false; // 현재 상태 질의 시작 및 음성출력 시작 플래그
bool short_sentence = false;
bool sharp = false;

int am;
int time_sharp;
int hour_a;
int min_w;
int min_s;

uint8_t alarm_track_folder = 2;
uint8_t alarm_track = 2;

bool alarm_on = false;
int alarm_h;
int alarm_m;
unsigned long int alarm_delay = 0;
bool alarm_sentence = false;
uint8_t alarm_count = 0;
bool alarm_time = false;

unsigned long int alarm_sequence_delay = 0;
uint8_t alarm_sequence = 0;
bool alarm_send_f = false;
bool alarm_stop = false; 

bool no_speak_sharp = false;
uint8_t no_speak_s = 0;
uint8_t no_speak_f = 0;

uint8_t time_volume = 30; // 음성출력 및 알람 볼륨

uint8_t pin = 7;

void setup() {
  if(EEPROM.read(0) != 1) {
    EEPROM.write(0, 1); // eeprom 저장 플래그
    EEPROM.write(1, no_speak_sharp); // 묵음 플래그
    EEPROM.write(2, alarm_on); // 알람 플래그
    EEPROM.write(3, meridian); // 24 플래그
    EEPROM.write(4, no_speak_s); // 묵음 시작
    EEPROM.write(5, no_speak_f); // 묵음 종료
    EEPROM.write(6, alarm_h); // 알람 시
    EEPROM.write(7, alarm_m); // 알람 분
    EEPROM.write(8, alarm_track_folder); // 알람 폴더
    EEPROM.write(9, alarm_track); // 알람 트랙
    EEPROM.write(10, time_volume); // 알람 볼륨
  } else {
    no_speak_sharp = EEPROM.read(1);
    alarm_on = EEPROM.read(2);
    meridian = EEPROM.read(3);
    no_speak_s = EEPROM.read(4);
    no_speak_f = EEPROM.read(5);
    alarm_h = EEPROM.read(6);
    alarm_m = EEPROM.read(7);
    alarm_track_folder = EEPROM.read(8);
    alarm_track = EEPROM.read(9);
    time_volume = EEPROM.read(10);
  }
  Serial.begin(9600);
  dfSerial.begin(9600); // DFplayer Serial
  pinMode(pin, INPUT_PULLUP);  // pullup 저항 이용 - 버튼쪽에 저항 없음 OFF - HIHG, ON - LOW
  send_command(0x06, 0, 0, volume); // 볼륨 
  lcd.begin();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0,0);  // row, col의 좌표로 커서를 위치 col: 0 ~ 15 row: 0, 1
  lcd.print("Arduino clock");
}

void loop() {
  cal_time();
  adjust_time();
  speak_time();
  delayCount();
  alarm_warning();
  alarm_send();
  if (dfSerial.available()) {   // 42 02 01
    uint8_t temp = dfSerial.read();
    if (temp == 0x42) receive_status = true; 
    if (receive_status == true) {
      status_val[status_count] = temp;
      status_count++; 
      if (temp == 0xEF) {
        status_check = true;
        receive_status = false;  
        status_count = 0; 
        if (status_val[2] == 2 && status_val[3] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
        else if (status_val[2] == 2 && status_val[3] == 0) played = false; // 인덱스 3 = 0 정지중
      }
    }
  }
  if (digitalRead(pin) == LOW) { // LOW - ON
    if (alarm_time == true) {
      alarm_stop = true;
      alarm_time = false;
      alarm_sentence = false;
      alarm_count = 0;
      alarm_send_f = true;
    }
    else {
      speak_option();
    }
  }
}

void adjust_time() {
  if (Serial.available() > 0) {
    String temp = Serial.readStringUntil('\n');
    if (temp == "dtime") display_time();
    else if (temp == "s") { s = 0; start_time = millis(); display_t = true; }
    else if (temp == "mm") { m++; display_t = true; }
    else if (temp == "m") { m--; display_t = true; }
    else if (temp == "hh") { h++; display_t = true; }
    else if (temp == "h") { h--; display_t = true; }
    else if (temp == "24") { meridian = !meridian; EEPROM.write(3, meridian); display_t = true; }
    else if (temp == "yy") { yy++; display_t = true; }
    else if (temp == "y") { yy--; display_t = true; }
    else if (temp == "mon") { mm++; adjust = true; display_t = true; }
    else if (temp == "mon-") { mm--; adjust = true; display_t = true; }
    else if (temp == "dd") { dd++; adjust = true; display_t = true; }
    else if (temp == "d") { dd--; adjust = true; display_t = true; }
    else if (temp == "ww") { week_num++; adjust = true; display_t = true; }
    else if (temp == "w") { week_num--; adjust = true; display_t = true; }
    else if (temp == "nsp") { no_speak_sharp = !no_speak_sharp; EEPROM.write(1, no_speak_sharp); }
    else if (temp.startsWith("atrack")) {
      String set = temp.substring(6, temp.indexOf(","));
      alarm_track_folder = set.toInt();
      set = temp.substring(temp.indexOf(",") + 1, temp.length());
      alarm_track = set.toInt();
      EEPROM.write(8, alarm_track_folder); // 알람 폴더
      EEPROM.write(9, alarm_track); // 알람 트랙
      Serial.print("Folder: "); Serial.print(alarm_track_folder); Serial.print(" ");
      Serial.print("Track: "); Serial.println(alarm_track); 
    }
    else if (temp.startsWith("avol")) {
      temp.remove(0, 4);
      time_volume = temp.toInt();
      EEPROM.write(10, time_volume); // 알람 볼륨
      Serial.print("alarm volume: "); Serial.println(time_volume); 
    }
    else if (temp.startsWith("tset")) {
      String set = temp.substring(4, 6);
      h = set.toInt();
      set = temp.substring(6, 8);
      m = set.toInt();
      set = temp.substring(8, 10);
      s = set.toInt();
      start_time = millis();
      adjust = true;
      display_t = true;
    }
    else if (temp.startsWith("dset")) {
      String set = temp.substring(4, 8);
      yy = set.toInt();
      set = temp.substring(8, 10);
      mm = set.toInt();
      set = temp.substring(10, 12);
      dd = set.toInt();
      adjust = true;
      display_t = true;
    }
    else if (temp == "alarm") { 
      alarm_on = !alarm_on;
      EEPROM.write(2, alarm_on); // 알람 플래그
      if (alarm_on) Serial.println("alarm on");
      else Serial.println("alarm off");
    }
    else if (temp.startsWith("aset")) {
      String set = temp.substring(4, 6);
      alarm_h = set.toInt();
      set = temp.substring(6, 8);
      alarm_m = set.toInt();
      EEPROM.write(6, alarm_h); // 알람 시
      EEPROM.write(7, alarm_m); // 알람 분
      Serial.print("alarm set: "); Serial.print(alarm_h); Serial.print(":"); Serial.println(alarm_m);
    }
    else if (temp.startsWith("setnsp")) {
      String set = temp.substring(6, 8);
      no_speak_s = set.toInt();
      set = temp.substring(8, 10);
      no_speak_f = set.toInt();
      EEPROM.write(4, no_speak_s); // 묵음 시작
      EEPROM.write(5, no_speak_f); // 묵음 종료
      Serial.print("no speak set: "); Serial.print(no_speak_s); Serial.print("~"); Serial.println(no_speak_f);
    }
    else if (temp == "test") speak_option();
    else if (temp == "stop") { 
      alarm_stop = true;
      alarm_time = false;
      alarm_sentence = false;
      alarm_count = 0;
      alarm_send_f = true;
    }
    else if (temp == "play") send_command(0x0F, 0, 2, 6);
    else if (temp == "pause") send_command(0x0E, 1, 0, 0);
    else if (temp == "previous") send_command(0x02, 1, 0, 0);
    else if (temp == "next") send_command(0x01, 1, 0, 0);
    else if (temp == "vv") { if(volume < 30) volume++; send_command(0x06, 0, 0, volume); }
    else if (temp == "v") { if(volume > 0) volume--; send_command(0x06, 0, 0, volume); }
    else if (temp == "reset") send_command(0x0C, 0, 0, 0);
    if (m >= 60) m = 0;
    else if (m < 0) m = 59;
    if (h >= 24) h = 0; 
    else if (h < 0) h = 23; 
    if (h < 12) now_am = true;
    else now_am = false; 
    hm = h; 
    if(meridian == true && hm == 12) { now_am = false; }
    else if (meridian == true && hm > 12) { hm = hm - 12; }
    cal_dd();
    if (display_t == true) { display_time(); display_t = false; }
  }
}

bool speak_finished = true;

void speak_option() {
  if (m == 0 && s != 0) {
    sharp = true;
    if (h < 13) time_sharp = 2200 + h;
    else time_sharp = 2200 + (h - 12);
  }
  else {
    sharp = false;
    if (h < 13) hour_a = 1000 + h;
    else hour_a = 1000 + (h - 12);
    if (h < 12) am = 1301;
    else am = 1302;
    int temp = m%10;
    if (temp == 0 || m < 10) {
      short_sentence = true;
      min_s = 1200 + m;
    }
    else {
      short_sentence = false;
      min_w = 1100 + (m - temp);
      min_s = 1200 + temp;
    }
  }
  speak = true;
  speak_finished = false;
}

uint8_t am_s = 4;
uint8_t vol_s = 6;
uint8_t hour_a_s = 21;
uint8_t long_min_w_s = 44;
uint8_t long_min_s_s = 65;
uint8_t long_min_s_end = 94;
uint8_t short_min_s_s = 44;
uint8_t short_min_s_end = 74;
uint8_t time_sharp_s = 4;
uint8_t time_sharp_end = 64;

void speak_time() {
  if (speak == true) {
    if (sequence == 0 && status_check == false) {
      send_command(0x42, 0, 0, 0);
      sequence++;
    }
    else if (sequence == 1 && status_check == true) {
      if (played == false) {
        send_command(0x0F, 0, 2, 255);
        sequence++; // 2
        delay_set = true;
      }
      else {
        delay_set = true;
        sequence = 3;
      }
    }
    else if (sequence >= am_s && sharp == false) {
      if (sequence == am_s) { advert(am); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == hour_a_s) { advert(hour_a); speak = false; }
      if (short_sentence == false) {
        if (sequence == long_min_w_s) { advert(min_w); speak = false; }
        else if (sequence == long_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == long_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
        }
      }
      else {
        if (sequence == short_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
        else if (sequence == short_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
        }
      }
    }
    else if (sequence >= time_sharp_s && sharp == true) {
      if (sequence == time_sharp_s) { advert(time_sharp); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == time_sharp_end) {
        send_command(0x06, 0, 0, volume);
        speak_finished = true;
        speak = false;
        status_check = false;
        sequence = 0;
        time_sharp_end = 64;
      }
    }
  }
}

void advert(int ad_num) {
  uint8_t ad_lsb = ad_num;
  uint8_t ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
}

void delayCount() {
  if (delay_set == true) {
    if (millis() - count_time >= 50) { // 시간 간격: 밀리초
      count_time = millis();
      sequence++;
      if (sharp == false) { 
        if (sequence == am_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == hour_a_s) speak = true;
        if (short_sentence == false) {
          if (sequence == long_min_w_s) speak = true;
          else if (sequence == long_min_s_s) speak = true;
          else if (sequence == long_min_s_end) { speak = true; delay_set = false; }
        }
        else {
          if (sequence == short_min_s_s) speak = true;
          else if (sequence == short_min_s_end) { speak = true; delay_set = false; }
        }
      }
      else {
        if (sequence == time_sharp_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == time_sharp_end)  { speak = true; delay_set = false; }
      }
    }
  }
}

bool change_millis = false; 
uint16_t millis_sec = 994;

void cal_time() {
  if (millis() - start_time >= millis_sec) { // 시간 간격: 밀리초
    start_time = millis(); // 상기 조건을 만족할때의 밀리초를 다시 start_time에 저장하여 조건 초기화
    change_millis = ! change_millis;
    if (change_millis == true)  millis_sec = 995;
    else millis_sec = 994;
    s++;
    if (s == 60) {
      s = 0;
      m++;
    }
    if(m == 60) { m = 0; h++; }
    if(h == 12) { now_am = false;}
    else if (h == 24) { h = 0; now_am = true; dd++; week_num++; cal_dd(); }
    hm = h;
    if(meridian == true && hm == 12) { now_am = false; }
    else if (meridian == true && hm > 12) { hm = hm - 12; }
    else now_am = true;
    display_time();
    if (no_speak_sharp == true) {
      if (no_speak_s == no_speak_f) no_speak_sharp = false;
    }
    if (alarm_on) {
      if (alarm_h == h && alarm_m == m && s == 0){
        alarm_time = true;
        alarm_send_f = true;
      }
    }
    if (alarm_time != true) {
      if (m == 0 && s == 0) {
        if (no_speak_sharp == false) {
          speak_sharp();
        }
        else {
          if (no_speak_s > no_speak_f) {
            if (h < no_speak_s && h > no_speak_f) {
              speak_sharp();
            }
          }
          else if (no_speak_s < no_speak_f) {
            if (h > no_speak_s && h < no_speak_f) {
              speak_sharp();
            }
          }
        }
      }
    }
  }
}

void speak_sharp() {
  if (speak_finished == true) {
    sharp = true;
    if (h < 13) time_sharp = 2100 + h;
    else time_sharp = 2100 + (h - 12);
    time_sharp_end = 48;
    speak = true;   
  }
}

void alarm_send() {
  if (alarm_send_f == true) {
    if (millis() - alarm_sequence_delay >= 50) { // 시간 간격: 밀리초
      alarm_sequence_delay = millis();
      alarm_sequence++;
      if (alarm_stop == true) {
        if (alarm_sequence == 1) send_command(0x06, 0, 0, volume);
        else if (alarm_sequence == 2) send_command(0x16, 0, 0, 0); 
        else if (alarm_sequence == 3) {
          send_command(0x15, 0, 0, 0); 
          alarm_send_f = false;
          alarm_sequence = 0;
          alarm_stop = false;
        }
      }
      else {
        if (alarm_sequence == 1) send_command(0x16, 0, 0, 0);
        else if (alarm_sequence == 2) send_command(0x06, 0, 0, 30); // 볼륨 30
        else if (alarm_sequence == 3) send_command(0x0F, 0, alarm_track_folder, alarm_track); // 폴더2 002번 사운드 파일
        else if (alarm_sequence == 8) {
          send_command(0x19, 0, 0, 0); // 반복재생
          alarm_sentence = true;
          alarm_count = 0;
          alarm_send_f = false;
          alarm_sequence = 0;
        }
      }
    }
  }
}

void alarm_warning() {
  if (alarm_sentence == true) {
    if (millis() - alarm_delay >= 1000) { // 시간 간격: 밀리초
      alarm_delay = millis();
      alarm_count++;
      if (alarm_count == 4) {
        advert(1401); 
      }
      else if (alarm_count == 12) {
        advert(1402);
      }
      else if (alarm_count == 25) {
        advert(1403);
      }
      else if (alarm_count == 70) {
        advert(1404);
        alarm_sentence = false;
        alarm_count = 0;
      }
    }
  }
}

void cal_dd() {
  uint8_t m_dds;
  switch (mm) {
    case 1:   m_dds = 31; break;
    case 2:   m_dds = 28; break;
    case 3:   m_dds = 31; break;
    case 4:   m_dds = 30; break;
    case 5:   m_dds = 31; break;
    case 6:   m_dds = 30; break;
    case 7:   m_dds = 31; break;
    case 8:   m_dds = 31; break;
    case 9:   m_dds = 30; break;
    case 10:  m_dds = 31; break;
    case 11:  m_dds = 30; break;
    case 12:  m_dds = 31; break;
  }
  if (adjust == false) {
    if (dd > m_dds) { dd = 1; mm++; }
    if (mm > 12) { mm = 1; yy++; }
    if (week_num > 7) week_num = 1;
    week_day_converter();
  }
  else {
    if (dd > m_dds) dd = 1;
    else if (dd < 1) dd = m_dds;
    if (mm > 12) mm = 1;
    else if (mm < 1) mm = 12;
    if (week_num > 7) week_num = 1;
    else if (week_num < 1) week_num = 7; 
    week_day_converter();
    adjust = false;
  }
}

void week_day_converter() {
  switch (week_num) {
    case 1:   week_day = "SUN"; break;
    case 2:   week_day = "MON"; break;
    case 3:   week_day = "TUE"; break;
    case 4:   week_day = "WED"; break;
    case 5:   week_day = "THU"; break;
    case 6:   week_day = "FRI"; break;
    case 7:   week_day = "SAT"; break;
  }
}

void display_time() {
  Serial.print(yy); Serial.print("."); 
  if (mm < 10) { Serial.print("0"); Serial.print(mm); Serial.print("."); }
  else { Serial.print(mm); Serial.print("."); }
  if (dd < 10) { Serial.print("0"); Serial.print(dd); Serial.print(". "); }
  else { Serial.print(dd); Serial.print(". "); }
  Serial.print(week_day); Serial.print(" ");  
  if (meridian == true) {
    if (now_am == true) Serial.print("AM ");
    else Serial.print("PM ");
    if (hm < 10) { Serial.print("0"); Serial.print(hm); Serial.print(":"); }
    else { Serial.print(hm); Serial.print(":"); }
  }
  else {
    if (h < 10) { Serial.print("0"); Serial.print(h); Serial.print(":"); }
    else { Serial.print(h); Serial.print(":"); }
  }
  if (m < 10) { Serial.print("0"); Serial.print(m); Serial.print(":"); }
  else { Serial.print(m); Serial.print(":"); }
  if (s < 10) { Serial.print("0"); Serial.println(s); }
  else Serial.println(s);
  lcd.setCursor(0,0); 
  lcd.print(yy); lcd.print("."); 
  if (mm < 10) { lcd.print("0"); lcd.print(mm); lcd.print("."); }
  else { lcd.print(mm); lcd.print("."); }
  if (dd < 10) { lcd.print("0"); lcd.print(dd); lcd.print(". "); }
  else { lcd.print(dd); lcd.print(". "); }
  lcd.print(week_day); lcd.print(" ");  
  lcd.setCursor(0,1);
  if (meridian == true) {
    if (now_am == true) lcd.print("AM ");
    else lcd.print("PM ");
    if (hm < 10) { lcd.print("0"); lcd.print(hm); lcd.print(":"); }
    else { lcd.print(hm); lcd.print(":"); }
  }
  else {
    if (h < 10) { lcd.print("0"); lcd.print(h); lcd.print(":"); }
    else { lcd.print(h); lcd.print(":"); }
  }
  if (m < 10) { lcd.print("0"); lcd.print(m); lcd.print(":"); }
  else { lcd.print(m); lcd.print(":"); }
  if (s < 10) { lcd.print("0"); lcd.print(s); lcd.print("     "); }
  else { lcd.print(s); lcd.print("     "); }  // 크리스탈 LCD에는 pritln 명령어 없음
}

앞선 글에서 부족했던 부분들을 보완해 주었다. 상기의 코드를 아두이노에 업로드하고 아래 그림과 같이 블루투스를 아두이노 하드웨어 시리얼에 연결을 해주면 시리얼 모니터가 사용했었던 하드웨어 시리얼을 똑같이 사용하므로 별도의 코드 변경 없이도 블루투스 앱의 텍스트 입력(전송옵션: 새글)을 통해 시리얼 모니터에서 제어하던 것처럼 말하는 알람시계의 시간을 설정하고 DFPlayer의 사운드 파일을 제어할 수 있게 된다. 

** 아두이노 하드웨어 시리얼에 블루투스가 연결된 상태에서는 스케치를 업로드할 수 없다. 만약 연결되어 있다면 스케치를 업로드할 때에는 연결핀을 뽑아 주어야 한다.

 

2019.09.09 수정사항 
speak_option() 함수와 speak_sharp()함수의 0시 음성출력을 위한 ADVERT 파일 번호 계산 오류를 수정 했으며, 묵음구간 적용이 안되어 cal_time() 함수의 묵음 구간 코드를 아래와 같이 수정해 주어야 한다.

 

speak_option() 함수

// 수정전
if (h < 13) time_sharp = 2200 + h;
else time_sharp = 2200 + (h - 12);

// 수정후
if (h == 0) time_sharp = 2212;
else if (h < 13 && h > 0) time_sharp = 2200 + h;
else time_sharp = 2200 + (h - 12);

// 수정전
if (h < 13) hour_a = 1000 + h;
else hour_a = 1000 + (h - 12);

// 수정후
if (h == 0) hour_a = 1012;
else if (h < 13 && h > 0) hour_a = 1000 + h;
else hour_a = 1000 + (h - 12);

speak_sharp()함수

// 수정전
if (h < 13) time_sharp = 2100 + h;
else time_sharp = 2100 + (h - 12);

// 수정후
if (h == 0) time_sharp = 2112;
else if (h < 13 && h > 0) time_sharp = 2100 + h;
else time_sharp = 2100 + (h - 12);

cal_time() 함수

// 수정전
if (no_speak_sharp == true) {
  if (no_speak_s == no_speak_f) no_speak_sharp = false;
}

if (alarm_time != true) {
  if (m == 0 && s == 0) {
    if (no_speak_sharp == false) {
      speak_sharp();
    }
    else {
      if (no_speak_s > no_speak_f) {
        if (h < no_speak_s && h > no_speak_f) {
          speak_sharp();
        }
      }
      else if (no_speak_s < no_speak_f) {
        if (h > no_speak_s && h < no_speak_f) {
          speak_sharp();
        }
      }
    }
  }
}

// 수정후
if (alarm_time != true) {
  if (m == 0 && s == 0) {
    if (no_speak_sharp == true) {
      if (no_speak_s == no_speak_f) {
        no_speak_sharp = false;
        speak_sharp();
      }
      else {
        if (no_speak_s > no_speak_f) {
          if (h < no_speak_s && h > no_speak_f) {
            speak_sharp();
          }
        }
        else if (no_speak_s < no_speak_f) {
          if (h < no_speak_s && h >= 0 || h > no_speak_f && h <= 24) {
            speak_sharp();
          }
        }
      }
    }
    else {
      speak_sharp();
    }
  }
}

 

2019.09.09 수정사항

음성 출력 파일들의 묵음 구간을 늘려 놓았으며 그에 따라 아래 코드를 수정하여 음성이 종료되기 전에 볼륨이 변경되는 오류를 수정하였다. DFPlayer의 SD카드 파일을 모두 삭제하고 아래 파일의 폴더들을 모두 선택하여 붙여넣기 한뒤 아래 코드를 수정해 주면 특정 시간 출력시 음성이 종료되기전에 이전 볼륨으로 변경되던 오류가 수정되게 된다.  

NodeMcu_DF_test.alz
10.00MB
NodeMcu_DF_test.a00
10.00MB
NodeMcu_DF_test.a01
3.56MB

// 음성 출력 변수 수정 64 -> 80
uint8_t time_sharp_end = 80;

// speak_time() 함수 수정 64 -> 80
time_sharp_end = 80;

// speak_sharp() 함수 수정 46 -> 48
time_sharp_end = 48;

안드로이드 블루투스 앱 대신 DFcontroller 앱을 이용해서 제어해 보자. 

DFcontroller는 arduino bluetooth controller PWM앱을 기반으로 DFPlayer를 MP3 플레이어 환경에 제어할 수 있도록 프로그램된 앱이다. 따라서 arduino bluetooth controller PWM의 기능과 더불어 DFPlayer를 손쉽게 제어를 할 수 있게 된다. 시간 동기화 및 알람 설정을 위해 DFcontroller의 Date/Time 전송 기능과 알람 시간 전송 기능을 이용하고 버튼 설정을 통해 알람 ON/OFF 등 필요 설정을 해보자.

 

DFcontroller - DFPlayer mini 원격제어 안드로이드 앱 버전 2.2 다운로드

DFcontroller 매뉴얼

 

1. 프로토콜

1-1. DFPlayere 제어

DFPlayere 매뉴얼상 프로토콜을 따른다 -> 7E FF 06 ~ 

 

1-2. 안드로이드 날짜 및 시스템 시간 전송

0xF8 + 07/24/2019(String) + 0x7C + 15:55:32(String) + \n(새 줄)

 

1-3. 알람 설정

0xF9 + 21:20(String) + \n(새 줄)

 

1-4. 디지털 버튼 송신 및 수신

앱 <-> 아두이노: 0xF0 + 1byte number + 0xF1 

 

상기 프로토콜에 따라 adjust_time() 함수에 아래와 같이 코드를 추가해 주었다. 

if (Serial.peek() == 0x7E) send_df = true; // DFPlayer 제어코드 일경우 
else if (Serial.peek() == 0xF0) get_pin_val = true; // 디지털 버튼 제어코드 일경우
if (send_df == true) {   // 안드로이드앱 데이터수신 후 DFPlayer로 제어 코드 송신
  vol_check++;
  uint8_t temp = Serial.read(); 
  if (vol_check == 4 && temp == 0x06) save_vol = true; // 볼륨에 관한 코드이면
  if (save_vol == true && vol_check == 7) {            // 앱에서 변경된 볼륨값 저장
    volume = temp;
    save_vol = false;
  }
  if (temp == 0xEF) { send_df = false; vol_check = 0; } // DFPlayer 제어코드 수신 종료 
  dfSerial.write(temp);  // send to DFplayer
} 
else if (get_pin_val == true) {  // 디지털 핀 제어값 수신
  uint8_t temp = Serial.read();
  pin_a[pin_count] = temp;
  pin_count++;
  if (temp == 0xF1) {            // 디지털 핀 제어값 수신 종료
    get_pin_val = false;
    pin_count = 0;
    pin_control();
  }
}
else if (Serial.peek() == 0xF8) {  // 날짜 및 시간 값 수신
  String temp = Serial.readStringUntil('\n');
  String set = temp.substring(temp.indexOf("|")-10, temp.indexOf("|")-8);
  mm = set.toInt();
  set = temp.substring(temp.indexOf("|")-7, temp.indexOf("|")-5);
  dd = set.toInt();
  set = temp.substring(temp.indexOf("|")-4, temp.indexOf("|"));
  yy = set.toInt();
  set = temp.substring(temp.indexOf("|")+1, temp.indexOf("|")+3);
  h = set.toInt();
  set = temp.substring(temp.indexOf("|")+4, temp.indexOf("|")+6);
  m = set.toInt();
  set = temp.substring(temp.indexOf("|")+7, temp.indexOf("|")+9);
  s = set.toInt();
  setTime(h, m, s, dd, mm, yy); // time 라이브러리 이용, 현재 날짜를 유닉스 UTC로 변경
  week_num = weekday(); // time 라이브러리 이용, 현재 유닉스 UTC를 요일로 변환
  week_day_converter();
  start_time = millis();
  adjust = true;
  display_t = true;
  clock_status();
}
else if (Serial.peek() == 0xF9) {  // 알람 세팅 수신
  String temp = Serial.readStringUntil('\n');
  String set = temp.substring(1, temp.indexOf(":"));
  alarm_h = set.toInt();
  set = temp.substring(temp.indexOf(":") + 1, temp.length());
  alarm_m = set.toInt();
  EEPROM.write(6, alarm_h); // 알람 시
  EEPROM.write(7, alarm_m); // 알람 분
  Serial.print("alarm set "); Serial.print(alarm_h); Serial.print(":"); Serial.println(alarm_m);
}
else {
  기존 코드
}

상기 코드에서 DFcontroller에서 조정된 볼륨 값을 DFPlayer로 전송하기 전에 아두이노 볼륨 변수에 저장해주어야 재 기동시나 아두이노 코드에 의해서 실행될 때 변경된 볼륨으로 재생되게 된다. 또한, 날짜 및 시간 데이터에서 요일에 대한 값은 포함되어 있지 않다. 따라서 수신된 날짜 및 시간 값을 유닉스 UTC값으로 변환한 뒤 다시 현재 날짜의 요일을 계산해야 한다. 이 과정을 TimeLib.h 라이브러리를 이용하여 수행하였다. TimeLib.h 라이브러리 함수중 setTime() 함수는 날짜 및 시간 값을 받아 유닉스 UTC값으로 변경해서 라이브러리 내 저장 변수에 저장하게 된다. weekday() 함수는 저장 변수를 이용해 요일을 산출하는 함수이다. 

 

DFPlayer에서 아두이노로 송신하는 코드는 음성 출력 시 질의하는 현재 status에 대한 응답 0x42를 제외한 모든 코드를 DFcontroller로 다시 보내 준다. 

if (dfSerial.available()) {   // 42 02 01
  uint8_t temp = dfSerial.read();
  if (temp == 0x42) receive_status = true; 
  if (receive_status == true) {
    status_val[status_count] = temp;
    status_count++; 
    if (temp == 0xEF) {
      status_check = true;
      receive_status = false;  
      status_count = 0; 
      if (status_val[2] == 2 && status_val[3] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
      else if (status_val[2] == 2 && status_val[3] == 0) played = false; // 인덱스 3 = 0 정지중
    }
  }
  else {  // DFcontroller로 코드 전송
    Serial.write(temp);
  }
}

DFcontroller는 블루투스 연결 시 Data/Time 값을 자동으로 1회 전송하는 기능이 있다. 이 기능을 통해 데이터가 들어올 때 현재 말하는 알람시계에 설정된 값을 디지털 버튼의 상태에 반영하도록 보내주는 코드를 작성해 주었다. 

void clock_status() {
  if (no_speak_sharp == false) send_pin_echo(10);
  else send_pin_echo(11);
  if (alarm_on == false) send_pin_echo(20);
  else send_pin_echo(21);
  if (meridian == true) send_pin_echo(30);
  else send_pin_echo(31);
}

DFcontroller의 디지털 버튼에 의해 송신된 값을 적용하기 위해 스위치 함수를 이용하여 추가해 주었다. 

묵음 사용: 묵음 사용 유무 no_speak_sharp = false; 제어

알람 ON: 알람 ON/OFF alarm_on = false; 제어

24 모드: 오전/오후 사용 유무 meridian = true; 제어

알람 트랙: 현재 알람용 사운드 트랙의 넘버를 표시 -> 알람 트랙 설정은 텍스트 명령을 통해 해야 한다.

알람 정지: 알람 시 정지 

알람 볼륨: 현재 알람 볼륨 표시 -> 알람 볼륨 설정은 텍스트 명령어 사용

현재 시간: 현재 시간 음성 출력

void pin_control() {
  pin_val = pin_a[1];
  if (pin_val == 11 && no_speak_s == no_speak_f) {
    pin_val = 0;
    send_pin_echo(10);
    Serial.print("Set NSP "); Serial.print(no_speak_s); Serial.print("-"); Serial.print(no_speak_f);
  }
  if (pin_val != 0) {
    switch (pin_val) {
      case 11: no_speak_sharp = true; 
               EEPROM.write(1, no_speak_sharp); // 묵음 플래그
               Serial.print("NSP ON ");
               Serial.print(no_speak_s);
               Serial.print("-");
               Serial.print(no_speak_f);
                  break;
      case 10: no_speak_sharp = false; 
               EEPROM.write(1, no_speak_sharp); // 묵음 플래그
               Serial.print("NSP OFF ");
               Serial.print(no_speak_s);
               Serial.print("-");
               Serial.print(no_speak_f);
                  break;
      case 21: alarm_on = true;
               EEPROM.write(2, alarm_on); // 알람 플래그
               Serial.print("Alarm ON ");
               Serial.print(alarm_h);
               Serial.print(":");
               Serial.print(alarm_m);
                  break;
      case 20: alarm_on = false;
               EEPROM.write(2, alarm_on); // 알람 플래그
               Serial.print("Alarm OFF ");
               Serial.print(alarm_h);
               Serial.print(":");
               Serial.print(alarm_m);
                  break;
      case 31: meridian = false;
               EEPROM.write(3, meridian); // 24 플래그
                  break;
      case 30: meridian = true;
               EEPROM.write(3, meridian); // 24 플래그
                  break;
      case 41: Serial.print("Folder: ");
               Serial.print(alarm_track_folder);
               Serial.print(" ");
               Serial.print("Track: ");
               Serial.print(alarm_track);
                  break;
      case 51: alarm_stop = true;
               alarm_time = false;
               alarm_sentence = false;
               alarm_count = 0;
               alarm_send_f = true;
                  break;
      case 61: Serial.print("alarm volume: ");
               Serial.print(time_volume);
                  break;
      case 71: do_speak_option = true;
                  break;
    }
  pin_val = 0;
  }
}

상기에서 묵음 제어 시 기존 코드에서 묵음 시작 시간과 종료 시간이 같으면 묵음 사용하지 않는 것으로 간주되는데 이때에 묵음 사용 버튼을 클릭할 경우 수신된 제어 값은 무시되고 버튼의 상태를 클릭 안된 상태로 변경하는 코드를 추가해 주었다. 

 

 

상기의 디지털 버튼의 상태 변화 코드를 전송하기 위한 함수를 추가해 주었다. 

void send_pin_echo(uint8_t pin_val){
  pin_echo[1] = pin_val;
  for (int i = 0; i < 3; i++) {
    Serial.write(pin_echo[i]);
  }
}

기존의 display_time() 함수에서 시리얼 모니터로 출력하는 코드를 별도의 사용자 함수 display_time_serial() 함수로 뽑아내어 매초 출력되던 것을 필요시에만 출력하도록 하였다. 

void display_time_serial() {
  Serial.print(yy); Serial.print("."); 
  if (mm < 10) { Serial.print("0"); Serial.print(mm); Serial.print("."); }
  else { Serial.print(mm); Serial.print("."); }
  if (dd < 10) { Serial.print("0"); Serial.print(dd); Serial.print(". "); }
  else { Serial.print(dd); Serial.print(". "); }
  Serial.print(week_day); Serial.print(" ");  
  if (meridian == true) {
    if (now_am == true) Serial.print("AM ");
    else Serial.print("PM ");
    if (hm < 10) { Serial.print("0"); Serial.print(hm); Serial.print(":"); }
    else { Serial.print(hm); Serial.print(":"); }
  }
  else {
    if (h < 10) { Serial.print("0"); Serial.print(h); Serial.print(":"); }
    else { Serial.print(h); Serial.print(":"); }
  }
  if (m < 10) { Serial.print("0"); Serial.print(m); Serial.print(":"); }
  else { Serial.print(m); Serial.print(":"); }
  if (s < 10) { Serial.print("0"); Serial.println(s); }
  else Serial.println(s);
}

2. DFcontroller 설정

앱 실행 후 블루투스 아이콘을 클릭

아래 그림대로 체크박스 설정 -> 설정 후 블루투스 아이콘 클릭하여 아두이노 메인화면으로 이동 

디스플레이 아이콘 클릭하고 버튼라벨 및 버튼 표시 설정

버튼 속성 아이콘을 클릭하고 "echo text color" 체크 해제 및 버튼 속성 설정

아두이노 아이콘 클릭하여 아두이노 메인화면 진입 후 텍스트 전송옵션을 새글(New Line - NL)로 변경

블루투스 아이콘 클릭하여 블루투스 연결

블루투스 연결되면 안드로이드 앱은 DFPlayer로 부터 트랙 정보 수신하고 트랙 목록을 앱에 표시 한다.

알람 설정 및 수동 시간 동기화 

 

 

시간 동기화의 경우 연결옵션에서 Send Data/Time에 체크가 된 상태이므로 블루투스 연결시 이미 시간값이 전송되었다. 만약 블루투스 연결후에도 시간 동기화가 안되어 있다면 수동으로 시간값을 보내주면 된다. 

아두이노 아이콘 클릭하여 아두이노 제어 화면으로 이동하면 화면 아래쪽에 화살표 아이콘을 볼 수 있다. 아이콘을 클릭하여 표시되는 아이콘중 시계 아이콘을 클릭하면 된다. 

알람 설정은 알람시계 아이콘을 클릭하고 설정하면 된다. 

아래 코드를 아두이노에 업로드 하고 시간 출력이 잘되는지 확인해보고 DFPlayer 내 사운드 파일도 제어해보자.

arduino_clock_dfplayer_DFcontroller.ino
0.02MB

더보기
#include <EEPROM.h>
#define EEPROM_SIZE 11 // 11 byte

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display

#include <SoftwareSerial.h> 
#define DF_rxPin 2 // DFplayer RX -> arduino 2
#define DF_txPin 3 // DFplayer TX -> arduino 3 
SoftwareSerial dfSerial(DF_txPin, DF_rxPin); // (RX, TX)

#include <TimeLib.h>

// DFplayer command 변수
uint8_t df_command[] = {0x7E, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0xEF};

void send_command(uint8_t cmd, uint8_t ack, uint8_t msb, uint8_t lsb) {
  df_command[3] = cmd;
  df_command[4] = ack;
  df_command[5] = msb;
  df_command[6] = lsb;
  for(int i = 0; i < sizeof(df_command); i++){
    dfSerial.write(df_command[i]);
  }
}

int h = 12; // initial Time display is 12:59:45 PM
int m = 56;
uint8_t s = 45;
bool meridian = true;
uint8_t hm;
bool now_am = true; //AM
uint16_t yy = 2019;
uint8_t mm = 1;
uint8_t dd = 1;
uint8_t week_num = 1;
String week_day = "SUN";
bool adjust = false;

bool display_t = false;
unsigned long int start_time = 0;
uint8_t volume = 20;

bool receive_status = false; // 질의 코드 0x42수신 여부 플래그
uint8_t status_count = 0;   // 질의값 저장 인덱스용 카운터
uint8_t status_val[7];         // 질의값 저장 배열
bool played = false;         // 질의결과 플래그 (재생중인지 아닌지)
bool status_check = false; // 질의 수신 완료 플래그

unsigned long int count_time = 0;
bool delay_set = false;
uint8_t sequence = 0;

bool speak = false; // 현재 상태 질의 시작 및 음성출력 시작 플래그
bool short_sentence = false;
bool sharp = false;

int am;
int time_sharp;
int hour_a;
int min_w;
int min_s;

uint8_t alarm_track_folder = 2;
uint8_t alarm_track = 2;

bool alarm_on = false;
int alarm_h;
int alarm_m;
unsigned long int alarm_delay = 0;
bool alarm_sentence = false;
uint8_t alarm_count = 0;
bool alarm_time = false;

unsigned long int alarm_sequence_delay = 0;
uint8_t alarm_sequence = 0;
bool alarm_send_f = false;
bool alarm_stop = false; 

bool no_speak_sharp = false;
uint8_t no_speak_s = 0;
uint8_t no_speak_f = 0;

uint8_t time_volume = 30; // 음성출력 및 알람 볼륨

uint8_t pin = 7;

void setup() {
  if(EEPROM.read(0) != 1) {
    EEPROM.write(0, 1); // eeprom 저장 플래그
    EEPROM.write(1, no_speak_sharp); // 묵음 플래그
    EEPROM.write(2, alarm_on); // 알람 플래그
    EEPROM.write(3, meridian); // 24 플래그
    EEPROM.write(4, no_speak_s); // 묵음 시작
    EEPROM.write(5, no_speak_f); // 묵음 종료
    EEPROM.write(6, alarm_h); // 알람 시
    EEPROM.write(7, alarm_m); // 알람 분
    EEPROM.write(8, alarm_track_folder); // 알람 폴더
    EEPROM.write(9, alarm_track); // 알람 트랙
    EEPROM.write(10, time_volume); // 알람 볼륨
  } else {
    no_speak_sharp = EEPROM.read(1);
    alarm_on = EEPROM.read(2);
    meridian = EEPROM.read(3);
    no_speak_s = EEPROM.read(4);
    no_speak_f = EEPROM.read(5);
    alarm_h = EEPROM.read(6);
    alarm_m = EEPROM.read(7);
    alarm_track_folder = EEPROM.read(8);
    alarm_track = EEPROM.read(9);
    time_volume = EEPROM.read(10);
  }
  Serial.begin(9600);
  dfSerial.begin(9600); // DFplayer Serial
  pinMode(pin, INPUT_PULLUP);  // pullup 저항 이용 - 버튼쪽에 저항 없음 OFF - HIHG, ON - LOW
  send_command(0x06, 0, 0, volume); // 볼륨 
  lcd.begin();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0,0);  // row, col의 좌표로 커서를 위치 col: 0 ~ 15 row: 0, 1
  lcd.print("Arduino clock");
}

void loop() {
  cal_time();
  adjust_time();
  speak_time();
  delayCount();
  alarm_warning();
  alarm_send();
  if (dfSerial.available()) {   // 42 02 01
    uint8_t temp = dfSerial.read();
    if (temp == 0x42) receive_status = true; 
    if (receive_status == true) {
      status_val[status_count] = temp;
      status_count++; 
      if (temp == 0xEF) {
        status_check = true;
        receive_status = false;  
        status_count = 0; 
        if (status_val[2] == 2 && status_val[3] == 1) played = true; // 인덱스 2 = 2 -> SD카드, 인덱스 3 = 1 재생중
        else if (status_val[2] == 2 && status_val[3] == 0) played = false; // 인덱스 3 = 0 정지중
      }
    }
    else {
      Serial.write(temp);
    }
  }
  if (digitalRead(pin) == LOW) { // LOW - ON
    if (alarm_time == true) {
      alarm_stop = true;
      alarm_time = false;
      alarm_sentence = false;
      alarm_count = 0;
      alarm_send_f = true;
    }
    else {
      speak_option();
    }
  }
}

uint8_t vol_check = 0;
bool save_vol = false;
bool send_df = false; 

// 블루투스 앱 디지털 핀 제어용 변수
bool get_pin_val = false;
uint8_t pin_count = 0;
uint8_t pin_a[3] = {0, };
uint8_t pin_val;

// 블루투스 앱 상태표시 변수
uint8_t pin_echo[] = {0xF0, 0, 0xF1};

void adjust_time() {
  if (Serial.available() > 0) {
    if (Serial.peek() == 0x7E) send_df = true; 
    else if (Serial.peek() == 0xF0) get_pin_val = true; 
    if (send_df == true) {   // 안드로이드앱 데이터 수신 DFPlayer로 제어 코드 송신
      vol_check++;
      uint8_t temp = Serial.read(); 
      if (vol_check == 4 && temp == 0x06) save_vol = true;
      if (save_vol == true && vol_check == 7) { // 앱에서 변경된 볼륨값 저장
        volume = temp;
        save_vol = false;
      }
      if (temp == 0xEF) { send_df = false; vol_check = 0; }
      dfSerial.write(temp);  // send to DFplayer
    } 
    else if (get_pin_val == true) {  // 디지털 핀 제어값 수신
      uint8_t temp = Serial.read();
      pin_a[pin_count] = temp;
      pin_count++;
      if (temp == 0xF1) {
        get_pin_val = false;
        pin_count = 0;
        pin_control();
      }
    }
    else if (Serial.peek() == 0xF8) {  // 날짜 및 시간 값 수신
      String temp = Serial.readStringUntil('\n');
      String set = temp.substring(temp.indexOf("|")-10, temp.indexOf("|")-8);
      mm = set.toInt();
      set = temp.substring(temp.indexOf("|")-7, temp.indexOf("|")-5);
      dd = set.toInt();
      set = temp.substring(temp.indexOf("|")-4, temp.indexOf("|"));
      yy = set.toInt();
      set = temp.substring(temp.indexOf("|")+1, temp.indexOf("|")+3);
      h = set.toInt();
      set = temp.substring(temp.indexOf("|")+4, temp.indexOf("|")+6);
      m = set.toInt();
      set = temp.substring(temp.indexOf("|")+7, temp.indexOf("|")+9);
      s = set.toInt();
      setTime(h, m, s, dd, mm, yy); // time 라이브러리 이용, 현재 날짜를 유닉스 UTC로 변경
      week_num = weekday(); // time 라이브러리 이용, 현재 유닉스 UTC를 요일로 변환
      week_day_converter();
      start_time = millis();
      adjust = true;
      display_t = true;
      clock_status();
    }
    else if (Serial.peek() == 0xF9) {  // 알람 세팅 수신
      String temp = Serial.readStringUntil('\n');
      String set = temp.substring(1, temp.indexOf(":"));
      alarm_h = set.toInt();
      set = temp.substring(temp.indexOf(":") + 1, temp.length());
      alarm_m = set.toInt();
      EEPROM.write(6, alarm_h); // 알람 시
      EEPROM.write(7, alarm_m); // 알람 분
      Serial.print("alarm set "); Serial.print(alarm_h); Serial.print(":"); Serial.println(alarm_m);
    }
    else {
      String temp = Serial.readStringUntil('\n');
      if (temp == "dtime") display_time();
      else if (temp == "s") { s = 0; start_time = millis(); display_t = true; }
      else if (temp == "mm") { m++; display_t = true; }
      else if (temp == "m") { m--; display_t = true; }
      else if (temp == "hh") { h++; display_t = true; }
      else if (temp == "h") { h--; display_t = true; }
      else if (temp == "24") { meridian = !meridian; EEPROM.write(3, meridian); display_t = true; }
      else if (temp == "yy") { yy++; display_t = true; }
      else if (temp == "y") { yy--; display_t = true; }
      else if (temp == "mon") { mm++; adjust = true; display_t = true; }
      else if (temp == "mon-") { mm--; adjust = true; display_t = true; }
      else if (temp == "dd") { dd++; adjust = true; display_t = true; }
      else if (temp == "d") { dd--; adjust = true; display_t = true; }
      else if (temp == "ww") { week_num++; adjust = true; display_t = true; }
      else if (temp == "w") { week_num--; adjust = true; display_t = true; }
      else if (temp == "nsp") { no_speak_sharp = !no_speak_sharp; EEPROM.write(1, no_speak_sharp); }
      else if (temp.startsWith("atrack")) {
        String set = temp.substring(6, temp.indexOf(","));
        alarm_track_folder = set.toInt();
        set = temp.substring(temp.indexOf(",") + 1, temp.length());
        alarm_track = set.toInt();
        EEPROM.write(8, alarm_track_folder); // 알람 폴더
        EEPROM.write(9, alarm_track); // 알람 트랙
        Serial.print("Folder: "); Serial.print(alarm_track_folder); Serial.print(" ");
        Serial.print("Track: "); Serial.println(alarm_track); 
      }
      else if (temp.startsWith("avol")) {
        temp.remove(0, 4);
        time_volume = temp.toInt();
        EEPROM.write(10, time_volume); // 알람 볼륨
        Serial.print("alarm volume: "); Serial.println(time_volume); 
      }
      else if (temp.startsWith("tset")) {
        String set = temp.substring(4, 6);
        h = set.toInt();
        set = temp.substring(6, 8);
        m = set.toInt();
        set = temp.substring(8, 10);
        s = set.toInt();
        start_time = millis();
        adjust = true;
        display_t = true;
      }
      else if (temp.startsWith("dset")) {
        String set = temp.substring(4, 8);
        yy = set.toInt();
        set = temp.substring(8, 10);
        mm = set.toInt();
        set = temp.substring(10, 12);
        dd = set.toInt();
        adjust = true;
        display_t = true;
      }
      else if (temp == "alarm") { 
        alarm_on = !alarm_on;
        EEPROM.write(2, alarm_on); // 알람 플래그
        if (alarm_on) Serial.println("alarm on");
        else Serial.println("alarm off");
      }
      else if (temp.startsWith("aset")) {
        String set = temp.substring(4, 6);
        alarm_h = set.toInt();
        set = temp.substring(6, 8);
        alarm_m = set.toInt();
        EEPROM.write(6, alarm_h); // 알람 시
        EEPROM.write(7, alarm_m); // 알람 분
        Serial.print("alarm set "); Serial.print(alarm_h); Serial.print(":"); Serial.println(alarm_m); 
      }
      else if (temp.startsWith("setnsp")) {
        String set = temp.substring(6, temp.indexOf("~"));
        no_speak_s = set.toInt();
        set = temp.substring(temp.indexOf("~") + 1, temp.length());
        no_speak_f = set.toInt();
        EEPROM.write(4, no_speak_s); // 묵음 시작
        EEPROM.write(5, no_speak_f); // 묵음 종료
        Serial.print("no speak set: "); Serial.print(no_speak_s); Serial.print("-"); Serial.println(no_speak_f); 
      }
      else if (temp == "test") speak_option();
      else if (temp == "stop") { 
        alarm_stop = true;
        alarm_time = false;
        alarm_sentence = false;
        alarm_count = 0;
        alarm_send_f = true;
      }
      else if (temp == "play") send_command(0x0F, 0, 2, 6);
      else if (temp == "pause") send_command(0x0E, 1, 0, 0);
      else if (temp == "previous") send_command(0x02, 1, 0, 0);
      else if (temp == "next") send_command(0x01, 1, 0, 0);
      else if (temp == "vv") { if(volume < 30) volume++; send_command(0x06, 0, 0, volume); }
      else if (temp == "v") { if(volume > 0) volume--; send_command(0x06, 0, 0, volume); }
      else if (temp == "reset") send_command(0x0C, 0, 0, 0);
      if (m >= 60) m = 0;
      else if (m < 0) m = 59;
      if (h >= 24) h = 0; 
      else if (h < 0) h = 23; 
      if (h < 12) now_am = true;
      else now_am = false; 
      hm = h; 
      if(meridian == true && hm == 12) { now_am = false; }
      else if (meridian == true && hm > 12) { hm = hm - 12; }
      cal_dd();
      if (display_t == true) { display_time(); display_t = false; }
    }
  }
}

void clock_status() {
  if (no_speak_sharp == false) send_pin_echo(10);
  else send_pin_echo(11);
  if (alarm_on == false) send_pin_echo(20);
  else send_pin_echo(21);
  if (meridian == true) send_pin_echo(30);
  else send_pin_echo(31);
}

bool speak_finished = true;

void speak_option() {
  if (m == 0 && s != 0) {
    sharp = true;
    if (h < 13) time_sharp = 2200 + h;
    else time_sharp = 2200 + (h - 12);
  }
  else {
    sharp = false;
    if (h < 13) hour_a = 1000 + h;
    else hour_a = 1000 + (h - 12);
    if (h < 12) am = 1301;
    else am = 1302;
    int temp = m%10;
    if (temp == 0 || m < 10) {
      short_sentence = true;
      min_s = 1200 + m;
    }
    else {
      short_sentence = false;
      min_w = 1100 + (m - temp);
      min_s = 1200 + temp;
    }
  }
  speak = true;
  speak_finished = false;
}

uint8_t am_s = 4;
uint8_t vol_s = 6;
uint8_t hour_a_s = 21;
uint8_t long_min_w_s = 44;
uint8_t long_min_s_s = 65;
uint8_t long_min_s_end = 96;
uint8_t short_min_s_s = 44;
uint8_t short_min_s_end = 74;
uint8_t time_sharp_s = 4;
uint8_t time_sharp_end = 64;

void speak_time() {
  if (speak == true) {
    if (sequence == 0 && status_check == false) {
      send_command(0x42, 0, 0, 0);
      sequence++;
    }
    else if (sequence == 1 && status_check == true) {
      if (played == false) {
        send_command(0x0F, 0, 2, 255);
        sequence++; // 2
        delay_set = true;
      }
      else {
        delay_set = true;
        sequence = 3;
      }
    }
    else if (sequence >= am_s && sharp == false) {
      if (sequence == am_s) { advert(am); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == hour_a_s) { advert(hour_a); speak = false; }
      if (short_sentence == false) {
        if (sequence == long_min_w_s) { advert(min_w); speak = false; }
        else if (sequence == long_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == long_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
        }
      }
      else {
        if (sequence == short_min_s_s) { advert(min_s); speak = false; }
        else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
        else if (sequence == short_min_s_end) {
          send_command(0x06, 0, 0, volume);
          speak_finished = true;
          speak = false;
          status_check = false;
          sequence = 0;
        }
      }
    }
    else if (sequence >= time_sharp_s && sharp == true) {
      if (sequence == time_sharp_s) { advert(time_sharp); speak = false; }
      else if (sequence == vol_s) { send_command(0x06, 0, 0, time_volume); speak = false; }
      else if (sequence == time_sharp_end) {
        send_command(0x06, 0, 0, volume);
        speak_finished = true;
        speak = false;
        status_check = false;
        sequence = 0;
        time_sharp_end = 64;
      }
    }
  }
}

void advert(int ad_num) {
  uint8_t ad_lsb = ad_num;
  uint8_t ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
}

void delayCount() {
  if (delay_set == true) {
    if (millis() - count_time >= 50) { // 시간 간격: 밀리초
      count_time = millis();
      sequence++;
      if (sharp == false) { 
        if (sequence == am_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == hour_a_s) speak = true;
        if (short_sentence == false) {
          if (sequence == long_min_w_s) speak = true;
          else if (sequence == long_min_s_s) speak = true;
          else if (sequence == long_min_s_end) { speak = true; delay_set = false; }
        }
        else {
          if (sequence == short_min_s_s) speak = true;
          else if (sequence == short_min_s_end) { speak = true; delay_set = false; }
        }
      }
      else {
        if (sequence == time_sharp_s) speak = true;
        else if (sequence == vol_s) speak = true;
        else if (sequence == time_sharp_end)  { speak = true; delay_set = false; }
      }
    }
  }
}

bool change_millis = false; 
uint16_t millis_sec = 994;

void cal_time() {
  if (millis() - start_time >= millis_sec) { // 시간 간격: 밀리초
    start_time = millis(); // 상기 조건을 만족할때의 밀리초를 다시 start_time에 저장하여 조건 초기화
    change_millis = ! change_millis;
    if (change_millis == true)  millis_sec = 995;
    else millis_sec = 994;
    s++;
    if (s == 60) {
      s = 0;
      m++;
    }
    if(m == 60) { m = 0; h++; }
    if(h == 12) { now_am = false;}
    else if (h == 24) { h = 0; now_am = true; dd++; week_num++; cal_dd(); }
    hm = h;
    if(meridian == true && hm == 12) { now_am = false; }
    else if (meridian == true && hm > 12) { hm = hm - 12; }
    else now_am = true;
    display_time();
    if (no_speak_sharp == true) {
      if (no_speak_s == no_speak_f) no_speak_sharp = false;
    }
    if (alarm_on) {
      if (alarm_h == h && alarm_m == m && s == 0){
        alarm_time = true;
        alarm_send_f = true;
      }
    }
    if (alarm_time != true) {
      if (m == 0 && s == 0) {
        if (no_speak_sharp == false) {
          speak_sharp();
        }
        else {
          if (no_speak_s > no_speak_f) {
            if (h < no_speak_s && h > no_speak_f) {
              speak_sharp();
            }
          }
          else if (no_speak_s < no_speak_f) {
            if (h > no_speak_s && h < no_speak_f) {
              speak_sharp();
            }
          }
        }
      }
    }
  }
}

void speak_sharp() {
  if (speak_finished == true) {
    sharp = true;
    if (h < 13) time_sharp = 2100 + h;
    else time_sharp = 2100 + (h - 12);
    time_sharp_end = 46;
    speak = true;   
  }
}

void alarm_send() {
  if (alarm_send_f == true) {
    if (millis() - alarm_sequence_delay >= 50) { // 시간 간격: 밀리초
      alarm_sequence_delay = millis();
      alarm_sequence++;
      if (alarm_stop == true) {
        if (alarm_sequence == 1) send_command(0x06, 0, 0, volume);
        else if (alarm_sequence == 2) send_command(0x16, 0, 0, 0); 
        else if (alarm_sequence == 3) {
          send_command(0x15, 0, 0, 0); 
          alarm_send_f = false;
          alarm_sequence = 0;
          alarm_stop = false;
        }
      }
      else {
        if (alarm_sequence == 1) send_command(0x16, 0, 0, 0);
        else if (alarm_sequence == 2) send_command(0x06, 0, 0, time_volume); // 볼륨 30
        else if (alarm_sequence == 3) send_command(0x0F, 0, alarm_track_folder, alarm_track); // 폴더2 002번 사운드 파일
        else if (alarm_sequence == 8) {
          send_command(0x19, 0, 0, 0); // 반복재생
          alarm_sentence = true;
          alarm_count = 0;
          alarm_send_f = false;
          alarm_sequence = 0;
        }
      }
    }
  }
}

void alarm_warning() {
  if (alarm_sentence == true) {
    if (millis() - alarm_delay >= 1000) { // 시간 간격: 밀리초
      alarm_delay = millis();
      alarm_count++;
      if (alarm_count == 4) {
        advert(1401); 
      }
      else if (alarm_count == 12) {
        advert(1402);
      }
      else if (alarm_count == 25) {
        advert(1403);
      }
      else if (alarm_count == 70) {
        advert(1404);
        alarm_sentence = false;
        alarm_count = 0;
      }
    }
  }
}

void cal_dd() {
  uint8_t m_dds;
  switch (mm) {
    case 1:   m_dds = 31; break;
    case 2:   m_dds = 28; break;
    case 3:   m_dds = 31; break;
    case 4:   m_dds = 30; break;
    case 5:   m_dds = 31; break;
    case 6:   m_dds = 30; break;
    case 7:   m_dds = 31; break;
    case 8:   m_dds = 31; break;
    case 9:   m_dds = 30; break;
    case 10:  m_dds = 31; break;
    case 11:  m_dds = 30; break;
    case 12:  m_dds = 31; break;
  }
  if (adjust == false) {
    if (dd > m_dds) { dd = 1; mm++; }
    if (mm > 12) { mm = 1; yy++; }
    if (week_num > 7) week_num = 1;
    week_day_converter();
  }
  else {
    if (dd > m_dds) dd = 1;
    else if (dd < 1) dd = m_dds;
    if (mm > 12) mm = 1;
    else if (mm < 1) mm = 12;
    if (week_num > 7) week_num = 1;
    else if (week_num < 1) week_num = 7; 
    week_day_converter();
    adjust = false;
  }
}

void week_day_converter() {
  switch (week_num) {
    case 1:   week_day = "SUN"; break;
    case 2:   week_day = "MON"; break;
    case 3:   week_day = "TUE"; break;
    case 4:   week_day = "WED"; break;
    case 5:   week_day = "THU"; break;
    case 6:   week_day = "FRI"; break;
    case 7:   week_day = "SAT"; break;
  }
}

void display_time() {
  lcd.setCursor(0,0); 
  lcd.print(yy); lcd.print("."); 
  if (mm < 10) { lcd.print("0"); lcd.print(mm); lcd.print("."); }
  else { lcd.print(mm); lcd.print("."); }
  if (dd < 10) { lcd.print("0"); lcd.print(dd); lcd.print(". "); }
  else { lcd.print(dd); lcd.print(". "); }
  lcd.print(week_day); lcd.print(" ");  
  lcd.setCursor(0,1);
  if (meridian == true) {
    if (now_am == true) lcd.print("AM ");
    else lcd.print("PM ");
    if (hm < 10) { lcd.print("0"); lcd.print(hm); lcd.print(":"); }
    else { lcd.print(hm); lcd.print(":"); }
  }
  else {
    if (h < 10) { lcd.print("0"); lcd.print(h); lcd.print(":"); }
    else { lcd.print(h); lcd.print(":"); }
  }
  if (m < 10) { lcd.print("0"); lcd.print(m); lcd.print(":"); }
  else { lcd.print(m); lcd.print(":"); }
  if (s < 10) { lcd.print("0"); lcd.print(s); lcd.print("     "); }
  else { lcd.print(s); lcd.print("     "); }  // 크리스탈 LCD에는 pritln 명령어 없음
}

void display_time_serial() {
  Serial.print(yy); Serial.print("."); 
  if (mm < 10) { Serial.print("0"); Serial.print(mm); Serial.print("."); }
  else { Serial.print(mm); Serial.print("."); }
  if (dd < 10) { Serial.print("0"); Serial.print(dd); Serial.print(". "); }
  else { Serial.print(dd); Serial.print(". "); }
  Serial.print(week_day); Serial.print(" ");  
  if (meridian == true) {
    if (now_am == true) Serial.print("AM ");
    else Serial.print("PM ");
    if (hm < 10) { Serial.print("0"); Serial.print(hm); Serial.print(":"); }
    else { Serial.print(hm); Serial.print(":"); }
  }
  else {
    if (h < 10) { Serial.print("0"); Serial.print(h); Serial.print(":"); }
    else { Serial.print(h); Serial.print(":"); }
  }
  if (m < 10) { Serial.print("0"); Serial.print(m); Serial.print(":"); }
  else { Serial.print(m); Serial.print(":"); }
  if (s < 10) { Serial.print("0"); Serial.println(s); }
  else Serial.println(s);
}

void pin_control() {
  pin_val = pin_a[1];
  if (pin_val == 11 && no_speak_s == no_speak_f) {
    pin_val = 0;
    send_pin_echo(10);
    Serial.print("Set NSP "); Serial.print(no_speak_s); Serial.print("-"); Serial.print(no_speak_f);
  }
  if (pin_val != 0) {
    switch (pin_val) {
      case 11: no_speak_sharp = true; 
               EEPROM.write(1, no_speak_sharp); // 묵음 플래그
               Serial.print("NSP ON ");
               Serial.print(no_speak_s);
               Serial.print("-");
               Serial.print(no_speak_f);
                  break;
      case 10: no_speak_sharp = false; 
               EEPROM.write(1, no_speak_sharp); // 묵음 플래그
               Serial.print("NSP OFF ");
               Serial.print(no_speak_s);
               Serial.print("-");
               Serial.print(no_speak_f);
                  break;
      case 21: alarm_on = true;
               EEPROM.write(2, alarm_on); // 알람 플래그
               Serial.print("Alarm ON ");
               Serial.print(alarm_h);
               Serial.print(":");
               Serial.print(alarm_m);
                  break;
      case 20: alarm_on = false;
               EEPROM.write(2, alarm_on); // 알람 플래그
               Serial.print("Alarm OFF ");
               Serial.print(alarm_h);
               Serial.print(":");
               Serial.print(alarm_m);
                  break;
      case 31: meridian = false;
               EEPROM.write(3, meridian); // 24 플래그
                  break;
      case 30: meridian = true;
               EEPROM.write(3, meridian); // 24 플래그
                  break;
      case 41: Serial.print("Folder: ");
               Serial.print(alarm_track_folder);
               Serial.print(" ");
               Serial.print("Track: ");
               Serial.print(alarm_track);
                  break;
      case 51: alarm_stop = true;
               alarm_time = false;
               alarm_sentence = false;
               alarm_count = 0;
               alarm_send_f = true;
                  break;
      case 61: Serial.print("alarm volume: ");
               Serial.print(time_volume);
                  break;
      case 71: speak_option();
               display_time_serial();
                  break;
    }
  pin_val = 0;
  }
}

void send_pin_echo(uint8_t pin_val){
  pin_echo[1] = pin_val;
  for (int i = 0; i < 3; i++) {
    Serial.write(pin_echo[i]);
  }
}

 

 

다음편에는 NodeMcu를 이용해 말하는 알람시계를 구현하고 DFcontroller Soft AP 연결을 이용하여 각종 제어, NodeMcu의 wifi로 NTP 서버 시간동기화도 해보겠다.

 

2019.09.09 수정사항
speak_option() 함수와 speak_sharp()함수의 0시 음성출력을 위한 ADVERT 파일 번호 계산 오류를 수정 했으며, 묵음구간 적용이 안되어 cal_time() 함수의 묵음 구간 코드를 아래와 같이 수정해 주어야 한다.

 

speak_option() 함수

// 수정전
if (h < 13) time_sharp = 2200 + h;
else time_sharp = 2200 + (h - 12);

// 수정후
if (h == 0) time_sharp = 2212;
else if (h < 13 && h > 0) time_sharp = 2200 + h;
else time_sharp = 2200 + (h - 12);

// 수정전
if (h < 13) hour_a = 1000 + h;
else hour_a = 1000 + (h - 12);

// 수정후
if (h == 0) hour_a = 1012;
else if (h < 13 && h > 0) hour_a = 1000 + h;
else hour_a = 1000 + (h - 12);

speak_sharp()함수

// 수정전
if (h < 13) time_sharp = 2100 + h;
else time_sharp = 2100 + (h - 12);

// 수정후
if (h == 0) time_sharp = 2112;
else if (h < 13 && h > 0) time_sharp = 2100 + h;
else time_sharp = 2100 + (h - 12);

cal_time() 함수

// 수정전
if (no_speak_sharp == true) {
  if (no_speak_s == no_speak_f) no_speak_sharp = false;
}

if (alarm_time != true) {
  if (m == 0 && s == 0) {
    if (no_speak_sharp == false) {
      speak_sharp();
    }
    else {
      if (no_speak_s > no_speak_f) {
        if (h < no_speak_s && h > no_speak_f) {
          speak_sharp();
        }
      }
      else if (no_speak_s < no_speak_f) {
        if (h > no_speak_s && h < no_speak_f) {
          speak_sharp();
        }
      }
    }
  }
}

// 수정후
if (alarm_time != true) {
  if (m == 0 && s == 0) {
    if (no_speak_sharp == true) {
      if (no_speak_s == no_speak_f) {
        no_speak_sharp = false;
        speak_sharp();
      }
      else {
        if (no_speak_s > no_speak_f) {
          if (h < no_speak_s && h > no_speak_f) {
            speak_sharp();
          }
        }
        else if (no_speak_s < no_speak_f) {
          if (h < no_speak_s && h >= 0 || h > no_speak_f && h <= 24) {
            speak_sharp();
          }
        }
      }
    }
    else {
      speak_sharp();
    }
  }
}

 

2019.09.09 수정사항

음성 출력 파일들의 묵음 구간을 늘려 놓았으며 그에 따라 아래 코드를 수정하여 음성이 종료되기 전에 볼륨이 변경되는 오류를 수정하였다. DFPlayer의 SD카드 파일을 모두 삭제하고 아래 파일의 폴더들을 모두 선택하여 붙여넣기 한뒤 아래 코드를 수정해 주면 특정 시간 출력시 음성이 종료되기전에 이전 볼륨으로 변경되던 오류가 수정되게 된다.  

NodeMcu_DF_test.alz
10.00MB
NodeMcu_DF_test.a00
10.00MB
NodeMcu_DF_test.a01
3.56MB

// 음성 출력 변수 수정 64 -> 80
uint8_t time_sharp_end = 80;

// speak_time() 함수 수정 64 -> 80
time_sharp_end = 80;

// speak_sharp() 함수 수정 46 -> 48
time_sharp_end = 48;

 

관련 글

[arduino] - DFplayer - 아두이노 사운드 모듈

[arduino] - NodeMcu(ESP8266)에서 DFplayer를 제어하는 코드

[arduino] - ESP32(DevKit)로 DFplayer 제어하기

[arduino] - 안드로이드 앱 DFcontroller를 이용하여 DFplayer 제어

[arduino] - 아두이노시계 예제, ESP01 WiFi 이용 시간 동기화 하기

[arduino] - 아두이노 말하는알람시계 예제 - DFPlayer

[arduino] - 말하는알람시계 - 블루투스 연결 및 시간 동기화, DFPlayer 제어

[arduino] - NodeMcu - 말하는 알람시계, wifi이용 시간 동기화 및 DFPlayer 원격제어

[arduino] - ESP8266 / ESP32 - time.h 라이브러리 이용 시간 출력 및 NTP 서버 시간 동기화 하기

 

 

 

반응형

+ Recent posts