SDカード               TOPページへもどる

 最近の記事 

 2007.04.04 最近SDカード、MMCカードをワンチップコンピュータに接続しているのを多く見るようになりました。このカードを使うと、膨大なデータを 保存することができます。音楽を扱おうとは思いませんが、eepromに入りきれないデータでも簡単に扱うことができます。また、カードの価格もずいぶん低くなりました。
と、思っていたところ3月のある日、日本橋を散歩していたら、64MBのminiSDカードに出会いました。@150で2枚購入しました。(その後は出ていません。もっと高く なっています。)
将来はmega64に繋ごうと思いますが、tiny26で読み書きしている報告もあるようですから、使い慣れたmega8でそろそろ楽しもうという考えです。多くのひとがWebに報告 されていますが、その中でGO2さんが精力的に報告されています。克明な実験報告に感動して、これなら何とかなるのではないかと実験をはじめることにしました。



回路の構想
基本的な回路はChaNさんの「サンプルプロジェクト avr-mmc」を参考にさせていただき、mega8とは次のように接続します。
 mega8MOSI−−DI SDC
 MISO−−DO 
 SS−−CS 
 SCK−−CLK 
 SDCの各端子は、CLKは47kでプルダウン、その他は47kでプルアップします。
 電源電圧は、SDCが2.7V〜3.6Vですから、無理をして3.3Vとします。このため、事前に3.3V動作のテストを予定します。
 PCとの通信はCP2102を用いた、USB-シリアル変換回路を使い、txdとrxdを使ってtera termで行います。
 クロックは手持ちの8MHz水晶を使います。
SDC周りの回路を簡単にするために、カードは挿したままの状態で実験しますので、カードの電源をon,offするFETスイッチは付けません。また、挿入検出の回路も 設けません。

3.3Vでの動作実験
いつもの通り、mega8(TQFP)の電源回路Vcc・GNDを繋ぎ0.1μFのパスコンを半田付けします。次に、プログラマ端子を配線し、この時点でavrspで読み取り、fuse 設定をします。スペック読み取りも、fuseの変更も正常に作動しました。3.3Vでもいけそうです。
SS端子を適当にon,offして矩形波を発生する簡単なプログラムを書いてみました。計算通り発生しています。次はPCとの通信実験です。

PCとの通信用の端子、mega8のRXD,TXD,GNDの3つを取り出し、USB変換器を接続します。クロック8MHzで、9600bps、8ビット、1ストップビット、ノンパリティ に設定しました。試しに、PCから'r'を送れば、'read'文字を返し、その他は送った文字を返すプログラムを作って通信実験をしました。
プログラマのVccはターゲットから取る設計になっていますから3.3Vは問題がありませんが、CP2102の変換器はバスパワーで動いていますから5V入力の危険があります。 強引に繋いで、PCから信号を送りましたが、RXD端子は3.3Vより高くなっていないのでそのまま使うことにしました。(これは間違いです。CP2102は内部レギュレータによって 3.3Vで動いています。当然I/Oも3.3Vです。むしろ、5V回路にCP2102を直結する方が問題かもしれません。ただしCP2102のI/O端子は絶対最大定格は5.8Vですから 使えるのかもしれません。07.05.23訂正)
 PCからの受信は割り込みを使ってフラグを立て、メインループで読み取ります。実は、はじめは正常に読み取りができていたのですが、実験が進むにつれて、正常に 作動しなくなりました。さんざん苦労して、一時は割り込みをやめてポーリングにしたのですが、グローバル変数にしていた文字変数とフラグにvolatileを付けると 正常に動きました。コンパイラが勝手に判断して、メインに渡してくれなくなっていたようです。

PCとの通信まで3.3Vで確認できたので、SDCとの通信実験に進みます。


 2007.04.12 どうにもならない
mega8にはSPIの機能がありますので、まずそれを使うことを考えました。SPI通信では互いのシフトレジスタを繋ぎ、CS信号で受け渡しを有効にして、8つの専用クロック でレジスタの中身を交換します。以前にAVR間を繋いだことがあるので、それを参考に進めました。
SPIモードには入ったらしく、0x01のレスッポンスが返り、続けてCMD0も0x00で正常に進みました。が、次のCMD17で悪戦苦闘の泥沼が始まり、どうしても抜け出すことが できません。詳しいGO2さんにお聞きしても、AVRのSPIは使っておられないとのこと。Web上を探しましたが関係するものを見つけ得ませんでした。
ChaNさんのページによると、CS端子の信号が直接働かず、クロックに同期しているとありましたのでそれが関係しているのかもしてません。AVRのSPIはデータを準備すると クロックが生成され、データに関係のないクロックは別に作る必要があるのではないかと思います。


 2007.04.14  ソフトウエアですべての処理をする
クロックを作るところからWinAVRで書いてみることに変更しました。
そうなるとまず、ポートの変更です。MISO,MOSIにこだわることがないので PORTCを使います。PC0:CS、PC1:MOSI(data-out)、PC2:MISO(data-in)、PC3:CLKとしました。
 mega8PC1DI SDC
 PC2DO 
 PC0CS 
 PC3CLK 
これでプログラマとの干渉がなくなりますので、SDCに関係なくプログラムを書くことができます。

クロック:SDCはSPIモードに入るまでは400kHz以下のクロックでないといけないようで、また、高速の読み書きを今は考えていないので、約100kHzで実験することに します。PORTC3=H → wait5μs → PORTC3=L → wait5μs とすると100kHzのクロックができます。連続クロックをループで作って、オシロと周波数カウンタで確認します。
シングルクロックやダブルクロックあるいは1バイト送受信用の8クロックなども確認します。
void clk(void){                 //クロックを1つ作る
  PORTC|=0b00001000; wait5(1); PORTC&=0b11110111; wait5(1);
}
1バイト送信(SDCへ): PC1(MOSI)をHまたはLにしてクロックを立ち上げます。クロックを立ち下げてH,Lを変更します。クロックの立ち上がりでSDCはデータを 取り込みます。この操作で同時にSDCの出すH,LがPORT2(MISO)に伝わる事になりますが、送信と受信を同時に行う必要がありませんのでPORT2の入力は無視します。
1バイトデータを1ビットのシリアルに変更するにはビットシフトするのが通常ですが、シフトを使いこなせませんので、とりあえずもたもたしました。
void sendbyte(uint8_t c){
  if(0b10000000&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  if(0b01000000&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  if(0b00100000&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  if(0b00010000&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  if(0b00001000&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  if(0b00000100&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  if(0b00000010&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  if(0b00000001&c){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  PORTC|=0b00000010;
}

1バイト受信(SDCから): クロックを送るとSDCは出力します。同時に、SDCは入力を受け付けますので、mega8がデータを受け取るときは、Hを送り続ける 事になります。HはSDCに対しての命令にはなりません。ここもまたシフトを使えていません。
uint8_t recbyte(void){
  uint8_t c=0,d;
  d=PINC; if(d&0b00000100){c+=128;} clk();
  d=PINC; if(d&0b00000100){c+=64;} clk();
  d=PINC; if(d&0b00000100){c+=32;} clk();
  d=PINC; if(d&0b00000100){c+=16;} clk();
  d=PINC; if(d&0b00000100){c+=8;} clk();
  d=PINC; if(d&0b00000100){c+=4;} clk();
  d=PINC; if(d&0b00000100){c+=2;} clk();
  d=PINC; if(d&0b00000100){c+=1;} clk();
  return(c);
}

 CMD0でSPIモードに入ること、CMD0で初期化して、CMD17で512バイトを読み出すこと
SDカードを扱うために次の手順で進めます。
@CS(Chip Select)端子をHにして、74個以上のクロックを送るとコマンド受付モードになります。(clk<400kHz)
ACSをアサート(L)にして、CMD0を送ります。
 コマンドは、6バイトの信号で、
   1バイト目がコマンド名を表し、0x40+コマンド番号(10進値)
   2、3、4、5バイト目が引数、
   6バイト目が5ビットのCRC値+1で、
 CMD0は 0x40,0x00,0x00,0x00,0x00,0x95 となります。最後のCRC+1は決め打ちです。
 mega8のDOをHにして(Hのみの連続信号はコマンドとして解釈されない)クロックを送り続け、CDカードからのレスポンスを待ちます。
 0x01のレスポンスが返りましたらSPIモードに入っていますので、CMD0は成功と考えられます。
 CSをネゲート(H)にしてCMD0を終わります。
BSPIモードの初期化を行います。CSをアサートにして、CMD1を送ります。
 コマンドは 0x41,0x00,0x00,0x00,0x00,0x95 となります。(最後のCRCは無視されますが、送出しなければな
 りません。最後の+1のため奇数です。)
 正常終了のレスポンス0x00が返らず苦しみました。0x00が返るまでCMD0を送り続けます。7,8回でやっと終了しまし
 た。
 CSをHにしてコマンドを終わります。
C512バイトデータを読みとるために、CMD17を実行します。0x40+十進17で0x51です。
 0x51,0x00,0x00,0x00,0x00,0x95 を送ります。(0x95は奇数なら可)
 レスポンス0x00が返るのを待ち、続いて読み続けて 0xfe が返れば、その次のバイトからデータです。
 引数の 00 00 00 00 は物理セクタ0を指定しています。

ハードウエアの改造:resetスタートで実験を繰り返していると、mega8はresetされますが、SDカードはresetされないので動作がおかしくなります。 これを避けるため、reset後に少しの時間だけSDCのVccをOFFにするとSDCもresetされてうまく動作します。そのために、SDCの電源回路にP-MOSFETを挿入し、ゲートを mega8のPORTD5でコントロールします。ゲートをLにすればVccがON、HでOFFになります。

この、物理セクタ0を読みとるまでのプログラムです。

tera-termで読みとったデータです。


 2007.04.15  512バイト読み取りルーチンの強化
番地を変更して、読み取りを試すわけですが変更の度にコンパイルするには時間がかかります。そこで、tera-taemからmega8への番地入力を待って、4バイトの 読み取り開始番地を受け取るとその番地から512バイトのデータを読み、UARTからPCにhex表示するように変更しました。

tera-termからCMD17の引数(アドレス)である16進4桁を連続して入力するとそのアドレスから512バイトを読み出して、tera-termに表示します。16進のa〜fは 小文字を使用します。変更したプログラムです。一部書き方が変わっていますが前述のものと同じです。また、このプログ ラムは前述のものをすべて含んでいます。

先に読みとった物理セクタ0(MBR)から、論理セクタ0を追加したプログラムで読みました。
MBRの1c6番地からリトルエンディアンで書かれている 27 00 00 00 から論理セクタ0の開始番地は、
27H*200H=4e00H となりますから、CMD17の引数に 00004e00 を入れて実行すると論理セクタ0(BPB=BIOS パラメータ ブロック)が読み出せます。
この方法で読み出したものが次の表です。

解析については、富士通デバイス(株)の資料を参考にしました。

  FATの解析
以上の読み取り結果から、このSDカードのファイル構造を次のように考えました。
1.1セクタは512Dバイト=200Hバイトである。
2.1クラスタは32Dセクタ=4000Hバイトである。
3.論理セクタ0(ここにBPBがある)は4e00H番地から始まっている。
4.論理セクタ1(5000H番地から始まる)からFAT1の領域で、FAT1は12Dセクタあるから
 5000H+(0c-1)H*200H=6600H で終わっている。
5.FAT2は次のセクタ 6800Hから始まり 6800H+(0c-1)*200H=7e00Hで終わっている。
6.したがって、ルートディレクトリは次のセクタ、すなわち、8000H番地からから始まる。
7.ルートディレクトリは1ディレクトリあたり32Dバイトで、最大は512Dであるから1クラスタに納まる。
  32バイトの中は次のようである。この項については「FAT FS フォーマットの実装についての覚え書き」 を参考にさせていただきました。
  00〜07 8バイト ファイル名
  08〜0a 3バイト ファイル拡張子
  0b   1バイト アトリビュート
  0c   1バイト 予約
  0d   1バイト 作成時刻10ms
  0e〜0f 2バイト 作成時間
  10〜11 2バイト 作成日付
  12〜13 2バイト アクセス日付
  14〜15 2バイト FAT32で使用
  16〜17 2バイト 変更時間
  18〜19 2バイト 変更日
  1a〜1b 2バイト スタートクラスタ番号
  1c〜1f 4バイト ファイルサイズ(バイト)、ディレクトリの時はゼロ

ルートディレクトリの読み取り:上に書きましたようにルートディレクトリは8000H番地から始まるセクタにありますから、読んでみました。

3つのファイルが書かれていることがわかります。これは、USB接続のリーダー・ライタを使ってあらかじめ書き込んで置いたものです。
ルートディレクトリのあるクラスタが、クラスタ番号1になるようですから、そして、8000Hから始まっていますから、クラスタ番号0は4000Hにある勘定になり ます(本当はありません)。 したがってクラスタ番号n(16進)から始まるファイルのCMD17で指定するアドレスは、4000H+n*4000H=(n+1)H*4000H となります。(16進数計算であることに注意してください。)
もし、0000.BIN をアクセスするなら、(4+1)*4000H=14000H を指定して、51 00 01 40 00 95 となります。

これで、一応ファイルを読むことができるようになりました。
ファイルシステムの構築はできませんが、このあと書き込みを調べれば、ファイルシステムを無視したデータ記憶装置として使えるのではないかと思います。




2007.04.17  512バイト書き込みルーチン
任意のセクタ512バイトの読み取りができましたので、次は書き込みルーチンです。読み取りは、Winのファイルシステムで書き込んだものをサンプルとしましたが、 書き込みはディレクトリ管理までは難しいので、ファイルシステムを無視した書き込みになります。ただ、予想されるデータファイルのダミーをWinで書き込んだものを 用意しておいて、ダミーファイルの大きさ以内でダミーを実データに書き換えれば、Winで読めないことはないかと思います。

512バイトの書き込みを次のように考えました。
.準備段階で、CS=Lで十分なクロックを送り、さらに0xffを送っておきます。
.CMD24を送ります。 58,00,00,00,00,95 を送ります。中の4バイトは書き込みアドレスです。95はダミーCRC+1ですから奇数ならokです。
.0x00 が返るまで0xffを送ります。0x00は正常終了のレスポンスです。
.1バイト以上の0xffを送り、続いて 0xfeを送ります。このfeのLSBのが、次のビットからデータであることを示します(そのように思いました)。
.512バイトのデータを送ります。
.ダミーCRCの0xffを送ります。このとき、0xffが返りますので空読みします。
.書き込み動作中(SDC内部で)はbusy信号が出るので、返り値はL(0xff以外)になります。busyが終わるまで読み続けます。
.CS=Hにして書き込みを終了します。

ここまでを実現したのがこのプログラムです。(これまでのすべてを含みます。)

このプログラムでは、
起動(リセット)すると、SPIモードに入り、初期化します。
物理セクタ0を読みとります。
続けて、読み取りループに入りますから、読み取りアドレスを4バイトで指定します。'q'でループを抜けます。
書き込みルーチンに入ります。書き込みダミーデータ(512バイト同じ文字)の文字を入力します。
書き込みアドレスを4バイトで指定します。'q'で書き込まずにプログラムを終了します。

1セクタが512バイトで、1クラスタが32セクタです。このことから、15kB程度の測定データの保存なら、セクタを管理しながらできるのではないかと 思います。アナログデータでなければ15kBはかなり大きなデータではないかと思います。プログラムを工夫すれば実用になりそうな気がします。


  SDカード取り扱いについて思うこと
コマンドを送る前に、データをHにして十分なクロックが必要なようです。このクロックでコマンドの受付準備ができるのでしょうか。
コマンドに続いて送るデータは、Lがスタートビットになるようです。読み取り時はCMD17を送ったあと0xfeが返ればその次からデータです。また、 CMD24で書き込みデータを送るときも0xfeに続けてデータを送出します。そのように考えれば、コマンドも最初に送るビットはLですから同じことだと 思います。
「十分なクロックを送って、内部状態を受け入れ準備okにすること、データの前は0xfeであること、事後にもクロックを送ること」が大事かな、と感じ ました。

あとはプログラムを整理すること、何か応用を考えること、が課題です。

2007.04.18  テストボードの写真と参考サイト



参考にさせていただいたサイト:
ChaNさんの「MMCの使いかた
GO2さんの「特別記事【SDCARDの読み書きについて】」、 「【FILESYSTEMの自作】
KabaVM Systemさんの「FAT FS フォーマットの実装についての覚え書き

今回は、私の理解できるプログラム(言語)がなかったので、記述されていることを丹念に考え、すべてを自分の言葉で書いてみました。C言語を勉強された方には 奇妙な表現も多いことと思いますが、独学の悲しさでうまい表現ができません。 ここに辿りつくまではGO2さんのような長い苦労はしていませんが、それはひとえに 先輩諸氏のご報告のお陰だと思っています。 でも、このボードの書き込み回数は300回を超えているでしょう。一時はギブアップしそうでした。

2007.04.20  プログラムの改良とSDC扱いメモ
プログラムの改良:
1.クロックとUARTを速くしました。SPI通信クロックを400kHzとしました。ChaNさんの記述によるとSPIモードに入る前の最高速度ですが実験的に成功しましたので 採用しました。また、それに伴ってUARTを32400bpsに上げました。SPIが遅ければUARTを速くしても意味がないようです。むしろデータが間に合わなくて、通信できなく なるようです。
これらを速くしたことでtera-termの表示が極めて速くなりました。
SPIクロックを速くした関係で、初期化のCMD1が返るのに(クロックに比べて)時間がかかります。素直にループを回すと20〜30回CMD1を送ることになりますから、 150msのwaitを入れて調整しています。

2.書き込みと読み取りルーチンをレジスタシフト動作に変えました。少しスマートになりました。
// SDCにSPIで1バイト送る
void writebyte(uint8_t c){
  uint8_t i;
  for(i=0;i<8;i++){
    if(0b10000000&(c<<i)){PORTC|=0b00000010;}else{PORTC&=0b11111101;} clk();
  }
  PORTC|=0b00000010;
}

// SDCからSPIで1バイト受け取る
uint8_t readbyte(void){
  uint8_t c=0,d,i;
  PORTC|=0b00000010;                            //DI=PC1をHighにして
  for(i=0;i<8;i++){
    d=PINC; if(d&0b00000100){c|=(1<<(7-i));}
    clk();
  }
  return(c);
}
改良したプログラムです。

SDC扱いメモ:(手元の64MBのもの一種類だけに関するデータですが)
1.電源投入後のリセット状態では、SDC固有のモードで、SPIモードではありません。
2.この状態では、クロックは400kHz以下でないと受け付けられません。
3.電源投入後1ms以上待ちます。
4.mcu→SDCデータをH、CS(Chip Select)=H(ネゲート)で74個以上のクロックを送るとコマンド受付状態になります。
5.ソフトウエアリセットのためにCMD0(40 00 00 00 00 95)を送ります。DOをポーリングして 0x01 が返れば成功です。
6.初期化のためにCMD1(41 00 00 00 00 95)を送ります。CS=Lにしてから0xffのダミーを入れないといけないようです。
  CSがクロック同期のためでしょうか。CMD0以外はダミーCRCは奇数なら良いのですが、95としています。
  内部動作のためか時間がかかります。レスポンス 00 が返るまでCMD1を送り続けます。(場合によっては数十回)
7.512バイトの読み取りには CMD17(51 00 00 00 00 95)を送ります。これで物理セクタ0のデータが読みとれます。
  このとき、CS=Lにしてから10ms待って、かつダミーの0xffを前もって送るとうまくいきました。
  データは0xfeの次からきますので、0xfeをポーリングで待ちます。
8.CMD17の 00 00 00 00 をバイト単位のアドレスに置き換えると、任意のセクタを読むことができます。
  ただし、セクタの区切り目(200H単位)でないとうまくいきません。
9.512バイトの書き込みは CMD24(58 xx xx xx xx 95)を送ります。FATに関係するセクタに書かないように。
  コマンドの前にクロックを8個以上送り、かつffを送っておくとうまくいきました。
  このとき、レスポンス00が返ったあと、ff,feを送り、feのあと512バイトのデータを送ります。その後、
  ダミーのCRC+1として奇数データ(ff,95など)を送ります。
  返り値はffのあとbusyのLが返りますので,Hになるまで待ち、CS=Hにします。あと10msほど待ちます。
10.ダミークロックを送ること、ダミーのffを送ること、データの前にL信号(fe)があること、待ち時間を設定すること、
  がこつでしょうか。


2007.04.28  バイナリデータをPCへ
tera-termでの読み取りは、手早く行え、デバグも簡単なのですがバイナリデータが扱えません。SDCにmcuから書き込んだ(将来書き込む)データを、バイナリ データのファイルに取り出せるとあとの使い勝手が良いと思います。
乏しい技術の中で、またまたDelphiと戦いながら、どうにか読み出しに成功しました。初期テストがtera-termでできていますので、mcuからPCへはデータのみを 送ることにしました。また、プログラムで使いますので読みだしコマンドも簡単にしました。かなり多くのテストを繰り返して、指定したアドレス(16進)から 1セクタ=512バイトを読み出すルーチンと、1クラスタ=32セクタ=16kBを読み出すルーチンを作りました。
Delphiのプログラムもmega8のプログラムもただいまはゴミだらけで大変です。画面だけをとりあえず書いておきます。

通信は38400bpsで、16kBの読み出しには数秒かかります。(エラー処理ルーチンはまだできていません)


2007.04.29  バイナリデータをPCへ…その2
動くことがわかりましたので、プログラムを少し整理しました。増築・改築を繰り返したゴミの山から、少しはゴミを整理して見やすくしました。
Delphiは使うたびに新人で苦労の連続です。「温かスープ」も使えるようですが、以前にUSB-IOで一度出会いましたが、やはり昔なじみのDelphiを引っ張り出 して使うことになりました。

少しはましになったでしょうか。
参考にもならないかもしれませんが、プログラムリストを置いておきましょう。ファームウエアDelphiの読み出しプログラムです。



2007.07.16  バイナリデータをPCへ…その3
USB-UART変換アダプタをFT232RLで作ったところ、COM6で認識されました。これを機にCOMポートを選択できるように変更しました。

Delphiのプログラムは最新版に上書きしています。



  





TOPページへもどる


inserted by FC2 system