Здесь записаны кое-какие сведения по результатам разборок с исходниками 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 аппаратных выхода выборки.
№ конт.
|
сигнал |
описание |
Вариант 1 подключения (SPI0, PIOA, периферия A) |
Вариант 2 подключения (SPI1, PIOA, периферия B) |
1 |
~CS |
выборка карты (режим DAT3 не используется) |
PA13 |
PA21 |
2 |
MOSI |
данные, приходящие на вход карты |
PA17 |
PA23 |
3 |
GND |
минус питания карты, сигнальная земля |
|
|
4 |
VCC |
плюс питания карты, от 2.7 до 3.6 вольт, самый лучший вариант 3 вольта |
|
|
5 |
SCK |
такты данных, поступающие на вход карты |
PA18 |
PA22 |
6 |
GND |
минус питания карты, сигнальная земля |
|
|
7 |
MISO |
данные, уходящие с выхода карты (DAT0) |
PA16 |
PA24 |
8 |
|
не используется (DAT1) |
|
|
9 |
|
не используется (DAT2) |
|
|
[Особенности внутренней реализации и использования библиотеки 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, чтобы можно было использовать символы русского языка:
/* ****************************************************************************
* Описание: эта функция принимает символ c, и если это недопустимый символ *
* для имени FAT, то возвращает символ 'X'. если это буква в нижнем регистре, *
* возвращается эквивалент в верхнем регистре. Остальные символы передаются *
* как есть, без изменений. *
* Возвращаемое значение: приведенный "верный" символ *
******************************************************************************/
euint8 file_validateChar(euint8 c)
{
if( (c < 0x20) || (c > 0x20 && c < 0x30 && c != '-' && c != '%') || (c > 0x39 && c < 0x41)
|| (c > 0x5A && c < 0x61 && c != '_') || (c > 0x7A && c < 0x80 && c != '~')
|| (c > 0xAF && c < 0xE0) || (c > 0xEF))
return(0x58);
if( c >= 0x61 && c < = 0x7A )
return(c-32);
return(c);
}
|
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. Дата закодирована следующим образом: [15][14][13][12][11][10][ 9][ 8][ 7][ 6][ 5][ 4][ 3][ 2][ 1][ 0] Y Y Y Y Y Y M M M M D D D D D
Время закодировано следующим образом: [15][14][13][12][11][10][ 9][ 8][ 7][ 6][ 5][ 4][ 3][ 2][ 1][ 0] H H H H H M M M M M M S S S S S
При этом секунды кодируются с точностью до пар секунд (в разряды 0..4 пишется число секунд, поделенное на 2).
Для ускорения получения даты и времени (без открытия файла) написал функцию getFileRecord:
/* ***************************************************************************\
Заполняет поля параметра file -> DirEntry. В случае успеха возвращает 0.
*/
esint8 getFileRecord (File* file, FileSystem *fs, eint8* filename)
{
FileLocation loc;
if(fs_findFile(fs,filename,&loc,0)==1)
{
dir_getFileStructure(fs,&file -> DirEntry,&loc);
return 0;
}
return -1;
}
|
Входные параметры этой функции:
File* file - просто указатель на тупо выделенную память под объект EmbeddedFile file. Все результат работы функции (в котором есть информация и по дате/времени) сохраняются в этом объекте.
FileSystem *fs - указатель на файловую систему, которую получаем при инициализации библиотеки EFSL. eint8* filename - указатель на строку с полным путем к нужному файлу.
Пример работы с функцией:
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
Было:
Поправил:
Так сделал потому, что ioman_writeSector проверяет результат if_writeBuf, и он должен быть >0.
14. Поддержка даты и времени файлов.
Для того, чтобы создании и модификации файлов корректно указывалось время, нужно сделать две вещи: - в файле arm7_efsl_0_2_7\inc\config.h раскомментировать строку #define DATE_TIME_SUPPORT - определить код для функций efsl_getYear, efsl_getMonth, efsl_getDay, efsl_getHour, efsl_getMinute, efsl_getSecond, fs_makeDate, fs_makeTime.
Вот пример объявления таких функций (файл 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. 2. IAR EW ARM: работа с файловой системой FAT на карточках SD/MMC. 3. IAR EW ARM: как сделать USB Mass Storage Device на основе MMC/SD. 4. EFSL: I/O Manager (менеджер ввода/вывода). 5. Другая популярная реализации файловой системы для микроконтроллеров - Petit FAT File System Module. См. также FatFs Generic FAT File System Module. |