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を解放します.

inserted by FC2 system