Реализация USB джойстика

USB-джойстик - стандартный пример реализации HID-устройства. В дескрипторе HID-устройства описывается формат посылки с джойстика, по которому драйвер в ОС должен разобрать посылку на стороне компьютера. Для джойстиков там есть специальные поля, причем есть как просто абстрактные оси X/Y, так и определенные Rudder/Aileron, предназначенные для целей привязки к управлению летающей техникой.

Основной этап конструирования такого джойстика именно формирование верного HID-дескриптора. В остальном отличий нет никаких: все реализуется на основе любого стандартного примера HID-устройства.

Преобразователь PPM-USB

Данный преобразователь повторяет по своим функциям то, что предоставляет обычный USB-шнурок для аппаратуры радиоуправления. На вход он получает PPM сигнал управления, а для компьютера представляется обычным HID устройством типа джойстик. Одна особенность: PPM сигнал в моем случае берется не с разъема пульта управления, а с приемника. Это позволяет обойтись без проводов, что в моем случае показалось очень удобным.

В качестве основы была взята плата управления на LPC134x. Было внесено минимальное изменение: соединено питание, поступающее с USB с линией питания разъемов сервомашинок. Это сделано простым замыканием выводов 1 и 2 диода Шоттки BAT54C.

В HID-дескрипторе, который описывает структуру посылки компьютеру были заданы аналоговые оси Aileron, Elevator, Throttle, Rudder. На каждую из них выведен соответствующий канал передатчика. Но здесь и начались танцы: Газ и руль направления работали, а еще 2 канала (идущих в посылке первыми) упорно оказывались видется. В начале я их задал как X-Y оси, затем попытался перевести на Slider/Dial. Но это не дало результата, так же обменял их с 2мя видимыми, что не дало результата.

Вот мой сгенерированный дескриптор:

    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    //0x15, 0x00,                    // LOGICAL_MINIMUM (0)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x05, 0x02,                    //   USAGE_PAGE (Simulation Controls)

    0x09, 0xb0,                    //   USAGE (Aileron)
	0x15, 0x81,                    //   LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	
    0x09, 0xb8,                    //   USAGE (Elevator)
	0x15, 0x81,                    //   LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	
    0x09, 0xbb,                    //   USAGE (Throttle)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)

    0x09, 0xba,                    //   USAGE (Rudder)
    0x15, 0x81,                    //   LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)

    0xc0,                          //   END_COLLECTION

Заставить его работать у меня так и не получилось. В итоге пришлось взять дескриптор из проекта MJoy, урезав до необходимых мне 4 каналов. С ним все завелось без переделок. Рабочий дескриптор:

    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x15, 0x00,                    // LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              // LOGICAL_MAXIMUM (255)
    0x75, 0x08,                    // REPORT_SIZE (8)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x82,                    //     INPUT (Data,Var,Abs,Vol)
    0xc0,                          //   END_COLLECTION
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x32,                    //     USAGE (Z)
    0x09, 0x33,                    //     USAGE (Rx)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x82,                    //     INPUT (Data,Var,Abs,Vol)
    0xc0,                          //   END_COLLECTION

Джойстик в стиле Wii

Когда я случайно наткнулся на беспроводной джойстик Nunchuck для приставки Wii, мне сразу понравилась эта концепция. Во-первых встроенный датчик положения (в данном случае акселерометр, во-вторых 2 кнопки и мини-джойстик под палец. В третьих: беспроводной интерфейс (но это не относится к фирменным нунчакам).

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

К моей плате джойстик подключается через стандартный разъем внешних I2C устройств. Код для работы с джойстиком по документации был написан заранее. Оставалось только проверить на живую. Вот здесь-то и начались проблемы.

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

nunchuck_connect.jpg (800×445)

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

nunchuck_trouble.jpg (800×505)

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

В качестве конечного применения было реализовано 2 программы:

  1. Мышка. Кнопки реализуют те же функции что и в обычной мышке, мало того, совпадают по пальцам. Мини-джойстик управляет курсором мыши. Потенциально акселерометр можно использовать для реализации прокрутки. HID дескриптор стандартный.
  2. Игровой джойстик. Представляется 4 аналоговыми осями (2 оси - джойстик, 2 оси - наклоны) и 2 кнопками. Применение данного набора уже зависит от конечного приложения или игры, в которых возможна настройка управления. Дескриптор совпадает с предыдущей реализацией.

Ссылки:

Original: http://igorkov.org/usbjoy,
Author: igorkov