Top.Mail.Ru

STM32 и FatFs. Файловая система FAT на внешней SD-карте.

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

Подписаться
Уведомить о
guest

16 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Elis
Elis
9 лет назад

Спасибо за статью. Весьма полезно.

GLEP
GLEP
9 лет назад

Отличная статья, спасибо!

GLEP
GLEP
9 лет назад

Статейка про тачскрин MiniSTM32 будет?

mishadesh
mishadesh
9 лет назад

Я автору блога выслал материалы, как он их оформит, так и будет статья в скором времени.

Павел
Павел
8 лет назад

Спасибо. Но лучше бы выложили проект рабочий, а так фиг знает подчеркивает половину вашего файла и непонятно что ему надо. Жалко было готовый проект выложить что ли...

Олег
Олег
8 лет назад

Будьте добры, приведите демо проект

Евгений
Евгений
8 лет назад

Какой толк от статьи без проекта. Ругается на отсутствие функций SD_GetStatus() и не знает что такое SD_TRANSFER_OK

Алексей
Алексей
Ответ на комментарий  Евгений
5 лет назад

У меня такая же проблема. Хотелось бы решение посмотреть... ну попробую сам допилить конечно..

Сергей
Сергей
8 лет назад

Не знаю, у кого-то может и запустился проект с приведенным здесь diskio.c, но у меня виснет на while. Пришлось переписать по-другому и все заработало. Тестил на stm32f103rgt6. Кому нужен исходник проекта - обращайтесь

Дима
Дима
Ответ на комментарий  Сергей
7 лет назад

Поделитесь, если можно)

Геннадий
Геннадий
Ответ на комментарий  Сергей
5 лет назад

Сергей, если можно поделитесь проектом. Спасибо.

Дмитрий
Дмитрий
7 лет назад

Приветствую. Подскажите, пытаюсь запустить stm32f103 по USB в режиме флешки (mass storage class). Как настроить файлик user_diskio.c? Проект собирал в CubMX, но файлы и функции для FATFS ровно те же. Заранее спасибо!

Dimaster
7 лет назад

День добрый. У меня опять вопрос. В общем реализовал я режим MSC, без использования каких либо физических носителей пока что. В общем с компа форматируется. Суть в чем. Создаю файлик с нужным именем (на носителе), пишу в него текст, вызываю функции, чтобы считать открыть и считать данные, все работает, вижу информация. Другой случай, вызываю функция f_open(&testFile, (char*)file_way, FA_WRITE | FA_CREATE_ALWAYS) и f_write(&testFile, write_buffer, 12, &testBytes) ну и f_close(&testFile). Смотрю в компе, файла нету, пусто. Вызываю функцию открыть и прочитать искомый файл, а он есть на носителе, все работает, только с компа его не видно. Передергиваю USB, файл нашелся.Как это исправить. Я так понимаю нужно, чтобы комп снова прочитал таблицу файловой системы внешнего носителя после того, как STMка записала данные. Но как это делать. Заранее спасибо!

Алексей
Алексей
5 лет назад

Добрый день!
Продолжаю разбираться с файловой системой и работой с картой через SDMMC.
Вопрос такой: в большинстве описаний низкоуровневой работы с SD с применением diskio. В большинстве источников пишут про некую функцию disk_timerproc которая должны вызываться раз в 10 мс. В HAL я её не нашел. Описаний, как её создавать тоже.
У меня родились подозрения, что её функционал реализовал cube и теперь не нужно заморачиваться по этому поводу.
Так ли это?

16
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x