По просьбам читателей нашего сайта сегодняшняя статья будет посвящена работе с файловой системой FAT. Будем использовать связку библиотеки FatFs и микроконтроллера STM32. Статья будет небольшая, но информативная, только голые факты и ничего лишнего )
Подключение карты памяти к микроконтроллеру уже было описано ранее - ссылка - поэтому сегодня мы сразу перейдем к обсуждению именно файловой системы FAT. Напомню только, что для экспериментов мы используем контроллер STM32 и самую обычную SD-карту. Подключение реализовано при помощи интерфейса SDIO. Осталось только прикрутить FatFs 👍
Пара слов о самой библиотеке FatFs. Она предназначена, в первую очередь, именно для встраиваемых систем на микроконтроллерах. Плюсов у этой библиотеки очень много:
- очень легко портируется на любую архитектуру
- способна работать с несколькими томами
- гибкая в настройке и т. д.
Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также статья на смежную тему из нового курса: STM32 и SD-карта. Настройка FatFs в STM32CubeMx.
Список всех функций, реализованных в FatFs приводить не буду, в процессе работы рассмотрим некоторые из них. Работа с файловой системой начинается, собственно, с "регистрации". Для этого API библиотеки FatFs содержит функцию f_mount():
FRESULT f_mount( BYTE Drive, // Номер диска FATFS* FileSystemObject // Указатель на объект файловой системы );
В общем-то на этом подготовка к работе и заканчивается. После выполнения операции f_mount() можно работать непосредственно с данными - создавать и удалять директории, файлы, записывать и считывать информацию и т. д.
Таким образом, с FatFs в принципе все ясно, но остался открытым следующий вопрос - а как вообще адаптировать библиотеку под конкретную задачу. В нашем случае необходимо настроить использование интерфейса SDIO. А для этого нам нужно будет внести изменения всего лишь в один файл, а именно discio.c (файл идет в составе библиотеки FatFs). Итак, открываем файл и видим следующие функции:
/***************************************************************************************/ DSTATUS disk_initialize ( BYTE pdrv /* Physical drive nmuber (0..) */ ) { return 0; } /***************************************************************************************/ DSTATUS disk_status( BYTE pdrv /* Physical drive nmuber (0..) */ ) { return 0; } /***************************************************************************************/ DRESULT disk_read( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to read (1..128) */ ) { return RES_OK; } /***************************************************************************************/ #if _USE_WRITE DRESULT disk_write( BYTE pdrv, /* Physical drive nmuber (0..) */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to write (1..128) */ ) { return RES_OK; } #endif /***************************************************************************************/ #if _USE_IOCTL DRESULT disk_ioctl( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ ) { return res; } /***************************************************************************************/
Тело функций я оставил пока пустым. Именно эти функции использует FatFs, и именно их нужно изменить в соответствии с используемым типом памяти/подключением. То есть, грубо говоря, если я использую SD-карту, то должен реализовать функцию disk_read() следующим образом:
DRESULT disk_read( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to read (1..128) */ ) { SD_ReadData(buff, sector, count); return RES_OK; }
В данном случае функция SD_ReadData() - это абстрактная функция записи данных на карту памяти SD, придуманная только для примера. Если я буду использовать NAND-память, то нужно будет функцию SD_ReadData() заменить на NAND_ReadData(). Как видите, идея очень проста. На деле все сложнее, но ненамного.
Итак, привожу полный код файла diskio.c, адаптированного под работу с картой памяти SD, подключенной к микроконтроллеру при помощи интерфейса SDIO:
/**************************************************************************************************/ /* Low level disk I/O module skeleton for FatFs (C)ChaN, 2013 */ /**************************************************************************************************/ /* If a working storage control module is available, it should be */ /* attached to the FatFs via a glue function rather than modifying it. */ /* This is an example of glue functions to attach various exsisting */ /* storage control module to the FatFs module with a defined API. */ /**************************************************************************************************/ #include "diskio.h" /* FatFs lower layer API */ #include "sdcard.h" /* Definitions of physical drive number for each media */ #define ATA 0 #define MMC 1 #define USB 2 #define SECTOR_SIZE 512U /**************************************************************************************************/ /* Inidialize a Drive */ /**************************************************************************************************/ DSTATUS disk_initialize( BYTE pdrv /* Physical drive nmuber (0..) */ ) { return 0; } /**************************************************************************************************/ /* Get Disk Status */ /**************************************************************************************************/ DSTATUS disk_status( BYTE pdrv /* Physical drive nmuber (0..) */ ) { return 0; } /**************************************************************************************************/ /* Read Sector(s) */ /**************************************************************************************************/ DRESULT disk_read( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to read (1..128) */ ) { if(count==1) { SD_ReadBlock(&buff[0] ,sector << 9,SECTOR_SIZE); while(SD_GetStatus() != SD_TRANSFER_OK); } else { SD_ReadMultiBlocks((&buff[0]), sector << 9,SECTOR_SIZE,count); while(SD_GetStatus() != SD_TRANSFER_OK); } return RES_OK; } /**************************************************************************************************/ /* Write Sector(s) */ /**************************************************************************************************/ #if _USE_WRITE DRESULT disk_write( BYTE pdrv, /* Physical drive nmuber (0..) */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to write (1..128) */ ) { if(count == 1) { SD_WriteBlock((BYTE*)(&buff[0]),sector << 9, SECTOR_SIZE); while(SD_GetStatus() != SD_TRANSFER_OK); } else { SD_WriteMultiBlocks((BYTE*)(&buff[0]) , sector << 9, SECTOR_SIZE, count); while(SD_GetStatus() != SD_TRANSFER_OK); } return RES_OK; } #endif /**************************************************************************************************/ /* Miscellaneous Functions */ /**************************************************************************************************/ #if _USE_IOCTL DRESULT disk_ioctl( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ ) { DRESULT res = RES_ERROR; SD_CardInfo CardInfo; switch (cmd) { case CTRL_SYNC: res = RES_OK; break; case GET_SECTOR_COUNT: SD_GetCardInfo(&CardInfo); *(DWORD*)buff = CardInfo.CardCapacity / 512; res = RES_OK; break; case GET_SECTOR_SIZE: *(DWORD*)buff = 512; res= RES_OK; break; case GET_BLOCK_SIZE: *(DWORD*)buff = 512; res= RES_OK; break; } return res; } #endif /**************************************************************************************************/
Теперь наша библиотека готова к работе. Давайте рассмотрим небольшой пример, включающий в себя монтирование диска с FAT и чтение данных из файла:
void main() { FATFS fileSystem; FIL testFile; uint8_t testBuffer[4]; UINT testBytes; if(f_mount(&fileSystem, "0", 1) == FR_OK) { uint8_t path[9] = "test.txt"; path[8] = '\0'; f_open(&testFile, (char*)path, FA_READ); f_read(&testFile, testBuffer, 4, &testBytes); } }
Что же тут происходит? Для начала мы регистрируем диск FAT, и если эта операция прошла успешно, то мы считываем 4 байта из файла test.txt. Важной особенностью является то, что путь к файлу в FatFs должен заканчиваться символом конца строки - "\0". Переменная testBytes нужна для хранения количества прочитанных байт. То есть в случае успешного чтения данных в этом примере после выполнения функции f_read() testBytes должна стать равной 4. Вот, в принципе, и все, что тут нужно обсудить )
Итак, завершаем сегодняшнюю статью! Для сборки полного проекта можно скачать проект для STM32 и SDIO из соответствующей статьи на нашем сайте и добавить в него модуль FatFS с измененным файлом discio.c.
Спасибо за статью. Весьма полезно.
Отличная статья, спасибо!
Статейка про тачскрин MiniSTM32 будет?
Я автору блога выслал материалы, как он их оформит, так и будет статья в скором времени.
Спасибо. Но лучше бы выложили проект рабочий, а так фиг знает подчеркивает половину вашего файла и непонятно что ему надо. Жалко было готовый проект выложить что ли...
Будьте добры, приведите демо проект
Какой толк от статьи без проекта. Ругается на отсутствие функций SD_GetStatus() и не знает что такое SD_TRANSFER_OK
У меня такая же проблема. Хотелось бы решение посмотреть... ну попробую сам допилить конечно..
Не знаю, у кого-то может и запустился проект с приведенным здесь diskio.c, но у меня виснет на while. Пришлось переписать по-другому и все заработало. Тестил на stm32f103rgt6. Кому нужен исходник проекта - обращайтесь
Поделитесь, если можно)
Сергей, если можно поделитесь проектом. Спасибо.
Приветствую. Подскажите, пытаюсь запустить stm32f103 по USB в режиме флешки (mass storage class). Как настроить файлик user_diskio.c? Проект собирал в CubMX, но файлы и функции для FATFS ровно те же. Заранее спасибо!
Добрый день, вот здесь есть пример - https://microtechnics.ru/stm32cube-i-usb-mass-storage-sd-card/
День добрый. У меня опять вопрос. В общем реализовал я режим MSC, без использования каких либо физических носителей пока что. В общем с компа форматируется. Суть в чем. Создаю файлик с нужным именем (на носителе), пишу в него текст, вызываю функции, чтобы считать открыть и считать данные, все работает, вижу информация. Другой случай, вызываю функция f_open(&testFile, (char*)file_way, FA_WRITE | FA_CREATE_ALWAYS) и f_write(&testFile, write_buffer, 12, &testBytes) ну и f_close(&testFile). Смотрю в компе, файла нету, пусто. Вызываю функцию открыть и прочитать искомый файл, а он есть на носителе, все работает, только с компа его не видно. Передергиваю USB, файл нашелся.Как это исправить. Я так понимаю нужно, чтобы комп снова прочитал таблицу файловой системы внешнего носителя после того, как STMка записала данные. Но как это делать. Заранее спасибо!
Добрый день!
Продолжаю разбираться с файловой системой и работой с картой через SDMMC.
Вопрос такой: в большинстве описаний низкоуровневой работы с SD с применением diskio. В большинстве источников пишут про некую функцию disk_timerproc которая должны вызываться раз в 10 мс. В HAL я её не нашел. Описаний, как её создавать тоже.
У меня родились подозрения, что её функционал реализовал cube и теперь не нужно заморачиваться по этому поводу.
Так ли это?
Добрый день!
Я на самом деле с упоминаниями этой функции сталкивался только когда-то давно на форумах AVR. Возможно это в какой-то версии FatFS было актуально, потому что в HAL и в версии FatFS, которая используется в Cube, эта функция вообще не упоминается и не используется...