Работа с SD картой памяти по SDIO STM32 F4 Discovery

Как Вы уже наверное знаете, SDIO — это интерфейс для передачи данных в/из карт памяти.
В этой статье речь пойдет о работе с microSD флеш картой памяти по SDIO, который имеется в контроллере stm32f407vgt6 платы stm32f4 discovery.

Работать с картами флеш памяти можно при помощи SPI интерфейса, чем большинство радиолюбителей и занимались последние пару лет (в следствии чего, если вы поищите как подключить карту памяти к контроллеру, сразу получите результат в виде схемы подключения SPI). Но техника не стоит на месте и у нас в руках не дорогие 32 битные контроллеры, которые имеют на борту модуль специально предназначенный для работы с картами памяти — SDIO, который существенно упрощает и ускоряет работу.

Сначала о контактах.

One-Bit SD Bus Mode — Однобитный режим SDIO
MMC
Pin
SD
Pin
miniSD
Pin
microSD
Pin
Имя I/O Logic Описание
1
1
1
2
NC
.
.
Не используется
2
2
2
3
CMD
I/O
PP,
OD
Command, Response
3
3
3
VSS
S
S
Ground
4
4
4
4
VDD
S
S
Power
5
5
5
5
CLK
I
PP
Serial Clock
6
6
6
6
VSS
S
S
Ground
7
7
7
7
DAT0
I/O
PP
SD Serial Data 0
8
8
8
NC
nIRQ
.
O
.
OD
Не используется (memory cards)
Interrupt (SDIO cards) (Negative Logic)
9
9
1
NC
.
.
Не используется
10
NC
.
.
Зарезервировано
11
NC
.
.
Зарезервировано

С картой можно работать по SDIO в режиме однобитной шины данных и 4ех битной (вроде бы уже есть и 8). Далее речь пойдет об одно битной. Никакой принципиальной разницы, кроме количества используемых проводников, это не играет.
Как видно контактов на флешке довольно много. Из них нам кроме питания и земли понадобятся только три.

  • CLK — тактирование карты.
  • CMD — по этой линии передаются команды.
  • DAT0 — линия данных (в случае 4-ех битной шины их будет 4).

Соединяем с контроллером:

  • PC8 — SDIO_D0 (DAT0)
  • PC12 — SDIO_CK (CLK)
  • PD2 — SDIO_CMD (CMD)

Я не использовал никаких подтяжек и все работало нормально. Правда иногда случаются сбои и карта не инициализируется.

Теперь перейдем к принципу работы.
Все черные дела за нас делает модуль SDIO. Мы просто должны знать команды, которые нужно посылать и читать данные.
Передача данных с/на карту неотъемлемо связана с вычислением контрольных сумм. Это происходит при каждой передаче.

Рассмотрим функцию инициализации модуля SDIO и GPIO:

void SD_LowLevel_Init(void) {
  uint32_t tempreg;
  
  GPIO_InitTypeDef GPIO_InitStructure;
  
  // GPIOC and GPIOD Periph clock enable
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD, ENABLE);

  //Initialize the pins
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_SDIO);

  // Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ; // | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; // раскомментируйте для 4ех битной шины
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  // Configure PD.02 CMD line
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &GPIO_InitStructure);

  // Configure PC.12 pin: CLK pin
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
  
  //Enable the SDIO APB2 Clock
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE);

  // Enable the DMA2 Clock
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  
  //Инициализируем SDIO (с начальным тактированием  HW_Flow Disabled, Rising Clock Edge, Disable CLK ByPass, Bus Width=0, Power save Disable
  SDIO->CLKCR=tempreg;

  // Включаем SDIO
  SDIO->>POWER = 0x03;

Думаю тут все более менее ясно.
Вот функция передачи команды:

static uint32_t SD_Command(uint32_t cmd, uint32_t resp, uint32_t arg) {
  //Response must be:
  //0,2:No response (expect cmdsent) ->NORESP
  //1:Short Response (expect cmdrend and ccrcfail) ->SHRESP
  //3:Long Response (expect cmdrend and ccrcfail) ->LNRESP
  
  //Clear the Command Flags
  SDIO->ICR=(SDIO_STA_CCRCFAIL | SDIO_STA_CTIMEOUT | SDIO_STA_CMDREND | SDIO_STA_CMDSENT);
  
  SDIO->ARG=arg; //First adjust the argument (because I will immediately enable CPSM next)
  SDIO->CMD=(uint32_t)(cmd & SDIO_CMD_CMDINDEX) | (resp & SDIO_CMD_WAITRESP) | (0x0400); //The last argument is to enable CSPM

  //Block till we get a response
  if (resp==NORESP) {
    //We should wait for CMDSENT
    while (!(SDIO->STA & (SDIO_STA_CTIMEOUT | SDIO_STA_CMDSENT))) {};
  }
  else {//SHRESP or LNRESP or R3RESP
    //We should wait for CMDREND or CCRCFAIL
    while (!(SDIO->STA & (SDIO_STA_CTIMEOUT | SDIO_STA_CMDREND | SDIO_FLAG_CCRCFAIL))) {};
  }
  
  //Check to see if the response is valid
  //We consider all R3 responses without a timeout as a valid response
  //It seems CMDSENT and CMDREND are mutually exlusive. (though I am not sure. Check this later)
  if (SDIO->STA & SDIO_STA_CTIMEOUT) {
    SD_Panic(cmd, "SDIO: Command Timeout Error\n");
  } else if ((SDIO->STA & SDIO_FLAG_CCRCFAIL) && (resp!=R3RESP)) {
    SD_Panic(cmd, "SDIO: Command CRC Error\n");
  }

  return SDIO->STA;
}

Тут уже все обстоит несколько сложнее.
На вход функции подаеться необходимая комана (cmd), идентификатор ответа (указывает ждать ли ответ после этой команды) и аргумент (arg).
Дальнейшая часть кода требует длительного «вкуривания» спецификации на SD карты.

Приведу также код функции инициализации карты (да, да.. её нужно еще и инициализировать).

void SD_Init(void){
  //uint32_t data;
  uint32_t response;
  uint32_t TimeOut=0xFFFF;
  uint32_t tempreg;
  
  //CMD0: GO_IDLE_STATE (No Response)
  SD_Command(CMD0, NORESP, 0);
      
  //CMD8: SEND_IF_COND //Response to CMD8 is R7. But I will ignore that response
  SD_Command(CMD8, SHRESP, 0x000001AA); //Non v2.0 compliant sd's will cause panic here due to the timeout
  SD_Response(&response, RESP_R7); //AA is the check pattern. If response does not match with it, execution will be blocked in panic
  
  
  while (1) {
    ////Send ACMD41
    //CMD55
    SD_Command(CMD55, SHRESP, 0); //Note that argument should be RCA. But at this point RCA of SD is 0. (It will be changed after cmd3)
    SD_Response(&response, RESP_R1);
    
    //ACMD41 (Response is R3 which does not contain any CRC)
    //Second argument in the argument indicates that host supports SDHC. We will check acmd41 response if the SD is SC or HC
    SD_Command(ACMD41, R3RESP, (uint32_t) 0x80100000 | (uint32_t) 0x40000000);
    SD_Response(&response, RESP_R3);
    
    //Check the ready status in the response (R3)
    if ((response >> 31) == 1) { //When card is busy this bit will be 0
      //Card is now initialized. Check to see if SD is SC or HC
      SDType=(response & 0x40000000) >> 30; //1=HC, 0=SC
      break;
    } else {
      TimeOut--;
      if (!TimeOut) {
        SD_Panic(ACMD41, "SDIO:ACMD41 Timeout\n");
      }
    }
   }
  
  //Now we are in the Ready State. Ask for CID using CMD2
  //Response is R2. RESP1234 are filled with CID. I will ignore them
  SD_Command(CMD2, LNRESP, 0);
  
  //Now the card is in the identification mode. Request for the RCA number with cmd3
  SD_Command(CMD3, SHRESP, 0);
  SD_Response(&response, RESP_R6);
  //Read the RCA
  RCA=response>>16;
  
  //Now the card is in stand-by mode. From this point on I can change frequency as I wish (max24MHz)
  
  
  //Use cmd9 to read the card specific information
  //Response is R2 with CSI. I will ignore the response
  SD_Command(CMD9, LNRESP, (RCA << 16));
  
  
  //Put the Card in the tranfer mode using cmd7. (I will change the clock spped later together with bus width)
  //Bus width can only be changed in transfer mode
  SD_Command(CMD7, SHRESP, (RCA << 16));
  SD_Response(&response, RESP_R1);
  
  //Change the bus-width with cmd6
  //CMD55
  SD_Command(CMD55, SHRESP, (RCA << 16)); //Note the real RCA in the argument
  SD_Response(&response, RESP_R1);
  //ACMD6
  SD_Command(ACMD6, SHRESP, 0x02);
  SD_Response(&response, RESP_R1);
  
  //Configure SDIO->CLKCr for wide-bus width and new clock
  tempreg=0; //Reset value

  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  //tempreg|=(0x01)<<11; //4 bit Bus Width
  // ОСТАВЛЯЕМ ПО УМОЛЧАНИЮ 1бит шины
  // биты 11-12 = 00
  
  tempreg|=SDIO_CLKCR_CLKEN; //Clock is enabled
  //Keep the rest at 0=> HW_Flow:Disabled, Rising Edge, Disable bypass, Power save Disable, Clock Division=0
  //As the clock divider=0 => New clock=48/(Div+2)=48/2=24
  SDIO->CLKCR=tempreg;
  
  //Now we can start issuing read/write commands
}

В ходе данной функции карта сбрасывается и настраивается для передачи данных.
После этого я выводил данные с карты в USART stm32.

Вот такой вывод:

0 block:
        
0000: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0010: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0020: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0030: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0040: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0050: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0060: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0070: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0080: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0090: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
00a0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
00b0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
00c0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
00d0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
00e0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
00f0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0100: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0110: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0120: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0130: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0140: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0150: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0160: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0170: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0180: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
0190: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
01a0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
01b0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 80 ff         
01c0: ff ff 0c ff ff ff 00 08   00 00 00 24 76 00 00 00         
01d0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
01e0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00         
01f0: 00 00 00 00 00 00 00 00   00 00 00 00 00 00 55 aa 

В нулевом секторе всегда должны быть не нулевые данные.
Прикрепляю проект для IAR.
Не пугайтесь названия. Просто все это «выросло» из проекта для USART.

PS. Основа работы с картой в файлах sdcard.c и sdcard.h

 

Похожий код:

Фото аватара
Алексей Петров

Программист, разработчик с 5 летним опытом работы. Учусь на разработчика игр на Unity и разработчика VR&AR реальности (виртуальной реальности). Основные языки программирования: C#, C++.

Оцените автора
Бла, бла код
Добавить комментарий

  1. Гость

    Тема вроде по f4xx, а проект по ссылке для f10x

    Ответить