
做一把完全属于自己的机械键盘听起来像是外设圈硬核玩家的专利。但说实话只要手里有一块 STM32F103C8T6 最小系统板这个项目离普通电子爱好者并不远。这颗芯片自带 USB 全速外设、64KB Flash、20KB RAM跑一套键盘矩阵扫描加 HID 上报绰绰有余而且板子成本极低国产替代型号更是遍地都是。我去年用一块蓝色小蓝丸板改了一把 60% 布局的键盘从画原理图到烧录固件前前后后折腾了三个周末中间踩了不少IC 手册里根本不会写的坑。这篇就把完整路径拆开讲一遍怎么选主控、怎么做矩阵电路、怎么画 PCB、怎么写扫描逻辑、怎么把固件灌进去最后附上我在实际调试中遇到的所有典型翻车现场和对应的排查方法。不管你是想给旧键盘换颗芯还是纯粹想从零做一把带 RGB 的客制化键盘这篇文章的目标都是让你少走弯路顺着一条验证过的路线把东西做出来。我会尽量把每一步的为什么也讲清楚而不是只丢给你一个能跑的方案。1. 方案选型与整体设计思路1.1 为什么是 STM32F103C8T6 而不是其他主控做键盘主控市面上常见的方案有 Arduino Pro MicroATmega32U4、树莓派 PicoRP2040、以及各类 STM32。Pro Micro 的优势是 QMK 官方的支持极其成熟插上就能刷但缺点是 Flash 只有 32KB主频 16MHz跑复杂灯效和宏定义的时候略显吃力而且现在芯片价格也涨了不少。RP2040 性能很强、USB 也是原生支持但如果你是第一次接触键盘固件它的 SDK 和 HID 例程对新人就没那么友好了。STM32F103C8T6 的定位刚好卡在中间性能足够72MHz 主频、20KB RAMUSB 外设原生支持最关键的是生态太丰富了——HAL 库、标准外设库、LL 库随便选CubeMX 半小时就能把 USB HID 工程搭出来。Flash 64KB 对键盘来说非常宽裕你甚至可以在里面塞一套完整的按键层切换逻辑、宏录制功能外加几百颗 WS2812 的灯效驱动。还有一个现实优势F103C8T6 的国产替代型号比如 GD32F103、APM32F103在市场上流通量极大板子价格可以压到十块钱以内。我实际测试过几块不同厂商的板子只要不是特别野的片子跑键盘固件基本都能稳定工作。但这里有一个要提前说清楚的坑国产替代在 USB 外设部分和原厂 ST 存在细微差异某些 GD32 批次在 USB 枚举时会出现时钟校准问题需要额外配置。如果你不想在最开始就陷入这种玄学问题第一块板子建议直接用原厂 ST 芯片的板子把整个流程跑通了再考虑换更便宜的方案。1.2 60% 键盘的整体架构与矩阵扫描方案我做的是一把 60% 布局的键盘去掉 F 区、数字区总共 61 个按键。加上预留的旋钮编码器位一共需要处理 60 多个输入点。如果每个按键直接占用一个 GPIOF103C8T6 虽然有 37 个可用 IO理论上也能勉强接完但那样 PCB 走线会非常痛苦而且后续想加 RGB、加旋钮就完全没有余量了。所以键盘电路几乎毫无例外都采用矩阵扫描结构把所有按键排成若干行和若干列每个按键接在对应行线和列线的交叉点上。按下某个键就是让这根行线和这根列线短路。主控轮流给每一行输出有效电平同时读回所有列的电平状态就能确定哪些按键被按下。我这个项目用了 8 行 8 列总共只需要 16 个 GPIO省下来的 IO 正好给了 WS2812 数据线和编码器。矩阵结构看起来简单但如果不加二极管就会出现一个经典问题同时按下三个成直角的按键第四颗鬼键也会被检测到也就是俗称的串键。解决方法是每个按键串联一颗 1N4148 二极管利用单向导通特性隔离反向电流让扫描结果只反映真实的按键状态。二极管方向接反是新手最常见的失误之一后面我会单独讲。至于固件方案QMK 官方其实已经支持部分基于 STM32F103 的板子bluepill但默认引脚映射和 PCB 上实际的走线往往对不上需要花时间改 matrix 配置。考虑到很多人只是想拿现成蓝丸板飞线做实验自己在 Keil 里写一套简洁的 HID 固件反而更直观也更容易理解每个环节在做什么。这篇就按自写固件的路线来讲把矩阵扫描、消抖、USB HID 上报这三件事拆开每件事都不复杂合起来就是一把完整键盘的灵魂。2. 电路设计从原理图到 PCB 布局2.1 最小系统与供电电路设计如果你用的是现成的 STM32F103C8T6 最小系统板这部分可以跳过。但如果想自己做 PCB最小系统部分有四件事必须做对时钟、复位、启动模式、电源去耦。首先是时钟。F103 的 USB 外设要求精确的 48MHz 时钟这颗 48MHz 只能由 PLL 从外部高速晶振HSE倍频而来。内部 RC 振荡器的精度达不到 USB 规范要求所以外部晶振不能省。我用的是一颗 8MHz 无源晶振搭配两颗 22pF 负载电容电容接在晶振两个引脚和地之间走线尽量短并且不要和其他信号线平行铺太长距离。晶振两个引脚之间的反馈电阻通常 1MΩ在很多参考设计里被省略了实际测试下来加上它启动更可靠尤其是低温环境下。其次是复位电路。NRST 引脚接一个 10kΩ 上拉电阻到 3.3V再对地接一颗 100nF 电容。这个电容的作用是滤除高频干扰防止意外复位。我见过有人随手焊了一个 10μF 的电容上去结果上电瞬间复位引脚电平拉不上去芯片一直处于复位状态屏幕上的现象就是完全没反应。然后是启动模式。BOOT0 引脚必须通过 10kΩ 下拉电阻接地让芯片从主 Flash 启动。如果你把 BOOT0 悬空在某些板子上会因为引脚电平不确定而随机进入系统存储器启动模式表现就是程序烧进去了但跑不起来或者干脆烧录不进去。最后是供电这是整个项目里排坑最多的地方。USB 的 5V 经过 AMS1117-3.3 降压到 3.3V 给 MCU 供电。AMS1117 是线性稳压器压差 1.7V输入 5V 输出 3.3V 完全够用。输入输出各放一颗 10μF 钽电容和一颗 100nF 陶瓷电容一个管低频纹波一个管高频噪声。MCU 每一组 VDD 引脚旁边都要有一颗 100nF 去耦电容而且要尽量贴近芯片引脚这是板级 EMC 的基本功。2.2 按键矩阵与 WS2812 灯效电路按键矩阵的连线规则我直接给一个经过验证的接法行线接主控 GPIO 的推挽输出列线接主控 GPIO 的上拉输入。扫描时主控只让当前这一行输出低电平其余行输出高电平然后读所有列。如果某列读到低电平说明这个行和列的交叉点上有按键被按下。二极管的接法是阳极接行线侧也就是按键的一端阴极接列线侧按键的另一端。这里我把方向再说细一点按键本身是不分正负的二极管才分。从行到列的方向上电流应该能流过二极管反方向被截止。如果接反了矩阵扫描时会出现诡异的一按一串键现象而且现象只在多个键同时按下时才出现单键测试一切正常极难排查。RGB 灯效方面我用了 WS2812B 灯珠数据线接 PA8驱动的核心思路是 PWMDMA。WS2812 的数据协议是高电平时间 350ns 左右代表 1低电平 800ns 左右代表 0总周期 1.25μs对应 800kHz 的数据率。用定时器的 PWM 输出把两个不同占空比的波形分别定义为逻辑 0 和逻辑 1通过 DMA 按需发送MCU 的 CPU 只在切换灯效时需要干活平时完全释放。这里要提醒两件事。第一WS2812 是 5V 器件而 F103 的 GPIO 输出是 3.3V虽然大多数 WS2812 在 3.3V 下也能识别高电平但为了提高可靠性最好在数据线上加一颗 74AHCT1G125 之类的电平转换芯片或者至少串一颗 100Ω 电阻限制过冲。第二WS2812 的供电一定要和 MCU 供电隔离——不是说完全分开而是要用较粗的走线单独给灯珠供电并且在灯珠供电入口放一颗大容量电解电容我放了 470μF否则灯珠全亮瞬间的大电流会把 MCU 电压拉垮直接导致复位。2.3 PCB 布局必须注意的 3 个细节这部分是我在画第二版 PCB 时才真正想明白的也是电路设计四个字里最值钱的部分。尤其当你把键盘主控、矩阵、灯珠全挤在一张板子上布局上的偷懒会在后期调试时加倍还回来。第一个要点是晶振区域要净空。晶振和负载电容周围不要走任何数字信号线尤其是矩阵扫描线。矩阵线在扫描时频繁翻转会产生大量的高频噪声如果这些线从晶振底下或者旁边长距离穿过噪声会通过耦合进入时钟回路导致 USB 枚举失败或者系统随机死机。我第一版的板子就是因为矩阵线太靠近晶振插上电脑能枚举成功但敲几下键就掉线折腾了整整一个周末才定位到。第二个要点是去耦电容的摆放顺序。去耦电容不是接在电源上就行而是要接在芯片电源引脚的入口处从电源网络过来的走线必须先经过电容再进入芯片引脚。如果走线绕到电容后面电容就形同虚设。我习惯在顶层把电容放在芯片附近底层铺完整地平面每个 VDD 引脚都用过孔直接连接到电容的一端路径越短越好。第三个要点是USB 差分线 D/D- 的处理。这两根线要尽量等长、平行走线不要中间打一堆过孔绕来绕去。虽然 USB Full Speed 的速率只有 12Mbps对阻抗容忍度比高速 USB 宽得多但差分线如果严重不等长会出现眼图质量下降、某些电脑上不稳定等问题。另外 D 线上的 1.5kΩ 上拉电阻是 USB 设备枚举的关键这个上拉告诉主机这是一个全速设备如果你的设计里没有它插上电脑会毫无反应。很多 STM32F103 的参考设计会把上拉集成在芯片内部通过软件控制但自制板我建议直接外部焊一颗 1.5kΩ 电阻到 3.3V省去软件配置的麻烦。3. 固件开发与烧录从 CubeMX 到 Keil3.1 用 CubeMX 十分钟搭出 USB HID 工程固件开发我选的是 STM32CubeMX HAL 库配合 Keil MDK 编译。CubeMX 免费、图形化对新手极其友好尤其是 USB 部分配置向导帮你把描述符都建好了你要做的只是填上 VID、PID 和报告描述符。具体配置步骤如下新建工程选择 STM32F103C8Tx在 RCC 里把 HSE 设为 Crystal/Ceramic Resonator在 Clock Configuration 里把系统主频配到 72MHz。这里注意 PLL 时钟源一定要选 HSE并且确认 USB 时钟源参数显示为 48MHz——如果这一步错了后面 USB 永远枚举失败。然后配置 GPIO8 个行引脚设为推挽输出初始电平设为高8 个列引脚设为上拉输入。USB 部分在 Connectivity 里选择 USBDevice 选择 Human Interface Device。USB 的 D、D- 引脚是固定的 PA11、PA12不用额外配置。生成代码前在 Project Manager 里选好工具链 MDK-ARM生成工程。生成的代码里核心文件是usbd_hid.c和usbd_hid_report.c具体文件名取决于 HAL 版本。你要改的就是 HID 报告描述符把键盘的标准修饰键、普通按键、LED 指示灯报满。一个标准的 Boot 键盘描述符可以直接参考 USB HID 规范里的键盘类定义一共 8 字节1 字节修饰键、1 字节保留、6 字节按键。这套描述符不用自己发明网上到处都有现成模板直接抄进报告描述符即可。3.2 按键扫描、消抖与 HID 上报逻辑固件的核心逻辑分三块扫描矩阵、软件消抖、USB 上报。扫描部分我的实现思路是这样的定义一个 8x8 的数组存当前所有按键状态。主循环里先把第 0 行拉低其余行拉高然后依次读 8 个列引脚把结果存进数组第 0 行再把第 1 行拉低其他行拉高继续读列……循环 8 次完成一轮完整扫描。每轮扫描间隔 1ms这个速度远高于人手按键的速度机械按键的稳定时间一般在 5ms 以上所以足够快。消抖是很多人容易忽略的一步。按键按下时触点会弹跳电平在几百微秒到几毫秒内反复跳变如果直接把原始电平上报给电脑就会出现一次按键触发多次、或者按下去偶尔没反应的毛病。我的消抖方案很简单维护一个上一轮状态数组当前状态数组和上一轮状态数组做异或比较发现某个键状态变化后不立即上报而是等下一轮扫描再次确认状态没变才判定为有效变化。这个方案等效于 2ms 的消抖时间实测手感干净利落不会感觉延迟。USB 上报这部分我直接在USBD_HID_SendReport里把 8 字节键盘报告发出去。发送时机是每个按键状态变化的那个周期也就是说按键从未按下变按下时发一组从按下变松开时再发一组。报告结构是byte0 是修饰键Ctrl、Shift、Alt、Winbyte1 保留置 0byte2~7 是 6 个按键的键码最多同时支持 6 键无冲突——这其实已经覆盖 99% 的日常输入场景了除非你想玩那种需要同时按十几个键的音游。如果你想加五向开关、旋钮编码器或者层切换功能原理是一样的编码器的 A、B 相用外部中断读取层切换直接在主循环里改一个当前层变量按键扫描时把物理行号加上层的偏移量再映射到键码表。3.3 固件烧录的三种方式与适用场景固件编译好之后怎么灌进芯片里是另一个容易出问题的地方。我实际用过三种方式各自的坑都踩过一遍。第一种是SWD 调试下载ST-Link / DAPLink这是最推荐的方式。只需要接 4 根线GND、SWDIO、SWCLK、3.3V在 Keil 的 Debug 设置里选择 ST-LinkUtilities 里点 Settings 确认能读到芯片 ID然后直接 Download。速度快、稳定还能在线调试打断点。唯一的坑是某些国产 ST-Link 仿制品在 Keil 新版本里会被识别成 CMSIS-DAP需要在 Debug 下拉菜单里改选驱动。第二种是串口 ISP 下载。把 BOOT0 拉高、BOOT1 拉低按一下复位芯片就进入系统存储器里的出厂引导程序此时可以通过 USART1PA9、PA10用 STM32CubeProgrammer 或者 FlyMcu 把固件下进去。下载完成后把 BOOT0 拉回低电平复位即可运行。这个方式不需要任何调试器一根 USB 转 TTL 就能搞定但每次下载都要手动拨 BOOT0 开关比较麻烦。而且要注意串口 ISP 需要目标板供电如果板子自己没电源TTL 的电平可能不够稳定我遇到过几次下载到一半失败就是因为供电电压在 3.3V 边缘徘徊。第三种是DFU 模式。F103 出厂的不太吃这套——它不像 F4 系列那样自带 USB DFU bootloader。如果非要用 USB 刷机你得先通过 SWD 烧一个 DFU bootloader 进去之后才能用 USB 刷应用固件。对键盘项目来说这属于画蛇添足除非你打算做一个不带调试接口的产品否则不建议折腾。3.4 固件烧录失败的高频原因烧录失败是新手必然遇到的一关我总结几个高频原因和对策。第一个是 Keil 报 No target connected 这个 90% 是 SWD 的四根线没接对或者目标板没上电。先用万用表量一下 3.3V 有没有SWDIO 和 SWCLK 有没有被程序复用成普通 IO。第二个坑就比较隐蔽了程序里如果把 SWD 引脚PA13、PA14配置成了普通 GPIO下载一次成功后第二次就会连不上调试器。解决办法是按着复位键不放点击 Download在芯片擦除的瞬间松开复位键——这是让芯片在运行用户程序之前先进入调试模式的老技巧。更稳妥的办法是在程序初始化时不要动 SWD 引脚反正它们默认就是调试功能。第三个问题是下载工具本身。Win10 或 Win11 下某些老款 ST-Link 的驱动会被 Windows Update 自动替换成不兼容版本导致 Keil 识别失败。解决办法是到 ST 官网装最新的 ST-Link 驱动工具包并且在设备管理器里手动更新。我踩过这个坑之后包里常备一个 DAPLink它不需要额外驱动在 Keil 里选 CMSIS-DAP 就能用省心很多。4. 常见问题与避坑指南4.1 电脑不识别 USBHID 枚举失败这是键盘 DIY 里最让人抓狂的问题之一。现象是插上 USB电脑完全没反应或者提示未知 USB 设备。排查顺序我建议按下面这张表来可能原因快速验证方法解决办法USB 时钟配置错误检查 CubeMX 时钟树里 USB 是否 48MHz改用 HSE 8MHz 晶振PLL 配置到 72MHz 后分频给 USB外部晶振没起振用示波器量晶振引脚或用 1MΩ 电阻并接辅助起振检查负载电容值、焊接虚焊更换晶振D 上拉电阻缺失量 PA12 对 3.3V 是否有 1.5kΩ补焊外部上拉电阻USB 描述符错误用 USBlyzer 或 Wireshark 抓枚举包核对报告描述符严格遵守 HID 类规范VBUS 检测引脚问题量 PA9如果是 VBUS 检测脚电压检查电路必要时直接接 5V我最常遇到的其实是第一种。一开始我用的是内部时钟想着省一颗晶振结果 USB 无论如何都枚举不了查了半天才发现是全速 USB 对时钟精度的要求非常苛刻内部 RC 振荡器的频率误差已经超出了 USB 规范允许的范围。后来老老实实加上外部晶振一次成功。4.2 按键串键、连击、漏键这类问题属于功能能跑但体验很差的类型原因通常有三个层次。第一层是二极管方向或者矩阵接线错误这个我前面讲过特征是多个键同时按下的特定组合会触发鬼键单键测试完全正常。第二层是消抖代码没写好特征是双击、连击或者偶尔丢键跟按下的速度有关系。第三层是电平读取出问题特征是整行或者整列按键失灵这时候优先怀疑行或者列对应的 GPIO 配置错了比如本来应该是上拉输入结果配成了浮空输入。还有一个容易被忽略的问题飞线方案的线缆电容效应。如果你是用杜邦线和面包板搭的原型长而松散的飞线在扫描频率下会引入不小的干扰可能导致误触发。我的经验是原型测试阶段用尽量短的线并且把行线和列线分层梳理不要缠在一起。如果用排线最好用带接地线的扁平排线。4.3 WS2812 失调、闪烁与掉电RGB 灯珠的问题几乎都集中在三个方向数据时序、供电、共地。时序问题在 PWMDMA 方案里基本被硬件规避了你只需要确认 DMA 配置里数据宽度、循环模式和发送长度没问题。如果灯珠颜色整体不对、或者第一个灯的数据总是不对先检查数据线上有没有串电阻、阻值是不是太大超过 330Ω 会把波形畸变搞到认不出电平再检查 WS2812 的数据链是不是从灯 0 开始顺序接的。供电问题的表现通常很直接接上灯效程序后MCU 自己复位了。这是因为灯珠全亮时瞬间电流可以达到几百毫安到一安培而 USB 口的输出能力本身有限加上线材压降电压会被拉低到 MCU 掉电阈值以下。解决办法是把灯珠的 5V 供电单独从 USB 输入处分出来中间加一颗 100μF 以上的储能电容再把 MCU 的 3.3V 稳压放在电容之后这样灯珠大电流瞬间由电容扛着MCU 的电压纹波会小很多。4.4 芯片本身与供应链的坑最后说几个运气不好才会遇到、但遇到了就想摔键盘的问题。首先是买到打磨片或劣质国产芯片。F103C8T6 这类热门芯片市场上有大量翻新片、打磨片表面丝印模糊、引脚氧化甚至有些万能片的实际 Flash 容量只有 32KB跑大固件时程序直接死机。验证方法用 STM32CubeProgrammer 连接后读取芯片 UID 和 Flash 容量如果读出来的型号跟印字不一致赶紧退换。正规渠道的散新片虽然贵几块钱但省心。其次是上电复位偶尔失败。现象是插电后指示灯常亮但键盘无响应按一下复位才正常。这多半是 NRST 的电容太大或者供电爬升太慢导致的。我的板子遇到过类似问题把复位电容从 100nF 改成 10nF 之后再也没有复现过。还有一个压箱底的经验F103C8T6 的 PA8 引脚默认可以输出 MCO时钟输出如果你引出的 GPIO 刚好是某些板子上的板载 LED 或时钟脚飞线之前一定要先查原理图。我第一版飞线时就把 PA8 直接连到了 WS2812 数据线上结果这块板子上的 PA8 竟然还连着一个板载 LED导致灯效和 LED 状态互相干扰白白排了一晚上错。5. 一点实际操作中的心得体会整条流程走下来我最深刻的体会是键盘 DIY 虽然叫DIY但它本质上是一个综合工程——电路、PCB、固件、USB 协议、机械结构任何一环掉链子最终都会以电脑没反应或者按键失灵这种极其抽象的方式暴露出来。所以强烈建议不要一口气追求一步到位第一次做就用现成的蓝丸板加飞线把固件跑通先看到键盘能打字、能亮灯再动手画 PCB、做整合。每增加一个变量就单独验证一次。另外一个小技巧在固件里加一个诊断模式比如按住某个键上电进入后所有按键按下时都通过串口打印对应的行号和列号同时板载 LED 随按键闪烁。这个模式只占几十行代码却能在后续硬件调试时省下大量时间——到底是 PCB 连错了线、还是固件映射错了键码一测便知。做键盘这件事前期的坑确实不少但每解决一个问题你对 MCU、USB 协议和电路设计的理解都会深一层。等你的键盘在电脑上打出第一行字的时候那种这块秀肌肉的板子是我亲手做出来的的满足感绝对值得这几个周末的折腾。后续如果你有兴趣还可以在这套框架上继续加旋钮、加屏幕、加无线模块F103 这颗芯片的潜力比你想象中大得多。