Одной из приятных особенностей 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>.