Do zero: como eu construí o teclado dos sonhos do desenvolvedor

Publicados: 2022-03-11

Trabalhando um dia em agosto de 2007, não pude deixar de perceber que meu teclado normal de PC não me serviu tanto quanto possível. Eu tinha que mover minhas mãos entre os vários blocos do meu teclado excessivamente, centenas, senão milhares de vezes por dia, e minhas mãos estavam desconfortavelmente próximas umas das outras. Deve haver uma maneira melhor, pensei.

Essa percepção foi seguida por um sentimento avassalador de empolgação enquanto eu pensava em personalizar o melhor teclado para desenvolvedores - e mais tarde, a percepção de que, como desenvolvedor de software embarcado freelancer, eu estava irremediavelmente sem noção sobre hardware.

Na época, eu estava bastante ocupado com outros projetos, mas não passou um dia em que eu não pensasse em construir o teclado hacker. Logo comecei a dedicar meu tempo livre para trabalhar no projeto. Consegui aprender um conjunto de habilidades totalmente novo, persuadir um amigo meu, Andras Volgyi, engenheiro mecânico extraordinário, a participar do projeto, reunir algumas pessoas-chave e dedicar tempo suficiente à criação de protótipos funcionais. Hoje em dia, o Ultimate Hacking Keyboard é uma realidade. Estamos progredindo diariamente e o lançamento de nossa campanha de crowdfunding está ao nosso alcance.

Comecei pensando em como mudar o layout do teclado e terminei com isso!

Passar de um background de software, sem saber nada sobre eletrônica, para projetar e construir um dispositivo de hardware poderoso e comercializável, é uma experiência interessante e fascinante. Neste artigo, descreverei o design de como essa obra-prima eletrônica funciona. Uma compreensão básica dos diagramas de circuitos eletrônicos pode ajudá-lo a acompanhar.

Como você faz um teclado?

Depois de dedicar milhares de horas da minha vida a esse tópico, é um grande desafio para mim dar uma resposta curta, mas há uma maneira interessante de responder a essa pergunta. E se começarmos com algo simples, como uma placa Arduino, e gradualmente a construirmos para ser o Ultimate Hacking Keyboard? Não deve ser apenas mais digerível, mas extremamente educativo. Portanto, deixe nossa jornada de tutorial de teclado começar!

Primeiro passo: um teclado sem teclas

Primeiro, vamos fazer um teclado USB que emite o caractere x uma vez por segundo. A placa de desenvolvimento Arduino Micro é uma candidata ideal para esse fim, pois possui o microcontrolador ATmega32U4 - um microcrontroller AVR e o mesmo processador que é o cérebro do UHK.

A placa Arduino Micro foi a base para construir meu teclado para desenvolvedores.

Quando se trata de microcontroladores AVR com capacidade USB, o Lightweight USB Framework for AVRs (LUFA) é a biblioteca de escolha. Ele permite que esses processadores se tornem o cérebro de impressoras, dispositivos MIDI, teclados ou quase qualquer outro tipo de dispositivo USB.

Ao conectar um dispositivo à porta USB, o dispositivo precisa transferir algumas estruturas de dados especiais chamadas descritores USB. Esses descritores informam ao computador host o tipo e as propriedades do dispositivo que está sendo conectado e são representados por uma estrutura em árvore. Para tornar as coisas ainda mais complexas, um dispositivo pode implementar não apenas uma, mas várias funções. Vamos ver a estrutura de descritores do UHK:

  • Descritor de dispositivo
    • Descritor de configuração
      • Descritor de interface 0: GenericHID
        • Descritor de endpoint
      • Descritor de interface 1: Teclado
        • Descritor de endpoint
      • Descritor de interface 2: Mouse
        • Descritor de endpoint

A maioria dos teclados padrão expõe apenas um único descritor de interface de teclado, o que faz sentido. No entanto, como um teclado de programação personalizado, o UHK também expõe um descritor de interface do mouse, pois o usuário pode programar teclas arbitrárias do teclado para controlar o ponteiro do mouse para que o teclado possa ser usado como um mouse. A interface GenericHID serve como um canal de comunicação, para trocar informações de configuração de todas as funções especiais do teclado. Você pode ver a implementação completa dos descritores de dispositivo e configuração do UHK no LUFA aqui.

Agora que criamos os descritores, é hora de enviar o caractere x a cada segundo.

 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; }

O USB é um protocolo de pesquisa, o que significa que o computador host consulta o dispositivo em intervalos regulares (geralmente 125 vezes por segundo) para descobrir se há novos dados a serem enviados. O retorno de chamada relevante é a função CALLBACK_HID_Device_CreateHIDReport() , que neste caso envia o scancode do caractere x para o host sempre que a variável isSecondElapsed contiver 1 . isSecondElapsed é definido como 1 no loop principal por segundo e definido como 0 no retorno de chamada.

Etapa dois: um teclado de quatro teclas

Neste ponto, nosso teclado não é muito útil. Seria bom se pudéssemos digitar nele. Mas para isso precisamos de teclas, e as teclas precisam ser organizadas em uma matriz de teclado. Um teclado de 104 teclas de tamanho normal pode ter 18 linhas e 6 colunas, mas simplesmente teremos uma humilde matriz de teclado 2x2 para inicializar. Este é o esquema:

Para personalizar um teclado hacker, você deve considerar cuidadosamente a matriz de teclas.

E é assim que fica em uma placa de ensaio:

A configuração da breadboard é uma etapa crítica na construção de um teclado para desenvolvedores.

Supondo que ROW1 esteja conectado a PINA0 , ROW2 a PINA1 , COL1 a PORTB0 e COL2 a PORTB1 , veja como é o código de digitalização:

 /* 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); } } }

O código varre uma coluna de cada vez e, dentro dessa coluna, lê os estados das chaves individuais. O estado dos comutadores de chave é salvo em uma matriz. Dentro de nossa função CALLBACK_HID_Device_CreateHIDReport() anterior, os códigos de varredura relevantes serão enviados com base no estado desse array.

Terceiro passo: um teclado com duas metades

Até agora, criamos o início de um teclado normal. Mas neste tutorial de teclado, estamos buscando uma ergonomia avançada e, como as pessoas têm duas mãos, é melhor adicionar outra metade do teclado à mistura.

A outra metade contará com outra matriz de teclado, funcionando da mesma forma que a anterior. A novidade empolgante é a comunicação entre as metades do teclado. Os três protocolos mais populares para interconectar dispositivos eletrônicos são SPI, I 2 C e UART. Para fins práticos, usaremos UART neste caso.

Para ser um bom teclado de programação, deve haver comunicação estelar entre as duas metades.

A comunicação bidirecional flui através de RX para a direita e através de TX para a esquerda de acordo com o diagrama acima. VCC e GND são necessários para transferir energia. A UART precisa que os pares usem a mesma taxa de transmissão, número de bits de dados e número de bits de parada. Uma vez que o transceptor UART de ambos os peers é configurado, a comunicação pode começar a fluir.

Por enquanto, a metade esquerda do teclado envia mensagens de um byte para a metade direita do teclado por meio do UART, representando eventos de pressionamento de tecla ou liberação de tecla. A metade direita do teclado processa essas mensagens e manipula o estado da matriz completa do teclado na memória de acordo. É assim que a metade esquerda do teclado envia mensagens:

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

O código para a metade direita do teclado para receber a mensagem é assim:

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

O manipulador de interrupção KeyboardRxCallback() é acionado sempre que um byte é recebido por meio do UART. Dado que os manipuladores de interrupção devem ser executados o mais rápido possível, a mensagem recebida é colocada em um buffer de anel para processamento posterior. O buffer de anel eventualmente é processado de dentro do loop principal e a matriz do teclado será atualizada com base na mensagem.

O acima é a maneira mais simples de fazer isso acontecer, mas o protocolo final será um pouco mais complexo. As mensagens de vários bytes terão que ser tratadas e as mensagens individuais terão que ser verificadas quanto à integridade usando somas de verificação CRC-CCITT.

Neste ponto, nosso protótipo de placa de ensaio parece bastante impressionante:

O protótipo da breadboard está começando a tomar a forma de um teclado customizado para desenvolvedores.

Etapa quatro: conheça o display de LED

Um de nossos objetivos com o UHK era permitir que o usuário defina vários mapas de teclado específicos de aplicativos para aumentar ainda mais a produtividade. O usuário precisa de alguma forma estar ciente do mapa de teclas real que está sendo usado, então um display de LED integrado é embutido no teclado. Aqui está um display protótipo com todos os LEDs acesos:

A tela de LED é fundamental para construir o melhor teclado para desenvolvedores neste tutorial.

O display LED é implementado por uma matriz de LED 8x6:

Os teclados de hackers não estariam completos sem uma matriz de LED 8x6.

Cada duas linhas de símbolos de LED de cor vermelha representam os segmentos de um dos displays de LED de 14 segmentos. Os símbolos de LED brancos representam os três indicadores de status adicionais.

Para conduzir a corrente através de um LED e acendê-lo, a coluna correspondente é configurada para alta tensão e a linha correspondente para baixa tensão. Uma consequência interessante deste sistema é que, a qualquer momento, apenas uma coluna pode ser habilitada (todos os LEDs dessa coluna que devem estar acesos têm suas linhas correspondentes configuradas para baixa tensão), enquanto o restante das colunas é desabilitado . Pode-se pensar que este sistema não pode funcionar para usar o conjunto completo de LEDs, mas na realidade as colunas e linhas são atualizadas tão rapidamente que nenhuma cintilação pode ser vista pelo olho humano.

A matriz de LEDs é acionada por dois circuitos integrados (CIs), um acionando suas linhas e o outro acionando suas colunas. O IC de origem que aciona as colunas é o driver de LED PCA9634 I2C:

Dois circuitos integrados acionam a matriz de LEDs no Ultimate Hacker Keyboard.

O IC do dissipador de matriz de LED que aciona as linhas é o registrador de deslocamento de potência TPIC6C595:

O IC que aciona as fileiras de LEDs se parece com isso.

Vamos ver o código relevante:

 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() é chamado a cada milissegundo, atualizando uma linha da matriz de LED. O array LedStates armazena o estado dos LEDs individuais, é atualizado via UART com base nas mensagens originadas da metade direita do teclado, praticamente da mesma forma que no caso do evento de pressionamento de tecla/liberação de tecla.

A grande imagem

Até agora, construímos gradualmente todos os componentes necessários para o nosso teclado hacker personalizado e é hora de ver o quadro geral. O interior do teclado é como uma mini rede de computadores: muitos nós interconectados. A diferença é que a distância entre os nós é medida não em metros ou quilômetros, mas em centímetros, e os nós não são computadores completos, mas minúsculos circuitos integrados.

O interior do nosso teclado tutorial é composto por nós interconectados.

Muito foi dito até agora sobre os detalhes do lado do dispositivo do teclado do desenvolvedor, mas não tanto sobre o UHK Agent, o software do lado do host. A razão é que, ao contrário do hardware e do firmware, o Agent é muito rudimentar neste ponto. No entanto, a arquitetura de alto nível do Agent é decidida, o que eu gostaria de compartilhar.

O UHK Agent é o aplicativo configurador por meio do qual o teclado pode ser personalizado para atender às necessidades do usuário. Apesar de ser um rich client, o Agent usa tecnologias web e roda em cima da plataforma node-webkit.

O agente se comunica com o teclado usando a biblioteca node-usb enviando solicitações de controle USB especiais e específicas do dispositivo e processando seus resultados. Ele usa Express.js para expor uma API REST para consumo por aplicativos de terceiros. Ele também usa Angular.js para fornecer uma interface de usuário organizada.

 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 ); }

Cada comando tem um identificador de 8 bits e um conjunto de argumentos específicos do comando. Atualmente, apenas o comando reenumerate é implementado. O sendReenumerateCommand() faz com que o dispositivo seja reenumerado como o carregador de inicialização esquerdo ou o carregador de inicialização direito, para atualizar o firmware ou como um dispositivo de teclado.

Pode-se não ter idéia dos recursos avançados que podem ser alcançados por este software, então vou citar alguns: O agente poderá visualizar o desgaste das chaves individuais e notificar o usuário sobre sua expectativa de vida, para que o usuário possa comprar um par de novos interruptores de chave para o reparo iminente. O agente também fornecerá uma interface de usuário para configurar os vários mapas de teclas e camadas do teclado do hacker. A velocidade e a aceleração do ponteiro do mouse também podem ser definidas, juntamente com muitos outros recursos do uber. O céu é o limite.

Criando o protótipo

Muito trabalho é feito para criar protótipos de teclado personalizados. Em primeiro lugar, o projeto mecânico deve ser finalizado, o que é bastante complexo por si só e envolve peças plásticas personalizadas, placas de aço inoxidável cortadas a laser, guias de aço fresadas com precisão e ímãs de neodímio que unem as duas metades do teclado. Tudo é projetado em CAD antes do início da fabricação.

O desenho CAD auxilia na construção de um teclado que funciona bem para os desenvolvedores.

É assim que o estojo do teclado impresso em 3D se parece:

Começamos imprimindo em 3D a caixa do teclado de programação.

Com base no projeto mecânico e no esquema, a placa de circuito impresso deve ser projetada. O PCB da direita se parece com isso no KiCad:

A programação de um teclado começa com o projeto de uma placa de circuito impresso.

Em seguida, o PCB é fabricado e os componentes montados na superfície devem ser soldados à mão:

Soldar os componentes personalizados do teclado garante que ele funcione corretamente quando estiver no estojo.

Finalmente, depois de fabricar todas as peças, incluindo impressão 3D, polir e pintar as peças plásticas e montar tudo, acabamos com um protótipo de teclado hacker funcional como este:

Conclusão

Eu gosto de comparar os teclados dos desenvolvedores com os instrumentos dos músicos. Os teclados são objetos bastante íntimos se você pensar bem. Afinal, nós os usamos o dia todo para criar o software de amanhã, personagem por personagem.

Provavelmente por causa do exposto, considero o desenvolvimento do Ultimate Hacking Keyboard um privilégio e, apesar de todas as dificuldades, na maioria das vezes tem sido uma jornada muito emocionante e uma experiência de aprendizado incrivelmente intensa.

Este é um tópico amplo, e eu só poderia arranhar a superfície aqui. Minha esperança é que este artigo tenha sido muito divertido e cheio de material interessante. Se você tiver alguma dúvida, por favor, deixe-me saber nos comentários.

Por fim, você pode visitar https://ultimatehackingkeyboard.com para obter mais informações e se inscrever para ser notificado sobre o lançamento de nossa campanha.