Статьи

С нуля: как я создал клавиатуру мечты разработчика

Автор  LÁSZLÓ MONDA — ПРОГРАММНЫЙ ИНЖЕНЕР TOPTAL 

Работая однажды в августе 2007 года, я не мог не понять, что моя обычная клавиатура для ПК не обслуживает меня настолько, насколько это возможно. Мне приходилось чрезмерно перемещать руки между различными блоками клавиатуры, сотни, если не тысячи раз в день, и мои руки неудобно находились близко друг к другу. Должен быть лучший способ, подумал я.

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

В то время я был довольно занят другими проектами, но не прошло и дня, чтобы я не думал о клавиатуре. Вскоре я начал посвящать свое свободное время работе над проектом. Мне удалось выучить совершенно новый набор навыков, убедить моего друга Андорса Фольджи, экстраординарного инженера-механика, присоединиться к проекту, собрать нескольких ключевых людей и посвятить достаточно времени созданию рабочих прототипов. В наши дни  Ultimate Hacking Keyboard  — это реальность. Мы добиваемся ежедневного прогресса, и запуск нашей краудфандинговой кампании находится в пределах досягаемости.

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

Как ты делаешь клавиатуру?

После того, как я посвятил этой теме тысячи часов своей жизни, мне сложно дать короткий ответ, но есть интересный способ ответить на этот вопрос. Что, если мы начнем с чего-то простого, такого как плата Arduino, и постепенно превратим его в Ultimate Hacking Keyboard? Он должен быть не только более усваиваемым, но и чрезвычайно образовательным. Поэтому пусть наше путешествие начнется тогда!

Шаг первый: клавиатура без клавиш

Для начала давайте создадим USB-клавиатуру, которая будет выдавать  x символ раз в секунду. Плата для   разработки Arduino Micro является идеальным кандидатом для этой цели, поскольку она оснащена  микроконтроллером ATmega32U4 — микроконтроллером  AVR  и тем же процессором, что и мозг UHK.

Когда речь идет о микроконтроллерах AVR с поддержкой USB, библиотека Lightweight USB Framework для AVR ( LUFA ) является предпочтительной библиотекой. Это позволяет этим процессорам стать мозгом принтеров, MIDI-устройств, клавиатур или почти любого другого типа USB-устройства.

При подключении устройства к порту USB, устройство должно передавать некоторые специальные структуры данных, называемые дескрипторами USB. Эти дескрипторы сообщают хост-компьютеру тип и свойства подключаемого устройства и представлены в виде древовидной структуры. Чтобы сделать вещи еще более сложными, устройство может реализовать не только одну, но несколько функций. Давайте посмотрим структуру дескрипторов UHK:

  • Дескриптор устройства

    • Дескриптор конфигурации

      • Дескриптор интерфейса 0: GenericHID

        • Дескриптор конечной точки
      • Дескриптор интерфейса 1: клавиатура

        • Дескриптор конечной точки
      • Дескриптор интерфейса 2: мышь

        • Дескриптор конечной точки

Большинство клавиатур предоставляют только один дескриптор интерфейса клавиатуры, что имеет смысл. Однако UHK также предоставляет дескриптор интерфейса мыши, поскольку пользователь может запрограммировать произвольные клавиши клавиатуры для управления указателем мыши, чтобы клавиатура могла использоваться в качестве мыши. Интерфейс GenericHID служит каналом связи для обмена информацией о конфигурации для всех специальных функций клавиатуры. Вы можете увидеть полную реализацию устройства и дескрипторы конфигурации UHK в LUFA  здесь .

Теперь, когда мы создали дескрипторы, пришло время отправлять  x персонажа каждую секунду.

uint8_t isSecondElapsed = 0;
 
int main(void)
{
    while (1) {
        _delay_us(1000);
        isSecondElapsed = 1;
    }
}
 
bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
                                         uint8_t* const ReportID,
                                         const uint8_t ReportType,
                                         void* ReportData,
                                         uint16_t* const ReportSize)
{
    USB_KeyboardReport_Data_t* KeyboardReport = (USB_KeyboardReport_Data_t*)ReportData;
    if (isSecondElapsed) {
        KeyboardReport->KeyCode[0] = HID_KEYBOARD_SC_X;
        isSecondElapsed = 0;
    }
    *ReportSize = sizeof(USB_KeyboardReport_Data_t);
    return false;
}

USB — это протокол опроса, который означает, что хост-компьютер запрашивает устройство на регулярной основе (обычно 125 раз в секунду), чтобы выяснить, есть ли какие-либо новые данные для отправки. Соответствующим обратным вызовом является  CALLBACK_HID_Device_CreateHIDReport() функция, которая в этом случае отправляет скан-код  x символа хосту всякий раз, когда  isSecondElapsed переменная содержит  1isSecondElapsed получает значение  1 из основного цикла каждую секунду и устанавливается  0 из обратного вызова.

Шаг второй: клавиатура из четырех клавиш

На данный момент наша клавиатура не очень полезна. Было бы хорошо, если бы мы могли печатать на нем. Но для этого нам нужны клавиши, а клавиши должны быть расположены в матрице клавиатуры. Полноразмерная 104-клавишная клавиатура может иметь 18 строк и 6 столбцов, но для запуска у нас просто будет скромная матрица клавиатуры 2х2. Это схема:

И вот как это выглядит на макете:

Предполагая, что  ROW1 это связано с  PINA0ROW2 с  PINA1COL1 к  PORTB0 и  COL2 к  PORTB1, вот как выглядит код сканирования:

/* A single pin of the microcontroller to which a row or column is connected. */
typedef struct {
    volatile uint8_t *Direction;
    volatile uint8_t *Name;
    uint8_t Number;
} Pin_t;
 
/* This part of the key matrix is stored in the Flash to save SRAM space. */
typedef struct {
    const uint8_t ColNum;
    const uint8_t RowNum;
    const Pin_t *ColPorts;
    const Pin_t *RowPins;
} KeyMatrixInfo_t;
 
/* This Part of the key matrix is stored in the SRAM. */
typedef struct {
    const __flash KeyMatrixInfo_t *Info;
    uint8_t *Matrix;
} KeyMatrix_t;
 
const __flash KeyMatrixInfo_t KeyMatrix = {
    .ColNum = 2,
    .RowNum = 2,
    .RowPins = (Pin_t[]) {
        { .Direction=&DDRA, .Name=&PINA, .Number=PINA0 },
        { .Direction=&DDRA, .Name=&PINA, .Number=PINA1 }
    },
    .ColPorts = (Pin_t[]) {
        { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB0 },
        { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB1 },
    }
};
 
void KeyMatrix_Scan(KeyMatrix_t *KeyMatrix)
{
    for (uint8_t Col=0; Col<KeyMatrix->Info->ColNum; Col++) {
        const Pin_t *ColPort = KeyMatrix->Info->ColPorts + Col;
        for (uint8_t Row=0; Row<KeyMatrix->Info->RowNum; Row++) {
            const Pin_t *RowPin = KeyMatrix->Info->RowPins + Row;
            uint8_t IsKeyPressed = *RowPin->Name & 1<<RowPin->Number;
            KeyMatrix_SetElement(KeyMatrix, Row, Col, IsKeyPressed);
        }
    }
}

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

Шаг третий: клавиатура с двумя половинками

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

Другая половина будет иметь другую матрицу клавиатуры, работающую так же, как и предыдущая. Захватывающая новинка — связь между половинами клавиатуры. Три наиболее популярных протокола для подключения электронных устройств — это  SPII2C  и  UART . В практических целях мы будем использовать UART в этом случае.

Двунаправленная связь протекает через RX вправо и через TX влево согласно схеме выше. VCC и GND необходимы для передачи питания. UART требует, чтобы одноранговые узлы использовали одинаковую скорость передачи данных, количество битов данных и количество стоп-битов. Как только приемопередатчик UART обоих пиров будет настроен, связь может начаться.

На данный момент левая половина клавиатуры отправляет однобайтовые сообщения на правую половину клавиатуры через UART, представляющие события нажатия клавиши или отпускания клавиши. Правая половина клавиатуры обрабатывает эти сообщения и соответственно управляет состоянием полного матричного массива клавиатуры в памяти. Вот как левая клавиатура наполовину отправляет сообщения:

USART_SendByte(IsKeyPressed<<7 | Row*COLS_NUM + Col);

Код правой половины клавиатуры для получения сообщения выглядит следующим образом:

void KeyboardRxCallback(void)
{
    uint8_t Event = USART_ReceiveByte();
    if (!MessageBuffer_IsFull(&KeyStateBuffer)) {
        MessageBuffer_Insert(&KeyStateBuffer, Event);
    }
}

KeyboardRxCallback() Обработчик прерывания получает срабатывает всякий раз , когда байт принимается через UART. Учитывая, что обработчики прерываний должны выполняться как можно быстрее, полученное сообщение помещается в кольцевой буфер для последующей обработки. Кольцевой буфер в конечном итоге обрабатывается из основного цикла, и матрица клавиатуры будет обновляться на основе сообщения.

Выше приведен самый простой способ сделать это, но окончательный протокол будет несколько сложнее. Многобайтовые сообщения должны быть обработаны, а отдельные сообщения должны быть проверены на целостность с помощью контрольных сумм CRC-CCITT.

На данный момент наш макет выглядит довольно внушительно:

Шаг четвертый: познакомьтесь со светодиодным дисплеем

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

Светодиодный дисплей выполнен в виде светодиодной матрицы 8×6:

Каждые два ряда светодиодных символов красного цвета представляют сегменты одного из 14-сегментных светодиодных дисплеев. Белые светодиодные символы обозначают три дополнительных индикатора состояния.

Чтобы пропустить ток через светодиод и загореться, соответствующая колонка установлена ​​на высокое напряжение, а соответствующая строка на низкое напряжение. Интересным следствием этой системы является то, что в любой данный момент может быть включен только один столбец (все светодиоды в этом столбце, которые должны гореть, имеют соответствующие строки на низком напряжении), в то время как остальные столбцы отключены , Можно подумать, что эта система не может работать с полным набором светодиодов, но в действительности столбцы и строки обновляются так быстро, что человеческий глаз не видит мерцания.

Светодиодная матрица управляется двумя интегральными схемами (ИС), одна из которых управляет своими рядами, а другая — своими столбцами. ИС источника, которая управляет столбцами, является  драйвером  светодиода PCA9634 I2C:

ИС приемника матрицы светодиодов, которая управляет строками, является  регистром  переключения мощности TPIC6C595 :

Давайте посмотрим соответствующий код:

uint8_t LedStates[LED_MATRIX_ROWS_NUM];

void LedMatrix_UpdateNextRow(bool IsKeyboardColEnabled)
{
    TPIC6C595_Transmit(LedStates[ActiveLedMatrixRow]);
    PCA9634_Transmit(1 << ActiveLedMatrixRow);

    if (++ActiveLedMatrixRow == LED_MATRIX_ROWS_NUM) {
          ActiveLedMatrixRow = 0;
    }
}

LedMatrix_UpdateNextRow() вызывается примерно каждую миллисекунду, обновляя строку светодиодной матрицы. В  LedStates магазинах массива состояние отдельных светодиодов, обновляется через UART на основе сообщений происходят от правой клавиатуры половины, почти такой же, как и в случае нажатия клавиши / ключевого события выпуска.

Большая картинка

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

До сих пор много говорилось о деталях клавиатуры на стороне устройства, но не так много о UHK Agent, программном обеспечении на стороне хоста. Причина в том, что, в отличие от аппаратного и встроенного программного обеспечения, агент на данный момент очень элементарен. Тем не менее, на высокоуровневой архитектуре Агента принято решение, которым я хотел бы поделиться.

UHK Agent — это приложение-конфигуратор, с помощью которого можно настроить клавиатуру в соответствии с потребностями пользователя. Несмотря на то, что он является богатым клиентом, агент использует веб-технологии и работает поверх платформы node-webkit.

Агент связывается с клавиатурой с помощью библиотеки node-usb, отправляя специальные, специфичные для устройства запросы управления USB и обрабатывая их результаты. Он использует Express.js для предоставления REST API для использования сторонними приложениями. Он также использует Angular.js для обеспечения аккуратного пользовательского интерфейса.

var enumerationModes = {
    'keyboard'         : 0,
    'bootloader-right' : 1,
    'bootloader-left'  : 2
};

function sendReenumerateCommand(enumerationMode, callback)
{
    var AGENT_COMMAND_REENUMERATE = 0;
    sendAgentCommand(AGENT_COMMAND_REENUMERATE, enumerationMode, callback);
}

function sendAgentCommand(command, arg, callback)
{
    setReport(new Buffer([command, arg]), callback);
}

function setReport(message, callback)
{
    device.controlTransfer(
        0x21,             // bmRequestType (constant for this control request)
        0x09,             // bmRequest (constant for this control request)
        0,                // wValue (MSB is report type, LSB is report number)
        interfaceNumber,  // wIndex (interface number)
        message,          // message to be sent
        callback
    );
}

Каждая команда имеет 8-битный идентификатор и набор аргументов, специфичных для команды. В настоящее время реализована только команда re-enumerate. Это  sendReenumerateCommand() заставляет устройство повторно перечислять в качестве левого загрузчика или правого загрузчика, для обновления прошивки или в качестве устройства клавиатуры.

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

Создание прототипа

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

Вот как выглядит корпус клавиатуры с 3D-печатью:

На основе механического дизайна и схемы, печатная плата должна быть разработана. Правая PCB выглядит так в  KiCad :

Затем изготавливается печатная плата, а компоненты для поверхностного монтажа должны быть припаяны вручную:

Наконец, после изготовления всех деталей, включая 3D-печать, полировку и покраску пластиковых деталей и сборку всего, мы получаем рабочий прототип, подобный этому:

Вывод

Мне нравится сравнивать клавишные инструменты с инструментами музыкантов. Клавиатуры — довольно интимные объекты, если подумать. В конце концов, мы используем их весь день для создания программного обеспечения завтрашнего дня, символ за персонажем.

Вероятно, из-за вышеизложенного, я считаю разработку Ultimate Hacking Keyboard привилегией, и, несмотря на все трудности, чаще всего это было очень захватывающее путешествие и невероятно интенсивный опыт обучения.

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

Наконец, вы можете посетить  https://ultimatehackingkeyboard.com  для получения дополнительной информации и подписаться там, чтобы получать уведомления о запуске нашей кампании.