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
10 лет назад

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

GLEP
GLEP
10 лет назад

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

GLEP
GLEP
10 лет назад

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

mishadesh
mishadesh
10 лет назад

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Dimaster
8 лет назад

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

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

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

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