Подключение SD карты к STM32 через 4-х битный интерфейс SDIO


• О проекте
• Обратная связь
• Полезные ссылки
• Полезные программы
• Друзья сайта


Последние комментарии

Аби: Подключение микроконтроллеров к шине RS-485
написано просто о...

Анатолий: Джойстик для денди на stm32
Автору 100500 рес...




           

Библиотека для AVR





AXLIB Генератор





Помощь сайту


				

Подключение SD карты к STM32 через 4-х битный интерфейс SDIO

	
	
	

Дата: 7 Февраля 2017. Автор: Алексей

	
	
Всем привет! Решил я накатать статеку на тему SD карт. Я уже несколько раз пытался понять принцип работы и все никак не мог записать ни единого файла. Вчера обновив CubeMX я с помощью хелпа все таки с горем по палам разобрался с этой темой. Дабы не забыть решил тут описать свой процесс мучений, ну и если кому пригодиться, то мне не жалко поделиться. И так, поехали. В качестве подопытного я буду использовать Китайскую платку BIT с МК STM32F103ZET6. На борту данной платки уже распаян разъем для microSD карты и все что нужно, это просто написать код.

Набор мучителя микросхем.

Схема

Схема подключения SD-карты.

Для начала нужно сгенерить проект под IAR. В этом нам поможет CubeMX. Запускаем программу. Если у вас она не установлена, то топаем на сайт, регистрируемся на нем и качаем свежую программу. На сегодняшний день это версия 4.19.0.

Первым делом нужно настроить тактирование. Для этого выбираем RCC в левом списке и выбираем внешний кварц.

Выбор кварца

После выбора источника тактирования, его нужно подключить. Для этого выбираем наверху вкладку Clock Configuration.

Настройка частоты.

Ставим галочку HSE и PLLCLK. Таким образом мы переведем МК на такирование от кварца. Далее в окошке HCLK запишем максимально возможную частоту. Они написана под окошком синим цветом. Правда при этом тактирование SDIO будет осуществлятся на частоте 36МГц и карта не заработает. Можно конечно понизить частоту всего МК до необходимой для SDIO, но я считаю что это не выход из положения. Все таки мы движемся в ногу со временем и наши устройства не должны отставать по скорости обработки данных.))) Так что пусть по максимуму)))

Отлично. Частоту тактирования настроили, а теперь настало самое время подключить нашу карту. Подключать карту будем по ее родному интерфейсу SDIO. Для этого просто в списке периферии выбираем SDIO, а в выпадающем списке 4-х битную шину.

Подключение SDIO

А теперь вспомним про избыточную частоту тактирования карты. Как это поправить. Для этих целей переходим во вкладку Configuration.

Конфигурация проекта

И жмем на кнопку SDIO.

Конфигурация SDIO

Таким образом мы открыли окно конфигурации SDIO. На данный момент нас интересует самая первая вкладка.

Конфигурация SDIO

Если прочитать снизу буржуйскую надпись, то можно понять что SDIO_CK это частота тактирования карты. Помним наши 36МГц. Смотрим опять на буржуйский текст и видим что максимальная частота должна быть не выше 24МГц. Как быть? Читаем внимательнее и видим что для задания частоты есть формула SDIO_CK = SDIOCLK / [CLKDIV + 2]. Ага, значит заменив какое-то из значений мы сможем поменять частоту тактирования карты. А какое значение менять? Поднимаем глаза по выше и видим строчку редактирования параметра SDIOCLK. А что вписать туда? Читаем буржуйский текст, а там написано по русски, что значение данного параметра может лежать в диапазоне от 0 до 255. А по умолчанию для максимальной частоты оно должно быть равно 0. Значит при 0 у нас 36МГц. Хм. Ну я чисто из соображений положения звезд на небе поставил цифру 3 и не стал разбираться дальше. И чтоб вы думали))) Попал. Карта читается, а значит частота тактирования приемлема.

Собственно карта подключена и можно уже с ней работать, но как-то сложилось исторически что 99,9% карты используют с файловой системой и хранят на них файлы. Значит и мы будем делать тоже самое. Для этих целей в генераторе кода предусмотрен набор функций с файловой системой FAT и называется она FATFS. Историю ее появления я не буду расписывать, благо об этом много написано в интернете, а вот как ее прикрутить и использовать в своих целях и пойдет дальше речь. Для того чтобы ее подключить, нужно просто напросто выбрать одноименный пункт меню и поставить галочку для работы с SD картой.

Подключение FATFS

Теперь осталось сгенерить проект и начать писать код. Для этого нужно нажать на иконку с желтой шестеренкой и в появившемся окне выбрать путь проекта и имя.

Генерация проекта

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

Генерация проекта

Вот как стал выглядеть проект. Каждой периферии свой файл. А в основном файле все проинклюдено и никакого бардака.

Вид проекта

Так, теперь давайте разбираться с функционалом. Все необходимое для работы с файловой системой лежит в файле ff.h. Давайте откроем его и глянем внутрь.

FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);	
FRESULT f_close (FIL* fp);
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br);
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw);
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf);
FRESULT f_lseek (FIL* fp, DWORD ofs);
FRESULT f_truncate (FIL* fp);
FRESULT f_sync (FIL* fp);				
FRESULT f_opendir (DIR* dp, const TCHAR* path);	
FRESULT f_closedir (DIR* dp);
FRESULT f_readdir (DIR* dp, FILINFO* fno);
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern);
FRESULT f_findnext (DIR* dp, FILINFO* fno);
FRESULT f_mkdir (const TCHAR* path);
FRESULT f_unlink (const TCHAR* path);
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new);
FRESULT f_stat (const TCHAR* path, FILINFO* fno);
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask);
FRESULT f_utime (const TCHAR* path, const FILINFO* fno);
FRESULT f_chdir (const TCHAR* path);
FRESULT f_chdrive (const TCHAR* path);
FRESULT f_getcwd (TCHAR* buff, UINT len);
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs);
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn);
FRESULT f_setlabel (const TCHAR* label);
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt);
FRESULT f_mkfs (const TCHAR* path, BYTE sfd, UINT au);
FRESULT f_fdisk (BYTE pdrv, const DWORD szt[], void* work);

А вот это варианты ответов этих функций.

typedef enum {
	FR_OK = 0,				
	FR_DISK_ERR,			
	FR_INT_ERR,				
	FR_NOT_READY,			
	FR_NO_FILE,				
	FR_NO_PATH,				
	FR_INVALID_NAME,		
	FR_DENIED,				
	FR_EXIST,				
	FR_INVALID_OBJECT,		
	FR_WRITE_PROTECTED,		
	FR_INVALID_DRIVE,		
	FR_NOT_ENABLED,			
	FR_NO_FILESYSTEM,		
	FR_MKFS_ABORTED,		
	FR_TIMEOUT,				
	FR_LOCKED,				
	FR_NOT_ENOUGH_CORE,		
	FR_TOO_MANY_OPEN_FILES,	
	FR_INVALID_PARAMETER	
} FRESULT;

Впечатляюще))) Но на первых парах мы не будем использовать все сразу. Наша задача подключиться к диску, создать файл и записать в него что-нибудь. Поехали. Для начала нам создать объект файловой системы для SD диска. Забегу вперед. Я все переменные и необходимые данные структур возьму из хелпа куба. Так будет проще если появится необходимость туда залезть. Для того чтобы создать этот объект, делаем в начале файла main.c следующее. В зоне USER CODE BEGIN PV пропишем строку FATFS SDFatFs;.

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
FATFS SDFatFs;

/* USER CODE END PV */

Теперь мы можем смело примонтировать диск. Для этого внутри поля USER CODE BEGIN 2 впишем функцию монтирования диска. В качестве аргументов первым идет указатель на объект диска, вторым передается указатель на том диска. В винде диски имеют названия A, B, C, D, и т.д., а здесь они именуются как 0:, 1: и т.д. Откуда взялась SD_Path? Топаем в файл fatfs.c который сгенерил куб и видим в начале char SD_Path[4]; /* SD logical drive path */. То есть за нас уже все сделали. Именно здесь и хранится путь к корню диска. А последний аргумент в виде единицы указывает на то, что мы хотим примонтировать данный диск. Функция, как можно заметить, вызывается в условии if. Это сделано для того чтобы можно было проверить выполнение данных действий. Если с диском будет что-то не так, то функция вернет ошибку и только если все хорошо, то можно продолжать работать с диском.

/* USER CODE BEGIN 2 */

if(f_mount(&SDFatFs, (TCHAR const*)SD_Path, 1) == FR_OK)
{
 
}
  
/* USER CODE END 2 */

Следующим шагом нам нужно открыть файл. Для этого в теле условия впишем строку
f_open(&MyFile, "myfile.txt", FA_CREATE_NEW | FA_WRITE);.
Данной строкой мы вызываем функцию открытия файла. В качестве первого аргумента нужно передать адрес объекта файла. Для этого вверху файла main.c после строчки FATFS SDFatFs; нужно создать этот объект написав строку FIL MyFile;. Вторым аргументом передается имя файла. Вот тут есть проблема. В среде IAR можно задавать имена файлов только латинскими буквами, а функция эти имена будет писать большими буквами. Я перекапал весь файл ff.h и нашел поддержку всех алфавитов и даже русский, но IAR выводит его кракозябами. Изменение сохранения кода в IAR на любую другую кодировку не дает результатов. Так что увы, но имена файлов нужно писать только латинскими буквами. Последним аргументом идет указание что делать с файлом. FA_CREATE_NEW создаст новый файл, а если такой уже есть, то выругнется. FA_WRITE разрешает производить запись в файл.

/* USER CODE BEGIN 2 */

if(f_mount(&SDFatFs, (TCHAR const*)SD_Path, 1) == FR_OK)
{
    f_open(&MyFile, "myfile.txt", FA_CREATE_NEW | FA_WRITE); 
}
  
/* USER CODE END 2 */

И так, файл мы создали. Теперь нужно что-то в него записать. Делается это вот таким способом.
res = f_write(&MyFile, wtext, sizeof(wtext), (void*)&byteswritten);
Опять куча новых слов.))) Поехали с самого начала. res - это переменная в которой функция отчиталась за проделанную работу. Она опять же взята не из головы, а мы ее добавим вначале файла main.c. После строки FIL MyFile; напишем FRESULT res;. Теперь мы будем знать что произошло внутри функции. Теперь поехали по аргументам. Ну первый нам уже известен. Второй это массив с текстом который нужно записать в файл. Давайте его создадим. После строки FRESULT res; напишем const char wtext[] = "Бла-бла-бла";. Да, да. Эта зараза в файл прекрасно записывает русские буквы. Так что полет фантазии может быть огромным. sizeof(wtext) этот аргумент передает функции количество байт которые необходимо записать. Собственно это видно из стандартной функции sizeof(). А последний аргумент передает функции указатель на переменную в которую функция положит количество реально записанных байт. А то вдруг не хватит места на диске или еще какая аказия случится. В любом случае сколько запишет байт на диск, столько и будет в этой переменной. Так что инициализируем эту переменную после массива const char wtext[] = "Бла-бла-бла"; строкой uint32_t byteswritten;. Как это будет выглядеть в коде.

/* USER CODE BEGIN 2 */

if(f_mount(&SDFatFs, (TCHAR const*)SD_Path, 1) == FR_OK)
{
    // Создать файл и открыть его на запись.
    f_open(&MyFile, "myfile.txt", FA_CREATE_NEW | FA_WRITE); 
    
    // Записать в файл заданный текст.
    res = f_write(&MyFile, wtext, sizeof(wtext), (void*)&byteswritten);
      
    // Проверить на качество выполненной задачи.
    if((byteswritten != sizeof(wtext)) || (res != FR_OK))
    {
       // Делаем что-то если не записали в файл
    }
    else
    {
       // Все хорошо, файл записан
    }
}
  
/* USER CODE END 2 */

Как видите для большего понимания я добавил комментарии. После вызова функции записи нам необходимо проверить, а записались ли данные. Поэтому использую условие, проверяем сколько реально записалось байт и не ругнулась ли функция на что-то. Если оба условия удовлетворяют, то есть количество байт записалось больше нуля, а функция вернула ЩЛ, то все замечательно. Последнее что нужно сделать, так это закрыть файл и отмантировать диск. Делается это следующими функциями. Для закрытия файла напишем.
f_close(&MyFile);
Я думаю здесь комментарии излишне, а вот для того чтоб отмантировать диск, нужно вызвать ту же функцию что и при монтировании. Только в качестве последнего аргумента единицу заменить на ноль. В итоге будет вот так.

/* USER CODE BEGIN 2 */

// Монтировать диск
if(f_mount(&SDFatFs, (TCHAR const*)SD_Path, 1) == FR_OK)
{
    // Создать файл и открыть его на запись.
    f_open(&MyFile, "myfile.txt", FA_CREATE_NEW | FA_WRITE); 
    
    // Записать в файл заданный текст.
    res = f_write(&MyFile, wtext, sizeof(wtext), (void*)&byteswritten);
      
    // Проверить на качество выполненной задачи.
    if((byteswritten != sizeof(wtext)) || (res != FR_OK))
    {
       // Делаем что-то если не записали в файл
    }
    else
    {
       // Все хорошо, файл записан
    }

    // Закрыть файл
    f_close(&MyFile);

    // Отмантировать диск
    f_mount(&SDFatFs, (TCHAR const*)SD_Path, 0);
}
  
/* USER CODE END 2 */

Вот такими незамысловатыми движениями можно работать с SD картой. Конечно перед выполнением этой программы нужно карточку отформатировать в FAT32, а потом подключать к МК. Но если посмотреть на набор функций, то можно и отформатировать карту силами МК. Например вот эта функция
FRESULT f_mkfs (const TCHAR* path, BYTE sfd, UINT au);
отформатирует диск.

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


igor    07.04.17 09:46

Алексей, можно привести схему подключения SD карты

Алексей    07.04.17 22:03

Можно.

kosmas    08.04.17 23:39



// Проверить на качество выполненной задачи.
if((byteswritten == 0) || (res != FR_OK))


а не правильнее ли будет написать что-то вроде


// Проверить на качество выполненной задачи.
if((byteswritten != sizeof(wtext)) || (res != FR_OK))

?

Алексей    10.04.17 09:08

Действительно, так будет правильней, так как последний аргумент при записи всегда будет больше нуля при записи хоть одного байта. Исправлю в тексте, спасибо.

Алексей    10.04.17 09:34

А вот такая штука в доке для куба написана))

if((byteswritten == 0) || (res != FR_OK)){
/* 'Hello.txt' file Write or EOF Error : set the red LED on */
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_10, GPIO_PIN_RESET);
while(1);
}


Евгений    12.06.17 07:19

Здравствуйте, за русским языком следите, хоть и аппаратно программируете иностранные комплектующие, Вы же русский. И стало быть надо за своей речью следить - естественно я под этим понимаю правильное изложение мыслей в тексте - естественно только при правильном написании текста. Ошибок много в начале статьи.

Алексей    12.06.17 16:11

Увы, но технический склад ума не совместим с гуманитарным. Нельзя делать два дела одновременно, а точнее когда течет мысль уже не до языка))) Для таких вещей нужен корректор, а нанять я не могу из-за отсутствия финансов))) Так что буду стараться "Как могу".

Евгений    17.12.17 21:30

Хорошая статья, спасибо вам! Не подскажите, как реализовать запись на флэш по типу логгера? Ну допустим мне надо писать в файл значение АЦП с частотой 10 Гц.

Алексей    18.12.17 11:51

10 записей в секунду это лихо. Нужно создать вещественный массив на 100 ячеек и закидывать туда данные, а после заполнения писать уже 100 записей в файл.

Евгений    18.12.17 20:00

А можно пример как реализовать это? Буду очень признателен)

Алёна    31.01.18 13:05

mx cube stm32f4 sdio не переходит с sd карты в 4-х битный режим. Подскажите пожалуйста как решить эту проблему?

Алексей    31.01.18 15:47

Какая версия куба?
Случайно камень не на дискавери четвертой? Если да, то у нее пины SDIO заняты и карту подключить нельзя. По крайне мере у меня так было.




Чтобы вставить ссылку используйте форму вида[url]http://www.адрес.ru[/url][text]текст ссылки[/text]
Чтобы вставить код используйте форму вида[code]код[/code]

Имя:   





  







Рейтинг@Mail.ru Яндекс.Метрика