USB мини клавиатура на Arduino Pro Micro

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

image-loader.svg

Само устройство подключается к серверу «умной дачи» по 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 Keypad4×4 Keypad

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

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

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

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

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

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

SD модуль с интерфейсом SPISD модуль с интерфейсом 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;
  }
}

© Habrahabr.ru