ESP8266 ( ESP-WROOM-02 ) SPI 通信高速化、その2 ( 複数デバイス、Mode、Watchdog Timer 問題 )

記事公開日:2016年10月12日


スポンサーリンク

では、Arduino core for ESP8266のSDカードライブラリとSPIライブラリ、そしてGPIO レジスタDirect Access を共用した使用例として、次のサンプルスケッチをご覧ください。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include <SPI.h>
#include <SD.h>

#define PIN_OUT *(volatile uint32_t *)0x60000300
#define PIN_OUT_SET *(volatile uint32_t *)0x60000304
#define PIN_OUT_SET_CLEAR *(volatile uint32_t *)0x60000308
#define PIN_ENABLE *(volatile uint32_t *)0x6000030C
#define PIN_ENABLE_SET *(volatile uint32_t *)0x60000310
#define PIN_ENABLE_CLEAR *(volatile uint32_t *)0x60000314
#define PIN_IN *(volatile uint32_t *)0x60000318
#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

File f;

char c;
int i;

void setup() {
  delay(1000);
  Serial.begin(115200);
  Serial.println();

  PIN_OUT = (1<<5 | 1<<4 | 1); // GPIO #0,#4,#5 を出力に設定
  PIN_ENABLE = (1<<5 | 1<<4 | 1); // GPIO #0,#4,#5 の出力を有効
  PIN_00 = 0; // 0=high SD CS
  PIN_05 = 0; // DC
  PIN_04 = 0; // Reset

  if (!SD.begin(15,20000000)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }

  f = SD.open("test.txt", FILE_READ);
  
  SPI.begin();
  SPI.setFrequency(20000000);
  SPI.setDataMode(SPI_MODE2);
  
  Serial.println("wait-------------------");
  delay(5000);
}

void loop() {
  c = f.read();
  f.seek(0);

  SPI.setFrequency(20000000);
  SPI.setDataMode(SPI_MODE2); //※これは MODE 3 なので注意

  SpiCommandWrite(B10110010);
  SpiDataWrite(B10001001);

  SPI.setFrequency(20000000);
  SPI.setDataMode(SPI_MODE0);

  SpiCommandWrite(B10110010);
  SpiDataWrite(B10001001);

  yield();
}

void SpiCommandWrite(byte b){
  PIN_05 = 1; // 1=LOW
  PIN_00 = 1; // 1=LOW
  SPI.write(b);
  PIN_00 = 0; // 0=HIGH 
}

void SpiDataWrite(byte b){
  PIN_05 = 0; // 0=HIGH
  PIN_00 = 1; // 1=LOW
  SPI.write(b);
  PIN_00 = 0; // 0=HIGH
}

【解説】

●4-18行:
GPIO レジスタアドレスの定義です。
好きな名前に設定できます。
アドレスはESPRESSIF社の以下のサイト
ESPRESSIF Support Documents
の esp8266-technical_reference_en.pdf というファイルに表記されている通りです。
今回使わないものもありますが、今後の為に載せておきます。

●20行:
SDカードのファイルハンドルを定義

●30-34行:
今回は別のSPIデバイスを使いませんが、測定の為の例として
CS       GPIO #0
RSET  GPIO #4
DC      GPIO #5
とすると、これをレジスタDirect Access で割り当てる場合、前回の記事で述べたように、ビットをそのピン番号までシフトさせる方式を使って出力を有効にします。

PIN_OUT = (1<<5 | 1<<4 |1);
PIN_ENABLE = (1<<5 | 1<<4 |1);

#0 番ピンは 1<<0 です。
シフトしないのと同じですので、こういう表記になります。
これで、レジスタDirect Access でGPIOピンの出力が有効になります。

そして、PIN_00 = 0; とすればHIGHレベルになります。
間違えないようにしてほしいのは、0がHIGH、 1がLOWです。

●36-49行:
これは、お馴染みのSDカードとSPIの標準的な使い方ですので、説明を省きます。
SDカードとSPI通信のクロック周波数は同じ20MHz にしておきます。

●53-54行:
53行目でSDカードのテキストを1文字(1バイト)読み込みます。
54行目で読み取り位置を最初の文字に戻します。

●56-57行:
SPIライブラリを起動し、SPIモードを3にします。
先にも述べたように、ライブラリの誤りで、SPI_MODE2 がモード3です。
SDカードのモードはゼロなので、ここで変えるわけです。

●59行目:
SPIデバイスではデータを送る前にコマンドを送ります。
それは71-76行で関数化しています。
これは、OLED SSD1351 の場合の例ですが、
DCピンをLOW にして、CSピンをLOWにしてコマンドを出力します。

●60行目:
SPIデバイスに実データを送ります。
78-83行で関数化してます。
これも、OLED SSD1351 の場合の例ですが、
DCピンをHIGHにして、CSをLOWにすればデータ出力できます。

【測定開始】

では、予めmicro SDHC カードにtext.txt ファイルを作っておき、”testing 1, 2, 3, ” と入力して保存しておきます。

シリアルモニタに “wait———–” と出たら5秒以内にロジアナ LAP-C を起動して測定するとこんな感じになりました。


メインloop() 開始直後、初回の micro SDHC カードの文字を読み込むまでは450us も時間がかかっています。
その後、テキストを読み込むわけですが、拡大した図はこんな感じになります。


SCLK 信号はちゃんと SPI mode 0 になってますね。
ただ、疑問なのが、c = f.read(); で1文字読み込むと思いきや、全文字読み込んでしまっています。これは何故でしょうか??? さっぱり分かりません。

その先の信号を拡大してみてみると、こうなります。


loop() を繰り返したら再びSDカードを読み込むと思いきや、一切読み込んでません。
はて???
これは、最初にテキストを読み込んだら、SRAMに記憶しているんでしょうか?
謎です。
どなたか解る方がいらっしゃったら教えていただきたいです。

さて、SDカードの読み書きをする場合のSPI信号はmode 0 でした。
OLED SSD1351 は mode 3 なので、SDカードを読み込んだ後、モードを変えなければなりません。
それが、56-57行目の
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
というわけです。

スケッチ中にこれを入れるのと入れないのとでは時間的に違いがあるのかどうかをロジアナで調べてみるとこんな感じでした。


入れない場合の休止時間が9.3us
入れた場合の休止時間が9.41us

ということで、それほど大きく遅れないということでヨシとします。

実際に入れた場合で、モード切替部分の波形を拡大してみるとこんな感じです。


ちゃんとモードが変えられてますね。
これが変えることができれば、複数のSPIデバイスをコントロールできるということになります。
しっかり、CSやDCピンも動作しているのでバッチリです。

以上です。

5.まとめ

今回はSPIインターフェースについて、更なる発見と多くを学びました。

SDカードも含めた複数のSPIデバイスを使うには、SCLK信号のモード切替ができるかどうかがキモでした。
それと、whileなどの無限ループを組む場合はウォッチドッグタイマを意識して、休止時間を設けることが重要だということを学びました。

そして、それを踏まえたうえで、ライブラリのクロック周波数を最大限まで上げて、GPIO レジスタDirect Access を使えば、かなり速度アップできることを学びました。

ライブラリをよくよく解読してみると、やっぱり、Arduino core for ESP8266 ライブラリでは SPI のレジスタ直叩きを使用しておりましたね。
これには感服いたしました。さすがです。

と、いうことで、これでSPIデバイスは怖くなくなりました。
次回からいろいろと応用していこうと思います。

ではまた・・・。


スポンサーリンク


 

mgo-tec電子工作ブログ管理人おすすめ
Amazon.co.jp
M5Stack Basic
スイッチサイエンス
Amazon.co.jp
ESPr Developer 32
スイッチサイエンス(Switch Science)
Amazon.co.jp
Amazon.co.jp

「ESP8266 ( ESP-WROOM-02 ) SPI 通信高速化、その2 ( 複数デバイス、Mode、Watchdog Timer 問題 )」への4件のフィードバック

  1. mgo-tecさん、こんにちわ。
    ロジックアナライザーやマニュアルの確認等々、詳細な追求をご苦労様でした。
    このような具体的な波形を含んだ丁寧な記事は大変助かっています。
    私も ESP8266 + 2.2″ 240×320 SPI TFT ILI9341 の高速抽画での WDT Error で悩まされています。
    結論:現在(2016.10.13現在)、Adafruit_ILI9341_ライブラリーのサンプルプログラム_graphicstestにてWDT Errorは発生していません。
    理由:サンプルプログラムに yield(); が 16カ所追加されています。
    ライブラリー:以下を使用。
    https://github.com/adafruit/Adafruit_ILI9341
    https://github.com/adafruit/Adafruit-GFX-Library
    2016年4月頃のgraphicstest サンプルには、yield(); が無く WDTが発生していました。
    その時の私の結論は ESP.wdtDisable(); を3カ所記載して動き済ましています。

    TFTの価格:以下は紹介されているアマゾン(1599円)より約半額で2個買えます。
    ebaでの最安値は、733円($7.07)です。業者は sensesmart。
    240×320 3.3V 2.4″ SPI TFT LCD Touch Panel Serial Port Module with PBC ILI9341
    http://www.ebay.com/itm/2-4-240×320-SPI-TFT-LCD-Touch-Panel-Serial-Port-Module-with-PBC-ILI9341-3-3V/172226401724?_trksid=p2047675.c100005.m1851&_trkparms=aid%3D222007%26algo%3DSIC.MBE%26ao%3D1%26asc%3D39242%26meid%3D73e174f5697a42ce9fbbd4c16b9ff194%26pid%3D100005%26rk%3D4%26rkt%3D6%26sd%3D162005196054

    販売数が1番の業者は modulefans で 736円です。
    http://www.ebay.com/itm/240×320-3-3V-2-4-SPI-TFT-LCD-Touch-Panel-Serial-Port-Module-with-PBC-ILI9341-/171983887298?hash=item280b09dbc2:g:4LkAAOSwI-BWMzzZ
    736円

    2.4″ 240×320 TFT の少し前の記事ですが参考になれば、、。
    Try ESP8266 Adafruit_ILI9341 again
    https://macsbug.wordpress.com/2016/04/20/try-esp8266-adafruit_ili9341-again-2/

    Using the TFT LCD display in the ESP8266
    https://macsbug.wordpress.com/2016/04/16/using-the-tft-display-in-the-esp8266/

    How to use the UTFT Library the TFT LCD in ESP8266
    https://macsbug.wordpress.com/2016/04/18/how-to-use-the-utft-library-the-tft-lcd-in-esp8266/

    How to touch operation of the TFT LCD in ESP8266
    https://macsbug.wordpress.com/2016/04/25/how-to-touch-operation-of-the-tft-lcd-in-esp8266-2/

    Bodmer氏はArduinoでTFTのグラフィックス描画性能にトライされ表にあるようにスピードアップされたとの事。
    ただし、これはArduinoでESP8266では動作しませんが参考になります。(答えでなくて申し訳ない)
    Arduino TFT display and font library
    http://www.instructables.com/id/Arduino-TFT-display-and-font-library/?ALLSTEPS
    Step 10: TFT_ILI9341 library now on Github

    1. macsbugさん

      とても有益な情報ありがとうございます。
      そして、記事をお読みいただき感謝いたします。
      yield()はかなり有効ですよね・・・。

      私も今、高速応答TFTまたはOLEDを探しております。
      なかなか安くてSPIの高速通信(例えば、SPI 40MHz以上)対応のものが無いんですよね。
      このリンクを参考に探してみます。

      いろいろとありがとうございました。

  2. nishioka.sstです。以前コメントさせて頂いたことがあります。
    いろいろな製作記事があってとても楽しみにしております。
    記事中のSoft WDT reset、yield(); ・・について参考になりました。
    私も仕事に関係するのですが、最近似たような?ことがありました。
    http://nskikaku.sakura.ne.jp/NS2016/ns2016.html
    (2016,10/5)の記事は関連していると思います。
    原因はまだ良くわからないのですがyield();で上手く動いていますね。
    でもdelay(1);ではダメだったです。

    1. nishioka.sstさん

      ご無沙汰しております。
      いつも当ブログに訪れていただいているようで、感謝いたします。

      記事拝見させていただきました。なるほど・・・!!
      かなり高度なことをやっておられますね。
      しかも、yield()は私が気付く前に既に試されていたんですね。
      スバらしいです!!
      当方の記事と全く同じ症状ですね。
      これは安定動作にはとても有効な方法だと思います。
      これからこういう情報がドンドン出てきて、ますますESP8266が安定して来そうですね。
      また何かありましたらドンドン情報くださいませ。
      m(_ _)m

コメントを残す

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

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください