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

ESP32 ( ESP-WROOM-32 )

修正された 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 を使っています。

ZEROPLUS ロジックアナライザ LAP-C(16032)
ZEROPLUS
¥19,358(2024/10/12 03:29時点)

これはロジアナとしては安いのに 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です。

コメント

  1. 匿名 より:

    You have “readBytes is so explosive that it is incomparable.”

    That should state writeBytes, not readBytes.

  2. nino より:

    ありがとうございます。とても
    参考になりました。
    受信側は複数バイト どうなるんでしょうか

    • mgo-tec mgo-tec より:

      ninoさん

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

      ただ、この記事は3年半以上も前に書いたもので、現在の環境とは異なっていることをご了承ください。
      Arduino core for the ESP32 では、だいぶ修正されてきています。

      また、この頃は私自身がまだまだ勉強不足で、ただ単に標準的なプログラミング手法を知らなかっただけということもありました。
      プロのプログラマーの間では複数バイトをまとめて送ることはあたりまえの作法だったようです。

      ESP32のSPI受信はやったことが無いので、想像でしか言えませんが、おそらく1バイトづつ受信するより、まとめて受信した方が断然高速だと思われます。

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