ESP8266 ( ESP-WROOM-02 ) SPI 通信の高速化に挑戦

記事公開日:2016年10月6日
最終修正日:2016年10月11日

スポンサーリンク

こんばんは。

今回はちょっとスゴイですよ。
ESP8266 ( ESP-WROOM-02 ) のSPI通信を、標準ライブラリ使用よりも倍以上高速化させることができました。

それは、SPIライブラリを使わずに、SPI信号発信のためにGPIO のレジスタにダイレクトアクセスして信号を生成したことで実現できました。

まずは、以下の動画をご覧ください。
左がAdafruit OLED SSD1351 ライブラリをESP8266用に改変してノーマルで使用したもの。
右がSPI 通信のGPIOをダイレクトアクセスしてレジスタを直接操作したものです。

いかがでしょうか。
かなりスピードアップすることができました。
このAdafruitライブラリのESP8266用の使用方法については過去記事の以下のページを参照してください。

Adafruit 16-bit フルカラー OLED ( SSD1351 ) を ESPr Developer ( ESP-WROOM-02 ) で動かしてみた

次の動画はTwitter検索結果をOLEDに表示して、文字列を電光掲示板風にスクロールするものを比較したものです。

左:ESP8266 SPIFFS ファイルシステム使用。SPI normal speed.
中:ESP8266 SD card 標準(4MHz)。 SPI normal speed.
右:ESP8266 SD card 40MHz .  SPI  GPIO Direct Access.

いかがでしょうか。
GPIOダイレクトアクセスがダントツで速度アップしています。
文字列スクロールについては、過去記事にもありますが、16×16 pixel (ドット)のフリー日本語フォント(東雲)をSDカードから読み出し、1pixelずつ描画しているので、どうしてもSPI通信の速度の影響をモロに受けてしまいます。
ですから、ライブラリを使ってしまうとスクロール速度に限界を感じていました。

しかし!!
ダイレクトアクセスを使ったら見事!、スクロール速度がアップしました!!!

SDカード読み込みもダントツで早くなってますが、文字列スクロールも2~3倍速くなってます。

やったね!!!

ちなみに、Twitter検索に関する記事は以下の記事を参照してください。

Twitter 検索結果のツイートを有機EL ( OLED )に表示させてみた( ESP-WROOM-02 ( ESP8266 )、SSD1351 使用)

前回の記事でロジアナ測定したように、Arduino core for ESP8266 Wi-Fi Chip のSDカード標準ライブラリをそのまま使う場合、SD.begin(15); という関数では4MHzの速度しか出ませんでした。

しかし、
SD.begin(15, 40000000);
とすればおよそ40MHz で読み書きできます。ESP8266 のCPU速度を160MHz にした場合の1/4 の速度ですね。
要するに、CPU周波数の1/4というのはSPI通信の推奨値です。
最大1/2までできるようなのですが、80MHzでは動作しませんでした。
不意のエラーを考えると1/4が良いと思います。
Class4 のmicroSDカードでも格段に速くなります。
これをClass10に変えても速度は変わりませんでした。

ESP8266内蔵フラッシュのSPIFFSでは、ライブラリ中では速度設定ができないようですので、SDカードを使った方が明らかに高速です。
SDカード速度アップについてはそこを変えるだけです。
ライブラリを改変してGPIOダイレクトアクセスをやらなくても、十分高速です。
この速さを知ってしまうと、もうSPIFFSには戻れなくなってきますね。
ただ、SPIFFSでは外付けのSDカードホルダーが要らないっていうメリットがあるので、使い方によりますね。

SDカードの高速化が出来たとしても、ディスプレイ表示や、文字列スクロールを高速化するにはプログラムの改善が必要です。
私の自作ライブラリで、できるだけ高速化を試みて、ほぼ限界の速度が先の動画の中央部分でした。
そこで、ネットで検索したら、なんと、標準ライブラリの digitalWrite() が速度が遅いということがわかりました。
Arduino でもレジスタに直接命令すれば高速化が図れるということは知っていましたが、やはり ESP8266でもあったんです。
これについてとても素晴らしいブログをみつけてしまいました。
macsbugさんという方のこちらのサイトです。
こちら→ ESP8266 の GPIO 速度

こちらも→ ESP8266 Direct Access Port で Lチカ

macsbugさんはとてもコアな記事が豊富で、オシロなどでよく検証されており、とても素晴らしいです。
メチャメチャ参考になりますので、電子工作好きの方は是非ご覧になると良いと思います。

文字列の電光掲示板風スクロールの速度はもう限界かと思っていましたが、この記事のおかげでSPI通信速度を倍以上あげることができました。
標準ライブラリを使わずに独自にSPIライブラリを組んだことも功を奏して、こういうことが自由にできました。

では、私なりにこれをロジックアナライザ ZEROPLUS LAP-C を使って検証してみました。

1.ESP8266 ( ESP-WROOM-02 )のdigitalWrite() の速度検証

スイッチサイエンス製のESPr Developer を使います。
Amazon.co.jp
測定器は前回の記事で紹介した ZEROPLUS社のLAP-C を使います。
それにしても、このロジックアナライザは格安で100MHz もあって、とても重宝してます。
Amazon.co.jp
まず、次のような簡単なスケッチをArduino IDE でコンパイルしてみます。

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println();
  Serial.println("wait-------------");
  delay(5000);
  pinMode(14,OUTPUT);
}

void loop() {
  for(int i=0; i<8; i++){
    digitalWrite(14,HIGH);
    digitalWrite(14,LOW);
  }
}

これはESPr Developer ( ESP-WROOM-02 ( ESP8266 )) のGPIO #14をライブラリ標準のdigitalWrite()関数を使ってHIGHレベルとLOWレベルを交互に繰り返しただけです。

シリアルモニターに “wait———–“ と表示されたら、5秒以内にロジックアナライザ LAP-C 測定をスタートます。
14番ピンがLOWレベルからHIGHレベルに切り替わったらトリガがかかり、ロジアナ測定ができます。

結果、こんな感じになりました。

SPI_speed_up01

まず分かったことは、pinMode(14,OUTPUT); 直後では GPIO はLOWレベルのままだったんです。
私はちょっと勘違いしていたのですが、OUTPUTにすれば自動的にHIGHになると思い込んでいました。
それは間違いだったわけです。ちゃんと測定しないとダメですね。

次にちょっとビックリしたのが、loop()関数に入ったあと、2回目の繰り返しに入るまえに11.15usもの間ブランクがあるんです。
そして、その後はloopを繰り返すたびに3.21usものブランクが毎回生じているんです。
forループは普通に連続で繰り返しています。

要は、loop()で繰り返すよりもforループの方が処理速度は断然速いんです。
この結果には驚きですね。
この件についてはmacsbugさんのサイトのコメント欄でも、とても詳しく解説されていました。
forループやwhileループでも3秒に1度、0.25sec の信号中断が発生しまう謎の動作があるそうです。
どうやらこれが途中でフリーズが起こってしまう原因になっているかもしれません。

次に、このforループ内の周波数を見てみましょう

SPI_speed_up02

約2MHz という速度が出ました。
ただ、SDカードライブラリで40MHz の速度が出せるのに、SPI通信では遅すぎますね。

因みに、forループのインクリメント、デクリメント、While関数で速度が変わるかどうかも測定してみました。

SPI_speed_up03

結果、全く変わりなしでした。
上図のように、パルス幅が全く同じです。
以前、どこかのサイトでデクリメントの方が速いということを目にした記憶がありましたが、Arduino IDE 上のESP8266では変化なしでした。

2.ESP8266 ( ESP-WROOM-02 )のGPIO レジスタ Direct Access の速度検証

digitalWrite()はあくまでライブラリ関数ですので、速度アップのためにはやはりレジスタに直接アクセスしてGPIOのHIGH, LOW を切り替える方が速いです。
Arduino でいうPORTD みたいな操作です。

このレジスタというのはプログラムを組まなくても、予め備わったアドレスに直接アクセスしてGPIOを一括して出力に設定できたり、電圧をHIGHにしたりLOWにしたりできるのです。
Arduino for ESP8266ライブラリのdigitalWrite()関数の中にはいろいろな計算式で成り立っているので、その分速度がおそくなります。
ですから、レジスタに直接アクセスして操作した方が速いわけです。
この方法は先に紹介したmacsbugさんのサイトにありました。

では、ここでもその検証をしてみます。
まず、次のようなレジスタ操作スケッチを書き込みます。

#define PIN_OUT *(volatile uint32_t *)0x60000300
#define PIN_ENABLE *(volatile uint32_t *)0x6000030C

#define PIN_00  *(volatile uint32_t *)0x60000328
#define PIN_02  *(volatile uint32_t *)0x60000330
#define PIN_04  *(volatile uint32_t *)0x60000338
#define PIN_05  *(volatile uint32_t *)0x6000033C
#define PIN_12  *(volatile uint32_t *)0x60000358
#define PIN_13  *(volatile uint32_t *)0x6000035C
#define PIN_14  *(volatile uint32_t *)0x60000360
#define PIN_15  *(volatile uint32_t *)0x60000364

void setup() {
  delay(1000);
  Serial.begin(115200);
  Serial.println();
  Serial.println("wait----------");
  delay(5000);
  PIN_OUT = (1<<14);
  PIN_ENABLE = (1<<14);
}

void loop() {
  for(int i=0; i<8; i++)
    PIN_14 = 1; //LOW
    PIN_14 = 0; //HIGH
  }
}

1行目では、#defineでレジスタアドレス0x60000300 をPIN_OUTという名前で定義してます。

このレジスタアドレスの詳しい動作はESPRESSIF社の以下のサイト
ESPRESSIF Support Documents
の esp8266-technical_reference_en.pdf というファイルの最後の方に記述されていますので参照してみてください。

そして、19行目で1というビットを右に14シフトして、14番目のビットを1にした数値をPIN_OUTに入力するとGPIO #14がOUTPUTモードになります。
そして、20行目でGPIO #14 のOUTPUTを有効にしています。

25~26行目が要注意です。
PIN_14 = 0; では普通はLOWレベルと思いますよね。
実は違っていて、HIGHレベルなんです。これおかしいですよねぇ・・・。
ロジアナLAP-C の測定結果はこうなりました。

SPI_speed_up05

テスターやロジアナで何度も調べましたが、ロジアナの極性が逆ということはありませんでした。
やはり、
1 = LOWレベル
0 = HIGH レベル
でした。
みなさん、注意してください。

それで、digitalWriteとレジスタDirect Access の測定を比較した、ロジアナLAP-Cの結果はこんな感じです。
パルスが出ているところはforループ内の動作で、休止がでているのはloop()関数の影響です。

SPI_speed_up07

パッと見た目で、明らかにレジスタDirect Accessの速度が速いのが分かると思います。
パルス幅が格段に狭くなっていて短い時間で処理を終えていることが分かりますね。
しかも、loop()の休止時間まで早くなっています。
では、forループ内の周波数を見てみましょう。

SPI_speed_up06

macsbugさんの結果とほぼ同じ、約6.7MHz の速度が出ました。
digitalWrite()関数の3倍ですね。
このGPIO #14 はSPI通信のSCLK 信号に使いますので、これができれば、他のGPIO の速度も上げられ、確実に速度アップできるわけです。
最初の方に紹介した動画の文字列スクロールでは、GPIOの速度がモロに影響されるので、速度アップの様子を見るには適しています。
実際のところ様々な要因があって、パッと見た目で速度は2~2.5倍アップというところでしょうか。
これだけでもアップすると、かなり早い印象がありますね。
この情報を公開して下さったmacsbugさんに感謝いたします。
ありがとうございました m(_ _)m

いかがでしたでしょうか。
これは画期的ですよね。

でも、ひとつ疑問があります。
SDカードのSPIパルスは40MHzまで出せるのに、GPIOのSPI出力は出せないんですかねぇ・・・。
これについてはいつか解明したいと思います。

今回は以上です。
Adafruitライブラリや、自作のOLED_SSD1351ライブラリの実際の改変方法については、ちょっと注意事項がありますので、また次回の記事で述べたいと思います。

ではまた・・・。

スポンサーリンク

mgo-tec電子工作 関連コンテンツ ( 広告含む )
Amazon.co.jp 当ブログのおすすめ





投稿者:

mgo-tec

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

「ESP8266 ( ESP-WROOM-02 ) SPI 通信の高速化に挑戦」への4件のフィードバック

  1. mgo-tecさん、SPI通信の高速化に挑戦 の記事に驚嘆しています。
    思わず高速な動きに見とれてしまいます。素晴らしいです。
    1.5″ Color OLEDの記事に刺激され 1.44″ 128×128 Color TFT を動かしてみました。
    その記事中の感想欄に mgo-tecさんの記事のリンクをさせて頂きました。
    もし、リンクで不都合がありましたらお手数ですが連絡ください。
    ブログ:1.44″ 128×128 Color TFT in ESP8266
    https://macsbug.wordpress.com/2016/10/07/1-44-128×128-color-tft-in-esp8266-2/

    1. macsbugさん自ら当ブログにお越しいただき、恐縮です。
      リンクを貼って頂いたことは、何も不都合ございません。
      ありがとうございます。m(_ _)m

      そうなんです、Adafruitライブラリは速度が遅いので、いろいろと改変しなければなりません。

      私が使ったOLEDはmicro SDカードホルダー付きとはいえ、Amazonで購入して5,000円以上しましたが、TFT液晶が1個278円は最強すぎますね。
      これは買いですね。
      今度試してみたいと思います。

      1. mgo-tecさん
        1個278円のTFTはSDは付いていません。
        mgo-tecさんに刺激されて SD 付きが何かと便利かと思っています。
        格安のTFTでSDが付いている物は以下があります。1個356円です。
        今度、調査購入してみようかと思っています。参考になれば、、。
        1.8″ Serial 128X160 SPI TFT LCD Module Display with PCB Adapter ST7735B IC SD
        http://www.ebay.com/itm/1-8-Serial-128X160-SPI-TFT-LCD-Module-Display-with-PCB-Adapter-ST7735B-IC-SD-/112099082229?hash=item1a19a03ff5:g:JbIAAOSwU-pXtF3l

        1. macsbugさん

          情報ありがとうございますm(_ _)m
          356円でSDカードホルダー付きってメチャメチャ安すぎますね。
          こんなんでよく商売できると思ってしまいますね。

          もし購入されたら、レポート記事を楽しみにしております。
          良ければ是非購入したいですね。

          私の方はしばらくこの高価なOLEDを使い倒そうと思ってます。
          5000円のもとを取らねば・・・。

コメントを残す

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

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