기초부터: 개발자의 꿈의 키보드를 구축한 방법
게시 됨: 2022-03-112007년 8월의 어느 날, 나는 일반 PC 키보드가 가능한 한 많은 도움이 되지 않는다는 것을 깨닫지 않을 수 없었습니다. 나는 키보드의 여러 블록 사이에서 손을 하루에 수천 번은 아니더라도 수백 번 과도하게 움직여야 했고 두 손은 불편할 정도로 가까이에 있었습니다. 더 나은 방법이 있을 거라고 생각했습니다.
이 깨달음은 개발자를 위한 최고의 키보드를 사용자 정의하는 것에 대해 생각할 때 압도적인 설렘을 느꼈고, 나중에는 프리랜스 임베디드 소프트웨어 개발자로서 내가 하드웨어에 대해 무지하다는 것을 깨달았습니다.
당시 나는 다른 프로젝트로 꽤 바빴지만 해커 키보드를 만들 생각을 하지 않은 날은 없었다. 곧 나는 자유 시간을 프로젝트 작업에 바치기 시작했습니다. 나는 완전히 새로운 기술을 배웠고, 내 친구인 뛰어난 기계 엔지니어 Andras Volgyi를 설득하여 프로젝트에 참여하고 핵심 인력을 모으고 작업 프로토타입을 만드는 데 충분한 시간을 할애했습니다. 요즘에는 Ultimate Hacking Keyboard가 현실입니다. 우리는 나날이 발전하고 있으며 크라우드 펀딩 캠페인의 시작이 얼마 남지 않았습니다.
전자공학에 대해 전혀 모르는 소프트웨어 배경에서 강력하고 판매 가능한 하드웨어 장치를 설계하고 구축하는 것은 흥미롭고 매혹적인 경험입니다. 이 기사에서는 이 전자 걸작이 작동하는 방식의 디자인을 설명합니다. 전자 회로도에 대한 기본적인 이해는 따라가는 데 도움이 될 수 있습니다.
키보드는 어떻게 만드나요?
내 인생의 수천 시간을 이 주제에 바친 후에 짧은 대답을 하는 것은 나에게 큰 도전이지만 이 질문에 대답할 흥미로운 방법이 있습니다. Arduino 보드와 같은 간단한 것으로 시작하여 점차적으로 Ultimate Hacking Keyboard로 구축하면 어떻게 될까요? 소화하기 쉬울 뿐만 아니라 매우 교육적이어야 합니다. 따라서 키보드 자습서 여행을 시작하겠습니다!
1단계: 키가 없는 키보드
먼저 1초에 한 번 x
문자를 내보내는 USB 키보드를 만들어 보겠습니다. Arduino Micro 개발 기판은 ATmega32U4 마이크로 컨트롤러(AVR 마이크로 컨트롤러 및 UHK의 두뇌 역할을 하는 동일한 프로세서)를 특징으로 하기 때문에 이러한 목적에 이상적인 후보입니다.
USB 지원 AVR 마이크로컨트롤러의 경우 AVR용 경량 USB 프레임워크(LUFA)가 선택되는 라이브러리입니다. 이를 통해 이러한 프로세서는 프린터, MIDI 장치, 키보드 또는 거의 모든 다른 유형의 USB 장치의 두뇌가 될 수 있습니다.
장치를 USB 포트에 연결할 때 장치는 USB 설명자라고 하는 몇 가지 특수 데이터 구조를 전송해야 합니다. 이러한 설명자는 호스트 컴퓨터에 연결 중인 장치의 유형과 속성을 알려주고 트리 구조로 표시됩니다. 문제를 훨씬 더 복잡하게 만들기 위해 장치는 하나가 아닌 여러 기능을 구현할 수 있습니다. UHK의 디스크립터 구조를 살펴보겠습니다.
- 장치 설명자
- 구성 설명자
- 인터페이스 설명자 0: GenericHID
- 끝점 설명자
- 인터페이스 설명자 1: 키보드
- 끝점 설명자
- 인터페이스 설명자 2: 마우스
- 끝점 설명자
- 인터페이스 설명자 0: GenericHID
- 구성 설명자
대부분의 표준 키보드는 단일 키보드 인터페이스 설명자만 노출하므로 의미가 있습니다. 그러나 사용자 정의 프로그래밍 키보드로서 UHK는 또한 마우스 인터페이스 설명자를 노출합니다. 사용자가 키보드를 마우스로 사용할 수 있도록 마우스 포인터를 제어하기 위해 키보드의 임의 키를 프로그래밍할 수 있기 때문입니다. GenericHID 인터페이스는 키보드의 모든 특수 기능에 대한 구성 정보를 교환하기 위한 통신 채널 역할을 합니다. 여기에서 LUFA에서 UHK의 장치 및 구성 설명자의 전체 구현을 볼 수 있습니다.
이제 설명자를 만들었으므로 매초 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()
함수로, 이 경우 isSecondElapsed
변수에 1
이 포함될 때마다 x
문자의 스캔 코드를 호스트로 보냅니다. isSecondElapsed
는 초당 기본 루프에서 1
로 설정되고 콜백에서 0
으로 설정됩니다.
2단계: 4개 키의 키보드
이 시점에서 우리의 키보드는 별로 유용하지 않습니다. 실제로 입력할 수 있다면 좋을 것입니다. 그러나 이를 위해서는 키가 필요하고 키가 키보드 매트릭스에 배열되어야 합니다. 전체 크기의 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()
함수 내에서 관련 스캔 코드는 해당 배열의 상태에 따라 전송됩니다.
3단계: 두 개의 반쪽이 있는 키보드
지금까지 일반 키보드의 시작 부분을 만들었습니다. 그러나 이 키보드 튜토리얼에서 우리는 고급 인체 공학을 목표로 하고 있으며 사람들이 두 손을 가지고 있다는 점을 감안할 때 믹스에 다른 키보드 절반을 추가하는 것이 좋습니다.
나머지 절반은 이전 것과 동일한 방식으로 작동하는 또 다른 키보드 매트릭스를 특징으로 합니다. 흥미로운 새로운 점은 키보드 반쪽 간의 통신입니다. 전자 장치를 상호 연결하기 위해 가장 널리 사용되는 세 가지 프로토콜은 SPI, I 2 C 및 UART입니다. 실용적인 목적을 위해 이 경우 UART를 사용합니다.
양방향 통신은 위의 다이어그램에 따라 RX를 통해 오른쪽으로, TX를 통해 왼쪽으로 흐릅니다. VCC와 GND는 전원을 전달하는 데 필요합니다. UART는 피어가 동일한 전송 속도, 데이터 비트 수 및 정지 비트 수를 사용해야 합니다. 두 피어의 UART 트랜시버가 설정되면 통신이 시작될 수 있습니다.
지금은 왼쪽 키보드 절반이 UART를 통해 오른쪽 키보드 절반으로 1바이트 메시지를 전송하여 키 누르기 또는 키 해제 이벤트를 나타냅니다. 오른쪽 키보드 절반은 이러한 메시지를 처리하고 그에 따라 메모리의 전체 키보드 매트릭스 배열의 상태를 조작합니다. 왼쪽 키보드 절반이 메시지를 보내는 방법은 다음과 같습니다.
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 체크섬을 사용하여 무결성을 확인해야 합니다.
이 시점에서 브레드보드 프로토타입은 매우 인상적입니다.
4단계: LED 디스플레이 만나기
UHK의 목표 중 하나는 사용자가 여러 응용 프로그램별 키보드 맵을 정의하여 생산성을 더욱 높일 수 있도록 하는 것이었습니다. 사용자는 사용 중인 실제 키맵을 인식할 수 있는 방법이 필요하므로 통합 LED 디스플레이가 키보드에 내장되어 있습니다. 다음은 모든 LED가 켜진 프로토타입 디스플레이입니다.
LED 디스플레이는 8x6 LED 매트릭스로 구현됩니다.
두 줄의 빨간색 LED 기호는 14세그먼트 LED 디스플레이 중 하나의 세그먼트를 나타냅니다. 흰색 LED 기호는 3개의 추가 상태 표시기를 나타냅니다.
LED를 통해 전류를 구동하고 조명을 켜기 위해 해당 열은 고전압으로 설정되고 해당 행은 저전압으로 설정됩니다. 이 시스템의 흥미로운 결과는 주어진 순간에 하나의 열만 활성화할 수 있고(켜져야 하는 해당 열의 모든 LED는 해당 행이 저전압으로 설정됨) 나머지 열은 비활성화된다는 것입니다. . 이 시스템이 전체 LED 세트를 사용할 수 없다고 생각할 수도 있지만 실제로는 열과 행이 너무 빨리 업데이트되어 사람의 눈으로 깜박임이 보이지 않습니다.
LED 매트릭스는 2개의 집적 회로(IC)에 의해 구동되며, 하나는 행을 구동하고 다른 하나는 열을 구동합니다. 열을 구동하는 소스 IC는 PCA9634 I2C LED 드라이버입니다.
행을 구동하는 LED 매트릭스 싱크 IC는 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()
는 밀리초마다 호출되어 LED 매트릭스의 행을 업데이트합니다. LedStates
어레이는 개별 LED의 상태를 저장하고 오른쪽 키보드 절반에서 발생한 메시지를 기반으로 UART를 통해 업데이트되며, 이는 키 누름/키 해제 이벤트의 경우와 거의 동일한 방식입니다.
큰 그림
이제 우리는 맞춤형 해커 키보드에 필요한 모든 구성 요소를 점진적으로 구축했으며 큰 그림을 볼 때입니다. 키보드 내부는 수많은 노드가 상호 연결된 미니 컴퓨터 네트워크와 같습니다. 차이점은 노드 사이의 거리가 미터나 킬로미터가 아니라 센티미터로 측정되고 노드가 완전한 컴퓨터가 아니라 작은 집적 회로라는 점입니다.
지금까지 개발자 키보드의 장치 측 세부 사항에 대해 많은 말이 있었지만 호스트 측 소프트웨어인 UHK Agent에 대해서는 그다지 많이 언급되지 않았습니다. 그 이유는 현재로서는 Agent가 하드웨어나 펌웨어와 달리 매우 초보적이기 때문입니다. 그러나 Agent의 상위 수준 아키텍처가 결정되었으며 공유하고 싶습니다.
UHK 에이전트는 사용자의 요구에 맞게 키보드를 사용자 정의할 수 있는 구성기 응용 프로그램입니다. 풍부한 클라이언트임에도 불구하고 Agent는 웹 기술을 사용하고 node-webkit 플랫폼 위에서 실행됩니다.
에이전트는 특별한 장치별 USB 제어 요청을 보내고 그 결과를 처리하여 node-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 기능과 함께 마우스 포인터의 속도와 가속도를 설정할 수 있습니다. 하늘이 한계입니다.
프로토타입 만들기
맞춤형 키보드 프로토타입을 만드는 데 많은 작업이 필요합니다. 우선 기계 설계를 마무리해야 하며, 그 자체로 상당히 복잡하며 맞춤형 설계 플라스틱 부품, 레이저 절단 스테인리스 강판, 정밀 가공된 강철 가이드 및 두 개의 키보드 반쪽을 함께 고정하는 네오디뮴 자석을 포함합니다. 제작이 시작되기 전에 모든 것이 CAD로 설계됩니다.
3D 프린팅된 키보드 케이스는 다음과 같습니다.
기계적 설계 및 회로도를 기반으로 인쇄 회로 기판을 설계해야 합니다. 오른쪽 PCB는 KiCad에서 다음과 같이 보입니다.
그런 다음 PCB가 제작되고 표면 실장 부품을 손으로 납땜해야 합니다.
마지막으로, 3D 프린팅, 플라스틱 부품 연마 및 페인팅을 포함한 모든 부품을 제작하고 모든 것을 조립한 후 다음과 같이 작동하는 해커 키보드 프로토타입이 완성됩니다.
결론
저는 개발자의 키보드를 음악가의 악기에 비유하는 것을 좋아합니다. 생각해보면 키보드는 상당히 친밀한 물건입니다. 결국 우리는 하루 종일 그것들을 사용하여 캐릭터별로 내일의 소프트웨어를 제작합니다.
아마도 위의 이유로 인해 Ultimate Hacking Keyboard 개발을 특권으로 생각하고 모든 어려움에도 불구하고 매우 흥미로운 여정과 믿을 수 없을 정도로 강렬한 학습 경험을 하는 경우가 많습니다.
이것은 광범위한 주제이며 여기에서 표면만 긁을 수 있습니다. 이 기사가 재미있고 흥미로운 자료로 가득 차 있기를 바랍니다. 질문이 있으시면 댓글로 알려주세요.
마지막으로 https://ultimatehackingkeyboard.com을 방문하여 자세한 정보를 확인하고 구독을 신청하면 캠페인 시작에 대한 알림을 받을 수 있습니다.