Знаю, что многие любители самоделок когда-либо пытались сделать собственную USB клавиатуру и/или мышь для автоматизации отправки команд. Это видно по количеству вопросов на данную тематику на Stack Overflow. Применений такого рода девайсам можно придумать много. От простейшего "дёргателя мышкой", чтобы компьютер не уходил в спящий режим (создавать имитацию активности пользователя), до управления различными устройствами. В моем случае девайс предназначен для управления системой "умная дача". Вернее, для тестирования ее функций. По нажатию кнопки открывается соответствующий файл с SD карты, и его содержимое выдается компьютеру так, как будто вы бы вводили его с обычной клавиатуры.

Само устройство подключается к серверу "умной дачи" по USB и определяется им как USB-клавиатура. Сам сервер представляет собой мини-компьютер Fujitsu-Siemens ESPRIMO Q5020, который внешне напоминает старый Mac Mini. Работает под Ubuntu 20. Однако описывать весь проект "умной дачи" - не тема данной статьи (надеюсь, я когда-нибудь напишу и про глобальный проект). Здесь я опишу только создание самодельной USB клавиатуры.

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

Arduino Pro Micro (Leonardo)
Arduino Pro Micro (Leonardo)

"Сердцем" данной коробочки является Arduino Pro Micro на Atmega32u4. Именно эта плата была выбрана потому, что она имеет аппаратный USB порт, что для моего проекта является главным. Потому что это всё-таки USB клавиатура. А если точно, то USB HID устройство (Human Interface Device), к этой категории относятся клавиатуры, мыши, джойстики и прочие устройства ввода. С разъемами я решил не заморачиваться, а паять всё прямо к плате.

4x4 Keypad
4x4 Keypad

В качестве клавиатуры взял пленочную клавиатуру 4х4 Keypad, которая у меня лежит без дела уже лет 7. Вот наконец, и ей нашлось применение. Я очень не люблю паять большое количество проводов (так сказать, страдаю проводобоязнью). Поэтому к клавиатуре приобрел модуль i2c на PCF8574Т. Модуль уже имел припаянный разъем, к которому подключается шлейф клавиатуры. В результате нам нужно паять всего лишь 4 провода: питание, а также SDA, SCL. Адрес устройства выбирается тремя DIP переключателями на плате модуля. У меня они все OFF, что соответствует адресу 0x20.

i2c модуль на PCF8574T
i2c модуль на PCF8574T

В случае с Arduino Pro Micro, SDA подключается к Pin 2, SCL - Pin 3. Не помню точно, но вроде бы, у Arduino Nano используются те же самые пины для i2c. Мне также хотелось как-то отображать состояние и текущий режим устройства. Можно было, конечно, подключить полноценный экран, например, модуль LCD1602 с тем же i2c. Или же просто тупо сделать индикацию при помощи нескольких светодиодов.

7-сегментный индикатор на TM1637
7-сегментный индикатор на TM1637

Но я решил взять что-то среднее - модуль, состоящий из четырех семисегментных индикаторов со встроенным контроллером TM1637. Подключение похоже на i2c, хотя здесь выводы называются DIO и CLK. Паяю к пинам 7 и 8 на плате Arduino, соответственно. Пример из стандартной библиотеки TM1637 работает сразу же, без каких либо модификаций.

Ну и конечно же, нам понадобится модуль для чтения SD карт. Я выбрал модуль с интерфейсом SPI.

SD модуль с интерфейсом SPI
SD модуль с интерфейсом SPI

Подключение: Pin 10 -> CS, Pin 16 -> MOSI, Pin 14 -> MISO, Pin 15 -> SCK. Тут также не возникло никаких проблем, и пример из библиотеки "завелся" сразу же, без дополнительных модификаций.

Библиотеки, использованные в проекте (либо стандартные, либо загружаемые через менеджер библиотек в Arduino IDE):

TM1637 Driver by AKJ v2.11 (загружаемая), SD by Arduino (стандартная), I2CKeyPad by Rob Tillaart v0.3.1 (загружаемая), Keyboard by Arduino (стандартная), Mouse by Arduino (стандартная)

Девайс в сборе
Девайс в сборе

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

Модуль SD
Модуль SD

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

Выглядит страшновато, но работает отлично
Выглядит страшновато, но работает отлично

Управление устройством осуществляется следующим образом. При подключении к компьютеру по USB оно определяется как HID устройство. Далее, на встроенной клавиатуре выбираем режим кнопками A, B, C, D. В моем случае задействована пока только кнопка A. Нажимаем ее, на индикаторе загорается соответствующий символ. Далее, цифровой клавишей выбираем скрипт, который будет передан через USB порт. При этом программа ищет на SD карте в корневом каталоге файл с именем, соответствующим нажатой клавише: от 0.txt до 9.txt. После чего файл читается по строчкам и тут же выдается в порт клавиатуры. То есть все происходит так, как будто вы текст из выбранного файла набирали бы с обычной клавиатуры, только мгновенно. Можно сделать эффект "печатающей машинки" - для этого в некоторых местах скетча можно раскомментировать delay(). Ввод текста можно в любой момент остановить, удерживая клавишу "звездочка" (*). Чтобы запустить его снова, нужно нажать клавишу А, и затем соответствующую нужному файлу цифру.

В дальнейшем я планирую расширить функционал устройства, добавив "проигрывание" перемещений мыши из СSV файла. Этот функционал уже почти написан, и будет соответствовать кнопке B на клавиатуре. Как я уже говорил ранее, это устройство является тестовым - чтобы можно было запускать определенные функции "умной дачи" с физической клавиатуры, а не только через веб-интерфейс.Однако ему можно найти и другие применения. Например, "спамить" командами через консоль клиентов онлайн игр. Ну и напоследок, сам скетч для Ардуино.

// Подключение к модулям Keypad, LED и SD
// Pin 2  -> KEY SDA
// Pin 3  -> KEY SCL
// Pin 7  -> LED DIO
// Pin 8  -> LED CLK
// Pin 10 -> SD  CS
// Pin 16 -> SD  MOSI
// Pin 14 -> SD  MISO
// Pin 15 -> SD  SCK

#include "Wire.h"
#include "I2CKeyPad.h"
#include "Mouse.h"
#include "Keyboard.h"
#include "TM1637.h"
#include "SPI.h"
#include "SD.h"
//#include "my_mouse.h"

// 7-сегментный индикатор на 4 знакоместа на TM1637
TM1637 tm(7, 8);
// Клавиатура на 16 клавиш (4х4), подключенная по i2c
I2CKeyPad keyPad(0x20);

// Режим не выбран (пауза)
#define MODE_0                0
// Была нажата клавиша А
#define MODE_A                1
// Была нажата клавиша B
#define MODE_B                2
// Была нажата клавиша C
#define MODE_C                3
// Была нажата клавиша D
#define MODE_D                4
// Режим чтения из файла на SD
#define MODE_FILE             5
// Расположение клавиш на клавиатуре
char keymap[19] = "123A456B789C*0#DNF";  // N = NoKey, F = Fail
// Текущий режим работы программы (пауза)
char mode = MODE_0;
// Последняя нажатая клавиша
int last_key = -1;
// Файл на SD карте
File f;
// Сообщение на дисплее и имя файла на SD
String msg, fname;
// Переменная для хранения строк из файла
String data;
// Номер текущей строки файла
int line = 0;

void setup()
{
  // Инициализируем клавиатуру
  Keyboard.begin();
  // Инициализируем мышку
  Mouse.begin();
  // Инициализируем монитор
  Serial.begin(115200);
  // Инициализация i2c
  Wire.begin();
  Wire.setClock(400000);
  if (keyPad.begin() == false)
  {
    // Мы потеряли клавиатуру на 16 клавиш :-(
    tm.display("KBER");
    while (1);
  }
  keyPad.loadKeyMap(keymap);
  // Инициализация дисплея с яркостью 1 (этого достаточно, все что выше - вырвиглаз)
  tm.begin();
  tm.setBrightness(1);
  tm.clearScreen();
  // CS = 10
  if (!SD.begin(10))
  {
    // Не инициализировалась SD карта
    tm.display("SDER");
    while (1);
  }
  // Если все ок, показать на дисплее соответствующую надпись и ждать команды
  tm.display("OK ");
}
// true, если нажатая клавиша - цифровая
bool is_num(int key)
{
  if ((key >= 0 && key <= 2) || (key >= 4 && key <= 6) || (key >= 8 && key <= 10)) return true;
  return false;
}

void loop()
{
  // Если режим - чтение файла
  if (mode == MODE_FILE)
  {
    // если строка закончилась (или еще не была прочитана)
    if (data.length() == 0)
    {
      // если еще не достигнут конец файла
      if (f.available())
      {
        // читать из файла строку до символа перевода строки
        data = f.readStringUntil('\n');
        // перевод строки в отладочную консоль
        Serial.println("");
        //delay(random(2000, 5000));
        tm.display("    ");
        if (line > 0)
        {
          // Если номер текущей строки >0, посылаем символ перевода строки
          Keyboard.write('\n');
        }
        line++;
      }
      else
      {
        // закрыть файл, показать EOF и переключить режим в "паузу"
        f.close();
        tm.display("EOF ");
        mode = MODE_0;
      }
    }
    else
    {
      // если в строке еще есть символы, отправляем в буфер клавиатуры первый символ из строки
      Keyboard.write(data[0]);
      // отправляем его же в отладочную консоль
      Serial.write(data[0]);
      // печатаем его на встроенном экранчике
      tm.display(data[0]);
      // удаляем его из строки
      data.remove(0,1);
      //delay(random(300, 1000));
    }
  }
  // если нажата клавиша на встроенной клавиатуре
  if (keyPad.isPressed())
  {
    // получаем символ
    char ch = keyPad.getChar();
    // получаем код клавиши
    int key = keyPad.getLastKey();
    if (last_key != key)
    {
      if (key == 12)
      {
        mode = MODE_0;
        //mouse_active = 0;
        //mouse_count = 0;
        //tm.display("OFF ");
      }
      // нажата кнопка А
      if (key == 3)
      {
        mode = MODE_A;
        tm.display("A   ");
      }
      // нажата кнопка B
      if (key == 7)
      {
        mode = MODE_B;
        tm.display("B   ");
      }
      // если активирован режим А и нажата цифровая клавиша
      if (mode == MODE_A && is_num(key))
      {
        // на экранчик выводим А (нажатая цифра)
        line = 0;
        msg = "A  ";
        msg.concat(ch);
        tm.display(msg);
        // имя файла - (нажатая цифра).txt, например: 1.txt
        fname = "";
        fname.concat(ch);
        fname.concat(".txt");
        // попробуем открыть выбранный файл
        f = SD.open(fname, FILE_READ);
        if (!f)
        {
          // если не удалось (например, файл не существует), то печатаем на экранчике Err (нажатая цифра), Например Err1
          msg = "ERR";
          msg.concat(ch);
          tm.display(msg);
          // возвращаем режим "пауза"
          mode = MODE_0;
        }
        else
        {
          // включаем режим чтения файла
          mode = MODE_FILE;
        }
      }
      // Режим B - зарезервирован на будущее
      if (mode == MODE_B && is_num(key))
      {
        msg = "B  ";
        msg.concat(ch);
        tm.display(msg);
      }
      last_key = key;
      Serial.print(key);
      Serial.print(" \t");
      Serial.println(ch);
    }
  }
  else
  {
    // Клавиша отпущена
    last_key = -1;
  }
}

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


  1. Javian
    12.01.2022 09:38

    Мало кнопок. Разве, что удобно использовать для мессенджера - включил/выключил клавиатуру/вебкамеру.

    офф Лет 10 назад продавалась клавиатура Logitech G11 Keyboard, у которой был слева блок из 18 программируемых клавиш по три блока памяти т.е. 54 кнопки. Недавно навешивал на них комбинации клавиш Android Studio - удобно, но жаль была не моя клавиатура.


  1. Lexicon
    12.01.2022 09:44
    +2

    ИМХО, если делать клавиатуру на ардуине, то приятнее вкладываться в механические, тактильные способы ввода: переключатели, слайдеры, кнопки, рычажки.

    Без ламповости, так-то можно положиться на экран смартфона с красивой веб-мордой


    1. alexraven Автор
      12.01.2022 12:03
      +1

      Ну этот проект был скорее обучением - я раньше не делал HID устройства. Теперь никто не мешает вместо пленочной подключить какую-нибудь клавиатуру на герконовых клавишах и оформить девайс в стиле стимпанк :-)


  1. Elsajalee
    12.01.2022 09:55
    +2

    Отверстие под шлейф пленочной клавиатуры прекрасно просверливается в верхней крышке, его не видно и клавиатура будет полностью прилегать к крышке.

    Можно практически без инструментов, два отверстия сверлом 3мм (можно даже вручную, если сверло шестигранное) по краям шлейфа, а потом канцелярским ножом по линейке.


    1. alexraven Автор
      12.01.2022 12:04

      Я изначально так и хотел сделать, но не хотел сильно переламывать шлейф


  1. Sdima1357
    12.01.2022 10:04

    Esp32-s2 на сегодня вдвое дешевле чем ардуино ,а также можно будет выкинуть sd card и вместо нее прописывать реакцию на клавиши сразу по wifi .


    1. sav13
      12.01.2022 10:32

      Только у отладочных плат S2 нативный USB не разведен, а разведен обычный USR/UART на CH340 или PL2302. Потому как USB загрузчик легко записывается и легко стирается из внешний Flash, а загрузчик UART в ROM зашит.

      А автору с его подходом придется еще внешний USB разъем на проводочках монстрячить )))


      1. Sdima1357
        12.01.2022 10:58

        Есть и с разведённы usb-otg https://aliexpress.ru/item/1005003145192016.html с 4 мегабайт флеш и 2 мега ram и дешевле ардуины и можно update over air...

        И можно даже micropython для извращенцев


        1. sav13
          12.01.2022 11:10

          А S3 еще никто не юзал? Как там "флагман" ESP поживает?


          1. Sdima1357
            12.01.2022 11:15

            Я не юзал. Но вот коре от s2 у меня на большинстве тестов проигрывает одному кору их предыдущей xtensa lx6 с тем же клоком


            1. sav13
              12.01.2022 11:24
              +1

              Тоже не увидел особого преимущества пред старыми 32S, если USB не нужны функции. Правда там ADC вроде заявлен получше разрешением. Ну и по операциям с плавающей точкой там вроде заявлена сумашедшая производительность )))

              А так согласен, на двух ядрах и безопеснее - никакое приложение не прервет работу WiFi, как на ESP8266


        1. Hait
          12.01.2022 22:23

          Так тут цена ниже из-за того, что не включена стоимость доставки


          1. Sdima1357
            12.01.2022 22:39

            У меня стоит цена доставки одинаковая с ардуиной. Да, и попробуйте поставить 10 штук. Цена доставки будет прежняя.


            1. Hait
              13.01.2022 19:52

              Спасибо, посмотрю. Просто хотел для пробы взять. Так что мне 10 штук пока ни к чему


          1. Sdima1357
            13.01.2022 01:22

            Еще опции

            stm32f401 https://www.aliexpress.com/item/4001116395973.html

            stm32c8t6 or stlink(тоже подойдет) https://www.aliexpress.com/item/1766455290.html


      1. alexraven Автор
        12.01.2022 11:58

        Именно поэтому я и взял плату с аппаратным USB, что не хотелось возиться с эмуляцией. Изначально я думал прошить все скрипты прямо в скетче, поэтому хотелось оставить как можно больше свободной памяти. Но потом отказался от этой идеи и подключил SD.

        Да, нашел много вариантов использования v-USB на Arduino UNO и Nano. https://github.com/NicoHood/HID

        UPD. Да, на Stack Overflow пишут, что v-USB страдает периодическим отваливанием, причем сам девайс об этом не знает.


        1. sav13
          12.01.2022 12:13
          +1

          Ну он во всех контроллерах "аппаратный". Только все равно библиотека нужна, которая поддерживает все эти USB-мыши, клавиатуры, геймпады, эмулирует айдишники USB-утройств и пр.

          Хоть в Atmega32U4, хоть в STM32 с USB или в том же ESP32S2/S3

          Просто S2 на голову превосходит 32U4 по характеристикам и функционалу. И на перспективу как раз сомотрелся бы лучше.

          Единственный плюс леонардо - это обилие примеров в интернете. Хотя по S2 уже есть примеры создания HID устройств в Arduino core for the ESP32

          https://github.com/espressif/arduino-esp32/tree/master/libraries/USB/examples


        1. Sdima1357
          12.01.2022 12:15

          Esp32-s2 - это аппаратный usb-otg ,а не эмуляция.. она и дешевле и памяти дофига и ножек больше , и можете даже из ардуины ide программировать..


  1. 200sx_Pilot
    13.01.2022 02:15

    Про мышь.
    Если брать не перемещение а, по аналогии с тач-падом, касание в координате, не красивее ли будет?


  1. ibloha
    13.01.2022 19:34

    Лично я, сделал бы все по другому.

    Взял бы обычную ардуино или аналог, например ATtiny85 USB Digispark, которую можно подключать к компьютеру по юсб. И передавал бы только номер скрипта на порт через ардуино. А на линуксе зупустил бы демон, котрый мониторит порт.

    Когда демон считывает номер скрипта, то запускает нужный скрипт.

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

    Еще как вариант, ардуино + ик-приемник + не нужный пульт. Тогда выйдет еще дешевле и красивее.