반응형

이전 글에서 아두이노를 이용한 시계를 코딩해 보았다. 이번에는 DFPlayer 사운드 모듈을 이용해 정시가 되면 시간을 스피커를 통해 알려주고, 사용자 입력이 있을 경우(버튼 또는 센서) 현재의 시간을 알려주며, 설정한 시간이 되면 알람이 울리도록 해보자.

 

DFPLAYER 구입처: 알리익스프레스

https://s.click.aliexpress.com/e/_DDFgoR5

 

10pcs/1pc Mini MP3 DF Player Module TF Card U Disk Audio Voice Module Board DFPlayer DF Play IO/Serial Port/AD For Arduino - Ali

Smarter Shopping, Better Living! Aliexpress.com

www.aliexpress.com

 

우선 앞선 예제에서 사용한 아두이노와 ESP01의 wifi를 이용한 시간 동기화를 그대로 사용하고 싶었으나 아래의 이유로 그렇게 할 수가 없었다. 

 

아두이노 + DFPlayer + ESP01을 동시에 연결하기 위해서는 DFPlayer나 ESP01중 하나를 아두이노 Software Serial에 연결하고 다른 하나는 Hardware Serial에 연결해야만 한다. 

 

1. Hardware Serial에 ESP01이 연결된 상태에서 Software Serial에 연결된 DFplayer가 불특정 하게 리셋되는 현상이 발생한다. 버튼을 이용해 DFplayer에 사운드 play 명령을 보내게 되면 play 되다 바로 리셋된다. 

2. Hardware Serial에 ESP01이 연결된 상태에서는 시리얼 모니터에서 텍스트 입력해도 아두이노에서 수신이 안된다. 아두이노 RX핀에 연결된 블루투스 TX핀을 제거해야만 시리얼 모니터의 텍스트 입력을 아두이노가 수신할 수 있게 된다. 즉 알람 시간을 설정해야 하나 설정할 수가 없다. 다른 방법으로 설정한다고 해도 1번의 이유로 정상적인 결과가 나오지 않는다. 

 

따라서 아두이노를 이용한 말하는 알람시계 예제에서는 ESP01을 사용하지 않고 진행하겠다. 시간 동기화는 시리얼 모니터 텍스트 명령을 통해 하거나 컴퓨터 연결 없이 독립된 외부 전원으로 기동 중일 때에는 블루투스 모듈을 Hardware Serial에 연결하고 안드로이드 앱을 이용하여 텍스트 명령으로 동기화시켜 주어야 한다.

 

아두이노 + DFPlayer + LCD 연결

DFPlayer의 전원을 3.3V에 연결해주고 GND는 브레드보드 공통에 연결하지 않고 아두이노 GND핀에 직접 연결해 주었다. 이전 글에서 사용했던 아두이노 시계 코드에서 ESP01 관련 코드를 제거한 아래 스케치를 업로드해주고 시리얼 모니터를 통해 텍스트 명령어를 입력하여 작동상태를 확인해보자. 시계 코드가 이해가 안 된다면 이전 글을 참조하기 바란다.

arduino_clock.ino
0.01MB

더보기
#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

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;

void setup() {
  Serial.begin(9600);
  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();
}

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.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;
    }
    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 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 (h == 1 && m == 1 && s == 10) { // NTP 동기화 시간
    }
  }
}

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의 SD 카드에 아래의 음성 예제 파일의 폴더 01, 02, ADVERT를 모두 선택 후 복사하여 붙여 넣기 해준다. 

 

사용자 요청에 의해 재생되는 음성파일은 마이크로소프트의 기본 TTS 엔진인 혜미를 이용하여 제작되었으며, 정시를 알리는 음성파일은 삼성 피쳐폰 시절의 정시 알림 파일을 구해서 넣어놓았다. 알람의 끼워넣기 멘트용으로는 TTS 엔진 혜미로 제작된 것과 알람 소리샘 파일을 구해서 넣었음을 알린다. DFPlayer에 관한 이전 글 "DFplayer - 아두이노 사운드 모듈"을 읽었다는 전제하에 진행하겠다.

DF_test.alz
10.00MB
DF_test.a00
10.00MB
DF_test.a01
3.60MB

DFPlayer를 이용해서 문장으로 연결된 음성 파일을 재생하는 방법으로는 일반 폴더 내에 있는 음성파일을 재생시키는 명령어를 사용하고 재생 종료 시 DFPlayer가 보내주는 재생 종료 코드를 받아 문장을 이루는 다음 연결 파일을 재생시키는 방법을 이용하면 좀 더 간단하게 구현할 수 있다. 하지만 이럴 경우에는 일반 폴더에 음성용 파일이 들어가 있어서 만약 일반 사운드 파일을 재생할 경우 음성파일도 재생목록에 포함되어 사운드 파일만 재생할 수가 없게 된다. 또한 사운드 파일이 재생되고 있을 때에는 종료를 시키고 음성파일을 재생해야 돼서 문장이 완료된 뒤에는 재생 중이던 사운드 파일을 이어서 재생할 수가 없다. 따라서 ADVERT폴더에 음성파일을 위치시키고 ADVERT 끼워넣기 재생 기능을 이용하여 구현하기로 한다. 하지만 ADVERT 끼워넣기 재생은 단점이 있는데 일반 파일 재생 명령과 달리 재생 종료 시 DFPlayer는 종료 코드를 송신해주지 않는다. 종료 코드를 받지 못한다면 연결된 문장을 만들기 위해 언제 다음 음성파일을 재생시켜야 할지 알 수 없게 되는데 이는 각 문장의 종료시간을 측정하여 해결하기로 하였다. 여기에서 문장의 종료 시간은 음성파일의 재생 종료 시간이 아니라 음성파일 내의 필요한 문장의 종료 시간을 의미한다.

 

 

ADVERT 재생 명령은 ADVERT 파일이 재생 중일 때에도 새로운 ADVERT파일에 대해 연속하여 재생 명령을 줄 수 있고 잘 작동한다. 단, 앞선 ADVERT파일의 재생이 종료되기 전에 연이어 ADVERT 재생 명령을 해줘야만 DFPlaeyr는 ADVERT 재생이 한 번만 된 것으로 인식하고 ADVERT 종료 시 이전에 사운드 파일이 재생되고 있었다면 일시 정지되었던 시점부터 사운드 파일을 이어서 재생하게 된다.   

 

시간 값에 따른 ADVERT 폴더 내 음성파일 넘버링

 

시간 멘트: 한시 ~ 열두 시 :: 1001 hour.mp3 ~ 1012 hour.mp3 :: 1150밀리 초

십 단위 멘트: 십 ~ 오십 :: 1110 min.mp3 ~ 1150 min.mp3 :: 1050밀리 초

분단위 문장: 일분입니다 ~ 구분입니다 :: 1201 min_sentence.mp3 ~ 1209 min_sentence.mp3 :: 1700밀리 초

십 분 단위 문장: 십 분입니다 ~ 오십 분입니다. :: 1210 min_sentence.mp3 ~ 1250 min_sentence.mp3 :: 1700밀리 초

오전/오후: 1301 AM.mp3, 1302 PM.mp3 :: 850밀리 초

알람 중 끼워넣기 문장: 1401 warning_sentence.mp3 ~ 1404 belsori-ceongug-ileonaseyo.mp3

정시 알림: 2101 k1.mp3 ~ 2112 k12.mp3 :: 2200밀리 초

사용자 정시 알림: 2201 w1.mp3 ~ 2212 w12.mp3 :: 2200밀리 초

오전 1시 21분: 1301 -> 1001 -> 1120 -> 1201

오후 1시 30분: 1302 -> 1001 -> 1230

 

아래와 같이 DFPlayer 제어를 위해 코드를 추가해 주었다. 

#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]);
  }
}

// 볼륨 제어를 위한 변수 설정
uint8_t volume = 10;

// setup() 함수 추가
dfSerial.begin(9600); // DFplayer Serial
send_command(0x06, 0, 0, volume); // 볼륨 10

// 시리얼 모니터 이용 DFPlayer 제어를 위해 adjust_time() 함수에 추가
else if (temp == "test") speak_time();
else if (temp == "stop") send_command(0x16, 0, 0, 0); 
else if (temp == "play") send_command(0x0F, 0, 2, 6);
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); }
    
// 음성 문장 출력 사용자 함수
void speak_time() {
  send_command(0x06, 0, 0, 30); // 볼륨 30
  send_command(0x0F, 0, 2, 255);
  delay(50);
  int ad_num = 1302; 
  uint8_t ad_lsb = ad_num;
  uint8_t ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(850);
  ad_num = 1005; 
  ad_lsb = ad_num;
  ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(1150);
  ad_num = 1130; 
  ad_lsb = ad_num;
  ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(1050);
  ad_num = 1203; 
  ad_lsb = ad_num;
  ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(1700);
  send_command(0x06, 0, 0, volume);
}

arduino_clock_dfplayer.ino
0.01MB

더보기
#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 = 10;

void setup() {
  Serial.begin(9600);
  dfSerial.begin(9600); // DFplayer Serial
  send_command(0x06, 0, 0, volume); // 볼륨 10
  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();
}

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.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 == "test") speak_time();
    else if (temp == "stop") send_command(0x16, 0, 0, 0); 
    else if (temp == "play") send_command(0x0F, 0, 2, 6);
    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); }
    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 speak_time() {
  send_command(0x06, 0, 0, 30); // 볼륨 30
  send_command(0x0F, 0, 2, 255);
  delay(50);
  int ad_num = 1302; 
  uint8_t ad_lsb = ad_num;
  uint8_t ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(850);
  ad_num = 1005; 
  ad_lsb = ad_num;
  ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(1150);
  ad_num = 1130; 
  ad_lsb = ad_num;
  ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(1050);
  ad_num = 1203; 
  ad_lsb = ad_num;
  ad_msb = ad_num >> 8;
  send_command(0x13, 0, ad_msb, ad_lsb);
  delay(1700);
  send_command(0x06, 0, 0, volume);
}

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 (h == 1 && m == 1 && s == 10) { // NTP 동기화 시간
    }
  }
}

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 명령어 없음
}

상기 스케치를 아두이노에 업로드하고 시리얼 모니터의 전송 옵션을 새 줄로 변경한 다음 텍스트 입력 란에 "test"를 입력하고 엔터를 쳐보자.

 

speak_time() 사용자 함수에 코딩된 대로 "오후 다섯 시 삼십 삼분입니다."라는 문장을 들을 수 있다. 

세부 내용을 살펴보면 우선, 알림이므로 볼륨을 일정 값으로 지정해준다. 현재 30으로 세팅했다.

ADVERT 폴더 내의 파일을 재생하기 위해서는 선행조건이 필요하게 되는데, 그 조건은 일반 폴더 내의 사운드 파일이 재생 중일 때에만 ADVERT 폴더내의 파일 재생 명령을 인식한다는 것이다. 즉, 일반 폴더 내의 사운드 파일이 재생 중일 때에는 바로 ADVERT 폴더 내의 사운드 파일을 ADVERT 재생 명령어로 재생할 수 있으나 사운드 파일이 재생 중이지 않을 때에는 ADVERT 재생 명령을 DFPlayer에 전송해도 아무런 반응을 하지 않게 된다. 따라서 사운드 파일이 재생 중이지 않을 때에는 묵음이 저장된 파일을 먼저 재생시킨 다음 ADVERT 폴더 내의 음성 파일을 재생시켜야만 정상적으로 재생할 수 있다.  DFPlayer의 명령어 코드가 이해가 안 된다면 이전 글 "DFplayer - 아두이노 사운드 모듈"을 참조하기 바란다.

 

speak_time() 함수의 send_command(0x0F, 0, 2, 255); 코드에 의해 폴더 2의 묵음이 저장된 사운드 파일 255번을 먼저 재생한 뒤 ADVERT폴더 내 음성파일이 재생되게 되는데 재생 순서는 1302 -> 1005 -> 1130 -> 1230 순서대로 재생되고 종료 후에는 이전 볼륨으로 돌아가게 된다. 하지만 사운드 파일이 재생 중일 경우에 묵음 파일 2번 폴더 255 파일을 재생하게 되면 재생 중이던 사운드 파일은 정지를 하게 되고 음성파일이 종료된 후에 재생중이던 파일이 이어서 재생되지 않는 문제가 발생한다. 이를 해결하기 위해서는 현재 사운드 파일이 재생 중인지 아닌지를 DFPlayer 모듈에 질의를 하고 그 결과에 따라 send_command(0x0F, 0, 2, 255); 코드를 실행시킬지 말지를 결정해줘야만 사운드 파일이 재생 중일 때는 일시 정지 후 음성파일을 출력하고 음성파일 재생이 완료되면 이어서 사운드 파일이 재생되며, 사운드 파일이 재생 중이지 않을 때에는 음성파일만 출력되는 작동을 하게 된다. 이를 구현하기 위해 아래 코드를 추가 및 수정해 주었다.

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

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

// adjust_time() 함수 내 test 실행코드 수정
else if (temp == "test") speak = true;

// loop() 함수내 추가
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 정지중
    } 
  } 
}

void speak_time() { 
  if (speak == true) {  // 음성 출력 상태이면
    send_command(0x42, 0, 0, 0); // DFplayer에 현재 상태 질의
    speak = false; 
  } 
  if (status_check == true) { // 질의 및 수신이 완료된 상태이면
    send_command(0x06, 0, 0, 30); // 볼륨 30 
    if (played == false) { // 질의에 대한 수신값이 재생중이 아닐때만
      send_command(0x0F, 0, 2, 255); // 묵음 파일 재생
      delay(50); 
    } 
    int ad_num = 1302;  
    .
    .
    send_command(0x06, 0, 0, volume); 
    status_check = false; 
  } 
}

상기의 코드로는 연결된 음성의 재생에는 문제가 없으나, speak_time() 사용자 함수 내에 사용한 delay() 함수 때문에 시리얼 모니터에 표시되던 시계의 시간이 음성 출력 시작 시부터 종료 시까지 정지되는 문제가 발생하게 된다.

 

millis() 함수의 시간 값은 시리얼 모니터상 시계가 정지된 것과는 상관없이 계속 증가하고 있다. 하지만 speak_time() 함수 내에서 사용한 delay() 시간 4650 밀리초(약 4.7초) 동안에는 speak_time() 함수에서 빠져나가지 못해 loop() 함수가 작동하지 않게 되므로 millis() 함수를 이용한 초 증가 코드는 작동을 하지 않게 된다. if (millis() - start_time >= 1000) 조건을 만족하면, 1000밀리 초 이상이 되면 1초 증가하도록 짜인 코드 때문에 약 4.7초 뒤에 loop() 함수가 작동하게 되어도 초 증가는 1초만 계산되게 되어 3.7초 이상의 오차가 발생하게 될 것이다. 이를 해결하기 위해 speak_time() 함수 내의 delay() 함수를 millis() 함수를 이용하여 대체해 주기로 하자.  

 

 

이전 글 아두이노 - 디지털 도어락 예제에서도 delay() 함수 대체 방법에 대해 설명했었다. 그때 적용한 코드는 LED의 순차점멸에 이용을 했었는데 그때의 코드에는 눈에 보이지 않는 에러를 내포하고 있었다. delay() 대체 사용자 함수가 loop() 함수에 위치하고 플래그에 의해 제어가 되는데, 플래그가 true가 되어 시간 값을 갖는 카운터가 동작을 할때 빠르게 반복하는 loop() 함수에의해 한번만 작동하는게 아니라 여러번 작동을 하게 된다. 당시에는 출력을 하는 코드가 LED를 켜고 끄는 코드였기에 계속 켜든 계속 끄던지 상관이 없었지만, DFplayer에 연속된 명령어를 보내게 되면 DFplayer가 정상적인 반응을 하지않게 된다. 따라서 delay() 대체 시간값을 갖는 카운터 함수가 실행될때 한번만 실행되도록 해주어야만 오류가 발생하지 않는다. 한번만 실행되도록 하기위해 시간값을 갖는 카운터 함수 내에 카운터와 관련되어 아래 조건을 걸어주었다. 

if (sequence == 6) speak = true; 
else if (sequence == 21) speak = true; 
else if (sequence == 44) speak = true; 
else if (sequence == 65) speak = true; 
else if (sequence == 101) speak = true; 

아래 코드는 speak_time() 함수내 delay() 함수를 대체하기 위해 추가하고 수정한 코드이다.

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

void delayCount() { // 시간값을 갖는 카운터 함수
  if (delay_set == true) { 
    if (millis() - count_time >= 50) { // 시간 간격: 밀리초 
      count_time = millis(); 
      sequence++; // 시간값을 갖는 카운터
      if (sequence == 6) speak = true; // 실행코드를 한번만 실행토록 하는 조건
      else if (sequence == 23) speak = true; 
      else if (sequence == 46) speak = true; 
      else if (sequence == 67) speak = true; 
      else if (sequence == 101) speak = true; 
    } 
  } 
}

void speak_time() { 
  if (speak == true) { 
    if (sequence == 0 && status_check == false) { 
      send_command(0x42, 0, 0, 0); 
      sequence++; 
    } 
    if (status_check == true) { 
      if (sequence == 1) { 
        send_command(0x06, 0, 0, 30); // 볼륨 30 
        sequence++; 
      } 
      else if (sequence == 2 && played == true) { 
        sequence = 5; 
        delay_set = true; 
      } 
      else if (sequence == 2 && played == false) { 
        send_command(0x0F, 0, 2, 255); 
        sequence++; 
      } 
      else if (sequence == 3 && played == false) { 
        delay_set = true; 
        sequence++; 
      } 
      else if (sequence == 6) { 
        int ad_num = 1302;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 23) { 
        int ad_num = 1005;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 46) { 
        int ad_num = 1130;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 67) { 
        int ad_num = 1203;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 101) { 
        Serial.println("enter5"); 
        send_command(0x06, 0, 0, volume); 
        speak = false; 
        status_check = false; 
        delay_set = false; 
        sequence = 0; 
      } 
    } 
  } 
}

아래 코드를 업로드하고 시리얼 모니터에서 텍스트 명령어 "play, next, previous, stop"를 통해 사운드 파일을 제어하고 "test" 명령어를 통해 음성 출력을 테스트하고 아두이노 시계의 시간이 정지 없이 잘 표시되는지 살펴보자.

arduino_clock_dfplayer_millis.ino
0.01MB

더보기
#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 = 10;

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

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

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

void setup() {
  Serial.begin(9600);
  dfSerial.begin(9600); // DFplayer Serial
  send_command(0x06, 0, 0, volume); // 볼륨 10
  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();
  delayCount();
  speak_time();
  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 정지중
      } 
    } 
  }
}

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.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 == "test") speak = true;
    else if (temp == "stop") send_command(0x16, 0, 0, 0); 
    else if (temp == "play") send_command(0x0F, 0, 2, 6);
    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 == "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 == "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 delayCount() { // 시간값을 갖는 카운터 함수
  if (delay_set == true) { 
    if (millis() - count_time >= 50) { // 시간 간격: 밀리초 
      count_time = millis(); 
      sequence++; // 시간값을 갖는 카운터
      if (sequence == 6) speak = true; // 실행코드를 한번만 실행토록 하는 조건
      else if (sequence == 23) speak = true; 
      else if (sequence == 46) speak = true; 
      else if (sequence == 67) speak = true; 
      else if (sequence == 101) speak = true; 
    } 
  } 
}

void speak_time() { 
  if (speak == true) { 
    if (sequence == 0 && status_check == false) { 
      send_command(0x42, 0, 0, 0); 
      sequence++; 
    } 
    if (status_check == true) { 
      if (sequence == 1) { 
        send_command(0x06, 0, 0, 30); // 볼륨 30 
        sequence++; 
      } 
      else if (sequence == 2 && played == true) { 
        sequence = 5; 
        delay_set = true; 
      } 
      else if (sequence == 2 && played == false) { 
        send_command(0x0F, 0, 2, 255); 
        sequence++; 
      } 
      else if (sequence == 3 && played == false) { 
        delay_set = true; 
        sequence++; 
      } 
      else if (sequence == 6) { 
        int ad_num = 1302;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 23) { 
        int ad_num = 1005;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 46) { 
        int ad_num = 1130;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 67) { 
        int ad_num = 1203;  
        uint8_t ad_lsb = ad_num; 
        uint8_t ad_msb = ad_num >> 8; 
        send_command(0x13, 0, ad_msb, ad_lsb); 
        speak = false; 
      } 
      else if (sequence == 101) { 
        Serial.println("enter5"); 
        send_command(0x06, 0, 0, volume); 
        speak = false; 
        status_check = false; 
        delay_set = false; 
        sequence = 0; 
      } 
    } 
  } 
}

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 (h == 1 && m == 1 && s == 10) { // NTP 동기화 시간
    }
  }
}

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 명령어 없음
}

이제 음성 출력 코드를 시간에 맞춰 출력되도록 해보자. 시간에 맞추기 위해서는 고려해야 할 사항이 있다. 시간에 따라  음성파일의 번호를 계산하는 코드와 더불어 짧은 문장과 긴 문장을 구분하여 ADVERT 내 음성파일이 재생되도록 해야만 한다. 이를 구현하기 위해 아래의 speak_option() 사용자 함수를 작성해 주었다.  

bool short_sentence = false; // 짧은 문장, 긴문장 구분 플래그
bool sharp = false; // 정시알림 플래그

int am; // AM/PM ADVERT 음성파일 번호 저장 변수
int time_sharp; // 정시 알림 ADVERT 음성파일 번호 저장 변수
int hour_a;     // 시 ADVERT 음성파일 번호 저장 변수
int min_w;      // 십단위 ADVERT 음성파일 번호 저장 변수
int min_s;      // 분단위 ADVERT 음성파일 번호 저장 변수

bool speak_finished = true;  // 전체 음성 출력 종료 확인 플래그

void speak_option() { 
  if (m == 0) { // 분이 0 이면 
    sharp = true; // 정시
    if (h < 13) time_sharp = 2200 + h; // 시가 13이하 이면 음성파일 번호 2200 + h
    else time_sharp = 2200 + (h - 12); 
  } 
  else { 
    sharp = false; // 정시 아님
    if (h < 13) hour_a = 1000 + h; // 시가 13이하 이면 음성파일 번호 1000 + h
    else hour_a = 1000 + (h - 12); 
    if (h < 12) am = 1301; // 오전이면 
    else am = 1302; 
    int temp = m%10; // 분을 10으로 나눈 나머지가
    if (temp == 0 || m < 10) { // 0 이거나 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; // 전체 음성 출력 종료 확인 플래그
}

 

delayCount() 함수와 speak_time() 함수의 코드도 speak_option() 함수의 조건에 맞게 수정하였고 시간 값을 갖는 카운트를 이용한 시퀀스를 두 함수 모두 동시 변경(오류 방지용) 및 제어하기 위해 아래 변수를 추가하였으며, 계산된 ADVERT 음성파일 번호를 DFPlayer에 전송하도록 입력값을 갖는 사용자 함수 advert(int ad_num)를 추가해 주었다. 

uint8_t am_s = 6; // 오전, 오후 음성출력 시작값
uint8_t hour_a_s = 23; // 시 음성출력 시작값
uint8_t long_min_w_s = 46;     // 긴문장 십단위 음성출력 시작값
uint8_t long_min_s_s = 67;     // 긴문장 분단위 음성출력 시작값
uint8_t long_min_s_end = 100;  // 긴문장 종료값 및 볼륨 조절 
uint8_t short_min_s_s = 46;    // 짧은 문장 분단위 음성출력 시작값
uint8_t short_min_s_end = 76;  // 짧은 문장 종료값 및 볼륨 조절 
uint8_t time_sharp_s = 6;      // 정시 음성 출력 시작값
uint8_t time_sharp_end = 63;   // 정시 음성 출력 종료값 및 볼륨 조절 

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);
}

cal_time() 함수에 의해 정시가 되었을 경우, 음성 출력을 위해 아래의 코드를 추가해 주었다. 

if (speak_finished == true) { // 음성출력이 안되고 있을경우에
  if (m == 0 && s == 0) { // 0분 0초가 되면
    sharp = true; 
    if (h < 13) time_sharp = 2100 + h; 
    else time_sharp = 2100 + (h - 12); 
    time_sharp_end = 48; // cal_time() 함수에 의해 시작된 음성 출력 종료값 설정
    speak = true; 
  } 
}

 

 

아래 스케치를 업로드하고 정시 음성 출력을 확인하고 test 명령을 통해 출력되는 음성 출력과 play 명령으로 사운드를 재생시킨 다음 test 명령을 통해 음성 출력이 잘되는지 확인해 보자.

arduino_clock_dfplayer_millis_option.ino
0.01MB

더보기
#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;

void setup() {
  Serial.begin(9600);
  dfSerial.begin(9600); // DFplayer Serial
  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();
  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 정지중
      }
    }
  }
}

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.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 == "test") speak_option();
    else if (temp == "stop") send_command(0x16, 0, 0, 0); 
    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 = 6;
uint8_t hour_a_s = 23;
uint8_t long_min_w_s = 46;
uint8_t long_min_s_s = 67;
uint8_t long_min_s_end = 100;
uint8_t short_min_s_s = 46;
uint8_t short_min_s_end = 76;
uint8_t time_sharp_s = 6;
uint8_t time_sharp_end = 63;

void speak_time() {
  if (speak == true) {
    if (sequence == 0 && status_check == false) {
      send_command(0x42, 0, 0, 0);
      sequence++;
    }
    if (status_check == true) {
      if (sequence == 1) {
        send_command(0x06, 0, 0, 30); // 볼륨 30
        sequence++;
      }
      else if (sequence == 2 && played == true) {
        sequence = 5;
        delay_set = true;
      }
      else if (sequence == 2 && played == false) {
        send_command(0x0F, 0, 2, 255);
        sequence++;
      }
      else if (sequence == 3 && played == false) {
        delay_set = true;
        sequence++;
      }
      if (sharp == false) {
        if (sequence == am_s) { advert(am); 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 == 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) { advert(time_sharp); 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 = 63;
        }
      }
    }
  }
}

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 == 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 == 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 (speak_finished == true) {
      if (m == 0 && s == 0) {
        sharp = true;
        if (h < 13) time_sharp = 2100 + h;
        else time_sharp = 2100 + (h - 12);
        time_sharp_end = 48;
        speak = true;
      }
    }
  }
}

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 명령어 없음
}

이제 알람 설정 코드를 추가하고 사용자 입력 버튼(또는 센서) 코드도 추가하여, 설정시간이 되면 알람이 울리고 버튼으로 알람을 정지시키고 또한 버튼으로 현재 시간을 출력할 수 있도록 해보자. 아울러 음성 출력이 되지않는 시간을 정하여 필요한 시간에만 음성출력이 되도록 해보자.

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 pin = 7; // 버튼 핀

// adjust_time() 함수 추가 코드
else if (temp == "nsp") no_speak_sharp = !no_speak_sharp;
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;     // 알람 기동용 함수 시작
}

// cal_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 >= 20) { // 시간 간격: 밀리초
      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; 
      } 
    } 
  } 
}

// loop() 함수 추가 코드
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();         // 현재 시간 출력
  } 
}

아래 스케치를 업로드하고 7번 핀에 점퍼선을 연결하고 GND 핀에도 점퍼선을 연결한 뒤에 버튼 대신 사용해서 테스트를 해보자. 우선 7번 핀과 GND핀을 점핑시켜보고 시간 출력이 잘 되는지 확인해 보자. 다음 알람 테스트를 위해 시리얼 모니터에 "alarm"을 입력하고 "aset1301"을 입력하여 13시 1분 알람 시간을 설정한다. 시간이 됐을 때 알람이 울리는지 확인해 보자. 정지 시에는 stop을 입력하거나 7번 핀과 GND핀을 점핑시켜도 정지하게 된다.

arduino_clock_dfplayer_millis_option_final.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 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 = 6;
uint8_t hour_a_s = 23;
uint8_t long_min_w_s = 46;
uint8_t long_min_s_s = 67;
uint8_t long_min_s_end = 100;
uint8_t short_min_s_s = 46;
uint8_t short_min_s_end = 76;
uint8_t time_sharp_s = 6;
uint8_t time_sharp_end = 63;

void speak_time() {
  if (speak == true) {
    if (sequence == 0 && status_check == false) {
      send_command(0x42, 0, 0, 0);
      sequence++;
    }
    if (status_check == true) {
      if (sequence == 1) {
        send_command(0x06, 0, 0, 30); // 볼륨 30
        sequence++;
      }
      else if (sequence == 2 && played == true) {
        sequence = 5;
        delay_set = true;
      }
      else if (sequence == 2 && played == false) {
        send_command(0x0F, 0, 2, 255);
        sequence++;
      }
      else if (sequence == 3 && played == false) {
        delay_set = true;
        sequence++;
      }
      if (sharp == false) {
        if (sequence == am_s) { advert(am); 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 == 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) { advert(time_sharp); 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 = 63;
        }
      }
    }
  }
}

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 == 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 == 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 >= 20) { // 시간 간격: 밀리초
      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 명령어 없음
}

* 마지막 종료 문장 TTS 음성파일의 끝부분의 묵음 부분을 조금 더 길게 하는 게 좋을 거 같다. 이전 볼륨으로 돌아가는 명령 코드를 보내는 타이밍을 맞추기가 좀 더 쉬워질 것이다. 타이밍이 맞지 않으면 사운드 파일 재생 중 이어서 재생하기가 풀리게 된다. 현재 사용 중인 TTS 파일은 묵음 구간이 너무 짧아 ADVERT 재생 종료 전 이전 볼륨으로 돌아가는 명령어를 전송할 타이밍을 잡기가 쉽지 않았다.  

 

 

말하는 알람시계는 완성되었다. 시리얼 모니터에서 알람을 설정하고 시간을 맞출 수 있으나, 컴퓨터 연결 없이 외부 전원을 이용할 경우에는 설정을 할 수 없게 된다. 다음 편에서는 블루투스 모듈을 연결하고 안드로이드 앱을 통해 시간 동기화 및 알람 설정을 하고 DFPlayer의 사운드 파일 제어를 해보겠다. 

 

 

2019.0901 추가사항

 

상기코드로는 사운드 파일 재생중 현재 시간 출력 시 간헐적으로 오전/오후 음성을 건너 뛰는 현상이 발행하여 아래와 같이 음성출력 관련 변수 및 코드 speak_time(), delayCount()등을 수정해 주었다. 이전 코드는 시간 관련 음성 출력전에 알람용 볼륨 설정 명령을 DFPlayer에 보낸 후 50밀리 초 뒤에 음성 출력 명령을 보냈었지만, 이때 간헐적으로 DFPlayer에서 볼륨 설정 명령 때문에 음성 출력 명령을 제대로 처리하지 못하는 것같다. 이에 음성 출력 명령 을 보내고 100밀리 초 후에 볼륨 설정 명령을 보내도록 코드를 수정하였다.   

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; }
      }
    }
  }
}

 

2019.09.04 추가사항

12시 이후에 시간동기화 할 경우 오후 반영안되는 현상이 발견되어 cal_time() 함수 수정함.

if(meridian == true && hm == 12) { now_am = false; }
else if (meridian == true && hm > 12) { hm = hm - 12; }
else now_am = true;

수정 후

if(meridian == true && hm >= 12) { now_am = false; }
else now_am = true;
if (meridian == true && hm > 12) { hm = hm - 12; }

 

다음편 블루투스 연결 스마트폰 시스템 시간 동기화에서 수정된 전체 코드를 적용토록 하겠다. 

 

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 원격제어

 

 

+ Recent posts