Digg StumbleUpon LinkedIn YouTube Flickr Facebook Twitter RSS Reset

Модернизация геймпада Genius MaxFire G-08X

Одной из приятных особенностей Arduino Pro Micro 5V 16MHz является наличие «железного» USB в AtMEGA32U4 и в ардуино IDE есть примеры скетчей, в категории USB, как использовать ардуинку в качестве клавиатуры или мыши. Я таким образом модернизировал свой геймпад Genius MaxFire G-08X и сделал из него пульт-клавиатуру для управления медиацентром KODI. С таким пультом очень удобно и быстро перемещаться по менюшкам, т.к. руки сами помнят куда нажимать.

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

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

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

Genius MaxFire G-08X использует схему 2 оси 8 кнопок и вот этих 8 кнопок просто впритык хватало, поэтому я добавил ещё 4 кнопки. Почему 4, да по количеству оставшихся свободных портов на ардуинке.Голубым цветом здесь обозначены доступные порты ввода-вывода, но т.к. RX TX используются для обмена данными по USB, то их трогать нельзя. Остаются 16 ножек, на 2 оси (кнопки вверх, вниз, влево, вправо) 4 кнопки и 12 кнопок (8 родных и 4 дополнительных).

Исследование библиотеки Keyboard в Arduino IDE и анализ скетчей из примеров показал доступные функции передачи нажатий клавиатуры:

Keyboard.begin();
Keyboard.write();
Keyboard.press();
Keyboard.releaseAll();

Этого вполне оказалось достаточно для написания управляющего кода. Аргументами для функций Keyboard.press и write являются HEX коды ASCII символов и зарезервированные имена:

Оператор Клавиша Её_HEX_код

#define KEY_LEFT_CTRL 0x80
#define KEY_LEFT_SHIFT 0x81
#define KEY_LEFT_ALT 0x82
#define KEY_LEFT_GUI 0x83
#define KEY_RIGHT_CTRL 0x84
#define KEY_RIGHT_SHIFT 0x85
#define KEY_RIGHT_ALT 0x86
#define KEY_RIGHT_GUI 0x87
#define KEY_UP_ARROW 0xDA
#define KEY_DOWN_ARROW 0xD9
#define KEY_LEFT_ARROW 0xD8
#define KEY_RIGHT_ARROW 0xD7
#define KEY_BACKSPACE 0xB2
#define KEY_TAB 0xB3
#define KEY_RETURN 0xB0
#define KEY_ESC 0xB1
#define KEY_INSERT 0xD1
#define KEY_DELETE 0xD4
#define KEY_PAGE_UP 0xD3
#define KEY_PAGE_DOWN 0xD6
#define KEY_HOME 0xD2
#define KEY_END 0xD5
#define KEY_CAPS_LOCK 0xC1
#define KEY_F1 0xC2
#define KEY_F2 0xC3
#define KEY_F3 0xC4
#define KEY_F4 0xC5
#define KEY_F5 0xC6
#define KEY_F6 0xC7
#define KEY_F7 0xC8
#define KEY_F8 0xC9
#define KEY_F9 0xCA
#define KEY_F10 0xCB
#define KEY_F11 0xCC
#define KEY_F12 0xCD

В данном случае нужно использовать именно KEY_F12, а не 0xCD

Коды ASCII можно посмотреть например здесь

http://www.industrialnets.ru/files/misc/ascii.pdf

Сначала я выполнил программу через применение функции Keyboard.write с задержками между нажатиями,

#include "Keyboard.h"

#define LEFT_ARROW 2 // LEFT_ARROW, Влево
#define RIGHT_ARROW 3 // RIGHT_ARROW, Вправо
#define UP_ARROW 4 // UP_ARROW, Вверх
#define DOWN_ARROW 5 // DOWN_ARROW, Вниз
#define button1 6
#define button2 7
#define button3 8
#define button4 9
#define button5 10
#define button6 15
#define button7 14
#define button8 16
#define buttonA0 A0
#define buttonA1 A1
#define buttonA2 A2
#define buttonA3 A3
const unsigned int DelayTimeRepeatPressingTheArrowsUpDown = 200;
const unsigned int DelayTimeRepeatPressingTheArrows = 200;
const unsigned int DelayTimeRepeatPressingVolume = 100;
const unsigned int DelayTimeRepeatPressing = 300;

void setup() {

pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, INPUT_PULLUP);
pinMode(7, INPUT_PULLUP);
pinMode(8, INPUT_PULLUP);
pinMode(9, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
pinMode(15, INPUT_PULLUP);
pinMode(16, INPUT_PULLUP);
pinMode(A0, INPUT_PULLUP);
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP);
Keyboard.begin();
}

void loop() {
if (digitalRead(LEFT_ARROW) == LOW) {
Keyboard.write(KEY_LEFT_ARROW);
delay(DelayTimeRepeatPressingTheArrows);
}
if (digitalRead(RIGHT_ARROW) == LOW) {
Keyboard.write(KEY_RIGHT_ARROW);
delay(DelayTimeRepeatPressingTheArrows);
}
if (digitalRead(UP_ARROW) == LOW) {
Keyboard.write(KEY_UP_ARROW);
delay(DelayTimeRepeatPressingTheArrowsUpDown);
}
if (digitalRead(DOWN_ARROW) == LOW) {
Keyboard.write(KEY_DOWN_ARROW);
delay(DelayTimeRepeatPressingTheArrowsUpDown);
}
if (digitalRead(button1) == LOW) { // button1, Enter, ввод
Keyboard.write(KEY_RETURN);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(button2) == LOW) { // button2, Escape
Keyboard.write(KEY_ESC);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(button3) == LOW) { // button3, Space, Play
Keyboard.write(0x20);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(button4) == LOW) { // button4, X, Stop
Keyboard.write(0x78);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(button5) == LOW) { // button5, +, Volume Up
Keyboard.write(0x3d);
delay(DelayTimeRepeatPressingVolume);
while(digitalRead(button5) == LOW) {
Keyboard.write(0x3d);
delay(10);
}
}
if (digitalRead(button6) == LOW) { // button6, Page Up, След. аудио дорожка
Keyboard.write(KEY_PAGE_UP);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(button7) == LOW) { // button7, -, Volume Down
Keyboard.write(0x2d);
delay(DelayTimeRepeatPressingVolume);
while(digitalRead(button7) == LOW) {
Keyboard.write(0x2d);
delay(10);
}
}
if (digitalRead(button8) == LOW) { // button8, L, Next subtitle
Keyboard.write(0x6c);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(buttonA0) == LOW) { // buttonA0, m, Экранное меню OSD
Keyboard.write(0x6d);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(buttonA1) == LOW) { // buttonA1, F8, Mute
Keyboard.write(KEY_F8);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(buttonA2) == LOW) { // buttonA2, TAB, Fullscreen playback
Keyboard.write(KEY_TAB);
delay(DelayTimeRepeatPressing);
}
if (digitalRead(buttonA3) == LOW) { // buttonA3, T, Вкл/Выкл субтитров
Keyboard.write(0x74);
delay(DelayTimeRepeatPressing);
}

}

но потом сделал программу через Keyboard.press() и Keyboard.releaseAll(),

#include "Keyboard.h"

#define LEFT_ARROW 2
#define RIGHT_ARROW 3
#define UP_ARROW 4
#define DOWN_ARROW 5
#define button1 6
#define button2 7
#define button3 8
#define button4 9
#define button5 10
#define button6 15
#define button7 14
#define button8 16
#define buttonA0 A0
#define buttonA1 A1
#define buttonA2 A2
#define buttonA3 A3

void setup() {

pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, INPUT_PULLUP);
pinMode(7, INPUT_PULLUP);
pinMode(8, INPUT_PULLUP);
pinMode(9, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
pinMode(15, INPUT_PULLUP);
pinMode(16, INPUT_PULLUP);
pinMode(A0, INPUT_PULLUP);
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP);
Keyboard.begin();
}

void loop() {
if (digitalRead(LEFT_ARROW) == LOW) {
Keyboard.press(KEY_LEFT_ARROW);
delay(100);
while (digitalRead(LEFT_ARROW) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(RIGHT_ARROW) == LOW) {
Keyboard.press(KEY_RIGHT_ARROW);
delay(100);
while (digitalRead(RIGHT_ARROW) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(UP_ARROW) == LOW) {
Keyboard.press(KEY_UP_ARROW);
delay(100);
while (digitalRead(UP_ARROW) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(DOWN_ARROW) == LOW) {
Keyboard.press(KEY_DOWN_ARROW);
delay(100);
while (digitalRead(DOWN_ARROW) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button1) == LOW) { // button1, Enter, ввод
Keyboard.press(KEY_RETURN);
delay(100);
while (digitalRead(button1) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button2) == LOW) { // button2, Escape
Keyboard.press(KEY_ESC);
delay(100);
while (digitalRead(button2) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button3) == LOW) { // button3, Space, Play
Keyboard.press(0x20);
delay(100);
while (digitalRead(button3) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button4) == LOW) { // button4, X, Stop
Keyboard.press(0x78);
delay(100);
while (digitalRead(button4) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button5) == LOW) { // button5, +, Volume Up
Keyboard.press(0x3d);
delay(100);
while (digitalRead(button5) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button6) == LOW) { // button6, Page Up, След. аудио дорожка, настроить в keymap.xml
Keyboard.press(KEY_PAGE_UP);
delay(100);
while (digitalRead(button6) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button7) == LOW) { // button7, -, Volume Down
Keyboard.press(0x2d);
delay(100);
while (digitalRead(button7) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(button8) == LOW) { // button8, L, Next subtitle
Keyboard.press(0x6c);
delay(100);
while (digitalRead(button8) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(buttonA0) == LOW) { // buttonA0, m, Экранное меню OSD
Keyboard.press(0x6d);
delay(100);
while (digitalRead(buttonA0) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(buttonA1) == LOW) { // buttonA1, F8, Mute
Keyboard.press(KEY_F8);
delay(100);
while (digitalRead(buttonA1) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(buttonA2) == LOW) { // buttonA2, TAB, Fullscreen playback
Keyboard.press(KEY_TAB);
delay(100);
while (digitalRead(buttonA2) == LOW);
Keyboard.releaseAll();
}
if (digitalRead(buttonA3) == LOW) { // buttonA3, T, Вкл/Выкл субтитров
Keyboard.press(0x74);
delay(100);
while (digitalRead(buttonA3) == LOW);
}
Keyboard.releaseAll();

}

т.к. в таком подходе есть преимущества. Кнопки в данном случае нажимаются как на физической клавиатуре, а обработкой их нажатия занимается сама система или KODI. Что это значит? Отвечаю. Например находясь в списке видео файлов вы нажимаете стрелочку вниз и держите её, тем самым вы перемещаетесь в конец списка и курсор на этом останавливается. При использовании Keyboard.write с задержкой нажатие посылается снова и снова, таким образом при нажатии на стрелку вниз вы будете доходить до низа списка, затем курсор окажется сверху и снова побежит вниз и так до бесконечности.

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

Схемотехника

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

Фото 1 — внешний вид ардуинки

На плате есть джампер J1 (который запаян), это перемычка для обхода внутреннего стабилизатора напряжения, который используется для питания с RAW входа, а так как у нас питание с USB, то его надо закоротить (иначе будет VCC в районе 4,6В).

Фото 2 — Подпайка к дорожкам

Фото 3 — разметка и сверление отверстий для новых кнопок

Разметил «на глазок», но всё получилось ровно.

Фото 4 — приклеил кнопки на Момент

И термоклей впоследствии, для временной фиксации, т.к. момент долго сохнет, но не отваливается со временем, как термоклей.

Фото 5 — вид кнопочек снаружи

Кнопочки разные, т.к. не было одинаковых в наличии.

Фото 6 — готовая борода из проводов

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

Программирование

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

Использованные кнопки клавиатуры:

1) button1, Enter, ввод
2) button2, Escape
3) button3, Space, Play
4) button4, X, Stop
5) button5, +, Volume Up
6) button6, PageUp, След. аудио дорожка, настроить в keymap.xml
7) button7, -, Volume Down
8) button8, L, Next subtitle
9) buttonA0, m, Экранное меню OSD
10) buttonA1, F8, Mute
11) buttonA2, TAB, Fullscreen playback
12) buttonA3, T, Вкл/Выкл субтитров

Вообще на buttonA3 я планировал установить функцию кнопки «menu» (находится рядом с правым CTRL), но пока не разобрался как это сделать.

А субтитры выключаются простым перебором кнопкой L.

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

в Windows C:\Program Files (x86)\Kodi\system\keymaps\keyboard.xml
в Xubuntu /usr/share/kodi/system/keymaps/keyboard.xml
В разделе

<FullscreenVideo>
<keyboard>
Добавьте строку или измените существующую на <pageup>AudioNextLanguage</pageup>
или впишите свою кнопку вместо pageup, можете ту которая есть в <global><keyboard> но нет в <FullscreenVideo>.