Подключение SD карты к STM32 через 4-х битный интерфейс SDIO
Дата: 7 Февраля 2017. Автор: Алексей
Всем привет! Решил я накатать статеку на тему SD карт. Я уже несколько раз пытался понять принцип работы и все никак не мог записать ни единого файла. Вчера обновив CubeMX я с помощью хелпа все таки с горем по палам разобрался с этой темой. Дабы не забыть решил тут описать свой процесс мучений, ну и если кому пригодиться, то мне не жалко поделиться. И так, поехали. В качестве подопытного я буду использовать Китайскую платку BIT с МК STM32F103ZET6. На борту данной платки уже распаян разъем для microSD карты и все что нужно, это просто написать код.
Схема
Для начала нужно сгенерить проект под IAR. В этом нам поможет CubeMX. Запускаем программу. Если у вас она не установлена, то топаем на сайт, регистрируемся на нем и качаем свежую программу. На сегодняшний день это версия 4.19.0.
Первым делом нужно настроить тактирование. Для этого выбираем RCC в левом списке и выбираем внешний кварц.
После выбора источника тактирования, его нужно подключить. Для этого выбираем наверху вкладку Clock Configuration.
Ставим галочку HSE и PLLCLK. Таким образом мы переведем МК на такирование от кварца. Далее в окошке HCLK запишем максимально возможную частоту. Они написана под окошком синим цветом. Правда при этом тактирование SDIO будет осуществлятся на частоте 36МГц и карта не заработает. Можно конечно понизить частоту всего МК до необходимой для SDIO, но я считаю что это не выход из положения. Все таки мы движемся в ногу со временем и наши устройства не должны отставать по скорости обработки данных.))) Так что пусть по максимуму)))
Отлично. Частоту тактирования настроили, а теперь настало самое время подключить нашу карту. Подключать карту будем по ее родному интерфейсу SDIO. Для этого просто в списке периферии выбираем SDIO, а в выпадающем списке 4-х битную шину.
А теперь вспомним про избыточную частоту тактирования карты. Как это поправить. Для этих целей переходим во вкладку Configuration.
И жмем на кнопку SDIO.
Таким образом мы открыли окно конфигурации SDIO. На данный момент нас интересует самая первая вкладка.
Если прочитать снизу буржуйскую надпись, то можно понять что SDIO_CK это частота тактирования карты. Помним наши 36МГц. Смотрим опять на буржуйский текст и видим что максимальная частота должна быть не выше 24МГц. Как быть? Читаем внимательнее и видим что для задания частоты есть формула SDIO_CK = SDIOCLK / [CLKDIV + 2]. Ага, значит заменив какое-то из значений мы сможем поменять частоту тактирования карты. А какое значение менять? Поднимаем глаза по выше и видим строчку редактирования параметра SDIOCLK. А что вписать туда? Читаем буржуйский текст, а там написано по русски, что значение данного параметра может лежать в диапазоне от 0 до 255. А по умолчанию для максимальной частоты оно должно быть равно 0. Значит при 0 у нас 36МГц. Хм. Ну я чисто из соображений положения звезд на небе поставил цифру 3 и не стал разбираться дальше. И чтоб вы думали))) Попал. Карта читается, а значит частота тактирования приемлема.
Собственно карта подключена и можно уже с ней работать, но как-то сложилось исторически что 99,9% карты используют с файловой системой и хранят на них файлы. Значит и мы будем делать тоже самое. Для этих целей в генераторе кода предусмотрен набор функций с файловой системой FAT и называется она FATFS. Историю ее появления я не буду расписывать, благо об этом много написано в интернете, а вот как ее прикрутить и использовать в своих целях и пойдет дальше речь. Для того чтобы ее подключить, нужно просто напросто выбрать одноименный пункт меню и поставить галочку для работы с SD картой.
Теперь осталось сгенерить проект и начать писать код. Для этого нужно нажать на иконку с желтой шестеренкой и в появившемся окне выбрать путь проекта и имя.
Далее можно нажать ОК, но я рекомендую сделать еще одно телодвижение. Лично я не люблю когда код превращается в кашу. Поэтому я стремлюсь его разбивать на отдельные файлы. Для того чтобы генератор разбил проект на отдельные файлы, нужно перейти во вторую вкладку и поставить галочку для разбития проекта на файлы.
Вот как стал выглядеть проект. Каждой периферии свой файл. А в основном файле все проинклюдено и никакого бардака.
Так, теперь давайте разбираться с функционалом. Все необходимое для работы с файловой системой лежит в файле 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
а не правильнее ли будет написать что-то вроде
?
Алексей 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);
}
/
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_10, GPIO_PIN_RESET);
}
Евгений 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 заняты и карту подключить нельзя. По крайне мере у меня так было.
Случайно камень не на дискавери четвертой? Если да, то у нее пины SDIO заняты и карту подключить нельзя. По крайне мере у меня так было.