С нуля: как я создал клавиатуру мечты разработчика
Опубликовано: 2022-03-11Работая однажды в августе 2007 года, я не мог не осознавать, что моя обычная компьютерная клавиатура не служила мне в полной мере. Мне приходилось чрезмерно перемещать руки между различными блоками клавиатуры, сотни, если не тысячи раз в день, и мои руки были неудобно близко друг к другу. Должен быть лучший способ, подумал я.
За этим осознанием последовало непреодолимое чувство волнения, когда я думал о настройке лучшей клавиатуры для разработчиков, а позже осознание того, что, будучи внештатным разработчиком встраиваемого программного обеспечения, я безнадежно не разбираюсь в аппаратном обеспечении.
В то время я был довольно занят другими проектами, но не проходило и дня, чтобы я не думал о создании хакерской клавиатуры. Вскоре я начал посвящать свободное время работе над проектом. Мне удалось освоить совершенно новый набор навыков, уговорить моего друга Андраша Волги, экстраординарного инженера-механика, присоединиться к проекту, собрать ключевых людей и посвятить достаточно времени созданию рабочих прототипов. В настоящее время Ultimate Hacking Keyboard стала реальностью. Мы ежедневно добиваемся прогресса, и запуск нашей краудфандинговой кампании не за горами.
Переход от опыта работы с программным обеспечением, ничего не зная об электронике, к разработке и созданию мощного, востребованного на рынке аппаратного устройства — это интересный и увлекательный опыт. В этой статье я опишу схему работы этого электронного шедевра. Базовое понимание электронных схем может помочь вам в этом.
Как сделать клавиатуру?
Посвятив тысячи часов своей жизни этой теме, мне трудно дать краткий ответ, но есть интересный способ ответить на этот вопрос. Что, если мы начнем с чего-то простого, например, с платы Arduino, и постепенно превратим ее в идеальную хакерскую клавиатуру? Он должен быть не только более удобоваримым, но и чрезвычайно познавательным. Итак, пусть наше путешествие по клавиатуре начнется!
Шаг первый: клавиатура без клавиш
Во-первых, давайте создадим USB-клавиатуру, которая выдает символ x
раз в секунду. Отладочная плата Arduino Micro является идеальным кандидатом для этой цели, потому что она оснащена микроконтроллером ATmega32U4 — микрокронтроллером AVR и тем же процессором, который является мозгом UHK.
Когда речь идет о микроконтроллерах AVR с поддержкой USB, предпочтительной библиотекой является Lightweight USB Framework for AVR (LUFA). Это позволяет этим процессорам стать «мозгами» принтеров, MIDI-устройств, клавиатур или почти любого другого типа USB-устройств.
При подключении устройства к USB-порту устройство должно передавать некоторые специальные структуры данных, называемые USB-дескрипторами. Эти дескрипторы сообщают хост-компьютеру тип и свойства подключаемого устройства и представлены в виде древовидной структуры. Еще больше усложняет ситуацию то, что устройство может выполнять не одну, а несколько функций. Посмотрим структуру дескрипторов UHK:
- Дескриптор устройства
- Дескриптор конфигурации
- Дескриптор интерфейса 0: GenericHID
- Дескриптор конечной точки
- Дескриптор интерфейса 1: Клавиатура
- Дескриптор конечной точки
- Дескриптор интерфейса 2: Мышь
- Дескриптор конечной точки
- Дескриптор интерфейса 0: GenericHID
- Дескриптор конфигурации
Большинство стандартных клавиатур предоставляют только один дескриптор интерфейса клавиатуры, что имеет смысл. Однако в качестве настраиваемой клавиатуры для программирования 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
содержит 1
. isSecondElapsed
получает значение 1
из основного цикла в секунду и значение 0
из обратного вызова.
Шаг второй: клавиатура из четырех клавиш
На данный момент наша клавиатура не очень полезна. Было бы неплохо, если бы мы могли печатать на нем. Но для этого нам нужны клавиши, а клавиши должны быть организованы в клавиатурную матрицу. Полноразмерная 104-клавишная клавиатура может иметь 18 строк и 6 столбцов, но для запуска у нас будет просто скромная матрица клавиатуры 2x2. Это схема:
А вот так это выглядит на макетной плате:
Предполагая, что ROW1
подключен к PINA0
, ROW2
к PINA1
, COL1
к 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()
соответствующие коды сканирования будут отправлены в зависимости от состояния этого массива.
Шаг третий: клавиатура с двумя половинками
На данный момент мы создали зачатки нормальной клавиатуры. Но в этом руководстве по клавиатуре мы стремимся к продвинутой эргономике, и, учитывая, что у людей две руки, нам лучше добавить еще одну половину клавиатуры.
Другая половина будет иметь другую матрицу клавиатуры, работающую так же, как и предыдущая. Захватывающая новинка — это связь между половинками клавиатуры. Тремя наиболее популярными протоколами для соединения электронных устройств являются SPI, I 2 C и 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 было дать пользователю возможность определять несколько раскладок клавиатуры для конкретных приложений, чтобы еще больше повысить производительность. Пользователю нужен какой-то способ быть в курсе фактической используемой раскладки клавиатуры, поэтому в клавиатуру встроен встроенный светодиодный дисплей. Вот прототип дисплея со всеми горящими светодиодами:
Светодиодный дисплей реализован светодиодной матрицей 8x6:
Каждые два ряда светодиодных символов красного цвета представляют собой сегменты одного из 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, программном обеспечении на стороне хоста. Причина в том, что, в отличие от аппаратного обеспечения и встроенного программного обеспечения, Агент на данный момент находится в зачаточном состоянии. Тем не менее, решена высокоуровневая архитектура Агента, которой я хотел бы поделиться.
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-битный идентификатор и набор специфичных для команды аргументов. В настоящее время реализована только команда повторного перечисления. sendReenumerateCommand()
заставляет устройство повторно перечисляться как левый загрузчик или правый загрузчик, для обновления прошивки или как клавиатурное устройство.
Кто-то может не знать о расширенных функциях, которые могут быть реализованы с помощью этого программного обеспечения, поэтому я назову несколько: Агент сможет визуализировать износ отдельных ключей и уведомлять пользователя об ожидаемом сроке их службы, чтобы пользователь мог приобрести пару новых клавишных выключателей для предстоящего ремонта. Агент также предоставит пользовательский интерфейс для настройки различных раскладок и слоев хакерской клавиатуры. Также можно было установить скорость и ускорение указателя мыши, а также множество других функций uber. Небо это предел.
Создание прототипа
Много работы уходит на создание индивидуальных прототипов клавиатуры. Прежде всего, необходимо доработать механическую конструкцию, которая сама по себе довольно сложна и включает в себя специально разработанные пластиковые детали, пластины из нержавеющей стали с лазерной резкой, стальные направляющие с прецизионной фрезеровкой и неодимовые магниты, которые скрепляют две половинки клавиатуры. Все проектируется в САПР до начала изготовления.
Вот так выглядит корпус клавиатуры, напечатанный на 3D-принтере:
На основе механической конструкции и схемы должна быть спроектирована печатная плата. Правая плата выглядит в KiCad так:
Затем изготавливается печатная плата, и компоненты для поверхностного монтажа необходимо припаять вручную:
Наконец, после изготовления всех деталей, включая 3D-печать, полировку и покраску пластиковых деталей и сборку всего, мы получаем рабочий прототип хакерской клавиатуры, подобный этому:
Заключение
Мне нравится сравнивать клавиатуры разработчиков с инструментами музыкантов. Клавиатуры — довольно интимные объекты, если подумать. В конце концов, мы используем их весь день, чтобы создавать программное обеспечение завтрашнего дня, персонаж за персонажем.
Вероятно, из-за вышесказанного я считаю разработку Ultimate Hacking Keyboard привилегией, и, несмотря на все трудности, чаще всего это было очень захватывающее путешествие и невероятно интенсивный опыт обучения.
Это широкая тема, и я мог только коснуться ее поверхности. Я надеюсь, что эта статья была очень веселой и полной интересного материала. Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать в комментариях.
Наконец, вы можете посетить https://ultimatehackingkeyboard.com для получения дополнительной информации и подписаться там, чтобы получать уведомления о запуске нашей кампании.