전자 종이 e-paper 디스플레이의 디지털 시계
이 튜토리얼에서는 전자 종이 디스플레이와 ESP32를 사용하여 디지털 시계를 만드는 방법을 알아봅니다. 또한 시계를 인터넷 시간 공급자(SNTP 서버)와 동기화하는 방법도 알아봅니다. 마지막으로 이러한 기능을 ESP의 딥슬립 기능과 결합하여 배터리 전원으로 실행 시간을 늘리는 방법을 알아봅니다.
필요한 부분부터 시작해 보겠습니다.
개요
필수 부품
2.9인치 전자종이 디스플레이
ESP32 lite
USB 데이터 케이블
Dupont 와이어 세트
브레드보드
전자종이 디스플레이
전자종이 연결 및 테스트
GxEPD2 라이브러리 설치
테스트 코드
전자종이에 디지털 시계 코드
라이브러리
상수
변수 및 객체
initDisplay 함수
initTime 함수
syncTime 함수
drawBackground 함수
drawTime 함수
setup 함수
결론
필수 부품
부품의 경우, 더 이상 사용되지 않지만 여전히 저렴하게 구입할 수 있는 오래된 ESP32 lite를 권장합니다. 사양이 개선된 후속 모델이 있습니다. 그러나 충분한 메모리가 있는 다른 ESP32, ESP8266도 작동합니다.
Makerguides.com은 Amazon.com에서 제품을 광고하고 링크하여 사이트에서 광고 수수료를 벌 수 있는 수단을 제공하도록 설계된 제휴 광고 프로그램인 Amazon Services LLC Associates Program에 참여하고 있습니다. Amazon Associate로서 우리는 적격 구매로 수익을 얻습니다.
전자종이 디스플레이
이 프로젝트에 사용된 전자종이 디스플레이는 296×128픽셀 해상도와 SPI 인터페이스가 있는 내장형 컨트롤러를 갖춘 2.9인치 디스플레이 모듈입니다.
이 모듈에는 4선 SPI에서 3선 SPI로 전환할 수 있는 작은 점퍼 패드/스위치가 있습니다. 여기서는 기본 4선 SPI를 사용하겠습니다.
디스플레이 모듈은 3.3V 또는 5V에서 작동하고, 0.01µA의 매우 낮은 수면 전류를 가지며, 새로 고침하는 동안 약 26.4mW만 소모합니다. 전자종이 디스플레이에 대한 자세한 내용은 다음 두 가지 튜토리얼을 참조하세요. 전자잉크 디스플레이에 Arduino 인터페이싱 및 전자종이 디스플레이의 기상 관측소.
전자종이 연결 및 테스트
먼저 전자종이의 기능을 연결하고 테스트해 보겠습니다. 다음 그림은 전원 및 SPI에 대한 전체 배선을 보여줍니다.
편의를 위해 모든 연결을 표로 정리했습니다. 디스플레이에 3.3V 또는 5V 전원을 공급할 수 있지만 ESP32-lite는 3.3V 출력만 있고 SPI 데이터 라인은 3.3V여야 합니다!
e-Paper display | ESP32 lite |
CS/SS | 5 |
SCL/SCK | 18 |
SDA/DIN/MOSI | 23 |
BUSY | 15 |
RES/RST | 2 |
DC | 0 |
VCC | 3.3V |
GND | G |
브레드보드를 사용하여 모든 것을 연결할 수 있습니다. 하지만 저는 실제로 Dupont 와이어로 디스플레이를 ESP32에 직접 연결했고 LiPo 배터리를 추가하여 배터리 전원으로 클록을 실행하는지 테스트했습니다.
GxEPD2 라이브러리 설치
e-Paper 디스플레이에 그림을 그리거나 쓰기 전에 두 개의 라이브러리를 설치해야 합니다. Adafruit_GFX 라이브러리는 공통적인 그래픽 기본 요소(텍스트, 점, 선, 원 등)를 제공하는 그래픽 라이브러리입니다. 그리고 GxEPD2 라이브러리는 SPI를 통해 e-Paper 디스플레이를 제어하는 그래픽 드라이버 소프트웨어를 제공합니다.
라이브러리를 평소와 같은 방식으로 설치하기만 하면 됩니다. 설치 후 라이브러리 관리자에 다음과 같이 표시되어야 합니다.
테스트 코드
라이브러리를 설치한 후 다음 테스트 코드를 실행하여 모든 것이 작동하는지 확인합니다.
#define ENABLE_GxEPD2_GFX 0
#include "GxEPD2_BW.h"
//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));
void setup() {
epd.init(115200, true, 50, false);
epd.setRotation(1);
epd.setTextColor(GxEPD_BLACK);
epd.setTextSize(2);
epd.setFullWindow();
epd.fillScreen(GxEPD_WHITE);
epd.setCursor(20, 20);
epd.print("Makerguides");
epd.display();
epd.hibernate();
}
void loop() {}
텍스트 코드는 "Makerguides"라는 텍스트를 인쇄하고 디스플레이가 약간 깜빡인 후(전체 새로 고침) 디스플레이에 표시되어야 합니다.
그렇지 않다면 문제가 있는 것입니다. 디스플레이가 올바르게 배선되지 않았거나 잘못된 디스플레이 드라이버가 선택되었을 가능성이 큽니다. 중요한 코드 줄은 다음과 같습니다.
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));
GxEPD2 라이브러리의 Readme에는 지원되는 모든 디스플레이가 나열되어 있으며 헤더 파일(예: GxEPD2.h)에서 세부 정보를 찾을 수 있습니다. 디스플레이에 맞는 디스플레이 드라이버를 찾으세요. 시행착오가 필요할 수 있습니다.
전자 종이에 디지털 시계를 위한 코드
위의 테스트 코드가 작동한다면 아래 코드에도 문제가 없을 것입니다. 인터넷 시간 서버(SNTP)와 시간을 자동으로 동기화하고 전자 종이 디스플레이에 시간과 날짜를 표시하는 디지털 시계를 위한 완전한 코드입니다.
디스플레이 업데이트 사이에 ESP32와 디스플레이는 배터리 전원을 보존하기 위해 딥 슬립 모드로 전환됩니다. 먼저 완전한 코드를 잠깐 살펴본 다음 세부 정보를 살펴보겠습니다.
#define ENABLE_GxEPD2_GFX 0
#include "GxEPD2_BW.h"
#include "Fonts/FreeSans12pt7b.h"
#include "Fonts/FreeSansBold24pt7b.h"
#include "WiFi.h"
#include "esp_sntp.h"
const char* SSID = "SSID";
const char* PWD = "PASSWORD";
const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char *DAYSTR[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
const char *MONTHSTR[] = { "Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
// W, H flipped due to setRotation(1)
const int W = GxEPD2_290_BS::HEIGHT;
const int H = GxEPD2_290_BS::WIDTH;
bool syncing = false;
RTC_DATA_ATTR uint16_t wakeups = 0;
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));
void initDisplay() {
bool initial = wakeups == 0;
epd.init(115200, initial, 50, false);
epd.setRotation(1);
epd.setTextSize(1);
epd.setTextColor(GxEPD_BLACK);
}
void initTime() {
setenv("TZ", TIMEZONE, 1);
tzset();
}
void syncTime() {
WiFi.begin(SSID, PWD);
while (WiFi.status() != WL_CONNECTED)
;
configTzTime(TIMEZONE, "pool.ntp.org");
syncing = true;
}
void drawBackground(const void* pv) {
epd.setFullWindow();
epd.fillScreen(GxEPD_WHITE);
}
void drawTime(const void* pv) {
static char buff[40];
struct tm t;
getLocalTime(&t);
epd.setPartialWindow(10, 10, W - 20, H - 20);
epd.setFont(&FreeSansBold24pt7b);
epd.setCursor(40, 60);
sprintf(buff, " %s %2d:%02d%s ",
DAYSTR[t.tm_wday],
t.tm_hour, t.tm_min,
syncing ? "*" : " ");
epd.print(buff);
epd.setFont(&FreeSans12pt7b);
epd.setCursor(55, 100);
sprintf(buff, " %s %02d-%02d-%04d ",
MONTHSTR[t.tm_mon],
t.tm_mday, t.tm_mon + 1, t.tm_year + 1900);
epd.print(buff);
}
void setup() {
initDisplay();
initTime();
if (wakeups % 50 == 0)
syncTime();
if (wakeups % 2000 == 0)
epd.drawPaged(drawBackground, 0);
wakeups = (wakeups + 1) % 100000;
epd.drawPaged(drawTime, 0);
epd.hibernate();
esp_sleep_enable_timer_wakeup(30 * 1000 * 1000);
esp_deep_sleep_start();
}
void loop() {}
ESP32에 코드를 업로드하면 아래와 같이 e-Paper에 시간과 날짜가 표시됩니다.
라이브러리
먼저 상수 ENABLE_GxEPD2_GFX를 0으로 정의합니다. 1로 설정하면 기본 클래스 GxEPD2_GFX가 매개변수로 디스플레이 인스턴스에 대한 포인터를 전달할 수 있습니다. 하지만 약 1.2k개의 코드를 더 사용하며 필요하지 않으므로 0으로 설정합니다.
#define ENABLE_GxEPD2_GFX 0
다음으로 흑백(BW) e-Paper 디스플레이를 위한 GxEPD2_BW.h 헤더 파일을 포함합니다. 3색 디스플레이가 있는 경우 GxEPD2_3C.h 또는 GxEPD2_4C.h를 포함합니다. 4색 디스플레이, 대신 7색 디스플레이의 경우 GxEPD2_7C.h를 사용합니다.
#include "GxEPD2_BW.h"
#include "Fonts/FreeSans12pt7b.h"
#include "Fonts/FreeSansBold24pt7b.h"
또한 시간과 날짜를 표시하는 데 사용되는 글꼴에 대한 두 개의 파일을 포함합니다. 시간을 더 크고 24pt 굵은 글꼴(FreeSansBold24pt7b)로 표시하고 날짜를 더 작고 7pt 글꼴(FreeSans12pt7b)로 표시하려고 합니다. AdaFruit GFX 글꼴에 대한 개요는 여기에서 확인할 수 있습니다.
마지막으로, WiFi.h와 esp_snt.h 라이브러리를 포함합니다. 이 라이브러리는 시계를 SNTP 인터넷 시간 서버와 동기화하는 데 필요합니다. 이에 대한 자세한 내용은 나중에 설명합니다.
#include "WiFi.h"
#include "esp_sntp.h"
상수
다음 세 개의 상수를 변경해야 합니다. 먼저 WiFi의 SSID와 PASSWORD, 그 다음에는 거주하는 TIMEZONE입니다.
const char* SSID = "SSID";
const char* PWD = "PASSWORD";
const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
위의 시간대 사양 "AEST-10AEDT,M10.1.0,M4.1.0/3"은 호주를 위한 것으로, 일광 절약 시간 조정이 적용된 호주 동부 표준시(AEST)에 해당합니다.
이 시간대 정의의 부분은 다음과 같습니다.
- AEST: 호주 동부 표준시
- -10: 협정 세계시(UTC)보다 10시간 앞선 UTC 오프셋
- AEDT: 호주 동부 일광 절약 시간
- M10.1.0: 일광 절약 시간제로의 전환은 10월 1일 일요일에 발생합니다.
- M4.1.0/3: 표준 시간으로의 전환은 4월 1일 일요일에 발생하며 UTC와 3시간 차이가 있습니다.
다른 시간대 정의는 Posix 시간대 데이터베이스를 참조하세요. 거기에서 찾은 문자열을 복사하여 붙여넣고 TIMEZONE 상수를 그에 맞게 변경하기만 하면 됩니다.
시간과 날짜 외에도 현재 요일과 월의 이름도 표시하려고 합니다. 다음 상수는 이러한 이름을 정의합니다. 언어를 변경하거나 더 긴 이름을 선택할 수 있습니다. 디스플레이에 맞는지 확인하기만 하면 됩니다.
const char *DAYSTR[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
const char *MONTHSTR[] = { "Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
마지막으로 디스플레이의 너비 W와 높이 H에 대한 상수를 정의합니다. 이는 주로 편의를 위한 것입니다. initDisplay 함수에서 디스플레이를 회전(setRotation(1))했기 때문에 너비와 높이가 뒤집힌다는 점에 유의하세요.
// W, H flipped due to setRotation(1)
const int W = GxEPD2_290_BS::HEIGHT;
const int H = GxEPD2_290_BS::WIDTH;
변수와 객체
다음으로 몇 가지 전역 변수와 객체를 정의합니다.
bool syncing = false;
RTC_DATA_ATTR uint16_t wakeups = 0;
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));
syncing 변수는 시계가 현재 SNTP 서버와 시간을 동기화하고 있는지 여부를 나타냅니다. 동기화가 발생하면 디스플레이에 시간 뒤에 '*' 문자가 표시됩니다. 이 변수가 사용되는 drawTime() 함수를 살펴보세요. 꼭 필요한 것은 아니지만 시간 동기화가 실제로 작동하는지 디버깅하는 데 좋습니다.
wakeups 변수는 ESP32가 딥슬립에서 깨어날 때마다 증가합니다. 디스플레이를 완전히 새로 고칠지 아니면 시간을 동기화할지 결정하는 데 사용됩니다. setup() 함수를 살펴보세요. 이 함수가 사용되는 곳입니다.
epd 객체는 디스플레이 객체(e-paper 디스플레이)를 정의합니다. 이 객체는 사용하는 e-Paper 디스플레이와 일치하도록 정의해야 합니다. 예제는 GxEPD2 라이브러리의 Readme와 GxEPD2.h 파일을 참조하세요.
initDisplay 함수
initDisplay 함수는 디스플레이를 초기화하고 디스플레이 방향을 가로로, 텍스트 크기를 1로, 텍스트 색상을 검은색으로 설정합니다.
void initDisplay() {
bool initial = wakeups == 0;
epd.init(115200, initial, 50, false);
epd.setRotation(1);
epd.setTextSize(1);
epd.setTextColor(GxEPD_BLACK);
}
initial 변수가 true이면 ESP32가 딥슬립에서 깨어날 때 e-Paper 디스플레이가 전체 새로 고침을 수행합니다. 이를 방지하려면 initial 변수를 false로 설정해야 합니다. 하지만 우리는 첫 번째 웨이크업 이후에만 이 작업을 하려고 하는데, 이것이 바로 웨이크업 == 0이 달성하는 것입니다.
initTime 함수
initTime 함수는 TIMEZONE을 설정할 뿐입니다. ESP32가 웨이크업될 때마다 이 함수가 실행되도록 해야 합니다. 그렇지 않으면 시계가 꺼집니다.
void initTime() {
setenv("TZ", TIMEZONE, 1);
tzset();
}
syncTime 함수
syncTime 함수는 WiFi 연결을 생성한 다음 configTzTime()을 호출하여 ESP32의 내부 시계를 SNTP 서버 "pool.ntp.org"와 동기화합니다.
다른 SNTP 서버 또는 여러 SNTP 서버를 지정하여 연결할 수 있습니다. 자세한 내용은 ESP32 시계를 SNTP 서버와 동기화하는 방법 튜토리얼을 참조하세요.
void syncTime() {
WiFi.begin(SSID, PWD);
while (WiFi.status() != WL_CONNECTED)
;
configTzTime(TIMEZONE, "pool.ntp.org");
syncing = true;
}
또한 동기화 변수를 true로 설정했는데, 이는 drawTime에서 시간 동기화가 수행되었음을 보여주는 데 사용됩니다. 다음 웨이크업(30초마다)에서 동기화 변수는 다시 false로 설정됩니다.
drawBackground 함수
drawBackground 함수는 e-Paper 디스플레이의 전체 새로 고침(setFullWindow)을 수행하고 화면을 흰색으로 채웁니다. 거의 변경되지 않는 정적 텍스트, 그래픽 또는 기타 정보를 추가할 수도 있습니다.
void drawBackground(const void* pv) {
epd.setFullWindow();
epd.fillScreen(GxEPD_WHITE);
}
전체 새로 고침은 느리고 깜빡임이 많기 때문에 가끔씩만 호출됩니다. 기본적으로 부분 새로 고침으로 인한 잔상이 "번인"되는 것을 방지하기 위해서입니다. Waveshare 매뉴얼에 이에 대한 자세한 정보가 있습니다.
drawTime 함수
drawTime 함수는 부분 새로 고침(setPartialWindow)을 수행하는데, 이는 전체 새로 고침보다 훨씬 빠르고, 가장 중요한 것은 깜빡임 없이 콘텐츠를 업데이트한다는 것입니다.
void drawTime(const void* pv) {
static char buff[40];
struct tm t;
getLocalTime(&t);
epd.setPartialWindow(10, 10, W - 20, H - 20);
epd.setFont(&FreeSansBold24pt7b);
epd.setCursor(40, 60);
sprintf(buff, " %s %2d:%02d ",
DAYSTR[t.tm_wday],
t.tm_hour, t.tm_min,
syncing ? "*" : " ");
epd.print(buff);
epd.setFont(&FreeSans12pt7b);
epd.setCursor(55, 100);
sprintf(buff, " %s %02d-%02d-%04d ",
MONTHSTR[t.tm_mon],
t.tm_mday, t.tm_mon + 1, t.tm_year + 1900);
epd.print(buff);
}
단순히 로컬 시간을 가져온 다음 화면의 특정 커서 위치에 다른 글꼴로 시간 및 날짜 정보를 인쇄합니다. 다른 시간 정보나 추가 시간 정보를 인쇄하려는 경우, 예를 들어 초 여기에 tm 구조체 데이터 유형에서 사용할 수 있는 모든 값이 있습니다.
Member Type Meaning Range
tm_sec int seconds after the minute 0-61*
tm_min int minutes after the hour 0-59
tm_hour int hours since midnight 0-23
tm_mday int day of the month 1-31
tm_mon int months since January 0-11
tm_year int years since 1900
tm_wday int days since Sunday 0-6
tm_yday int days since January 1 0-365
tm_isdst int Daylight Saving Time flag
setup 함수
setup 함수는 ESP32가 깨어날 때마다 실행됩니다. 위에서 설명한 대로 디스플레이를 초기화하고 시간대를 설정하는 것으로 시작합니다.
void setup() {
initDisplay();
initTime();
if (wakeups % 50 == 0)
syncTime();
if (wakeups % 2000 == 0)
epd.drawPaged(drawBackground, 0);
wakeups = (wakeups + 1) % 100000;
epd.drawPaged(drawTime, 0);
epd.hibernate();
esp_sleep_enable_timer_wakeup(30 * 1000 * 1000);
esp_deep_sleep_start();
}
그 후 ESP32가 wakeups 변수에서 계산된 빈도에 따라 다른 동작을 수행합니다.
구체적으로, 50번째 웨이크업마다 시간을 동기화합니다. 딥슬립 기간이 30초로 설정되어 있으므로 30초 * 50 / 60초 = 25분마다 동기화한다는 의미입니다. 이를 변경할 수 있지만 동기화 빈도가 높을수록 배터리 소모량이 높아집니다. 반면에 일광 절약 시간과 표준 시간 간의 전환을 놓치지 않으려면 최소한 한 시간에 한 번은 동기화해야 합니다.
if (wakeups % 50 == 0)
syncTime();
2000번의 웨이크업마다 전체 새로 고침(drawBackground)을 수행하여 시간과 날짜(drawTime)를 그리는 부분 새로 고침 작업으로 인해 남은 잔상을 제거합니다. 다시 말하지만, 이 작업을 더 자주 하거나 덜 자주 할 수 있습니다. 현재 상태에서는 30초 * 2000 / 60초 / 60분으로 16.6시간마다 전체 새로 고침이 발생합니다.
if (wakeups % 2000 == 0)
epd.drawPaged(drawBackground, 0);
정수 오버런을 피하기 위해 재설정하기 전에 최대 100000까지 웨이크업을 증가시킵니다. 웨이크업 변수는 RTC 메모리에 저장되고 딥슬립 상태에서도 값을 유지합니다.
웨이크업 = (웨이크업 + 1) % 100000;
마지막으로 drawTime을 호출하여 시간과 날짜 표시를 새로 고친 다음 배터리 전원을 절약하기 위해 e-Paper를 최대 절전 모드로 전환합니다.
그런 다음 ESP32를 30초 동안 딥슬립 상태로 전환합니다. 즉, 디스플레이가 30초마다 업데이트됩니다. 예를 들어 45초와 같이 조금 더 느리게 하거나 10초마다 조금 더 빠르게 할 수 있습니다. 이전과 마찬가지로 매우 반응성 있는 시간 표시와 배터리 전원 보존 사이에서 균형을 이룹니다.
웨이크업 및 새로 고침 간격은 Waveshare 매뉴얼의 e-Paper 디스플레이 권장 사항을 따르도록 선택되었습니다. 자세한 내용은 e-Paper 디스플레이 부분 새로 고침 자습서도 참조하세요.
결론
이 튜토리얼에서는 SNTP 서버와 시간을 동기화하고 e-Paper 디스플레이에 항상 정확한 시간과 날짜를 표시하는 디지털 시계를 만드는 방법을 배웠습니다. 이 시계는 ESP32의 딥슬립 기능을 활용하여 전력 소비를 줄입니다. 이를 통해 배터리로 이 시계를 오랫동안 실행할 수 있습니다.
제안된 ESP32 LOLIN Lite를 사용하면 충전식 LiPo 배터리를 연결하는 것이 간단합니다. MAX1704X로 배터리 수준을 모니터링하고 충전 수준을 시각화하기 위해 작은 배터리 기호를 표시하는 것도 좋습니다.
디지털 시계의 또 다른 일반적인 확장 기능은 주변 온도 또는 날씨 데이터를 표시하는 것입니다. 자세한 내용은 e-Paper 디스플레이 튜토리얼의 기상 관측소를 참조하세요.
디지털 시계 대신 아날로그 시계를 원하시면 이 튜토리얼과 매우 유사한 e-Paper 디스플레이의 아날로그 시계 튜토리얼을 참조하세요.
즐거운 땜질 ; )
'개발자 > 부품' 카테고리의 다른 글
4채널 릴레이 드라이버 회로 및 PCB 설계 (0) | 2024.11.22 |
---|---|
ULN2803 및 릴레이용 전류 제한기 저항을 계산하는 방법은 무엇인가요? (0) | 2024.11.22 |
Meshtastic T-Beam ESP32 LoRa 개발 보드, 433, 868, 915MHz LoRaWAN 모듈 회로, WiFi, 블루투스, GPS, OLED (1) | 2024.11.16 |
전자 종이 e-paper 디스플레이의 기상 관측소 (6) | 2024.11.15 |
S8550 트랜지스터 데이터시트, 핀아웃, 회로 및 용도 (0) | 2024.11.13 |
9mm 초소형 시그널 피에조 부저(904RP/A) (0) | 2024.11.12 |
전자 종이 e-Paper 디스플레이 온도 플로터 (1) | 2024.11.12 |
AHT-10 상대 습도 온도 센서 (1) | 2024.11.11 |
더욱 좋은 정보를 제공하겠습니다.~ ^^