Работа с SD картой. Библиотека Petit FatFs. Ч2

14/02/2014 - 11:09 Pavel Bobkov

Введение

В этой части мы рассмотрим библиотеку для работы с дисками, имеющими файловую систему FAT. Это всем известная библиотека Elm Chan`a - Petit FatFs. Она представляет собой облегченную версия библиотеки FatFs и предназначена для микроконтроллеров с небольшим объемом оперативной памяти. Конечно, функционал Petit FatFs сильно ограничен, но имеет смысл начать знакомство с нее, потому что в ней проще разобраться. 


В этой части изложена информация справочного характера, которую я почерпнул из документации на библиотеку, а в следующей части будет уже практический материал.

Состав библиотеки

Библиотека Petit FatFs состоит из 5 файлов. Изначально она не заточена под какой-т о конкретный носитель памяти с файловой системой FAT и может использоваться с любым из них. Для этого к библиотеке нужно добавить реализацию трех низкоуровневых функций для работы с диском - функцию инициализации, записи и чтения.

integer.h - здесь объявлены целочисленные типы данных, используемые в библиотеке

diskio.h - описаны прототипы низкоуровневых функций для работы с диском и статусные коды, возвращаемые функциями.

diskio.c - файл реализации низкоуровневых функций для работы с диском. Изначально содержит "пустышки".

pff.h - здесь задается конфигурация библиотеки, объявлены типы данных и прототипы функций для взаимодействия с файловой системой диска

pff.c - файл реализации функций для взаимодействия с файловой системой диска

Низкоуровневая работа с SD картой

Как вы понимаете из описания выше, для работы с SD картой библиотеку Petit FatFs нужно "допиливать". А именно, писать реализацию низкоуровневых функций в файле diskio.c. На сайте автора есть архив с примерами использования библиотеки Petit FatFs с различными дисками и микроконтроллерами. Есть там и пример использования SD карт с AVR. В принципе, можно не разбираться с низкоуровневой работой с SD картой, а взять уже готовый код, что я и сделаю в следующий части.

Подключение библиотеки и настройка

1. Копируем файлы библиотеки Petit FatFs в папку проекта

2. Подключаем сишные файлы (diskio.c, pff.c)к проекту внутри среды разработки

3. Задаем конфигурацию библиотеки Petit FatFs в файле pff.h

4. Включаем (инклюдим) заголовочные файлы библиотеки (integer.h, pff.h и diskio.h) в сишный файл, где будут использоваться ее функции.

5. Реализуем низкоуровневые функции для работы с диском или подставляем вместо diskio.c какой-нибудь "готовый" файл, например из примеров Elm Chan`a. 

Ну а дальше используем функции библиотеки.

Функции библиотеки Petit FatFs

FRESULT pf_mount (FATFS* fs) - смонтировать/демонтировать диск. Эта функция должна вызываться перед началом работы с диском. Она получает данные о структуре файловой системы диска и позволяет продолжить работу с его содержимым. Также функция демонтирует или виртуально отключает диск, если ее вызывать с нулевым указателем. Когда диск демонтирован, все остальные функции библиотеки возвращают FR_NOT_ENABLED (диск не смонтирован). Функция pf_mount() доступна всегда.

Параметры

FATFS *fs - указатель на объект типа FATFS. Это объект библиотеки, в котором описана структура файловой системы. На него можно взглянуть в файле pff.h. Ну а по сути, это просто переменная определенного типа, которая должна быть предварительно объявлена.

Возвращаемые значения

FR_OK - успешное завершение функции
FR_NOT_READY - не удалось инициализировать диск
FR_DISK_ERR - ошибка диска
FR_NO_FILESYSTEM - на диске нет правильного FAT раздела

Пример использования


FATFS fs; //объявление объекта FATFS
FRESULT res; //переменная для возвращаемых значений

...
//смонтировать диск
res = pf_mount(&fs);
if (res == FR_OK){
   //диск смонтирован, продолжаем работу
   ...

   //демонтируем диск, передав функции нулевой указатель
   pf_mount(NULL);
}
else{
   //не удалось смонтировать диск
   ...
}


FRESULT pf_open (const char* path) - открывает существующий файл. Функция должна вызываться перед тем, как выполняется любая работа с файлом - чтение, запись, изменение указателя. С открытым файлом можно работать до тех пор, пока не будет открыт другой файл. Функция pf_open() доступна всегда.

Параметры

const char *path - указатель на строку, показывающую путь к файлу. Строка должна заканчиваться нулевым символом. Путь нужно указывать полностью, разделяя подкаталоги символом слэша.

"test.txt" - путь к файлу test.txt, лежащему в корне диска
"Folder/code.txt" - путь к файлу сode.txt, лежащему в папке Folder

Возвращаемые значения

FR_OK - успешное завершение функции
FR_NO_FILE - не удалось найти файл
FR_NO_PATH - не удалось найти путь
FR_DISK_ERR - ошибка диска
FR_NOT_ENABLED - не смонтирован диск

Пример использования


FATFS fs; //объявление объекта FATFS
FRESULT res; //переменная для возвращаемых значений
...
//монтируем диск
res = pf_mount(&fs);

if (res == FR_OK){
   //открываем файл test.txt, лежащий в корне диска
   if(pf_open("test.txt") == FR_OK){
      ...

   }
....

}


FRESULT pf_read(void* buff, WORD btr, WORD* br) - прочитать данные из файла. Функция читает заданное количество байт из открытого файла и записывает их в буфер пользователя. Также она подсчитывает количество прочитанных байт. Если заданное количество байт и прочитанное не совпадают, значит при чтении был достигнут конец файла. Функция доступна когда параметр _USE_READ равен 1.

Параметры

void* buff - указатель на буфер, в котором будут сохраняться прочитанные данные. Если передать нулевой указатель, то прочитанные данные будут перенаправлены в другой поток, некую функцию, которую нужно описывать самостоятельно.

WORD btr - количество байт, которые нужно прочитать.

WORD* br - указатель на переменную, в которой функция pf_open сохранит количество прочитанных байтов.

Возвращаемые значения

FR_OK - успешное завершение функции
FR_DISK_ERR - ошибка диска
FR_NOT_OPENED - файл не был открыт
FR_NOT_ENABLED - не смонтирован диск

Пример использования


FATFS fs; //объявление объекта FATFS
FRESULT res; //переменная для возвращаемых значений
BYTE buff[32]; //буфер для чтения файла
WORD br; //счетчик прочитанных байт

...
//монтируем диск и открываем файл без проверки
pf_mount(&fs);
pf_open("test.txt");

//читаем 32 байта из файла
res = pf_read(buff, 32, &br);
if(res == FR_OK){

   //если прочитали меньше 32,
   // значит достигли конца файла
   if (br != 32){
      ...
   }

}


FRESULT pf_write(const void* buff, WORD btw, WORD* bw) - эта функция записывает данные в предварительно открытый файл. Она доступна только тогда, когда параметр _USE_WRITE равен 1. Из-за того, что библиотека Petit FatFs рассчитана на микроконтроллеры с маленьким объемом памяти, функцию записи имеет ряд ограничений:

- нельзя создать новый файл,
- нельзя увеличить размер файла,
- нельзя обновить временную метку файла,
- файловый атрибут "только для чтения" не может запретить запись,
- операцию записи можно запустить/остановить только на границе сектора диска. 

Последний пункт, пожалуй, требует пояснения. SD карта разбита на сектора по 512 байт. Функция pf_write может выполнять запись только с начала какого-либо сектора. При этом, если записывается меньше 512 байт, остаток заполняется нулями. Например, мы порциями записываем 612 байтов полезной информации - первый сектор будет заполнен полностью, а во втором будет 100 байтов полезной информации и 412 нулевых байтов.

Функция pf_lseek(..), о которой рассказано ниже, позволяет перемещать указатель чтения/записи данных. При использовании функции записи указатель нужно устанавливать на начало сектора. Если установить указатель в середину, то он будет округлен к нижней границе сектора и запись все равно будет выполняться с его начала. 

Таким образом, операция записи выполняется в следующей последовательности:

1. Устанавливаем указатель записи на какой-нибудь сектор.
2. Вызываем функцию записи.
3. Если записаны не все данные и конец файла не достигнут повторяем 2-й шаг. 
4. Финализируем запись, вызвав функцию записи с нулевым указателем.

Параметры

const void* buff - указатель на буфер содержащий данные для записи. Нулевое значение завершает текущую операцию записи. 

WORD btw - количество байт, которые нужно записать в файл. 

WORD* bw - указатель на 16-и разрядную переменную, в которой сохранится количество байт, которые удалось записать. По значению этой переменной можно определять, когда достигнут конец файла. 

Возвращаемые значения

FR_OK - успешное завершение функции
FR_DISK_ERR - ошибка диска
FR_NOT_OPENED - файл не был открыт
FR_NOT_ENABLED - не смонтирован диск

Пример использования


FATFS fs; //объявление объекта FATFS
FRESULT res; //переменная для возвращаемых значений
BYTE buff[32]; //буфер для чтения файла
WORD br; //счетчик прочитанных байт

.....
//монтируем диск и открываем файл без проверки
pf_mount(&fs);
pf_open("test.txt");

//устанавливаем указатель на первый сектор
pf_lseek(0);
do{
   //здесь, допустим, заполняем буфер
   ....

   //а здесь записываем его на карту
   pf_write(buff, 32, &br);
}while(br == 32);

//финализируем запись
pf_write(0, 0, &br);


FRESULT pf_lseek(DWORD offset) - смещает указатель чтения/записи открытого файла. Эта функцию используется чтобы указать с какого байта выполнять чтение, или с какого сектора диска выполнять запись. Можно выполнять абсолютное смещение указателя, передавая функции число, а можно выполнять смещение относительно текущей позиции, передавая значение указателя fs.fptr и величину смещения (смотри пример). Функция доступна, когда параметр _USE_LSEEK равен 1.

Параметры

DWORD offset - количество байт, на которые нужно сместить указатель.

Возвращаемые значения

FR_OK - успешное завершение функции
FR_DISK_ERR - ошибка диска
FR_NOT_OPENED - файл не был открыт

Пример использования



//смещаем указатель на 1000 байт
pf_lseek(1000);

//смещаем указатель на 2000 байт
// относительно текущей позиции
pf_lseek(fs.fptr + 2000);


FRESULT pf_opendir(DIR* dp, const char * path) - эта функцию открывает существующую директорию и инициализирует переменную типа DIR. В дальнейшем эта переменная может использоваться для получения списка файлов открытой директории. Функцию доступна, когда параметр _USE_DIR равен 1.

Параметры

DIR *dp - указатель на переменную типа DIR. Она должна быть предварительно объявлена.

const char* path - указатель на строку-путь к директории. Строка должна заканчиваться нулевым символом. Путь нужно указывать полностью, разделяя подкаталоги символом слэша. Написание пути к директории подчиняется тем же правилам, что и написание пути к файлу (смотри функцию pf_open).

Возвращаемые значения

FR_OK - успешное завершение функции
FR_NO_PATH - не удалось найти путь
FR_NOT_READY - не удалось инициализировать диск
FR_DISK_ERR - ошибка диска
FR_NOT_ENABLED - не смонтирован диск

Пример использования


//объявление переменных
FATFS fs;
DIR dir;

..
//монтируем диск без проверки
pf_mount(&fs);

//открываем директорию
pf_opendir(&dir, "FOLDER1");
...


FRESULT pf_readdir(DIR* dp, FILINFO* fno) - эта функцию позволяет прочитать содержимое директории. Для этого ее нужно вызывать несколько раз, пока функция не возвратит нулевую строку в одном из членов переменной fno - fno.fname[]. Функция доступна, когда параметр _USE_DIR равен 1. Читаемая директория должна быть предварительно открыта с помощью функции pf_opendir(..).

Параметры

DIR *dp - указатель на переменную типа DIR. Переменная должна быть предварительно объявлена.

FILINFO *fno - указатель на переменную типа FILINFO. Переменная должна быть предварительно объявлена.

Возвращаемые значения

FR_OK - успешное завершение функции
FR_DISK_ERR - ошибка диска
FR_NOT_OPENED - не открыта директория

Пример использования


FATFS fs;
FRESULT res;
FILINFO fno;
DIR dir;

....
//монтируем диск без проверки
pf_mount(&fs);

//открываем директорию
pf_opendir(&dir, "FOLDER1");

//чи таем содержимое директории
for(;;){
res = pf_readdir(&dir, &fno);
if ((res != FR_OK) || (fno.fname[0] == 0)){
break;
}
USART_SendStr(fno.fname);
USART_SendStr(" \r");
}

....

Ссылки 

Библиотека Petit FatFs

Продолжение следует...

Comments   

# dim 2014-02-26 13:03
Красота!
# skullhead 2014-02-26 20:14
Ура,продолжение !)Спасибо за статью!
# pyrodoctor 2014-03-10 05:36
Эх, кто бы написал статью по работе с sd без этой библиотеки! Статей про fatfs в сети полно, но затраты времени на изучение этой библиотеки, я подозреваю, сопоставимы с написанием кода с нуля.
# Pashgan 2014-03-17 20:48
Нет. Не сопоставимы. Написать код с нуля будет на порядок сложнее. Для изучения Petit FatFs достаточно двух статей, которое я написал.
# Kodex 2014-04-26 14:26
Такой вопрос, сколько эта библиотека ресурсов отжирает? Особенно интересно про RAM, ну еще сколько flash и сколько времени процессорного.
# Pashgan 2014-04-26 17:29
У автора библиотеки на сайте написано: "Very small RAM consumption (44 bytes work area + certain stack).Very small code size (2K-4K bytes)." Atmega8 тянет, на меньших не пробовал. Автор библиотеки вроде запускал ее и на attiny.

В статье про воспроизведение wav файла я писал, сколько занимает времени занимает чтение SD карты - единицы миллисекунд. Остальные времена не анализировал.
# Rus_Partizan 2014-04-26 18:58
Спасибо, всё работает.
Долго тупил насчёт пункта 3, наверно нужно было всё же дополнить немного:
"3. Задаем конфигурацию библиотеки Petit FatFs в файле pff.h"

Находим строчки, выставляем 1 напротив нужных функций (чтение, директории, указатель, запись)
#define _USE_READ 1
#define _USE_DIR 1
#define _USE_LSEEK 1
#define _USE_WRITE 1
# Pashgan 2014-04-26 19:29
Хорошо, добавлю.
# Dim 2014-09-21 11:30
а как записывать в txt правильно?
конкретно не понятно как сделать много строк - постоянно записывает разную информацию только в первую строку, дальше не идет
# ДмитрийВ 2014-09-22 11:31
Очень просто, нужно в конце каждой строки добавить "0D0A" и следующий символ будет с новой строки...
Например код "31 31 0D 0A 32 32 0D 0A"
откроется в блокноте как:
11
22
# Dim 2014-09-22 14:27
Впринципе проблему я понял, но не понял почему так происходит: делал я так - смонтировать образ, потом открыть txt, записать в него строку, потом демонтировать образ, далее снова монтировать, открыть файл, сместить указатель на количество байт и записать новую строку. При это второе обращение к txt затирало первую запись в блокноте второй записью. При этом если делать много записей при только одном обращении к блокноту, то все корректно, но не совсем удобно, если записи изредка появляются
# ДмитрийВ 2014-09-26 07:50
В статье описана функция "FRESULT pf_write(const void* buff, WORD btw, WORD* bw" , это похоже на ее поведение! Лично я делаю так : сначала запись в ОЗУ ( там у меня буфер, равный по размеру сектору во флешке, 512 байт) а после его заполнения запись в саму флеш...
# sann 2015-02-08 15:31
Пробую Petit fat на меге16 не компилируется выбивает ошибку Error: diskio.c(47): declaration 'enum DRESULT' is incompatible with the previous one: 'flash enum DRESULT' from file: 'diskio.h', line: 33

и выделяет DRESULT disk_writep {

кто работал с pff мож подскажете что делать?
# wadim_chip 2015-03-07 13:30
Такая же проблема c disk_writep...
Компилятор: Atmel Studio 6.2
Камень: mega328p

В чем может быть дело?
# wadim_chip 2015-03-07 13:33
Quoting wadim_chip:
Такая же проблема c disk_writep...
Компилятор: Atmel Studio 6.2
Камень: mega328p

В чем может быть дело?


причем на SPI микроконтроллер а нет никаких сигналов
# павел1996 2015-08-18 08:41
У меня при попытке открыть файл test.txt (rfopen())выска кивает ошибка FR_DISK_ERR . карта памяти меньше 2 гб. Если я инициализирую FATFS переменную без модификатора static то пишет что не смонтирован
# Stas 2016-06-03 11:40
Как получить список файлов и папок в корне диска ???

У вас недостаточно прав для комментирования.