Я уже писал кое-что про USB.
Тема эта очень сложная, но с другой стороны очень полезная и нужная. У меня одна статья была про USB протокол вообще, а вторая – это был уже первый проект USB для платы Марсоход. Хорошо бы, чтоб Вы сперва прочитали те статьи. Тогда и эту будет легче понять. К сожалению в том первом проекте не все было сделано достаточно хорошо, и вот, потратив еще немного времени, мы выпускаем следующую версию проекта низкоскоростного устройства USB для нашей платы Марсоход.
Что было улучшено?
- Десктипторы и всякие другие пакеты раньше кодировались в логике, а теперь перенесены в UFM (User Flash Memory) чипа. Это позволило освободить немного места в чипе и сделать дескрипторы «длиннее и осмысленней».
- Теперь мы предлагаем Windows драйвер для нашего устройства!
- Теперь устройство должно правильно распознавать назначение USB адреса, а значит, может работать через USB хабы без конфликтов с другими устройствими.
- Были исправлены ошибки связанные с «bit stuffing»
- Теперь в наше устройство можно не только писать, но и можно читать из него!
Итак, попробую описать мой проект.
В этом проекте USB сейчас всего 5 модулей. Это модуль чтения пакетов из флешки чипа pkt_reader, модуль altufm_none0, который представляет флеш память чипа и встроенный генератор (около 5Mhz), USB приемник ls_usb_recv, USB передатчик ls_usb_send, и ядро USB функции ls_usb_core.
1. Чтение USB пакетов из UFM.
Чип CPLD, который установлен на плате Марсоход имеет встроенную флеш память небольшого объема – всего 512 шестнадцатибитных слов. Это не много, но вполне достаточно для хранения всяких USB descriptors – описателей USB устройства. Как я уже писал в предыдущих статьях, при подключении USB устройтва оно должно быть распознано системой. Операционная система определяет тип устройства, его название, серийный номер (если есть) читая эти описатели из устройства. Кроме того, устройству назначается USB адрес, который оно должно использовать во время работы. Для всех эти обменов данными в обе стороны нужны зараннее заготовленные пакеты. Мы разместили все пакеты дескрипторов в этой флеш памяти UFM в специальном формате.
Например, вот фрагмент файла table2.mif, который описывает содержимое флеш:
-- Descriptor String Serial Number
01e0 : 4b8d;
01e1 : 0312;
01e2 : 0030;
01e3 : 0030;
01e4 : 0030;
01e5 : d112;
01e6 : 0000;
01e7 : 0000;
Пакет дескриптора описан так, как он будет передаваться в USB шину: байт за байтом, от первого байта PID, до последних двух байтов контрольной суммы включительно. Единственное исключение – нулевой байт описателя. Младшая тетрада обозначает длину передаваемого пакета. Вместо этого нулевого байта всегда будет посылаться, как положено байт SYN == 0x80, обозначающий начало пакета.
Кроме этого, в самом начале файла table2.mif имеется 32 специальных коротких пакета – это те пакеты, которые мы сможем читать из нашего устройства как "полезные данные". Я решил, что нужно сделать возможным чтение состояния 4-х кнопочек платы Марсоход. Четыре кнопочки – это 16 комбинаций. Но поскольку передача пакетов по шине USB нумеруется как четный-нечетный, то всего нужно «заготовить» уже 32 пакета. Таким образом, когда программа на компьютере захочет прочитать состояние кнопочек платы, она посылает запрос на чтение и наше USB CORE сразу знает адрес флеш памяти для ответного пакета – адрес прямо определяется 4-мя битами кнопок плюс один бит четности. Зараннее заготовленные пакеты хороши тем, что не нужно делать логику по подсчету контрольной суммы USB.
Исходный текст модуля чтения пакетов из флешки можно посмотреть здесь.
2. Модуль USB приемника.
В этот модуль были внесены несколько важных изменений. Во-первых, исправлена ошибка приема при «bit stuffing». Во-вторых, добавлено детектирование сигнала сброса шины USB. Конечно, оно несколько примитивное, но вполне работоспособное. У меня сигнал usb_reset появляется если обе линии USB DP и DM находятся в нуле более 16 тактов. Это конечно не 10мс, как по USB спецификации, но такое решение работает. Сигнал USB сброса важен для правильного назначения USB адреса устройству.
Приемник не работает, когда наше устройство само что-то передает. То есть мы не принимаем свои же данные. Это сделано с помощью входного сигнала enable (инверсия от bus_enable).
Принятые байты выдаются на шине rdata[7..0] по сигналу rdata_ready. Кроме того, номер принятого байта в принимаемом пакете выдается по шине rbyte_cnt[3:0]. Все эти сигналы подаются на вход USB CORE, которое и решает, что же дальше нужно делать.
Вот исходный текст модуля на Verilog. Зараннее хочу попросить прощения у некоторых возможных критиков. В этом модуле кое-где используется ассинхронный сброс триггеров не по прямому назначению (приведение схемы в исходное состояние после включения питания), а для непосредственной работы приемника. Это сделано умышленно с единственной целью экономии места в чипе. Все таки у меня не очень тривиальная задача сделать правильно работающее устройство в 240 логических элементах ПЛИС. Пришлось реально поломать голову как разместить всю логику, чтобы еще и на будущее развитие осталось.
Здесь можно посмотреть как USB приемник работает, его временные диаграммы (кликните на изображении, чтобы увеличить):
3. Модуль USB передатчика.
Передатчик простой. По сигналу start_pkt начинается передача первого байта. Включается сигнал bus_enable и двунаправленная шина USB переключается на передачу от нашего устройства к хосту. Передаваемые данные подаются в передатчик по шине sbyte[7..0]. Когда передатчику требуется следующий байт пакета он выставляет сигнал show_next и USB CORE должно предоставить этот следующий байт на шине sbyte[7..0]. Так же, USB CORE должно зараннее уведомить передатчик о последнем байте в пакете сигналом last_pkt_byte. Когде передан последний байт, передача завершается состоянием SE0 (сигналы DP и DM в нуле в течении двух периодов передающей частоты 1,5Mhz) и затем снимается сигнал bus_enable, шина переводится в направление «на прием».
Вот исходный текст модуля USB передатчика на Verilog.
Временные диаграммы (кликните на изображение, чтобы увидеть крупнее):
Вот еще крупнее:
4. Модуль USB CORE, определяет всю логику устройства.
Самое главное предназначение этого модуля – распознавать принятые от хоста пакеты и решать, что с ними делать дальше, возможно отвечать своими пакетами в нужной последовательности. Именно этот модуль определяет когда и какой пакет из какого дескриптора посылать. Этот модуль вырабатывает запрос на чтение пакета из флешки и передает считанные данные в передающий модуль. Адрес пакета во влеш памяти UFM определяется типом запроса от хоста.
Еще одна необходимая и важная функция этого модуля – правильная реакция на назначение USB адреса. Устройство не должно ничего отвечать, если запрос от хоста идет для другого адреса. Такое возможно, если устройство подключенно через USB хаб.
Исходный текст модуля здесь.
5. Драйвер устройства и пользовательская программа.
Поскольку мы решили все сделать как можно правильней, то нам, конечно, нужен драйвер к нашему устройству. Написание драйверов операционной системы Windows - это отдельная и сложная тема. Скажу лишь, что в данном случае мы не написали ни строчки кода. Мы взяли пример драйвера USB устройства из пакета Microsoft Windows Driver Developer Kit. У меня сейчас стоит версия WINDDK 2600.1106. Возможно (наверняка) есть и более свежий пакет разработчика драйверов от Microsoft. Поищите на сайте Microsoft.
В том DDK, что есть у меня, я нашел пример прототипа драйвера в папке c:\winddk\2600.1106\src\wdm\usb\bulkusb
В этой папке есть пример драйвера bulkusb.sys и пример пользовательского приложения, которое читает из устройства или пишет в него RwBulk.exe.
Я просто откомпилировал эти программы и их сразу можно пытаться использовать и нашим устройством. Правда есть одно небольшое «но». Сперва нужно установить драйвер в ОС и для этого нужно иметь INF файл.
В примере Microsoft есть и пример INF файла. Он нам, в общем, подходит. Только нужно заменить в нем строку описывающую идентификатор производителя устройства и идентификатор устройства. Эти параметры напрямую определяются USB дескрипторами устройства – а они у нас записаны во флеш памяти.
Наша строка должна выглядеть вот так:
%USB\VID_09FB&PID_60A5.DeviceDesc%=BULKUSB.Dev, USB\VID_09FB&PID_60A5
Для нашего устройства мы взяли idVendor = 0x9FB (Altera) и idProduct=0x60A5. Эти числа стоят в нашем Configuration Descriptor.
Итак. Компилируем наш проект. Прошиваем плату Марсоход. Подключаем провод с разъемом USB к плате. Подключаем плату к USB порту компьютера. Появляется окно «Найдено новое устройство». Далее следуете указаниям в этом окне. Установка драйвера вручную. Указываете путь к INF фйлу и путь к драйверу bulkusb.sys. Все. Драйвер установлен. В этом легко убедиться, если зайти в диспетчер устройств:
Для работы с устройством я написал (Microsoft Visual Studio v6.0) простую тестовую программу. Она позволяет зажигать на плате Марсоход светодиодики и читать состояние кнопочек платы.
Теперь у нас все сделано более правильно. Чтобы зажечь светодиод программа пишет в устройство. Передача туда идет на USB Endpoint = 2. Чтение из устройства идет на USB Endpoint =1.
Исходные тексты драйвера, INF файла и этой программы Вы найдете в архиве вместе с проектом Altera Quartus II для платы Марсоход:
Вот подключаем нашу плату к ноутбуку через USB хаб.
К этому же хабу подключена USB мышь. Так проверяем, что наше устройство работает через хаб не конфликтуя с другими устройствами.
Вот на этом видео это то же видно. Я могу зажигать светодиоды на плате нажимая чекбоксы в программе. Когда нажимаю кнопочки на плате Марсоход, то программа это видит и меняет имя кнопочки на экране.
Вот так. Надеюсь на базе этого проекта скоро мы сможем показать вам еще много интересных вещей.
Подробнее...