有機EL ( OLED ) SSD1331 ライブラリを作成しました ( ESP32 用 )

ESP32 ( ESP-WROOM-32 )

こんばんは。

最初にちょっとお知らせです。
今回のタイトルとは全然異なりますが、GitHub の Arduino core for the ESP32 ページで、WiFi.begin 関数が修正されました。
今まで WiFi接続が上手くいかなかった方々は、ZIPファイルをダウンロードして、get.exe を実行してくださいね。

では、話を戻しまして、フルカラー 有機EL ( OLED ) ディスプレイ SSD1331 モジュールの、SPIインターフェース用ライブラリを作成しました。
Arduino core for the ESP32 専用です。
ESP32 – DevKitC や ESP-WROOM-32 で動作します。

基本的には以前の記事で紹介した動作と全く同じものをライブラリ化しただけです。
でも、ライブラリ化したことによって、使い勝手が向上しました。

以前の記事でも紹介しましたが、このディスプレイドライバ SSD1331 の SPI クロックは最少 150ns なので、あまり高速表示できません。
しかし、グラフィックアクセラレーションコマンドがあるおかげで、それをうまく利用すると SSD1351 を凌ぐ高速表示ができます。
これはフルカラーのために、赤、緑、青の三原色を1ピクセル毎に指定するため、横幅が最大 96 pixel 、縦が 64 pixel です。
モノクロの SSD1306 の 128 x 96 pixel に比べれば少ないのですが、私的にはこれで十分な情報表示ができると思います。

巷ではモノクロディスプレイの SSD1306 が流行っていますが、フルカラーの SSD1331 を使うと、もう SSD1306 には戻れないほど素晴らしいデバイスだと思います。
ちょっと高いのが難点ですが、既に Amazon では安いものが出始めておりますので、今後注目ですね。

スポンサーリンク

OLED SSD1331 モジュールの販売しているところ

Amazon.co.jp さんでは、現在、販売店は中国のものしかないようです。
注文すると10日~2週間くらいで到着するようです。

Amazon 以外ですが、秋月電子通商さんにもありました。

http://akizukidenshi.com/catalog/g/gM-11560/

Pmod というのは良く分からなく、これは使ったことが無いのですが、同じ SSD1331 で SPI 通信なので使えると思われます。

その他、eBay などではもっと安く、沢山売っているようです。

ライブラリの掲載先

私の自作した Arduino core for the ESP32 専用ライブラリは GitHub の以下のページにアップしています。

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

ZIP ファイルをダウンロードして、Arduino IDE にインストールしてください。
Arduino IDE バージョンは 1.8.2 以降推奨です。
ZIP ファイルから直接インストールする方法は以下のページを参照してください。

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

その他、まだ Arduino IDE に Arduino core for the ESP32 をインストールしていない方は以下のページを参照してください。

Arduino core for the ESP32 のインストール方法

接続方法

以前の記事でも掲載しましたが、念のため載せておきます。
これは、ESP32 – DevKitC での配線方法です。

※このライブラリは HSPI 接続のみ対応です。

関数(クラス)の解説

先ほども述べましたが、このライブラリは HSPI 接続のみ対応です。
VSPI 端子には対応しておりませんのでご注意ください。
HSPI と VSPI については以下の記事を参照してください。

ESP32 の SPI_MODE が修正。HSPI , VSPI , 複数SPIデバイス制御 , SPI高速化などについて

では、以下でそれぞれの関数(クラス)の説明をします。

ESP32_SSD1331 ssd1331(SCLK, MISO, MOSI, CS, DC, RST);

これはグローバル変数領域でクラスの宣言です。
小文字の ssd1331 は好きな名前にすることができます。

それぞれ、GPIO のピン割り当てを指定します。
ただし、HSPI 端子のみで、以下の端子は決まっています。

SCLK —- GPIO #14
MOSI —- GPIO #13
MISO —- GPIO #12
CS —- GPIO #15

ただ、SSD1331 の場合はMISO は不要です。

DC ( Data/Command ) , RST ( Reset )ピンは任意に設定できます。

SSD1331_Init();

SSD1331 の SPI クロックの最少は 150ns なので、周波数を受け入れ可能な最速の 7MHz に設定してあり、SPI_MODE3 に設定してあります。

CommandWrite( byte );

SSD1331 の制御コマンドを1バイト送信します。

DataWrite( byte );

SSD1331 へピクセル表示データを1バイト送信します。

CommandWriteBytes(uint8_t *b, uint16_t n);

SSD1331 の制御コマンドを任意のバイト数を連続して送信します。
連続のコマンド送信はこれが圧倒的に速いです。

DataWriteBytes(uint8_t *b, uint16_t n);

SSD1331 へピクセル表示データを連続して送れます。
連続データはこれで送った方が圧倒的に高速です。

Brightness(uint8_t brightness);

画面の明るさを 0-255 の間で調整できます。

Brightness_FadeIn(uint8_t interval);

画面の明るさをフェードインします。
interval ではdelay関数を使って遅らせています。
ゼロにすると瞬時に明るくなります。

Brightness_FadeOut(uint8_t interval);

画面の明るさをフェードアウトします。
ゼロにすると瞬時に暗くなります。

Display_Clear(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1);

画面をクリアする範囲を決めます。
x0 , x1 は 0-95 の範囲内。
y0 , y1 はそれぞれ 0-63 の範囲内です。

以下、座標系は下図のようになります。

SSD1331_Copy(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t X, uint8_t Y);

指定した範囲の画面をコピーします。
x0-x1 , y0-y1 , の範囲をコピーし、X , Y がコピー先の左上端の座標です。
このコマンドをうまく使うと、高速アニメーションが可能です。

SSD1331_8x16_Font_DisplayOut(uint8_t txtMax, uint8_t x0, uint8_t y0, uint8_t red, uint8_t green, uint8_t blue, uint8_t Fnt[][16]);

16×16 ドットフォントや、8×16 ドットフォントを表示できます。
全角6文字表示させる場合、txtMax は12 にします。
1列のみ表示できます。
全角ならば最大6文字。
半角ならば最大12文字表示できます。
表示させる左上端の座標を指定し、RGB三原色の256色カラーを指定します。

red ( 0-7 )
green ( 0-7 )
blue ( 0-3 )

Fnt[][16] の配列にフォントを代入します。

Time_Copy_V_Scroll(uint8_t Direction, uint8_t ZorH, uint8_t buf[2][16], uint8_t *SclCnt, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t col_R, uint8_t col_G, uint8_t col_B);

8×16フォント、または16×16フォントの数値で時計表示するために使うものです。
これは縦方向にスクロールします。
スクロールカウント SclCnt が16 になったら次のフォントを読み込むというプログラムを組む必要があります。
Direction を0 または 1 に設定することにより、スクロールする方向が変わります。
これの使い方は以下のページを参照してください。

この関数は今後改良を重ねる予定です。

エクセル で 16 x 16 ドットフォントを自作して、フルカラー 有機EL ( OLED ) 時計を作ってみた

Time_Copy_H_Scroll(uint8_t Direction, uint8_t ZorH, uint8_t buf[2][16], uint8_t *SclCnt, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t col_R, uint8_t col_G, uint8_t col_B);

上記のスクロールの水平方向のものです。
半角 8x 16 フォントの場合は、スクロールカウント SclCnt が8 になったら次のフォントを読み込むというプログラムを組まなければなりません。

この関数も改良を重ねる予定です。

Drawing_Pixel_256color(uint8_t x0, uint8_t y0, uint8_t R, uint8_t G, uint8_t B);

256色カラーで1点のみ表示する関数です。

赤R( 0-7 )
緑G ( 0-7 )
青B( 0-3 )

Drawing_Pixel_65kColor(uint8_t x0, uint8_t y0, uint8_t R, uint8_t G, uint8_t B);

65000色カラーで1点のみ表示する関数です。

赤R( 0-31 )
緑G ( 0-63)
青B( 0-31 )

これは1点表示させるために2バイト送信するので、256カラーより速度が遅くなります。

Drawing_Line(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t Line_R, uint8_t Line_G, uint8_t Line_B);

直線を描きます。
65000色カラーで指定します。

赤R( 0-31 )
緑G ( 0-63)
青B( 0-31 )

Drawing_Rectangle_Line(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t Line_R, uint8_t Line_G, uint8_t Line_B);

四角形の枠線だけ描きます。
65000色カラーで指定します。

赤R( 0-31 )
緑G ( 0-63)
青B( 0-31 )

Drawing_Rectangle_Fill(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t Line_R, uint8_t Line_G, uint8_t Line_B, uint8_t Fill_R, uint8_t Fill_G, uint8_t Fill_B);

四角形を塗りつぶして描画します。
枠線と塗りつぶしカラーを65000色で指定します。

Drawing_Circle_Line_256color(uint8_t x0, uint8_t y0, uint16_t r, uint8_t Line_R, uint8_t Line_G, uint8_t Line_B);

円の枠線のみを256色カラーで指定します。

x0 , y0 で中心点を指定し、r で半径を指定します。

Drawing_Circle_Line_65kColor(uint8_t x0, uint8_t y0, uint16_t r, uint8_t Line_R, uint8_t Line_G, uint8_t Line_B);

円の枠線のみを 65000色カラーで指定します。

X0 , y0 で中心点を指定し、 r で半径を指定します。

Drawing_Circle_Fill(uint8_t x0, uint8_t y0, uint16_t r, uint8_t Line_R, uint8_t Line_G, uint8_t Line_B);

円を塗りつぶして描画します。
直線を描いて塗りつぶすため、65000カラーで指定します。

サンプルスケッチ

では、上記の関数(クラス)を使ったサンプルスケッチは以下の通りです。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include "ESP32_SSD1331.h"

const uint8_t SCLK_OLED =  14; //SCLK
const uint8_t MOSI_OLED =  13; //MOSI (Master Output Slave Input)
const uint8_t MISO_OLED =  12; //これは実際は使っていない。MISO (Master Input Slave Output)
const uint8_t CS_OLED = 15;
const uint8_t DC_OLED =  16; //OLED DC(Data/Command)
const uint8_t RST_OLED =  4; //OLED Reset
 
ESP32_SSD1331 ssd1331(SCLK_OLED, MISO_OLED, MOSI_OLED, CS_OLED, DC_OLED, RST_OLED);

void setup() {
  ssd1331.SSD1331_Init();
}
 
void loop() {
  int i, j;
  uint8_t R, G, B, Dot1, Dot2;
 
  ssd1331.Display_Clear(0, 0, 95, 63);
 
  ssd1331.CommandWrite(0xAE); //Set Display Off
  delay(1000);
 
  ssd1331.CommandWrite(0xA0); //Remap & Color Depth setting 
    ssd1331.CommandWrite(0b00110010); //A[7:6] = 00; 256 color.
 
  R=7; G=0; B=0; //256 color : R (0-7), G (0-7), B (0-3)
  Dot1 = (R << 5) | (G << 2) | B;
  for(j=0; j<64; j++){
    for(i=0; i<96; i++){
      ssd1331.DataWrite(Dot1);
    }
  }
 
  ssd1331.CommandWrite(0xAF); //Set Display On
  delay(110); //0xAFコマンド後最低100ms必要
  ssd1331.Brightness_FadeIn(4);
  delay(1000);
  ssd1331.Brightness_FadeOut(4);
  delay(1000);
  ssd1331.CommandWrite(0xAE); //Set Display Off
  delay(1000);
 
  ssd1331.Brightness_FadeIn(0);
  ssd1331.Display_Clear(0, 0, 95, 63);
  ssd1331.CommandWrite(0xAF); //Set Display On
  delay(110); //0xAFコマンド後最低100ms必要
  R=0; G=7; B=0; //256 color : R (0-7), G (0-7), B (0-3)
  Dot1 = (R << 5) | (G << 2) | B;
  for(j=0; j<64; j++){
    for(i=0; i<96; i++){
      ssd1331.DataWrite(Dot1);
    }
  }
  delay(2000);
 
  R=0; G=0; B=3; //256 color : R (0-7), G (0-7), B (0-3)
  Dot1 = (R << 5) | (G << 2) | B;
  for(j=0; j<64; j++){
    for(i=0; i<96; i++){
      ssd1331.DataWrite(Dot1);
    }
  }
  delay(2000);
 
  ssd1331.Display_Clear(0, 0, 95, 63);
 
  ssd1331.CommandWrite(0xA0); //Remap & Color Depth setting 
    ssd1331.CommandWrite(0b01110010); //A[7:6] = 01; 65k color format
 
  R=31; G=63; B=31; //65k color : R (0-31), G (0-63), B (0-31)
  uint8_t DotDot[2];
  DotDot[0] = (R << 3) | (G >> 3);
  DotDot[1] = (G << 5) | B;
  for(j=0; j<64; j++){
    for(i=0; i<96; i++){
      ssd1331.DataWriteBytes(DotDot, 2); //65k colorモードでは、2バイトデータを送る
    }
  }
  delay(2000);
 
  ssd1331.Display_Clear(0, 0, 95, 63);
  ssd1331.Drawing_Line(0, 0, 95, 63, 31, 0, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Drawing_Line(95, 0, 0, 63, 0, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Drawing_Line(48, 0, 48, 63, 0, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  for(i=0; i<63; i=i+5){
    ssd1331.Drawing_Line(i, 63, 95, 63-i, 0, i, 31);
  }
  delay(2000);
 
  ssd1331.Display_Clear(0, 0, 95, 63);
  ssd1331.Drawing_Rectangle_Line(20, 20, 40, 40, 31, 0, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Drawing_Rectangle_Line(0, 0, 60, 60, 0, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Drawing_Rectangle_Line(70, 10, 80, 63, 0, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Display_Clear(0, 0, 95, 63);
  ssd1331.Drawing_Rectangle_Fill(0, 0, 60, 60, 0, 31, 0, 31, 0, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Drawing_Rectangle_Fill(20, 20, 40, 40, 0, 0, 31, 0, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Drawing_Rectangle_Fill(70, 10, 80, 63, 31, 63, 31, 0, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
 
  ssd1331.Display_Clear(0, 0, 95, 63);

  ssd1331.CommandWrite(0xA0); //Remap & Color Depth setting 
    ssd1331.CommandWrite(0b00110010); //A[7:6] = 00; 256 color.
 
  ssd1331.Drawing_Circle_Line_256color(31, 31, 31, 7, 0, 0); //Red(0-7), Green(0-7), Blue(0-3)
  delay(2000);
  ssd1331.Drawing_Circle_Line_256color(50, 31, 20, 0, 7, 0); //Red(0-7), Green(0-7), Blue(0-3)
  delay(2000);
  ssd1331.Drawing_Circle_Line_256color(70, 31, 10, 0, 0, 3); //Red(0-7), Green(0-7), Blue(0-3)
  delay(2000);
 
  ssd1331.Display_Clear(0, 0, 95, 63);

  ssd1331.Drawing_Circle_Fill(31, 31, 31, 31, 0, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
  ssd1331.Drawing_Circle_Fill(50, 31, 20, 0, 63, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);
  ssd1331.Drawing_Circle_Fill(70, 31, 10, 0, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  ssd1331.Display_Clear(0, 0, 95, 63);
  ssd1331.CommandWrite(0xA0); //Remap & Color Depth setting 
    ssd1331.CommandWrite(0b01110010); //A[7:6] = 01; 65k color format
 
  ssd1331.Drawing_Circle_Line_65kColor(31, 31, 31, 0, 63, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(1000);
  ssd1331.Drawing_Circle_Line_65kColor(50, 31, 20, 31, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(1000);
  ssd1331.Drawing_Circle_Line_65kColor(70, 31, 10, 31, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(1000);
 
  ssd1331.SSD1331_Copy(60, 21, 80, 41, 0, 0);
  ssd1331.SSD1331_Copy(60, 21, 80, 41, 75, 0);
  ssd1331.SSD1331_Copy(60, 21, 80, 41, 0, 43);
 
  for(i=0; i<76; i++){
    ssd1331.SSD1331_Copy(60, 21, 80, 41, 75-i, 43);
    if(i>0){
      ssd1331.Display_Clear(96-i, 43, 95, 63);
    }
    delay(20);
  }
  delay(2000);
}

実行結果

以前の記事と同様でこんな感じに表示されればOKです。

まとめ

私はまだまだプログラミングに関してはアマチュアの素人なので、何か間違いがあったらコメント等でご連絡いただけると助かります。

今後もプログラミングを重ねるにつれて、多々変更していきますのでご了承ください。

この OLED モジュールはフルカラーでありながら、小型でとても使いやすいデバイスですので個人的にお勧めです。
まだ日本の販売店が少ないのが残念ですね。

また、毎回言っておりますが、小型有機EL ( OLED )のパラレル通信のものが殆ど見当たりません。
日本でなぜ出さないのだろうか? といつも疑問に思っております。
ディスプレイドライバではパラレルも対応しているものが殆どなのに、I2C や SPI ばかりです。
パラレルの高速通信が速く出てほしいものです。

以上、次回はこれを使っていろいろと応用していこうと思います。

ではまた・・・。

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

スイッチサイエンス ESPr Developer 32 Type-C SSCI-063647
スイッチサイエンス
¥2,420(2024/03/28 13:45時点)
ZEROPLUS ロジックアナライザ LAP-C(16032)
ZEROPLUS
¥22,504(2024/03/28 20:55時点)
Excelでわかるディープラーニング超入門
技術評論社
¥1,700(2024/03/28 09:40時点)

コメント

  1. 匿名 より:

    KKHMFのほうに、amazon発送の出品がありますね。
    3500円なので、他の2倍~3倍程度ですが、翌日確実に届くので良し悪しでしょうか。
    https://www.amazon.co.jp/gp/offer-listing/B01M8JCPYX/ref=dp_olp_new?ie=UTF8&condition=new

  2. lingmu より:

    拝見しました。自分もOLED+Arduinoでプログラミングをよろうと考えています。
    質問です。OLEDの特徴として、ピクセルごとに輝度コントロールできると聞いています。このシステムでそれは可能でしょうか?
    可能でないなら、可能な制御基板などの情報があれば教えて下さい。

  3. 鳴海耕助 より:

    記事、拝見しました。
    まだ試していないのですが、SSD1331がmode3ですと、SPIのチャンネルを分けたほうが無難ですね。
    気になったのが、記事ではCSも固定と書かれています。これはおそらくSPI-slaveの時の仕様だと思います。
    SPI-masterの時は、CSは接続するデバイスごとに割り振らなければならないので。いかがでしょう?

    • mgo-tec mgo-tec より:

      鳴海さん

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

      この記事は随分昔に書いたもので、もうすっかり忘れてしまいました。
      当時は、私自身も不勉強な事が多いのに、ライブラリ作ったりしてエラそうな事書いていて、お恥ずかしい限りです。
      独学アマチュアですので、間違いが多々あることをお許しください。

      「SPIのチャンネル」という概念が不勉強で良く解らいのですが、僅かな記憶を頼りにお答えすると、ESP32-DevKitCにある2つのSPI(VSPIとHSPI)のうち、HSPIを使っているので、分ける必要は無いかと思います。
      勘違いしていたらゴメンナサイ。

      それと、「CSを固定」とは、どこに書いてましたっけ???
      ちょっと見当たらなかったのですが、ESP32がmasterとして作っているので意味が分かりません。

      これよりずっと後の記事ではこれを発展させてCSでデバイスを切り替えたりしていました。
      それに、SPI MODEが異なるデバイスについては、HSPIとVSPIの2つのSPIで分けて動かしたりしています。
      例えば、micro SDカードはVSPI、SSD1331はHSPIで動かしたりしています。

      ただ、この辺は随分昔に書いたもので、今はちゃんと動くかどうか検証しておりません。
      以上、何か勘違いしていたらすみません。

タイトルとURLをコピーしました