では、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さん、こんにちわ。
ロジックアナライザーやマニュアルの確認等々、詳細な追求をご苦労様でした。
このような具体的な波形を含んだ丁寧な記事は大変助かっています。
私も 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
macsbugさん
とても有益な情報ありがとうございます。
そして、記事をお読みいただき感謝いたします。
yield()はかなり有効ですよね・・・。
私も今、高速応答TFTまたはOLEDを探しております。
なかなか安くてSPIの高速通信(例えば、SPI 40MHz以上)対応のものが無いんですよね。
このリンクを参考に探してみます。
いろいろとありがとうございました。
nishioka.sstです。以前コメントさせて頂いたことがあります。
いろいろな製作記事があってとても楽しみにしております。
記事中のSoft WDT reset、yield(); ・・について参考になりました。
私も仕事に関係するのですが、最近似たような?ことがありました。
http://nskikaku.sakura.ne.jp/NS2016/ns2016.html
(2016,10/5)の記事は関連していると思います。
原因はまだ良くわからないのですがyield();で上手く動いていますね。
でもdelay(1);ではダメだったです。
nishioka.sstさん
ご無沙汰しております。
いつも当ブログに訪れていただいているようで、感謝いたします。
記事拝見させていただきました。なるほど・・・!!
かなり高度なことをやっておられますね。
しかも、yield()は私が気付く前に既に試されていたんですね。
スバらしいです!!
当方の記事と全く同じ症状ですね。
これは安定動作にはとても有効な方法だと思います。
これからこういう情報がドンドン出てきて、ますますESP8266が安定して来そうですね。
また何かありましたらドンドン情報くださいませ。
m(_ _)m
mgo-tecさん、こんにちは。
私は今ESP32devkitcとSSD1351を使用してmgo-tecさんのようにスピードアップを試みています。
Adafruit_SPITFT.*内のSPI_CS_HIGH()等をdigitalWrite()からDirect Accessに変更することでそれが実現できると踏んでいましたが、何も表示されず上手くいきません。
私の力不足故のことではありますが、アドバイスの程よろしくお願いします。
nagiさん
記事をご覧いただきありがとうございます。
3件同じ内容で投稿されていたので、直近の1件を挙げてお答えします。
まず、この記事は古く、現在の環境ではうまく動かないことがあることをご了承ください。
そして、残念ながらこの記事のGPIO Direct Accessは、ESP8266専用です。
ESP32 は全く異なるレジスタ番号になるので、ESP32では動きません。
ただ単に表示を高速化したいのであれば、最もお勧めなのが、ライブラリのLovyanGFXを使うことです。
これはTwitterでもお世話になっているLovyanさんが製作されたものです。
これは現在、ESP32で最も高速でLCDを描画できるライブラリです。
インストール方法は以下の記事
LovyanGFXライブラリのインストール
で紹介してますし、使い方はネットでも徐々に出始めています。
最近日本語フォントに対応したらしいので、Twitterでお世話になっているたなかまさゆきさんの以下の記事
LovyanGFX入門 その3 日本語フォント描画系
に書かれています。
そのブログには、LovyanGFXの使い方が多数紹介されていますので参考にしてみてください。
私もESP32やM5StackのLCD描画高速化にいろいろ挑戦しましたが、Lovyanさんの仕事量と知識量には到底かないませんでした。
とにかく超高速なのでおススメですよ。