かずの不定期便ブログ

備忘録代わりに書きます

ESP32でIIC OLED SSH1106に時計を表示させました

1.3インチ I2C駆動のOLEDディスプレイSSH1106を買いました。解像度は128x64です。
ESP32を使って7segフォントを使ったデジタル時計を作ってみました。

ESP32でI2C OLED SSH1106にデジタル時計を表示させる

ライブラリとSSH1106ドライバの導入

Adafruit_GFXライブラリとSH1106のドライバを導入すれば、文字表示はすぐに出来ます。

  • Arduino IDEのメニューよりツール→ライブラリ管理と辿って" adafruit gfx "というキーワードで検索してAdafruit GFX Libraryをインストールします。
  • esp32-sh1106-oledというドライバをインストールします。

github.com

動作確認

  • ファイル→スケッチ例→Adfruits SH1106を選択してoledをロード

ESP32ボードとOLEDを繋ぎます。VCCには3V3を繋ぎました。
ソースを見ると,以下の様になっていますので12pinをSDA, 14pinをSCLにつないで

#define OLED_SDA 12
#define OLED_SCL 14

とやりますと、コンパイル終了後、ボードへの書き込みフェーズで以下の様なエラーが出てプログラムが書き込まれません。

Warning: Could not auto-detect Flash size (FlashID=0xffffff, SizeID=0xff), defaulting to 4MB
Compressed 8192 bytes to 47...
A fatal error occurred: Timed out waiting for packet content
A fatal error occurred: Timed out waiting for packet content

先のSH1106ドライバのREADME.mdに記載があるURL
How to use Arduino ESP32 to display information on OLED http://www.iotsharing.com/2017/05/how-to-use-arduino-esp32-to-display-oled.html
コメント欄に記載がありまして、利用するピン番号を変更すればよいようです。IO12はブートストラップに使われている?ESP32のTRMを斜め読みしましたが該当記述は見つけられず…(多分OLEDボード側で該当ピンをプルアップしているのかな?)私は以下の様にSDAをpin27, SCLをpin26とすることで解決しました。

//#define OLED_SDA 12
//#define OLED_SCL 14
#define OLED_SDA 27
#define OLED_SCL 26

//Adafruit_SH1106 display(12, 14);
Adafruit_SH1106 display(OLED_SDA, OLED_SCL);

トラブルありましたが、無事に動作しました。

デジタル時計の作成

この方がSSD1302ですがNTPサーバーから時刻を取得して時計を表示させています。
qiita.com
大変有用なプログラムでした。wifiの使い方から分かるようになり小さいながら中身がぎゅっと詰まったプログラムです。ありがとうございます。
プログラムの骨格は出来たも同然です。
SSD1306と記述してある部分をSH1106のサンプルスケッチの様に記述を変えるだけです。具体的にはSSD1306→SH1106へ文字列を変更するのとドライバのインスタンス部の引数をSH1106のサンプルスケッチの様に変更しました。
これで、時計は表示できました。
SH1106はSSD1302に比べて垂直解像度が倍あるので下半分が空いていて寂しいです。またどうせならデジタル時計っぽく7SEG風にしたいかなと思い、フォントを変更する事にします。

7SEG風フォントの導入

フォントは「Font "DSEG" by けしかん」さんから拝借しました。
www.keshikan.net
こちらに置いてあるフォントはベクトルフォントでttfやwoff形式でした。このままではAdafruit_GFXライブラリ側から扱えないので、形式を変換します。変換の仕方は以下のAdafruit_GFXライブラリの使い方サイトの"Adding New Fonts"に記載がありました。
learn.adafruit.com

  • FreeTypeライブラリの導入
  • Adafruit_GFX_Libraryパッケージにあるfontconvertを使ってttf形式から*.hへ変換するようです。

FreeTypeライブラリの導入

fontconvertのコンパイルが必要なので、手軽なwindows linuxであるWSL2にて作業を行います。
windows10のWSL2(ubuntu20)にてFreeTypeライブラリをインストールします。
https://freetype.org/
上記サイトから最新版をダウンロードします(私はfreetype-2.12.1.tar.gzをダウンロード)。
Ubuntuのホームdirへ上記ダウンロードしたファイルをコピーして、以下の様に展開してコンパイル及びインストールを行います。

tar xvfz freetype-2.12.1.tar.gz
cd freetype-2.12.1
mkdir build
cd build
cmake ..
make
sudo make install

fontconvertのコンパイル

Adafruit_GFX_Libraryパッケージにあるfontconvertフォルダをフォルダ全部をWSL2上にコピーします。
fontconvert.cを修正します。
#include "../gfxfont.h" // Adafruit_GFX font structures
の行を以下の様に../を削除します。
#include "gfxfont.h" // Adafruit_GFX font structures
そして、Adafruit_GFX_Libraryパッケージの直下にあるファイル"gfxfont.h"をWSL2へコピーしたfontconvertフォルダの配下へコピーします。後はmakeするだけです。

make

これでfontconvertという実行バイナリが生成されます。

ttfファイルをAdafruit_GFX_Libraryで扱える形式へ変換

さきほど作成したfontconvertと同じdirへ「Font "DSEG" by けしかん」さんのところでダウンロードしたttfファイルをコピーし、好みのptサイズのフォントを作成します。私はClassic_Italicなフォントで18ptと6ptを作成しました。

./fontconvert DSEG7Classic-Italic.ttf 18 >DSEG7Classic_Italic18pt7b.h
./fontconvert DSEG7Classic-Italic.ttf 6 >DSEG7Classic_Italic6pt7b.h

デジタル時計の作成(フォント7SEG風)

18ptで作成したフォントだとhh:mmと表示するだけで横の128pix分使い切りました。。。
秒はその下の右に小さく表示しました。

さらに、hh:mmの:部分を点滅表示にしてみました。けしかん氏のサイトの見本と同じ感じに。。
drawClock()関数の前半は":"付きで500ms表示させたのち、
display.clearDisplay();で一旦全体を削除、その後":"無し版の時間(変数rtime_wo_colon)を
表示させています。

GFXライブラリのdisplay.write()関数は既に"1"つまりドットが存在していると、消すことなくそのままにする仕様の様です。そのため、一旦display.clearDisplay()関数を呼び出して消しています。

出来上がったソースコードは以下になります。誰かの参考になればと。

#include <WiFi.h>
#include <time.h>

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
//#include <Fonts/FreeSansBold12pt7b.h>
//#include <Fonts/FreeSansBold18pt7b.h>
//#include "DSEG7Classic_Italic24pt7b.h"
#include "DSEG7Classic_Italic18pt7b.h"
//#include "DSEG7Classic_Italic16pt7b.h"
//#include "DSEG7Classic_Italic12pt7b.h"
#include "DSEG7Classic_Italic6pt7b.h"

//#define OLED_SDA 12
//#define OLED_SCL 14
#define OLED_SDA 27
#define OLED_SCL 26
//128x64 SH1106

//Adafruit_SH1106 display(12, 14);
Adafruit_SH1106 display(OLED_SDA, OLED_SCL);

// WiFi Setting
#define WIFI_SSID   "xxxxxx"
#define WIFI_PASSWORD   "xxxxxxx"
#define JST     3600*9

#if (SH1106_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SH1106.h!");
#endif

void setup()   {                
  Serial.begin(115200);
  delay(100);
  Serial.print("\n\nReset:\n");
  
  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
  // init done

  display.clearDisplay();
  display.setTextColor(WHITE);

  // WiFi starting
  drawLog("WiFi connecting...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while(WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
  }
  Serial.println();
  Serial.printf("Connected, IP address: ");
  Serial.println(WiFi.localIP());
  drawLog("WiFi connected!");

  // NTP start
  configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
  delay(1000);


}

 

void loop() {
  time_t t;
  struct tm *tm;
  static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
  char rdate[30], rtime[30], rtime2[30];
  char rtime_wo_colon[30];
  
  t = time(NULL);
  tm = localtime(&t);

  Serial.printf(" %04d/%02d/%02d(%s) %02d:%02d:%02d\n",
        tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
        wd[tm->tm_wday],
        tm->tm_hour, tm->tm_min, tm->tm_sec);
  sprintf(rdate, " %04d/%02d/%02d(%s)",
        tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, wd[tm->tm_wday]);
  //sprintf(rtime, " %02d:%02d:%02d", 
  //      tm->tm_hour, tm->tm_min, tm->tm_sec);
  sprintf(rtime, "%02d:%02d", 
        tm->tm_hour, tm->tm_min);
  sprintf(rtime_wo_colon, "%02d %02d", 
        tm->tm_hour, tm->tm_min);
  sprintf(rtime2, "%02d", 
        tm->tm_sec);
  drawClock(rdate, rtime, rtime2,rtime_wo_colon );      

  delay(1000 - millis()%1000); 
  //loop_cnt++;
  //if(loop_cnt==50)
  //  while(1){};
}

void drawClock(const char* rdate, const char* rtime, const char* rtime2, const char* rtime_wo_colon) {
  display.clearDisplay();

  display.setFont(&DSEG7Classic_Italic18pt7b);
  display.setCursor(0,36);
  display.setTextSize(1);
  drawText(rtime);


  display.setTextSize(1);
  display.setFont();
  display.setCursor(0,64-8);
  drawText(rdate);

  //display.setFont();  // default font
  display.setFont(&DSEG7Classic_Italic6pt7b);
  display.setCursor(16*6+8,30+24);
  display.setTextSize(1);
  drawText(rtime2);
  display.display();
  delay(500);

// : なし
  display.clearDisplay();

  display.setFont(&DSEG7Classic_Italic18pt7b);
  display.setCursor(0,36);
  display.setTextSize(1);
  drawText(rtime_wo_colon);

  display.setTextSize(1);
  display.setFont();
  display.setCursor(0,64-8);
  drawText(rdate);
  
  display.setFont(&DSEG7Classic_Italic6pt7b);
  display.setCursor(16*6+8,30+24);
  display.setTextSize(1);
  drawText(rtime2);
  
  display.display();
  delay(1);
}

// 1がセットされている部分は書きこまない仕様
void drawText(const char* text) {
  for (uint8_t i=0; i < strlen(text); i++) {
    display.write(text[i]);
  }    
}


void drawLog(const char* msg) {
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(1);
  drawText(msg);

  display.display();
  delay(1);
}
ESP32でI2C OLED SSH1106にデジタル時計を表示させる