ESP32 で I2C OLED SSD1306 に東雲フォントを4倍角で表示させてみた

記事公開日:2017年12月31日

ついに大晦日です。

本年は皆さまにはこのブログをご覧いただき、誠にありがとうございました。

思えば、プログラミングやブログ更新、その他、本業の仕事などで辛いだけの1年でした。
来年は何とかしたいな・・・。

ということで、今年最後に、人気の極小有機EL ( OLED )ディスプレイ SSD1306 に日本語東雲フォントを全画面スクロールしてみたので紹介します。

今回のポイントの1つは、SPI通信に比べて速度の遅い I2C 通信のディスプレイで、しかもグラフィック表示がとても扱いにくい SSD1306 で、16×16 pixel の日本語フォントを2倍角、4倍角にしてスクロール表示させることができた点です。

特に、SSD1306 は 8 pixel毎に区切られたページというものがあるので、16×16 pixel を2倍角、4倍角にすることは、とっても難しかったんです。
これは数週間に渡ってかなりの時間を費やしました。
途中でやめようとも思ったのですが、倍角表示が意外と便利だったので、やっているうちに後に引けなくなってしまいました。

年の瀬なので、プログラミング最中にいろいろな雑念が湧きに沸いてきました。
「年末で忙しいからこんなことやってられない」とか、「こんなことやっても意味ないんじゃないか」とか・・・。

でも、この雑念を振り払って、ずっと継続してプログラミングしていると、最後の方でちょっとした光が見えてきて、何とか動かすことができたんです。
いや~、今回もシビれました・・・。

そして、もう一つのポイントは、メモリを削減するために、1文字スクロールする度にSPIFFSからフォントを読み込んでいますが、読み取り速度が遅いので、スクロールがカクカクしていましたが、今回は ESP32 のマルチタスクを使って、疑似キャッシュっぽいことをやってみたら、ある程度スクロールをスムースにすることができました。
これは ESP8266 ではできないことなので、なかなかイイ感じですよ。

そして、SSD1306のページというものをうまく利用してスクロールさせることによって、4倍角でもスクロール速度を上げることができたことですね。

ということで、以下の動画をご覧ください。

因みに、このデバイスは Wemos のフェイク品ですが、私的には便利なので使っています。

因みに、ここで使用しているマグネットUSBケーブルは前回の記事で紹介したものです。

いかがでしょうか?
動画の画像がちょっと粗いのですが、実際はもっと良く見えます。
スクロール速度はこれが最高です。
それでも4倍角でここまでの速度が I2C通信の SSD1306 で出せたのは、素人の私にとっては大きな成果です。
この速度を出すのに、いろいろ苦労しました・・・。

多少、動きのスムースさが甘いのですが、SPIFFS の読み取り速度が遅いので、マルチタスクを使っても、私の頭ではここまでが限界です。
本当は、フォントを全部読み込めば、確実にスムースになりますが、メモリを消費してしまうので、私としてはスムースさが失われてもメモリが大きい方が良いので、これでOKとします。

これ、フリーマーケットやコミケ会場に置いておいて、値札っぽいこともできそうですよね。
I2C の OLED SSD1306ディスプレイは安価なので、手軽にできますね。

では、この作り方を説明します。

因みに、自作ライブラリは素人の即席ですので、動作保証は一切しません。
予めご了承ください。

※この記事を書いた当初のソースコードを変更しました。
2018/7月以降にArduino core for the ESP32が大幅にアップデートされ、I2C(Wireライブラリ)関連のバグが修正されました。

(参照記事)
Arduino – ESP32 大幅更新( 2018/06/28以降 )と I2C 不具合解決、その他気付いたこと

それにより、ESP32をマルチタスクで動かす時、I2C関連関数を同じタスク内で動かさないと正常に動かないことが判明。
コメント投稿で指摘されて、古いソースコードを修正しました。
(2019/05/06)

 

スポンサーリンク

準備するもの

ESP-WROOM-32 開発ボード

当ブログで何度も紹介している Wi-Fi & Bluetooth マイコンボードです。
秋月電子通商さんや、マルツパーツさんでも販売しています。

Amazonさんでは以下で販売しています。

※2018/01/30時点で、Amazon.co.jp では Espressif system純正の ESP32-DevKitC は販売されていません。秋月電子通商さんか、マルツパーツさんで購入してください。

I2C OLED SSD1306 ボード

これも最近、秋月電子通商さんでも販売されました。
人気の I2C 有機EL ( OLED ) SSD1306 ボードです。
Amazonさんでは以下で販売しています。

Amazon.co.jp

その他、以下のもので、偽物の、Wemos と表示されている、ESP32 と一体型ボードがありますが、ここでは私は買ったことがありません。
自己責任で検討してみてください。

Amazon.co.jp
Wemos? Lolin ESP32 OLED モジュール Arduino ESP32 OLED WiFi + ブルートゥース デュアル ESP-32 ESP-32S ESP8266 OLED モジュール [並行輸入品]
Wemos? Lolin ESP32 OLED モジュール Arduino ESP32 OLED WiFi + ブルートゥース デュアル ESP-32 ESP-32S ESP8266 OLED モジュール

接続する

接続方法は以下の通りです。

esp32_ssd1306_sizeup_font_01.jpg


Arduino core for ESP32、SPIFFSファイルアップローダー、SPIFFS領域拡大、フォントダウンロードを予め済ませておく

次の事前設定が必要です。

●Arduino core for ESP32 ( stable ver 1.0.2 )

※stable ver 1.0.2 の場合、SPIFFSのパーティションテーブルを変える必要はありません。
ボード設定を以下のようにしてください。
USB comポートはご自分の環境に合わせて変えてください。

arduino_esp32_102_board01.jpg


(2019/05/06)

 

●SPIFFSファイルアップローダーインストール ( ver 1.0 )
●東雲フォントおよび UTFtoSJISテーブルファイルのダウンロード

以下の記事を参照して、事前に済ませておいてください。
手抜きでスイマセン。

Arduino – ESP32 で、3つの SPI 通信 OLED ディスプレイ に Twitter Trend データを 表示させてみた

自作ライブラリのダウンロード

ESP8266用のライブラリは作っていたのですが、今回、Arduino -ESP 32用の SPIFFS I2C SSD1306 ライブラリを新たに作りました。
先ほども述べたように、16x16pixelフォントを2倍角と4倍角表示とスクロールさせることができるようになりました。

また、東雲フォントライブラリと UTF8toSJISライブラリをバージョンアップしました。

以下のライブラリをダウンロードして、ZIP形式のまま Arduino IDE にインストールしておいてください。
インストールする前に、古いライブラリはフォルダごと削除しておくことを忘れないでください。

ZIPライブラリから直でArduino IDE にインストールする方法は以下の記事を参照してください。

GitHubにある ZIP形式ライブラリ のインストール方法 ( Arduino IDE )

ESP32_I2C_SSD1306 ライブラリ beta ver 1.20

I2C 通信で、OLED SSD1306ディスプレイを表示させるライブラリです。
新規に作成しました。
ESP8266 のライブラリよりも、フォントをサイズアップさせる関数と、ページ単位で表示させる関数が新たに追加されています。

https://github.com/mgo-tec/ESP32_I2C_SSD1306

ESP32_SPIFFS_ShinonomeFNT ライブラリ beta ver 1.32

Beta ver 1.32です。
ESP-WROOM-32 の SPIFFSフラッシュから東雲フォントを表示させるライブラリです。

https://github.com/mgo-tec/ESP32_SPIFFS_ShinonomeFNT

ESP32_SPIFFS_UTF8toSJIS ライブラリ beta ver 1.20

今回、バージョンを 1.20 としました。

https://github.com/mgo-tec/ESP32_SPIFFS_UTF8toSJIS

スケッチの入力

では、サンプルスケッチとして以下を入力してみてください。

※この記事を書いた当初のソースコードを変更しました。
2018/7月以降にArduino core for the ESP32が大幅にアップデートされ、I2C(Wireライブラリ)関連のバグが修正されました。

(参照記事)
Arduino – ESP32 大幅更新( 2018/06/28以降 )と I2C 不具合解決、その他気付いたこと

それにより、ESP32をマルチタスクで動かす時、I2C関連関数を同じタスク内で動かさないと正常に動かないことが判明。
コメント投稿で指摘されて、古いソースコードを修正しました。
(2019/05/06)

 

//Arduino core for the ESP32 stable ver 1.0.2
//SPIFFS uploader plugin ver 1.0
#include "ESP32_I2C_SSD1306.h" //ver beta 1.20
#include "ESP32_SPIFFS_ShinonomeFNT.h" //ver beta 1.32
//ESP32_SPIFFS_UTF8toSJIS ver beta 1.20

const uint8_t ADDRES_OLED =  0x3C;
const int sda = 5;
const int scl = 4;
const uint8_t Horizontal_pixel = 128;
const uint8_t Vertical_pixel = 64;

const char* UTF8SJIS_file = "/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* Shino_Zen_Font_file = "/shnmk16.bdf"; //全角フォントファイル名を定義
const char* Shino_Half_Font_file = "/shnm8x16.bdf"; //半角フォントファイル名を定義

ESP32_I2C_SSD1306 ssd1306(ADDRES_OLED, sda, scl, Horizontal_pixel, Vertical_pixel);
ESP32_SPIFFS_ShinonomeFNT SFR;

enum { Display_Max_num = 4, MaxTxtByte = 100 ,cash = 2};

String utf8_str[Display_Max_num];
uint8_t sj_txt[Display_Max_num][MaxTxtByte] = {0}; //Shift_JISコード格納
uint16_t sj_length[Display_Max_num] = {0}; //Shift_JISコードの長さ
uint16_t sj_cnt[Display_Max_num] = {0}; //Shift_JISコード半角文字数カウント

uint8_t Size = 1;

uint8_t cash_font_cnt[Display_Max_num] = {0};
uint8_t cash_font_read_cnt[Display_Max_num] = {0};
uint8_t disp_buf[Display_Max_num][16][16] = {0};

String test_str;
uint8_t test_buf[16][16] = {0};
uint8_t test_sj_txt[MaxTxtByte] = {0};
uint16_t test_sj_length;

boolean fnt_read_ok[Display_Max_num] = {true, true, true, true};
boolean FontReakOK[Display_Max_num] = {false, false, false, false};

uint32_t SclTime1 = 0;
uint32_t SclTime2 = 0;

uint8_t CashFont[cash][Display_Max_num][2][16] = {0};
uint8_t Zen_or_Han[cash][Display_Max_num] = {0};

int str_number = 0;

void setup() {
  Serial.begin(115200);
  ssd1306.SSD1306_Init(400000); //I2C Max=400kHz
  ssd1306.Display_Clear_All();
  //3つのSPIFFSファイルを同時オープン
  SFR.SPIFFS_Shinonome_Init3F(UTF8SJIS_file, Shino_Half_Font_file, Shino_Zen_Font_file); //ライブラリ初期化。2ファイル同時に開く
  Serial.println();

  test_str = "大晦日!";
  Size = 1;
  test_sj_length = SFR.StrDirect_ShinoFNT_readALL(-90, "大晦日!",test_buf);
  ssd1306.Font8x16_1line_Page_DisplayOut(test_sj_length, 0, 0, test_buf);
  delay(1500);
  ssd1306.Display_Clear_All();

  Size = 2;
  ssd1306.SizeUp_8x16_Font_DisplayOut(Size, test_sj_length, 0, 0, test_buf);
  delay(1500);
  ssd1306.Display_Clear_All();

  test_str = "大晦";
  Size = 4;
  test_sj_length = SFR.StrDirect_ShinoFNT_readALL(-90, test_str, test_buf);
  ssd1306.SizeUp_8x16_Font_DisplayOut(Size, test_sj_length, 0, 0, test_buf);
  delay(1500);
  ssd1306.Display_Clear_All();

  test_str = "♪";
  Size = 4;
  test_sj_length = SFR.StrDirect_ShinoFNT_readALL(-90, test_str, test_buf);
  ssd1306.SizeUp_8x16_Font_DisplayOut(Size, test_sj_length, 32, 0, test_buf);
  delay(2000);
  ssd1306.Display_Clear_All();

  //キャッシュバイトを使う場合、文字の最後に全角スペース適当に入れておけば、文字スクロール終了を検出可能
  //4倍角ならスペース5つ入力
  sj_length[0] = SFR.UTF8toSJIS_convert("← ← ESP32 いいのができました!!東雲フォント4倍角!     ", sj_txt[0]);
  sj_length[1] = SFR.UTF8toSJIS_convert("●1.ESP32 とOLED SSD1306はマルチタスクにすると意外と使える! ", sj_txt[1]);
  sj_length[2] = SFR.UTF8toSJIS_convert("●2.SSD1306 は¥690と安いですよ~~ ", sj_txt[2]);
  sj_length[3] = SFR.UTF8toSJIS_convert("★本年は当ブログをご覧いただきありがとうございます。 ", sj_txt[3]);

  //マルチタスクでスムースにスクロールするための、キャッシュバイト2バイト事前読み込み
  for(int j=0; j<cash; j++){
    for(int str_number=0; str_number<Display_Max_num; str_number++){
      Zen_or_Han[j][str_number] = SFR.Sjis_inc_FntRead_Rot(&sj_cnt[str_number], -90, 0, str_number, sj_txt[str_number], sj_length[str_number], CashFont[j][str_number]);
    }
  }

  TaskHandle_t th; //ESP32 マルチタスク ハンドル定義
  xTaskCreatePinnedToCore(Task1, "Task1", 4096, NULL, 5, &th, 0); //マルチタスク core 0 実行

  SclTime1 = millis();
  SclTime2 = millis();
}
//************************************************
void loop() {
  //I2C(Wire)関連は全て同じタスクに置かないと正常動作しないので注意
  if(millis() - SclTime1 > 0){
    if( sj_cnt[0] < (sj_length[0] - 2) ){ //スクロール終了検出はsj_lengthから全角2バイト分引く
      str_number=0;
      if(ssd1306.Scroller_Font8x16_PageReplace(4, str_number, Zen_or_Han[cash_font_cnt[str_number]][str_number], CashFont[cash_font_cnt[str_number]][str_number], disp_buf[str_number])){
        cash_font_cnt[str_number]++;
        if(cash_font_cnt[str_number] >= cash) cash_font_cnt[str_number] = 0;
        FontReakOK[str_number] = true;
      }
      ssd1306.SizeUp_8x16_Font_DisplayOut(4, 4, 0, 0, disp_buf[str_number]);
      SclTime2 = millis();
    }else if((millis()-SclTime2<20000)){
      str_number=1;
      if(ssd1306.Scroller_Font8x16_PageReplace(16, str_number, Zen_or_Han[cash_font_cnt[str_number]][str_number], CashFont[cash_font_cnt[str_number]][str_number], disp_buf[str_number])){
        cash_font_cnt[str_number]++;
        if(cash_font_cnt[str_number] >= cash) cash_font_cnt[str_number] = 0;
        FontReakOK[str_number] = true;
      }
      ssd1306.Font8x16_1line_Page_DisplayOut(16, 0, 0, disp_buf[str_number]);

      str_number=2;
      if(ssd1306.Scroller_Font8x16_PageReplace(16, str_number, Zen_or_Han[cash_font_cnt[str_number]][str_number], CashFont[cash_font_cnt[str_number]][str_number], disp_buf[str_number])){
        cash_font_cnt[str_number]++;
        if(cash_font_cnt[str_number] >= cash) cash_font_cnt[str_number] = 0;
        FontReakOK[str_number] = true;
      }
      ssd1306.Font8x16_1line_Page_DisplayOut(16, 0, 2, disp_buf[str_number]);

      str_number=3;
      if(ssd1306.Scroller_Font8x16_PageReplace(8, str_number, Zen_or_Han[cash_font_cnt[str_number]][str_number], CashFont[cash_font_cnt[str_number]][str_number], disp_buf[str_number])){
        cash_font_cnt[str_number]++;
        if(cash_font_cnt[str_number] >= cash) cash_font_cnt[str_number] = 0;
        FontReakOK[str_number] = true;
      }
      ssd1306.SizeUp_8x16_Font_DisplayOut(2, 8, 0, 4, disp_buf[str_number]);
    }else if(millis()-SclTime2>=10000){
      SclTime2 = millis();
      sj_cnt[0] = 0;
    }

    SclTime1 = millis();
  }
}
//************* マルチタスク ****************************************
void Task1(void *pvParameters){
  int str_number=0;
  while(1){
    //1文字スクロールしたらフォントを読み込む
    for(str_number=0; str_number<Display_Max_num; str_number++){
      if(FontReakOK[str_number] == true) {
        Zen_or_Han[ cash_font_read_cnt[str_number] ][str_number] = SFR.Sjis_inc_FntRead_Rot(&sj_cnt[str_number], -90, 0, str_number, sj_txt[str_number], sj_length[str_number], CashFont[ cash_font_read_cnt[str_number] ][str_number]);
        cash_font_read_cnt[str_number]++;

        if(cash_font_read_cnt[str_number] >= cash) cash_font_read_cnt[str_number] = 0;
        FontReakOK[str_number] = false;
      }
    }
    delay(1);//マルチタスクのwhileループでは必ず必要
  }
}

【解説】

●20行:
128×64 pixel の OLED SSD1306 では、16×16 pixel の東雲フォントは4行しか表示できませんので、ここで設定しておきます。
今回は疑似キャッシュっぽいことをやっているので、cashを2としていますが、値を大きくしても変化ありません。

●44行:
マルチタスクを使って事前にフォントを全角2文字(半角4文字)読み込んでおくための配列です。

●54行:
SPIFFSフォントファイル類を3つ同時に開いておきます。

●59行:
SSD1306 の場合、ビット配列が縦方向に配置されています。
以下の記事を参照してみてください。
有機EL ( OLED ) SSD1306 を再検証してみました ( I2C 通信用 )

ですから、-90度回転させてフォントを読み込む関数を作りました。
String文字列を東雲フォントに変換しています。

●60行:
この関数は、SSD1306 のページを基準として配置して、それをディスプレイ表示するようにしています。
その方が描画速度が速いということに気付きました。

●65行:
フォントサイズを2又は4倍角で表示させる関数です。

SizeUp_8x16_Font_DisplayOut(Size, テキストバイト数, 水平座標, ページ, フォントバイト);

ページ数は0~7の値で設定します。

●85-88行:
String文字列をShift_JISコード文字列に変換します。
UTF8コードよりも Shift_JISコードの方が全角と半角を判別しやすいのです。

●91-95行:
フォントを全角2文字分、予め読み込んでおきます。
93行では、文字数をカウントするための変数 sj_cnt を設定して、その数値を返せるようにしています。
ここで全角2文字分読み込んでおくので、スクロールした場合のスクロール完了を検知するためには、全角スペースを最後入力しておく必要があります。
4倍角なら5文字分くらいが適当かと思います。
各自いろいろ調整してみてください。

●97-98行:
ESP32 のデュアルコアCPU を使って、マルチタスクにする関数です。
マルチタスクの運用方法は以下の記事を参照してください。

Arduino – ESP32 のマルチタスク ( Dual Core ) を試す

●99-111行:
メインループです。
メインループのCPU番号は1です。
SSD1306を駆動する I2C ( Wire )ライブラリの初期化は setup 関数内で行っているので、そのCPUと同じものを使います。
すると、ディスプレイ表示関連はこのメインループに置かないと、正常に表示されません。
ここでは主に文字をスクロール表させています。
107-116行では、4倍角文字スクロールさせています。
107行では、sj_length-2 としないとスクロール終了を検知しないので注意です。
109行の関数では、1文字スクロールし終わったらキャッシュのフォントから読み込んでスクロールバッファに入れるようにしています。
キャッシュのフォントは全角2文字分なので、1文字スクロールしている間に別の1文字を取得するようにしています。
また、後で述べますが、フォント読み込みは CPU core 0 にします。

●149-164行:
メインループとは別のCPU0番のタスクです。
ここでは、1文字スクロールする毎に FontReadOK = true として返って来るので、その都度1文字読み込むようにしています。
155行では、文字を1文字読み込んだら sj_cnt が1つインクリメント(増加)します。
そうしたら、キャッシュ文字カウントも1つ増加するようにしています。

●116-140行:
ここでは2行を16x16pixel のままスクロールし、1行を2倍角でスクロールさせています。

コンパイル書き込み実行

では、コンパイル書き込み実行させてみてください。
最初に紹介した動画のように表示できていればOKです。

まとめ

以上、いかがでしたでしょうか。

これを使えば、フリーマーケットやコミケなどで机の上に置いて、値札の様な使用もできるし、デスクに置いてとっても見やすいニュース電光掲示板も作れますね。

SSD1306 は Amazon で690円ほどですから、安価なディスプレイができますね。

ということで、本年はここまでです。

ここまで当ブログをご覧いただき感謝いたします。

また来年もどうぞよろしくお願いいたします。
m(_ _)m

良いお年を・・・

スポンサーリンク


Amazon.co.jp 当ブログのおすすめ
Amazon.co.jp
M5Stack Basic
スイッチサイエンス
Amazon.co.jp
ESPr Developer 32
スイッチサイエンス(Switch Science)
Amazon.co.jp
Amazon.co.jp

「ESP32 で I2C OLED SSD1306 に東雲フォントを4倍角で表示させてみた」への4件のフィードバック

  1. はじめまして。
     どの記事も楽しく拝見させてもらっています。
     この記事のコードを試してみようとそのままコピペして実行したところ、大晦日の倍角表示と音符までは正しく表示されたのですが、スクロールに入ったところで表示がおかしくなってしまいました。よく見てみると尋常じゃない速度でスクロールしているように見えます。動画をtwitter(@Ashigara_elect)のほうにアップしたのでご確認お願いします。
    対策としてIDEを再起動しもう一度書き込み、ボードの電源の入れなおしを行いましたが改善されませんでした。
    ライブラリ等は全て今月ダウンロードしたもので、事前設定も正しく行ったはずです。

    1. 足柄さん

      記事をご覧いただき、ありがとうございます。

      この記事を書いたのはかなり昔なので、今はかなり仕様が変わっていて、ちゃんと動かなかったようです。
      私も試してみたら同じ症状でした。
      本来は古い記事を変更することはしないのですが、今回は比較的簡単に修正できたので、この記事内容も大幅に変更しました。

      これは、2018/7月以降に Arduino core for the ESP32 が大幅アップデートした時に、SSD1306を駆動する I2C(Wire)ライブラリのバグが修正されたことに伴い、CPUデュアルコアマルチタスクで動かす場合、I2C(Wire)ライブラリは同じタスク内で動かさねばならないことが判明し、その関連の不具合です。
      以下の記事を参照してください。
      Arduino – ESP32 大幅更新( 2018/06/28以降 )と I2C 不具合解決、その他気付いたこと

      この記事を書いた当初はsetup関数でI2C(Wire)ライブラリ関連のSSD1306を初期化して、文字列スクロールをそれと別CPUの core 0 で動かしていたので正常に表示されませんでした。
      そのため、ソースコードを修正して、文字スクロールをsetup関数と同じCPU core 1 のメインループ内に変更しました。
      SPIFFSからのフォント取得はCPU core 0 で動作させるようにしました。

      これで、現時点の最新バージョンで動作すると思います。
      Arduino core for the ESP32 stable ver 1.0.2
      SPIFFS アップローダープラグイン ver 1.0
      ESP32_I2C_SSD1306ライブラリ beta ver 1.20
      ESP32_SPIFFS_ShinonomeFNT ライブラリ beta ver 1.32
      ESP32_SPIFFS_UTF8toSJIS ライブラリ beta ver 1.20

      以上、この記事は古い記事で、これ以上の詳細な変更はできませんのでご容赦いただきたいと思います。

      1. mgo-tecさん、対応してくださってありがとうございます。

        修正していただいたコードの正常な動作を確認しました。
        このコードをベースにESP32とSSD1306で色々作っていきたいと思います!

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください