Библиотека EFSL |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Здесь записаны кое-какие сведения по результатам разборок с исходниками EFSL. Если кто не знает, EFSL - бесплатная библиотека для работы с файловой системой FAT (поддерживается FAT12, FAT16 и FAT32). Расшифровывается аббревиатура как Embedded Filesystems Library (предназначенная для микроконтроллеров библиотека файловой системы). [Подключение к карте SD/MMC] Примеры вариантов подключения карты SD/MMC к микроконтроллеру ARM7 (AT91SAM7X128, AT91SAM7X256, AT91SAM7X512) приведены в таблице ниже. Можно подключить карту либо к SPI0, либо к SPI1. Например, в макетной плате Olimex SAM7-EX256 используется вариант 1 подключения карты SD/MMC. В бутлоадере, можно выбрать любой вариант в зависимости от макроопределений (см. файл at91lib\board\board.h, макрос SELECT_TO_MMC_SPI1). Для подключения сигнала выборки ~CS существует еще больше вариантов, так как каждый из SPIn имеет по 4 аппаратных выхода выборки.
[Особенности внутренней реализации и использования библиотеки EFSL] 1. Итак, EFSL поддерживает FAT12, FAT16 and FAT32. VFAT (расширение FAT, позволяющее оперировать длинными именами) не поддерживается, т. е. все имена записываются и читаются строго в формате 8.3. К сожалению, Windows штатными методами (через свойства тома) не позволяет посмотреть тип файловой системы Вашей флешки, т. е. и FAT12, и FAT16 будут отображаться одинаково как FAT. Отформатировать нужным образом (выбрать FAT12, FAT16 или FAT32) тоже нельзя, нужно использовать специальные программы. При использовании FAT12 и FAT16 есть ограничение на количество файлов в корневом каталоге - например, мне не удалось записать больше 330 файлов. 2. Библиотека EFSL, что попала мне в руки (0.2.7, 2005 год) изначально была не рассчитана на поддержку символов русского языка в именах файлов (кодировка CP866) - функция file.c\file_validateChar старательно вырезала символы русского языка (с кодами больше 127) и заменяла их на букву X. Также и заменялись символы '%'. Я немного доработал код функции file_validateChar, чтобы можно было использовать символы русского языка:
3. Для получения списка файлов и папок каталога используют последовательные вызовы функций efs_init, ls_openDir (которой в последнем параметре указывается путь до каталога, список которого надо получить) и ls_getNext (эта функция последовательно получает все имена указанного каталога и атрибут типа объекта - файл, папка или метка тома) - см. примеры из документации по EFSL. Например, чтобы получить список корневого каталога карточки MMC, нужно для функции ls_openDir в качестве пути указать "/", а чтобы прочитать содержимое папки fold01 корневого каталога, нужно указать путь "/fold01/" (путь должен обязательно завершаться слешем!). Функция ls_getNext выбирает имена друг за другом в неотсортированном порядке (сохранен порядок создания файлов, т. е. появления записей в каталоге). Навигацию можно начать заново (функция ls_getNext при этом спозиционируется снова в начало списка), если еще раз вызвать ls_openDir (функции, "закрывающей" каталог, нет). Навигацию с помощью функции можно продолжить с произвольной позиции каталога, если её предварительно запомнить в переменной типа DirList, а потом запомненное значение использовать в вызове ls_getNext. По окончании работы с файловой системой нужно вызвать функцию fs_umount. 4. Чтобы получить количество файлов в папке, нужно последовательно прочитать весь список файлов в папке (функциями ls_openDir и ls_getNext), другого более быстрого метода нет. 5. Чтобы получить дату и время создания файла, необходимо открыть файл функцией file_fopen, и прочитать 16-битные значения из полей (EmbeddedFile)file.DirEntry.WriteDate, (EmbeddedFile)file.DirEntry.WriteTime. Дата закодирована следующим образом: Время закодировано следующим образом: При этом секунды кодируются с точностью до пар секунд (в разряды 0..4 пишется число секунд, поделенное на 2). Для ускорения получения даты и времени (без открытия файла) написал функцию getFileRecord:
Входные параметры этой функции: File* file - просто указатель на тупо выделенную память под объект EmbeddedFile file. Все результат работы функции (в котором есть информация и по дате/времени) сохраняются в этом объекте. FileSystem *fs - указатель на файловую систему, которую получаем при инициализации библиотеки EFSL. Пример работы с функцией: EmbeddedFileSystem efsl; ... //инициализируем файловую систему if (!efs_initialized) { if ( efs_init (&efsl, 0) !=0 ) { //printf ( "Could not open filesystem.\n" ); ErrMsg(); return; } else { efs_initialized = true; } } //открываем нужный путь if (ls_openDir ( &list, &(efsl.myFs), arcpath ) != 0) { //printf ( "Could not open list at %s.\n", arcpath ); ErrMsg(); memset (foldname, 0, sizeof(foldname)); crm = CAT_READ_DIRS; return; } ... DirList list; ... if (0 != ls_getNext (&list)) { //обработка ошибки } EmbeddedFile file; char name[9]; char ext [4]; char full_path [TXTBUF_SIZE/*1 +8+1+3 +1 +8+1+3 +1*/]; memset(name, 0, sizeof(name)); memset(ext , 0, sizeof(ext )); strncpy(name,(char*) list->currentEntry.FileName, 8); strncpy(ext, (char*)&list->currentEntry.FileName[8],3); trim(name); trim(ext); //теперь получим дату и время файла strcpy(full_path, arcpath); strcat(full_path, name); strcat(full_path, "."); strcat(full_path, ext); if (0 != getFileRecord(&file,&efsl.myFs,full_path)) { //Обработка ошибки: printf ( "Can`t get file record %s.%s\n", name, ext ); ... } else { DecodeDate(file.DirEntry.WriteDate); DecodeTime(file.DirEntry.WriteTime); } char* DecodeDate (u16 date) { u8 m, d; char y[5]; sprintf (y,"%4i", 1980 + ((date >> 9)&0x3F)); m = (date >> 5)&0x0F; d = date & 0x1F; sprintf (datestr, "%02i.%02i.%s", d, m, &y[2]); datestr[8] = 0; return datestr; } char* DecodeTime (u16 time) { u8 h, m, s; h = (time >> 11)&0x1F; m = (time >> 5) &0x3F; s = (time &0x1F) * 2; sprintf (timestr, "%02i:%02i:%02i", h, m, s); timestr[8] = 0; return timestr; } 6. Конфигурация кэша arm7_efsl_0_2_7\inc\config.h - #define IOMAN_NUMBUFFER 6, размер кэша 512*3 = 3 кБайта. Попробовал выключить (указать #define IOMAN_NUMBUFFER 0) - чтобы проверить, как кэш влияет на скорость чтения каталога - но компилятор начал ругаться. Если указать #define IOMAN_NUMBUFFER 1, видимого замедления чтения каталога не происходит. 7. С помощью светодиода выяснил, что больше всего времени при прокрутке каталога (вычитывании данных по файлу) занимает подпрограмма dir.c\dir_findinCluster. Она вызывается переменное количество раз для одного файла, причем чем дальше по каталогу, тем бОльшее количество. Из-за этого навигация по каталогу при уходе от начала вглубь сильно замедляется. 8. Функция ls_openDir требует, чтобы в имени открываемой папки в конце присутствовал завершающий слеш. 9. В библиотеке EFSL нет функции удаления папки. Есть только функция rmfile, которая удаляет файлы. Эта функция папки удалять не может (как пустые, так и заполненные). Оказалось все довольно просто - можно взять за основу rmfile, и переделать её так, что она будет удалять и папки. В начале этой функции есть поиск файла вызовом fs_findFile. При удалении файла ожидается, что функция fs_findFile вернет 1. При удалении директории нужно ждать, что функция fs_findFile вернет 2. Вот функция rmdir для удаления папки: /* **************************************************************************** * esint16 rmdir (FileSystem *fs, euint8* dir) * Description: функция удаляет папку по имени, путем освобождения её цепочки * кластеров, и удаления его записи из директории. * Return value: 0 если О.К., -1 при ошибке типа файл не найден. */ esint16 rmdir (FileSystem *fs, euint8* dir) { FileLocation loc; ClusterChain cache; euint8* buf; euint32 firstCluster=0; if((fs_findFile(fs, (eint8*)dir, &loc,0))==2) { buf=part_getSect(fs->part,loc.Sector,IOM_MODE_READWRITE); firstCluster = ex_getb16(buf,loc.Offset*32+20); firstCluster << = 16; firstCluster += ex_getb16(buf,loc.Offset*32+26); /* Bugfix: * Очищая всю структуру, Вы отмечаете конец каталога. * Если не этот случай, то файлы дальше не могут быть больше открыты * реализациями, которые соответствуют стандарту. */ /*memClr(buf+(loc.Offset*32),32);*/ *(buf+(loc.Offset*32)+0) = 0xE5; /* Пометить файл удаленным */ part_relSect(fs -> part,buf); cache.DiscCluster = cache.LastCluster = cache.Linear = cache.LogicCluster = 0; cache.FirstCluster = firstCluster; fat_unlinkClusterChain(fs,&cache); return(0); } return(-1); } Эту функцию нужно использовать с осторожностью, так как она может "удалить" непустую папку - нет проверки на то, что удаляемая папка содержит файлы. При этом на диске образуются пропавшие кластеры (они относятся к тем файлам, которые были в этой папке). Поэтому перед вызовом rmdir нужно проверить, есть ли в удаляемой папке файлы, и если есть, то либо отказаться от удаления папки, либо предварительно удалить все файлы из папки, а потом вызвать rmdir. 10. При всех операциях записи (удаление файлов, добавление каталогов и т. п.) на карточку напрямую ничего не пишется - все идет через кэш, которым управляет подсистема ввода-вывода второго уровня - IOMan, или iomanager. Таким образом, если Вы удалите какой-нибудь файл функцией rmfile, затем вытащите карточку, не размонтировав файловую систему (вызовом fs_umount(&efsl.myFs)), то удаленный файл все равно на карточке останется. Для принудительной синхронизации кэша и носителя (без размонтирования) удобно пользоваться функцией ioman_flushAll(&efsl.myIOman). Подробнее про IOMan читайте в статье EFSL: I/O Manager (менеджер ввода/вывода). 11. Нашел и исправил несколько ошибок ui.c\short listFiles(FileSystem *fs, char *dirname). Эта функция подсчитывает количество файлов в папке dir. Одна из ошибок была в том, что эта функция не могла посчитать файлы, у которых первая буква в имени была русская. Это поправить было довольно легко. Другая ошибка была в том, что не инициализировались поля локальной переменной File dir, из-за чего не отрабатывал вызов функции file_fread(&dir,offset,512,buf) в условии цикла while. Не инициализировалось поле dir.FileStatus - поправил, добавив вызов file_setAttr(&dir, FILE_STATUS_OPEN, 1), а также поле FileSize, и из-за этого file_fread не могла прочитать файл директории. Тупо исправил ошибку, проинициализировав dir.FileSize значением -1 (потому что не смог разобраться, как узнать размер файла директории). Вот подправленная функция listFiles: short listFiles(FileSystem *fs, char *dirname) { unsigned long startCluster; unsigned char fileEntryCount; unsigned short counter=0; unsigned long offset=0; FileRecord fileEntry; FileLocation loc; unsigned char buf[512]; File dir; unsigned short i; /* Проверка - поиск идет в корневом каталоге, или нет */ if(dirname[0]=='/' && dirname[1]=='\0') { if( (fs -> type == FAT12) || (fs -> type == FAT16) ) { for(i=0; i < = (fs -> volumeId.RootEntryCount/16); i++) { loc.Sector=fs -> FirstSectorRootDir + i; part_readBuf(fs -> part,loc.Sector,buf); /* I STOPPED HERE*/ /* FIXME */ } } } else /* Обычная директория */ { /* Проверка - указывает ли путь на директорию */ if(fs_findFile(fs,dirname,&loc,0)!=2) { //FUNC_OUT((TXT(""))); return(-1); } /* Узнаем начальный кластер каталога директории */ part_readBuf(fs -> part, loc.Sector, buf); fileEntry = *(((FileRecord*)buf) + loc.Offset); startCluster = (((unsigned long)fileEntry.FirstClusterHigh) << 16) + fileEntry.FirstClusterLow; /* Инициализация директории */ dir.fs=fs; dir.Cache.LogicCluster=-1; dir.Cache.FirstCluster=startCluster; dir.DirEntry.Attribute=ATTR_DIRECTORY; file_setAttr(&dir, FILE_STATUS_OPEN, 1); dir.FileSize = (euint32)(-1); while((file_fread(&dir,offset,512,buf))) { DBG((TXT("Read 512 bytes from dir with offset %li.\n"),offset)); for(fileEntryCount=0; fileEntryCount < 16; fileEntryCount++) { fileEntry = *(((FileRecord*)buf) + fileEntryCount); if( !( (fileEntry.Attribute & 0x0F) == 0x0F ) ) { if (fileEntry.FileName[0] == 0xE5) continue; if ( (fileEntry.FileName[0] > = 'A' && fileEntry.FileName[0] < = 'Z') || (fileEntry.FileName[0] > = '0' && fileEntry.FileName[0] < = '9') || (fileEntry.FileName[0] > = 'a' && fileEntry.FileName[0] < = 'z') || (fileEntry.FileName[0] > = 0x80) ) { DBG((TXT("Filename: %s\n"),fileEntry.FileName)); counter++; } } } offset+=512; } } FUNC_OUT((TXT(""))); file_setAttr(&dir, FILE_STATUS_OPEN, 0); return(counter); } 12. При проверке корректности удаления файлов и папок из библиотеки EFSL выяснилась интересная фича. Когда я в папке удаляю файлы с русскими именами (они короткие 8.3), то место освобождается не полностью. chkdsk g: пишет, что нашел "поврежденный длинный элемент папки" - сколько было удалено файлов, столько и "поврежденных длинных элементов". Когда я удаляю после этого папку, откуда удалил все файлы, то chkdsk пишет, что ошибок нет. Когда я таким образом удаляю с диска все папки, то ошибок тоже нет, и флешка восстанавливает полностью свободное место (оно равно по объему величине сразу после форматирования). Если я тупо удаляю папку, не удаляя предварительно из неё файлы, то свободное место на диске теряется на потерянные кластеры. Таким образом, из этого можно сделать вывод - библиотека EFSL работает вполне корректно в плане освобождения места при удалении только в том случае, если не используются длинные имена файлов. Возможно, что русские имена тоже относятся к длинным именам, поэтому и происходят ошибки с "поврежденными длинными элементами". 13. Исправил следующие ошибки: ioman.c -> ioman_flushSector Было: if(!(ioman_writeSector(ioman, ioman -> sector[bufplace],buf))) { ioman_setError(ioman,IOMAN_ERR_WRITEFAIL); return(-1); } Поправил: if(-1 == ioman_writeSector(ioman, ioman -> sector[bufplace], buf)) { ioman_setError(ioman, IOMAN_ERR_WRITEFAIL); return(-1); } sc.c -> sd_writeSector Было: return(0); Поправил: return(1); Так сделал потому, что ioman_writeSector проверяет результат if_writeBuf, и он должен быть >0. 14. Поддержка даты и времени файлов. Для того, чтобы создании и модификации файлов корректно указывалось время, нужно сделать две вещи: Вот пример объявления таких функций (файл time.h): /*****************************************************************************\ * efs - General purpose Embedded Filesystem library * * --------------------- ----------------------------------- * * * * Filename : types.h * * Description : Headerfile for types.c * * * * (c)2006 Lennart Yseboodt * * (c)2006 Michael De Nil * \*****************************************************************************/ #ifndef __TIME_H_ #define __TIME_H_ /*****************************************************************************/ #include "types.h" /*****************************************************************************/ #ifdef DATE_TIME_SUPPORT #define time_getYear(void) efsl_getYear() #define time_getMonth(void) efsl_getMonth() #define time_getDay(void) efsl_getDay() #define time_getHour(void) efsl_getHour() #define time_getMinute(void) efsl_getMinute() #define time_getSecond(void) efsl_getSecond() #define time_getDate(void) fs_makeDate() #define time_getTime(void) fs_makeTime() #else #define time_getYear(void) 0x0; #define time_getMonth(void) 0x0; #define time_getDay(void) 0x0; #define time_getHour(void) 0x0; #define time_getMinute(void) 0x0; #define time_getSecond(void) 0x0; #define time_getDate(void) 0x0; #define time_getTime(void) 0x0; #endif #ifdef DATE_TIME_SUPPORT euint16 efsl_getYear(void); euint8 efsl_getMonth(void); euint8 efsl_getDay(void); euint8 efsl_getHour(void); euint8 efsl_getMinute(void); euint8 efsl_getSecond(void); #endif euint8 fs_hasTimeSupport(void); #endif Вот пример определения таких функций (файл time.c): /*****************************************************************************\ * efs - General purpose Embedded Filesystem library * * --------------------- ----------------------------------- * * * * Filename : time.c * * Description : This file contains functions for time support * * * * (c)2006 Lennart Yseboodt * * (c)2006 Michael De Nil * \*****************************************************************************/ /*****************************************************************************/ #include "time.h" #include "arm7_efsl_0_2_7\inc\fs.h" /*****************************************************************************/ #include "include/settings.h" #include "include/vars.h" euint16 efsl_getYear(void) { //return(2005); return(2000 + 10*(currTime.Year >> 4) + (currTime.Year & 0x0F)); } euint8 efsl_getMonth(void) { //return(5); return (10*(currTime.Month >> 4) + (currTime.Month & 0x0F)); } euint8 efsl_getDay(void) { //return(11); return (10*(currTime.Day>>4) + (currTime.Day&0x0F)); } euint8 efsl_getHour(void) { //return(13); return (10*(currTime.Hour >> 4) + (currTime.Hour & 0x0F)); } euint8 efsl_getMinute(void) { //return(14); return (10*(currTime.Minute>>4) + (currTime.Minute&0x0F)); } euint8 efsl_getSecond(void) { //return(40); return (10*(currTime.Second>>4) + (currTime.Second&0x0F)); } euint16 fs_makeDate(void) { #ifndef DATE_TIME_SUPPORT return(0); #else euint8 m,d; euint16 y; y = time_getYear()-1980; m = time_getMonth(); d = time_getDay(); return( (y > 127? 127 << 9: (y & 0x3F) << 9) | ((m == 0 || m > 12)? 1: (m & 0xF) << 5) | ((d == 0 || d > 31)? 1: (d & 0x1F)) ); #endif } /*****************************************************************************/ euint16 fs_makeTime(void) { #ifndef DATE_TIME_SUPPORT return(0); #else euint8 s,m,h; s = time_getSecond(); m = time_getMinute(); h = time_getHour(); return( (h > 23? 0: (h & 0x1F) << 11) | (m > 59? 0: (m & 0x3F) << 5) | (s > 59? 0: (s-s%2)/2) ); #endif } /*****************************************************************************/ euint8 fs_hasTimeSupport(void) { #ifdef DATE_TIME_SUPPORT return(1); #else return(0); #endif } /*****************************************************************************/ 15. Быстро позиционировать позицию чтения по файлу можно при помощи функции file.c -> file_setpos. Второй способ - можно просто поменять значение свойства file.FilePtr (переменная file имеет тип File или EmbeddedFile). То же самое, кстати, делает и функция file_setpos, поэтому лучше применять именно её. 16. Некоторые карты SD, которые мне попадались, требуют двойной инициализации после подачи питания на карту (после того, как она подключена к считывающему устройству с EFSL). На фото показаны две совершенно идентичные карты SD Kingston 1 Gb. Карта, что на фото слева, требует двойной инициализации физического интерфейса карты, иначе запуск инициализации файловой системы завершится с ошибкой. Та карта, что справа, работает как обычно, с одиночной инициализацией. Вот исправления (выделено красным), которые нужно внести в процедуру if_initInterface (файл at91_spi.c): esint8 if_initInterface(hwInterface* file, eint8* opts) { euint32 sc; if_spiInit(file); if_spiInit(file); //добавочный вызов инициализации интерфейса if(sd_Init(file) < 0) { //printf("Card failed to init, breaking up...\n\r"); return(-1); } if(sd_State(file) < 0) { //printf("Card didn't return the ready state, breaking up...\n\r"); return(-2); } /* file -> sectorCount = 4; */ /* FIXME ASAP!! */ /* mthomas: - somehow done - see below */ sd_getDriveSize(file, &sc); file -> sectorCount = sc/512; if( (sc%512) != 0) file -> sectorCount --; //DBG((TXT("Card Capacity is %u Bytes (%i Sectors)\n\r"), sc, file -> sectorCount)); if_spiSetSpeed(SPI_SCBR_MIN); //if_spiSetSpeed(100); /* debug - slower */ return(0); } С такой правкой нормально работают все известные мне карты SD/MMC. [Ссылки] 1. Сайт EFSL. |