Skip to content
KyuHyuk Blog

ATmega328P SDCard 구현 (3)

이전 글에서 CMD0, CMD8 그리고 CMD58을 사용하는 방법을 소개했습니다. 이제 SDCard의 초기화 과정을 거의 완료했습니다. 아래 다이어그램을 보면 다음 단계는 ACMD41라는 것을 알 수 있습니다.

SDCard SPI Mode Initalization Flow

ACMD41 (SD_SEND_OP_COND)

ACMD41(Send Operating Condition)는 SDCard의 초기화 과정을 시작합니다. 여기서 주의해야 하는 점은 ACMD41과 같이 A로 시작하는 Command는 사용하기 전에 CMD55(APP_CMD)를 보내서 다음에 사용하려는 Command이 ACMD라는 것을 SDCard에게 알려야 합니다.

다음은 CMD55의 형식입니다:

CMD55 Format

ACMD41의 형식은 아래와 같습니다:

ACMD41 Format

ACMD41의 대부분 비트는 Reserved이지만, 고용량 카드를 지원함을 나타내기 위해 Bit 30을 1로 설정하는 부분이 있습니다. CRC는 이러한 명령에 대해 무시되므로 아무거나 설정할 수 있습니다.

#define CMD55       55
#define CMD55_ARG   0x00000000
#define CMD55_CRC   0x00

#define ACMD41      41
#define ACMD41_ARG  0x40000000
#define ACMD41_CRC  0x00

ACMD41CMD41과 마찬가지로 Command Index를 41을 사용합니다. CMD55가 먼저 전송되지 않는 경우에는 CMD41가 전송됩니다.

CMD55ACMD41R1의 Response를 반환합니다.

uint8_t sdSendApp()
{
    // SDCard CS Assert
    spiTransfer(0xFF);
    CS_ENABLE();
    spiTransfer(0xFF);

    // CMD55 전송
    sdCommand(CMD55, CMD55_ARG, CMD55_CRC);

    // Response를 읽습니다
    uint8_t r1 = sdReadRes1();

    // SDCard CS Deassert
    spiTransfer(0xFF);
    CS_DISABLE();
    spiTransfer(0xFF);

    return r1;
}

uint8_t sdSendOpCond()
{
    // SDCard CS Assert
    spiTransfer(0xFF);
    CS_ENABLE();
    spiTransfer(0xFF);

    // ACMD41 전송
    sdCommand(ACMD41, ACMD41_ARG, ACMD41_CRC);

    // Response를 읽습니다
    uint8_t r1 = sdReadRes1();

    // SDCard CS Deassert
    spiTransfer(0xFF);
    CS_DISABLE();
    spiTransfer(0xFF);

    return r1;
}

위에서 설명했듯이 ACMD41은 초기화 과정을 시작합니다. Startup Sequence에서 SDCard가 R1 = 0x00인 ‘in_idle_state’로 응답할 때까지 ACMD41(항상 CMD55를 먼저 보내고 실행)을 계속 보냅니다.

R1 = 0x00을 받으면 CCS 값을 보기 위해 CMD58을 보내야 합니다. CCS를 통해 SDCard가 SDHC(고용량 SDCard)인지 SCXC(확장 용량 SDCard)인지 알 수 있습니다.

이전 글에서 작성한 main()을 아래와 같이 수정합니다.

/*
  CPU의 Frequency를 16MHz로 설정합니다
*/
#define F_CPU 16000000UL
#include <util/delay.h>

int main(void)
{
  uint8_t res[5];
  char c;

  // UART를 초기화 합니다
  const unsigned int baudRate = (F_CPU / 16 / 9600) - 1;
  uartInit(baudRate);

  // SPI를 초기화 합니다
  spiInit();

  // SDCard에 VCC가 충분히 공급될때까지 기다립니다
  _delay_ms(10);

  // Power Up Sequence를 시작합니다
  sdPowerUpSeq();

  while (1)
  {
    // 메뉴를 출력합니다
    uartPuts("MENU\r\n");
    uartPuts("------------------\r\n");
    uartPuts("0 - Send CMD0\r\n1 - Send CMD8\r\n2 - Send CMD58\r\n");
    uartPuts("3 - Send CMD55\r\n4 - Send ACMD41\r\n");
    uartPuts("------------------\r\n");

    // 사용자에게 명령(문자)를 입력받습니다
    c = uartGet();

    if (c == '0')
    {
      // CMD0을 보내고 응답을 읽고 출력합니다
      uartPuts("Sending CMD0...\r\n");
      res[0] = sdGoIdleState();
      uartPuts("Response:\r\n");
      sdPrintR1(res[0]);
    }
    else if (c == '1')
    {
      // CMD8을 보내고 응답을 읽고 출력합니다
      uartPuts("Sending CMD8...\r\n");
      sdSendIfCond(res);
      uartPuts("Response:\r\n");
      sdPrintR7(res);
    }
    else if (c == '2')
    {
      // CMD58을 보내고 응답을 읽고 출력합니다
      uartPuts("Sending CMD58...\r\n");
      sdReadOcr(res);
      uartPuts("Response:\r\n");
      sdPrintR3(res);
    }
    else if (c == '3')
    {
      // CMD55를 보내고 응답을 읽고 출력합니다
      uartPuts("Sending CMD55...\r\n");
      res[0] = sdSendApp();
      uartPuts("Response:\r\n");
      sdPrintR1(res[0]);
    }
    else if (c == '4')
    {
      // ACMD41을 보내고 응답을 읽고 출력합니다
      uartPuts("Sending ACMD41...\r\n");
      res[0] = sdSendOpCond();
      uartPuts("Response:\r\n");
      sdPrintR1(res[0]);
    }
    else
    {
      uartPuts("Unrecognized command\r\n");
    }
  }
}

CMD0을 보낸 다음 CMD8CMD58을 보냅니다. SDCard가 Ready 되었다고 출력될 때까지 CMD55ACMD41을 차례대로 보냅니다.
CLI 1

SDCard가 Ready가 되었다면, CCS를 얻기 위해 마지막에 CMD58을 보냅니다.
CLI 2

ACMD41을 처음 보낼 때는 IDLE 상태로 출력되었지만, 두 번째에는 Ready 상태임을 확인할 수 있습니다. 그 이후에 CMD58을 사용해서 OCR을 읽을 때 SDCard가 Power Up Process를 완료했으므로 CCS 비트가 유효하다고 볼 수 있습니다. 위의 경우에는 1로 설정되어 있는데, 이것은 SDXC 또는 SDHD를 사용하고 있음을 의미합니다.

SDCard 초기화

이제 SDCard를 초기화하는 sdInit()을 구현해 보겠습니다. SDCard 초기화를 구현하기 위해서 Power Up Diagram을 다시 봐봅시다:

Power-up Diagram

위의 다이어그램을 보면, ACMD41의 타임아웃은 1초입니다. 최소 1초 동안 SDCard 초기화를 계속 시도해야 합니다. 우리는 ACMD41을 다시 시도할 때 10ms의 Delay를 설정하고, 최대 100번을 시도하도록 구현할 것입니다.

#define SD_READY    0
#define SD_SUCCESS  0
#define SD_ERROR    1

uint8_t sdInit()
{
    uint8_t res[5], cmdAttempts = 0;

    sdPowerUpSeq();

    /*
      CMD0을 보내서 SDCard를 IDLE로 만듭니다
      최대 10번 Retry 합니다
    */
    while((res[0] = sdGoIdleState()) != 0x01)
    {
        cmdAttempts++;
        if(cmdAttempts > 10) return SD_ERROR;
    }

    // CMD8을 보냅니다
    sdSendIfCond(res);
    if(res[0] != 0x01)
    {
        return SD_ERROR;
    }

    // Echo Pattern을 확인합니다
    if(res[4] != 0xAA)
    {
        return SD_ERROR;
    }

    // ACMD41을 통해 SDCard 초기화를 시도합니다
    cmdAttempts = 0;
    do
    {
        if(cmdAttempts > 100) return SD_ERROR;

        // ACMD41을 보내기전에 CMD55를 보냅니다
        res[0] = sdSendApp();

        // Response에 Error가 없다면 ACMD41을 보냅니다
        if(res[0] < 2)
        {
            res[0] = sdSendOpCond();
        }

        // 10ms동안 기다립니다
        _delay_ms(10);

        cmdAttempts++;
    }
    while(res[0] != SD_READY);

    // OCR을 읽습니다
    sdReadOcr(res);

    // SDCard가 Ready 상태인지 확인합니다
    if(!(res[1] & 0x80)) return SD_ERROR;

    return SD_SUCCESS;
}

여기까지 모두 진행했다면 SPI 모드에서 SDCard를 초기화하는 것을 성공한 것입니다!


Share this post:

Previous Post
ATmega328P SDCard 구현 (2)
Next Post
ATmega328P SDCard 구현 (4)