SD/MMC карта памяти и микроконтроллер AVR (часть 3) Система FatFs
В этой заметке рассмотрим полноценную FatFs от мистера ChaN (elm-chan.org) и совместную работу с картой памяти типа Secure Digital отформатированной в FAT. Для экспериментов нам понадобиться стенд с картой памяти, описанный ранее, микроконтроллер Atmega32 или что-то помощнее, например Atmega64-128 и програма Terminal 1.9 или какой-то другой монитор последовательного порта. Сразу оговоримся про использованную терминологию. Кластером (cluster) будем называть наименьший объем информации, которым оперирует файловая система. Кластер состоит из секторов или блоков (количество секторов в кластере зависит от файловой системы). Сектор или блок - минимальный объем информации, который можно хранить на носителе. В случае нашей SD карты памяти сектор равен 512 байт. Далее носитель делиться на логические диски (как С:/ и D:/ в вашей windows). Отформатированный в файловой системе логический диск называется разделом. Отметим, что FatFs может работать одновременно с 10-ю логическими дисками.
Задачу на заметку поставим так, чтобы использовать все нужные и интересные возможности FatFs: в корне карты памяти создадим каталог с именем newdir, в этом каталоге создадим еще один каталог newdir2; в каталоге newdir создадим newfile.txt в который будем писать и читать данные; После чего просканируем newdir на наличие файлов и вложенных каталогов; потом почистим за собой, удалим newdir2 и newfile.txt из каталога newdir (для удаления файлов и папок будем запрашивать ответ пользователя, если пользователь при запросе на удаление вышлет 'y' по UART, то будем удалять, в противном случае – оставим все как есть). Исходная карта памяти может быть отформатирована в FAT16 или в FAT32 для FatFs это не имеет разницы, она умеет работать с обоими. Как и для работы с Petit FatFs нам потребуется написать функции взаимодействия с носителем, только в данном случае их будет не три, а целых шесть:
1)DSTATUS disk_initialize (BYTE Drive);
Инициализирует носитель Drive и подготавливает его к операциям чтения/записи. Эту функцию не следует лишний раз вызывать из своей программы, иначе есть хорошая вероятность попортить все данные на карточке. В случае удачного вызова возвращает 0. В случае, если носитель не инициализировался, возвращает STA_NOINIT, а если носитель отсутствует - STA_NODISK.
2)DSTATUS disk_status (BYTE Drive);
Узнает статус носителя Drive. Возвращает STA_NOINIT если диск не инициализирован, а если носитель отсутствует - STA_NODISK. Также в случае, если носитель защищен от записи, то возвращается STA_PROTECTED.
3)DRESULT disk_read (BYTE Drive, BYTE* Buffer, DWORD SectorNumber, BYTE SectorCount);
Читает SectorCount из носителя Drive в буфер, на который указывает указатель Buffer начиная с сектора SectorNumber в LBA адресации. Возвращает DRESULT:
typedef enum { //Статус дисковой операции
RES_OK = 0, //Операция успешно завершена
RES_ERROR, //Ошибка носителя
RES_NOTRDY, //Носитель не готов
RES_PARERR //Ошибочный аргумент
} DRESULT;
Буфер должен иметь размер количество секторов для чтения * размер сектора в байтах. Количество считываемых за раз секторов SectorCount должно быть о 1 до 128.
4)DRESULT disk_write (BYTE Drive, const BYTE* Buffer, DWORD SectorNumber, BYTE SectorCount);
Полный аналог disk_read, только не считывает данные, а пишет их на карточку.
5) DRESULT disk_ioctl (BYTE Drive, BYTE Command, void* Buffer);
Довольно интересная функция, которая передает носителю Drive команду Command, а ответ на команду пишет в буфер, на который указывает Buffer. Таблица необходимых команд приведена ниже. Проконтролировать результат позволяет возвращаемое значение DRESULT.
6) DWORD get_fattime (void);
Возвращает текущее дату и время, запиханное в DWORD следующим образом:
bit31:25 – года, начиная с 1980 (0..127)
bit24:21 – месяца (1..12)
bit20:16 – дни месяца (1..31)
bit15:11 – часы (0..23)
bit10:5 – минуты (0..59)
bit4:0 – секунды деленные на 2 (0..29)
Для конфигурации read-only не нужна. Если в системе не поддерживаются часы с реальным временем, то нужно возвращать какую-то реальную дату и время. Нужна для маркировки времени изменения/создания файлов.
Ознакомившись на сайте ChaN (elm-chan.org) с примером работы FatFs в связке с AVR, я переделал этот пример под себя. Функцию get_fattime модифицировал таким образом, что она все время возвращает одну и тоже дату и время (в моем макете RTC не предусмотрено), теперь все файловые операции датируются 1 января 1980 года 00:00:00 :). Также переделал функции низкоуровневого обмена данных с картой SD под свой макет. В итоге мой проект по работе с картой памяти в AVR Studio 4 стал выглядеть вот так:
Сами файлы драйвера файловой системы FAT находятся в файлах ff.h и ff.c. Так же как и в Petit FatFs, в полноценной FatFs типы char, int, long int были переопределены в integer.h. В файле diskio.h находятся заголовки функций взаимодействия с носителем данных (disk_initiali, disk_status, disk_read и т.д.). В mmc.c содержится вся требуха для работы с SD|MMC картой, а также сами тела функций взаимодействия с носителем. В FAT_FS_sample.c приведена программа, которая выполняет то, что было оговорено в начале заметки.
Для подключения карты памяти к микроконтроллеру используется все та же макетка, что и для Petit FatFs. Если у вас подключение карты к PortB другое, чем у меня, тогда следует переопределить следующие строки:
#define SD_DI 0 #define SD_DO 1 #define SD_CLK 2 #define SD_CS 3 #define SD_INS 4 #define SD_WP 5
Теперь перейдем к рассмотрению функций, которыми можно пользоваться в FatFs. Все функции без исключения возвращают результат FRESULT:
typedef enum {
FR_OK = 0, (0), удачное завершение
FR_DISK_ERR, (1), низкоуровневая I/O ошибка диска
FR_INT_ERR, (2), внутренняя ошибка носителя или ошибка FAT на носителе
FR_NOT_READY, (3), физический носитель не может выполнить требуемую операцию или не готов
FR_NO_FILE, (4), невозможно найти файл
FR_NO_PATH, (5), нет пути к файлу/каталогу
FR_INVALID_NAME, (6), формат пути к файлу/каталогу некорректен
FR_DENIED, (7), в доступе отказано, файл/каталог read-only или каталог переполнен
FR_EXIST, (8), создание объекта невозможно, так как объект с таким именем уже существует
FR_INVALID_OBJECT, (9), объект file/directory поврежден
FR_WRITE_PROTECTED, (10), диск защищен от записи
FR_INVALID_DRIVE, (11), номер логического диска неверный
FR_NOT_ENABLED, (12), раздел не связан
FR_NO_FILESYSTEM, (13), нет корректного FAT раздела на физическом носителе
FR_MKFS_ABORTED, (14), функция f_mkfs() завершена с ошибкой параметра
FR_TIMEOUT, (15), невозможно достучаться к разделу на протяжении периода ожидания
FR_LOCKED, (16), функция отклонена в связи с sharing политикой доступа, см. константу _FS_SHARE
FR_NOT_ENOUGH_CORE, (17) рабочий буфер не может быть создан
FR_TOO_MANY_OPEN_FILES (18) количество открытых файлов более _FS_SHARE
} FRESULT;
В FatFs есть набор констант, которые позволяют/запрещают использовать некоторые операции, содержаться они в файле ffconf.h. Эти константы можно просмотреть в таблице ниже (но использовать их нужно зная работу и понимая что делаешь):
Теперь опишем функции «конечного пользователя» в FatFs:
1. FRESULT f_mount (BYTE Drive, FATFS* FileSystemObject);
Связывает FAT систему на логическом диске с номером Drive и структуру, на которую указывает указатель FileSystemObject. Для работы все логические тома должны быть связаны с соответствующими структурами. Монтирование разделов происходит при первом обращении к разделу. Для отмены связывания нужно сделать f_mount (Drive, NULL); Может возвращать: FR_OK, FR_INVALID_DRIVE.
2. FRESULT f_open (FIL* FileObject, const TCHAR* FileName, BYTE ModeFlags);
Открывает/создает новый файл. FileObject – указатель на структуру файла в FatFs, FileName – путь к файлу в виде строки, заканчивающейся на «/0». ModeFlags определяют, будет ли создан или открыт существующий файл, эти флаги приведены в таблице ниже. Флаги могут быть комбинированы с помощью побитового И ( операция «|»).
Функция может возвращать: FR_OK, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_EXIST, FR_DENIED, FR_NOT_READY, FR_WRITE_PROTECTED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_LOCKED.
Выполнение с флагами FA_WRITE, FA_CREATE_ALWAYS, FA_CREATE_NEW и FA_OPEN_ALWAYS, невозможно, если константа _FS_READONLY == 1.
3. FRESULT f_close (FIL* FileObject);
Закрывает ранее открытый файл FileObject, все данные с кэша пишутся на носитель, FileObject уничтожается. Может возвращать: FR_OK (0), FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_OBJECT.
4. FRESULT f_read (FIL* FileObject, void* Buffer, UINT ByteToRead, UINT* ByteRead);
Считывает из ранее открытого файла FileObject BytesToRead байт в буфер, на который указывает Buffer. На количество считанных байт указывает ByteRead. Если *ByteRead < ByteToRead, то мы достигли конца файла при чтении. Курсор чтения/записи файла увеличивается на количество считанных байт. Может возвращать FR_OK, FR_DENIED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_OBJECT.
5. FRESULT f_write (FIL* FileObject, const void* Buffer, UINT ByteToWrite, UINT* ByteWritten);
Пишет в ранее открытый файл FileObject BytesToWrite из буфера, на который указывает указатель Buffer. На количество записанных байт указывает ByteWritten. Если *ByteWritten < ByteToWrite, то свободное место на разделе закончилось. При этом может потребоваться значительно время на выполнение функции. Может возвращать FR_OK (0), FR_DENIED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_OBJECT.
Доступна для использования, когда константа _FS_READONLY == 0.
6. FRESULT f_lseek (FIL* FileObject, DWORD Offset);
Перемещает курсор внутри открытого файла FileObject на Offset байт от начала. Если курсор выходит за границы файла, и файл открыт для записи, то происходит увеличение размера файла (данные в «расширенной» области не определены). После удачного завершения функции f_seek нужно проверить параметр fptr в структуре FileObject. Если значение fptr не равняется ожидаемому значению, то возможно файл открыт для чтения и был достигнут его конец или свободное место на логическом диске закончилось. Также есть опция использования для быстрого перемещения курсора внутри файла, он с этой опцией невозможно увеличивать размер файла да и нужно кэшировать таблицу кластеров, которые занимает файл (CLMT), что требует дополнительной оперативной памяти. Для использования этой опции нужно установить значение константы _USE_FASTSEEK==1, создать массив из DWORD и указатель на этот массив записать в переменную cltbl структуры FileObject. Далее следует вызвать функцию f_seek с параметром Offset == CREATE_LINKMAP. Если она завершилась успешно, то CLMT таблица была создана. Если она завершилась с ошибкой FR_NOT_ENOUGH_CORE, то размер созданного массива недостаточный для таблицы CLMT. Требуемый размер массива для таблицы CLMT вычисляется вот так (количесво фрагментов файла+1)*2. Возвращает следующие значения: FR_OK, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_OBJECT, FR_NOT_ENOUGH_CORE. Доступна для использования когда константа _FS_MINIMIZE <= 2.
7. FRESULT f_truncate (FIL* FileObject);
Обрезает открытий файл FileObject до курсора внутри файла. Т.е. вся информация, которая находилась после курсора удаляется. Может возвращать: FR_OK, FR_DENIED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_OBJECT. Доступна, когда константы _FS_READONLY == 0 и _FS_MINIMIZE == 0.
8. FRESULT f_sync (FIL* FileObject);
Производит принудительную запись данных на из кэша на носитель. Практически такой же эффект как от f_close, только файл можно продолжить писать/читать. Пригодиться для логгеров, когда данные пишутся долго и понемногу, для предотвращения потерь данных. Может возвращать: FR_OK, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_OBJECT. Доступна, когда константа _FS_READONLY == 0.
9. FRESULT f_opendir (DIR* DirObject, const TCHAR* DirName);
Открывает (связывает) каталог с путем DirName с структурой DirObject, которая может быть использована для последующих действий, эта структура при удалении не требует вызова какой-то спец. функции. Возвращает следующие значения: FR_OK, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_READY, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM. Доступна для использования, если _FS_MINIMIZE <= 1.
10. FRESULT f_readdir (DIR* DirObject, FILINFO* FileInfo);
Данная функция считывает объект (файл, папку) из открытой директории DirObject в структуру на которую указывает указатель FileInfo. Для считывания информации о следующем объекте необходимо вызвать функцию еще раз. Если объектов в директории больше не осталось, то вместо имени файла f_name[] в структуре, на которую указывает указатель FileInfo будет содержаться NULL при этом сбрасывается read index в объекте директории и можно снова считывать данные о объектах в директории начиная с первого. При использовании данной функции есть некоторые заморочки с LFN (Long File Name), но надеюсь никому не придет в голову использовать длинные имена на кириллице. Функция возвращает следующий результат: FR_OK, FR_NOT_READY, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT. Доступна для использования, если константа _FS_MINIMIZE <= 1.
11. FRESULT f_getfree (const TCHAR* Path, DWORD* Clusters, FATFS** FileSystemObject);
Возвращает указатель на количество свободных кластеров Clusters. Путь к логическому диску задается указателем Path, объект файловой системы FAT задается указателем на указатель FileSystemObject.
Перед применением нужно использовать f_sync, иначе можно получить неверные данные. Количество секторов в кластере можно посмотреть в атрибутах FileSystemObject. Возвращает FR_OK, FR_INVALID_DRIVE, FR_NOT_READY, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM. Доступна для вызова, если _FS_READONLY == 0 и _FS_MINIMIZE == 0.
12. FRESULT f_stat (const TCHAR* FileName, FILINFO* FileInfo);
Собирает информацию про файл/каталог с путем FileName в структуру, на которую указывает FileInfo. Возвращает следующие значения: FR_OK, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_READY, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM. Доступна для вызова, когда константа _FS_MINIMIZE >= 1.
13. FRESULT f_mkdir (const TCHAR* DirName);
Создает каталог с именем DirName. Возвращает следующий результат выполнения: FR_OK, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_DENIED, FR_EXIST, FR_NOT_READY, FR_WRITE_PROTECTED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM. Доступна при константах _FS_READONLY == 0 и _FS_MINIMIZE == 0
14. FRESULT f_unlink (const TCHAR* FileName);
Удаляет файл или директорию с именем FileName. Нельзя использовать для открытых файлов! Возвращает следующие значения: FR_OK, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_DENIED, FR_NOT_READY, FR_WRITE_PROTECTED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_LOCKED. Доступна для использования, когда константы _FS_READONLY == 0 и _FS_MINIMIZE == 0.
15. FRESULT f_chmod (const TCHAR* FileName, BYTE Attribute, BYTE AttributeMask );
Изменяет атрибуты (read-only, archive etc). файла или директории с именем FileName. Сами атрибуты задаются в Attribyte. AttributeMask служит для маскирования атрибутов, меняются только атрибуты, которые разрешает менять маска. Атрибуты можно объединять с помощью побитных операций. Сами атрибуты приведены ниже в таблице.
Например, чтобы установить read-only флаг и убрать archive при этом не затронув остальные, нужно выполнить:
f_chmod("file.txt", AR_RDO, AR_RDO | AR_ARC);
Возвращает следующие результаты: FR_OK, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_READY, FR_WRITE_PROTECTED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM. Доступна для использования, если _FS_READONLY == 0 и _FS_MINIMIZE == 0.
16. FRESULT f_utime (const TCHAR* FileName, const FILINFO* TimeDate);
Изменяет время/дату создания файла или директории FileName на время/дату заданную в структуре, на которую указывает TimeDate (является структурой).
Возвращает следующие результаты: FR_OK, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_READY, FR_WRITE_PROTECTED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM. Доступна при константах _FS_READONLY == 0 and _FS_MINIMIZE == 0.
17. FRESULT f_rename (const TCHAR* OldName, const TCHAR* NewName);
Переименовывает файл/каталог с именем OldName в NewName. Нельзя переименовывать открытые файлы! Может возвращать следующие результаты FR_OK, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_READY, FR_EXIST, FR_DENIED, FR_WRITE_PROTECTED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_LOCKED. Возможно использовать, если константы _FS_READONLY == 0 и _FS_MINIMIZE == 0.
18. FRESULT f_mkfs (BYTE Drive, BYTE PartitioningRule, UINT AllocSize);
Создает файловую систему FAT на диске Drive с размером кластера AllocSize в байтах. Размер кластера должен быть 2 в степени N, но не менее размера сектора и не более размера 128 секторов. Если значение AllocSize не удовлетворяет вышеприведенным критериям – то размер кластера выбирается исходя из размера диска. Несколько разделов на носителе не поддерживаются, и все существующие будут удалены при форматировании. PartitiongRule определяет, где будет создана таблица разделов, если PartitiongRule==0, то эта таблица создается в MBR диска, и раздел считается primary, такое форматирование называется FDISK и применяется для жестких дисков и карт памяти. Если PartitiongRule==1, то таблица разделов не создается и FAT система стартует с первого сектора, такое форматирование называется SFD и применяется для дискет и оптических носителей. Подтип файловой системы (FAT12/FAT16/FAT32) выбирается исходя из размера диска и рамера кластера. Если эти параметры находятся близко к лимитам конкретной файловой системы, функция завершается с результатом FR_MKFS_ABORTED. Функция возвращает следующие результатывыполнения:FR_OK, FR_INVALID_DRIVE, FR_NOT_READY, FR_WRITE_PROTECTED, FR_NOT_ENABLED, FR_DISK_ERR, FR_MKFS_ABORTED. Доступна для использования, когда константы _FS_READOLNY == 0 и _USE_MKFS == 1.
19. FRESULT f_forward (FIL* FileObject, UINT (*Func)(const BYTE*,UINT), UINT ByteToFwd, UINT* ByteFwd);
Используется для перенаправления данных с открытого файла FileObject в пользовательскую функцию, на которую указывает указатель Func, без использования промежуточного буфера. Перенаправляется ByteToFwd байт, в области на которую указывает ByteFwd содержиться количество байт, которые были перенаправлены. Если *ByteFwd < ByteToFwd, это означает, что был достигнут конец файла, либо пользовательская функция не справляется с потоком данных. Используется для экономии оперативной памяти микроконтроллера. Может возвращать следующие значения: FR_OK, FR_DENIED, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_OBJECT. Доступна для вызова, если константы _USE_FORWARD == 1 и _FS_TINY == 1.
20. FRESULT f_chdir (const TCHAR* Path);
Изменяет текущую директорию в локальном разделе на Path. При монтировании текущая директория по умолчанию – корень раздела. Возвращает следующие результаты: FR_OK, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_READY, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM. Доступна для использования, если константа _FS_RPATH >= 1.
21. FRESULT f_chdrive (BYTE Drive);
Изменяет текущий номер локального раздела на Drive. При монтировании, по умолчанию текущий раздел считается с номером 0. Возвращает следующие результаты: FR_OK, FR_INVALID_DRIVE. Вызывается, если константа _FS_RPATH >= 1.
22. FRESULT f_getcwd (TCHAR* Buffer, UINT BufferLen);
Возвращает в Buffer полный путь к текущей директории (включая номер текущего логического раздела). BufferLen – длина буфера в байтах. Возвращает следующий результат FR_OK, FR_NOT_READY, FR_DISK_ERR, FR_INT_ERR, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_NOT_ENOUGH_CORE. Мозможно вызвать, если константа _FS_RPATH == 2.
23. TCHAR* f_gets ( TCHAR* Str, int Size, FIL* FileObject );
Считывает строку в буфер, на который указывает Str размером Size байт из открытого файла FileObject. Считывание продолжается, до того момента как будет встречен символ перевода каретки '\n', будет достигнут конец файла или буфер будет заполнен. В конце считанной строки дописывается символ '\0'. Если возникла ошибка в процессе считывания, то функция возвращает NULL. Доступна для использования, когда константа _USE_STRFUNC==1 или 2, если значение 2 устаовлено, то символы '\r' будут убираться из буфера.
24. int f_putc (TCHAR Chr, FIL* FileObject);
Функция пишет в открытый файл FileObject символ Chr. В случае удачной записи возвращает записанный символ. В случае ошибки, возвращаемое значение равно -1. Доступна, когда константы _FS_READONLY == 0 и _USE_STRFUNC == 1 или 2. Когда _USE_STRFUNC == 2, символ конца строки '\n' будет сконвертирован в "\r\n".
25. int f_puts (const TCHAR* Str, FIL* FileObject);
Пишет в открытый файл FileObject строку Str. Возвращает количество записанных символов. Если произошла ошибка, возвращаемое значение равно -1. Доступна, когда константы _FS_READONLY == 0 и _USE_STRFUNC == 1 или 2. Когда _USE_STRFUNC == 2, символ конца строки '\n' будет сконвертирован в "\r\n".
26. int f_printf (FIL* FileObject, const TCHAR* Format, ...);
Пишет в файл FileObject данные, определенные форматированием Format. Горячо любимая сишниками :). Возвращает количество записанных символов, если произошла ошибка, то возвращает -1. Пример использования:
f_printf(&fil, "%d", 1234); // "1234" f_printf(&fil, "%6d,%3d%%", -200, 5); // " -200, 5%" f_printf(&fil, "%-6u", 100); // "100 " f_printf(&fil, "%ld", 12345678L); // "12345678" f_printf(&fil, "%04x", 0xA3); // "00a3" f_printf(&fil, "%08LX", 0x123ABC); // "00123ABC" f_printf(&fil, "%016b", 0x550F); // "0101010100001111" f_printf(&fil, "%s", "String"); // "String" f_printf(&fil, "%-4s", "abc"); //"abc " f_printf(&fil, "%4s", "abc"); // " abc" f_printf(&fil, "%c", 'a'); // "a" f_printf(&fil, "%f", 10.0); // f_printf не поддерживает числа с плавающей точкой
Доступна для вызова, если константы _FS_READONLY == 0 и _USE_STRFUNC == 1 или 2. Когда _USE_STRFUNC == 2, символ конца строки '\n' будет сконвертирован в "\r\n".
Теперь про то, как отслеживать ошибки и конец файла, точнее про макросы f_eof(), f_error() которые возвращают 1, если был достигнут конец файла или обнаружена ошибка соответственно, в противном случае возвращается 0. Как видно, инструментарий в FatFs очень приличный, с его помощью напишем программу для демонтрации возможностей FatFs, о которой говорили в начале статьи:
#include <avr/io.h> #include <avr/pgmspace.h> #include <avr/interrupt.h> #include <string.h> #include "ff.h" #include "diskio.h" volatile WORD Timer; // 100Hz increment timer char buff[128]; // Read-write buffer ISR(TIMER2_COMP_vect) { Timer++; // Performance counter for this module disk_timerproc(); // Drive timer procedure of low level disk I/O module } static void IOInit () { UBRRH = 0x00; // Init UART baudrate 256000 bit per second UBRRL = 0x01; UCSRB = (1<<RXEN)|(1<<TXEN); // TX, RX enable UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); OCR2 = 90-1; // Timer2: 100Hz interval (OC2) TCCR2 = (1<<WGM21) | (1<<CS22) | (1<<CS20); TIMSK = (1<<OCIE2); // Enable TC2.oc, interrupt sei(); // Enable interrupts } void uart_transmit(char data ) // Transmit one byte to UART { while ( !( UCSRA & (1<<UDRE)) ); UDR = data; } unsigned char uart_receive (void) //Receive one byte from UART { while ( !(UCSRA & (1<<RXC)) ) ; return UDR; } void uart_transmit_message(char* msg) //Transmit string to UART { unsigned char i; i=0; while ((i<256)&(msg[i]!=0x00) ) { uart_transmit(msg[i]); i++; } } FRESULT scan_files (char* path) //Scan files and directories on path { FRESULT res; FILINFO fno; DIR dir; int i; char *fn; res = f_opendir(&dir, path); if (res == FR_OK) { i = strlen(path); for (;;) { res = f_readdir(&dir, &fno); //Read dir items if (res != FR_OK || fno.fname[0] == 0) break; if (fno.fname[0] == '.') continue; fn = fno.fname; if (fno.fattrib & AM_DIR) { // Check item type, DIR or FILE uart_transmit_message ("DIR: "); uart_transmit_message (fn); uart_transmit_message ("\r\n"); } else { uart_transmit_message ("FILE: "); uart_transmit_message (fn); uart_transmit_message ("\r\n"); } } } return res; } int main() { FRESULT f_err_code; static FATFS FATFS_Obj; FIL fil_obj; IOInit (); // Init UART and Timer2 disk_initialize(0); // Init drive f_err_code=f_mount(0, &FATFS_Obj); //Mount Fat Fs uart_transmit_message ("mounting FAT "); if(f_err_code==0) {uart_transmit_message ("OK\r\n"); f_err_code=f_mkdir ("0:newdir"); // Create newdir uart_transmit_message ("creating newdir "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); f_err_code=f_chdir ("0:newdir"); // Set newdir to current directore uart_transmit_message ("change default dir to newdir "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); f_err_code=f_mkdir ("newdir2"); //Create newdir2 into newdir uart_transmit_message ("creating newdir2 into newdir "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); f_err_code=f_open(&fil_obj, "newfile.txt",FA_CREATE_NEW|FA_WRITE); //Create newfile into newdir uart_transmit_message ("creating newfile.txt into newdir "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); f_puts ("creating and writing ok if you see this\r",&fil_obj); //Writing to newfile uart_transmit_message ("writing data to newfile.txt \r\n"); f_err_code=f_close(&fil_obj); // Close newfile uart_transmit_message ("closing newfile.txt "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message ("FAIL\r\n"); f_err_code=f_open(&fil_obj, "newfile.txt",FA_READ); //Open newfile for reading uart_transmit_message ("open newfile.txt "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); uart_transmit_message ("data in newfile.txt:\r\n "); uart_transmit_message (f_gets (buff,128,&fil_obj)); //Read data from newfile f_err_code=f_close(&fil_obj); uart_transmit_message ("closing newfile.txt "); //Close newfile if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message ("FAIL\r\n"); uart_transmit_message("scaning newdir:\r\n"); scan_files ("0:/newdir"); //Scan newdir uart_transmit_message("scaning ended\r\n"); uart_transmit_message("remove newdir2 into newdir? y/n \r\n"); if (uart_receive()=='y') {f_err_code=f_unlink ("newdir2"); // Delete newdir2 uart_transmit_message ("remoing newdir2 "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); } uart_transmit_message("remove newfile.txt into newdir? y/n \r\n"); if (uart_receive()=='y') {f_err_code=f_unlink ("newfile.txt"); //Delete newfile uart_transmit_message ("remoing newfile "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); } f_err_code=f_mount(0, NULL); //Unmount Fat Fs uart_transmit_message ("unmounting FAT "); if(f_err_code==0) uart_transmit_message ("OK\r\n"); else uart_transmit_message("FAIL\r\n"); } else uart_transmit_message("FAIL\r\n"); while (1); return 0; }
Код программы прост, Timer2 используется для определения таймаутов при работе с картой памятью, чтобы микроконтроллер не завис, когда карта не отвечает длительное время. Время таймаута задается константой _FS_TIMEOUT и по умолчанию составляет 1000 тиков Timer2.
Настроив окошко Terminal 1.9b на скорость 256000 kbps и запустив вышеприведенную программу, можем проследить за ходом выполнения программы:
А это скриншот проводника с открытым каталогом newdir2, который здесь я не удалял, чтобы продемонстрировать работу программы:
Несложно убедиться, что все работает. Теперь сделаем небольшое резюме по циклу статей про карту памяти формата SD|MMC и про файловую систему FAT. SD|MMC карта очень хорошо подходит в качестве большого массива памяти для AVR микроконтроллера. Использовав драйвер файловой системы Petit FatFs мы можем беспрепятственно считывать файлы с SD|MMC карточки памяти, отформатированной в файловой системе FAT. Дела с записью обстоят похуже, мы не можем писать большой файл с помощью Petit FatFs, также невозможно создание и удаление файлов. Но это компенсируется небольшим размером занимаемой памяти программ и оперативной памяти микроконтроллера возможно значительно уменьшить занимаемый размер флеш-памяти, и впихнуть все файловые операции в 8кб). Для создания устройства, которому нужно создавать и писать большие файлы подойдет полноценная FatFs, но возрастают требования к микроконтроллеру (их можно уменьшить тюнингом констант в ffconf.h). Надеюсь, что данная информация будет кому-то полезна при разработке.
Скачать исходный код в виде проекта для AVR Studio 4:
Скачать Проект для работы с FAT FS
Предведущая статья цикла «SD/MMC карта памяти и микроконтроллер AVR (часть 2) Система FAT, Petit FatFs.»
Первая статья цикла: «SD/MMC карта памяти и микроконтроллер AVR (часть 1) Базовые операции.»
Что-то у меня отказывается
Что-то у меня отказывается видеть SD карту на 512Мб. Точнее ее не видит именно FatFs, а disk_initialize(0); находит. В чем может быть проблема? МК Mega2560.
А как можно к этому коду
А как можно к этому коду прикрутить дислейчик от 3310 ? Переношу карту на порт pc. на pb подключаю дисплей .
в main дописываю-
int main()
{
LcdInit();
LcdClear();
LcdImage(Picture);
LcdUpdate();
подключаю библиотеку с этого сайта на дисплей и получается лажа... в окне терминала постоянно бежит строка "mounting fat ok" и на дисплее постоянно перегружается картинка...
подскажите что делать...
Тут заюзан интерфейс SPI так
Тут заюзан интерфейс SPI так что еще одно устройство посадить будет проблематично надо порядок команд переписывать и продумывать порядок работы устройства. Лучше взять дисплей работающий на шине I2C и так будет правильно!!!
Возможно ли узнать размер файла или папки?
Возможно ли узнать размер файла или папки?
А как сделать переменным имя файла?
Как сделать переменным имя файла??? Типа 110827.txt, а потом 110828.txt.
Например, 110827.txt? Т.е. чтобы были цифры 11 + 08 + 27, а из них собрать путь?
Вопрос возник, про функции
Вопрос возник, про функции power_on, power_off и power_status, для чего там используется порт D? он же никуда не подключен
Можно подключить транз по
Можно подключить транз по питанию карточки и подавать питание, когда карточка уже находится в разъеме. В моем случае это не используется.
картинки не очень и нужны, а
картинки не очень и нужны, а вот то что проекта нету это не очень хорошо
Не отображаются картинки в
Не отображаются картинки в статье
Вечером поправлю.
Вечером поправлю.
ещё сократить код?
Спасибо! Классная статья! Как-то один линуксоед показал мне афигенную опцию GCC, называется -fwhole-program она сокращает объём кода ещё почти вдвое. Правдо, рядом с
пришлось дописать
это не испортит программу?
Нет, должно все исправно
Нет, должно все исправно работать.
Правда незнаю как будет работать с оптимизацией. Это нужно экспериментально проверить.
OCR2 = 90-1; // Timer2: 100Hz
При такой постановке вопроса прерывания происходят каждые 1,44 мс при 8 Mhz, а программа расчитана на 10 мс.
Угу, здесь неправильно, но
Угу, здесь неправильно, но это не критично. Так как в данной реализации нет RTC.