LPCXpresso Урок 10. USB-MSC. Разбираем пример.

Продолжим знакомство с LPC13xx в среде разработки CodeRed. На сей раз в рамках курса для новичков изучим пример использования встроенного класса USB mass storage (или попросту USB-флешки).

В контроллер LPC13xx встроен драйвер для USB-MSC класса. Этот драйвер используется USB загрузчиком, но и вы его можете использовать в своих целях.
Изложенный материал базируется на аппноуте AN10905 (код), который вы можете найти в разделе Application Notes сайта NXP.

Схема

Для работы нам потребуется собрать схему, приведённую в прошлом посте . В зависимости от ваших навыков работы с паяльником и понимания схемотехники можете собрать как упрощенную версию, так и полную. Отличие в обоих случаях одно и важное: FT/GPIO должен быть отключен от земли (переключатель в положение «выполнение»).
При использовании упрощенной схемы у вас возникнут некоторые проблемы с работой устройства при перезапуске отладчика.

Готовим пример

Извлекаем из архива AN10905_lpc1300_usbmemrom.zip архив lpcxpresso_usbmemrom.zip Импортируем из этого архива пример usbmemrom. Для работы нам так же потребуется CMSIS. Все остальные проекты могут быть спокойно закрыты.
Тех, кому сильно не терпится ждет облом. Связан он с тем, что памяти под образ диска в примере выделено 13*512байт = 6.5кБ, а данных самого образа числится размер 12кБайт. Это сносит винде крышу и она предлагает диск отформатировать. Но вся проблема в том, что и форматирование не помогает.
Перед запуском немного дорабатываем код:
В файле diskimg.h (строка 17) изменяем константу количества блоков нашем образе диска на 32:
#define MSC_BlockCount  32

В файле diskimg.h (строка 21) в объявление образа диска DiskImage определяем константным т.к. 16 кБайт уже не поместится в оперативной памяти
extern const unsigned char DiskImage[MSC_MemorySize];   /* Disk Image */

В файле diskimg.c (строка 6) образ диска так же делаем константным
const unsigned char DiskImage[MSC_MemorySize] = {

В файле msccallback.c (строка 52) в функции MSC_MemoryWrite комментируем строку записи, т.к. обназ константный.
//DiskImage[offset+n] = src[n];

Доработанный код есть в прикреплённом архиве.

Запускаем проект

Компилируем пример, запускаем на исполнении. Пошаговую отладку при первом запуске тут лучше не использовать. Надо дать системе возможность корректно настроить драйвер для устройства.
После подключения добавленного USB разъема к компьютеру у вас появится съемный диск (при первом подключении так же будет установлен соответствующий драйвер). На диске будет расположен текстовый файл README.TXT, гласящий:
This is a USB Memory Device demonstration for
the NXP NXP13XX Board with NXP LPC1343.
На этот диск можете попробовать что-нибудь записать — только бестолку. Функцию записи мы отключили.

Возможные проблемы

При подключении устройства у вас появляется USB-HID устройство или виртуальный COM-порт. Это связано с тем, что у вас на компьютере уже запускался другой пример. Вам надо выбрать пункт «Устранение неполадок» либо удалить драйвер вручную и повторно подключить устройство.
ОС Windows предлагает отформатировать диск — значит вы запустили проект без описанных выше изменений.

Разбор кода

Начнем сразу с основного файла usbmemrom_main.c, и по мере необходимости будем рассматривать остальные.
Функция mian начинается с инициализации структуры MscDevInfo, имеющей тип MSC_DEVICE_INFO. Данный тип определён в файле rom_drivers.h и нужен для конфигурирования (настройки) встроенного в контроллер драйвера Mass Storage Class (USB MSC). И так, собственно инициализация:
MscDevInfo.idVendor = USB_VENDOR_ID;	// VID, PID и Device ID стройства (что это и зачем описано в вики)
MscDevInfo.idProduct = USB_PROD_ID;	//  используемые константы определены в файле config.h
MscDevInfo.bcdDevice = USB_DEVICE;	//
MscDevInfo.StrDescPtr = (uint32_t)&USB_StringDescriptor[0];	// Строка описания устройства, определена в usbdesc.c, нужна для отображения на компе
MscDevInfo.MSCInquiryStr = (uint32_t)&InquiryStr[0];	// определена в msccallback.c (тоже где-то отображалась, забыл где)
MscDevInfo.BlockSize = MSC_BlockSize;	// Размер блока в нашем USB-диске (определен в diskimg.h)
MscDevInfo.BlockCount = MSC_BlockCount;	// Количество блоков в нашем USB-диске (определён в diskimg.h)
MscDevInfo.MemorySize = MSC_MemorySize;	// Общий размер памяти в нашем USB-диске (определен в diskimg.h)
MscDevInfo.MSC_Read = MSC_MemoryRead;	// Функция, корорая будет вызвана при чтении данных компьютером с нашего USB-диска
MscDevInfo.MSC_Write = MSC_MemoryWrite;	// Функция, корорая будет вызвана при записи данных компьютером на наш USB-диск

функции рассмотрим чуть попозже, а пока к структуре DeviceInfo, описанной как MSC_DEVICE_INFO. Данный тип так же определен в файле rom_drivers.h и тоже предназначен для определения типа требуемого устройства.
DeviceInfo.DevType = USB_DEVICE_CLASS_STORAGE;	// Сообщаем встроенному драйверу USB что нам требуется функционал для USB-диска
DeviceInfo.DevDetailPtr = (uint32_t)&MscDevInfo;	// Передаем встроенному драйверу USB указатель на настройки

Далее идет включение тактирования требуемой нам для работы периферии, а это первый 32битный таймер (используется для служебных целей драйвером USB, мы должны таймер разрешить, но сами при этом не можем его использовать), порты ввода-вывода, подсистема USB:
LPC_SYSCON->SYSAHBCLKCTRL |= (EN_TIMER32_1 | EN_IOCON | EN_USBREG);

После чего вызываются функция настройки встроенного драйвера:
(*rom)->pUSBD->init_clk_pins();		// настройка периферии (PLL и выводов)
for (n = 0; n < 75; n++) { }		// Необходимая небольшая задержка
(*rom)->pUSBD->init(&DeviceInfo);	// Инициализация встроенного драйвера в соответствии с заполненной конфигурацией
init_msdstate();			// Инициализация "машины состояний" для корректной работы USB-MSC

Всё это дело определено так же в файле rom_drivers.h и предназначено для работы со встроенным драйвером USB. Указатель rom инициализируется константой (ROM **)0x1fff1ff8. Это ни что иное, как адрес в ROM памяти, по которому расположена структура, содержащая адреса встроенных в ROM функций. Адрес определён в документации к контроллеру. init_msdstate() определена как макрос, обнуляющий ячейку памяти, которую встроенный драйвер будет использовать в личных целях.
После завершения инициализации подключаемся к USB шине
(*rom)->pUSBD->connect(TRUE);

после этого нам остаётся только ждать. Для этих целей используется команда WFI (Wait For Interrupt) которая усыпляет ядро до возникновения прерываний. Вся периферия же продолжает работать дальше.
while (1)		// бесконечный цикл
	__WFI();	// в ожидании событий

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

void USB_IRQHandler(void) {
(*rom)->pUSBD->isr();
}

Всё. С USB разобрались. Осталась мелочь. А именно образ нашего диска и функции для работы с ним.
Параметры (геометрия диска) и описание нашего образа приведены в файле diskimg.h. Так у нас получается 32 блока (не забываем про исправления) по 512 байт и того 32 * 512 = 16 кБайт диск:
#define MSC_BlockCount  32
#define MSC_BlockSize   512
#define MSC_MemorySize  (MSC_BlockCount*MSC_BlockSize)
extern unsigned char DiskImage[MSC_MemorySize];   // описываем образ диска

Образ находится в файле diskimg.c (так же не забываем про исправления). Данный образ для нас любезно подготовили создатели примера, приводить содержимое здесь не буду.
Ну и в конце то концов, USB драйвер надо научить работать с нашим образом. Для этих целей используются уже упомянутые функции MSC_MemoryRead и MSC_MemoryWrite, описанные в msccallback.h и реализованные в msccallback.c. Так чтение образа выглядит следующим образом:
void MSC_MemoryRead (uint32_t offset, uint8_t dst[], uint32_t length) {
  uint32_t n;

  for (n = 0; n<length; n++)
  {
    dst[n] = DiskImage[offset+n];
  }
}

Эта функция просто копирует из образа в буфер запрошенную область диска (область с позиции offset и длиной length байт).
Функция записи ни чем не сложнее:
void MSC_MemoryWrite (uint32_t offset, uint8_t src[], uint32_t length) {
  uint32_t n;

  for (n = 0; n<length; n++)
  {
    //DiskImage[offset+n] = src[n];	// Отключили, т.к. не можем писать в константный массив
  }
}

Её задача записать данные из буфера в указанную область диска (область с позиции offset и длиной length байт). Только вот само копирование мы удаляем (комментируем), т.к. мы не можем писать в константный массив. Просто учтем на будущее, для чего эта функция нужна в оригинале.

Линкер

Не рассмотренным остался следующий момент. Как гласит даташит и аппноут, встроенный драйвер USB-MSC использует в служебных целях первые 384 байта оперативной памяти контролера. И если мы ничего не предпримем, то ту же самую область задействует линкер для переменных нашего кода, он то не знает, что в контроллере будет кто-то использовать эту область. Тут то и будет лажа.
Что бы разрешить возникшую проблему, нам надо попросить линкер не использовать данную область для переменных кода. Как это сделать, описано в разделе 3.1.1 аппноута AN10905.

В качестве заключения

Встроенный драйвер позволяет нам не знать ничего про то, как работает USB шина и осуществляет обмен тот или иной класс устройств. Нам не надо писать код реализации протокола. Драйвер предоставляет нам более простой и понятный интерфейс взаимодействия, да к тому же экономит нам Flash память для реализации собственных задач.
Недосмотр в примере немного усложняет «быстрый» старт, но эта ошибка находится довольно просто в отладчике пли добавлении отладочного вывода. Наверное, поэтому она до сих пор и не исправлена, а может, просто никто и не жаловался.
Отключение записи несколько сократило функционал и привело к некорректной работе нашего устройства при удалении и копировании на него файлов. Но это только учебный пример, а не конечное устройство.
В контроллере LPC13xx так же присутствует драйвер USB-HID класса. Для него существует аппноут AN10904. Предлагаю вам ознакомиться с ним самостоятельно. Не смотря на то, что это довольно популярный способ подключения самодельных устройств к компьютеру, в своем цикле я его рассматривать не буду.
  • +1
  • 19 сентября 2011, 09:20
  • angel5a
  • 1
Файлы в топике: usbmemrom.zip

Комментарии (10)

RSS свернуть / развернуть
… как только до всех дойдут халявные Xpresso, завалят вопросами :))
0
Аффтар продолжай в том же духе!
У меня есть половина еспрессы и я намерен [когда-нибудь] её одолеть :)
0
WFI (Wait For Ivent)
Grammar Nazi негодует! Во первых, Event, во вторых инструкция — Wait For Interrupt.

Алсо, сомневаюсь чтобы в NXP так лоханулись с примером. Хотя самому поколупать его не на чем.

А вообще запихивать ROM с библиотеками — забавная фишка LPC'шек.
0
  • avatar
  • Vga
  • 20 сентября 2011, 04:57
Я тоже негодую, думал об одном, а написал другое. Пардон.

Я могу скинуть «выколупенный образ» подмонтируйте в любом никсе и увидите что размер диска, заявленный в образе, составляет 14кБ, а в коде прописано 6.5кБ. Подете и без образа по хексам просмотреть и разобрать FAT12 заголовок. Догадываюсь что такой глюк есть только в семёрке (XP нет у меня, проверить не могу).
Винда делает обращение к последнему сектору диска и повидимому обнаруживает неладное. Видать они тоже научены китайскими дисками на 500гигов с гайками.
0
Дык они ж закольцованы, все пишется/читается.
0
  • avatar
  • Vga
  • 20 сентября 2011, 17:09
Наивный :) Оригинал выглядит так:
void MSC_MemoryRead (uint32_t offset, uint8_t dst[], uint32_t length) {
  uint32_t n;
  for (n = 0; n<length; n++)
  {
    dst[n] = DiskImage[offset+n];
  }
}

void MSC_MemoryWrite (uint32_t offset, uint8_t src[], uint32_t length) {
  uint32_t n;
  for (n = 0; n<length; n++)
  {
    DiskImage[offset+n] = src[n];
  }
}


Вообще поправлюсь. Мы возвращаем размер диска 6.5кБ, а в ФС записано 12кБ. Тут винда и паникует, мол размер указан больше чем физически — значит диск не форматирован или бит. А вот при форматирование не помню что там случается. Их вообще 3 примера существует. один запускается нормально, второй предлагает форматировать и форматируется, третий (мой) предлагает отформатироваться и не форматируется. Я не стал перебирать все варианты имеющиеся у меня на компе (и не понятно откуда появившиеся) а просто выбрал определённую версию.
0
Но-но. Я не наивный. Я про китайские «диски». Как раз таки читаются/пишутся они без проблем, даже в последние сектора. Вот только данные пишутся по кругу и в результате весь файл кроме того, что в размер флешки влезло оказывается забит мусором.
В конце диска винда скорее какие-нить служебные данные ищет. Хз правда какие. FAT таблицы вроде обе в начале…
0
  • avatar
  • Vga
  • 20 сентября 2011, 17:27
Тут тоже если добавить проверку индекса и увеличить заявленный размер — всё читается и пишется. такой эксперемент желающие проведут сами. Изначально такой способ и хотел описать, с эмуляцией диска на 2 гига, но передумал.
0
Кстати, насколько гибко оно позволяет настраивать свойства девайса? Можно сделать девайс чтобы не молча жрал данные на запись, отправляя в /dev/null, а ругался «read only!»?
0
  • avatar
  • Vga
  • 21 сентября 2011, 14:08
Была идея сделать CD но не из этого драйвера, а из композитного устройства (MSC+CDC), но и то как-то обошлось и эксперементировать не стал. Самый простой способ проверить, это не устанавливать функцию записи. Либо сработает, либо по исключению отвалится.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.