ESP32-DevkitCとRTC DS3231SN、OLED、u8g2で作る日本語表示の時計

ESP32-DevkitCとRTC、SSD1309搭載の2.42インチとSSD1306搭載の0.96インチのOLED(有機ELディスプレイ)で作った時計の時分秒や年月日、曜日といった文字をu8g2.drawXBM()で描画表示するスケッチ(プログラム)作成時のメモです。

OLEDは黒色がくっきりで表示が鮮明、斜めからも良く見えます
目次

準備したパーツ、モジュール間をI2C接続

昨年作ったNTP同期のLCD時計で使っていたキャラクタ型のLCDをグラフィック型のOLEDに変更しました。OLED用のライブラリとしてu8g2を使ってスケッチをプログラム変更しました。

あわせて読みたい
ESP32-DevKitCでNTPサーバと時刻同期して RTCモジュール DS3231SNの時刻合わせを行うLCD時計:環境モニ... ESP32-DevKitC ESP-WROOM-32開発ボード(以下、ESP32-DevKitC)を使ってインターネット上のNTP(Network Time Protocol)サーバと時刻同期してRTC(リアルタイムクロック)...

OLEDの仕様、集めたパーツ

ネット通販で集めたOLEDの仕様です。サイズ、発光色の異なる2個で試しました。

#サイズ
発光色
解像度接続方式(I2Cアドレス)ドライバーIC電源電圧(V)
12.42インチ
黄色
128×64I2Cに設定変更(0x3C)
※デフォルトはSPI
SSD13093.3
20.96インチ
青色
128×64I2C(0x3C)SSD13063.3〜5.0

OLED以外のパーツです。モジュールの電源電圧とI2Cの電圧レベルは3.3Vで統一です。

#パーツ個数
1ESP32-DevKitC ESP-WROOM-32開発ボード1
2DS3231SN  I2C RTCモジュール1
3コイン形リチウムイオン2次電池「LIR2032」(4個入り)1
4サンハヤト ニューブレッドボード SAD-011
5ジャンパーワイヤ(オス-オス、メス-オス)適量
6USBケーブル(USB A オス to microB オス、PC接続用)1
(手持ち)

2.42インチOLED(有機ELディスプレイ)をSPI接続からI2C接続に変更

購入した2.42インチOLEDはSPI接続がデフォルトでした。転送速度はSPIが高速ですが、今回は時計用途なので配線数が少ないI2C接続に変更しました。

あわせて読みたい
2.42インチOLED(有機ELディスプレイ)をSPI接続からI2C接続に変更 ネット通販で購入したSSD1309搭載の2.42インチOLED(有機ELディスプレイ)の接続インターフェースを基板裏面のランドパターンを使ってSPIからI2Cに変更した際のメモです...

ブレッドボード上で結線

モジュール間の結線は、I2Cの2線(SCL、SDA)と電源の2線(3.3V、GND)の4線なので容易です。ブレッドボード上に配置したESP32-DevkitC、DS3231SN RTCモジュール、OLEDを4色に色分けしたジャンパーワイヤでつないでいきます。

それぞれのOLED単体でも動作しますが、今回両方のOLEDを同じI2CアドレスのままI2Cバスにつないで、一つのスケッチで同時表示しています。

OLED時計の結線図
OLED時計の結線図
日時を日本語形式で表示するOLED時計
日時を日本語形式で表示するOLED時計

電源は外部電源でなく、PCからUSBケーブル経由で5Vを給電しました。この5VからESP32-DevkitC内部のLDO(Low Dropout)で降圧された3.3Vを3V3ピンから得ています。

ESP32-DevkitCによってはスケッチの書き込み中にエラーが起こることがあります。connecting・・・が始まって Writing at・・・(xx%) 表示が出るまでの間、ESP32-Devkit基板上のBOOTボタンを押すことで回避します。

参考:
ESP32-DevKitC ESP-WROOM-32開発ボード回路図 | akizukidenshi.com
ESP32-DevKitC-1 pin-layout | espressif.com 

OLED表示のスケッチ、漢字は16×16ピクセルのHEXデータをu8g2.drawXBM()で描画

以前、U8glib.hを使ってOLEDで漢字表示を行ったのですがメモリ消費も少なく使い勝手も良かったです。今回はライブラリとしてU8g2lib.hを利用させていただきました。

あわせて読みたい
Arduinoと小型ディスプレイ(OLED)を使ってラックスマンFMチューナーキットLXV-OT8の受信中のFM局名を表示 音楽之友社のムック本付録のラックスマンFMチューナーキットにArduino、OLEDとu8gライブラリを使って受信中のFM局名を表示するプチ改造を行いました。FM局名を表示する...

U8g2lib.hは、Arduino IDEメニューの ツール –> ライブラリの管理 からライブラリマネージャを開いて「U8g2」で検索してインストールします。
U8g2の詳細は下記にあります。今回使ったOLEDのドライバーIC:SSD1306とSSD1309もサポートされています。
https://github.com/olikraus/u8g2
https://github.com/olikraus/u8g2/wiki/u8g2reference

u8g2.setFont()で使うフォント見本は、
https://github.com/olikraus/u8g2/wiki/fntlistall
を参考にしました。今回は英数字の表示用フォントとして「u8g2_font_fub11_tf」、「u8g2_font_fub17_tf」を使っています。

OLED時計で使った漢字は「時、分、秒、年、月、日、火、水、木、金、土」と少ないので、メモリ消費を抑えるために、利用する特定の文字のみを漢字で表示します。これらの漢字は画像(BMPファイル)から変換した16×16ピクセルのHEXデータをu8g2.drawXBM()で描画します。

最大1310720バイトのフラッシュメモリのうち、スケッチが691549バイト(52%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が40096バイト(12%)を使っていて、ローカル変数で287584バイト使うことができます。

Windows標準アプリのペイント3Dで100×100ピクセル程度の漢字のBMPファイルを作った後、キャンパスを16×16ピクセルに変更して圧縮します。
その16×16ピクセルのBMPファイルからHEXデータに変換します。HEXデータへの変換にはProgramResource.netサイトの「nfBmptoHex.exe」を利用させていただきました。

電源を入れてから時計表示までの間に少し時間がかかる(OLEDは黒い画面のままで寂しい)ので、無線LAN接続やJSTとの時刻合わせ状況のメッセージ表示を加えています。

esp32_ds3231sn_oled_clock.ino  ※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <U8g2lib.h>    // U8g2ライブラリ
#include <WiFi.h>
#include <time.h>       // Timeライブラリ
#include <DS3232RTC.h>  // DS3232、DS3231用ライブラリ
#include <esp_sntp.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
DS3232RTC myRTC(false);

// 漢字画像(BMP)から変換したHEXデータ
static unsigned char yar_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xF8, 0x3F, 0x0C, 0x02, 0x06, 0x02, 0xF8, 0x3F, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0xFE, 0x7F, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, };
static unsigned char mon_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x18, 0x30, 0x18, 0x30, 0x18, 0x30, 0xF8, 0x3F, 0x18, 0x30, 0x18, 0x30, 0xF8, 0x3F, 0x18, 0x30, 0x08, 0x30, 0x0C, 0x30, 0x04, 0x30, 0x06, 0x1F, 0x00, 0x00, };
static unsigned char sun_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0xFC, 0x3F, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0xF8, 0x1F, 0x00, 0x00, };
static unsigned char tue_bmp[] PROGMEM = {0x00, 0x00, 0x80, 0x00, 0x80, 0x01, 0x84, 0x21, 0x84, 0x31, 0x8C, 0x31, 0x88, 0x31, 0x88, 0x11, 0x88, 0x19, 0x80, 0x01, 0xC0, 0x02, 0x60, 0x06, 0x30, 0x1C, 0x0C, 0x70, 0x02, 0x00, 0x00, 0x00, };
static unsigned char wed_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0xBE, 0x61, 0xA0, 0x1B, 0xA0, 0x0F, 0xB0, 0x07, 0xB0, 0x05, 0x90, 0x0D, 0x98, 0x19, 0x8C, 0x31, 0x86, 0x61, 0x90, 0x01, 0xE0, 0x01, 0x00, 0x00, };
static unsigned char thu_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0xFC, 0x7F, 0x80, 0x01, 0xC0, 0x03, 0xC0, 0x07, 0xE0, 0x0D, 0xB0, 0x09, 0x98, 0x19, 0x8C, 0x71, 0x82, 0x61, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, };
static unsigned char fri_bmp[] PROGMEM = {0x00, 0x00, 0xC0, 0x03, 0x60, 0x06, 0x30, 0x1C, 0x1C, 0x30, 0xF7, 0xEF, 0x80, 0x01, 0x80, 0x01, 0xFE, 0x7F, 0x80, 0x01, 0x8C, 0x31, 0x98, 0x11, 0x98, 0x19, 0x90, 0x09, 0xFE, 0x7F, 0x00, 0x00, };
static unsigned char sat_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xFC, 0x3F, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xFE, 0x7F, 0x00, 0x00, };
static unsigned char jik_bmp[] PROGMEM = {0x00, 0x04, 0x1E, 0x04, 0xD3, 0x7F, 0x13, 0x04, 0x13, 0x04, 0x13, 0x04, 0xD3, 0x7F, 0x1F, 0x20, 0x13, 0x30, 0xD3, 0x7F, 0x93, 0x21, 0x13, 0x23, 0x13, 0x22, 0x1E, 0x20, 0x00, 0x3F, 0x00, 0x00, };
static unsigned char fun_bmp[] PROGMEM = {0x00, 0x00, 0x30, 0x0C, 0x18, 0x08, 0x08, 0x18, 0x0C, 0x30, 0x06, 0x60, 0xFA, 0x5F, 0x60, 0x30, 0x40, 0x30, 0x60, 0x30, 0x20, 0x30, 0x30, 0x30, 0x18, 0x30, 0x0C, 0x30, 0xC6, 0x1F, 0x00, 0x00, };
static unsigned char byo_bmp[] PROGMEM = {0x00, 0x00, 0x3E, 0x04, 0x08, 0x04, 0x88, 0x25, 0xBE, 0x65, 0x8C, 0x65, 0x8C, 0x44, 0x8C, 0x44, 0x9C, 0xE4, 0x6E, 0xE4, 0x2A, 0x24, 0x08, 0x37, 0x08, 0x18, 0x08, 0x0C, 0x88, 0x03, 0x00, 0x00, };

const char* weekStr[7] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
const char* ssid      = "your ssid";
const char* password  = "your password";
const char* ntpServer = "ntp.nict.jp";
const long  gmtOffset_sec = 32400;
const int   daylightOffset_sec = 0;

void setup(void) {
//  u8g2.setI2CAddress(0x3C *2);  // I2Cアドレスを指定する場合
  u8g2.begin();          // OLED初期化
  u8g2.setFlipMode(0);   // 引数のデフォルトは0、1で180°回転
  u8g2.setContrast(0);  // 明るさも多少変わる(0~255)@2022-05-19追加
struct tm timeInfo;
Serial.begin(115200);
myRTC.begin();
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fub11_tf);
  u8g2.drawStr(0,17,"welcome");
  u8g2.sendBuffer();
// WiFi接続
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
delay(500);
}
  u8g2.clearBuffer();
  u8g2.drawStr(0,17,"wifi connected");
  u8g2.sendBuffer();
delay(1000);
// NTPサーバからJST取得
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 内蔵RTCの時刻がNTP時刻に合うまで待機
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) {
delay(500); 
}
  u8g2.clearBuffer();
  u8g2.drawStr(0,17,"jst synchronized");
  u8g2.sendBuffer();
delay(1000); 
// 内蔵RTCの時刻の取得
getLocalTime(&timeInfo);
// 内蔵RTCの時刻をDS3231に時刻設定
// setTime(23, 59, 55, 30, 4, 2022);  // 手動設定・動作確認用(時、分、秒、日、月、年)
setTime(timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec, timeInfo.tm_mday, timeInfo.tm_mon + 1, timeInfo.tm_year + 1900);
myRTC.set(now());
// WiFi切断
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}

void loop() {
tmElements_t tm;
myRTC.read(tm);
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fub17_tf);
// 時分秒の描画
   char hur[2];
   char mnt[2];
   char sec[2];
   dtostrf(tm.Hour,2,0,hur);
   u8g2.drawStr(0,17,hur);
      u8g2.drawXBM(28, 3, 16, 16, jik_bmp);
   dtostrf(tm.Minute,2,0,mnt);
   u8g2.drawStr(45,17,mnt);
      u8g2.drawXBM(71, 3, 16, 16, fun_bmp);
   dtostrf(tm.Second,2,0,sec);
   u8g2.drawStr(87,17,sec);
      u8g2.drawXBM(113, 3, 16, 16, byo_bmp);
// 年月日の描画
   char yar[4];
   char mth[2];
   char dat[2];
   dtostrf(tm.Year + 1970,4,0,yar);
   u8g2.drawStr(0,40,yar);
      u8g2.drawXBM(54, 25, 16, 16, yar_bmp);
   dtostrf(tm.Month,2,0,mth);
   u8g2.drawStr(0,64,mth);
      u8g2.drawXBM(28, 48, 16, 16, mon_bmp);
   dtostrf(tm.Day,2,0,dat);
   u8g2.drawStr(48,64,dat);
      u8g2.drawXBM(76, 48, 16, 16, sun_bmp);
// 曜日の描画
      u8g2.drawStr(94,64,"(");
      u8g2.drawStr(121,64,")");
         if ( (weekStr[tm.Wday - 1]) == "Sun" ) {
      u8g2.drawXBM(104, 48, 16, 16, sun_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Mon" ) {
      u8g2.drawXBM(104, 48, 16, 16, mon_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Tue" ) {
      u8g2.drawXBM(104, 48, 16, 16, tue_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Wed" ) {
      u8g2.drawXBM(104, 48, 16, 16, wed_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Thu" ) {
      u8g2.drawXBM(104, 48, 16, 16, thu_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Fri" ) {
      u8g2.drawXBM(104, 48, 16, 16, fri_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Sat" ) {
      u8g2.drawXBM(104, 48, 16, 16, sat_bmp);
   }
      u8g2.sendBuffer();
}
よかったらシェアしてね!
  • URLをコピーしました!
目次