veiner.eu
ÚVODODDÍLY A SOUBOROVÝ SYSTÉM FAT32ZAPOJENÍ, KOMUNIKACE A PROTOKOL
INICIALIZACE, ČTENÍ, ZÁPIS
CO NAJDEME NA KARTĚVYUŽITÍZDROJOVÉ KÓDY

SD karta - inicializace, čtení, zápis

8.6.2018

Řekněme, že vstup karty CS mám připojený k pinu RC1 mikrokontroleru. V programu si ho označím jako SD_CS. Na počátku je karta neaktivní, SD_CS tedy nastavím na jedna. Dále si připravím proměnnou pro text odesílaný po seriové lince.

#define SD_CS LATCbits.LATC1

SD_CS=1;

BYTE s[50];

Inicializace

Postup při inicializaci sestává z několika kroků. Nejprve se kartě odešle několik dummy bytů (uvádí se minimálně asi 70 cyklů neboli bitů - v ukázce odesílám 20 bytů), kdy je SD_CS stále na jedničce. Tímto se karta přepne do režimu SPI a bude reagovat na zasílané příkazy. Kartu aktivujeme nastavením SD_CS na nulu.

Následuje série příkazů, jejichž cílem je zjistit typ karty. V mém případě je to SDHC a kód níže by měl v proměnné type vrátit hodnotu 3.

BYTE type;

for (BYTE i=0;i<20;i++)
{
  spiWriteSD(0xFF);
}

SD_CS=0;

sendSerialString("\nsending CMD0\n");
while ((status=sendCommandSD(CMD0,0))!=0x01)
  ;
sprintf(s,"status=%d\n",status);
sendSerialString(s);
sendSerialString("\nsending CMD8\n");
if ((status=sendCommandSD(CMD8,0x1AA)) & 0x04)
{
  type=1;
}
else
{
  unsigned long status2=0;
  for (BYTE i=0;i<4;i++)
  {
    status2=((status2<<8)+spiReadSD());
  }
  sprintf(s,"status2=0x%lX\n",status2);
  sendSerialString(s);
  type=2;
}
sprintf(s,"status=%d\n",status);
sendSerialString(s);
sprintf(s,"type=%d\n",type);
sendSerialString(s);
unsigned long arg=((type==2) ? 0x40000000 : 0);

sendSerialString("\nsending ACMD41\n");  
int count=0;
while ((status=sendACommandSD(ACMD41,arg))!=0x00)
  ;
sprintf(s,"status=%d\n",status);
sendSerialString(s);

if (type==2)
{
  sendSerialString("\nsending CMD58\n");
  sendCommandSD(CMD58,0);
  if ((spiReadSD() & 0xC0)==0xC0)
    type=3;
  for (unsigned char i=0;i<3;i++)
    spiReadSD();
  sprintf(s,"type=%d\n",type);
  sendSerialString(s);
}

Kartě ještě můžu prostřednictvím příkazu číslo 16 sdělit, že chci mít velikost bloku 512 bytů. Taktéž můžu zvýšit rychlost SPI komunikace.

sendSerialString("\nsending CMD16\n");
status=sendCommandSD(CMD16,0x200);
sprintf(s,"status=%d\n",status);
sendSerialString(s);

SSP2CON1bits.SSPM=0b0000;

Výstup programu vypadá po inicializační části následovně:

Čtení

Víme, že karta je logicky rozčleněna do bloků (sektorů) po 512 bytech. Každý z těchto bloků jednoduše určíme jeho pořadovým číslem počínaje nulou. Při čtení a zápisu je možné pracovat buď s jediným blokem nebo číst/zapisovat do více následných bloků jedním příkazem. Budeme se zabývat pouze první variantou.

Čtení bloku se zahájí odesláním příkazu číslo 17 s argumentem obsahujícím adresu požadovaného bloku. Následně karta zašle byte obsahující hodnotu 0xFE, čímž signalizuje, že další odeslaná data již bude 512 bytů čteného bloku. Načteme tedy těchto 512 bytů a následně ještě dva byty CRC.

BYTE data[512];

void readBlockSD(unsigned long blocknum,BYTE *data)
{
  if (sendCommandSD(CMD17,blocknum)!=0x00)
  {
    while (spiReadSD()!=0x00)
      ;
  }
  
  while (spiReadSD()!=0XFE)
    ;
  
  for (int i=0;i<512;i++)
    data[i]=spiReadSD();
  
  spiReadSD();
  spiReadSD();
}

Zápis

Zápis dat probíhá podobně. Odešleme příkaz číslo 24 společně s číslem bloku, do kterého chceme zapisovat. Dále pošleme byte 0xFE a hned potom 512 bytů dat. Nakonec 2 byty CRC, které nemusíme počítat, stačí poslat hodnotu 0xFFFF. Karta hned poté odešle jeden byte s informací o tom, jak bude naloženo s přijatými daty. Jsou tři možnosti, data budou zpracována, data nebudou zpracována z důvodu chyby CRC nebo chyby zápisu. Informace o bezproblémovém přijetí dat má podobu bytu 0bxxx00101, kde na místě x může být cokoli. Jakákoli jiná hodnota znamená chybu. Dále odešleme příkaz číslo 13 s argumentem 0xFF a čekáme na to, než karta data zapíše neboli než vrátí hodnotu 0xFF.

void writeBlockSD(unsigned long blocknum,BYTE *data)
{
  if (sendCommandSD(CMD24,blocknum)!=0x00)
  {
    while (spiReadSD()!=0x00)
      ;
  }
  
  spiWriteSD(0xFE);
  
  for (int i=0;i<512;i++)
    spiWriteSD(data[i]);
  
  spiWriteSD(0xFF);
  spiWriteSD(0xFF);
  
  status=spiReadSD();
  sprintf(s,"status=%X\n",status);
  sendSerialString(s);
  
  sendSerialString("\nsending CMD13\n");
  status=sendCommandSD(CMD13,0xFF);
  status=spiReadSD();
  sprintf(s,"status=%X\n",status);
  sendSerialString(s);
  while ((status=spiReadSD())!=0xFF)
    ;
  sprintf(s,"status=%X\n",status);
  sendSerialString(s);
}

Výstup programu po zápisu dat vypadá takto:

V tomto případě má stavový byte hodnotu 0xE5 neboli 0b11100101, což znamená, že data byla přijata k zápisu.