STM32F7を使ってみる10 -いにしえのマルチメディアカードを動かす(うごかす編)-

ぇえええぇぇえええぇええ(どん引き)
↑イレギュラー化した部下に突如愛の告白されるシグマタイチョウ風に

いったいいつからIF誌はねむいさんのお株を奪うようなシモネタを炸裂させる
ようになったのでしょうか…???今回はちょっと笑えたので許しますが。
ああプロテインってそういう・・・




そんなどうでもいいことは置いといてさっさと本題に掛かります!!!!!前回は
microSD->SDの変換アダプタ作っただけで力尽きましたが
今回は実際に動かします。



先ずはebayで購入した2GBのMMCPlusカードです!

MMCPlusの規格なので8bitバスで転送もできます…がソケットの互換性を考えると、
4bitバスモードで動かした方が無難でしょう。
因みに妙に汚いように見えますがUSEDでした。MMC自体最早新規生産なんかしてない
ので致し方なしです。カードとして正しく動作すればOKです。


で、その前に問題のソフトウエアの方ですが…こちらでも紛糾してるように
STM32CubeHALライブラリにMMCのサポートは全く有りません。それどころかなんと
過去のSPLには対応していたSDv1.xx系カードのサポートすらもHALライブラリでは
おもいくそぶっちされておりました!!!F**K!!

とりあえずSDv1.xx系のサポートは復活させて動作確認したのですがMMCネイティブ
モードにおけるイニシャライズはどうしたらいいのか全く分からなかったので
暫くの間ネットの海にダイブして情報を沢山漁ってきました。

幸いにもJEDECのMMCの規格は2016年現在は一般人でも公式のJEDECのサイトから
容易に入手することが出来るようにになっていてさらに中華圏のサイトでは
動作するかどうかは不明ですがSTM32向けのeMMCドライバも入手出来たので
それを参考にMMC向けのドライバをF7のHALライブラリ向けに組み込みました。

当然最初は思った通りに動かなかったのですがSTM32のSDIO/SDMMCのハード
ウエア自身にかなりクセがあることが分かり、クセを理解して全種類のカードを
イニシャライズせしめるよう克服していきました。
以下にMMCPlus/MMCv3なカードを使い認識していく過程をデバッガで実動作を
追いながら解説していきます。

STM32のSDカードドライバは基本的にSDv2->SDv1->MMCの順に認識しようとして
いきます。これは現在のSDカードの規格においても推奨される初期化手順と
されています。カードをリセットするCMD0を除くと最初に放たれるのはCMD8です。


MMCとSDv1系ではSDHC用初期化CMD8:SEND_IF_CONDはタイムアウトエラーとなります。
過去のSTM32のSPLに収録されているsdカードのサンプルではその状態から
なぜかCMD55を単品で投げてから改めてCMD55+ACMD41を投げていました。

CMD8が撥ねられた後いきなりCMD55+ACMD41を撃つとSDIOモジュールがエラーで
コケる対策にCMD55を空撃ちしていた感じです(SPLではあったけどHALライブラリ
はそれすらない!)。これはCMD55を受け付けるSDカード系は問題ないのですが
MMCv3以前だとCMD8が撥ねられた後のCMD55の空撃ちがILLEGAL_COMMANDとなり
またまた撥ねられてしまい、先に進めません(※MMCv4以降はこのCMD55が通ります)。


と言うわけでCMD55の代わりにCMD0でカードをリセット状態にしてから改めて
CMD55+ACMD41を送るようにするとうまく先に進めました!勿論SDv1系でも
CMD55の空撃ちではなくCMD0で問題有りませんでした。


MMCではCMD55+ACMD41は当たり前ですがタイムアウトエラーとなります。


先ほどと同じくCMD0を送りMMCをリセットしますが、eMMC対応を考えて引数を
0xF0F0F0F0にしたCMD0の後0x0000000な引数のCMD0を送るようにしてみました。


ここまで来てようやく本来のMMCの初期化ができます。
CMD1を送ってSDカードの「ACMD41」と同じように初期化します。
が、ブロックアドレッシング必須のSDHCと同じくCCSビット(0x40000000)を
ORして引数に渡さないとブロックアドレッシングを必要とするMMCv4.x以降の
規格では初期化が完了できませんのでしっかりとおっ立てた状態で引数として
わたしましょう。ちなみにv3以前のカードにおいてはCCSビットが立っていても
影響は受けずスルーされて初期化がそのまま通るので問題ないです。


上手く初期化が出来たらOCRレジスタの0x80FF8080が読みだされます。
MMCPlusでDualVoltage対応なので上記の値となっています。
因みにv3以前のカードでは0x80FF8000,ブロックアドレッシングな
v4系のデバイスでは0xC0FF8080が帰ってきます。
ここまで認識が通ったらだいぶ気が楽になります。



所でMMCv4.x系のカードでは新たに追加されたExtCSDレジスタから512バイト分の
追加情報を取得可能です。実はこのExtCSDは4GB以上のeMMCの総容量計算では
必須のビットが存在しています。

212〜215バイト目がそのSEC_COUNTフィールドなのですがここに総セクタ数が
記録されていて512を掛けると総バイト数が分かる仕組みになっています。
この2GBのMMCplusではSEC_COUNTフィールドからの算出方法でもCSDレジスタ
から算出する従来の容量計算方法(計算式はSDv1系と全く同じ)でも両方とも
可能です。



初期化を越えたらクロックを転送用の高速クロックに上げていきますが…
MMCv4系では最大52MHz,8bitバスモードまで動かせられます。当ぶろぐでは
4bitバスモードまでとします。MMC専用の呪文を投げる以外はSDカードの
4bit化やHighSpeedMode化と流れは同じです。そして…


無事ファイラーからディレクトリが読みだされました♥


サイズのでかい動画でもカクカクせずスムーズに動きます☆
(後でかなり早いカードなことが分かった)



お次はさらに古いMMCv3世代のカードです。容量はたったの128MB!
そしてもちろん中古品。


初期化を越えたらクロックを転送用の高速クロックに上げていきますが…
10年以上前のものですがちゃんと現在のPCカードリーダーからも認識できます。


MMCv3系のカードなので数が7ピンしかありません。MMCネイティブモードでも
当然バス数が1bitです。早速こちらも動きを追ってみましょう!


CMD1の初期化を終えるとOCRレジスタの0x80FF8000が読みだされました。


初期化を越えたらクロックを転送用の高速クロックに上げていきますが…
MMCv3系はクロック上限値が20MHzできっちり線引きされていてそれ以上はまず
動作しないので20MHz以下にクロックを落とす工夫が必要です。もちろんデータ
バス幅は1ビットのままです。


さすがに遅いですけど無事認識です♥





上記2つのカードの各レジスタをSDカードと同じようにパースしてみました。
CIDレジスタの扱いがSDカードと微妙に違うので注意です。
そんでもってMMCv4以上の細かいバージョン識別はExtCSDの"ExtCSDVER"
フィールドを読みだして解釈する必要があります。
eMMCの使用をにらんでこちらも正しく解釈できるようにしておきました。
2GB MMCPlus

>ds 0
rc=0
Drive size: 3939328 sectors
Erase block size: 256 sectors
Default r/w block size: 1024 bytes
Card type: MMC(Byte)

CSD:
00000000 90 2F 00 2A 1F 5A 83 C1 B6 DB 9F FF 96 80 00 3F ./.*.Z.........?
CID:
00000000 37 FF FF 4D 4D 43 30 32 47 10 F3 02 67 50 2C F5 7..MMC02G...gP,.

Parsing MMC CID Register
Manufacturer ID :0x37
OEM/Application ID :
Product Name :MMC02G
Product Rev :1.0
Serial Number :0xF3026750
DateCode.Month :2
DateCode.Year :2009

Detected as MMCv4.0 Device!
OCR:
00000000 80 FF 80 80 ....

128MB MMCv3
>ds 0
rc=0
Drive size: 250880 sectors
Erase block size: 32 sectors
Default r/w block size: 512 bytes
Card type: MMC(Byte)
CSD:
00000000 8C 26 01 2A 0F 59 81 E9 F6 DA 81 E3 9E 40 00 7D .&.*.Y.......@.}
CID:
00000000 11 00 00 30 31 32 38 4D 32 0A 05 80 FE CB 28 7D ...0128M2.....(}

Parsing MMC CID Register
Manufacturer ID :0x11
OEM/Application ID :<0><0>
Product Name :0128M2
Product Rev :0.10
Serial Number :0x0580FECB
DateCode.Month :2
DateCode.Year :2005

Detected as MMCv3.xx Device!
OCR:
00000000 80 FF 80 00 ....
>


いろいろありましたがMMCの動かし方も上手くモノにすることができるように
なりました!次はいよいよMMCから進化したeMMCの動作に挑みます!!

今回の更新はすでにおきぱに上げてあります。eMMCのSD変換アダプタを持ってるひとは
いいことあるかも・・・!?



おまけ
前回言及したCubeF7のバグですが、「とりあえずuint64_tでキャストしときゃ
いいだろ」が祟って出てしまったカルマなのですが実際どうなるかをSTM32上で
確かめて見ました。


先ず使用する変数/定数は以下とします。
#define SECTOR_SIZE 512
uint64_t unk0;
uint32_t tnk = 8388612; /*512を掛けるとオーバーフロー*/

まずキャスト無しの場合…
unk0 = tnk * SECTOR_SIZE;
この場合は、普通にuint32_tの演算として扱われオーバーフローしたぶんは
そのまま頭が切られます。よって0x100000800ではなく0x800がunk0に代入
されました。


次に括弧でくくってuint64_tでキャストしてみます。今回フォーラムで指摘
されてたのがこのバグです。
unk0 = (uint64_t)(tnk * SECTOR_SIZE);
これもunk0に0x800が代入されてしまいました。*の演算を括弧でくくってしま
ったためにやっぱり0x800になっちゃいました。

正しいキャストの仕方がこちらです。
unk0 = (uint64_t)tnk * SECTOR_SIZE;
uint32_t変数のtnkをuint64_tにキャストしたうえでSECTOR_SIZEを掛けることで
0x100000800がunk0に正しく代入されます。

これ結構引っ掛かりやすいポイントなので私も気を付けるようにします。
フォーラムで指摘されるまで私も気づいて無くて同じミスしてましたがorz


因みにMMCv4.x系のSEC_COUNTの計算で
(代入先が64bit変数の場合)
(uint64_t)((uint64_t)(ext_csd.EXT_CSD.SEC_COUNT[3] << 24 | ¥
ext_csd.EXT_CSD.SEC_COUNT[2] << 16 | ¥
ext_csd.EXT_CSD.SEC_COUNT[1] << 8 | ¥
ext_csd.EXT_CSD.SEC_COUNT[0]));
もしくは
(uint64_t)((ext_csd.EXT_CSD.SEC_COUNT[3] << 24 | ¥
ext_csd.EXT_CSD.SEC_COUNT[2] << 16 | ¥
ext_csd.EXT_CSD.SEC_COUNT[1] << 8 | ¥
ext_csd.EXT_CSD.SEC_COUNT[0]));
もしくは
((ext_csd.EXT_CSD.SEC_COUNT[3] << 24 | ¥
ext_csd.EXT_CSD.SEC_COUNT[2] << 16 | ¥
ext_csd.EXT_CSD.SEC_COUNT[1] << 8 | ¥
ext_csd.EXT_CSD.SEC_COUNT[0]));

と誤った式にしてしまうとext_csd.EXT_CSD.SEC_COUNT[3]の最上位ビットに1が立つ
ような値がSDカード/eMMCから得られた場合、uint64だろうがint64だろうが負の値と
みなされた状態で整数拡張
されて上位32bitが0xFFFFFFFFでパディングされた状態で
64bit変数にキャストされてしまい、結果として意図しない値がストアされてしまいます。


ですので、
(uint64_t)((uint32_t)(ext_csd.EXT_CSD.SEC_COUNT[3] << 24 | ?
ext_csd.EXT_CSD.SEC_COUNT[2] << 16 | ¥
ext_csd.EXT_CSD.SEC_COUNT[1] << 8 | ¥
ext_csd.EXT_CSD.SEC_COUNT[0]));
もしくは
(uint32_t)((ext_csd.EXT_CSD.SEC_COUNT[3] << 24 | ?
ext_csd.EXT_CSD.SEC_COUNT[2] << 16 | ¥
ext_csd.EXT_CSD.SEC_COUNT[1] << 8 | ¥
ext_csd.EXT_CSD.SEC_COUNT[0]));

として頭をきっちり削っておけば問題ありません。


ってわけで型の合わない変数に何も考えずキャストとか代入したらこうなります
なったorz

Comments

Post a Comment








Go to top of page