个性化阅读
专注于IT技术分析

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

本文概述

2007年8月的一天, 我忍不住意识到普通的PC键盘无法为我提供尽可能多的服务。我不得不在键盘的各个块之间过度动手, 每天要动动数百次(即使不是数千次), 而且彼此的手也不舒服。我想一定有更好的方法。

当我想到为开发人员定制最好的键盘时, 这种感觉随之而来, 给人以压倒性的兴奋感;后来, 我意识到, 作为一名自由嵌入式软件开发人员, 我对硬件毫无头绪。

当时, 我非常忙于其他项目, 但是一天没想到不打算构建黑客键盘。不久, 我开始将自己的空闲时间投入到该项目中。我设法学习了一套全新的技能, 并说服了我的一个朋友机械工程师ExtrarásVölgyi加入该项目, 召集了一些关键人员, 并花了足够的时间来创建工作原型。如今, 终极黑客键盘已成为现实。我们每天都在进步, 而且众筹活动即将启动。

我首先考虑如何更改键盘布局,然后完成了!

从软件背景到对电子学一无所知, 再到设计和构建功能强大的适销对路的硬件设备, 是一种有趣而有趣的经历。在本文中, 我将描述该电子杰作的设计。对电子电路图的基本了解可以帮助你继续学习。

你如何制作键盘?

在将我的一生投入数千小时之后, 简短地回答这个问题对我来说是一个巨大的挑战, 但是有一种有趣的方式可以回答这个问题。如果我们从诸如Arduino板之类的简单事物开始, 逐步将其构建成为Ultimate Hacking Keyboard键盘, 该怎么办?它不仅应该更易于消化, 而且应该具有很高的教育意义。因此, 让我们的键盘教程之旅开始吧!

第一步:没有按键的键盘

首先, 让我们制作一个USB键盘, 该键盘每秒发出一次x字符。 Arduino Micro开发板是实现此目的的理想选择, 因为它具有ATmega32U4微控制器-AVR微控制器和UHK的大脑相同的处理器。

Arduino Micro板是为开发人员构建键盘的基础。

对于支持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显示屏对于为开发人员构建最佳键盘至关重要。

LED显示由一个8×6 LED矩阵实现:

没有8x6 LED矩阵,黑客键盘就不完整。

每两行红色LED符号代表14段LED显示屏之一的段。白色LED符号代表另外三个状态指示灯。

为了驱动电流通过LED并点亮它, 相应的列设置为高电压, 相应的列设置为低电压。该系统的一个有趣结果是, 在任何给定时刻, 只能启用一列(该列上所有应点亮的LED均将其对应的行设置为低电压), 而其余列均被禁用。可能有人认为该系统无法使用全套LED, 但实际上列和行的更新速度如此之快, 以至于肉眼看不到闪烁。

LED矩阵由两个集成电路(IC)驱动, 一个驱动其行, 另一个驱动其列。驱动列的源IC是PCA9634 I2C LED驱动器:

两个集成电路驱动Ultimate Hacker Keyboard上的LED矩阵。

驱动行的LED矩阵接收器IC是TPIC6C595电源移位寄存器:

驱动LED行的IC看起来像这样。

让我们看一下相关代码:

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设计。

CAD图有助于构建对开发人员功能良好的键盘。

这是3D打印键盘盒的外观:

我们从3D打印编程键盘盒开始。

基于机械设计和原理图, 必须设计印刷电路板。在KiCad中, 右侧PCB如下所示:

对键盘进行编程始于设计印刷电路板。

然后制作PCB, 并且必须用手焊接表面安装的组件:

焊接自定义键盘组件可确保其在装入外壳后能够正常工作。

最后, 在制造完所有零件, 包括3D打印, 抛光和上漆塑料零件并组装好所有零件之后, 我们最终得到了一个可行的黑客键盘原型, 如下所示:

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

总结

我喜欢将开发人员的键盘与音乐家的乐器进行比较。如果考虑一下, 键盘是相当私密的对象。毕竟, 我们一整天都在使用它们来逐字逐句地制作明天的软件。

可能由于上述原因, 我认为开发Ultimate Hacking Keyboard是一种特权, 尽管有许多困难, 但总的来说, 这是一段非常激动人心的旅程, 并且获得了难以置信的强烈学习经验。

这是一个广泛的话题, 我只能在这里进行介绍。我希望这篇文章很有趣, 并且充满了有趣的材料。如有任何疑问, 请在评论中让我知道。

最后, 欢迎你访问https://ultimatehackingkeyboard.com了解更多信息, 并订阅该网站, 以通知我们广告系列的启动。

赞(0)
未经允许不得转载:srcmini » 从头开始:我如何构建开发人员的梦想键盘

评论 抢沙发

评论前必须登录!