Простой USB HID контроллер 4-й версии предназначен в первую очередь для управления мультимедиа в авто ( его замена USB HID контроллер F401 )
Питается только от BAT, от 6 до 18В.
Сигнал ACC при наличии на нем напряжения не ниже 10.5В аппаратно включает контроллер.
При пропадании ACC этот сигнал является информационным и позволяет осуществить любые действия связанные с завершением работы мультимедиа, включая отключение питания самому себе.
Поэтому контроллер от BAT в выключенном состоянии практически ничего не потребляет (около 350 микро-ампер).
Контроллер обладает следующими возможностями:
— имеет встроенный источник питания DC/DC 12/5В до 1А с возможностью полного программного отключения при отсутствии АСС;
— имеет входы BAT для постоянного питания и ACC для управления включением-выключением;
— имеет выход для питания внешних устройств 5В (5В на нем присутствует пока включен контроллер);
— имеет управляемый выход для питания внешних устройств 5В (выдачей 5В можно управлять с помощью событий);
— имеет 4 канала для подключения резистивных кнопок руля или внешних кнопок, джойстиков… (до 15 кнопок на канал);
— имеет 2 канала аналоговых выходов для управления штатными магнитолами имитируя нажатие резистивных кнопок (выходное напряжение 0…5В);
— содержит 2 выхода типа открытый коллектор с током до 500mA для управления кнопкой включения планшета, тв-бокса, либо внешним реле или выдачи ИК кода…;
— имеет 6 цифровых входов/выходов общего применения с защитой (выходное напряжение 0…3.3В, последовательно резистор 330 Ом). Из них 2 шины — UART (для подключения UART GPS приемника…);
— любая из 11 ножек контроллера может быть использована для генерации события по указанному уровню и необходимому таймауту;
— поддерживает 4-х проводные резистивные тачпанели. Для перехвата тачпанели у штатной системы имеет внешнюю плату мультиплексора. Она выполнена на плате 25×20мм с кабелем 20 см и всеми необходимыми разъемами для подключения в разрыв без пайки;
— поддерживает работу совместно с контроллером CAP2RES для замены родного резистивного тача на емкостный с сохранением штатной работы. При этом для штатной системы эмулируется резистивный тач, а для внешнего CARPC он обеспечивает мультитач управление;
— можно подключить 2 цифровых энкодера с программным предделителем;
— может эмулировать 2 цифровых энкодера;
— имеет 2 линии аналогового входа 0…18В с возможностью отслеживания любых изменений и выдачи любых реакций на них;
— имеет поддержку шины Toyota/Lexus IEBUS (AVC-LAN) для эмуляции внешнего навигатора, внешнего DVD и CD чейнджера, управления штатным усилителем Toyota/Lexus без штатного головного устройства, чтения кнопок по шине, управления с родного тача головных устройств…;
— поддерживает шину BMW iBUS и работу приложения I-BUS APP;
— поддерживает автономное управление с джойстиков iDrive от BMW с тачпадом и без по CAN (работающих на скоростях 100KBit/s или 500KBit/s);
— поддерживает подключение внешних UART GPS приемников или других устройств (контролер представляет собой виртуальный COM порт);
— поддерживает протоколы внешних навигаторов для головных устройств ALPINE, KENWOOD, PIONEER;
— поддерживает подключение и управление внешними навигационными блоками GARMIN, PHANTOM, KENWOOD и т.д.;
— любая из 11 ножек контроллера может быть настроена для приема ИК посылок по протоколам NEC, JVC, RC5, SONY, SAMSUNG, APPLE как с прямой, так и с инверсной кодировкой (см. на вкладке ИК вход);
— любая из 13 ножек контроллера может быть настроена для передачи ИК посылок по протоколам NEC, JVC, RC5, SONY, SAMSUNG, APPLE как с прямой, так и с инверсным выходом (для ALPINE например) только огибающая без несущей;
— поддерживает работу с блоком кнопок и джойстика HONDA ODYSSEY;
— поддерживает работу по CAN кнопок руля Opel Astra J по ее шине GMLAN;
— поддерживает работу по CAN кнопок руля Mercedes CLK 320 W209 W212;
— поддерживает работу по CAN кнопок руля VW MKV GTI;
— поддерживает работу по CAN кнопок руля Volvo;
— поддерживает работу по CAN кнопок руля VW GOLF7 и Citroen C5 X7 (с 2008г.);
— поддерживает работу по шине comfort CAN AUDI RNS-E;
— поддерживает работу по CAN кнопок руля BMW E81 и аналогичных;
— поддерживает работу по CAN кнопок руля Crysler 300C;
— поддерживает работу по CAN кнопок руля Skoda Superb 2013/ETI 2017;
— поддерживает работу по CAN кнопок руля Hyundai Genesis;
— в версии для CAN встроен CAN логгер;
— поддерживает шину GaNet Acura RDX/MDX + вывод данных климата через Serial Manager и использование джойстика;
— поддерживает кнопки по шине LIN для Opel Astra J, Opel Insignia, Citroen C5 X7 (с 2008г.);
— поддерживает PS2 тачпад от Synaptics;
— поддерживает управление через Serial Manager;
— поддерживает управление через блютуз модуль на BK3231 или JDY-33 с SPP-C профилем через Serial Manager;
— реализован ИК сниффер для добавления любого ИК пульта. Включается командой i2 в терминале;
— поддерживает автономную работу (включая энкодер сзади) под рулевого джойстика от Renault на 6 или 9 кнопок;
— поддерживает автономную работу кнопок руля AUDI А6С7, А7, А8 4Н (AUDI А4В8, А4В9, Q7) с 2012г. по шине LIN включая подогрев;
— поддерживает автономную работу кнопок руля VW Polo GT, GOLF VII по шине LIN;
— поддерживает работу джойстика Mazda CX5 по шине LIN;
— для подключения имеет разъем микро-USB + возможность припайки внешнего кабеля USB (питается контроллер только от BAT);
— может быть внутрисхемным программатором SPI FLASH для скалера RTD2662;
— размер платы 50×65мм
Вариант с мультиплексором тачпанели:
Вариант без мультиплексора тачпанели:
Один из вариантов блок схемы подключений:
ПО настройки:
Графическая оболочка для программирования под windows YAMDT_GUIV4.zip
ПО контроллера droid_touch4.zip (Прошивки с _wo_keys в имени без поддержки USB клавиатуры, но с поддержкой мультимедиа кнопок и кнопок управления питанием. Они для применения в андроиде где при подключении внешней USB клавиатуры пропадает виртуальная. Можно из маркета поставить например smart keyboard и пользоваться обычной версией. Прошивки с _wo_cdc для работы на андроиде без поддержки композитных устройств. Все работает кроме виртуального ком-порта).
inf файл виртуального COM порта для windows DROID-TOUCH-V4-INF.zip
Чтобы не было курсора при нажатиях вообще, этот файл (после распаковки) положить в /system/usr/idc, и выставить права 644.
Описание YAM-DROID-TOUCH-V4_V0419.zip
Подключение YAM_DROID_TOUCHV4_CONN.zip
Если при нажатии на ссылку ничего не скачивается, то по правой кнопке мыши выбираем «Сохранить ссылку как…»
Настройка кнопок джойстика от Рено:
Работа джойстика от Renault Fluence на магнитоле Joying Intel Sofia 6.0.1:
Пример работы контроллера с джойстиком iDrive от BMW на андроид магнитоле JOYING:
iDrive на андроид свистке:
Подключение контактов джойстика iDrive от BMW:
1 — питание +12В
2 — масса
3 — к контакту X4/5
4 — к контакту X4/6
Подключение для проверки на столе:
При проверке ACC и BAT можно объединить.
Работа проверялась на таких джойстиках:
Просмотр событий от джойстика в терминале — набрать CV1.
Подключение руля AUDI А6С7, А7, А8 4Н (AUDI А4В8, А4В9, Q7) с 2012г. по шине LIN.
Подключение, руль:
коричневый — GND
красный — +12V
сиреневый — шина LIN
Подключение, шина:
X5/8 и X5/10 вместе — шина LIN, необходимо подключить резистор подтяжки 1К между шиной и +12V
Подключение, обогреватель руля:
X5/7 и +12V — обмотка реле включения нагревателя
Нормально разомкнутые контакты реле, один через предохранитель 10…15А к +12V, второй к элементу нагревателя.
Так-же можно поставить P-MOSFET с током коммутации не менее 10А.
Реализованные события (вкладка событие LIN):
— энкодер громкости ENCODER VOL UP, ENCODER VOL DN
— нажатие на энкодер громкости KEY MUTE
— энкодер вверх-вниз ENCODER UP, ENCODER DN
— нажатие на энкодер вверх-вниз KEY OK
— кнопка влево KEY BACKWARD
— кнопка вправо KEY FORWARD
— кнопка меню KEY MENU
— кнопка nav KEY NAV
— кнопка голос KEY VOICE
— кнопка обогрев руля KEY SW HEAT
все кнопки поддерживают как короткое, так и длинное нажатие.
события:
— обогрев выключен SW HEAT OFF
— обогрев включен SW HEAT ON
Реализованные реакции (вкладка реакция LIN):
— выключить подсветку руля
— включить подсветку руля
— выключить подогрев руля
— включить подогрев руля на 20°C
— включить подогрев руля на 25°C
— включить подогрев руля на 30°C
— включить подогрев руля на 35°C
— включить подогрев руля на 40°C
при установленной галке триггер реакции имеют переключающий эффект, т.е. если что-то было выключено оно включается и наоборот.
Как пример, для управления подогревом руля можно создать:
событие LIN, выбрать KEY SW HEAT, длинное нажатие(для защиты от возможных коротких нажатий), реакция LIN включить подогрев руля на 30°C + триггер.
и для индикации включенного состояния нагрева светодиодом на ножке X6/6 (анод к X6/6, катод на массе):
На нижней вкладке Выводы для этой ножки выбрать Акт выход 0В,
добавить событие LIN SW HEAT ON, реакцию Вывод X6/6 Полное управление Однократно включить
добавить событие LIN SW HEAT OFF, реакцию Вывод X6/6 Полное управление Однократно выключить
Как пример для включения подсветки руля при включении габаритов:
подключить +12В от габаритов к скажем X5/9 это VIN1
добавить событие Напряжение VIN1 > 6000mV, реакция LIN подсветку включить
добавить событие Напряжение VIN1 < 6000mV, реакция LIN подсветку выключить
Подключение руля VW Polo GT с 2013г. по шине LIN.
Подключение руля VW GOLF VII по шине LIN (5G0 419 091).
Подключение разъема на руле:
1 GND
2 +12V
3 LIN
4 кнопка HORN
5 кнопка HORN
Между +12V и LIN подключить резистор 1К
Ножки X5/8 и X5/10 вместе — шина LIN
Реализованы события для всех кнопок (вкладка событие LIN) включая кнопку подачи звукового сигнала.
все кнопки поддерживают как короткое, так и длинное нажатие.
Так-же можно включать или выключать подсветку кнопок. По аналогии с подсветкой для руля от AUDI.
Программатор SPI FLASH для скалера RTD2662 из контроллера:
Версия ПО для контроллера версии 4 и ПО оболочки программатора YAM_RTDPROG_V4.zip
Типы SPI FLASH памяти прописываются в файле jedec.ini по аналогии с моими записями.
Подключение:
X6/4 — GND
X6/5 — SDA
X6/6 — SCL
Или в VGA разъем или в разъем за ним. Как раз слева направо в нем SCL-SDA-GND.
После программирования можно запустить контроллер в работу кнопкой «Сброс RTD2662» без передергивания питания.
Чтение памяти W25X040 ~80 сек, запись ~40 сек (зависит от содержимого, т.к. блоки с xFF пропускаются).
Биты защиты в регистре статуса после записи восстанавливаются в состояние до программирования.
Для обратной перешивки на штатную версию просто нажмите кнопочку на плате, подайте питание и запустите ПО оболочки настройки.
В современных Android — приложениях для взаимодействия с другими устройствами чаще всего используются беспроводные протоколы передачи данных, как например Bluetooth. В годы, когда часть устройств имеют беспроводную зарядку, сложно представить себе связку Android устройства и периферийного модуля, в которой необходимо использование проводных интерфейсов. Однако когда такая необходимость возникает, на ум сразу же приходит USB.
Давайте разберем с вами гипотетический кейс. Представьте, что к вам приходит заказчик и говорит: “Мне нужно Android приложение для управления устройством сбора данных и вывода этих самых данных на экран. Есть одно НО — приложение надо написать на одноплатный компьютер с операционной системой Android, а периферийное устройство подключено по USB”
Звучит фантастически, но и такое иногда случается. И тут как нельзя кстати пригодится глубокое знание USB стека и его протоколов, но данная статья не об этом. В данной статье мы рассмотрим, как управлять периферийным устройством по протоколу USB Custom HID с Android устройства. Для простоты напишем Android-приложение (HOST), которое будет управлять светодиодом на периферийным устройством (DEVICE) и получать состояние кнопки (нажатия). Код для периферийной платы приводить не буду, кому интересно — пишите в комментариях.
Итак, приступим.
Теория. Максимально коротко
Для начала немного теории, максимально коротко. Это упрощенный минимум, достаточный для понимания кода, но для большего понимания советую ознакомиться с этим ресурсом.
Для общения по USB на периферийном устройстве необходимо реализовать интерфейс взаимодействия. Разные функции (например, USB HID, USB Mass Strorage или USB CDC) будут реализовывать свои интерфейсы, а некоторые будут иметь несколько интерфейсов. Каждый интерфейс содержит в себе конечные точки — специальные каналы связи, своего рода буферы обмена.
На моем периферийном устройстве реализован Custom HID с одним интерфейсом и с двумя конечными точками, одной для приёма, другой для передачи. Обычно информация с существующими на устройстве интерфейсами и конечными точками написана в спецификации на устройство, в противном случае определить их можно через специальные программы, к примеру USBlyzer.
Устройства в USB HID общаются через репорты. Что такое репорты? Так как данные передаются через конечные точки, то нам надо как-то идентифицировать, а также распарсить в соответствие с протоколом. Устройства не просто кидают друг другу байты данных, а обмениваются пакетами, имеющими четко определенную структуру, которая описывается на устройстве в специальном дескрипторе репорта. Таким образом, по дескриптору репорта, мы можем точно определить, какой идентификатор, структуру, размер и частоту передачи имеют те или иные данные. Идентификация пакета происходит по первому байту, который представляет из себя ID репорта. Например данные о состоянии кнопки, идут в репорта с ID = 1, а светодиодом мы управляем через репорт с ID = 2.
Подальше от железа, поближе к Android
В Android поддержка USB устройств появилась начиная с API версии 12 (Android 3.1) Для работы с периферийным устройством нам необходимо реализовать режим USB host. Работа с USB достаточно неплохо описана в документации.
Для начала необходимо идентифицировать ваше подключаемое устройство, среди всего разнообразия USB девайсов. USB девайсы идентифицируются по сочетанию vid (vendor id) и pid (product id). Создадим в папке xml файл device_filter.xml со следующим содержимым:
<resources>
<usb-device vendor-id="1155" product-id="22352" />
</resources>
Теперь необходимо внести соответствующие разрешения и action (если вам они необходимы) в манифест приложения:
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" />
<activity android:name=".MainActivity">
<intent-ilter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
В android:resource мы указываем файл с необходимыми фильтрами для устройств. Также, как я уже говорил ранее, можно назначить intent фильтры, для запуска приложения, к примеру, в результате подключения вашего устройства.
Для начала необходимо получить UsbManager, найти устройство, интерфейс и конечные точки устройства. Это необходимо делать при каждом подключении устройства.
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
private var usbConnection: UsbDeviceConnection? = null
private var usbInterface: UsbInterface? = null
private var usbRequest: UsbRequest? = null
private var usbInEndpoint: UsbEndpoint? = null
private var usbOutEndpoint: UsbEndpoint? = null
fun enumerate(): Boolean {
val deviceList = usbManager.deviceList
for (device in deviceList.values) {
/* Находим девайс девайс с нашими VID и PID */
if ((device.vendorId == VENDOR_ID) and (device.productId == PRODUCT_ID)) {
/* Получаем интерфейс по известному номер */
usbInterface = device.getInterface(CUSTOM_HID_INTERFACE)
/* Перебираем конечные точки интерфейса
и находим точки на прием и передачу */
for (idx in 0 until usbInterface!!.endpointCount) {
if (usbInterface?.getEndpoint(idx)?.direction == USB_DIR_IN)
usbInEndpoint = usbInterface?.getEndpoint(idx)
else
usbOutEndpoint = usbInterface?.getEndpoint(idx)
}
usbConnection = usbManager.openDevice(device)
usbConnection?.claimInterface(usbInterface, true)
usbRequest = UsbRequest()
usbRequest?.initialize(usbConnection, usbInEndpoint)
}
}
/* Возвращаем статус подключения */
return usbConnection != null
}
Здесь мы видим те самые интерфейсы и конечные точки, речь о которых шла в прошлом разделе. Зная номер интерфейса, мы находим обе конечные точки, на прием и передачу, и инициируем usb соединение. На этом все, теперь можно читать данные.
Как я уже говорил ранее, устройства общаются через репорты.
fun sendReport(data: ByteArray) {
usbConnection?.bulkTransfer(usbOutEndpoint, data, data.size, 0)
}
fun getReport(): ByteArray {
val buffer = ByteBuffer.allocate(REPORT_SIZE)
val report = ByteArray(buffer.remaining())
if (usbRequest.queue(buffer, REPORT_SIZE)) {
usbConnection?.requestWait()
buffer.rewind()
buffer.get(report, 0, report.size)
buffer.clear()
}
return report
}
В метод sendReport мы передаем массив байт, в котором нулевым байтом является репорт ID, берем текущее USB подключение к устройству и выполняем передачу. В качестве параметров в метод BulkTransfer передаем номер конечной точки, данные, их размер и таймаут передачи. Стоит отметить, что класс UsbDeviceConnection имеет методы для реализации обмена данными с устройством USB — методы bulkTransfer и controlTransfer. Их использование зависит от типа передачи, который поддерживает та или иная конечная точка. В данном случае используем bulkTransfer, хотя для HID чаще всего характерно использование конечных точек с типом control. Но у нас Custom HID, так что делаем что хотим. Про тип передачи советую почитать отдельно, так как от него зависит объем и частота передаваемых данных.
Для получения данных необходимо знать размер получаемых данных, который можно, как знать заранее, так и получить из конечной точки.
Метод получения данных по USB HID является синхронным и блокирующим и выполнять его необходимо в другом потоке, кроме того, репорты от устройства могут приходить постоянно, либо в любое время, поэтому необходимо реализовать постоянный опрос репорта, чтобы не пропустить данные. Сделаем это при помощи RxJava:
fun receive() {
Observable.fromCallable<ByteArray> { getReport() }
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.repeat()
.subscribe({
/* check it[0] (this is report id) and handle data */
},{
/* handle exeption */
})
}
Получив массив байт, мы должны проверить нулевой байт, так как он является report ID и в соответствии с ним парсить полученные данные.
По завершении всех действий с USB нужно закрыть соединение. Можно выполнять это в onDestroy activity или в onCleared во ViewModel.
fun close() {
usbRequest?.close()
usbConnection?.releaseInterface(usbInterface)
usbConnection?.close()
}
Заключение
В статье рассмотрен очень небольшой и примитивный, исключительно демонстративный код с реализацией для конкретного устройства. Конечно, классов USB существует много, не только HID и для них естественно реализация будет иная. Однако все методы достаточно неплохо документированы и имея хорошее представление о USB стеке можно легко разобраться в том, как их применять.
X. Полезные материалы
- Исходный код проекта
- USB in a NutShell
- USB host overview
В моем проекте нужно было управлять реле из программы на устройстве с OS Android 4.4.2 (плеер Tronsmart VEGA S89 [1]), и также нужно отслеживать замыкание на выходе датчика цепи системы безопасности, и оповещать от этом пользователя (любая современная система пожарной безопасности имеет у себя на борту такие сухие контакты для передачи информации о пожаре на сторонние устройства).
Управлять реле нужно было для отключения питания мониторов, а состояние датчика говорило о том, сработала сигнализация или нет.
На корпусе Tronsmart VEGA S89 есть несколько портов USB, через которые и хотелось управлять релюшками. Разумеется, в первую очередь я подумал об Ардуино. И это действительно вполне подходящее решение, потому что написано множество статей на тему «как сделать что угодно на Arduino» — бери и пользуйся. Однако случайно наткнулся на макетную плату AVR-USB162 [2], построенную на микроконтроллере AT90USB162. Заинтересовало наличие аппаратной поддержки USB на плате AVR-USB162, простота схемы и разумеется цена — намного ниже, чем у стандартного Ардуино. Плата AVR-USB162 была заказана и через несколько дней получена.
Примечание: мои знания в сфере микроконтроллеров до этого времени были равны абсолютному нулю. Знания в сфере ассемблера так же ноль. СИ на уровне Hello world. Было несколько опытов программирования с Raspberry Pi на Python. И пользовался ActionScript для своих нужд. Поэтому всё-всё описанное далее не стоит рассматривать как инструкцию. Может быть, это будет интересно подобным начинателям, но не более.
[Программное обеспечение для микроконтроллера (firmware)]
Плату AVR-USB162 получил, начал разбираться. Чтобы можно было наладить связь через USB с микроконтроллером, в него должно быть записано firmware, которое работает как какое-то стандартное устройство USB. Самый популярный класс стандартных устройств USB — класс USB HID, с помощью которых решается большинство задач ввода и вывода.
Не сразу до меня дошло, какие примеры и какие библиотеки для программирования микроконтроллера надо использовать, по ошибке сначала начал с библиотеки V-USB. Оказалось, что это совсем не то, эта библиотека для «обычных» AVR, без аппаратного интерфейса USB. Для моего AT90USB162, у которого на борту аппаратная поддержка USB, подойдут примеры готового кода из библиотеки LUFA [3], там есть примеры реализации устройств USB HID.
Я скачал последнюю версию пакета LUFA с сайта автора [4], и нашел там нужные мне примеры кода на языке C (взял пример из папки Demos/Device/ClassDriver/GenericHID). Последняя версия LUFA 140928 имеет документацию на английском языке (комментарии в коде, DoxyGen), но в сети можно найти перевод на русский язык версии 101122 (см. [3], или прогуглите Библиотека LUFA 101122). Примеры кода из библиотеки LUFA компилируются запуском из командной строки команд make, поэтому нужно установить тулчейн AVR.
Примечание: тулчейн — это различные библиотеки для AVR, заголовочные файлы, компилятор языка C и ассемблера. В среде Windows можно использовать как тулчейн пакет WinAVR, или можно использовать тулчейн, входящий в состав установки Atmel Studio (на Linux, FreeBSD и других UNIX-подобных системах нужно установить тулчейн AVR-libc).
Я скачал и установил WinAVR (инсталлятор можно легко найти на сайте sourceforge.net, прогуглите Download WinAVR), попробовал скомпилировать пример Demos/Device/ClassDriver/GenericHID. Пример GenericHID, как и все проекты LUFA, компилируется командами make clean и make hex, выполненными из каталога проекта:
В результате в папке проекта GenericHID появится файл GenericHID.hex. Это прошивка firmware, который нужно прошить в память микроконтроллера либо с помощью программатора, либо с помощью USB загрузчика Flip (я пользовался Flip). Я прошил свой микроконтроллер через USB, но устройство USB HID у меня не заработало. Оказывается, нужно правильно отредактировать опции в файле makefile проекта, чтобы они соответствовали типу макетной платы, типу микроконтроллера и действительной рабочей тактовой частоте (опции MCU, BOARD, F_CPU). Вот содержимое файла makefile, которое должно соответствовать макетной плате AVR-USB162 (старые строчки опций MCU, BOARD, F_CPU я закомментировал символом #, и исправленные опции для наглядности выделил здесь жирным шрифтом):
# Выполните "make help" для получения подробной подсказки. #MCU = at90usb1287 MCU = at90usb162 ARCH = AVR8 #BOARD = USBKEY BOARD = MICROSIN162 #F_CPU = 8000000 F_CPU = 16000000 F_USB = $(F_CPU) OPTIMIZATION = s TARGET = GenericHID SRC = $(TARGET).c Descriptors.c $(LUFA_SRC_USB) $(LUFA_SRC_USBCLASS) LUFA_PATH = ../../../../LUFA CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER -IConfig/ LD_FLAGS = # Цель по умолчанию all: # Include LUFA build script makefiles include $(LUFA_PATH)/Build/lufa_core.mk include $(LUFA_PATH)/Build/lufa_sources.mk include $(LUFA_PATH)/Build/lufa_build.mk include $(LUFA_PATH)/Build/lufa_cppcheck.mk include $(LUFA_PATH)/Build/lufa_doxygen.mk include $(LUFA_PATH)/Build/lufa_dfu.mk include $(LUFA_PATH)/Build/lufa_hid.mk include $(LUFA_PATH)/Build/lufa_avrdude.mk include $(LUFA_PATH)/Build/lufa_atprogram.mk
Опции MCU, BOARD, F_CPU нужно вводить именно так, как указано, с соблюдением регистра символов. Теперь заново перекомпилируйте проект командами make clean, make hex, прошейте полученную прошивку в память микроконтроллера, и оно определится в компьютере как настоящее устройство USB HID!
Это параметры для управления препроцессором компилятора AVR GCC, которые встречаются в коде проектов LUFA.
MCU. Этот параметр показывает, какая целевая модель микроконтроллера должна использоваться в проекте (компилируемое приложение для микроконтроллера). Опцию MCU нужно установить в одно из возможных значений, поддерживаемых тулчейном (опция MCU управляет подключением заголовочных файлов в папке avr\include\avr тулчейна). В зависимости от того, какой микроконтроллер используется, в опции после знака = нужно указать конкретное значение — например для микроконтроллера AT90USB1287 нужно указать MCU = at90usb1287). Для макетной платы AVR-USB162 нужно указать MCU = at90usb162. Внимание: после знака = значение опции нужно указывать маленькими буквами!
Не все демонстрационные проекты поддерживают все типы микроконтроллеров Atmel AVR USB, поэтому см. список поддерживаемых архитектур в разделе Demo Compatibility файла описания проекта (например, для проекта GenericHID это файл описания GenericHID.txt).
BOARD. Эта опция показывает, под какое аппаратное устройство (макетную плату) будет скомпилирован проект. Некоторые куски кода библиотеки LUFA и примеров кода устройств зависят от аппаратуры макетной платы — чаще всего это касается управления индикационными светодиодами и опроса кнопок. Если Вы используете одну из поддерживаемых в LUFA макетных плат, то Вам нужно задать опцию BOARD в конкретное значение, соответствующее Вашей плате. Все возможные варианты для опции BOARD можно подсмотреть в файле LUFA\Drivers\Board\Board.h библиотеки LUFA.
Макетная плата AVR-USB162 также поддерживается LUFA, для неё надо указать значение опции BOARD = MICROSIN162.
F_CPU. Эта опция отражает реальную тактовую частоту, на которой работает микроконтроллер, частота указана в Гц. Эта опция также используется препроцессором как в модулях тулчейна, так и в коде примеров самой библиотеки LUFA. Опция F_CPU позволяет корректировать поведение кода в плане корректного отслеживания реального времени, правильно задавать настройки аппаратуры.
Для микроконтроллера AT90USB162 для поддержки USB возможны только 2 варианта выбора тактовой частоты: 8 МГц или 16 МГц. На моей макетной плате AVR-USB162 был установлен кварц на 16 МГц, поэтому надо указать значение опции F_CPU = 16000000.
В память микроконтроллера AT90USB162 уже записан USB-загрузчик, который работает вместе специальным программным обеспечением FLIP от компании Atmel. Поэтому нужно просто скачать с сайта Atmel утилиту FLIP, установить её, установить драйвер для устройства USB DFU Flip. Это очень простые операции, все достаточно подробно описано в [2].
[ПО хоста: как управлять устройством USB HID]
В пакете библиотеки LUFA [4], в папке GenericHID\HostTestApp лежат уже готовые программы для тестирования прошитой платы. Приятно было обнаружить вариант на питоне, который у меня уже был установлен (Python 2.7). Тем, у кого этого ПО нет, настоятельно советую установить [7]. Для запуска скрипта так же необходимо установить библиотеку pywinusb [8]. После того, как всё установлено, можно запустить скрипт:
python test_generic_hid_winusb.py
После запуска скрипта светодиод начал мигать – можно сказать, что мы получили «Hello world», что после нескольких дней мучений показалось просто супер чудом!
Если заглянуть в код скрипта, мы увидим, что всё достаточно просто. После запуска скрипта запускается функция main(), где сначала происходит инициализация устройства (с помощью функции get_hid_device_handle()), а затем в цикле (условием которого является подключенное устройство) раз в 0.2 секунды происходит отправка на плату команды длиной 9 байтов (1 байт зарезервирован под идентификатор репорта, а остальные получает устройство, причем 2-ой символ отвечает за то, включен или выключен наш светодиод). Отправляет данные в устройство USB функция send_led_pattern, а принимает received_led_pattern.
""" LUFA Library Copyright (C) Dean Camera, 2014. dean [at] fourwalledcubicle [dot] com www.lufa-lib.org """ """ Тестовый скрипт хоста для демонстрационного устройства USB HID (LUFA Generic HID).
Этот скрипт будет отправлять непрерывный поток стандартных репортов в устройство,
о чем будет сигнализировать изменяющееся состояние светодиодов на целевой
отладочной плате с микроконтроллером AVR USB. Отправленные и принятые данные
будут печататься в окне терминала. Для работы скрипт требует библиотеки pywinusb (https://pypi.python.org/pypi/pywinusb/). """ import sys from time import sleep import pywinusb.hid as hid # Стандатные идентификаторы VID, PID для устройства USB HID и длина репорта
# (полезной нагрузки). Длина репорта увеличена на 1, чтобы учесть байт для
# идентификатора репорта (Report ID), который должен идти первым. device_vid = 0x03EB device_pid = 0x204F report_length = 1 + 8
def get_hid_device_handle(): hid_device_filter = hid.HidDeviceFilter(vendor_id=device_vid, product_id=device_pid) valid_hid_devices = hid_device_filter.get_devices() if len(valid_hid_devices) is 0: return None else: return valid_hid_devices[0] def send_led_pattern(device, led1, led2, led3, led4): # Данные репорта для демо это report ID (всегда 0), за которым # следуют данные включения/выключения светодиодов LED report_data = [0, led1, led2, led3, led4] # Остальные данные в массиве заполняются нулями report_data.extend([0] * (report_length - len(report_data))) # Отправить сгенерированные данные в устройство device.send_output_report(report_data) print("Sent LED Pattern: {0}".format(report_data[1:5])) def received_led_pattern(report_data): print("Received LED Pattern: {0}".format(report_data[1:5])) def main(): hid_device = get_hid_device_handle() if hid_device is None: print("No valid HID device found.") sys.exit(1) try: hid_device.open() print("Connected to device 0x%04X/0x%04X - %s [%s]" % (hid_device.vendor_id, hid_device.product_id, hid_device.product_name, hid_device.vendor_name)) # Настройка обработчика входных репортов HID, чтобы можно было принять
# данные от устройства: hid_device.set_raw_data_handler(received_led_pattern) p = 0 while (hid_device.is_plugged()): # Преобразовать текущий номер светодиода p в битовую маску
# и отправка её в устройство, чтобы управлять светодиодами. send_led_pattern(hid_device, (p >> 3) & 1, (p >> 2) & 1, (p >> 1) & 1, (p >> 0) & 1) # Вычислить номер следующего зажигаемого светодиода LED p = (p + 1) % 16 # Задержка для визуального эффекта sleep(.2) finally: hid_device.close() if __name__ == '__main__': main()
Это значит, что подцепившись к контакту P16 макетной платы AVR-USB162 (порт PD4 микроконтроллера AT90USB162), я смогу управлять реле (плюс у меня ещё и световая индикация будет — с помощью светодиода VD1, который уже установлен на плате AVR-USB162). Теперь надо было подцепить к питону кнопку (которая так же есть на плате). Кнопка подключена к порту PD7 микроконтроллера AT90USB162.
[Доработка firmware USB HID под мою задачу]
Как уже писал, моя задача состоит в управлении через USB реле, и в приеме через USB сигнала от нажатия кнопки. Как управлять реле и так понятно (тест-программа HostTestApp как раз делает то что нужно), осталось разобраться с кнопкой.
Я решил не изобретать велосипед, а чуть-чуть подправить исходный код GenericHID. Я добавил в файл GenericHID.h строчку: #include < LUFA/Drivers/Board/Buttons.h >. Это нужно для того, чтобы из описания платы MICROSIN162 (это плата AVR-USB162) подцепилось описание уже имеющейся на плате кнопки.
А затем подправил код GenericHID.c таким образом, чтобы во второй байт (байт Data[1]) сообщения, которое USB HID посылает в компьютер (т. е. в ПО хоста, которое может быть либо скриптом на Python, либо программой Android), записывался 0, когда кнопка не нажата, а когда кнопка нажата, записывалась 1. Для этого внесены изменения в функцию CALLBACK_HID_Device_CreateHIDReport:
bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo, uint8_t* const ReportID, const uint8_t ReportType, void* ReportData, uint16_t* const ReportSize) { uint8_t* Data = (uint8_t*)ReportData; uint8_t CurrLEDMask = LEDs_GetLEDs(); uint8_t ButtonStatus_LCL = Buttons_GetStatus(); Data[0] = (CurrLEDMask & LEDS_LED1) ? 1 : 0; Data[1] = (ButtonStatus_LCL & BUTTONS_BUTTON1) ? 1 : 0; Data[2] = (CurrLEDMask & LEDS_LED3) ? 1 : 0; Data[3] = (CurrLEDMask & LEDS_LED4) ? 1 : 0; *ReportSize = GENERIC_REPORT_SIZE; return false; }
Как Вы уже догадались, состояние кнопки считывает вызов функции Buttons_GetStatus. Для того, чтобы порт кнопки работал как вход, я еще добавил одну строчку в функцию SetupHardware (в этой функции делаются все предварительные настройки):
После внесения изменений проект нужно перекомпилировать (make clean, make hex), и записать в память микроконтроллера платы AVR-USB162 с помощью USB-загрузчика (как это делается, см. [2, 5]).
Теперь надо немного изменить тестовый скрипт на Python, чтобы проверить наше новое USB HID устройство.
import sys from time import sleep import pywinusb.hid as hid
# Идентификаторы VID, PID стандартного HID-устройства, и длина репорта/полезной нагрузки (длина # увеличена на 1, чтобы учесть байт Report ID, который должен идти перед данными репорта) device_vid = 0x03EB device_pid = 0x204F report_length = 1 + 8
def get_hid_device_handle(): hid_device_filter = hid.HidDeviceFilter(vendor_id=device_vid, product_id=device_pid) valid_hid_devices = hid_device_filter.get_devices() if len(valid_hid_devices) is 0: return None else: return valid_hid_devices[0] def send_led_pattern(device, led1): # Данные репорта для демо это report ID (всегда 0), за которым идет байт данных # для включения/выключения светодиода LED report_data = [0, led1] # Остальную область репорта заполняем нулями report_data.extend([0] * (report_length - len(report_data))) # Отправить сгенерированный репорт в устройство device.send_output_report(report_data) print("Sent LED Pattern: {0}".format(report_data[1:5]))
def received_led_pattern(report_data): print("Received LED Pattern: {0}".format(report_data[1:5]))
def main(): hid_device = get_hid_device_handle() if hid_device is None: print("No valid HID device found.") sys.exit(1) try: hid_device.open() print("Connected to device 0x%04X/0x%04X - %s [%s]" % (hid_device.vendor_id, hid_device.product_id, hid_device.product_name, hid_device.vendor_name)) # Установка обработчика HID input report handler для приема репортов hid_device.set_raw_data_handler(received_led_pattern) p = 0 while (hid_device.is_plugged()): # Преобразование текущего индекса шаблона в битовую маску и отправка send_led_pattern(hid_device, (p >> 0) & 1) # Вычислить следующий шаблон светодиода LED p = (p + 1) % 16 # Небольшая задержка для визуального эффекта sleep(.2) finally: hid_device.close()
if __name__ == '__main__': main()
В скрипте сделаны минимальные изменения – убрана в основном цикле передача для всех светодиодов, оставлен только первый. И в функции send_led_pattern в передаваемых параметрах оставлен также один светодиод.
Теперь при запуске скрипта мы увидим, что при нажатой кнопке на месте второго байта появляется 1. Если же кнопка не нажата, мы видим 0 (принятые данные выводит обработчик received_led_pattern).
Итак, полдела сделано — получен рабочий USB-контроллер, с которого мы можем считать и отправить на него всю нужную нам информацию. Теперь надо написать программу для Андроид.
[ПО хоста для Android]
Пример работы с устройством USB HID из Android я нашел на сайте microsin.net. Внимание: чтобы программа заработала с Вашим устройством USB, обязательно нужно редактировать параметры USB под свою плату. Эти параметры, к сожалению, не лежат на «поверхности». Во многом помог сайт stackoverflow.com. А потом, я нашел информацию о кодах USB в документации LUFA.
Вот переменные класса MainActivity, без правильной установки которых ничего не заработает (весь проект приложения Android см. по ссылке [9]):
private static final int USB_TYPE_VENDOR = 0x20; private static final int USB_RECIP_DEVICE = 0x01; private static final int USB_ENDPOINT_IN = 0x80; private static final int USB_ENDPOINT_OUT = 0x00; private static final int CUSTOM_RQ_SET_STATUS = 9; private static final int CUSTOM_RQ_GET_STATUS = 1;
Также необходимо поменять код функций getled и setled. Теперь мы передаем и принимаем строку из 8 байт. В первом байте информация о состоянии светодиода, во втором информация о нажатой кнопке.
private boolean getledstate() { boolean ledstate = false; //Длина нашего запроса - 8 байт byte buf[] = new byte[8]; if (usbOpenDevice()) { UsbDeviceConnection connection = usbmanager.openDevice(usbdev); connection.claimInterface(usbif, true); connection.controlTransfer(USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, CUSTOM_RQ_GET_STATUS, 0, 0x0, buf, buf.length, 5000); //Запись в лог принимаемой от контроллера информации (для отладки): clog("getledstate "+buf[0]+buf[1]+buf[2]+buf[3]+buf[4]+buf[5]+buf[6]+buf[7]); //Получение информации о текущем состоянии светодиода: ledstate = (buf[0]!=0)?false:true; } return ledstate; } private void setled(int value) { // В коде на сайте microsin.net предлагается передавать value напрямую, // но у меня подобное решение не сработало. Я решил добавлять преобразованное // значение value в первую ячейку массива buf. byte buf[] = new byte[8]; buf[0] =(byte) value;
// Затем этот массив передаем в микроконтроллер.
if (usbOpenDevice()) { UsbDeviceConnection connection = usbmanager.openDevice(usbdev); connection.claimInterface(usbif, true); connection.controlTransfer(USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, CUSTOM_RQ_SET_STATUS, 0, 0, buf, buf.length, 5000); } }
В остальном код, который был взят с сайта microsin.net, остался без изменений. Программа работает, светодиод включается и выключается. При нажатии на кнопку (и опросе светодиода) мы получаем ответ о том, что во второй ячейке переменной buf появляется единичка, т. е. кнопка на плате так же работает. Если запустить цикл, который будет периодически проверять данное значение, мы сможем отслеживать нажатие кнопки.
В результате мы получили заготовку устройства USB HID и тестовое приложение для Android, которые работают вместе, и передают друг другу данные через интерфейс USB.
Дело в том, что система в том виде, в котором описана в статье, будет работать только после физического подключения устройства в USB порт. Т. е. если Android выключить и включить заново, устройство не даст программе разрешения на использование устройства (не сработает intent). По идее есть возможность прописывать разрешение на доступ к конкретному устройству в самой программе, но в андроиде глюк (я прошерстил кучу форумов — это действительно глюк самой системы). Проблема в том, что система Android не запоминает в таком случае разрешение, которое дает пользователь (галка в запросе ставится, но в следующий раз спрашивает опять).
Есть способ решения — частично поправить системные файлы Android. Но данный способ непростой и достаточно ёмкий по времени. Неудобно.
И я придумал свой способ. Я запустил в программе микроконтроллера устройства USB (на плате AVR-USB162) таймер. И если за отведенное время (около 30 секунд) на плату в третий пин наша программа на андроиде не записывает единичку (читай, андроид не дал разрешения на использование устройства), плата перезагружается. Андроид определяет это как новое подключение устройства и срабатывает интент на обнаружение устройства USB.
В итоге у меня всё работает и после выключения/включения плеера Android. Может пригодится и вам когда-нибудь подобное решение.
В качестве готового исполнительного устройства заказал на ebay плату с реле [6], эту плату можно напрямую подключить к выходу микроконтроллера:
[Ссылки]
1. Tronsmart VEGA S89 site:ebay.com.
2. Макетная плата AVR-USB162.
3. LUFA — бесплатная библиотека USB для микроконтроллеров Atmel AVR.
4. LUFA site:fourwalledcubicle.com — сайт автора библиотеки LUFA. Тут можно найти ссылки на закачку последнего стабильного релиза, а также предыдущих релизов.
5. AVR4023: протокол FLIP USB DFU.
6. 1 Channel 5V Indicator Light LED Relay Module For Arduino ARM PIC New Durable site:ebay.com.
7. Python download site:python.org.
8. Pywinusb library site:pypi.python.org.
9. 141113USB-HID-Android-src.zip — исходный код устройства USB HID, проект приложения Androd (ПО хоста, управляющего устройством USB).
I want to use a plastic drum pad to trigger sounds on the computer. It is connected by USB. It is not supported by Mac OS X by default. It is not a MIDI device.
User space driver
- USB Roll-up drum kit user-space driver for linux
Device connection
The following appears in /var/log/messages
Dec 4 21:11:13 colourbox kernel: usb 1-2: new low speed USB device using uhci_hcd and address 3 Dec 4 21:11:13 colourbox kernel: usb 1-2: configuration #1 chosen from 1 choice Dec 4 21:11:13 colourbox kernel: usbcore: registered new driver hiddev Dec 4 21:11:13 colourbox kernel: hiddev96: USB HID v1.00 Device [HID 1941:8021] on usb-0000:00:14.2-2 Dec 4 21:11:13 colourbox kernel: usbcore: registered new driver usbhid Dec 4 21:11:13 colourbox kernel: drivers/usb/input/hid-core.c: v2.6:USB HID core driver
The following device node appears
# ls -al /dev/usb/hiddev0 crw-rw---- 1 root root 180, 96 2008-12-04 21:53 /dev/usb/hiddev0
# lsusb Bus 001 Device 004: ID 1941:8021 Bus 001 Device 001: ID 0000:0000
# lsusb -D /proc/bus/usb/001/004 Device: ID 1941:8021 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x1941 idProduct 0x8021 bcdDevice 1.00 iManufacturer 0 iProduct 0 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Devices bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.00 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 52 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10 Device Status: 0x0000 (Bus Powered)
Using the code Chuck hid example code from here.
# chuck hdi.ck /dev/usb/hiddev0 [hiddev0]:line(1).char(1): illegal token [hiddev0]:line(1).char(2): illegal token [hiddev0]:line(1).char(3): illegal token ....
Useful commands
sudo apt-get -y install joystick joy2key dpkg -L joystick | grep bin dpkg -L joy2key | grep bin
See also
- Userspace driver for JACK MIDI
- Linux Journal article about joysticks
- Chuck hid example code
- forum post
- joystick debian package — perhaps the same as the main one?
Introduction¶
The HID Gadget driver provides emulation of USB Human Interface
Devices (HID). The basic HID handling is done in the kernel,
and HID reports can be sent/received through I/O on the
/dev/hidgX character devices.
For more details about HID, see the developer page on
https://www.usb.org/developers/hidpage/
Configuration¶
g_hid is a platform driver, so to use it you need to add
struct platform_device(s) to your platform code defining the
HID function descriptors you want to use — E.G. something
like:
#include <linux/platform_device.h> #include <linux/usb/g_hid.h> /* hid descriptor for a keyboard */ static struct hidg_func_descriptor my_hid_data = { .subclass = 0, /* No subclass */ .protocol = 1, /* Keyboard */ .report_length = 8, .report_desc_length = 63, .report_desc = { 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ 0x09, 0x06, /* USAGE (Keyboard) */ 0xa1, 0x01, /* COLLECTION (Application) */ 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ 0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */ 0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ 0x75, 0x01, /* REPORT_SIZE (1) */ 0x95, 0x08, /* REPORT_COUNT (8) */ 0x81, 0x02, /* INPUT (Data,Var,Abs) */ 0x95, 0x01, /* REPORT_COUNT (1) */ 0x75, 0x08, /* REPORT_SIZE (8) */ 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */ 0x95, 0x05, /* REPORT_COUNT (5) */ 0x75, 0x01, /* REPORT_SIZE (1) */ 0x05, 0x08, /* USAGE_PAGE (LEDs) */ 0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */ 0x29, 0x05, /* USAGE_MAXIMUM (Kana) */ 0x91, 0x02, /* OUTPUT (Data,Var,Abs) */ 0x95, 0x01, /* REPORT_COUNT (1) */ 0x75, 0x03, /* REPORT_SIZE (3) */ 0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */ 0x95, 0x06, /* REPORT_COUNT (6) */ 0x75, 0x08, /* REPORT_SIZE (8) */ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 0x25, 0x65, /* LOGICAL_MAXIMUM (101) */ 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ 0x19, 0x00, /* USAGE_MINIMUM (Reserved) */ 0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */ 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ 0xc0 /* END_COLLECTION */ } }; static struct platform_device my_hid = { .name = "hidg", .id = 0, .num_resources = 0, .resource = 0, .dev.platform_data = &my_hid_data, };
You can add as many HID functions as you want, only limited by
the amount of interrupt endpoints your gadget driver supports.
Configuration with configfs¶
Instead of adding fake platform devices and drivers in order to pass
some data to the kernel, if HID is a part of a gadget composed with
configfs the hidg_func_descriptor.report_desc is passed to the kernel
by writing the appropriate stream of bytes to a configfs attribute.
Send and receive HID reports¶
HID reports can be sent/received using read/write on the
/dev/hidgX character devices. See below for an example program
to do this.
hid_gadget_test is a small interactive program to test the HID
gadget driver. To use, point it at a hidg device and set the
device type (keyboard / mouse / joystick) — E.G.:
# hid_gadget_test /dev/hidg0 keyboard
You are now in the prompt of hid_gadget_test. You can type any
combination of options and values. Available options and
values are listed at program start. In keyboard mode you can
send up to six values.
For example type: g i s t r —left-shift
Hit return and the corresponding report will be sent by the
HID gadget.
Another interesting example is the caps lock test. Type
—caps-lock and hit return. A report is then sent by the
gadget and you should receive the host answer, corresponding
to the caps lock LED status:
--caps-lock recv report:2
With this command:
# hid_gadget_test /dev/hidg1 mouse
You can test the mouse emulation. Values are two signed numbers.
Sample code:
/* hid_gadget_test */ #include <pthread.h> #include <string.h> #include <stdio.h> #include <ctype.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUF_LEN 512 struct options { const char *opt; unsigned char val; }; static struct options kmod[] = { {.opt = "--left-ctrl", .val = 0x01}, {.opt = "--right-ctrl", .val = 0x10}, {.opt = "--left-shift", .val = 0x02}, {.opt = "--right-shift", .val = 0x20}, {.opt = "--left-alt", .val = 0x04}, {.opt = "--right-alt", .val = 0x40}, {.opt = "--left-meta", .val = 0x08}, {.opt = "--right-meta", .val = 0x80}, {.opt = NULL} }; static struct options kval[] = { {.opt = "--return", .val = 0x28}, {.opt = "--esc", .val = 0x29}, {.opt = "--bckspc", .val = 0x2a}, {.opt = "--tab", .val = 0x2b}, {.opt = "--spacebar", .val = 0x2c}, {.opt = "--caps-lock", .val = 0x39}, {.opt = "--f1", .val = 0x3a}, {.opt = "--f2", .val = 0x3b}, {.opt = "--f3", .val = 0x3c}, {.opt = "--f4", .val = 0x3d}, {.opt = "--f5", .val = 0x3e}, {.opt = "--f6", .val = 0x3f}, {.opt = "--f7", .val = 0x40}, {.opt = "--f8", .val = 0x41}, {.opt = "--f9", .val = 0x42}, {.opt = "--f10", .val = 0x43}, {.opt = "--f11", .val = 0x44}, {.opt = "--f12", .val = 0x45}, {.opt = "--insert", .val = 0x49}, {.opt = "--home", .val = 0x4a}, {.opt = "--pageup", .val = 0x4b}, {.opt = "--del", .val = 0x4c}, {.opt = "--end", .val = 0x4d}, {.opt = "--pagedown", .val = 0x4e}, {.opt = "--right", .val = 0x4f}, {.opt = "--left", .val = 0x50}, {.opt = "--down", .val = 0x51}, {.opt = "--kp-enter", .val = 0x58}, {.opt = "--up", .val = 0x52}, {.opt = "--num-lock", .val = 0x53}, {.opt = NULL} }; int keyboard_fill_report(char report[8], char buf[BUF_LEN], int *hold) { char *tok = strtok(buf, " "); int key = 0; int i = 0; for (; tok != NULL; tok = strtok(NULL, " ")) { if (strcmp(tok, "--quit") == 0) return -1; if (strcmp(tok, "--hold") == 0) { *hold = 1; continue; } if (key < 6) { for (i = 0; kval[i].opt != NULL; i++) if (strcmp(tok, kval[i].opt) == 0) { report[2 + key++] = kval[i].val; break; } if (kval[i].opt != NULL) continue; } if (key < 6) if (islower(tok[0])) { report[2 + key++] = (tok[0] - ('a' - 0x04)); continue; } for (i = 0; kmod[i].opt != NULL; i++) if (strcmp(tok, kmod[i].opt) == 0) { report[0] = report[0] | kmod[i].val; break; } if (kmod[i].opt != NULL) continue; if (key < 6) fprintf(stderr, "unknown option: %s\n", tok); } return 8; } static struct options mmod[] = { {.opt = "--b1", .val = 0x01}, {.opt = "--b2", .val = 0x02}, {.opt = "--b3", .val = 0x04}, {.opt = NULL} }; int mouse_fill_report(char report[8], char buf[BUF_LEN], int *hold) { char *tok = strtok(buf, " "); int mvt = 0; int i = 0; for (; tok != NULL; tok = strtok(NULL, " ")) { if (strcmp(tok, "--quit") == 0) return -1; if (strcmp(tok, "--hold") == 0) { *hold = 1; continue; } for (i = 0; mmod[i].opt != NULL; i++) if (strcmp(tok, mmod[i].opt) == 0) { report[0] = report[0] | mmod[i].val; break; } if (mmod[i].opt != NULL) continue; if (!(tok[0] == '-' && tok[1] == '-') && mvt < 2) { errno = 0; report[1 + mvt++] = (char)strtol(tok, NULL, 0); if (errno != 0) { fprintf(stderr, "Bad value:'%s'\n", tok); report[1 + mvt--] = 0; } continue; } fprintf(stderr, "unknown option: %s\n", tok); } return 3; } static struct options jmod[] = { {.opt = "--b1", .val = 0x10}, {.opt = "--b2", .val = 0x20}, {.opt = "--b3", .val = 0x40}, {.opt = "--b4", .val = 0x80}, {.opt = "--hat1", .val = 0x00}, {.opt = "--hat2", .val = 0x01}, {.opt = "--hat3", .val = 0x02}, {.opt = "--hat4", .val = 0x03}, {.opt = "--hatneutral", .val = 0x04}, {.opt = NULL} }; int joystick_fill_report(char report[8], char buf[BUF_LEN], int *hold) { char *tok = strtok(buf, " "); int mvt = 0; int i = 0; *hold = 1; /* set default hat position: neutral */ report[3] = 0x04; for (; tok != NULL; tok = strtok(NULL, " ")) { if (strcmp(tok, "--quit") == 0) return -1; for (i = 0; jmod[i].opt != NULL; i++) if (strcmp(tok, jmod[i].opt) == 0) { report[3] = (report[3] & 0xF0) | jmod[i].val; break; } if (jmod[i].opt != NULL) continue; if (!(tok[0] == '-' && tok[1] == '-') && mvt < 3) { errno = 0; report[mvt++] = (char)strtol(tok, NULL, 0); if (errno != 0) { fprintf(stderr, "Bad value:'%s'\n", tok); report[mvt--] = 0; } continue; } fprintf(stderr, "unknown option: %s\n", tok); } return 4; } void print_options(char c) { int i = 0; if (c == 'k') { printf(" keyboard options:\n" " --hold\n"); for (i = 0; kmod[i].opt != NULL; i++) printf("\t\t%s\n", kmod[i].opt); printf("\n keyboard values:\n" " [a-z] or\n"); for (i = 0; kval[i].opt != NULL; i++) printf("\t\t%-8s%s", kval[i].opt, i % 2 ? "\n" : ""); printf("\n"); } else if (c == 'm') { printf(" mouse options:\n" " --hold\n"); for (i = 0; mmod[i].opt != NULL; i++) printf("\t\t%s\n", mmod[i].opt); printf("\n mouse values:\n" " Two signed numbers\n" "--quit to close\n"); } else { printf(" joystick options:\n"); for (i = 0; jmod[i].opt != NULL; i++) printf("\t\t%s\n", jmod[i].opt); printf("\n joystick values:\n" " three signed numbers\n" "--quit to close\n"); } } int main(int argc, const char *argv[]) { const char *filename = NULL; int fd = 0; char buf[BUF_LEN]; int cmd_len; char report[8]; int to_send = 8; int hold = 0; fd_set rfds; int retval, i; if (argc < 3) { fprintf(stderr, "Usage: %s devname mouse|keyboard|joystick\n", argv[0]); return 1; } if (argv[2][0] != 'k' && argv[2][0] != 'm' && argv[2][0] != 'j') return 2; filename = argv[1]; if ((fd = open(filename, O_RDWR, 0666)) == -1) { perror(filename); return 3; } print_options(argv[2][0]); while (42) { FD_ZERO(&rfds); FD_SET(STDIN_FILENO, &rfds); FD_SET(fd, &rfds); retval = select(fd + 1, &rfds, NULL, NULL, NULL); if (retval == -1 && errno == EINTR) continue; if (retval < 0) { perror("select()"); return 4; } if (FD_ISSET(fd, &rfds)) { cmd_len = read(fd, buf, BUF_LEN - 1); printf("recv report:"); for (i = 0; i < cmd_len; i++) printf(" %02x", buf[i]); printf("\n"); } if (FD_ISSET(STDIN_FILENO, &rfds)) { memset(report, 0x0, sizeof(report)); cmd_len = read(STDIN_FILENO, buf, BUF_LEN - 1); if (cmd_len == 0) break; buf[cmd_len - 1] = '\0'; hold = 0; memset(report, 0x0, sizeof(report)); if (argv[2][0] == 'k') to_send = keyboard_fill_report(report, buf, &hold); else if (argv[2][0] == 'm') to_send = mouse_fill_report(report, buf, &hold); else to_send = joystick_fill_report(report, buf, &hold); if (to_send == -1) break; if (write(fd, report, to_send) != to_send) { perror(filename); return 5; } if (!hold) { memset(report, 0x0, sizeof(report)); if (write(fd, report, to_send) != to_send) { perror(filename); return 6; } } } } close(fd); return 0; }