从头开始(我如何构建开发人员的梦想键盘)

本文概述

  • 第一步:没有按键的键盘
  • 第二步:四个键的键盘
  • 第三步:两半键盘
  • 第四步:认识LED显示屏
  • 整体情况
  • 创建原型
  • 总结
2007年8月的一天, 我忍不住意识到普通的PC键盘无法为我提供尽可能多的服务。我不得不在键盘的各个块之间过度动手, 每天要动动数百次(即使不是数千次), 而且彼此的手也不舒服。我想一定有更好的方法。
当我想到为开发人员定制最好的键盘时, 这种感觉随之而来, 给人以压倒性的兴奋感;后来, 我意识到, 作为一名自由嵌入式软件开发人员, 我对硬件毫无头绪。
当时, 我非常忙于其他项目, 但是一天没想到不打算构建黑客键盘。不久, 我开始将自己的空闲时间投入到该项目中。我设法学习了一套全新的技能, 并说服了我的一个朋友机械工程师ExtrarásV?lgyi加入该项目, 召集了一些关键人员, 并花了足够的时间来创建工作原型。如今, 终极黑客键盘已成为现实。我们每天都在进步, 而且众筹活动即将启动。
从头开始(我如何构建开发人员的梦想键盘)

文章图片
从软件背景到对电子学一无所知, 再到设计和构建功能强大的适销对路的硬件设备, 是一种有趣而有趣的经历。在本文中, 我将描述该电子杰作的设计。对电子电路图的基本了解可以帮助你继续学习。
你如何制作键盘?
在将我的一生投入数千小时之后, 简短地回答这个问题对我来说是一个巨大的挑战, 但是有一种有趣的方式可以回答这个问题。如果我们从诸如Arduino板之类的简单事物开始, 逐步将其构建成为Ultimate Hacking Keyboard键盘, 该怎么办?它不仅应该更易于消化, 而且应该具有很高的教育意义。因此, 让我们的键盘教程之旅开始吧!
第一步:没有按键的键盘 首先, 让我们制作一个USB键盘, 该键盘每秒发出一次x字符。 Arduino Micro开发板是实现此目的的理想选择, 因为它具有ATmega32U4微控制器-AVR微控制器和UHK的大脑相同的处理器。
从头开始(我如何构建开发人员的梦想键盘)

文章图片
对于支持USB的AVR微控制器, 轻便的AVR USB框架(LUFA)是首选的库。它使这些处理器成为打印机, MIDI设备, 键盘或几乎任何其他类型的USB设备的大脑。
将设备插入USB端口时, 设备必须传输一些称为USB描述符的特殊数据结构。这些描述符告诉主机计算机所连接设备的类型和属性, 并由树结构表示。使事情变得更加复杂的是, 设备不仅可以实现一个功能, 而且可以实现多种功能。我们来看看UHK的描述符结构:
  • 设备描述符
    • 配置描述符
      • 接口描述符0:GenericHID
        • 端点描述符
      • 接口描述符1:键盘
        • 端点描述符
      • 接口描述符2:鼠标
        • 端点描述符
大多数标准键盘只公开一个键盘接口描述符, 这很有意义。但是, 作为自定义编程键盘, 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字符的扫描码发送给主机。从回调到0。
第二步:四个键的键盘 在这一点上, 我们的键盘还不是很有用。如果我们可以实际输入, 那就太好了。但是为此, 我们需要按键, 并且按键必须排列在键盘矩阵中。完整的104键键盘可以包含18行和6列, 但我们仅需一个不起眼的2× 2键盘矩阵即可启动。这是原理图:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
这是在面包板上的外观:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
假设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, I2C和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); } }

每当通过UART接收到字节时, 都会触发KeyboardRxCallback()中断处理程序。假定中断处理程序应尽快执行, 则将接收到的消息放入环形缓冲区中以供以后处理。环形缓冲区最终会从主循环中进行处理, 并且键盘矩阵将基于该消息进行更新。
上面是实现此目的的最简单方法, 但是最终协议会稍微复杂一些。必须处理多字节消息, 并且必须使用CRC-CCITT校验和检查单个消息的完整性。
在这一点上, 我们的面包板原型看起来非常令人印象深刻:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
第四步:认识LED显示屏 我们使用UHK的目标之一是使用户能够定义多个特定于应用程序的键盘图, 以进一步提高生产力。用户需要某种方式来了解正在使用的实际键映射, 因此键盘内置了集成的LED显示屏。这是所有LED都点亮的原型显示器:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
LED显示由一个8× 6 LED矩阵实现:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
每两行红色LED符号代表14段LED显示屏之一的段。白色LED符号代表另外三个状态指示灯。
为了驱动电流通过LED并点亮它, 相应的列设置为高电压, 相应的列设置为低电压。该系统的一个有趣结果是, 在任何给定时刻, 只能启用一列(该列上所有应点亮的LED均将其对应的行设置为低电压), 而其余列均被禁用。可能有人认为该系统无法使用全套LED, 但实际上列和行的更新速度如此之快, 以至于肉眼看不到闪烁。
LED矩阵由两个集成电路(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是配置程序应用程序, 通过该应用程序可以自定义键盘以满足用户需求。尽管是富客户端, 但Agent使用Web技术并在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()使设备重新枚举为左引导加载程序或右引导加载程序, 以升级固件或键盘设备。
人们可能不知道此软件可以实现的高级功能, 因此我仅举几例:代理将能够可视化各个按键的磨损并通知用户其预期寿命, 因此用户可以购买了几个新的钥匙开关, 以备即将维修。代理还将提供一个用户界面, 用于配置黑客键盘的各种按键映射和层。还可以设置鼠标指针的速度和加速度, 以及其他超级功能的负载。天空是极限。
创建原型 创建定制的键盘原型需要大量工作。首先, 必须完成机械设计, 该机械设计本身非常复杂, 涉及定制设计的塑料部件, 激光切割的不锈钢板, 精密铣削的钢制导板和将两个半键盘固定在一起的钕磁铁。在制造开始之前, 一切都以CAD设计。
从头开始(我如何构建开发人员的梦想键盘)

文章图片
这是3D打印键盘盒的外观:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
基于机械设计和原理图, 必须设计印刷电路板。在KiCad中, 右侧PCB如下所示:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
然后制作PCB, 并且必须用手焊接表面安装的组件:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
最后, 在制造完所有零件, 包括3D打印, 抛光和上漆塑料零件并组装好所有零件之后, 我们最终得到了一个可行的黑客键盘原型, 如下所示:
从头开始(我如何构建开发人员的梦想键盘)

文章图片
总结 我喜欢将开发人员的键盘与音乐家的乐器进行比较。如果考虑一下, 键盘是相当私密的对象。毕竟, 我们一整天都在使用它们来逐字逐句地制作明天的软件。
可能由于上述原因, 我认为开发Ultimate Hacking Keyboard是一种特权, 尽管有许多困难, 但总的来说, 这是一段非常激动人心的旅程, 并且获得了难以置信的强烈学习经验。
这是一个广泛的话题, 我只能在这里进行介绍。我希望这篇文章很有趣, 并且充满了有趣的材料。如有任何疑问, 请在评论中让我知道。
【从头开始(我如何构建开发人员的梦想键盘)】最后, 欢迎你访问https://ultimatehackingkeyboard.com了解更多信息, 并订阅该网站, 以通知我们广告系列的启动。

    推荐阅读