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 другое, чем у меня, тогда следует переопределить следующие строки:

  1. #define SD_DI 0
  2. #define SD_DO 1
  3. #define SD_CLK 2
  4. #define SD_CS 3
  5. #define SD_INS 4
  6. #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. Пример использования:

  1. f_printf(&fil, "%d", 1234); // "1234"
  2. f_printf(&fil, "%6d,%3d%%", -200, 5); // " -200, 5%"
  3. f_printf(&fil, "%-6u", 100); // "100 "
  4. f_printf(&fil, "%ld", 12345678L); // "12345678"
  5. f_printf(&fil, "%04x", 0xA3); // "00a3"
  6. f_printf(&fil, "%08LX", 0x123ABC); // "00123ABC"
  7. f_printf(&fil, "%016b", 0x550F); // "0101010100001111"
  8. f_printf(&fil, "%s", "String"); // "String"
  9. f_printf(&fil, "%-4s", "abc"); //"abc "
  10. f_printf(&fil, "%4s", "abc"); // " abc"
  11. f_printf(&fil, "%c", 'a'); // "a"
  12. 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, о которой говорили в начале статьи:

  1. #include <avr/io.h>
  2. #include <avr/pgmspace.h>
  3. #include <avr/interrupt.h>
  4. #include <string.h>
  5. #include "ff.h"
  6. #include "diskio.h"
  7.  
  8. volatile WORD Timer; // 100Hz increment timer
  9. char buff[128]; // Read-write buffer
  10.  
  11. ISR(TIMER2_COMP_vect)
  12. { Timer++; // Performance counter for this module
  13. disk_timerproc(); // Drive timer procedure of low level disk I/O module
  14. }
  15.  
  16. static void IOInit ()
  17. { UBRRH = 0x00; // Init UART baudrate 256000 bit per second
  18. UBRRL = 0x01;
  19.  
  20. UCSRB = (1<<RXEN)|(1<<TXEN); // TX, RX enable
  21. UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
  22.  
  23. OCR2 = 90-1; // Timer2: 100Hz interval (OC2)
  24. TCCR2 = (1<<WGM21) | (1<<CS22) | (1<<CS20);
  25.  
  26. TIMSK = (1<<OCIE2); // Enable TC2.oc, interrupt
  27. sei(); // Enable interrupts
  28. }
  29.  
  30. void uart_transmit(char data ) // Transmit one byte to UART
  31. {
  32. while ( !( UCSRA & (1<<UDRE)) );
  33. UDR = data;
  34. }
  35.  
  36. unsigned char uart_receive (void) //Receive one byte from UART
  37. { while ( !(UCSRA & (1<<RXC)) ) ;
  38. return UDR; }
  39.  
  40. void uart_transmit_message(char* msg) //Transmit string to UART
  41. { unsigned char i; i=0;
  42.  
  43. while ((i<256)&(msg[i]!=0x00) )
  44. { uart_transmit(msg[i]); i++; }
  45. }
  46.  
  47. FRESULT scan_files (char* path) //Scan files and directories on path
  48. {
  49. FRESULT res;
  50. FILINFO fno;
  51. DIR dir;
  52. int i;
  53. char *fn;
  54.  
  55. res = f_opendir(&dir, path);
  56. if (res == FR_OK) {
  57. i = strlen(path);
  58. for (;;)
  59. {
  60. res = f_readdir(&dir, &fno); //Read dir items
  61. if (res != FR_OK || fno.fname[0] == 0) break;
  62. if (fno.fname[0] == '.') continue;
  63. fn = fno.fname;
  64. if (fno.fattrib & AM_DIR) { // Check item type, DIR or FILE
  65. uart_transmit_message ("DIR: ");
  66. uart_transmit_message (fn);
  67. uart_transmit_message ("\r\n");
  68. }
  69. else {
  70. uart_transmit_message ("FILE: ");
  71. uart_transmit_message (fn);
  72. uart_transmit_message ("\r\n");
  73. }
  74. }
  75. }
  76. return res;
  77. }
  78.  
  79. int main()
  80. { FRESULT f_err_code;
  81. static FATFS FATFS_Obj;
  82. FIL fil_obj;
  83.  
  84. IOInit (); // Init UART and Timer2
  85.  
  86. disk_initialize(0); // Init drive
  87. f_err_code=f_mount(0, &FATFS_Obj); //Mount Fat Fs
  88. uart_transmit_message ("mounting FAT ");
  89. if(f_err_code==0)
  90. {uart_transmit_message ("OK\r\n");
  91.  
  92. f_err_code=f_mkdir ("0:newdir"); // Create newdir
  93. uart_transmit_message ("creating newdir ");
  94. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  95. else uart_transmit_message("FAIL\r\n");
  96.  
  97. f_err_code=f_chdir ("0:newdir"); // Set newdir to current directore
  98. uart_transmit_message ("change default dir to newdir ");
  99. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  100. else uart_transmit_message("FAIL\r\n");
  101.  
  102. f_err_code=f_mkdir ("newdir2"); //Create newdir2 into newdir
  103. uart_transmit_message ("creating newdir2 into newdir ");
  104. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  105. else uart_transmit_message("FAIL\r\n");
  106.  
  107. f_err_code=f_open(&fil_obj, "newfile.txt",FA_CREATE_NEW|FA_WRITE); //Create newfile into newdir
  108. uart_transmit_message ("creating newfile.txt into newdir ");
  109. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  110. else uart_transmit_message("FAIL\r\n");
  111.  
  112. f_puts ("creating and writing ok if you see this\r",&fil_obj); //Writing to newfile
  113. uart_transmit_message ("writing data to newfile.txt \r\n");
  114.  
  115. f_err_code=f_close(&fil_obj); // Close newfile
  116. uart_transmit_message ("closing newfile.txt ");
  117. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  118. else uart_transmit_message ("FAIL\r\n");
  119.  
  120. f_err_code=f_open(&fil_obj, "newfile.txt",FA_READ); //Open newfile for reading
  121. uart_transmit_message ("open newfile.txt ");
  122. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  123. else uart_transmit_message("FAIL\r\n");
  124.  
  125. uart_transmit_message ("data in newfile.txt:\r\n ");
  126. uart_transmit_message (f_gets (buff,128,&fil_obj)); //Read data from newfile
  127.  
  128. f_err_code=f_close(&fil_obj);
  129. uart_transmit_message ("closing newfile.txt "); //Close newfile
  130. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  131. else uart_transmit_message ("FAIL\r\n");
  132.  
  133. uart_transmit_message("scaning newdir:\r\n");
  134. scan_files ("0:/newdir"); //Scan newdir
  135. uart_transmit_message("scaning ended\r\n");
  136.  
  137. uart_transmit_message("remove newdir2 into newdir? y/n \r\n");
  138. if (uart_receive()=='y')
  139. {f_err_code=f_unlink ("newdir2"); // Delete newdir2
  140. uart_transmit_message ("remoing newdir2 ");
  141. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  142. else uart_transmit_message("FAIL\r\n"); }
  143.  
  144. uart_transmit_message("remove newfile.txt into newdir? y/n \r\n");
  145. if (uart_receive()=='y')
  146. {f_err_code=f_unlink ("newfile.txt"); //Delete newfile
  147. uart_transmit_message ("remoing newfile ");
  148. if(f_err_code==0) uart_transmit_message ("OK\r\n");
  149. else uart_transmit_message("FAIL\r\n"); }
  150.  
  151. f_err_code=f_mount(0, NULL); //Unmount Fat Fs
  152. uart_transmit_message ("unmounting FAT ");
  153. if(f_err_code==0)
  154. uart_transmit_message ("OK\r\n");
  155. else
  156. uart_transmit_message("FAIL\r\n"); }
  157. else
  158. uart_transmit_message("FAIL\r\n");
  159. while (1); return 0;
  160. }

Код программы прост, 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 она сокращает объём кода ещё почти вдвое. Правдо, рядом с

  1. #include "ff.h"
  2. #include "diskio.h"

пришлось дописать
  1. #include "ff.c"
  2. #include "mmc.c"

это не испортит программу?

Нет, должно все исправно

Нет, должно все исправно работать.
Правда незнаю как будет работать с оптимизацией. Это нужно экспериментально проверить.

OCR2 = 90-1; // Timer2: 100Hz

  1. OCR2 = 90-1; // Timer2: 100Hz interval (OC2)
  2. TCCR2 = (1 < < WGM21) | (1 < < CS22) | (1 < < CS20);</b>>

При такой постановке вопроса прерывания происходят каждые 1,44 мс при 8 Mhz, а программа расчитана на 10 мс.

Угу, здесь неправильно, но

Угу, здесь неправильно, но это не критично. Так как в данной реализации нет RTC.