ArduinoとRTC DS3231SN、OLED、u8gで作る日本語表示の時計

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

ArduinoとRTC DS3231SN、OLED、u8gで作る日本語表示の時計
OLEDは黒色がくっきりで表示が鮮明、斜めからも良く見えます  
目次

準備したパーツ、I2Cバス用双方向電圧レベル変換モジュールを追加

「ESP32-DevkitCとDS3231SN RTCモジュールを使ったOLED時計」の「ESP32-DevkitC」を「Arduino Uno Rev3」に置き換えました。ArduinoのI2C信号レベルに合わせて5Vから3.3Vへの双方向電圧レベル変換とU8glib.h対応のスケッチ修正を行います。

あわせて読みたい
ESP32-DevkitCとRTC DS3231SN、OLED、u8g2で作る日本語表示の時計 ESP32-DevkitCとRTC、SSD1309搭載の2.42インチとSSD1306搭載の0.96インチのOLED(有機ELディスプレイ)で作った時計の時分秒や年月日、曜日といった文字をu8g2.drawXBM(...

OLEDの仕様、集めたパーツ

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

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

OLED以外のパーツです(No.1、2は秋月電子通商、他はAmazon)。モジュールの電源電圧とI2Cの電圧レベルは3.3Vで統一です。ArduinoのI2C電圧レベルは5Vなので双方向電圧レベル変換モジュールを入れて3.3Vにしています。

#パーツ個数
1Arduino Uno Rev31
2I2Cバス用双方向電圧レベル変換モジュール(PCA9306)
プルアップ抵抗付き(1kΩx4個)
1
3DS3231SN  I2C RTCモジュール1
4コイン形リチウムイオン2次電池「LIR2032」(4個入り)1
5サンハヤト ニューブレッドボード SAD-1011
6ジャンパーワイヤ(オス-オス、メス-オス)適量
7USBケーブル(USB A オス to B オス、PC接続用)1
(手持ち)

大型のOLEDは消費電流も増えます。Arduino Unoのバージョンによっては3.3V出力が不足するかもしれません。Uno Rev3は3.3VのレギュレータとしてLP2985-33DBVRを搭載しているのでマージンがあったものと思います。

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線なので容易です。Arduino Uno Rev3とブレッドボード上に配置した双方向電圧レベル変換モジュール、DS3231SN RTCモジュール、OLEDを4色に色分けしたジャンパーワイヤでつないでいきます。

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

Arduino+OLED時計の結線図
Arduino+OLED時計の結線図
ArduinoとRTC DS3231SN、OLED、u8gで作る日本語表示の時計
日時を日本語形式で表示するArduino+OLED時計  

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

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

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

ESP32-DevkitCにはWiFi機能があったのでNTPサーバとの時刻合わせを行いましたが、今回のArduino版のスケッチでは時刻合わせに「Arduino DS3232RTC Library sample sketch」のcompileTime()を利用させていただきました。「add fudge factor to allow for compile time」とあるように正確な時刻設定には微調整が必要です。

RTCはバッテリーバックアップが行われているので電源が復旧した際にスケッチ実行時刻:compileTimeに初期化されないように、RTC.getよりもcompileTimeが新しい時のみcompiletimeをRTC.setするように if 文を挿入しました。

OLEDモジュール表示に必要なライブラリ「U8glib.h」は、Arduino IDEメニューの ツール –> ライブラリの管理 からライブラリマネージャで「U8glib」を検索してインストールします。

u8g.setFont()で使うフォント見本は fontsize | olikraus/u8glib を参考にさせていただきました。今回は英数字の表示用フォントとして「u8g_font_fub17」を使っています。

ライブラリ U8glib のインストール
ライブラリ U8glib のインストール

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

最大32256バイトのフラッシュメモリのうち、スケッチが15800バイト(48%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が532バイト(25%)を使っていて、ローカル変数で1516バイト使うことができます。

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

時分秒、月日などが数字1桁の場合、2桁分の描画スペースの片側に寄ってバランスが悪いので、中央に配置されるように void disp_centre(int digit) で数字の先頭にスペースを加えてセンターリングしています。

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

U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);
DS3232RTC myRTC(false);

// 漢字画像(BMP)から変換したHEXデータ
const 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, };
const 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, };
const 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, };
const 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, };
const 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, };
const 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, };
const 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, };
const 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, };
const 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, };
const 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, };
const 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"};

void setup(void) {
  u8g.setColorIndex(1);
  u8g.setContrast(0);  // 明るさも多少変わる(0~255)@2022-05-19追加
struct tm timeInfo;
myRTC.begin();
// myRTC.get()よりcompileTime()が新しい時はcompiletimeをmyRTC.set
time_t time_now, compiletime;;
time_now = myRTC.get();
compiletime = compileTime();
  if (time_now < compiletime) {
myRTC.set(compiletime);  // set compiled time to RTC
  }
// DS3231に手動設定(書込み時間を加算した値で調整)
// setTime(9, 45, 0, 4, 5, 2022);  // 時、分、秒、日、月、年
// myRTC.set(now());
}

void loop() {
tmElements_t tm;
myRTC.read(tm);
  u8g.firstPage();
  do {
  u8g.setFont(u8g_font_fub17);
// 時分秒の描画
u8g.setPrintPos(0,17);
disp_centre(tm.Hour);
u8g.drawXBMP(28, 3, 16, 16, jik_bmp);
u8g.setPrintPos(45,17);
disp_centre(tm.Minute);
u8g.drawXBMP(71, 3, 16, 16, fun_bmp);
u8g.setPrintPos(87,17);
disp_centre(tm.Second);
u8g.drawXBMP(113, 3, 16, 16, byo_bmp);  
// 年月日の描画
u8g.setPrintPos(0,40);
u8g.print(tm.Year + 1970);
u8g.drawXBMP(54, 25, 16, 16, yar_bmp);
u8g.setPrintPos(0,64);
disp_centre(tm.Month);
u8g.drawXBMP(28, 48, 16, 16, mon_bmp);
u8g.setPrintPos(48,64);
disp_centre(tm.Day);
u8g.drawXBMP(76, 48, 16, 16, sun_bmp); 
// 曜日の描画
u8g.setPrintPos(94,64);
u8g.print("(");
u8g.setPrintPos(121,64);
u8g.print(")");
         if ( (weekStr[tm.Wday - 1]) == "Sun" ) {
    u8g.drawXBMP(104, 48, 16, 16, sun_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Mon" ) {
    u8g.drawXBMP(104, 48, 16, 16, mon_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Tue" ) {
    u8g.drawXBMP(104, 48, 16, 16, tue_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Wed" ) {
    u8g.drawXBMP(104, 48, 16, 16, wed_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Thu" ) {
    u8g.drawXBMP(104, 48, 16, 16, thu_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Fri" ) {
    u8g.drawXBMP(104, 48, 16, 16, fri_bmp);
   }else if ( (weekStr[tm.Wday - 1]) == "Sat" ) {
    u8g.drawXBMP(104, 48, 16, 16, sat_bmp);
   }
      } while ( u8g.nextPage() );
}

// 1桁数字は先頭にスペースを加えてセンターリング
void disp_centre(int digit) {
if(digit < 10)
u8g.print(" ");
u8g.print(digit);
}

// function to return the compile date and time as a time_t value
// from alarm_ex1.ino in Arduino DS3232RTC Library sample sketch by Jack Christensen.
time_t compileTime() {
const time_t FUDGE(10);    //fudge factor to allow for upload time, etc. (seconds, YMMV)
const char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec";
char compMon[4], *m;
strncpy(compMon, compDate, 3);
compMon[3] = '\0';
m = strstr(months, compMon);
tmElements_t tm;
tm.Month = ((m - months) / 3 + 1);
tm.Day = atoi(compDate + 4);
tm.Year = atoi(compDate + 7) - 1970;
tm.Hour = atoi(compTime);
tm.Minute = atoi(compTime + 3);
tm.Second = atoi(compTime + 6);
time_t t = makeTime(tm);
return t + FUDGE;        //add fudge factor to allow for compile time
}
よかったらシェアしてね!
  • URLをコピーしました!
目次