SDカードドライバ3 ----SDカードにFAT16でログを保存する----
SDカードドライバ2でblock read/writeができるようになりました.
つぎのステップでは、ログファイルをFAT16でSDカード上に残すようにしたいと思います.
これを書いている2012.3現在ではSDカードの容量は32GBが¥2000ぐらいで流通してい
ますが、FAT16が2GBまでしか対応してないようですので、以下で解説するのは2GB容量しかありません.
また、4GBあるいは8GBのSDカードを挿入すると正常には動きませんでした.
STM8S-DISCOVERYでFAT16を扱うときの悩み
FAT16のread/writeをするには、メモリに展開したくなるワークエリアとして次があげられます.
その理由は、write中に書き換えなくちゃいけない場所はデータ+FAT+RDEの3つの領域だからです.
さらに、データとFATは書き継ぎ型です.なので、メモリの節約を考えずに欲しいだけワークエリアを確保して良いのなら下記が欲しくなります.
現在write中のブロック 512BYTE
次にwriteするブロック 512BYTE
現在write中のFAT 512BYTE
次にwriteするFAT 512BYTE
RDE
512BYTE
ところが、STM8S-DISCOVERYの内蔵RAMは2kBしかありません.
しかも、スタックとか変数領域もこの2kBの中に展開されますので、実際にワークエリアとして使えるのは1kB強だというのがわたしの感触です.
なので、こんなにたくさんのワークエアリアをメモリに展開できるわけがないのでどうしましょうか?って悩みにはまります.
しかたないので、ワークエリアを512BYTEに制限して、read-modify-write的に処理することに決めました.
すなわちこんなフローです. SDstr[512]をワークエリアの変数名とします.
writeしたいデータが来る →
SDstr[512]に積む →
512BYTEに達したら以下を行う →
SDstr[512]をwrite(ブロック504〜4194303) →
FATをread → SDstr[512]を書き換える → SDstr[512]をwrite(ブロック6〜471) →
RDEをread → SDstr[512]のファイルサイズ書き換える → SDstr[512]をwrite(ブロック472〜503)
ちなみに、先頭のBPB領域はformatしたら書き換えません.
というわけで上記のように、FAT16のプログラミング方法を決めました.
つぎにログファイルの作り方をどうするかですが、最もシンプルにします.
SDカードをformatすると、トップ階層にlog.txtという空のファイルができます.(もちろんwindowsで開けます)
ログデータをこのlog.txtに追記してゆきます.(もちろんwindowsで開けます)
どんなユーザーインターフェースにするか?
terminal softの画面で操作します.
enterキーを押すとこんなコマンドメニューが出ます.
inc 2 : write incremental to block 2 (adrs=2*512)
fill 49 129 : write 49 to block 129 (adrs=129*512)
fill 49 12 129 : write 49 to block 12-129
txt hello 996 : write hello to block 996 (adrs=996*512)
format : format 2GB FAT16
log 10000 v : genrate log file 10000BYTE (v:verbose)
3
: dump block 3 (adrs=3*512)
3 18 : dump block from 3 to 18
コマンドの説明をします.今回新たに作ったコマンドはformatとlogです.
inc 2 → block 2 に、00から始まるインクリメンタルデータをwriteします.
fill 49 129 → block 129を、49で埋め尽くします.
fill 49 12 129 → blovk 49〜129を、49で埋め尽くします.
txt hello 996 → block 996の先頭に、helloという文字を書き込みます.
format → SDカードを2GBにFAT16でformatします.
log 10000 → SDカードに記録するダミーログを自動発生します.ダミーデータは10000BYTE分のインクリメンタルデータです.
log 10000 v → 同上. vervose mode
3 → block 3 を dumpします.
3 18 → block 3〜18をdumpします.
動作例1)
SDカードを挿入後、resetします.
inc 2
2
block 0x00000002
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f | ................
10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f | ................
20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./
30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f | 0123456789:;<=>?
40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f | @ABCDEFGHIJKLMNO
50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f | PQRSTUVWXYZ[\]^_
60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f | `abcdefghijklmno
動作例2)
SDカードを挿入後、resetします.
format →SDカードトップに空のlog.txtがある状態にformatします(windows7で開けます)
Wrote block 0-5 (BPB)
Wrote block 6 (FAT)
Wrote block 472 (RDE)
0 →BPB領域ダンプ
block 0x00000000
eb 3c 90 4d 53 44 4f 53 35 2e 30 00 02 40 06 00 | .<.MSDOS5.0..@..
02 00 02 00 00 f8 e9 00 3f 00 ff 00 00 00 00 00 | ........?.......
00 10 3a 00 80 00 29 29 f0 4a ec 4e 4f 20 4e 41 | ..:...)).J.NO NA
4d 45 20 20 20 20 46 41 54 31 36 20 20 20 33 c9 | ME FAT16 3ノ
6 →FAT領域ダンプ
block 0x00000006
f8 ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
472 →RDE領域ダンプ
block 0x000001d8
53 44 43 41 52 44 20 20 20 20 20 08 00 00 00 00 | SDCARD .....
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
4c 4f 47 20 20 20 20 20 54 58 54 20 18 00 00 00 | LOG TXT ....
00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 | ................
動作例3)
format
Wrote block 0-5 (BPB)
Wrote block 6 (FAT)
Wrote block 472 (RDE)
log 50000 →50000BYTEのダミーログデータをSDに記録する
CLUSTER2----------------------------------------------------------------
CLUSTER3---------------------------------
SD flushing
EndCLUSTER 3 EndBLOCK 601 TotalSIZE 0000c350[BYTE], FAT updated, RDE updated
SDカードをwindowsで開くと、log.txtというファイルがある.中身はこんなかんじです.
0000000000000001000000020000000300000004000000050000000600000007
00000008000000090000000a0000000b0000000c0000000d0000000e0000000f
0000001000000011000000120000001300000014000000150000001600000017
00000018000000190000001a0000001b0000001c0000001d0000001e0000001f
動作例4)
format
Wrote block 0-5 (BPB)
Wrote block 6 (FAT)
Wrote block 472 (RDE)
log 10000 v →10000BYTEのダミーログデータをSDに記録する.verbose modeなので詳細を表示する.
CLUSTER2 →クラスタ2をwrite中
BLK[504/0]SizeB[00000200]FAT[6/4]
↑writeステータス:
block504へwrite中.そこはクラスタ内の0 block目.
これまでwriteし終えたのは200H[BYTE].
FATはblock6の4BYTE目に存在する.
BLK[505/1]SizeB[00000400]FAT[6/4]
BLK[506/2]SizeB[00000600]FAT[6/4] BLK[507/3]SizeB[00000800]FAT[6/4]
BLK[508/4]SizeB[00000a00]FAT[6/4] BLK[509/5]SizeB[00000c00]FAT[6/4]
BLK[510/6]SizeB[00000e00]FAT[6/4] BLK[511/7]SizeB[00001000]FAT[6/4]
BLK[512/8]SizeB[00001200]FAT[6/4] BLK[513/9]SizeB[00001400]FAT[6/4]
BLK[514/10]SizeB[00001600]FAT[6/4] BLK[515/11]SizeB[00001800]FAT[6/4]
BLK[516/12]SizeB[00001a00]FAT[6/4] BLK[517/13]SizeB[00001c00]FAT[6/4]
BLK[518/14]SizeB[00001e00]FAT[6/4] BLK[519/15]SizeB[00002000]FAT[6/4]
BLK[520/16]SizeB[00002200]FAT[6/4] BLK[521/17]SizeB[00002400]FAT[6/4]
BLK[522/18]SizeB[00002600]FAT[6/4]
SD flushing
EndCLUSTER 2 EndBLOCK 523 TotalSIZE 00002710[BYTE], FAT updated, RDE updated
↑最終クラスタは2.最終blockは523.サイズは2710H BYTE.FATとRDEを変更しました.
作る物の仕様
SD card
リセットSW
USBシリアル変換基板
PCのTerminaソフト画面にSD cardへのアクセス結果が出力される.
termina softからの操作で、SD cardへの書き込みおよびダンプができる.
SD cardのSPIモードで読み書きする.
CRCは使わない.
SD cardの属性情報はケアしない.
手持ちの2GBのmicro SD cardで動作確認しました.
4GBと8GBのSDカードでは正常に動作しませんでした.
基板写真
右下の14pin ピンヘッダはLCDを取り付けるためにつけたものなので、今回は無関係なので無視してください.
基板回路図
●リセットSWは必須ではありませんが、あったほうが便利です.写真のCN1に付いているプッシュSWがそれです.
●USBシリアル変換基板は、USB経由でCOM PORT接続するためです.
●SD card CNは、秋葉のマルツで買いました.
ヒロセの製品です.製品資料はここにあります. http://www.hirose.co.jp/catalogj_hp/j60900048.pdf
写真にあるように、ユニバーサル基板にコネクタをハンダづけしたドータ基板をつくり、STM8S-DISCOVERYのCN2にグサッと挿すようにしました.
SD card CNの近くの電源にコンデンサをつけました.
SD cardを挿した瞬間にSTM8Sにリセットがかかってしまったので、それを防止するためです.
今回製作する基板はUSBシリアル変換基板内蔵の3.3VをSTM8Sの電源として流用しているので、その3.3Vが弱いのかもしれません.
ハードウエアの製作
@USBシリアル変換回路
まず、UARTをUSBに変換する回路を、STM8S-DISCOVERYにどうやって取り付けるかを説明します.
秋月のUSBシリアル変換基板をSTM8S-DISCOVERYに取り付けます.
回路図は下記です.配線は上記回路図の4本です.
●STM8SのPD5とPD6には、UARTの送信信号と受信信号が出てきますので、それをUSBシリア ル基板に配線しています.
●STM8S-DISCOVERYでは、電源はFLASH焼き回路から5V or 3.3Vが供給されますが、これを切断し、USBシリアル変換基板から3.3Vが供給されるようにします.
ただし5Vじゃダメってことではありません.3.3Vにしたのはなんとなくです.
そのために、ジャンパーをつぎの1)2)のように設定 してください.
1) USBシリアル変換基板のジャンパーピンは、下図の赤色の位置にします.
2) STM8S-DISCOVERYのJP1のジャンパーピンは、外しておきます.
USBシリアル変換基板の取付完了状態はこのようになります.
右の黄色の囲いがUSBシリアル変換回路です.同基板に刺さっているMini-USBがPCのUSBへ行きます.
中央の水色の囲いがSTM8Sマイコンです.USBシリアル変換回路と配線されています.
左の赤い囲いは、ブロック図には描きませんでしたがSTM8Sを焼くために必須な回路です.同基板に刺さっているUSBケーブルは、STM8Sを焼くためのケーブルです.やはりPCに接続します.
この回路は、PCのUSBポートを都合2ヶ占有することになります.
プログラムを焼いてしまえば、焼き用のUSBケーブルは不要になるわけで、下図のようにUSBを外してしまいたくなるのが人情です.
ところが、USBケーブルを外しただけでは、STM8Sにリセットがかかってしまって動かなくなってしまいます.
そこで、下の写真の丸の中にあるSB1とSB2をハンダごてでOPENにしてやればリセット線が切断されるので、STM8Sが動きます.
この方法は憶えておきましょう.
AリセットSW
これは適当につけてください.
プルアップ抵抗は不要で、対GNDへ落とす形式でOKです.
BSD card CN
回路図のとおりに作ってください.
STM8S-DISCOVERYのCN2に挿すドータ基板状態に作るのがオススメです.
電源コンデンサはつけましょうね.私はしっかりとトラブりましたので.
ソースコードの解説
SD cardドライバは説明すべきコードがたくさんあります.
サンプルプログラムをこちらに置きます.
main.c
#include "stdio.h" Cの標準ライブラリ
#include "stdlib.h"
#include "string.h"
#include "stm8s.h" STM社のライブラリ
#include "usrlib-uart.h" UARTの自作ライブラリ
#include "usrlib-sd.h" SDカードの自作ライブラリ
char UARTstrTX[80]; UARTへ送信する文字列バッファ
void UART_Menu(void); メニュー処理ルーチンのプロトタイプ
void main(void) メインルーチン
{
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, @CLK_SOURCE_HSI, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
CLK_HSIPrescalerConfig( ACLK_PRESCALER_HSIDIV1 );
CLK_SYSCLKConfig( BCLK_PRESCALER_CPUDIV1 );
@なんとはなしに、内部16MHz(HSI)に設定しました.
AHSIのプリスケーラをx1に設定します.なので16MHzが供給されます.
BCPUクロックのプリスケーラをx1に設定します.なので16MHzが供給されます.
結果として、CPUにもタイマにもみんな16MHzが供給されます.
UART2_DeInit();
UART2_Init((u32)115200, UART2_WORDLENGTH_9D, UART2_STOPBITS_1,
UART2_PARITY_ODD, UART2_SYNCMODE_CLOCK_DISABLE,
UART2_MODE_TXRX_ENABLE);
UART2_ITConfig(UART2_IT_RXNE_OR, ENABLE);
UART2_Cmd(ENABLE);
UARTを使うための設定です.この設定だと、115200bps,8bit,stop1bit,parity ODD と設定されます.
GPIO_Init(GPIOD, 0b00000001, GPIO_MODE_OUT_PP_HIGH_FAST);
LEDのインジケータポートを出力に設定.
enableInterrupts(); 割り込みイネーブル.
SerialPutString("\r\nstart SD card IF test\r\n"); UARTにオープニングメッセージを送信します.
GPIO_Init(SD_DETECT_GPIO_PORT, SD_DETECT_PIN, GPIO_MODE_IN_PU_NO_IT);
SDカードの挿入検出SWが接続されたポートをプルアップされた入力ポートに設定します.
SerialPutString("waiting for SD card\r\n"); SDカードの挿入待ちメッセージ
while(SD_Detect()==SD_NOT_PRESENT){;} SDカード挿入までwait
SerialPutString("SD card inserted\r\n"); SDカード挿入メッセージ
SD_Init(); // Initialization SPI and SD card SDカードインターフェース初期化
while(1) { UART_Menu(); } メニュー処理の無限ループ
} main()終了
void DumpBlock(void) 整形してダンプを表示する関数です.ただのprint文なので解説は省略.
{
省略
}
u32 SD_size_byte; SDカードに書き込んだトータルデータサイズ(BYTE)です
u8 SD_full; SDカードが満杯になったら1になるフラグ
void log_dummy(u32 x, char v)
ダミーログデータを発生させるルーチン
xはダミーログデータのバイト数.vはverbose指定.
{
u32 i;
SD_write_sequencial_init();
SD_write_sequence()というルーチンは、文字列を入力すると、シーケンシャルにSDカードにライトしてくれます.
その関数を初期するのが SD_write_sequencial_init() です.
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=0; ワークエリアをゼロで初期化
for(i=0;i<(x>>3);i++) { 3bitシフトしている理由は、8文字のインクリメンタルデータを作るからです.
char c[12];
sprintf(c,"%04x%04x",(u16)((i&0xFFFF0000)>>16),(u16)(i&0xFFFF));
iを8文字のHEXデータに変換します.
RaisonanceのC compilerのprintf()は32bit整数が通らないので、16bitに分割して処理しています.
SD_write_sequencial(c,v); SDカードにシーケンシャルライトする関数です.cは任意長の文字列.vはverbose指定.
}
SD_write_sequencial("",v);
空文字でSD_write_sequencial()をコールすると、ワークエリアに残っているデータをSDカードに書き込みます.
これは重要な操作です.
}
void UART_Menu(void) メニュー処理ルーチンです.
{
uint32_t i;
if(UART_STR_EXIST==1) UARTが1行受信するとこのフラグが1になります.
{
int fieldnumber;
uint32_t address;
fieldnumber = separate_line(command);
たとえば、コマンド行が fill 0 10 40 だったら、separate_line()関数は次を出力します.
これをつかって以降のコマンド処理を行います.
fieldnumber=3
Field[0]=”fill”
Field[1]="0"
Field[2]="10"
Field[3]="40"
if(strcmp(Field[0],"inc")==0 && fieldnumber==2) { inc xのコマンド処理です.
address=(uint32_t)(atof(Field[1])*SD_BLOCK_SIZE); SDカードのアドレスをx*512で計算します
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=(uint8_t)i; ワークエリアをインクリメンタルデータで満たします
SD_WriteBlock(SDstr,
address); ワークエリアをSDカードにwriteします
}
else if(strcmp(Field[0],"fill")==0 && fieldnumber==3) { fill x yのコマンド処理です.
uint8_t c;
c=(uint8_t)atof(Field[1]); 満たすデータ
address=(uint32_t)(atof(Field[2])*SD_BLOCK_SIZE); SDカードのアドレス
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=c; ワークエリアを満たします
SD_WriteBlock(SDstr,
address); ワークエリアをSDカードにwriteします
}
else if(strcmp(Field[0],"fill")==0 && fieldnumber==4) { fill x y zのコマンド処理です.
uint8_t c;
uint32_t block_stt, block_end, block;
c=(uint8_t)atof(Field[1]); 満たすデータ
block_stt=(uint32_t)atof(Field[2]); 満たすblock先頭
block_end=(uint32_t)atof(Field[3]); 満たすblock末尾
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=c; ワークエリアを満たします
SerialPutString("\r\n"); UARTに改行を送信
for(block=block_stt;block<=block_end;block++) 満たすblock先頭〜末尾まで
{
if((block % 6)==5)SerialPutString("\r\n"); 単なる表示の整形のための改行
address=(uint32_t)(block*SD_BLOCK_SIZE); SDカードアドレス
SD_WriteBlock(SDstr, address); ワークエリアをSDカードにwriteします
sprintf(UARTstrTX,"0x%04x%04x
",(int)(block>>16),(int)(block&0xFFFF)); メッセージ表示文字列
SerialPutString(UARTstrTX); UARTからPCへ送信
}
}
else if(strcmp(Field[0],"txt")==0 && fieldnumber==3) { txt helloのコマンド処理です.
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=0xFF; ワークエリアをFFで満たします.FFに特に意味はないです.
strcpy(SDstr,Field[1]); ワークエリアの先頭に"hello"を書きます
address=(uint32_t)(atof(Field[2])*SD_BLOCK_SIZE); SDカードアドレス
SD_WriteBlock(SDstr,
address); ワークエリアをSDカードにwriteします
}
else if(strcmp(Field[0],"format")==0 && fieldnumber==1) { formatのコマンド処理です.
SerialPutString("\r\n"); UARTに改行を送信
SD_format_2GB(); 2GBのSDカードをformatするルーチンをコールします
}
else if(strcmp(Field[0],"log")==0 && (fieldnumber==2||fieldnumber==3)) { logコマンド処理です.
u32 n;
n=(u32)(atof(Field[1])); ログファイルのバイト数
SerialPutString("\r\n"); UARTに改行を送信
if(fieldnumber==2) log_dummy(n,' '); log 100000 の場合
if(fieldnumber==3) log_dummy(n,Field[2][0]); log 100000 v の場合はverbose mode
}
else if(fieldnumber==1) { ダンプコマンド処理
address=(uint32_t)(atof(Field[0])*SD_BLOCK_SIZE); SDカードアドレス
if(address==0 &&
Field[0][0]!=0x30) goto NG; // adrs 0 but not "0" コマンドが数字じゃない場合は却下します
sprintf(UARTstrTX,"\r\nblock
0x%04x%04x\r\n",(int)(((uint32_t)atof(Field[0]))>>16),(int)(((uint32_t)atof(Field[0]))&0xFFFF)); ブロック番号を表示
SerialPutString(UARTstrTX); UARTへ送信
SD_ReadBlock(SDstr, address); 目的のブロックをSDカードから読んでワークエリアに格納する
DumpBlock(); ダンプ表示します
}
else if(fieldnumber==2) { x〜yまでのダンプ処理
uint32_t block_stt, block_end;
block_stt=(uint32_t)atof(Field[0]); 先頭ブロック
block_end=(uint32_t)atof(Field[1]); 末尾ブロック
if(block_stt==block_end) goto NG; チェック
if(block_stt>block_end) goto NG; チェック
for(i=block_stt;i<=block_end;i++) 先頭ブロック〜末尾ブロックまで
{
address=(uint32_t)(i*SD_BLOCK_SIZE); SDカードアドレス
sprintf(UARTstrTX,"\r\nblock
0x%04x%04x\r\n",(int)((uint32_t)i>>16),(int)((uint32_t)i&0xFFFF)); ブロック番号を表示
SerialPutString(UARTstrTX); UARTへ送信
SD_ReadBlock(SDstr, address); 目的のブロックをSDカードから読んでワークエリアに格納する
DumpBlock(); ダンプ表示します
}
}
else
{
NG: コマンドエラーのとき
SerialPutString("\r\n\nCOMMAND NG!\r\n");
}
MENU: メニューをUARTへ送信
SerialPutString("\r\n");
SerialPutString("inc
2 : write incremental
to block 2 (adrs=2*512)\r\n");
SerialPutString("fill 49 129 : write 49 to block 129 (adrs=129*512)\r\n");
SerialPutString("fill 49 12 129: write 49 to block 12-129\r\n");
SerialPutString("txt hello 996 : write hello to block 996 (adrs=996*512)\r\n");
SerialPutString("format :
format 2GB FAT16\r\n");
SerialPutString("log 10000 v : genrate log file 10000BYTE (v:verbose)\r\n");
SerialPutString("3
: dump block 3 (adrs=3*512)\r\n");
SerialPutString("3
18 : dump block
from 3 to 18\r\n");
UART_STR_EXIST=0;
}
return;
}
usrlib_sd.h
SD cardのドライバルーチンのためのヘッダファイルで、ポート番号とかコマンド番号とかエラーコードとかを定義しています.
#include "stm8s.h"
おなじみのライブラリのインクルードです.
// hardware definitions
#define SD_SPI_CLK CLK_PERIPHERAL_SPI
#define
SD_SPI_SCK_GPIO_PORT
GPIOC //
GPIOC
#define
SD_SPI_SCK_PIN
GPIO_PIN_5 // PC5
#define SD_SPI_MISO_GPIO_PORT GPIOC // GPIOC
#define
SD_SPI_MISO_PIN
GPIO_PIN_7 // PC7
#define SD_SPI_MOSI_GPIO_PORT GPIOC // GPIOC
#define
SD_SPI_MOSI_PIN
GPIO_PIN_6 // PC6
#define
SD_CS_GPIO_PORT
GPIOE //
GPIOE
#define
SD_CS_PIN
GPIO_PIN_5 // PE5
#define
SD_DETECT_GPIO_PORT
GPIOG //
GPIOG
#define
SD_DETECT_PIN
GPIO_PIN_0 // PG0
これらは、GPIOのポートの定義をしています.
回路図と一致するように定義しているわけです.
もしも、STM8S105C6じゃなくて、別のラインナップを使うのであれば、ここをカスタマイズすればOKです.
// SD card parameters
#define SD_PRESENT ((uint8_t)0x01)
#define SD_NOT_PRESENT ((uint8_t)0x00)
SD cardがある/なしのbitパターンを定義しています.
SD card挿入SWはPG0に接続しましたから、bit0にそのSWの影響が生じるわけです.
#define SD_BLOCK_SIZE 0x200
SD cardはblockが512BYTEです.
#define SD_DUMMY_BYTE 0xFF
あとで解説しますけど、SD cardに対する用事が特にないけれどもダミークロックを送信したい時には仕方ないので何かデータを送信せざるを得ないので、そのときはFFを送信します.
// SD commands
#define SD_CMD_GO_IDLE_STATE 0 /*!< CMD0 = 0x40 */
#define SD_CMD_SEND_OP_COND 1 /*!< CMD1 = 0x41 */
#define
SD_CMD_SEND_CSD
9 /*!< CMD9 = 0x49 */
#define
SD_CMD_SEND_CID
10 /*!< CMD10 = 0x4A */
#define SD_CMD_STOP_TRANSMISSION 12 /*!< CMD12 = 0x4C */
#define SD_CMD_SEND_STATUS 13 /*!< CMD13 = 0x4D */
#define SD_CMD_SET_BLOCKLEN 16 /*!< CMD16 = 0x50 */
#define SD_CMD_READ_SINGLE_BLOCK 17 /*!< CMD17 = 0x51 */
#define SD_CMD_READ_MULT_BLOCK 18 /*!< CMD18 = 0x52 */
#define SD_CMD_SET_BLOCK_COUNT 23 /*!< CMD23 = 0x57 */
#define SD_CMD_WRITE_SINGLE_BLOCK 24 /*!< CMD24 = 0x58 */
#define SD_CMD_WRITE_MULT_BLOCK 25 /*!< CMD25 = 0x59 */
#define
SD_CMD_PROG_CSD
27 /*!< CMD27 = 0x5B */
#define SD_CMD_SET_WRITE_PROT 28 /*!< CMD28 = 0x5C */
#define SD_CMD_CLR_WRITE_PROT 29 /*!< CMD29 = 0x5D */
#define SD_CMD_SEND_WRITE_PROT 30 /*!< CMD30 = 0x5E */
#define SD_CMD_SD_ERASE_GRP_START 32 /*!< CMD32 = 0x60 */
#define SD_CMD_SD_ERASE_GRP_END 33 /*!< CMD33 = 0x61 */
#define SD_CMD_UNTAG_SECTOR 34 /*!< CMD34 = 0x62 */
#define SD_CMD_ERASE_GRP_START 35 /*!< CMD35 = 0x63 */
#define SD_CMD_ERASE_GRP_END 36 /*!< CMD36 = 0x64 */
#define SD_CMD_UNTAG_ERASE_GROUP 37 /*!< CMD37 = 0x65 */
#define
SD_CMD_ERASE
38 /*!< CMD38 = 0x66 */
SD cardにいろいろなコマンドを送ってwriteしたりreadしたりするので、そのコマンド番号の定義です.
このサンプルプログラムで使うのは、0,1,17,24だけです.
それだけ知ってりゃ読み書きできるってことです.
// Start Data tokens
#define SD_START_DATA_SINGLE_BLOCK_READ 0xFE /*!< Data token start byte, Start Single Block Read */
#define SD_START_DATA_MULTIPLE_BLOCK_READ 0xFE /*!< Data token start byte, Start Multiple Block Read */
#define SD_START_DATA_SINGLE_BLOCK_WRITE 0xFE /*!< Data token start byte, Start Single Block Write */
#define SD_START_DATA_MULTIPLE_BLOCK_WRITE 0xFD /*!< Data token start byte, Start Multiple Block Write */
#define SD_STOP_DATA_MULTIPLE_BLOCK_WRITE 0xFD /*!< Data toke stop byte, Stop Multiple Block Write */
SD cardにコマンドを送りますと、SD cardは「ああそうwriteね」などと気づくわけです.
で、SD cardは、データが来るのを待ち構えるわけです.
そのときに、データの始まりの合図がFEです.
そのFEをstart data tokenと呼びます.
いろいろと定義してありますけど、このサンプルプログラムで使うのはFEだけです.
FEがstart data tokenだというのはSD card formatで決められているので、FEを別の値にカスタマイズするのは不可能です.
// SD command responses and error flags
#define SD_RESPONSE_NO_ERROR 0x00
#define SD_IN_IDLE_STATE 0x01
#define SD_ERASE_RESET 0x02
#define SD_ILLEGAL_COMMAND 0x04
#define SD_COM_CRC_ERROR 0x08
#define SD_ERASE_SEQUENCE_ERROR 0x10
#define SD_ADDRESS_ERROR 0x20
#define SD_PARAMETER_ERROR 0x40
#define SD_RESPONSE_FAILURE 0xFF
コマンドに対するSD cardの返値の定義です.
SD cardでは、返値のことをresponseと呼びます.
このサンプルプログラムでケアしているのは0と1ぐらいです.
// Data response error flags
#define
SD_DATA_OK
0x05
#define SD_DATA_CRC_ERROR 0x0B
#define SD_DATA_WRITE_ERROR 0x0D
#define SD_DATA_OTHER_ERROR 0xFF
データに対するSD cardの返値です.コマンド返値とは別です.
このサンプルプログラムでは5をケアしています.
5だけは憶えておきましょう.データを正しく受け取ったら5だと.
/*----- FAT16 function -----*/
extern u32 SD_size_byte; SDカードにwriteしたトータルサイズバイト.ただし0からカウント
extern u8 SD_full; SDカードが満杯になったフラグ
u16 SDstr_ptr(void); 512BYTEのワークエリアの末尾を示すポインタ 0-511
u32 SD_block(void); ブロックの末尾を示すポインタ 504-4194303
u8 SD_block_cnt(void); 1クラスタ=64ブロックです.クラスタ内におけるブロック末尾ポインタ 0-63
u16 SD_cluster(void); クラスタの末尾を示すポインタ 2-65535
u16 SD_FAT_block(void); FATの末尾ブロックを示すポインタ 6-471
u16 SD_FAT_block_ptr(void); 末尾のFATブロック内におけるFATポインタ 0-510
void SD_write_sequencial_init(void); シーケンシャルライトを初期化
void SD_write_sequencial(u8* wdata, char v); シーケンシャルライトルーチン
void SD_format_2GB(void); 2GBのSDカードをformat
/*----- High layer function -----*/
void SD_DeInit(void);
SD cardのインターフェースのためのIO PORTを解放するルーチンです.
uint8_t SD_Init(void);
SD cardのインターフェースのためのIO PORTを設計するルーチンです.
uint8_t SD_Detect(void);
SD cardが挿入されているかどうかをチェックするルーチンです.
ちなみに、このサンプルプログラムでは、ライトプロテクトはケアしてません.
uint8_t SD_ReadBlock(uint8_t* pBuffer, uint32_t ReadAddr);
block読みルーチン
uint8_t SD_WriteBlock(uint8_t* pBuffer, uint32_t WriteAddr);
block書きルーチン
/*----- Medium layer function -----*/
void SD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc);
コマンドを送信するルーチン
uint8_t SD_GetResponse(uint8_t Response);
コマンドレスポンスをチェックするルーチン.
SD cardから引数のレスポンスが返ってくるまでループし続けるのが特徴です.
timeout機能もあります.
uint8_t SD_GetDataResponse(void);
データレスポンスをチェックするルーチン.
uint8_t SD_GoIdleState(void);
SD cardをSPIモードにするルーチン.初期化時に使います.
/*----- Low layer function -----*/
uint8_t SD_WriteByte(uint8_t byte);
SD cardに1BYTE送信するルーチン.SPIにアクセスします.
uint8_t SD_ReadByte(void);
SD cardから1BYTE受信するルーチン.SPIにアクセスします.
void SD_LowLevel_DeInit(void);
SPIを解放するルーチンです.
void SD_LowLevel_Init(void);
SPIを設定するルーチンです.
#define SD_CS_LOW() GPIO_WriteLow(SD_CS_GPIO_PORT, SD_CS_PIN)
#define SD_CS_HIGH() GPIO_WriteHigh(SD_CS_GPIO_PORT, SD_CS_PIN)
SD cardのCSをassert(LOW)、deassert(HIGH)するルーチンです.
usrlib-sd.c
SD cardのドライバルーチンが入っています.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stm8s.h"
#include "usrlib-uart.h"
#include "usrlib-sd.h"
ヘッダファイルを読みます.
uint8_t SDstr[SD_BLOCK_SIZE]; ブロックread/writeのワークエリア512BYTE
void SD_format_2GB(void) 2GB SDカードをformatします
{
int i;
最初にBPBをformatします
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=0; ワークエリアをゼロで満たします
SDstr[0] =0xeb; // BLOCK 0
:
中略
:
SDstr[63]=0xc9;
ここではBPBの先頭64BYTEを下記のようにします.
eb 3c 90 4d 53 44 4f 53 35 2e 30 00 02 40 06 00 | .<.MSDOS5.0..@..
02 00 02 00 00 f8 e9 00 3f 00 ff 00 00 00 00 00 | ........?.......
00 10 3a 00 80 00 29 29 f0 4a ec 4e 4f 20 4e 41 | ..:...)).J.NO NA
4d 45 20 20 20 20 46 41 54 31 36 20 20 20 33 c9 | ME FAT16 3ノ
SD_WriteBlock(SDstr, 0); ワークエリアをSDカードのブロック0にwriteします
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=0; ワークエリアをクリア
SD_WriteBlock(SDstr, (uint32_t)(1*SD_BLOCK_SIZE)); ブロック1にwrite
SD_WriteBlock(SDstr, (uint32_t)(2*SD_BLOCK_SIZE)); ブロック2にwrite
SD_WriteBlock(SDstr, (uint32_t)(3*SD_BLOCK_SIZE)); ブロック3にwrite
SD_WriteBlock(SDstr, (uint32_t)(4*SD_BLOCK_SIZE)); ブロック4にwrite
SD_WriteBlock(SDstr, (uint32_t)(5*SD_BLOCK_SIZE)); ブロック5にwrite
SerialPutString("Wrote block 0-5 (BPB)\r\n"); メッセージを表示
つぎにFATをformatします.
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=0; ワークエリアをクリア
SDstr[0]=0xF8; 先頭をF8FFFFFFにします
SDstr[1]=0xFF;
SDstr[2]=0xFF;
SDstr[3]=0xFF;
SD_WriteBlock(SDstr, (uint32_t)(6*SD_BLOCK_SIZE)); ワークエリアをSDカードのブロック6にwriteします
SerialPutString("Wrote block 6 (FAT)\r\n"); メッセージを表示
つぎにRDEをformatします.RDEはブロック472〜503までですが、ここでは先頭の472だけをformatします.
SDstr[0] ='S';
:
中略
:
SDstr[32+0x1F]=0;
53 44 43 41 52 44 20 20 20 20 20 08 00 00 00 00 | SDCARD ←SDカードのvolume nameです
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
4c 4f 47 20 20 20 20 20 54 58 54 20 18 00 00 00 | LOG TXT ←log.txtファイル名です
00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 | ................
SD_WriteBlock(SDstr, (uint32_t)(472*SD_BLOCK_SIZE)); ワークエリアをSDカードのブロック472にwriteします
SerialPutString("Wrote block 472 (RDE)\r\n"); メッセージを表示
return;
}
下記の関数は、SDカードのパラメータを計算する関数です.
extern u32 SD_size_byte; SDカードにwriteしたトータルサイズバイト.ただし0からカウント
u16 SDstr_ptr(void) {return (u16)( SD_size_byte%512);} 512BYTEのワークエリアの末尾を示すポインタ 0-511
u32 SD_block(void) {return (u32)((SD_size_byte>>9)+504);} ブロックの末尾を示すポインタ 504-4194303
u8 SD_block_cnt(void) {return (u8) ((SD_size_byte>>9)%64);} 1クラスタ=64ブロックです.クラスタ内におけるブロック末尾ポインタ 0-63
u16 SD_cluster(void) {return (u16)(((SD_block()-504)>>6)+2);} クラスタの末尾を示すポインタ 2-65535
u16 SD_FAT_block(void) {return (u16)((SD_cluster()>>8)+6);} FATの末尾ブロックを示すポインタ 6-471
u16 SD_FAT_block_ptr(void) {return (u16)((SD_cluster()%256)*2);} 末尾のFATブロック内におけるFATポインタ 0-510
extern u8 SD_full; SDカードが満タンであるフラグ
extern char UARTstrTX[80]; UARTのバッファ
void SD_write_sequencial_init(void) { シーケンシャルwriteを初期化します
SD_size_byte=0xFFFFFFFF; SDカードにwriteしたトータルバイト数をクリア
SD_full=0; SDカード満タンフラグクリア
}
void SD_write_sequencial(u8* wdata, char v) { シーケンシャルwriteするルーチン
int i;
u8* p;
p=wdata; wdataはwriteしたい任意長の文字列です
if(SD_full){ SDカードが満タンだったらreturnします
SerialPutString("SD card full\r\n");
return; // SD card full ?
}
if(wdata[0]==0){ wdataが空文字だったら、ワークエリアに残った文字列をSDカードにwriteします
SerialPutString("\r\nSD flushing\r\n"); UARTへ表示
if(SDstr_ptr()==511)SerialPutString("\r\n"); ワークエリアのポインタ=511だったらすでにSDカードにwrite済みなのでなにもしない
else { SDカードに書くべきデータがワークエリアに残っている
SD_WriteBlock(SDstr,(uint32_t)(SD_block()*SD_BLOCK_SIZE)); SDカードにワークエリアをwriteする
sprintf(UARTstrTX,"EndCLUSTER %d
EndBLOCK %d TotalSIZE %04x%04x[BYTE],
",SD_cluster(),(u16)SD_block(),(u16)(((SD_size_byte+1)&0xFFFF0000)>>16),(u16)((SD_size_byte+1)&0xFFFF)); UARTへ表示
SerialPutString(UARTstrTX); UARTへ表示
}
SD_ReadBlock(SDstr,(uint32_t)((u32)SD_FAT_block()*SD_BLOCK_SIZE));
次にFATを更新するので、FATの末尾ブロックをワークエリアにreadします.
SDstr[SD_FAT_block_ptr()] =0xFF; FATのポインタにFFFFを書きます
SDstr[SD_FAT_block_ptr()+1]=0xFF;
SD_WriteBlock(SDstr,(uint32_t)((u32)SD_FAT_block()*SD_BLOCK_SIZE)); FATをwriteします
SerialPutString("FAT updated, "); UARTへ表示
SD_size_byte++; write済みBYTEはゼロから数えているのでファイルサイズを表すために+1します
SD_ReadBlock(SDstr,(uint32_t)(472*SD_BLOCK_SIZE)); RDEをreadします
SDstr[32+0x1C]=(u8)( SD_size_byte&0xFF); RDEのファイルサイズを格納する場所を変更します
SDstr[32+0x1D]=(u8)((SD_size_byte&0xFF00)>>8);
SDstr[32+0x1E]=(u8)((SD_size_byte&0xFF0000)>>16);
SDstr[32+0x1F]=(u8)((SD_size_byte&0xFF000000)>>24);
SD_WriteBlock(SDstr,(uint32_t)(472*SD_BLOCK_SIZE)); RDEをwriteします
SerialPutString("RDE updated\r\n"); UARTへ表示
} 以上でワークエリアに残った文字列をSDカードにwriteする処理はおしまいです.
for(i=0;i<strlen(wdata);i++) { 以下ではワークエリアにwdataを格納してゆき、ワークエリアが満杯になったらSDカードにwriteします
SD_size_byte++; ファイルサイズを+1
SDstr[SDstr_ptr()]=*p++; ワークエリアの末尾にwdataを1文字格納
if(SDstr_ptr()==511) { もしもワークエリアの末尾ポインタ=511になって満杯になっていたら、、、、
SD_WriteBlock(SDstr,(uint32_t)(SD_block()*SD_BLOCK_SIZE)); ワークエリアをSDカードにwriteする
SD_ReadBlock(SDstr,(uint32_t)((u32)SD_FAT_block()*SD_BLOCK_SIZE)); FATを更新するためreadします
SDstr[SD_FAT_block_ptr()] =(u8)((SD_cluster()+1)&0xFF); FAT末尾にクラスタ番号を追記
SDstr[SD_FAT_block_ptr()+1]=(u8)(((SD_cluster()+1)&0xFF00)>>8);
SD_WriteBlock(SDstr,(uint32_t)((u32)SD_FAT_block()*SD_BLOCK_SIZE)); ワークエリアをFATブロックにwriteします
for(i=0;i<SD_BLOCK_SIZE;i++) SDstr[i]=0; 次のwdataのためにワークエリアをクリア
if(SD_block_cnt()==0) { UARTへクラスタ番号を表示
sprintf(UARTstrTX,"\r\nCLUSTER%d",SD_cluster());
SerialPutString(UARTstrTX);
if(v=='v')SerialPutString("\r\n");
}
if(v=='v') { verbose modeなら詳細を表示する
sprintf(UARTstrTX,"BLK[%d/%d]SizeB[%04x%04x]FAT[%d/%d]
",(u16)SD_block(),SD_block_cnt(),(u16)(((SD_size_byte+1)&0xFFFF0000)>>16),(u16)((SD_size_byte+1)&0xFFFF),SD_FAT_block(),SD_FAT_block_ptr());
SerialPutString(UARTstrTX);
if((SD_block_cnt()%2)==1)SerialPutString("\r\n"); 表示の整形のために改行
}
else SerialPutString("-"); 非verbose modeなら"-"を表示
if(SD_cluster()>=65530) { クラスタが65530になったらSDカードを満杯とします
SerialPutString("SD card is full\r\n"); UARTへ表示
SD_full=1; SDカード満杯フラグを立てる
}
} ワークエリアが満杯になってSDカードにwriteする処理の終わり
} ワークエリアにwdataを格納する処理の終わり
return;
}
uint8_t SD_WriteByte(uint8_t Data)
{
// Wait until the transmit buffer is empty
while (SPI_GetFlagStatus(SPI_FLAG_TXE) == RESET) {}
SPI_SendData(Data);
// Wait to receive a byte
while (SPI_GetFlagStatus(SPI_FLAG_RXNE) == RESET) {}
return SPI_ReceiveData();
}
SD cardに1BYTE送信するルーチンです.
これはSTM8SのSPIを直接操作するLOWレベル関数です.
じつは、このソースを読んで、なんじゃこりゃと思ったわたしです.
なぜなら、transmitなのに、なぜかrecieveしているからです.
SPIの仕様をよーく読んで理解したことはこうでした.
●SPIの全2重モードで動いている
●なので、transmitとrecieveが必ず同時に生じる
●動作シーケンスはこうなっている
SPI_FLAG_TXEが空表示になるまで待つ
→ SPIのレジスタに1BYTE書く
→ SPIが1BYTE送信する
→ SPIが裏で送信と同時に受信する
→ SPI_FLAG_RXNEが空表示になるまで待つ
→ SPIのレジスタから1BYTE空読みする
uint8_t SD_ReadByte(void)
{
uint8_t Data = 0;
// Wait until the transmit buffer is empty
while (SPI_GetFlagStatus(SPI_FLAG_TXE) == RESET) {}
SPI_SendData(SD_DUMMY_BYTE);
// Wait until a data is received
while (SPI_GetFlagStatus(SPI_FLAG_RXNE) == RESET) {}
Data = SPI_ReceiveData();
return Data;
}
SD cardから1BYTE受信するルーチンです.
これはSTM8SのSPIを直接操作するLOWレベル関数です.
このルーチンも、全2重ゆえにダミーデータを送信して、目的のデータを受信しています.
動作シーケンスはこうなっています.
SPI_FLAG_TXEが空表示になるまで待つ
→ SPIのレジスタにダミーデータ FF を書く
→ SPIが1BYTE送信する
→ SPIが裏で送信と同時に受信する
→ SPI_FLAG_RXNEが空表示になるまで待つ
→ SPIのレジスタから1BYTE読む
void SD_LowLevel_Init(void)
{
// Enable SPI clock
CLK_PeripheralClockConfig(SD_SPI_CLK, ENABLE);
// Set the MOSI,MISO and SCK at high level
GPIO_ExternalPullUpConfig(SD_SPI_SCK_GPIO_PORT, (GPIO_Pin_TypeDef)(SD_SPI_MISO_PIN | SD_SPI_MOSI_PIN | \
SD_SPI_SCK_PIN), ENABLE);
// SD_SPI Configuration
SPI_Init( SPI_FIRSTBIT_MSB, SPI_BAUDRATEPRESCALER_4, SPI_MODE_MASTER,
SPI_CLOCKPOLARITY_HIGH, SPI_CLOCKPHASE_2EDGE,
SPI_DATADIRECTION_2LINES_FULLDUPLEX,
SPI_NSS_SOFT, 0x07);
SPI_Cmd( ENABLE); // SD_SPI enable
// Set MSD ChipSelect pin in Output push-pull high level
GPIO_Init(SD_CS_GPIO_PORT, SD_CS_PIN, GPIO_MODE_OUT_PP_HIGH_SLOW);
}
SD cardのためのLOWレベル設定です.SPIの設定が主です.
CLK_PeripheralClockConfig()は、SPIへclockを供給しています.
GPIO_ExternalPullUpConfig()は、MISO,MOSI,SCKをプルアップしています.SPIは味噌とか模試とか変な名前ですね.
SPI_Init()の引数のメジャーな設定は、MSB first、マスター、clock極性、全2重 です.
この他にマイナーな設定は、
・SPI_BAUDRATEPRESCALER_4 はプリスケ4分周ですから、源発信16MHz/4=4MHzがSD cardに供給されます.clock周波数上限がSD cardの都合で決まっていると思いますけど、よく知りません.
・SPI_CLOCKPHASE_2EDGE はおまじない的にこのままにしてください.
・SPI_NSS_SOFT はおまじない的にこのままにしてください.CSをhardwareで制御するのではなく、softで明示的に制御するという意味です.
・最後の引数の0x07は、CRCの生成多項式の設定ですが、このサンプルプログラムではCRCを使いませんので、設定しても無視されています.
uint8_t SD_Init(void)
{
uint32_t i = 0;
SD_LowLevel_Init(); // Initialize SD_SPI
SD_CS_LOW(); // SD chip select assert
for (i = 0; i <= 9; i++) // wait 80 clocks cycles
{
SD_WriteByte(SD_DUMMY_BYTE); // Send dummy byte 0xFF
}
// SD initialized and set to SPI mode properly */
return (SD_GoIdleState());
}
SD cardを使うための設定です.
最初にSD_LowLevel_Init()でSPIを設定し、その後にSD cardをSPIモードにするというのがおおまかな仕事です.
まず、CSをassert(LOW)にします.
つぎに、SD cardのformatの決まりで、80clock待つことになっているので、ダミーデータFFを10ヶ送信して80clockをSDに与えています.
-----SD_writeByte()では受信もしているので160clockになっているんじゃないのという疑問は間違いです-----
SD_GoIdleState()をコールします.
uint8_t SD_GoIdleState(void)
{
@ SD_CS_LOW(); // SD CS assert
// Send CMD0 (SD_CMD_GO_IDLE_STATE) to put SD in SPI mode
A SD_SendCmd(SD_CMD_GO_IDLE_STATE, 0, 0x95);
// Wait for In Idle State Response (R1 Format) equal to 0x01
B if (SD_GetResponse(SD_IN_IDLE_STATE))
{
// No Idle State Response: return response failure
return SD_RESPONSE_FAILURE;
}
//---------Activates the card initialization process-----------
Cdo
{
SD_CS_HIGH(); // SD CS deassert
D SD_WriteByte(SD_DUMMY_BYTE); // dummy 8 clocks
SD_CS_LOW(); // SD CS assert
// Send CMD1 (Activates the card process) until response equal to 0x0
E SD_SendCmd(SD_CMD_SEND_OP_COND, 0, 0xFF);
// Wait for no error Response (R1 Format) equal to 0x00
}
Fwhile (SD_GetResponse(SD_RESPONSE_NO_ERROR));
SD_CS_HIGH(); // SD CS deassert
GSD_WriteByte(SD_DUMMY_BYTE); // dummy 8 clocks
return SD_RESPONSE_NO_ERROR;
}
SD cardをSPIモードに入れる処理です.やや複雑です.
この複雑さを理解するのがSD cardの処理のクセを理解することになると思います.
処理を順に見てゆきます.
@CS=assertにしていますが、すでにassertされているので無駄って言えば無駄な処理です.
ACMD0を送信します.CMD0はIDLE stateになれというコマンドです.
BCMD0へのコマンドレスポンスは1BYTEだけ返ってきます.IDLE状態なら01が返ってきます.
SD_GetResponse()の中ではtimeout処理とかごにょごにょやってます.
Cdo-whileで囲われた処理をレスポンスがノーエラーになるまで何度も繰り返します.
こういうところがSD cardドライバのめんどくさいところです.
実機動作では、1度では失敗し、2度目で成功します.なんでだろ?
Dダミーバイトを送信している理由は、SD cardが必要とする8発のclockを発生するためです.送受信のためではありません.
ここも厄介なところです.ダミーバイト送信の前にCSをdeassertしていますが、必ずしもそうする必要はなく、CS assertのままでも良いしいです.
ECMD1を送信しますが、1度で成功するかどうかは保証の限りではありません.
F成功するまで何度でもやります.わたしの経験では2度目で成功します.
Gここでもダミークロックを8発送ります.
void SD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc)
{
uint32_t i = 0x00;
uint8_t Frame[6];
Frame[0] = (uint8_t)(Cmd | 0x40); // Construct byte 1
Frame[1] = (uint8_t)(Arg >> 24); // Construct byte 2
Frame[2] = (uint8_t)(Arg >> 16); // Construct byte 3
Frame[3] = (uint8_t)(Arg >> 8); // Construct byte 4
Frame[4] = (uint8_t)(Arg); // Construct byte 5
Frame[5] =
(Crc);
// Construct CRC: byte 6
for (i = 0; i < 6; i++)
{
SD_WriteByte(Frame[i]); // Send the Cmd bytes
}
}
コマンドを送信するルーチンです.
コマンドフォーマットはこのように決まっています.
●全部で6バイト
●byte0がコマンド番号
●byte1〜4が32bitのコマンド引数で、たとえばwriteアドレスだったりする
●byte5がCRC.ただし、SPIモードではデフォルトでCRCは使わない設定なので何を送信しても無視される.
uint8_t SD_Detect(void)
{
__IO uint8_t status = SD_PRESENT;
// Check GPIO to detect SD
if (GPIO_ReadInputData(SD_DETECT_GPIO_PORT) & SD_DETECT_PIN)
{
status = SD_NOT_PRESENT;
}
return status;
}
SD cardが挿入されているかどうかをチェックするために、PG0のbit0のLOW/IHGHをチェックしています.
uint8_t SD_ReadBlock(uint8_t* pBuffer, uint32_t ReadAddr)
{
uint32_t i = 0;
uint8_t rvalue = SD_RESPONSE_FAILURE;
@SD_CS_LOW(); // SD CS assert
// Send CMD17 (SD_CMD_READ_SINGLE_BLOCK) to read one block
ASD_SendCmd(SD_CMD_READ_SINGLE_BLOCK, ReadAddr, 0xFF);
// Check if the SD acknowledged the read block command: R1 response (0x00: no errors) */
Bif (!SD_GetResponse(SD_RESPONSE_NO_ERROR))
{
// wait data token(0xFE) to signify the start of the data
C if (!SD_GetResponse(SD_START_DATA_SINGLE_BLOCK_READ))
{
// Read 512 BYTE data
D for (i = 0; i < SD_BLOCK_SIZE; i++)
{
*pBuffer = SD_ReadByte();
pBuffer++;
}
// Get CRC bytes (not really needed by us, but required by SD)
E SD_ReadByte();
SD_ReadByte();
rvalue = SD_RESPONSE_NO_ERROR;
}
}
SD_CS_HIGH(); // SD CS deassert
FSD_WriteByte(SD_DUMMY_BYTE); // dummy 8 clocks
return rvalue;
}
指定のblockを読んで512BYTEのバッファにデータを格納します.
@CSをassertします
ACMD17を送信します
Bコマンドレスポンスがノーエラー00になるまでwaitします.
Cデータ先頭であることを示すFEが来るまでwaitします.
D512BYTEを一気に受信します
EこのサンプルプログラムではCRCを使いませんけど、SD cardがCRCを2BYTE送信してくるので空読みします.
FSD cardへダミークロックを8発送信します.
uint8_t SD_WriteBlock(uint8_t* pBuffer, uint32_t WriteAddr)
{
uint32_t i = 0;
uint8_t rvalue = SD_RESPONSE_FAILURE;
@SD_CS_LOW(); // SD CS assert
// Send CMD24 (SD_CMD_WRITE_SINGLE_BLOCK) to write a block
ASD_SendCmd(SD_CMD_WRITE_SINGLE_BLOCK, WriteAddr, 0xFF);
// Check command response R1 (0x00: no errors)
Bif (!SD_GetResponse(SD_RESPONSE_NO_ERROR))
{
C SD_WriteByte(SD_DUMMY_BYTE); // Send a dummy byte
D SD_WriteByte(0xFE);// Send data token
for (i = 0; i < SD_BLOCK_SIZE; i++) // Send data
{
E SD_WriteByte(*pBuffer);
pBuffer++;
}
FSD_ReadByte(); // Send CRC
SD_ReadByte();
// Read data response
G if (SD_GetDataResponse() == SD_DATA_OK)
{
rvalue = SD_RESPONSE_NO_ERROR;
}
}
SD_CS_HIGH(); // SD CS deassert
HSD_WriteByte(SD_DUMMY_BYTE); // dummy 8 clocks
return rvalue;
}
指定のblockへ512BYTEのバッファのデータを書きます.
@CSをassertします
ACMD24を送信します
Bコマンドレスポンスがノーエラー00になるまでwaitします
Cダミークロックを8発送ります
Dデータ先頭であることを示すFEを送ります
E512BYTEのデータを一気に送信します
FこのサンプルプログラムではCRCを使いませんが、SD cardの仕様なのでCRCもどきを2BYTE送信します.
送信とは言っても、ReadByteをコールしているのはなんでかというと、SPIが全2重モードで動いているので、clockを8発生成するという意味ではWriteでもreadでも同じってことなのです.
Gデータレスポンスを読んでチェックします.SD_GetDataResponse()の中ではtimeoutとかごにょごにょと処理しています.
HSD cardへダミークロックを8発送信します.
uint8_t SD_GetDataResponse(void)
{
uint32_t i = 0;
uint8_t response = 0, rvalue = 0;
@while (i <= 64)
{
A response = SD_ReadByte(); // Read response
response &= 0x1F; // Mask unused bits
switch (response)
{
B case SD_DATA_OK:
{
rvalue = SD_DATA_OK;
break;
}
C case SD_DATA_CRC_ERROR: return SD_DATA_CRC_ERROR;
D case SD_DATA_WRITE_ERROR: return SD_DATA_WRITE_ERROR;
E default:
{
rvalue = SD_DATA_OTHER_ERROR;
break;
}
}
// Exit loop in case of data ok
if (rvalue == SD_DATA_OK) break;
i++;
}
Fwhile (SD_ReadByte() == 0); // Wait null data
return response;
}
データレスポンスをチェックするルーチンです.
@64バイト受信してもまともなレスポンスが返ってこなかったら、timeoutでexitするためのチェックです.
Aとりあえず1BYTE受信して、不要なbitを無視するために1FでANDします.
Bその結果が05であれば正常なので、breakします
CDEなんらかのエラーであればreturnします
F正常であったら、さらにデータが00になるまでwaitしつづけます
uint8_t SD_GetResponse(uint8_t @Response)
{
Auint32_t Count = 0xFFF;
// Check if response is got or a timeout is happen
Bwhile ((SD_ReadByte() != Response) && Count)
{
Count--;
}
if (Count == 0) // timeout occured
{
return SD_RESPONSE_FAILURE;
}
else // Good response
{
return SD_RESPONSE_NO_ERROR;
}
}
コマンドレスポンスをチェックするルーチンです.
@所望のコマンドレスポンスが返ってくるまで待ちます
ABただし、FFF回受信しても所望のコマンドレスポンスが来なかったらtimeoutします
void SD_LowLevel_DeInit(void)
{
SPI_Cmd(DISABLE); // SD_SPI disable
// SD_SPI Peripheral clock disable
CLK_PeripheralClockConfig(SD_SPI_CLK, DISABLE);
// Configure SD_SPI pins: SCK
GPIO_Init(SD_SPI_SCK_GPIO_PORT, SD_SPI_SCK_PIN, GPIO_MODE_IN_FL_NO_IT);
// Configure SD_SPI pins: MISO
GPIO_Init(SD_SPI_MISO_GPIO_PORT, SD_SPI_MISO_PIN, GPIO_MODE_IN_FL_NO_IT);
// Configure SD_SPI pins: MOSI
GPIO_Init(SD_SPI_MOSI_GPIO_PORT, SD_SPI_MOSI_PIN, GPIO_MODE_IN_FL_NO_IT);
// Configure SD_SPI_CS_PIN pin: SD Card CS pin
GPIO_Init(SD_CS_GPIO_PORT, SD_CS_PIN, GPIO_MODE_IN_FL_NO_IT);
// Configure SD_SPI_DETECT_PIN pin: SD Card detect pin
GPIO_Init(SD_DETECT_GPIO_PORT, SD_DETECT_PIN, GPIO_MODE_IN_FL_NO_IT);
}
SPIを解放します.
void SD_DeInit(void)
{
SD_LowLevel_DeInit();
}
SPIを解放します.