STM Урок 88. SD. SPI. FATFS. Часть 2



 

Урок 88

 

Часть 2

 

SD. SPI. FATFS

 

В предыдщей части нашего урока мы подключили SD-карту, создали ряд служебных функций для её работы и начали писать функцию инициализации.

 

Вернём частоту на заявленную в Cube. Также опустим ножку CS и попробуем передать по шине SPI пару каких нибудь величин

 

  SPI_Release();

hspi2.Init.BaudRatePrescaler = temp;

HAL_SPI_Init(&hspi2);

SS_SD_SELECT();

SPI_SendByte(0x35);

SPI_SendByte(0x53);

 

Соберём код, прошьём контроллер и посмотрим результат в окне анализатора (нажмите на картинку для увеличения изображения)

 

image27_0500

 

Мы видим, что импульсы на ножке синхронизации внушительно увеличили свою частоту. растянем график и посмотрим

 

image28

 

Мы видим, что частота стала 4,8 мегагерц, также близкая к заявленной (5 мегагерц). На таких больших частотах это нормальная точность для такого класса аналализатора. Теперь давайте включим анализ в режиме SPI в программе анализирования

 

image29

 

Настроим каналы, остальное не трогаем, так как по идее у нас SPI режим 0. Этот тот единственный режим, в котором работает карта SD

 

image31

 

Настроим отображение результатов в шестнадцатеричном виде

 

image32

 

Только при изменении частот SPI анализатор путается в измерениях, поэтому закомментируем в коде переключение частоты. Карта и без этого сработает, то есть и на большой частоте.

 

sdinfo.type = 0;

//temp = hspi2.Init.BaudRatePrescaler;

//hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128; //156.25 kbbs

//HAL_SPI_Init(&hspi2);

SS_SD_DESELECT();

for(i=0;i<10;i++) //80 импульсов (не менее 74) Даташит стр 91

SPI_Release();

//hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;

//HAL_SPI_Init(&hspi2);

SS_SD_SELECT();

 

Опять соберём код и прошьём контроллер. В результате теперь анализатор будет всё корректно отображать

 

image33 

 

Также посмотрим и график, на котором теперь тоже всё отображается (нажмите на картинку для увеличения изображения)

 

image34_0500

 

Удалим пробный код

 

SPI_SendByte(0x35);

SPI_SendByte(0x53);

 

Теперь попробуем послать команду карте.

Для этого нам нужна будет специальная функция, которую мы добавим выше функций включения и инициализации, чтобы не делать прототип

 

//-----------------------------------------------

static uint8_t SD_cmd (uint8_t cmd, uint32_t arg)

{

  uint8_t n, res;

  return res;

}

//-----------------------------------------------

 

Кратко опишу процесс передачи команды в карту.

Откроем пример в даташите

 

image35

 

У каждой команды есть индекс. У данной команды индекс 8, так как она именуется CMD8. Также существуют различия в типах команд. Но об этом потом, нас пока интересует именно такой тип, причём именно эту команду нам потом также придется передавать.

Мы видим что передача команды состоит из 48 бит, то есть из 6 байтов. Первая строка показывает позицию бита в команде, вторая — величину параметра в битах, третья — значение, а четвёртая — разъяснение параметра.

Стартовый бит — всегда 0.

Бит передачи — 1.

индекс команды — в случае данной команды равен 8.

затем идут зарезервированные биты — целых 20 штук, все равные нулю.

Затем идут параметры, 7 бит контрольной суммы и стоповый бит — всегда 1.

Также команды существуют обычные (CMD) и расширенные (ACMD), которые состоят как бы сразу из двух команд, первая из которых всегда CMD55.

После передачи команды как правило следует результат.

Причём команды, которые мы передаём в карту, могут не только с помощью них чем-то управлять, кое-что могут и возвращать.

Причём возвращаемый результат, мало того, бывает разных типов (я выше говорил про типы команд, это ещё не то) — начинается от типа R1 и т.д.

Вот, например, тип R1

 

image36

 

 

А вот R2

 

image37

 

Сначала добавим макросы команд вверху файла

 

extern UART_HandleTypeDef huart1;

//--------------------------------------------------

// Definitions for MMC/SDC command

#define CMD0 (0x40+0) // GO_IDLE_STATE

#define CMD1 (0x40+1) // SEND_OP_COND (MMC)

#define ACMD41 (0xC0+41) // SEND_OP_COND (SDC)

#define CMD8 (0x40+8) // SEND_IF_COND

#define CMD9 (0x40+9) // SEND_CSD

#define CMD16 (0x40+16) // SET_BLOCKLEN

#define CMD17 (0x40+17) // READ_SINGLE_BLOCK

#define CMD24 (0x40+24) // WRITE_BLOCK

#define CMD55 (0x40+55) // APP_CMD

#define CMD58 (0x40+58) // READ_OCR

//--------------------------------------------------

 

Продолжим нашу функцию передачи команды, вторым аргументом которой являются наши аргументы, объединённые в одну 32-битную величину, и напишем код, который будет определять, какого типа у нас команда. Команду ACMD отличает от CMD включенный седьмой бит

 

uint8_t n, res;

// ACMD<n> is the command sequense of CMD55-CMD<n>

if (cmd & 0x80)

{

  cmd &= 0x7F;

  res = SD_cmd(CMD55, 0);

  if (res > 1) return res;

}

 

Далее производим выбор карты вот таким вот интересным образом

 

  if (res > 1) return res;

}

// Select the card

SS_SD_DESELECT();

SPI_ReceiveByte();

SS_SD_SELECT();

SPI_ReceiveByte();

 

Сначала мы поднимаем ножку CS, затем прогоняем по шине байт 0xFF, затем опускаем ножку и прогоняем ещё один такой же байт.

Далее уже передаём непосредственно команду

 

SPI_ReceiveByte();

// Send a command packet

SPI_SendByte(cmd); // Start + Command index

SPI_SendByte((uint8_t)(arg >> 24)); // Argument[31..24]

SPI_SendByte((uint8_t)(arg >> 16)); // Argument[23..16]

SPI_SendByte((uint8_t)(arg >> 8)); // Argument[15..8]

SPI_SendByte((uint8_t)arg); // Argument[7..0]

 

То есть здесь мы последовательно передаём в шину аргументы команды, сначала передав её индекс.

Далее занесём контрольную сумму вместе со стоповым битом в переменную в зависимости от команды. То есть для этих двух команд сумма уже просчитана, для других нулевая, понятное дело со стоповым битом (0x01)

 

SPI_SendByte((uint8_t)arg); // Argument[7..0]

n = 0x01; // Dummy CRC + Stop

if (cmd == CMD0) {n = 0x95;} // Valid CRC for CMD0(0)

if (cmd == CMD8) {n = 0x87;} // Valid CRC for CMD8(0x1AA)

SPI_SendByte(n);

 

Пока нас интересует только первый тип. Если будут нужны другие типы, то первый байт нам все равно ждать, а с остальными разберёмся в коде после вызова команды

Добавим условный цикл в нашу функцию

 

  SPI_SendByte(n);

  // Receive a command response

  n = 10; // Wait for a valid response in timeout of 10 attempts

  do {

    res = SPI_ReceiveByte();

  } while ((res & 0x80) && --n);

  return res;

}

 

То есть здесь мы ждём отклика от карты.

Теперь вернёмся в функцию инициализации.

Давайте в технической документации посмотрим процесс инициализации, пока упрощённый

 

image38

 

Таймаут включения мы добавили, 74 цикла мы уже проделали, теперь надо передать команду CMD0, которая делает программную перезагрузку, вводя карту в режим IDLE, в котором мы можем считать определённые параметры, прежде чем решить, как вести себя дальше с картой. В качестве аргументов данная команда требует все нули

 

  SS_SD_SELECT();

  if (SD_cmd(CMD0, 0) == 1) // Enter Idle state

  {

  }

  else

  {

    return 1;

  }

  return 0;

}

 

Соберём код, прошьём контроллер и проверим анализатором, что мы получили желаемый ответ от карты

 

image39

 

Причём дождались мы ответа довольно-таки быстро, пропустив только один цикл.

 

 

Ну что ж, отлично! Карта откликается, будем инициализировать её дальше, исследуя её тип.

Подробная инициализация с описанием того, для чего и какие команды мы вызываем, проиллюстрирована также в технической документации (нажмите на картинку для увеличения изображения)

 

index42_0500

 

Добавим глобальный строчный массив в файле sd.c

 

sd_info_ptr sdinfo;

char str1[60]={0};

 

Вернёмся в нашу функцию инициализации и отправим команду CMD8 в нашу карту с целью узнать версию нашей карты. Команду мы вызываем в теле положительного возврата из функции вызова команды CMD0. В качестве аргументов выступает регистр OCR, данные в который мы и заносим. Мы заносим 0,0,1,0xAA, такие требования именно для версии 2 карты SD.

 

Вот описание команды CMD8

 

index49

 

То есть байт со значением 1 — это значение регистра VHS

 

index50

А ещё есть сноска следующего содержимого на этой же странице

 

index51

 

 

А '10101010' — это ничто иное, как 0xAA. Вот отсюда и берутся выходные данные для команды.

Перед вызовом команды прогоним байт через шину

 

if (SD_cmd(CMD0, 0) == 1) // Enter Idle state

{

  SPI_Release();

  if (SD_cmd(CMD8, 0x1AA) == 1) // SDv2

  {

  }

  else //SDv1 or MMCv3

  {

  }

}

 

Следует отметить что команда CMD8 возвращает уже результат формата R7

 

index43

 

Приведу также описание формата R7

 

index44

 

Как мы видим, возвращённый результат состоит из 5 байтов, самый старший из них (он же тот, который прийдёт первым) является результатом формата R1. Мы его получим сразу в возврате из функции. А остальные 4 байта мы получим отдельно. Поэтому подготовим для них локальный массив в нашей функции инициализации

 

uint8_t i;

uint8_t ocr[4];

 

Получим данные байты и отобразим их в терминальной программе, если конечно в возвращённом картой результате будут требуемые нам байты, то есть карта будет именно версии 2.

Есть ещё вот такое описания результата формата R7

 

index52

 

То есть если всё нормально, то 8 байтов [15:8] будут такими же, как они пришли в параметре команды, поэтому они и числятся в описании как «echo-back of check pattern».

А байты [19:6] — это поддерживаемые напряжения

 

index53

 

Таблица точь в точ такая же, как и во входе в команду, поэтому и возвращается при наличии карты именно такого типа, какой мы хотим то же самое, что и послали

 

if (SD_cmd(CMD8, 0x1AA) == 1) // SDv2

{

  for (i = 0; i < 4; i++) ocr[i] = SPI_ReceiveByte();

  sprintf(str1,"OCR: 0x%02X 0x%02X 0x%02X 0x%02Xrn",ocr[0],ocr[1],ocr[2],ocr[3]);

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

}

 

Мы назвали переменную ocr, и используем также OCR в строке, хотя это никакой не OCR. С регистром OCR мы познакомимся когда будем рассматривать результат другого формата нескользо позже.

Соберём код, прошьём контроллер и посмотрим вывод результата в терминальной программе

 

index45

 

Также весь результат формата R7 мы можем увидеть и в программе логического анализатора

 

index47

 

В принципе,теперь мы можем логический анализатор отключить и присоединить модуль карты MicroSD обычными проводами мама-мама

 

index56

 

Также раскомментируем тот код, который мы комментировали для корректного анализа шины

 

temp = hspi2.Init.BaudRatePrescaler;

hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128; //156.25 kbbs

HAL_SPI_Init(&hspi2);

SS_SD_DESELECT();

for(i=0;i<10;i++) //80 импульсов (не менее 74) Даташит стр 91

SPI_Release();

hspi2.Init.BaudRatePrescaler = temp;

HAL_SPI_Init(&hspi2);

 

Добавим ещё одну переменную для команды в нашу функцию

 

uint8_t sd_ini(void)

{

  uint8_t i, cmd;

 

Теперь рассмотрим противный случай. Тот случай, если карта оказалась не того типа, то есть вообще команда не вернула даже результат типа R1 единицу.

Это значит что у насл либо тип 1 либо вообще MMC. Проверим это

 

if (SD_cmd(CMD8, 0x1AA) == 1) // SDv2

{

  for (i = 0; i < 4; i++) ocr[i] = SPI_ReceiveByte();

  sprintf(str1,"OCR: 0x%02X 0x%02X 0x%02X 0x%02Xrn",ocr[0],ocr[1],ocr[2],ocr[3]);

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

}

else //SDv1 or MMCv3

{

  if (SD_cmd(ACMD41, 0) <= 1)

  {

    sdinfo.type = CT_SD1; cmd = ACMD41; // SDv1

  }

  else

  {

    sdinfo.type = CT_MMC; cmd = CMD1; // MMCv3

  }

  for (tmr = 25000; tmr && SD_cmd(cmd, 0); tmr--) ; // Wait for leaving idle state

  if (!tmr || SD_cmd(CMD16, 512) != 0) // Set R/W block length to 512

  sdinfo.type = 0;

}

 

Тут мы уже используем команду ACMD41. Вот здесь есть её краткое описание

 

index54

 

В описании мы видим, что команда возвращает результат типа R3

 

index55

 

Вот тут-то как раз и используется регистр OCR. Вот его содержимое

 

index48

 

Нам не нужны все биты, мы будем иметь дело только с некоторыми. А в этом теле цикла нам вообще важен только результат типа R1, по которому мы определим тип карты. Дальше будет цикл и условие, которое работает на тип карты MMC. Там мы уже используем команду CMD16. Честно говоря, этот участок кода был взят из примера работы с библиотекой FatFS и я даже не вдавался в смысл данной команды в силу того, что вряд ли сейчас карты такого типа вообще используются. При желании вы можете данную команду изучить самостоятельно.

Теперь продолжим тело условия при положительном результате. Мы отобразили данные в терминальной программе, теперь узнаем, что нам вернулись именно 0 и 0xAA в результате вызова команды CMD8

 

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

// Get trailing return value of R7 resp

if (ocr[2] == 0x01 && ocr[3] == 0xAA) // The card can work at vdd range of 2.7-3.6V

{

}

 

Дальше в теле этого условия мы вызовем команду ACMD41 с включенным битом HCS

 

if (ocr[2] == 0x01 && ocr[3] == 0xAA) // The card can work at vdd range of 2.7-3.6V

{

  for (tmr = 12000; tmr && SD_cmd(ACMD41, 1UL << 30); tmr--)

    ; // Wait for leaving idle state (ACMD41 with HCS bit)

}

 

Затем мы вызываем команду CMD58 для чтения регистра OCR, в котором мы узнаём состояние бита CCS, и если он установлен, это позволяет нам считать, что карта у нас типа SDHC, то есть повышенной вместимости, а если состояние бита ноль, то SDSC (стандартной вместимости). Входные аргументы — нули

 

  ; // Wait for leaving idle state (ACMD41 with HCS bit)

if (tmr && SD_cmd(CMD58, 0) == 0) { // Check CCS bit in the OCR

  for (i = 0; i < 4; i++) ocr[i] = SPI_ReceiveByte();

 

Отобразим в терминальной программе наш OCR (теперь действительно OCR) и занесём тип карты в соответствующее поле данных структуры

 

    for (i = 0; i < 4; i++) ocr[i] = SPI_ReceiveByte();

      sprintf(str1,"OCR: 0x%02X 0x%02X 0x%02X 0x%02Xrn",ocr[0],ocr[1],ocr[2],ocr[3]);

      HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

      sdinfo.type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; // SDv2 (HC or SC)

    }

  }

}

else //SDv1 or MMCv3

 

Теперь в конце функции отобразим тип нашей карты в шестнадцатеричном виде

 

    return 1;

  }

  sprintf(str1,"Type SD: 0x%02Xrn",sdinfo.type);

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  return 0;

}

 

Соберём код, прошьём контроллер и посмотрим результат

 

index57

 

На этом инициализация карты, будем считать, закончена.

 

В следующей части нашего урока мы добавим необходимый код в пользовательские функции библиотеки FATFS и попробуем прочитать текстовый файл с карты SD.

 

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Отладочную плату можно приобрести здесь STM32F103C8T6

Модуль Micro-SD SPI можно приобрести здесь Micro-SD SPI

Переходник USB to TTL можно приобрести здесь USB to TTL ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM SD. SPI. FATFS

14 комментариев на “STM Урок 88. SD. SPI. FATFS. Часть 2
  1. Guanch:

    А когда будет 3 часть?

  2. Nick:

    Narod stream. Прошу помощи!
    Сделал уже досконально, в точности как в уроке, но карта не откликается на команды. 
    Пробовал 2 разных карты, форматировал в FAT16 и 32, кидал 80 и даже больше импульсов на килогерцах, и на мегагерцах.
    Итог печальный, карты не отвечают ничего кроме 0xFF.

    В чем может быть проблема?

    • Что-то перепутано. 0xFF не всегда означает ответ, так как это просто постоянно поднятая шина MISO, если это именно MISO. Если всё правильно, то ответ будет однозначно.

  3. Дмитрий:

    Здавствуйте, кажется в функции SD_PowerOn после цикла while должна быть операция инкрементироваиния Timer1++, иначе получается бесконечный цикл. Или я ошибаюсь?

     

    • Здравствуйте!
      Данная операция у нас в функции обработки прерываний от таймера.

      • Дмитрий:

        Точно, спасибо. Пропустил букву в одном месте и операция инкрементирования не выполнялась. 

        Есть еще одна проблема, терминальная программа выводит следующее:

        OCR: 0x00 0x00 0x01 0xAA
        Type SD: 0x00

        Не отображается вторая строчка с OCR и тип карты некорректный. При использовании Debug Session для поиска ошибки программа стопорится на строчке: for (tmr = 12000; tmr && SD_cmd(ACMD41, 1UL << 30); tmr—)
                                ; // Wait for leaving idle state (ACMD41 with HCS bit) 

        Буду признателен за помощь, уже три дня голову ломаю.

  4. Billidean:

    Добрый день.
    Начал изучать работу с SD картой.
    При инициализации у меня возникла такая же проблема, что и у Дмитрия.
    На консоль выводится:
    OCR: 0x00 0x00 0x01 0xAA
    Type SD: 0x00
    Подскажите, пожалуйста, в чем может быть проблема.

  5. александр:

    пытался сотворить ваш урок на stm32103ct6
    не получилось в полном объеме — поп причине sd-card держателей небыла в разном виде.
    потом флешка была до16 метров.
    решил соблюсти частотут эксперимента и потратился на stm32f103tct6
    и разные sd держатели для моделирования.

    OCR: 0x00 0x00 0x01 0xAA
    OCR: 0xC0 0xFF 0x80 0x00
    Type SD: 0x0C

    вот результат .

    но там тоже не всё спокойно было .
    хорошо , что на сях под винду и dos работал и паскалях тоже. разрулил.

    урок отличный до и баги полезны — когда всё клёва тоже не очень…
    теперь на следующий этап попбеду.
    делаю это в отпуске. — эт к чему — нужно терпение и время.
    или просто несколько выходных потратить или вечеров или ночей , чтобы осилить.
    у меня в сумме полается пол дня , а в разрезе нескольких подходов , то три дня..
    тут еще надо правильно соединить и потом анализатора у меня нет. думаю надо преобрести…

    храмов а а

  6. александр:

    да энергообеспечение для флешки вывел отдельно от usb, но естественно общую grn обеспечил.
    важно!!! хотел конденсатор еще приспособить — флешка кушает много при записи.

  7. W4d1m:

    В терминал выводит только OCR: 0x00 0x00 0x01 0xAA и
    Type SD: 0x00. Карта на 32GB. Не выводит второй раз OCR и тип карты по нулям. В чём может быть проблема?

  8. Евгений:

    Здравствуйте. Пытаюсь настроить по Вашему уроку. Застрял на точке подачи CMD 0x00 для. В ответе(Response R1), выдает постоянно значение 0x08, что означает ошибка в CRC.
    Куда дальше двигаться, понятия не имею. Буду благодарен, если дадите какую-то подсказку.
    Спасибо.

  9. Евгений:

    Здравствуйте. Работаю по Вашему уроку. Застрял в точке подачи команды CMD 0x00. Ответом(Response R1) является значение 0х08, что означает ошибка в CRC. Буду благодарен за любую подсказку и помощь.
    Спасибо.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*