Inkplate 6COLORで卓上カレンダーを作った

またもやずいぶん久しぶりの更新になってしまいました。最近マイコン熱が再燃してきたので書きます。

InkplateはSolderedっていうクロアチアの会社が取り扱っているe-inkモジュールで、6COLORという製品名のとおり6色のカラー電子ペーパーです。

クラウドファンディングでInkplate 6COLORを買った

2022年2月28日、Crowd Supplyで141241番目のオーダーでした。価格は99.00USD。2023年4月現在公式サイトの価格は109.95EUR。急激な円安を差っ引いても結構安く買えてますね。

届いたのはおよそ7ヶ月後の2022年10月1日。もうすっかり忘れちゃっていました。

無線が使えるディスプレイだ!

詳細は公式サイトにもあるとおりですが、概略こんな感じ。

  • 5.85インチ(600 x 488px)の電子ペーパー
  • 6色の内訳は、黒/赤/黄/青/緑/橙
  • ボード上にESP32-WROVER-E(技適付き)搭載
  • マイクロSDカードスロット搭載
  • 外付けRTCモジュールPCF85063A搭載
  • PHコネクタ付きリチウムポリマー電池(充放電回路付き)対応
  • 基板上に3つのタッチパッド搭載
  • 電源供給とプログラム書き込みはUSB-Cコネクタ
  • I2C含むGPIO端子は基板端から取り出し可

とまあESP32付きの電子ペーパーというべきか、電子ペーパー付きのESP32というべきか。Wi-FiとBluetoothが使えるディスプレイっていいよね!

色に関しては背景色の白を加えて7色と表現されていますが、黄色はコントラスト低いし、黒と青も識別しにくいです。

リチウムポリマー電池の充放電に関しては、一応保護は効いているみたいです。正確に測ったわけじゃないけど4.2Vほどで充電がストップするし、3.3V程度で電源が落ちるっぽい。

マルツで買ってきた300mAhの小さなリチウムポリマー電池を繋いで、問題なく動いています。

NTPで時刻合わせする卓上カレンダー

どうやって使うのか皆目見当がつかなかったので、届いてから半年ほど放置していました。そもそもESP32搭載ってこともよくわかっていなかった始末。電子ペーパーだから絵でも表示して飾ればいいかななんて思っていました。

重い腰を上げて絵の表示方法を調べ、公式サイトの説明に従ってArduino IDEにInkplateのライブラリを読み込みました。サンプルコードを組み合わせて日めくりカレンダーを作ることにします。

背景画像の上に日付などのテキストを載せています。こだわりポイントは以下のとおり。

  • 日付を序数であらわすので微妙に表示位置調整している
  • 電池持ちを考慮してほとんどDeep Sleepにしている
  • 日めくりの際にNTPを読みに行ってRTCを補正している

Wi-Fi接続が電力を食いそうなのでRTC補正時だけ接続しています。初めはdelay()で24時間カウントしていたんですが、ESP32のDeep Sleep機能を使って省電力運転したところ、14日間稼働しました。

まだまだ改善の余地はあるものの、初めてにしてはなかなかいいものが出来上がったんじゃないでしょうか。

#include <Inkplate.h>

// Next 3 lines are a precaution, you can ignore those, and the example would also work without them
#ifndef ARDUINO_INKPLATECOLOR
#error "Wrong board selection for this example, please select Soldered Inkplate 6COLOR in the boards menu."
#endif

#include "Inkplate.h"   //Include Inkplate library to the sketch
#include "WiFi.h"       //Include library for WiFi
#include "time.h"
#include "Fonts/FreeMono9pt7b.h"
#include "Fonts/FreeMonoBold9pt7b.h"
#include "Fonts/FreeSerif24pt7b.h"
#include "driver/rtc_io.h" // Include ESP32 library for RTC pin I/O (needed for rtc_gpio_isolate() function)
#include <rom/rtc.h>       // Include ESP32 library for RTC (needed for rtc_get_reset_reason() function)
// It is in same folder as this sketch. You can even open it (read it) by clicking on battSymbol.h tab in Arduino IDE
Inkplate display; // Create an object on Inkplate library

const char ssid[] = "SSID";
const char *password = "PASSWORD";
int wifi_weit;
int retry_time;
struct tm timeInfo;
static const char *mon[12] = {"January","February","March","April","May","June","July", "August", "September", "October", "November", "December"};
static const char *wd[7] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
uint8_t hour;
uint8_t minutes;
uint8_t seconds;
uint8_t weekday;
uint8_t day;
uint8_t month;
uint16_t year;
// Set up a 15 seconds timer
int countdown_time = 60;
int suffix_pos_adj;
String date_suffix;
int remain_sec;
float voltage = 0;
float prev_voltage = 0;

void setup() {
  display.begin(); // Init Inkplate library (you should call this function ONLY ONCE)
  // Connect to the WiFi network.
  WiFi.mode(WIFI_MODE_STA);
  WiFi.begin(ssid, password);
  wifi_weit = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(10);
    wifi_weit++;
    if(wifi_weit > 300){break;}
  }
  retry_time = 0;
  // 2000年以前だったらリトライ
  while (timeInfo.tm_year < 30) {
    delay(1000);
    configTzTime("JST-9", "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); 
    getLocalTime(&timeInfo);
    retry_time++;
    if(retry_time > 5){break;}
  }
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);

  suffix_pos_adj = 0;
  if (timeInfo.tm_mday < 10) {
    suffix_pos_adj = 20;
  }
  switch (timeInfo.tm_mday) {
    case 1:
    case 21:
    case 31:
      date_suffix = "st";
      break;
    case 2:
    case 22:
      date_suffix = "nd";
      break;
    case 3:
    case 23:
      date_suffix = "rd";
      break;
    default:
      date_suffix = "th";
  }
  // 2000年以降なら設定
  if (timeInfo.tm_year > 30) {
    pinMode(39, INPUT_PULLUP);
    hour = timeInfo.tm_hour;
    minutes = timeInfo.tm_min;
    seconds = timeInfo.tm_sec;
    weekday = timeInfo.tm_wday;
    day = timeInfo.tm_mday;
    month = timeInfo.tm_mon + 1;
    year = timeInfo.tm_year + 1900;
    display.rtcSetTime(hour, minutes, seconds);    // Send time to RTC
    display.rtcSetDate(weekday, day, month, year); // Send date to RTC
    display.rtcTimerSet(Inkplate::TIMER_CLOCK_1HZ, countdown_time, false, false);
  }

  display.sdCardInit();
  display.clearDisplay(); // Clear everything in frame buffer of e-paper display
  display.drawImage("image.bmp", 0, 0, 1);
  display.setTextColor(INKPLATE_GREEN);
  display.setFont(&FreeMonoBold9pt7b);
  display.setTextSize(0.5);
  display.setCursor(300, 15);
  display.print("The Nice Calendar System");
  display.setFont(&FreeMono9pt7b);
  display.setCursor(300, 35);
  display.printf("Wi-Fi negotiation: %d.%02dsec", wifi_weit / 100, wifi_weit % 100);
  display.setCursor(300, 55);
  display.printf("Updated time: %02d:%02d:%02d JST", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
  display.setCursor(300, 75);
  prev_voltage = voltage;
  voltage = display.readBattery(); // Read battery voltage
  display.print("LiPo Batt: ");
  if (prev_voltage != 0) {
    display.printf("%.2fV -> ", prev_voltage);
  }
  display.printf("%.2fV", voltage); // Print battery voltage
  display.setCursor(300, 95);
  display.printf("NTP access: %d", retry_time);
  display.setTextColor(INKPLATE_BLACK);
  display.setCursor(330, 443);
  display.print("(c)2023 Takeru-chan");
  display.setFont(&FreeSerif24pt7b);
  display.setTextSize(1);
  display.setCursor(50, 50);
  display.print(timeInfo.tm_year + 1900);
  timeInfo.tm_year = 0;
  display.setCursor(50, 100);
  display.print(mon[timeInfo.tm_mon]);
  if (timeInfo.tm_wday == 0) {
    display.setTextColor(INKPLATE_RED);
  } else if (timeInfo.tm_wday == 6) {
    display.setTextColor(INKPLATE_BLUE);
  }
  display.setTextSize(2);
  display.setCursor(350 + suffix_pos_adj, 170);
  display.print(timeInfo.tm_mday);
  display.setTextSize(1);
  display.setCursor(445 - suffix_pos_adj, 170);
  display.print(date_suffix);
  display.setCursor(350, 220);
  display.print(wd[timeInfo.tm_wday]);
  display.sdCardSleep(); // Put sd card in sleep mode
  display.display(); // Send everything to display (refresh the screen)
  remain_sec = 86400 - (timeInfo.tm_hour * 60 * 60 + timeInfo.tm_min * 60 + timeInfo.tm_sec);
  // Set RTC alarm remaining seconds from now
  display.rtcSetAlarmEpoch(display.rtcGetEpoch() + remain_sec, RTC_ALARM_MATCH_DHHMMSS);
  // Enable wakup from deep sleep on gpio 39 where RTC interrupt is connected
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_39, 0);
  // Go to sleep
  esp_deep_sleep_start();
}

void loop() {
    // Never here! If you are using deep sleep, the whole program should be in setup() because the board restarts each
    // time. loop() must be empty!
}
Code language: Arduino (arduino)