フルカラー OLED SSD1331 を ESP32 ( ESP-WROOM-32 )で動かしてみた

記事公開日:2017年4月25日
最終修正日:2017年5月24日

スポンサーリンク

2017/5/8時点の最新版 Arduino core for the ESP32 にすると、SPI_MODE3 は正しく修正されておりました。
最新版を再インストールする場合、旧版で get.exe を実行するだけでは最新版になりません。
必ず、旧版を削除して、GitHub から ZIPファイルをダウンロードして、get.exe を実行してください。

こんばんは。

今回は、SPI インターフェースの小さなフルカラー 有機EL ( OLED )ディスプレイ SSD1331 モジュールを ESP32-DevKitC ( ESP-WROOM-32 開発ボード )で動かしてみました。
使ったライブラリはArduino core for the ESP32 標準の SPI ライブラリのみです。
あとは自力で構成してみました。

当ブログでは今まで有機EL ( OLED )については、ドライバIC のSSD1306 , SSD1351 などを扱ってきましたが、その姉妹品?のようなものです。

私の場合、SPI 通信の 有機EL ( OLED )を使う場合には、ドライバ IC の SPIクロックサイクルタイムを重視しています。
なぜかというと、このサイクルが短いほど高速描画ができるからです。

SPI 通信よりも更に高速なのがパラレル通信ですが、手軽に買える安価なフルカラーディスプレイでパラレルモジュールというものはなかなか売ってないものです。

以前、ESP8266 ( ESP-WROOM-02 ) でGPIO を増設してパラレルディスプレイを動かしたことがありました。

大き目ドットの有機EL, WS0010 で Yahoo ニュース リアルタイム 電光掲示板を作ってみた

ESP32-DevKitC ( ESP-WROOM-32 開発ボード )が出てGPIOが多くなり、パラレル通信を直接叩くことが容易になったので、是非ともメーカーさんにパラレル通信のフルカラーディスプレイを販売して欲しいと思いますね。
出来れば Amazon さんなんかで販売してくれると助かりますね。

というわけで、ザッとドライバIC の SPI クロックサイクルタイムを比較すると、こんな感じです。

SSD1306  モノクロ 100ns
SSD1351  フルカラー 50ns
SSD1331  フルカラー 150ns

SSD1351 はさすがに高いだけあって早いですね。
SSD1331 はそれに比べて遅いですね。
でも、比較的安価なので仕方ないです。

ただ、SSD1331 には特殊な内蔵コマンドがあり、直線や四角形を描くことが出来ます。
つまり、ドットを並べて線や面を描かなくても、コマンドで描くことができるのです。
それに、画像コピー機能もありました。
これは有り難い機能です。

では、これの使い方について説明します。

使うもの

有機EL ( OLED ) フルカラーディスプレイ SSD1331 モジュール

ドライバIC  SSD1331 は SOLOMON SYSTECH社製です。
それをパッケージにしたものはこんな感じのものです。

ESP32_SSD1331_01

ピンヘッダは予めハンダ付けしてありました。
1年くらい前に購入していて、ようやく動かしたという感じです。
画面に傷がついているように見えますが、これは保護カバーシート上の傷です。
Amazon.co.jp ではここにありました。
ここで購入したかどうか定かではないのですが、同じパーツ構成なので同じだと思います。

このページには回路図がありました。
これはとても見にくくて困りますね。
でも、何とか読み取れます。
3.3V レギュレーターや昇圧コンバーター LM2733 、インダクターなどが組み込まれているのが分かります。

やはり、LCD と違って、フルカラー OLED は比較的値段が高めですね。
この販売店は中国なので、家に届くまで1週間くらいかかるかもしれません。要注意です。
SSD1331 は日本の販売店では見たことないので、中国から買うしかありません。
そもそも、ESP32 も中国製なので、今や日本製にこだわっていては何も工作ができませんね。

ESP32-DevKitC ( ESP-WROOM-32 開発ボード )
ESPRESSIF社製

私は秋月電子通商さんで購入しましたが、Amazonさんでも販売しています。

ESP32-DevKitC は ESP32 を日本の電波法をクリアして技適取得してパッケージにした ESP-WROOM-32 に、USBシリアル変換や、電圧レギュレーター等を組み込んで使い易くしたボードです。

ブレッドボード SAD-101 ( サンハヤト製 )

ESP32-DevKitC をセットしても、片側2列、もう片側1列の空きピンがある、お勧めブレッドボードです。

ジャンパーワイヤー等

接続方法

ESP32-DevKitC の場合、GitHub のArduino core for the ESP32 の以下のページにピンアサインがあります。

https://github.com/espressif/arduino-esp32

これによると、SPI 通信は HSPI 端子と VSPI 端子がありますが、Arduino core for the ESP32 の SPI ライブラリでは何故か VSPI アサインになってしまいます。
そもそも、私自身、HSPI と VSPI の違いが分かりません。
ネットでいろいろ調べてみても、ちゃんと説明しているサイトは殆どありません。
これは謎です。

ということで、VSPI アサインで以下のように接続します。
ただ、今後 SD カードも同時に使うことを考えて、それと競合しないようにピンアサインしました。
SCLK と MOSI は SD カードと共有できます。

ESP32_SSD1331_02

もし、うまく動作しない場合には、それぞれのデータ線を10kΩの抵抗でプルアップしてみると動作するかも知れません。
私の場合はプルアップ無しで動作しました。

写真はこんな感じです。

ESP32_SSD1331_05

Arduino IDE の設定

Arduino IDE は 1.8.2 で動作確認済みです。
Arduino IDE には、予め Arduino core for the ESP32 をインストールしておいてください。
インストール方法は以下のページを参照してください。

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

既にインストールされている方は、get.exe を実行して、最新版にしておいてください。
この get.exe ファイルはセキュリティソフトに引っかかりますね。
悪さしていないとは思いますが・・・。
心配ならば、毎回ZIPファイルを解凍してインストールすると良いと思います。

SSD1331 データシートを読み解く

有機EL ( OLED ) SSD1331 のデータシートはネット上で検索するといろいろなサイトでダウンロードできると思いますので、それを参照してください。
あまり訪れたことが無いようなサイトばかりなので、ここではリンクは紹介しませんのでご了承ください。

これによると、Vcc ( Operating Voltage )が 8V-16V とあり、3.3Vでは動作しないのかな? と疑問に思いましたが、モジュール内に3.3Vレギュレーター (662k) や LM2733 昇圧コンバーター、インダクターが組み込まれていたので、モジュールのVcc 端子には3.3V を入力すればOKでした。

データシートの Table 21 に SPI 通信のタイミングチャートがあります。
それに Clock Cycle Time が 150ns とあります。
つまり、SCL 端子に入力するクロックの最高周波数は、約 6.6MHz くらいです。

SPI通信の場合はCPUの周波数から割り切れて、近い周波数にクロック設定するのが良いとされています。
Arduino core for the ESP32 の CPU 最高周波数は現在 80MHz なので、割り切れる近似値は、5MHz です。
ということで、SPI クロック周波数はプログラム上で 5MHz に設定します。

ClockDivider で設定しても良いのですが、イマイチうまく動かなかったので、今回は使いません。

そして、タイミングチャート図を見ていただけると分かるのですが、下図と同じ SPI_MODE3 です。

SPI_mode_3

ただし、ここで注意していただきたいのです!!!

単純にSPI信号を出力する以下のスケッチを入力して、コンパイル実行してみてください。

#include <SPI.h>
//自動で VSPI となる。SCLK= #18 , MOSI= #23 は自動アサイン。

void setup() {  
  SPI.begin(); //VSPI
  SPI.setFrequency(5000000); //SSD1331 のSPI Clock Cycle Time 最低150ns
  SPI.setDataMode(SPI_MODE3); //オシロで測ると、ESP32のSPI_MODE2はMODE3だったので要注意
}

void loop() {
  SPI.write(0b10100101);
}

このように、SPI.write(); 関数を使えば、SSD1331 へデータバイトを送ることができます。
この場合は 10100101 というバイトをSCLピンへ永遠と送信しつづけます。

そして、SCLK ピンと MOSIピンをオシロスコープで波形を確認するとこうなりました。

ESP32_SSD1331_03

これは明らかに SPI_MODE 2 です。
ESP8266 でもそうでしたが、ESP32 でも同じバグが引き継がれてしまっていますね。
早く直してほしいものです。

では、スケッチを SPI_MODE 2 に修正した場合、波形はこうなりました。

ESP32_SSD1331_04

これが本来の SPI_MODE 3 です。
ここは十分気を付けてください。
このバグは 2017/4/25 時点のもので、将来修正されてしまうかも知れませんので、頭の隅に置いておいた方が良いです。

2017/5/8時点の最新版 Arduino core for the ESP32 にすると、SPI_MODE3 は正しく修正されておりました。
最新版を再インストールする場合、旧版で get.exe を実行するだけでは最新版になりません。
必ず、旧版を削除して、GitHub から ZIPファイルをダウンロードして、get.exe を実行してください。

次に、実際に SSD1331 へデータバイトを制御コマンドとして認識させるためには、CSピンを LOW にして、DCピンをLOW にします。
また、データとして認識させるためには、CSピンをLOW にして、 DCピンを HIGH にします。
(データシートの7.1.3 参照)

Write Command :   CS – LOW,  DC – LOW
Write Data:  CS – LOW,  DC –  HIGH

制御コマンドを送る際には、コマンドレジスタ番号を送信したら、その後はデータではなく、コマンドを送る形にします。
つまり、Display Start Line コマンドで、ゼロという値を送る際に、まず、DCをLOW にして、0xA1 を送信した後、再びDCをLOW にして、ゼロというコマンドを送信します。

簡単なサンプルスケッチ入力

以上を踏まえて、簡単なプログラムを組むとこうなります。

#include <SPI.h>
//自動で VSPI となる。SCLK= #18 , MOSI= #23 は自動アサイン。
const uint8_t cs_OLED = 17; //CS (Chip Select)
const uint8_t DCpin =  16; //OLED DC(Data/Command)
const uint8_t RSTpin =  4; //OLED Reset

void setup() {
  int i, j;
  uint8_t R, G, B, Dot1, Dot2;
  
  SSD1331_Init(cs_OLED, DCpin, RSTpin);

  for(j=0; j<64; j++){ //画面黒塗りつぶし
    for(i=0; i<96; i++){
      DataWrite(0);
    }
  }

  R = 7; G = 0; B = 0; //256 color : R (0-7), G (0-7), B (0-3) 
  Dot1 = (R << 5) | (G << 2) | B;
  
  R = 0; G = 0; B = 3; //256 color : R (0-7), G (0-7), B (0-3) 
  Dot2 = (R << 5) | (G << 2) | B;
  
  for(j=0; j<64; j++){
    for(i=0; i<96; i++){
      if(j<8 && i<16) {
        DataWrite(Dot1);
      }else{
        DataWrite(Dot2);
      }
    }
  }
}

void loop() {
  
}

//*********** SSD1331 初期化 ****************************
void SSD1331_Init(uint8_t CS, uint8_t DC, uint8_t RST){  
  pinMode(RST, OUTPUT);
  pinMode(DC, OUTPUT);
  pinMode(CS, OUTPUT);

  digitalWrite(RST, HIGH);
  digitalWrite(RST, LOW);
  delay(1);
  digitalWrite(RST, HIGH);

  digitalWrite(CS, HIGH);
  digitalWrite(DC, HIGH);

  SPI.begin(); //VSPI
  SPI.setFrequency(5000000); //SSD1331 のSPI Clock Cycle Time 最低150ns
  SPI.setDataMode(SPI_MODE2); //オシロで測ると、ESP32のSPI_MODE2はMODE3だったので要注意

  CommandWrite(0xAE); //Set Display Off
  CommandWrite(0xA0); //Remap & Color Depth setting 
    CommandWrite(0b00110010); //A[7:6] = 00; 256 color. A[7:6] = 01; 65k color format
  CommandWrite(0xA1); //Set Display Start Line
    CommandWrite(0);
  CommandWrite(0xA2); //Set Display Offset
    CommandWrite(0);
  CommandWrite(0xA4); //Set Display Mode (Normal)
  CommandWrite(0xA8); //Set Multiplex Ratio
    CommandWrite(63); //15-63
  CommandWrite(0xAD); //Set Master Configration
    CommandWrite(0b10001110); //a[0]=0 Select external Vcc supply, a[0]=1 Reserved(reset)
  CommandWrite(0xB0); //Power Save Mode
    CommandWrite(0x1A); //0x1A Enable power save mode. 0x00 Disable
  CommandWrite(0xB1); //Phase 1 and 2 period adjustment
    CommandWrite(0x74);
  CommandWrite(0xB3); //Display Clock DIV
    CommandWrite(0xF0);
  CommandWrite(0x8A); //Pre Charge A
    CommandWrite(0x81);
  CommandWrite(0x8B); //Pre Charge B
    CommandWrite(0x82);
  CommandWrite(0x8C); //Pre Charge C
    CommandWrite(0x83);
  CommandWrite(0xBB); //Set Pre-charge level
    CommandWrite(0x3A);
  CommandWrite(0xBE); //Set VcomH
    CommandWrite(0x3E);
  CommandWrite(0x87); //Set Master Current Control
    CommandWrite(0x06);
  CommandWrite(0x15); //Set Column Address
    CommandWrite(0);
    CommandWrite(95);
  CommandWrite(0x75); //Set Row Address
    CommandWrite(0);
    CommandWrite(63);
  CommandWrite(0x81); //Set Contrast for Color A
    CommandWrite(255);
  CommandWrite(0x82); //Set Contrast for Color B
    CommandWrite(255);
  CommandWrite(0x83); //Set Contrast for Color C
    CommandWrite(255);
  CommandWrite(0xAF); //Set Display On
  delay(110); //0xAFコマンド後最低100ms必要
}
//********** SPI コマンド出力 ****************************
void CommandWrite(uint8_t b){  
  digitalWrite(cs_OLED, LOW);
  digitalWrite(DCpin, LOW);//DC
  SPI.write(b);
  digitalWrite(cs_OLED, HIGH);
}
//********** SPI データ出力 ****************************
void DataWrite(uint8_t b){  
  digitalWrite(cs_OLED, LOW);
  digitalWrite(DCpin, HIGH);//DC
  SPI.write(b);
  digitalWrite(cs_OLED, HIGH);
}

【解説】

Arduino core for the ESP32 の SPI ライブラリでは、なぜか VSPI アサインとなってしまいます。
私は VSPI と HSPI の違いが分からなくて申し訳ないのですが、このライブラリでは何故か自動的に VSPI のピンアサインになってしまいます。

41行目から SSD1331 の初期化になります。
コマンド送信は、104-109行目のようにします。
データ送信は、111-116行目のようにします。

●55行目:
周波数を5MHz にすると、SPI Clock Cycle Time は 200ns 程になりますので、ちょっと遅いですね。

●56行目:
先ほど注意したように、SPI_MODE2 にしておくことに注意してください。

●59-60行目;
ここのコマンドが重要です。
ここで、256色カラーにするか、65000色にするか決定します。
ここでは、256色カラーの設定にしています。
7bit をゼロ、6bit をゼロとすれば、256色カラーとなります。
その場合、19-23行目のように、赤(R)、緑(G)、青(B) それぞれ指定した数値をビットシフトして、8bit に収めて送信します。
また、60行目の0bit – 5bit までは、ディスプレイの始点や描画方向を設定しています。
この場合は、画面の左上端を始点 ( 0, 0 ) としたい場合の設定です。
(データシート 7.5.2 参照)

●88-93行;
ここで、実際に描画する範囲を決定します。

●100行目:
データシートによると、ディスプレイONコマンドを送信したら、安定するまで最低100ms 必要らしいです。

その他コマンドは良く分からないものもあります。
概ねデータシートにあるRESET値を使用しました。

実際に画面にピクセルを表示させるためには、13-17行のようにゼロというデータビットを送って黒画面にします。

19-23行で256色のデータビットを設定して、25-33行でデータビットを連続で送ると、指定した描画範囲内だけGDDRAM に書き込み、そこのピクセルが光ります。

サンプルスケッチコンパイル実行

では、スケッチをコンパイル実行してみてください。
こんな感じで表示されればOKです。

ESP32_SSD1331_06

赤い四角が青い四角より少しはみ出して見えると思います。
これは、ピクセルが赤色、緑色、青色の合計3つのピクセルで成り立っているためです。
三原色を使って色を作っていきます。

グラフィックアクセラレーションコマンドを使ってみる

では、SSD1331 に元々装備されている、グラフィックアクセラレーションコマンドを使ってみます。
データシートにコマンド使用方法が書かれていますので、それに従います。

例えば、Draw Line コマンドならば、始点 ( x0, y0 ) 、終点 ( x1, y1 ) を指定して、カラーを RGB 65k色で指定すれば直線が描けます。

グラフィックアクセラレーションコマンドの良いところは、例えば四角形を塗りつぶす場合に、自力でピクセルを連続してSPI 通信でデータを送信することよりも、遙かに少ないバイト数送信で描けることです。
このコマンドがあることによって、プログラムを効率よく書けることになります。

では、私なりにサンプルスケッチを作ってみましたので、以下を入力してみてください。

#include <SPI.h>
//自動で VSPI となる。SCLK= #18 , MOSI= #23 は自動アサイン。
const uint8_t cs_OLED = 17; //CS (Chip Select)
const uint8_t DCpin =  16; //OLED DC(Data/Command)
const uint8_t RSTpin =  4; //OLED Reset

void setup() {
  SSD1331_Init(cs_OLED, DCpin, RSTpin);
}

void loop() {
  int i, j;
  uint8_t R, G, B, Dot1, Dot2;

  Display_Clear(0, 0, 95, 63);

  CommandWrite(0xAE); //Set Display Off
  delay(1000);

  CommandWrite(0xA0); //Remap & Color Depth setting 
    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++){
      DataWrite(Dot1);
    }
  }

  CommandWrite(0xAF); //Set Display On
  delay(110); //0xAFコマンド後最低100ms必要
  Brightness_FadeIn(4);
  delay(1000);
  Brightness_FadeOut(4);
  delay(1000);
  CommandWrite(0xAE); //Set Display Off
  delay(1000);

  Brightness_FadeIn(0);
  Display_Clear(0, 0, 95, 63);
  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++){
      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++){
      DataWrite(Dot1);
    }
  }
  delay(2000);

  Display_Clear(0, 0, 95, 63);

  CommandWrite(0xA0); //Remap & Color Depth setting 
    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)
  Dot1 = (R << 3) | (G >> 3);
  Dot2 = (G << 5) | B;
  for(j=0; j<64; j++){
    for(i=0; i<96; i++){
      DataWrite(Dot1); //65k colorモードでは、2バイトデータを送る
      DataWrite(Dot2);
    }
  }
  delay(2000);

  Display_Clear(0, 0, 95, 63);
  Drawing_Line(0, 0, 95, 63, 31, 0, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  Drawing_Line(95, 0, 0, 63, 0, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  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){
    Drawing_Line(i, 63, 95, 63-i, 0, i, 31);
  }
  delay(2000);

  Display_Clear(0, 0, 95, 63);
  Drawing_Rectangle_Line(20, 20, 40, 40, 31, 0, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  Drawing_Rectangle_Line(0, 0, 60, 60, 0, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  Drawing_Rectangle_Line(70, 10, 80, 63, 0, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  Display_Clear(0, 0, 95, 63);
  Drawing_Rectangle_Fill(0, 0, 60, 60, 0, 31, 0, 31, 0, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  Drawing_Rectangle_Fill(20, 20, 40, 40, 0, 0, 31, 0, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  Drawing_Rectangle_Fill(70, 10, 80, 63, 31, 63, 31, 0, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(2000);

  Display_Clear(0, 0, 95, 63);
  CommandWrite(0xA0); //Remap & Color Depth setting 
    CommandWrite(0b00110010); //A[7:6] = 00; 256 color.

  Drawing_Circle_Line_256color(31, 31, 31, 7, 0, 0); //Red(0-7), Green(0-7), Blue(0-3)
  delay(2000);
  Drawing_Circle_Line_256color(50, 31, 20, 0, 7, 0); //Red(0-7), Green(0-7), Blue(0-3)
  delay(2000);
  Drawing_Circle_Line_256color(70, 31, 10, 0, 0, 3); //Red(0-7), Green(0-7), Blue(0-3)
  delay(2000);

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

  Display_Clear(0, 0, 95, 63);
  CommandWrite(0xA0); //Remap & Color Depth setting 
    CommandWrite(0b01110010); //A[7:6] = 01; 65k color format

  Drawing_Circle_Line_65kColor(31, 31, 31, 0, 63, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(1000);
  Drawing_Circle_Line_65kColor(50, 31, 20, 31, 0, 31); //Red(0-31), Green(0-63), Blue(0-31)
  delay(1000);
  Drawing_Circle_Line_65kColor(70, 31, 10, 31, 31, 0); //Red(0-31), Green(0-63), Blue(0-31)
  delay(1000);

  Copy(60, 21, 80, 41, 0, 0);
  Copy(60, 21, 80, 41, 75, 0);
  Copy(60, 21, 80, 41, 0, 43);

  for(i=0; i<76; i++){
    Copy(60, 21, 80, 41, 75-i, 43);
    if(i>0){
      Display_Clear(96-i, 43, 95, 63);
    }
    delay(20);
  }

  delay(2000);
}

//*********** SSD1331 初期化****************************
void SSD1331_Init(uint8_t CS, uint8_t DC, uint8_t RST){  
  pinMode(RST, OUTPUT);
  pinMode(DC, OUTPUT);
  pinMode(CS, OUTPUT);

  digitalWrite(RST, HIGH);
  digitalWrite(RST, LOW);
  delay(1);
  digitalWrite(RST, HIGH);

  digitalWrite(CS, HIGH);
  digitalWrite(DC, HIGH);

  SPI.begin(); //VSPI
  SPI.setFrequency(5000000); //SSD1331 のSPI Clock Cycle Time 最低150ns
  SPI.setDataMode(SPI_MODE2); //オシロで測ると、ESP32のSPI_MODE2はMODE3だったので要注意

  CommandWrite(0xAE); //Set Display Off
  CommandWrite(0xA0); //Remap & Color Depth setting 
    CommandWrite(0b00110010); //A[7:6] = 00; 256 color. A[7:6] = 01; 65k color format
  CommandWrite(0xA1); //Set Display Start Line
    CommandWrite(0);
  CommandWrite(0xA2); //Set Display Offset
    CommandWrite(0);
  CommandWrite(0xA4); //Set Display Mode (Normal)
  CommandWrite(0xA8); //Set Multiplex Ratio
    CommandWrite(63); //15-63
  CommandWrite(0xAD); //Set Master Configration
    CommandWrite(0b10001110); //a[0]=0 Select external Vcc supply, a[0]=1 Reserved(reset)
  CommandWrite(0xB0); //Power Save Mode
    CommandWrite(0x1A); //0x1A Enable power save mode. 0x00 Disable
  CommandWrite(0xB1); //Phase 1 and 2 period adjustment
    CommandWrite(0x74);
  CommandWrite(0xB3); //Display Clock DIV
    CommandWrite(0xF0);
  CommandWrite(0x8A); //Pre Charge A
    CommandWrite(0x81);
  CommandWrite(0x8B); //Pre Charge B
    CommandWrite(0x82);
  CommandWrite(0x8C); //Pre Charge C
    CommandWrite(0x83);
  CommandWrite(0xBB); //Set Pre-charge level
    CommandWrite(0x3A);
  CommandWrite(0xBE); //Set VcomH
    CommandWrite(0x3E);
  CommandWrite(0x87); //Set Master Current Control
    CommandWrite(0x06);
  CommandWrite(0x15); //Set Column Address
    CommandWrite(0);
    CommandWrite(95);
  CommandWrite(0x75); //Set Row Address
    CommandWrite(0);
    CommandWrite(63);
  CommandWrite(0x81); //Set Contrast for Color A
    CommandWrite(255);
  CommandWrite(0x82); //Set Contrast for Color A
    CommandWrite(255);
  CommandWrite(0x83); //Set Contrast for Color A
    CommandWrite(255);
  CommandWrite(0xAF); //Set Display On
  delay(110); //0xAFコマンド後最低100ms必要
}
//***********ディスプレイ消去****************************
void Display_Clear(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1){
  delayMicroseconds(500); //クリアーコマンドは400μs 以上の休止期間が必要かも
  CommandWrite(0x25); //Clear Window
    CommandWrite(x0); //Column Address of Start
    CommandWrite(y0); //Row Address of Start
    CommandWrite(x1); //Column Address of End
    CommandWrite(y1); //Row Address of End
  delayMicroseconds(800); //ここの間隔は各自調節してください。
}
//***********画面明るさフェードイン**********************
void Brightness_FadeIn(uint8_t interval){
  for(int brightness = 0; brightness < 256; brightness++){
    CommandWrite(0x81); //Set Contrast for Color A
      CommandWrite(brightness);
    CommandWrite(0x82); //Set Contrast for Color A
      CommandWrite(brightness);
    CommandWrite(0x83); //Set Contrast for Color A
      CommandWrite(brightness);
    delay(interval);
  }
}
//***********画面明るさフェードアウト********************
void Brightness_FadeOut(uint8_t interval){
  for(int brightness = 255; brightness >= 0; brightness--){
    CommandWrite(0x81); //Set Contrast for Color A
      CommandWrite(brightness);
    CommandWrite(0x82); //Set Contrast for Color A
      CommandWrite(brightness);
    CommandWrite(0x83); //Set Contrast for Color A
      CommandWrite(brightness);
    delay(interval);
  }
}
//************ 256色カラー 1ピクセル 描画 *****************
void Drawing_Pixel_256color(uint8_t x0, uint8_t y0, uint8_t R, uint8_t G, uint8_t B){
  //R (0-7), G (0-7), B (0-3)
  CommandWrite(0x15); //Set Column Address
    CommandWrite(x0);
    CommandWrite(x0);
  CommandWrite(0x75); //Set Row Address
    CommandWrite(y0);
    CommandWrite(y0);

  uint8_t Dot = (R << 5) | (G << 2) | B;

  DataWrite(Dot);
}
//************ 65000色カラー 1ピクセル 描画 *****************
void Drawing_Pixel_65kColor(uint8_t x0, uint8_t y0, uint8_t R, uint8_t G, uint8_t B){
  uint8_t Dot1, Dot2;
  CommandWrite(0x15); //Set Column Address
    CommandWrite(x0);
    CommandWrite(x0);
  CommandWrite(0x75); //Set Row Address
    CommandWrite(y0);
    CommandWrite(y0);

  Dot1 = (R << 3) | (G >> 3);
  Dot2 = (G << 5) | B;

  DataWrite(Dot1);
  DataWrite(Dot2);
}
//************ 65000色 線 描画******************************
void 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){
  //Line_R (0-31), Line_G (0-63), Line_B (0-31)
  uint8_t R, B;
  R = Line_R <<1;
  B = Line_B <<1;

  CommandWrite(0x21);
    CommandWrite(X0);
    CommandWrite(Y0);
    CommandWrite(X1);
    CommandWrite(Y1);
    CommandWrite(R);
    CommandWrite(Line_G);
    CommandWrite(B);
}
//************ 65000色 四角 線描画***************************
void 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){
  //Line_R (0-31), Line_G (0-63), Line_B (0-31)
  uint8_t R, B;
  R = Line_R <<1;
  B = Line_B <<1;

  CommandWrite(0x26); //Fill Enable or Disable
    CommandWrite(0b00000000); //A0=1 Fill Disable
    
  CommandWrite(0x22); //Drawing Rectangle
    CommandWrite(X0); //Column Address of Start
    CommandWrite(Y0); //Row Address of Start
    CommandWrite(X1); //Column Address of End
    CommandWrite(Y1); //Row Address of End
    CommandWrite(R);
    CommandWrite(Line_G);
    CommandWrite(B);
    CommandWrite(0);
    CommandWrite(0);
    CommandWrite(0);
}
//************ 65000色 四角 塗りつぶし描画***************************
void 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){
  //Line_R (0-31), Line_G (0-63), Line_B (0-31)
  //Fill_R (0-31), Fill_G (0-63), Fill_B (0-31)
  uint8_t lineR, lineB, fillR, fillB;
  lineR = Line_R <<1;
  lineB = Line_B <<1;
  fillR = Fill_R <<1;
  fillB = Fill_B <<1;
  
  CommandWrite(0x26); //Fill Enable or Disable
    CommandWrite(0b00000001); //A0=0 Fill Enable

  CommandWrite(0x22); //Drawing Rectangle
    CommandWrite(X0); //Column Address of Start
    CommandWrite(Y0); //Row Address of Start
    CommandWrite(X1); //Column Address of End
    CommandWrite(Y1); //Row Address of End
    CommandWrite(lineR);
    CommandWrite(Line_G);
    CommandWrite(lineB);
    CommandWrite(fillR);
    CommandWrite(Fill_G);
    CommandWrite(fillB);
}
//************* 256色カラー 円 線描画***********************
void 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){
  //Line_R (0-7), Line_G (0-7), Line_B (0-3)
  uint8_t x1, y1;

  for(int i=0; i<360; i++){
    x1 = round((float)(x0 + (r * cos(radians(i)))));
    y1 = round((float)(y0 + (r * sin(radians(i)))));
    Drawing_Pixel_256color(x1, y1, Line_R, Line_G, Line_B);
  }
  delay(1);
  //描画範囲を元に戻して置く
  CommandWrite(0x15); //Set Column Address
      CommandWrite(0);
      CommandWrite(95);
    CommandWrite(0x75); //Set Row Address
      CommandWrite(0);
      CommandWrite(63);
}
//************* 65000色カラー 円 線描画***********************
void 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){
  uint8_t x1, y1;

  for(int i=0; i<360; i++){
    x1 = round((float)(x0 + (r * cos(radians(i)))));
    y1 = round((float)(y0 + (r * sin(radians(i)))));
    Drawing_Pixel_65kColor(x1, y1, Line_R, Line_G, Line_B);
  }
  delay(1);
  //描画範囲を元に戻して置く
  CommandWrite(0x15); //Set Column Address
      CommandWrite(0);
      CommandWrite(95);
    CommandWrite(0x75); //Set Row Address
      CommandWrite(0);
      CommandWrite(63);
}
//************* 65000色カラー 円塗りつぶし描画*****************
void Drawing_Circle_Fill(uint8_t x0, uint8_t y0, uint16_t r, uint8_t Line_R, uint8_t Line_G, uint8_t Line_B){
  //Line_R (0-31), Line_G (0-63), Line_B (0-31)
  uint8_t R, B;
  R = Line_R <<1;
  B = Line_B <<1;

  uint8_t x1, y1;

  for(int i=0; i<360; i++){
    x1 = round((float)(x0 + (r * cos(radians(i)))));
    y1 = round((float)(y0 + (r * sin(radians(i)))));
    Drawing_Line(x0, y1, x1, y1, R, Line_G, B);
  }
}
//********** 範囲コピー *********************************
void Copy(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t X, uint8_t Y){
  CommandWrite(0x23); //Copy 
    CommandWrite(x0); //コピーする範囲水平始点
    CommandWrite(y0); //コピーする範囲垂直始点
    CommandWrite(x1); //コピーする範囲水平終点
    CommandWrite(y1); //コピーする範囲垂直終点
    CommandWrite(X); //コピー先の左上X点
    CommandWrite(Y); //コピー先の左上Y点
}
//********** SPI コマンド出力 ****************************
void CommandWrite(uint8_t b){
  digitalWrite(cs_OLED, LOW);
  digitalWrite(DCpin, LOW);//DC
  SPI.write(b);
  digitalWrite(cs_OLED, HIGH);
}
//********** SPI データ出力 ****************************
void DataWrite(uint8_t b){
  digitalWrite(cs_OLED, LOW);
  digitalWrite(DCpin, HIGH);//DC
  SPI.write(b);
  digitalWrite(cs_OLED, HIGH);
}

【解説】

●15行:
222-230行のディスプレイ消去関数です。
これの注意点は、delayMicroseconds 関数を適度に入れないと、完全に消去し切れずに画面が乱れますのでご注意ください。
特に、連続して消去する場合には前後に delayMicroseconds 関数を置いて適度に調整してください。

●17行:
一旦画面をオフにします。

●20-21行:
一旦、256色カラーモードにします。

●23-29行:
256色カラーで、画面全体を赤色にします。
これは、グラフィックアクセラレーションを使わずに、自力でドット(ピクセル)を表示させています。

●31-32行:
画面ONコマンドを送信したら、100ms 以上待ちます。

●33行:
232-242行の明るさ( Brightness )フェードイン関数です。
RGBそれぞれのピクセルのコントラストを256段階で調整できます。
ただ、コントラストなので、ゼロにしても僅かに光っています。

●35行:
コントラストのフェードアウト関数です。

●64-65行:
65000色カラーに設定するコマンドです。

●67-75行:
65000色カラー、format 1 でピクセルを表示させるには、2バイト送ります。
そのためにRGBそれぞれビットシフトして2バイトに収めます。
ここでは白画面を表示しています。

●79-90行:
グラフィックアクセラレーションコマンドの Drawing Line を使って直線を描いています。
カラーは 65000色です。
ただ、注意していただきたいのは、289-290行のようにRとBはビットを1左にずらさねばなりません。

●94-100行:
302-322行の四角形の線画コマンドを使います。
これは、塗りつぶし可否のコマンドを送信してから描画します。
これも65000色カラーですが、RとBは1ビット左へシフトします。

●104-110行:
324-347行の四角形塗りつぶしコマンドを使います。
これは、外周の線と塗りつぶし色を別指定できます。

●117-121行:
349-366行で、256色カラーで円の線画を描く関数を自作しました。
ピクセルを1つずつ計算して表示させます。
256-268行で1ピクセルを描く関数を呼んでいますが、ここでは、1ピクセルずつ範囲指定コマンドを送信しています。
ですから、円を描き終わった後は範囲指定を画面全体に戻しておくことが必要です。

●125-129行:
386-399行にあるように、Drawing Line コマンドを使って円を塗りつぶす関数を作りました。
円の塗りつぶしはドットを表示させるよりも、線で塗りつぶした方が遙かに効率が良いです。

●133-140行:
65000色カラーで円の線画を描いています。
368-384行で関数化しています。

●143-145行:
指定した範囲の画面をコピーして、指定した場所に表示させる関数です。
401-409行にあるように、グラフィックアクセラレーションコマンドがあります。
これは便利ですよ。
使いようによってはとっても面白いものができます。

●147-153行:
コピーコマンドとクリアーコマンドをうまく使って、アニメーション的にしてみました。

グラフィックアクセラレーションを実行してみる

では、これをコンパイルして実行してみてください。
以下の動画のように表示されればOKです。

いかがでしょうか。
なかなかこのグラフィックアクセラレーションコマンドは使えますよね。
これをうまく使えば、電光掲示板のようなこともできそうです。
SPIクロックサイクルが150ns と遅いので、これをつかえばカバーできそうです。

以上、今回はここまでです。

OLED SSD1331 を使って Yahoo! RSS ニュース電光掲示板も既に作ってみましたが、SSD1351 に負けない良いものができていますので、近々記事に上げたいと思っています。

いつか信頼あるメーカーさんから格安パラレル通信のフルカラーディスプレイが販売されることを願って・・・。

ではまた・・・。

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

スポンサーリンク

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







投稿者:

mgo-tec

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

コメントを残す

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

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