SDカードドライバ2 ----block read/writeする----
SDカードドライバ1はSTMicroによるSTM8S200の向けのサンプルコードを、STM8S105に移植したものでした.
正直いってあまり発展性のある例ではありませんでした.
今回は、任意のblockにread/writeできるプログラムですので、発展性があると思います.
どんなことをさせるか?
terminal softの画面で操作します.
こんなmenuが出ます.
inc 2 : write incremental to block 2 (adrs=2*512)
fill 49 129 : write 49 to block 129 (adrs=129*512)
txt hello 996 : write hello to block 996 (adrs=996*512)
3 : dump block 3 (adrs=3*512)
3 18 : dump block from 3 to 18
incコマンドは、指定のblockに、00から始まるインクリメンタルデータを書き込みます.
fillコマンドは、指定のblock全部を、指定の数値で埋め尽くします.
txtコマンドは、指定のblockに、指定の文字列を書きます.
単に数字を打つと、そのblockをダンプします.
数字を2つ打つと、最初の数字のblock〜2番目の数字のblockまでを連続してダンプします.
以下はblock 0のダンプ例です.あらかじめ inc 0 でインクリメンタルデータで埋めておいたのでインクリメンタルデータが表示された状態です.
0
block 0x00000000
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
70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f | pqrstuvwxyz{|}~.
80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f | ................
90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f | ................
a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af | 。「」、・ヲァィゥェォャュョッ
b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf | ーアイウエオカキクケコサシスセソ
c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf | タチツテトナニヌネノハヒフヘホマ
d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df | ミムメモヤユヨラリルレロワン゙゚
e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef | ................
f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff | ................
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
70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f | pqrstuvwxyz{|}~.
80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f | ................
90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f | ................
a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af | 。「」、・ヲァィゥェォャュョッ
b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf | ーアイウエオカキクケコサシスセソ
c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf | タチツテトナニヌネノハヒフヘホマ
d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df | ミムメモヤユヨラリルレロワン゙゚
e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef | ................
f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff | ................
作る物の仕様
SD card
リセットSW
USBシリアル変換基板
PCのTerminaソフト画面にSD cardへのアクセス結果が出力される.
termina softからの操作で、SD cardへの書き込みおよびダンプができる.
SD cardのSPIモードで読み書きする.
CRCは使わない.
SD cardの属性情報はケアしない.
(手持ちの32MBと2GBのmicro SD cardで動作確認しました)
基板写真
右下の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
サブルーチンを説明する前に、大まかな流れを理解するのがよいと思うので、main.cから説明します.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
文字列操作の関数をいろいろ使うので、Cの標準ライブラリをインクルードしておきます.
#include "stm8s.h"
STMicroのライブラリを使うためのインクルードです.
#include "usrlib-uart.h"
UARTを使うための自作のライブラリのためのインクルードです.
#include "usrlib-sd.h"
SD cardの自作のライブラリのためのインクルードです.
uint8_t SDstrTX[SD_BLOCK_SIZE]; // SD card write buffer 512bytes
uint8_t SDstrRX[SD_BLOCK_SIZE]; // SD card read buffer 512bytes
char UARTstrTX[80]; // USRT send buffer
バッファ領域を確保しておきます.
1kBYTE以上のRAMをドーンと確保できるかどうかちょっと心配だったのですが、大丈夫のようです.
SDstrTXはSD cardへの書き込み用です.SDstrRXはSD cardからの読み込み用です.
UARTstrTXは terminal softへの表示用です.
void UART_Menu(void);
terminal softとやりとりするmenuルーチンのプロトタイプ宣言です.
// TIM3 interrupt routine to flash LED
void TIM3_UPD_OVF_BRK_IRQHandler(void) interrupt 15
{
GPIO_WriteReverse(GPIOD, GPIO_PIN_0); // LED flashing
TIM3_ClearFlag(TIM3_FLAG_UPDATE);
}
インジケータとしてLEDをチカチカさせるための割り込み処理ルーチンです.
void main(void)
メインルーチン始まりです.
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
CLK_HSIPrescalerConfig( CLK_PRESCALER_HSIDIV1 );
CLK_SYSCLKConfig( CLK_PRESCALER_CPUDIV1 );
メインルーチン始まりです.
最初にclockを設定します.内部発信器の16MHz(HSI)を使います.プリスケーラの分周比は”1”です.
clockについてはこちらのページを参照して思い出してください.
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、stop bit1、奇数パリティ に設定しています.
UARTについてはこちらのページ と こちらのページを参照して思い出してください.
GPIO_Init(GPIOD, 0b00000001, GPIO_MODE_OUT_PP_HIGH_FAST);
TIM3_DeInit();
TIM3_TimeBaseInit( TIM3_PRESCALER_1024, 7812 );
TIM3_ITConfig(TIM3_IT_UPDATE, ENABLE);
TIM3_Cmd(ENABLE);
インジケータとしてLEDをチカチカさせるためにTIM3を設定しています.
タイマについてはいろいろと書きましたが、タイマ割り込みについては こちらのページ と こちらのページ を参照すればよいと思います.
enableInterrupts();
割り込みオンします.
SerialPutString("\r\nstart SD card IF test\r\n");
terminal softにオープニングメッセージを出力します.
// SD card detect pin pullup
GPIO_Init(SD_DETECT_GPIO_PORT, SD_DETECT_PIN, GPIO_MODE_IN_PU_NO_IT);
SD cardコネクタには、cardが挿入されたかどうかのSWがついています.
そのSWをこの回路ではPG0に接続しています.
ただしプルアップ抵抗をつけてないので、そのままではSD card挿入を検出できませんので、ここでプルアップするように設定しています.
GPIO_MODE_IN_PU_NO_IT のPUがプルアップの意味です.
GPIOについてはこちらのページを参照してください.
// Wait for SD card insertion
SerialPutString("waiting for SD card\r\n");
while(SD_Detect()==SD_NOT_PRESENT){;}
SerialPutString("SD card inserted\r\n");
SD_Detect()がSD card挿入SWを調べるルーチンです.
SD cardが挿入されるまでwhile()の中に留まり続けます.
SD_Init(); // Initialization SPI and SD card
ここでSD cardのイニシャライズをします.SPIのイニシャライズもします.
while(1) { UART_Menu(); } // main loop
}
terminal softとのやりとりをするルーチンを無限ループでコールし続けます.
このルーチンの中で、ダンプしたりいろいろな処理をします.
void DumpBlock(void)
{
省略
}
SD cardの中身をダンプするルーチンです.中身の説明は省略します.
void UART_Menu(void)
{
terminal softのコマンド処理をするルーチンです.
uint32_t i;
if(UART_STR_EXIST==1) // there is a command line
{
UARTの受信ルーチンから、1ライン受信したらUART_STR_EXISTフラグが1になりますので、以下の処理をします.
int fieldnumber;
uint32_t address;
説明省略
fieldnumber = separate_line(command); // divide a line into words
コマンドラインをスペースで分離するルーチンをコールします.
たとえばコマンドラインが、"txt hello 4567" だったとしたら、
単語が3ヶなので3が返ってきます.
それぞれの単語は、
Field[0]="txt"
Field[1]="hello"
Field[2]="4567"
という変数に文字列として格納されて返ってきます.
if(strcmp(Field[0],"inc")==0 && fieldnumber==2)
{
address=(uint32_t)(atof(Field[1])*SD_BLOCK_SIZE);
for(i=0;i<SD_BLOCK_SIZE;i++) SDstrTX[i]=(uint8_t)i; // set Tx data
SD_WriteBlock(SDstrTX, address); // Write block of 512 bytes
}
incコマンドの処理をします.
"inc 10"というコマンドなら、block10にインクリメンタルデータを書きます.
Field[0]=="inc" かつ Field[1]==何か かつ Field[2]==NULL なら処理開始です.
まず アドレスを計算します.ここでいうアドレスとはblock番号x512です.
SD cardのデータは1アドレスあたり1BYTEです.
変数addressは32bitなので、この仕様だと4GBまでしかアドレッシングできませんね.
8GBのSD cardを挿したらどうなるんだろう?
幸い今デバッグに使っているSD cardは2GB品なのです.
まぁそれはさておいて先に進みます.
次のfor()文は、SD card書き込みバッファにインクリメンタルデータを書いています.
最後の SD_WriteBlock()で、所望のアドレスに、バッファの内容を書いています.
SD cardのアクセス単位は、block単位で、1block=512BYTEです.
else if(strcmp(Field[0],"fill")==0 && fieldnumber==3)
{
uint8_t c;
c=(uint8_t)atof(Field[1]);
address=(uint32_t)(atof(Field[2])*SD_BLOCK_SIZE);
for(i=0;i<SD_BLOCK_SIZE;i++) SDstrTX[i]=c; // set Tx data
SD_WriteBlock(SDstrTX, address); // Write block of 512 bytes
}
fillコマンドの処理をします.
"fill 56 3210"というコマンドなら、block3210を56で埋め尽くします.
やってることは、みればわかるでしょう.
else if(strcmp(Field[0],"txt")==0 && fieldnumber==3)
{
for(i=0;i<SD_BLOCK_SIZE;i++) SDstrTX[i]=0xFF; // set Tx data
strcpy(SDstrTX,Field[1]); // set Tx data
address=(uint32_t)(atof(Field[2])*SD_BLOCK_SIZE);
SD_WriteBlock(SDstrTX, address); // Write block of 512 bytes
}
txtコマンドの処理をします.
"txt hello 3210"というコマンドなら、block3210の先頭に"hello"を書きます.
helloの後の空白はFFを書きます.
やってることは、みればわかるでしょう.
else if(fieldnumber==1) // dump a block
{
address=(uint32_t)(atof(Field[0])*SD_BLOCK_SIZE);
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);
SD_ReadBlock(SDstrRX, address); // Read block of 512 bytes
DumpBlock();
}
ダンプコマンドの処理をします.
コマンドは、先頭で数字9988が入力されるとblock9988の中身512BYTEをダンプします.
アドレスの計算は、block番号x512 です.
たとえaddress=0であっても、先頭の文字が"0"じゃなかったら、怪しいので却下してNGに飛びます.
sprintfでblock番号の文字列UARTstrTXを作ります.
その文字列を、SerialPutString()でterminal softに送信します.
SD_ReadBlock()で、所望のアドレスの512BYTEを読みます.
読んだ結果は、SDstrRXに格納されます.
それを DumpBlock()でterminal softへ表示します.
else if(fieldnumber==2) // dump blocks
{
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);
sprintf(UARTstrTX,"\r\nblock
0x%04x%04x\r\n",(int)((uint32_t)i>>16),(int)((uint32_t)i&0xFFFF));
SerialPutString(UARTstrTX);
SD_ReadBlock(SDstrRX, address); // Read block of 512 bytes
DumpBlock();
}
}
ダンプコマンドの処理をします.
こちらは、block N〜block Mまでを連続的にダンプします.
中身は読めばわかるでしょう.
else
{
NG:
SerialPutString("\r\n\nCOMMAND NG!\r\n");
}
コマンド文字列が変だったらCOMMND NG!と表示します.
MENU:
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("txt hello 996 : write hello to block 996 (adrs=996*512)\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だと.
/*----- 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 "usrlib-sd.h"
ヘッダファイルの定義を読みます.
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を解放します.