从零自制microduck:基于RP2040的USB HID键盘开发实战
2026/9/12 18:26:07 网站建设 项目流程

你有没有过这种瞬间:每天要重复输入几十遍同样的命令,或者做演示时想一键打出一段固定台词,又或者单纯想搞明白“电脑为什么认得键盘按下的键”。我就是在那个瞬间打开了GitHub,搜到microduck这个项目,然后花了一个周末把它从零做了出来。整个过程比我想象中顺畅,也比预期中更有收获——这根本不是简单“照着抄一个键盘模拟器”,而是一张从硬件选型到第一行代码的完整路线图,带你走完嵌入式开发、USB通信、PCB打样、固件调试的全流程。

这篇文章就是我的实际路线图。不管你之前有没有碰过单片机,只要会用电脑,愿意动手焊几颗按键,就可以照着做一个属于自己的microduck。我会把每一个环节的选择逻辑、踩过的坑、为什么这么选都讲清楚,不是单纯丢给你一堆积木,而是教你理解这些积木是怎么拼在一起的。

1. 先搞清楚microduck是什么,再决定要不要踩这个坑

1.1 microduck在GitHub上到底指什么

microduck不是一个某某公司的商业产品,而是一类开源项目的统称。你在GitHub上搜索microduck,会看到很多仓库,它们的共同点是:做一个袖珍的可编程USB输入设备。外形可以很小,大概两三个硬币叠起来那么厚,上面有几颗按键、一个USB-C口,插到电脑上会立刻被识别成一个键盘。

往深了说,它的本质是一个HID(Human Interface Device,人机交互设备)的完整实现案例。电脑看到它,跟看到普通键盘没有区别,但它内部是由你自己控制的芯片,你想让它什么时候按什么键,它就什么时候按什么键。这样一个东西能做什么,取决于你的想象力:写代码时一键输入常用代码模板、剪视频时一键切换工具、演讲时一键调出下一页,甚至可以把它做成一个带OLED屏幕的信息牌显示CPU占用率。

我最推荐把它当成学习项目的原因在于:它的工程量非常适中。不像做无人机,需要处理电机控制、姿态解算、无线通信这些复杂模块;也不像写一个网页后端,几十个依赖库装下来人都麻了。microduck只需要你把一套最简单的“按键输入到电脑输出”的链路跑通,这刚好卡在“有点挑战”和“不会劝退”之间的平衡点。

1.2 项目整体分几步走

如果只看最终成品,觉得它很神秘,但只要把它拆成阶段,每一步都清晰可见。以软件工程师的视角类比一下:硬件选型相当于选服务器,固件开发相当于写操作系统,按键配置相当于业务逻辑层。这三层各司其职,你可以分别学习、分别验证。

整个项目就五步:

  • 硬件选型:决定主控芯片、按键、电路板、外壳。这一步的产出是一张采购清单。
  • 电路搭建:把芯片、按键、电容电阻按原理图连起来。新手阶段直接用现成开发板加洞洞板就能跳过PCB设计,先跑通功能。
  • 固件开发:写代码让芯片识别按键并模拟键盘输入。这是最花时间的部分,也是乐趣所在。
  • 调试验证:解决按键失灵、乱码、不识别、连击等问题。
  • 外壳与收尾:给它一个能随身携带的家。

我把目标定得很小:第一个目标不是“做一个完美的microduck”,而是“按下我焊的那个按键,电脑上能打出字母A”。只要这个目标达成,整个链路就通了,后续的扩展只是量变,不是质变。

1.3 时间、成本与难度评估

如果你每天能抽出两小时,从零基础到跑通第一行代码,大概一周就够了。如果已经玩过Arduino,一个周末就能完成。

阶段预计时长难度关键产出
硬件选型与采购2至4小时采购清单
开发板与按键搭建2至3小时中低可通电的硬件
开发环境配置1至2小时能烧录的固件链
第一行代码1小时LED点亮
HID键盘实现3至6小时可输入字符的设备

成本方面,我用的方案是RP2040核心板加几颗微动开关,核心板约15元,开关和杂七杂八的零件不超过10元,加上一个几块钱的亚克力外壳,总成本控制在40元以内。就算中途焊坏两片板子,也不心疼。这也是我选择这类主控的原因之一:容错成本足够低。

2. 硬件选型:别一开始就盯着旗舰芯片

2.1 选型前先理清需求

很多人做硬件项目有一个通病:看到芯片评测就上头,觉得核心越多越好、频率越高越好、内存越大越好。我在第一版microduck的选型时就犯了类似的错误,差点去买一块带Wi-Fi、蓝牙、双核、大内存的旗舰芯片,后来发现除了增加SDK学习成本,毫无必要。

选型之前先把需求写清楚。我的需求其实非常朴素:

  • 能被电脑识别为键盘:需要芯片自带USB外设,或者外挂USB转HID方案。
  • 至少能读5到10颗按键:GPIO数量够用就行,不需要几十个引脚。
  • 最好体积小、功耗低:方便做成便携设备。
  • 成本低、资料多:第一次做,资料多少直接决定踩坑后能不能爬出来。

按照这个需求,我锁定了三类主控:RP2040、ESP32系列、以及以ED330为代表的一批低成本国产MCU。本质上它们都能做,但付出的学习和调试成本差别很大。

2.2 主控怎么选:RP2040、ESP32还是ED330

GitHub上microduck的讨论区里,ED330是一个出现频率很高的词。市面上流行的说法是,ED330最早是一批低成本开发板上印的丝印,底层是ESP32-C3的兼容方案,集成USB和蓝牙,价格能压到10元上下。不少人在复刻microduck时都会问一句“用ED330行不行”,答案是可以,但我得泼一盆冷水:它便宜,但代价是SDK和社区资料明显更少。如果你是对着文档能自学的类型,用ED330完全没问题;如果你是第一次接触嵌入式,我建议绕开它。

我自己的选择是RP2040核心版。原因是这颗芯片背后有树莓派基金会的生态加持,官方文档写得像教科书,而且有一个杀手级特性:按住BOOTSEL按钮插入USB,它会直接把你电脑变成一个U盘,拖进去一个UF2文件就能烧录固件。不需要额外的烧录器,不需要装驱动,对新手极其友好。

主控方案核心优势明显短板建议人群
RP2040资料极多、烧录简单、USB功能完善无无线能力,需外挂蓝牙第一次做microduck的人
ESP32-S3内置USB与蓝牙,可做无线版本环境配置略复杂,功耗较高想直接做蓝牙版的人
ED330类低成本MCU价格极低,适合量产或小批量资料少,踩坑全靠自己摸已有一定经验的人

我的建议很明确:第一次做,优先选RP2040。它能让你把注意力集中在理解HID协议和按键扫描上,而不是浪费在折腾开发环境上。

2.3 按键、连接器和外壳的选料清单

主控定了,剩下就是按键、连接器和外壳。按键我推荐最常见的6mm x 6mm微动开关,一颗几毛钱,手感清晰,寿命也足够。如果你想要更好的手感,可以换机械键盘的轴体,但相应地需要设计更大的外壳和轴体固定结构,工程量会涨一截。第一版不建议上机械轴,先把功能跑通再说。

连接器方面,如果买的核心板自带USB-C口,那就直接用。需要注意一个小细节:USB-C的插座在物理上很结实,但在拉扯时容易把焊盘拽掉。所以做外壳时最好给USB口留一个卡槽,让外壳帮忙受力,而不是全靠焊点硬扛。

外壳可以在3D打印、亚克力切割和手工塑料盒之间选。没有3D打印机的话,最简单的方式是买一个现成的迷你铝型材外壳,里面用双面胶固定电路板,亲测也能用很久。

2.4 开发板还是自制PCB,第一次到底怎么选

第一次做,建议直接买现成的开发板,不要自己画PCB去打样。原因不是打样多难,而是当电路不工作时,你需要区分是硬件问题还是固件问题。品牌开发板经过大量验证,通电就能进入已知可用状态,你可以把所有变量缩小到“自己的代码”上。如果第一版就自己画板子,按键不响应时你根本不知道是GPIO配置错了,还是电路短接了。

我是在第二版才开始画PCB的。有了第一版稳定运行的软硬件作为参照,打样回来如果出现问题,至少可以确定主控是好的、固件是好的,问题大概率出在新增的电路部分,排查范围一下子小了很多。这个“先买核心板跑通,再画底板”的顺序,建议所有新手都遵守。

打样方面,嘉立创这类国内厂商已经非常成熟,普通双层板5元就能打样,一周内收货。设计工具我推荐KiCad,免费开源,而且有大量microduck相关项目的原理图可以直接参考。

3. 开发环境搭建:让电脑认识这块小板子

3.1 固件方案横向对比

硬件拿到手后,下一步是让电脑能往芯片里写代码。microduck可选的固件方案非常多,选择不同的方案,后续的代码风格完全不同。

目前主流的做法有三类。第一类是CircuitPython或MicroPython,这类Python解释器型方案极大降低了开发门槛,修改代码后保存文件就能运行,非常适合新手,缺点是按键响应速度稍慢,对于普通输入场景完全够用。第二类是C/C++加TinyUSB库,这是性能最优的方案,也是很多开源项目默认的代码结构,适合想深入理解USB协议栈的人。第三类是Zephyr RTOS,在蓝牙无线方案中很常见,功能强盛但配置复杂,不适合入门。

我第一版用的是CircuitPython,因为它的逻辑非常简单:把开发板连电脑后,它会显示为一个磁盘,你用文本编辑器改里面的code.py文件,保存后设备立刻执行新代码。这个特性让我能把精力完全放在“如何实现键盘消息”上,而不是“怎么把代码编进去”。等代码逻辑验证通过,再迁移到C语言方案提升性能和可控性。

3.2 搭建环境的三个经典坑

搭建开发环境这一步,我踩过三个坑,每个都能让人卡半小时以上。

第一个坑是串口驱动缺失。ESP32系列和不少国产开发板通过串口转USB芯片与电脑通信,最常见的是CH340芯片。Windows系统第一次插上设备时,经常不会自动安装驱动,导致设备管理器里出现一个带黄色感叹号的未知设备。解决方法是去芯片厂商官网下载对应驱动,或者安装驱动精灵类工具自动识别,但我更建议手动装,原因是不想让电脑被一堆全家桶绑架。

第二个坑是RP2040的BOOTSEL模式没进对。很多人第一次接触RP2040,直接插上USB等U盘出现,结果是它直接正常启动了。正确做法是按住BOOTSEL按钮后再把USB线插进去,这时候才会进入烧录模式。我第一次尝试时就卡在这里,以为是板子坏了,后来看了官方文档才明白是自己操作顺序错了。

第三个坑是编辑器选得不好。如果你选Python方案,可以只用文本编辑器加命令行;如果你选C语言方案,对新手最友好的组合是VS Code加PlatformIO插件,它会自动管理工具链和依赖库,省去大量手动配置环境变量的时间。Arduino IDE虽然更简单,但对RP2040这类相对复杂的芯片来说,项目设置不够灵活。

3.3 烧录第一版固件并运行

以RP2040为例,烧录过程比想象中简单得多。先把UF2格式的固件下载下来,按住板子上的BOOTSEL按钮,用USB线连接电脑,等电脑上出现一个名为RPI-RP2的U盘,把UF2文件拖进去,板子会自动重启,U盘消失,烧录完成。

这里有个小技巧:第一次烧录,不要一上来就烧录带业务逻辑的固件,先烧录官方出厂固件或者一个最简单的Blink程序。Blink程序就是让板载LED以一定频率闪烁,它证明主控芯片、电源电路、LED、烧录链路全部正常。做完这步,后面的每一步出问题都可以排除掉“板子坏了”这个可能性。

我第一次烧录时,看到LED亮起来那一下,心里踏实了许多。这相当于买了一台新服务器,先ping通内网IP,再部署业务,是一个道理。

4. 第一行代码:从点亮到能打字

4.1 先弄懂HID键盘是怎么上报按键的

很多教程上来就给代码,但我觉得先花十分钟理解HID协议,后续写代码会顺畅很多。所谓HID键盘,其实做的事情很简单:设备每隔一小段时间,比如1毫秒,向电脑发送一个8字节的数据包,描述当前键盘状态。电脑收到后解析这个包,然后触发对应的键盘事件。

这8字节的结构是固定的。第0字节是修饰键状态,比如Ctrl、Shift、Alt是否被按下;第1字节是保留位,通常填0;后面6个字节是同时按下的普通按键码。举个例子,键盘按下字母“A”时,发送的数据是00 00 04 00 00 00 00 00,其中04就是字母A的键码。松开后,发送全0数据包。

理解了这个机制,你再看microduck的代码,就会发现它其实就是在维护这8个字节的状态。按下按键时把对应键码填进去,松开时清空,仅此而已。

4.2 第一行代码:点亮板载LED

不管选什么固件方案,第一行代码都建议从点灯开始。这不是走形式,而是建立你的最小可信链路。以CircuitPython为例,代码只有几行:

import board import digitalio import time led = digitalio.DigitalInOut(board.LED) led.direction = digitalio.Direction.OUTPUT while True: led.value = True time.sleep(0.5) led.value = False time.sleep(0.5)

把这段代码保存为code.py,放到开发板出现的U盘里,重新插拔USB,板载LED就会以0.5秒间隔闪烁。如果灯不亮,优先检查板子的电源指示灯是否亮、USB线是不是只能充电不能传数据,以及代码里的引脚名称是否与板子对应。

4.3 第二行代码:模拟一次按键输入

LED亮了,接下来就可以写第一段真正像microduck的代码了:让电脑认为连接了一个键盘。在CircuitPython里,这个过程非常直观:

import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard_keycodes import Keycode import time keyboard = Keyboard(usb_hid.devices) time.sleep(2) keyboard.press(Keycode.A) keyboard.release_all()

插上开发板后,等两秒,它会自动向电脑发送一次字母A的按下和释放事件。如果光标当前在某一个文本输入框里,你会看到屏幕里多了一个字母A。

这一步的意义非同小可。它标志着你的代码已经进入USB协议栈,真正在和操作系统交互了。完成这一步之后,你的microduck已经从“一个会闪灯的板子”升级为“一台属于你的输入设备”。

4.4 把按键扫描逻辑补齐

键盘模拟成功,接下来要处理一个实际问题:如何读取你自己焊的按键,并映射到对应的HID键码。物理世界和数字世界之间有一层“抖动”现象:按下机械开关时,触点并不会立刻稳定接通,而是在几毫秒内反复通断,如果直接读GPIO电平,程序会把一次按下识别成很多次按下。

我用的消抖方式是状态机加延时判断。单片机每隔10毫秒扫描一次按键电平,如果连续两次读到相同状态,才认为是有效变化。配合一个布尔变量记录当前按键状态,就可以可靠地检测“按下”和“松开”。

下面是简化的扫描逻辑:

import board import digitalio from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard_keycodes import Keycode import time keyboard = Keyboard(usb_hid.devices) key_pin = digitalio.DigitalInOut(board.GP0) key_pin.direction = digitalio.Direction.INPUT key_pin.pull = digitalio.Pull.UP last_state = True stable_state = True while True: current_state = key_pin.value if current_state == stable_state: if last_state != current_state: # 状态稳定,且发生了变化 if current_state == False: # 按键被按下 keyboard.press(Keycode.A) else: keyboard.release_all() last_state = current_state stable_state = current_state time.sleep(0.01)

这个代码的核心思路是:把单次读取变成带确认机制的状态变更事件,避免抖动造成重复输入。实测下来,普通微动开关加10毫秒延时就可以达到很稳定的手感。

4.5 升级方向:蓝牙、旋钮、OLED

按键输入跑通后,你的microduck已经具备核心能力。接下来可以按兴趣扩展。想摆脱线缆的束缚,就研究蓝牙HID,把主控换成ESP32-S3或nRF52840,固件方案换到Zephyr或Arduino蓝牙库。想要更多交互方式,就加一颗旋转编码器,用来调节音量、滚动页面。想要状态可视化,就加一块0.91寸OLED显示屏,用I2C通信显示当前配置或时间信息。

我建议不要在第一版同时加入所有功能。每加入一个功能,排查范围就会呈指数增长。先把有线按键版本做到稳定,再逐步叠加,你会发现每一步都有清晰的对照,出了问题也能快速定位。

5. 我踩过的坑:调试与排查经验

5.1 常见问题速查表

做microduck的过程里,我把遇见的问题和解决方法整理成了一张速查表。这些问题非常典型,如果你照着做,大概率也会遇到其中一两个。

现象可能原因解决办法
电脑完全不识别设备USB线只能充电不能传数据换一根能传数据的线
识别出U盘但烧录失败UF2固件文件损坏或不对应重新下载目标板对应的固件
按键偶尔输入两次未做消抖或消抖时间太短增加状态机消抖逻辑
按键完全没反应GPIO引脚配置错误或接线松动用万用表测引脚通断
设备反复掉线重启供电不足或某引脚短路检查电源电路和裸漏焊点
多个按键同时按冲突HID报告最多支持6个普通键进行组合场景测试

这张表的价值在于:大多数问题并不是“代码逻辑写错了”,而是环境、接线和使用方式的问题。先检查硬件和接线,再检查代码,可以节约大量时间。

5.2 为什么我的按键会“连击”

有朋友复刻时遇到一个很恼人的问题:按一下按键,电脑上出现好几个A。这种问题十有八九是消抖没做好。裸用GPIO读按键时,机械触点弹跳产生的信号足以让操作系统认为按了多次。

我验证过几种消抖方案。最简单的是检测到电平变化后延时20毫秒再确认一次;更可靠的是本文前面展示的状态机方案。如果用了状态机还连击,就看按键到主控之间的线是不是引入了额外噪声,或者消抖间隔设置得太短。微动开关一般10到20毫秒足够,机械键盘轴体建议稍微加长到20到30毫秒。

5.3 用系统日志和USB描述符定位问题

有一类问题特别让人头疼:按键功能完全正常,但电脑把microduck识别成了未知USB设备。这通常与HID描述符有关。描述符相当于设备的自我介绍,它告诉电脑“我是键盘、有几颗按键、支持哪些报告格式”。如果描述符写得不对,电脑就不认。

排查这类问题,Windows下可以看设备管理器里的设备状态,也可以在控制面板的“设备和打印机”里找到你的设备,右键查看属性中的HID描述符。Linux下可以直接运行dmesg命令查看内核日志,比如插入设备后输入:

dmesg | tail -20

日志里会明确提示是USB枚举失败,还是HID描述符校验失败。顺着日志给出的方向去查代码,比盲猜高效得多。

5.4 一个典型的排查案例

给大家讲一个我自己的翻车现场。某天我给microduck加装了一块OLED屏幕,上电后设备反复重启,屏幕偶尔亮一下又熄灭,电脑端不断发出设备插入拔出的提示音。一开始我怀疑是OLED驱动代码有问题,但注释掉代码后问题依旧,于是意识到这跟软件无关。

我拿万用表量了OLED电源引脚,发现电压只有2.8伏左右且持续波动,再顺着导线查下去,发现是一根飞线不小心搭到了相邻的GPIO上,导致供电被拉低。将飞线拨开后,一切恢复正常。这个案例提醒我:硬件调试的顺序永远是先查供电和物理连接,再怀疑软件逻辑。

6. 做完之后,它的后续玩法

6.1 变成桌面快捷键面板

microduck做完后,第一个落地场景就是桌面快捷键面板。我给它配了六颗按键,分别设置成影片剪辑时用到的快捷键组合,如切刀、删除、回放、打点。每次使用,手可以直接摸到实体按键,比键盘组合键更快速,也不用记繁杂的快捷键。

配置文件单独放一个JSON文件,改配置不用重新编译固件,插上电脑直接改内容再重启就行。这样你换工作场景时,调整成本极低。

6.2 变成带屏幕的信息牌

给microduck加一块小OLED屏幕后,它就不再是单纯的输入工具了。通过读取系统状态接口,它可以显示当前CPU温度、内存占用、收件箱未读邮件数,甚至是天气信息。这类功能本质上变成了一个桌面信息聚合器。

实现思路也很清晰:主控通过串口或网络获取数据源,再在OLED上绘制文本。如果固件选型时用了Micropython或CircuitPython,开发这类功能的效率会很高,因为可以直接用现成的HTTP客户端和显示驱动库。

6.3 回馈开源社区

我把自己的复刻过程整理成了一个带注释的配置库,有朋友照着做成功后,建议我把它推回GitHub原仓库,我才意识到开源协作的门槛比想象中低。

如果你也想参与,最好的方式不是上来就写大段代码,而是先做两件小事:第一,把原项目README翻译成中文,帮你身边看不懂英文的人降低门槛;第二,记录下硬件选型和调试过程中与原作者不同的经验,提交为文档补充。代码层面的贡献可以从完善异常处理开始,比如加一个键位配置的校验函数,这远比复制一份项目再改个名字发到自己主页有价值。

最后再分享一个小技巧:第一次做这类硬件项目,把每一阶段都做成“最小验证”。先验证烧录链路,再验证LED,再验证键盘功能,每一步都只改动一个变量。这样即使出了问题,你也能用排除法快速定位。我在做完microduck后最大的收获,不是手里多了一个能打字的设备,而是终于透过代码看到了计算机与现实世界交互的那一面。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询