Здесь записаны кое-какие сведения по результатам разборок с исходниками 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'. если это буква в нижнем регистре, *
* возвращается эквивалент в верхнем регистреed. Остальные символы передаются *
* как есть, без изменений. *
* Возвращаемое значение: приведенный "верный" символ *
******************************************************************************/
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:
* Очищая всю структуру, Вы отмечаете конец каталога.
* If this is not the case, files that are further away cannot
* be opened anymore by implementations that follow the spec. */
/*memClr(buf+(loc.Offset*32),32);*/
*(buf+(loc.Offset*32)+0) = 0xE5; /* Mark file deleted */
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;
/* Find out if we are searching in the root dir or in */
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 /* Normal directory */
{
/* Check if path given is a directory */
if(fs_findFile(fs,dirname,&loc,0)!=2)
{
//FUNC_OUT((TXT("")));
return(-1);
}
/* Find out what the startcluster of the directory is */
part_readBuf(fs->part,loc.Sector, buf);
fileEntry = *(((FileRecord*)buf) + loc.Offset);
startCluster = (((unsigned long)fileEntry.FirstClusterHigh)<<16)
+ fileEntry.FirstClusterLow;
/* Init of dir */
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. Поддержка даты и времени файлов.
Для того, чтобы создании и модификации файлов корректно указывалось время, нужно сделать две вещи:
- в файле 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.
|