USB устройство как последовательный порт (и USB ScratchBoard)

Вот пришла мне такая мысль в голову.
Хотелось бы сделать наш сайт интересным даже детям. хотелось бы заинтересовать их электроникой, программированием, робототехникой!

Конечно, программирование ПЛИС - не очень простая задача, но может это им и не нужно? Есть замечательная среда программирования для детей - это Scratch. Эта среда программирования может использоваться со специальными ScratchBoard платами и к ним можно подключить датчики и моторчик.

Я уже писал раньше про Scratch. Более того, у меня даже был проект для платы Марсоход - ее можно было подключить к компьютеру как ScratchBoard. Правда был у того проекта недостаток - плата подключалась через последовательный порт. Поэтому кроме платы Марсоход нужно было еше паять дополнительную платку преобразователь уровней для RS232 - ведь последовательный порт компьютера использует уровни сигналов +12В и -12В. Кроме того, сейчас уже не у каждого компьютера есть последовательный порт. Поэтому иногда нужен еще кабель USB-Serial. В общем не очень просто...

Вот поэтому я подумал - нужно сделать новый проект для платы Марсоход.
Я сделал такую "прошивку", что теперь плата Марсоход работает как USB устройство, распознается компьютером как "последовательный порт" и посылает компьютеру данные в формате Scratch! Все стало гораздо проще! Четыре кнопочки платы Марсоход действуют как датчики сопротивление-А, сопротивление-B, сопротивление-C, сопротивление-D в программе Scratch. Значения эти датчики правда дают не аналоговые я только 0 и 100. Моторчик коллекторный, небольшой мощности (ведь у нас все питается от USB) подключается к пинам F3 и F4.

Вот посмотрите: на языке Scratch "пишем" вот такую простую программу:

программа на языке Scratch для управления моторчиком

Смысл этой программы очень простой - моторчик включается, когда срабатывает датчик света. К ноутбуку или компьютеру через USB подключаем нашу плату Марсоход (прошитую моим новым проектом), к плате моторчик и опто-транзистор - это датчик света. Смотрим как это работает:



Конечно это только простой пример использования. Ну а дальше я думаю ваша фантазия и фантазия ваших детей. Творите и программируйте!

Дальше я расскажу о самой "прошивке" для платы Марсоход. Проект довольно сложный, но в принципе, те кто заинтересуются могут просто, не разбираясь в тонкостях проекта ПЛИС, один раз зашить плату и использовать ее как ScratchBoard.


Итак. Что нам нужно?
Исходная отправная точка - USB проект, уже опубликованный ранее на нашем сайте. К плате Марсоход нужно припаять кварцевый генератор на 24Мгц. Этот генератор нам нужен для стабильной работы устройства.

В нашем USB устройстве все дескрипторы USB записаны во встроенной флеш памяти ПЛИС EPM240T100C5 на плате Марсоход. Именно эти дескрипторы определяют тип или класс, к которому принадлежит устройство. Когда устройство USB подключается к компьютеру, то операционная система читает дескрипторы из устройства и определяет какой драйвер загружать. Таким образом, нам нужно записать во флеш "правильные" дескрипторы, которые описывают устройство как последовательный порт. Если мы правильно зададим все дескрипторы и сделаем поведение нашего устройства "стандартным", то нам даже не придется писать драйвер к нашему устройству - операционная система использует уже имеющийся у нее набор драйверов (хотелось бы верить).

Вообще-то существует специальный документ (на английском языке) - спецификация USB для коммуникационных устройств. Я положил этот документ в раздел для загрузки на нашем сайте:

Вы можете выкачать его и почитать. В нем все довольно подробно расписано (на английском), но, к сожалению, мало что понятно Undecided

Попробую дать некоторые пояснения.

Когда устройство подключено к порту USB компьютера, то первое, что делает операционная система компьютера - читает из устройства так называемый DeviceDescriptor - описатель устройства. Структура DeviceDescriptor, если описывать на языке С выглядит вот так:


// USB Standard Device Descriptor
unsigned char usb_device_descriptor[] = {
0x12,   // bLength
USB_DEVICE_DESCRIPTOR_TYPE,     // bDescriptorType = 1
0x10,
0x01,   // bcdUSB = 1.10
0x02,   // bDeviceClass: CDC = 2
0x00,   // bDeviceSubClass
0x00,   // bDeviceProtocol
0x08,   // bMaxPacketSize0
0xFB,
0x09,   // idVendor = 0x09FB
0xA6,
0x60,   // idProduct = 0x60A6
0x00,
0x01,   // bcdDevice = 1.00
1,      // Index of string descriptor describing manufacturer
2,      // Index of string descriptor describing product
0,      // Index of string descriptor describing the device's serial number
0x01    // bNumConfigurations
};


Мы делаем свое USB устройство и должны запулнить эту структуру. Нужно обязательно заполнить следующие поля:

  • idVendor - я пишу 0x09FB, это id компании Альтера (надеюсь они меня не побьют? ведь  id по правилам я должен купить у USB.ORG за большие деньги, а я использую чужой, компании Альтера)
  • idProduct - берем с потолка, но помним, что у одного производителя все "продукты" пронумерованы, то есть на самом деле мы не можем взять число, уже использующееся компанией Альтера.
  • bDeviceClass - ставим число 2, это будет значить устройство типа COM порта, модема и т.д, тоесть коммуникационное устройство Communication Class Device
  • bMaxPacketSize0 - ставим 8, это обычная длина пакета для низко-скоростных устройств.

Вот у нас есть структура DeviceDescriptor длиной 18 байт. Мне нужно расположить ее во флеш микросхемы CPLD платы Марсоход по некоторому адресу. Но не все так просто. Дело в том, что USB транзакции сами по себе происходят за несколько довольно мудреных шагов, передача идет пакетами данных не более 8 байт и защищены контрольными суммами.

Поскольку мое USB устройство весьма примитивно, то я хочу во флеш разместить уже готовые пакеты, со всеми дополнительными байтами типа SYN, PID, CRC (зараннее посчитанные). Вот я это и сделал - содержимое флеш в чипе CPLD описывается файлом c с расширением *.mif (memory initialization file). Вот его фрагмент, который описывает DeviceDescriptor:


-- Descriptor Device
0080 : 4B8D; -- SYN&Len, PID
0081 : 0112; -- Data..
0082 : 0110;
0083 : 0002;
0084 : 0800;
0085 : CF10; -- CRC16
0086 : 0000;
0087 : 0000;

0088 : C38D; -- SYN&Len, PID
0089 : 09FB; -- Data
008A : 60A6;
008B : 0100;
008C : 0201;
008D : 5C21; -- CRC16
008E : 0000;
008F : 0000;

0090 : 4B87; -- SYN&Len, PID
0091 : 0100; -- Data
0092 : 8F3F; -- CRC16
0093 : 0000;


Так, DeviceDescriptor будет прочитан за 3 передачи, две по 8 байт данных и одна короткая 2 байта данных.

Вообще, чтобы ощутить всю "прелесть" и "замороченность" USB протокола можете скачать программу USB анализатора с сайта http://www.ellisys.com/products/usbex260/download.php Эта программа к аппаратному анализатору USB протокола - это такая коробочка, которая ставится между исследуемым устройством и компьютером и записывает весь USB трафик между ними. Конечно, у вас такой коробочки нет, но, запустив программу анализатора, вы сможете посмотреть уже записанные примеры для устройств разных классов и вы сможете посмотреть какие пакеты там ходят.

Добавлю, что у меня есть этот анализатор, что делает разработку USB устройств гораздо проще и удобнее.

Кроме DeviceDescriptor еще очень нужный описатель - это ConfigurationDescriptor. Он описывает как операционная система должна обращаться с устройством, какие у устройства есть Endpoints - каналы передачи данных, их тип и так далее. Вот эти дескрипторы особенно сложны для понимания Tongue out Особенно для класса CDC (Communication Device Class) - так я по правде мало что понимаю здесь. Ну вот привожу его как есть (я посмотрел множество разных примеров реализации CDC устройств и сделал свое подобное):


UCHAR usb_configuration_descriptor[] = {
/*Configuation Descriptor*/
0x09,  
/* bLength: Configuation Descriptor size */
USB_CONFIGURATION_DESCRIPTOR_TYPE,      /* bDescriptorType: Configuration */
VIRTUAL_COM_PORT_SIZE_CONFIG_DESC,       /* wTotalLength:no of returned bytes */
0x00,
0x02,   /* bNumInterfaces: 2 interface */
0x01,   /* bConfigurationValue: Configuration value */
0x00,   /* iConfiguration: Index of string descriptor describing the configuration */
0x80,   /* bmAttributes: self powered */
0x32,   /* MaxPower 0 mA */
/*Interface Descriptor*/
0x09,   /* bLength: Interface Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE,  /* bDescriptorType: Interface */
/* Interface descriptor type */

0x00,   /* bInterfaceNumber: Number of Interface */
0x00,   /* bAlternateSetting: Alternate setting */
0x01,   /* bNumEndpoints: One endpoints used */
0x02,   /* bInterfaceClass: Communication Interface Class */
0x02,   /* bInterfaceSubClass: Abstract Control Model */
0x01,   /* bInterfaceProtocol: Common AT commands */
0x00,   /* iInterface: */
/*Header Functional Descriptor*/

0x05,   /* bLength: Endpoint Descriptor size */
0x24,   /* bDescriptorType: CS_INTERFACE */
0x00,   /* bDescriptorSubtype: Header Func Desc */
0x10,   /* bcdCDC: spec release number */
0x01,
/*Call Managment Functional Descriptor*/
0x05,   /* bFunctionLength */
0x24,   /* bDescriptorType: CS_INTERFACE */
0x01,   /* bDescriptorSubtype: Call Management Func Desc */
0x03,   /* bmCapabilities: D0+D1 */
0x01,   /* bDataInterface: 1 */
/*ACM Functional Descriptor*/

0x04,   /* bFunctionLength */
0x24,   /* bDescriptorType: CS_INTERFACE */
0x02,   /* bDescriptorSubtype: Abstract Control Management desc */
0x0f,   /* bmCapabilities */
/*Union Functional Descriptor*/
0x05,   /* bFunctionLength */
0x24,   /* bDescriptorType: CS_INTERFACE */
0x06,   /* bDescriptorSubtype: Union func desc */
0x00,   /* bMasterInterface: Communication class interface */
0x01,   /* bSlaveInterface0: Data Class Interface */
/*Endpoint Descriptor*/

0x07,   /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE,   /* bDescriptorType: Endpoint */
0x82,   /* bEndpointAddress: (IN2) */
0x03,   /* bmAttributes: Interrupt */
VIRTUAL_COM_PORT_INT_SIZE,      /* wMaxPacketSize: */
0x00,
0x20,   /* bInterval: */
/*Data class interface descriptor*/
0x09,   /* bLength: Endpoint Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE,  /* bDescriptorType: */
0x01,   /* bInterfaceNumber: Number of Interface */
0x00,   /* bAlternateSetting: Alternate setting */
0x02,   /* bNumEndpoints: Two endpoints used */
0x0A,   /* bInterfaceClass: CDC */
0x00,   /* bInterfaceSubClass: */
0x00,   /* bInterfaceProtocol: */
0x00,   /* iInterface: */
/*Endpoint Descriptor*/
0x07,   /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE,   /* bDescriptorType: Endpoint */
0x03,   /* bEndpointAddress: (OUT3)*/
0x02,   /* bmAttributes: Bulk */
VIRTUAL_COM_PORT_DATA_SIZE,   /* wMaxPacketSize: */
0x00,
0x00,   /* bInterval: ignore for Bulk transfer */
/*Endpoint Descriptor*/
0x07,   /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE,   /* bDescriptorType: Endpoint */
0x83,   /* bEndpointAddress: (IN3)*/
0x02,   /* bmAttributes: Bulk */
VIRTUAL_COM_PORT_DATA_SIZE,   /* wMaxPacketSize: */
0x00,
0x00    /* bInterval: ignore for Bulk transfer */
};


Этот описатель ОС читает после назначения устройству USB адреса. Конечно, эта структура тоже будет вписана специальным образом во флеш чипа CPLD.

Я написал программу на зыке С (Microsoft Visual Studio) которая формирует из этих структур те, что мне нужны в *.mif файле. Программа есть в архиве. Она разбивает длинные дескрипторы на пакеты USB, считает контрольные суммы пакетов и т.д. В общем остается только взять и вставить в *.mif файл.

Проект для платы Марсоход, INF файлы для установки драйверов, программу для формирования mif файла из дескрипторов можно взять здесь.

Сам проект я сделал довольно быстро. У меня уже был проект, который я взял за основу. Я только поменял модуль ls_usb_core.v (ведь теперь должен обрабатывать целых 3 Endpoints и посылать данные в формате Scratch) и поменял mif файл, где теперь хранятся новые описатели USB пакетов.

Сказать по правде больше всего времени, почти неделю, я потерял на... драйвера.

Вообще-то сколько я примеров смотрел (как правило они реализованы на микроконтроллерах AVR), обычно люди говорят, что с драйверами никаких проблем нет, так как используются родные драйвера Windows. Для ОС Windows нужно только подсунуть некий *.INF файл описывающий CDC устройство, и в нем должна быть прописаны строки типа


[Devices]
%DESCRIPTION% = DriverInstall, USB\VID_09FB&PID_60A6


Здесь VID_09FB - это наш id производителя, а PID_60A6 - это id устройства.

Так же в INF файле есть ссылка на драйвер Microsoft USBSER.SYS - именно он поддерживает Communication класс:


[DriverInstall.nt.CopyFiles]
usbser.sys,,,0x20


Вот я взял, сделал INF файл, подсунул своему Windows XP SP2 и - заработало! Уррра!

К сожалению я рано радовался. Всякие мои попытки запустить мое устройство на Windows 7 32bit оказывались безуспешними. Windows 7 мне говорил, что устройство не может быть запущено (ошибка 10). Уж чего я только не пробовал - не помогает и все. Однако, я нашел японский (!) сайт, где написано как бороться с этой проблемой. Там Osamu Tamura предлагает свой драйвер "фильтра", который и решает проблему. Вот я воспользовался этим драйвером и теперь работает и под Windows 7! Конечно, его INF файл я исправил, поставив мои idVendor и idProduct.

Вот теперь можно считать работу почти законченной. Поэтому я и выкладываю этот проект на всеобщее обозрение:

USB ScratchBoard ( 180752 bytes )

Что касается проблемы драйверов - я думаю проблема в том, что мое устройство низко-скоростное и по каким-то причинам Windows 7 считает, что оно по этому не может быть класса Communication. Эта версия нуждается в проверке. Может быть новую версию устройства я позже сделаю high-speed, посмотрим..


 


Добавить комментарий