Arduino DUEでSDカードI/Fを使おうとすると、通常はSAM3X8Eに搭載されたSPIユニットを使用するので、DUEのSPI端子から配線を引き出して使います。
「#12-6 DUEにSDカードI/Fを接続する」
問題はSPI端子の引き出し位置なのですが、UNO等の機体でICSP端子が配置されたピンヘッダ位置に引き出されているのです(下図)。
これが、何かと使い難い。
実は、TFTシールド+Arduino MEGAで使う場合には、SOFTWARE_SPIという#defineマクロを有効にしてコンパイルすることで、SDライブラリを使ってTFTシールド上のSDカードI/Fにアクセスすることが出来る(※)みたいなのです。
※手元にMEGAが無いので未確認。SDライブラリのソースコードを解析してみた印象です。
MEGAで可能なのであれば、DUEだってSOFTWARE_SPIを有効化できるのでは?今回のテーマは、DUE+TFTシールド+SDライブラリで、SOFTWARE_SPIを有効化して、TFTシールド上のSDカードI/Fにアクセスしてみよう、です。
ただし、今回はCygwinを使ってライブラリのファイルにパッチを当てたり、Arduino IDEの設定ファイルを変更したり、少しハードルが高いので、覚悟してください。本ページでは、記載以上の詳細説明は致しませんので、自力で以下の処理を実行しきる実力がある方のみを対象とします。
今回は、ArduinoのSDライブラリにパッチを当てることになります。筆者が使っているSDライブラリはバージョン1.2.3です。異なるバージョンをインストールしてある場合は、以下文中のファイルパス(ディレクトリ)が異なったり、パッチが失敗する場合があります。
Arduino IDEを起動して、「スケッチメニュー ⇒ ライブラリをインクルード ⇒ ライブラリを管理」を実行します。開いたウインドウで、SDライブラリを探してインストールされているバージョンを確認してください(下図)。
バージョンが違う場合、所望のバージョンを選択してインストールしたり、新しいバージョンにアップデートしたりすることが可能です。
今回確認した時点で、既にバージョン1.2.4がリリースされていました。が、本ページでパッチを当てたのは、バージョン1.2.3ですので、ご注意ください。
バージョン1.2.3をインストールしてあると、SDライブラリのファイルは以下の場所にあります。
<User>\Documents\Arduino\libraries\SD\
<User>はWindowsのユーザディレクトリで、Windowsのエディションや、ログイン名などの設定によって異なります。
SDライブラリ(バージョン1.2.3)にパッチを当てて、DUEでSOFTWARE_SPIを使えるようにします。
パッチはCygwin上のpatchコマンドで当てますので、こちらを参照の上、WindowsにCygwinとpatchコマンドをインストールしておいてください。
Cygwinを実行して、適切なシェル(以下説明はtcshを使用した場合です)ウインドウを開き、以下の通り作業を進めてください。
【 】内はCygwinシェルウインドウからの実行コマンド
ここで一旦Windowsに戻って、上で作ったディレクトリSD_PATCHをエクスプローラで開いておいてください。そうしたら、以下のファイルをダウンロードして、SD_PATCHディレクトリにコピーします。
パッチファイル:Sd2Card_due.patch.txt
更に、Windowsでの作業が続きます。以下のファイルをSD_PATCHディレクトリにコピーします。
<User>\Documents\Arduino\libraries\SD\src\utility\
Sd2Card.cpp
ここまで済んだら、Cygwinに戻ります。
【 】内はCygwinシェルウインドウからの実行コマンド
最後のpatchコマンドがエラー(FAILED)無く終了すればOKです。「Sd2Card.cpp」にパッチが当たって、内容が更新されています。Windowsのエクスプローラで「Sd2Card.cpp」を元の場所に上書きコピーしてください。それで終了です。
もし、以下の様なエラー(FAILED)が発生したら、パッチは失敗しています。SDライブラリのバージョンが異なっていないか、ご自身で「Sd2Card.cpp」を変更して使っていないか、等、確認して原因を取り除いてください。
【patch < Sd2Card_due.patch.txt】
patching file Sd2Card.cpp
Hunk #1 succeeded at 52 with fuzz 1.
Hunk #2 succeeded at 168 (offset 2 lines).
Hunk #3 succeeded at 234 (offset 10 lines).
Hunk #4 FAILED at 322.
Hunk #5 FAILED at 574.
Hunk #6 FAILED at 583.
Hunk #7 succeeded at 631 (offset 27 lines).
3 out of 7 hunks FAILED -- saving rejects to file Sd2Card.cpp.rej
patchコマンドが見つからないといった趣旨のエラーが発生した場合は、こちらを参照して、patchコマンドをインストールしてください。
※本HPではCygwinを活用しますが、細かい使い方の説明はしませんので、自力で使える方のみご利用ください。
パッチを当てただけでは、まだ、SOFTWARE_SPIは有効になっていませんので、普通にSDライブラリを使ってしまうと、いつもの様にICSPピンヘッダからSPI端子を引き出さないとアクセスできません。
コンパイル時に「SOFTWARE_SPI」という#defineマクロを定義しておく必要があります。そのためには、Arduino IDEの設定ファイルを追加/変更する必要があります。
以下のファイルがあるかどうかを確認してください。
<User>\AppData\Local\Arduino15\packages\arduino\hardware\sam\1.6.12\
platform.local.txt
※AppDataディレクトリには不可視属性が付いているので、普通、エクスプローラでは表示されません。エクスプローラの設定で、不可視属性のファイルを表示するオプションを有効にして辿ってください。
もし、platform.local.txtが無かった場合は、空のテキストファイル📝として作成してください。
これで全行程終了です。お疲れさまでした。
以降、DUEを使い、SDライブラリを#includeしてコンパイルすると、常にSOFTWARE_SPIが有効になっています。よってSDライブラリでは、ICSPピンヘッダから配線を引き出しても、SDカードにアクセスできなくなります。
SOFTWARE_SPIが有効になったことで、以下の通り、UNOと同じピンソケット位置にSOFTWARE_SPI端子が引き出されているためです。
SOFTWARE_SPI端子表 | ||
Arduinoピン番号 | 端子名 | 備考 |
D10 | SS | スレーブセレクト |
D11 | MOSI | マスタアウト・スレーブイン |
D12 | MISO | マスタイン・スレーブアウト |
D13 | SCK | シリアルクロック |
SOFTWARE_SPIが有効な間は、上表のピンソケットからSDカードI/Fに接続して使ってください。TFTシールドを装着すると、上表の端子とTFTシールド上のSDカードI/Fが接続されます。つまり、TFTシールド上のSDカードI/Fを使ってSDカードにアクセスすることが出来ます。
なお、上で変更した「platform.local.txt」ファイルは、DUEのコンパイル時だけ有効なので、UNOやSTM32等の機体でコンパイルする時は、影響ありません。UNOやSTM32等、DUE以外の機体では、従来通りSPIユニットの出力端子とSDカードI/Fを接続して使えます。
DUEのSDライブラリでSPIユニットを使ってアクセスしたい場合には、「platform.local.txt」をテキストエディタで開き、「-DSOFTWARE_SPI」を消去してください。
また、SOFTWARE_SPIは、SDライブラリだけで有効なので、SPIライブラリを直接使う場合には、DUEであってもSPIユニットが使用されます。
パッチを当てたSDライブラリを削除したり、別バージョンをインストールしたりした場合には、パッチは消えてしまうので、上の処理をやり直しになります。また、バージョンが異なれば、パッチが成功する保証はありません。
まず、今回のSOFTWARE_SPIコードがDUEだけで有効になるように、マクロ「__SAM3X8E__」で場合分けしてあります。
#ifdef SOFTWARE_SPI
:
: (共通のコード等)
:
#ifdef __SAM3X8E__
:
: (DUEで有効なSOFTWARE_SPIコード)
:
#else // __SAM3X8E__
:
: (元々のSOFTWARE_SPIコード、MEGA用?)
:
#endif // __SAM3X8E__
:
#endif // SOFTWARE_SPI
以下の様なマクロを定義してあって、SPI端子の制御や、nop命令1個分(=1/84MHz[s])の時間稼ぎなどを簡略化しています。
// NOP tune software spi timing
#define spi_NOP __asm__ __volatile__ ("nop")
// SCK: D13(PB27/NCS3/TIOB0)
#define spi_SCK0 REG_PIOB_CODR=(1U<<27)
#define spi_SCK1 REG_PIOB_SODR=(1U<<27)
// MOSI: D11(PD7/A17/BA1/TIOA8)
#define spi_MOSI0 REG_PIOD_CODR=(1U<<7)
#define spi_MOSI1 REG_PIOD_SODR=(1U<<7)
// MISO: D12(PD8/A21/NANDALE/TIOB8)
#define spi_MISO (REG_PIOD_PDSR&(1U<<8))
端子はこちらで示したピン番号で固定なので、I/Oレジスタを直接叩いて高速化しています。
SDライブラリの中で、SPI通信は以下2つの関数に集約されているので、この2つをDUE用に作成しました。
uint8_t spiRec (void)
{
int idx;
int data;
// no interrupts during byte receive - about 1 us
noInterrupts ();
// output pin high - like sending 0XFF
spi_MOSI1;
data=0;
for (idx=0; idx<8; idx++)
{
spi_SCK1;
data <<= 1;
data |= (spi_MISO)? 1: 0;
spi_SCK0;
}
// enable interrupts
interrupts ();
return (data);
}
void spiSend (
uint8_t data)
{
int idx;
// no interrupts during byte send - about 1 us
noInterrupts ();
for (idx=0; idx<8; idx++)
{
spi_SCK0;
if (data&0x80)
spi_MOSI1;
else
spi_MOSI0;
data <<= 1;
spi_SCK1;
}
spi_SCK0;
// enable interrupts
interrupts ();
}
本来は、クロック(SCK)とデータ(MISO,MOSI)のセットアップやホールド規定を満たす為、間にnop命令を挟み込んでタイミング調整すべきです。
コンパイル後のアセンブルリストを眺めてみたところ、間に色々な命令が挟まっていて、SCKを10MHz程度でトグルさせることを考えた時には、特にnop命令を挿入する必要なしと判断しました。もし、信号のセットアップやホールドなどのタイミング違反に起因するエラーが発生する場合には、各自、調整してください。
※コンパイラ(GCC)のバージョンが変わって新しい最適化が適用されたりすると、タイミング違反を起こすコードが生成されないとも限りません。本来であれば、インラインアセンブラなどを使って、コードを固定すべきでしょう。その辺りも含めて、以下(↓↓)となります!
※本パッチ(Sd2Card_due.patch.txt)を含めて、本HPで公開するプログラム/回路等は全て無保証です。お使いになる皆様の責任でご利用ください。
DUEにTFTシールドを装着(下図)して、TFTシールド上のSDカードスロットに差したmicroSDカードへアクセスできるかどうか?でテストします。
従って、使用するのは「#12-6 DUEにSDカードI/Fを接続する」で作成した以下のプログラムで良いでしょう。
テストプログラム:sd_filer.ino
上のプログラムで、SOFTWARE_SPIの実力(SDカードのアクセス速度)を評価できるように、【copy】コマンドの実行時間を計測してシリアルモニタに表示する評価用コードを追加しておきました。プログラム先頭付近の「#define SHOW_COPY_TIME」を有効化してから、DUEにコンパイル&書き込んでください。
実験サンプル:【 】内はシリアルモニタからの送信データ
【dir】
OK+ dir /
SYSTEM~1/
INDEXE~1 76
MEMO.TXT 951
QVGA.BMP 153654
S8_11K.WAV 2607502
OK: dir
↑microSDカードの中身が表示されましたか?
ファイルをコピー(【copy】コマンド)すると、コピーに要した時間を表示するようになっています(↓)。
実験サンプル:【 】内はシリアルモニタからの送信データ
【copy,MEMO.TXT,TEMP.TMP】
INFO: copy 0% done.
INFO: copy 10% done.
INFO: copy 20% done.
INFO: copy 30% done.
INFO: copy 40% done.
INFO: copy 50% done.
INFO: copy 60% done.
INFO: copy 70% done.
INFO: copy 80% done.
INFO: copy 90% done.
OK: copy (7632[ms]/951[byte]) MEMO.TXT TEMP.TMP
えっ?ビックリするほど遅いです。【copy】コマンド、用意しては見たものの、Arduinoでファイルコピーする機会なんてほぼ無いので、今まで全く気が付きませんでした。たった951バイトのテキストファイルコピーに7.6sもかかってしまいました。
BMPファイルやWAVファイルなど、サイズの大きなバイナリファイルをコピーしようとすると、ハングしたのかと見まがうほどの時間がかかります。サイズに寄りますが、優に数分間戻ってきません。これは何だ〜?。
これまで何度もSDカードI/Fは使っています。BMPファイルの読み出しやWAVファイルの再生など、読み出しに数分かかる様では、全く使い物にならないはずです。つまり、今までの使い方では気にならないアクセス速度を持っていたはず。どうやら、単純にファイルコピー(【copy】コマンド)が遅いだけ、みたいです。
ファイルコピーは以下の様に実行していました。
while (fin.available ())
fout.write (fin.read ());
入力ファイルfinから1バイト読み出して(fin.read()メソッド)、そのまま出力ファイルfoutへ書き込むだけ(fout.write()メソッド)です。
SDライブラリの内部処理を解析していないので詳しいことは不明ですが、1バイト毎に2つのファイルを切り替えながらリード/ライトを繰り返す、上のコードが速度低下の原因だろうと推測しました。
そこで、コピーバッファを確保して、バッファサイズ毎にリード/ライトを分離して実行したところ、ファイルコピー速度が向上しました(↓)。
const int copy_buf_siz=512; // copy buffer size
int chr;
int idx;
int dsiz;
uint8 dbuf[copy_buf_siz];
// read sd => buffer
for (dsiz=0; dsiz<copy_buf_siz; dsiz++)
{
chr=fin.read ();
if (chr<0)
break;
dbuf[dsiz]=chr;
}
// write buffer => sd
for (idx=0; idx<dsiz; idx++)
fout.write (dbuf[idx]);
コピーバッファサイズcopy_buf_sizに関しては、実験を続けて最適値を探します。
ここで、一旦SOFTWARE_SPIを無効にして、従来通りのSPIユニットを使ってアクセスする実験を行いました。SPIユニットを使ったDUEとSDカードI/Fの接続方法についてはこちらをご確認ください。
まずは、バイト毎コピー版で確認します(↓)。
実験サンプル:【 】内はシリアルモニタからの送信データ
【del,TEMP.TMP】
OK: del TEMP.TMP
【copy,MEMO.TXT,TEMP.TMP】
OK: copy (8351[ms]/951[byte]) MEMO.TXT TEMP.TMP
先ほどのSOFTWARE_SPIの場合と比較してどうでしょうか?コピーに要した時間はほとんど変わりませんでした(約8.4秒、SOFTWARE_SPIより遅いくらい)。やはり、バイト毎のコピーにとてつもない時間がかかってしまう状況は、SPIユニットでも同じだった様です。
SPIユニットでも、前節のコピーバッファを使用することで、コピー速度が向上しました。
次は、SOFTWARE_SPIとSPIユニットのアクセス速度の差を知りたいですね。各条件でコピー速度を計測しましたので、以下表に示します。
【copy】コマンド実行時間[ms] (copy_buf_sizを変化しながら計測) | ||||||||
ファイル名 |
ファイル サイズ [バイト] |
SOFTWARE_SPI | SPIユニット | |||||
バイト毎 | 256 | 512 | 1,024 | 2,048 | バイト毎 | 512 | ||
MEMO.TXT | 951 | 7,632 | 240 | 249 | 199 | − | 8,351 | 244 |
QVGA.BMP | 153,654 | − | 7,136 | 3,316 | 3,286 | 3,313 | − | 3,564 |
S8_11K.WAV | 2,607,502 | − | 110,411 | 56,196 | 53,994 | 53,751 | − | 57,419 |
以下雑感です。
「#13-1 TFTシールドにBMPを表示する」にて作成した、BMPファイルをSDカードから読み出してTFTシールドに表示するプログラムを拡張しました。以下にリンクしておきます。
BMPロード/セーブプログラム:load_bmp.ino
シリアルモニタから実行可能なコマンドについては、こちらをご参照ください。従来のコマンドに加えて以下コマンドを拡張してあります。
このプログラム(load_bmp.ino)を、SOFTWARE_SPIを有効にした状態でコンパイル&書き込むと、DUE+TFTシールドにおいて、TFTシールド上のSDカードI/Fに挿入されたSDカードへのアクセスが可能となります。
実験サンプル:【 】内はシリアルモニタからの送信データ
【load,QVGA.BMP】
DBG: 320x240(16b) rot90=1
OK: load_bmp(0): 2194[ms]
↑SDカード上のBMPファイルを読み出して、TFTシールドへ表示しました。
SDカードへのSOFTWARE_SPIアクセスがTFTシールド表示を通して目視で確認できるので、期待通りの画像が表示されているか?よく確かめてください。もし、SOFTWARE_SPIの挙動が誤っていると、一部間違った画像が表示されたり、あるいはまったく画像が表示されなかったりするはずです。
【save,TEMP.BMP】
OK+ start save_bmp, wait a moment...
OK: save_bmp(0): 3288[ms]
↑新たに追加した【save】コマンドで、現在TFTシールドに表示されている画像をBMPファイルとして保存しました。
【clear】
OK: clear tft
【load,TEMP.BMP】
DBG: 320x240(16b) rot90=1
OK: load_bmp(0): 2193[ms]
↑一旦画面をクリア(【clear】コマンド)して、上でセーブしたBMPファイルを読み出してみました。セーブした画像が正しく表示されたかどうか、よく確認してください。
以上、BMPファイルのロード/セーブを通して、SDカードへのSOFTWARE_SPIによる正常アクセスを確認しました。テストを完了します。
SOFTWARE_SPIのパッチによって、UNOと同じピンソケットからSPI端子を引き出してSDカードへアクセスできるようになりました。アクセス速度もそん色ないですし、使い難いICSPピンヘッダから解放され、何より、TFTシールド上のSDカードI/Fを使えるようになったのは嬉しい。