Однако красной нитью через цикл проходит разработка анализатора шины USB 2.0 на основе модуля ULPI. Возможно, кто-то статьи читал не из-за FX3, а из-за интриги, выйдет или не выйдет. Вышло! Давайте сегодня я покажу, как скрестить ежа и ужа (ежом будет голова анализатора из этой статьи, а ужом – туловище из этой с доработками, размазанными по всему циклу статей). Дальше пробежимся намного по программе для работы с этим делом и по тому, как правильно разбирать логи.
Итак, начинаем внедрять в жизнь идеи Мичурина! Приступаем к скрещиванию!
- Начинаем опыты с интерфейсом USB 3.0 через контроллер семейства FX3 фирмы Cypress
- Дорабатываем прошивку USB 3.0, используя анализатор SignalTap, встроенный в среду разработки Quartus
- Учимся работать с USB-устройством и испытываем систему, сделанную на базе контроллера FX3
- Боремся с таймаутами при использовании USB 3.0 через контроллер FX3, возникающими при определенных условиях
- Добавляем поддержку Vendor-команд к USB3.0 устройству на базе FX3
- Делаем блок SPI to AVALON_MM для USB-устройства на базе FX3
- Асинхронная работа с libusb 1.0
Схема процессорной системы
Как всегда, покажу схему процессорной системы. Но так как все её части мы уже когда-то рассматривали, сделаю это без подробностей. Я просто взял систему, которую мы делали на протяжении данного цикла и заменил источник (таймер) головой анализатора, которую мы сделали под конец предыдущего цикла статей про Redd. Получилось так:
Как видно, во время длительной многоэтапной разработки возник некоторый бардак с тактированием. Блок clk остался для входа сигнала от кнопки reset, а истинные такты идут из блока ULPI. Почему – об этом я подробно рассказывал, когда мы проектировали этот блок. Наша система тактируется от выхода 60 МГц, идущих из ULPI.
Но на этом кажущиеся странности прохождения сигналов не заканчиваются. Сигнал от кнопки Reset проходит через блок FX3_to_ST. Напомню, он там фильтруется от дребезга.
В общем, осталась масса мелких артефактов, каждый из которых имеет свои исторические корни, но ни один не мешает работе. Готовый код будет размещён в раздаточных материалах.
База программной части
Программная часть построена по принципам, описанным в статье про асинхронную работу с библиотекой libusb. Собственно, я там писал код специально с заделом, что он будет перенесён в боевую программу. Так что принципы открытия и работы с устройством нет особого смысла описывать.
Программа шлёт команды в регистры ULPI. Но и это мы уже делали. Тут описан принцип посылки, а тут лежит прошлая версия функций записи, сделанная на TCL. Так что опять же, я не знаю, что описывать. Так что просто оставлю ссылки на старые описания.
Соответствующие функции на C++ выглядят так (функция чтения – для самоконтроля, в бою она пока что не используется, и не факт, что будет использоваться, но благодаря ей я поймал неработоспособность системы):
bool MainWindow::UlpiWriteReg(uint32_t reg, uint32_t data)
{
if (!m_usbAnalyzer.WriteDword(avalonRegUlpiAddr,reg))
{
return false;
}
if (!m_usbAnalyzer.WriteDword(avalonRegUlpiData,data))
{
return false;
}
return true;
}
bool MainWindow::UlpiReadReg(uint32_t reg, uint32_t &data)
{
if (!m_usbAnalyzer.WriteDword(avalonRegUlpiAddr,reg))
{
return false;
}
if (!m_usbAnalyzer.WriteDword(avalonRegUlpiControl,1))
{
return false;
}
return m_usbAnalyzer.ReadDword(avalonRegUlpiData,data);
}
Как всегда, я не жду готовности автомата ULPI, так как шина SPI заведомо медленнее, чем этот автомат.
Шина USB изобилует подтягивающими и терминирующими резисторами. На разных скоростях (FS/HS) и в разных режимах, надо включать разные подтягивающие резисторы. Вот какой блок резисторов есть в ULPI:
Причём в описании есть ещё терминирующие резисторы, которые можно подключать и отключать. Но у нас всё-таки анализатор! Мы просто подключились двумя крокодилами к проводам и слушаем линию, стараясь не выдать своё присутствие! Поэтому программа просто отключает все возможные резисторы. Также она отключает все передатчики. Ну, и здесь же приёмник принудительно переводится в выбранный скоростной режим. LS меня не интересует, поэтому в коде есть только варианты FS и HS. Итого, получается:
bool MainWindow::InitUlpiRegs()
{
// Отключим подтяжки напрочь
if (!UlpiWriteReg (0x0c,0x07))
{
return false;
}
// Включаем режим Non Driving (биты 4:3 = 01)
uint32_t maskSet = (1<<3);
uint32_t maskClear = (1<<4);
if (ui->actionHigh_Speed_Mode->isChecked())
{
// Включаем режим High Speed (биты 1:0 = 01)
maskClear |= (3<<0);
} else
{
// Включаем режим Full Speed (биты 1:0 = 01)
maskSet |= (1<<0);
maskClear |= (1<<1);
}
// Зануляем TermSelect (бит 2)
if (!UlpiWriteReg (0x06,maskClear))
{
return false;
}
if (!UlpiWriteReg (0x05,maskSet))
{
return false;
}
return true;
}
Больше не знаю, о чём рассказывать, касательно программы. Обо всём уже рассказано. Такая получается не статья, а реферат со ссылками на предыдущие статьи цикла.
Слитный исходный текст только перегрузит статью. Желающие смогут скачать его из раздаточных материалов, здесь же я просто поясняю потенциально непонятные участки кода.
Что такое RXD_CMD
Дальше я буду много ссылаться в тексте на некие входящие команды. Сначала надо напомнить, что это такое. ULPI периодически шлёт нам состояние важных на его взгляд битов состояния, объединив их в байт RXD_CMD. Вот так этот байт описан в документации:
Что такое Linestate, документация на USB3300 умалчивает. Более того, в стандарте ULPI нет чёткого определения этих битов, есть только сильно размазанное по тексту. Но есть масса других документов, где всё разъяснено. Это состояние линий DM и DP. А в рамках ULPI их состояние следует трактовать в зависимости от режима работы. Для FS:
Для HS:
Есть ещё такой Chirp Mode. Что это такое, мы рассмотрим чуть ниже. В этом режиме их трактуют так:
С Linestate разобрались, идём дальше. Биты 3:2 позволяют определить напряжение питания, поданное на разъём. Соответственно, по нему можно определять факт включения нашего кабеля в хост.
При помощи битов 5:4 в теории можно определить, идёт передача по шине или нет. Правда, чуть ниже я покажу, что не всё так хорошо.
Бит 6 для анализатора не нужен.
Теперь, когда мы вспомнили, какие биты регистра команд за что отвечают, можно начать рассуждать о реальной работе. Напомню, что эти команды вполне могут перемежаться с данными в едином потоке:
Доработки головы
Признаки начала и конца пакета
При реальной работе с USB-головой, сделанной летом (и описанной в ряде статей про цикл Redd), выяснилось, что её следует немного доработать. Все доработки касаются фильтра. В этой статье я добавил фильтр, вставляющий регистр команд при переходе RXActive из состояния 0 в состояние 1. Оказалось, что этого недостаточно.
Для режима FS всё отлично. Сначала приходит команда с установленным битом RxActive, затем идёт кадр (как я уже рассказывал, с кучей вставленных команд, но у всех у них RxActive в единице, в примере ниже это 0x5D), после чего приходит кадр с RxActive в нуле, в примере ниже – это 0x4D. Напомню, каждый второй байт как раз и содержит состояние RxActive (0x01 — сигнал взведён, 0x00 — сброшен).
5D 01 A5 00 A0 00 F7 00 4D 01
В режиме HS всё хуже. Там этот флаг взлетает и падает, когда хочет. Пришлось добавить маску 0x4000 при каждом включении приёма и добавить специальный символ с маской 0x8000 при его выключении. Кто интересовался исходниками головы, найдёт изменения в строках, начинающихся на «source_data <=». Ещё чуть изменилось условие сохранения команд. Сейчас оно выглядит так:
assign activated = (active_prev != source_data[5:4]) | source_data[15] | source_data[14];
То есть, если поменялись биты «RX Event Encoding» в команде, либо есть те самые флаги 0x8000 или 0x4000.
Имея этим маски (0x4000, когда ULPI начинает выдавать нам эти данные, и 0x8000 – когда заканчивает), я могу показать, какая порнография идёт в режиме HS.
A5 40 17 00 F3 00 00 80
A5 40 17 00 F3 00 00 80
A5 40 18 00 FB 00 00 80
5D 41 A5 00 18 00 FB 00 00 80
Как видим, три раза токен SOF передавался без признака включения передачи. А один – с этим признаком, но без признака выключения.
А вот ещё более интересный случай:
A5 40 18 00 FB 00 5D 01 4D 01 00 80
Пробежали данные, затем активировался флаг RXACTIVE и тут же упал. В общем, в режиме HS совершенно нельзя ориентироваться на него. Он ведёт себя очень странно. Так что добавление флагов 0x4000 и 0x8000, отмечающих процесс передачи, спасёт нас в режиме HS.
Привязка пакетов ко времени
В этой статье мы делали голову обычного логического анализатора, у которой была привязка каждого набора входных сигналов ко времени. При разработке головы для USB-анализатора я решил, что если добавить временнЫе маркеры тем же способом, поток станет чрезмерно большим. Но очень надеялся, что что-то можно будет сделать. Похоже, эта возможность пришла к нам сама собой.
В предыдущем разделе я добавил формирование слова 0x8000 в конце каждого блока данных. Давайте я немного порассуждаю, чтобы показать все этапы эволюции мысли, вытекающей из этого. Итак, мы вставляем этот маркер в состоянии автомата wait1, когда на шине идёт операция TURN AROUND. Шина ULPI переключает своё направление из передачи данных к нам в приём команд от нас. То есть, на этот такте поток данных из ULPI в AVALON_ST отсутствует. Раньше, при нахождении автомата в этом состоянии, мы просто ничего не передавали в шину AVALON_ST. Сейчас – передаём слово ради статусного бита.
Но у этого флага всегда имеется пятнадцать нулевых бит! Там ценность представляет только старший бит, а младшие – никак не используются. А что, если завести таймер, который будет тикать по кругу, а в этих пятнадцати битах вставлять его содержимое? Тогда мы сможем отмерять расстояние от текущей транзакции до предыдущей. Если между двумя транзакциями таймер не успеет переполниться, мы всегда будем иметь возможность восстановить временнУю шкалу.
Что можно уместить в 15 бит? На самом деле, немало. Стандарт USB говорит, что не реже, чем раз в миллисекунду следуют маркеры SOF. Ради каждого из них на шине начинается передача и заканчивается. То есть, не реже, чем раз в миллисекунду наше слово 0x8000 будет вставлено в поток!
У нас имеется тактовый сигнал 60 МГц. За одну миллисекунду он успеет тикнуть 60 тысяч раз. В пятнадцатибитное слово это не поместится. Но если брать каждый второй тик, то уже поместится. Максимум 30000 тиков при допустимом максимальном значении 32767. Даже запас в 8% останется!
Но если мы будем помещать значение таймера в конце пакета, то возникнет трудность в вычислении времени его начала. Дело в том, что время передачи данных по шине USB зависит не только от количества байт, но и от их значения! Есть там такая неприятная штука, как вставляемый бит. Поэтому для того, чтобы вычислить начало пакета, мне пришлось бы вставить в программу сложную функцию, которая разложила бы фактические данные на те, которые пробегут по шине и добавила бы вставляемые биты. После чего уже посчитала бы время, необходимое на их передачу.
Чтобы облегчить себе задачу, я даже задумал помещать младшие 5 бит счётчика в маркер начала пакета, ведь там мы передаём маску 0x4000, где сама маска занимает 2 бита, 8 бит отводятся на данные, а один – на признак «команда-данные». Итого у нас пять бит свободно.
И тут меня осенило! Кто мешает в начале пакета защёлкивать содержимое таймера в специально выделенном регистре, при этом пять бит его – подмешивать в слово с маской 0x4000, а пятнадцать – хранить до тех пор, пока не придёт время передавать маску 0x8000 и подмешать их туда? Никто не мешает!
В итоге, теперь я могу без проблем вставлять в поток двадцатибитный таймер, который не занимает ни одного нового бита в потоке (он размещается там, где раньше всегда шли нули) и работает с довольно хорошим разрешением. Соответственно, его максимальное значение равно 1048575, то есть, он переполнится через 17 миллисекунд. Вполне неплохой задел для защиты, учитывая, что на скорости FS токены SOF должны идти с периодом 1 мс, а для HS – и вовсе 125 мкс.
Таким образом, в код головы добавились счётчик и защёлкивающий регистр:
// Таймер для транзакций
logic [20:0] trTimer = 0;
logic [14:0] trLatch = 0;
always_ff @(posedge ulpi_clk)
begin
trTimer <= trTimer + 1;
end
А занесение данных в соответствующих состояниях стало выглядеть так:
end
// Здесь мы просто пропускаем такт TURN_AROUND
wait1 : begin
state <= wr_st;
// Начиная со следующего такта, можно ловить данные
source_valid_priv <= 1;
// Бит 14 в единице отмечает начало пакета
source_data <= {1'b0,1'b1,trTimer[19:16],trTimer[0],!ulpi_nxt,ulpi_data};
trLatch <= trTimer [15:1];
end
// Пока не изменится сигнал DIR - гоним данные в AVALON_ST
wr_st : begin
if (ulpi_dir)
begin
// На следующем такте, всё ещё ловим данные
source_valid_priv <= 1;
source_data <= {7'h0,!ulpi_nxt,ulpi_data};
end else
begin
// В документе было ещё состояние wait2,
// но я решил, что оно лишнее.
state <= idle;
source_valid_priv <= 1;
source_data <= {1'b1,trLatch};
end
end
Что когда подключаем
Модуль ULPI фирмы WaveShare имеет такую схему подключения USB разъёмов:
Как видно, они просто запараллелены и подключены к микросхеме ULPI. Они как бы являются частью кабеля. Получается, что в один надо воткнуть кабель, уходящий к хосту, в другой – устройство. Так?
Электрически – да. Организационно – всё не так просто. Начнём с того, что ещё летом, когда анализатор выглядел так:
Мне довелось во всей красе ощутить, что электроника – это наука о контактах. Я специально взял фото со снятой крышкой, чтобы было видно, как тогда крепились платы (при этом, обе они были привинчены к пластиковому корпусу). Вставляем USB флэшку в разъём платы ULPI – почти наверняка получаем сбой. То ли тактовых импульсов (60 МГц – не шутка). То ли питания. Не знаю. Короче, почти всегда всё портилось. Поэтому на новой плате всё крепится уже к текстолиту (он попрочней) и все внешние устройства обязательно подключаются через гибкие провода! Причём к удалённому концу провода.
Серебристый провод уходит к хосту, в чёрный включаются устройства. С этим понятно. Но если мы проверяем процесс старта устройства, что должно быть включено исходно, а что втыкаться уже после старта сбора данных?
Давайте я начну с неправильного варианта. Он просто так и напрашивается. Анализатор всегда подключён к хосту, а устройство включаем уже в активной фазе работы анализатора. Получаем такой лог:
4C 41 00 80
4C 41 00 80
48 41 00 80
4C 41 00 80
4D 41 00 80
4C 41 00 80
4D 41 00 80
4E 41 00 80
4D 41 00 80
4E 41 00 80
4D 41 00 80
4E 41 00 80
4D 41 00 80
4E 41 00 80
5D 41 A5 00 9F 00 97 00 4D 01 00 80
4E 41 00 80
4D 41 00 80
4E 41 00 80
Перед первым SOF-ом идёт какое-то шевеление линий данных. Но к чему оно – да кто ж его знает? Момент включения устройства не отследить. Питание всё время хорошее.
Если же устройство воткнуто в анализатор всё время, а втыкаем мы анализатор в хост, то процесс нарастания питания мы вполне можем отследить. Вот уже декодированный текст:
Отслеживается проход «меньше»-«больше». Для сравнения есть следующие компараторы:
То есть мы можем видеть, что питание ниже, чем 0.5 вольта (меньше, чем Sess_end), между 0.5 и 1.4 вольта, между 1.4 и 4.75 вольта и более, чем 5 вольт. В логе мы видели только два этих значения.
Правда, делать автоматическое определение режима я не стал. Уж больно оно сложное. Проще выбрать вручную. Однако, раз уж я более-менее разобрался в том, как всё должно быть, давайте, для информации, всё покажу. И мы увидим, что с точки зрения анализатора, всё не так, как должно быть с точки зрения хоста или устройства.
Теория определения скорости
Краткое описание процесса согласования скоростей, можно найти здесь: USB Made Simple — Part 6. Более подробно и красиво, причём применительно к ULPI – здесь: ULPI interface (cross-hair.co.uk). Правда, в логе анализатора мы увидим чуть иную картинку. Но в целом, при желании отличить FS от HS сможем.
Итак. Сначала в мире существовали варианты скоростей FS и LS. Чтобы отличить их, одна из линий USB подтягивалась к питанию. При включении устройства (когда питание прошло от низа до верха) ничего не подаём на линию. Она контролируется только подтягивающими резисторами. Понятно, что при такой схеме:
резистор в полтора килоома на устройстве победит пятнадцатикилоомный резистор на хосте. Поэтому на линии D+ появится единица. А на линии D- резистор на хосте обеспечит ноль.
В случае с LS единица будет на линии D-, но нас это особо не интересует. Нам интересны только FS и HS.
В случае HS старт начинается точно так же, как и в случае FS. Устройство обеспечивает тот же резистор на линии D+. Так что процесс старта будет одинаковым, скоро мы это увидим. Но затем, для проверки, не поддерживает ли устройство высокую скорость, хост подключает подтягивающие резисторы 45 Ом на землю (слева на рисунке ниже):
Шина переходит в состояние SE0. То есть, Single End 0. Вообще, на уважающей себе дифф. паре такое невозможно, но именно поэтому и нельзя делать USB-шину на дифф. парах ПЛИС. Тут такое – норма вещей. Итак, шина перешла в SE0. Если перед нами обычное FS-устройство, то она как перешла в это состояние, так в нём и осталась. Никто ничего не пытается сделать. Видя это, хост снова переходит в режим FS, снимает чудовищную подтяжку и начинает работу на скорости FS. Давайте посмотрим лог старта такого устройства, снятый анализатором:
Красным я выделил нахождение в состоянии SE0. Примерно через 2.5 миллисекунды, мы видим возврат в состояние J. А ещё через миллисекунду в логе начинается обычная работа в режиме FS. Каждую миллисекунду посылается токен SOF, предваряемый последовательностью состояний JKJKJK…
*** +927 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.249.99 us
SOF 651.0 (0x28b)
A5 8B C2
*** +999 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.249.99 us
SOF 652.0 (0x28c)
A5 8C BA
Если же перед нами HS-устройство, то оно, увидев состояние SE0, начнёт «действовать, действовать, действовать». Дальше я перевожу, как могу, страницу, ссылку на которую дал выше. Дело в том, что анализатором я вижу нечто другое, так что практического опыта у меня маловато. Да и раздел – скорее справочный. Итак, увидев состояние SE0, устройство, не менее чем через 2.5 микросекунды должно переключиться в режим Chirp. Как я понял (а я могу ошибаться), оно отключает полуторакилоомный резистор, но и 45-омные резисторы ещё тоже не подключает. Тут оно подаёт ток (17.76 мА) на линию D- как минимум на миллисекунду. Со стороны хоста линия терминирована, со стороны устройства – нет. Согласно тексту, который я перевожу, хост увидит на линии D- напряжение около 800 мВ. Именно это состояние называется K-Chirp.
Пришла пора скопировать какую-нибудь картинку. Скажем, вот эту:
Мы сейчас дошли до состояния Device Responds.
Если хост не увидел ответа – он держит состояние до конца слота Reset и начинает работать в режиме FS. Если же ответ есть, то не более, чем через 100 мкс, хост приступает к передаче состояний Chirp K и J. Каждое состояние K и K должно удерживаться не менее, чем 40 и не более, чем 60 мкс.
Ну, а устройство, которое заметило эти переходы K J K J K J, переключается в состояние HS, включает правильное терминирование (свои сорокапятиомные резисторы на землю), и система начинает работу.
Красота? Однозначно! Смотрим на реальную систему. Напряжение поднимается, затем переходим в состояние SE0 на 4.3 миллисекунды:
*** +10285 us(00) SE0 Vsess_vld < VBus < Vbus_vld -
*** +0.2 us(00) SE0 Vsess_vld < VBus < Vbus_vld -
*** +399 us(00) SE0 Vbus_vld < Vbus -
*** +4300 us(01) J Vbus_vld < Vbus -
Дальше я буду показывать с небольшими перехлёстами, так как последняя строка блока содержит время от него, но данные для следующего блока. Итак. Мы начали дёргаться между SE0 и J:
*** +4300 us(01) J Vbus_vld < Vbus -
*** +16967 us(00) SE0 Vbus_vld < Vbus -
*** +13031 us(01) J Vbus_vld < Vbus -
*** +0.05 us(11) SE1 Vbus_vld < Vbus -
*** +401 us(10) K Vbus_vld < Vbus -
*** +0.333333 us(00) SE0 Vbus_vld < Vbus -
*** +4643 us(01) J Vbus_vld < Vbus -
Честно говоря, это – не то, что я ожидал увидеть… Но с другой стороны – от того, что мы видели для случая FS старта, всё-таки отличается.
Дальше там начинается что-то, что больше похоже на HS SOFы, пойманные FS-приёмником, так как время следования составляет 125 мкс или кратную им величину. Если бы я переключил приёмник в режим HS – было бы лучше видно:
*** +250 us(00) SE0 Vbus_vld < Vbus -
*** +125 us(00) SE0 Vbus_vld < Vbus -
*** +125 us(00) SE0 Vbus_vld < Vbus -
*** +250 us(00) SE0 Vbus_vld < Vbus -
*** +125 us(00) SE0 Vbus_vld < Vbus -
*** +500 us(00) SE0 Vbus_vld < Vbus -
*** +250 us(00) SE0 Vbus_vld < Vbus -
*** +250 us(00) SE0 Vbus_vld < Vbus -
*** +250 us(00) SE0 Vbus_vld < Vbus -
*** +250 us(00) SE0 Vbus_vld < Vbus -
В общем, автоматический выбор скорости при условии, что мы не являемся ни хостом, ни устройством – сложная штука. Не будем им заниматься. Лучше будем задавать скорость вручную, зная, что будет подключено для анализа. По крайней мере, в текущей моей версии ПО. Что будет завтра и что будет у других желающих поиграть в ULPI – я не знаю.
Чего ещё не хватает
На самом деле, не хватает много чего. Надо дорабатывать систему фильтров. Я уже показывал, что каждый пакет обрамлён вспомогательными посылками. Например, для FS:
*** +52 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.249.99 us(01) J Vbus_vld < Vbus -
*** +0.49.998 us
SOF 683.0 (0x2ab)
A5 AB 4A
*** +2 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.249.99 us
IN Addr: 0 (0x0), EP: 0
69 00 10
*** +2 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.83.33 us(01) J Vbus_vld < Vbus -
*** +0.83.33 us(10) K Vbus_vld < Vbus -
*** +0.283.322 us
DATA1
4B 12 01 00 02 00 00 00 40 83 04 48 37 00 01 01 02 03 01 77 88
Каждая посылка требует памяти. Возможно, их надо отфильтровывать ещё на уровне головы. Но это – в будущем, если реально понадобится. Пока – просто в программе можно отключить экспорт таких вещей в текст. Тогда всё выглядит более-менее симпатично:
*** +1000 us
SOF 780.0 (0x30c)
A5 0C 23
*** +1000 us
SOF 781.0 (0x30d)
A5 0D DB
*** +129 us
SETUP Addr: 10 (0xa), EP: 0
2D 0A D8
*** +3 us
DATA0
C3 80 06 00 01 00 00 12 00 E0 F4
*** +8 us
ACK
D2
*** +2 us
IN Addr: 10 (0xa), EP: 0
69 0A D8
*** +3 us
NAK
5A
Дальше – надо сделать красивый графический вывод. Все крутые анализаторы выводят данные красиво, с возможностью раскрывать большую посылку на мелкие транзакции или не раскрывать. Сейчас найду на Гугле первый попавшийся пример:
У меня пока что в программе есть только заготовка под графический вывод. Которая вообще не работает. Пока что я могу только экспортировать в текст. Примеры экспорта были выше. Красивый вывод требует много времени сначала на отработку, потом – на реализацию. Правда, есть вариант экспортировать пакеты в формат usbcap, благо он открытый, и потом просто загружать файлы в программу wireshark для просмотра. Как минимум, по логике. Данные о физике частично будут утеряны.
А физика тут – самое интересное. Логику для устройств, подключённых к PC, мы можем снять хоть WireSharkом, хоть BusHoundом, хоть ещё каким программным анализатором. Свою аппаратуру для логики имеет смысл городить, если мы отлаживаем связь двух OTG-устройств. А так, самое интересное – это именно физика.
Вот, например. Теория гласит, что команда в EP0 передаётся так:
Эта замечательная картинка кочует из учебника в учебник. В целом, в жизни всё именно так, как показано. Но в деталях… У-у-у-у-у! Вот подалась команда:
*** +125 us
SOF 676.5 (0x2a4)
A5 A4 42
*** +70 us
SETUP Addr: 0 (0x0), EP: 0
2D 00 10
*** +0.333333 us
DATA0
C3 80 06 00 01 00 00 40 00 DD 94
*** +0.566667 us
ACK
D2
И тут вечер перестаёт быть томным, так как фаза данных, оказывается, выглядит вот так:
*** +1 us
IN Addr: 0 (0x0), EP: 0
69 00 10
*** +0.4 us
NAK
5A
*** +21 us
IN Addr: 0 (0x0), EP: 0
69 00 10
*** +0.4 us
NAK
5A
*** +20 us
IN Addr: 0 (0x0), EP: 0
69 00 10
*** +0.416667 us
NAK
5A
<Тут идёт очень много повторов IN-NAK>
*** +0.416667 us
NAK
5A
*** +20 us
IN Addr: 0 (0x0), EP: 0
69 00 10
*** +0.416667 us
DATA1
4B 12 01 00 02 00 00 00 40 E3 05 51 07 04 14 03 04 00 01 65 CD
*** +0.8 us
ACK
D2
*** +1 us
OUT Addr: 0 (0x0), EP: 0
E1 00 10
*** +0.333333 us
DATA1
4B 00 00
*** +0.45 us
ACK
D2
Кто бы мог подумать, что устройство так долго не давало данных? И анализ подобных вещей может облегчить задачу профилирования и оптимизации работы систем.
Или, скажем, вот забавная вещь. Я нашёл душещипательное описание, для чего нужен токен NYET. Дело в том, что хост, выдав на шину посылку с данными, может получить всё тот же NAK. Ему придётся вновь и вновь посылать эту посылку. Но в отличие от того, что мы видели выше, где посылается только токет IN, тут уходит и сам токен OUT, и блок данных. Устройств на шине много, все хотят что-то прокачать, а тут мы занимаем шину тем, что посылаем и посылаем то, что неизвестно когда понадобится!
Поэтому появился токен NYET. Устройство посылает его вместо NAK, сообщая, что оно не просто занято, но ещё долго будет занято. Тогда хост начинает посылать уже не пакеты OUT, а токены PING. Когда устройство будет готово – оно вернёт ACK, и хост сможет послать блок данных, будучи уверенным, что тот будет принят.
Красота? Втыкаем китайскую SD-читалку и наблюдаем…
*** +18 us
OUT Addr: 14 (0xe), EP: 2
E1 0E C9
*** +0.333333 us
DATA0
C3 55 53 42 43 A0 E9 63 E1 24 00 00 00 80 00 06 12 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00 5E F5
*** +0.916667 us
NYET
96
<некоторое время не обращаемся>
*** +29 us
PING Addr: 14 (0xe), EP: 2
B4 0E C9
*** +0.416667 us
ACK
D2
*** +2 us
OUT Addr: 14 (0xe), EP: 2
E1 0E C9
*** +0.333333 us
DATA1
4B 55 53 42 43 A0 E9 63 E1 60 00 00 00 80 00 06 12 00 00 00 60 00 00 00 00 00 00 00 00 00 00 00 A8 C5
*** +0.95 us
NYET
96
Вот и верь после этого людям! Сказала, что свободна, а тут – опять занята! А остальные устройства на шине – страдают от нехватки полосы…
Вот какие интересные факты вскрываются, когда мы получаем доступ к физике!
Заключение
В общем, дел ещё предстоит много. Может, даже на годы. Но главное – она вертится! И как раз по работе свалился проектик, где я смогу всю эту вновь разработанную аппаратуру испытать в бою.
Но в целом – можно сказать, что цикл статей по разработке анализатора, подошёл к концу. Если вдруг кто-то захочет воспользоваться наработками – их можно скачать тут.
Пока статья лежала и ждала, когда же её выложат на Хабр, случилось знаковое событие. Блок статей начался с того, что я рассуждаю про совместимость версий среды разработки от Кипариса. Так вот. Эта среда автоматически обновилась. Сам бы я не стал этого делать. Но она сказала, мол, ничего не знаю, а я обновлюсь… И в новой версии исходники опять перестали собираться. Привет фирме Cypress. Поэтому в раздаточном материале я выкладываю и обновлённые исходники для FX3 тоже. Это даже хорошо, что не было времени перетащить DOC файл на Хабр… Теперь они снова соответствуют самой свежей версии среды разработки. Но что будет завтра – я уже боюсь подумать.
Спасибо всем за внимание!
jaiprakash
То, что исходники не собираются в новой версии среды (или собираются не в то, что нужно), это уже привычно. А что среда обновляется без спроса... Может в настройках есть галка на автообновление?
EasyLy Автор
Нет, всё было не настолько плохо. Она спросила. И в целом — можно и отказаться. Но она будет надоедать своим поведением. Я в своё время с PSoC этой же фирмы разбирался. Тогда месяц постоянно отказывался, потом всё равно сдался. Но я тогда искал, как автообновление отключить — не нашёл. Поэтому сейчас особо и не стал спорить. Их софт обновляется весь оптом. Один менеджер показывает список для всего, что установлено. Сам стартует, сам смотрит, сам нудит, мол, пора, а то завтра снова пристану.