Идея

Недавно купил длинный HDMI-кабель для просмотра киношек на телевизоре с комфортной кровати, а не сидя за ПК в углу.

Возникла проблема: часто вставать и ходить до ПК, чтобы поставить видео на паузу или изменить настройки плеера (качество, звук, озвучка). Захотелось чего-то компактного и простого, наподобие «Плей‑Пауза» пульта. Был вариант просто купить дешёвый Bluetooth‑комплект мышь‑клавиатура или только беспроводную мышь, Но это показалось мне неинтересным и простым.

Было принято решение разработать специфическое устройство для этих задач.

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

Во-первых, определим, какие функции и, как следствие, устройства ввода необходимы.

  1. В качестве устройства, позволяющего не только выполнять разовый «клик» по кнопке паузы, но и имеющего возможность передвигать курсор мыши, был выбран стик KY-023, наподобие тех, что применяются в контроллерах DualShock для PS4.

    Joystick KY-023
    Joystick KY-023

    KY-023 имеет:
    1) 2 аналоговых выхода: Ось X, Ось Y;
    2) 1 дискретный выход: Кнопка самого «стика».

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

    Таким образом получили конструкцию со следующим функционалом: правый «стик» должен отвечать за перемещение курсора (подобно как правый стик джойстика от «плойки» управляет обзором персонажа в играх); Левый стик должен будет управлять громкостью плеера и перемоткой видео (пока не реализован, в процессе разработки функций); внутренние кнопки «стиков» копируют ЛКМ и ПКМ.

  2. Т. к. только начинаю заниматься программированием, было решено использовать как контроллер плату Arduino. Почитав про особенности «камней», применяемых в «разношерстных» платформах, я выбрал это:

    Arduino pro mini на базе «камня» AtMega 168 (5V, 16MHz)
    Arduino pro mini на базе «камня» AtMega 168 (5V, 16MHz)

    Arduino pro mini на базе «камня» AtMega 168 (5V, 16MHz) в роли передатчика;

    Arduino pro micro на базе «камня» AtMega 32u4
    Arduino pro micro на базе «камня» AtMega 32u4














    Arduino pro micro на базе «камня» AtMega 32u4 в роли приемника и устройства, имеющего свойство двустороннего общения с ПК, определяется как HID-устройство (клавиатура, мышь).

  3. Необходимо было устройство приема‑передачи информации между платами Arduino. Выбрал радиомодули NRF24L01, работающие в диапазоне частот 2.4–2.5 ГГц. Также в связи с особенностями питания «камня» NRF потребовался адаптер со стабилизатором напряжения, способный использовать внешнее питание от 4.8V до 12V и подавать на плату NRF 3.3V.

    NRF24L01
    NRF24L01

    Немного характеристик:
    Напряжение питания: 1,9В – 3,6В;
    Интерфейс обмена данными: SPI;
    Частота приёма и передачи: 2,4 ГГц;
    Количество каналов: 128 с шагом 1МГц;
    Тип модуляции: GFSK;

    NRF24L01
    NRF24L01

    Скорость передачи данных: 250kbps, 1Mbps и 2Mbps;
    Чувствительность приёмника: -82 dBm;
    Расстояние приёма/передачи данных: 100м — прямая видимость; 30м — помещение;
    Коэффициент усиления антенны: 2dBm;
    Диапазон рабочей температуры: -40оС…+85оС;
    Организация сети на одном канале: 7 модулей (1 приёмник и 6 передатчиков).

Электросхема

На схеме не указан адаптер, но распиновка NRF и адаптера идентичные, и все необходимые сигналы прописаны в литографии на маске плат.

TX
TX
RX
RX

Программа

  1. Перво-наперво необходимо было научиться считывать и обрабатывать сигналы со стиков и управлять курсором напрямую с Arduino Pro Micro.

    #include <Mouse.h>
    
    const int X1_Pin = A0;
    const int Y1_Pin = A1;
    const int X2_Pin = A2;
    const int Y2_Pin = A3;
    
    const int SW1_Pin = 3;
    const int SW2_Pin = 2;
    
    int SW1_Stage;
    int SW2_Stage;
    
    int SW1;
    int SW2;
    
    const int Sp = 5;
    
    void setup() {
      Serial.begin(9600);
    
      Mouse.begin();
    
      pinMode(SW1_Pin, INPUT_PULLUP);
      pinMode(SW2_Pin, INPUT_PULLUP);
      pinMode(8, OUTPUT);
      pinMode(9, OUTPUT);
    
    }
    
    void loop() {
      //Доп пины питания
      digitalWrite(8, HIGH);
      digitalWrite(9, HIGH);
    
      //Чтение портов
      int x1 = analogRead(X1_Pin);
      int y1 = analogRead(Y1_Pin);
      int x2 = analogRead(X2_Pin);
      int y2 = analogRead(Y2_Pin);
    
      SW1_Stage = digitalRead(SW1_Pin);
      SW2_Stage = digitalRead(SW2_Pin);
    
      int x1pos, y1pos;
      int x2pos, y2pos;
    
      //Фильтр XY1
      if (x1 > 450 and x1 < 550)
        x1pos = 0;
      if (x1 >= 550)
        x1pos = map(x1, 550, 1023, 0, Sp);
      if (x1 <= 450)
        x1pos = map(x1, 450, 0, 0, -Sp);
      if (y1 > 450 and y1 < 550)
        y1pos = 0;
      if (y1 >= 550)
        y1pos = map(y1, 550, 1023, 0, Sp);
      if (y1 <= 450)
        y1pos = map(y1, 450, 0, 0, -Sp);
    
      //Обработка кнопки ЛКМ
      if (SW1_Stage == LOW)
        SW1 = 1;
      else
        SW1 = 0;
    
      //Фильтр XY2
      if (x2 > 450 and x2 < 550)
        x2pos = 0;
      if (x2 >= 550)
        x2pos = map(x2, 550, 1023, 0, Sp);
      if (x2 <= 450)
        x2pos = map(x2, 450, 0, 0, -Sp);
      if (y2 > 450 and y2 < 550)
        y2pos = 0;
      if (y2 >= 550)
        y2pos = map(y2, 550, 1023, 0, Sp);
      if (y2 <= 450)
        y2pos = map(y2, 450, 0, 0, -Sp);
    
      //Обработка кнопки ПКМ
      if (SW2_Stage == LOW)
        SW2 = 1;
      else
        SW2 = 0;
    
      //Управление курсором
      Mouse.move(x1pos, y1pos);
    
      if (SW1) {
        Mouse.press(MOUSE_LEFT);
      } else {
        Mouse.release(MOUSE_LEFT);
      }
      if (SW2) {
        Mouse.press(MOUSE_RIGHT);
      } else {
        Mouse.release(MOUSE_RIGHT);
      }
      /*/Отладка
        Serial.print(x1pos);
        Serial.print(":");
        Serial.print(y1pos);
        Serial.print(":");
        Serial.println(SW1_Stage);
        Serial.print(":");
        Serial.print(x2pos);
        Serial.print(":");
        Serial.print(y2pos);
        Serial.print(":");
        Serial.println(SW2_Stage);
       /*/
      delay(10);
    
    }
  2. Учимся «дружить» NRF‑ки и моргать лампочкой по нажатию кнопки.

    TX:

    #include <SPI.h>
    #include "nRF24L01.h"
    #include "RF24.h"
    
    RF24 radio(9, 10);
    byte address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"};
    byte button = 3;
    byte transmit_data[1];
    byte latest_data[1];
    boolean flag;
    void setup() {
      Serial.begin(9600);
    
      pinMode(button, INPUT_PULLUP);
      radio.begin();
      radio.setAutoAck(1);
      radio.setRetries(0, 15);
      radio.enableAckPayload();
      radio.setPayloadSize(32);
      radio.openWritingPipe(address[0]);
      radio.setChannel(0x60);
      radio.setPALevel (RF24_PA_MAX);
      radio.setDataRate (RF24_250KBPS);
      radio.powerUp();
      radio.stopListening();
    }
    
    void loop() {
      transmit_data[0] = !digitalRead(button);
    
      for (int i = 0; i < 3; i++) {
        if (transmit_data[i] != latest_data[i]) {
          flag = 1;
          latest_data[i] = transmit_data[i];
        }
      }
    
      if (flag == 1) {
        radio.powerUp();
        radio.write(&transmit_data, sizeof(transmit_data));
        flag = 0;
        radio.powerDown();
      }
    }

    RX:

    #include <SPI.h>
    #include "nRF24L01.h"
    #include "RF24.h"
    #include <Servo.h>
    
    RF24 radio(9, 10);
    byte recieved_data[1];
    byte L = 13;
    byte address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"};
    
    void setup() {
      Serial.begin(9600);
      pinMode(L, OUTPUT);
      radio.begin();
      radio.setAutoAck(1);
      radio.setRetries(0, 15);
      radio.enableAckPayload();
      radio.setPayloadSize(32);
      radio.openReadingPipe(1, address[0]);
      radio.setChannel(0x60);
      radio.setPALevel (RF24_PA_MAX);
      radio.setDataRate (RF24_250KBPS);
      radio.powerUp();
      radio.startListening();
    }
    
    void loop() {
      byte pipeNo;
      while ( radio.available(&pipeNo)) {
        radio.read(&recieved_data, sizeof(recieved_data));
        digitalWrite(L, recieved_data[0]);
      }
    }
  3. А теперь самое интересное! Объединить эти два кода. Не обошлось и без плясок с бубном, и убегающим в «самоволку» курсором.

    TX:

    #include <SPI.h>
    #include "nRF24L01.h"
    #include "RF24.h"
    
    RF24 radio(9, 10);        //Создать модуль на пинах 9 и 10
    
    byte address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"};   //возможные номера труб
    
    const int XL_Pin = A0;    //Аналоговый вход левого стика ось X
    const int YL_Pin = A1;    //Аналоговый вход левого стика ось Y
    const int XR_Pin = A2;    //Аналоговый вход правого стика ось X
    const int YR_Pin = A3;    //Аналоговый вход правогоо стика ось Y
    byte LBM_Pin = 2;         //Цифровой вход ЛКМ
    byte RBM_Pin = 3;         //Цифровой вход ПКМ
    
    byte transmit_data[6];    //Массив, хранящий передаваемые данные
    byte latest_data[6];      //Массив, хранящий последние переданные данные
    
    boolean flag;             //Флаг отправки данных
    
    void setup() {
      Serial.begin(9600);               //Открываем порт для связи с ПК
    
      pinMode(XL_Pin, INPUT);           //Настройка порта левого стика ось X
      pinMode(YL_Pin, INPUT);           //Настройка порта левого стика ось Y
      pinMode(XR_Pin, INPUT);           //Настройка порта правого стика ось X
      pinMode(YR_Pin, INPUT);           //Настройка порта правого стика ось Y
      pinMode(LBM_Pin, INPUT_PULLUP);   //Настройка порта ЛКМ
      pinMode(RBM_Pin, INPUT_PULLUP);   //Настройка порта ЛКМ
    
      pinMode(7, OUTPUT);               //Доп питание
      pinMode(8, OUTPUT);               //Доп питание
      digitalWrite(7, HIGH);            //Доп пин питания левого стика
      digitalWrite(8, HIGH);            //Доп пин питания правого стика
    
      radio.begin();                        //Активировать модуль
      radio.setAutoAck(1);                  //Режим подтверждения приёма, 1 вкл 0 выкл
      radio.setRetries(0, 15);              //Время между попыткой достучаться, число попыток
      radio.enableAckPayload();             //Разрешить отсылку данных в ответ на входящий сигнал
      radio.setPayloadSize(32);             //Размер пакета, в байтах
      radio.openWritingPipe(address[0]);    //Труба 0, открыть канал для передачи данных
      radio.setChannel(0x70);               //Выбираем канал (в котором нет шумов!)
    
      radio.setPALevel (RF24_PA_MAX);       //Уровень мощности передатчика. На выбор RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
      radio.setDataRate (RF24_250KBPS);     //Скорость обмена. На выбор RF24_2MBPS, RF24_1MBPS, RF24_250KBPS должна быть одинакова на приёмнике и передатчике!при самой низкой скорости имеем самую высокую чувствительность и дальность!!
    
      radio.powerUp();                      //Начать работу
      radio.stopListening();                //Не слушаем радиоэфир, мы передатчик
    
    }
    
    void loop() {
    
      //Чтение портов
    
      transmit_data[0] = map(analogRead(XL_Pin), 0, 1023, 0, 255);    //Записать значение XL на 1 место в массиве
      transmit_data[1] = map(analogRead(YL_Pin), 0, 1023, 0, 255);    //Записать значение YL на 2 место в массиве
      transmit_data[2] = map(analogRead(XR_Pin), 0, 1023, 0, 255);    //Записать значение XR на 3 место в массиве
      transmit_data[3] = map(analogRead(YR_Pin), 0, 1023, 0, 255);    //Записать значение YR на 4 место в массиве
      transmit_data[4] = !digitalRead(LBM_Pin);                       //Записать сигнал ЛКМ на 5 место в массиве
      transmit_data[5] = !digitalRead(RBM_Pin);                       //Записать сигнал ПКМ на 5 место в массиве
    
      radio.powerUp();                                      // включить передатчик
      radio.write(&transmit_data, sizeof(transmit_data));   // Отправить по радио
      for (int i = 0; i < 6; i++) {                         // В цикле от 0 до числа каналов
        if (transmit_data[i] != latest_data[i]) {           // Если есть изменения в transmit_data
          flag = 1;                                         // Поднять флаг отправки по радио
          latest_data[i] = transmit_data[i];                // Запомнить последнее изменение
        }
      }
    
      if (flag == 1) {
        radio.powerUp();                                      // Включить передатчик
        radio.write(&transmit_data, sizeof(transmit_data));   // Отправить по радио
        flag = 0;                                             // Опустить флаг
        radio.powerDown();                                    // Выключить передатчик
      }
      /*/Отладка, проверка сигнала на A0,A1,A2,A3
          Serial.print(analogRead(XL_Pin));
          Serial.print(":");
          Serial.print(analogRead(YL_Pin));
          Serial.print(":");
          Serial.println(!digitalRead(2));
          Serial.print("\n");
          Serial.print(analogRead(XR_Pin));
          Serial.print(":");
          Serial.print(analogRead(YR_Pin));
          Serial.print(":");
          Serial.println(!digitalRead(3));
          delay(10);
      */
    
    }
    //Список занятых пинов: A0,A1,A2,A3,1,2,3,4,7,8,9,10,11,12,13
    //Список передаваемых пинов: A0,A1,A2,A3,2,3
    //Список доп пинов питания 7,8 (В итоговой версии необходимо объединить на 5V, Gnd объединить)

    RX:

    #include <SPI.h>
    #include "nRF24L01.h"
    #include "RF24.h"
    #include <Mouse.h>
    
    RF24 radio(9, 10);      //Создать модуль на пинах 9 и 10
    
    byte recieved_data[6];  //Массив принятых данных
    
    byte XLP;               //Значения левого стика ось X
    byte YLP;               //Значения левого стика ось Y
    byte XRP;               //Значения правого стика ось X
    byte YRP;               //Значения правого стика ось Y
    int LBMP;               //Значения ЛКМ
    int RBMP;               //Значения ПКМ
    
    const int Sp = 30;      //Скорость курсора (10,20,30,40,50,60,70) чем больше, тем медленне
    
    byte address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"}; //возможные номера труб
    
    void setup() {
      Serial.begin(9600);   //Открываем порт для связи с ПК
    
      Mouse.begin();
    
      radio.begin();  //Активировать модуль
      radio.setAutoAck(1);                    // Режим подтверждения приёма, 1 вкл 0 выкл
      radio.setRetries(0, 15);                // Время между попыткой достучаться, число попыток)
      radio.enableAckPayload();               // Разрешить отсылку данных в ответ на входящий сигнал
      radio.setPayloadSize(32);               // Размер пакета, в байтах
      radio.openReadingPipe(1, address[0]);   // Слушаем трубу 0
      radio.setChannel(0x70);                 // Выбираем канал (в котором нет шумов!)
    
      radio.setPALevel (RF24_PA_MAX);         // Уровень мощности передатчика. На выбор RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
      radio.setDataRate (RF24_250KBPS);       // Скорость обмена. На выбор RF24_2MBPS, RF24_1MBPS, RF24_250KBPS должна быть одинакова на приёмнике и передатчике! Gри самой низкой скорости имеем самую высокую чувствительность и дальность!!
    
      radio.powerUp();                        // Начать работу
      radio.startListening();                 // Начинаем слушать эфир, мы приёмный модуль
    
    }
    
    void loop() {
      byte pipeNo;
      while ( radio.available(&pipeNo)) {                     // Есть входящие данные
        radio.read(&recieved_data, sizeof(recieved_data));    // Читаем входящий сигнал
    
        XLP = recieved_data[0];   //Читаем входящие данные оси X левого стика и записываем значение
        YLP = recieved_data[1];   //Читаем входящие данные оси Y левого стика и записываем значение
        XRP = recieved_data[2];   //Читаем входящие данные оси X правого стика и записываем значение
        YRP = recieved_data[3];   //Читаем входящие данные оси Y правого стика и записываем значение
        LBMP = recieved_data[4];  //Читаем входящие данные ЛКМ и записываем значение
        RBMP = recieved_data[5];  //Читаем входящие данные ПКМ и записываем значение
    
      }
    
      int xLpos, yLpos;
      int xRpos, yRpos;
    
      //Фильтр XYL
      if (XLP > 120 and XLP < 130)
        xLpos = 0;
      if (XLP >= 130)
        xLpos = map(XLP, 130, 255, 0, 80);
      if (XLP <= 120)
        xLpos = map(XLP, 120, 0, 0, -80);
    
      if (YLP > 120 and YLP < 130)
        yLpos = 0;
      if (YLP >= 130)
        yLpos = map(YLP, 130, 255, 0, 80);
      if (YLP <= 120)
        yLpos = map(YLP, 120, 0, 0, -80);
    
      //Фильтр XYR
      if (XRP > 120 and XRP < 130)
        xRpos = 0;
      if (XRP >= 130)
        xRpos = map(XRP, 130, 255, 0, 80);
      if (XRP <= 120)
        xRpos = map(XRP, 120, 0, 0, -80);
    
      if (YRP > 120 and YRP < 130)
        yRpos = 0;
      if (YRP >= 130)
        yRpos = map(YRP, 130, 255, 0, 80);
      if (YRP <= 120)
        yRpos = map(YRP, 120, 0, 0, -80);
    
      //Управление курсором
      Mouse.move(xRpos / Sp, yRpos / Sp);
    
      if (LBMP) {
        Mouse.press(MOUSE_LEFT);
      } else {
        Mouse.release(MOUSE_LEFT);
      }
      if (RBMP) {
        Mouse.press(MOUSE_RIGHT);
      } else {
        Mouse.release(MOUSE_RIGHT);
      }
      /*//Отладка A0,A1,A2,A3
        Serial.print(XLP);
        Serial.print(":");
        Serial.print(YLP);
        Serial.print(":");
        Serial.println(LBMP);
        Serial.print("\n");
        Serial.print(XRP);
        Serial.print(":");
        Serial.print(YRP);
        Serial.print(":");
        Serial.println(RBMP);
        Serial.print("\n");
        Serial.print(xRpos);
        Serial.print(":");
        Serial.print(yRpos);
        delay(5);
      */
    }
    //Список занятых пинов: 9,10,14,15,16
    //Список передаваемых пинов: A0,A1,A2,A3,2,3
    
    //LBM-LeftButtonMouse(ЛКМ) RBM-RightButtonMouse(ПКМ)

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

В обоих случаях последовательность действий при подключении радиомодулей одинакова: от адаптера питания NRF отпаиваем колодку 6pin(F) и припаиваем напрямую NRF гребёнкой 6pin(M), короткими проводами припаиваемся к платам Arduino. Аккуратно и компактно складываем и сжимаем «бутерброд». ПОЛЕЗНО! Вокруг антенны для каждого модуля NRF намотать пару‑тройку витков монтажного провода (в моём случае 0.2mm^2). Загоняем все в широкую термоусадку и фиксируем.

Для передатчика припаиваем «стики» на удобном вам расстоянии и также все фиксируем термоусадкой.

RX
RX
RX
RX
TX (Joystick)
TX (Joystick)
TX (Joystick)
TX (Joystick)

Итог

Получился рабочий прототип GamePad, похожий на DualShock.

Баги

ВАЖНО! Сначала подключать к питанию передатчик, после — приемник к ПК. Т.к. имеется баг в виде убегающего курсора при обратном порядке подключения, но он сразу же подчиняется, как только подаёшь питание на передатчик.

Высокая чувствительность стиков и резкое перемещение курсора (сложно попадать по кнопкам диалоговых окон).

Дальнейшее развитие

  1. Сейчас в разработке корпус для данного прототипа.

  2. Также требуется создать систему питания на основе аккумулятора 18 650 с модулями заряда и преобразователя напряжения до 5V.

  3. В планах добавить энкодер для имитации колёсика мыши; ряд тактовых кнопок с различными функциями, расширить код — придать действия для осей левого стика.

Заметки

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

Комментарии (16)


  1. voldemar_d
    21.07.2023 21:02
    +2

    У Arduino Pro Micro чип микроконтроллера называется 32u4, а не 23u4. Опечатка, наверное.

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

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


    1. numb13
      21.07.2023 21:02

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


      1. voldemar_d
        21.07.2023 21:02

        Отладочные сообщения в коде закомментированы.


    1. SamDrankRaven Автор
      21.07.2023 21:02
      +1

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


  1. voldemar_d
    21.07.2023 21:02
    +1

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


  1. Keroro
    21.07.2023 21:02
    +1

    Я аналогичную проблему решил аэро-мышью. Нажимаешь кнопку, и взмахи пультом управляют движением курсора (со временем очень точно можно наловчиться это делать). Кнопочки переназначил на смену аудио-дорожек, субтитров. Плэй\Пауза\Громкость само собой там тоже есть. Одна из кнопок программируется для включения\выключения ТВ по инфракрасному каналу (есть не во всех моделях таких пультов).


  1. NNMTM
    21.07.2023 21:02
    +4

    Я, конечно, за DIY.
    Но готовый беспроводной геймпад очень удобен в качества пульта управления для компьютера - кинотеатра/игровой станции.
    Использую Logitech Cordless Rumblepad 2 и JoyToKey.
    Вот раскладка кнопок, позволяет по быстрому рулить в Total commander и многих других приложениях.

    В медиаплеере (MPC-HC) вообще полный контроль - перемотка разной продолжительности с модификаторами ALT/CTRL/Shift, переключение субтитров Shift + Up/Down, переключение звуковых дорожек CTRL + Up/Down. На CTRL + Shift +Up/Down повесил переключение шейдеров и создал несколько профилей с разной гаммой, получилось переключение гаммы.
    Для игр в JoyToKey назначено правило C:\Games - при запуске пририложений из этой папки, JoyToKey отключается и не мешает.


    1. SamDrankRaven Автор
      21.07.2023 21:02
      +1

      Да, есть такое, но захотелось попробовать сделать своё)


  1. nikolz
    21.07.2023 21:02

    можно сделать еще круче и проще.

    Подключаем к ПК TOF датчик. Получаем бесконтактный джойстик.

    Управляем им движениями руки или ноги - вправо влево, вверх вниз.

    Можно просто через микрофон управлять голосом лежа на диване.


  1. Grey83
    21.07.2023 21:02
    +2

    Также требуется создать систему питания на основе аккумулятора 18 650 с модулями заряда и преобразователя напряжения до 5V.
    Любая плата для повербанка.

    Я для зарядки головного светильника (он не имеет встроенного контроллера заряда для своего Li-Ion аккумулятора, а внешнее ЗУ — неудобная фигня) использую плату на основе микросхемы ip5306 (платки у некоторых произодителей маркируются как mh-cd42).
    Размеры у платки примерно с первую фалангу пальца (24*16мм) и имеется светодиодный индикатор уровня заряда из 4 светодиодов.
    Единственные проблемы: ток заряда — 2А, при малом потреблении (порядка 50мА) может отключить преобразователь (на нагрузку способна выдавать до 2А).

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


    1. SamDrankRaven Автор
      21.07.2023 21:02
      +1

      О, спасибо за идею!


    1. SamDrankRaven Автор
      21.07.2023 21:02
      +1

      Думал использовать этот модуль. Компактный и надёжный, к тому же есть возможность замены АКБ без перепайки.
      Модуль питания WeMos на аккумуляторе Li-Ion 18650 купить оптом и в розницу в СompactTool с доставкой по Москве и России (compacttool.ru)


  1. Diordna
    21.07.2023 21:02

    Ребята подскажите пожалуйста а можно сделать геймпад которого допустим на четыре кнопки больше чем на геймпаде для Sony PlayStation с учётом стиков?


    1. SamDrankRaven Автор
      21.07.2023 21:02

      Почему нет? Два путя:

      1)найди и купить какого нить мутант с Китая

      2) Подобрать контроллер с достаточно необходимым кол-вом входов/выходов и добавить свои кнопки.


    1. NNMTM
      21.07.2023 21:02

      Проводных геймпадов/джойстиков куча вариантов на микроконтроллере, проект Mjoy например. С беспроводными сложнее.

      Но можно и проще:

      1. Берёшь USB-клавиатуру (лучше нормального бренда, например Logitech), проверяешь что выбранные клавиши будут работать одновременно.

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

      3. Плату от клавиатуры помещаешь внутрь джойстика и сверяясь с плёнками клавиатуры распаиваешь провода к кнопкам.

      4. Можно взять и беспроводную/Bluetooth клавиатуру.

      Я когда-то давно, перейдя со спектрума на P1 и озадачившись отсутствием геймпадов для PC, делал вывод из клавиатуры матрицы кнопок 12345 x 1QAZ т.е. 9 проводов, 20 кнопок макс.
      В эмуляторе NES эти кнопки ремапились на джойстик. Предварительно долго тыкал клавиши одновременно, выясняя возможности клавиатуры (Chikony KB-5311). Получилось неплохо - до 5 кнопок одновременно гарантированно, т.е. два направления на крестовине + 3 кнопки. И больше, в зависимости от положения кнопок в матрице.


      1. NNMTM
        21.07.2023 21:02
        +1

        P.S. Ещё интересная штука по теме - метод ввода текста с джойстика - Daisywheel, используется в Steam Big Picture. Порт на JS и ссылка на тестовый сайт - https://github.com/likethemammal/daisywheeljs
        Если кто прикрутит возможность использовать в Windows для всех приложений - просигнальте.