понедельник, 12 января 2015 г.

[Из песочницы] Эмуляция носителя FAT32 на stm32f4


Недавно возникла данная задача — эмуляция носителя FAT32 на stm32f4.


Её необычность заключается в том, что среди обвязки микроконтроллера вовсе может не быть накопителя.


В моём случае накопитель был, но правила работы с ним не позволяли разместить файловую систему. В ТЗ, тем не менее, присутствовало требование организовать Mass Storage интерфайс для доступа к данным.


Результатом работы явился модуль, который я озаглавил «emfat», состоящий из одноимённого .h и .c файла.


Модуль независим от платформы. В прилагаемом примере он работает на плате stm32f4discovery.


Функция модуля — отдавать куски файловой системы, которые запросит usb-host, подставляя пользовательские данные, если тот пытается считать некоторый файл.



Кому может быть полезным




В первую очередь — полезно в любом техническом решении, где устройство предлагает Mass Storage интерфейс в режиме «только чтение». Эмуляция FAT32 «на лету» в этом случае позволит хранить данные как Вам угодно, без необходимости поддерживать ФС.

Во вторую очередь — полезно эстетам. Тому кто не имеет физический накопитель, но хочет видеть своё устройство в виде диска в заветном «Мой компьютер». В корне диска при этом могут находиться инструкция, драйверы, файл с описанием версии устройства, и пр.


В этом случае, нужно заметить, вместо эмуляции носителя, можно отдавать хосту части «вкомпиленного» слепка преподготовленной ФС. Однако в этом случае, вероятнее всего, расход памяти МК будет существенно выше, а гибкость решения — нулевая.


Итак, как это работает.





При попытке пользователя прочитать или записать файл, соответствующий вызов транслируется в usb-запросы, которые передаются нашему устройству. Суть запросов проста — записать или считать сектор на конечном носителе.


При этом, надо отметить, винда (или другая ОС) ведёт себя как хозяйка в плане организации хранения на носителе. Только она знает какой сектор хочет считать или записать. А захочет — и вовсе дефрагментирует нас, устроив хаотичное «жанглирование» секторами… Таким образом, функция типового USB MSC контроллера — безропотно вылить на носитель порцию в 512 байт со сдвигом, или считать порцию.


Теперь вернёмся к функции эмуляции.


Сразу предупрежу, мы не эмулируем запись на носитель. Наш «носитель» только для чтения.


Это связано с повышенной сложностью контроля за формированием файловой таблицы.


Тем не менее, в API модуля присутствует функция-пустышка emfat_write. Возможно, в будущем будет найдено решение для корректной эмуляции записи.


Задача модуля при запросе на чтение — «отдать» валидные данные. В этом и состоит его основная работа. В зависимости от запрашиваемого сектора, этими данными могут являться:



  • Запись MBR;

  • Загрузочный сектор;

  • Один из секторов файловой таблицы FAT1 или FAT2;

  • Сектор описания директории;

  • Сектор данных, относящийся к файлу.




Надо отметить, что на ускорение принятия решения «какие данные отдать» был сделан акцент. Поэтому накладные расходы были минимизированы.

Из-за того что мы отказались от обслуживания записи на накопитель, мы вольны организовать структуру хранения, как нам захочется:



Всё совершенно стандартно, кроме нескольких деталей:



  • Данные не фрагментированы;

  • Отсутствуют некоторые ненужные области FAT;

  • Свободных кластеров нет (размер носителя «подогнан» под размер данных);

  • Размер FAT-таблиц также «подогнан» под размер данных.




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

API модуля




API составлен всего из трёх функций:

bool emfat_init(emfat_t *emfat, const char *label, emfat_entry_t *entries);
void emfat_read(emfat_t *emfat, uint8_t *data, uint32_t sector, int num_sectors);
void emfat_write(emfat_t *emfat, const uint8_t *data, uint32_t sector, int num_sectors);




Из них главная функция — emfat_init.

Её пользователь вызывает один раз — при подключении нашего usb-устройства или на старте контроллера.

Параметры функции — экземпляр файловой системы (emfat), метка раздела (label) и таблица элементов ФС (entries).


Таблица задаётся как массив структур emfat_entry_t следующим образом:



static emfat_entry_t entries[] =
{
// name dir lvl offset size max_size user read write
{ "", true, 0, 0, 0, 0, 0, NULL, NULL }, // root
{ "autorun.inf", false, 1, 0, AUTORUN_SIZE, AUTORUN_SIZE, 0, autorun_read_proc, NULL }, // autorun.inf
{ "icon.ico", false, 1, 0, ICON_SIZE, ICON_SIZE, 0, icon_read_proc, NULL }, // icon.ico
{ "drivers", true, 1, 0, 0, 0, 0, NULL, NULL }, // drivers/
{ "readme.txt", false, 2, 0, README_SIZE, README_SIZE, 0, readme_read_proc, NULL }, // drivers/readme.txt
{ NULL }
};




Следующие поля присутствуют в таблице:

name: отображаемое имя элемента;

dir: является ли элемент каталогом (иначе — файл);

lvl: уровень вложенности элемента (нужен функции emfat_init чтобы понять, отнести элемент к текущему каталогу, или к каталогам выше);

offset: добавочное смещение при вызове пользовательской callback-функции чтения файла;

size: размер файла;

user: данное значение передаётся «как есть» пользовательской callback-функции чтения файла;

read: указатель на пользовательскую callback-функцию чтения файла.


Callback функция имеет следующий прототип:



void readcb(uint8_t *dest, int size, uint32_t offset, size_t userdata);




В неё передаётся адрес «куда» читать файл (параметр dest), размер читаемых данных (size), смещение (offset) и userdata.

Также в таблице присутствует поле max_size и write. Значение max_size всегда должно быть равным значению size, а значение write должно быть NULL.

Остальные две функции — emfat_write и emfat_read.


Первая, как говорилось раньше, пустышка, которую, однако, мы вызываем, если от ОС приходит запрос на запись сектора.

Вторая — функция, которую мы должны вызывать при чтении сектора. Она заполняет данные по передаваемому ей адресу (data) в зависимости от запрашиваемого сектора (sector).


При чтении сектора данных, относящегося к файлу, модуль emfat транслирует номер сектора в индекс читаемого файла и смещение, после чего вызывает пользовательскую callback-функцию чтения. Пользователь, соответственно, отдаёт «кусок» конкретного файла. Откуда он берётся библиотеке не интересно. Так, например, в проекте заказчика, файлы настроек я отдавал из внутренней flash памяти, другие файлы — из ОЗУ и spi-flash.


Код примера



#include "usbd_msc_core.h"
#include "usbd_usr.h"
#include "usbd_desc.h"
#include "usb_conf.h"
#include "emfat.h"

#define AUTORUN_SIZE 50
#define README_SIZE 21
#define ICON_SIZE 1758

const char *autorun_file =
"[autorun]\r\n"
"label=emfat test drive\r\n"
"ICON=icon.ico\r\n";

const char *readme_file =
"This is readme file\r\n";

const char icon_file[ICON_SIZE] =
{
0x00,0x00,0x01,0x00,0x01,0x00,0x18, ...
};

USB_OTG_CORE_HANDLE USB_OTG_dev;

// Экземпляр виртуальной ФС
emfat_t emfat;

// callback функции чтения файлов
void autorun_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata);
void icon_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata);
void readme_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata);

// Элементы ФС
static emfat_entry_t entries[] =
{
// name dir lvl offset size max_size user read write
{ "", true, 0, 0, 0, 0, 0, NULL, NULL }, // root
{ "autorun.inf", false, 1, 0, AUTORUN_SIZE, AUTORUN_SIZE, 0, autorun_read_proc, NULL }, // autorun.inf
{ "icon.ico", false, 1, 0, ICON_SIZE, ICON_SIZE, 0, icon_read_proc, NULL }, // icon.ico
{ "drivers", true, 1, 0, 0, 0, 0, NULL, NULL }, // drivers/
{ "readme.txt", false, 2, 0, README_SIZE, README_SIZE, 0, readme_read_proc, NULL }, // drivers/readme.txt
{ NULL }
};

// callback функция чтения файла "autorun.inf"
void autorun_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata)
{
int len = 0;
if (offset > AUTORUN_SIZE) return;
if (offset + size > AUTORUN_SIZE)
len = AUTORUN_SIZE - offset; else
len = size;
memcpy(dest, &autorun_file[offset], len);
}

// callback функция чтения файла "icon.ico"
void icon_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata)
{
int len = 0;
if (offset > ICON_SIZE) return;
if (offset + size > ICON_SIZE)
len = ICON_SIZE - offset; else
len = size;
memcpy(dest, &icon_file[offset], len);
}

// callback функция чтения файла "readme.txt"
void readme_read_proc(uint8_t *dest, int size, uint32_t offset, size_t userdata)
{
int len = 0;
if (offset > README_SIZE) return;
if (offset + size > README_SIZE)
len = README_SIZE - offset; else
len = size;
memcpy(dest, &readme_file[offset], len);
}

// Три предыдущие функции можно объединить в одну, но оставлено именно так - для наглядности

// Точка входа
int main(void)
{
emfat_init(&emfat, "emfat", entries);

#ifdef USE_USB_OTG_HS
USBD_Init(&USB_OTG_dev, USB_OTG_HS_CORE_ID, &USR_desc, &USBD_MSC_cb, &USR_cb);
#else
USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_MSC_cb, &USR_cb);
#endif

while (true)
{
}
}




Также ключевая часть модуля StorageMode.c (обработка событий USB MSC):

int8_t STORAGE_Read(
uint8_t lun, // logical unit number
uint8_t *buf, // Pointer to the buffer to save data
uint32_t blk_addr, // address of 1st block to be read
uint16_t blk_len) // nmber of blocks to be read
{
emfat_read(&emfat, buf, blk_addr, blk_len);
return 0;
}

int8_t STORAGE_Write(uint8_t lun,
uint8_t *buf,
uint32_t blk_addr,
uint16_t blk_len)
{
emfat_write(&emfat, buf, blk_addr, blk_len);
return 0;
}

int8_t STORAGE_GetMaxLun(void)
{
return STORAGE_LUN_NBR - 1;
}


Выводы




Для использования в своём проекте Mass Storage не обязательно иметь накопитель с организованной на нём ФС. Можно воспользоваться эмулятором ФС.

Библиотека реализовывает только базовые функции и имеет ряд ограничений:



  • Нет поддержки длинных имён (только 8.3);

  • Имя должно быть на латинице строчного регистра.




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

Демонстрационный проект можно скачать здесь.


Recommended article: Chomsky: We Are All – Fill in the Blank.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий