修正された SPI_MODE の動作確認
では、最新の Arduino core for the ESP32 の SPI_MODE が修正されているか確認してみます。
以下のスケッチを入力して、コンパイル書き込みしてみてください。
10100110
というビットを送信した場合です。
これは SPI_MODE3 にしています。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* * VSPI * SCLK #18 * MOSI #23 * MISO #19 * CS(SS) #5 */ #include <SPI.h> byte b=0b10100110; void setup() { SPI.begin(); SPI.setFrequency(7000000); //SSD1331 のSPI Clock Cycle Time 最低150ns SPI.setDataMode(SPI_MODE3); } void loop() { SPI.write(b); }
SCLK と MOSI ( Master Output Slave Input ) 信号をオシロスコープで確認してみるとこうなりました。
ちゃんと、本来の SPI MODE 3 に直っていましたね。
念のため、SPI_MODE2 にしてみます。
これもちゃんと SPI MODE 2 に修正されていますね。
これで、過去のソースコードを全て修正しなければならなくなりました。
ただ、誤ったMODE でもあまり違和感なく普通に動いているのが悩ましいところです。
電子工作系ブログ記事の場合、こういう修正がエライ大変ですね。
これからブログを立ち上げる方は電子工作系はやめておいた方が良いですよ~・・・。
VSPI や HSPI とは
そもそも、VSPI と HSPI とは何ぞや? ということですが、いろいろ調べてみて何となく分かったことがあります。
ESP32 ( ESP-WROOM-32 )には、Dual SPI や Quad SPI デバイスを動かすために、SPI インターフェースを4つ装備しているようです。
Quad SPI は4つの SPI を同時通信して、外部デバイスのSRAM や Flash を高速で読み書きするためのものだそうです。
ESP-WROOM-32 のデータシートをよ~く見てみると、Parallel QSPI ( Quad SPI ) と書いてあるところがそれです。
その Quad SPI 端子のうち、SHD/SD2 とか、SDO/SD0 のような SD カードっぽい GPIO 名称があります。
ESP32 – DevKitC では、下図の様な赤色囲いのところです。
GitHub の以下のページの下の方にもピンアサインがありますのでそれも参照してみてください。
https://github.com/espressif/arduino-esp32
これから、赤色のところの SDカードインターフェースは、HS ( Hi-Speed ) 通信用端子であることが分かりました。
つまり、以前の記事で説明した SDアソシエーション という団体にライセンス料を払って開発する高速通信モード用端子のようです。
この端子も通常のSPIモードで使えるかも知れませんが、まだ試していません。
ということで、現時点で Arduino core for the ESP32 で使える SPI 端子は、 VSPI と HSPI 端子ということのようです。
VSPI の ‘ V ‘ や HSPI の ‘ H ‘ の意味は良く分かりません。
Twitter で Vertical , Horizontal ではないかという意見もありました。
でも、機能的には通常の SPI と何ら変わりありませんでした。
Hがついているからといって、Hi-Speed ではありませんよ・・・。
HSPI で動かす
OLED SSD1331 を VSPI で動かす方法は以前の記事で説明しました。
では、これを HSPI で動かす場合を説明します。
HSPI 端子は以下のようになります。
GPIO #14 —– SCLK ( SPI Clock )
GPIO #13 —– MOSI ( Master Output Slave Input )
GPIO #12 —– MISO ( Master Input Slave Output )
GPIO #15 —– CS ( Chip Select )
まず、ESP32 – DevKitC に何も接続せずに、以下のスケッチを入力してみてください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* * HSPI * SCLK #14 * MOSI #13 * MISO #12 * CS(SS) #15 */ #include <SPI.h> byte b=0b10100110; SPIClass hspi(HSPI); void setup() { pinMode(15, OUTPUT); digitalWrite(15, HIGH); hspi.begin(14, 12, 13, 15); hspi.setFrequency(7000000); //SSD1331 のSPI Clock Cycle Time 最低150ns hspi.setDataMode(SPI_MODE3); } void loop() { digitalWrite(15, LOW); hspi.write(b); digitalWrite(15, HIGH); }
これで、HSPI 端子から信号が出力されていると思いますので、オシロスコープやロジックアナライザーで確認してみてください。
このスケッチでは、CS ( Chip Select )ピンを digitalWrite で HIGH-LOW を切り替えていますが、実はこれを使わなくても良い方法があります。
それは、 setHwCs 関数を使うことです。
以下のスケッチを入力してみてください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* * HSPI * SCLK #14 * MOSI #13 * MISO #12 * CS(SS) #15 */ #include <SPI.h> byte b=0b10100110; SPIClass hspi(HSPI); void setup() { hspi.begin(14, 12, 13, 15); hspi.setFrequency(7000000); //SSD1331 のSPI Clock Cycle Time 最低150ns hspi.setDataMode(SPI_MODE3); hspi.setHwCs(true); } void loop() { hspi.write(b); }
setHwCs 関数は、ハードウェアCSピンを使うか使わないか選択できる関数です。
begin関数で指定したCSピンがハードウェアCSピンです。
Arduino IDE 系の digitalWrite 関数は速度が遅いということで有名ですよね。
ロジックアナライザーを使ってdigitalWriteを使った様子を見てみたいと思います。
因みに、私のロジックアナライザーは ZEROPLUS LAP-C を使っています。
これはロジアナとしては安いのに 100MHz という優れものです。
では、CSピンを digitalWrite で High – Low 切り替えた場合の波形はこんな感じです。
ちょっと見にくくて申し訳ないのですが、SCK や MOSI 信号発信が無いところでも、しばらくCSピンがLOWになったままですね。
では、setHwCs 関数を使うとこうなります。
見事に SCK や MOSI 信号が発信されたところだけピンポイントで CS が LOW になっていますね。
ただ、これを使ったからと言って、バイト毎の送信間隔はあまり変わらないので、高速化は望めないようです。
でも、CS ( Chip Select )ピンをdigitalWriteで High – Low に切り替えなくて済むので、プログラムを節約できることが、この関数を使う最大の利点です。
これは有り難いですね。
では、実際に フルカラー OLED ( 有機EL ) SSD1331 を HSPI 接続して動作させてみましょう。
接続はこんな感じです。
VSPI 接続の以前の記事の、ブルー画面スケッチを HSPI 接続用に変更したものが以下のスケッチになります。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* * HSPI * SCLK #14 * MOSI #13 * MISO #12 * CS(SS) #15 */ #include <SPI.h> const uint8_t SCLK_OLED = 14; //SCLK (SPI Clock) const uint8_t MOSI_OLED = 13; //MOSI (Master Output Slave Input) const uint8_t MISO_OLED = 12; //SSD1331では実際は使わない。MISO (Master Input Slave Output) const uint8_t CS_OLED = 15; //OLED ChipSelect const uint8_t DC_OLED = 16; //OLED DC (Data/Command) const uint8_t RST_OLED = 4; //OLED Reset SPIClass hspi(HSPI); void setup() { delay(1000); int i, j; uint8_t R, G, B, Dot1, Dot2; SSD1331_Init(SCLK_OLED, MISO_OLED, MOSI_OLED, CS_OLED, DC_OLED, RST_OLED); 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 sck, uint8_t miso, uint8_t mosi, uint8_t cs, uint8_t dc, uint8_t rst){ pinMode(rst, OUTPUT); //RES pinMode(dc, OUTPUT); //DC digitalWrite(rst, HIGH); digitalWrite(rst, LOW); delay(1); digitalWrite(rst, HIGH); digitalWrite(dc, HIGH); hspi.begin(sck, miso, mosi, cs); hspi.setFrequency(7000000); //SSD1331 のSPI Clock Cycle Time 最低150ns hspi.setDataMode(SPI_MODE3); hspi.setHwCs(true); 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(0b00111111); //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(0b00000000); //0x1A Enable power save mode 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); for(int j=0; j<64; j++){ //Display Black OUT for(int i=0; i<96; i++){ DataWrite(0x00); DataWrite(0x00); DataWrite(0x00); } } CommandWrite(0xAF); //Set Display On delay(110); //ディスプレイONコマンドの後は最低100ms 必要 } //********** SPI コマンド出力 ************************** void CommandWrite(uint8_t b){ digitalWrite(DC_OLED, LOW); hspi.write(b); } //********** SPI データ出力 **************************** void DataWrite(uint8_t b){ digitalWrite(DC_OLED, HIGH); hspi.write(b); }
SSD1331 の場合は、データやコマンドを送る前に CS ( Chip Select )ピンを Low にして、DCピンを以下のようにします。
コマンド送信 DC — LOW
データ送信 DC — HIGH
今回は HSPI と VSPI に分けて、setHwCs 関数を使ったので、CSピンを切り替える必要は無く、digitalWrite を使うのは DC ( Data/Command )ピンだけで、とてもシンプルになりました。
では、これをコンパイル実行させてみてください。
こんな感じで表示できていればOKです。
コメント
You have “readBytes is so explosive that it is incomparable.”
That should state writeBytes, not readBytes.
Thanks for finding the mistake.
I fixed that word.
ありがとうございます。とても
参考になりました。
受信側は複数バイト どうなるんでしょうか
ninoさん
記事をご覧いただき、ありがとうございます。
ただ、この記事は3年半以上も前に書いたもので、現在の環境とは異なっていることをご了承ください。
Arduino core for the ESP32 では、だいぶ修正されてきています。
また、この頃は私自身がまだまだ勉強不足で、ただ単に標準的なプログラミング手法を知らなかっただけということもありました。
プロのプログラマーの間では複数バイトをまとめて送ることはあたりまえの作法だったようです。
ESP32のSPI受信はやったことが無いので、想像でしか言えませんが、おそらく1バイトづつ受信するより、まとめて受信した方が断然高速だと思われます。