STM32F7を使ってみる11 -最新のeMMCを動かす-

前回までは古き世代の遺産であるマルチメディアカードをSTM32F7で再び動作せしめる
までになりましたが、今回は新しい世代へと進化したeMMCモジュールの動作に
ついに、ついに挑みます!


eMMCは最早カードの形状ではなく、ごく一般的なICの形となっていますがそれが
故に使い慣れたSDカードコネクタとかで使うには少々骨が折れます。
市販のギャングプログラマ用のSDカード変換付きソケットは100$以上するのであんまし
手を出したくはないです…
簡単かつ確実に使う良い方法ないかといろいろ探ってみるとODROIDと言うLinux系の
ARMボード向けのオプションパーツとしてeMMC実装済みのMicroSDの変換基板が販売
されているのを発見しました!

SouthKorea製なのがほんの少し心配でしたが値段も送料込みで38USD
(2016年3月下旬時点)だったので早速注文してみました!!


で、発注後4日くらいで親父の家に届きました。一週間以上かかるかと思いましたが
予想以上に早かったです。Androidを意識した質素の箱の作りで"Do it yourself"
なるコメントがあります。


一応ぷちぷちにくるまれていましたがこの色って静電対策してないやt


これって静電対策してないやt
まぁでもうごけばいいのですよぅ動けば!!



ボードとしてはコテコテの南朝鮮製ですが実装されているeMMCは東芝製で容量8GB,
そして規格はeMMCv5の最新版です!!!


一応コリアンボードのmicroSDへの変換パタンもやっけつではなく信号のIntegrity
を考慮した配線をしてくれているようです。


eMMCは古いタイプのカードリーダーでは認識すらしない場合があります。
ねむいさんが昨年購入したUSB3.0のTranscend製のカードリーダーでは無事に
認識できました♥
もともとこのeMMCはODROIDのオプションパーツとして販売されているので、
こんな風に最初から何らかのOSが仕込んであります。


ねむいさんはそんなもん関係なくeMMCを使用すること自体が目的なので速攻
FAT32でフォーマットして更地にてやりました!!!

前回の最後でちょろっと漏らしているように実は公開したF7のサンプルでは
既にeMMC(とMMCのブロックアドレッシング)に対応しております。


ビルド時間は結構長いので暇つぶしがてらF7からeMMCのルーチンを移植中の
STM32F4の板に差して反応見てみるかと思ったらなんと普通に認識してしまった…。
拍子抜けするくらいあっさり読めちゃって感動もへったくれもない…
ぇっ!?黒澤ダイヤちゃんのお口がイケナイことになってるって!?何のことやらハハ
虹裏メイドねむいさんは「ラブライブ!サンシャイン!!」を応援します。




さてビルドが終わってF7のプログラムの書き換えも終わったのでF4板から外すか
…と思ったら…


あ…あれ…???

・・・
案の定HELL朝鮮製ボードに罠がありました…微妙にサイズが大きいようで刺さる
には刺さるんですけど安易に抜けません(意味深)。
無理やり引き抜くと基板で擦れてMicroSDコネクタを壊してしまいます…


てわけで削ります。
いきなりF7-Discoveryで試さなくてよかった…


ゴリゴリ削ってこんな感じに…


うまいこと削れたのでF7-Discoveryのコネクタにスムーズに差さります。


F7でも無事に認識して画像や動画データも自由自在に読み出しできます♥


このeMMCは8GBの容量でSDHCと同じくブロックアドレッシングになります。
私の移植例ではMMCv4以降に新設されたブートパーティション等にアクセス
するための特別なコマンド群は使用しておらず、SDHC/SDXCと同じ感覚で読み
書きします。腕に自信がある人、私のコードを参考に拡張してください・・・(懇願)



そしていつも通りカード(?)の情報を読み出してみました。

>ds 0
rc=0
Drive size: 15269888 sectors
Erase block size: 1024 sectors
Default r/w block size: 512 bytes
Card type: MMC(Block)
CSD:
00000000 D0 5E 00 32 0F 59 03 FF FF FF FF EF 92 40 00 D3 .^.2.Y.......@..
CID:
00000000 11 01 00 30 30 38 47 45 30 00 5A DD 41 40 B2 0D ...008GE0.Z.A@..

Parsing MMC CID Register
Manufacturer ID :0x11
OEM/Application ID :<0>
Product Name :008GE0
Product Rev :0.0
Serial Number :0x5ADD4140
DateCode.Month :11
DateCode.Year :1999

Detected as MMCv5.0x Device!
OCR:
00000000 C0 FF 80 80 ....
前回述べたとおりMMCv4.xx以降はExtCSDレジスタを読み出すのは重要です。
MMCv4以降のバージョン判定はExtCSDREVフィールドを参照し解釈する必要
性があります。ちなみにこの東芝のeMMCはv5とのことですがですが読み出すと
ExtCSDREVは”7”となりちゃんとv5のデバイスであることが分かります。
ちなみにdatecodeが1999とあるのはCIDレジスタの年数を示すフィールドが4bit分
しかないので2012年を境に年数が一巡してしまっているからです。最新のMMCAの
データシートではExtCSDREVから2012年以降を判別せよというようになっていますので
次回リリースでこちらも修正します。差し替えました。
一方、SDカードでは年数を示すフィールドは6bit分あるのであと50年くらい安泰です。


FatFs module test terminal for STM32F746NGH6
LFN Enabled, Code page: 932
AppVersion : W.I.P
Build Date : Mar 31 2016
>fg piano
rc=0 FR_OK
>fo 1 ftbt.mp3
rc=0 FR_OK
>fr 132949600
132949600 bytes read with 22000 kB/sec.
>
シーケンシャルライトも見てみました。F7のサンプルではSDIO_CLKはMAX48MHz
かつ4bitモードの設定がeMMCで可能ですがご覧のとおり理論値に近い読み出し
速度が出ました。やるじゃない!
FatFs module test terminal for STM32F746NGH6
LFN Enabled, Code page: 932
AppVersion : W.I.P
Build Date : Mar 31 2016
>fg piano
rc=0 FR_OK
>fo 1 ftbt.mp3
rc=0 FR_OK
>fr 132949600
132949600 bytes read with 20753 kB/sec.
>
因みに前回紹介したMMCPLusも結構速いです。

と言うわけでユーザー領域のみですがeMMCでもSDHC/SDXCと同じく読み書きする
ことが出来るようになりました!今秋くらいに市場に出回るであろうSTM32F777等の
最強に強まったF7シリーズではSDMMCが二つに増えるので基板付け置きのeMMC
(とかMCP)が今後は発言力を増してくると思います!!


今回の成果はSTM32F4系STM32F1系、そしてLPC4088のMCIにも移植しています
のでどんどん試して下さい!ついでにバグもこっそり教えてもらえると助かります☆

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

Go to top of page