12주차 ‐ ESP32 OLED NTP서버 활용 - boguuu/SmartDevice_2025-1 GitHub Wiki

12주차 — ESP32 OLED NTP서버 활용

ESP32를 사용하여 NTP(Network Time Protocol) 서버에서 시간을 가져오고, OLED 디스플레이에 현재 시간을 표시하는 디지털 시계를 제작

준비물


  • ESP32
  • ESP32 확장 실드
  • OLED 디스플레이

회로연결


1

NTP (Network TimeProtocol) 서버


✅ NTP 개요

  • NTP(Network Time Protocol): 네트워크를 통해 컴퓨터 시스템 간 시간을 동기화하는 프로토콜
  • 개발자: David L. Mills (1980년대)
  • 활용처: 인터넷과 로컬 네트워크에서 광범위하게 사용됨

⚙️ 동작 원리

  • NTP 서버는 GPS나 원자 시계로 정확한 시간 유지
  • 클라이언트는 서버에 주기적으로 접속해 시간 정보를 받아 동기화
  • 정밀도: 밀리초 단위의 높은 정확도 가능
  • 네트워크 지연을 고려하여 시간 조정 수행

🏗️ NTP 계층 구조 (Stratum)

  • Stratum 0: GPS, 원자 시계 등 (직접 네트워크 연결 X)
  • Stratum 1: Stratum 0과 연결된 NTP 서버
  • Stratum 2 이상: 상위 Stratum 서버로부터 시간 수신
  • Stratum 15까지 존재, 숫자가 높을수록 정확도 낮아짐

🔁 동기화 과정

  1. 클라이언트 → 서버로 시간 요청
  2. 서버 → 클라이언트로 시간 전송
  3. 클라이언트가 시간 조정
  4. 주기적으로 반복

🎯 NTP의 중요성

  • 보안 로그 정확성 확보
  • 금융 거래 시간 신뢰성 보장
  • 분산 시스템 동기화 유지
  • 과학 실험 시간 기록에 필수

🌐 주요 NTP 서버

  • pool.ntp.org: 전 세계 분산형 서버
  • time.nist.gov: 미국 NIST 제공
  • time.windows.com: Microsoft 기본 서버

📝 결론

  • NTP는 시스템 간 정확하고 일관된 시간 유지를 가능하게 하여 보안, 거래, 시스템 운영의 기반을 강화하는 핵심 기술

동작 설명


1. 아두이노 IDE 라이브러리 설치


Adafruit_SSD1306 라이브러리와 Adafruit_GFX 라이브러리 두 가지 라이브러리를 설치

2

2. 코드 작성 - “Hello, World!”를 두 가지 크기의 글자로 번갈아 표시


#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
  Serial.begin(9600);

  // OLED 디스플레이 초기화
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("SSD1306 오류");
    while (true);
  }

  // OLED 디스플레이 클리어
  display.clearDisplay();
}

void loop() {
  // "Hello, World!"를 크기가 다른 세 개의 텍스트로 표시
  display.clearDisplay();
  
  // 크기 6의 폰트로 텍스트 표시
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("Hello, World!");

  // 크기 8의 폰트로 텍스트 표시
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 15);
  display.println("Hello, World");

  display.display();
  delay(2000);
}

코드 설명


🧾 헤더와 설정

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
  • 🖼️ Adafruit_GFX: 폰트, 도형, 좌표 같은 그래픽 기능 담당
  • 💻 Adafruit_SSD1306: OLED 디스플레이 제어 라이브러리 (SSD1306 칩 전용)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
  • 📏 OLED 디스플레이의 해상도 지정
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
  • 🔌 I²C 통신으로 SSD1306 제어
  • 🛠️ 리셋 핀은 사용 안 함 (1)

🚀 setup() 함수

Serial.begin(9600);
  • 🐞 시리얼 모니터 출력용 (디버깅용)
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
  Serial.println("SSD1306 오류");
  while (true);
}
  • ⚠️ OLED 초기화 실패 시 무한 루프
  • 🧠 주소 0x3C는 대부분의 I²C OLED에서 기본값
display.clearDisplay();
  • 🧽 디스플레이 화면 초기화

🔁 loop() 함수

display.clearDisplay();
  • 🧼 화면 지우고 새로 그림
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("Hello, World!");
  • 🔡 작은 폰트 (기본 크기)로 "Hello, World!" 출력
display.setTextSize(2);
display.setCursor(0, 15);
display.println("Hello, World");
  • 🔠 더 큰 폰트 (2배 확대)로 같은 문구 출력
display.display();
delay(2000);
  • 🖥️ 버퍼 내용을 실제 화면에 표시
  • ⏱️ 2초 동안 화면 유지 후 반복

3. 코드 업로드


3

4. 실행 결과


4

5. 코드 작성 - NTP서버 활용 OLED에 실시간 시계표시


#include <WiFi.h>  // WiFi 통신을 위한 라이브러리
#include <time.h>  // 시간과 관련된 함수를 위한 라이브러리

#include <Adafruit_GFX.h>      // 디스플레이를 위한 그래픽 라이브러리
#include <Adafruit_SSD1306.h>  // SSD1306 OLED 디스플레이를 위한 라이브러리
// SSD1306 디스플레이 객체 초기화
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire, -1);

const char* ssid = "Your_SSID";          // 여기에 사용하는 WiFi 네트워크 이름 (SSID)을 입력하세요
const char* password = "Your_Password";  // 여기에 사용하는 WiFi 네트워크 비밀번호를 입력하세요

int GMTOffset = 60 * 60 * 9;  // 시간 오프셋 설정, 한국은 UTC/GMT +9입니다.
int daylightOffset = 0;       // 국가에서 서머타임을 사용하는 경우 오프셋 값을 설정하세요.

const String weekDays[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };  // 요일 이름 배열

void setup() {
  Serial.begin(115200);  // 디버깅을 위한 시리얼 통신 시작

  // SSD1306 디스플레이 초기화
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 초기화 실패"));
    while (1)
      ;  // 디스플레이 초기화에 실패하면 프로그램 실행 중지
  }

  delay(2000);                  // 2초 동안 대기
  display.clearDisplay();       // 디스플레이 지우기
  display.setTextSize(1);       // 텍스트 크기를 1로 설정
  display.setCursor(0, 0);      // 커서 위치를 디스플레이 왼쪽 위 모서리로 설정
  display.setTextColor(WHITE);  // 텍스트 색상을 흰색으로 설정

  WiFi.begin(ssid, password);  // 제공된 SSID와 비밀번호를 사용하여 WiFi 네트워크에 연결
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting...");
  }

  Serial.println("Connected to Wi-Fi!");
  // 시간 설정을 위한 NTP 서버 설정
  configTime(GMTOffset, daylightOffset, "pool.ntp.org", "time.nist.gov");
}

void loop() {

  // 현재 시간 가져오기
  time_t rawtime = time(nullptr);
  struct tm* timeinfo = localtime(&rawtime);

  // 시리얼 모니터에 날짜 출력
  Serial.print(timeinfo->tm_mday);
  Serial.print("/");
  Serial.print(timeinfo->tm_mon + 1);
  Serial.print("/");
  Serial.print(timeinfo->tm_year + 1900);

  Serial.print(" ");

  // 시리얼 모니터에 시간 출력
  Serial.print("Time: ");
  Serial.print(timeinfo->tm_hour);
  Serial.print(":");
  Serial.print(timeinfo->tm_min);
  Serial.print(":");
  Serial.println(timeinfo->tm_sec);

  // OLED 디스플레이 초기화
  display.clearDisplay();
  display.setTextSize(3);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);

  // 시간 출력
  if (timeinfo->tm_hour < 10)
    display.print("0");
  display.print(timeinfo->tm_hour);

  display.print(":");

  if (timeinfo->tm_min < 10)
    display.print("0");
  display.print(timeinfo->tm_min);

  display.print(":");

  // 초 출력
  display.setTextSize(2);
  display.setCursor(102, 5);

  if (timeinfo->tm_sec < 10)
    display.print("0");
  display.print(timeinfo->tm_sec);

  // 날짜 출력
  display.setTextSize(1);
  display.setCursor(0, 25);
  display.print(timeinfo->tm_mday);
  display.print("/");
  display.print(timeinfo->tm_mon + 1);
  display.print("/");
  display.print(timeinfo->tm_year + 1900);

  display.print(" ");
  display.print(weekDays[timeinfo->tm_wday]);

  // 디스플레이에 표시
  display.display();

  delay(1000);
}

코드 설명


📦 라이브러리 포함

#include <WiFi.h>
#include <time.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
  • 📡 WiFi.h: ESP32를 WiFi에 연결할 수 있게 해줌
  • 🕐 time.h: NTP 서버에서 시간 가져올 때 사용
  • 🖼️ Adafruit_GFX + Adafruit_SSD1306: OLED 그래픽 출력 담당

🖥️ 디스플레이 객체 생성

Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire, -1);
  • 128×32 해상도의 OLED 객체 생성
  • I²C 방식 사용 (Wire), 리셋 핀 없음 (-1)

🌐 WiFi 정보 설정

const char* ssid = "Your_SSID";
const char* password = "Your_Password";
  • 🔐 연결할 WiFi의 이름과 비밀번호 입력

⏰ 시간 설정 관련 변수

int GMTOffset = 60 * 60 * 9;
int daylightOffset = 0;
  • ⌛ 한국 기준 GMT+9
  • 🌞 서머타임 미사용 (0)
const String weekDays[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  • 요일 문자열 배열

🔧 setup() 함수

Serial.begin(115200);
  • 시리얼 모니터 디버깅용
if (!display.begin(...)) {
  Serial.println(F("SSD1306 초기화 실패"));
  while (1);
}
  • 디스플레이 초기화 시 실패하면 멈춤
delay(2000);
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.setTextColor(WHITE);
  • 초기 설정 및 OLED 클리어
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting...");
}
Serial.println("Connected to Wi-Fi!");
  • 📶 WiFi 연결 시도 → 성공할 때까지 대기
configTime(GMTOffset, daylightOffset, "pool.ntp.org", "time.nist.gov");
  • 🌐 NTP 서버에서 시간 동기화 설정

🔁 loop() 함수

📥 현재 시간 가져오기

time_t rawtime = time(nullptr);
struct tm* timeinfo = localtime(&rawtime);
  • 시스템에서 현재 시간 정보 받아오기

🖨️ 시리얼에 시간 출력

Serial.print(timeinfo->tm_mday);
...
Serial.println(timeinfo->tm_sec);
  • 📆 날짜와 ⏱️ 시간 시리얼에 출력

🖥️ OLED 출력

⏱️ 시:분:초 출력

display.clearDisplay();
display.setTextSize(3);
display.setCursor(0, 0);

if (timeinfo->tm_hour < 10) display.print("0");
display.print(timeinfo->tm_hour);
display.print(":");
...
  • 10보다 작을 땐 앞에 0 붙여서 두 자리로 표현
  • 시, 분은 크게 (setTextSize(3)), 초는 작게 (setTextSize(2))

📆 날짜 + 요일 출력

display.setTextSize(1);
display.setCursor(0, 25);
display.print(timeinfo->tm_mday);
...
display.print(weekDays[timeinfo->tm_wday]);
  • 하단에 날짜와 요일 표시
display.display();
delay(1000);
  • 화면 갱신하고 1초 대기

📚 라이브러리 포함

#include <WiFi.h>  // ESP32의 Wi-Fi 기능 사용
#include <time.h>  // 시간 관련 함수 사용 (NTP 동기화)

#include <Adafruit_GFX.h>      // OLED 디스플레이를 위한 그래픽 라이브러리
#include <Adafruit_SSD1306.h>  // SSD1306 OLED 디스플레이 라이브러리
  • ESP32의 Wi-Fi 연결과 시간 동기화를 위해 WiFi.htime.h를 포함
  • OLED 화면 출력을 위해 Adafruit_GFX와 Adafruit_SSD1306를 포함

🖥️ OLED 객체 초기화

Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire, -1);
  • 128x32 픽셀 크기의 OLED 디스플레이 객체를 생성
  • I2C 인터페이스를 사용(Wire), 리셋 핀은 -1로 설정(사용 안 함).

🌐 Wi-Fi 및 시간 설정

const char* ssid = "Your_SSID";          // Wi-Fi 네트워크 이름
const char* password = "Your_Password";  // Wi-Fi 비밀번호

int GMTOffset = 60 * 60 * 9;  // UTC+9 (한국 표준시)
int daylightOffset = 0;       // 서머타임 오프셋 (한국은 사용 안 함)
  • 접속할 Wi-Fi의 SSID와 비밀번호를 지정
  • 시간대를 한국(UTC+9)으로 설정하고 서머타임은 비활성화

요일 배열

const String weekDays[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  • tm 구조체의 tm_wday 값(0~6)을 문자열 요일로 매핑하기 위해 배열을 준비

🔧 setup() 초기 설정

Serial.begin(115200);  // 디버깅용 시리얼 통신 시작
  • ESP32가 컴퓨터와 시리얼 통신(속도 115200bps)으로 디버깅 메시지를 출력할 수 있게 설정
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
  Serial.println(F("SSD1306 초기화 실패"));
  while (1);
}
  • OLED 초기화 시 실패하면 오류 메시지를 출력하고 무한 루프로 멈춤
delay(2000);                  // OLED 초기화 후 잠시 대기
display.clearDisplay();       // OLED 화면 초기화
display.setTextSize(1);       // 텍스트 크기 지정
display.setCursor(0, 0);      // 텍스트 위치 지정
display.setTextColor(WHITE);  // 텍스트 색상 지정
  • OLED를 초기화하고 글자 크기, 위치, 색상을 설정
WiFi.begin(ssid, password);  // Wi-Fi 연결 시작
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting...");
}
Serial.println("Connected to Wi-Fi!");
  • Wi-Fi 연결을 시도하며 연결 완료 시까지 "Connecting..." 출력 후 "Connected to Wi-Fi!" 출력
configTime(GMTOffset, daylightOffset, "pool.ntp.org", "time.nist.gov");
  • 지정된 NTP 서버에서 현재 시간을 동기화하도록 설정

🔁 loop() 반복 동작

time_t rawtime = time(nullptr);
struct tm* timeinfo = localtime(&rawtime);
  • 현재 시간을 time_t 형식으로 받아오고, 이를 tm 구조체(연,월,일,시,분,초 등으로 분리)로 변환
Serial.print(timeinfo->tm_mday); ... Serial.println(timeinfo->tm_sec);
  • 현재 날짜와 시간을 시리얼 모니터에 출력
display.clearDisplay();  // OLED 화면 지우기
display.setTextSize(3);  // 시간 표시용 큰 글자
display.setCursor(0, 0);
  • OLED 화면을 초기화하고 시간 표시용 큰 글자 크기로 설정

🕒 시/분/초 출력

if (timeinfo->tm_hour < 10) display.print("0");
display.print(timeinfo->tm_hour);
display.print(":");
if (timeinfo->tm_min < 10) display.print("0");
display.print(timeinfo->tm_min);
  • 시와 분이 한 자리수일 때 앞에 0을 붙여 가독성을 높임
display.setTextSize(2);
display.setCursor(102, 5);
if (timeinfo->tm_sec < 10) display.print("0");
display.print(timeinfo->tm_sec);
  • 초는 다른 위치(오른쪽)와 글자 크기로 출력

📅 날짜 및 요일 출력

display.setTextSize(1);
display.setCursor(0, 25);
display.print(timeinfo->tm_mday);
display.print("/");
display.print(timeinfo->tm_mon + 1);
display.print("/");
display.print(timeinfo->tm_year + 1900);
display.print(" ");
display.print(weekDays[timeinfo->tm_wday]);
  • 날짜(일/월/년)와 요일을 한 줄로 OLED 하단에 출력
display.display();  // OLED에 최종 출력
delay(1000);        // 1초마다 갱신
  • 변경된 내용을 OLED에 표시하고 1초마다 화면을 갱신

📌 요약

  • Wi-Fi로 연결하여 인터넷 NTP 서버에서 시간 정보를 가져옴
  • OLED에 현재 시각(시:분:초)과 날짜/요일을 1초 단위로 갱신해 출력

6. 코드 업로드


5

7. 결과 확인


6

⚠️ **GitHub.com Fallback** ⚠️