Nagits's Blog

programming, fizfak science, etc…

AVR: работаем с SD\MMC-картами памяти

Разбираемся с библиотекой FatFs, пишем программу в AVR Studio, симулируем все в Proteus

 

Немного о FatFs

FatFs — это модуль файловой системы для микропроцессоров. Он написан полностью на Си и поддерживается не только AVR, но и многими другими микропроцессорами: PIC, ARM, 8051 и др. Вообще говоря, из той горы вариантов что я нашел, альтернатив этой библиотеке нет. По сравнению с другими, у FatFs сплошные плюсы — и их много! :). Даже перечислять их лень — взгляните на официальную страничку модуля — http://elm-chan.org/fsw/ff/00index_e.html (Features).

  • С помощью FatFs можно работать не только с картами памяти, но и флешками, жесткими дисками, имеющими файловую систему FATxx.
  • Модуль функционален — предоставляет множество возможностей по работе с файловой системой. Но он и занимает места немало, поэтому максимум возможностей может быть реализуем на контроллере с flash-памятью как у ATmega32 — это минимум. Для контроллеров с небольшой памятью (порядка mega16) библиотека легко трансформируется в укороченный вариант. Даже для Tiny эта библиотека не стоит в сторонке! Для AVR с крохотным flash специально разработан Petit FatFs module — урезанный FatFs.
  • FatFs великолепно документирован.

Разбираемся, что входит в состав FatFs

На момент написания статьи последняя версия, доступная для загрузки — восьмая R0.08а (August 29, 2010). Дополнительно, нужно загрузить архив с примерами FatFs sample projects — он нам пригодится.

Распаковываем архив модуля, открываем папку scr:

  • Файлы diskio.h и diskio.с отвечают за низкоуровневую работу с носителем файловой системы — SD-картой, жестким диском, usb-флешкой. Эти файлы — только каркас для будущей программы. Откройте diskio.с — внутри только заготовки ATA_disk_initialize(); MMC_disk_initialize(); USB_disk_initialize(); — самих этих функций нету. Поэтому данный файл нужно модифицировать самому, применительно к своему проекту. А функции для ATA\MMC(SD)\USB нужно искать в примерах (FatFs sample projects).
  • ff.c и ff.h — непосредственно сам модуль FatFs. Эти файлы отвечают за работу с файловой системой FATxx. Настройки этого модуля вынесены в отдельный файл ffconf.h.
  • integer.h — переопределение типов, например typedef unsigned char UCHAR;
  • option\ccXXXX.c — кодировки:

Модуль поддерживает длинные имена файлов (Long file name support in ANSI/OEM or Unicode). По дефолту, эта поддержка отключена, так как она требует дополнительное количество кода (flash-памяти) и не малое. Без данной поддержки длинные имена будут преобразованы в т.н. «8.3 filename»: «TextFile.Mine.txt» ->  «TE021F~1.TXT». Чтобы работать с длинными именами, библиотеке нужны специальные таблицы кодировок — они находятся в папке scr/option. Посмотрите на объемы файлов с китайскими-корейскими-японскими 🙂 кодировками — это уж какой контроллер надо с объемом флеш > 500 КБ!

К счастью, мы воспринимаем русский\английский текст — это файл option\ccsbcs.c объемом 30 КБ. Однако, реально, в размере он меньше, т.к. бОльшая часть файла откинется компилятором (в зависимости, какую кодировку мы выбрали):

#if _CODE_PAGE == 437
#define _TBLDEF 1
static const WCHAR Tbl[] = {...................};

#elif _CODE_PAGE == 720
#define _TBLDEF 1
static const WCHAR Tbl[] = {...................};

#elif _CODE_PAGE == 737
и т.д.

Первые шаги в создании проекта для работы с SD-картой на основе модуля FatFs

 

Итак, первое и самое главное, нам нужен файл diskio.c заточеный специально для работы с SD-картой. Таковой ищем в архиве с примерами, скачанный с официального сайта модуля. В папке avr\ находится большое количество файлов, но все что нужно — это файл mmc.c — тот самый, который умеет общаться с MMC\SD-картой. А, да, еще прихватите rtc.c и rtc.h — они нам позже пригодятся.

Давайте переименуем mmc.c на diskio.c, а старый diskio.c удалим.

Итого, в папке с проектом должны находится файлы:

  • diskio.h и новый diskio.с
  • ff.c и ff.h, ffconf.h
  • integer.h и ВСЕ
  • rtc.c и rtc.h
  • ccsbcs.c

Дополнительно подготовьте ваши любимые библиотеки для работы с UART — чтобы выводить отладочные сообщения по каналу TX (позже мы будем симулировать наш проект в Proteus).

  • Танцы с diskio.c (mmc.c)

а) Дело касается функции void disk_timerproc (void). По задумке, нам самим нужно реализовывать таймер и вызывать эту функцию каждые 10 ms. Она выполняет роль привычного нам _delay_ms. Только _delay_ms — синхронная, а эта функция получается асинхронной — в этом есть плюс.

Если вы дошли до SD-карт, то я думаю вы легко справитесь с реализацией таймера. Если вас смущает disk_timerproc, и вам больше по душе привычный _delay_ms, то я вас направлю на http://frank.circleofcurrent.com/cache/fat_sd.htm — там модифицируют diskio.c так, чтобы можно было обойтись без таймера.

UPD1: в версии PetitFatFs функции в diskio.c реализованы без таймера и disk_timerproc. Так что можете поглядеть там.

б) Последние штрихи в этом файле нужно внести в функциях void power_on (void) и void power_off (void), а также в #define CS_LOW() и #define CS_HIGH(). В первых двух функциях изменяем инициализацию порта DDRB так, как нам нужно: MISO — должно быть входом, остальные MOSI, SCK и SS должны быть выходами. Кстати, в качестве SS может сгодиться любая другая ножка контроллера (разумеется, относящаяся к порту ввода\вывода). Саму эту SS мы и указываем в директивах #define CS_LOW() и #define CS_HIGH().

Создаем проект (я работаю в AVR STUDIO), забыв о SD-карте: первым делом настраиваем UART, реализовываем таймер с частотой 10 ms. В качестве прошиваемого контроллера в проекте укажите ATMEGA32. Проверьте все в симуляторе Proteus — нужно точно убедиться что все работает, чтобы потом не искать ошибку там, где ее нет.

После, подключаем следующие файлы:

  • diskio.h и новый diskio.с
  • ff.c и ff.h, ffconf.h
  • integer.h и ВСЕ

Остальные файлы пока подключать не будем:

rtc.c и rtc.h, ccsbcs.c — они будут задействованы позже.

Не забудьте void disk_timerproc (void) поместить в прерывание вашего таймера.

Общие настройки для работы с SD-картой на основе модуля FatFs

 

Все нужные настройки находятся в файле ffconf.h — открываем его. Вот некоторые из опций:

  • _FS_TINY 1 —нужно оставить по умолчанию.
  • _FS_MINIMIZE 0 -оставляем по умолчанию: минимизация отключена, поскольку FatFs со всеми функциями вполне поместится в mega32.

Специально для упрощения работы с текстовыми файлами в файловой системе FATxx в FatFs написаны функции:

int f_putc (int, FIL*); /* Put a character to the file */
int f_puts (const char*, FIL*); /* Put a string to the file */
int f_printf (FIL*, const char*, ...); /* Put a formatted string to the file */
char* f_gets (char*, int, FIL*); /* Get a string from the file */

Их можно активизировать, выставив _USE_STRFUNC в 1 или 2. Чем различается 1 и 2 — написано у них на сайте. Честно, я непомню. Все равно нам пока это не важно:

  • _USE_STRFUNC 0.
  • Далее все оставляем по дефолту, про некоторые оставшиеся параметры расскажу по ходу статьи..

Все, с первоначальной настройкой ffconf.h закончили.

Теперь заранее создадим проект в симуляторе Proteus, чтобы потом уже не отвлекаться от программы.

Симуляция SD\MMC-карты памяти в Proteus.

Сложность тут не в подключении карты, а в образе диска, который нужно указать в настройках SD\MMC-карты. Странно, но в Proteus-е я так и не нашел какого-либо упоминания, чем нужно воспользоваться, чтобы создать образ диска .mmc. Пришлось перерыть немножко гугл с яндексом.

В итоге что-то нашел — воспользоваться нужно программой WinImage, увы, не бесплатной. Скачайте её откуда-нибудь :).

Когда скачаете, давайте попробуем создать виртуальный образ диска. Открываем меню File->New…. В окне Format выбираем Select custom image format. В следующем окне я указал файловую систему FAT12/16 и количество секторов 8192 — это соответствует 4 МБ (4096 КБ — отображено строкой ниже). Указать можете что-то свое, но помните, что в Proteus можно выбрать только целые значения 1,2,4,8…..128 МБ. Нажимаем ОК, сохраняем образ в файле sd.ima.

Перед нами пустое окно — диск пуст. Перетащите любые файлы\папки из проводника Windows в окно WinImage и сохраните образ. Внизу, в статусной строке указан объем «диска» — 4096 КБ и сколько КБ свободно. Посмотрите на файл sd.ima, он занимает сейчас менее 4 МБ — т.е. WinImage создал динамический образ, виртуально он занимает 4 МБ, реально — 4 МБ минус свободное пространство.

Чтобы этот файл sd.ima можно было скормить Proteus-у, переименуйте его в sd.mmc. Теперь переходим в сам симулятор, добавляем в проект mega32, MMC-карту (она же SD в нашем случае) и терминал из вкладки виртуальные инструменты. В свойствах MMC-карты указываем объем 4 МБ и файл sd.mmc. Подключаем терминал к mega32, в его свойствах укажите baud rate, какой вы будете использовать в программе для UART. Подключаем SD-карту к контроллеру: DI->MOSI, DO->MISO, CLK->SCK, CS->SS(или другая, которая указана в директивах CS_LOW() и CS_HIGH()).

Не забудьте также указать в свойствах контроллера тактовую частоту кварца и соответствующие фьюзы. Прошивку укажем позже.

Схема в Proteus

Схема в Proteus

Итак, начинаем с самого простого:

***********I. ТОЛЬКО ЧТЕНИЕ КАРТЫ; БЕЗ ПОДДЕРЖКИ ДЛИННЫХ ИМЕН ФАЙЛОВ*********

Настройки

 

  • _FS_READONLY 1. Для начала выставляем его в 1, тем самым отгораживая себя пока что от лишних функций и файлов. Модуль выбрасывает функции f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename, f_truncate, f_getfree — все, что связано с записью. (Кстати модуль FatFs с _FS_READONLY 1, хорошо укорачивается в размере и в итоге, помещается на ATmega16).
  • _USE_LFN 0

_CODE_PAGE нам не важна(оставьте какая есть), т.к. _USE_LFN(поддержка длинных имен файлов) для начала мы отключим (отключено по умолчанию).

Разбираемся с основными READ_ONLY-функциями модуля FatFs

 

Да, еще раз напомню, что на официальном сайте хорошо документированы все функции модуля, для некоторых приведены примеры на Си. Все доступные функции можно посмотреть на сайте или открываем файл ff.h. Все функции модуля (за исключением дополнительных, включаемых флагом _USE_STRFUNC: f_puts, f_printf и др.) возвращают FRESULT. В случае успешного выполнения функции возвращают (FRESULT)FR_OK = 0, в ином случае FRESULT \= 0 — полный их перечень приведен над списком функций в файле ff.h.

Самое первое и главное, нужно карту инициализировать. Для этого вызывается функция

DSTATUS disk_initialize (
	BYTE drv		/* Physical drive nmuber (0) */
)

, определенная в diskio.c.

Возвращает …

/* Disk Status Bits (DSTATUS) */

#define STA_NOINIT  0x01  /* Drive not initialized */
#define STA_NODISK  0x02  /* No medium in the drive */
#define STA_PROTECT  0x04  /* Write protected */

…в случае определенных проблем с инициализацией карточки или 0x00 в случае успеха.

Далее «монтируется» логический раздел функцией

FRESULT f_mount (BYTE, FATFS*);  /* Mount/Unmount a logical drive */

Т.е. мы указываем модулю FatFS, с каким разделом носителя нужно работать (на SD-картах и USB-флешках обычно один раздел, а вот харды зачастую разбиты на несколько логических разделов).

Функция возвращает FR_OK=0 в случае успеха или FR_INVALID_DRIVE (The drive number is invalid) в противном случае.

Режима READ_ONLYбудет достаточно в тех проектах, где нужно всего лишь что-то считать с носителя информации. Ну вот давайте мы и попробуем вывести в терминал строку из файла, находящегося на SD-карте.

Для этого нам потребуются 3 функции — f_open, f_read и f_close. Если у вас есть опыт работы с WinAPI, например, на C++, или даже Pascal — то вам эти функции покажутся очень знакомыми.

FRESULT f_open (
  FIL* FileObject,       /* Pointer to the blank file object structure */
  const TCHAR* FileName, /* Pointer to the file neme */
  BYTE ModeFlags         /* Mode flags */
);

С помощью f_open мы открываем существующий файл или создаем новый — в зависимости от указанных флагов BYTE ModeFlags. (В случае READ_ONLY, флаги FA_WRITE   FA_OPEN_ALWAYS   FA_CREATE_NEW   FA_CREATE_ALWAYS по понятным причинам использовать нельзя.)

Первым параметром указывается ссылка на новую структуру file object (struct _FIL — файл ff.h).

Вторым — имя файла, третьим выставляем FA_READ.

После того, как файл успешно открыт (FR_OK), с ним можно работать.

FRESULT f_read (
  FIL* FileObject,    /* Pointer to the file object structure */
  void* Buffer,       /* Pointer to the buffer to store read data */
  UINT ByteToRead,    /* Number of bytes to read */
  UINT* ByteRead      /* Pointer to the variable to return number of bytes read */
);

Первым параметром указываем ссылку на заполненную предыдущей функцией структуру FileObject, вторым — ссылку на переменную(или массив переменных), куда будем записывать то, что читаем из файла. ByteToRead — сколько байт читаем из файла, и, ByteRead — указатель на переменную, в которую запишется, сколько было байт считано в итоге после завершения функции. Это нужно для контроля, если вдруг функция неожиданно 🙂 уткнется в конец файла, т.е. если мы находимся в 5-ти байтах от конца файла, а пытаемся читать 20. Тогда будет считано только 5, а 15 остануться нетронутыми. Я это покажу в примере ниже..

Если вы истинный джентельмен, то после использования файла его нужно закрыть:

FRESULT f_close (
  FIL* FileObject     /* Pointer to the file object structure */
);

Демонстрация чтения с карты

Итак, давайте создадим файл foo.txt в любой папке windows, запишем в него «Привет из файла!», сохраним. Теперь открываем наш образ sd-диска в WinImage (не поленитесь, и укажите, чтобы файлы .mmc открывались в WinImage по-умолчанию) и кидаем в него foo.txt, сохраняем образ диска, обязательно закрываем WinImage, чтобы доступ к образу был свободен для симулятора.

Пишем программу:

~~~~~~~~~~~~~~~~~~~~~~
unsigned char buf[150]; //буфер для UART
static FRESULT f_err_code; //FRESULT для функций модуля
static FATFS FATFS_Obj; //структура - логический раздел
FIL fil_obj; //структура файла, с которым работаем
char var[30]=""; //буфер, сюда мы поместим то, что считаем из файла.
UINT ByteRead = 0;

int main(void)
{
	uart0_init(); //настраиваем UART. С UART работайте сами.
	sendStr("*************Чтение из файла*************"); //отправляю через UART строку на терминал. Эту функцию реализуем также сами, т.к. статья не про UART сегодня 🙂

	BYTE status = disk_initialize(0); //инициализируем карточку
	sprintf((char*)buf,"initialize return: %X",status); //возвратит 0 в случае успеха
	sendStr(buf);

	f_err_code = f_mount(0, &FATFS_Obj); //монтируем логический раздел
	sprintf((char*)buf,"f_mount return: %X",f_err_code); //возвратит FR_OK=0 в случае успеха
	sendStr(buf);

	f_err_code = f_open(&fil_obj, "foo.txt", FA_READ); //пытаемся открыть файл
	sprintf((char*)buf,"f_open return: %X",f_err_code); //возвратит FR_OK=0 в случае успеха, FR_NO_FILE=4, если файл не существует...(остальные значения см. в ff.h)
	sendStr(buf);

	f_err_code = f_read(&fil_obj,var,20,&ByteRead); //пытаемся читать 20 байтов с начала файла в переменную var
	sprintf((char*)buf,"f_read return: %X, byte read: %d",f_err_code,ByteRead); //возвратит FR_OK=0 в случае успеха, также выводим сколько байтов удалось считать
	sendStr(buf);

	sprintf((char*)buf,"var: %s",var);
	sendStr(buf);TX_NEWLINE; //выводим в терминал значение буфера

	f_close(&fil_obj); //закрываем файл
}

Строим проект, получаем и указываем в Proteus-е прошивку для контроллера. Запускаем.

Демонстрация чтения с SD-карты

Демонстрация чтения с SD-карты

На фото видно, что удалось считать лишь 16 байт из 20-ти, т.к. «заранее» был достигнут конец файла (в строке «Привет из файла!» как раз 16 байт).

 

 

***********II. ЧТЕНИЕ И ЗАПИСЬ НА КАРТУ; БЕЗ ПОДДЕРЖКИ ДЛИННЫХ ИМЕН ФАЙЛОВ*********

Настройки

  • _FS_READONLY 0. Сбрасываем флаг только чтение. Мы хотим создавать и записывать информацию в файлы и папки.

Теперь давайте отвлечемся и подумаем, что происходит, когда мы создаем на жестком диске файл или папку. Операционная система вначале записывает аттрибуты файла\папки — дата создания, скрытый-системный и т.д. Откуда наш модуль будет знать, скока щас время? Правильно, нам нужен таймер (к слову —  т.е. получается, выставляя флаг _FS_READONLY 1, мы не только сильно экономим память нашего контроллера, но и нам не нужно тратить время на реализацию часов в программе или на подключение внешней микросхемы). Но в системе FATxx время записывается в специальном формате, а не в том как мы привыкли видеть. Поэтому нужна некоторая функция, которая будет преобразовывать время (попробуйте откомпилировать проект с _FS_READONLY 0, и вы увидите недостаток в виде одной функции get_fattime()).

Вот мы и подошли вплотную к файлам rtc.c, rtc.h.

В самом начале статьи я попросил вас эти файлы кинуть в папку с проектом, но не подключать их. Давайте откроем rtc.c в текстовом редакторе, в блокноте например. Сразу бросается в глаза I2C — да, для получения времени здесь используется внешняя микросхема с интерфейсом I2C. Часы реального времени, конечно, можно реализовать и самому внутри того же mega32. Однако статья не о том, поэтому мы подправим нужную нам функцию DWORD get_fattime() таким образом, чтобы она всегда возвращала одно и тоже время. Исходную функцию DWORD get_fattime() вы можете найти в архиве с примерами в файле main.c:

DWORD get_fattime ()
{
	RTC rtc;

	/* Get local time */
	rtc_gettime(&rtc); //определена в rtc.c

	/* Pack date and time into a DWORD variable */
	return	  ((DWORD)(rtc.year - 1980) << 25)
			| ((DWORD)rtc.month << 21)
			| ((DWORD)rtc.mday << 16)
			| ((DWORD)rtc.hour << 11)
			| ((DWORD)rtc.min << 5)
			| ((DWORD)rtc.sec >> 1);
}

Давайте изменим её, чтобы она всегда возвращала нам одно и тоже время. Т.е. rtc.c и rtc.h нам вообще не понадобятся, подключать их к проекту не нужно.

Вот такую функцию вставьте в программу:

DWORD get_fattime ()
{
	/* Pack date and time into a DWORD variable */
	return	  ((DWORD)(/*rtc.year*/2010 - 1980) << 25)
			| ((DWORD)/*rtc.month*/10 << 21)
			| ((DWORD)/*rtc.mday*/7 << 16)
			| ((DWORD)/*rtc.hour*/1 << 11)
			| ((DWORD)/*rtc.min*/26 << 5)
			| ((DWORD)/*rtc.sec*/6 >> 1);
}

Она всегда будет записывать в атрибуты создания\изменения файла дату 07.10.2010 01:26:06

  • _USE_LFN 0

_CODE_PAGE нам не важна(оставьте какая есть), т.к. _USE_LFN(поддержка длинных имен файлов) для начала мы отключим (оно отключено по умолчанию). Поэтому файл ccsbcs.c вначале я попросил к проекту не подключать.

 

Разбираемся с основными WRITE-функциями модуля FatFs

 

Из основных write-функций я бы выделил:

  • FRESULT f_write (FIL*, const void*, UINT, UINT*);        /* Запись данных в файл*/
  • FRESULT f_unlink (const char*);  /* Удаление файла или директории*/
  • FRESULT f_mkdir (const char*);  /* Создание директории*/
  • FRESULT f_rename (const char*, const char*);  /* Переименование файла и…….перемещение файла*/

 

Странно, но функция копирования не реализована (v0.08a). Ну, впрочем не страшно, ничего сложного в ней нет, можно и самому написать, если надобность в этом будет.

Хочется отметить функцию  f_rename, которая

1) переименовывает файл, если OldName и будущее название файла NewName указаны в одной директории, или

2) переименовывает и перемещает файл, если OldName и будущее название файла NewName указаны в разных директориях.

Привожу пример с офиц. сайта:

    /* Файл будет переименован*/
    f_rename("oldname.txt", "newname.txt");

    /* Файл будет переименован и перемещен */
    f_rename("oldname.txt", "dir1/newname.txt");

С функциями f_unlink и f_mkdir все понятно, а вот f_write нужно разобрать поподробнее.

FRESULT f_write (
 FIL* FileObject,     /* Указатель на структуру file object, которую заполняет f_open*/
 const void* Buffer,  /* Указатель на буфер, содержимое которого записываем в файл*/
 UINT ByteToWrite,    /* Количество байт, которое хотим записать*/
 UINT* ByteWritten    /* Количество байт, которое успешно было записано*/
);

Итак, давайте запишем в файл foo.txt строку, но не в начало файла, а в конец, после строки «Привет из файла!».

Для этого нам потребуется сместить указатель перед записью с помощью функции f_lseek:

FRESULT f_lseek (
 FIL* FileObject,   /* Указатель на структуру file object, которую заполняет f_open*/
 DWORD Offset       /* Смещение от начала файла */
);

По идее нужно указывать смещение 16 байт, и тогда мы «перепрыгнем» строку «Привет из файла!».

Но давайте усложним задачу и предположим, что мы не знаем что записано в файл, т.е. мы не знаем какое смещение нужно указывать. В таком случае нужно будет обратиться непосредственно к структуре _FIL и узнать размер файла.

 

Демонстрация записи на карту

~~~~~~~~~~~~~~~~~~~~~~
unsigned char buf[150]; //буфер для UART
static FRESULT f_err_code; //FRESULT для функций модуля
static FATFS FATFS_Obj; //структура - логический раздел
FIL fil_obj; //структура файла, с которым работаем
char* var="Здарова из программы"; // 🙂 Строка, которую запишем в конец файла
UINT ByteWrite = 0;

int main(void)
{
 uart0_init(); //настраиваем UART. С UART работайте сами.
 sendStr("*************Запись в файл*************"); //отправляю через UART строку на терминал. Эту функцию реализуем также сами, т.к. статья не про UART сегодня 🙂

 BYTE status = disk_initialize(0); //инициализируем карточку
 sprintf((char*)buf,"initialize return: %X",status); //возвратит 0 в случае успеха
 sendStr(buf);

 f_err_code = f_mount(0, &FATFS_Obj); //монтируем логический раздел
 sprintf((char*)buf,"f_mount return: %X",f_err_code); //возвратит FR_OK=0 в случае успеха
 sendStr(buf);

 f_err_code = f_open(&fil_obj, "foo.txt", FA_WRITE); //пытаемся открыть файл в режиме записи
 sprintf((char*)buf,"f_open return: %X",f_err_code); //возвратит FR_OK=0 в случае успеха, FR_NO_FILE=4, если файл не существует...(остальные значения см. в ff.h)
 sendStr(buf);

 f_err_code = f_lseek(&fil_obj,fil_obj.fsize);//смещаем указатель на кол-во байт, равное размеру файла. Т.е. переходим в конец файла. Заодним выведем размер файла до записи строки "Здарова из программы".
 sprintf((char*)buf,"f_lseek return: %X, size of file: %d",f_err_code,fil_obj.fsize);
 sendStr(buf);

 f_err_code = f_write(&fil_obj,var,strlen(var),&ByteWrite); //записываем в конец файла строку var. Пишем ровно столько байт, сколько букв(байт) в строке - strlen(var) = 20
 sprintf((char*)buf,"f_write return: %X, byte wrote: %d",f_err_code,ByteWrite); //возвратит FR_OK=0 в случае успеха, также выводим сколько байтов удалось записать
 sendStr(buf);

 f_close(&fil_obj); //закрываем файл
}

Строим проект, получаем и указываем в Proteus-е прошивку для контроллера. Запускаем.

Демонстрация записи на SD-карту

Демонстрация записи на SD-карту

Затем, открываем образ SD-карты и проверяем файл foo.txt:

размер стал 16+20=36 байт, содержимое — «Привет из файла!Здарова из программы». Кстати, посмотрите на дату изменения файла — 07.10.2010 01:26:06, как раз ее мы и указали в функции get_fattime().

 

***********III. ПОДДЕРЖКA ДЛИННЫХ ИМЕН ФАЙЛОВ*********

 

Для начала я продемонстрирую т.н. 8.3-имена файлов на примере получения списка файлов SD-карты.

Список файлов можно получить с помощью двух функций

  • FRESULT f_opendir (DIR*, const char*);  /* Open an existing directory */
  • FRESULT f_readdir (DIR*, FILINFO*);  /* Read a directory item */

DIR — аналог структуры FIL, только для директорий:

typedef struct _DIR {
 WORD  id;                                /* Owner file system mount ID */
 WORD  index;               /* Current index number */
 FATFS*            fs;                                /* Pointer to the owner file system object */
 DWORD           sclust;              /* Table start cluster (0:Static table) */
 DWORD           clust;                /* Current cluster */
 DWORD           sect;                /* Current sector */
 BYTE*  dir;                   /* Pointer to the current SFN entry in the win[] */
 BYTE*  fn;                                /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */
#if _USE_LFN
 WCHAR*          lfn;                   /* Pointer to the LFN working buffer */
 WORD  lfn_idx; /* Last matched LFN index (0xFFFF:No LFN) */
#endif
} DIR;

В структуре высматривается макрос _USE_LFN, т.е. если поддержка длинных имен файлов включена, то названия помещаются в переменную lfn типа WIDE CHAR (unsigned short), иначе в fn типа BYTE (unsigned char) — имена типа 8.3: file[8],ext[3].

Пример получения списка файлов с помощью f_opendir и f_readdir есть на офиц. сайте модуля FatFs. Я немного модифицировал его, что бы названия файлов выводились в терминал.

FRESULT scan_files (char* path)
{
 FRESULT res;
 FILINFO fno;
 DIR dir;
 int i;
 char *fn;
#if _USE_LFN
 static char lfn[_MAX_LFN * (_DF1S ? 2 : 1) + 1];
 fno.lfname = lfn;
 fno.lfsize = sizeof(lfn);
#endif

 res = f_opendir(&dir, path);
 if (res == FR_OK) {
 i = strlen(path);
 for (;;) {
 res = f_readdir(&dir, &fno);
 if (res != FR_OK || fno.fname[0] == 0) break;
 if (fno.fname[0] == '.') continue;
#if _USE_LFN
 fn = *fno.lfname ? fno.lfname : fno.fname;
#else
 fn = fno.fname;
#endif
 if (fno.fattrib & AM_DIR) {
 sprintf(&path[i], "/%s", fn);
 res = scan_files(path);
 if (res != FR_OK) break;
 path[i] = 0;
 } else {
 /*printf("%s/%s\n", path, fn);*/                 !!!!!! убрал
 unsigned char buf[_MAX_LFN * (_DF1S ? 2 : 1) + 1]; !!!!!добавил
 sprintf((char*)buf,"%s/%s", path, fn);     !!!!!добавил
 sendStr(buf);   !!!!!добавил
 }
 }
 }

 return res;
}

Следующий вызов процедуры выведет все файлы, находящиеся на SD-карте — scan_files_orig(«»);

 

Демонстрация вывода 8.3(коротких) имен файлов — получение списка файлов SD-карты

~~~~~~~~~~~~~~~~~~~~~~
unsigned char buf[150]; //буфер для UART
static FRESULT f_err_code; //FRESULT для функций модуля
static FATFS FATFS_Obj; //структура - логический раздел

FRESULT scan_files (char* path)
{
 ~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~
}

int main(void)
{
 uart0_init();
 sendStr("*************Список файлов на SD(8.3)*************");

 BYTE status = disk_initialize(0); //инициализируем карточку
 sprintf((char*)buf,"initialize return: %X",status); //возвратит 0 в случае успеха
 sendStr(buf);

 f_err_code = f_mount(0, &FATFS_Obj); //монтируем логический раздел
 sprintf((char*)buf,"f_mount return: %X",f_err_code); //возвратит FR_OK=0 в случае успеха
 sendStr(buf);

 f_err_code = scan_files("");//выводим всё
 sprintf((char*)buf,"scan_files returns %d",f_err_code);
 sendStr(buf);TX_NEWLINE;
}

На скриншоте хорошо видны файлы с именами в формате 8.3:

Демонстрация вывода списка файлов с SD-карты (8.3 имена файлов)

Демонстрация вывода списка файлов с SD-карты (8.3 имена файлов)

 

Настройки

 

Если вы делаете проект, где на экран выводятся файлы или список файлов пересылается на компьютер, то конечно, конечному пользователю куда приятнее было б видеть имена файлов без «~1». Давайте и настроим проект под такой случай.

Поддержка длинных имен файлов включается в файле ff.h:

  • _USE_LFN  1 (_USE_LFN  2)

После взведения этого флага важной частью программы становится кодировка: When LFN is enabled, the code page must always be set correctly. По умолчанию стоит кодировка 932 — Japanese Shift-JIS. Поддержка ихних закорючек нам не нужна, поэтому кодировку надо сменить на другую, нужную нам из списка:

  • 437 — U.S.
  • 720 — Arabic
  • 737 — Greek
  • 775 — Baltic
  • 850 — Multilingual Latin 1
  • 852 — Latin 2
  • 855 — Cyrillic
  • 857 — Turkish
  • 858 — Multilingual Latin 1 + Euro
  • 862 — Hebrew
  • 866 — Russian
  • 874 — Thai
  • 932 — Japanese Shift-JIS       (DBCS)
  • 936 — Simplified Chinese GBK   (DBCS)
  • 949 — Korean                   (DBCS)
  • 950 — Traditional Chinese Big5 (DBCS)
  • 1258 — Vietnam

 

Внимание! Смена кодировки на 866 (Russian) не даст желаемого эффекта — русское название файла будет отображаться неверно. Причина в том, что типы character в avrlibc по умолчанию имеют кодировку UTF-8. Необходимо реализовывать поддержку Unicode в вашей программе — это проблема не простая и в этой статье затронута не будет.

 

Смените кодировку на 437(U.S.):

  • _CODE_PAGE 437

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

/* Unicode - OEM code conversion */
#if _USE_LFN
WCHAR ff_convert (WCHAR, UINT);
#endif
  • Подключаем к программе файл ccsbcs.c, который в начале статьи мы поместили в папку с проектом. В этом файле содержатся таблицы кодировок и функция ff_convert.

 

 

Демонстрация вывода длинных имен файлов — получение списка файлов SD-карты

 

Код программы оставляем тем же самым, как в предыдущем примере. Ну, изменим только заголовок

sendStr("*************Список файлов на SD(long names)*************"); 

Собираем проект, переходим в Proteus и запускаем симуляцию.

Демонстрация вывода списка файлов с SD-карты (длинные имена файлов)

Демонстрация вывода списка файлов с SD-карты (длинные имена файлов)

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

 

 

Проект в AVR STUDIO и PROTEUS 7.7 SP2 + образ sd карты с расширением mmc (открыть можно в WinImage)
http://www.box.net/shared/fb2bhjkozu

Реклама

Written by nagits

Октябрь 18, 2010 в 22:08

Опубликовано в AVR, SD-CARD

Tagged with , , ,

комментария 23

Subscribe to comments with RSS.

  1. есть ли возможность считать не сначала файла, а например с 21-того байта ?

    Artur

    Май 2, 2012 at 03:49

  2. Отличная статья! Подскажите пожалуйста, что надо изменить чтобы данный проект работал на atmega128? как я понял надо изменить в diskio.c назначение портов под spi… Пробовал изменить, лучше не стало… Что делать?

    Александр

    Декабрь 12, 2011 at 11:05

    • А в свойствах проекта поменять целевой контроллер не забыли?
      Если нет, то что конкретно не работает? что выводится в терминал?

      nagits

      Декабрь 12, 2011 at 19:41

      • Целевой контроллер указал. в терминале выводится следующее:
        Enter Idle state error
        Initialization failed
        Initialization return:1
        f_mount return: 0
        Enter Idle state error
        Initialization failed
        f_open return: 3
        f_read return: 9, byte read: 0

        Александр

        Декабрь 13, 2011 at 01:26

    • К сожалению брейншторм не удался, а студии с симулятором под рукой нету (отладчик нужен).
      По крайней мере, понятно куда копать — неполадки с передачей по SPI, поскольку disk_initialize() запинается уже на передаче ‘reset & go idle’ command. Если что получится, напишите, я и читатели будут благодарны, удачи)

      nagits

      Декабрь 13, 2011 at 21:07

      • Спасибо)
        Ура, все получилось) пины портов не так прописал в diskio.c

        Александр

        Декабрь 19, 2011 at 12:20

  3. […] образа диска (про финт с протеусом и образом прочитал здесь, а то бы морочил голову очень […]

  4. f_err_code = f_write(&fil_obj,,var,strlen(var),&ByteWrite);

    Почему передаете var, а не &var? (const void* Buffer,)

    Marek

    Июль 25, 2011 at 16:27

    • Не, с var все понятно, она указатель на массив символов, пройдет — все ок.

      Marek

      Июль 25, 2011 at 16:30

      • Да, конечно, ведь var тоже самое что и &var[0]

        nagits

        Июль 25, 2011 at 17:51

  5. Самая лучшая статья которую видел про SD и avr. Подскажите реально ли уменьшить код для mega8 ?

    Albert

    Июнь 20, 2011 at 23:05

    • пользуйтесь Petit FatFs — это урезанный вариант FatFs, как раз для контроллеров с маленькой памятью (Petit FatFs влезет даже на Tiny). Но разумеется, многих функций вы там не досчитаетесь, по сравнению с полным вариантом FatFs.

      nagits

      Июнь 21, 2011 at 13:31

      • Мне нужна только запись на карту и все, если на там есть то хорошо:)

        Albert

        Июнь 21, 2011 at 22:39

  6. Спасибо Очень полезная статья
    Как правило в инете описано работу с SD карточками в режиме SPI Может кто-нибудь работал с SD карточкой в режиме SDIO с использованием DMA режима Было интресно посмотреть исходники, и вообще любуб инфу по этому вопросу. Жаль что в Протеусе нет полноценной модели на SD карточку.

    Sergey

    Март 21, 2011 at 00:40

    • SDIO с DMA — это уже другой класс процессоров, не AVR. В частности, подобные вещи есть на LPC17xx (NXP), LM3SXX (TI), STM32F10x, 20x (STMicroelectronics), AT7SAMxx/AT9SAMxx (Atmel). Это все ARM архитектура. У производителей есть готовые библиотеки на эту тему и в интернете находил порт FatFs под них. Скорости работы впечатляют (например, чтение — 5,5 МБ/сек на SDHC Class 6).

      romanetz

      Июль 14, 2012 at 06:27

  7. Воткнулся в проблему, процесс чтения остановился на f_open — выдает ошибку 3 (FR_NOT_READY). В чем может быть проблема? В протеусе или имидже или коде.
    Помогите пожалуйста!
    Можете выложить рабочий пример?

    Alhen

    Январь 10, 2011 at 15:05

    • Без проблем! Ловите… Проект в AVR STUDIO и PROTEUS 7.7 SP2 + образ sd карты с расширением mmc (открыть можно в WinImage)
      http://www.box.net/shared/fb2bhjkozu

      nagits

      Январь 10, 2011 at 19:43

      • Благодарю автора и варажаю свой респект, с примером разобрался, все работает!
        Я почему-то, сразу кинулся монтировать по данной статье проект под IAR, не обратив внимание что примеры идут под студию.
        Теперь когда есть опорная точка, опять попытаюсь переложить в IAR.
        Еще раз огромное спасибо!

        Alhen

        Январь 11, 2011 at 15:01

  8. Выражаю огромную благодарность, сам долго разбирался пол года назад как писать
    на SD карту, столкнулся с проблемой при записи файлов более 20 кб дальнейшая запись не получалась, стал делать файлы меньшего размера закрывая и создавая новый для записи. Обнаружил, что закрытие файла не всегда успешно. Может в обновленной версии это исправлено?

    gwiktor

    Январь 10, 2011 at 00:05

    • Честно говоря я более 20кб не писал, поэтому ничего сказать не могу. Историю версий можно посмотреть здесь http://elm-chan.org/fsw/ff/updates.txt, но про исправление ошибки с записью >20кб там я ничего не нашел. Поспрашивайте лучше на форумах, может у кого-то была такая же проблема…

      nagits

      Январь 10, 2011 at 13:01

  9. Спасибо за интересную статью.

    Юрий

    Ноябрь 20, 2010 at 14:36

    • Очень полезная статья, большое спасибо!!!

      Anton

      Декабрь 6, 2010 at 17:31


Обсуждение закрыто.

%d такие блоггеры, как: