본문 바로가기

카테고리 없음

전자 종이 e-paper 디스플레이의 아날로그 시계

반응형

 

 

전자 종이 디스플레이의 아날로그 시계

 

이 튜토리얼에서는 4.2인치 전자 종이 디스플레이와 ESP32를 사용하여 아날로그 시계를 만들어 보겠습니다.

 

 

시계는 인터넷 시간 공급자(SNTP 서버)와 시간을 동기화합니다. 또한 ESP32와 디스플레이는 디스플레이 업데이트 사이에 절전 모드로 전환되어 배터리 전원으로 시계의 실행 시간을 늘립니다.

 

필요한 부분부터 시작해 보겠습니다.

 

개요

필수 부품

4.2인치 전자종이 디스플레이

ESP32 lite

USB 데이터 케이블

Dupont 와이어 세트

브레드보드

전자종이 디스플레이

전자종이 연결 및 테스트

GxEPD2 라이브러리 설치

테스트 코드

전자종이의 아날로그 시계 코드

라이브러리

상수

변수 및 객체

initDisplay 함수

setTimezone 함수

syncTime 함수

printAt 함수

polar2cart 함수

drawClockFace 함수

drawTriangle 함수

drawClockHands 함수

drawDateDay 함수

drawClock 함수

setup 및 loop 함수

결론

 

필수 부품

 

저는 ES32 lite를 마이크로프로세서로 사용하고 있습니다. 저렴하고 배터리 충전 인터페이스가 있어서 LiPo 배터리로 시계를 실행할 수 있기 때문입니다. 충분한 메모리가 있는 다른 ESP32 또는 ESP8266도 작동하지만 배터리 충전 인터페이스가 있는 제품을 사용하는 것이 좋습니다.

 

4.2인치 전자종이 디스플레이

ESP32 lite Lolin32

USB 데이터 케이블

Dupont 와이어 세트

브레드보드

 

Makerguides.com은 Amazon.com에서 제품을 광고하고 링크하여 사이트에서 광고 수수료를 벌 수 있는 수단을 제공하도록 설계된 제휴 광고 프로그램인 Amazon Services LLC Associates Program에 참여하고 있습니다. Amazon Associate로서 우리는 적격 구매로 수익을 얻습니다.

 

전자종이 디스플레이

 

이 프로젝트에 사용된 전자종이 디스플레이는 400×300픽셀 해상도, 4단계 회색 레벨, 0.4초의 부분적 새로 고침 시간, SPI 인터페이스가 있는 내장형 컨트롤러를 갖춘 4.2인치 디스플레이 모듈입니다.

 

4.2인치 전자종이 디스플레이 모듈의 앞면과 뒷면

 

 

모듈 뒷면에는 4선 SPI에서 3선 SPI로 전환하기 위한 작은 점퍼 패드/스위치가 있습니다. 여기서는 기본 4선 SPI를 사용할 것입니다. 따라서 아무것도 변경할 필요가 없습니다.

 

SPI 인터페이스와 컨트롤러가 있는 디스플레이 모듈 뒷면

 

 

디스플레이 모듈은 3.3V 또는 5V에서 작동하고, 0.01µA의 매우 낮은 수면 전류를 가지며, 새로 고침하는 동안 약 26.4mW만 소모합니다. 전자종이 디스플레이에 대한 자세한 내용은 일반적으로 다음 두 가지 튜토리얼을 참조하세요. Arduino를 전자잉크 디스플레이에 인터페이싱하기 및 전자종이 디스플레이의 기상 관측소.

 

전자종이 연결 및 테스트

 

먼저 전자종이의 기능을 연결하고 테스트해 보겠습니다. 다음 그림은 전원 및 SPI에 대한 전체 배선을 보여줍니다.

 

SPI를 통해 4.2인치 전자종이를 ESP32에 연결

 

 

편의를 위해 모든 연결부를 표로 정리했습니다. 디스플레이에 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 배터리를 추가하여 배터리 전원으로 시계를 실행하는지 테스트했습니다. 아래 그림은 제 설정이 어떻게 생겼는지 보여줍니다.

 

전자종이와 LiPo 배터리로 배선된 ESP32

 

 

 

GxEPD2 라이브러리 설치

 

e-Paper 디스플레이에 그림을 그리거나 글을 쓰려면 두 개의 라이브러리를 설치해야 합니다. Adafruit_GFX 그래픽 라이브러리는 일반적인 그래픽 기본 요소(텍스트, 점, 선, 원 등)를 제공합니다. 그리고 GxEPD2 라이브러리는 e-Paper 디스플레이용 그래픽 드라이버 소프트웨어를 제공합니다.

 

라이브러리를 평소와 같은 방식으로 설치하기만 하면 됩니다. 설치 후 라이브러리 관리자에 다음과 같이 표시됩니다.

 

라이브러리 관리자의 Adafruit_GFX 및 GxEPD2 라이브러리

 

 

테스트 코드

 

라이브러리를 설치한 후 다음 테스트 코드를 실행하여 모든 것이 작동하는지 확인합니다.

 

#include "GxEPD2_BW.h"

//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(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(90, 190);
  epd.print("Makerguides");  
  epd.display();
  epd.hibernate();
}

void loop() {}

 

 

텍스트 코드는 "Makerguides"라는 텍스트를 인쇄하고 디스플레이가 약간 깜빡인 후(전체 새로 고침) 아래와 같이 디스플레이에 표시되어야 합니다.

 

4.2인치 전자종이 디스플레이에서 테스트 출력

 

 

그렇지 않다면 문제가 있는 것입니다. 디스플레이가 올바르게 배선되지 않았거나 잘못된 디스플레이 드라이버가 선택되었을 가능성이 큽니다. 중요한 코드 줄은 4.2인치 디스플레이 드라이버를 지정하는 다음 줄입니다.

 

GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));

 

 

GxEPD2 라이브러리의 Readme에는 지원되는 모든 디스플레이가 나열되어 있으며 헤더 파일에서 구체적인 내용을 찾을 수 있습니다(예: GxEPD2.h. 디스플레이에 맞는 디스플레이 드라이버를 찾으세요. 시행착오가 필요할 수 있습니다.

 

전자 종이에 아날로그 시계를 위한 코드

 

다음 코드는 인터넷 시간 서버(SNTP)와 시간을 자동으로 동기화하고 4.2인치 전자 종이 디스플레이에 시간과 날짜를 표시하는 아날로그 시계의 전체 코드입니다. 콘텐츠가 새로 고쳐지는 사이에 디스플레이와 ESP32는 배터리 전원을 보존하기 위해 절전 모드에 있습니다. 먼저 코드를 잠깐 살펴본 다음 세부 사항을 살펴보겠습니다.

 

#include "GxEPD2_BW.h"
#include "Fonts/FreeSans9pt7b.h"
#include "Fonts/FreeSansBold9pt7b.h"
#include "WiFi.h"
#include "esp_sntp.h"

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* SSID = "SSID";
const char* PWD = "PASSWORD";

const char* DAYSTR[] = { 
  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 
};

// W, H flipped due to setRotation(1)
const int H = GxEPD2_420_GDEY042T81::WIDTH;
const int W = GxEPD2_420_GDEY042T81::HEIGHT;

const int CW = W / 2;
const int CH = H / 2;
const int R = min(W, H) / 2;

const uint16_t WHITE = GxEPD_WHITE;
const uint16_t BLACK = GxEPD_BLACK;

RTC_DATA_ATTR uint16_t wakeups = 0;
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));

void initDisplay() {
  bool initial = wakeups == 0;
  epd.init(115200, initial, 50, false);
  epd.setRotation(1);
  epd.setTextSize(1);
  epd.setTextColor(BLACK);
}

void setTimezone() {
  setenv("TZ", TIMEZONE, 1);
  tzset();
}

void syncTime() {
  if (wakeups % 50 == 0) {
    WiFi.begin(SSID, PWD);
    while (WiFi.status() != WL_CONNECTED)
      ;
    configTzTime(TIMEZONE, "pool.ntp.org");
  }
}

void printAt(int16_t x, int16_t y, const char* text) {
  int16_t x1, y1;
  uint16_t w, h;
  epd.getTextBounds(text, x, y, &x1, &y1, &w, &h);
  epd.setCursor(x - w / 2, y + h / 2);
  epd.print(text);
}

void printfAt(int16_t x, int16_t y, const char* format, ...) {
  static char buff[64];
  va_list args;
  va_start(args, format);
  vsnprintf(buff, 64, format, args);
  printAt(x, y, buff);
}

void polar2cart(float x, float y, float r, float alpha, int& cx, int& cy) {
  alpha = alpha * TWO_PI / 360;
  cx = int(x + r * sin(alpha));
  cy = int(y - r * cos(alpha));
}

void drawClockFace() {
  int cx, cy;
  epd.setFont(&FreeSansBold9pt7b);
  epd.drawCircle(CW, CH, R - 2, BLACK);
  epd.fillCircle(CW, CH, 8, BLACK);

  for (int h = 1; h <= 12; h++) {
    float alpha = 360.0 * h / 12;
    polar2cart(CW, CH, R - 20, alpha, cx, cy);
    printfAt(cx, cy, "%d", h);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 3, BLACK);
  }

  for (int m = 1; m <= 12 * 5; m++) {
    float alpha = 360.0 * m / (12 * 5);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 2, BLACK);
  }
}

void drawTriangle(float alpha, int width, int len) {
  int x0, y0, x1, y1, x2, y2;
  polar2cart(CW, CH, len, alpha, x2, y2);
  polar2cart(CW, CH, width, alpha - 90, x1, y1);
  polar2cart(CW, CH, width, alpha + 90, x0, y0);
  epd.drawTriangle(x0, y0, x1, y1, x2, y2, BLACK);
}

void drawClockHands() {
  struct tm t;
  getLocalTime(&t);

  float alphaM = 360 * (t.tm_min / 60.0);
  float alphaH = 30 * (t.tm_hour % 12);

  drawTriangle(alphaM, 8, R - 50);
  drawTriangle(alphaH, 8, R - 65);
}

void drawDateDay() {
  struct tm t;
  getLocalTime(&t);
  
  epd.setFont(&FreeSans9pt7b);
  printfAt(CW, CH+R/3, "%02d-%02d-%02d",          
          t.tm_mday, t.tm_mon + 1, t.tm_year -100);
  printfAt(CW, CH-R/3, "%s", DAYSTR[t.tm_wday]);           
}

void drawClock(const void* pv) {
  if (wakeups % 120 == 0) {
    epd.setFullWindow();
  } else {
    epd.setPartialWindow(0, 0, W, H);
  }
  epd.fillScreen(WHITE);
  drawClockFace();
  drawClockHands();
  drawDateDay();
}

void setup() {
  initDisplay();
  setTimezone();
  syncTime();

  epd.drawPaged(drawClock, 0);
  epd.hibernate();

  wakeups = (wakeups + 1) % 1000;

  esp_sleep_enable_timer_wakeup(30 * 1000 * 1000);
  esp_deep_sleep_start();
}

void loop() {
}

 

 

ESP32에 코드를 업로드하면 다음 시계가 표시됩니다.

 

4.2인치 전자 종이 디스플레이의 아날로그 시계

 

시계 면, 두 개의 시계 바늘, 현재 요일과 날짜가 표시됩니다.

 

라이브러리

 

흑백(BW) 전자 종이 디스플레이의 경우 GxEPD2_BW.h 헤더 파일을 포함하는 것으로 시작합니다. 3색 디스플레이가 있는 경우 대신 GxEPD2_3C.h 또는 4색 디스플레이의 경우 GxEPD2_4C.h를 포함하고 7색 디스플레이의 경우 GxEPD2_7C.h를 포함합니다.

 

#include "GxEPD2_BW.h"

#include "Fonts/FreeSans9pt7b.h"

#include "Fonts/FreeSansBold9pt7b.h"

 

또한 글꼴에 대한 두 개의 파일을 포함합니다. 시계 레이블과 날짜를 표시하는 데 사용됩니다. 시계 레이블은 굵은 글꼴(FreeSansBold9pt7b)이고 날짜는 얇은 글꼴(FreeSans9pt7b)입니다. 위의 시계 그림을 참조하세요. 글꼴을 변경하려는 경우 AdaFruit GFX 글꼴 개요를 여기에서 확인할 수 있습니다.

 

마지막으로 WiFi.h 및 esp_snt.h 라이브러리를 포함합니다. Wi-Fi를 통해 SNTP 인터넷 시간 서버와 시계를 동기화하는 데 필요합니다.

 

#include "WiFi.h"

#include "esp_sntp.h"

 

상수

 

다음으로 몇 가지 상수를 정의합니다. 가장 중요한 것은 WiFi의 SSID와 PASSWORD, 그리고 거주하는 시간대를 정의해야 한다는 것입니다.

 

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 Timezones Database에서 확인하세요. 찾은 문자열을 복사하여 붙여넣고 TIMEZONE 상수를 그에 맞게 변경합니다.

 

날짜 외에도 현재 요일의 이름(예: 목요일)도 표시하려고 합니다. DAYSTR 상수는 요일 이름을 정의합니다. 언어를 변경하거나 더 짧은 이름을 선택해도 되지만 순서는 유지해야 합니다.

 

const char* DAYSTR[] = { 
  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 
};

 

 

다음으로 디스플레이의 너비 W와 높이 H에 대한 상수를 다시 정의합니다. 이는 주로 편의를 위한 것입니다. initDisplay 함수에서 디스플레이를 회전(setRotation(1))하기 때문에 너비와 높이가 바뀐다는 점에 유의하세요.

 

const int H = GxEPD2_420_GDEY042T81::WIDTH;

const int W = GxEPD2_420_GDEY042T81::HEIGHT;

 

마지막으로 디스플레이의 중심 위치(CW, CH), 디스플레이의 원의 최대 반지름 R, 흑백 색상의 짧은 이름에 대한 상수를 정의합니다.

 

const int CW = W / 2;
const int CH = H / 2;
const int R = min(W, H) / 2;

const uint16_t WHITE = GxEPD_WHITE;
const uint16_t BLACK = GxEPD_BLACK;

 

변수 및 객체

 

상수 다음에 웨이크업과 디스플레이 객체를 계산하는 전역 변수를 정의합니다.

 

RTC_DATA_ATTR uint16_t wakeups = 0;
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));

 

 

ESP32가 딥 슬립에서 깨어날 때마다 wakeups 변수가 증가합니다. ESP32가 딥 슬립 모드로 전환될 때에도 값을 보존하도록 RTC 메모리에 저장됩니다. 디스플레이를 완전히 또는 부분적으로 새로 고칠 시기와 시간을 동기화할 빈도를 결정하는 데 사용합니다.

 

epd 객체는 디스플레이(e-paper 디스플레이)를 나타냅니다. 이 객체 정의는 사용하는 e-Paper 디스플레이 유형과 일치해야 합니다. 여기서는 4.2인치(= _420) e-디스플레이가 있습니다. 지원되는 디스플레이에 대한 Readme for GxEPD2 라이브러리와 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(BLACK);
}

 

 

epd.init()는 일반적으로 실행 시 디스플레이를 완전히 새로 고칩니다. 그러나 ESP32를 재설정한 후에만 완전히 새로 고침하고 딥 슬립에서 깨어날 때는 부분적으로 새로 고침하고 싶습니다. 이를 위해 wakeups == 0일 때(재설정 후) initial 매개변수 epd.init() 함수를 true로 설정하고, 그렇지 않으면 initial을 false로 설정하여 부분적으로 새로 고칩니다.

 

setTimezone 함수

 

setTimezone 함수는 TIMEZONE을 설정합니다. 딥슬립이나 리셋 후에는 시간대 정보가 손실되므로 setup 함수에서 매번 호출해야 합니다.

 

void setTimezone() {
  setenv("TZ", TIMEZONE, 1);
  tzset();
}

 

 

syncTime 함수

 

syncTime 함수는 WiFi 연결을 생성한 다음 configTzTime()을 호출하여 ESP32의 내부 시계를 SNTP 서버 "pool.ntp.org"와 동기화합니다.

 

연결할 다른 SNTP 서버 또는 여러 SNTP 서버를 지정할 수 있습니다. 자세한 내용은 ESP32 시계를 SNTP 서버와 동기화하는 방법 튜토리얼을 참조하세요.

 

void syncTime() {
  if (wakeups % 50 == 0) {
    WiFi.begin(SSID, PWD);
    while (WiFi.status() != WL_CONNECTED)
      ;
    configTzTime(TIMEZONE, "pool.ntp.org");
  }
}

 

 

그러나 가끔씩만 시간을 동기화하고 싶습니다. ESP32는 30초 동안 절전 모드로 설정되어 있으므로(루프 함수 참조) 표현식 wakeups % 50 == 0은 시간이 25분(30초 * 50 / 60초)마다만 동기화되도록 보장합니다.

 

이를 변경할 수 있지만 동기화 빈도가 높을수록 배터리 전력이 더 많이 소모됩니다(Wi-Fi는 전력을 많이 소모합니다). 반면 동기화 빈도가 낮으면 일광 절약 시간제로 전환할 때 시계가 더 느리게 반응합니다.

 

printAt 함수

 

printAt 함수는 지정된 화면 좌표에 가운데 정렬된 텍스트를 표시합니다.

 

void printAt(int16_t x, int16_t y, const char* text) {
  int16_t x1, y1;
  uint16_t w, h;
  epd.getTextBounds(text, x, y, &x1, &y1, &w, &h);
  epd.setCursor(x - w / 2, y + h / 2);
  epd.print(text);
}

void printfAt(int16_t x, int16_t y, const char* format, ...) {
  static char buff[64];
  va_list args;
  va_start(args, format);
  vsnprintf(buff, 64, format, args);
  printAt(x, y, buff);
}

 

 

printAt()는 일반 텍스트만 인쇄하지만 printfAt() 함수는 printf처럼 작동하며 형식 지정자를 제공할 수 있습니다(예: printfAt(x, y, "%02d-%02d-%02d", m, h, y);

 

polar2cart 함수

 

polar2cart 함수는 반지름 r과 각도 alpha로 주어진 극좌표를 데카르트 좌표 cx, cy로 변환합니다.

 

void polar2cart(float x, float y, float r, float alpha, int& cx, int& cy) {
  alpha = alpha * TWO_PI / 360;
  cx = int(x + r * sin(alpha));
  cy = int(y - r * cos(alpha));
}

 

 

이 함수는 매우 유용합니다. 8:15 o’clock과 같은 시간 데이터는 본질적으로 시계의 극좌표이고 디스플레이에서 사용하는 데카르트 좌표계로 변환해야 하기 때문입니다.

 

drawClockFace 함수

 

drawClockFace() 함수는 이름에서 알 수 있듯이 시계의 면을 그립니다. 여기에는 프레임, 분 눈금, 시간 레이블 및 눈금이 포함됩니다. 보시다시피, 이것은 우리가 이전에 논의했던 polar2cart() 함수를 잘 활용하고 있습니다.

 

void drawClockFace() {
  int cx, cy;
  epd.setFont(&FreeSansBold9pt7b);

  // Frame
  epd.drawCircle(CW, CH, R - 2, BLACK);
  epd.fillCircle(CW, CH, 8, BLACK);

  // Hour ticks and labels
  for (int h = 1; h <= 12; h++) {
    float alpha = 360.0 * h / 12;
    polar2cart(CW, CH, R - 20, alpha, cx, cy);
    printfAt(cx, cy, "%d", h);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 3, BLACK);
  }

  // Minute ticks
  for (int m = 1; m <= 12 * 5; m++) {
    float alpha = 360.0 * m / (12 * 5);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 2, BLACK);
  }
}

 

 

drawTriangle 함수

 

drawTriangle() 함수는 삼각형을 그리지만 극좌표로 그립니다. 삼각형의 밑변은 시계의 중심에 있고 삼각형은 각도 alpha로 각도가 지정됩니다. width는 밑변의 너비이고 len은 삼각형의 길이입니다. 이 함수는 시계 바늘을 그리는 데 사용됩니다.

 

void drawTriangle(float alpha, int width, int len) {
  int x0, y0, x1, y1, x2, y2;
  polar2cart(CW, CH, len, alpha, x2, y2);
  polar2cart(CW, CH, width, alpha - 90, x1, y1);
  polar2cart(CW, CH, width, alpha + 90, x0, y0);
  epd.drawTriangle(x0, y0, x1, y1, x2, y2, BLACK);
}

 

 

채워진 시계 바늘을 선호하는 경우 epd.drawTriangle()에서 epd.fillTriangle()로 변경할 수 있습니다. 보기는 더 쉽지만 현재 요일 및 날짜 표시를 방해합니다.

 

drawClockHands 함수

 

시계 바늘의 실제 그리기는 drawClockHands() 함수에서 수행합니다. 현재 로컬 시간을 가져오고, 분과 시간을 각도로 변환한 다음 drawTriangle()을 사용하여 시계 바늘을 그립니다.

 

void drawClockHands() {
  struct tm t;
  getLocalTime(&t);

  float alphaM = 360 * (t.tm_min / 60.0);
  float alphaH = 30 * (t.tm_hour % 12);

  drawTriangle(alphaM, 8, R - 50);
  drawTriangle(alphaH, 8, R - 65);
}

 

 

drawDateDay 함수

 

시계 바늘이 표시하는 시간 외에도 현재 날짜와 요일도 표시하고 싶었습니다. drawDateDay() 함수는 현재 로컬 시간과 날짜를 가져온 다음 요일 이름(예: 목요일)을 시계 면의 맨 위에, 날짜(예: 05-09-24)를 맨 아래에 인쇄합니다. 귀하의 국가의 날짜 형식을 변경하기 위해 형식 지정자를 쉽게 변경할 수 있습니다.

 

void drawDateDay() {
  struct tm t;
  getLocalTime(&t);
  
  epd.setFont(&FreeSans9pt7b);
  printfAt(CW, CH+R/3, "%02d-%02d-%02d",          
          t.tm_mday, t.tm_mon + 1, t.tm_year -100);
  printfAt(CW, CH-R/3, "%s", DAYSTR[t.tm_wday]);           
}

 

drawClock 함수

 

drawClock() 함수는 모든 것을 하나로 합칩니다. 화면을 지운 다음 시계 면, 시계 바늘, 날짜를 그립니다.

 

void drawClock(const void* pv) {
  if (wakeups % 120 == 0) {
    epd.setFullWindow();
  } else {
    epd.setPartialWindow(0, 0, W, H);
  }
  epd.fillScreen(WHITE);
  drawClockFace();
  drawClockHands();
  drawDateDay();
}

 

 

웨이크업 카운터에 따라 디스플레이를 전체 또는 부분적으로 새로 고칩니다. 전체 새로 고침은 느리고(2~4초) 디스플레이가 많이 깜빡입니다. 부분 새로 고침은 훨씬 빠르고(0.5초 미만) 깜빡임이 없습니다.

 

그러나 부분 새로 고침은 이전 콘텐츠의 희미한 잔상을 남깁니다. 아래 그림을 자세히 보면 분침이 5에서 10으로 이동하는 동안 남은 잔상을 볼 수 있습니다.

 

e-Paper 디스플레이의 잔상

 

 

잔상을 제거하기 위해 가끔 디스플레이를 전체 새로 고침하고 싶을 때가 있습니다. drawClock() 함수에서 웨이크업 카운터가 120에 도달하면 이 작업을 수행합니다. 웨이크업 % 120 == 0. 딥슬립 시간이 30초로 설정되어 있으므로 30초*120/60초 = 60분마다 전체 새로 고침을 의미합니다.

 

설정 및 루프 함수

 

마지막으로 일반적인 설정 및 루프 함수가 있습니다. 루프 함수는 비어 있는데, ESP32는 설정 함수가 끝날 때 딥슬립 모드로 전환되고 따라서 루프 함수가 실행되지 않기 때문입니다.

 

void setup() {
  initDisplay();
  setTimezone();
  syncTime();

  epd.drawPaged(drawClock, 0);
  epd.hibernate();

  wakeups = (wakeups + 1) % 1000;

  esp_sleep_enable_timer_wakeup(30 * 1000 * 1000);
  esp_deep_sleep_start();
}

void loop() {
}

 

 

설정 함수는 디스플레이를 초기화하고, 시간대를 설정하고, (아마도) 시간을 동기화하는 것으로 시작합니다(웨이크업 카운터에 따라 다름).

 

그런 다음 drawPaged를 호출하여 drawClock 함수를 실행한 다음 디스플레이를 최대 절전 모드로 전환하여 에너지를 절약합니다. 자세한 내용이 필요하면 e-Paper 디스플레이의 부분적 새로 고침 튜토리얼을 참조하세요. 여기서 drawPaged 함수를 더 자세히 설명합니다.

 

ESP32를 30초(30 * 1000 * 1000 마이크로초) 동안 딥 슬립 모드로 전환하기 전에 웨이크업 카운터를 증가시키고 1000으로 모듈로 계산하여 카운터 오버플로를 방지합니다.

 

그게 다입니다! 이 코드를 사용하면 항상 정확하고 배터리 전원으로 작동할 수 있는 멋진 아날로그 시계를 만들 수 있습니다.

 

결론

 

이 튜토리얼에서는 SNTP 서버와 시간을 동기화하고 항상 정확한 시간과 날짜를 표시하는 4.2인치 전자 종이 디스플레이에 아날로그 시계를 만드는 방법을 배웠습니다. 이 시계는 ESP32의 딥슬립 기능을 활용하여 전력 소비를 줄이고 배터리의 작동 시간을 늘립니다.

 

제안된 ESP32 LOLIN Lite를 사용하는 경우 충전식 LiPo 배터리를 연결하는 것이 간단하고 MAX1704X를 사용하여 충전 수준을 모니터링할 수 있습니다.

 

마지막으로 아날로그 시계보다 디지털 시계를 선호하는 경우 전자 종이 디스플레이의 디지털 시계 튜토리얼을 살펴보세요.

 

즐거운 땜질 ; ) 

 

문서의 원 출처는 이 링크를 따라가면 만날 수 있습니다.

 

배움을 멈추지 마세요!

 

 

반응형

더욱 좋은 정보를 제공하겠습니다.~ ^^