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

記事公開日:2017年12月31日
最終修正日:2018年3月7日
※Arduino core for the ESP32 は、
2018/03/05以降の最新版
を使用してください。

ついに大晦日です。

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

思えば、プログラミングやブログ更新、その他、本業の仕事などで辛いだけの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ディスプレイは安価なので、手軽にできますね。

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

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

スポンサーリンク

準備するもの

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さんでは以下で販売しています。

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

接続する

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

ESP32_ssd1306_sizeup_font_01

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

※2018/01/18頃更新された、GitHub の Arduino core for ESP32 のインストールはしない方が賢明です。
SPIFFSファイルシステムが上手く動作してくれません。
2017/12/19 バージョンは正常動作しています。

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

●Arduino core for ESP32
●SPIFFSファイルアップローダーインストール
●SPIFFSファイル領域拡大
●東雲フォントおよび 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.01

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

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

ESP32_SPIFFS_ShinonomeFNT ライブラリ beta ver 1.1

今回バージョンアップしました。
Beta ver 1.1 です。
ESP-WROOM-32 の SPIFFSフラッシュから東雲フォントを表示させるライブラリです。

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

ESP32_SPIFFS_UTF8toSJIS ライブラリ beta ver 1.1

今回、バージョンを 1.1 としました。
軽微な修正です。

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

スケッチの入力

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

#include "ESP32_I2C_SSD1306.h"
#include "ESP32_SPIFFS_ShinonomeFNT.h"

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};

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() {
  int str_number;
  //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;
    }
  }
}
//************* マルチタスク ****************************************
void Task1(void *pvParameters){
  int str_number=0;
  while(1){
    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();
    }
    delay(1);//マルチタスクのwhileループでは必ず必要
  }
}

【解説】

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

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

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

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

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

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

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

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

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

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

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

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

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

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

●113-159行:
メインループとは別のCPU0番のタスクです。
主に文字をスクロール表させています。
117-126行では、4倍角文字スクロールさせています。
117行では、sj_length-2 としないとスクロール終了を検知しないので注意です。
119行の関数では、1文字スクロールし終わったらキャッシュのフォントから読み込んでスクロールバッファに入れるようにしています。
キャッシュのフォントは全角2文字分なので、1文字スクロールしている間に別の1文字を取得するようにしています。

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

コンパイル書き込み実行

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

まとめ

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

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

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

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

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

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

良いお年を・・・

mgo-tec電子工作 関連コンテンツ ( 広告含む )

スポンサーリンク

Amazon.co.jp 当ブログのおすすめ

投稿者:

mgo-tec

Arduino , ESP32 ( ESP-WROOM-32 ) , ESP8266 ( ESP-WROOM-02 )等を使って、主にスマホと連携した電子工作やプログラミング記事を書いてます。ライブラリも作ったりしてます。趣味、独学でやってますので、動作保証はしません。 電子回路やプログラミングの専門家ではありません。 畑違いの仕事をしていて、介護にドップリ浸かりそうな年配者です。 少しだけ電気の知識が必要な仕事なので、電気工事士や工事担任者等の資格は持っています。

コメントを残す

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

*画像の文字を入力してください。(スパム防止の為)

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