반응형

ESP32 / ESP8266 아두이노 IDE 코어의 기본 라이브러리 time.h를 이용하여 NTP 서버와 시간 동기화를 시키고 각 시간 및 날짜, 요일 값을 각각 지정 변수에 저장시켜 크리스털 LCD나 기타 디스플레이 모듈에 출력할 수 있도록 하고, 필요시 사용자가 특정 시간으로 변경할 수 있도록 코드를 수정해 보자.  

 

1. ESP32

아두이노 IDE에서 파일 -> 예제 -> ESP32 -> Time -> SimpleTime를 실행한다. 

아래 코드는 기본 예제의 변수 const long  gmtOffset_sec = 3600;과 const int   daylightOffset_sec = 3600;를 timezone과 summertime 변수로 변경하고 단순화시킨 코드이다.

ESP32_SimpleTime_example.ino
0.00MB

#include <WiFi.h>
#include "time.h"

const char* ssid       = "YOUR_SSID";
const char* password   = "YOUR_PASS";

const char* ntpServer = "pool.ntp.org";
uint8_t timeZone = 9;
uint8_t summerTime = 0; // 3600

void printLocalTime() {
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}

void setup() {
  Serial.begin(115200);
  Serial.printf("Connecting to %s ", ssid);  //connect to WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }
  Serial.println(" CONNECTED");
  configTime(3600 * timeZone, 3600 * summerTime, ntpServer); //init and get the time
  printLocalTime();
  WiFi.disconnect(true);  //disconnect WiFi as it's no longer needed
  WiFi.mode(WIFI_OFF);
}

void loop() {
  delay(1000);
  printLocalTime();
}

상기 스케치에 사용 중인 와이파이 공유기의 SSID와 비밀번호를 입력해주고 아두이노에 업로드해준다.   

const char* ssid       = "YOUR_SSID";  // 공유기 wifi 접속 ID 
const char* password   = "YOUR_PASS";  // 비밀번호

와이파이가 연결되면 NTP 서버와 동기화가 되고 현재 시간이 시리얼 모니터에 출력된다. 

 

 

코드 분석

configTime(3600 * timeZone, 3600 * summerTime, ntpServer); //init and get the time

상기 코드는 NTP 서버에 연결하고 서버로부터 유닉스 UTC 값을 수신하여 시간 값에 반영하고 time.h에 의해 시간 값(10자리 UTC 값)은 매 1초마다 자동으로 증가한다(시계 작동 시작). NTP 서버로부터 값을 받지 못하면 시간 값은 1970년  1월 1일 09 : 00 : 00에서 증가하게 된다. 

 

void printLocalTime() { 
  struct tm timeinfo;  // 구조체 변수 선언
  if(!getLocalTime(&timeinfo)){ 
    Serial.println("Failed to obtain time"); 
    return; 
  } 
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); 
}

상기 코드는 구조체 변수 timeinfo를 선언하고 UTC형식 시간 값을 로컬 시간으로 변경해주는 함수와 변경된 로컬 시간을 서식에 맞게 출력해주는 코드이다.

                                 

시간 출력이 서식에 맞게 잘되고 있다. 하지만 상기 코드로는 ESP32를 리셋시켜야만 NTP 서버와 시간 동기화를 할 수 있어서 매일 1회 또는 정해진 시간마다 NTP 서버와 시간 동기화를 시킬 수가 없다.

또한, 크리스탈 LCD 등에 출력하기에는 서식 출력 코드를 사용하는데 제약이 있고 사용자 시간을 설정 역시 할 수 없다.

 

원하는 시간에 NTP 서버와 시간 동기화를 할 수 있도록 사용자 함수 get_NTP() 함수를 만들어 주었다. 만약 어떠한 이유로 동기화가 되지 않는다면 약 5초 정도 시간 동기화 시도 후 사용자 시간 설정 set_time() 함수가 실행되어 미리 설정해둔 시간 값을 시작 값으로 하여 시계가 작동하게 된다.  

void get_NTP() {     // NTP 서버 시간 동기화  
  configTime(3600 * timeZone, 3600 * summerTime, ntpServer);  // NTP 서버 접속 및 동기화 함수
  while(!getLocalTime(&timeinfo)){  // 로컬 시간 반영 함수의 리턴 값이 false이면, 시간 값없으면 
    Serial.println("Failed to obtain time");  // 메시지 표시 
    set_time();   // set time                 // 초기값으로 시간 설정 
    return; 
  } 
}

 

시, 분, 초, 년, 월, 일에 해당하는 변수의 값을 변경하고 set_time() 사용자 함수를 실행하면 시계의 시간을 원하는 시간으로 변경할 수 있다.  

void set_time() {   // 사용자 시간 설정 함수 
  struct tm tm;     // 사용자 시간 설정용 구조체 변수 선언 
  tm.tm_year = s_yy - 1900;  
  tm.tm_mon = s_MM - 1; 
  tm.tm_mday = s_dd; 
  tm.tm_hour = s_hh; 
  tm.tm_min = s_mm; 
  tm.tm_sec = s_ss; 
  time_t t = mktime(&tm);    // UTC 값으로 변환 
  printf("Setting time: %s", asctime(&tm)); // 설정 시간을 문자열로 출력 
  struct timeval now = { .tv_sec = t };     // 설정시간을 현재 시간에 반영  
  settimeofday(&now, NULL);  
}

 

시간 서식 출력 코드를 시, 분, 초 등으로 쪼개어 사용할 수 있도록 시간 출력용 사용자 함수 printLocalTime()를 만들어 주었다.  "time(&now)"는 시간 동기화가 되거나 또는 set_time() 함수에 의해 사용자 시간 설정이 되면 시계 라이브러리를 통해 자동으로 매 초마다 1씩 증가하는 10자리의 UTC 시간 값이다. 이렇게 증가하는 UTC 시간 값과 이전 시간 값을 저장해준 prevEpoch와 비교하여 다르면 시간 출력 코드를 실행하도록 해주었다. 즉 별도의 millis() 함수를 사용하지 않고도 1초가 증가할 때마다 조건을 만족하여 코드를 반복적으로 실행하게 된다.  

void printLocalTime() { 
  if (time(&now) != prevEpoch) { // 현재 UTC 시간 값과 이전 UTC 시간 값이 다르면  
    Serial.println(time(&now));  // 현재 UTC 시간 값 출력 
    getLocalTime(&timeinfo);     // 로컬 변경 함수 이용 UTC 시간 값 변경 
    int dd = timeinfo.tm_mday;       // 구조체 내 해당 값 가져오기 
    int MM = timeinfo.tm_mon + 1; 
    int yy = timeinfo.tm_year +1900; 
    int ss = timeinfo.tm_sec; 
    int mm = timeinfo.tm_min; 
    int hh = timeinfo.tm_hour; 
    int week = timeinfo.tm_wday; 
    Serial.print(week); Serial.print(". "); 
    Serial.print(yy); Serial.print(". "); 
    Serial.print(MM); Serial.print(". "); 
    Serial.print(dd); Serial.print(" "); 
    Serial.print(hh); Serial.print(": "); 
    Serial.print(mm); Serial.print(": "); 
    Serial.println(ss); 
    prevEpoch = time(&now); // 현재 UTC 시간 값을 저장하여 1초마다 실행되도록 함. 
  } 
}

 

ESP32 아두이노 IDE 코어의 time.h 라이브러리에 정의된 로컬 시간 관련 구조체 변수는 아래와 같다. 

struct tm { 
  int tm_sec; 
  int tm_min; 
  int tm_hour; 
  int tm_mday; 
  int tm_mon; 
  int tm_year; 
  int tm_wday; 
  int tm_yday; 
  int tm_isdst; 
#ifdef __TM_GMTOFF 
  long  __TM_GMTOFF; 
#endif 
#ifdef __TM_ZONE 
  const char *__TM_ZONE; 
#endif 
};

week값은 "0 = 일요일, 1 = 월요일, , , 6 = 토요일"이다.

 

시리얼 모니터에서 전송 옵션을 "새 글"로 하고 "1"을 입력하면 시간 설정 함수가 실행되고 "2"를 입력하면 NTP 서버 동기화가 실행되도록 시리얼 통신 제어 코드를 아래와 같이 작성해 주었다. 

  if(Serial.available() > 0){ 
    String temp = Serial.readStringUntil('\n'); 
    if (temp == "1") set_time();     // set time 
    else if (temp == "2") get_NTP(); // NTP Sync 
  }

 

아래 스케치는 상기 코드를 반영한 것이다.

ESP32_SimpleTime.ino
0.00MB

#include <WiFi.h>  // 와이파이 라이브러리
#include "time.h"  // 시계 라이브러리

const char* ssid       = "SK_WiFiGIGA40F7"; // 공유기 SSID
const char* password   = "1712037218";      // 공유기 비밀번호

const char* ntpServer = "pool.ntp.org";     // NTP 서버
uint8_t timeZone = 9;                       // 한국 타임존 설정
uint8_t summerTime = 0; // 3600             // 썸머타임 시간

int s_hh = 12;         // 시간 설정용 시 변수 및 초기값, < 0 조건 위해 자료형 int
int s_mm = 59;         // 시간 설정용 분 변수 및 초기값 
uint8_t s_ss = 45;     // 시간 설정용 초 변수 및 초기값
uint16_t s_yy = 2017;  // 시간 설정용 년 변수 및 초기값 
uint8_t s_MM = 11;     // 시간 설정용 월 변수 및 초기값 
uint8_t s_dd = 19;     // 시간 설정용 일 변수 및 초기값 

time_t now;          // 현재 시간 변수 
time_t prevEpoch;    // 이전 UTC 시간 변수
struct tm timeinfo;  // 로컬 시간 반영용 구조체 변수 선언

void get_NTP() {     // NTP 서버 시간 동기화 
  configTime(3600 * timeZone, 3600 * summerTime, ntpServer); // NTP 서버 접속 및 동기화 함수
  while(!getLocalTime(&timeinfo)){  // 로컬 시간 반영함수이 리턴 값이 false이면, 시간값 없으면
    Serial.println("Failed to obtain time");  // 메시지 표시
    set_time();   // set time                 // 초기값으로 시간 설정
    return;
  }
}

void set_time() {   // 사용자 시간 설정 함수
  struct tm tm;     // 사용자 시간 설정용 구조체 변수 선언
  tm.tm_year = s_yy - 1900; 
  tm.tm_mon = s_MM - 1;
  tm.tm_mday = s_dd;
  tm.tm_hour = s_hh;
  tm.tm_min = s_mm;
  tm.tm_sec = s_ss;
  time_t t = mktime(&tm);    // UTC 값으로 변환
  printf("Setting time: %s", asctime(&tm)); // 설정 시간을 문자열로 출력
  struct timeval now = { .tv_sec = t };     // 설정시간을 현재 시간에 반영 
  settimeofday(&now, NULL); 
}

void printLocalTime() {
  if (time(&now) != prevEpoch) { // 현재 UTC 시간 값과 이전 UTC 시간 값이 다르면 
    Serial.println(time(&now));  // 현재 UTC 시간 값 출력
    getLocalTime(&timeinfo);     // 로컬 변경함수이용 UTC 시간값 변경
    int dd = timeinfo.tm_mday;       // 구조체 내 해당값 가져오기
    int MM = timeinfo.tm_mon + 1;
    int yy = timeinfo.tm_year +1900;
    int ss = timeinfo.tm_sec;
    int mm = timeinfo.tm_min;
    int hh = timeinfo.tm_hour;
    int week = timeinfo.tm_wday;
    Serial.print(week); Serial.print(". ");
    Serial.print(yy); Serial.print(". ");
    Serial.print(MM); Serial.print(". ");
    Serial.print(dd); Serial.print(" ");
    Serial.print(hh); Serial.print(": ");
    Serial.print(mm); Serial.print(": ");
    Serial.println(ss);
    prevEpoch = time(&now); // 현재 UTC 시간 값을 저장하여 1초마다 실행되도록 함.
  }
}

void setup() {
  Serial.begin(115200);
  Serial.printf("Connecting to %s ", ssid); // 공유기 아이디 표시
  WiFi.begin(ssid, password);               // 와이파이 STA모드 시작 및 연결
  while (WiFi.status() != WL_CONNECTED) {   // 와이파이 연결이 안된 상태이면 
    delay(500);                             // 0.5초마다
    Serial.print(".");                      // '.' 표시
  }
  Serial.println(" CONNECTED");
  get_NTP();                                // NTP 서버 시간 동기화 시작
}

void loop() {
  if(Serial.available() > 0){
    String temp = Serial.readStringUntil('\n');
    if (temp == "1") set_time();     // set time
    else if (temp == "2") get_NTP(); // NTP Sync
  }
  printLocalTime();                  // 로컬 시간 출력
}

 

상기 코드에서는 매 초에 대한 오차값 보정을 해줄 수는 없다. 이전 글 아두이노 시계 예제, ESP01 WiFi 이용 시간 동기화 하기 는 자체 millis() 함수를 사용하여 시계를 구현했으므로 실제 초와 millis() 함수의 초값에 발생하는 오차를 측정하고 millis() 함수의 조건 범위를 조정하여 좀 더 정확한 시계를 구현할 수 있었다. 

 

 

ESP32의 경우 매 초의 오차값이 얼마인지는 확인해 보지 않았으나 만약 매 초의 오차값이 커서 보정이 필요하다면 상기 코드에 이전 글의 millis() 시계 코드를 접목시켜야 할 것이다.                                               

 

상기의 코드로도 시계 출력 및 사용자 시간 설정 등을 제어하는 데는 어떠한 문제도 없지만 아직 뭔가 부족한 것 같다. 매일 일정 간격으로 NTP 서버와 시간 동기화를 하고 있다고 할 때 와이파이의 연결이 끊어진 상태에서 NTP 서버와 동기화를 시도할 경우와 처음부터 와이파이 연결이 안 될 때 어떻게 할 것인가에 대한 코드 적용이 안되어 있는 것이다. 또한 NTP 서버와 동기화가 안되었을 경우에 대한 처리도 코드 적용이 안되어 있다. 

 

1. 와이파이 연결이 안 된 상태

setup() 함수 진입 시 10초간 대기하다 set_time() 함수를 실행하여 사용자 시간 값으로 시계를 출력한다.

이후 시리얼 모니터나 블루투스 연결을 통해 현재 시간을 설정해 주어야 한다.(해당 코드는 이전 글 아두이노시계 예제, ESP01 WiFi 이용 시간 동기화 하기말하는알람시계 - 블루투스 연결 및 시간 동기화, DFPlayer 제어를 참조하기 바란다)

 

  uint8_t wifi_check = 0; 
  while (WiFi.status() != WL_CONNECTED) { 
    delay(500); 
    Serial.print("."); 
    wifi_check ++; 
    if (wifi_check == 20) { 
      WiFi.disconnect(true); 
      WiFi.mode(WIFI_OFF); 
      break;                  // while 루프 빠져나감 
    } 
  } 
  if (wifi_check == 20) { 
    Serial.println("Failed wifi connection"); 
    set_time(); 
  }

 

2. 와이파이 연결 후 초기 NTP 서버 시간 동기화 실패 

configTime(3600 * timeZone, 3600 * summerTime, ntpServer);코드에 의한 NTP 서버와 시간 동기화에 실패할 경우 ESP32 아두이노 코어 라이브러리에 정의된 대로 getLocalTime(&timeinfo) 함수의 bool 값은 false가 되고 약 5초 동안 10 밀리초 간격으로 로컬 시간 값을 읽도록 시도하고 정해진 시간 안에 로컬 시간 값을 읽지 못하면 "Failed to obtain time"메시지를 출력하며 set_time() 함수를 실행하여 사용자 시간 값으로 시계를 출력한다. 이후는 1번과 동일한 과정을 거치면 된다. 

만약 NTP 서버와 시간 동기화에 성공한 경우에는 로컬 시간 값을 읽으면 bool 값은 true가 되어 메시지 출력과 set_time() 함수 실행을 건너뛰게 된다. 

 

  else { 
    Serial.println(" CONNECTED"); 
    configTime(3600 * timeZone, 3600 * summerTime, ntpServer); // NTP sync 
    while(!getLocalTime(&timeinfo)){                           // 5초 동안 딜레이 
      Serial.println("Failed to obtain time"); 
      set_time();                                              // set time 
      return; 
    } 
  }

 

3. 초기 진입 후 정해진 스케줄에 따라 NTP 서버 시간 동기화 실패할 경우 

NTP 서버 시간 동기화에 성공하거나 사용자 설정 시계가 표시되고 있는 경우에 시계는 작동하고 있을 것이다 이때 정해진 시간에 NTP 서버 시간 동기화를 시도했으나 실패했을 경우에는 NTP 서버 시간 동기화 직전 시간 값에 NTP 서버 시간 동기화 시도에 소요된 초 값을 더해서 다시 시계를 출력한다. 이 기능을 수행하기 위해서는 스케줄에 의한 NTP 서버 시간 동기화를 시도 할때의 현재 시간 값을 저장하고 시간 동기화 성공 및 실패 여부를 확인하기 위해 UTC 시간 값을 0으로 초기화 한 뒤에 NTP 서버 시간 동기화를 시도한다. 만약 5초 이내에 UTC 시간 값이 5 이상의 값이 되지 않는다면 시간 동기화에 실패한 것으로 판단하고 이전 시간에 5초를 더해서 시간을 계속 표시한다.  NTP 서버 동기화를  시도하는 5초 동안에는 총 3회에 걸쳐서 동기화를 시도하게 된다. 

만약 시간 동기화에 성공한다면 바로 그 시간을 시계에 반영한다. 

 

bool ntp_failed = false; 

void check_ntp_sync() {   
  s_dd = timeinfo.tm_mday;              // 현재 시간 값을 시간 설정 변수에 저장 
  s_MM = timeinfo.tm_mon + 1; 
  s_yy = timeinfo.tm_year +1900; 
  s_ss = timeinfo.tm_sec; 
  s_mm = timeinfo.tm_min; 
  s_hh = timeinfo.tm_hour; 
  Serial.println("check NTP sync"); 
  struct timeval now = { .tv_sec = 0 }; // UTC 시간 값 0으로 초기화 
  settimeofday(&now, NULL); 


void get_NTP() { 
  check_ntp_sync();           // 현재 시간 저장 및 UTC 시간 값 0으로 초기화 
  configTime(3600 * timeZone, 3600 * summerTime, ntpServer); // NTP sync 
  prevEpoch = time(&now); 
  Serial.println(prevEpoch);  // NTP sync value 
  while(prevEpoch < 5) {      // NTP sync가 안 된 동안
    if (time(&now) > 5) return;         // NTP sync가 성공했으면 빠져나간다
    else if (time(&now) != prevEpoch) { // 1초마다 3번 NTP sync 시도 
      if (time(&now) > 5) return;       // NTP sync가 성공했으면 빠져나간다
      else { 
        Serial.println(time(&now)); 
        if (time(&now) == 1) configTime(3600 * timeZone, 3600 * summerTime, ntpServer); 
        else if (time(&now) == 2) configTime(3600 * timeZone, 3600 * summerTime, ntpServer); 
        else if (time(&now) == 3) configTime(3600 * timeZone, 3600 * summerTime, ntpServer);  
        else if (time(&now) == 4) {             // NTP sync 3번 실패할 경우 이전 시간으로 복귀 
          Serial.println("Failed ntp sync");     
          ntp_failed = true; 
          set_time();                           // 저장된 시간 값으로 설정하기 
          return; 
        } 
      } 
      prevEpoch = time(&now); 
    } 
  } 


void set_time() { 
  struct tm tm; 
  tm.tm_year = s_yy - 1900; 
  tm.tm_mon = s_MM - 1; 
  tm.tm_mday = s_dd; 
  tm.tm_hour = s_hh; 
  tm.tm_min = s_mm; 
  tm.tm_sec = s_ss; 
  time_t t = mktime(&tm); 
  printf("Setting time: %s", asctime(&tm)); 
  if (!ntp_failed) {  // 시간 동기화 실패가 아닌 경우, 사용자에 의한 시간 설정 
    struct timeval now = { .tv_sec = t }; 
    settimeofday(&now, NULL); 
  } 
  else {              // 스케줄에 따른 NTP 서버 시간 동기화의 경우 
    struct timeval now = { .tv_sec = t + 5};  // NTP 시도에 걸린 시간 가산 
    settimeofday(&now, NULL); 
    ntp_failed = false; 
  } 
}

 

아래 스케치는 상기 코드를 모두 적용한 것이다.

ESP32_SimpleTime_sync_check.ino
0.00MB

#include <WiFi.h>
#include "time.h"

const char* ssid       = "SK_WiFiGIGA40F7";
const char* password   = "1712037218";
const char* ntpServer  = "pool.ntp.org";
uint8_t timeZone = 9;
uint8_t summerTime = 0; // 3600

int s_hh = 12;      // 시간 설정 변수 < 0 조건 위해 자료형 int
int s_mm = 59;
uint8_t s_ss = 45;
uint16_t s_yy = 2019;
uint8_t s_MM = 10;
uint8_t s_dd = 19;

time_t now;
time_t prevEpoch;
struct tm timeinfo;

bool ntp_failed = false;

void check_ntp_sync() {  
  s_dd = timeinfo.tm_mday;              // 현재 시간값을 시간 설정 변수에 저장
  s_MM = timeinfo.tm_mon + 1;
  s_yy = timeinfo.tm_year +1900;
  s_ss = timeinfo.tm_sec;
  s_mm = timeinfo.tm_min;
  s_hh = timeinfo.tm_hour;
  Serial.println("check NTP sync");
  struct timeval now = { .tv_sec = 0 }; // UTC 시간값 0으로 초기화
  settimeofday(&now, NULL);
}

void get_NTP() {
  check_ntp_sync();           // 현재 시간 저장 및 UTC 시간값 0으로 초기화
  configTime(3600 * timeZone, 3600 * summerTime, ntpServer); // NTP sync
  prevEpoch = time(&now);
  Serial.println(prevEpoch);  // NTP sync value
  while(prevEpoch < 5) {      // NTP sync가 안된동안
    if (time(&now) > 5) return;         // NTP sync가 성공했으면 빠져 나간다
    else if (time(&now) != prevEpoch) { // 1초마다 3번 NTP sync 시도
      if (time(&now) > 5) return;       // NTP sync가 성공했으면 빠져 나간다
      else {
        Serial.println(time(&now));
        if (time(&now) == 1) configTime(3600 * timeZone, 3600 * summerTime, ntpServer);
        else if (time(&now) == 2) configTime(3600 * timeZone, 3600 * summerTime, ntpServer);
        else if (time(&now) == 3) configTime(3600 * timeZone, 3600 * summerTime, ntpServer); 
        else if (time(&now) == 4) {             // NTP sync 3번 실패할 경우 이전 시간으로 복귀
          Serial.println("Failed ntp sync");    
          ntp_failed = true;
          set_time();                           // 저장된 시간값 으로 설정하기
          return;
        }
      }
      prevEpoch = time(&now);
    }
  }
}

void set_time() {
  struct tm tm;
  tm.tm_year = s_yy - 1900;
  tm.tm_mon = s_MM - 1;
  tm.tm_mday = s_dd;
  tm.tm_hour = s_hh;
  tm.tm_min = s_mm;
  tm.tm_sec = s_ss;
  time_t t = mktime(&tm);
  printf("Setting time: %s", asctime(&tm));
  if (!ntp_failed) {  // 시간 동기화 실패가 아닌경우, 사용자에 의한 시간 설정
    struct timeval now = { .tv_sec = t };
    settimeofday(&now, NULL);
  }
  else {              // 스케쥴에 따른 NTP 서버 시간동기화의 경우
    struct timeval now = { .tv_sec = t + 5};  // NTP 시도에 걸린 시간 가산
    settimeofday(&now, NULL);
    ntp_failed = false;
  }
}

void printLocalTime() {
  if (time(&now) != prevEpoch) {
    Serial.println(time(&now));  // UTC 시간 값 출력
    getLocalTime(&timeinfo);
    int dd = timeinfo.tm_mday;
    int MM = timeinfo.tm_mon + 1;
    int yy = timeinfo.tm_year +1900;
    int ss = timeinfo.tm_sec;
    int mm = timeinfo.tm_min;
    int hh = timeinfo.tm_hour;
    int week = timeinfo.tm_wday;
    Serial.print(week); Serial.print(". ");
    Serial.print(yy); Serial.print(". ");
    Serial.print(MM); Serial.print(". ");
    Serial.print(dd); Serial.print(" ");
    Serial.print(hh); Serial.print(": ");
    Serial.print(mm); Serial.print(": ");
    Serial.println(ss);
    prevEpoch = time(&now);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  uint8_t wifi_check = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    wifi_check ++;
    if (wifi_check == 20) {
      WiFi.disconnect(true);
      WiFi.mode(WIFI_OFF);
      break;                  // while 루프 빠져나감
    }
  }
  if (wifi_check == 20) {
    Serial.println("Failed wifi connection");
    set_time();
  }
  else {
    Serial.println(" CONNECTED");
    configTime(3600 * timeZone, 3600 * summerTime, ntpServer); // NTP sync
    while(!getLocalTime(&timeinfo)){                           // 5초 동안 딜레이
      Serial.println("Failed to obtain time");
      set_time();                                              // set time
      return;
    }
  }
}

void loop() {
  if(Serial.available() > 0){
    String temp = Serial.readStringUntil('\n');
    if (temp == "1") set_time();   // set time
    else if (temp == "2") get_NTP(); //NTP Sync
  }
  printLocalTime();
}

 

* 추가 시간 출력 코드: Serial.println(ctime(&now)); // 캐릭터 문자열로 시간값을 출력해주는 함수 및 코드

 

 

2. ESP8266

ESP8266 아두이노 IDE 코어에는 time.h 라이브러리를 이용한 기본 예제가 없다. 

상기의 ESP32 코드와 유사하며 단지, 와이파이용 라이브러리와 몇몇 함수 명칭이 다르다.  

 

#include  <ESP8266WiFi.h> // ESP8266 와이파이 라이브러리  

struct tm * timeinfo; // 로컬 시간 반영용 포인터 변수 선언

포인터 변수 선언에 따라 로컬 시간 값을 불러오는 방식에 변경이 생겼다. 

timeinfo = localtime(&now);  // 로컬 변경 함수 이용 UTC 시간 값 변경 
int dd = timeinfo->tm_mday;  // 구조체 내 해당 값 가져오기 
int MM = timeinfo->tm_mon + 1; 
int yy = timeinfo->tm_year +1900; 
int ss = timeinfo->tm_sec; 
int mm = timeinfo->tm_min; 
int hh = timeinfo->tm_hour; 
int week = timeinfo->tm_wday;

 

NTP 서버 동기화 시 동기화가 안될 경우 1970년이 출력된다. 1970년이면 사용자 설정 시간으로 변경하도록 코드를 변경해 주었다. 

void get_NTP() {     // NTP 서버 시간 동기화 
  configTime(3600 * timeZone, 3600 * summerTime, ntpServer); // NTP 서버 접속 및 동기화 함수 
  timeinfo = localtime(&now);                 // 로컬 시간 값 확인 
  while (timeinfo->tm_year +1900 == 1970) {   // 시간 동기화 안되어 있으면, 1970년 
    Serial.println("Failed to obtain time");  // 메시지 표시 
    set_time();   // set time                 // 초기값으로 시간 설정 
    localtime(&now);                          // 로컬 시간 반영 재실행 
    return; 
  } 
}

 

ESP8266에 맞게 수정한 코드이다.

ESP8266_SimpleTime.ino
0.00MB

#include <ESP8266WiFi.h> // 와이파이 라이브러리 
#include "time.h"  // 시계 라이브러리

const char* ssid       = "SK_WiFiGIGA40F7"; // 공유기 SSID
const char* password   = "1712037218";      // 공유기 비밀번호

const char* ntpServer = "pool.ntp.org";     // NTP 서버
uint8_t timeZone = 9;                       // 한국 타임존 설정
uint8_t summerTime = 0; // 3600             // 썸머타임 시간

int s_hh = 12;        // 시간 설정용 시 변수 및 초기값, < 0 조건 위해 자료형 int
int s_mm = 59;        // 시간 설정용 분 변수 및 초기값 
uint8_t s_ss = 45;    // 시간 설정용 초 변수 및 초기값 
uint16_t s_yy = 2017; // 시간 설정용 년 변수 및 초기값 
uint8_t s_MM = 11;    // 시간 설정용 월 변수 및 초기값 
uint8_t s_dd = 19;    // 시간 설정용 일 변수 및 초기값 

time_t now;           // 현재 시간 변수 
time_t prevEpoch;     // 이전 UTC 시간 변수
struct tm * timeinfo; // 로컬 시간 반영용 포인터 변수 선언

void get_NTP() {     // NTP 서버 시간 동기화
  configTime(3600 * timeZone, 3600 * summerTime, ntpServer); // NTP 서버 접속 및 동기화 함수
  timeinfo = localtime(&now);                 // 로컬 시간값 확인
  while (timeinfo->tm_year +1900 == 1970) {   // 시간 동기화 안되어 있으면, 1970년
    Serial.println("Failed to obtain time");  // 메시지 표시
    set_time();   // set time                 // 초기값으로 시간 설정
    localtime(&now);                          // 로컬 시간 반영 재실행
    return;
  }
}

void set_time() {   // 사용자 시간 설정 함수
  struct tm tm_in;     // 사용자 시간 설정용 구조체 변수 선언
  tm_in.tm_year = s_yy - 1900;
  tm_in.tm_mon = s_MM - 1;
  tm_in.tm_mday = s_dd;
  tm_in.tm_hour = s_hh;
  tm_in.tm_min = s_mm;
  tm_in.tm_sec = s_ss;
  time_t ts = mktime(&tm_in);                  // UTC 시간 값으로 변환
  printf("Setting time: %s", asctime(&tm_in)); // 설정 시간을 문자열로 출력
  struct timeval now = { .tv_sec = ts };       // 설정시간을 현재 시간에 반영 
  settimeofday(&now, NULL);
}

void printLocalTime() {
  if (time(&now) != prevEpoch) { // 현재 UTC 시간 값과 이전 UTC 시간 값이 다르면 
    Serial.println(time(&now));  // 현재 UTC 시간 값 출력
    timeinfo = localtime(&now);  // 로컬 변경함수이용 UTC 시간값 변경
    int dd = timeinfo->tm_mday;  // 구조체 내 해당값 가져오기
    int MM = timeinfo->tm_mon + 1;
    int yy = timeinfo->tm_year +1900;
    int ss = timeinfo->tm_sec;
    int mm = timeinfo->tm_min;
    int hh = timeinfo->tm_hour;
    int week = timeinfo->tm_wday;
    Serial.print(week); Serial.print(". ");
    Serial.print(yy); Serial.print(". ");
    Serial.print(MM); Serial.print(". ");
    Serial.print(dd); Serial.print(" ");
    Serial.print(hh); Serial.print(": ");
    Serial.print(mm); Serial.print(": ");
    Serial.println(ss); 
    prevEpoch = time(&now); // 현재 UTC 시간 값을 저장하여 1초마다 실행되도록 함.
  }
}

void setup() {
  Serial.begin(115200);
  Serial.printf("Connecting to %s ", ssid); // 공유기 아이디 표시
  WiFi.begin(ssid, password);               // 와이파이 STA모드 시작 및 연결
  while (WiFi.status() != WL_CONNECTED) {   // 와이파이 연결이 안된 상태이면 
      delay(500);                           // 0.5초마다
      Serial.print(".");                    // '.' 표시
  }
  Serial.println(" CONNECTED");
  get_NTP();                                // NTP 서버 시간 동기화 시작
}

void loop() {
  if(Serial.available() > 0){
    String temp = Serial.readStringUntil('\n');
    if (temp == "1") set_time();     // set time
    else if (temp == "2") get_NTP(); // NTP Sync
  }
  printLocalTime();                  // 로컬 시간 출력
}

 

관련 글

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

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

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

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

 

 

+ Recent posts