STM32F4シリーズを使ってみる14 - FatFsとSDカード再考その3(SDIOでDMAした時の不具合対策編) -

サブタイトルがどんどん長くなる・・・それはおいといて
前回の解説でまぐろ様と言う方より最初に4Byteの倍数以外の数のデータを書き込んだ際
に書いたデータがずれる
と言う不具合報告をいただきました。結局原因はDMAする
際にメモリアドレスがWORD(4バイト)のアライメントの境界にそろっておらずずれた状態で
DMAしていたことだったのですが、私自身も勘違いしたまま使っていたのでこの場で
情報を整理して根本的にどう対処すればよいのかを記しておきます。
その前にどうでもいいですが"Alignment"はアラインメントでもアライメントでも発音は
正しいそうですが以後はアライメントで統一します。

●ズレる
たとえばファイル"mankoi.txt"を書き込みモードで開き、ヘッダとデータの塊をf_sync()を
挟んで書き込むコードを実行するとします。ここで"sakisan","gff"は共に書き込む予定の
バイト数を満たす十分な大きさをもち4バイトの境界に揃ったconst charの配列のポインタ
とします。またf_系の返り値のチェックは下では解説のために省略していますが実コード
では付与しております。

f_open(&File[0], "mankoi.txt", FA_OPEN_ALWAYS | FA_WRITE);
f_write(&File[0], sakisan, 37, &s2); /* 4の倍数でないバイト数 */
f_sync(&File[0]);
f_write(&File[0], gff, 4096, &s2); /* マルチブロックで一気に書き込む予定 */
f_sync(&File[0]);
f_close(&File[0]);

期待される動作はsakisanで示された37バイトのデータを書き込んだ後gffで示された
4096バイトデータを書き込みファイルをクローズして無事終了…のはずです。

SDIOでDMAを使用しないFIFOポーリングによる書き込みではちゃんと期待される動作
となります。しかしDMAを使用した場合最初のブロック(=512バイト)を書き込んだ次の
ブロックの最初の書き込もうとするデータが1〜3バイトずれてしまいます。
このずれ方は転送予定だった最初のバイト数を4で割った余りと等しくなります。

STM32F4のマニュアルではDMAをする際のメモリアドレスの境界はFIFOバースト長さ/
INC値に合わせよと明記があります。STのサンプルでは送り元メモリ及び送り先ペリ
フェラルはそれぞれ4バイトとしていたのでそれに倣って4バイトの境界にメモリアドレスを
合わせる必要があります(私のサンプルのSPI版の場合はDMAの転送サイズはByte
のため今回の影響はありません)。
ねむいさんはてっきりFatFsのデータのやり取りに使用する為に静的に確保したバッファ
の配列を4バイトアライメントにしておけばそれで問題なかろう・・・という致命的な勘違いを
しておりました。

上記f_writeからはSDIOドライバと結合したdisk_writeが呼ばれますがこのとき渡される
内部バッファのポインタアドレスが4バイトの境界にそろっているとは限りません。
しかしながらdisk_write内のSD_WriteBlock及びSD_WriteMultiBlockはDMAで転送する
際は送付元メモリアドレスが4バイト境界(4で割り切れる数)になっている必要があります。
ズレる状況で実際にどういうことが起こっているかデバッガで追いかけてみましょう。


最初にシングルブロック転送で37バイト分書き込む時です。最初なので当然メモリの
アドレスも4バイトの境界にいます。


f_sync()の処理を終えてから次の4096バイト(実際は最初の512-37バイトを引かれた値)を
マルチブロック転送で書き込んだ時です。ご覧のように渡されたポインタbuffのアドレス
値が4で割り切れない数になってます。


当然のことながらmankoi.txtに書き込まれた文字列はズレます。

●対策
前回も述べましたがSTM32F2/F4は対策はとても容易でDMAの設定でメモリ側のデータサ
イズを1バイトの"Byte",FIFOバッファのメモリ側バースト長をSingleにすれば
1バイト
ごとの転送となり効率は落ちますがアライメントは関係なくなり問題は解決します。

しかしSTM32F1系はSDIOはF2/F4系と違いAHBバスにぶら下がっていてなおかつAHB
バスに直接ぶら下がったペリフェラルへのDMA転送は常にWORD(4Byte)単位でなければ
ならないという制約があり、F2/F4みたいな技が不可能です。したがって下記に示す根本的
な対策を行う必要性があります。
/* If unligned memory address situation,copy dmabuf to aligned by 4-Byte. */
/* SECTOR_SIZE = 512 (Byte) */
uint8_t dmabuf[SECTOR_SIZE] __attribute__ ((aligned (4)));

if((uint32_t)buff & 3)	/* Check 4-Byte Alignment */
{ /* Unaligned Buffer Address Case (Slower) */
for (unsigned int secNum = 0; secNum < count && Status == SD_OK; secNum++){
/* Use optimized memcpy for ARMv7-M, std memcpy was override by optimized one. */
memcpy(dmabuf, buff+SECTOR_SIZE*secNum, SECTOR_SIZE);
Status = SD_WriteBlock(dmabuf,
(uint64_t)(sector+secNum)*SECTOR_SIZE,
(uint8_t)SECTOR_SIZE);
}
} else {
/* Aligned Buffer Address Case (Faster) */
if(count==1){
Status = SD_WriteBlock((uint8_t*)(buff),
((uint64_t)(sector)*SECTOR_SIZE),
SECTOR_SIZE);
}
else{
Status = SD_WriteMultiBlocks((uint8_t*)(buff),
((uint64_t)(sector)*SECTOR_SIZE),
SECTOR_SIZE
,count);
}
}

f_writeから渡されるbuffのアドレスの下位2ビットを比較して4Byteの境界にない物は
整列された配列にコピーし直しシングルブロック転送を行うものです。
このアライメント補正したシングルブロック転送を行っていくとブロックサイズの境界
(512Byte=128*4Byte)に揃い改めて高速なマルチブロック転送が可能となるので効率を
なるべく落とさないような仕組みにしてあります。勿論Readの際もチマチマ読み込みの際は
同じような対策でズレを防止できます。

これの対策の元ネタはSTマイクロのフォーラムにあったやり取りです。かれこれ3年
以上経ってましたがねむいさんずっと勘違いしてたせいでこの対策の意味が今更分か
ったorzそれにしてもClive1...貴方は何者なんだ…!

そしてChaNさんのページでもズレるから各自対策してね★ってしっかりと
注意書きがしてありました
…orz見落としてただけジャン私orz

で、でも現行のSTM32F4Cubeとかのサンプルって1.4.0になってもアライメントの事ガン無視
ですし、ま、まぁこれに気づく人のほうがたぶんす、少ないですってハハ♥


・・・と言うわけでおきぱにあるSTM32F2/F4のサンプルは上記の根本対策を講じております。
好みに合わせてDMAの設定だけで逃げるお手軽対策もできるようにしてあります。

またF1系,LPC1788/LPC4088のFatFsでも根本対策を施していますのでご利用ください。
ちなみにLPC2388に関してはChaNさん謹製のMCIドライバを使用していますがちゃんと
アラインド/アンアラインド化をしているためもともと大丈夫です。

●そういえばFatFsの設定で・・・
FatFsの設定のためのffconf.hには_WORD_ACCESSなる定義があり、1にするとポインタの
参照が32bit単位になり高速化ができる・・・はずですが32bitマイコンの場合はCPUコアの
アライメントの制約で1にすると上で述べたDMAみたくCPU例外が起こってしまいます。
しかしながらCortex-M3/M4ではアンアラインドなアクセスが一部の命令で可能なため
1にする事ができます。"一部"なのでChaNさんは0を推奨しています。
20160620追:
FatFs0.12ではこのオプションは廃止されました。
コンパイル時にアラインド状態のアクセスになるようにコードが変わっています。
20160620追:

ねむいさんが試したところではff.cではまだDWORD(8Byte)やそれ以上のマルチバイトに
アクセスする状況が発生していないのでアンアラインド転送の制約に引っかかるSTRD,
STM,LDRD,LDMの命令は現状のコンパイラではff.c内では一切使用されず例外も発生
しないので1で問題はないと言い切れます!もちろんアンアラインドなアクセスではペナル
ティが発生してその時の速度は低下しますがそれでも全体的にはバイトアクセスの時より
速度もコードサイズでも優れているので積極的に1にしていきましょう!
さらにGCCのコンパイラ・オプションで”-munaligned-access"を有効にするとアンアラインド
アクセスを承知でコードの効率化
が図れます。

ちなみにアンアラインドなアクセスが起こった事を知るための機能もあります。
SCB->CCR |= SCB_CCR_UNALIGN_TRP_Msk;

SCBのCCRレジスタにはアンアラインドなアクセスの発生をトラップするビットがあります。
これを立てるとアンアラインド転送が起こった時にHardFaultにさせることができます。

一方Cortex-M0,M0+ではコアのアーキテクチャが違うのでアンアラインドなデータアクセス
は許されず、問答無用でHardFaultになりますので常にバイトアクセスかもしくはアライメント
がそろった転送をしましょう。

そういうわけで不具合もしっかりと修正されたので今度こそ実際にパフォーマンス比較
をやっていきたいと思います!

Comments

Post a Comment








Go to top of page