Русский
Русский
English
Статистика
Реклама

Usb

Повесть о трекболах

22.09.2020 12:22:19 | Автор: admin

В сфере моих интересов есть различные нестандартные устройства ввода. И ведь, вроде придумали клавиатуру и мышь, да на этом успокоились, но мне этого мало. Параллельно с миром манипуляторов типа мышь, существует мир удивительных устройств ввода, таких как трекбол. К мышке мы все привыкли и знаем как ей пользоваться, поэтому если рядового человека посадить за трекбол и заставить вращать шар, мозг у него ломается. И это круто, когда ты используешь не со всем то, что и все. И не каждый может использовать твой инструмент, а уж тем более работать на твоём компьютере.

Поэтому я хочу рассказать вам о своём многолетнем опыте использования трекболов, почему это круто и в то же время, какие медицинские проблемы вызывает его использование. Поехали!

Что же такое трекбол?


Трекбол, в стародавние времена, называли перевёрнутой мышью. Раньше мыши были шариковыми, оптико-механическими, поэтому трекбол был будто бы бы обратным вариантом мышки, где шар вращали пальцами. Те, кто застал эти типы мышек помнят проблемы грязи на валиках оптико-механической системы. У трекболов того времени эта проблема стояла особенно остро, поскольку жир с пальцев собирался на валиках и там твердел. В связи с этим, чистка трекболов была значительно более частой, чем классических шариковых мышей.

Прежде чем мы пойдём разбирать весь зоопарк трекболов, которые прошли через мои руки, расскажу об основных типах трекболов.

Есть, конечно, типичные и очевидные отличия трекболов, например, по способу считывания перемещения шара: это оптико-механическая система и либо только оптическая; по способу подключения к компьютеру: интерфейс USB, ps/2, COM-порт и т.п. Однако, на самом деле это несущественные отличия, так как большой разницы не будет при использовании трекболов с разным интерфейсом. Тем более, что современные трекболы имеют интерфейс только USB и они все, без исключения, оптические. Главным же конструктивным отличием является то, каким органом мы вращаем шар (гусары, молчать): одним большим пальцем, либо указательным, безымянным и средним пальцем одновременно.

Мне больше всего нравятся трекболы под большой палец; на мой взгляд, они удобнее и на них лучше лежит рука. Да и на рынке данные модели получили значительное распространение. К сожалению, по медицинским причинам, мне пришлось отказаться от эксплуатации данных моделей. Скоро вы узнаете почему.

Чем же так хороши трекболы и за что я таких их люблю? Трекболы удобны для программистов, писателей и чертёжников, так как приходится меньше отвлекаться на использование манипулятор. Трекбол не требует места, не портит покрытие стола и может быть легко использован даже на коленке. Мне нравится пользоваться трекболом во время разработки ПО, чтобы минимально отвлекаться от клавиатуры. Вообще, как по мне, идеальный трекбол, встроенный в клавиатуру в самом центре, как на ноутбуках стародавних времён. В целом, клавиатура с трекболом это предел моих мечтаний.

Мой первый трекбол


Моё знакомство с трекболами началось с манипулятора The Microsoft Ballpoint mouse (его фото представлено на КДПВ). Его особенность в том, что он крепился специальным зажимом к клавиатуре или ноутбуку, и позволял весьма комфортно работать с ним левой или правой рукой. Для этих целей на этой модели кнопки были продублированы с обеих сторон.


Картинка с коробки от трекбола.

Мне он достался новенький, в коробочке, с проводами. К нему шла толстенная книжка с инструкцией по эксплуатации, провод для СОМ-порта и ps/2, крепление к клавиатуре и сам трекбол. Кстати, трекбол легко отстёгивался от крепления и мог использоваться просто в руке, что на тот момент было очень даже круто, да что говорить, мне и сегодня очень не хватает такого вот манипулятора!

Эта модель привила у меня любовь к устройствам ввода такого типа. Шар вращался большим пальцем, и он был очень удобный и тяжёлый, даже сейчас помню те ощущения холодной мягкой резины. Многие мои друзья просили меня дать данное устройство в пользование и испытывали большой восторг от его эксплуатации. Мы даже брали его с собой в компьютерные клубы и играли в различные стрелялки, настолько он был удобнее чем мышка.

Но не всё было так радужно, у каждой медали две стороны. Недостаток трекбола данной конструкции был один: он был оптико-механическим, это его и погубило. В конце концов смазка подшипников вращения засохла и трекбол перестал работать. К сожалению, в то время мне не хватило ни опыта, ни умения его восстановить.

Сегодня, для статьи искал фотографии этого трекбола, и он оказался весьма редким зверем, фотографии удалось найти с большим трудом. Особенно, если ты не помнишь название модели. Конкретно эта модель появилась ещё в доинтернетную эпоху. Фотографии в статье взяты с сайта Microsoft.

Трекбол в ноутбуке


Вторым моим трекболом был манипулятор, встроенный в ноутбук Compaq Contura 410C. Этот ноутбук, на удивление, жив до сих пор, разве что где-то утрачен в годах его блок питания.


Трекбол в Compaq Contura 410C.

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

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

Главная моя рабочая лошадка


При работе на обычном ПК я использовал обычную мышку. И обычно мне этого было мало, так как мыша была у всех, а хотелось чего-то эдакого, необычного, да так чтобы было максимально удобно. И тогда я встретил его, трекбол моей мечты! И это положило основу моего увлечения трекболами, а данный модельный ряд пробыл со мной более десяти лет, в разных и более совершенных версиях. Встречайте: Logitech TrackMan Wheel Optical.


Logitech TrackMan Wheel Optical.

Это был первый оптический трекбол на моей практике. То есть теперь положение шарика считывалось также как у современных мышек: с помощью камеры. Подсветка шара осуществлялась ИК-диодом, так что свет никак не мешал и не отвлекал.

Это устройство было совершенной бомбой и совершило полный переворот в моей трекбольной практике. Большой шар, удобная конструкция, будто была специально слеплена для моей здоровенной лапищи. Шар, после мытья нужно было слегка покатать между ладонями, чтобы чуть смазался, и тогда он восхитительно вращался.

С этим трекболом я не вообще расставался, он всегда был со мной, разве что спать не ложился с ним. В конце концов, я решил, что играть буду тоже с трекболом. Вообще, будем честны, скорость реакции и точность у данных устройство ввода значительно хуже, чем у стандартной мышки. Но я принял волевое решение отточить скорость реакции и точность до уровня мышиных соперников, и сумев это сделать, долгое время был в первой тройке лидеров на наших серверах Quake III по точности попаданий.


Скриншот окончания дуэли в CTF на карте q3ctf4 (E+ мод, конфиг madsod), красным выделена статистика, В реальном бою, на конфиге madsod 30% из рельсы это был очень хороший показатель, с учётом безумной динамики игры. Увы, после этого конфига, все остальные игры мне кажутся забегами черепах и играть в них не могу.

Понятное дело, что при таком интенсивном использовании мне его приходилось периодически ремонтировать. В первую очередь сточился пластик, который нажимает/ передаёт усилие с клавиши на кнопку. В результате сделал там же подкладку из алюминиевой банки. Во разборки и ремонта я фотографировал мою прелесть, так что мы можем заглянуть во внутрь.



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


У меня было 4 версии этого типа трекбола, беспроводная версия, лазерная версия. К сожалению, фотографий всех версий этих устройств у меня не сохранилось.


Очередной новый трекбол.

С каждой новой версией были приятные изменения: другой скрол, лучшая чувствительность, более приятный шар, который, лучше скользит и многое другое. Так же шар при длительной эксплуатации стирается и хуже вращается. из-за образовавшихся борозд на нём. Разве что беспроводные меня радовали меньше, чем проводные, так как всплывали проблемы с зарядкой и батарейками. Плюс поиск станции. Я не помню точного наименования всех моделей, но их было четыре типа.

Последним трекболом такого формфактора был шикарный Logitech M570. Он был идеальным.


Logitech M570.

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

Заболевание фанатичных трекболистов


В один день я вдруг почувствовал неприятные боли в суставах большого пальца при мелкой моторике вращения шара трекбола. По началу я не придавал ей значения, но боль с каждым днём постоянно усиливалась и в конце концов стала невыносимой.


Места болей.

Поняв, что само оно не пройдёт, я пошёл к врачу хирургу в районной поликлинике. Пройдя 13 кругов ада был направлен на рентген и диагноз был неутешительный.


Заключение (подчёркнуто): выраженные признаки артроза.

Потом уколы, мази и прочие процедуры и о трекболах для большого пальца правой руки пришлось забыть. Забыть навсегда. Теперь, даже спустя так много лет, если я сажусь за трекбол данного типа, то спустя буквально десять минут работы у меня начинают болеть суставы. Да, даже сообщения на телефоне теперь я набираю только левой рукой.

Таким образом, всё изнашивается, в том числе и наши суставы. Компьютерные игры, тренировки в клане по 5 часов в день слишком быстро износило суставы моего пальца, на которые приходилась вся нагрузка. И это привело к печальным, непоправимым последствиям.

Но если вы думаете, что моя история работы с трекболами прекращена, то нет.

Другие модели трекболов, которые мне довелось использовать


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

Первый шикарный трекболл был A4 Tech Wintrack PC XT трекбол, на СОМ-порт. Несмотря на то, что он был оптико-механическим, да ещё и на СОМ-порт, он был трёхкнопочным. И если бы сейчас у него было бы колесо прокрутки, то я бы до сих пор его использовал, ибо нахожу его весьма удобным и интересным.


A4 Tech Wintrack PC XT.

Устройство реально здоровенное, но очень прикольное, особенно доставляют огромные клавиши. К сожалению, у меня не прижился, так как СОМ-порт в современных компьютерах сегодня днём с огнём не сыщешь, а переходник это такой себе метод, так как нужны дополнительные драйвера и прочие проблемы. Даже стало любопытно, а может ли современная Windows 10 работать с СОМ-портовыми мышами?

Другая модель, которая находилась у меня в пользовании некоторое время это Logitech TrackMan Marble Silver.


Logitech TrackMan Marble Silver.

Про него могу сказать, что модель весьма необычная. В целом устройство мне понравилось. К главным недостаткам можно отнести отсутствие колёсика прокрутки, которое ставит крест на его комфортном использовании. Этой моделью я пользовался некоторое время после получения диагноза артроз, так как он задействует другие пальцы. Но всё же она не прижилась у меня, и пошла дальше по рукам. Как-то ни у кого не задержалась на долго.

Крайний трекбол, и самый необычный, которым я пользовался совсем недавно, был Logitech Cordless TrackMan Optical Trackball. Данную модель очень выгодно купил на одной интернет площадке несколько месяцев назад.


Logitech Cordless TrackMan Optical Trackball.

Он оказался очень непривычным устройством, даже для меня. Главной странностью было совершенно необычное расположение клавиш: привычная левая кнопка мыши находится под большим пальцем, а правая кнопка мышки находится за шаром.


Большая клавиша сбоку это ЛКМ.

Из-за проблем с суставом на большом пальце, я не могу использовать кнопку под большой палец. Поэтому, это явилось причиной отказа от использования мной данной модели. В принципе, возможно перепрограммировать кнопки данного трекбола с помощью ПО, идущего в комплекте. Но на деле оказалось, что перепрограммирование осуществляется на уровне операционной системы, к которой подключён трекбол, а не на уровне контроллера самого манипулятора.


Большая клавиша сбоку это ЛКМ.

Поэтому манипулятор пошёл дальше по рукам.

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

Что же в итоге?


К моему большому сожалению, из-за травмы не могу использовать больше трекболы под большой палец под правую руку. Да, и как говорят мои знакомые любители трекболов, которые используют их около 5-10 лет, что у них так же наблюдаются признаки артроза (ноют суставы большого пальца). Поэтому на данный момент я использую манипулятор типа мышь. Но, всё же поглядываю на интересные модели. Пример таких моделей, это трекбол под большой палец для левой руки ELECOM М-XT4DRBK.


ELECOM М-XT4DRBK

Либо другая модель, которая своей красотой мне не даёт покоя это Microsoft Explorer 1.0.


Microsoft Explorer 1.0

Конечно, у данной модели кнопки так же находятся под большой палец, но сама модель выглядит просто космически. И является основным предметом мечтаний у заядлых трекболистов.

Из самых интересных и редких моделей трекболов, которые мне было бы интересно пощупать это CST Laser Trackball.


CST Laser Trackball (фото отсюда)

Модель так редка, что даже на аукционах она встречается очень редко и за безумные деньги.
Проблема всех этих моделей их цена. Если вы зайдёте на известный западный аукцион или интернет-магазин, и посмотрите цены на эти устройства, то окажется, что мышки несоизмеримо дешевле. Но, если вы прям истинный гик, то это не должно вас останавливать, нет преград патриотам. Экспериментируйте с устройствами ввода и подбирайте подходящее под ваши вкусы и интересы!



Подробнее..

Как я делаю цифровую минигитару

06.09.2020 16:13:36 | Автор: admin
image

В этой статье я постараюсь в общих чертах описать путь создания девайса от идеи до реализации юзабельного прототипа.

Меня зовут Дмитрий Дударев. Я занимаюсь разработкой электроники и очень люблю создавать различные портативные девайсы. Еще я люблю музыку. Полгода назад я взял у друга акустическую гитару чтобы попытаться научиться на ней играть по урокам из ютуба и табулатурам. Было тяжело. То ли я неправильно что-то делал, то ли плохо старался, то ли в обществе моих предков мелкая моторика вредила размножению. В любом случае, ничего кроме звуков дребезжащих струн у меня не выходило. Мое негодование усиливала постоянная расстройка струн. Да и окружающим тысячный раз слушать мою кривую Nothing else matters удовольствия не доставляло.

Но в этих муках про главное правило электронщика я не забыл. Если что-то существует, значит туда можно вставить микроконтроллер. Или, хотя бы, сделать портативную электронную модификацию.


Идея


В голове начала вырисовываться структура цифровой гитары.

Первым делом я составил список требований к девайсу:

  1. Устройство должно имитировать гитару с 6-ю струнами и 12-ю ладами на грифе
  2. Должно быть компактным, в идеале складным, чтобы можно было брать его с собой куда угодно
  3. Должно подключаться ко всем популярным осям Android, IOS, Windows, Linux, MacOS и определяться там как MIDI устройство без каких-либо драйверов
  4. Работа от аккумулятора
  5. Подключение должно производиться без проводов (но раз уж там будет USB разъем для зарядки, то и по проводу пусть тоже подключается)
  6. Возможность сразу начать играть, без необходимости в долгих тренировках по адаптации кистевых связок
  7. На каждой струне и каждом элементе грифа должно быть по светодиоду, чтобы можно было запустить табулатуру мелодии, и гитара сама показывала куда нужно прикладывать руки
  8. Возможность использования основных техник игры на гитаре: hummer on, pull off, slide, vibrato
  9. Задержка передачи midi команд не более 10мс
  10. Все должно собираться из подручных материалов без сложных техпроцессов и дорогой электроники

Разумеется, для мобильных платформ потребуется написать приложение, в котором можно будет выбрать табулатуру для обучения светодиодами, выбрать инструмент (акустика, классика, электрогитара с различными пресетами фильтров, и т.д.), и воспроизводить звуки.

По идее, должен получиться компактный инструмент, на котором можно играть как на гитаре, лишенный аналоговых недостатков и оснащенный наглядной системой обучения.
Звучит реализуемо.

Аналоги


Итак, первым делом, вооружившись мыслью о том, что в наше время почти невозможно придумать что-то новое и кто-то уже это делал, иду гуглить. Действительно, оказалось, что первая цифровая гитара была создана еще в 1981 году, но из-за ограниченной функциональности широкого применения не нашла.



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



Значит, можно приступать.

Proof of concept


Начать решил с проверкой концепции. Минимальный прототип на скорую руку.
Для начала нужно определиться с элементной базой.

Контроллер

В своих проектах я чаще всего использую STM32. Они мощные, дешевые, доступные. Выбрал STM32F042. В нем есть USB (причем, со специальным внутренним генератором на 48МГц чтобы не вешать внешний кварц), 32-битное ядро, и вся необходимая периферия. И все это при стоимости меньше бакса.

Беспроводное подключение решил оставить на следующую итерацию.

Струны на дэке

В качестве струн решил напечатать пластиковые язычки, закрепить их на потенциометрах с пружинками и измерять углы отклонения.

Замоделил в солиде и напечатал для оценки эргономики.





Получилось довольно приятно на ощупь. Должно работать.

Сенсоры на грифе

На гитаре предполагаются 6 струн и 12 ладов. Суммарно это 72 сенсора на грифе и еще 6 на дэке. Можно было бы использовать на каждый элемент по тактовой кнопке, но, во-первых, они щелкают, во-вторых, не получится реализовать техники вроде slide или vibrato. Хотелось бы еще и усилие нажатия определять.

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



В продаже их найти в нужном количестве оказалось очень сложно, да и стоят немало. Пришлось заказать на али.

АЦП

Для считывания состояния каждого датчика нужен ацп. В STM-ке их всего несколько штук, так что для опроса 78 датчиков понадобится что-то еще. Можно было бы использовать внешние многоканальные ацп микросхемы, но они слишком дорогие. Поэтому я решил поставить 5 дешевых 16-канальных аналоговых мультиплексора CD74HC4067 и подключить к каждому по одному каналу ацп STM-ки.

Плата

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







Прежде чем заказывать печать платы, решил дождаться тензорезисторов. И, как оказалось, не зря.

Из 80-ти датчиков рабочими оказались только несколько, и то с разными параметрами.



От изображения на сайте продавца они отличаются заметно в худшую сторону.

И чего я ожидал, покупая электронику на али?..

И тут меня осенило.

Можно ведь применить другой метод детектирования измерение емкости как в датчиках прикосновения. Это гораздо дешевле и доступнее. А если правильно спроектировать механику, то можно и усилие определять.

Что ж. Удаляю все что было сделано.



Начинаю сначала


В новой версии минималистичного proof of concept-а в качестве сенсорных элементов я выбрал напиленные из 4мм медного прутка цилиндрики, припаянные к плате.

Теперь нужно придумать как измерять 78 емкостей.

Опрос сенсоров

Немного погуглив, выяснил, что существует множество микросхем контроллеров сенсорных клавиатур. Среди них удалось найти дешевый 12-канальный измеритель емкости общего назначения. Он измеряет емкость в масштабах единиц пикофарад, чего должно быть достаточно для схемы измерения усилия, которую я планирую реализовать в следующих модификациях.
Дополнительно на всякий случай повесил на каждый элемент грифа по посадочному месту для кнопки или чего-то подобного. И сделал вырезы в плате, чтобы можно было не только прикоснуться к цилиндрику, но и прожать его внутрь. Можно будет поэкспериментировать с разными техниками игры.

Микросхема подключается по I2C интерфейсу и имеет 2 конфигурационных пина, задающих адрес. Соответственно, на одну шину можно повесить максимум 4 микросхемы. А мне нужно 12. Не проблема, распределяю их на три группы и подключаю к шине STM-ки через мультиплексор.

Платы





На этот раз плату удалось заказать и даже дождаться ее изготовления.

После запайки комплектухи, медных цилиндриков и потенциометров понял, что конструкция с пластиковыми струнами получается слишком сложной. Поэтому решил пока что повесить на дэку такие же сенсорные цилиндрики, но подлиннее. Чтобы не переделывать всю плату, сделал небольшую платку-накладку и подключил ее ко второй I2C шине STM-ки.



Железяка готова. Следующая задача заставить ее играть.

Софт


План следующий:

  1. Скачать виртуальный синтезатор, который может работать с MIDI устройствами и издавать гитарные звуки.
  2. Написать прошивку, которая будет опрашивать сенсоры и передавать результаты в комп через USB custom HID интерфейс около 100 раз в секунду.
  3. Написать программу на питоне, которая будет принимать эти данные, эмулировать виртуальное MIDI устройство, генерировать MIDI пакеты и отправлять их на виртуальный синтезатор.

Разбираться как сразу прикинуться MIDI устройством решил чуть позже.

Чем воспроизводить звук?

Виртуальных синтезаторов под винду с поддержкой MIDI оказалось довольно много. Я попробовал Ableton live, RealGuitar, FL studio, Kontakt. Остановился на RealGuitar из-за простоты и заточенности именно под гитару. Он даже умеет имитировать несовершенства человеческой игры скольжение пальцев по струнам, рандомизированные параметры извлечения нот.



Подключение к виртуальному синтезатору

Для питона удалось найти библиотеку mido, которая может эмулировать виртуальный midi порт, который через внешний эмулятор midi кабеля loopmidi можно подключить ко входу виртуального синтезатора.



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



Для того, чтобы удары по струнам гитары вызывали проигрывание правильных нот, нужно замапить все 72 сенсора на грифе на соответствующую ноту.

Оказалось, что из 72 элементов на 12-ти ладах всего 37 уникальных нот. Они расположены по определенной структуре, так что удалось вместо построения большой таблицы вывести простое уравнение, которое по номеру сенсора выдает номер соответствующей ноты.



Тест

Похоже, все готово для первого теста. Пилить прутки и паять все 12 ладов мне было лень, поэтому ограничился 8-ю. Момент истины:


ITS ALIVE! Жизнеспособность концепта подтверждена. Счастью не было предела! Но нельзя расслабляться.

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

Светодиоды

Для подключения 84 светодиодов я выбрал самый простой пусть daisy chain из 14-ти 8-битных сдвиговых регистров. Их удобно подключить к SPI MOSI выводу STM-ки и слать по DMA массив данных без участия ядра.

Акселерометр

Особых требований к акселерометру у меня не было, поэтому взял самый простой LIS3D. С его помощью гитара будет определять свой наклон относительно горизонта, что позволит модулировать различные звуковые фильтры во время игры движениями рук.

Беспровод

Для беспроводной передачи данных решил поставить ESP32. Оно поддерживает различные протоколы Bluetooth и WI-FI, будет с чем поэкспериментировать (на тот момент я еще не знал, что в моем случае существует только один правильный способ подключения).

Корпус

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

Включение питания будет происходить при раскрытии корпуса за счет приближения магнитика на грифе к датчику Холла на плате дэки.

Начинаю работу


Было проделано много работы по экспериментам с различными конструкциями тактильных элементов грифа и рассеивателями для светодиодов. Хотелось, чтобы равномерно светилась вся поверхность элемента, при этом сохранялась возможность детектирования прикосновения и нажатия на кнопки.



Я обратился к другу, который профессионально занимается пром дизайном. Мы придумали конструкцию узла сгибания гитары, после чего он спроектировал и напечатал прототип корпуса.



Вроде, все продумано, можно начинать разводку платы.











MIDI устройство

В новой версии, в первую очередь я хотел, чтобы при подключении по USB, гитара определялась как MIDI устройство без всяких лишних программ.

Оказалось, сделать это не так сложно, все спецификации есть на официальном сайте usb.org. Но все алгоритмы, которые выполнялись на стороне питонского приложения, пришлось переписывать на C в контроллер.

Я был удивлен, что оно сразу заработало на всех устройствах. Windows 10, MacOS, Debian 9, Android (через USB переходник). Достаточно просто воткнуть провод и в системе появляется MIDI устройство с названием Sensy и распознается всеми синтезаторами. С айфоном пока протестировать не удалось т.к. нет переходника. Но должно работать так же.



Беспроводной интерфейс

Следующая задача организовать работу без проводов.

Погуглить сразу я поленился, поэтому потратил несколько дней на тестирование различных беспроводных интерфейсов. BLE я отмёл сразу, т.к. в моей голове Low energy прочно ассоциировалось с низкой частотой передачи пакетов. Пробовал WI-FI в режиме клиента, WI-FI в режиме точки доступа, Bluetooth в режиме SPP и т.д. Везде была одна и та же проблема огромная задержка (больше 100мс на глаз) и неравномерность прихода пакетов во времени. Это делало игру невозможной.

Я уже собирался сдаться и сделать отдельный донгл, который бы втыкался в USB мобильника или компа и принимал данные с гитары по кастомному радио.

Но тут я случайно наткнулся на спецификации новых версий протокола BLE и увидел, что минимальный connection interval там 7.5мс, что отлично вписывается в мои требования.

Более того, оказалось, что существует протокол BLE MIDI, который поддерживается всеми новыми операционками и работает без всяких драйверов прямо как по USB MIDI.

Единственной проблемой оказалось то, что такой низкий connection interval и вообще BLE MIDI поддерживаются только относительно новыми платформами. Подробности еще предстоит выяснить, но тесты с доступными мне девайсами прошли успешно.

На некоторых новых айфонах даже имеется предустановленный виртуальный синтезатор Garage Band, способный издавать качественные гитарные звуки (если нет, можно скачать в App Store бесплатно).

Прошивка

Написав весь минимальный необходимый функционал, я уперся ровно в размер флэша STM-ки. Свободными осталось всего 168 байт. Очевидно, кремниевые боги мне благоволили, значит иду в правильном направлении.



Можно было бы углубиться в оптимизацию кода и значительно сократить объем занимаемой памяти, но проще будет в следующей версии использовать контроллер потолще, который стоит на 5 центов дороже и не тратить время. Тем более, мало ли какие еще фичи захочется добавить.
Но минимального функционала недостаточно, нужно еще поработать с техниками игры. В первую очередь, хочу реализовать slide. Это когда начинаешь играть ноту с определенным зажатым ладом и проскальзываешь рукой по грифу, перескакивая с лада на лад.

С USB я уже поразвлекался, поэтому можно весь код, связанный с ним, закомментить и освободить память. Тестировать можно и по беспроводу:


При включении всех светодиодов, гитару можно использовать, если вы заблудились в темной пещере.



Какие минусы у этой конструкции?

  1. На сенсорах нигде не измеряется усилие нажатия. Это влечет за собой три проблемы:
    • Постоянно происходят случайные задевания соседних струн как на дэке, так и на грифе. Это делает игру очень сложной.
    • Все играемые ноты извлекаются с одинаковой громкостью. Большинство подопытных этого не замечают, но хотелось бы более приближенной к настоящей гитаре игры
    • Невозможность использовать техники hammer on, pull off и vibrato
  2. Светодиоды одноцветные. Это ограничивает наглядность при игре по табулатурам. Хочется иметь возможность разными цветами указывать на различные приемы игры.
  3. Форма корпуса не подходит для левшей. С точки зрения софта я уже реализовал инверсию струн по акселерометру. Но механический лепесток, необходимый для удержания гитары рукой во время игры, поворачивается только в сторону, удобную правшам.
  4. Отсутствие упора для ноги. Сейчас, при игре сидя, нижняя струна почти касается ноги, а это неудобно.
  5. Сустав сгибания гитары требует осмысления и доработки. Возможно, он недостаточно надежен и стабилен.

Время переходить к разработке следующей версии.


Переезжаю на контроллер серии STM32F07. На нем уже 128КБ флэша этого хватит на любой функционал. И даже на пасхалки останется.

Использовать ESP32 в финальной версии гитары было бы слишком жирно, поэтому я пошел искать что-то более православное. Выбор нал на NRF52 по критериям доступности, наличию документации и адекватности сайта.

Конечно, будут реализованы и три главных нововведения:

  • светодиоды теперь RGB,
  • на каждом сенсоре грифа будет измерение усилия (тактовые кнопки больше не нужны),
  • струны на дэке станут подвижными.

На данный момент плата дэки выглядит так (футпринт ESP на всякий случай оставил):


Проект называется Sensy и сейчас находится в активной разработке. Уже есть полная уверенность в том, что весь задуманный функционал будет реализован, поэтому было принято решение о дальнейшем развитии.

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

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

Кому интересно следить за новостями проекта оставляйте почту в форме на сайте и подписывайтесь на соцсети.

Очень надеюсь на обратную связь от Хабрасообщества с комментариями и предложениями!

Забавный эпизод из процесса разработки
Сижу отлаживаю NRF52, пытаюсь вывести данные через UART. Ничего не выходит. Проверял код, пайку, даже перепаивал чип, ничего не помогает.

И тут случайно нестандартным способом перезагружаю плату в терминал приходит буква N в ascii. Это соответствует числу 0x4E, которое я не отправлял. Перезагружаю еще раз приходит буква O. Странно. Может быть проблема с кварцевым резонатором и сбился baud rate? Меняю частоту в терминале, перезагружаю плату опять приходит N. С каждой новой перезагрузкой приходит по новой букве, которые в итоге составляют повторяющуюся по кругу фразу NON GENUINE DEVICE FOUND.

Что эта NRF-ка себе позволяет? Прошивку я обнулял. Как она после перезагрузки вообще помнит, что отправлялось в предыдущий раз? Это было похоже на какой-то спиритический сеанс. Может, я и есть тот самый NON GENUINE DEVICE?

Залез в гугл, выяснил, что производители ftdi микросхем, которые стоят в USB-UART донглах, придумали способ бороться с китайскими подделками. Виндовый драйвер проверяет оригинальность микросхемы и на лету подменяет приходящие данные на эту фразу в случае, если она поддельная. Очевидно, мой донгл оказался подделкой и переход на другой решил эту проблему.

Снова спасибо китайцам.

Спасибо за внимание!
Подробнее..

MIDI2USB музыка нас связала

09.10.2020 22:10:21 | Автор: admin
Российско-китайско-американский конвертер MIDI в USB.Рис.1 Российско-китайско-американский конвертер MIDI в USB. Фото автора.

Люди любят музыку. Многие умеют играть на музыкальных инструментах. А некоторые пробуют импровизировать и даже сочинять музыку. Электронные музыкальные инструменты можно подключать к компьютеру и получать дополнительные творческие возможности. Это вроде бы простое дело, но большинство дешёвых китайских адаптеров USB-MIDI работают посредственно. Кому интересно, как я сделал свой MIDI2USB-адаптер, приглашаю читать

Постановка задачи


Пару лет назад мой племянник, который учится музыке, начал импровизировать и сочинять музыку. Мне хотелось, чтобы его творчество не пропало, но записывать его музыкальные этюды удавалось только на диктофон. Качество такой записи было неудовлетворительным. Хотелось осуществлять запись нот напрямую в Cubase или MuseScore, а затем их редактировать. Для этого я решил купить китайский адаптер (конвертер) USB-в-MIDI.
Анекдот в тему
Отец ведёт сына в первый класс и говорит:
Вот если будешь хорошо учиться, я тебе куплю компьютер!
Пап, а если плохо?
Тогда куплю фортепьяно!

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

Способы доработки китайского адаптера


В интернете есть немало дискуссий как улучшить или доработать китайский адаптер. В некоторых версиях этого адаптера предусмотрен, но не распаян оптрон, который обеспечивает гальваническую развязку компьютера и синтезатора. Увы, в моём случае доработка была затруднительна, т.к. вместо оптрона установлены два NPN-транзистора. Отмечу, что MIDI-стандарт прямо указывает использовать оптоизолятор, например, PC900V или 6N138. Схожими характеристикам обладают оптопары H11L1M (DIP-8) или H11L1SM (SO-6). Можно использовать и другие компоненты с подходящими параметрами.

Китайский адаптер в процессе демонтажа
Рис.2. Китайский адаптер в процессе демонтажа. Фото автора.

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

Но недостаточно обеспечить оптическую изоляцию музыкального инструмента и компьютера. Требуется ещё точный кварцевый генератор или резонатор, чтобы обеспечить тактирование последовательного интерфейса UART в соответствии со стандартом MIDI. В китайском адаптере, который я купил, отсутствует не только оптопара, но и кварцевый резонатор. Конечно, существуют микросхемы, в которых блоки тактирования калибруются на заводе, но тут ничего подобного нет. В общем, работоспособность этого китайского изделия низкая. Существуют адаптеры, построенные на микросхеме CH345 преобразователе USB в MIDI в корпусе SSOP-20, но это не мой случай. Микросхема CH345 имеет аппаратные USB-метки Vendor ID: 1a86, Product ID:752d. Впрочем, любая левая микросхема может выдавать (и выдаёт) такие же идентификаторы и даже может притвориться чем угодно.

Последний небольшой недостаток, который я выявил в китайском адаптере это программное обеспечение (прошивка). Если говорить точнее это малый размер буфера для конечных точек (EndPoints), всего по 8 байт. Этого достаточно для передачи нажатых нот, потому что MIDI-сообщение по USB интерфейсу состоит из 4 байт (номер кабеля, номер команды и 2 байта данных). А вот всякие расширения, например SysEx, могут быть большего размера.

Через некоторое время я купил другой кабель-адаптер, который носил громкое название Professional USB MIDI Interface. Этот адаптер стоил существенно дороже и работал значительно лучше, но всё равно с ошибками. Проявлялось это в том, что спустя несколько минут игры на синтезаторе, он вдруг начинал пропускать нажатия клавиши или наоборот не воспринимал отпускание клавиши. Я был разочарован результатами работы китайских адаптеров я и решил последовать совету: Если хочешь сделать что-то хорошо, то сделай это сам.

Аппаратная часть


Сначала надо было продумать схему будущего устройства и изучить опыт других инженеров. Имеющийся адаптер внешне выглядел очень хорошо, поэтому я решил использовать от него корпус, светодиоды и экранированные кабели. Тем более, что в Москве MIDI-кабели стоят дороже, чем готовый китайский адаптер. Китайскую плату я вытащил, измерил её габариты и стал изучать MIDI-стандарт и удачные MIDI-проекты в открытом доступе.

Рис.3 Адаптер USB-MIDI в корпусе и с кабелями.

На момент написания этой статьи мне известны несколько интересных проектов:
  1. Схема из документации на чип CH345 фирмы Nanjing Qinheng Microelectronics.
  2. Старые проекты на микроконтроллерах Atmega с программной реализацией протокола USB. В них используется режим Low Speed, который устарел и не поддерживается в Windows 7.
  3. Библиотека MIDIUSB для плат семейства Arduino с аппаратной поддержкой USB-интерфейса (Atmega32u4, Cortex-M), а также Maple и т.д.

Электрические принципиальные схемы во всех проектах содержат много типовых фрагментов, основанных на рекомендациях стандарта MIDI. Поэтому оставалось выбрать микроконтроллер с поддержкой USB режима Full Speed, найти в продаже оптрон PC900V и розетку DIN-5 (MIDI).

Сердцем моего MIDI2USB адаптера стал 8-битный микроконтроллер EFM8UB20F64G фирмы Silicon Laboratories. Мне он очень нравится, и я использую его везде, где могу. Этот контроллер является преемником (после ребрендинга) контроллера С8051F380, который пришёл на смену легендарному C8051F320 удачной разработке фирмы Cygnal, которую в 2003 купила SiLabs.

Перечислю свои аргументы в пользу микроконтроллера EFM8UB20F64:
  • удобство разработки ПО, которое выражается в наличии быстрых и простых в использовании GPIO, SPI, UART, USB, PCA;
  • улучшенное 8051-ядро (1-2 такта на команду, 48MIPS), изменение частоты на лету;
  • встроенный регулятор напряжения, толерантность выводов к +5В, ток до 100 мА;
  • встроенный точный тактовый генератор с калибровкой от USB-хоста ( 0.25%);
  • наличие библиотек USBXpress, VCPXpress, USB Device API и примеры для быстрого старта;
  • чистая errata.

Программировать этот контроллер приятно, т.к. регистров мало и можно сосредоточиться на решении прикладной задачи. Увы, арифметические операции (особенно 32-битные) выполняются медленно, но в остальном EFM8 хорош. Разработка программного обеспечения для USB-устройств это не простая задача. И тут есть главное преимущество контроллеров SiLabs это библиотеки USBXpress, VCPXpress, USB Device API. Даже фирма Texas Instruments в своих платах SmartRF использует контроллеры C8051F320.

Оптрон это второй по важности компонент в адаптере. Я решил взять Sharp PC900V, потому что именно он указан в рекомендуемой схеме MIDI-спецификации. Особенность этого оптрона быстрые времена включения и выключения (1мкс и 2мкс), а также наличие цифрового выхода. Но есть и недостатки большие размеры микросхемы (7х10мм) и выгорание на 50% через 5 лет эксплуатации. Габариты оптрона не позволили разметить все компоненты на одной стороне платы. Ещё мне не хотелось отказываться от разъёма MIDI, который занимал много места.
Задняя сторона платы с оптроном и светодиодами.
Рис.4 Задняя сторона платы с оптроном PC900V и светодиодами. Фото автора.

Выходной каскад собран по рекомендованной стандартом схеме на логической микросхеме 74LVC2G04, состоящей из двух инверторов. Основная цель этого компонента преобразование уровней логических сигналов из 3В => 5В и обеспечение выходного тока не менее 10 mA.

Ещё анекдот
На конкурсе песни выступает чукча:
-Увезу тебя я в тундру, зелёный, увезу к седым снегам, белой шкурою медвежьей, красный, брошу их к твоим ногам
И так всю песню. Председатель жюри спрашивает:
А почему у вас в песне слова какие-то странные?
Цветомузыка, однако!

Остальные компоненты выполняют вспомогательные функции и не оказывают существенного влияния на работу устройства. Резисторы, конденсаторы, диоды и светодиоды могут быть заменены в разумных пределах. Вместо разъёма mini-USB можно поставить micro-USB или сделать штыревой разъём под пайку кабеля, как делают китайцы. Разъём MIDI занимает много места и в корпус не помещается, поэтому он используется только в версии адаптера без корпуса. Сигналы MIDI-IN и MIDI-OUT выведены на штыревой разъём для распайки кабеля. В общем, следовало бы скорректировать расположение светодиодов и разъёмов для их оптимального расположения в корпусе.

Рис.5 Отладочная и коробочная версии адаптера MIDI2USB. Фото автора.

Общий ток потребления не превышает 50 mA. Он складывается из следующих частей:
  • микроконтроллер, 15mA;
  • три светодиода, 15mA (3х5mA);
  • микросхема 74LVC2G04, 10 mA;
  • оптрон PC900V, 10 mA.

Двухслойная печатная плата была изготовлена американцами в OSH Park, толщина 1.6мм, медь 0.035мм, материал FR-4.

Программная часть


Создание программного обеспечения для оборудования важный и ответственный этап разработки. К счастью, во всех современных операционных системах есть драйверы для MIDI устройств, подключаемых к порту USB. Задача сокращается и требуется написать только прошивку (firmware) для адаптера.

Обычно я использую Keil uVision PK51 совместно с Configuration Wizard 2, иногда IAR Embedded Workbench, и совсем редко SiLabs Simplicity Studio. Каждая среда имеет достоинства и недостатки. В этом проекте я решил использовать IAR, потому что хотелось иметь С с классами. Кроме того, компилятор IAR предоставляет доступ ко всем битам системных регистров. Например, P2_bit.B0 = 1; или PCA0MD_bit.WDTE = 0;
Нет необходимости использовать магические константы или многоэтажные битовые выражения, которыми пестрят CMSIS или SI_EFM8UB2_Register_Enums.h. Увы, весь этот функционал объявлен в файле ioEFM8UB20F64G.h, который оказался не совместим с библиотеками si_toolchain.h (например, макрос B0..B3). Переводить проект в Keil uVision PK51 я не стал, а просто писал совместимый код на С для всех сред разработки.

Код проекта разделён на несколько функциональных частей.


  1. В файле main.c находится точка входа, объявления глобальных переменных, вызов инициализация периферии и главный цикл программы.
  2. В файл init.c содержит настройку тактирования, портов, UART и его прерываний.
  3. В файле descriptors.c можно найти USB-дескрипторы для устройства типа Audio Class.
  4. В файле midi.c находятся две функции для преобразования MIDI-сообщений в USB-события и обратно. Используется автомат состояний.
  5. Файл usbconfig.h содержит макросы и определения (#define) для настройки режимов работы библиотеки USB Device API.

Посмотрим на функцию main() с настройкой портов, периферии и главным циклом.
int main( void ){WDT_Init();                             // Disable WDTimer (not used)PORT_Init();                            // Initialize ports (UART, LEDs)SYSCLK_Init();                          // Set system clock to 48MHzUART0_Init();                           // Initialize UART0 @31250, 8-N-1USBD_Init( &usbInitStruct );            // Initialize USB, clock calibrateLED_IN  = 1;                            // Blink LEDLED_OUT = 1;                            // Blink LEDIE_EA   = 1;                            // Global enable IRQwhile(1){//--- MIDI => USBif( nMidiCount > 0 ){IE_EA  = 0;                     // Begin: Critical sectionif( USB_STATUS_OK==USBD_Write(EP1IN,aMidiBuffer,nMidiCount,false) ){nMidiCount = 0;             // Reset MIDI data byte counter}IE_EA  = 1;                     // End of: Critical sectionLED_IN = 0;                     // Turn off input LED}//--- USB => MIDIif( nUsbCount ){uint8_t i;LED_OUT = 1;                    // Turn on Led on New packetfor(i = 0; i < nUsbCount; i++)  // Process every data byte{USB2MIDI( aUsbBuffer[i] );  // Convert USB packet into MIDI}nUsbCount = 0;                  // Reset counterUSBD_Read(EP2OUT, aUsbBuffer, sizeof(aUsbBuffer), true);LED_OUT = 0;                    // Turn off Led, when done}}}


Библиотека фирмы SiLabs для USB-устройств состоит из набора подпрограмм, которые компилируются и включаются в проект в зависимости от настроек в файле usbconfig.h. Это очень напоминает библиотеку libusb, V-USB, которую можно встретить в коде для микроконтроллеров фирмы Atmel (ныне Microchip). Надо отметить, что у SiLabs получилась хорошая и удобная библиотека с точки зрения программиста.

Важную роль в работе любого USB-устройства играют описатели (дескрипторы) устройства, конфигурации и интерфейсов. С помощью этих дескрипторов устройство сообщает хосту (компьютеру) о своих требованиях, возможностях, параметрах и т.д. Функция обработки запросов дескрипторов обычно имеется в каждой USB-библиотеке, а от программиста требуется лишь правильно заполнить структуры данных, содержащих эти дескрипторы.
Код с дескрипторами
SI_SEGMENT_VARIABLE(usbDeviceDesc[], const USB_DeviceDescriptor_TypeDef, SI_SEG_CODE) ={USB_DEVICE_DESCSIZE,               // bLength, 18 bytesUSB_DEVICE_DESCRIPTOR,             // bDescriptorType, 1htole16(0x0110),                   // bcdUSB Ver, 1.100x00,                              // bDeviceClass, 0 for Audio0x00,                              // bDeviceSubClass, 0 for Audio0x00,                              // bDeviceProtocol, 0 for AudioSLAB_USB_EP1IN_MAX_PACKET_SIZE,    // bMaxPacketSize0, 64 byteshtole16(0x1209),                   // idVendor, Free GPL (SiLabs 0x10C4)htole16(0x7522),                   // idProducthtole16(0x0100),                   // bcdDevice, 1.000x01,                              // iManufacturer string0x02,                              // iProduct string0x03,                              // iSerialNumber (no serial string)0x01                               // bNumConfigurations};


Обо всех дескрипторах, топологии и терминологии подробно и детально написано в стандарте Universal Serial Bus Device Class Definition for MIDI Devices. А для быстрого старта и погружения в тему достаточно изучить информацию, которую предоставляют программы usbview.exe из пакета Windows Driver Kit 7600 или USB Descriptor Dumper. Кое-что можно даже скопировать к себе в программу.

Рис.6 Информация о дескрипторах в программе usbview.exe

Дескрипторы и соответствующие массивы и структуры размещается во флэш-памяти микроконтроллера (сегмент кода), потому что эти данные не изменяются (константы). Хранение констант во флэш-памяти типичный программистский приём, который позволяет экономить оперативную память.

Следует обратить внимание на поля Vendor_ID и Product_ID в структуре описателя устройства. Это пара чисел для уникальной идентификации USB-устройства. Чтобы получить для своего устройства такой номер надо заплатить денег организации USB-IF или направить запрос владельцу существующего Vendor_ID (производителю микроконтроллеров) и получить Product_ID. А можно, например, как китайцы использовать чужие наиболее подходящие VID & PID. Для открытых проектов есть вариант получить бесплатно Product_ID.

Ещё один момент, на который следует обратить внимание при разработке USB-устройств звукового класса MIDI Streaming это разъёмы (Jack). Разъёмы это воображаемые (виртуальные) сущности для описания топологии и связей между устройством и хостом. Они бывают входные (In Jack) и выходные (Out Jack), внутренние (Embedded) и внешние (External). У каждого разъёма есть уникальный идентификатор Jack_Id (число от 0 до 15). Выходные разъёмы содержат номер источника Source Id, т.е. номер разъёма для подключения. Наконец, поверх образованных каналов (потоков ввода и вывода) работают звуковые конечные точки (audio end-point, EP). Это почти обычные Bulk EP, у которых в дескрипторах есть информация о привязке к разъёму.
Embedded and External Jacks
Рис. 7 Разъёмы Jacks и виртуальные потоки в USB (класс MIDI).

Обмен данными в звуковом USB-устройстве класса MIDI заключается в передаче 32-битных пакетов (USB-MIDI Event Packet). Из MIDI-устройства приходят сообщения длиной 1, 2 или 3 байта. При передаче по USB к этим байтам добавляется головной байт с номером кабеля и кодом команды. Если пакет получается менее 4 байт, то он дополняется 0. В текущей версии прошивки я не заполняю нулями до 32-битной границы. Это работает. Вопрос остаётся открытым.
Например, в кабеле 1 команда нажатия клавиши Note On (время передачи 960us) преобразуется в следующий пакет:
MIDI: 0x90 0x60 0x7f => USB: 0x19 0x90 0x60 0x7f

USB-MIDI Event Packet
Рис.8 Схема пакета USB-MIDI Event Packet из USB спецификации.
typedef union{struct PACKET{uint8_t  cable : 4;            // Cable Number (we use #0)uint8_t  cin   : 4;            // Code Index Number (cmd: 0x08)uint8_t  cmd;                  // MIDI command (status byte)uint8_t  data1;                // MIDI data byte #1uint8_t  data2;                // MIDI data byte #2};uint8_t buffer[sizeof(struct PACKET)];} MIDI_EVENT_PACKET;


Прямое и обратное преобразование выполняются функциями MIDI2USB(uint8_t dataByte) и USB2MIDI (uint8_t dataIn). В этих функциях применён автомат состояний, когда по мере поступления входных данных функция переходит из состояния ожидания (IDLE) в состояние приёма команд (STATUS), а затем в состояние приёма данных (DATA), и, наконец, отправка данных с возвратом в исходное состояние ожидания.

В MIDI-протоколе байты данных в сущности являются 7-битными (0..127). У них всегда старший 8-ой бит установлен в 0. Команды (байты статуса) наоборот всегда идут с установленным старшим битом в 1, т.е. имеют значения от 128 до 255.
Types of MIDI bytes
Рис. 9 Типы байтов в MIDI-протоколе.
Анекдот про разрядность чисел
Телефонный звонок:
Алло, это квартира Сидорова Ивана Петровича?
Hет, это квартира Каца Абрама Самуиловича.
Извините, это 11-22-33?
Hет, это 11-22-34.
Hадо же! В шестом знаке ошибка, а такой эффект!


Все схемы и исходные тексты, а также готовая прошивка находятся у меня в git-хранилише. Лицензия MIT.

Программное обеспечение


После монтажа платы следует запрограммировать микроконтроллер. Для этого можно использовать или фирменный/клон SiLabs C2 Debug Adapter, или J-Link v10+ (с поддержкой EFM8), или прошитый на заводе bootloader (ревизия Rev-B), или, наконец, Arduino с соответствующим скриптом. Для проверки и отладки MIDI-сообщений очень помогает программа MIDI-OX.
MIDI-OX
Рис.10 Интерфейс программы MIDI-OX.

Если работать с Cubase, то следует установить Asio-драйверы, потому что при использовании DirectSound и DirectInput наблюдается задержка между нажатием клавиши и воспроизведением ноты. Задержка не связана с аппаратной частью и является особенностью реализации ОС. В общем, устройство отлично выполняет свои функции с инструментом Casio CDP-100.
Cubase MIDI Configuration
Рис.11 Интерфейс программы Cubase 5.

Экспериментальные прошивки генерировали максимально возможный поток нот и других MIDI-команд. Какофония была ужасная, но всё работало, как задумано. А с помощью MuseScore 3.2 можно записывать и воспроизводить mid-файлы.
Анекдот последний
1990-е. Братва празднует новый год в ресторане. Из музыки только караоке. Все недоволны. Братки отлавливают администратора:
Слушай, подгони музыкантов!
Да, вы что! Новый год все заняты!
Ты не понял! Быстро музыкантов!
Ну есть у меня одна группа. Играют крутой джаз!
Давай! Крутой джаз нам подходит!
Приезжают джазмены. Распаковываются и начинают играть. Одну, пьесу, другую, третью В зале становится тихо. Братки шепчутся, трезвеют. Один из братков подходит к сцене и спрашивает:
Что, пацаны, не получается?


Результаты работы


Адаптер работает! Кажется, мне удалось сделать добротный конвертер MIDI в USB. Для своего устройства я использовал корпус, некоторые детали и кабели от китайского адаптера. Mini-USB разъём оказался глубоко в корпусе и пришлось переделывать USB-кабель и поработать напильником. Светодиоды хотя и под углом, но плотно вошли в отверстия. Требуется доработка платы под китайский корпус.
Mini-USB cable
Рис. 12. Компактная разобранная вилка mini-USB.

Решение применить 8-битный микроконтроллер EFM8UB20 кому-то может показаться спорным. Конечно, есть и другие варианты и контроллеры. Альтернативный путь это выбрать сугубо аппаратное решение на преобразователе CH345 и сделать устройство по рекомендованной китайцами референс-схеме. Но мой вариант универсальный, т.к. позволяет изменить прошивку, добавить нужный функционал или исправить найденные ошибки. В конце концов я получил знания, опыт и моральное удовлетворение от законченного проекта. И, наконец, я дописал статью, а вы её дочитали.

Полезные ссылки



Спасибо за внимание.
Подробнее..

Как я делаю цифровую минигитару. Часть 2

08.03.2021 14:16:44 | Автор: admin

Прошло 4 месяца с написания предыдущей статьи, за это время произошло довольно много нового как по технической части, так и по позиционированию девайса.

Напомню какие требования к девайсу я поставил, когда начинал разработку:

  • Устройство должно имитировать гитару с 6-ю струнами и 12-ю ладами на грифе

  • Должно быть компактным, в идеале складным, чтобы можно было брать его с собой куда угодно

  • Должно подключаться ко всем популярным осям Android, IOS, Windows, Linux, MacOS и определяться там как MIDI устройство без каких-либо драйверов

  • Работа от аккумулятора

  • Подключение должно производиться без проводов по Bluetooth Low Energy (но раз уж там будет USB разъем для зарядки, то и по проводу пусть тоже подключается)

  • Возможность сразу начать играть, без необходимости в долгих тренировках по адаптации кистевых связок

  • На каждой струне и каждом элементе грифа должно быть по светодиоду, чтобы можно было запустить табулатуру мелодии, и гитара сама показывала куда нужно прикладывать руки

  • Возможность использования основных техник игры на гитаре: hummer on, pull off, slide, vibrato

  • Задержка передачи midi команд не более 10мс

  • Все должно собираться из подручных материалов без сложных техпроцессов и дорогой электроники

Реализовать мне все это удалось, и даже больше. Был дополнительно интегрирован акселерометр для управления параметрами фильтрации звука наклоном гитары и вибромотор (правда, я до сих пор не придумал зачем).

На момент написания предыдущей статьи выглядело оно так:

Было принято решение пытаться делать стартап и выходить на кикстартер.

И так, что было дальше?

Следующим шагом стал стандартный этап поиска pre-seed раунда инвестирования. Деньги нужны были на доработку и изготовление нового прототипа, проведения пиар кампании и оплату юридических манипуляций (для участия на кикстартере необходимо юр. лицо в США со всеми вытекающими организационными тратами). Эту задачу нам с моим партнером, отвечающим за бизнес процессы, удалось решить за 2 месяца.

Позиционирование

Для подтверждения полезности продукта и его функционала мы решили провести встречи со всеми Питерскими и Московскими музыкантами, с которыми смогли связаться, и заодно поснимать видеороликов для соц сетей.

Профессиональные гитаристы отнеслись к девайсу ожидаемо скептически. И я их полностью понимаю, это не замена настоящей полноценной гитаре. Это девайс для применений, в которых важны портативность, универсальность, простота, возможность играть в наушниках и т.д. Сидишь, например, в самолете, вокруг шум, суета, плачущие дети, кислородные маски выпадают. Спать невозможно. И тут внезапно наступает вдохновение, достаешь девайс, надеваешь наушники и записываешь новый трек.

Широкой публикой гитара была оценена очень позитивно не смотря на пока что ограниченный функционал и большие конструктивные неудобства. Девайс оказался настолько интересным, что даже популярный в рунете блогер Ваганыч снял на него обзор.

По результатам общения мы открыли для себя новую целевую аудиторию - саунд дизайнеры, мьюзик мейкеры - люди, которые пишут цифровую музыку. Оказалось, у них есть большие сложности с записью гитарных и других струнных партий на миди клавиатурах. Это очень сложно, долго, муторно настолько, что зачастую им приходится отказываться от струнных инструментов, органичивая свое творчество. Наш девайс они восприняли с максимальным энтузиазмом, пророча ему большую популярность.

Конкуренты

Есть на этом рынке и конкуренты. Мы купили по экземпляру каждого для оценки.

1. Artiphon - панель, чувствительная к нажатию, по форме напоминает гитару, но позиционируется скорее как настольная клавиатура.

Интересная, но дорогая штука. В целом, работает неплохо, можно извлекать разнообразные звуки. Имеет встроенные динамики, но лучше их не включать. Подключается только по USB.

2. Jammy - гитарный форм-фактор, состоит из разъединяющихся элементов с реальными струнами. Датчики независимо отслеживают удары по струнам на деке и прикосновение струны к ладу на грифе. Знакомым гитаристам не удалось сыграть на ней что-то внятное - ноты то не извлекались, то извлекались по нескольку раз. Возможно, требуется длительное привыкание. На реальных роликах в интернете также не удалось найти полноценной гитарной игры, в основном это игра медленным перебором по отдельным струнам.

3. Jamtik - игрушка с 7-ю ладами на батарейках. Сыграть на ней не удалось даже В траве сидел кузнечик.

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

Теперь самое интересное новый прототип.

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

  • Конечно, RGB подсветка

  • Подвижные струны на деке с детектированием как касания, так и величины отклонения при извлечении ноты. Это позволит избавиться от проблемы задевания пальцами соседних струн и расширит возможности игры за счет поддержки velocity (разные параметры нарастания звука и громкости ноты) и глушения струн прикосновениями как в настоящей гитаре

  • Детектирование силы нажатия на сенсоры грифа для реализации стандартных гитарных техник игры

  • Встроенный синтезатор со встроенной библиотекой инструментов и разъем Jack 3.5мм для подключения наушников или внешних колонок. Встроенные динамики делать не стал добиться хорошего звучания было бы слишком сложно и дорого

  • Мобильное приложение со встроенным качественным синтезатором и функционалом обучения

  • Пады с подсветкой для записи лупов

  • Упоры на деке и удобного удержания сидя и стоя, крепления для ремешка

Корпус

Корпус получил множество изменений, особенно в узлах со струнами на деке и в механизме складывания. Сенсоры на грифе теперь покрыты матовыми рассеивателями и светятся всей поверхностью.

Основная задача - сделать девайс таким, чтобы был применим мануальный опыт игры на настоящей гитаре. Были проработаны расстояния между ладами, механика струн, геометрия деки и равесовка. Пока что это только рендеры корпуса, но изготовление прототипов уже идет полным ходом.

Электроника

Электронику пришлось разделить на 4 платы:

  • Гриф

Адресные RGB светодиоды подключены последовательно к одной ноге STM-ки. Пришлось повозиться с двойной буферизацией и выводом данных через DMA 100 раз в секунду. Зато, теперь оно работает очень быстро и можно запускать цветные визуальные анимации на поверхности грифа.

  • Плата с падами и подпружиненными контактами для соединения с грифом в разложенном состоянии гитары

  • Основная плата со струнами, мозгами, силовой частью, радио частью, синтезатором и датчиками

Здесь происходит детектирование как прикосновения к струнам, так и величины их отклонения. Это решает проблему со случайными задеваниями соседней струны при игре и дает возможность глушить струны как на настоящей гитаре.

  • Плата с разъемами USB type-C, Jack 3.5мм и тремя индикаторными светодиодами

Мобильное приложение

После выкладывания предыдущей статьи, мне написал мобильный разработчик Юрий Дубовой с предложением помочь в разработке приложения под iOS.

Мы сформировали протокол общения гитары с приложением и разбили его на несколько интерфейсов:

  • Midi команды, разумеется, по умолчанию передаются по стандартному BLE-Midi интерфейсу. Таким образом, к приложению при желании можно будет подключить и другие midi устройства, например, клавиатуру

  • Опционально поддерживается прием midi команд и по проводному USB-Midi интерфейсу. Это будет полезно для старых телефонов без поддержки BLE, а также в случае необходимости сокращения задержки до минимума (порядка 5мс)

  • Отдельный кастомный BLE сервис для передачи уникальных команд, связанных с управлением светодиодами, режимами работы девайса, синхронизацией состояния и т.д.

  • Стандартный BLE battery service для передачи уровня заряда аккумулятора. Он поддерживается на уровне операционной системы и, в случае в виндой, даже отображается соответствующая иконка в панели устройств

Приложение разбито на несколько экранов, соответствующих разным режимам работы:

Свободная игра

В этом режиме пользователь выбирает один из инструментов (акустика, электрогитара, пианино, укулеле, барабаны, и т.д.) и просто играет как ему хочется. Есть возможность загружать свои инструменты в виде саундфонтов в формате .sf2.

Игра по табулатурам

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

Обучение

Пока у нас нет даже прототипа этого режима, но предполагается интерактивное обучение нотной грамоте и гитарным основам в игровой форме с использованием светодиодов на девайсе.

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

Теперь играть на ней можно тремя разными способами:

  1. Подключение через BLE MIDI протокол к телефону или компу, где девайс распознается как миди устройство, и игра через внешние виртуальные синтезаторы (Ableton, FL studio, Garage Band и т.д. или наше приложение)

  2. То же самое, но с подключением через USB MIDI (работает со всеми хостами, которые я проверял Android, IOS, Windows, MacOS, Debian)

  3. Игра внутренним синтезатором, с подключением наушников или внешней колонки напрямую в гитару. В этом случае звук будет не самым Hi-Fi, но вполне приемлемым для игры для себя

Интересно, что можно играть всеми тремя способами одновременно, может кому-то пригодится.

Завершение

После завершения изготовления корпуса и тестирования нового прототипа я планирую написать следующую статью, в которой будет больше технических подробностей. Если среди читателей Хабра есть люди, желающие поучаствовать в создании контента, поделиться предложениями или помочь с продвижением пожалуйста, пишите мне. А также будем рады помощи с изготовлением корпуса и разработкой мобильного приложения.

Кому интересно следить за новостями проекта или оформить предзаказ оставляйте почту в форме на сайте и подписывайтесь на соцсети.

Спасибо за внимание! Буду рад обратной связи в комментариях.

Подробнее..

Моя первая Ардуинка переключатель USB

04.04.2021 20:17:04 | Автор: admin

Пару лет назад я обзавёлся топовым смартфоном одной южнокорейской компании. Среди его особенностей оказалась поддержка DeX - способа запуска на большом экране, подключаемом к док-станции через HDMI, отдельных приложений и даже целого Linux в контейнере (к сожалению, последнее ушло со свежей версией Android). Кроме того, порадовала поддержка переферийных устройств - так, внешняя звуковая карта Asus Xonar U7, с которой у меня пущен сигнал на ресивер с большими колонками, завелась без проблем. Отсюда возникло желание превратить телефон в мини-рабочее место, научив делить переферию с системником - напирмер, чтобы вой кулеров не мешал слушать музыку или смотреть видео. По сути, требовалось KVM-решение, удовлетворяющее ряду хотелок. Так я познакомился с Arduino.


Требования

До этого у меня на столе "жил" простой четырёхпортовый USB-хаб с клавишей отключения. К нему были подключены проводные клавиатура и мышь, а также Web-камера. Камеру я обычно физически отключал от хаба вне времени использования (паранойя), а клавиша отключения на хабе оказалась удобна тем, что позволяла с комфортом чистить клавиатуру и мышь, не выдёргивая разъёмы и не выключая компьютер. Кроме того, в хаб периодически включался аппаратный менеджер паролей. Звуковая карта и принтер были подключены напрямую к разъёмам на системном блоке, и этим набор USB-переферии ограничивается. Все перечисленные устройства работают по стандарту USB 2.0. На док-станции присутствуют два порта USB той же версии.

Заводить отдельную клавиатуру для телефона, конечно же, не хотелось. Кроме того, я очень привык к удобной мыши Logitech G600 с двенадцатью хорошо прощупываемыми дополнительными кнопками под большим пальцем, обладающими тем преимуществом, что назначенные на них комбинации работают под любой операционной системой и даже в BIOS без установки дополнительного ПО. Поэтому я решил использовать существующие клавиатуру и мышь, но делить их между устройствами. Кроме того, должен существовать способ отключить их совсем. Звуковая карта тоже должна переключаться между хостами и отключаться (чтоб не жрала энергию в отсутствие питания от сети). Отключение камеры решено было внести в ту же систему. Принтер остался в стороне, поскольку смартфон не захотел с ним работать.

Таким образом был сформирован следующий список требований к "умному" USB-переключателю:

  1. Клавиатура и мышь должны совместно переключаться между телефоном и системным блоком, а также отключаться.

  2. Звуковая карта должна переключаться между устройствами и отключаться независимо от клавиатуры и мыши.

  3. Камера должна подключаться к системному блоку и отключаться независимо от других устройств. Факт того, что камера сейчас подключена (даже если на ней самой не горит лампочка), должен привлекать к себе внимание (например, миганием светодиода).

  4. Переключения могут производиться вручную (кнопками) или автоматически (логикой хаба, например, при отключении или подключении устройств).

  5. Монитор переключается независимо от остальных устройств. Поскольку видеокарта и док-станция включены на разные его входы, это делается кнопкой на самом мониторе.

  6. Под рукой должен быть хаб или USB-разъём на гибком проводе для подключения менеджера паролей к системному блоку. Это может быть простой удлиннитель, уходящий под стол.

  7. Телефон может быть подключен к переключателю как через док-станцию с внешним питанием, так и напрямую (например, чтобы включать музыку во время мытья пола, когда на сетевом фильтре под столом и, соответственнно, на док-станции нет питания). Желательно иметь для подключения через док-станцию и напрямую разные интерфейсы, чтобы не выдёргивать каждый раз разъём из док-станции. Вместо телефона может также быть подключен ноутбук (если HDMI-кабель с док-станции вручную переключить на него же, получится полноценное рабочее место, пока стационарный компьютер на обслуживании).

  8. Питание на переключателе и на подключенных к нему подчинённых устройствах должно присутствовать, если включено хоть одно хост-устройство (системный блок, док-станция или телефон).

  9. Должна присутствовать индикация, показывающая текущее состояние коммутации.

  10. Вся конструкция должна занимать не слишком много места на столе.

Из имеющихся на рынке решений мне на глаза попались KVM-переключатели, а также специализированные переключатели USB вроде этого. Все встреченные мной модели работали по принципу "всё или ничего", коммутируя все подключенные к ним устройства совместно. Мне же хотелось иметь возможность, например, слушать музыку с телефона во время создания образа системного диска с использованием клавиатуры и мыши, а также отключать камеру независимо от остальных устройств. Кроме того, было желание поковыряться и попробовать собрать что-то самостоятельно. Друг посоветовал посмотреть в сторону Arduino, и я начал изучение.

Элементная база

Моё первое представление, составленное со слов друга, сводилось к тому, что для решения моей задачи достаточно будет подключить к контроллеру один или несколько шилдов с достаточным количеством USB-разъёмов, кнопок и индикаторов, а потом запрограммировать контроллер так, чтобы обеспечить коммутацию пакетов USB. Это оказалось неверным по нескольким причинам.

  1. Шилды с мастер-разъёмами USB предназначены для того, чтобы контроллер мог сам опрашивать подключенную к ним переферию, выступая ведущим устройством на шине. При этом непосредственно в формировании и разборе пакетов Arduino не участвует - это делает сторонняя микросхема.

  2. Слэйв-разъёмы предназначены почти исключительно для прошивки и отдалки контроллера, а за коммуникацию по ним отвечает не сам контроллер, а отдельная микросхема, обеспечивающая конвертацию серийных протоколов и определяющаяся как COM-порт. Заставить её вместо этого представиться USB-хабом и начать пропускать пакеты от других устройств невозможно.

  3. Сами контроллеры не применяются для коммутации пакетов, и я не уверен, что мне с моим нулевым опытом на этой платформе удалось бы решить эту задачу, равно как и что она вообще имеет решение.

В этот момент я понял, что придётся паять. Опыта самоделок такого уровня у меня не было - тем выше был соблазн попробовать.

Идея состояла в том, чтобы взять два обычных хаба USB 2.0 (в сборе либо в виде микросхем) и под управлением Arduino коммутировать связи между ними и внешними устройствами, добиваясь того же эффекта, как если бы я вручную перетыкал разъёмы. Прежде всего это касается пары сигнальных проводов, поскольку подачу питания можно организовать отдельно. Вооружившись каталогом одного из магазинов радиодеталей, я приступил к поискам подходящей элементной базы.

Найти контроллеры хабов USB в таком виде, чтобы их можно было распаять на плате, мне не удалось - возможно, потому, что я неправильно искал. Поэтому я просто зашёл в компьютерный магазин рядом с домом и купил два самых простых и дешёвых хаба. Дома я вскрыл корпуса, извлёк из них платы и удалил с них разъёмы. Остались небольшие (примерно 2 на 8 сантиметров) панели, которые можно спрятать в корпус устройства, соединив с остальными компонентами монтажным проводом.

С коммутирующими элементами мне повезло больше - на глаза попался переключатель шины FST3125. Он представляет собой четыре ключа, каждый из которых замыкает либо размыкает два вывода:

FST3125FST3125

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

Несложный подсчёт показывает, что одной такой микросхемы достаточно для переключения пары выводов D+ и D- одного устройства USB между двумя источниками. Стало быть, для управления одним каналом коммутации требуется два бита (или два вывода ардуинки):

Прямое управление ключамиПрямое управление ключами

Тем не менее, напрямую подавать управляющий сигнал от контроллера к коммутатору мне не хотелось. Дело в том, что, помимо трёх нужных мне состояний ("отключено", "подключено к первому источнику", "подключено ко второму источнику"), такая схема подключения даёт возможность открыть все ключи одновременно (например, из-за ошибки в программе или во время загрузки, когда значения выходных сигналов ещё не выставлены), что приведёт к соединению шин данных сразу трёх устройств, два из которых являются ведущими. Чтобы избежать такого сценария, я вставил между ардуинкой и коммутатором аппаратную логику, исключающую возможность одновременного открытия всех ключей:

Подключение FST3125, исключающее одновременное открытие всех ключейПодключение FST3125, исключающее одновременное открытие всех ключей

Инвертор перед нижним элементом "или" позволяет сигналу "SIDE" выбирать группу ключей, которая будет открыта. В то же время, высокий сигнал на входе "ON" отключает все ключи сразу.

В качестве контроллера я взял Arduino Nano. Питание на него я подал через пин +5V, а вообще подачу питания организовал через пятивольтовые механические реле V23079A1001B301 (почему не через полупроводники - расскажу ниже). Светодиоды взял первые попавшиеся - убедившись, что через 220-омные резисторы ток и яркость получаются адекватными. Управление яркостью светодиодов взяли на себя встроенные ШИМы контроллера.

Поскольку ножек у Ардуинки не хватало, управление светодиодами (а также некоторой другой логикой на плате, см. ниже) я организовал через микросхемы серии AC32 (на макетке пробовал сначала DIP-исполнение, а потом перешёл на SOIC). Кроме того, в нескольких местах используются инверторы - модели LS04.

Дизайн

Теперь нужно было определиться с тем, как мой умный хаб должен выглядеть и управляться. Я начал с дизайна хаба, лежавшего у меня на столе, и мысленно дополнил его недостающими элементами. Этот процесс показан на рисунке ниже.

Готовый USB-хаб (слева) и проект "умного" хаба (справа)Готовый USB-хаб (слева) и проект "умного" хаба (справа)

Работать такое устройство должно было следующим образом. Пять разъёмов USB-A, расположенных столбиком, по проекту были объединены в четыре группы и управлялись четырьмя кнопками (на рисунке - справа). Первая группа (два разъёма) предназначалась для клавиатуры и мыши, вторая (один разъём) - для звуковой карты, третья (один разъём) - для камеры, четвёртая (один разъём) была оставлена про запас.

Под крышкой находились те самые два хаба, подключенные к ведущим устройствам через три разъёма USB-B, расположенные в верхнем торце устройства. Правый разъём предназначен для подключения к компьютеру и всегда связан с "правым" хабом. К левому и среднему разъёмам подключались телефон и док-станция: когда телефон подключен, "левым" хабом владел он, в противном случае хаб доставался док-станции. Верхний ряд светодиодов отображал состояние "сторон"-хабов: зелёный - подключено к постоянному устройству, оранжевый - подключено к портативному устройству (телефону), красный - отключено.

Светодиоды с правой и левой стороны от каждого разъёма показывали, к какой стороне он сейчас подключен (а также к какой стороне он подключится при изменении внешних условий, например, возвращении питания на данную сторону). Первые две группы устройств (клавиатуру, мышь и звуковую карту) можно было подключать к любой из сторон и отключать. Камеру - подключать только к правой стороне или отключать. Последний разъём подключался к порту, оставшемуся свободным на левом хабе, и тоже мог отключаться.

Этот проект был затем пересмотрен по нескольким параметрам.

  1. Я решил, что все группы должны вести себя полностью одинаково - меньше будет путаницы. Это подразумевало, что камера сможет, как и прочие каналы, подключаться к каждой из сторон, а пятый разъём исчезнет.

  2. Иллюминация было урезана - остались по два светодиода на группу, загорающиеся при подключении её к правой и левой стороне, соответственно. Цвета этих диодов хватает, чтобы отслеживать также статус сторон.

  3. Поскольку вся схема с ардуинкой, реле, коммутаторами и деталями обвязки значительно превосходила по размерам старый хаб, я решил спрятать её под стол, оставив наверху лишь маленький пульт с кнопками и светодиодами.

  4. Я добавил два "аварийных" светодиода, тлеющих и иногда ярко вспыхивающих красным светом: первый напоминает, что камера в данный момент подключена, второй указывает, что звуковой карте может не хватать питания (об этом - ниже). Эти светодиоды органично вписались в соответствующие кнопки.

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

Пульт управления крупным планом и вместе с печатной платой в процессе распайки деталейПульт управления крупным планом и вместе с печатной платой в процессе распайки деталей

Кнопки работают наиболее очевидным способом: одиночные нажатия последовательно переключают соответствующий канал между состояниями "подключен к компьютеру", "подключен к телефону", "отключен". Длинные или множественные нажатия не используются.

Светодиоды расположены над каждой кнопкой попарно и отображают состояние данного канала. Левый светодиод горит, когда канал подключен к системному блоку, правый - когда он подключен к телефону. Зелёный сигнал означает, что устройство, к которому выполнено подключение, активно, красный - что напряжения на этом направлении нет.

Макетирование

Проект пережил две макетки, в сумме прожившие у меня на столе более полугода. Их фотографии я прикрепляю ниже.

Первая макетка - питание через диодыПервая макетка - питание через диодыВторая макетка - добавились реле и дополнительные светодиоды в левом верхнем углуВторая макетка - добавились реле и дополнительные светодиоды в левом верхнем углу

Первый вопрос касался схемы подключения коммутирующих микросхем. Я использовал описанную выше аппаратную защиту от одновременного открытия ключей, причём клавиатура и мышь управлялись одной парой сигналов. Кроме того, я использовал эту же схему для переключения "левого" хаба между телефоном и док-станцией - правда, не по сигналу контроллера, а по наличию напряжения на разъёме подключения телефона.

Далее, нужно было выбрать схему подключения светодиодов. Поскольку 6 двуцветных светодиодов и 2 одноцветных - это 14 каналов, нечего было и думать подключить их напрямую к ардуинке - ног не хватило бы. Кроме того, мне хотелось поиграться с ШИМами, снизив яркость индикаторов каналов в простое, но зажигая их ярко при нажатии на любую кнопку. "Аварийные" светодиоды я решил заставить мигать - за 4.5 секундами тления должна была следовать яркая вспышка длительностью в полсекунды.

Arduino Nano предоставляет всего шесть ШИМов. Я распределил их следующим образом:

  • Управление яркостью красной компоненты "левых" светодиодов;

  • Управление яркостью зелёной компоненты "левых" светодиодов;

  • Управление яркостью красной компоненты "правых" светодиодов;

  • Управление яркостью зелёной компоненты "правых" светодиодов;

  • Управление светодиодом "камера включена";

  • Управление светодиодом "звуковой карте не хватает питания".

Таким образом, "аварийные" светодиоды подключены напрямую к контроллеру, а остальные - к выходам элементов "или", смешивающих сигнал яркости с сигналом подключения канала к соответствующей стороне.

Разобравшись с этим, я приступил к распайке первой макетки. При тестировании всплыл ряд проблем, которые были частично исправлены при переходе ко второй макетке, а частично - при разводке окончательной платы.

Первая проблема была тривиальной и сводилась к нехватке питания. Как показал эксперимент, просто взять и подключить все четыре устройства к одному хабу нельзя: клавиатура и мышь в сумме потребляют до 300 мА, камера - до 440, звуковая карта - до 540, и это не учитываея питания самой схемы. (Напоминаю, что я решил ограничиться стандартом USB 2.0 с 500 мА на порт, пусть даже материнские платы обычно и разрешают чуть более высокое энергопотребление.) В итоге я решил подключить компьютер через два кабеля вместо одного, предоставив звуковой карте как самому прожорливому потребителю отдельную шину питания - это показалось проще, чем поднимать версию USB. Если же компьютер не выдаёт напряжения на USB-разъёмы, то питание предоставляется оставшимся хостом (телефоном или док-станцией): совместное использование всех устройств в такой конфигурации невозможно, но и юзкейза для подключения всей переферии при обесточенном компьютере я не придумал. Соответственно, "аварийный" светодиод звуковой карты должен мигать, если при питании не от компьютера включены она и что-то ещё.

Кстати, это освободило один интерфейс USB A на хабе, подключенном к компьютеру. Я использовал его для подключения генератора паролей.

Вторая проблема тоже была связана с питанием. "Плюс" с трёх различных источников я изначально планировал свести через диоды с общим катодом, но испытания на макетке показали, что те диоды, которые попались мне под руку, под максимальной нагрузкой давали просадку напряжения почти на целый вольт, что я счёл неприемлимым (стандарт USB допускает минимум в 4.75В). Вместо диодов я организовал подачу питания через механические реле - 30 мА "лишнего" тока на источник выглядели разумной платой за гарантированно равные потенциалы, хотя предполагаю, что опытные радиолюбители нашли бы решение получше.

Переход на реле, впрочем, вызвал другую, неожиданную проблему. Поскольку логика работы контроллера (как минимум, по части управления светодиодами) зависит от того, на какие из разъёмов типа B подано напряжение, эта информация должна быть каким-то образом подана на него. Не мудрствуя лукаво, я завёл эти напряжения на аналоговые входы ардуинки, подтянув к нулю через резисторы номиналом 33k. Это нормально работало с диодами, однако реле добавили задержку, равную времени срабатывания механики, между появлением напряжения на аналоговом входе и на пине +5V. В итоге контроллер получал питание от аналогового пина и зависал на старте. Для исправления ситуации оказалось достаточно поставить между вводом питания и аналоговым пином ещё один резистор, на 18k - теперь до подачи питания на пин +5V Arduino не хватало тока, чтобы включиться, и старт проходил нормально.

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

  1. Отключаем линии связи со "старым" ведущим устройством.

  2. Отключаем питание.

  3. Ждём две секунды.

  4. Включаем питание.

  5. Включаем линии связи с "новым" ведущим устройством.

В таком режиме переключение стало происходить чётко и быстро - как при ручном подключении соответствующих разъёмов. Правда, для управления реле пришлось выделить отдельные пины на контроллере, чтобы светодиод, отображающий состояние коммутации, не гас на время принудительного отключения - такое поведение несколько дизориентировало. Это привело к тому, что остальные компоненты пришлось "втискивать" в оставшиеся свободными выводы ардуинки - сменив клавиатуру на аналоговую и даже сведя входные напряжения от обоих кабелей, ведущих к системному блоку, на один пин.

Ещё одной проблемой макеток были небольшие потери пакетов высокоскоростных устройств - изредка выпадали кадры с камеры, а в колонках при работе с телефона раздавались щелчки. Эти проблемы удалось нивелировать, сократив длину монтажного провода и, где возможно, подключив разрезанные USB-кабели напрямую к коммутаторам вместо распайки разъёмов USB-A и USB-B на макетке.

Итоговый проект

Финальная структурная схема коммутации линий данных выглядит следующим образом:

Линии передачи данных USBЛинии передачи данных USB

Здесь все переключатели с K1 по K5 представлены микросхемами FST3125. K1 и K2 управляются совместно, остальные - независимо. K1..K4 переключаются сигналами с контроллера, K5 - наличием напряжения на разъёме USB B, к которому подключается телефон.

Разводка питания выполнена по следующей схеме:

Разводка питанияРазводка питания

Земля (на схеме опущена) на всех разъёмах и на плате общая. Переключатели с K6 по K11 - это реле V23079A1001B301. Легко видеть, что питание схемы и большей части переферии обеспечивается системным блоком, если он выключен - то док-станцией, а в крайнем случае - телефоном. Для этого реле K6 и K7 управляются напрямую наличием питания на соответствующих разъёмах.

Реле K8..K11 управляются контроллером и используются для временного отключения устройств при переключении (переферии - между ведущими устройствами, первого хаба - между телефоном и док-станцией). Нагрузка подключена на их нормально замкнутые контакты, чтобы не увеличивать пиковый ток за счёт обмоток. Звуковая карта имеет приоритетное питание напрямую от системного блока.

Для создания финальной схемы и разводки дорожек я использовал EasyEDA, расположив элементы таким образом, чтобы минимизировать длину дорожек, по которым идут данные USB. Размеры платы я подогнал под пластиковый корпус, купленный в местном радиомагазине. Пульт собрал в отдельном маленьком корпусе и соединил с основной схемой жгутом монтажного провода, затянутом в термоусадочную трубку.

Плату я заказал в Китае. В готовом виде она выглядит следующим образом:

Готовые платыГотовые платы

Правда, тут вышла небольшая накладка: распределяя пины контроллера для упрощения разводки, я случайно поменял местами пин с ШИМом и пин без него. В итоге управление яркостью светодиодов на одном из каналов стало невозможным. К счастью, это была единственная крупная ошибка, и я решил проблему, перерезав пару дорожек и соединив их крест-накрест монтажным проводом. Кроме того, я несколько неверно разметил выводы для подключения светодиодов, но это решилось иной распайкой проводов с пульта заменой номера пинов в программе.

Неприятным сюрпризом стали проблемы с потерей пакетов с камеры. Вскоре выяснилось, что пакеты теряются, только если камера подключена к хабу, соединённому с компьютером без промежуточной логики. При подключении через второй хаб, соединённый с ведущими устройствами через коммутатор FST3125, потерь не было. Не было их также и при переключении камеры на разъём, предназначенный для звуковой карты и подключенный к системному блоку в обход хабов. Наконец, подключив хаб напрямую к компьютеру обрезком USB-кабеля (а не через разъём USB-B, распаянный на плате), я снова добился отсутствия потерь.

Таблица ниже содержит информацию о том, в каких случаях наблюдались потери пакетов.

Состав тракта

Потери пакетов

Компьютер - кабель - разъём USB B - дорожки на плате - провода между платой и хабом - хаб - провода между хабом и платой - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Да

Компьютер - кабель - хаб - провода между хабом и платой - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Нет

Компьютер - кабель - разъём USB B - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Нет

Компьютер - кабель - разъём USB B - дорожки на плате - микросхема-коммутатор - дорожки на плате - провода между платой и хабом - хаб - провода между хабом и платой - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Нет

Из этой таблицы становится ясно, что потери пакетов наблюдаются только в том случае, если после разъёма USB B, в который включен кабель, пришедший с компьютера, следует отрезок провода, ведущий на плату хаба, причём между двумя этими отрезками нет микросхемы-коммутатора. По-видимому, кабель USB и перемычка из монтажного провода обладают разными волновыми сопротивлениями, что ведёт к появлению отражений на их границе и, соответственно, к потере пакетов. Микросхема FST3125, включенная между этими отрезками, эффективно гасит подобные отражения.

Чтобы проверить эту теорию, я соединил хаб с основной платой не монтажным проводом, а отрезком того самого кабеля, которым он далее подключен к компьютеру через разъём USB B. Результат превзошёл все ожидания: потери кадров полностью прекратились, и всё заработало как часы.

Плату я разместил в пластиковом корпусе, закрепив двумя гайками и прорезав окошки для разъёмов. Контроллер поставил в колодки, чтобы иметь возможность извлечь его для обновления прошивки. Хабы подвесил на проводах - они прижаты к верхней крышке корпуса их жёсткостью, что не должно создавать проблем. Сам корпус я подвесил на шнурке к креплению настольной лампы, а пульт положил на стол. Разъёмы подписал белым маркером.

Основной модуль в сбореОсновной модуль в сборе

Программа написана на C++ и доступна по ссылке. Я постарался разделить сущности, отвечающие за взаимодействие с различными компонентами схемы, хотя и поленился выделить код методов в файлы cpp, рассудив, что система сборки Arduino Studio всё равно не извлечёт из этого какой-либо выгоды. За работу с кнопками (прежде всего, антидребезг) отвечает библиотека GyverButton. За тайминг (отключение устройств, мигание и снижение яркости светодиодов) - GyverTimer. И то, и другое достпно в пакете GyverLibs - пользуясь случаем, выражаю его автору свою благодарность.

Резюме

Задача оказалась хорошей в учебном плане - благодаря работе над проектом я обрёл новые навыки и знания. Это касается как работы с железом (раздача питания, коммутация потоков данных, управление индикацией, пайка микросхем в форм-факторе SOIC, отражения в высокочастотном тракте), так и представлений о современных контроллерах и способах их использования. Получившееся изделие прекрасно справляется со своей задачей - использование телефона в качестве рабочего места или источника музыки происходит теперь весьма просто.

В то же время, существующее железо для Arduino не позволило пойти по пути быстрого прототипирования, поскольку многое пришлось изобретать самому, что сильно затянуло проект. Надеюсь, следующую задумку - управление работой дистиллятора - удастся реализовать более прямым путём.

Подробнее..

Из песочницы Чёрный ящик для вашего офиса

14.08.2020 16:23:02 | Автор: admin
Первой об этом устройстве заговорила наша бухгалтер, женщина профессионально благодетельная, но рассеянная (теряет мелкие пластмассовые предметы: помаду, тюбики, секретные флэшносители). Сложно установить, где она увидела сочетание слов USB over IP, но в ее интерпретации это звучало так: Железная коробочка, в которую можно воткнуть много флэшек, чтобы они не терялись. С этой формулировкой, она пошла к генеральному, потому что могла открыть дверь его кабинета не то чтоб ногой, но плечом. После ее визита генеральный, которого слегка мутит от всех этих компьютерных коробочек, вызвал главного айтишника и двадцать минут с ним беседовал, пытаясь запомнить фразу USB over IP. Я, кстати, сумел бы все объяснить и за десять, но я младший айтишник-стажер и имею возможность всего лишь рассказать о динамике событий и о самом устройстве, которое спасло нашего бухгалтера и облегчило жизнь многим, кто о таком облегчении и не мечтал. Это нам, продвинутым стажерам, все ясно с USB over IP, а для остальных страждущих рассказываю.

Американскую версию прибора мы покупать не стали по объективной причине ценовой политики. Немецкую тоже проигнорировали, Китай вообще обделил нас данным чудом техники. Нашли отечественного производителя, изучили и остановились на агрегате управляемый USB over IP концентратор Dis, но стоп, стоп. Мне за рекламу никто не платил, поэтому название ищите сами. Нам уже хорошо вам пока плохо. Перехожу к сухим фактам.

Управляемый USB over IP концентратор предназначен для подключения USB устройств к компьютерной сети, после чего появляется возможность удаленно управлять включением и подключением этих USB устройств со своего компьютера или ноутбука. У нас есть ряд ключей, которые работают крайне не стабильно, а мне как младшему сотруднику постоянно приходилось бегать перетыкать эти ключики для нормальной работы.

Запускается устройство на всех видах операционок, кроме мобильных осей. К нему можно присоединять ключи электронной защиты, например, серии eToken, ruToken а так же ключи для программных продуктов 1С, сканеров, принтеров, МФУ, сенсоров и т.д.

В наличии встроенный Wi-Fi-модуль и сетевой адаптер Ethernet (RJ-45). И поэтому можно подключать устройство, как по проводным, так и по беспроводным (Wi-Fi) каналам связи. Мощность сигнала Wi-Fi оставляет желать лучшего, полагаю из-за того что устройство находится в достаточно толстом металлическом корпусе. Зато у вас появится загадочный черный ящик, который сохранит в себе тайну всех секретных ключей и прочих носителей.

Модельный ряд управляемых концентраторов имеет 4 разновидности: на 16, 32, 48 и 64 портов USB. У нас компания относительно компактная, и мы обошлись аппаратом на 32 персоны, а наши богатенькие соседи, купили сразу 64 порта, высокомерно заметив, что 32 это полумера и это несерьезно.

Так как вся идея прибора вертится вокруг понятия безопасность и ради нее затевалась, сразу сообщу, что имеется двухступенчатая защита USB устройств при совместном использовании USB по сети:

  1. Удаленное физическое включение и выключение USB устройств;
  2. Авторизация для подключения USB устройств по логину, паролю и IP адресу.
  3. Журналирование как всех включений и подключений USB устройств клиентами.

Прошивка черного ящика позволяет избирательно настраивать доступ пользователей к портам и устройствам USB, шифровать трафик между устройством и клиентом (это особенно полезно для удаленных пользователей и филиалов). Для интеграции со сторонними решениями имеет API и брандмауэр. Точно говорю, что реализовать подобную функциональность самостоятельно очень непросто, даже если вы сможете куда-нибудь втюхнуть десяток свистков USB.

Мною так же была обнаружена аппаратная схем защиты от нештатных ситуаций, вроде перегрева или бросков пускового тока.

Чтобы пробросить устройство USB на компьютер, надо запустить не требующую установки (Portable) версию приложения, доступную для актуальных версий Windows, GNU/Linux и OSX. Клиент работает в графическом режиме, как сервис (демон) или в режиме командной строки.

После этого можно присоединить к системе любое устройство USB, включенное в порт концентратора: оно будет видно как локальное. Никаких проблем с совместимостью ключей не возникнет: производитель заявляет, что несовместимых ключей на территории России, Белоруссии, Казахстана, Канады и всей Европы найдено не было.

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

А прикупили мы этот железный ящичек по адресу: www. Ха-Ха! Опять думаете, что мне забашляли за рекламу, и я начну сыпать подробностями. Нееет. Сами, сами вчитывайтесь в черный космос айти новинок.
Подробнее..

Chia Coin, или сказка о том, как Флешки выжили в войне криптовалют

07.06.2021 12:06:42 | Автор: admin
Привет, Хабр! USB-флешки единственные, кто не пострадал в спекуляциях на тему цен вокруг носителей информации. Благодаря монете Chia спрос на носители HDD, SSD взлетел за месяц до невиданных высот. С полок магазинов пропали HDD емкостью выше 1-2 ТБ, SSD емкостью более 0,5 ТБ, но потребности в переносе данных еще остались.



Будем откровенны, ажиотаж вокруг видеокарт и жестких дисков фактически парализовал IT сектор. Не хватает нам только монет, добываемых на CPU, и о компьютерах можно будет только мечтать, рассматривая буклеты интернет-магазинов.

Что же делать интеграторам, системным администраторам, пользователям и обслуживающему персоналу дата центров? Вместо внешних HDD им придется переключиться на другие носители, и здесь наступает очередь емких флешек, которые хоть как-то компенсируют нехватку и дороговизну носителей и позволят выполнять работу.



В этот ответственный момент компания Kingston выпустила на рынок две привлекательные модели USB Flash: DataTraveler Kyson и DataTraveler Exodia. Благодаря отсутствию ажиотажного спроса на данный тип носителей модели располагает большой емкостью для перемещения данных, распространенным разъемом USB и приличными скоростями. К тому же цена на них вполне демократичная, точнее 1/25 от мейнстрим видеокарт GTX 3060 Ti.

DataTraveler Exodia



Самобытное устройство, выпускаемое в четырех емкостях и четырех цветах. Оснащена пластиковым колпачком и не имеет ни единого светодиода.



Очень легкая (11 г) благодаря пластиковому корпусу. Разъем подключения USB Type A, поддерживаемый протокол USB 3.2 Gen 1. Доступна в размере 32, 64, 128 и 256 ГБ. Стиль и исполнение флешки напоминают старую школу просто и со вкусом, без лишней иллюминации и с возможностью привязать устройство к шнурку или связке ключей. Стоит помнить, что колпачок хоть и фиксируется плотно, да и к тому же легкий, но в кармане может слететь. Следите за ним внимательно.



Из коробки DataTraveler Exodia 256G отформатирована в системе FAT32 и доступный для пользователя размер составляет 230 ГБ в системе исчисления Windows 10.

DataTraveler Kyson



Полная противоположность выше рассмотренной флешке, т.к. выполнена в современном и стильном дизайне: лишена колпачка, металлический корпус, минималистичный вид. В отличие от Exodia производитель гарантирует высокую скорость чтения (до 200 МБ/сек) и приличную скорость записи (до 60 МБ/сек). Цветовых вариаций нет, зато можно выбрать из четырех размеров: 32, 64, 128 и 256 ГБ.



Компактные размеры и смешной вес (39x12,6x4,9 мм, 4 г) не будут оттягивать карман хозяина. Крупное кольцо позволяет привязать флешку буквально к любому предмету. По спецификации флешка совместима с разъемом USB Type A и протоколом USB 3.2 Gen 1.



В заводском исполнении предварительно отформатирована в FAT32, доступный для пользователя системы Windows 10 объем составляет ровно 230 ГБ.

Новые горизонты





Раз речь зашла о недоступности устройств хранения и переноса информации из-за тотального шествия новой криптовалюты, то не стоит думать, что флешки больше ни на что не сгодятся. Более тонкий тех. процесс производства памяти сделал из них почти полную замену внешним жестким дискам и SATA SSD с оговоркой на ресурс и скорость. Естественно, ни в какое сравнение не идет и разница в стоимости устройств. На столь емких флешках будет удобно брать с собой различную информацию больших (в рамках типичного пользователя ПК) размеров будь то фильмы, дистрибутивы, фотографии и многое другое. На 256 ГБ действительно влезет пару десяткой рипов фильмов или несколько сезонов популярного сериала, а посмотреть их можно будет, просто воткнув флешку в телевизор или приставку. Благо TV 2017 и более поздних годов выпуска оснащаются как раз USB Type A разъемами.

По ряду профессий емкие флешки с высокой скоростью чтения удобны для системных администраторов: храня и разворачивая образы различных систем, начиная от ESXi и заканчивая Windows Server 2019. В конце концов 256 ГБ позволяют переносить на сервера образы готовых систем заранее оптимизированные и сконфигурированные в VMware Workstation Pro путем конвертирования их в программе VMware Converter.



Особенно остро вопрос стоит о конфиденциальности информации. Бывают моменты, когда некий большой объем данных попросту не должен попадать в on-line, и тогда самым распространенным способом переноса остаются как раз флешки из-за их дешевизны и мобильности, т.к. внешние жесткие диски изредка нуждаются даже в электрической розетке.

Благодаря доступному объему можно легко отказаться от принятия решений, что хранить на флешке и ничем не жертвовать из-за нехватки места.

Методика тестирования


Так как флешки в первую очередь предназначены для ПК и ноутбуков, то процесс измерения производительности будет проведен на ПК с интерфейсом USB 3.2 Gen 2 и скоростью 10 Гбит/с.

Типичный сценарий использования USB Flash это более 80% времени работы тратится на чтение данных. Запись обычно происходит реже, либо записываются сжатые архивы/образы. В повседневном использовании флешки должны отрабатывать различные не серверные задачи и тем более нет смысла использовать потребительские USB Flash в качестве места долгого хранения, или считать их местом для создания бекапов. Поэтому в списке приложений мы отдаем предпочтение распространенным программам для тестирования. Это AS SSD Benchmark, CrystalDiskMark, HD Tune Pro 5.75, PCMark 10 Storage, H2Test и другие. В качестве бонуса проведем тесты записи и чтения папки с образами, фото и фильмами, имитируя типичную работу пользователя с устройствами.

Используемая конфигурация ПК:
Maximus XI Hero (Wi-Fi), процессор Intel Core i7 9900K, встроенная видеокарта Intel, 64-Гб памяти DDR4-3200 и операционная система Windows 10 x64. Для проверки совместимости флешки подключались к raspberry, ноутбукам с чипсетами Intel, AMD, а также NAS накопителю.

Результаты тестирования



AS SSD Benchmark


Copy Benchmark оценивает скорость работы и затраченного на это времени при копировании разных групп файлов (ISO образ, папка с программами, папка с играми).

DataTraveler Exodia



DataTraveler Kyson



CrystalDiskMark


Тестирование проводилось с 5 повторениями, каждый объемом 16 ГБ и 1 ГБ.
Последовательное чтение/запись с глубиной 8.
Последовательное чтение/запись с глубиной 1.
Случайное чтение/запись блоками по 4 кб с глубиной 32 и 16 потоков.
Случайное чтение/запись блоками по 4 кб с глубиной 1.

DataTraveler Exodia



DataTraveler Kyson



HD Tune Pro 5.75


Линейная скорость чтения и записи блоками 512 Кбайт.
Время доступа.
Расширенные тесты чтения и записи.

DataTraveler Exodia



DataTraveler Kyson



PCMark 10 Storage


Quick System Drive Benchmark: короткий тест, эмулирующий легкую нагрузку на систему хранения. Используются наборы тестов, повторяющие реальные действия системы и программ с накопителем;
Data Drive Benchmark: повторяет нагрузку на систему хранения в виде наборов тестов для NAS, (хранение и использование файлов различного типа).

DataTraveler Exodia



DataTraveler Kyson



H2Test


Позволяет оценить среднюю скорость чтения и записи на диск, выдавая информацию в Мбайт/сек.

DataTraveler Exodia



DataTraveler Kyson



USB Flash Benchmark


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

DataTraveler Exodia



DataTraveler Kyson



Натуральный тест


И последний, натуральный тест на копирование образа Windows 10 x64.iso плюс папка с распакованным образом Windows 10, а также Windows Server 2019 x64 iso и папка с распакованным образом Windows Server 2019. Добавляем сюда 5 крупных фрагментов сериала и 77 RAW фотографий. По папкам:
Фильмы 29,1 ГБ (5 крупных фрагментов);
ISO 17,6 ГБ (смешанные файлы большого, среднего и маленького размера);
Фото 5,4 ГБ (77 RAW фотографий);


Запись фильмов, ISO, фото размером 53453 МБ за 1339 сек или 40 МБ/сек


Чтение фильмов, ISO, фото размером 53453 МБ за 447 сек или 120 МБ/сек

DataTraveler Kyson


Запись фильмов, ISO, фото размером 53453 МБ за 1236 сек или 43,2 МБ/сек


Чтение фильмов, ISO, фото размером 53453 МБ за 214 сек или 250 МБ/сек

Выводы


Kingston DataTraveler Kyson



Серия Kyson это универсальные и доступные флеш-карты для любых видов файлов. Они прекрасно работают с мелкими, средними и крупными файлами. Сильной стороной Kyson стала именно универсальность использования в любых видах перемещения данных. Заявленная скорость чтения даже превысила паспортные характеристики и достигает половины производительности хорошего SATA SSD, ведь в тестах на чтение флешка показала от 250 до 270 МБ/сек. Если обратить взгляд на запись, то кажется будто 40 с хвостиком МБ/сек не такая и большая цифра. Но Kingston DataTraveler Kyson не пасует перед мелкоблочными данными, развивая гарантированную скорость более 44 МБ/сек. Еще одним достоинством стал температурный режим и стабильность работы. После часовых тестов флешка не нагревается выше 40С и не впадает в анабиоз. Ее совместимость с различными контроллерами (Intel, AMD, VLI, ASM) подтвердилась на практике. Одним словом, если вам нужна универсальная и скоростная флешка для чтения и постоянная скорость на запись с любыми видами данных, то Kyson отлично подходит на эту роль.

Kingston DataTraveler Exodia



Бюджетно не значит плохо! Kingston DataTraveler Exodia относится к Entry Level флеш-устройству. У нее нет индикации, а корпус пластиковый. Большая емкость, легкий вес, защитный колпачок и доступная цена вот ее основные достоинства. Наилучшей стороной Exodia стали тесты с копированием и записью файлов размером больше 512 Кб. Все, что больше это ее родная стихия. На мелкоблочных операциях чтения и записи скорость падает, но как только вы решите записать большие файлы, то результат под 60 МБ/сек на запись вас впечатлит в разрезе стоимости малышки. Фактический результат с учетом особенностей: до 125 МБ/сек чтения и до 60 МБ/сек на запись.

Дополнительную информацию о новинках DataTraveler Kyson и DataTraveler Exodia ищите на официальном сайте Kingston.



Для получения дополнительной информации о продуктах Kingston Technology обращайтесь на официальный сайт компании.
Подробнее..

USB4 все тот же USB?

07.09.2020 10:18:05 | Автор: admin

В конце 2020 года ожидается выход устройств с поддержкой интерфейсов нового поколения USB4/Thunderbolt 4. Данные интерфейсы похожи, однако имеют ряд принципиальных отличий. Среди таких отличий можно выделить наиболее значимое: спецификации на USB4 общедоступные и любой желающий может изучить основные принципы работы данного интерфейса, в отличие от Thunderblot 4.


В данной статье мы рассмотрим изменения, которые претерпел интерфейс USB4 по сравнению с предыдущей версией (USB 3.2), разберем архитектуру USB4 и перечислим его основные характеристики.


Более подробную информацию можно найти в спецификации на USB4.


Что мы знаем о USB4?



Основные характеристики USB4:


Разъем: аналогично современным интерфейсам от Intel (Thunderbolt 3/4) USB4 поддерживает только разъем USB-C.


  • Разъем USB-C

Скорость передачи данных: тут уже дело обстоит немного сложнее и все не так однозначно, попытаемся разобраться: минимальная поддерживаемая скорость для устройства, имеющего USB4-сертификацию, составляет 20 Гбит/c. Но также может поддерживаться скорость 40 Гбит/c, если хост, устройство и кабель на это способны. И данная пропускная способность уже ничем не уступает своему конкуренту от Intel Thunderbolt 4.


  • Минимум 20 Гбит/с
  • Возможность поддержки 40 Гбит/c

Туннелирование интерфейсов: одной из основных задач, которые необходимо было решить на этапе разработки USB4-интерфейса, являлось объединение нескольких различных протоколов, работающих через разъем USB-C, в единый физический интерфейс. Основные интерфейсы, работающие в режиме туннелирования:


  • Enhanced SuperSpeed USB (USB3) (предыдущее поколение);
  • DisplayPort (DP);
  • PCI Express (PCIe) (не является обязательной опцией).

Поддержка конфигурации шины: поддержка возможности специального соединения между двумя хостами (host-to-host).


Питание USB4: для работы USB4 питание устанавливается и регулируется в соответствии со спецификациями USB Type-C и USB PowerDelivery (PD). Реализована возможность передачи питания до 100 Вт.


Поддержка Thunderbolt 3: хост или устройство, работающее по интерфейсу USB4, может также взаимодействовать с устройствами, поддерживающими подключение по интерфейсу Thunderbolt 3. Но данный функционал не является обязательным, поэтому поддержка данной возможности зависит только от разработчиков устройств.


Сравнение с USB 3.2



После того как мы рассмотрели основные характеристики USB4, можем выделить ряд значительных изменений по сравнению с предыдущей версией стандарта:


Разъем: предыдущие версии разъемов не поддерживаются. В связи с необходимостью использовать дополнительную сигнальную линию данных (Sideband Channel), нет возможности использовать такие разъемы, как USB Type A/B.


  • Отказ от разъемов USB Type A/B, miniUSB, microUSB

Скорость передачи данных: для последней версии USB 3.2 Gen2 x 2 максимальная скорость передачи данных составляет 20 Гбит/c, но данная скорость достигалась только с использованием одновременно двух линий передачи данных, то есть только для разъема USB-C. На предыдущих версиях разъемов скорость была вдвое меньше 10 Гбит/c.


  • Увеличение скорости передачи данных вдвое с 20 до 40 Гбит/c

Питание: распределение питания в стандарте USB 3.2 регламентируется аналогично стандарту USB 2.0, с увеличением потребляемого тока для устройств, работающих на SuperSpeed-шине. Стандарты USB BC (Battery Charging) и USB PD для них являются дополнением, расширяющим возможности питания. Для USB4, в отличие от USB 2.0 и USB 3.2, не определена собственная модель питания устройства и регламентируется только с помощью спецификаций USB PD и USB Type-C.


  • В отличие от USB 3.2, у USB4 не определена собственная модель питания

Поддержка дополнительных возможностей подключения: для интерфейса USB 3.2 полностью отсутствуют какие-либо дополнительные возможности. Отсутствуют режимы туннелирования для интерфейсов DP и PCIe, нет возможности организовать специальное соединение между двумя хостами. Только при использовании разъема USB-C появляется несколько опциональных альтернативных режимов (USB-C Alt Mode), например DisplayPort Alternate Mode, но данный функционал относится именно к использованию разъема USB-C и регламентируется спецификациями конкретного вендора, а не стандартом USB 3.2.


  • Для USB 3.2 полностью отсутствует возможность дополнительных сторонних подключений

Отличие архитектуры USB4 от USB 3.2


На рисунке ниже представлена архитектура подключения системы USB 3.2. Как мы видим из рисунка, в системе присутствуют два параллельно работающих интерфейса Enhanced SuperSpeed и USB 2.0, за счет чего обеспечивается обратная совместимость интерфейса USB 3.2 с более ранней версией USB 2.0. Так как обе шины работают параллельно, то они могу быть активны одновременно.



Архитектура системы USB4 имеет ряд отличий. Для совместной работы с интерфейсом USB 2.0 все так же присутствует шина, которая функционирует независимо от других интерфейсов. Так как обмен данными по интерфейсу USB 3.2 выполняется по тем же линиям данных, по которым обеспечивается обмен данными и для других поддерживаемых интерфейсов, необходимо использовать туннелированный протокол. Подробная схема системной архитектуры USB4 представлена на рисунке.



Для туннелирования таких интерфейсов, как USB 3.2 и PCIe, необходимо использовать специальные адаптеры протокола (Protocol Adapters). Так, для туннелирования интерфейса USB 3.2 используется специальный хаб (Enhanced SuperSpeed Hub). В свою очередь для PCIe используется специальный коммутатор (PCIe Switch), необходимый для обработки связанных с протоколом маршрутизации пакетов и обеспечивающий буферизацию данных. Для туннелирования DP не требуется никакой промежуточной логики. Соединение устанавливается напрямую, как сквозное.


В каждом маршрутизаторе (Router) системы установлен блок, отвечающий за синхронизацию и распределение времени. На схеме он обозначен как TMU (Time Management Unit).


Маршрутизатор (Router) основной блок, необходимый для построения архитектуры USB4. Он отвечает за сопоставление трафика туннельных протоколов с пакетами USB4, формирует и направляет пакеты через структуру USB4. За счет внутреннего TMU-блока маршрутизатор синхронизирует время по всей структуре передачи USB4. За настройку и обнаружение маршрутизатора на линии отвечает диспетчер подключения (Connection Manager), расположенный на стороне хост-устройства. Выделяются всего два типа маршрутизаторов: маршрутизатор устройств (Device Router) и хост-маршрутизатор (Host Router).


Для полной поддержки интерфейса USB4 необходимо, чтобы на обеих сторонах располагался USB4-порт. Он состоит из линий приема и передачи данных (RX/TX) и двухпроводного дополнительного канала (Sideband (SB)) (SBTX/SBRX). USB4-порт может работать в двух режимах: одноканальном или двухканальном. В одноканальном режиме линия 1 (Lane 1) будет отключена. В двухканальном режиме линии 0 и 1 связаны и обеспечивают общий канал данных. На рисунке ниже представлены оба режима работы.




Дополнительный SB-канал необходим для инициализации устройства на линии с хостом и для управления между портами.


Важно понимать, что интерфейс USB 2.0 не входит в маршрутизатор USB4 и работает параллельно с ним.


Таким образом, для порта USB Type-C с поддержкой USB4 полный режим подключения включает в себя:


  • порт USB4;
  • шину данных USB 2.0 (D+/D-);
  • канал конфигурации по CC-линии, необходимый для передачи протокола USB PD;
  • шины питания (VBUS/VCONN/GND).

Новые уровни в функциональной модели USB4


На рисунке ниже представлена функциональная модель USB 3.2. Присутствуют три основных уровня: физический уровень (Physical Layer), канальный уровень (Link Layer) и наивысший уровень уровень протокола (Protocol Layer).



В USB4 мы уже видим другую модель, так как были внесены значительные изменения. Самым низким из всех остается физический уровень (Physical Layer), который в свою очередь состоит из двух подуровней: логического (Logical Layer) и электрического (Electrical Layer). Уровнем выше расположен транспортный уровень (Transport Layer). Наивысшими равнорасположенными уровнями являются: уровень конфигурации (Configuration Layer) и уровень протокола адаптера (Protocol Adapter Layer). Уровень протокола адаптера был добавлен в связи с реализацией туннелирования. Он используется для обработки данных туннелированных интерфейсов.


На рисунке ниже представлена схема функциональной модели USB4.



Рассмотрим каждый из них подробнее:


Электрический уровень (Electrical Layer) определяет электрические характеристики для USB4-соединения: уровни напряжения сигнала, фазовое дрожание (jitter), скремблирование и кодирование сигнала.


Логический уровень (Logical Layer) расположен над электрическим уровнем и ниже транспортного. Данный уровень устанавливает соединения между двумя маршрутизаторами и необходим для передачи потоков байт между ними.


Транспортный уровень (Transport Layer) определяет формат передаваемого пакета, маршрутизацию, синхронизирует передачу по времени и управляет передаваемыми потоками. На данном уровне выполняется передача туннелированных пакетов и пакетов управления (Control Packets) через шину.


Уровень конфигурации (Configuration Layer) необходим для обработки входящих пакетов управления и обеспечивает настройку конфигурации маршрутизатора. Данный уровень также определяет схему адресации для пакетов управления в домене и гарантирует наличие надежного транспортного механизма для пакетов управления, которые предоставляют доступ к пространству конфигурации маршрутизатора.


Уровень протокола адаптера (Protocol Adapter Layer) необходим для преобразования пакетов между транспортным уровнем и туннельным протоколом. На данном уровне определяется тип туннельного протокола.


Новый канал связи (Sideband Channel)


Основное отличие интерфейса USB4 от предыдущей версии и в то же время одной из причин невозможности использования нового интерфейса с предыдущими версиями разъема является появление нового дополнительного канала связи, расположенного на дополнительных линиях разъема USB Type-C (SBU1/SBU2).


Данный канал выполняет ряд функций:


  • инициализацию линии передачи данных;
  • подключение и отключение устройств от USB4-порта;
  • включение и отключение линии передачи данных;
  • ввод и вывод из состояния сна (Sleep state).

Транзакции дополнительного канала отправляются по линии SBTX и принимаются по линии SBRX. На рисунке ниже показано несколько примеров подключения SBTX и SBRX между двумя маршрутизаторами с использованием активных, пассивных кабелей и встроенных на устройствах ретаймеров.



Существуют три основных типа транзакций для данного канала. Каждая из них отвечает за свой функционал:


  • Link Type (LT) LT-транзакция, предназначенная для инициализации дорожки. Данная транзакция также используется для того, чтобы сигнализировать об изменениях состояния подключенного адаптера, например об отключении линии передачи данных или о переходе в состояние низкого энергопотребления;
  • Administrative Type (AT) AT-транзакция, предназначенная для чтения и записи информации в специальную конфигурационную область;
  • Re-timer Type (RT) RT-транзакция, необходимая для общения маршрутизаторов с ретаймерами.

Каждый порт USB4 реализует набор регистров конфигурации канала. Каждый ретаймер, установленный на линии, также имеет собственную конфигурацию. Маршрутизатор использует AT-транзакции для получения доступа к регистровому пространству другого маршрутизатора или RT-транзакции для доступа к регистровому пространству ретаймера.


Вывод


Обещание, данное Intel в 2017 году, было выполнено, благодаря чему стандарт USB4 вобрал в себя многое от Thunderbolt 3. В итоге можно сказать, что USB4 остается все тем же стандартным интерфейсом, выполняющим роль обмена данными между хост-устройством и широким спектром одновременно доступных периферийных устройств. В то же время в нем появилось множество изменений, которые выглядят крайне положительными и многообещающими на текущий момент: избавление от различных версий интерфейса и объединение в одно общее название (USB4), отказ от различных разъемов в сторону одного единого (USB Type-C), попытка сделать общедоступным объединение различных интерфейсов, таких как DisplayPort, PCI Express, USB3, добавление новых дополнительных возможностей, например соединение host-to-host все эти факторы, а также открытость стандарта (в отличие от Thunderbolt 4) свидетельствуют о том, что USB4 имеет все шансы стать более массовым интерфейсом, чем Thunderbolt 4.

Подробнее..

Stm32 USB на шаблонах C. Продолжение. Делаем HID

27.03.2021 20:12:03 | Автор: admin

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

Разделение прерывания

Кроме обозначенных ранее ресурсов конечные точки также делят единственное прерывание. Соответственно, общий (главный) обработчик должен корректно передать управление обработчику прерывания нужной конечной точки. Номер конечной точки, к которой обращается хост, записано в битах EP_ID регистра ISTR. Придерживаясь линии реализации полностью шаблонной библиотеки, получил следующий класс:

using EpRequestHandler = std::add_pointer_t<void()>;template<typename...>class EndpointHandlersBase;template<typename... Endpoints, int8_t... Indexes>class EndpointHandlersBase<TypeList<Endpoints...>, Int8_tArray<Indexes...>>{public:  // Массив указателей на обработчики  static constexpr EpRequestHandler _handlers[] = {Endpoints::Handler...};  // Индексы обработчиков  static constexpr int8_t _handlersIndexes[] = {Indexes...};public:  inline static void Handle(uint8_t number, EndpointDirection direction)  {    _handlers[_handlersIndexes[2 * number + (direction == EndpointDirection::Out ? 1 : 0)]]();  }};

Ключевым элементом класса является массив _handlersIndexes, который отображает номер конечной точки и направление в конкретный обработчик. Для получения данного массива реализован специальный класс:

template<int8_t Index, typename Endpoints>class EndpointHandlersIndexes{  // Предикат для поиска очередной конечной точки.  using Predicate = Select<Index % 2 == 0, IsTxOrBidirectionalEndpointWithNumber<Index / 2>, IsRxOrBidirectionalEndpointWithNumber<Index / 2>>::value;  static const int8_t EndpointIndex = Search<Predicate::template type, Endpoints>::value;public:  // В конец массива индекса вставляется номер соответствующей конечной точки или -1 в случае пропуска.  using type = typename Int8_tArray_InsertBack<typename EndpointHandlersIndexes<Index - 1, Endpoints>::type, EndpointIndex>::type;};template<typename Endpoints>class EndpointHandlersIndexes<-1, Endpoints>{public:  using type = Int8_tArray<>;};

Из такой реализации, кстати, вытекает рекомендация объявлять конечные точки с номерами по порядку, потому что размер массива индексов обработчиков равен удвоенному максимальному номеру конечной точки.

Класс конечной точки

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

template <uint8_t _Number, EndpointDirection _Direction, EndpointType _Type, uint16_t _MaxPacketSize, uint8_t _Interval>class EndpointBase...

А также расширенный, с адресами буферов и регистра (которые назначаются менеджером, описанным в прошлой статье). Из-за различий между конечными точками разных типов/направлений (например, у однонаправленной конечной точки типа Interrupt будет один буфер, а у двунаправленной или Bulk с двойной буферизацией - два) для каждого типа объявлен свой класс:

template <typename _Base, typename _Reg>class Endpoint : public _Base...template<typename _Base, typename _Reg, uint32_t _TxBufferAddress, uint32_t _TxCountRegAddress, uint32_t _RxBufferAddress, uint32_t _RxCountRegAddress>class BidirectionalEndpoint : public Endpoint<_Base, _Reg>...template<typename _Base, typename _Reg, uint32_t _Buffer0Address, uint32_t _Count0RegAddress, uint32_t _Buffer1Address, uint32_t _Count1RegAddress>class BulkDoubleBufferedEndpoint : public Endpoint<_Base, _Reg>

Конечная точка на текущий момент реализована простой: экспортирует метод инициализации (в котором заполняется регистр EPnR), метод заполнения дескриптора, методы управления битами регистра (Очистка битов CTR_TX/RX, установка битов TX/RX_STATUS), а также отправку данных.

Класс интерфейса

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

template <uint8_t _Number, uint8_t _AlternateSetting = 0, uint8_t _Class = 0, uint8_t _SubClass = 0, uint8_t _Protocol = 0, typename... _Endpoints>class Interface{public:  using Endpoints = Zhele::TemplateUtils::TypeList<_Endpoints...>;  static const uint8_t EndpointsCount = ((_Endpoints::Direction == EndpointDirection::Bidirectional ? 2 : 1) + ...);  static void Reset()  {    (_Endpoints::Reset(), ...);  }  static uint16_t FillDescriptor(InterfaceDescriptor* descriptor)  {    uint16_t totalLength = sizeof(InterfaceDescriptor);    *descriptor = InterfaceDescriptor {      .Number = _Number,      .AlternateSetting = _AlternateSetting,      .EndpointsCount = EndpointsCount,      .Class = _Class,      .SubClass = _SubClass,      .Protocol = _Protocol    };        EndpointDescriptor* endpointsDescriptors = reinterpret_cast<EndpointDescriptor*>(++descriptor);    totalLength += (_Endpoints::FillDescriptor(endpointsDescriptors++) + ...);    return totalLength;  }};

Класс конфигурации

То же самое можно сказать о конфигурации, которая есть контейнер для интерфейсов. Читая описание USB я понял, что все на самом деле сложнее и хост вправе выбирать конфигурации/интерфейсы, однако на данный момент не осознал сути и не поддерживаю подобные механизмы. В то же время предполагаю, что для реализации взаимодействия хоста с конфигурациями и интерфейсами нужно будет сделать немного - добавить диспетчеризацию обращений к ним аналогично диспетчеризации обращений к конечным точкам.

template <uint8_t _Number, uint8_t _MaxPower, bool _RemoteWakeup = false, bool _SelfPowered = false, typename... _Interfaces>class Configuration{public:  using Endpoints = Zhele::TemplateUtils::Append_t<typename _Interfaces::Endpoints...>;  static void Reset()  {    (_Interfaces::Reset(), ...);  }...

Класс устройства

Наконец, в вершине иерархии находится непосредственно само устройство. В моей реализации соответствующий класс является производным от нулевой конечной точки, поскольку она особая и обмен данными с нулевой конечной точкой - это обмен данными (как правило, управляющими) с самим девайсом.

template<  typename _Regs,  IRQn_Type _IRQNumber,  typename _ClockCtrl,   uint16_t _UsbVersion,  DeviceClass _Class,  uint8_t _SubClass,  uint8_t _Protocol,  uint16_t _VendorId,  uint16_t _ProductId,  uint16_t _DeviceReleaseNumber,  typename _Ep0,  typename... _Configurations>class DeviceBase : public _Ep0{  using This = DeviceBase<_Regs, _IRQNumber, _ClockCtrl, _UsbVersion, _Class, _SubClass, _Protocol, _VendorId, _ProductId, _DeviceReleaseNumber, _Ep0, _Configurations...>;  using Endpoints = Append_t<typename _Configurations::Endpoints...>;  using Configurations = TypeList<_Configurations...>;  // Replace Ep0 with this for correct handler register.  using EpBufferManager = EndpointsManager<Append_t<_Ep0, Endpoints>>;  using EpHandlers = EndpointHandlers<Append_t<This, Endpoints>>;...

Класс содержит главный обработчик прерывания, диспетчеризуя запросы по различными конечным точкам:

static void CommonHandler(){  if(_Regs()->ISTR & USB_ISTR_RESET)  {    Reset();  }  if (_Regs()->ISTR & USB_ISTR_CTR)  {    uint8_t endpoint = _Regs()->ISTR & USB_ISTR_EP_ID;    EpHandlers::Handle(endpoint, ((_Regs()->ISTR & USB_ISTR_DIR) != 0 ? EndpointDirection::Out : EndpointDirection::In));  }  NVIC_ClearPendingIRQ(_IRQNumber);}

Являясь одновременно и нулевой конечной точкой, класс Device содержит обработчик обращений к нулевой точке, реализуя, на данный момент, процесс нумерации:

Обработчик прерывания нулевой конечной точки
static void Handler(){  if(_Ep0::Reg::Get() & USB_EP_CTR_RX)  {    _Ep0::ClearCtrRx();    if(_Ep0::Reg::Get() & USB_EP_SETUP)    {      SetupPacket* setup = reinterpret_cast<SetupPacket*>(_Ep0::RxBuffer);      switch (setup->Request) {      case StandartRequestCode::GetStatus: {        uint16_t status = 0;        _Ep0::Writer::SendData(&status, sizeof(status));        break;      }      case StandartRequestCode::SetAddress: {        TempAddressStorage = setup->Value;        _Ep0::Writer::SendData(0);        break;      }      case StandartRequestCode::GetDescriptor: {        switch (static_cast<GetDescriptorParameter>(setup->Value)) {        case GetDescriptorParameter::DeviceDescriptor: {          DeviceDescriptor tempDeviceDescriptor;          FillDescriptor(reinterpret_cast<DeviceDescriptor*>(&tempDeviceDescriptor));          _Ep0::Writer::SendData(&tempDeviceDescriptor, setup->Length < sizeof(DeviceDescriptor) ? setup->Length : sizeof(DeviceDescriptor));          break;        }        case GetDescriptorParameter::ConfigurationDescriptor: {          uint8_t temp[64];          uint16_t size = GetType<0, Configurations>::type::FillDescriptor(reinterpret_cast<ConfigurationDescriptor*>(&temp[0]));          _Ep0::Writer::SendData(reinterpret_cast<ConfigurationDescriptor*>(&temp[0]), setup->Length < size ? setup->Length : size);          break;        }        case GetDescriptorParameter::HidReportDescriptor: {          uint16_t size = sizeof(GetType_t<0, Configurations>::HidReport::Data);          _Ep0::Writer::SendData(GetType_t<0, Configurations>::HidReport::Data, setup->Length < size ? setup->Length : size);          break;        }        default:          _Ep0::SetTxStatus(EndpointStatus::Stall);          break;        }        break;      }      case StandartRequestCode::GetConfiguration: {        uint16_t configuration = 0;        _Ep0::Writer::SendData(&configuration, 1);        break;      }      case StandartRequestCode::SetConfiguration: {        _Ep0::Writer::SendData(0);        break;      }      default:        _Ep0::SetTxStatus(EndpointStatus::Stall);        break;      }    }    _Ep0::SetRxStatus(EndpointStatus::Valid);  }  if(_Ep0::Reg::Get() & USB_EP_CTR_TX)  {    _Ep0::ClearCtrTx();    if(TempAddressStorage != 0)    {      _Regs()->DADDR = USB_DADDR_EF | (TempAddressStorage & USB_DADDR_ADD);      TempAddressStorage = 0;    }    _Ep0::SetRxStatus(EndpointStatus::Valid);  }}

Интерфейс HID

HID-устройство - это устройство как минимум с одним интерфейсом типа HID, поэтому в библиотеке класс HID - это производный от интерфейса:

Класс интерфейса hid
template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Hid, typename... _Endpoints>class HidInterface : public Interface<_Number, _AlternateSetting, 0x03, _SubClass, _Protocol, _Endpoints...>{  using Base = Interface<_Number, _AlternateSetting, 0x03, _SubClass, _Protocol, _Endpoints...>;public:  using Endpoints = Base::Endpoints;  static uint16_t FillDescriptor(InterfaceDescriptor* descriptor)  {    uint16_t totalLength = sizeof(InterfaceDescriptor);    *descriptor = InterfaceDescriptor {      .Number = _Number,      .AlternateSetting = _AlternateSetting,      .EndpointsCount = Base::EndpointsCount,      .Class = 0x03,      .SubClass = _SubClass,      .Protocol = _Protocol    };    _Hid* hidDescriptor = reinterpret_cast<_Hid*>(++descriptor);    *hidDescriptor = _Hid {    };    uint8_t* reportsPart = reinterpret_cast<uint8_t*>(++hidDescriptor);    uint16_t bytesWritten = _Hid::FillReports(reportsPart);    totalLength += sizeof(_Hid) + bytesWritten;    EndpointDescriptor* endpointsDescriptors = reinterpret_cast<EndpointDescriptor*>(&reportsPart[bytesWritten]);    totalLength += (_Endpoints::FillDescriptor(endpointsDescriptors++) + ...);    return totalLength;  }private:};

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

HID-устройство

Теперь давайте из всего этого сделаем устройство, которое будет содержать один светодиод (потому что так удобно, он есть на плате BluePill) и поддерживать возможность управления этим светодиодом с компьютера (через USB HID Demonstrator).

Основной любого HID-устройства является Report, определяющий порядок взаимодействия. В нашем случае он будет достаточно простым:

using Report = HidReport<  0x06, 0x00, 0xff,    // USAGE_PAGE (Generic Desktop)  0x09, 0x01,          // USAGE (Vendor Usage 1)  0xa1, 0x01,          // COLLECTION (Application)  0x85, 0x01,          //   REPORT_ID (1)  0x09, 0x01,          //   USAGE (Vendor Usage 1)  0x15, 0x00,          //   LOGICAL_MINIMUM (0)  0x25, 0x01,          //   LOGICAL_MAXIMUM (1)  0x75, 0x08,          //   REPORT_SIZE (8)  0x95, 0x01,          //   REPORT_COUNT (1)  0xb1, 0x82,          //   FEATURE (Data,Var,Abs,Vol)  0x85, 0x01,          //   REPORT_ID (1)  0x09, 0x01,          //   USAGE (Vendor Usage 1)  0x91, 0x82,          //   OUTPUT (Data,Var,Abs,Vol)  0xc0                 // END_COLLECTION>;

Далее нужно определить всю ирерахию: конечные точки, интерфейс, конфигурацию и устройство:

using HidDesc = HidDescriptor<0x1001, Report>;using LedsControlEpBase = OutEndpointBase<1, EndpointType::Interrupt, 4, 32>;using EpInitializer = EndpointsInitializer<DefaultEp0, LedsControlEpBase>;using Ep0 = EpInitializer::ExtendEndpoint<DefaultEp0>;using LedsControlEp = EpInitializer::ExtendEndpoint<LedsControlEpBase>;using Hid = HidInterface<0, 0, 0, 0, HidDesc, LedsControlEp>;using Config = HidConfiguration<0, 250, false, false, Report, Hid>;using MyDevice = Device<0x0200, DeviceClass::InterfaceSpecified, 0, 0, 0x0483, 0x5711, 0, Ep0, Config>;

В общем-то всё, осталось написать обработчик для конечной точки управления светодиодом:

using Led = IO::Pc13Inv; // Inv - инвертированный.template<>void LedsControlEp::Handler(){  LedsControlEp::ClearCtrRx();  uint8_t* buffer = reinterpret_cast<uint8_t*>(LedsControlEp::Buffer);  bool needSet = buffer[1] != 0;  // Код почти целиком позаимствован из поста "STM32 и USB-HID  это просто".  // Не стал изменять его для удобной навигации.  switch(buffer[0])  {  case 1:    needSet ? Led::Set() : Led::Clear();    break;  }  LedsControlEp::SetRxStatus(EndpointStatus::Valid);}

Целиком файл main.c для Stm32f103 выглядит так (по-моему, достаточно компактно):

Полный код программы
#include <clock.h>#include <iopins.h>#include <usb.h>using namespace Zhele;using namespace Zhele::Clock;using namespace Zhele::IO;using namespace Zhele::Usb;using Report = HidReport<  0x06, 0x00, 0xff,        // USAGE_PAGE (Generic Desktop)  0x09, 0x01,          // USAGE (Vendor Usage 1)  0xa1, 0x01,          // COLLECTION (Application)  0x85, 0x01,          //   REPORT_ID (1)  0x09, 0x01,          //   USAGE (Vendor Usage 1)  0x15, 0x00,          //   LOGICAL_MINIMUM (0)  0x25, 0x01,          //   LOGICAL_MAXIMUM (1)  0x75, 0x08,          //   REPORT_SIZE (8)  0x95, 0x01,          //   REPORT_COUNT (1)  0xb1, 0x82,          //   FEATURE (Data,Var,Abs,Vol)  0x85, 0x01,          //   REPORT_ID (1)  0x09, 0x01,          //   USAGE (Vendor Usage 1)  0x91, 0x82,          //   OUTPUT (Data,Var,Abs,Vol)  0xc0               // END_COLLECTION>;using HidDesc = HidDescriptor<0x1001, Report>;using LedsControlEpBase = OutEndpointBase<1, EndpointType::Interrupt, 4, 32>;using EpInitializer = EndpointsInitializer<DefaultEp0, LedsControlEpBase>;using Ep0 = EpInitializer::ExtendEndpoint<DefaultEp0>;using LedsControlEp = EpInitializer::ExtendEndpoint<LedsControlEpBase>;using Hid = HidInterface<0, 0, 0, 0, HidDesc, LedsControlEp>;using Config = HidConfiguration<0, 250, false, false, Report, Hid>;using MyDevice = Device<0x0200, DeviceClass::InterfaceSpecified, 0, 0, 0x0483, 0x5711, 0, Ep0, Config>;using Led = IO::Pc13Inv;void ConfigureClock();void ConfigureLeds();int main(){  ConfigureClock();  ConfigureLeds();  Zhele::IO::Porta::Enable();  MyDevice::Enable();  for(;;)  {  }}void ConfigureClock(){  PllClock::SelectClockSource(PllClock::ClockSource::External);  PllClock::SetMultiplier(9);  Apb1Clock::SetPrescaler(Apb1Clock::Div2);  SysClock::SelectClockSource(SysClock::Pll);  MyDevice::SelectClockSource(Zhele::Usb::ClockSource::PllDividedOneAndHalf);}void ConfigureLeds(){  Led::Port::Enable();  Led::SetConfiguration<Led::Configuration::Out>();  Led::SetDriverType<Led::DriverType::PushPull>();  Led::Set();}template<>void LedsControlEp::Handler(){  LedsControlEp::ClearCtrRx();  uint8_t* buffer = reinterpret_cast<uint8_t*>(LedsControlEp::Buffer);  bool needSet = buffer[1] != 0;  switch(buffer[0])  {  case 1:    needSet ? Led::Set() : Led::Clear();    break;  }  LedsControlEp::SetRxStatus(EndpointStatus::Valid);}extern "C" void USB_LP_IRQHandler(){  MyDevice::CommonHandler();}

Заключение

Не совсем очевидная реализация библиотечного кода (в прошлой статье получил заслуженные комментарии в стиле "Не хотел бы увидеть такой код в продакшне", "Как это поддерживать" и т.п.) позволила максимально упростить непосредственно реализацию устройства, не нужно даже вручную объявлять дескрипторы: все генерируется из подставленных в шаблоны аргументов. Использование variadic-шаблонов помогло избавиться от лишних зависимостей. Прошивка тоже получается компактной, код из примера выше с оптимизацией Og вышел в 2360 байтов Flash и 36 байтов RAM (с оптимизацией Os прошивка весит 1712 байтов, но не работает. Пока не разобрался, почему именно), что я считаю неплохим результатом.

Благодарности

За замечательный пост про HID благодарен @RaJa. Также менее, чем за неделю до написания этого поста вышел еще крутой материал по HID от @COKPOWEHEU. Без этих постов я бы ничего не осилил. Еще большую помощь оказали пользователи с форума radiokot (COKPOWEHEU и VladislavS), был приятно удивлен оперативностью ответов и желанием помочь.

Подробнее..

Stm32 USB на шаблонах C. Продолжение. Делаем CDC

30.05.2021 20:12:29 | Автор: admin

Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры Stm32, в прошлой статье рассказал об успешной (почти) реализации HID устройства. Еще одним популярным классом USB является виртуальный COM-порт (VCP) из класса CDC. Популярность объясняется тем, что обмен данными осуществляется аналогично привычному и простому последовательному протоколу UART, однако снимает необходимость установки в устройство отдельного преобразователя.

Интерфейсы

Устройство класса CDC должно поддерживать два интерфейса: интерфейс для управления параметрами соединения и интерфейс обмена данными.

Интерфейс управления представляет собой расширение базового класса интерфейса с тем отличием, что содержит одну конечную точку (хотя, насколько я понял, без необходимости поддержки всех возможностей можно обойтись вообще без конечной точки) и набор "функциональностей", определяющих возможности устройства. В рамках разрабатываемой библиотеки данный интерфейс представлен следующим классом:

template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint, typename... _Functionals>class CdcCommInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint>{  using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint>;  static LineCoding _lineCoding;  ...

В базовом случае интерфейс должен поддерживать три управляющих (setup) пакета:

  • SET_LINE_CODING: установка параметров линии: Baudrate, Stop Bits, Parity, Data bits. Некоторые проекты, на которые я ориентировался (основным источников вдохновения стал этот проект), игнорируют данный пакет, однако в этом случае некоторые терминалы (например, Putty), отказываются работать.

  • GET_LINE_CODING: обратная операция, в ответ на эту команду устройство должно вернуть текущие параметры.

  • SET_CONTROL_LINE_STATE: установка состояния линии (RTS, DTR и т.д.).

Код обработчика setup-пакетов:

switch (static_cast<CdcRequest>(setup->Request)){case CdcRequest::SetLineCoding:  if(setup->Length == 7)  {    // Wait line coding    _Ep0::SetOutDataTransferCallback([]{      memcpy(&_lineCoding, reinterpret_cast<const void*>(_Ep0::RxBuffer), 7);      _Ep0::ResetOutDataTransferCallback();      _Ep0::SendZLP();    });    _Ep0::SetRxStatus(EndpointStatus::Valid);  }  break;case CdcRequest::GetLineCoding:  _Ep0::SendData(&_lineCoding, sizeof(LineCoding));  break;case CdcRequest::SetControlLineState:  _Ep0::SendZLP();  break;default:  break;}

Ключевой момент нумерации, а именно формирование дескрипторов, выполнен по уже привычной схеме раскрытия variadic-ов, что позволяет избавиться от зависимости классов в иерархии:

static uint16_t FillDescriptor(InterfaceDescriptor* descriptor){  uint16_t totalLength = sizeof(InterfaceDescriptor);    *descriptor = InterfaceDescriptor {    .Number = _Number,    .AlternateSetting = _AlternateSetting,    .EndpointsCount = Base::EndpointsCount,    .Class = DeviceAndInterfaceClass::Comm,    .SubClass = _SubClass,    .Protocol = _Protocol  };  uint8_t* functionalDescriptors = reinterpret_cast<uint8_t*>(descriptor);  ((totalLength += _Functionals::FillDescriptor(&functionalDescriptors[totalLength])), ...);  EndpointDescriptor* endpointDescriptors = reinterpret_cast<EndpointDescriptor*>(&functionalDescriptors[totalLength]);  totalLength += _Endpoint::FillDescriptor(endpointDescriptors);  return totalLength;}

Второй интерфейс, предназначенный для непосредственно обмена данными, абсолютно примитивный, он не должен поддерживать управляющих сообщений, а является просто контейнером для двух конечный точек (точнее одной двунаправленной). Объявление класса:

template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint>class CdcDataInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint>{  using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint>;  ...

Поскольку мои познания в CDC-устройствах весьма небольшие, из просмотренных примеров я сделал вывод, что управляющий интерфейс почти всегда одинаковый и содержит 4 функциональности: Header, CallManagement, ACM, Union, поэтому добавил упрощенный шаблон интерфейса:

template<uint8_t _Number, typename _Ep0, typename _Endpoint>using DefaultCdcCommInterface = CdcCommInterface<_Number, 0, 0x02, 0x01, _Ep0, _Endpoint, HeaderFunctional, CallManagementFunctional, AcmFunctional, UnionFunctional>;

Применение разработанных классов

Для использования разработанных классов достаточно объявить две конечные точки (Interrupt для первого интерфейса и двунаправленную Bulk для второго), объявить оба интерфейса, конфигурацию с ними и, наконец, инстанцировать класс устройства:

using CdcCommEndpointBase = InEndpointBase<1, EndpointType::Interrupt, 8, 0xff>;using CdcDataEndpointBase = BidirectionalEndpointBase<2, EndpointType::Bulk, 32, 0>;using EpInitializer = EndpointsInitializer<DefaultEp0, CdcCommEndpointBase, CdcDataEndpointBase>;using Ep0 = EpInitializer::ExtendEndpoint<DefaultEp0>;using CdcCommEndpoint = EpInitializer::ExtendEndpoint<CdcCommEndpointBase>;using CdcDataEndpoint = EpInitializer::ExtendEndpoint<CdcDataEndpointBase>;using CdcComm = DefaultCdcCommInterface<0, Ep0, CdcCommEndpoint>;using CdcData = CdcDataInterface<1, 0, 0, 0, Ep0, CdcDataEndpoint>;using Config = Configuration<0, 250, false, false, CdcComm, CdcData>;using MyDevice = Device<0x0200, DeviceAndInterfaceClass::Comm, 0, 0, 0x0483, 0x5711, 0, Ep0, Config>;

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

template<>void CdcDataEndpoint::HandleRx(){  uint8_t* data = reinterpret_cast<uint8_t*>(CdcDataEndpoint::RxBuffer);  uint8_t size = CdcDataEndpoint::RxBufferCount::Get();  if(size > 0)  {    if(data[0] == '0')    {      Led::Clear();      CdcDataEndpoint::SendData("LED is turn off\r\n", 17);    }    if(data[0] == '1')    {      Led::Set();      CdcDataEndpoint::SendData("LED is turn on\r\n", 16);    }  }  CdcDataEndpoint::SetRxStatus(EndpointStatus::Valid);}

Отладка и тестирование

Написать код правильно с первого раза практически невозможно, поэтому очень полезным оказалось все-таки разобраться с инструментами перехвата USB-пакетов, поэтому кратко опишу особенности и проблемы, с которыми столкнулся лично я.

Так и не удалось применить логический анализатор, он просто ничего не показывает. Полагаю, что дело в том, что это самый дешевый клон Seale Logic и если бы был в наличи нормальный аппарат, то все бы получилось. Главное преимущество логического анализатора заключается в том, что он позволяет отслеживать обмен данными еще в процессе нумерации, в то время как программы на стороне хоста показывают пакеты только для тех устройств, которые эту нумерацию успешно прошли.

WireShark с установленным UsbPcap оказался весьма удобным, он нормально парсит все данные, так что поиск ошибок значительно упрощается. Главное, что нужно сделать - правильно установить фильтры. Не нашел ничего лучше, кроме выполнить следующие две операции:

Сначала отфильтровать по заведомо известному значению. Например, по значению PID, которое присутствует в ответе устройства на запрос GET_DEVICE_DESCRIPTOR. Фильтр: "usb.idProduct == 0x5711". Это позволит быстро определить адрес устройства.

Далее отфильтровать по адресу устройства с помощью оператора contains. Дело в том, что отображаемый адрес состоит из трех частей, последняя из которых является номером конечной точки (можно, конечно, перечислить все адреса). Фильтр: "usb.addr contains "1.19"".

Однако стоит заметить, что UsbPcap может доставить некоторые трудности, под катом опишу ситуацию, в которую недавно попал и потратил кучу времени и нервов.

Проблема с usbpcap

Для большей мобильности завел себе внешний SSD, на котором установлена Windows 10 To Go (Windows, предназначенная для установки на внешние носители). Хотя Microsoft вроде отказалась от поддержки этой технологии, в целом все работает. Прихожу с диском в новое место, гружусь с него, система подтягивает драйвера и все нормально (и быстро) работает.

Однажды Windows просто не загрузилась с синим экраном "inaccessible boot device". Потратил целые выходные, восстановить так и не смог, пришлось все переустановить. Через некоторое время та же проблема и снова потраченные на переустановку выходные. Спустя пару дней система опять не грузится, начал вспоминать и анализировать, что я такого делал. Выяснил, что проблема возникала после установки как раз WireShark с usbpcap. На одном из форумов наткнулся на сообщение от пользователя, который жаловался на проблему с мышкой/клавиатурой после установки usbpcap. Снес через LiveCD драйвер и Windows запустилась. Не уверен на 100%, но предположение такое: при запуске компьютера Windows начинается загружаться, подгружает драйвера usbpcap, тот блокирует USB, система дальше грузиться не может и падает в BSOD. Очень неочевидное поведение, жаль потраченного времени.

Тестировал написанный код в программе Terminal v1.9b, на скриншоте приведен результат отправки на устройство сообщений "0" и "1".

Полный код примера можно посмотреть в репозитории. Пример протестирован на STM32F072B-DISCO. Как и в случае с HID, громоздкая библиотека (особенно менеджер конечных точек) сильно облегчили реализацию поддержки CDC, на все ушел примерно полный день. Далее планирую добавить еще класс Mass Storage Device, и на этом, наверно, можно остановиться. Приветствую вопросы и замечания.

Подробнее..

Что мы привезли на CES 2021 от накопителей до кардридеров

22.01.2021 14:05:41 | Автор: admin
Привет, Хабр! В этом году выставка потребительской электроники CES 2021 прошла в уже привычном для многих онлайн-формате. И хотя многие бренды, которые выставлялись на выставке в прошлом году, не стали анонсировать свои новинки в начале текущего, Kingston продолжает блюсти традиции. Переход в онлайн позволил компании без посредников поведать о своих планах гораздо большему количеству людей, нежели в режиме традиционной выставки.



Итак, в рамках CES 2021 мы дискутировали не только о новых трендах, которых компания будет придерживаться на протяжении всего года, но и показали новые устройства для специалистов, а также решения корпоративного класса. Далее по тексту расскажем какие.

Kingston Ghost Tree PCIe NVMe Gen 4.0


Большинство потребительских NVMe-накопителей, вышедших в 2020 году и соответствующих спецификациям PCIe 4.0 выпущены на базе контроллеров Phison PS5016-E16. К сожалению, они не способны полностью реализовать потенциал нового интерфейса и полностью задействовать его пропускную способность.

Kingston же не спешил с анонсами своих решений, поэтому Ghost Tree PCIe NVMe Gen 4.0 получился оптимизированным и сбалансированным. В то время как конкуренты предлагают схожие накопители с пропускной способностью на уровне 4-5 Гбайт/с, мы говорим о скорости чтения/записи на уровне 7 Гбайт/с.

Накопители серии Ghost Tree также построены на 8-канальном контроллере. Пока не скажем, на каком именно, но можем с уверенностью утверждать, что SSD-решения с емкостью от 1 до 4 Тбайт смогут на полную задействовать производительность интерфейса PCIe 4.0. Убедитесь в этом сами, когда мы опубликуем первые тесты на страницах нашего блога.

Kingston NV Series Gen. 3.0x4


Наравне с накопителями топового класса, в рамках CES 2021 мы анонсировали линейку PCIe 3.0-устройств, которые ориентированы на начинающих пользователей. Максимальная емкость таких накопителей будет ограничена двумя терабайтами, что помогает снизить розничную стоимость и сделать данные SSD доступными для большинства потребителей.

Kingston SSD DC1500M U.2 NVMe


Накопитель Kingston SSD DC1500M U.2 NVMe относится к решениям корпоративного класса и приходит на смену SSD Kingston DC1000M U.2 NVMe. Эта новинка обеспечивает высокую пропускную способность и низкую латентность, и предназначена для размещения в серверах последнего поколения.

DC1500M нацелен на решение широкого спектра задач и нагрузок, которые подразумевают интенсивное использование данных большим количеством пользователей единовременно. Подходит накопитель и для ЦОД, занимающихся облачными вычислениями, а также хостинг-провайдеров и виртуальных инфраструктур, которые обслуживают и оптимизируют бизнес-процессы компаний.

Накопители NVMe SSD с поддержкой PCIe 4.0


В прошлом году локомотивом выставочных анонсов стал SSD-накопитель Kingston DC1000B M.2 NVMe, но он предлагал поддержку PCIe 3.0, что вполне логично. С появлением системных плат, предлагающих PCIe поколения 4.0 вырос и спрос на соответствующие накопители, поэтому форвардами 2021 года в линейке наших NVMe-устройств станут твердотельные решения с поддержкой нового интерфейса. В этом году мы планируем выпускать как клиентские U.2-решения, так и профессиональные устройства для серверных кластеров. Давайте коротко пройдемся по каждой из моделей накопителей, которые уже скоро появятся на рынке.

Внешний SSD Kingston XS2000 с поддержкой USB 3.2



Помимо NVMe-решений корпоративного и потребительского уровня, мы анонсировали внешний SSD-накопитель USB 3.2 Gen 2x2, который будет предлагаться с емкостями от 500 Гбайт до 2000 Гбайт. Kingston XS2000 позиционируется как производительное хранилище для фотографий, видеороликов и других файлов, а его главными особенностями являются высокая скорость записи (до 2500 Мбайт/с) и быстрый доступ к требуемым данным. Для подключения к ПК и ноутбукам используется порт USB-C, являющийся основным при использовании USB 3.2 Gen 2.

Kingston Workflow: кардридеры и док-станции для них


И последняя интересная штука с CES 2021 внешние кардридеры (Workflow SD Reader и Workflow microSD Reader), которые можно использовать и как самостоятельные устройства, и вкупе с док-станцией Workflow Station. Кому и для чего такие гаджеты могут понадобиться? Если с карт-ридерами все понятно (как правило, они нужны фотографам и видеографам для доступа к данным, находящимся на картах памяти и переброски их на ПК), то о док-станции стоит поговорить отдельно.



По сути Kingston Workflow Station позволяет работать сразу с несколькими типами кардридеров, что актуально, если у пользователя есть необходимость работать с данными, записанными на картах памяти разного типоразмера. Приведем пример: допустим, что вы снимаете 4K-видео для YouTube на профессиональную камеру (с установленной внутри SD-картой) и параллельно ведете видеозапись на смартфон (с установленной внутри microSD-картой) для размещения получившегося видео в сервисах типа Instagram и TikTok. В этом случае наличие мини-хаба позволит вам одновременно подключить к компьютеру и ноутбуку две карты памяти разных типоразмеров, не используя переходники (microSD на SD).

Казалось бы, аргументов недостаточно, поэтому давайте представим, что видео, с которым вам предстоит поработать, обладает разрешением 4K/8K. Как следствие, конечные файлы обладают большим весом, поэтому для копирования их на ПК и для манипуляций непосредственно в рамках карты памяти, необходима высокая пропускная способность, которая реализована через подключение Type-C (USB 3.2 Gen 2). Как итог: в сочетании с высокопроизводительными картами памяти и USB-накопителями Kingston это позволит значительно ускорить рабочие процессы на стадии постпродакшна.

Итоги


На этом мы заканчиваем экскурс в анонсы новинок, представленных на CES 2021, и переходим к обсуждениям. Расскажите в комментариях, какие устройства вы считаете наиболее интересными и тесты каких накопителей вы хотели бы увидеть на страницах нашего блога?

____________________________________________________________________________________________________________

Для получения дополнительной информации о продуктах Kingston Technology обращайтесь на официальный сайт компании.
Подробнее..

Полноценная GDB отладка через USB на плате BluePill (STM32F103С8T6)

09.03.2021 18:08:33 | Автор: admin

В данной статье речь пойдет о программировании и полноценной отладке микроконтроллера STM32F103C8T6 через USB.

Однажды, от коллег поступило предложение о участии в IoT проекте. Система предусматривала однопоточный запуск скриптов. Отладка производилась с помощью логов. И тут мне в голову пришла мысль о полноценной удаленной отладке проектов под микроконтроллеры.

Для начала нужно было опробовать все на прототипе. В качестве отладочной платы была выбрана почти всем знакомая BluePill на микроконтроллере STM32F103. Поскольку на данной отладке имеется интерфейс MicroUSB, было принято решение в прототипе использовать именно этот интерфейс. В будущем предполагался переход на UART подключенный к GSM модулю.

Требовалось реализовать загрузчик имеющий несколько функциональных блоков. Задачу на подзадачи и решать их последовательно. Рассмотрим их.

  1. Драйвер интерфейса USB со стороны микроконтроллера.

  2. Код обновления прошивки микроконтроллера с помощью GDB.

  3. GDB сервер.

  4. Вывод отладочных логов.

Обо все по порядку. Для прототипирования был реализован загрузчик (bootloader).

1. Первым был реализован протокол отладочного интерфейса. Т.е. USB. В качестве класса USB-устройств был выбран WinUSB. Для его реализации я использовал воспользовался исходным кодом библиотеки libopencm3. Для этого необходимо описать дескриптор устройства, дескрипторы конфигурации, интерфейса, конечных точек, а так же дескрипторы содержащие стоки "MSFT100" и "WINUSB". Последние два дескриптора требуются для определения устройства как WinUSB. Конфигурация конечных точек (USB-Endpoint) выбрана следующим образом control endpoint 0, bulk out endpoint 1, bulk in endpoint 81, bulk in endpoint 82. Конечная точка с номером ноль присутствует во всех устройствах USB, endpoint 1- применяется для передачи команд в загрузчик микроконтроллера, endpoint 81 - для передачи ответов на команды на компьютер, а 82 - для передачи текстовой информации (логов). Подробнее о USB можно прочитать в публикациях из разряда USB in a NutShell.

2. Требовался код работы с флеш памятью. Вы можете подумать что тут все просто. Это так и не так одновременно. Первая проблема, которая возникла,- невозможность стереть флеш память в обработчике прерывания. Дело в том, что архитектура Cortex M предусматривает два режима работы процессора. Thread и Handler. В первом режиме процессор находится после старта, а так же когда нет активных прерываний. В Handler mode исполняются все обработчики исключений и прерываний. К сожалению, стирание flash-памяти на STM32F103C8T6 в Handler режиме приводит к корректному статусу стирания памяти, но сама память не стирается.

Эта проблема решается посредством запуска кода стирания Flash в Thread режиме. Сделать это можно, так, как обычно происходит в операционных системах. Для этого нужно понимать что такое контекст потока. Это состояние набора регистров процессора, стека, описывающий конкретный момент работы системы. при входе в обработчик прерывания сохраняется контекст текущего работающего потока, а при выходе из обработчика, он восстанавливается и выполнение программы продолжается. Нам при выходе из обработчика нужно лишь восстановить "свой" контекст начала функции стирания Flash памяти. Подобная операция происходит в операционных системах при переключении задач.

Другая проблема является более сложной. Заключается она в том, что при работе с флеш памятью может происходить выполнение обработчика прерывания, той прошивки, которая находится под отладкой. Эта проблема решается несколькими действиями перед стиранием памяти. Первое что требуется сделать,- заблокировать вызов любых обработчиков прерываний, используемых в отлаживаемой прошивке. Или проще говоря тех, которые не используются в Bootloader-e. Но даже в этом случае команда на стирание памяти может поступить в то время, когда один из обработчиков уже выполняется. Для решения этого вопроса и решил воспользоваться пошаговым режимом работы процессора и в таком режиме вывести процессор из всех обработчиков прерываний. После этого флеш-память можно стирать.

3. Требовалось реализовать GDB-сервер. Я воспользовался исходным кодом проекта BlackMagic, для обработки команд приходящих из среды разработки. На самом деле приходящих от приложения arm-none-eabi-gdb. Далее команды транслировались в команды бинарного протокола, который используется для п процессе взаимодействия с микроконтроллером. Нижний уровень GDB-сервера выполнен с использованием библиотеки WinUSB.

4. После того как прототип заработал, я пришел к решению добавить вывод отладочной информации с использованием printf. Для передачи отладочных сообщений использовал endpoint 82. На самом деле 8 - это единица в старшем разряде, указывающая направление передачи данных по шине USB в сторону компьютера (Host-а).

Но таким образом функцией printf можно было пользоваться только в bootloader-е. А как же быть с отлаживаемым приложением? Обычно для взаимодействия с операционной системой используются прерывания/системные вызовы. Так BIOS использова int13, ms-dos int21. Мы же на микроконтроллере воспользуемся системным вызовом, т.е. командой svc. При выполнении данной команды в прошивке, будет вызван обработчик прерывания SVC, находящийся в bootloader-е. Что нам и требовалось сделать.

Bootloader использует 10Kb flash памяти, но зарезервировано 16Kb с целью расширения функционала. Так же используется 4K оперативной памяти. Оперативная память применяется для хранения буферов USB, контекста прерванного процесса, а так же как память стека обработчиков прерываний. Итого. Остается 16Kb из 20Kb оперативной памяти и 48Kb flash памяти. Хотя на самом деле flash-память в контроллере STM32F103C8T6 не 64Kb а 128Kb,- соответственно остается 112Kb.

В процессе отладки прошивки, возникает один интересный момент. Если, в отладчике делать шаг на потоке, а в это время произойдет вызов обработчика прерывания, то отладчик шагнет в обработчик прерывания. Чтобы такого не происходило, в коде я использовал step режим для выхода из обработчика прерываний. При этом если отладчик в прерывании наткнется на точку останова, будет произведена остановка отладки на точке останова.

И наконец, - что поддерживается:

  1. Загрузка прошивки на плату с использованием GDB. Т.е. непосредственно из среды программирования/отладки. В моем случае это STM32CubeIDE. Адрес вектора прерываний должен находится по адресу 0x8004000.

  2. Просмотр и изменение памяти.

  3. Просмотр и изменение регистров периферии.

  4. Восемь точек останова.

  5. Режим пошаговой отладки.

  6. Принудительная остановка.

В отлаживаемой прошивке нельзя изменять адрес вектора обработчика прерываний. Нельзя изменять приоритеты прерываний. Приоритет должен быть выше или равен 0x40 по значению. Нельзя запрещать прерывания systick, прерывание usb, и прерывания DebugMon, SvcHandler, а так же всех FaultHandler-s.

Код прототипа проекта доступен по ссылке

Видео работы с платой в среде STM32CubeIDE

Подробнее..

Швейцарский нож инженера дата-центра Zalman ZM-VE500

23.09.2020 18:06:49 | Автор: admin

Профессиональных секретов и инструментов достаточно у любого системного администратора или инженера. Сегодня мы расскажем об одном крайне полезном девайсе, Zalman ZM-VE500, которым пользуются системные инженеры дата-центров Selectel.

Несколько лет назад основным запросом пользователей была установка операционной системы. Когда клиенты заказывали у нас выделенный сервер, то в большинстве случаев установка и базовая настройка ОС ложилась на плечи системных инженеров. Мы тогда запрашивали, какую именно версию операционной системы установить, а также желаемую разметку дисков. Спустя непродолжительное время клиент получал от нас реквизиты доступа в ОС.

Что оставалось за кадром


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

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

До массового появления серверов с UEFI проблема загрузки стояла особенно остро. Далеко не все серверы оснащались оптическим дисководом, а самым идеальным вариантом для Legacy-загрузки всегда был запуск с CD или DVD-диска. Очевидным способом было каждый раз подключать внешний привод и записывать диск. Но он имел один существенный недостаток: это долго и не всегда успешно. А если образ превышал по размеру стандартную DVD-болванку, то задача и вовсе становилась нетривиальной.

Разумеется, можно каждый раз записывать образ на флешку и загружаться с нее. Но этот метод имел несколько серьезных недостатков. Главным недостатком было то, что для разных дистрибутивов требовались разные утилиты для записи. Какого-либо универсального способа просто не существует. Так, например, для записи дистрибутива с Windows Server лучше всего использовать Rufus в режиме записи ISO, а для Ubuntu подойдет и обычный Win32 Disk Imager. Когда имеешь дело с зоопарком серверного и десктопного железа, нет никакой гарантии, что метод с записью на флешку сработает именно так, как задумано.

Эмуляция привода


Что если нам заставить жесткий диск представляться как CD/DVD-привод, а ISO-образы монтировать как диски? Примерно так и подумали инженеры Zalman, после чего на свет появилась линейка устройств ZM-VE*, имеющих возможность эмулировать оптический привод.

Слева ZM-VE500, справа ZM-VE300
Использовать эти устройства очень просто, везде одна и та же логика работы:

  1. Ставим внутрь любой отформатированный в NTFS жесткий диск или SSD.
  2. Переключаем устройство в режим HDD.
  3. В корне создаем папку _ISO.
  4. Закидываем образы в созданную папку.
  5. Переключаем устройство в режим VCD.
  6. Подключаемся к нужному компьютеру или серверу.
  7. Выбираем образ и монтируем его с помощью кнопки Mount.
  8. ...
  9. Профит!

Zalman ZM-VE500 с установленным 2.5 HDD
Большинство ноутбуков, компьютеров и серверов опознают Zalman в качестве оптического привода, подключенного по USB, и корректно загружаются с этого устройства.

Особенности использования


Как говорилось в известном анекдоте, Но! Есть один нюанс.... А именно некоторые моменты, которые важно знать перед покупкой и использованием этого девайса. Первый потребление тока. Не все USB-порты обладают возможностью запитать устройство корректно, особенно на некоторых старых серверах. В этом случае поможет отдельно запитанный от розетки USB-хаб. Это, пожалуй, самое большое неудобство, замеченное при постоянной экcплуатации в условиях дата-центра.

Второй момент время инициализации и сброс USB-питания. Некоторые серверные платформы в процессе запуска иногда сбрасывают питание USB, что полностью гасит Zalman и заставляет его повторно инициализироваться. Порой бывает так, что платформа проходит POST-тесты быстрее, чем инициализируется Zalman. Вкупе с автоматической сменой загрузочного устройства в BIOS это вызывает крайне негативные эмоции.

Третий нюанс заключается в том, что на некоторых платформах Zalman определяется некорректно и загрузка с него невозможна. На практике я с этим встречался всего два-три раза на достаточно древних серверах.

Остальные особенности четко прописаны в инструкции по эксплуатации и не должны вызывать дополнительных вопросов. Так, к примеру, есть ограничение на количество ISO-образов. ZM-VE500, равно как и предыдущие устройства этой линейки, поддерживают только 32 образа внутри директории _ISO.

Оригинал или копия


Этот вопрос для меня остается открытым. Все дело в том, что существует такой производитель, как IODD. Он выпускает аналогичные контейнеры для дисков с такими же возможностями по эмуляции оптического дисковода.

IODD-2541 весьма схож с ZALMAN ZM-VE300. Источник фото.
Кто-то говорит, что Zalman всего лишь перелицованная копия устройств IODD с измененной прошивкой. Другие утверждают, что IODD детально скопировал разработку Zalman. Из различных отзывов можно сделать вывод, что IODD работает стабильнее, поддерживает большее количество языков в меню и функций, но нам эти устройства использовать не доводилось.

На многих ресурсах сообщается, что некоторые модели Zalman, а именно ZM-VE200, 300 и 400, могут быть прошиты прошивкой от IODD и наоборот. Модели же 350 и 500 собственная разработка Zalman, и прошить их можно только оригинальными прошивками.

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


Да, устройство вовсе не лишено недостатков, однако сэкономило массу времени системным инженерам наших дата-центров. Возможность оперативно смонтировать любой ISO-образ на скорости USB-порта позволяет сократить время обработки заявок и легко установить практически любую операционную систему без необходимости готовить загрузочные флешки или записывать образы на оптические диски.

Надежны ли эти устройства Zalman? На мой взгляд, да, так как в условиях постоянного использования ZM-VE300 прожил более 5 лет и только потом вышел из строя. Стоит ли девайс своих денег? Однозначно ответ положительный.

Расскажите о своем опыте использования таких устройств.
Ждем вас в комментариях!

Подробнее..

Делаем из ENC28J60 внешнюю USB сетевую карту

01.12.2020 12:23:05 | Автор: admin

ENC28J60 - простой Ethernet контроллер, который может выступать в роли внешней сетевой карты у одноплатных компьютеров с GPIO (для raspberry есть даже готовый драйвер) и прочих ардуин. У моего лэптопа GPIO не выведены, попробуем исправить этот недостаток и прикрутить к нему ENC28J60 посредством STM32F103 и шнурка USB.

Давайте посмотрим, как это можно сделать.

Нам понадобится:

  • ENC28J60

  • Отладочная плата с STM32 с поддержкой USB device (например, вот такая):

  • Компьютер с Linux (я использую Ubuntu 16)

  • Второй компьютер с Ethernet для тестирования соединения (у меня raspberry pi), подключенный по wi-fi (и в одной локальной сети с первым)

Настройку STM32F103 описывать не буду, мануалов в сети хватает. Как и программирование самого ENC28J60 (в своем примере я использовал вот этот код c минимальными модификациями). Подключение ENC28J60 к STM32F103 по SPI1 описано там же.

Соединение

Компьютер (usb) -> stm32(SPI) -> ENC28J60(Ethernet кабель) -> raspberry

Как все работает

Обойдемся без написания драйверов ядра, будем работать в user space. На компьютере создадим виртуальный сетевой tap интерфейс (второго уровня, с заголовками Ethernet фреймов, есть еще tun интерфейсы, которые работают только с ip пакетами), к которому подсоединим нашу программу (назовем ее tap_handler.c). Чтобы фрейм попал в сетевой стек Linuxа, tap_handler'у достаточно записать его в tap интерфейс. Ну и наоборот, пакеты, адресованные tap интерфейсу, будут читаться tap_handler'ом, который может с ними что-то дальше сделать. В итоге, tap_handler бегает в бесконечном цикле и ждет появления данных либо со стороны tap интерфейса, либо со стороны /dev/ttyACM0 (это представление нашего USB девайса в Linuxе). В первом случае полученные данные пишем в /dev/ttyACM0, во втором в tap интерфейс.

Более подробно про работу с виртуальными интерфейсами написано здесь (для демонстрации работы vpn). Отсюда же я взял код, отвечающий за работу с виртуальными интерфейсами.

На STM32 при помощи CubeMX подключаем библиотеки для работы с USB CDC (virtual com port). После подключения SMT32 к компу Linux создаст файл /dev/ttyACM0 (ну или другой номер). Из этого файла можно прочитать данные, которые нам отправил STM32, а если записать туда данные, их сможет прочитать STM32.

Прошивка STM32 работает аналогично. В бесконечном цикле читаем данные с компа (функция CDC_Receive_FS в файле usbd_cdc_if.c) и записываем их в ENC28J60, который уже передает их дальше в сеть, а также считываем фреймы из ENC28J60 и направляем их в комп функцией CDC_Transmit_FS.

Насколько я понимаю, CDC должен передавать данные без ошибок. Однако я столкнулся с тем, что ошибки все же есть. Более того, первый пакет обычно всегда дублируется (причину я не нашел, дубляж виден в том числе в wireshark при прослушивании шины usb). Гуглеж показал, что у кого-то это происходило из-за использования неподабающего генератора частоты на STM32, что вряд ли является причиной проблемы в моем случае, т.к. я просто использовал внешний кварц. Поэтому и пришлось городить огород с метками.

Работа со стороны компа

Сетевой интерфейс создаем командой:

sudo openvpn --mktun --dev tap0

Присваиваем ip адрес:

sudo ifconfig tap0 10.0.0.1/24 up

tap_handler.с

Для правильной работы с /dev/ttyACM0 нужно передавать данные в raw виде и не давать драйверу терминала вносить в них изменения (например, считать нетекстовые данные управляющими символами и пр.). Настройка терминала:

char cdc_name[20]="/dev/ttyACM0";int tty_fd = open(cdc_name, O_RDWR | O_NOCTTY); struct termios portSettings;tcgetattr(tty_fd, &portSettings);cfmakeraw(&portSettings);tcsetattr(tty_fd, TCSANOW, &portSettings);tcflush(tty_fd, TCOFLUSH);

Подключаем tap_handler к tap0:

/* в dev передаем имя уже созданного интерфейса tap0*/int tun_alloc(char *dev, int flags) {    struct ifreq ifr;    int fd, err;    char *clonedev = "/dev/net/tun";    /* отталкиваемся от устройства  /dev/net/tun */    if( (fd = open(clonedev , O_RDWR)) < 0 ) {        perror("Opening /dev/net/tun");        return fd;    }        memset(&ifr, 0, sizeof(ifr));    ifr.ifr_flags = flags;        /* используем уже созданный tap0 */    if (*dev) {        strncpy(ifr.ifr_name, dev, IFNAMSIZ);    }    /* подсоединяемся к интерфейсу */    if( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) {        perror("ioctl(TUNSETIFF)");                close(fd);        return err;    }        strcpy(dev, ifr.ifr_name);    return fd;}

Вызываем эту функцию в main.c следующим образом:

strcpy(tun_name, "tap0");int tap_fd = tun_alloc(tun_name, IFF_TAP | IFF_NO_PI);

Флаг IFF_TAP указывает на тип интерфейса (tap). Флаг IFF_NO_PI нужен, чтобы ядро не добавляло префиксные байты перед началом пакета.

Проверяем наличие данных в tap0 и в /dev/ttyACM0. Пока данных нет, tap_handler находится в блокирующем select:

while(1) {    int ret;    fd_set rd_set;    FD_ZERO(&rd_set);    /* tap_fd - tap inteface descriptor */    FD_SET(tap_fd, &rd_set);    /* tty_fd - /dev/ttyACM0 descriptor */    FD_SET(tty_fd, &rd_set);    ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL);

После получения данных проверяем источник. При получении фрейма от tap0 tap_handler формирует пакет для STM32 (структура пакета: пакет начинается с метки - определенных 4 байт, чтобы идентифицировать начало фрейма, следующие 2 байта - длина фрейма, затем уже сам фрейм) и записывает его в /dev/ttyACM0. Затем небольшая задержка, чтобы данные прошли успешно:

if(FD_ISSET(tap_fd, &rd_set)) {        uint16_t nread = cread(tap_fd, buffer, BUFSIZE);       uint8_t buf[6];    *(uint32_t *)buf = PACKET_START_SIGN;    *(uint16_t *)(buf + 4) = nread;        cwrite(tty_fd,(char *)buf,6);        cwrite(tty_fd, buffer, nread);    delay_micro(delay_m);    }

Если есть данные в /dev/ttyACM0, убеждаемся что они начинаются с правильной метки (те же 4 байта), затем считываем длину фрейма, и потом сам фрейм. Полученный фрейм записываем в tap интерфейс:

if(FD_ISSET(tty_fd, &rd_set)) {    uint32_t sign;    /* считываем метку */    int nread = read_n(tty_fd, (char *)&sign, sizeof(sign));    /* дескриптор закрыт, выходим из программы */    if(nread == 0) {              break;    }    /* если не совпадает, пытаемся найти подпись в следующих 4 байтах */    if(sign != PACKET_START_SIGN){             continue;    }    /* читаем длину фрейма */    nread = read_n(tty_fd, (char *)&plength, 2);    if(nread == 0) {              break;    }    if (nread != 2){              continue;    }  /* здесь обрабатываем ситуацию, когда после запуска программы первый пакет дублируется */    if(flag){      flag = 0;      nread = cread(tty_fd, buffer, sizeof(buffer));      if(nread != 6){                continue;      }    }  /* слишком большая длина пакета, заканчиваем программу */    if(plength > BUFSIZE){            break;    }    /* читаем фрейм (plength байт)  и пишем его в tap interface*/    nread = read_n(tty_fd, buffer, plength);                if (nread != 0){                    cwrite(tap_fd, buffer, nread);                    delay_micro(delay_m);    }  }

Со стороны STM32

Кроме USB CDC в CubeMX подключим также HAL драйверы для работы с SPI1 и светодиодом.

Прием данных выполняется в callback'е CDC_Receive_FS (файл usbd_cdc_if.c), запуск которого инициируется прерываниями в библиотеке USB. В этом месте нельзя ставить долгоиграющие операции, поэтому пришедшие данные копируем в кольцевой буфер, из которого забираем их в основном цикле и отправляем наружу через ENC28J60. При риске переполнения буфера данные отбрасываются:

/* USB_POINTERS_ARRAY_SIZE - размер array_pos *//* MAX_FRAMELEN - максимальная длина фрейма *//* USB_BUFSIZE - размер кольцевого буфера */extern uint8_t usb_buf[]; /* кольцевой буфер для полученных от компа данных */extern uint32_t pos_int; /* индекс для размещения следующего пакета в кольцевом буфере */extern uint32_t array_pos[]; /* кольцевой массив из индексов, которые указывают на полученные пакеты в кольцевом буфере */extern uint32_t p_a; /* индекс следующей записи в array_pos для CDC_Receive_FS*/extern uint32_t pl_a;/* индекс следующей записи в array_pos у основного потока *//* USB_POINTERS_ARRAY_SIZE - размер array_pos *//* MAX_FRAMELEN - максимальная длина фрейма *//* USB_BUFSIZE - размер кольцевого буфера */static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len){  int8_t memok = 1;  /* отбрасываем пришедшие данные, если не хватает места в кольцевом буфере */  if( pl_a !=0 && p_a !=0){    int32_t mem_lag = array_pos[(p_a - 1) % USB_POINTERS_ARRAY_SIZE] - array_pos[(pl_a - 1) % USB_POINTERS_ARRAY_SIZE];    if(mem_lag > USB_BUFSIZE - MAX_FRAMELEN)      memok = 0;  }  /* Копируем поступившие данные в кольцевой буфер,    обновляем массив индексов пришедших пакетов (array_pos) */  if(*Len < USB_BUFSIZE && *Len != 0 && memok){    uint16_t offset = pos_int % USB_BUFSIZE;    uint16_t new_pos = offset + *Len;    uint8_t split = 0;    if (new_pos > USB_BUFSIZE){      split = 1;    }    if(split){      int len1 = USB_BUFSIZE - offset;      int len2 = *Len - len1;      memcpy(usb_buf + offset, Buf, len1);      memcpy(usb_buf, Buf + len1, len2);    }    else      memcpy(usb_buf + offset, Buf, *Len);    pos_int += *Len;    array_pos[p_a % USB_POINTERS_ARRAY_SIZE] = pos_int;    p_a++;  }  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);  USBD_CDC_ReceivePacket(&hUsbDeviceFS);  return (USBD_OK);}

В основном потоке (main.c) смотрим на наличие данных в буфере и отправляем их через ENC28J60:

if(pl_a < p_a){      uint32_t prev = 0;  if(pl_a > 0)    prev = array_pos[(pl_a - 1) % USB_POINTERS_ARRAY_SIZE];  /* размер пакета (кусочек фрейма), принятого в CDC_Receive_FS */  int32_t n = array_pos[pl_a % USB_POINTERS_ARRAY_SIZE] - prev;//usb frame size  /* указатель на пакет в буфере */  uint8_t *from = usb_buf + prev % USB_BUFSIZE;  /* признак корректности пакета */  uint8_t right_n = 1;  if (n < 0 || n > MAX_FRAMELEN){    right_n = 0;  }  /* проверка на новый фрейм. В пакете должно быть минимум 6 байтов (подпись 4 байта и 2 байта длина) */  if((packet_len == 0) && packet_start && (n > 5) && right_n){    /* спец. функция для чтения из кольцевого буфера */    uint32_t sign = read32(from,usb_buf);     /* получаем указатель на данные через 4 байта */    uint8_t *next = next_usb_ptr(from,usb_buf,4);    /* читаем размер фрейма */    packet_size = read16(next,usb_buf);// 2 bytes after sign is packet length    /* отбрасываем неправильный пакет */    if (packet_size > MAX_FRAMELEN || sign != PACKET_START_SIGN){            packet_size = 0;    }    else{      /* копируем принятый пакет данных в буфер фрейма */      next = next_usb_ptr(from,usb_buf,6);      copy_buf(packet_buf, next, usb_buf, n - 6);            packet_len = n - 6;      packet_next_ptr = packet_buf + packet_len;      packet_start = 0;    }  }  /* обрабатываем последующие пакеты в фрейме */  else if(packet_len < packet_size && right_n){  /* копируем принятый пакет данных в буфер фрейма */        copy_buf(packet_next_ptr, from, usb_buf, n);        packet_len += n;    packet_next_ptr = packet_buf + packet_len;  }  /* отбрасываем ошибочный фрейм */  else if (packet_len > packet_size){        packet_len = 0;    packet_start = 1;  }  /* отправляем фрейм через enc28j60 */  if(packet_len == packet_size && packet_size > 0){        enc28j60_packetSend(packet_buf, packet_size);    packet_len = 0;    packet_start = 1;  }  pl_a++;}

а также проверяем наличие данных в ENC28J60 и при наличии отправляем их в USB и мигаем светодиодом:

len = enc28j60_packetReceive(net_buf,sizeof(net_buf));if (len > 0) {  *((uint16_t*)(sign_buf + 4)) = len;  while(CDC_Transmit_FS(sign_buf, sizeof(sign_buf)) == USBD_BUSY_CDC_TRANSMIT);  while(CDC_Transmit_FS(net_buf, len) == USBD_BUSY_CDC_TRANSMIT);  HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);}

В CDC_Transmit_FS я немного изменил код, чтобы функцию можно было поместить в while. В этом месте CDC_Transmit_FS возвращаем новый статус USBD_BUSY_CDC_TRANSMIT вместо просто USBD_BUSY. Без изменений узнать приняты ли данные к отправке мне не удалось:

if (hcdc->TxState != 0){    return USBD_BUSY_CDC_TRANSMIT;}

В функции инициализации ENC28J60 enc28j60_ini нужно разрешить принимать и передавать любые фреймы, не только адресованные ему (promiscuous mode):

enc28j60_writeRegByte(ERXFCON,0);

Настройка и проверка

Raspberry

Поднимаем eth0 и устанавливаем ip адрес. Запускаем ping

sudo ifconfig eth0 up 10.0.0.2/24ping 10.0.0.2

В другом окне можно и tcpdump:

sudo tcpdump -i eth0

Комп

Подсоединяем прошитый STM32 к компу, соединяем ENC28J60 сетевым кабелем с raspberry. На STM32 начинает мигать светодиод, показывая приход arp / icmp пакетов (от ping). Убеждаемся, что появился /dev/ttyACM0:

ls /dev/ttyACM*

Компилируем и запускаем tap_handler:

gcc tap_handler.c -o tap_handler./tap_handler

tap_handler запускает всю цепочку - подхватывает приходящие пакеты от raspberry, адресует их tap0, получает от него фрейм в ответ, шлет его STM32, когда фрейм доходит до raspberry мы видим, что пинги начинают проходить.

Делаем интернет на компе

Если пинги проходят проверяем работу нашей сетевой карты.

Комп

Я подсоединился к raspberry по ssh через wi-fi и управляю им с компа, соединение нам терять не стоит, поэтому просто меняем default gateway. Браузер перестает работать, но соединение с raspberry не теряется:

sudo route del default gateway 192.168.1.1sudo route add default gateway 10.0.0.2

Может потребоваться корректировка DNS сервера (в /etc/resolv.conf нужно прописать в качестве nameserver, например, 8.8.8.8).

Raspberry

Разрешаем переход пакетов из eth0 в wlan0 и включаем NAT:

echo 1 | sudo tee -a /proc/sys/net/ipv4/ip_forwardsudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE

Проверяем комп. Интернет должен появиться, правда небыстрый (у меня 0.5 Мбит/с).

Можно не заморачиваться с raspberry, а напрямую подсоединить сетевой провод от рутера в ENC28J60 (нужно отключить wi-fi на компе и задать правильный адрес tap0). Но тестировать проще с raspberry, в tcpdump видно все что происходит.

Зачем все это

Использовать такую связку в жизни наверно не очень удобно (особенно при наличии недорогих usb ethernet адаптеров в продаже), но сделать ее было очень интересно. Спасибо за внимание. Ссылка на код (проект в Atollic TrueStudio).

Подробнее..

Перевод Сравнение коннекторов USB-C за 5 и 20 центов

31.03.2021 10:06:06 | Автор: admin
Давайте сыграем в игру Какая деталь дешевле?

Вот два 16-контактных коннектора USB-C. Один стоит примерно 20 центов, а другой примерно 5 центов при заказе от 100 штук.

image

image

image

image

image

image

Ещё не разобрались? Разница очень малозаметна. На самом деле о качестве здесь говорит очень немногое, просто разные процессы изготовления.

Более дешёвый находится слева, с золотыми контактами внутри. Это SHOU HAN TYPE-C16PIN

Справа HRO TYPE-C-31-M-12

Самое серьёзное отличие заметно здесь. Крылышки на коннекторе SHOU немного потрескались при сгибании. Возможно, это вызвано тем, что их сгибали быстрее, дальше или потому, что у металла меньший предел разрушения. Но это вполне нормально, трещины в буквальном смысле микроскопические.

image

Коннектор HRO согнут чуть аккуратнее, с едва заметным зазором. Коннектор SHOU имеет очень маленький зазор. В этом опять же нет ничего серьёзного.

image

Как вы, вероятно, заметили, контакты коннектора HRO имеют покрытие не полностью. Это не особо важно, но, строго говоря, со временем обеспечит бОльшую поверхность атаки для коррозии. То есть, как оказалось, цена не всегда говорит о качестве, вероятно, SHOU просто разработала более эффективный техпроцесс.

А, и ещё у SHOU более короткие контакты на печатной плате. Это вполне нормально, но усложняет пайку вручную, поэтому стоит учитывать при покупке деталей для своих проектов.

Я решил, что будет интересно посмотреть и на литьё. По сути, именно оно формирует коннектор и играет важную роль в дешевизне и надёжности детали.

Начнём с коннектора HRO. Мы уже видим, что это очень сложное литьё, которое определённо не было дешёвым.

image


С другой стороны:

image

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

А вот коннектор SHOU. Мы чётко видим, что это гораздо менее сложное литьё и, по моему мнению, именно из-за этого возникла разница в цене.

image

С другой стороны. Похоже, снова литьё из двух или нескольких частей, но в целом гораздо менее сложное.

image

О, вот это тоже интересно. Более дорогой коннектор HRO содержит покрытую золотом медь (как можно догадаться по фотографии спереди). Покрытие не особо толстое, я смог отскрести его пинцетом. А у коннектора SHOU позолота очень глубокая.

imageimage

Я не особо хорошо знаю металлургию. Единственное, что я смог придумать это медь с позолотой, что тоже может повлиять на разницу в ценах. Сложно понять, насколько толст слой позолоты на коннекторе SHOU.

Если приблизить изображение спереди коннектора HRO и повысить его насыщенность, то можно достаточно хорошо разглядеть позолоту. То, что мы видим медь спереди, просто говорит, что покрытие было нанесено до того, как разъём отрезали от полосы.

image

А вот доказательство того, что коннектор SHOU на самом деле состоит из двух частей (передняя часть повредилась, пока я пытался процарапать покрытие). Однако похоже, что коннектор HRO на самом деле отлит целиком, а то, что я посчитал срезом, было просто линией разъёма.

image

Любопытно, что удерживащая коннектор защёлка намного больше у SHOU.

image
Подробнее..

USB на регистрах STM32L1 STM32F1

21.03.2021 16:08:22 | Автор: admin

Еще более низкий уровень (avr-vusb)
USB на регистрах: bulk endpoint на примере Mass Storage
USB на регистрах: interrupt endpoint на примере HID
USB на регистрах: isochronous endpoint на примере Audio device

С программным USB на примере AVR мы уже познакомились, пришла пора взяться за более тяжелые камни stm32. Подопытными у нас будут классический STM32F103C8T6 а также представитель малопотребляющей серии STM32L151RCT6. Как и раньше, пользоваться покупными отладочными платами и HAL'ом не будем, отдав предпочтение велосипеду.

Раз уж в заглавии указано два контроллера, стоит рассказать об основных отличиях. В первую очередь это резистор подтяжки, говорящий usb-хосту, что в него что-то воткнули. В L151 он встроен и управляется битом SYSCFG_PMC_USB_PU, а в F103 нет, придется впаивать на плату снаружи и соединять либо с VCC, либо с ножкой контроллера. В моем случае под руку попалась ножка PA10. На которой висит UART1 А другой вывод UART1 конфликтует с кнопкой замечательную плату я развел, не находите? Второе отличие это объем флеш-памяти: в F103 ее 64 кБ, а в L151 целых 256 кБ, чем мы когда-нибудь и воспользуемся при изучении конечных точек типа Bulk. Также у них немного отличаются настройки тактирования, да и лампочками с кнопочками могут на разных ногах висеть, но это уже совсем мелочи. Пример для F103 доступен в репозитории, так что адаптировать под него остальные эксперименты с L151 будет несложно. Исходные коды доступны тут: github.com/COKPOWEHEU/usb

Общий принцип работы с USB


Работа с USB в данном контроллере предполагается с использованием аппаратного модуля. То есть мы ему говорим что делать, он делает и в конце дергает прерывание я готовое!. Соответственно, из основного main'а нам вызывать почти ничего не надо (хотя функцию usb_class_poll я на всякий случай предусмотрел). Обычный цикл работы ограничен единственным событием обмен данными. Остальные сброс, сон и прочие события исключительные, разовые.

В низкоуровневые подробности обмена я на этот раз углубляться не буду. Кому интересно, может почитать про vusb. Но напомню, что обмен обычными данными идет не по одному байту, а по пакету, причем направление передачи задает хост. И названия этих направлений диктует тоже он: IN передача означает что хост принимает данные (а устройство передает), а OUT что хост передает данные (а мы принимаем). Более того, каждый пакет имеет свой адрес номер конечной точки, с которой хост хочет общаться. Пока что у нас будет единственная конечная точка 0, отвечающая за устройство в целом (для краткости я еще буду называть ее ep0). Для чего нужны остальные я расскажу в других статьях. Согласно стандарту, размер ep0 составляет строго 8 байт для низкоскоростных устройств (к каковым относится все тот же vusb) и на выбор 8, 16, 32, 64 байта для полноскоростных вроде нашего.

А если данных слишком мало и они не заполняют буфер полностью? Тут все просто: помимо данных в пакете передается и их размер (это может быть поле wLength либо низкоуровневая комбинация сигналов SE0, обозначающая конец передачи), так что даже если нам надо через ep0 размером 64 байта передать три байта, то переданы будут именно три байта. В результате расходовать пропускную способность, гоняя ненужные нули, мы не будем. Так что не стоит мельчить: если можем себе позволить потратить 64 байта, тратим не раздумывая. Помимо прочего это несколько уменьшит загруженность шины, ведь передать за раз кусок в 64 байта (плюс все заголовки и хвосты) проще, чем 8 раз по 8 байт (к каждому из которых опять-таки заголовки и хвосты).

А если данных напротив слишком много? Тут сложнее. Данные приходится разбивать по размеру конечной точки и передавать порциями. Скажем, размер ep0 у нас 8 байт, а передать хост пытается 20 байт. При первом прерывании к нам придут байты 0-7, во втором 8-15, в третьем 16-20. То есть чтобы собрать посылку целиком нужно получить целых три прерывания. Для этого в том же HAL придуман хитрый буфер, с которым я попытался разобраться, но после четвертого уровня пересылки одного и того же между функциями, плюнул. Как результат, в моей реализации буферизация ложится на плечи программиста.

Но хост хотя бы всегда говорит сколько данных пытается передать. Когда же данные передаем мы, надо как-то хитро дернуть низкоуровневые состояния ножек чтобы дать понять что данные закончились. Точнее, дать понять модулю usb, что данные закончились и что надо дернуть ножки. Делается это вполне очевидным способом записью только части буфера. Скажем, если буфер у нас 8 байт, а мы записали 4, то очевидно, данных у нас всего 4 байта, после которых модуль пошлет волшебную комбинацию SE0 и все будут довольны. А если мы записали 8 байт, это значит что у нас всего 8 байт или что это только часть данных, которая влезла в буфер? Модуль usb считает что часть. Поэтому если мы хотим остановить передачу, то после записи 8-байтного буфера должны записать следом 0-байтный. Это называется ZLP, Zero Length Packet. Про то, как это выглядит в коде, я расскажу чуть позже.

Организация памяти


Согласно стандарту, размер конечной точки 0 может достигать 64 байт. Размер любой другой аж 1024 байт. Количество точек также может отличаться от устройства к устройству. Те же STM32L1 поддерживают до 7 точек на вход и 7 на выход (не считая ep0), то есть до 14 кБ одних только буферов. Которые в таком объеме скорее всего никому никогда не понадобятся. Непозволительный расход памяти! Вместо этого модуль usb отгрызает себе кусок общей памяти ядра и пользуется ей. Эта область называется PMA (packet memory area) и начинается с USB_PMAADDR. А чтобы указать где внутри нее располагаются буферы каждой конечной точки, в начале выделен массив на 8 элементов каждый со следующей структурой, и только потом собственно область для данных:
typedef struct{    volatile uint32_t usb_tx_addr;    volatile uint32_t usb_tx_count;    volatile uint32_t usb_rx_addr;    volatile union{      uint32_t usb_rx_count;      struct{        uint32_t rx_count:10;        uint32_t rx_num_blocks:5;        uint32_t rx_blocksize:1;      };    };}usb_epdata_t;


Здесь задаются начало буфера передачи, его размер, потом начало буфера приема и его размер. Обратите внимание во-первых, что usb_tx_count задает не собственно размер буфера, а количество данных для передачи. То есть наш код должен записать по адресу usb_tx_addr данные, потом записать в usb_tx_count их размер и только потом дернуть регистр модуля usb что данные мол записаны, передавай. Еще большее внимание обратите внимание на странный формат размера буфера приема: он представляет собой структуру, в которой 10 бит rx_count отвечают за реальное количество прочитанных данных, а вот остальные уже действительно за размер буфера. Надо же железке знать докуда писать можно, а где начинаются чужие данные. Формат этой настройки тоже довольно интересный: флаг rx_block_size говорит в каких единицах задается размер. Если он сброшен в 0, то в 2-байтных словах, тогда размер буфера равен 2*rx_num_blocks, то есть от 0 до 62. А если выставлен в 1, то в 32-байтных блоках, соответственно размер буфера тогда оказывается 32*rx_num_blocks и лежит в диапазоне от 32 до 512 (да, не до 1024, такое вот ограничение контроллера).

Для размещения буферов в этой области будем использовать полудинамический подход. То есть выделять память по запросу, но не освобождать ее (еще не хватало malloc / free изобретать!). На начало неразмеченного пространства будет указывать переменная lastaddr, изначально указывающая на начало области PMA за вычетом таблицы структур, рассмотренной выше. Ну а при каждом вызове функции настройки очередной конечной точки usb_ep_init() она будет сдвигаться на указанный там размер буфера. И в соответствующую ячейку таблицы будет вносится нужное значение, естественно. Значение этой переменной сбрасывается по событию ресета, после чего тут же следует вызов usb_class_init(), в котором точки настраиваются заново в соответствии с юзерской задачей.

Работа с регистрами приема-передачи


Как было только что сказано, на прием мы читаем сколько же реально данных пришло (поле usb_rx_count), потом читаем сами данные, потом дергаем модуль usb что буфер свободен, можно принимать следующий пакет. На передачу наоборот: записываем данные в буфер, потом выставляем в usb_tx_count сколько же записано и наконец дергаем модуль что буфер заполнен, можно передавать.

Первые грабли начинаются при собственно работе с буфером: он организован не по 32 бита, как весь остальной контроллер, и не по 8 бит, как можно было ожидать. А по 16 бит! В результате запись и чтение в него осуществляются по 2 байта, выровненные по 4 байта. Спасибо, ST, что сделали такое извращение! Как бы скучно без этого жилось! Теперь обычным memcpy не обойтись, придется городить специальные функции. Кстати, если кто любит DMA, то оно такое преобразование делать вроде умеет самостоятельно, хотя я это не проверял.

И тут же вторые грабли с записью в регистры модуля. Дело в том, что за настройку каждой конечной точки за ее тип (control, bulk и т.д.) и состояние отвечает один регистр USB_EPnR, то есть просто так в нем бит не поменяешь, надо следить чтобы не попортить остальные. А во-вторых, в этом регистре присутствуют биты аж четырех типов! Одни доступны только на чтение (это замечательно), другие на чтение и запись (тоже нормально), третьи игнорируют запись 0, но при записи 1 меняют состояние на противоположное (начинается веселье), а четвертые напротив игнорируют запись 1, но запись 0 сбрасывает их в 0. Скажите мне, какой наркоман додумался в одном регистре сделать биты, игнорирующие 0 и игнорирующие 1?! Нет, я готов предположить что это сделано ради сохранения целостности регистра, когда к нему обращаются и из кода, и из железа. Но вам что, лень было поставить инвертор чтобы биты сбрасывались записью 1? Или в другом месте инвертор чтобы другие биты инвертировались записью 0? В результате выставление двух битов регистра выглядит так (еще раз спасибо ST за такое извращение):
#define ENDP_STAT_RX(num, stat) do{USB_EPx(num) = ((USB_EPx(num) & ~(USB_EP_DTOG_RX | USB_EP_DTOG_TX | USB_EPTX_STAT)) | USB_EP_CTR_RX | USB_EP_CTR_TX) ^ stat; }while(0)


Ах да, чуть не забыл: доступа к регистру по номеру у них тоже нет. То есть макросы USB_EP0R, USB_EP1R и т.д. у них есть, но вот если номер пришел в переменной, то увы. Пришлось изобретать свой USB_EPx() а что поделать.

Ну и для соблюдения формальностей укажу, что флаг готовности к чтению (то есть что предыдущие данные мы уже прочитали) задается битовой маской USB_EP_RX_VALID, а к записи (то есть что данные мы записали полностью и их можно передавать) маской USB_EP_TX_VALID.

Обработка IN и OUT запросов


Возникновение прерывания USB может сигнализировать о разных вещах, но сейчас мы сосредоточимся на запросах обмена данными. Флагом такого события будет бит USB_ISTR_CTR. Если увидели его, можем разбираться с какой точкой хочет общаться хост. Номер точки скрывается под битовой маской USB_ISTR_EP_ID, а направление IN или OUT под битами USB_EP_CTR_TX и USB_EP_CTR_RX соответственно.

Поскольку точек у нас может быть много, и каждая со своим алгоритмом обработки, заведем им всем callback-функции, которые бы вызывались по соответствующим событиям. Например, послал хост данные в endpoint3, мы прочитали USB->ISTR, вытащили оттуда что запрос у нас OUT и что номер точки равен 3. Вот и вызываем epfunc_out[3](3). Номер точки в скобках передается если вдруг юзерский код захочет повесить один обработчик на несколько точек. Ах да, еще в стандарте USB принято входные точки IN помечать взведенным 7-м битом. То есть endpoint3 на выход будет иметь номер 0x03, а на вход 0x83. Причем это разные точки, их можно использовать одновременно, друг другу они не мешают. Ну почти: в stm32 у них настройка типа (bulk, interrupt, ...) общая и на прием, и на передачу. Так что та же 0x83-я точка IN будет соответствовать callback'у epfunc_in[3](3 | 0x80).

Тот же принцип используется и для ep0. Разница только в том, что ее обработка происходит внутри библиотеки, а не внутри юзерского кода. Но что делать если нужно обрабатывать специфичные запросы вроде какого-нибудь HID не лезть же ковырять код библиотеки? Для этого предусмотрены специальные callback'и usb_class_ep0_out и usb_class_ep0_in, которые вызываются в специальных местах и имеют специальный формат, рассказывать про который я буду ближе к концу.

Стоит упомянуть еще про один не очень очевидный момент, связанный с возникновением прерываний обработки пакетов. С OUT запросами все просто: данные пришли, вот они. А вот IN прерывание генерируется не тогда, когда хост послал IN запрос, а когда в буфере передачи пусто. То есть по принципу действия это прерывание аналогично прерыванию по опустошению буфера UART. Следовательно, когда мы хотим что-то передать хосту, мы это просто записываем данные в буфер передачи, ждем прерывания IN и дописываем что не поместилось (не забываем про ZLP). И ладно еще с обычными endpoint'ами, ими программист управляет, можно пока не обращать внимание. Но вот через ep0 обмен идет всегда. Поэтому и работа с ней должна быть встроена в библиотеку.

Как следствие, начало передачи осуществляется функцией ep0_send, которая записывает в глобальную переменную адрес начала буфера и объем данных для передачи, после чего, обратите внимание, сама в первый раз дергает обработчик события IN. В дальнейшем этот обработчик будет вызываться по аппаратным событиям, но толчок дать все же надо.

Ну а сам обработчик устроен довольно просто: записывает очередной кусок данных в буфер передачи, сдвигает адрес начала буфера и уменьшает количество оставшихся для передачи байтов. Отдельный костыль связан с тем самым ZLP и необходимостью на некоторые запросы отвечать пустым пакетом. В данном случае конец передачи обозначается тем, что адрес данных стал NULL. А пустой пакет что он равен константе ZLPP. И то и другое происходит при равенстве размера нулю, так что реальной записи не происходит.

Похожий алгоритм придется реализовывать и при работе с другими конечными точками. Но это уже забота пользователя. Да и логика их работы зачастую отличается от работы с ep0, так что в ряде случаев такой вариант окажется удобнее буферизации на уровне библиотеки.

Логика общения по USB


Сам факт подключения хост определяет по наличию подтягивающего резистора между какой-нибудь линией данных и питанием. Он производит сброс устройства, назначает ему адрес на шине и пытается определить что же именно в него воткнули. Для этого он читает дескрипторы устройства и конфигурации (а если надо, то и специфичные). Также он может прочитать строковые дескрипторы чтобы понять как устройство само себя называет (хотя если пара VID:PID ему знакома, предпочтет вытянуть строки из своей базы данных). После этого хост может подгрузить соответствующий драйвер и работать с устройством уже на понятном ему языке. В понятный ему язык входят специфичные запросы и обращения к конкретным интерфейсам и конечным точкам. До этого тоже дойдем, но сначала надо чтобы устройство хотя бы отобразилось в системе.

Обработка SETUP запросов: DeviceDescriptor


Человек, хоть немного ковырявший USB, уже давно должен был насторожиться: COKPOWEHEU, ты говоришь про запросы IN и OUT, но ведь в стандарте прописан еще и SETUP. Да, так и есть, но это скорее разновидность OUT запроса, специально структурированная и предназначенная исключительно для конечной точки 0. Об ее структуре и особенностях работы и поговорим. Сама структура выглядит следующим образом:
typedef struct{  uint8_t bmRequestType;  uint8_t bRequest;  uint16_t wValue;  uint16_t wIndex;  uint16_t wLength;}config_pack_t;


Поля этой структуры рассмотрены во множестве источников, но все же напомню.
bmRequestType битовая маска, биты в которой означают следующее:
7: направление передачи. 0 от хоста к устройству, 1 от устройства к хосту. Фактически, это тип следующей передачи, OUT или IN.
6-5: класс запроса
0x00 (USB_REQ_STANDARD) стандартный (обрабатывать пока будем только их)
0x20 (USB_REQ_CLASS) специфичные для класса (до них дойдем в следующих статьях)
0x40 (USB_REQ_VENDOR) специфичные для производителя (надеюсь, не придется их трогать)
4-0: собеседник
0x00 (USB_REQ_DEVICE) устройство в целом
0x01 (USB_REQ_INTERFACE) отдельный интерфейс
0x02 (USB_REQ_ENDPOINT) конечная точка

bRequest собственно запрос
wValue небольшое 16-битное поле данных. На случай простых запросов, чтобы не гонять полноценные пересылки.
wIndex номер получателя. Например, интерфейса, с которым хост хочет пообщаться.
wLength размер дополнительных данных, если 16 бит wValue недостаточно.

Первым делом при подключении устройства хост пытается узнать что же именно в него воткнули. Для этого он посылает запрос со следующими данными:
bmRequestType = 0x80 (запрос на чтение) + USB_REQ_STANDARD (стандартный) + USB_REQ_DEVICE (к устройству в целом)
bRequest = 0x06 (GET_DESCRIPTOR) запрос дескриптора
wValue = 0x0100 (DEVICE_DESCRIPTOR) дескриптор устройства в целом
wIndex = 0 не используется
wLength = 0 дополнительных данных нет
После чего шлет запрос IN, куда устройство должно положить ответ. Как мы помним, запрос IN от хоста и прерывание контроллера слабо связаны, так что записывать ответ будем сразу в буфер передатчика ep0. Теоретически, данные из этого, да и всех прочих, дескрипторов привязаны к конкретному устройству, поэтому помещать их в ядро библиотеки бессмысленно. Соответствующие запросы передаются функции usb_class_get_std_descr, которая возвращает ядру указатель на начало данных и их размер. Дело в том, что некоторые дескрипторы могут быть переменного размера. Но DEVICE_DESCRIPTOR к ним не относится. Его размер и структура стандартизованы и выглядят так:
uint8_t bLength; //размер дескриптораuint8_t bDescriptorType; //тип дескриптора. В данном случае USB_DESCR_DEVICE (0x01)uint16_t bcdUSB; //число 0x0110 для usb-1.1, либо 0x0200 для 2.0. Других значений я не встречалuint8_t bDeviceClass; //класс устройстваuint8_t bDeviceSubClass; //подклассuint8_t bDeviceProtocol; //протоколuint8_t bMaxPacketSize0; //размер ep0uint16_t idVendor; // VIDuint16_t idProduct; // PIDuint16_t bcdDevice_Ver; //версия в BCD-форматеuint8_t iManufacturer; //номер строки названия производителяuint8_t iProduct; //номер строки названия продуктаuint8_t iSerialNumber; //номер строки версииuint8_t bNumConfigurations; //количество конфигураций (почти всегда равно 1)

В первую очередь обратите внимание на первые два поля размер дескриптора и его тип. Они характерны почти для всех дескрипторов USB (кроме HID, пожалуй). Причем если bDescriptorType это константа, то bLength приходится чуть ли не считать вручную для каждого дескриптора. В какой-то момент мне это надоело и был написан макрос
#define ARRLEN1(ign, x...) (1+sizeof((uint8_t[]){x})), x

Он считает размер переданных ему аргументов и подставляет вместо первого. Дело в том, что иногда дескрипторы бывают вложенными, так что один, скажем, требует размер в первом байте, другой в 3 и 4 (16-битное число), а третий в 6 и 7 (снова 16-битное число). Точные значения аргументов макросам безразличны, но хотя бы количество совпадать должно. Собственно, макросы для подстановки в 1, в 3 и 4, а также в 6 и 7 байты там тоже есть, но их применение я покажу на более характерном примере.
Пока же рассмотрим 16-битные поля вроде VID и PID. Понятное дело что в одном массиве смешать 8-битные и 16-битные константы не выйдет, да плюс endiannes в общем, на выручку снова приходят макросы: USB_U16( x ).

В плане выбора VID:PID вопрос сложный. Если планируется выпускать продукцию серийно, все же стоит купить персональную пару. Для личного же пользования можно подобрать чужую от похожего устройства. Скажем, у меня в примерах будут пары от AVR LUFA и STM. Все равно хост определяет по этой паре скорее специфичные баги реализации, чем назначение. Потому что назначение устройства подробно расписывается в специальном дескрипторе.

Внимание, грабли! Как оказалось, Windows привязывает к этой паре драйвера, то есть вы, например, собрали устройство HID, показали системе и установили драйвера. А потом перепрошили устройство под MSD (флешку), не меняя VID:PID, то драйвера останутся старые и, естественно, работать устройство не будет. Придется лезть в управление оборудованием, удалять драйвера и заставлять систему найти новые. Я думаю, ни для кого не станет неожиданностью, что в Linux такой проблемы нет: устройства просто подключаются и работают.

StringDescriptor


Еще одной интересной особенностью дескрипторов USB является любовь к строкам. В шаблоне дескриптора они обозначаются префиксом i, как например iSerialNumber или iPhone. Эти строки входят во многие дескрипторы и, честно говоря, я не знаю, зачем их так много. Тем более что при подключении устройства видны будут только iManufacturer, iProduct и iSerialNumber. Как бы то ни было, строки представляют собой те же дескрипторы (то есть поля bLength и bDescriptorType в наличии), но вместо дальнейшей структуры идет поток 16-битных символов, похожих на юникод. Смысл данного извращения мне опять непонятен, ведь подобные названия даются все равно обычно на английском, где и 8-битного ASCII хватило бы. Ну хорошо, хотите расширенный набор символов, так UTF-8 бы взяли. Странные люди Для удобного формирования строк удобно применять угадайте что правильно, макросы. Но на этот раз не моей разработки, а подсмотренные у EddyEm. Поскольку строки являются дескрипторами, то и запрашивать их хост будет как обычные дескрипторы, только в поле wValue подставит 0x0300 (STRING_DESCRIPTOR). А вместо младшего байта будет собственно индекс строки. Скажем, запрос 0x0300 это строка с индексом 0 (она зарезервирована под язык устройства и почти всегда равна u"\x0409"), а запрос 0x0302 строка с индексом 2.

Внимание, грабли! Сколь бы ни был велик соблазн засунуть в iSerialNumber просто строку, даже строку с честной версией вида u''1.2.3'' не делайте этого! Некоторые операционные системы считают, что там должны быть только шестнадцатеричные цифры, то есть '0'-'9', 'A'-'Z' и все. Даже точек нельзя. Наверное, они как-то считают от этого числа хэш чтобы идентифицировать при повторном подключении, не знаю. Но проблему такую заметил при тестировании на виртуальной машине с Windows 7, она считала устройство бракованным. Что интересно, Windows XP и 10 проблему не заметили.

ConfigurationDescriptor


С точки зрения хоста устройство представляет набор отдельных интерфейсов, каждый из которых предназначен для решения какой-то задачи. В дескрипторе интерфейса описывается его устройство и привязанные конечные точки. Да, конечные точки описываются не сами по себе, а только как часть интерфейса. Обычно интерфейсы со сложной архитектурой управляются SETUP запросами (то есть через ep0), в которых поле wIndex номеру интерфейса и соответствует. Максимум позволяется прикарманить конечную точку для прерываний. А от интерфейсов данных хосту нужны только описания конечных точек и обмен будет идти через них.

Интерфейсов в одном устройстве может быть много, причем очень разных. Поэтому чтобы не путаться где заканчивается один интерфейс и начинается другой, в дескрипторе указывается не только размер заголовка, но и отдельно (обычно 3-4 байтами) полный размер интерфейса. Таким образом интерфейс складывается подобно матрешке: внутри общего контейнера (который хранит размер заголовка, bDescriptorType и полный размер содержимого, включая заголовок) может находиться еще парочка контейнеров поменьше, но устроенных точно так же. А внутри еще и еще. Приведу пример дескриптора примитивного HID-устройства:
static const uint8_t USB_ConfigDescriptor[] = {  ARRLEN34(  ARRLEN1(    bLENGTH, // bLength: Configuration Descriptor size    USB_DESCR_CONFIG,    //bDescriptorType: Configuration    wTOTALLENGTH, //wTotalLength    1, // bNumInterfaces    1, // bConfigurationValue: Configuration value    0, // iConfiguration: Index of string descriptor describing the configuration    0x80, // bmAttributes: bus powered    0x32, // MaxPower 100 mA  )  ARRLEN1(    bLENGTH, //bLength    USB_DESCR_INTERFACE, //bDescriptorType    0, //bInterfaceNumber    0, // bAlternateSetting    0, // bNumEndpoints    HIDCLASS_HID, // bInterfaceClass:     HIDSUBCLASS_NONE, // bInterfaceSubClass:     HIDPROTOCOL_NONE, // bInterfaceProtocol:     0x00, // iInterface  )  ARRLEN1(    bLENGTH, //bLength    USB_DESCR_HID, //bDescriptorType    USB_U16(0x0101), //bcdHID    0, //bCountryCode    1, //bNumDescriptors    USB_DESCR_HID_REPORT, //bDescriptorType    USB_U16( sizeof(USB_HIDDescriptor) ), //wDescriptorLength  )  )};


Здесь уровень вложенности небольшой, плюс ни одной конечной точки не описано ну так я старался выбрать устройство попроще. Некоторое недоумение здесь могут вызвать константы bLENGTH и wTOTALLENGTH, равные восьми- и шестнадцатибитному нулям. Поскольку в данном случае для расчета размера используются макросы, было бы странно дублировать их работу и считать байты руками. Как и странно писать нули. А константы штука заметная, наглядности кода способствующая.

Как можно видеть, данный дескриптор состоит из заголовка USB_DESCR_CONFIG (хранящего полный размер содержимого включая себя!), интерфейса USB_DESCR_INTERFACE (описывающего подробности устройства) и USB_DESCR_HID, в общих чертах говорящего что же именно за HID мы изображаем. Причем именно что в общих чертах: конкретная структура HID описывается в специальном дескрипторе HID_REPORT_DESCRIPTOR, рассматривать который я здесь не буду, просто потому что слишком плохо его знаю. Так что ограничимся копипастом из какого-нибудь примера.

Вернемся к интерфейсам. Учитывая, что у них есть номера, логично предположить, что в одном устройстве интерфейсов может быть много. Причем они могут отвечать как за одну общую задачу (скажем, интерфейс управления USB-CDC и интерфейс данных), так и за принципиально несвязанные. Скажем, ничто не мешает нам (кроме отсутствия знаний пока) на одном контроллере реализовать два переходника USB-CDC плюс флешку плюс, скажем, клавиатуру. Очевидно, что интерфейс флешки знать не знает про COM-порт. Впрочем, тут есть свои подводные камни, которые, надеюсь, когда-нибудь рассмотрим. Еще стоит отметить, что один интерфейс может иметь несколько альтернативных конфигураций (bAlternateSetting), отличающихся, скажем, количеством конечных точек или частотой их опроса. Собственно, для того и сделано: если хост считает, что лучше пропускную способность поберечь, он может переключить интерфейс в какой-нибудь альтернативный режим, который ему больше понравился.

Обмен данными с HID


Вообще говоря, HID-устройства имитируют объекты реального мира, у которых есть не столько данные, сколько набор неких параметров, которые можно измерять или задавать (запросы SET_REPORT / GET_REPORT) и которые могут уведомлять хост о внезапном внешнем событии (INTERRUPT). Таким образом, собственно для обмена данными данные устройства не предназначены но кого это когда останавливало!

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

Начнем с более простого чтения по запросу HIDREQ_GET_REPORT. По сути это такой же запрос, как и всякие DEVICE_DESCRIPTOR, только специфичный именно для HID. Плюс этот запрос адресован не устройству в целом, а интерфейсу. То есть если мы реализовали в одном устройстве несколько независимых HID-устройств, их можно различить по полю wIndex запроса. Правда, именно для HID это не лучший подход: проще сам дескриптор сделать составным. В любом случае до таких извращений нам далеко, так что даже не будем анализировать что и куда хост пытался послать: на любой запрос к интерфейсу и с полем bRequest равным HIDREQ_GET_REPORT будем возвращать собственно данные. По идее, такой подход предназначен чтобы возвращать дескрипторы (со всеми bLength и bDescriptorType), но в случае HID разработчики решили все упростить и обмениваться только данными. Вот и возвращаем указатель на нашу структуру и ее размер. Ну и небольшая дополнительная логика вроде обработки кнопок и счетчика запросов.

Более сложный случай запрос на запись. Это первый раз, когда мы сталкиваемся с наличием дополнительных данных в SETUP запросе. То есть ядро нашей библиотеки должно сначала прочитать сам запрос, и только потом данные. И передать их юзерской функции. А буфера у нас, напоминаю, нет. В результате некоторой низкоуровневой магии был разработан следующий алгоритм. Callback вызывать будем всегда, но укажем ему с какого по счету байта данные сейчас лежат в буфере приема конечной точки (offset) а также размер этих данных (size). То есть при приеме самого запроса значения offset и size равны нулю (данных-то нет). При приеме первого пакета offset все еще равен нулю, а size размеру принятых данных. Для второго offset будет равен размеру ep0 (потому что если данные пришлось разбивать, делают это по размеру конечной точки), а size размеру принятых данных. И так далее. Важно! Если данные приняты, их надо считать. Это может сделать либо обработчик вызовом usb_ep_read() и возвратом 1 (мол я там сам считал, не утруждайся), либо просто вернув 0 (мне эти данные не нужны) без чтения тогда очисткой займется ядро библиотеки. По этому принципу и построена функция: она проверяет в наличии ли данные и если да, то читает их и зажигает светодиоды.

Софт для обмена данными


Тут я велосипед изобретать не стал, а взял готовую программу из предыдущей статьи.

Заключение


Вот, собственно, и все. Основы работы с USB при помощи аппаратного модуля в STM32 я рассказал, некоторый грабли тоже пощупал. Учитывая значительно меньший объем кода, чем тот ужас, что генерирует STMCube, разобраться в нем будет проще. Собственно говоря, в Cube'ической лапше я так и не разобрался, уж больно много там вызовов одного и того же в разных комбинациях. Гораздо лучше для понимания вариант от EddyEm, от которого я отталкивался. Конечно, и там не без косяков, но хотя бы пригодно для понимания. Также похвастаюсь, что размер моего варианта едва ли не в 5 раз меньше ST'шного (~2.7 кБ против 14) при том, что оптимизацией я не занимался и наверняка можно еще ужать.

Отдельно хочу отметить разницу поведения различных операционных систем при подключении сомнительного оборудования. Linux просто работает, даже если в дескрипторах ошибки. Windows XP, 7, 10 при малейших ошибках ругаются что устройство поломанное, я с ним работать отказываюсь. Причем XP иногда даже в BSOD падала от негодования. Ах да, еще они постоянно выводят устройство может работать быстрее, что с этим делать я не знаю. В общем, как бы хорош Linux не был для разработки, он прощает слишком многое, тестировать надо и на менее юзер-френдли системах.

Дальнейшие планы: рассмотреть остальные типы конечных точек (пока что был пример только с Control); рассмотреть другие контроллеры (скажем, у меня еще at90usb162 (AVR) и gd32vf103 (RISC_V) валяются), но это совсем далекие планы. Также хорошо бы поподробнее разобраться с отдельными USB-устройствами вроде тех же HID, но тоже не приоритетная задача.
Подробнее..

USB на регистрах interrupt endpoint на примере HID

10.04.2021 12:12:31 | Автор: admin

Еще более низкий уровень (avr-vusb)
USB на регистрах: STM32L1 / STM32F1
USB на регистрах: bulk endpoint на примере Mass Storage
USB на регистрах: isochronous endpoint на примере Audio device

Продолжаем разбираться с USB на контроллерах STM32L151. Как и в предыдущей части, ничего платформо-зависимого здесь не будет, зато будет USB-зависимое. Если точнее, будем рассматривать третий тип конечной точки interrupt. И делать мы это будем на примере составного устройства клавиатура + планшет (ссылка на исходники).
На всякий случай предупреждаю: данная статья (как и все остальные) скорее конспект того, что я понял, разбираясь в этой теме. Многие вещи так и остались магией и я буду благодарен если найдется специалист, способный объяснить их.

Первым делом напомню, что протокол HID (Human Interface Device) не предназначен для обмена большими массивами данных. Весь обмен строится на двух понятиях: событие и состояние. Событие это разовая посылка, возникающая в ответ на внешнее или внутреннее воздействие. Например, пользователь кнопочку нажал или мышь передвинул. Или на одной клавиатуре отключил NumLock, после чего хост вынужден и второй послать соответствующую команду, чтобы она это исправила, также послав сигнал нажатия NumLock и включила его обратно отобразила это на индикаторе. Для оповещения о событиях и используются interrupt точки. Состояние же это какая-то характеристика, которая не меняется просто так. Ну, скажем, температура. Или настройка уровня громкости. То есть что-то, посредством чего хост управляет поведением устройства. Необходимость в этом возникает редко, поэтому и взаимодействие самое примитивное через ep0.

Таким образом назначение у interrupt точки такое же как у прерывания в контроллере быстро сообщить о редком событии. Вот только USB штука хост-центричная, так что устройство не имеет права начинать передачу самостоятельно. Чтобы это обойти, разработчики USB придумали костыль: хост периодически посылает запросы на чтение всех interrupt точек. Периодичность запроса настраивается последним параметром в EndpointDescriptor'е (это часть ConfigurationDescriptor'а). В прошлых частях мы уже видели там поле bInterval, но его значение игнорировалось. Теперь ему наконец-то нашлось применение. Значение имеет размер 1 байт и задается в миллисекундах, так что опрашивать нас будут с интервалом от 1 мс до 2,55 секунд. Для низкоскоростных устройств минимальный интервал составляет 10 мс. Наличие костыля с опросом interrupt точек для нас означает, что даже в отсутствие обмена они будут впустую тратить полосу пропускания шины.

Логичный вывод: interrupt точки предназначены только для IN транзакций. В частности, они используются для передачи событий от клавиатуры или мыши, для оповещения об изменении служебных линий COM-порта, для синхронизации аудиопотока и тому подобных вещей. Но для всего этого придется добавлять другие типы точек. Поэтому, чтобы не усложнять пример, ограничимся реализацией HID-устройства. Вообще-то, такое устройство мы уже делали в первой части, но там дополнительные точки не использовались вовсе, да и структура HID-протокола рассмотрена не была.

ConfigurationDescriptor


static const uint8_t USB_ConfigDescriptor[] = {  ARRLEN34(  ARRLEN1(    bLENGTH, // bLength: Configuration Descriptor size    USB_DESCR_CONFIG,    //bDescriptorType: Configuration    wTOTALLENGTH, //wTotalLength    1, // bNumInterfaces    1, // bConfigurationValue: Configuration value    0, // iConfiguration: Index of string descriptor describing the configuration    0x80, // bmAttributes: bus powered    0x32, // MaxPower 100 mA  )  ARRLEN1(    bLENGTH, //bLength    USB_DESCR_INTERFACE, //bDescriptorType    0, //bInterfaceNumber    0, // bAlternateSetting    2, // bNumEndpoints    HIDCLASS_HID, // bInterfaceClass:     HIDSUBCLASS_BOOT, // bInterfaceSubClass:     HIDPROTOCOL_KEYBOARD, // bInterfaceProtocol:     0x00, // iInterface  )  ARRLEN1(    bLENGTH, //bLength    USB_DESCR_HID, //bDescriptorType    USB_U16(0x0110), //bcdHID    0, //bCountryCode    1, //bNumDescriptors    USB_DESCR_HID_REPORT, //bDescriptorType    USB_U16( sizeof(USB_HIDDescriptor) ), //wDescriptorLength  )  ARRLEN1(    bLENGTH, //bLength    USB_DESCR_ENDPOINT, //bDescriptorType    INTR_NUM, //bEdnpointAddress    USB_ENDP_INTR, //bmAttributes    USB_U16( INTR_SIZE ), //MaxPacketSize    10, //bInterval  )  ARRLEN1(    bLENGTH, //bLength    USB_DESCR_ENDPOINT, //bDescriptorType    INTR_NUM | 0x80, //bEdnpointAddress    USB_ENDP_INTR, //bmAttributes    USB_U16( INTR_SIZE ), //MaxPacketSize    10, //bInterval  )  )};


Внимательный читатель тут же может обратить внимание на описания конечных точек. Со второй все в порядке IN точка (раз произведено сложение с 0x80) типа interrupt, заданы размер и интервал. А вот первая вроде бы объявлена как OUT, но в то же время interrupt, что противоречит сказанному ранее. Да и здравому смыслу тоже: хост не нуждается в костылях чтобы передать в устройство что угодно и когда угодно. Но таким способом обходятся другие грабли: тип конечной точки в STM32 устанавливается не для одной точки, а только для пары IN/OUT, так что не получится задать 0x81-й точке тип interrupt, а 0x01-й control. Впрочем, для хоста это проблемой не является, он бы, наверное, и в bulk точку те же данные посылал что, впрочем, я проверять не стану.

HID descriptor


Структура HID descriptor'а больше всего похожа на конфигурационных файл имя=значение, но в отличие от него, имя представляет собой числовую константу из списка USB-специфичных, а значение либо тоже константу, либо переменную размером от 0 до 3 байт.
Важно: для некоторых имен длина значения задается в 2 младших битах поля имени. Например, возьмем LOGICAL_MINIMUM (минимальное значение, которое данная переменная может принимать в штатном режиме). Код этой константы равен 0x14. Соответственно, если значения нет (вроде бы такого не бывает, но утверждать не буду зачем-то же этот случай ввели), то в дескрипторе будет единственное число 0x14. Если значение равно 1 (один байт) то записано будет 0x15, 0x01. Для двухбайтного значения 0x1234 будет записано 0x16, 0x34, 0x12 значение записывается от младшего к старшему. Ну и до кучи число 0x123456 будет 0x17, 0x56, 0x34, 0x12.

Естественно, запоминать все эти числовые константы мне лень, поэтому воспользуемся макросами. К сожалению, я так и не нашел способа заставить их самостоятельно определять размер переданного значения и разворачиваться в 1, 2, 3 или 4 байта. Поэтому пришлось сделать костыль: макрос без суффикса отвечает за самые распространенные 8-битные значения, с суффиксом 16 за 16-битные, а с 24 за 24-битные. Также были написаны макросы для составных значений вроде диапазона LOGICAL_MINMAX24(min, max), которые разворачиваются в 4, 6 или 8 байтов.

Как и в конфигурационных файлах, здесь присутствуют секции, называемые страницами (usage_page), которые группируют устройства по назначению. Скажем, есть страница с базовой периферией вроде клавиатур, мышек и просто кнопок, есть джойстики и геймпады (искренне рекомендую посмотреть какие именно! Там и для танков, и для космических кораблей, и для подводных лодок и для всего чего угодно), даже дисплеи есть. Правда, где искать софт, умеющий со всем этим работать, я без понятия.

Внутри каждой страницы выбирается конкретное устройство. Например, для мышки это указатель и кнопки, а для планшета стилус или палец юзера (что?!). Ими же обозначаются составные части устройства. Так, частью указателя являются его координаты по X и Y. Некоторые характеристики можно сгруппировать в коллекцию, но для чего это делается я толком не понял. В документации к полям иногда ставится пометка из пары букв о назначении поля и способе работы с ним:

CA Collection(application) Служебная информация, никакой переменной не соответствующая
CL Collection(logical) -/-
CP Collection(phisical) -/-
DV Dynamic Value входное или выходное значение (переменная)
MC Momentary Control флаг состояния (1-флаг взведен, 0-сброшен)
OSC One Shot Control однократное событие. Обрабатывается только переход 0->1


Есть, разумеется, и другие, но в моем примере они не используются. Если, например, поле X помечено как DV, то оно считается переменной ненулевой длины и будет включено в структуру репорта. Поля MC или OSC также включаются в репорт, но имеют размер 1 бит.

Один репорт (пакет данных, посылаемый или принимаемый устройством) содержит значения всех описанных в нем переменных. Описание кнопки говорит о всего одном занимаемом бите, но для относительных координат (насколько передвинулась мышка, например) требуется как минимум байт, а для абсолютных (как для тачскрина) уже нужно минимум 2 байта. Плюс к этому, многие элементы управления имеют еще свои физические ограничения. Например, АЦП того же тачскрина может иметь разрешение всего 10 бит, то есть выдавать значения от 0 до 1023, которое хосту придется масштабировать к полному разрешению экрана. Поэтому в дескрипторе помимо предназначения каждого поля задается еще диапазон его допустимых значений (LOGICAL_MINMAX), плюс иногда диапазон физических значений (в миллиматрах там, или в градусах) и обязательно представление в репорте. Представление задается двумя числами: размер одной переменной (а битах) и их количество. Например, координаты касания тачскрина в создаваемом нами устройстве задаются так:
USAGE( USAGE_X ), // 0x09, 0x30,USAGE( USAGE_Y ), // 0x09, 0x31,LOGICAL_MINMAX16( 0, 10000 ), //0x16, 0x00, 0x00,   0x26, 0x10, 0x27,REPORT_FMT( 16, 2 ), // 0x75, 0x10, 0x95, 0x02,INPUT_HID( HID_VAR | HID_ABS | HID_DATA), // 0x91, 0x02,

Здесь видно, что объявлены две переменные, изменяющиеся в диапазоне от 0 до 10000 и занимающие в репорте два участка по 16 бит.

Последнее поле говорит, что вышеописанные переменные будут хостом читаться (IN) и поясняется как именно. Описывать его флаги подробно я не буду, остановлюсь только на нескольких. Флаг HID_ABS показывает, что значение абсолютное, то есть никакая предыстория на него не влияет. Альтернативное ему значение HID_REL показывает что значение является смещением относительно предыдущего. Флаг HID_VAR говорит, что каждое поле отвечает за свою переменную. Альтернативное значение HID_ARR говорит, что передаваться будут не состояния всех кнопок из списка, а только номера активных. Этот флаг применим только к однобитным полям. Вместо того, чтобы передавать 101/102 состояния всех кнопок клавиатуры можно ограничиться несколькими байтами со списком нажатых клавиш. Тогда первый параметр REPORT_FMT будет отвечать за размер номера, а второй за количество.

Поскольку размер всех переменных задается в битах, логично спросить: а что же с кнопками, ведь их количество может быть не кратно 8, а это приведет к трудностям выравнивания при чтении и записи. Можно было бы выделить каждой кнопке по байту, но тогда бы сильно вырос объем репорта, что для скоростных передач вроде interrupt, неприятно. Вместо этого кнопки стараются расположить поближе друг к другу, а оставшееся место заполняют битами с флагом HID_CONST.

Теперь мы можем если не написать дескриптор с нуля, то хотя бы попытаться его читать, то есть определить, каким битам соответствует то или иное поле. Достаточно посчитать INPUT_HID'ы и соответствующие им REPORT_FMT'ы. Только учтите, что именно такие макросы придумал я, больше их никто не использует. В чужих дескрипторах придется искать input, report_size, report_count, а то и вовсе числовые константы.

Вот теперь можно привести дескриптор целиком:
static const uint8_t USB_HIDDescriptor[] = {  //keyboard  USAGE_PAGE( USAGEPAGE_GENERIC ),//0x05, 0x01,  USAGE( USAGE_KEYBOARD ), // 0x09, 0x06,  COLLECTION( COLL_APPLICATION, // 0xA1, 0x01,    REPORT_ID( 1 ), // 0x85, 0x01,    USAGE_PAGE( USAGEPAGE_KEYBOARD ), // 0x05, 0x07,    USAGE_MINMAX(224, 231), //0x19, 0xE0, 0x29, 0xE7,        LOGICAL_MINMAX(0, 1), //0x15, 0x00, 0x25, 0x01,    REPORT_FMT(1, 8), //0x75, 0x01, 0x95, 0x08         INPUT_HID( HID_DATA | HID_VAR | HID_ABS ), // 0x81, 0x02,     //reserved    REPORT_FMT(8, 1), // 0x75, 0x08, 0x95, 0x01,    INPUT_HID(HID_CONST), // 0x81, 0x01,                  REPORT_FMT(1, 5),  // 0x75, 0x01, 0x95, 0x05,    USAGE_PAGE( USAGEPAGE_LEDS ), // 0x05, 0x08,    USAGE_MINMAX(1, 5), //0x19, 0x01, 0x29, 0x05,      OUTPUT_HID( HID_DATA | HID_VAR | HID_ABS ), // 0x91, 0x02,    //выравнивание до 1 байта    REPORT_FMT(3, 1), // 0x75, 0x03, 0x95, 0x01,    OUTPUT_HID( HID_CONST ), // 0x91, 0x01,    REPORT_FMT(8, 6),  // 0x75, 0x08, 0x95, 0x06,    LOGICAL_MINMAX(0, 101), // 0x15, 0x00, 0x25, 0x65,             USAGE_PAGE( USAGEPAGE_KEYBOARD ), // 0x05, 0x07,    USAGE_MINMAX(0, 101), // 0x19, 0x00, 0x29, 0x65,    INPUT_HID( HID_DATA | HID_ARR ), // 0x81, 0x00,             )  //touchscreen  USAGE_PAGE( USAGEPAGE_DIGITIZER ), // 0x05, 0x0D,  USAGE( USAGE_PEN ), // 0x09, 0x02,  COLLECTION( COLL_APPLICATION, // 0xA1, 0x0x01,    REPORT_ID( 2 ), //0x85, 0x02,    USAGE( USAGE_FINGER ), // 0x09, 0x22,    COLLECTION( COLL_PHISICAL, // 0xA1, 0x00,      USAGE( USAGE_TOUCH ), // 0x09, 0x42,      USAGE( USAGE_IN_RANGE ), // 0x09, 0x32,      LOGICAL_MINMAX( 0, 1), // 0x15, 0x00, 0x25, 0x01,      REPORT_FMT( 1, 2 ), // 0x75, 0x01, 0x95, 0x02,      INPUT_HID( HID_VAR | HID_DATA | HID_ABS ), // 0x91, 0x02,      REPORT_FMT( 1, 6 ), // 0x75, 0x01, 0x95, 0x06,      INPUT_HID( HID_CONST ), // 0x81, 0x01,                      USAGE_PAGE( USAGEPAGE_GENERIC ), //0x05, 0x01,      USAGE( USAGE_POINTER ), // 0x09, 0x01,      COLLECTION( COLL_PHISICAL, // 0xA1, 0x00,                 USAGE( USAGE_X ), // 0x09, 0x30,        USAGE( USAGE_Y ), // 0x09, 0x31,        LOGICAL_MINMAX16( 0, 10000 ), //0x16, 0x00, 0x00, 0x26, 0x10, 0x27,        REPORT_FMT( 16, 2 ), // 0x75, 0x10, 0x95, 0x02,        INPUT_HID( HID_VAR | HID_ABS | HID_DATA), // 0x91, 0x02,      )    )  )};

Помимо рассмотренных ранее полей тут есть и такое интересное поле, как REPORT_ID. Поскольку, как понятно из комментариев, устройство у нас составное, хосту надо как-то определить, чьи данные он принимает. Для этого данное поле и нужно.

И еще одно поле, на которое хотелось бы обратить внимание OUTPUT_HID. Как видно из названия, оно отвечает не за прием репорта (IN), а за передачу (OUT). Расположено оно в разделе клавиатуры и описывает индикаторы CapsLock, NumLock, ScrollLock а также два экзотических Compose (флаг ввода некоторых символов, для которых нет собственных кнопок вроде , или ) и Kana (ввод иероглифов). Собственно, ради этого поля мы и заводили OUT точку. В ее обработчике будем проверять не надо ли зажечь индикаторы CapsLock и NumLock: на плате как раз два диодика и разведено.

Существует и третье поле, связанное с обменом данными FEATURE_HID, мы его использовали в первом примере. Если INPUT и OUTPUT предназначены для передачи событий, то FEATURE состояния, которое можно как читать, так и писать. Правда, делается это не через выделенные endpoint'ы, а через обычную ep0 путем соответствующих запросов.

Если внимательно рассмотреть дескриптор, можно восстановить структуру репорта. Точнее, двух репортов:
struct{  uint8_t report_id; //1  union{    uint8_t modifiers;    struct{      uint8_t lctrl:1; //left control      uint8_t lshift:1;//left shift      uint8_t lalt:1;  //left alt      uint8_t lgui:1;  //left gui. Он же hyper, он же winkey      uint8_t rctrl:1; //right control      uint8_t rshift:1;//right shift      uint8_t ralt:1;  //right alt      uint8_t rgui:1;  //right gui    };  };  uint8_t reserved; //я не знаю зачем в официальной документации это поле  uint8_t keys[6]; //список номеров нажатых клавиш}__attribute__((packed)) report_kbd;struct{  uint8_t report_id; //2  union{    uint8_t buttons;    struct{      uint8_t touch:1;   //фактнажатия на тачскрин      uint8_t inrange:1; //нажатие в рабочей области      uint8_t reserved:6;//выравнивание до 1 байта    };  };  uint16_t x;  uint16_t y;}__attribute__((packed)) report_tablet;


Отправлять их будем по нажатию кнопок на плате, причем. поскольку пишем мы всего лишь пример реализации, а не законченное устройство, делать это будем по-варварски посылая два репорта, в первом из которых нажимая клавиши, а во втором отпуская. Причем с огромной тупой задержкой между посылками. Если не посылать репорт с отпущенными клавишами, система посчитает что клавиша осталась нажатой и будет ее повторять. Естественно, ни о какой эффективности тут не идет и речи, о безопасности тоже, но для теста сойдет. Ах да, куда ж без очередных граблей! Размер структуры должен совпадать с тем, что описано в дескрипторе, иначе винда сделает вид, что не понимает чего от нее хотят. Как обычно, линукс подобные ошибки игнорирует и работает как ни в чем не бывало.

В процессе тестирования наткнулся на забавный побочный эффект: в Windows7 при нажатии на тачскрин вылезает окошко рукописного ввода. Я об этой фиче не знал.

Если к вам попало готовое устройство


и хочется посмотреть на него изнутри. Первым делом, естественно, смотрим, можно даже от обычного пользователя, ConfigurationDescriptor:
lsusb -v -d <VID:PID>

Для HID-дескриптора же я не нашел (да и не искал) способа лучше, чем от рута:
cat /sys/kernel/debug/hid/<address>/rdes

Для полноты картины сюда стоило бы добавить как смотреть подобные вещи в других ОС. Но у меня соответствующих знаний нет, может в комментариях подскажут. Желательно, конечно, без установки стороннего софта.

Заключение


Вот, собственно, и все, что я нарыл по HID. План-минимум научиться читать готовые дескрипторы, эмулировать несколько устройств одновременно и реализовать планшетный ввод выполнен. Ну и философию interrupt точек рассмотрели заодно.

Как и в плошлый раз, немножко документации оставил в репозитории на случай если дизайнеры USB-IF снова решат испортить сайт.
Подробнее..

USB на регистрах isochronous endpoint на примере Audio device

23.05.2021 14:09:40 | Автор: admin
image<картинка с платой и наушниками>
Еще более низкий уровень (avr-vusb): habr.com/ru/post/460815
USB на регистрах: STM32L1 / STM32F1
USB на регистрах: bulk endpoint на примере Mass Storage
USB на регистрах: interrupt endpoint на примере HID

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

Как ни странно, этот тип конечной точки оказался самым мозговыносящим (и это после всего, что я успел повидать с stm'ками!). Тем не менее, сегодня мы сделаем аудиоустройство и заодно чуть-чуть допилим ядро библиотеки USB. Как обычно, исходные коды доступны:
github.com/COKPOWEHEU/usb/tree/main/4.Audio_L1
github.com/COKPOWEHEU/usb/tree/main/4.Audio_F1

Доработка ядра


Допиливать ядро нужно потому, что у STM изохронные точки могут быть только с двойной буферизацией, то есть, грубо говоря, нельзя сделать 0x01 изохронной, а 0x81 управляющей. То есть в дескрипторе USB это прописать, конечно, можно, но внутренности контроллера это не изменит, и реальный адрес точки просто будет отличаться от видимого снаружи. Что, естественно, повысит риск ошибок, поэтому в эту сторону извращаться не будем.

Стоит отметить, что двойная буферизация бывает не только у изохронных точек, но и у bulk, причем включается и работает она иначе. Если изохронные точки включают буферизацию автоматически, просто потому что не умеют по-другому, то для соответствующей настройки bulk приходится использовать специальный бит USB_EP_KIND, который нужно установить вместе с собственно настройкой типа точки.

Сама по себе буферизация означает, что если раньше одной точке соответствовал один буфер на передачу и один на прием, то теперь оба буфера будут работать либо на передачу, либо на прием, причем работать они будут только вместе. Как результат, настройка буферизованной точки сильно отличается от обычной, ведь нужно настроить не один буфер, а два. Поэтому не будем лепить лишние условия в обычную инициализацию, а создадим на ее основе отдельную функцию usb_ep_init_double().

Прием и передача пакетов отличается не так сильно, хотя и отняла гораздо больше времени сначала на попытки понять как же она должна работать по логике ST, потом на подгонку заклинания из интернета чтобы все-таки заработало. Как говорилось раньше, если для обычной точки два буфера независимы и отличаются направлением обмена, то для буферизованной они одинаковы и отличаются только смещением. Так что немножко изменим функции usb_ep_write и usb_ep_read чтобы они принимали не номер точки, а номер смещения. То есть если раньше эти функции предполагали существование восьми сдвоенных точек, то теперь 16 одинарных. Соответственно, номер новой полуточки на запись равен всего лишь номеру обычной, умноженному на два, а для usb_ep_read надо еще добавить единицу (см. распределение буферов в PMA). Собственно, это и делается инлайн-функциями usb_ep_write и usb_ep_read для обычных точек. А вот логику буферизованных рассмотрим чуть подробнее.

Согласно документации, один буфер такой точки доступен для железа, второй для софта. Потом они переключаются и снова не мешают друг другу. Для OUT точки флагом со стороны железа является бит USB_EP_DTOG_RX, который нужно прочитать чтобы понять в какой из буферов только что закончилась запись и откуда соответственно софт может читать. Когда он прочитал свой буфер, нужно дернуть бит USB_EP_DTOG_TX, что собственно переключит буферы. Не уверен, что подразумевалось именно это, но оно, по крайней мере, работает.

Симметричная ситуация должны была быть с IN точками. Но на практике оказалось, что и проверять, и дергать надо USB_EP_DTOG_RX. Почему не TX я так и не понял Спасибо пользователю kuzulis за ссылку на github.com/dmitrystu/libusb_stm32/edit/master/src/usbd_stm32f103_devfs.c

За счет инлайна функций особого оверхеда не добавилось, не считая инициализации. Но ее можно при желании выкинуть флагами линковщика. А можно и не выкидывать: не так много места занимает, да и вызывается только при инициализации. Это вам не HAL, где функции мало того что тяжелые, но и вызывают друг друга постоянно.

В результате конечные точки научились работать и в буферизованном режиме если не дышать на них слишком сильно.

Для пользователя разница невелика: вместо usb_ep_init использовать usb_ep_init_double, а вместо usb_ep_write и usb_ep_read соответственно usb_ep_write_double и usb_ep_read_double.

Устройство AudioDevice


А вот теперь, когда с техническими граблями разобрались, перейдем к самому интересному настройке аудиоустройства.

Согласно стандарту USB аудиоустройство представляет собой набор сущностей (entity), соединенных друг с другом в некую топологию, по которой и проходит аудиосигнал. Каждая сущность имеет свой уникальный номер (bTerminalID, он же UnitID), по которому к ней могут подключаться другие сущности или конечные точки, по нему же обращается хост, если хочет изменить какие-то параметры. И он же считается единственным выходом данной сущности. А вот входов может вообще не быть (если это входной терминал), а может быть и больше одного (bSourceID). Собственно записью в массив bSourceID номеров сущностей, от которых текущая получает аудиосигнал, мы и описываем всю топологию, которая в результате может получиться весьма резвесистой. Для примера приведу топологию покупной USB-звуковой карты (цифрами показаны bTerminalID / UnitID):

lsusb и его расшифровка
Bus 001 Device 014: ID 0d8c:013c C-Media Electronics, Inc. CM108 Audio Controller#Тут пока ничего интересногоDevice Descriptor:  bLength                18  bDescriptorType         1  bcdUSB               1.10  bDeviceClass            0   bDeviceSubClass         0   bDeviceProtocol         0   bMaxPacketSize0         8  idVendor           0x0d8c C-Media Electronics, Inc.  idProduct          0x013c CM108 Audio Controller  bcdDevice            1.00  iManufacturer           1   iProduct                2   iSerial                 0   bNumConfigurations      1  #интересное начинается тут  Configuration Descriptor:    bLength                 9    bDescriptorType         2    wTotalLength       0x00fd    bNumInterfaces          4  # общее количество интерфейсов    bConfigurationValue     1    iConfiguration          0     bmAttributes         0x80      (Bus Powered)    MaxPower              100mA    #интерфейс 0 - описание топологии    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        0      bAlternateSetting       0      bNumEndpoints           0      bInterfaceClass         1 Audio      bInterfaceSubClass      1 Control Device      bInterfaceProtocol      0       iInterface              0       AudioControl Interface Descriptor:        bLength                10        bDescriptorType        36        bDescriptorSubtype      1 (HEADER)        bcdADC               1.00        wTotalLength       0x0064        bInCollection           2  # ВАЖНО! количество интерфейсов данных (2)        baInterfaceNr(0)        1  #номер перовго из них        baInterfaceNr(1)        2  #номер второго ##### Топологоия ###### 1 InputTerminal (USB, на динамик)       AudioControl Interface Descriptor:        bLength                12        bDescriptorType        36        bDescriptorSubtype      2 (INPUT_TERMINAL)        bTerminalID             1  # Вот номер данной сущности        wTerminalType      0x0101 USB Streaming        bAssocTerminal          0        bNrChannels             2  # Здесь задается количество каналов        wChannelConfig     0x0003  # А здесь - их расположение в пространстве          Left Front (L)          Right Front (R)        iChannelNames           0         iTerminal               0         # 2 InputTerminal (микрофон)      AudioControl Interface Descriptor:        bLength                12        bDescriptorType        36        bDescriptorSubtype      2 (INPUT_TERMINAL)        bTerminalID             2        wTerminalType      0x0201 Microphone        bAssocTerminal          0        bNrChannels             1        wChannelConfig     0x0001          Left Front (L)        iChannelNames           0         iTerminal               0         # 6 OutputTerminal (динамик), вход соединен с сущностью 9      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      3 (OUTPUT_TERMINAL)        bTerminalID             6        wTerminalType      0x0301 Speaker        bAssocTerminal          0        bSourceID               9  # Номера входов указываются здесь        iTerminal               0         # 7 OutputTerminal (USB), вход соединен с сущностью 8      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      3 (OUTPUT_TERMINAL)        bTerminalID             7        wTerminalType      0x0101 USB Streaming        bAssocTerminal          0        bSourceID               8        iTerminal               0         # 8 Selector, входы соединены только с сущностью 10      AudioControl Interface Descriptor:        bLength                 7        bDescriptorType        36        bDescriptorSubtype      5 (SELECTOR_UNIT)        bUnitID                 8        bNrInPins               1  # У сущностей с несколькими входами указывается их количество        baSourceID(0)          10  # а потом номера        iSelector               0         # 9 Feature, вход соединен с сущностью 15      AudioControl Interface Descriptor:        bLength                10        bDescriptorType        36        bDescriptorSubtype      6 (FEATURE_UNIT)        bUnitID                 9        bSourceID              15        bControlSize            1        bmaControls(0)       0x01          Mute Control        bmaControls(1)       0x02          Volume Control        bmaControls(2)       0x02          Volume Control        iFeature                0         # 10 Feature, вход соединен с сущностью 2      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      6 (FEATURE_UNIT)        bUnitID                10        bSourceID               2        bControlSize            1        bmaControls(0)       0x43          Mute Control          Volume Control          Automatic Gain Control        bmaControls(1)       0x00        iFeature                0         # 13 Feature, вход соединен с сущностью 2      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      6 (FEATURE_UNIT)        bUnitID                13        bSourceID               2        bControlSize            1        bmaControls(0)       0x03          Mute Control          Volume Control        bmaControls(1)       0x00        iFeature                0         # 15 Mixer, входы соединены с сущностями 1 и 13      AudioControl Interface Descriptor:        bLength                13        bDescriptorType        36        bDescriptorSubtype      4 (MIXER_UNIT)        bUnitID                15        bNrInPins               2  # Снова массив входов        baSourceID(0)           1  # и их номера        baSourceID(1)          13        bNrChannels             2        wChannelConfig     0x0003          Left Front (L)          Right Front (R)        iChannelNames           0         bmControls(0)        0x00        iMixer                  0 ##### конец топологии ###### Интерфейс 1 (основной) - заглушка без конечных точек    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        1      bAlternateSetting       0      bNumEndpoints           0      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       # Интерфейс 1 (альтернативный) - рабочий с одной конечной точкой    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        1      bAlternateSetting       1      bNumEndpoints           1      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       AudioStreaming Interface Descriptor:        bLength                 7        bDescriptorType        36        bDescriptorSubtype      1 (AS_GENERAL)        bTerminalLink           1        bDelay                  1 frames        wFormatTag         0x0001 PCM      AudioStreaming Interface Descriptor:        bLength                14        bDescriptorType        36        bDescriptorSubtype      2 (FORMAT_TYPE)        bFormatType             1 (FORMAT_TYPE_I)        bNrChannels             2        bSubframeSize           2        bBitResolution         16        bSamFreqType            2 Discrete        tSamFreq[ 0]        48000        tSamFreq[ 1]        44100      Endpoint Descriptor:        bLength                 9        bDescriptorType         5        bEndpointAddress     0x01  EP 1 OUT        bmAttributes            9          Transfer Type            Isochronous          Synch Type               Adaptive          Usage Type               Data        wMaxPacketSize     0x00c8  1x 200 bytes        bInterval               1        bRefresh                0        bSynchAddress           0        AudioStreaming Endpoint Descriptor:          bLength                 7          bDescriptorType        37          bDescriptorSubtype      1 (EP_GENERAL)          bmAttributes         0x01            Sampling Frequency          bLockDelayUnits         1 Milliseconds          wLockDelay         0x0001          # Интерфейс 2 (основной) - заглушка    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        2      bAlternateSetting       0      bNumEndpoints           0      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       # Интерфейс 2 (альтернативный)    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        2      bAlternateSetting       1      bNumEndpoints           1      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       AudioStreaming Interface Descriptor:        bLength                 7        bDescriptorType        36        bDescriptorSubtype      1 (AS_GENERAL)        bTerminalLink           7        bDelay                  1 frames        wFormatTag         0x0001 PCM      AudioStreaming Interface Descriptor:        bLength                14        bDescriptorType        36        bDescriptorSubtype      2 (FORMAT_TYPE)        bFormatType             1 (FORMAT_TYPE_I)        bNrChannels             1        bSubframeSize           2        bBitResolution         16        bSamFreqType            2 Discrete        tSamFreq[ 0]        48000        tSamFreq[ 1]        44100      Endpoint Descriptor:        bLength                 9        bDescriptorType         5        bEndpointAddress     0x82  EP 2 IN        bmAttributes            9          Transfer Type            Isochronous          Synch Type               Adaptive          Usage Type               Data        wMaxPacketSize     0x0064  1x 100 bytes        bInterval               1        bRefresh                0        bSynchAddress           0        AudioStreaming Endpoint Descriptor:          bLength                 7          bDescriptorType        37          bDescriptorSubtype      1 (EP_GENERAL)          bmAttributes         0x01            Sampling Frequency          bLockDelayUnits         0 Undefined          wLockDelay         0x0000##### Конец описания аудиоинтерфейсов ###### Интерфейс 3 "Клавиши громкости и всего остального" (не интересно)    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        3      bAlternateSetting       0      bNumEndpoints           1      bInterfaceClass         3 Human Interface Device      bInterfaceSubClass      0       bInterfaceProtocol      0       iInterface              0         HID Device Descriptor:          bLength                 9          bDescriptorType        33          bcdHID               1.00          bCountryCode            0 Not supported          bNumDescriptors         1          bDescriptorType        34 Report          wDescriptorLength      60         Report Descriptors:            ** UNAVAILABLE **      Endpoint Descriptor:        bLength                 7        bDescriptorType         5        bEndpointAddress     0x87  EP 7 IN        bmAttributes            3          Transfer Type            Interrupt          Synch Type               None          Usage Type               Data        wMaxPacketSize     0x0004  1x 4 bytes        bInterval               2



image

Мы же будем делать нечто более простое (заготовку брал отсюда):

image

Здесь видно две независимых ветки распространения сигнала: либо от USB через фичу к динамику, либо из микрофона через другую фичу к USB. Микрофон и динамик не просто так взяты в кавычки: на моей отладочной плате их нет, поэтому вместо собственно звука будем пользоваться кнопками и светодиодами. Впрочем, ничего нового. Фичи в моем случае ничего не делают и добавлены скорее для красоты.

Сразу же стоит уточнить, что сигнал в данной модели считается составленным из одного или более логических каналов. То есть если, например, я поменяю монофонический динамик на стереофонический, сама топология останется неизменной, изменится только формат сигнала.

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

1. Входной терминал (Input Terminal)
Как следует из названия, именно через него в аудиоустройство попадает звуковой сигнал. Это может быть USB, может быть микрофон обыкновенный, микрофон гарнитурный, даже микрофонный массив.

2. Выходной терминал (Output Terminal)
Тоже вполне очевидно то, через что звук покидает наше устройство. Это может быть все тот же USB, может быть динамик, гарнитура, динамик в мониторе, динамики различных частот и куча других устройств.

3. Микшер (Mixer Unit)
Берет несколько входных сигналов, усиливает каждый на заданную величину и складывает то, что получилось, в выходной канал. При желании можно задать усиление в ноль раз, что сведет его к следующей сущности.

4. Селектор (Selector Unit)
Берет несколько входных сигналов и перенаправляет один из них на выход.

5. Фильтр (Feature Unit)
Берет единственный входной сигнал, меняет параметры звука (громкость, тембр и т.п.) и выдает на выход. Естественно, все эти параметры одинаковым способом прикладываются ко всему сигналу, без взаимодействия логических каналов внутри него

6. Processing Unit
А вот эта штука уже позволяет проводить манипуляции над отдельными логическими каналами внутри каждого входного. Более того, позволяет сделать количество логических каналов в выходном не равным количеству во входных.

7. Extension Unit
Весь набор нестандартных сущностей, чтобы больной фантазии производителей оборудования было раздолье. Соответственно, и поведение, и настройки будут зависеть от этой самой фантазии.

Некоторые сущности обладают параметрами вроде коэффициента усиления или номера канала, на которые хост может повлиять при помощи setFeature / getFeature запросов по номеру сущности. Но тут, если честно, я не слишком понимаю как это вообще проверить. Наверное, нужен какой-то спецсофт, которого у меня нет. Ну да и ладно, все равно я в это полез только чтобы все типы точек проверить на свою голову

Грабли в дескрипторе


В отличие от предыдущих USB-устройств, здесь дескриптор сложный, многоуровневый и склонный пугать виндоусы до BSOD'а. Как мы видели выше, топология у аутиоустройства может быть весьма сложной и развесистой. Под ее описание выделяется целый интерфейс. Очевидно, endpoint'ов он содержать не будет, зато будет содержать список дескрипторов сущностей и описаний к чему подключены их входы. Тут особо описывать смысла не вижу, проще посмотреть в коде и документации. Отмечу только главные грабли: здесь описывается какие интерфейсы с соответствующими конечными точками относятся именно к данному устройству. Скажем, если вы захотите изменить мою конфигурацию и убрать оттуда динамик, придется не просто удалить половину сущностей (слава макросам, хотя бы с подсчетом длины дескриптора проблемы не будет), но и уменьшить поле bInCollection до 1, после чего из следующего за ним массива bInterfaceNr убрать номер лишнего интерфейса.

Дальше находятся интерфейсы, отвечающие за обмен данными. В моем случае 1-й интерфейс отвечает за микрофон, а 2-й за динамик. Здесь стоит обратить внимание в первую очередь на два варианта каждого из этих интерфейсов. Один с bAlternateSetting равным 0, второй с 1. Они отличаются наличием конечной точки. То есть если наше устройство в данный момент не используется, хост просто переключается на тот альтернативный интерфейс, который конечной точкой не оборудован, и уже не тратит на нее пропускную способность шины.

Вторая особенность интерфейсов данных это формат аудиосигнала. В соответствующем дескрипторе задается тип кодирования, количество каналов, разрешение и частота дискретизации (которая задается 24-битным числом). Вариантов кодирования предусмотрено довольно много, но мы будем использовать самый простой PCM. По сути это просто последовательность значений мгновенной величины сигнала без какого-либо кодирования, причем величина считается целым числом со знаком. Разрешение сигнала задается в двух местах (зачем непонятно): в поле bSubFrameSize указывается количество байтов, а в bBitResolution количество битов. Вероятно, можно указать, что диапазон нашей звуковой карты не доходит до полного диапазона типа данных, скажем, int16_t и составляет всего 10 бит.

Ну и наконец дескриптор собственно конечной точки. Он тоже немного отличается от обычных, поскольку предусматривает во-первых несколько вариантов синхронизации, а во-вторых, номер сущности, с которой эта точка ассоциирована (bTerminalLink). Варианты синхронизации прописываются старшими битами прямо в тип конечной точки (именно поэтому изохронная точка вынесена в ветку default в функции инициализации), но их подробностями я не занимался, поэтому ничего интересного рассказать не смогу. Вместо синхронизации мы воспользуемся обычным таймером контроллера, который будет генерировать прерывания с примерно нужной частотой.

Ах да, чуть не забыл упомянуть очередную пачку BSOD'ов при тестировании неправильных дескрипторов. Еще раз напоминаю: количество интерфейсов данных должно соответствовать числу bInCollection, а их номера следующему за ним массиву!
Скрытый текст
Как представлю отладку подобного кода под виндами, с этими постоянными вылетами, да еще без нормальной консоли. бр-р-р.


Логика работы устройства


Как я уже говорил, для тестов не имеет смысла городить на отладочную плату навесные компоненты, поэтому все тестирование будет осуществляться тем, что уже установлено кнопки да светодиоды. Впрочем, в данном случае это проблемы не составляет: микрофон может просто генерировать синусоиду частотой, скажем, 1 кГц, а динамик включать светодиод при превышении порогового значения звука (скажем, выше числа 10000: при указанных 16 битах разрешения, что соответствует диапазону -32768 +32767, это примерно треть).

А вот с тестированием возникла небольшая проблема: я не нашел простого способа перенаправить сигнал с микрофона на stdin какой-нибудь программы. Вроде бы раньше это делалось просто чтением /dev/dsp, но сейчас что-то поломалось. Впрочем, ничего критичного, ведь есть всякие библиотеки взаимодействия с мультимедией SDL, SFLM и другие. Собственно на SFML я и написал простенькую утилиту для чтения с микрофона и, если надо, визуализации сигнала.

Особое внимание уделю ограничениям нашего аудиоустройства: насколько я понял, изохронный запрос IN отправляется один раз в миллисекунду (а вот OUT'ов может быть много), что ограничивает частоту дискретизации. Допустим, размер конечной точки у нас 64 байта (учитывая буферизацию, в памяти она занимает 128 байт, но хост об этом не знает), разрешение 16 бит, то есть за раз можно отправить 32 отсчета. Учитывая интервал в 1 мс получаем теоретический предел 32 кГц для одного канала. Самый простой способ это обойти увеличить размер конечной точки. Но тут надо помнить, что размер общего буфера PMA у нас всего 512 байт. Минус таблица распределения точек, минус ep0, получаем максимум 440 байт, то есть 220 байт на единственную точку с учетом буферизации. И это теоретический предел.

Но то, что хост может за один фрейм послать несколько OUT запросов наводит на мысли, что и устройство тоже так может. Осталось понять как. Возможно, это решается грамотной настройкой синхронизации. Но для меня этот вопрос интереса уже не представляет: изохронные точки работают, буферизованные точки работают, аудиоустройство работает задача выполнена.

Заключение (общее для цикла)


Ну вот мы и познакомились с устройством USB в контроллерах STM32F103 и STM32L151 (и других с аналогичной реализацией), поудивлялись логике некоторых архитектурных решений (особенно меня впечатлил регистр USB_EPnR, впрочем двойная буферизация тоже не отстает), рассмотрели все типы конечных точек и проверили их, построив соответствующие устройства. Так что можно сказать, что данный цикл статей подошел к логическому заключению. Хотя это, конечно, не значит, что я заброшу контроллеры или USB: в отдаленных планах еще разобраться с составными устройствами (пока что выглядит несложно, но ведь и изохронные точки тоже проблем не предвещали) и USB на контроллерах других семейств.
Подробнее..

Наступает смена поколений, встречаем 2021 год с USB Type-C

10.12.2020 10:23:48 | Автор: admin


Эй фотограф, возьми с собой мобильный бэкап! Популяризация USB Type-C привела к появлению USB устройств с данным разъемом, теперь помимо облачных решений можно переносить фотографии, видео, полезные для работы файлы через удобную флешку Kingston DataTraveler 80.

Несмотря на засилье Type-A разъемов в стационарных компьютерах, мобильные устройства буквально за несколько лет быстро мигрировали на Type-C. Постепенно производители материнских плат и корпусов оснащают им ПК, но все это происходит медленнее, чем хотелось бы. С другой стороны, смартфоны (особенно Android) и планшеты почти повально используют удобный и универсальный Type-C разъем. Kingston DataTraveler 80 это мостик между вашим ПК, ноутбуком, смартфоном и планшетом. С помощью этого флеш-накопителя можно быстро переместить файлы, или смотреть библиотеки с фильмами или фотографиями прямо на экране ноутбука.

USB-накопители, какие цели на пороге 2021 года?


Казалось бы, на носу 21 год, а компактные флешки до сих пор продаются и активно используются. Перечень сценариев их применения великое множество: от банального переноса данных, до резервных копий. Некоторым пользователям нравится перемещать на флешке дистрибутивы для развёртывания различных операционных систем, либо носить с собой мобильную ОС. USB Flash поможет при обновлении BIOS материнских плат, для хранения ЭЦП и т.п. К сожалению, далеко не все телефоны имеют на сегодня опцию расширения память за счет microSD. А так как USB Type-C все же имеет больше шансов на повсеместное распространение в качестве основного разъема для мобильных устройств, то и отношение к нему соответствующее. Стоит напомнить, что объемы информации не стоят на месте и растут день ото дня. 4к и 8к камеры этому способствуют, а скорость сети оставляет желать лучшего. Отчасти простую передачу файлов и фото/видео можно было бы сделать через Bluetooth соединение, для этого многие производители вводят свои способы и стандарты, но скорость передачи явно недостаточная. Облачные хранилища удобны, но не лишены недостатка вы напрямую зависите от скорости интернета оператора сети. Да и в роуминге 3-5G обойдутся вам в копеечку.

Маленькое счастье большим объемом




USB-накопитель DataTraveler 80 существует в четырех размерах:
o 32ГБ DT80/32GB;
o 64ГБ DT80/64GB;
o 128ГБ DT80/128GB;
o 256ГБ DT80/256GB;

Для любого объема заявленная скорость чтения не опускается ниже 200МБ/с, хотя производитель скромно указывает ее с приставкой До. Поддерживаемый разъем USB 3.2 Gen 1. Напомним, что с 2019 года организация USB-IF объявила о ребрендинге версий USB 3.0 и USB 3.1. USB 3.1 Gen 1 согласно современному тренду называется USB 3.2 Gen 1 и поддерживает скорости до 5 Гбит/с (640 Мбайт/сек). Версии флеш-накопителя DataTraveler 80 на 128 и 256 ГБ записывают данные со скоростью до 60МБ/с. Любая из них обеспечена 5-летней гарантией и бесплатной технической поддержкой.

Kingston DataTraveler 80 это высокопроизводительный USB-накопитель для ноутбуков, ПК, смартфонов и планшетов с портами Type-C. Благодаря высокой скорости работы (до 200МБ/с при чтении и 60 МБ/с при записи) DataTraveler 80 обеспечивает быструю и удобную передачу информации. Компактная конструкция с колпачком отличается прочным корпусом и снабжена брелоком. Устройство удобно всегда иметь при себе.





Полезный объем тестируемой флешки 256ГБ. В пересчете на exFAT получилось 231 ГБ. Для переноса устройства Kingston специально предусмотрела отверстие под веревку или небольшой карабин.

Условия тестирования




Так как Флешка в первую очередь предназначена для мобильных устройств, то процесс измерения производительности будет проведен на ПК с интерфейсом USB 3.2 Gen 2 и скоростью 10 Гбит/с (1280 Мбайт/сек), ноутбуке с комбинированным USB, поддерживающим как вывод на монитор, так и зарядку PD (сейчас такой тип USB Type-C очень часто встречается у производителей), и на телефоне с поддержкой On the Go USB Type-C.

Результаты тестирования


CrystalDiskMark



Тест последовательного 100% чтения / 100% записи / 70% времени чтения и 30% времени записи блоками по 1 Мбайт в 1 поток глубиной очереди 8;
Тест последовательного 100% чтения / 100% записи / 70% времени чтения и 30% времени записи блоками по 1 Мбайт в 1 поток глубиной очереди 1;
Тест случайного 100% чтения / 100% записи / 70% времени чтения и 30% времени записи блоками 4 Кбайт в 1 поток глубиной очереди 32;
Тест случайного 100% чтения / 100% записи / 70% времени чтения и 30% времени записи блоками 4 Кбайт в 1 поток глубиной очереди 1;

Copy Benchmark

Оценивает скорость работы и затраченного на это времени при копировании разных групп файлов (ISO образ, папка с программами, папка с играми).



AIDA64



Скорость линейного чтения, фактически бесполезный тест для USB флешек, т.к. они однозадачные, но мы можем посмотреть на среднюю скорость чтения по всему объему NAND памяти.



Аналогичный тест, только на чтение. Блоки можно выбирать было любые, чем он больше, тем менее точными будут показания, т.к. аппаратно флешка будет принимать 512 битными блоками. Нам же интересен средний результат, который получился на уровне 46 Мбайт/сек.

Натуральный тест
И последний, натуральный тест на копирование образа Windows.iso плюс папка с распакованным образом Windows 10.


Скорость копирования на DataTraveler 80 составила 46,8 МБ/сек.


Скорость копирования с DataTraveler 80 составила 236,6 МБ/сек.

Перемещаем флешку в ноутбук и проверяем скорости в универсальном Type-C разъеме, совмещающий в себе видеовыход, зарядку и передачу данных.



Первый тест и неожиданно низкие результаты. При подробном изучении вопроса предположительным виновником стал USB-HUB, встроенный в ноутбук. Не получается у производителя поддерживать все мультимедийные возможности в 1 порту и не снижать скорости. Увы, такой нюанс сейчас проявляется сразу у нескольких производителей ноутбуков, и пока решения нет. Возможно, будут выпущены обновления прошивки. А пока нужно запомнить, что проблема со скоростями USB далеко не всегда связана непосредственно с флешкой.

Очередное подтверждения проблемы некоторых USB Type-C портов, реальная производительность на чтении



И запись на носитель



Скорости действительно слишком низкие, всего 12,8 МБ/сек записи и 37,5 МБ/сек на чтении. Причем этот же накопитель через переходник в нормальном USB показывает заявленные производителем скорости.

Переходим на тест с участием смартфона, где предположительно флешка и будет работать. Но надо сделать обязательное отступление. Дело в том, что современные методы кеширования на смартфонах, начиная с Android 9 позволяют использовать свободную оперативную память телефона под прямой кеш. В итоге вставляя USB накопитель, операционная система быстро кеширует область, сопоставимую со свободной ОЗУ. Во время записи процесс записи принимает вид пилы: сначала Android перемещает файл в кеш, потом быстро его переносит на флешку.





На графике записи отчетливо виден процесс кеширования операционной системой Android. Итоговые значения чтения и записи установились на числах:


49-51 МБ/сек и 215-225 МБ/сек.

Сравнение с облачным хранилищем


Нам еще повезло, что в средней части России доступен весьма быстрый Интернет. В Москве, в частности, в СВАО один из операторов сотовой связи выдает до 200 Мбит/сек на скачивание и всего 15-16 Мбит/сек на закачку. Как видите, приоритет отдается потреблению трафика, а никак не закачке. В итоге загрузка пачки фотографий и нескольких видео объемом несколько сотен мегабайт (460 МБ) занимает как минимум 6-7 минут (1,2-1,3 МБ/сек) на Российское облако. При перемещении архива в облако иностранного агента время автоматически вырастает до 10-12 минут ввиду хостинга на зарубежных серверах. Скачивание данных на другой телефон естественно происходит быстрее, раза в два. Аналогичная операция с переносом файлов через флешку потребовала не более 10 секунд, и на флешке остается запасная копия. Можно, конечно, в поездке использовать Wi-Fi отеля, однако, как показывает практика, лучше вовсе без интернета, чем с Wi-Fi из отеля.

Со смартфона на смартфон

Всеми забытая функция передачи файлов с одного телефона на другой или на планшет все еще актуальна. Особенно если вокруг вас нет доступной Wi-Fi сети или ноутбука/компьютера. В давние времена был популярен Bluetooth протокол, но он никак не вписался в современные реалии из-за низкой скорости. Ему уготована участь передачи музыки или голоса в носимых устройствах, а в смартфонах развили передачу данных через Wi-Fi. Естественно все решается благодаря созданию Hot Spot или пробросом данных через Wi-Fi сеть. В последнем случае вы рискуете, передавая данные через неизвестную общедоступную сеть. Проще и безопасней использовать приложение, создающее точку доступа для другого телефона. Сейчас в магазине приложений есть масса таких программ, и выбор всегда остается за потребителем. Причем есть как платные, так и бесплатные версии и даже без назойливой рекламы.



Для теста мы использовали два современных смартфона. Один обладает поддержкой стандарта Wi-Fi 6, второй ограничился AC протоколом. Сначала выберем типичный набор файлов в виде фотографий и пары видеозаписей и перешлем их через Hot Spot на другое устройство. В качестве точки доступа будет использован протокол AC, а данные поместим на флешку. Пиковая скорость в 11 МБ/сек, не высока и процесс передачи точно не тормозится физическими возможностями Kingston DataTraveler 80. В обратную сторону благодаря поддержке Wi-Fi 6 скорость увеличилась почти в два раза и все еще Kingston DataTraveler 80 не является узким местом системы. Таким образом, мы продемонстрировали хоть и частную, но вполне жизненную ситуацию, когда необходимо передать файлы с одного устройства на другое использую внешний носитель Kingston DataTraveler 80.

Выводы




Даже в 2021 году флеш-накопители будут востребованы в различных сферах применения. От банального копирования и переноса файлов с ПК на ПК, до хранения нужно информации в поездках. Постепенное внедрение удобного разъема USB Type-C превращает USB Flash Drive в универсальное автономное устройство, не зависящее от скорости интернета, роуминга, погоды и настроения. Не нужно синхронизировать и передавать пароли, пытаться сопрячь два телефона с различными операционными системами, достаточно иметь всего один USB Type-C порт на каждое устройство. Гарантированные скорости чтения более 200 МБ/сек и записи выше 50 МБ/сек не делают из Kingston DataTraveler 80 абсолютного чемпиона, но умеренная стоимость и поддержка On the Go точно придутся по вкусу многим.

Отдельно стоить помнить о совместимости Type-C портов в современной технике, что далеко не все порты реально поддерживают настоящий USB 3.2 Gen 2 стандарт. Поэтому перед покупкой стоит внимательно изучить технические характеристики ноутбука или планшета.

Для получения дополнительной информации о продуктах Kingston Technology обращайтесь на официальный сайт компании.
Подробнее..

Издеваемся над USB

08.08.2020 22:12:45 | Автор: admin

В очередной раз втыкая скоростную USB флешку в порт USB 3.0, я увидел надпись "Это устройство может работать быстрее...". Но подождите, я и так его воткнул в порт 3.0! Неужели контакт барахлит? И если так, то как флешка определяет, на какой скорости ей работать? Ведь современные ПК поддерживают целых три стандарта соединения USB 1.1, 2.0 и 3.0. Можно ли понизить стандарт USB, насильно заставив устройство работать, к примеру, на USB 1.1? Не на все эти вопросы в сети удалось найти ответ, и я решил разобраться сам, по ходу столкнувшись с довольно неочевидными ситуациями.

Понижаем USB 3.0 до 2.0

Вы сейчас скажете да что может быть проще, просто возьми USB 2.0 кабель и будете совершенно правы. Если в кабеле или разъёме нет USB 3.0 контактов, у устройства не будет иного выхода, кроме как завестись на скорости USB 2.0:

Но отключение каких конкретно проводков приведёт к переключению на USB 2.0? Что будет, если отключить только один, или замкнуть соседние? Интересно же выйти за рамки стандарта и поэкспериментировать!
Для экспериментов я спаял USB-маму и USB-папу проводками на макетной плате:

На фотографии не просто так один проводок находится в воздухе. Выяснилось, что соединение прекрасно работает даже без одного из проводников SS_TX дифф. пары! (для пары SS_RX такой фокус уже не работает)

Более того, если отключить все USB 3.0 контакты, кроме SS_TX, девайс продолжает считать, что он подключен к USB 3.0 и вообще никак не обнаруживается в системе. Честно говоря, я был уверен, что соединение в этом случае переключится на 2.0 режим:
здесь отключены SS_RX-, SS_RX+ и SS_TX+

Итого делаем вывод, что USB 3.0 устройство проверяет наличие SuperSpeed соединения по линии SS_TX, причём трансивер настолько устойчив к ошибкам, что ему плевать на обрыв одной из линий пары. Для гарантированного переключения устройства на USB 2.0 нужно рвать обе линии: SS_TX- и SS_TX+.

Понижаем USB до 1.1

USB 2.0 всем хорош, да больно уж шустрый. Если вы когда-нибудь пытались заснифать его логическим анализатором, у вас либо очень крутой анализатор, либо вы нашли древний USB-хаб вроде такого:

Несмотря на то, что интернет пестрит вопросами как понизить USB 2.0 до 1.1, простого решения я нигде не увидел:


Давайте глянем внимательнее! По стандарту USB, скорость работы согласуется на сигнальном уровне. Устройство поднимает вольтаж D- до 0.8в, а хост отвечает пилообразным сигналом:

То же самое видим на нашем экспериментальном стенде на осциллографе:

То есть, нужно сделать так, чтобы хост не увидел этого повышения напряжения. А значит ставим диод в разрыв линии D- (Шоттки, чтобы минимизировать падение):

Ииии он успешно подавляет сигнал от устройства, не мешая обычной передаче данных:

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

Собираем даунгрейдер

Как обобщение вышеописанных экспериментов, я сделал простенький пассивный переключатель USB режимов 1.1/2.0/3.0

Моё стремление к простоте порой невозможно сдерживать. Захотелось всё реализовать на единственном трёхпозиционном переключателе, вот таком:



Первоначальная идея была один ряд контактов переключает D- между:
  • диод (USB 1.1)
  • пусто (USB 3.0)
  • D- (USB 2.0)

А другой ряд контактов соединяет SS_TX- только в режиме USB 3.0:
  • пусто (USB 1.1)
  • SS_TX- (USB 3.0)
  • пусто (USB 2.0)
Но эту идею я отбросил из-за сомнений вряд ли все USB 3.0 устройства смогут работать только на одной линии дифф. пары. Поэтому я переделал выключатель кусачками:

Теперь средний контакт переключается между крайними, а в среднем положении выключатель замыкает две пары независимых контактов. Идеально! Осталось припаять и готово:
TODO: развести печатную платку и сделать красиво

Всё, теперь можно быть уверенным, что флешка работает именно в 3.0 (2.0, 1.1) режиме, а иначе она просто не обнаружится в системе. В заключение, тестируем наш картридер в различных положениях выключателя:
3.0:
2.0:

почему-то скорость USB 1.1 не понравилась Crystal Disk Mark, и в результате теста он показал нули

Вопрос на засыпку

В USB 3.0 разъёме две пары контактов USB 2.0 и USB 3.0, мы уже выяснили, что устройство (флешка, картридер) сначала лезет на контакты 3.0, а если не получается, переходит в 2.0 режим.

Что, если к 3.0 контактам подключить одно устройство, а к 2.0 контактам другое? Какое из устройств увидит компьютер?

Попробуйте ответить в опросе ниже перед тем, как заглядывать под спойлер :)
Ответ
Для этого эксперимента, спаяем вместе USB 3.0 SATA-адаптер и USB 2.0 флешку:


Вставляем в комп и

Увиделось оба устройства! Да, на самом деле в каждом физическом USB 3.0 порту сразу два независимых порта. По крайней мене, у ПК на чипсетах Intel.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru