ホームに戻る
 PIC16F88 でSDカードにFAT12で書き込み

0、はじめに

当初はUSBメモリにPICから記録をしたかったのだが適当な資料が無かった。
探すとSDカードについての資料はいくつかあったのでSDカードでやってみる。
SDカードはSPI通信に対応しておりPICから操作しやすい。
ただし、時とともにSDカードは性能が良くなっているので、
どの世代のSDカードなのかを最初に識別する必要がある。

目標はとりあえずSDカードで読み書きをすることである。
最新のSDカードを使うとすると識別に手間がかかるだろう。
よって、もっとも単純な操作で使える古いSDカードを今回は使用する。
メーカーは「HAGIWARA SYS-COM」と書いてある512MBのものを使用する。
例えば、SDカードバージョン2や大容量SDカードの場合は、
CMD1を使わずCMD8を使うとかあるがそのあたりには対応しない。

以下の記述はあの手この手でSDカードへの書き込みを試み、
FAT12のフォーマットでデータをSDカードに書き込み、
PCでそのデータを読み込むところまで成功させた記録である。
きちんとやればこんな稚拙なコードにはならないだろうが、
趣味の範囲であるし単調なので逆にわかりやすいかもしれない。
きちんとやりたい人はSDカードの仕様をきっちり確認して欲しい。

また「フラッシュ・メモリ・カードの徹底研究」という書籍を参考にした。
情報が低レベル(「簡単」という意味では無い)なので便利です。

1、SDカードについて

SDカードを裏返して金属部分を向こうにして置く。
すると、いちばん左の金属部分が一段下がっているいる。
その一段下がったピンが9番である。
そこから右方向に1番、2番・・・8番ピンまである。
いちばん右のピンは真ん中に線が入っていると思うがそれで7番、8番である。
SPI通信で使用するのは1番から7番まである。
8番と9番ピンは使用しない。
1番から7番ピンはでの用途については以下のようになる。

1番:チップセレクト
2番:入力
3番:GND
4番:+3.3V
5番:クロック
6番:GND
7番:出力

SDカードを使う場合には電源、信号ともに+3.3Vである必要がある。
また、SDカードを扱う場合は専用のソケットが売っているので利用したほうが良い。
専用のソケットを使えばまず接触不良などのトラブルは起こらないだろう。

2、PICの選択

SPI通信が行えるものが必要なので今回は PIC16F88 を使用する。
PIC16F88 は3.3Vでも動作する。
よって、PICとSDカードの電源を1つの3.3V三端子レギュレーターで確保することにする。
振動子についてはあまり早い振動子だと通信に失敗するらしい。
今回は10MHzのセラミック振動子を使用した。
SPI通信のクロックについては10MHz/64で使用することにする。
100kHzから400kHzが適当らしいが詳細は不明。

3、回路設計

PIC16F88 とSDカードを繋ぐ場合。
送信ピン(PIC:RB2)から受信ピン(SDカード:2番)に繋ぐ。
受信ピン(PIC:RB1)から送信ピン(SDカード:7番)に繋ぐ。
クロック(PIC:RB4)はクロック(SDカード:5番)に繋ぐ。
PIC:RB5 からチップセレクト(SDカード:1番)に繋ぐ。

PICの振動子は10MHzのセラミック振動子とする。
電源はPICとSDカードとともに3.3Vの三端子レギュレーターで供給する。

4、FAT12について

最初はSDカードに書いたデータをPCからダンプしようとしました。
しかし、ダンプする上手な方法が見つかりませんでした。
せっかくSDカードに書き込めてもPCで読めないとおもしろくありません。
よって、今回はファイルシステムFAT12を利用することにします。

 A:ヘッダの説明(リトルエンディアンです)

000 EB 3C 90 4D 53 44 4F 53 35 2E 30 00 02 02 08 00
010 02 00 02 40 1F F8 0C 00 01 00 01 00 00 00 00 00
020 00 00 00 00 00 00 29 3E 00 9E 40 4E 4F 20 4E 41
030 4D 45 20 20 20 20 46 41 54 31 32 20 20 20

 最初の3バイト(EB 3C 90)

ジャンプ命令 90 は NOP

 次の8バイト(4D 53 44 4F 53 35 2E 30)

OEM Nameで「MSDOS5.0」 と書いてある。

 次の2バイト(00 02)

1セクタのバイト数(0x0200 なので512バイト)

 次の1バイト(02)

1クラスタのセクタ数(1クラスタは2セクタ すなわち1クラスタは1024バイト)

 次の2バイト(08 00)

FATテーブルの位置(先頭から +8 セクタめ)

 次の1バイト(02)

FATテーブルの個数(通常は2)

 次の2バイト(00 02)

ルートディレクトリに記載できる最大数(512個)

 次の2バイト(40 1F)

セクタの総数(0x1F40 セクタ=8000セクタ)

 次の1バイト(F8)

メディアのタイプ(F8 は固定ディスクであるらしい)

 次の2バイト(0C 00)

FATテーブルのセクタ数(0x000C =12セクタ)

 次の2バイト(01 00)

1トラックのセクタ数

 次の4バイト(00 00 00 00)

パーティション関係(メディアでは0のはず)

 次の4バイト(00 00 00 00)

セクタの総数(FAT12では 0xFFFF より大きくはならないので必ず0)

 次の1バイト(00)

ドライブ番号(0x80 だとブート用)

 次の1バイト(00)

予約

 次の1バイト(29)

ブート情報

 次の4バイト(3E 00 9E 40)

ボリュームID

 次の11バイト(4E 4F 20 4E 41 4D 45 20 20 20 20)

ボリュームラベル(「NO NAME」と書いてある)

 次の8バイト(46 41 54 31 32 20 20 20)

ファイルシステムタイプ(「FAT12」と書いてある)

 B:FATテーブルの説明

ヘッダの情報で次のことがわかります。
FATテーブルの位置、サイズ、個数の3つの情報です。
上記例では位置が+8セクタ、サイズは12セクタ、個数は2つです。
よって、8セクタめと20セクタめに12セクタ分のテーブルが2つあるといえます。
テーブルは2つありますが20セクタからのは予備のようなものなので今回無視します。

FATテーブルは12バイト単位のリトルエンディアンです。

よって、「12 50 34」と書いた場合にFATでは 「012 345」と読みます。

0x000:未使用領域
0x002-0xff6:次のクラスタ番号
0xff7:異常領域
0xff8:先頭
0xff8-0xfff:データ領域の終了

 例えばテーブルの情報が、

F8 FF FF 03 40 00 FF FF FF FF 0F 00 

とある場合に、

0x00 0xff8
0x01 0xfff
0x02 0x003
0x03 0x004
0x04 0xfff
0x05 0xfff
0x06 0xfff
0x07 0x000

となります。

先頭 0x00 はかならず 0xff8 です。
0x01 は1クラスタで終了します。
0x02 は 0x03 と 0x04 にまたがる3クラスタぶんの領域です
0x05 と 0x06 はそれぞれ1クラスタぶんの領域です。
0x07 は未使用領域です。

 C:ルートディレクトリ

FATテーブルが8セクタからの12セクタと20セクタからの12セクタを使用するので、
ルートディレクトリ用の領域は32セクタから使用することになります。
ヘッダの情報よりルートディレクトリに記載できる最大数は512個です。
また、1ファイルの定義が32バイトであることから512×32バイト必要になります。
ルートディレクトリの定義に必要なセクタ数は32セクタとなります。

46 41 54 31 32 44 41 54 54 58 54 20 00 00 00 00
00 00 00 00 00 00 00 00 00 00 02 00 12 00 00 00

 最初の8バイト(46 41 54 31 32 44 41 54)

ファイル名(「FAT12DAT」と書いてある)

 次の3バイト(54 58 54)

拡張子(「TXT」と書いてある)

 次の1バイト(20)

ファイルの種類(20 はアーカイブファイル)

0x01 読み込み専用
0x02 隠しファイル
0x04 システムファイル
0x08 ボリュームID
0x10 ディレクトリ
0x20 アーカイブファイル

 次の1バイト(00)

予約

 次の3バイト(00 00 00)

作成時刻(時、分、秒)

 次の2バイト(00 00)

作成日(年、月、日)

 次の2バイト(00 00)

最終アクセス日(年、月、日)

 次の2バイト(00 00)

クラスタ番号の上位16ビット(FAT12は関係ありません)

 次の2バイト(00 00)

書き込み時刻(時、分、秒)

 次の2バイト(00 00)

書き込み日(年、月、日)

 次の2バイト(02 00)

クラスタ番号の下位16ビット(2番目のクラスタにデータ本体がある)

 次の4バイト(12 00 00 00)

ファイルサイズ(0x00000012 なので18バイトのデータがある)

 D:ファイル領域

ルートディレクトリの次からがファイル本体部分です。
ファイル領域は64セクタめからスタートします。
この64セクタめがファイル領域の2番目のクラスタになるので注意してください。
ファイル領域の0と1のクラスタは存在しません。
1クラスタは2セクタなのでファイル領域の3番目のクラスタは66セクタからはじまります。

1ファイルが何クラスタ使用するかはFATテーブルで定義されています。
また、ファイルの名前やサイズはルートディレクトリの領域で定義されています。

この領域には純粋に0バイトめから記録したいデータ本体を書き込みます。

 5、初期設定

PIC16F88 の初期設定です。

RXDATA EQU 20H  ; 変数 RXDATA を用意
T0 EQU 21H  ; 変数 T0 を用意

 BSF STATUS, RP0
 MOVLW B'00000010'  ; RB1 を入力に設定
 MOVWF TRISB
 MOVLW B'00000000'
 MOVWF SSPSTAT      ; SSPSTAT をクリア
 BCF STATUS, RP0
 MOVLW B'00100010'  ; SPIモード マスター クロック周波数(/64)の設定
 MOVWF SSPCON

 6、SPI通信

通常のSPI通信です。

SPI
 MOVWF SSPBUF
 BSF STATUS,RP0
SPI_LOOP
 BTFSS SSPSTAT,BF
 GOTO SPI_LOOP
 BCF STATUS,RP0
 MOVF SSPBUF,W
 MOVWF RXDATA
 RETURN

 7、コマンド0

SDカードの準備をするにはまずコマンド0を送る。
その前にまずSDカードのチップセレクトをHighにしてクロックを80以上送る。
実際にはSPI通信を10回以上繰り返せばクロックを80回以上送ったことになる。
このとき送受信するデータには意味は無い。
次にチップセレクトをLowにする。
以降、チップセレクトはずっとLowのままで良い。

つぎにコマンド0を送る。

コマンド0は 01 000000 で始まる 01 がコマンドを意味し 000000 が「0」を意味する。
01000000 は16進数で表記すると 0x40 となる。
次の4バイトは転送するデータ部分だがここはすべて0にする。
最後の1バイトはCRCだがここは必ず 0x95 にすること。

よってコマンド0は5バイトの 「40 00 00 00 00 95」 である。

次にSPI通信でSDカードに 0xFF を転送し続ける。
このとき 0x01 が返ってくればコマンド0は成功。
コマンド0が成功すれば正常にSDカードとSPI通信ができたと判断できる。

SPI_INIT
 BSF PORTB,5  ; SDカードのチップセレクトをHighに
 MOVLW D'15'  ; 15*8=120 クロックを120個送る
 MOVWF T0
LOOP_INIT
 MOVLW B'11111111'
 CALL SPI
 DECFSZ T0,F
 GOTO LOOP_INIT
 BCF PORTB,5  ; SDカードのチップセレクトをLowに

CMD0
 MOVLW 0x40
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x95
 CALL SPI

LOOP_CMD0
 MOVLW B'11111111'
 CALL SPI

 BTFSC RXDATA,7
 GOTO LOOP_CMD0
 BTFSC RXDATA,6
 GOTO LOOP_CMD0
 BTFSC RXDATA,5
 GOTO LOOP_CMD0
 BTFSC RXDATA,4
 GOTO LOOP_CMD0
 BTFSC RXDATA,3
 GOTO LOOP_CMD0
 BTFSC RXDATA,2
 GOTO LOOP_CMD0
 BTFSC RXDATA,1
 GOTO LOOP_CMD0
 BTFSS RXDATA,0
 GOTO LOOP_CMD0
 RETURN

 8、コマンド1

コマンド1は 01 000001 なので 0x41 で始まる。
4バイトのデータ領域はすべて0とする。
コマンド1からCRCは無効になるので 0x01 を送っておけば良い。

よってコマンド1は5バイトの 「41 00 00 00 00 01」 である。

コマンド1を送ったあとは 0xFF を送り続ける。
このとき 0x00 が返ってくれば正常である。
コマンド0は 0x01 だがコマンド1は 0x00 なので間違えないように。

CMD1
 MOVLW 0xFF  ; ダミー
 CALL SPI
 MOVLW 0x41
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x01
 CALL SPI

LOOP_CMD1
 MOVLW B'11111111'
 CALL SPI

 BTFSC RXDATA,7
 GOTO LOOP_CMD1
 BTFSC RXDATA,6
 GOTO LOOP_CMD1
 BTFSC RXDATA,5
 GOTO LOOP_CMD1
 BTFSC RXDATA,4
 GOTO LOOP_CMD1
 BTFSC RXDATA,3
 GOTO LOOP_CMD1
 BTFSC RXDATA,2
 GOTO LOOP_CMD1
 BTFSC RXDATA,1
 GOTO LOOP_CMD1
 BTFSC RXDATA,0
 GOTO CMD1
 RETURN

 9、コマンド9

コマンド9は必須コマンドではない。
ただし読み込みのテストをする場合に便利なのでコマンド9を使ってみる。

コマンド9は5バイトの 「49 00 00 00 00 01」 になる。

0xFF を送り続けて 0x00 が返ってくれば成功。

続けて 0xFF を送り続けるとこんどは 0xFE が返ってくる。
以降、16バイトを受信する。
そして最後に2バイトのCRCを送ってくる。

受信した16バイトのデータはCSDと言ってSDカードのサイズなどの領域情報が得られる。
またコマンド10ではCIDといってSDカードのベンダー情報などが得られる。

以下のサンプルは読み取り部分を省略し最後は無限ループするので注意。

CMD9
 MOVLW 0xFF  ; ダミー
 CALL SPI
 MOVLW 0x49
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x01
 CALL SPI

LOOP_CMD9
 MOVLW B'11111111'
 CALL SPI

 BTFSC RXDATA,7
 GOTO LOOP_CMD9
 BTFSC RXDATA,6
 GOTO CMD9
 BTFSC RXDATA,5
 GOTO CMD9
 BTFSC RXDATA,4
 GOTO CMD9
 BTFSC RXDATA,3
 GOTO CMD9
 BTFSC RXDATA,2
 GOTO CMD9
 BTFSC RXDATA,1
 GOTO CMD9
 BTFSC RXDATA,0
 GOTO CMD9

LOOP_CSD  ; 無限ループです
 MOVLW B'11111111'
 CALL SPI

 ; ここに RXDATA を読む処理を追加

 GOTO LOOP_CSD

 10、コマンド24

最初に、このコードはSDカードに書き込みをします。
すでにあるSDカード内のデータが読めなくなるので注意。

コマンド24はSDカードへの書き込みコマンドです。
コマンド24は4バイトのデータ領域に開始バイト位置を記述します。
例えば2セクタめから書き込みたいとします。
このとき1セクタのサイズは512バイトなので、開始位置は1024バイトのところになります。
よって、データ領域は 00 00 04 00 となります。
このとき、コマンド24は5バイトの 「24 00 00 04 00 01」 と書きます。

コマンド24は 0xFF を送り続けて 0x00 が返ってくれば成功です。

コマンド24が成功したらダミーをいくつか送ります。
書き込み開始は最初に 0xFE を送ります。
次に1セクタぶんの512バイトを送信します。
最後に2バイトのCRCを送りますがここは 0x00 0x01 を送ります。

次に 0xFF を送り2進数で ???00101 が返れば送信成功です(? は不定)。
SDカードは書き込みが終わるまで出力ピンがLowになります。
出力ピンがLowからHighになるまでコマンドは送らないようにします。

さて、これで書き込みは成功したのですが、
書き込んだあとの返信が

11110010 10000000 00000011 11111111

となってしまいました。
確かに 00101 となっている部分があるので成功なのかなと思います。
ただし、1ビットずれて1バイト単位におさまっていません。
こういうものなのか、これでは失敗なのかわかりません。
もしかすると1つずつクロックを送って1ビット単位でのチェックが必要なのかもしれません。
ともあれ書き込みはうまくいきました。

CMD24
 MOVLW 0xFF  ; ダミー
 CALL SPI
 MOVLW 0x58
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x01
 CALL SPI

LOOP_CMD24
 MOVLW B'11111111'
 CALL SPI

 BTFSC RXDATA,7
 GOTO LOOP_CMD24
 BTFSC RXDATA,6
 GOTO CMD24
 BTFSC RXDATA,5
 GOTO CMD24
 BTFSC RXDATA,4
 GOTO CMD24
 BTFSC RXDATA,3
 GOTO CMD24
 BTFSC RXDATA,2
 GOTO CMD24
 BTFSC RXDATA,1
 GOTO CMD24
 BTFSC RXDATA,0
 GOTO CMD24

 MOVLW B'11111111'  ; ここからダミーをいくつか送る
 CALL SPI
 MOVLW B'11111111'
 CALL SPI
 MOVLW B'11111111'
 CALL SPI
 MOVLW B'11111111'
 CALL SPI

 MOVLW B'11111110'  ; この次から書き込み開始
 CALL SPI

; ここからFAT12のヘッダデータを書き込み
;0-15
 MOVLW 0xEB
 CALL SPI
 MOVLW 0x3C
 CALL SPI
 MOVLW 0x90
 CALL SPI
 MOVLW 0x4D
 CALL SPI
 MOVLW 0x53
 CALL SPI
 MOVLW 0x44
 CALL SPI
 MOVLW 0x4F
 CALL SPI
 MOVLW 0x53
 CALL SPI

 MOVLW 0x35
 CALL SPI
 MOVLW 0x2E
 CALL SPI
 MOVLW 0x30
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x02
 CALL SPI
 MOVLW 0x02
 CALL SPI
 MOVLW 0x08
 CALL SPI
 MOVLW 0x00
 CALL SPI

;16-31
 MOVLW 0x02
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x02
 CALL SPI
 MOVLW 0x40
 CALL SPI
 MOVLW 0x1F
 CALL SPI
 MOVLW 0xF8
 CALL SPI
 MOVLW 0x0C
 CALL SPI
 MOVLW 0x00
 CALL SPI

 MOVLW 0x01
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x01
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI

;32-47
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x00
 CALL SPI
 MOVLW 0x29
 CALL SPI
 MOVLW 0x3E
 CALL SPI

 MOVLW 0x00
 CALL SPI
 MOVLW 0x9E
 CALL SPI
 MOVLW 0x40
 CALL SPI
 MOVLW 0x4E
 CALL SPI
 MOVLW 0x4F
 CALL SPI
 MOVLW 0x20
 CALL SPI
 MOVLW 0x4E
 CALL SPI
 MOVLW 0x41
 CALL SPI

;48-63
 MOVLW 0x4D
 CALL SPI
 MOVLW 0x45
 CALL SPI
 MOVLW 0x20
 CALL SPI
 MOVLW 0x20
 CALL SPI
 MOVLW 0x20
 CALL SPI
 MOVLW 0x20
 CALL SPI
 MOVLW 0x46
 CALL SPI
 MOVLW 0x41
 CALL SPI

 MOVLW 0x54
 CALL SPI
 MOVLW 0x31
 CALL SPI
 MOVLW 0x32
 CALL SPI
 MOVLW 0x20
 CALL SPI
 MOVLW 0x20
 CALL SPI
 MOVLW 0x20
 CALL SPI

; ここまで62バイトの書き込み

; 66バイトの 0x00 の書き込み
 MOVLW D'66'
 MOVWF T0
LOOP_W0
 MOVLW B'00000000'
 CALL SPI
 DECFSZ T0,F
 GOTO LOOP_W0

; 128バイトの 0x00 の書き込み
 MOVLW D'128'
 MOVWF T0
LOOP_W1
 MOVLW B'00000000'
 CALL SPI
 DECFSZ T0,F
 GOTO LOOP_W1

; 128バイトの 0x00 の書き込み
 MOVLW D'128'
 MOVWF T0
LOOP_W2
 MOVLW B'00000000'
 CALL SPI
 DECFSZ T0,F
 GOTO LOOP_W2

; 128バイトの 0x00 の書き込み
 MOVLW D'128'
 MOVWF T0
LOOP_W3
 MOVLW B'00000000'
 CALL SPI
 DECFSZ T0,F
 GOTO LOOP_W3

; CRCは 0x00 01 を送る
 MOVLW B'00000000'
 CALL SPI

 MOVLW B'00000001'
 CALL SPI

LOOP_WRITE  ; 無限ループです
 MOVLW B'11111111'
 CALL SPI

 ; 書き込み成功の確認をする

 GOTO LOOP_WRITE

inserted by FC2 system