ESP32-DevkitCとRTC、SSD1309搭載の2.42インチとSSD1306搭載の0.96インチのOLED(有機ELディスプレイ)で作った時計の時分秒や年月日、曜日といった文字をu8g2.drawXBM()で描画表示するスケッチ(プログラム)作成時のメモです。
準備したパーツ、モジュール間をI2C接続
昨年作ったNTP同期のLCD時計で使っていたキャラクタ型のLCDをグラフィック型のOLEDに変更しました。OLED用のライブラリとしてu8g2を使ってスケッチをプログラム変更しました。
OLEDの仕様、集めたパーツ
ネット通販で集めたOLEDの仕様です。サイズ、発光色の異なる2個で試しました。
# | 発光色 | サイズ解像度 | 接続方式(I2Cアドレス) | ドライバーIC | 電源電圧(V) |
1 | 2.42インチ 黄色 | 128×64 | I2Cに設定変更(0x3C) ※デフォルトはSPI | SSD1309 | 3.3 |
2 | 0.96インチ 青色 | 128×64 | I2C(0x3C) | SSD1306 | 3.3〜5.0 |
OLED以外のパーツです。モジュールの電源電圧とI2Cの電圧レベルは3.3Vで統一です。
# | パーツ | 個数 |
1 | ESP32-DevKitC ESP-WROOM-32開発ボード | 1 |
2 | DS3231SN I2C RTCモジュール | 1 |
3 | コイン形リチウムイオン2次電池「LIR2032」(4個入り) | 1 |
4 | サンハヤト ニューブレッドボード SAD-01 | 1 |
5 | ジャンパーワイヤ(オス-オス、メス-オス) | 適量 |
6 | USBケーブル(USB A オス to microB オス、PC接続用) | 1 (手持ち) |
2.42インチOLED(有機ELディスプレイ)をSPI接続からI2C接続に変更
購入した2.42インチOLEDはSPI接続がデフォルトでした。転送速度はSPIが高速ですが、今回は時計用途なので配線数が少ないI2C接続に変更しました。
ブレッドボード上で結線
モジュール間の結線は、I2Cの2線(SCL、SDA)と電源の2線(3.3V、GND)の4線なので容易です。ブレッドボード上に配置したESP32-DevkitC、DS3231SN RTCモジュール、OLEDを4色に色分けしたジャンパーワイヤでつないでいきます。
それぞれのOLED単体でも動作しますが、今回両方のOLEDを同じI2CアドレスのままI2Cバスにつないで、一つのスケッチで同時表示しています。
電源は外部電源でなく、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を利用させていただきました。
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();
}