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社製です。
それをパッケージにしたものはこんな感じのものです。
ピンヘッダは予めハンダ付けしてありました。
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 カードと共有できます。
もし、うまく動作しない場合には、それぞれのデータ線を10kΩの抵抗でプルアップしてみると動作するかも知れません。
私の場合はプルアップ無しで動作しました。
写真はこんな感じです。
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信号を出力する以下のスケッチを入力して、コンパイル実行してみてください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#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ピンをオシロスコープで波形を確認するとこうなりました。
これは明らかに SPI_MODE 2 です。
ESP8266 でもそうでしたが、ESP32 でも同じバグが引き継がれてしまっていますね。
早く直してほしいものです。
では、スケッチを SPI_MODE 2 に修正した場合、波形はこうなりました。
これが本来の 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 にして、ゼロというコマンドを送信します。
簡単なサンプルスケッチ入力
以上を踏まえて、簡単なプログラムを組むとこうなります。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#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です。
赤い四角が青い四角より少しはみ出して見えると思います。
これは、ピクセルが赤色、緑色、青色の合計3つのピクセルで成り立っているためです。
三原色を使って色を作っていきます。
グラフィックアクセラレーションコマンドを使ってみる
では、SSD1331 に元々装備されている、グラフィックアクセラレーションコマンドを使ってみます。
データシートにコマンド使用方法が書かれていますので、それに従います。
例えば、Draw Line コマンドならば、始点 ( x0, y0 ) 、終点 ( x1, y1 ) を指定して、カラーを RGB 65k色で指定すれば直線が描けます。
グラフィックアクセラレーションコマンドの良いところは、例えば四角形を塗りつぶす場合に、自力でピクセルを連続してSPI 通信でデータを送信することよりも、遙かに少ないバイト数送信で描けることです。
このコマンドがあることによって、プログラムを効率よく書けることになります。
では、私なりにサンプルスケッチを作ってみましたので、以下を入力してみてください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#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 に負けない良いものができていますので、近々記事に上げたいと思っています。
いつか信頼あるメーカーさんから格安パラレル通信のフルカラーディスプレイが販売されることを願って・・・。
ではまた・・・。
Amazon.co.jp 当ブログのおすすめ
コメント
mgo-tec 様
「Arduino core for the ESP32 のインストール方法」の件では大変お世話になりました。
早速実践で試してみようと、「フルカラー OLED SSD1331 をESP32 で動かしてみた」に挑戦してみました。
「ソースコード」スケッチの入力、コンパイル実行はうまくいったのですが、「簡単なサンプルスケッチ入力」は、コンパイル時、5行目の、
const uint8_t RSTpin = 4; //OLED Reset
のところで、「stray ‘\302’ in program」というエラー表示となります。
初心者の私には、このメッセージの意味が理解できません。
初歩的な質問で申し訳ありません、ご教示の程お願いいたします。
juchangさん
記事をお読みいただき、ありがとうございます。
私の場合はそういうエラーは再現できませんでした。
そのエラーメッセージをネットで調べてみると、スペースが全角になっていたりすると出る場合があります。
プログラムのスペースのところは全て半角にしてみてください。
日本語のところ以外は全て半角です。
その他、ソースコードをコピペして IDE に貼り付けたら、必ず一旦保存してください。
それからコンパイルしてみてください。
コピペする場合は、先頭をクリックして、最後をシフトキーを押しながらクリックすると、そのコード全体をコピペできます。
他のソースコードがうまくいったのならば、おそらく文字入力が誤っているものと思われます。
mgo-tec 様
Web でいろいろ調べていましたら、Arduino IDE を使う場合、Aruduino core for the ESP32 以外に、Silicon Labs の USB-UART ブリッジドライバが必要との記事がありました。
私の場合は、Arduino を使い始めたばかりで、むろんインストールはしていません。
コンパイル時に、const … のところでエラー表示となることと関連はありますでしょうか。
また、このドライバのインストールの要否につきましても、ご教示の程お願い致します。
juchangさん
他のコードで正常にコンパイルできていたならば、USBシリアルドライバはインストールされているということです。
ESP32-DevKitC や、ESPr Developer 32 の場合、Windows10 パソコンならば、USBケーブルを挿すだけで、自動的にドライバを検出してくれます。
MAC の場合は私は分かりません。
ESPr Developer 32 は FTDI社製の USBシリアルを使っていて、Windows PC と相性が良いです。
ESP32-DevKitC はあまり相性は良くないですが、最近は特に問題無く動作しています。
const は C言語の宣言なので、ドライバとは一切関係ありません。
mgo-tec 様
丁寧なご指導をいただきありがとうございます。
ご指摘の通り、コピペのやり方が間違っていました。
アドバイス通りにコピペをしましたら、コンパイル実行がすんなりと行き、「フルカラー OLED SSD1331 を ESP32 で動かしてみた」を完成することができました。
これから、「ESP32 で 日本語漢字フォント をカラー OLED に表示させ、4行同時スクロール」に挑戦したいと思います。
今後とも、ご指導の程よろしくお願い致します。
juchangさん
無事動いて良かったです。(^^)
こちらこそ、今後とも当ブログをよろしくお願いいたします。
m(_ _)m
こんにちは
1.8″SPITFTModule(ST7735)とESP32との組合わせを学習中です。
接続コードの本数が少ない方が簡単そうに思い
[VSPI]で試したいのですが・・
私のところでは、どうも・・・「自動的に」 HSPI になるようで
電源の線を除いて5本ないと どうしても、描画されません。
ESP32では[VSPI]がディフォルトとの記述をWebにみます・・
それで、何度も試して見ましたがVSPIでは、上手く行きませんでした。
液晶モジュールは「MSP1803」のキーワードで検索ヒットするモノです。
昨年暮れのセール(中華モール)で安かったので通販購入しました。
セッテングは、こんな感じです。これで描画されるので、
後は使いこんで、慣れれば良いのかも知れません。
でも[VSPI]での記述も知りたいのです。
ST7735のpin 対 ESP32
Vcc 3.3V
GND GND
CS 15
reset 4
A0 12
SDA 13
SCK 14
LED 3.3V
円の描画 Webのページを参考にしたもの
======== Enn-TEST-sketch_
#include
#include
#include
// Adafruit_ST7735.h Hardware枠のpinを使うと自動的に、HSPIの設定になる?
#define TFT_RST 4//2でも可。
#define TFT_CS 15
#define TFT_DC 12
#define TFT_MOSI 13
#define TFT_SCLK 14
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);//括弧内の順番は defineの記述順番でなくともよい。これがどうやらHSPIの記述らしい。
void setup() {
tft.initR(INITR_ GREENTAB); // initialize a ST7735S chip, black tab//シールタブの色によって表示する色が変わる!
tft.fillScreen(ST7735_WHITE); // 画面を白色にして、画面をクリアーする
tft.setTextSize(1); // 文字サイズ、2だと大きい
tft.setTextColor(ST7735_BLACK); // 文字色
tft.drawPixel(tft.width()/2, tft.height()/2, ST7735_BLUE); // ドット表示 (x = 画面幅/2 , y = 画面高さ/2, 色)
tft.setCursor(15, (tft.height()/2)-15); // 文字の表示位置
tft.print(“Center “);
tft.print(tft.width()/2);
tft.print(“, “);
tft.println(tft.height()/2);
tft.drawCircle(tft.width()/2, tft.height()/2, 50, ST7735_BLUE); // 円描画(中心x座標, y , 半径, 色)
delay(2000); //この行 無くても可なのか?、コメントアウトしても下に続く記述が無い所為か、異常起こらない、それとも根拠有りか。
}
void loop() {}
========
学習中につき、いろいろ参照させていただいてます
とんかつさん
記事をごらんいただき、ありがとうございます。
まず、過去の以下の記事をご覧になったという前提でお話します。
https://www.mgo-tec.com/blog-entry-esp32-spimode-hspi-vspi-hispeed.html
ST7735は使ったこと無いですし、Adafruitのライブラリも使っていないので正直分からないのですが、まず、VSPIのピンアサインは以下のサイトを参照してください。
https://github.com/espressif/arduino-esp32
そこにはESP32-DevKitCの場合のピンアサインがありますので参考になると思います。
Adafruitのライブラリは良く解らないのですが、おそらくMISOは接続しなくても動作するかもしれません。
GPIO #23 — MOSI
GPIO #19 — MISO (これは繋げなくても良いかも)
GPIO #18 — SCK
GPIO # 5 — CS
DC や RST は任意のピンで良いです。
このブログ記事にあるようにDCは#16, RSTは#4でも良いと思います。
あとは、良く解りません。
あくまで私の想像です。
コメントありがとうございます。
ESP32に於いてユーザーが一般的に使う事のできる、二通りの方法 VSPI或いはHSPIのうち、一方のHSPIで今はともかく描画出来ています。この二つの方法では、単に接続するpinの「グループ」の違いに依って「v」になるのか「H」になるのか?解りません。
接続する線の本数が半分以下に減らせるモノでもなさそうですので 今、この液晶モジュール+ESP32で出来ている「H」方法を使おうと思います。「ESP32」は、ボードメーカーによってpinの名称に違いがある、よくpinアサインを確かめないとなりません。
とんかつさん
私もVSPIとHSPIの違いは今でもよく分かりません。
私は独学アマチュアなので、自分の得た知識でお答えすると、ESP32には4つのSPIモジュールが備わっていて、その一つがVSPIおよびHSPIと言う感じだと思います。
VSPIの場合は、SPIモードで使うSDカード読み書きと共用になるので、SDカードを使う場合はHSPIを使った方が良いのですが、M5Stackのようにmicro SDカードとLCDをVSPI端子で接続している場合は、時分割制御するという対応しています。
その分、VSPIで制御線を共用していますので、制御線の本数が節約できるという利点があると思います。
また、私が知っている限りでは、SPI通信で接続する線を半分以下にすることはできないと思います。
本数を減らすと言っても、全二重通信を半二重通信にして、MOSIとMISOを共用するくらいです。
半分以下にする場合は、I2C通信くらいかなと思います。
I2C通信はSPIに比べて通信速度がかなり遅いですが。。。
ただ、お使いのST7735がI2Cに対応しているのかは分かりませんし。。。
いずれにしても、HSPIで通信出来ているのならば、それで良いのかと私も思います。
色々、スケッチをいじっていたら、今動いている直前の
スケッチの内容の一部が、今のスケッチと
同居して描画されます?!
スケッチをアップロードすれば、最前のスケッチは
上書きされて残らないと思っていましたが
そうではないようです。データの一部がESP32のどこかに
格納されて残って居るのか?TFTモジュールの問題では無く、スケッチの描き方か、開発ボード(ESP32)の問題か。Arduio/ESP32関係すべて削除しインストールし直しましたが、この現象は、再インストール後も発現します。
スケッチに問題あるようですが・・・・
一番可能性の有るのは、スケッチの記述間違いですが・・
何度みても、記述間違いは無いですね(と・・思います)。
スケッチの前半の「共通項」は「ひな形」として
使っているのです(=スクリーンの初期化行までは)
が、他で問題なく動いています。
コンパイルエラーも出ない、アップロード出来る・・
ソレなのに、一つ前のデータが現在のスケッチに同居して
描画される・・「面白い現象です」
問題箇所は、せいぜい1~4行程度です、どこの問題が
潜んでいるのやら・・・
色の指示も、保護シールタブの色でスクリーンを初期化しても、下流で記述した色に描画してくれないケースが多いです。
「光の三原色」の色の重ね加減(?)で、発光状態が上手くコントロール出来ないみたいな感じです。
(レイヤーみたいなことなのか?)
私のTFTモジュールは「緑」のタブが
付いて居たので「緑でスクリーンの初期化」しているのですが
下流の色指定が、記述(意図)と違う色になります。
「黒」で初期化すると、下流は記述指示通りの色になります。.ppcファイルの中の記述は、「解って居ない私」としては、いじりたくないのですが・・・必要があるかも知れません。TFTモジュールもESP32ボードも「コピー品」だからかも知れませんが・・結構、遊ばれています:)
遊んでいるつもりですが・・実際は、遊ばれてます、あはは。
失礼します。
出来ました。
スケッチの記述に問題が有りました。
始めの頃、出来ていた感じでしたが・・
途中から、「迷子」になりました。
意図通りに、描画出来ました
とんかつさん
無事描画できてよかったですね!!!
(^^)
いつも参考にして勉強させていただいています。
早速ですが、スィッチサイエンスさんのESRr32にSSD1331をつなげて無事に成功し、次いで秋月さんのESP32-DevKitC-VEを購入して同じようにつなぐのですが、全くうまくいきません。
VSPI~HSPIに接続を変えても同様です。
wifi接続やLチカなどは問題なくうまくいきます。
接続のPINを変えたり、、、、ESP32-DevKitを追加で購入したり、、、、ハマッテすでに4日目に突入して、万策尽きてご相談申し上げる次第です。
調べますと当該esp32は昨年11月に発売されたものとの事。
どのように抜け出しら良いのかご教示賜りたく、、、、。
shiyoiさん
記事をご覧いただき、ありがとうございます。
この記事はもう3年半前に書いたもので、今のハードウェアもライブラリも大幅に変わっています。
ですから、残念ながら現在の環境で動くことは保証できません。
ただ、ESP32-DevKitC-VE というものを初めて知ったので、秋月電子通商のページにある回路図を調べてみました。
すると、GPIOの#16と#17はNCとなっており、未接続ということでした。
これは、通常のESP32-DevKitCと違って、VEの方はESP32-WROVERが搭載されていて、ESP32-WROVERにはPSRAMが搭載されています。
そのPSRAM用にGPIO #16, #17が使われている為、ボード上のGPIO #16, #17はピンは出ているけど使えないということです。
(何でこんなピン使えるように出ているのか大いに疑問ですが…。)
よって、#16, #17を他の別のピンに変えて、プログラミングもアサインも変えてコンパイルするか、標準のESP32-DevKitCを使うかだと思います。
VEだと、今後、沢山GPIOを使いたい時に足りなくなる恐れがることを考慮しておいた方が良いですね。
ただ、標準のESP32-DevKitCのV4は問題無さそうですが、私は使ったことないので、動くかどうか分かりません。
mgo-tecさん
ご助言ありがとうございました。
#define sclk 18
#define mosi 23
#define cs 5
#define rst 4
#define dc 19
これで解決しました。
暗く長いトンネルからやっと抜け出しました。
通常のESP32-DevKitCと思い込んでしまいました。
Areduino IDE の再インストールや別のPCでのコンパイルなど遠回りをしてしまいました。
私のような素人は出始めの物には手を出さない方が良いことを痛感しました。
ありがとうございました。
動いて良かったですね。
確かに新しいデバイスは要注意ですね。
因みに、ESP32やM5Stack界隈でお勧めなのが、「らびやん」さん作成のLovyanGFXライブラリを使うと高速描画できますよ~。