По просьбам читателей нашего сайта сегодняшняя статья будет посвящена работе с файловой системой 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.