
简介针对Qt Widget与Qt for Android平台开发的纯数字键盘组件适合需要限制数字输入的移动或桌面应用场景如电话号码、密码、金额填写等。资源共5个文件以C头文件、源文件与Qt UI布局文件为主包含主键盘逻辑、自定义输入框及界面定义整体压缩包仅4KB结构紧凑便于直接集成到现有工程。键盘设计覆盖0-9数字键、小数点及清除键通过Qt信号与槽机制与主界面交互并考虑了跨平台显示效果与误触风险。开发者可参考其中对图形视图类的运用扩展复制、粘贴、确认等自定义功能。当前已有532人浏览学习适合Qt初学者理解数字键盘实现思路也可为有经验的开发者提供轻量级输入组件的实用范例。 打开“纯数字键盘123.rar”这个压缩包之前先说说我为什么非要自己动手做一把纯数字键盘。年初换了一台轻薄本键盘没有数字小键盘每天在财务系统里录订单、核金额右手不得不在字母区、数字区之间来回横跳一个月下来手腕酸痛不说录入速度也实在看不下去。市面上的小键盘不是没有但几十块钱的货要么按键手感稀碎要么根本不支持自定义映射连换个键位都要靠AutoHotkey硬改。后来我决定自己写一个于是有了这个打包好的项目。这个压缩包不是一张图纸也不是一份代码而是一个完整的DIY纯数字键盘项目固件源码、电路设计、外壳STL、上位机配置工具全在里面。如果你也在纠结“买个现成的还是自己搞”或者想彻底搞懂USB HID键盘是怎么工作的这个项目的完整记录应该能帮你少走不少弯路。1. 为什么我会做一个只有数字键的小键盘1.1 被笔记本键盘逼出来的需求很多人觉得“数字键盘”就是给会计用的其实不然。订单号、快递单、发票号码、验证码、座机号、Excel表格里的批量数据凡是需要连续输入纯数字的场景小键盘都能让效率翻倍。独立数字键盘的核心优势是“盲打不影响主键盘区”——你的左手可以停在原位右手在小键盘上飞快录入不需要低头找键位。但市面成品小键盘最大的问题是默认走NumLock逻辑。当你插上某把成品小键盘按0-9发送的是小键盘区扫描码操作系统会根据NumLock状态决定是输出数字还是方向键。这个设计在Windows上还好在macOS和Linux上经常出现“按了数字却变成鼠标移动”的怪事。我实在不想被这种“祖宗设计”绑架所以从一开始就明确我的键盘只在数据录入场景使用它必须永远输出0-9绝不允许被NumLock干扰。1.2 为什么自己做的而不是直接买从成本角度看自己做的确不便宜一套外壳轴体主控下来可能比成品还贵。但对比之下有几个东西是成品给不了的键位完全自定义我可以把小数点、回车、退格都放到最顺手的位置不需要迁就开模定死的公模布局。手感可控选什么轴、什么键帽按下去是清脆还是绵软自己说了算。固件可改想给某个键加长按功能、组合键、宏指令改几行代码重新烧录就行不用装一堆常驻后台的驱动软件。排错的过程就是学习的过程做这个项目之前我只知道“键盘就是个输入设备”做完之后我对USB协议、HID设备类、MCU中断的理解完全拉满。所以这个压缩包其实是一份“学习实用”的打包产物适合两类人一类是想要一把顺手小键盘的普通用户另一类是正在找USB HID练手项目的嵌入式入门者。1.3 “123”是什么压缩包里到底有什么“123”其实是我内部版本号的简写完整版本是v1.2.3也就是第三次结构调整后的配置。这个压缩包包含firmware/基于C编写的固件源码支持按键扫描、去抖、HID上报和层切换功能。hardware/原理图与PCB工程文件用的是开源EDA格式可以直接打开并打样。case/3D打印外壳的STL模型文件。tool/一个简单的上位机配置脚本用Python写的可以无源码修改按键映射。README.md完整的手工焊接说明、烧录步骤和调试日志。test/我实测过程中录下的按键上报抓包记录方便对照验证。你可以把压缩包理解成一份“逆向小白也能复刻”的完整方案。接下来我从按键布局、主控、固件到实测踩坑把这个项目的每个关键决策都拆开讲清楚。2. 按键布局与功能定义数字键盘的设计取舍2.1 键位方案看似照搬其实做了三处改动纯数字键盘的经典布局是4行3列共12键加上回车、退格、小数点总共15键左右。我的方案如下行按键第1行7 8 9第2行4 5 6第3行1 2 3第4行0 小数点 回车左侧独立键退格最底层拇指位空格默认作为FN层切换这基本是沿用了传统小键盘的肌肉记忆但我做了三个调整第一把退格挪到了左边用左手拇指去按。原因是右手在小键盘上连续录入时如果按右边回程去够退格键很容易误触数字键。左手拇指天然处于闲置状态分担退格操作后右手的动作幅度大幅缩小。第二没有放NumLock键。前面说过NumLock在现代自定义键盘里就是一个反直觉的坑绝大多数人根本不需要它。如果你确实要在某些场景临时开启数字锁定模式可以通过固件层映射实现。第三加了FN层。FN键放在左下角拇指位按住FN时数字键会变成F1-F12、媒体控制键和批量输入快捷键。这个设计让一把“纯数字键盘”其实具备完整键盘80%的扩展能力后面第6章细说。2.2 关于NumLock的坑为什么必须绕开它这里值得多说几句。USB键盘规范里有两套数字键扫描码一套是主键盘区上方的数字键0-9Usage ID 0x1E到0x27另一套是小键盘专用码Usage ID 0x53到0x5F。很多成品小键盘固执地发送小键盘专用码于是系统收到后还要查NumLock状态才能判断是什么意思。你按一下小键盘的“2”如果NumLock被不小心关了出来的可能是“下箭头”。这个问题的根源在PC/AT时代的遗留设计从1980年代一直传到今天属于“历史的包袱”。现代固件QMK、KMK、以及我这个项目的自定义固件都建议直接使用主键盘数字键的Usage ID上报这样操作系统会无脑输出数字完全绕开NumLock状态机。实测下来在Windows、macOS、Linux上表现完全一致一句话总结别用数字键盘专用码直接用主数字区的码能省掉你90%的兼容性问题。2.3 轴体与键帽手感差异比想象中大数字键盘作为高频录入工具轴体选择直接影响手感和手部疲劳度。我对比过几类Cherry茶轴带段落感按压反馈清晰适合长时间打字但噪音稍大。静音红轴线性手感下压顺滑噪音小适合办公室环境但容易误触。矮轴按键行程短触发快适合放在笔记本旁边当作延伸手感比普通轴体轻巧。剪刀脚常见于成品小键盘手感偏肉回弹一般胜在便宜低调。我最终选了静音红轴配矮键帽。原因很简单这款键盘要和笔记本并排使用普通高度轴体加键帽会让整体高出笔记本键盘一大截手跨过去时高度差会绷着前臂肌肉。矮轴把总高度压低到差不多和笔记本键盘齐平手腕的过渡更自然。键帽方面我用了OEM高度的PBT键帽表面带一点磨砂纹理手指出汗时不容易打滑。有一个细节值得注意普通键帽的圆柱凹面设计放在数字键上其实不如扁平键面舒服因为手指在数字键上是快速点按而不是横向滑动凹面反而会增加手指的定位感。这一点纯属个人偏好我的建议是买一套键帽回来试按几天再决定不用一步到位。3. 主控选型与电路从按键矩阵到USB3.1 主控芯片对比与选择理由做USB键盘最核心的部件是一颗带USB外设的单片机。我对比过几款主流方案芯片架构内置USB开发资源适合场景ATmega32U4AVR 8位是极丰富QMK原生支持键盘DIY标准答案RP2040ARM Cortex-M0是丰富支持CircuitPythonPIO玩法多可做小屏幕STM32F072ARM Cortex-M0是较多性能强可玩复杂固件CH5528051是较少便宜适合低成本量产3.2 为什么这项目选ATmega32U4我选ATmega32U4有几个很实际的理由一是原生USB控制器的库很成熟在AVR上做HID键盘只需要处理几段中断代码即可不需要像STM32那样配置复杂的时钟树和USB外设寄存器。二是开发资料太完善了。QMK固件生态里大量代码都是针对这颗芯片写的有任何问题都能在网上查到讨论帖。对新手来说“老”不是缺点反而是最大的优点。三是价格稳定。虽然这几年芯片价格波动大但ATmega32U4的货源一直很稳定最小系统板也就30多块钱坏了不心疼。当然如果你准备量产或者追求极致性价比CH552是更抠成本的选择一颗芯片几块钱固件开发起来稍微费点劲。我只是做个自用分享选32U4最省心。3.3 按键矩阵为什么不是一颗按键接一个引脚很多人第一次接触键盘电路时会问一颗按键接一个GPIO不就行了十五个键就需要十五个引脚确实不多。但如果你把键盘键数扩展到几十个这方案就扛不住了。按键矩阵的思路是把按键排列成N行M列每个按键位于某一行和某一列的交点。扫描时MCU逐行拉低电平再读取每一列的电平状态就能判断哪个按键被按下。矩阵扫描还有一个必须处理的问题鬼键Ghost Key。当三个按键恰好形成一个矩形第四角的按键即使没被按下也会被读取成按下状态。解决办法是在每个按键上串联一个二极管方向统一让电流只能从行流向列阻断回流路径。我顺手把所有按键的二极管都焊上了成本几毛钱但杜绝了键位误报这笔账非常划算。3.4 电路与供电的注意事项ATmega32U4的USB D引脚默认需要外接1.5kΩ上拉电阻到3.3V用来告知主机这是一个全速设备但很多最小系统板内部已经处理好了你在画PCB时务必确认这一点。另外USB信号线上我加了22Ω的串联电阻用来抑制信号振铃防止长线缆时出现偶发性的枚举失败。供电方面正常情况下USB口提供5V可以直接给MCU供电。但要注意电脑USB口有短路保护当你在焊接时如果出现短暂短路不一定损坏电脑但保险起见建议用带隔离的USB hub供电测试。还有一个隐藏坑是USB线材质量劣质线材的D/D-阻抗不匹配会导致设备无法识别我有一段时间反复折腾排查最后发现根本原因是手边那根线是坏的——这类低级坑放在后面第5章细讲。外壳方面我设计的是上下壳结构底部留了Type-C开孔和一颗复位按键孔位。材料选的PLA材质打印因为PLA打印尺寸稳定、无翘边日常使用强度足够没必要上ABS。组装时注意固定柱的位置要和PCB开孔对得上三层叠放底壳→PCB→中板→键帽的定位精度我调了好几版期间花了大量时间反复打印测试。如果你想直接复刻建议先用我打包好的STL打印一套BOM测试件确认无误后再上正式材料。4. 固件实现按键扫描、去抖与HID上报4.1 框架选择从零手写还是基于QMK这里有个路线选择直接用QMK固件改配置是最快的几行JSON就能定义键盘矩阵和按键映射但我的目标是彻底搞懂键盘固件的工作原理所以我选择了从零手写一版精简固件只保留最核心的扫描和HID上报逻辑。手写固件的好处是代码量小逻辑完全透明每个函数你都知道它在干什么。坏处是扩展性不如QMK比如RGB灯效、旋转编码器这些高级功能都要自己加。对于纯数字键盘这种简单设备手写完全够了。主体代码结构如下usb.cUSB控制器初始化和HID类请求处理。hid_report.c构造8字节HID报文。matrix.c矩阵扫描与按键去抖。keymap.c按键码到扫描码的映射表支持层切换。4.2 扫描与去抖的核心逻辑矩阵扫描的逻辑非常朴素for (row 0; row ROWS; row) { set_row_low(row); // 将当前行拉低 delay_us(5); // 等电平稳定 for (col 0; col COLS; col) { if (read_col(col) LOW) { matrix[row][col] 1; } } set_row_low_disable(row); // 释放当前行 }但实际固件不能简单扫描一次就上报因为机械开关在按下和释放瞬间会产生抖动时间一般在5~20ms如果不处理一次按键会被系统判定成多次点击。我采用状态机加时间戳去抖IDLE状态检测到按键按下记录时间戳进入BOUNCE状态。BOUNCE状态持续等待10ms再读一次按键状态如果确认仍然按下说明是真实按下进入PRESSED状态并上报按键。PRESSED状态检测到释放后再次进入10ms去抖稳定后进入IDLE。这套做法比单纯delay(10)的好处是去抖期间不会阻塞其他按键的扫描多个按键同时按下也能正确识别。实测下来静音红轴的抖动大概在6~8ms10ms去抖窗口足够稳定。如果你遇到按键偶尔连击先检查去抖窗口是不是太小别急着怀疑硬件。4.3 USB HID上报8字节报文怎么填键盘的HID报文固定格式是8字节字段如下typedef struct { uint8_t modifier; // 修饰键比如Ctrl0x01, Shift0x02 uint8_t reserved; // 厂商保留通常置0 uint8_t keys[6]; // 最多同时上报6个普通按键 } __attribute__((packed)) keyboard_report_t;按键码用的是HID Usage ID。例如主键区数字“1”对应0x1E“0”对应0x27回车是0x28。我固件里建了一张键码映射表把矩阵坐标映射到对应的HID Usage IDconst uint8_t keymap[ROWS][COLS] { {0x1E, 0x1F, 0x20}, // 7 8 9 {0x22, 0x23, 0x24}, // 4 5 6 {0x26, 0x27, 0x28}, // 1 2 3注意这里是示意 // ... };调试阶段我直接用Python脚本监听USB HID报告确认每个按键上报的字节与预期一致这样比肉眼观察屏幕上是否输出数字高效得多。核心代码参考如下import usb.core import usb.util dev usb.core.find(idVendor0x1234, idProduct0x5678) if dev is None: raise ValueError(Device not found) while True: try: data dev.read(0x81, 8, timeout1000) print(data.tolist()) except usb.core.USBTimeoutError: pass固件烧录我用的是AVR的burn-bootloader流程具体命令在README里有。有一点要提醒ATmega32U4的引导加载程序Bootloader和应用程序共用Flash如果你烧录时不小心覆盖了Bootloader地址之后就只能通过ICSP接口恢复了所以烧录前一定确认fuse位配置正确。4.4 连接主机枚举过程的几个关键点USB设备插入后主机会向设备发送标准请求比如读取设备描述符、配置描述符等。设备必须在90秒内正确响应并完成枚举否则会被挂起。我调试时遇到最多的枚举失败原因无非三类D上拉电阻没接好主机认为设备是低速设备直接不响应。晶振频率不准确AVR内部RC振荡器偏差大导致USB时钟不稳。线材质量差信号衰减严重握手失败。第二点值得展开ATmega32U4有两个时钟源一个是内部RC振荡器另一个是外部16MHz晶振。USB协议对时钟精度要求达到±0.25%内部RC振荡器根本达不到必须使用外部晶振。我在电路上留了16MHz晶振和两个22pF负载电容。有一次我电容焊接得离晶振太远寄生电容破坏了震荡幅度设备时好时坏。后来把两个电容尽量靠近晶振引脚并保证地线路径短问题才彻底解决。5. 实测踩坑记录这三个坑让我改了三版5.1 第一版问题USB枚举时随机失败第一版固件写好后插上电脑有概率识别不到设备重新插拔又好了。这种“玄学”问题最烦人因为它断断续续很难抓现行。我的排查链路是这样的先用一个USB分析仪抓设备上电后的USB物理信号发现D的上拉信号偶尔根本没出现说明MCU里的USB模块没有正确启动。检查代码后发现我在初始化USB前先做了一个较长时间的EEPROM读取操作如果主机在这个间隙发出复位信号MCU就错过了之后USB外设就进入了一个死锁状态。解决方案是把EEPROM读取放在USB初始化完成之后同时给USB初始化函数加一个带重试的握手逻辑确保USB模块真正进入配置状态后才继续执行其他代码。改完之后插拔两百多次没再出现枚举失败。这里也暴露了一个常见认知误区不是“上电后立刻初始化USB就万事大吉”主机侧的时序要求和设备侧的任务调度同样重要。5.2 第二版问题按键漏字与连发进入稳定测试阶段后我发现某些按键偶尔会漏字少数按键会“连发”按一下出来两个字符。一开始我怀疑是轴体质量问题换了一颗新的轴问题依旧。后来写了个压力测试脚本每个键连续点击200次并记录上报次数把有问题的键位汇总后发现漏字和连发都集中在同一行和同一列的交叉点上指向性很明确。用万用表逐一量测后发现某一行信号线上有一颗二极管虚焊接触不良导致扫描时该行电平状态不稳定。系统有时能读到按下有时又丢失。虚焊的二极管在静态测试时接触正常但每次按键的微小震动都会改变它的接触电阻。处理方式很粗暴把所有焊点重新过了一遍焊锡然后用放大镜检查有没有锡桥。顺便把去抖窗口从原来的5ms调到了10ms补充一个保护逻辑如果按键状态连续两次扫描不一致不改变上报状态等待下次扫描确认。这样双管齐下之后连发和漏字都消失了。这个经历让我养成了一个习惯任何键盘固件问题先排查硬件连接再动软件参数不要一上来就怀疑代码。5.3 第三版问题编译内存溢出加了FN层切换和宏功能之后固件编译报错Arduino IDE提示Flash空间不够但当时我只用了不到一半Flash报错信息却指向text section overflow。查了半天发现问题出在字符串常量上我在代码里用char *str Key Pad这样的方式存了一堆调试打印字符串在AVR 8位架构上这种字符串默认被放在RAM里而ATmega32U4的RAM只有2.5KB装不下那么多调试字符串。解决办法是用PROGMEM关键字把常量字符串放到Flash区#include avr/pgmspace.h const char str_kp[] PROGMEM Key Pad;同时把调试打印改成只在按键状态变化时输出而不是每轮扫描都打印。这样一来RAM占用瞬间掉到1KB以下编译通过系统运行也更稳定了。这个小教训提醒我在资源受限的MCU上做开发内存和Flash的管理是基本功不是只有大系统才需要考虑。6. 把纯数字键盘变成生产力工具映射与宏6.1 层切换数字键变功能键“纯数字键盘”听起来功能很单一但只要加上层切换它的战斗力是完全不一样的。我的FN层设计如下按住FN 1 F1按住FN 2 F2以此类推把数字键变成功能键。按住FN 0 切换层再按一下回到数字层相当于临时开启“功能层驻留模式”。FN 回车 媒体播放/暂停FN 退格 音量加减。这种设计在写代码时特别有用。比如调试时你需要连续按F5刷新页面常规做法是手移到笔记本键盘上找F5现在右手按住FN5即可手完全不用离开数字区。层切换的实现逻辑很简单按键映射表从一个二维数组变成三维数组多了一个层索引字段扫描状态机在判断按键时如果当前FN键处于按下状态就去查另一张映射表。6.2 宏一键录入订单号和时间戳在固件里实现字符串宏需要把一串字符转换成对应的HID按键事件然后延时发送。比如我给“FN小数点”绑定了时间戳宏触发后自动输出当前日期和时间的固定格式例如2025-01-15 14:32:08。实际使用中这种宏在录单场景非常香不用再手工输一大串日期。宏的实现思路可以看着像这样void send_macro(const uint8_t *keys, uint8_t len) { for (uint8_t i 0; i len; i) { keyboard_report.keys[0] keys[i]; send_report(); // 按下 clear_report(); // 释放 send_report(); delay(10); } }由于宏涉及较长延时我在FN层设计里刻意避开了“宏执行期间处理其他按键”的复杂并发逻辑宁可让宏执行期间其他按键排队等待。对纯数字录入场景来说这样的取舍简单可靠远比搞一套异步事件调度划算。6.3 最终配置我日常使用的三套方案项目包里默认配置了三套按键方案Layout A默认经典小键盘布局FN层带F1-F12和媒体键适合通用办公。Layout B财务0键宽度加倍加号、减号、回车都集中在右侧适合Excel高频录入。Layout C开发数字键在主键盘区FN层全部绑定编辑快捷键比如复制、粘贴、撤销、切换标签页。切换方案的方式很简单把对应键位定义的头文件替换到工程目录重新编译烧录即可。Python上位机工具也能直接读取和修改USB描述符和默认键位对不改固件的用户友好很多。我个人的使用体会是一开始不要做得太复杂先保证基础布局顺手再去折腾宏和层映射。我前前后后改了十几版配置最后常用的反而还是最基础的Layout A只有AltTab和数字符号这类高频组合键被挂在了FN层上。这把键盘也是我办公桌上用得最频繁的外设比那些刷RGB灯效的成品在“实用”这条路上走得远得多。最后分享一个小技巧如果你也在做类似项目建议把每一版固件的配置文件和打印日志都保存在一个带日期后缀的目录里别像我早期那样直接在同一个文件上改改完发现想退回上一版时只能靠重写。版本管理这种事在个人小项目里最容易被忽略等真的需要回退时就会感激当初那个“多存一份”的自己。本文还有配套的精品资源点击获取