⌨ QMK 键盘开发指南
汇总基于 QMK 固件开发自定义键盘的核心知识:主控芯片选型、Bootloader 与刷写工具链、RGB / OLED / 编码器 / MIDI 等常用外设的硬件接线与固件配置。面向键盘 DIY 爱好者,基于 QMK 官方文档与实践经验整理。
🧠 一、主控芯片选型对比
以下对比涵盖 STM32 原厂及国产 APM32 替代型号。关键指标:内核架构、主频、Flash/SRAM、ADC、USB 规格、封装、QMK 默认晶振频率及参考价格。
选型对比总表(25 款芯片)
表格横向可滚动。✔ = QMK 默认支持;◐ = 需适配。注意:本表只判断「能不能编译、跑不跑得动」,没判断「能不能 USB 刷」——后者看芯片 ROM DFU 的 VID:PID 在不在 QMK / Toolbox 名单里,见 兼容芯片能否直接烧。
| 型号 | 品牌 | 内核架构 | 主频 | Flash | SRAM | ADC 位/通道 | USB | 封装 | 默认晶振频率* | 参考价格* | 支持 / 备注 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| ST 原版 · STM32 | |||||||||||
| F072 系列 · Cortex-M0 · 48MHz | |||||||||||
| STM32F072CBT6 | ST | Cortex-M0 | 48 MHz | 128 KB | 16 KB | 12-bit / 16ch | FS | LQFP-48 | 无(内置 HSI48)* | ~¥10-15 | ✔ * |
| STM32F072RBT6 | ST | Cortex-M0 | 48 MHz | 128 KB | 16 KB | 12-bit / 16ch | FS | LQFP-64 | 无(内置 HSI48)* | ~¥12-18 | ✔ * |
| F103 系列 · Cortex-M3 · 72MHz | |||||||||||
| STM32F103CBT6 | ST | Cortex-M3 | 72 MHz | 128 KB | 20 KB | 12-bit / 10ch | FS | LQFP-48 | 8 MHz | ~¥10-15 | ✔ * |
| STM32F103RGT6 | ST | Cortex-M3 | 72 MHz | 1 MB | 96 KB | 12-bit / 16ch | FS | LQFP-64 | 8 MHz | ~¥25-35 | ◐ * |
| F303 系列 · Cortex-M4F · 72MHz | |||||||||||
| STM32F303CCT6 | ST | Cortex-M4F | 72 MHz | 256 KB | 40 KB | 12-bit / 15ch | FS | LQFP-48 | 8 MHz | ~¥21-26 | ✔ * |
| STM32F303RET6 | ST | Cortex-M4F | 72 MHz | 512 KB | 64 KB | 12-bit / 18ch | FS | LQFP-64 | 8 MHz | ~¥25-35 | ✔ * |
| F401 系列 · Cortex-M4F · 84MHz | |||||||||||
| STM32F401CCT6 | ST | Cortex-M4F | 84 MHz | 256 KB* | 64 KB | 12-bit / 10ch | FS | LQFP-48 | 8 MHz | ~¥15-22 | ✔ * |
| STM32F401RET6 | ST | Cortex-M4F | 84 MHz | 512 KB | 96 KB | 12-bit / 16ch | FS | LQFP-64 | 8 MHz | ~¥18-25 | ✔ * |
| F405 / F407 系列 · Cortex-M4F · 168MHz | |||||||||||
| STM32F405RGT6 | ST | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 12 MHz* | ~¥28-37 | ✔ * |
| STM32F407VGT6 | ST | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-100 | 8 MHz | ~¥35-45 | ✔ * |
| F411 / F446 系列 · Cortex-M4F | |||||||||||
| STM32F411CET6 | ST | Cortex-M4F | 96 MHz | 512 KB | 128 KB | 12-bit / 10ch | FS | LQFP-48 | 8 MHz | ~¥18-25 | ✔ * |
| STM32F411RET6 | ST | Cortex-M4F | 96 MHz | 512 KB | 128 KB | 12-bit / 16ch | FS | LQFP-64 | 8 MHz | ~¥20-28 | ✔ * |
| STM32F446RET6 | ST | Cortex-M4F | 180 MHz | 512 KB | 128 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 8 MHz | ~¥25-35 | ✔ * |
| H7 系列 · Cortex-M7 · 550MHz | |||||||||||
| STM32H723VGT6 | ST | Cortex-M7 | 550 MHz | 1 MB | 564 KB | 12-bit / 20ch | FS+HS* | LQFP-100 | 8 MHz* | ~¥55-75 | ✔ * |
| APM32 · 极海(国产替代) | |||||||||||
| E103 系列 · F103 兼容 · Cortex-M3 | |||||||||||
| APM32E103CET6 | 极海 | Cortex-M3 | 72 MHz* | 512 KB* | 128 KB* | 12-bit / 21ch | FS | LQFP-48 | 8 MHz | ~¥7-10 | ◐ * |
| APM32E103RET6 | 极海 | Cortex-M3 | 72 MHz* | 512 KB* | 128 KB* | 12-bit / 21ch | FS | LQFP-64 | 8 MHz | ~¥8-11 | ◐ * |
| F072 / F402 · 低成本线 | |||||||||||
| APM32F072CBT6 | 极海 | Cortex-M0+ | 48 MHz | 128 KB | 16 KB | 12-bit / 16ch | FS | LQFP-48 | 无(内置 HSI48)* | ~¥6-8 | ✔ * |
| APM32F072RBT6 | 极海 | Cortex-M0+ | 48 MHz | 128 KB | 16 KB | 12-bit / 16ch | FS | LQFP-64 | 无(内置 HSI48)* | ~¥6-9 | ✔ * |
| APM32F402RBT6 | 极海 | Cortex-M4F | 120 MHz | 128 KB* | 32 KB | 12-bit / 21ch | FS | LQFP-64 | 8 MHz | ~¥6-9 | ✔ * |
| F405 / F407 系列 · Cortex-M4F · 168MHz | |||||||||||
| APM32F405RGT6 | 极海 | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 12 MHz* | ~¥15-22 | ✔ * |
| APM32F407RGT6 | 极海 | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 8 MHz | ~¥15-25 | ✔ * |
| F411 / F446 系列 · Cortex-M4F | |||||||||||
| APM32F411CET6 | 极海 | Cortex-M4F | 120 MHz* | 512 KB | 128 KB | 12-bit / 16ch | FS | LQFP-48 | 8 MHz | ~¥11-14 | ✔ * |
| APM32F446RET6 | 极海 | Cortex-M4F | 180 MHz | 512 KB | 128 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 8 MHz | ~¥15-25 | ✔ * |
| 其他架构 | |||||||||||
| RP2040 | 树莓派 | 双核 Cortex-M0+ | 133 MHz | 2 MB* | 264 KB | 12-bit / 4ch | FS | QFN-56 | 12 MHz | ~¥10 ⚠* | ✔ * |
| ATMEGA32U4 | Microchip | AVR 8-bit | 16 MHz | 32 KB* | 2.5 KB | 10-bit / 12ch | FS | TQFP-44 | 16 MHz | ~¥18 ⚠* | ✔ * |
参考价格为 2026-10 散片单买口径(如 F405RGT6 立创 1+ ¥36.6 / 100+ ¥27.5、F411CET6 立创 1+ ¥14.0),同规格 APM32 约为 ST 一半价。
为什么 ST 推荐立创商城:小批量直接买立创现货散价——淘宝散片来源不透明,很多不知道是哪年的老片子,甚至可能是二手翻新片,翻车风险高;立创现货可追溯、散价也公道。
为什么 APM32 在淘宝「联科芯」买而不在立创:作者长期在该店购买,已向极海官方确认其为代理,货源可靠、价格实在——顺便算是给他们打个广告。
行情随时浮动,下单前以实时查询为准;标 ⚠ 的价格为倒挂 / 不适用口径,不要照它下单。
推荐 & 建议
QMK 默认晶振适配: 各芯片 QMK 默认按固定晶振频率计算时钟(F405 按 12MHz、H723 按 25MHz),选板时留意即可,并非优劣之分。
国产 APM32 实测: APM32F072 / F4xx 全系列直接可用;APM32E103 已验证可复用 F103 配置,使用完整 Flash/SRAM 时需匹配对应链接脚本。
APM32 入选口径: ① QMK 下直接套用对应 STM32 预设即可达到对等性能——虽发挥不到纸面 100%,但对键盘应用绰绰有余;② 产品品类多、价格合适,国内购买方便——本站作者长期在淘宝「联科芯」购买,已经向极海官方确认其为代理之一,散片好买、价格实在;③ 工具链与开发生态成熟。三条不满足者不列入本表推荐。
作者立场: 本人多块自研键盘均采用 APM32——同性能更便宜、国内随手买得到、封装对手焊友好,是国产替代的忠实用户与推广者。
STM32F103C8T6/CBT6/RCT6/RET6/RGT6(Cortex-M3 @ 72MHz)或国产 APM32E103CET6/RET6(已验证可复用 F103 配置,当前按 72MHz 运行)。参考价 ~¥10-20。
烧录方式: 固件定稿不再改动时,可直接用 ST-Link 烧录最终固件;一般开发流程建议先刷 stm32duino Bootloader,之后通过 USB 用 QMK Toolbox 反复烧录更新(见第二章)。
- 最便宜,国产 APM32E103 白菜价,直接兼容 STM32F103 生态
- 引脚兼容 F103,外围电路方案极其成熟,参考设计多
- LQFP-48/64 封装,手焊友好
- 容量选择灵活:F103RGT6 达 1MB Flash,APM32E103CET6/RET6 可按实际链接脚本使用 512KB Flash / 128KB SRAM
- 出厂 Bootloader 不带 USB,未刷 stm32duino 前更新固件需外部工具
- APM32E103 已验证可直接复用 F103 外设配置;若要使用完整 Flash/SRAM,仍需匹配对应链接脚本
- 无 FPU,Cortex-M3 性能一般
STM32F401CEU6/RET6(M4F @ 84MHz)、STM32F411CCU6/CEU6/RET6(M4F @ 96MHz,Flash 256KB 或 512KB / SRAM 128KB)。注:F411 容量后缀 C = 256KB、E = 512KB(CEU6/RET6 均为 E 容量 512KB Flash)。参考价 ~¥20-30。国产替代 APM32F411CET6 直接兼容。
- Cortex-M4F 带 FPU,性能充裕,复杂固件毫无压力
- F411 版:C 容量 (CC): 256KB Flash,E 容量 (CEU6/RET6/CET6): 512KB Flash,SRAM 均为 128KB,RGB+OLED+宏全开不紧张
- APM32F411CET6 手焊友好:国产版为 LQFP-48 封装,引脚外露两侧,相比 ST 原版 CEU6 的 UFQFPN-48(底部焊盘)焊接方便得多,手焊 / 返修都容易,批量 DIY 优先选它
- 内置 USB FS OTG,无需外挂 PHY
- QMK 官方支持完善,开发板便宜好买(正品 WeAct Black Pill 仍在售,注意别买到无 Logo 的仿板,见 USB 章)
- 开发板晶振规格不统一:Black Pill 同一版本号下 25MHz 与 8MHz 同时在售,买错无法直接用;别图省事套
BLACKPILL板级,自己的板一律GENERIC_STM32_F411XE(见 USB 章) - CEU6 为 UFQFPN-48 贴片封装,手焊稍难(选国产 APM32F411CET6 的 LQFP-48 可避开)
- ADC 仅 1 路(10-16 通道),模拟扩展弱
- 对键盘性能略过剩
M4F @ 168MHz,1MB Flash / 192KB SRAM,3×ADC + 2×DAC。国产 APM32F407RGT6 直接兼容实测可用。
晶振:QMK 默认按 12MHz 适配 F405(APM32F407 按 8MHz),选板时对齐即可。
- 168MHz 性能旗舰级,多 RGB + 多 OLED + 复杂模拟全开
- 1MB Flash / 192KB SRAM,容量充裕
- 3×12bit ADC + 2×DAC,模拟输入输出都丰富
- 可外挂 USB3300 PHY 升级 USB HS
- 开发板晶振规格更不统一:市面上 8MHz / 12MHz / 25MHz 板子都有,且 QMK 对 F405 默认按 12MHz、F407 按 8MHz 适配,买错需改时钟配置
- 功耗相对较高,价格也更高
- LQFP-64/100 封装较大,PCB 占用面积多
双核 Cortex-M0+ @ 133MHz,Flash 2MB(可扩 16MB),SRAM 264KB,GPIO 30。参考价 ~¥10。
- Flash 2MB 可扩至 16MB,需要大量存储(音色库/图片素材)时首选
- 价格最低,SRAM 264KB 巨大
- PIO 硬件时序驱动 WS2812,CPU 零开销
- UF2 拖拽刷写零门槛,跨平台免驱动
- GPIO 仅 30 个,引脚数量受限,做不了大键盘,全键 RGB 大矩阵会捉襟见肘
- 外围电路繁琐:芯片自身没有内部 Flash,必须外挂 QSPI Flash;供电去耦还需大量外挂电容电阻,BOM 和布线都比较繁琐
- QFN-56 封装,手工焊接麻烦,裸片 DIY 难度高(直接用成品 Pico 板可绕开)
- ADC 仅 4 通道 12bit,模拟输入扩展受限
- 无 FPU,双核 M0+ 单核性能弱于 M4
- 无内置 DAC / 比较器 / PGA 等模拟外设
AVR 8-bit @ 16MHz,Flash 32KB(可用 ~28KB),SRAM 2.5KB,10-bit ADC。参考价 ~¥18。
- 外围元器件很简单:内置 USB + 内部 RC 振荡器,最小系统只需几个电阻电容,飞线手焊最容易
- 可直接 5V 供电:5V 逻辑对大多数周边元器件都友好,USB 5V 直供即可,省掉 LDO
- QMK 鼻祖芯片,教程资料最多、踩坑样本最全
- 内置 USB,Pro Micro 开发板便宜,DIY 生态成熟
- 毁灭性:单芯片价格倒挂——散片 32U4 比 Pro Micro 整板还贵,自己画板反而不划算
- 引脚过少——Pro Micro 只引出部分 I/O;全引脚引出的 Arduino Micro 价格起飞,DIY 不划算
- 3.3V 版本几乎买不到——市面上基本全是 5V 方案,需要 3.3V 电平的外设得自己转电平或换方案
- Flash 仅 32KB,开 RGB 灯效就逼近上限
- SRAM 2.5KB,多层键位+宏+OLED 容易爆内存
- 8-bit 架构 + 16MHz 主频,性能是四款里最弱
- 10-bit ADC,模拟精度一般
📌 QMK 官方芯片支持速查(晶振 × 预设主频 × 容量覆盖)
下表逐板提取自 QMK 官方源码(platforms/chibios/boards/*/configs/board.h 与 configs/mcuconf.h)。「预设 / 标称」= 不改任何官方配置直接编译得到的实际 SYSCLK 与芯片标称主频。晶振列是 QMK 预设写死的频率——板载晶振与它不符时 PLL 会跑飞或锁不上,这正是 F405(12MHz)与 F407 / F411(8MHz)混用时的经典坑。「容量覆盖」列逐个后缀写明能否直接编译:官方板级只带一个容量的链接脚本,更大容量需按列内说明另配文件。APM32 官方零支持、借用对应 STM32 板级时,晶振与容量规则跟随所借配置(如 APM32F405RGT6 = 12MHz + xG 直接满配)。
| 芯片系列 | QMK 官方板级 | 晶振要求 (QMK 预设) | 预设 / 标称 (MHz) | 容量覆盖(后缀直用情况) | 备注 / 坑 |
|---|---|---|---|---|---|
| STM32 全系 · ChibiOS 官方板级 | |||||
| STM32F042x6 | GENERIC_STM32_ F042X6 | 无晶振(HSI) | 48 / 48 满速 | x6 = 全系直用(32KB 已是最大) | 预设 HSE 关闭,HSI/2×12 直接满速;USB 由内部时钟供 |
| STM32F072xB | GENERIC_STM32_ F072XB | 无晶振(内置 HSI48) | 48 / 48 满速 | xB = 最大容量(128KB),CBT6 / RBT6 直接编译 | 同 F042;F0 全系内置 HSI48,不接晶振即可满速跑 USB |
| STM32F103xB | STM32_F103_ STM32DUINO | 8 MHz(写死) | 72 / 72 满速 | ⚠ 默认仅 xB(128KB):CBT6 直用;RCT6 / RET6 / RGT6 需另配 xC / xE / xG 链接脚本——官方 stm32duino 只有 x6/x8/xB,脚本本页可下载 | 必须外接 8MHz 晶振 |
| STM32F303xC | GENERIC_STM32_ F303XC QMK_PROTON_C | 8 MHz | 72 / 72 满速 | xC(256KB)直用 → CCT6 直接编译;RET6(512KB)需在 rules.mk 显式 MCU_LDSCRIPT = STM32F303xE(ChibiOS 自带该脚本,无需自建) | Proton-C 同款配置 |
| STM32F401xC | GENERIC_STM32_ F401XC | 8 MHz | 84 / 84 满速 | xC(256KB)直用 → CCT6 直接编译;RET6(512KB)需显式 MCU_LDSCRIPT = STM32F401xE(官方 common/ld 已有,无需自建) | 别用 BLACKPILL 板级:预设写死 25MHz(只对正品 WeAct V3.1 成立,仿板/自画板多为 8MHz,见 USB 章) |
| STM32F405xG | GENERIC_STM32_ F405XG | ⚠ 12 MHz (board.h 写死) | 168 / 168 满速 | xG(1MB)→ RGT6 直接满配,无需任何额外文件 | ⚠ 最经典的坑:预设按 12MHz 配,8MHz 板直接跑错;APM32F405 借用此配置 = 12MHz |
| STM32F407xE | GENERIC_STM32_ F407XE | 8 MHz (board.h 写死) | 168 / 168 满速 | xE(512KB)→ VET6 直接编译;VGT6(1MB)需自建 xG 脚本(官方无,本页可下载) | ⚠ 与 F405 不同(8MHz),两系列晶振别混用 |
| STM32F411xE | GENERIC_STM32_ F411XE | 8 MHz | 96 / 100 | xE(512KB)→ CET6 / RET6 直接编译;CC(256KB)需显式 MCU_LDSCRIPT = STM32F411xC(官方 common/ld 已有) | QMK 预设 96 略低于标称 100;别用 BLACKPILL 板级(25MHz 预设,见 USB 章) |
| STM32F446xE | GENERIC_STM32_ F446XE | 8 MHz | 180 / 180 满速 | xE(512KB)→ RET6 直接编译 | — |
| STM32H723xG | GENERIC_STM32_ H723XG | 8 MHz (M=4/N=275/P=1) | 550 / 550 满速 | xG(1MB)→ VGT6 直接满配 | ⚠ 市售 H723 核心板多载 25MHz 晶振,官方配置直接编译会跑飞,需改 PLLM |
| STM32G0B1xB | GENERIC_STM32_ G0B1XB | 无晶振(HSI16) | 64 / 64 满速 | xB 全容量直接编译 | — |
| STM32G431 / G474 | GENERIC_STM32_ G431XB G474XE | 无晶振(HSI16) | 160 / 170 | XB / XE 均直接编译 | QMK 预设略低于标称 |
| STM32L412 / L432 / L433 | GENERIC_STM32_ L412XB L432XC / L433XC | 无晶振(HSI16) | 80 / 80 满速 | XB / XC 均直接编译 | — |
| 非 ST 的 ARM · 官方板级 | |||||
| GD32VF103(RISC-V) | SIPEED_ LONGAN_NANO | 8 MHz | 108 / 108 满速 | 官方板级直接编译 | — |
| WB32F3G71xx / FQ95xx | GENERIC_WB32_* | 12 MHz(写死) | 72 / 72 满速 | 官方板级直接编译 | — |
| AT32F415(雅特力) | GENERIC_AT32_ F415XX | 8 MHz(写死) | 144 / 200 | 官方板级直接编译 | QMK 预设(×18)远低于标称 |
| 其他架构 | |||||
| RP2040 | GENERIC_RP_RP2040 (等 5 个板级) | 12 MHz(XOSC 硬件标配) | 125 / 133 | 官方板级直接编译 | QMK 默认跑 125MHz |
| Teensy 3.1 / 3.5 / 3.6(MK20DX) | IC_TEENSY_3_1 PJRC_TEENSY_ 3_5 / 3_6 | 16 MHz(Teensy 标配) | 按底板 / 120·180 | 官方板级直接编译 | — |
| Teensy 4.1(iMXRT1062) | IC_TEENSY_4_1 | 24 MHz | 600 / 600 满速 | 官方板级直接编译 | — |
| AVR(ATmega32U4 等) | — | 16 MHz(F_CPU 标准) | 16 / 16 满速 | — | USB 必须外部晶振;8MHz 板需改 F_CPU |
「满速」= QMK 预设即达芯片标称主频。容量后缀速记:C=256KB、E=512KB、G=1MB(F103 系列为 8=64K、B=128K、C=256K、E=512K、G=1M);封装后缀 C=48 脚、R=64 脚、V=100 脚。「直用」= 官方板级自带的链接脚本容量正好覆盖,rules.mk 不用加任何东西;「需另配 / 需显式」= 要加一行 MCU_LDSCRIPT 或自建脚本文件。自建板级(APM32 / F103 xC·xE·xG / F407xG 等)时,晶振与 PLL 需自行对齐后再算 SYSCLK。
📌 「兼容 STM32」的芯片能不能直接烧?看 DFU 的 VID:PID
BOOTLOADER 对应的 VID:PID 写进 .bin 后缀,dfu-util 刷之前拿它和实际设备比对——芯片换了品牌,只要 ROM DFU 报出来的 VID:PID 与原厂不同,就会卡在 Error: File ID … does not match device,一个字节都写不进去(机制详见 7.1)。QMK 能认的 DFU 档位是一张封闭的表(stm32-dfu / apm32-dfu / at32-dfu / wb32-dfu / gd32v-dfu / stm32duino / tinyuf2 / rp2040 …),QMK Toolbox 更是只对它内置了设备类的那几个 VID:PID 生效。所以其它品牌哪怕号称「引脚与寄存器全兼容」,能不能直接烧只取决于一件事:它的 ROM bootloader 报出来的 VID:PID 落在不在名单里。
| 兼容芯片的 ROM DFU 报什么 ID | Toolbox 能否识别 | 能否直接 USB 刷 | 实际怎么办 |
|---|---|---|---|
直接照抄原厂 0483:DF11(少数兼容片确实如此,取决于厂商固件版本) | 认成 STM32 DFU | ✅ 可用 BOOTLOADER = stm32-dfu | 按 STM32 正常走 USB;先小批试,注意 EEPROM 模拟等边角行为可能不同 |
自己的 VID:PID,且 QMK 有对应档(雅特力 AT32 = 2E3C:DF11、GD32VF103 = 28E9:0189、WB32) | AT32 认不出(Toolbox 无此设备类);GD32V / WB32 能认 | ⚠ 只有 qmk flash 命令行能刷 | Toolbox 这条路走不通,用 QMK 自带的 DFU_ARGS 走命令行 |
| 自己的 VID:PID,QMK 名单里没有(多数国产兼容 MCU 属于这档) | 认不出 | ❌ | 要么手改参数硬走命令行(见下),要么只能 SWD / 厂商 ISP 工具 |
# rules.mk —— 借 stm32-dfu 这一档,但把 ID 换成实际芯片的
MCU = STM32F103
BOOTLOADER = stm32-dfu
DFU_ARGS = -d 1234:5678 -a 0 -s 0x08000000:leave
DFU_SUFFIX_ARGS = -v 1234 -p 5678
# 两个变量在 QMK 里都是 ?= 定义,rules.mk 里写了就优先用自己的
# 之后只能:qmk flash -kb xxx -km xxx(Toolbox 依然认不出设备)
🚀 二、起步:环境搭建与第一个键盘工程
本篇为纯新手路线:从空电脑到编译出第一个能刷进键盘的固件。章节框架已就位,正文逐步补全。
2.1 QMK MSYS 安装与 qmk setup框架 · 正文待补
将覆盖:QMK MSYS 下载与安装 → 首次 qmk setup 拉取 qmk_firmware 仓库 → 国内网络下的镜像与代理建议 → qmk doctor 自检环境 → 常见安装报错处理。
官方对照:QMK Getting Started
2.2 创建键盘工程:目录结构与各文件职责框架 · 正文待补
将覆盖:qmk new-keyboard 生成工程 → keyboards/ 目录结构全解(keyboard.json / rules.mk / config.h / keymaps/default/keymap.c 各管什么)→ rules.mk 与 keyboard.json 的分工(新版 QMK 优先写 json,本指南大量示例用 rules.mk 表述,将给出对照表)→ 矩阵行/列引脚在哪里声明。
官方对照:Keyboard Guidelines · info.json 参考
2.3 第一次编译与刷写框架 · 正文待补
将覆盖:qmk compile -kb <键盘> -km default 产出 .hex/.bin → 按芯片选择刷写方式(跳转到「烧录固件」章)→ 刷完按键无反应时的第一轮自检。
⚙️ 三、快速配置向导
下拉选择实际型号后,页面会同时给出 keyboard.json 和 rules.mk:前者唯一声明 processor / bootloader,后者只保留板级、链接脚本等其余设置,避免同一选项在两个文件中重复定义。
型号写法遵循“系列 + 封装代号 + Flash 容量代号”,例如 STM32F401*C。
⚙️ F103 / E103 配置
QMK 的“主控型号”与“板级配置”不是一一对应的。
以 F103 / E103 为例:
board/board.h、configs/mcuconf.h:负责内核、时钟和外设配置- 链接脚本
*.ld:决定固件可使用的 Flash、SRAM 以及程序起始地址 - 使用
stm32duino时:程序从0x08002000开始,前 8KB 留给 Bootloader
qmk_firmware/platforms/chibios/boards/STM32_F103_STM32DUINO/
├─ board/board.h # F103 板级定义、8MHz 外部晶振、USB 引脚
├─ board/board.c # 启动时钟初始化、Bootloader 标志
├─ configs/mcuconf.h # PLL、总线分频、USB/SPI 等外设开关
└─ ld/
├─ STM32F103x6_stm32duino.ld # 32KB Flash / 10KB RAM
├─ STM32F103x8_stm32duino.ld # 64KB Flash / 20KB RAM
└─ STM32F103xB_stm32duino.ld # 128KB Flash / 20KB RAM
其中 x6、x8、xB 是容量配置,不是封装名称。C、R 等字母表示封装/引脚数量,8、B、C、E、G 才对应不同容量等级。
官方原版核对(GitHub master): stm32duino 官方只有 x6 / x8 / xB 三个链接脚本,xC / xE / xG 均不存在,需按上文方法自建(放键盘目录 ld/ 或直接放官方 boards 的 ld/ 目录都能被识别)。APM32 官方零支持——没有任何 APM32 板级或链接脚本;官方唯一引用 APM32F103 的键盘(mechlovin adelais rev4)也是直接按 processor: STM32F103 + bootloader: stm32duino 复用 xB(128KB / 20KB 上限)跑的。
本站提供自建脚本下载(含文件头自写标注,与作者本地库完全一致):STM32F103xC(RCT6 · 256KB/48KB)· STM32F103xE(RET6 · 512KB/64KB)· STM32F103xG(RGT6 · 1MB/96KB)· APM32E103xE(CET6/RET6 · 512KB/128KB)
| 实际型号 | 板载 Flash | 板载 SRAM | stm32duino 配置文件 | 完整使用条件 | 未适配时的实际上限 |
|---|---|---|---|---|---|
| STM32F103C8T6 | 64KB | 20KB | STM32F103x8_现有文件 | ✔ 可完整使用无需额外修改 | 64KB Flash20KB SRAM72MHz |
| STM32F103CBT6 | 128KB | 20KB | STM32F103xB_现有文件 | ✔ 可完整使用无需额外修改 | 128KB Flash20KB SRAM72MHz |
| STM32F103RCT6 | 256KB | 48KB | 新建 STM32F103xC_需改 Flash/RAM 容量 | ◐ 需新建链接脚本主频无需修改 仍为 72MHz | 128KB Flash20KB SRAM72MHz直接套用 xB |
| STM32F103RET6 | 512KB | 64KB | 新建 STM32F103xE_需改 Flash/RAM 容量 | ◐ 需新建链接脚本主频无需修改 仍为 72MHz | 128KB Flash20KB SRAM72MHz直接套用 xB |
| STM32F103RGT6 | 1MB | 96KB | 新建 STM32F103xG_需改 Flash/RAM 容量 | ◐ 需新建链接脚本主频无需修改 仍为 72MHz | 128KB Flash20KB SRAM72MHz直接套用 xB |
| APM32E103CET6 | 512KB | 128KB | 新建 APM32E103xE_建议独立命名 | ◐ 需改多个文件链接脚本 MCU / 时钟 / Flash 配置 | 128KB Flash20KB SRAM72MHz复用 F103 xB 配置 |
| APM32E103RET6 | 512KB | 128KB | 新建 APM32E103xE_建议独立命名 | ◐ 需改多个文件链接脚本 MCU / 时钟 / Flash 配置 | 128KB Flash20KB SRAM72MHz复用 F103 xB 配置 |
表格中的“未适配时上限”按以下顺序显示:
Flash:可用于存放固件的程序空间SRAM:运行时内存CPU 主频:当前时钟配置
STM32F103:大容量型号只需补充匹配容量的链接脚本,主频仍为官方 72MHz。
APM32E103:不能只新建链接脚本。虽然已经验证可以复用 F103 的外设配置,但要使用完整 Flash/SRAM 并提高主频,还需要同步修改 MCU 识别、时钟、Flash 等待周期和相关启动配置。
仅复用现有 F103 xB 配置时,实际上限仍是 128KB Flash / 20KB SRAM / 72MHz。
没有对应配置文件时如何新建
最稳妥的方法:
- 复制同目录下现有的
STM32F103xB_stm32duino.ld - 只修改 Flash 和 SRAM 容量定义
- 不要直接修改公共文件
stm32duino_bootloader_common.ld,因为多个容量脚本都会引用它
/* 例如 STM32F103RET6:使用 stm32duino,保留 8KB Bootloader */
f103_flash_size = 512k;
f103_ram_size = 64k;
/* 下面这行保持不变 */
INCLUDE stm32duino_bootloader_common.ld
将文件保存为键盘目录的 ld/STM32F103xE_stm32duino.ld,然后在键盘的 rules.mk 中指定:
MCU = STM32F103
BOOTLOADER = stm32duino
MCU_LDSCRIPT = STM32F103xE_stm32duino
如果是 RCT6,将容量改为 256k / 48k 并使用 xC;如果是 RGT6,改为 1m / 96k 并使用 xG。APM32E103CET6 和 APM32E103RET6 都是 512KB Flash,但 SRAM 应按实际芯片数据手册填写为 128KB,不能照抄 STM32F103RET6 的 64KB。
APM32E103 全频率配置(实验性)
APM32E103 的“完整容量”和“全频率”是两个独立目标:
- 完整容量模式:新建匹配容量的链接脚本,使用 512KB Flash / 128KB SRAM;主频仍为当前配置的 72MHz。
- 全频率模式:在完整容量基础上,还要修改时钟初始化、Flash 等待周期、USB 48MHz 时钟以及必要的 MCU/HAL 启动配置。
后续适配时需要重点检查以下文件:
键盘目录/
├─ rules.mk # MCU、Bootloader、链接脚本选择
├─ ld/APM32E103xE_stm32duino.ld # 512KB Flash / 128KB SRAM
├─ board/board.h # 外部晶振频率和板级定义
├─ config/mcuconf.h # PLL、AHB/APB、USB 时钟
└─ board/apm32_clock.c # 如 HAL 默认初始化不足,增加专用时钟初始化
不能只把 PLL 倍频从 9 改成更大的数值。目标主频必须同时满足 APM32E103 数据手册的系统时钟、AHB/APB、Flash 等待周期和 USB 时钟要求;每次修改后都应通过 USB 枚举、键盘扫描、定时器、长时间运行和复位启动测试。
因此,在完整测试完成前,教程和配置向导应将该方案标记为:
APM32E103 全频率模式:实验性,尚未完成完整测试
当前已验证模式:F103 兼容配置,72MHz
无 Bootloader 直接烧录
无 Bootloader 时不能继续使用上面的 *_stm32duino.ld,因为它们都从 0x08002000 开始。应复制该文件并把链接脚本中的 Flash 区域改为:
flash0 (rx) : org = 0x08000000, len = 512k
同时删除或绕开 BOOTLOADER = stm32duino 的自动选择逻辑,并通过 ST-Link 等 SWD 工具把完整固件烧录到 0x08000000。Bootloader 是否存在不会改变 F103 的 72MHz 时钟;时钟仍由 board/board.h 和 configs/mcuconf.h 控制。
⚙️ STM32F4 配置
QMK 对 STM32F4 的配置由三部分共同决定:
board/和configs/mcuconf.h:板级定义、MCU 外设和时钟配置common/ld/*.ld:Flash、SRAM 地址及容量- 实际开发板晶振:必须与
board.h中的STM32_HSECLK一致
| 实际型号 | 板载 Flash | 板载 SRAM | QMK 配置文件 | 当前主频 | 完整使用条件 |
|---|---|---|---|---|---|
| STM32F401*C | 256KB | 64KB | STM32F401xC.ldF401_MCUCONF | 84MHz | ✔ 基本完整确认晶振;默认按 xC |
| STM32F401*E | 512KB | 96KB | STM32F401xE.ld需显式指定 | 84MHz | ◐ 需指定链接脚本否则可能按 xC 使用 |
| STM32F405*G | 1MB | 192KB + CCM | STM32F405xG.ldF405/F407 共用 MCU 配置 | 168MHz | ◐ 确认 12MHz 晶振CCM RAM 需单独放置变量 |
| STM32F407*E | 512KB | 192KB + CCM | STM32F407xE.ldF405/F407 共用 MCU 配置 | 168MHz | ◐ 确认 8MHz 晶振CCM RAM 需单独放置变量 |
| STM32F411*C | 256KB | 128KB | STM32F411xC.ld需显式指定 | 96MHz | ◐ 需指定链接脚本否则可能按 xE 使用 |
| STM32F411*E | 512KB | 128KB | STM32F411xE.ldF411_MCUCONF | 96MHz | ✔ 基本完整确认晶振和板级引脚 |
| STM32F446*E | 512KB | 128KB + 备份 SRAM | STM32F446xE.ldF446_MCUCONF | 180MHz | ✔ 基本完整确认 8MHz 晶振 |
| APM32F405RG | 1MB | 192KB + CCM | STM32F405xG.ld沿用 F405 配置BOOTLOADER = apm32-dfu | 168MHz | ◐ 确认 12MHz 晶振实测可用,无需新建文件 |
| APM32F411CET6 | 512KB | 128KB | STM32F411xE.ld沿用 F411 配置BOOTLOADER = apm32-dfu | 96MHz(额定 120MHz) | ✔ 基本完整实测可用;跑满 120MHz 需改时钟 |
表中的“基本完整”表示 QMK 已有对应的板级配置、MCU 配置和链接脚本,但仍需确认实际开发板的晶振、USB 电路和引脚定义。
F401E / F411C:对应链接脚本虽然存在,但默认选择可能不是实际容量,建议在键盘的 rules.mk 中显式指定 MCU_LDSCRIPT。
板级选择:自己画板一律用 GENERIC_STM32_F4xxx。不要用 BLACKPILL_STM32_F401 / F411——那是给 WeAct 正品开发板用的,HSE 写死 25MHz;自画板一般是 8MHz,市面无 Logo 的仿板也常见 8MHz。套错的结果是 USB 完全不枚举、板子却还在跑(详见 4.3 Black Pill 与板级预设)。
F405 / F407:两者共用部分 F4 MCU 配置,但常见板子的晶振不同。F405 配置按 12MHz 晶振计算 168MHz;F407 配置按 8MHz 晶振计算 168MHz,不能直接互换。
CCM RAM:F405/F407 的 CCM RAM 虽然在链接脚本中定义,但不是普通 SRAM,不能默认当作所有 QMK 全局内存使用,必要时需要使用对应段属性。
国产极海 APM32F4xx 与 STM32F4 外设兼容,QMK 编译时 MCU、BOARD、MCU_LDSCRIPT 直接沿用对应 STM32 型号的配置,无需新建任何文件,只把 Bootloader 换成 APM32 的 DFU:
// rules.mk — APM32F405RGT6 示例(APM32F411CET6 同理,MCU 换 STM32F411)
MCU = STM32F405
BOARD = GENERIC_STM32_F405XG
MCU_LDSCRIPT = STM32F405xG
BOOTLOADER = apm32-dfu
要点:① apm32-dfu 对应极海 ROM DFU,刷写入口依赖 PA10 上拉(见「USB 硬件设计」章节);② 晶振按对应 STM32 型号的 QMK 默认对齐(F405 = 12MHz、F411 = 8MHz);③ APM32F411 额定 120MHz,高于 ST 原版(100MHz),但 QMK 现有 F411 配置按 96MHz 运行,跑满 120MHz 需自行修改 mcuconf 并验证 USB 48MHz 时钟;④ F411 的 LQFP-64 封装无 PB11,VCAP 为单电容方案,与 F405 不同(见「USB 硬件设计」章节)。
F4 常用配置文件位置
qmk_firmware/platforms/chibios/boards/
├─ GENERIC_STM32_F401XC/configs/mcuconf.h
├─ GENERIC_STM32_F405XG/configs/mcuconf.h
├─ GENERIC_STM32_F407XE/configs/mcuconf.h
├─ GENERIC_STM32_F411XE/configs/mcuconf.h
└─ GENERIC_STM32_F446XE/configs/mcuconf.h
qmk_firmware/platforms/chibios/boards/common/ld/
├─ STM32F401xC.ld / STM32F401xE.ld
├─ STM32F405xG.ld / STM32F407xE.ld
└─ STM32F411xC.ld / STM32F411xE.ld / STM32F446xE.ld
官方原版核对(GitHub master): boards 目录只有上面 5 个 GENERIC 板级(另有 BLACKPILL_STM32_F401 / F411 开发板配置),common/ld 里各脚本另有 *_tinyuf2.ld 变体(TinyUF2 引导用)。官方没有 STM32F407xG.ld,也没有 GENERIC_STM32_F401XE / GENERIC_STM32_F407XG 板级目录和任何 APM32 板级——需要 F401xE 独立板级、F407xG 或 APM32 支持时都得自建(板级目录 = board/configs/ld 三件套,不只是链接脚本)。本站提供自建脚本下载:STM32F407xG.ld(F407VGT6 用,1MB Flash,QMK 默认只有 512KB 的 xE)。
F4 在 rules.mk 中显式选择容量
# 例如 STM32F401E:512KB Flash / 96KB SRAM
MCU = STM32F401
MCU_LDSCRIPT = STM32F401xE
# 例如 STM32F411C:256KB Flash / 128KB SRAM
MCU = STM32F411
MCU_LDSCRIPT = STM32F411xC
🔋 四、USB 硬件设计(F1 / F4 全系列)
这一章解决一个很具体的问题:自己画一块键盘板,USB 那几根线到底怎么接。之所以不能只给一套图,是因为各系列的 USB 控制器根本不是同一代东西——F1 没有内置 D+ 上拉,1.5K 电阻必须外接;F4 内置了,外接反而违规。同一张图抄错系列,轻则枚举时好时坏,重则电脑连"未知设备"都不弹。
下面按「先分类 → F1 体系 → F4 体系 → 其余芯片 → 自检清单」的顺序讲,每条结论都给依据(芯片手册、ST 应用笔记、QMK 源码或上游 issue),不做"据说"式的口口相传。QMK 与自研固件对硬件的要求完全一致,一份 PCB 通吃。
4.1 一页速查:你的芯片属于哪一类
先在这张表里找到自己用的芯片,再去对应小节看细节。「D+ 1.5K 上拉」这一列是整章最容易出事的地方,也是不同系列之间最硬的一条分界线。
| 芯片系列 | D+ 1.5KΩ 上拉 | 数据线 | 时钟 | 细节 |
|---|---|---|---|---|
| F103 / F102 APM32E103 | 必须外接 | 直连 | 外部 8MHz 晶振 | 老式全速设备控制器,无内部上拉、无 VBUS 检测;4.2 |
| F303 | 芯片内置 | 直连 | 8MHz 晶振 | 同为老控制器,但 ST 官方表标「Embedded」,别按 F103 抄;4.2 |
| F0 系列 F042·F072 | 芯片内置 | 直连 | F072 可省晶振 F042 需 8MHz | F0 内置上拉;F072 有 HSI48 可晶体-less,APM32F072 同;4.4 |
| F401 / F411 / F446 APM32 同档 | 内置 · 禁止外接 | 直连 | 8MHz 晶振 | OTG_FS 内置 PHY,片内可编程上拉;4.3 |
| F405 / F407 APM32 同档 | 内置 · 禁止外接 | 直连 | F405 12MHz F407 8MHz | 同 OTG_FS,另多一个需外置 PHY 的 OTG_HS,QMK 不用;4.3 |
| F5 / F7 | 内置 · 禁止外接 | 直连 | 按型号 | 与 F4 同属 OTG 体系,接法通用;PLL 预设需自行核对;4.3 |
| H723 | 芯片内置 | 直连 | 25MHz 晶振 | 多一个 VDD33USB 电源域,漏了完全不工作;4.4 |
| RP2040 | 芯片内置 | 串 27Ω | 12MHz 晶振 | 唯一要串阻的;内部上下拉齐全,12MHz 是 USB bootloader 的硬要求;4.4 |
| ATmega32U4 | 芯片内置 | 直连 | 16MHz 晶振 | 16MHz 经 PLL 出 48MHz,换频率就不认;4.4 |
「直连」= D+ / D- 直接走到 USB 座,不串任何电阻;「内置」= 上拉在芯片内部、由固件使能,外部再画一颗就是并联。
4.2 STM32F1 体系(F103 / APM32E103 / F303)
F1 是这批芯片里唯一必须手工补一颗 1.5K 电阻的系列,也是最多人栽跟头的地方——因为大量教程是照着 F4 写的,看到"1.5K 上拉"顺手就删了。
为什么 F1 的 1.5K 必须外接
USB 全速设备靠 D+ 被 1.5K 上拉到 3.3V 来告诉主机"我插上了、我是全速设备",主机检测到这个电平才开始枚举。F103 的 USB 外设没有这颗内部电阻,ST 官方应用笔记 DM00296349《Introduction to USB hardware and PCB guidelines》 的对照表写得很直白:F102 / F103 一栏是「1.5 kΩ resistor must be added」,而 F0、F3、F4 各栏是「Embedded」。
漏了它的症状非常干脆:板子上电、固件在跑、串口能打印,电脑端毫无反应——连"未知 USB 设备"都不弹,因为主机压根没检测到插入事件。不是驱动问题、不是固件问题、不是线的问题,就是少了一颗电阻。
| 对比项 | STM32F103(F1 体系) | STM32F4(F4 体系) |
|---|---|---|
| D+ 上拉 | 必须外部 1.5K | 片内自带,外接即违规 |
| 不接 / 接错的后果 | 主机完全看不到设备 | 并联成 ~750Ω,枚举不稳或失败 |
| 谁控制"露面" | 硬件决定(或 GPIO 控制) | 固件 soft-connect 决定 |
| ChibiOS 驱动 | USBv1 | OTGv1 |
| PA9 的角色 | 与 USB 无关,可随便用 | VBUS 检测,有历史 bug(见 4.3) |
| VBUS 检测引脚 | 没有 | PA9 |
标准接法:D+ 经 1.5K 到 3.3V
# F103 最小 USB 接法(总线供电)
USB 座 VBUS ──────────────→ LDO → 3.3V
USB 座 D- ──────────────→ PA11 (USB_DM)
USB 座 D+ ───┬──────────→ PA12 (USB_DP)
└─[ 1.5kΩ ]─→ 3.3V # 这颗不能省
USB 座 GND ──────────────→ GND
注意电阻接的是 3.3V,不是 VBUS 的 5V。接 5V 一方面把 D+ 抬到超出 USB 规范的电平,另一方面在 MCU 未上电、USB 已插入时会经 IO 反向灌电(back-powering),板子会出现"没供电但 LED 微亮"之类的怪现象。
要不要用 GPIO 控制这颗上拉?有些设计在 D+ 与 3.3V 之间串一个三极管/MOS,由 GPIO 控制通断,好处是固件可以"自己拔插自己"强制主机重新枚举。代价很实在:进了 bootloader,GPIO 回到复位态,上拉就断了——而现成的 stm32duino bootloader 并不会接管你这个自定义引脚,于是 DFU 枚举不出来,直接变砖。所以:做键盘就老实硬接 3.3V,需要重新枚举拔一次线,别为省一次插拔引入一类新故障。
时钟:8MHz 晶振 + USB 固定 1.5 分频
F103 的 USB 需要精确的 48MHz,路径是死的:外部 8MHz → PLL ×9 = 72MHz → USB 预分频 ÷1.5 = 48MHz。这条链上任何一环不对(晶振不是 8MHz、PLL 倍频改了、误用了内部 HSI),USB 就完全不工作,而 CPU 看起来一切正常,非常迷惑。
所以 F103 板子的晶振不能省、不能换频率。APM32E103 是对标 F103 的增强型,时钟树一致,同样按 8MHz 设计;USB 控制器沿用同一套方案,按 F103 的画法外接 1.5K 最稳妥(若极海新版数据手册标注了内置上拉,以手册为准)。
F1 特有的两个坑
- USB 与 CAN 共享 512 字节专用 SRAM:F103 的 USB 包缓冲区不是普通 SRAM,和 CAN 共用一块(ST 官方表注:「cannot be used at the same time」)。键盘不用 CAN,所以无碍;若你想在同一颗片子上同时跑 USB 与 CAN,得二选一。
- F303 是"半个例外":它同样走 USBv1 这套老控制器、同样要外部晶振,但 ST 官方表里 D+ 上拉是「Embedded」,不需要外接 1.5K。QMK 有现成板级
GENERIC_STM32_F303XC。判断依据永远是具体型号的手册,不是"F1/F3 都是老片子"这种印象。
PA9 / PA10 在 F1 上:不归 USB 管,但 bootloader 仍要管
F1 的 USB 只用 PA11(D-)/ PA12(D+),没有 VBUS 检测引脚,所以下一节那场 PA9 的 GCCFG 事故与 F1 完全无关——PA9 拿去做矩阵行/列随便用,不会有内部下拉。
但 PA10 的风险一样存在:它是 USART1_RX,而 F103 的出厂 bootloader 同样支持 USART1 烧录(AN2606)。上电检测相若 PA10 悬空乱飘,bootloader 可能误判"串口那头有人说话"而切走。做 F103 板子时给 PA10 一颗 22K 上拉到 3.3V,成本一颗 0402,能挡掉一类"时好时坏进不去 bootloader"的故障(机理详见 4.3 的 PA10 小节,与芯片系列无关)。
stm32duino 的"软重枚举"就靠这颗电阻
QMK 在 F103 上常用 stm32duino bootloader。它的 generic 版本没有 Maple 那套专用 USB 复位电路,靠的是把 PA12(D+)临时切成 GPIO 拉低一小会儿,再切回 USB 模式,让主机看到一次"拔出—插入"。前提正是 D+ 上那颗约 1.5K 的上拉——没有它,这个技巧无从谈起。这是"F1 的 1.5K 必须硬接"的第二个理由。
4.3 STM32F4 / F5 / F7 体系(OTG_FS,内置 PHY)
从 F4 开始,USB 换成了 OTG 控制器 + 片内全速 PHY:上拉、终端匹配、收发器全部集成,外部只剩"把两根线接出去"。这一节的结论对 F401 / F405 / F407 / F411 / F446、APM32F4 全系,以及 F5 / F7 通用,差异集中在 VCAP 和晶振上(见型号对齐表)。
数据线:直连,别画蛇添足
PA11 = D-、PA12 = D+(F7 等大封装型号可重映射到 PB14 / PB15),两根线直接走到 USB 座即可。片内 PHY 自带终端匹配与驱动能力,这是它与外挂 ULPI 高速 PHY(如 USB3300)最大的区别——后者才需要一堆外围。
布线建议(不是硬性规定,但照做省事):
- D+ / D- 走差分对,尽量等长、平行、短;USB 2.0 全速对阻抗要求宽松(目标 90Ω 差分),普通两层板 1.6mm 厚度、线宽 0.2mm / 间距 0.2mm 左右就够用,不必为它上四层板。
- 差分对不要跨过地平面裂缝,也不要和其他高速信号(如 WS2812 的 DIN)长距离并行。
- USB 座尽量靠近 MCU,走线控制在几厘米内;实在要拉长,优先保证 D+/D- 不被其他信号切割。
D+ 的 1.5KΩ 上拉:芯片内部已有,外接反而违规
原理:F4 的 OTG_FS 把这颗 1.5K 电阻集成在片内 PHY 里,而且是可编程的——由 USB 内核在准备好之后才挂上(soft-connect),固件可以自己决定何时"露面"。
外接的后果:片内 1.5K 与外接 1.5K 并联约 750Ω,D+ 静态电平偏离规范。轻则主机识别时序错乱、枚举时好时坏,重则速度协商失败直接不认设备。有人外接后"能用",是因为片内上拉在 soft-connect 之前还没挂上,属于两头都不对的巧合,不能当作可行方案。
PA9 / PA10:两根引脚、两个完全不相干的问题
搜索"F4 PA9 不能用",会看到一大堆互相矛盾的帖子。混乱的根源是:网上把两个彼此无关的问题混成了一件。先把它们拆开,后面就都好懂了。这一节只针对 OTG 体系(F4 / F5 / F7);F1 没有 VBUS 检测引脚,PA9 不受影响。
| 问题 | 出在哪一层 | 波及引脚 | 与 QMK 版本的关系 |
|---|---|---|---|
| GCCFG 初始化错误 (VBUS 检测没真正关掉) | 固件运行期:ChibiOS 的 OTGv1 驱动 | PA9 | 有关。2023-04 上游修复、2024-10 起 QMK 收录;旧版 QMK 必踩 |
| Bootloader 检测相 误判为串口烧录 | 芯片出厂 ROM:进入 DFU 之前 | PA10 | 无关。ROM 里是 ST / 极海写死的代码,换什么版本固件都一样 |
所以那句最常见的困惑——"我都升到最新 QMK 了,PA9 怎么还是不行"——答案通常是:那其实是 PA10 那边的问题,跟固件版本没有任何关系。
PA9(VBUS)与那场持续四年的 bug
引脚的双重身份:PA9 平时是普通 GPIO(也兼 USART1_TX),但 F4 的 OTG_FS 默认把它当作 VBUS 检测输入——用来判断 USB 线是否插上、主机是否供电。键盘都是总线供电,"插上就有电",这个功能用不上,反而占着一根宝贵的 GPIO。
设计意图:为此 QMK 给所有 F4 板级定义了 BOARD_OTG_NOVBUSSENS,期望按参考手册置 OTG_FS_GCCFG.NOVBUSSENS = 1("不需要 VBUS 检测"),把 PA9 释放成普通 GPIO。这个思路完全正确。
真正的 bug:ChibiOS 的 OTGv1 驱动在初始化 GCCFG 寄存器时,把 VBUSASEN、VBUSBSEN 和 NOVBUSSENS 一起置了 1。而 VBUSBSEN = 1 的含义恰恰是「PA9 作为 VBUS 检测输入启用」,并且会给它挂上内部下拉——于是 PA9 名义上被"释放",实际仍被 USB 外设占用、呈下拉状态。
症状:PA9 做矩阵列(上拉输入)时读不到高电平,整列按键失灵;做行输出时拉高也被内部下拉拖住。2020~2024 年间,凡是把 PA9 用在矩阵里的键盘,基本都栽在这一处,而且极难自查——因为代码、接线、焊接看起来都对。
完整时间线:从首次报告到上游修复
| 时间 | 事件 |
|---|---|
| 2020-01-10 | 用户 yulei 在 QMK 仓库开 issue #7855:NOAH 键盘(STM32F411)改用标准矩阵后,PA9 拉不到低电平,无法作为矩阵引脚;当时没人知道原因 |
| 2021-05-01 | rgoulter 复现:WeAct F401 上 PA9 做输入时内部上拉无效、始终读低;外接上拉就正常——首次把矛头指向"内部下拉没关掉" |
| 2021-06-12 | jiaxin96 确认只出现在 STM32F4 系列(F401 / F411),并给出临时办法:自写 matrix.c 替代标准矩阵扫描 |
| 2022-01-22 | zhaqian12 定位根因:只置 NOVBUSSENS 不够,还必须清掉 VBUSBSEN,在 STM32F2 / F4 上验证通过;同日 jiaxin96 指出这是 ChibiOS 的 bug,正确写法应为 GCCFG_INIT_VALUE = (GCCFG_NOVBUSSENS | GCCFG_PWRDWN) |
| 2022-08-14 | QMK 维护者 drashna 追问"上游修了吗" |
| 2023-04-10 | 仍有用户踩坑(做分体键盘烧了两天,一直怀疑自己焊错);jiaxin96 回复:上游尚未接受,只能自己打补丁,并贴出 ChibiOS 论坛的报告链接 |
| 2023-04-14 | ChibiOS 作者 Giovanni 在论坛回复「Change committed」——上游正式修复(forum topic 6226) |
| 2024-10-13 | drashna 确认"已由上游打补丁并收录",QMK issue #7855 关闭 |
整个过程四年多,中间无数人怀疑过自己的 PCB、排针和焊接——这也是为什么值得把这段历史留在文档里:下次再遇到"整列失灵且查不出硬件问题",先想想是不是引脚被外设占用了。
怎么确认手上的 QMK 已经修复
问题出在这一行宏定义上,路径固定:
lib/chibios/os/hal/ports/STM32/LLD/OTGv1/hal_usb_lld.c
// 修复后:只留 NOVBUSSENS + PWRDWN,不再置 VBUSASEN / VBUSBSEN
#if defined(BOARD_OTG_NOVBUSSENS)
#define GCCFG_INIT_VALUE (GCCFG_NOVBUSSENS | GCCFG_PWRDWN)
#else
#define GCCFG_INIT_VALUE (GCCFG_VBUSASEN | GCCFG_VBUSBSEN | GCCFG_PWRDWN)
#endif
一条命令确认(在 qmk_firmware 根目录执行):
grep -A3 "BOARD_OTG_NOVBUSSENS" lib/chibios/os/hal/ports/STM32/LLD/OTGv1/hal_usb_lld.c
若输出里还带着 GCCFG_VBUSBSEN,就是没修的旧版;只剩 NOVBUSSENS | PWRDWN 就是修好的。本指南核对时(2026-10),QMK 主线锁定的 ChibiOS 子模块已经是修复后的写法。
旧版 QMK 的三种绕过方式
只在维护老工程(不能升级 QMK)时才需要;新工程直接用新版即可。
| 办法 | 做法 | 代价 |
|---|---|---|
| 矩阵避开 PA9 | 换一个空闲引脚 | 最省事;改板或飞线 |
| 自写 matrix.c | init_pins 里先 setPinOutput(pin) 再 setPinInputHigh(pin) | 2020 年的临时招,部分板子无效 |
| 直接打补丁 | 改 hal_usb_lld.c 的 GCCFG_INIT_VALUE | 升级 QMK 会被覆盖,需重新打 |
现在到底怎么接(默认不用 5V)
按实际情况选一行。默认推荐第一行——PA9、PA10 各一颗 22KΩ 上拉到 3.3V,其余全部不接,5V 一根线都不进 MCU。
| 你的情况 | PA9 | PA10 |
|---|---|---|
| 只做有线键盘 (推荐默认) | 22KΩ 上拉到 3.3V 不接 5V、不做分压、不留悬空 | 22KΩ 上拉到 3.3V 与 PA9 同阻值,BOM 少一个料号 |
| 只跑新版 QMK 且想省元件 | 可以悬空;仍建议接 22KΩ,防矩阵扫描时浮空被误读 | 22KΩ 上拉到 3.3V |
| 要兼顾旧版 QMK 或别人刷的固件 | 100KΩ 上拉到 3.3V,下拉那颗不焊 | 22KΩ 上拉到 3.3V |
| 带电池 / 需要 真实 VBUS 检测 | 另开一个普通 GPIO 做检测(VBUS 经 100K / 100K 分压),PA9 仍按上一行处理 | 22KΩ 上拉到 3.3V |
BOARD_OTG_NOVBUSSENS 把检测关掉了。而 5V 上拉有两个真实代价:① 倒灌——USB 断电瞬间 5V 轨先崩、3.3V 还有残压时,电流经上拉网络推回 5V 轨(back-powering,可让某些 hub 锁死);② 上电时序——VBUS 已有 5V 而 LDO 还没起来时,分压中点 2.5V 直接打在未上电的 MCU 引脚上,经 ESD 二极管灌进 3.3V 网络,表现为“平时正常、热插拔偶尔死机”。正确做法见上表第三行:只留 100K 上拉到 3.3V,下拉那颗(DNP / 不焊);只跑新版 QMK 的话,直接用第一行的 22K 更省事。
依据:QMK GENERIC_STM32_F411XE 板级配置定义了 BOARD_OTG_NOVBUSSENS 1,USB 内核把 VBUS 视为常有效,PA9 不参与枚举;旧版 ChibiOS 的 GCCFG 缺陷曾让它维持内部下拉,2023-04 上游修复、2024-10 起 QMK 收录。QMK F411 板级配置 · ST 社区:NOVBUSSENS 可释放 PA9
PA10:ROM Bootloader 的"接口嗅探"(与固件版本无关)
这一条和上面那场 bug 毫无关系,但症状很像——都是"电脑里看不到设备"。而且它跟 QMK 版本、跟 ChibiOS 全都无关,升到再新的固件也救不了,因为出问题的代码在芯片出厂时就烧死在 ROM 里了。对 F1 / F4 / APM32 一样成立:只要用了 ST 系或极海系的出厂 bootloader,PA10 就不能悬空。
依据:AN2606 的原文要求
ST 的 AN2606《STM32 microcontroller system memory boot mode》第 4.3 节原文:
翻译成人话:bootloader 上电后要挨个"嗅探"可用的烧录接口——USART1、USART3、CAN2、USB,谁先有动静就走谁。而那些没打算用的接口,它们的 RX 引脚必须处于确定电平(高或低都行),否则电平乱飘会被误认为"有人正在发数据",从而把那个接口激活。
为什么偏偏是 PA10 中招
因为 PA10 正好是 USART1_RX(F1 / F4 都是)。悬空时它像一根天线,手靠近、板子受潮、甚至上电瞬间的容性耦合,都可能让它读到几个"看起来像起始位"的电平跳变。bootloader 一旦判定"串口那头有人在跟我说话",就会切到串口烧录模式,把 USB DFU 关掉——于是你在电脑设备管理器里根本看不到 DFU 设备,连 QMK Toolbox 都刷不进去,表现为"救砖失败"。
这种失败最坑的地方是时好时坏:有人拔插十次能进一次,于是误以为是 USB 线、电脑或驱动的问题。STM32duino 社区 2022-12 的实测帖写得很直白:多块 F401 板子约 80% 概率枚举失败、报 “Device descriptor request failed”,把 PA10 拉到确定电平后稳定进 DFU。
为什么是 22K,能不能换别的值
高电平同样算"确定电平",所以上拉、下拉都可以,关键是不能悬空。实际板子几乎都用上拉到 3.3V,因为下拉会让"USART 空闲态"看起来像线路异常,而上拉正好符合 UART 空闲为高的习惯。
- 22K(WeAct / QMK Blackpill 文档的推荐值):噪声环境下足够"硬",远强于板面漏电与容性耦合;又不会在 PA10 当普通 GPIO 用时干扰外部驱动,静态电流只有 ~150µA。
- 10K ~ 100K 都可用:越小越抗干扰、静态电流越大;越大越省电、抗干扰越差。22K 是这两者之间的常用折中。
- 不要省:一颗 0402 电阻的成本,换掉一整类"时好时坏、无法复现"的故障,没有任何理由省。
型号对齐:谁和谁能共用一套图
结论先给:F4 / F5 / F7 的 USB 外围接法完全相同,差异只在板级配置文件放在哪、晶振频率、以及 VCAP 接法。下表按 LQFP-64 口径整理,封装不同时引脚号以数据手册为准。
| 芯片 | QMK 通用板级 | 默认晶振 | VCAP | 备注 |
|---|---|---|---|---|
| STM32F401 | GENERIC_STM32_F401XC | 8MHz | 单 VCAP(30 脚) | 只有 OTG_FS;LQFP-64 的 31 / 47 脚是 VSS,必须接地 |
| STM32F411 | GENERIC_STM32_F411XE | 8MHz | 单 VCAP(30 脚) | issue #7855 的原型芯片;板级 config.h 定义 BOARD_OTG_NOVBUSSENS 1 |
| STM32F446 | GENERIC_STM32_F446XE | 8MHz | 双 VCAP(按手册) | 另有 OTG_HS(需外置 ULPI PHY),QMK 用不到;USB 部分按 OTG_FS 接 |
| STM32F405 | GENERIC_STM32_F405XG | 12MHz | 双 VCAP(31 / 47 脚) | 板级 board.h 定义 BOARD_OTG_NOVBUSSENS;PA9 兼 USART1_TX |
| STM32F407 | GENERIC_STM32_F407XE | 8MHz | 双 VCAP(31 / 47 脚) | 与 F405 引脚兼容但晶振预设不同,混用时最容易翻车的一对 |
| STM32F5 / F7 | 按型号自建 或套用相近板级 | 按型号 | 多为双 VCAP | 同为 OTG 体系,D+ / PA9 / PA10 结论通用;PLL 与引脚映射必须核对 mcuconf.h |
| APM32F411 | GENERIC_STM32_F411XE | 8MHz | 单 VCAP(30 脚) | LQFP-64 与 STM32F411 一一对应,PCB 可共用;ROM 的 USART1 同样走 PA9 / PA10 |
| APM32F405/407 | GENERIC_STM32_F405XG | F405 = 12MHz F407 = 8MHz | 双 VCAP | 极海 ROM 支持 USB + USART + CAN,接口集合与 ST 一致 |
Black Pill:板子本身是好板,但「BlackPill 板级」只对 25MHz 正品成立
这块板的原创方是 WeAct Studio(微行工作室),不是无名山寨——它在国产核心板里属于做得相当用心的那类:411 用沉金 + 无铅工艺、排针镀金、25MHz 与 32.768kHz 都用金属外壳晶振、USB-C 串了防倒灌二极管、背面预留 SPI Flash 焊盘(附 USBDisk / FATFS 例程)、原理图与例程在 GitHub 同步更新(WeActStudio.MiniSTM32F4x1,2026-10 仍有提交),连出厂都预烧了自己的 HID Bootloader。官方淘宝店:shop118454188.taobao.com。
2026-10 复核的现状(依据其仓库 README 的「出售情况」与料号说明):
| 型号 | 主频 / Flash / RAM | 官方在售 | 备注 |
|---|---|---|---|
| STM32F401CCU6 | 84MHz / 256K / 64K | 停售 | 官方已停售,市面流通多为库存或仿板 |
| STM32F401CEU6 | 84MHz / 512K / 96K | 停售 | 同上 |
| STM32F411CEU6 | 100MHz / 512K / 128K | 在售(V3.1) | 25MHz + 32.768kHz、三个按键(NRST / BOOT0 / 用户键 PA0)、USB-C;出厂预烧 WeAct HID Bootloader |
官方在 README 里点名说过这件事,值得照抄过来:
- 背面没有
WeAct StudioLogo 和版本号的,都不是他们生产的——这条是唯一可靠的辨识方法,买到仿板「质量问题和技术问题请自行找卖家解决」。 - 工艺:正品 411 沉金无铅、401 无铅、排针镀金、符合 ROHS;仿板为压成本普遍用有铅工艺。
- 芯片:正品用当前批次的新片;仿板常见翻新片 / 老批次。
- 出厂检测:正品每块跑自家产测程序;仿板「出厂检测敷衍」,于是出现各种莫名其妙的问题——最后背锅的往往是 QMK。
正品也有一个先天短板(QMK 官方文档与 WeAct 自己的说明都提到):25MHz 晶振在某些板子上冷机进不去 DFU,把芯片捂到 25℃ 左右就能进。这是起振裕量问题,属于开发板硬件层面,不是固件锅——如果你的自画板也要「吹热才能进 DFU」,去查晶振负载电容与起振裕量。
所以就出现一个看着矛盾、其实很合理的结论:板子值得买、厂家也靠谱,但「BlackPill」这个名字不是一个可依赖的硬件基线——正品是 25MHz、仿板可能换 8MHz,同一个商品名买到的是两种时钟。而 QMK 的 BLACKPILL_STM32_F401 / F411 板级把 HSE 写死成 25MHz(PLL 也按 25MHz 配)。你自画板用的是 8MHz 或 12MHz,一旦顺手套上这个板级,USB 会彻底不枚举,但板子本身看起来还活着——这类故障极难往「板级选错」上想。
源码里到底写死了什么
两个板级的实现很短,问题一目了然(路径相对 QMK 根目录):
// platforms/chibios/boards/BLACKPILL_STM32_F411/configs/config.h
#define STM32_HSECLK 25000000U // ← 25MHz 写死在这里(外面套了 #ifndef,可被更早的 -D 覆盖)
// platforms/chibios/boards/BLACKPILL_STM32_F411/configs/mcuconf.h
STM32_PLLM_VALUE 25 STM32_PLLN_VALUE 384
STM32_PLLP_VALUE 4 STM32_PLLQ_VALUE 8
// platforms/chibios/boards/GENERIC_STM32_F411XE/configs/mcuconf.h(自画板用这个)
STM32_PLLM_VALUE 4 STM32_PLLN_VALUE 96
STM32_PLLP_VALUE 2 STM32_PLLQ_VALUE 4
两套参数各自都算得出 96MHz 主频 + 48MHz USB,但前提都是"喂给它的晶振正好是它期望的那一个":
| 板级 | 期望 HSE | PLL(M / N / P / Q) | 验算 |
|---|---|---|---|
| BLACKPILL_STM32_F4xx | 25MHz | 25 / 384 / 4 / 8 | 25÷25=1MHz → ×384=384MHz → ÷4=96MHz、÷8=48MHz |
| GENERIC_STM32_F4xxx | 8MHz (F405 为 12MHz) | 4 / 96 / 2 / 4 | 8÷4=2MHz → ×96=192MHz → ÷2=96MHz、÷4=48MHz |
错配之后会怎样:USB 死,CPU 还在跑
- 板上是 8MHz,却用了 BLACKPILL 板级:8÷25=0.32MHz 送进 PLL,而 F4 要求 PLL 输入在 1~2MHz 之间。就算勉强锁上,VCO 只有 122.88MHz → 主频 30.72MHz、USB 时钟 15.36MHz(要 48MHz)。
- 板上是 25MHz,却用了 GENERIC 板级:25÷4=6.25MHz 远超上限,VCO 算到 600MHz(F411 上限 432MHz)→ PLL 根本锁不上,芯片退回内部 HSI 跑 16MHz。
两种情况的表现完全一样:板子能跑、矩阵能扫描、串口可能还有输出,但插上电脑连"未知 USB 设备"都不弹。注意这和前面 PA9 / PA10 悬空的症状一模一样——三条完全不同的根因、一个表现,这就是这类问题最难查的原因。
那到底用哪个板级
| 你的情况 | 用哪个板级 |
|---|---|
| 自己画 PCB (默认,也推荐) | GENERIC_STM32_F401XC / F411XE / F405XG …,板上晶振按表配 8MHz(F405 用 12MHz) |
| 手上是原版 BlackPill 晶振丝印 25.000 | 可以用 BLACKPILL_STM32_F401 / F411(预设就是为它写的) |
| 手上是无 Logo 的仿板, 晶振 8.000 | 改用 GENERIC_STM32_F411XE,不要用 BLACKPILL 板级——仿板换晶振正是为了省成本 |
| 已经在用 BLACKPILL 板级, 但硬件是 8MHz | 换板级(一行改动),不要去改 mcuconf.h 硬凑——改的是 QMK 本体,一次更新就丢 |
分辨自己手上是哪种,最直接的办法是看晶振丝印:BlackPill 用的是 3225 贴片晶振,表面会印 25.000 或 8.000。另一条路是拿万用表/示波器量,但没必要——丝印就在那儿。
BlackPill 板级还顺带带了一堆"开发板假设"
晶振只是最要命的一条,其它还有:
EARLY_INIT_PERFORM_BOOTLOADER_JUMP TRUE:开机早期就尝试跳 bootloader(原板用按键触发 DFU 的那套逻辑)。- PA0 是板载用户按键、PC13 是板载 LED(上拉到 3.3V)、A4~A7 是背面的 SPI Flash 焊盘——这些引脚在你的板子上可能正接着矩阵。
- 板载 LSE 32.768kHz、固定 USB 端点 FIFO 等参数,都是照那颗芯片的外围配的。
用在自己 PCB 上,等于"借别人的板子定义":能跑的时候无所谓,一旦出事,你根本分不清是哪条假设在作怪。
WeAct 出厂预烧的是自家的 WeAct HID Bootloader(按住 KEY 键上电/复位,C13 闪即进入),按其仓库示例工程,应用的起始地址是 0x08004000——也就是 bootloader 占了前 16KB。
QMK 这边没有这个 bootloader 的档位,走的是 BOOTLOADER = stm32-dfu(芯片 ROM 里的系统 DFU),固件链接在 0x08000000。所以第一次用 QMK Toolbox 刷进 QMK 固件,就会把那段 HID Bootloader 顶掉。之后你按住 KEY 键自然进不去 HID 模式了——这不是板子坏了,是它本来就被替换了。
换来的好处是:QMK 之后靠 ROM DFU 刷(0483:DF11),进 DFU 用 QK_BOOT 或 BOOT0 键 + 复位,一样不拆壳、不接线。想回到 HID Bootloader,去 WeAct 仓库下 Soft/WeAct_HID_FW_Bootloader 重新烧回 0x08000000 即可。
VCAP 与供电:F405 和 F411 不能照抄
VCAP 是芯片内核稳压器的外接储能电容,漏了或值不对,芯片表现为随机死机、USB 时通时断,而且很难往这上面想。F4 这一代里,LQFP-64 的 30 / 31 / 47 三个脚在不同型号上含义完全相反,这是混板设计最容易出事的地方:
| 芯片 | VCAP 引脚 | 电容方案 | ESR 要求 |
|---|---|---|---|
| STM32F405 / F407 APM32F405 / F407 | 31、47 脚(双 VCAP) | 各 1 × 2.2µF | < 2Ω |
| STM32F411 / F401 APM32F411 | 30 脚(单 VCAP) | 1 × 4.7µF | < 1Ω |
三个必须记住的差异:
- F411 / F401 的 LQFP-64 没有 PB11,不要照着 F405 的引脚图去接。
- F411 / F401 上 31 / 47 脚是 VSS,必须直接接地,不能当 VCAP 用;反过来,F405 / F407 上这两脚必须接电容——接错一个是死机,一个是短路。
- F411 / F401 的 30 脚是 VCAP,而 F405 / F407 的 30 脚是普通 GPIO。这是"一块板兼容两代"时最容易漏掉的一处,因为 30 脚在两边都是"要处理"的引脚,含义却相反。
想用一套 PCB 同时兼容 F405 和 F411(做升级款或降本款时很常见):30 / 31 / 47 三个位置各留一组 0Ω 选择位——装 F405 时 31 / 47 接电容、30 脚按 GPIO 处理;装 F411 时 30 脚接电容、31 / 47 用 0Ω 短接到地。同时注意晶振也不同(12MHz vs 8MHz),要留两个焊盘或选可换的晶振封装。
提醒:官方文档这段已经过时
QMK 官方 WeAct Blackpill 文档至今仍写着「PA9 有内部下拉,不要用;非用不可加 ~5.1K 上拉」「PA10 加 ~22K 上拉」。前一句描述的是 2020~2023 年旧 ChibiOS 的行为,2024-10 修复后已不成立,但文档没有跟进。
于是网上大量教程、博客、甚至某些卖家的说明还停在"PA9 不能用、必须外接上拉"的年代。分辨办法很简单:看它有没有提到 ChibiOS 的 GCCFG 修复(2023-04 / 2024-10)——没提的,多半是从旧帖抄来的。本指南的结论以源码和芯片手册为准。
4.4 其余芯片:F0 / RP2040 / ATmega32U4 / H7
这几类不属 F1 也不属 F4,但都有人用,共同点是上拉已经在芯片内部,差别在晶振、串阻和电源域。
STM32F0(F042 / F072 / APM32F072):上拉内置,晶振看型号
- D+ 上拉内置(ST 官方表标 Embedded),不需要外接 1.5K,也不需要外接三极管。
- F072 有 HSI48:内部 48MHz RC 直接供 USB,连晶振都可以不接,这是它做小键盘最大的优势(QMK 板级
GENERIC_STM32_F072XB就是按无晶振配的)。 - F042 没有 HSI48:必须外接晶振(8MHz)经 PLL 出 48MHz,别照着 F072 省晶振——这是两兄弟最容易混的地方。
- 数据线直连,不需要串阻;F0 没有 VBUS 检测引脚,不存在 PA9 那类问题。
RP2040:唯一需要串 27Ω 的
RP2040 的 USB 1.1 控制器带片内 PHY,总线上下拉都在内部,所以不需要 1.5K 上拉;但官方数据手册明确要求 USB_DP / USB_DM 各串一颗 27Ω(串联终端,兼作阻尼),并且 USB bootloader 必须用 12MHz 晶振或 12MHz 时钟输入。这两条与 STM32 的"直连、别串阻"正好相反,照抄 ST 的图会得到一块"能跑但高速眼图很差"的板子,照抄"省晶振"则会连 UF2 都进不去。Pico 官方原理图上那两颗 27Ω 就是干这个的。
ATmega32U4:16MHz 是硬要求
上拉内置(由 USB 控制器的 DETACH 位控制),D+ / D- 直连、不串阻。但 16MHz 晶振不能省、不能换:USB 的 48MHz 由 16MHz 经 PLL 得到,晶振不对就完全不枚举。这也是为什么 Pro Micro 类板子清一色 16MHz。
STM32H723:接法同 F4,但多一个电源域
H7 的 USB 也是 OTG 体系(OTG_HS 带内置全速 PHY),D+ 上拉、PA9 / PA10 的结论与 F4 一致(QMK 板级 GENERIC_STM32_H723XG,默认 25MHz 晶振)。额外的坑在供电:H7 的 USB 收发器挂在独立的 VDD33USB 电源域上,这一路没供好,USB 就是彻底不工作——按数据手册把它接到内部 USB 稳压器输出(配去耦电容)或外部 3.3V,别想当然地认为"VDD 有电就行"。
4.5 打板前自检清单
画完原理图,对着自己那块板所属的体系逐条勾,能挡掉绝大多数"USB 不认"的返工。
| 检查项 | F1 板(F103 / E103 / F303) | F4 板(F401/F405/F407/F411/F446 · APM32F4) |
|---|---|---|
| D+ 1.5K 上拉 | 必须有(F303 除外,它内置) | 必须没有(有就删) |
| D+ / D- 串阻 | 不要 | 不要(RP2040 才要 27Ω) |
| 晶振 | 8MHz,不可省 | F405 = 12MHz、其余 = 8MHz,负载电容按晶振规格配 |
| PA9 | 随便用(与 USB 无关) | 只跑新版 QMK:可悬空;要兼容旧固件:100K 上拉 3.3V,不装下拉 |
| PA10 | 22K 上拉 3.3V(bootloader 检测相要求) | 22K 上拉 3.3V(必接) |
| VCAP | 无此概念(31 / 47 是 VSS,接地) | F405/407 两脚各 2.2µF;F411/F401 单脚 4.7µF 且 31/47 接地 |
| 3.3V 去耦 | 每个电源脚一颗 100nF,全局一颗 4.7µF | 同左;H7 另需 VDD33USB 供电 |
| 混板兼容 | 若一块板要兼容 F103 与 F4:1.5K 上拉做成 0Ω / 跳线可选,F4 工作时必须断开;30 / 31 / 47 脚留 0Ω 选择位 | |
4.6 参考链接
QMK issue #7855(2020-01-10 开,2024-10-13 关闭) — PA9 内部下拉问题的完整讨论
ChibiOS 论坛 topic 6226(2023-04-14 上游提交修复) — Giovanni 回复 "Change committed"
QMK GENERIC_STM32_F411XE 板级配置 —
BOARD_OTG_NOVBUSSENS 1 的出处ST AN2606 — System memory boot mode,PA10 要求的原始依据(§4.3)
STM32duino 社区(2022-12) — PA10 悬空导致 DFU 枚举失败的实测
STM32duino bootloader — generic 板靠拉低 PA12 强制重枚举,依赖 D+ 的 1.5K 上拉
QMK:WeAct Blackpill 引脚限制 — 旧说法,仅作历史参考
⌨ 五、键盘矩阵硬件设计框架 · 正文待补
回答"这颗 MCU 能做多大的键盘":矩阵怎么排、二极管怎么放、引脚够不够。与「USB 硬件设计」同级,属于不画 PCB 也必须掌握的硬件常识。
将覆盖:
① 矩阵扫描原理:为什么 N 个键不需要 N 个引脚,行×列扫描是怎么省引脚的;
② COL2ROW / ROW2COL:两种二极管方向的区别、QMK 里在哪配置、接反了的典型症状;
③ 二极管选型:1N4148(直插/贴片)即可,为什么不能用普通整流管,每个键一颗、方向朝向;
④ 引脚预算速算:行数+列数 = 占用 GPIO 数,常用布局对照(如 60% ≈ 5×14=19 脚、40% ≈ 4×12=16 脚、小键盘 4×5=9 脚),再预留 RGB/OLED/编码器/ADC 引脚后反推 MCU 封装够不够用;
⑤ NKRO 与消抖:全键无冲靠什么实现、debounce 是什么、什么情况下要调;
⑥ 直排/斜列矩阵:非规则布局(方向键区、旋钮位)如何折算矩阵。
💾 六、烧录 Bootloader
先分清一件事:bootloader 是「负责把固件收进芯片」的那段小程序,它和 QMK 固件是两份东西。绝大多数芯片出厂就带一份能用的,只有两族例外。
| 芯片 | 出厂自带什么 | 要不要自己烧 |
|---|---|---|
| F103 / E103(APM32) | 只有 ST / 极海的ROM 系统引导:必须 BOOT0 拉高才进、只认整片 Flash、没有「应用区」概念 | 要:烧 stm32duino,见 6.1 / 6.2 |
| ATmega32U4(AVR) | 多为空片:没有 USB 引导,必须先走 ISP 烧 atmel-dfu / Caterina 进去 | 要,但流程最麻烦,且和上面完全是两套 → 6.4(占位,待补) |
| F4 / F0 / F3 / F7 / RP2040 / WB32 / AT32 | ROM 里的系统 DFU,或原生 UF2(RP2040) | 不用,直接看 第七章 烧录固件 |
F103 / E103 出厂带的是 ST / 极海写在 ROM 里的 System Bootloader。它有三个硬限制,放到键盘上条条要命:
- 必须把
BOOT0拉高才进得去。键盘装进壳之后,这个跳线要么在壳里、要么根本没引出来——每更新一次固件就得拆一次壳。 - 只认整片 Flash,没有「应用区」。它眼里的起点永远是
0x08000000,不存在「只更新固件、保留前面那一段」的概念。 - 它在 ROM 里,改不了也删不掉。好处是永远不会丢;坏处是它不会配合你的固件做「跳进 DFU」这种动作。
stm32duino(Maple Bootloader,社区维护、QMK 原生支持)常驻在 0x08000000 起的前 8KB,把 0x08002000 之后划给应用——也就是你的 QMK 固件。它自带 USB DFU 接口,枚举成 1EAF:0003。
于是更新固件变成:按一下 QK_BOOT(或按住 Bootmagic 键插 USB)→ 电脑出现 DFU 设备 → Toolbox 点 Flash。全程不拆壳、不碰跳线。APM32E103 完全适用,已实测。
这 8KB 一旦在位,QMK 那边只需要两行配置(见 6.2 末尾),后面所有升级都走 USB。
| 自己一块板,偶尔改键 | 能忍:拆壳、拨 BOOT0、插线、烧完再拨回来。改一次骂一次,但过得去 |
| 打算做几块给人用 | 不能忍:对方没有 ST-Link、也不会拆壳。没有 stm32duino 就等于没有升级通道 |
| 量产 / 返修 | 每块都要上台架接线,效率极低;而且 BOOT0 焊死拉低的板子,连 ROM 引导都进不去 |
| 方式 | 需要什么 | 什么时候用 |
|---|---|---|
| A · STM32CubeProgrammer | ST-Link + 一根 USB 线 | 新手首选。图形界面,能看见「连接 → 选文件 → 填地址 → 开始」每一步,出问题也知道卡在哪 |
| B · ST-LINK Utility | ST-Link + 旧版工具 | 老电脑、或 CubeProgrammer 装不上(要注册 ST 账号)。ST 已停止更新,但离线可用、体积小 |
| C · st-flash 命令行 | ST-Link + QMK MSYS | 已经装了 QMK MSYS,想一条命令解决;适合反复烧同一块板 |
| D · USB-TTL 串口 | USB-TTL 模块 | SWD 被锁、芯片翻新、手头没有 ST-Link 时的兜底方案 |
都是走 SWD 接口(SWDIO/SWCLK,即 PA13/PA14),四根线:
| ST-Link | 开发板 | 说明 |
|---|---|---|
| SWDIO / DIO | SWDIO / PA13 | 数据线 |
| SWCLK / CLK | SWCLK / PA14 | 时钟线 |
| 3.3V | 3.3V | 供电 |
| GND | GND | 共地 |
找不到 SWD 排针就直接接 PA13/PA14。注意 ST-Link 的 3.3V 只用于给目标供电,别同时插着键盘的 USB 线。
ST-Link 版本: 建议 V2.1(比 V2 多虚拟串口和文件拖拽)。V2 基础款也可用,V3 太贵不划算。Win10/11 自带驱动,黄色感叹号才需搜 STSW-LINK009 装驱动。
这是 ST 现在主推的官方烧录工具,Windows / macOS / Linux 都有。下载要注册一个 ST 账号,也可在装 STM32CubeMX 时一起勾选安装。
1. 连接芯片:
- 接好上面四根线,ST-Link 插电脑
- 打开 CubeProgrammer,右上角把 Port 选
SWD(默认就是) - 点 Connect。成功时右侧信息栏会出现芯片型号、Flash 大小、UID
- 连不上就把 Reset mode 从
Normal改成Under reset再点 Connect——这是 F103 最常见的连不上原因:芯片已经在跑用户程序、把 SWD 引脚挪作他用了,只有在复位期间才肯让调试器接管
2. 写入 bootloader:
- 左侧切到 Erasing & Programming 页
- File path 选
generic-none_bootloader.bin(约 7KB) - Start address 必须填
0x08000000。.bin 是纯二进制、不带地址信息,工具不知道往哪写;.hex / .elf 自带地址,可以留空 - 勾上 Verify programming;Run after programming 可以不勾(bootloader 自己会跑)
- 点 Start Programming,等进度条走完,出现
File download complete/Download verified successfully即成功
3. 验证:拔掉 ST-Link,用 USB 线插键盘。因为 0x08002000 之后还是空的,bootloader 会停在 DFU 模式等你刷固件——设备管理器里应该能看到 1EAF:0003 这个设备。看到它就说明 bootloader 活了。
ST 早已停止更新(最终版 v4.6.0,官网已归档),新项目建议直接用方式 A。但它在老机器上跑得动、不需要登录,手上已经装了的话完全可以继续用。
操作步骤:
Target→Connect,下方日志出现Device connected和芯片型号即为连上。连不上同样在Target→Settings里把 Connect Under Reset 勾上再连File→Open File...,选generic-none_bootloader.bin- 弹出 Start address 对话框,填
0x08000000→ OK。这一步漏了或填错,后面就白烧 Target→Program & Verify...,确认勾了Verify after programming,点Start- 日志出现
Programmed successfully/Verification... OK即完成
地址对话框是 ST-LINK Utility 最容易漏的一步:它不像 CubeProgrammer 把地址摆在界面上,而是打开文件时弹一次窗,关掉后就再也看不见当前写的是哪个地址。
1. 部署工具链:
# 在 QMK MSYS 中执行
pacman -S mingw-w64-x86_64-stlink mingw-w64-x86_64-openocd
2. 验证识别:
st-info --probe
正常返回 chipid: 0x041x。
3. 烧录:
st-flash --reset --format binary write <路径> 0x08000000
# 示例:
st-flash --reset --format binary write E:/QMKKeyboard/F103bootloader.bin 0x08000000
文件为 generic-none_bootloader.bin(约 7KB),在 STM32duino-bootloader 仓库的 bootloader_only_binaries 目录。看到 Flash written and verified! jolly good! 即完成。
Flash written and verified! 即为成功。适用于 SWD 被锁、芯片翻新、或无 ST-Link。
1. 硬件连接:
| USB-TTL | 开发板 | 说明 |
|---|---|---|
| RX | PA9 (TX) | 交叉 |
| TX | PA10 (RX) | 交叉 |
| 3.3V | 3.3V | 供电(⚠ 务必 3.3V) |
| GND | GND | 共地 |
BOOT0 跳线拨到 1(或接 3.3V),进入系统 Bootloader。
2. Flash Loader Demonstrator 操作:
- 选串口号 → Next,绿灯
Target is readable正常 - APM32 首次: 可能提示锁定,先点「解锁」等十几秒(卡死就强制结束进程、重插 TTL、重开工具)
Download to device→ 选generic-none_bootloader.bin→ 勾Global Erase+Verify after download→ Next- 绿色进度条
Download operation finished successfully即完成
完成后断开 TTL,BOOT0 恢复 0(接地),插入电脑即可识别为 stm32duino 设备。
# rules.mk
MCU = STM32F103
BOOTLOADER = stm32duino
只写这两行就够。QMK 看到 BOOTLOADER = stm32duino 会自动把 BOARD 设为 STM32_F103_STM32DUINO、链接脚本换成 STM32F103x8_stm32duino 系列(固件从 0x08002000 开始)、DFU 参数设为 -d 1EAF:0003 -a 2。手写 BOARD 属于重复定义,写错了反而编译不过。
0x08000000 写了一份 QMK 固件。结果键盘彻底不动,QMK Toolbox 里再也看不到设备,插上电脑连「未知 USB 设备」都不弹。这事看着像「手贱」,其实是被三套长得几乎一样的流程绕进去了。把 Flash 的地址摊开看,一眼就明白。
Flash 被切成两半
| 起始 | 结束 | 里面是什么 | 谁来写 |
|---|---|---|---|
0x08000000 | 0x08001FFF | stm32duino bootloader(8 KB) | ST-Link / 串口,一辈子只写这一次 |
0x08002000 | 0x0800FFFF | QMK 固件(64 KB 型号剩 56 KB) | USB 上的 1EAF:0003 |
QMK 编译 BOOTLOADER = stm32duino 时,链接脚本 stm32duino_bootloader_common.ld 里写死了这一行:
flash0 : org = 0x08002000, len = f103_flash_size - 0x2000
意思是:固件里的中断向量表、所有跳转地址,全部以 0x08002000 为基准。所以这份 .bin 只有落在 0x08002000 才是对的。写到 0x08000000 会发生两件事,同时发生:
- 前 8KB 被覆盖 → bootloader 没了,USB DFU 永久消失,按
QK_BOOT也没用(那行跳转代码本身就在被覆盖的区域里) - 固件整体前移了 8KB,但代码里写死的跳转目标还指向
0x08002000之后 → 向量表错位,程序跑飞,表现为完全不启动
两个错误叠在一起,所以症状是「既不能当键盘用,也不能再刷」,只能拿 ST-Link 重新来过。
三条通道,别再混着用
| 通道 | USB 上是谁 | 写到哪 | 什么时候用 |
|---|---|---|---|
| ST-Link / SWD | 不是 USB 设备,调试器自己枚举 | 你手动填的地址 | 只用来烧 bootloader,地址必须 0x08000000 |
| stm32duino DFU | 1EAF:0003 | 0x08002000(alt 2) | F103 日常刷固件的唯一正确通道,QMK Toolbox 认的就是它 |
| ST 系统 bootloader | 0483:DF11 | 0x08000000(整片) | F4 / F0 等芯片刷固件用;F103 上走这条会覆盖掉 stm32duino |
关键差别就在写哪个地址:1EAF:0003 的 alt 2 是 bootloader 自己划出来的「应用区」,它知道该写 0x08002000;0483:DF11 的 alt 0 是「整片 Flash」,起点永远是 0x08000000,不管里面有没有别人的东西。
怎么判断自己走错路了
QMK Toolbox 显示 STM32Duino | 对。这是你刷的那个 8KB bootloader,点 Flash 会写到 0x08002000,放心刷 |
QMK Toolbox 显示 STM32 DFU | 停手。这是 ST 出厂的系统 bootloader(0483:DF11),点下去就是写 0x08000000。F103 上到这一步,说明 BOOT0 还挂在 1——先断电把 BOOT0 拨回 0,再插 USB 重来 |
两个都看不到,只有 ST-Link 或串口 | 芯片没在跑 USB。要么 bootloader 还没刷,要么已经被覆盖了 |
命令行同理,两条命令长得像,结果完全不同:
# 正确:stm32duino 的 alt 2,bootloader 自己决定写 0x08002000
dfu-util -a 2 -d 1EAF:0003 -D firmware.bin
# 错误:ST 系统 bootloader 的 alt 0,写死 0x08000000,F103 上会覆盖 bootloader
dfu-util -a 0 -d 0483:DF11 -s 0x08000000:leave -D firmware.bin
第二条是 F4 / F0 系列的标准命令(QMK 的 stm32-dfu 就长这样),在网上到处能搜到。看到「-d 0483:DF11、-s 0x08000000」就跟自己对一下:手上是 F103 就别用。
QK_BOOT 为什么不需要抢时间窗口
Maple bootloader 上电后只等两秒左右,没等到 DFU 通信就跳去跑固件。听起来很不友好,但 QMK 绕过了这个窗口——stm32duino.c 里按下 QK_BOOT 干的事是:
BKP->DR10 = RTC_BOOTLOADER_JUST_UPLOADED;
NVIC_SystemReset();
先在备份寄存器里留个标记再软复位,bootloader 醒来看到这个标记就直接停在 DFU 等你,不用抢那两秒。所以永远有办法进 DFU,前提是 bootloader 还在——它一旦被覆盖,这个标记就没人读了。
已经搞坏了怎么办
- 不要再试 USB,插多少次都不会有设备出现,bootloader 已经没了
- 回到 6.2,用 ST-Link 或串口重烧一次 bootloader 到
0x08000000 - 拔掉 ST-Link,插 USB,确认设备是
1EAF:0003 - 用 QMK Toolbox 刷固件。这一次,也只会有这一次用得上外部工具
0x08000000,只干一次;之后所有固件都交给 USB 上的 1EAF:0003,它会把东西放到 0x08002000。两件事不要互相代劳。顺带说一句:反过来「用 QMK Toolbox 刷 bootloader」也不行——Toolbox 里给 STM32 用的写入口就是 0483:DF11 那条,同样落到 0x08000000,但 bootloader 的 .bin 在 USB 上是刷不进去的(那时芯片还没跑 stm32duino,USB 根本不存在)。
为什么它也要烧:ATmega32U4 出厂是空片——USB 控制器虽然在片内,但没有引导程序。不先烧 atmel-dfu(或 Caterina)进去,它就是一块没有 USB 的 8 位单片机,插上电脑什么都不会发生。
为什么说它麻烦(这才是重点):
- 通道不一样:AVR 走 ISP(SPI 六根线:MOSI / MISO / SCK / RESET / VCC / GND),和 STM32 那套 SWD 完全不通用,工具也另买(USBasp,或用另一块 Arduino 当 ISP)。
- 烧完 bootloader 还得配熔丝位(LFUSE / HFUSE / EFUSE):时钟源选外部晶振还是内部 RC、CKDIV8 是否 8 分频、BOOTRST 复位向量指向哪、BOOTSZ 划多大 boot 区。错一位就可能跑错频率(USB 直接不工作)或变砖,而熔丝改错常常只能上高压并行编程器才救得回来。
- 晶振必须对:QMK 的 AVR 全线按 16MHz 算(
F_CPU),板子上贴 8MHz 就得连熔丝、F_CPU一起改。 - bootloader 与
BOOTLOADER值必须配对:atmel-dfu / caterina / qmk-dfu / usbasploader,烧的是哪个,rules.mk 里就得写哪个,否则「烧进去了但刷不了固件」。
这一节将补:ISP 接线(USBasp / Arduino-as-ISP 两种)→ avrdude 命令与熔丝位对照表 → atmel-dfu 与 Caterina 怎么选 → 熔丝锁死后的救砖路径。
官方对照:QMK Flashing 的 Atmel DFU / Caterina 两节
📥 七、烧录固件
Bootloader 就位(或芯片自带)之后,就是往里面灌 QMK 固件。这一步有四条路,先认清各自适合什么场合,再看细节——绝大多数人一辈子只需要第一条。
| 通道 | 用什么 | 适合什么场合 |
|---|---|---|
| ① QMK Toolbox 图形化 | 一个 exe + 一根 USB 线 | 日常主力:改键、试灯效、换固件。能看见设备名,出错也知道卡在哪。7.1 |
② 命令行 qmk flash | QMK MSYS / 终端 | 用得偏少:编译+刷写一条命令、写脚本批量、或 Toolbox 认不出设备(AT32 这类)时。7.2 |
| ③ UF2 拖 U 盘 | 文件管理器,无需任何工具 | 最省事:RP2040 原生就支持;STM32 装了 TinyUF2 / uf2boot 也能。7.3 |
| ④ SWD(ST-LINK) | ST-LINK + CubeProgrammer / ST-LINK Utility | 例外通道:量产批量、无 bootloader 裸固件、救砖。7.5 |
下载 QMK Toolbox → 打开 → 选 .hex / .bin → 键盘进 Bootloader → 点击 Flash → 完成。
支持: Atmel DFU、Caterina、STM32 DFU、APM32 DFU、stm32duino、WB32 DFU、GD32V DFU、AVR ISP 等。
点 Flash 之前,先看它认成了什么设备:
STM32Duino(1EAF:0003)— F103 的正确姿势,写到0x08002000,放心刷STM32 DFU(0483:DF11)— ST 出厂系统 bootloader,写的地址是0x08000000。F103 上出现这个,说明BOOT0还挂在 1,别点,先断电拨回 0APM32 DFU(314B:0106)— APM32 芯片的出厂 DFU,正常
板子是 STM32F405,rules.mk 里却写着 BOOTLOADER = apm32-dfu——设备能正常枚举,Toolbox 也正确地认成 STM32 DFU,可一点 Flash 就只剩一行报错,一个字节都没写进去:
Error: File ID 314b:0106 does not match device (0483:df11 or 0483:df11)
这不是刷坏了,而是 dfu-util 在动手之前,把固件的「身份证」和设备的「身份证」对了一遍,发现不是同一个人,于是直接退出。bootloader 没被动过,板子也没砖——它是在保护你别把张家的户口本塞进李家。
rules.mk的BOOTLOADER先翻译成DFU_SUFFIX_ARGS(platforms/chibios/bootloader.mk):apm32-dfu→-v 314B -p 0106;stm32-dfu→-v 0483 -p DF11。- 编译到
%.bin那一步,builddefs/common_rules.mk会跑dfu-suffix -v 314B -p 0106 -a xx.bin,在.bin末尾追加 16 字节后缀(VID / PID / 版本 / CRC)。所以这个.bin里确实「写着」它是 APM32。 - 插上板子:Toolbox 按枚举到的 VID/PID 归类设备,再调用这个设备类硬编码的命令(
Apm32DfuDevice.cs/Stm32DfuDevice.cs),形如dfu-util -a 0 -d <VID:PID> -s 0x08000000:leave -D file.bin——这一步的 ID 取自设备,不读固件。 - dfu-util 写之前把文件后缀里的 VID/PID 和实际设备比对。dfu-suffix 手册原话:后缀就是给 host 工具「check that the device is matching」用的,而且不会被写进芯片。对不上就
errx()退出。
| rules.mk 的 BOOTLOADER | 后缀 VID:PID | Toolbox 显示 | 写入参数 |
|---|---|---|---|
stm32-dfu | 0483:DF11 | STM32 DFU | -a 0 -s 0x08000000:leave |
apm32-dfu | 314B:0106 | APM32 DFU | -a 0 -s 0x08000000:leave |
stm32duino | 1EAF:0003 | STM32Duino | -a 2 -R(落在 0x08002000) |
gd32v-dfu | 28E9:0189 | GD32V DFU | -a 0 -s 0x08000000:leave |
at32-dfu | 2E3C:DF11 | 不显示(Toolbox 没有这个设备类) | 只能 qmk flash 或手敲 dfu-util |
wb32-dfu | 无后缀 | WB32 DFU | QMK 未定义 DFU_SUFFIX_ARGS,不会撞这个问题 |
- 改配置重编译(正解)。 板上是什么芯片就写什么:真 STM32 用
stm32-dfu,APM32 用apm32-dfu。重新qmk compile,后缀自然跟着换。 - 应急:把后缀删掉再刷。
dfu-suffix --delete firmware.bin之后 VID/PID 退回通配0xFFFF,dfu-util 不再比对,可以写进去。代价是每次重编译都要再删一次,只能当一次性手段;想先看里面写了什么,用dfu-suffix --check firmware.bin。 - 走 SWD 直写。 ST-LINK / CubeProgrammer 只搬二进制,根本不看 DFU 后缀,见 7.5。救急可以,同样别当日常流程。
stm32-dfu 和 apm32-dfu 在 QMK 里同属 BOOTLOADER_TYPE = stm32_dfu,链接地址、跳转逻辑完全一样,差的只是那串 VID/PID。但下次再刷同一个 .bin,同样的错还会再报一次:「能跑」不等于「配对」,该改的配置还得改。这条路不是主力:平时改个键,Toolbox 点两下更快。但下面四种情况它明显更顺,值得记着。
| 编译 + 刷写一次完成 | 改完 keymap.c 直接 qmk flash,不用先找 .bin 再拖进 Toolbox |
| Toolbox 认不出设备 | 如 at32-dfu(2E3C:DF11)这类没有内置设备类的,只能走命令行;必要时在 rules.mk 里自填 DFU_ARGS(见 第一章) |
| 批量 / 脚本 | 一次编译多个 keymap、CI 里自动产出固件、给一堆板子按序列刷 |
| 无 GUI 环境 | 远程 SSH 到一台连着键盘的机器上刷 |
# 编译并自动刷写(最常用)
qmk flash -kb my_keyboard -km my_keymap
# 只编译不刷写,产物在 .build/ 下
qmk compile -kb my_keyboard -km my_keymap
# 强制指定刷写器(正常不用写,QMK 会按 BOOTLOADER 自动选)
qmk flash -kb my_keyboard -km my_keymap -bl dfu-util
# 常用 -bl:avrdude(Atmel DFU/Caterina) / dfu-util(STM32) / uf2conv(RP2040/TinyUF2)
QK_BOOT。几秒内没找到设备就会报 No device found,重新让板子进 DFU 再跑一次即可。这是一整类刷写方式,不是 RP2040 专属。共同点:板子以「U 盘」身份出现,固件是 .uf2,拖进去就刷完——没有驱动、没有工具、没有管理员权限问题,Windows / macOS / Linux 一个样。
操作:按住 BOOTSEL 插 USB → 资源管理器里出现 RPI-RP2 盘 → 把 .uf2 拖进去 → 板子自动重启进固件。QMK 侧 BOOTLOADER = rp2040,产物就是 .uf2。
这是 RP2040 的默认方式:ROM 里自带这层引导,永远刷不坏,也永远不需要额外工具。
TinyUF2 是 Adafruit 维护的一套 UF2 引导,覆盖部分 STM32F4 板。它的存在形式是先往芯片里装一份 TinyUF2(用 SWD 或 ROM DFU 装一次),之后这块板就变成「插上就是 U 盘」。
QMK 侧写 BOOTLOADER = tinyuf2,qmk compile 产出 .uf2,之后拖拽即可。前提是那块板真的装了 TinyUF2——没有它,.uf2 拖进去没有 U 盘可拖。
F103 也有一条 UF2 路线:BOOTLOADER = uf2boot(QMK 内部仍映射到 STM32_F103_STM32DUINO 板级,产物换成 .uf2)。同样需要先装对应的 UF2 引导,装法与 6.2 的 stm32duino 一样是「SWD/串口写一次」。
取舍:UF2 的优点是刷写体验最好;代价是引导占的 Flash 通常比 stm32duino 的 8KB 更大,且生态没有 stm32duino 那么普及。F103 上默认还是推荐 stm32duino + Toolbox。
RPI-RP2 或其它名字)。有 → 直接用 UF2;没有 → 你这块板没装 UF2 引导,走 7.1 或 7.2。1. Bootmagic Reset: 默认矩阵 (0,0) 按键(通常左上角)。断开 USB,按住此键再插入即可。
2. 物理按钮: RP2040 的 BOOTSEL、部分 F103 开发板的 BOOT0 跳线/按钮。
3. Keycode QK_BOOT: 键位布局中绑定,按下即进入 Bootloader(无需插拔)。建议放不常用的层避免误触。
结论先说:USB 能刷,就永远别碰 SWD。 这一节不是教你怎么天天用 ST-LINK,而是把「什么时候非它不可」讲清楚——顺便把两个容易搞混的 ST 官方软件分清。
先把两个软件分清
| 软件 | 定位 | 要点 |
|---|---|---|
| STM32CubeProgrammer | 当前官方主力 | ST 现在唯一在维护的烧录工具,GUI + 命令行(STM32_Programmer_CLI)双形态。SWD / DFU / 串口都能走,支持整片擦除、校验、读保护、批量脚本。下载要注册 ST 账号,也可装 CubeMX 时勾选。新手首选它。 |
| STM32 ST-LINK Utility | 已停更的老工具 | ST 早已停止更新(最终版 v4.6.0,官网归档),只支持 SWD,界面老但极轻、离线可用、不需要登录。老机器或不想注册账号时装它。 |
| STM32CubeMX | 不是烧录工具 | 只做引脚配置与生成代码,不能把 .bin 写进芯片。别把它和上面两个混为一谈。 |
两者操作步骤写在 6.2(以「烧 bootloader」为例,同样的界面也能烧任何固件)。
什么时候才值得动它
| 场景 | 为什么非它不可 | 怎么做 |
|---|---|---|
| 量产批量烧 | Toolbox 一次一块,还得人工点 Flash;SWD 能命令行批处理、整片擦除、写完校验,也能顺手开读保护 | CubeProgrammer CLI,见下方命令 |
| 不带 bootloader 的裸固件 | 芯片上还没有任何 USB 通道,USB 刷无从谈起——F103 / E103 首次上电就是这状态 | ST-LINK 写 0x08000000(地址规则见本节末) |
| 救砖 | bootloader 被冲掉后 USB 上一个设备都不出现,DFU 都进不去 | 回 6.2 重烧 bootloader |
# 单板:整片擦除 → 写 bin → 校验 → 复位
STM32_Programmer_CLI.exe -c port=SWD -e all -w firmware.bin 0x08000000 -v -rst
# 只写不擦(想保留自己划的数据区时)
STM32_Programmer_CLI.exe -c port=SWD -w firmware.bin 0x08000000 -v -rst
# 批量:外面套一层循环,逐块换板即可;-rst 让每块写完自动重启进应用
地址规则:固件链接在哪,就得写到哪
这是最容易翻车的一处:QMK 固件的起始地址由 BOOTLOADER 决定,和你怎么刷没有半点关系。
BOOTLOADER = stm32duino→ 自动套STM32F103xB_stm32duino.ld,固件从0x08002000开始(前面 8KB 留给 bootloader)BOOTLOADER = stm32-dfu(F103)或apm32-dfu(E103)→ 没有专用链接脚本,走芯片默认的,固件从0x08000000开始
所以「我要一份不带 bootloader、ST-LINK 写进去就能跑的固件」,配置得切到 ROM DFU 这一档:
# rules.mk —— F103 用 ST 系统 DFU;E103 / APM32 换成 apm32-dfu
MCU = STM32F103
BOOTLOADER = stm32-dfu
# 只编译不刷写,拿 bin 去 SWD 写
qmk compile -kb my_keyboard -km my_keymap
rules.mk 还写着 stm32duino,固件就是链接到 0x08002000 的,你拿 ST-LINK 往 0x08000000 一写——正是 6.3 那个事故:前 8KB 被固件头部占了,向量表错位,USB 彻底不出现。顺带一个好处: 选了 stm32-dfu 不等于以后都得插 ST-LINK。QK_BOOT 会往 RAM 顶部写一个 magic 再软复位,复位后启动代码直接跳到系统存储区的 ROM bootloader(STM32_BOOTLOADER_ADDRESS),不用拨 BOOT0,之后照样能用 QMK Toolbox 走 USB 刷。ROM 里的那层擦不掉,也正因如此,工程上说的「不用 bootloader」其实是「不用你自己装的那个」。
🔌 八、支持的功能
以下功能均列明硬件接线和 rules.mk / config.h / keymap.c 配置。完整文档见 QMK 官方文档。点击标题展开/折叠详细内容。
最常见的可寻址 RGB LED,单线串联,QMK 内置完整灯效引擎(数十种预设动画)。
硬件接线: DIN → GPIO(串 100-470Ω),VDD → 5V(每颗全白 ~60mA,注意总电流),GND → 共地。建议 DIN 前加电阻、每颗旁并联 0.1μF 电容。
RGB_MATRIX_ENABLE = yes
RGB_MATRIX_DRIVER = ws2812 // 或 apa102 / is31fl3731 等
// WS2812 数据引脚
#define WS2812_DI_PIN GP0 // RP2040 示例,或 B15(STM32)、D3(AVR)
#define RGB_MATRIX_LED_COUNT 68 // LED 总数
#define RGB_MATRIX_KEYPRESSES // 按键触发灯效
#define RGB_MATRIX_FRAMEBUFFER_EFFECTS // 帧缓冲特效(雨滴等)
#define RGB_MATRIX_MAXIMUM_BRIGHTNESS 150
#define RGB_MATRIX_DEFAULT_MODE RGB_MATRIX_CYCLE_ALL
WS2812_PIO_USE_PIO1 可启用 PIO 驱动,CPU 零开销。QMK 支持为 Caps Lock / Num Lock / Scroll Lock / 层状态等设置独立 LED 指示灯。
硬件: LED 正极 → GPIO(串 220-1KΩ 限流电阻),负极 → GND。高电平点亮。
// 标准指示灯
#define LED_CAPS_LOCK_PIN B2
#define LED_NUM_LOCK_PIN B3
#define LED_SCROLL_LOCK_PIN B4
#define LED_PIN_ON_STATE 1 // 1=高电平点亮,0=低电平点亮
bool led_update_user(led_t led_state) {
writePin(B5, IS_LAYER_ON(1)); // Layer 1 亮灯
return true;
}
支持 SSD1306 驱动的 128x32 / 128x64 OLED,I2C 地址通常 0x3C。可显示当前层、按键状态、自定义动画等。
硬件: SCL → I2C 时钟,SDA → I2C 数据,VCC → 3.3V/5V,GND → 共地。建议 SCL/SDA 加 4.7KΩ 上拉。
OLED_ENABLE = yes
OLED_DRIVER = ssd1306
#define OLED_DISPLAY_128X64 // 或 OLED_DISPLAY_128X32
#define I2C_DRIVER I2CD1 // STM32 示例
#define I2C1_SCL_PIN B6
#define I2C1_SDA_PIN B7
bool oled_task_user(void) {
oled_write_ln(get_u8_str(get_highest_layer(layer_state), ' '), false);
return false;
}
外接 I2C 存储用于保存宏、动态键位、用户配置等非易失数据,容量远超芯片内部存储(AVR 仅 1KB,多数 STM32 无内部 EEPROM)。推荐两款引脚兼容、可直接互换的 32KB 芯片:24LC256(EEPROM)与 MB85RC256V(FRAM 铁电存储)。QMK 内置 i2c 驱动,读写 API 与内部 EEPROM 完全一致,后期切换存储介质无需改业务代码。
与 OLED 共用总线: 两芯片均占用 I2C 地址 0x50-0x57(由 A0/A1/A2 引脚决定),与 OLED(0x3C)不冲突,可同挂一条 I2C 总线。
| EEPROM 引脚 | 开发板 | 说明 |
|---|---|---|
| SDA | I2C SDA | STM32: PB7 / RP2040: GP2 |
| SCL | I2C SCL | STM32: PB6 / RP2040: GP3 |
| VCC | 3.3V / 5V | 24LC 系列供电范围 1.8-5.5V |
| GND | GND | 共地 |
| WP | GPIO(推荐)或 GND | 高电平禁止写入;建议接 GPIO 并定义 EXTERNAL_EEPROM_WP_PIN,驱动初始化会自动拉低使能写入,悬空可能因上拉进入写保护,导致初始化或写入失败 |
| A0/A1/A2 | GND 或 VCC | 地址引脚,接地=0、接 VCC=1 |
建议 SCL/SDA 加 4.7KΩ 上拉;与 OLED 同挂一条总线时共用上拉即可。
A0/A1/A2 三引脚组合出 8 个地址:全接地为 0x50,随引脚接 VCC 递增,全接 VCC 为 0x57。QMK 的 EXTERNAL_EEPROM_I2C_BASE_ADDRESS 按 8 位地址填写(7 位地址左移一位),默认值 0b10100000(= 0xA0)即对应 7 位地址 0x50。
两芯片引脚兼容、容量同为 32KB、I2C 地址一致,PCB 可直接互换。MB85RC256V 是铁电存储器,写入无需等待、寿命长,适合频繁读写配置 / 宏 / via 动态改键的场景,做动态存储时优先推荐。
| 对比项 | 24LC256(EEPROM) | MB85RC256V(FRAM) |
|---|---|---|
| 写入等待 | 约 5ms 写周期,期间不可访问 | 无需等待,写入立即生效 |
| 擦写寿命 | 约 100 万次 | 约 100 亿次(10¹⁰) |
| 页写限制 | 64B/页,跨页回绕 | 无页概念,可任意逐字节写 |
| 接口 / 地址 | I2C,0x50-0x57 | I2C,0x50-0x57(引脚兼容) |
| 价格 | 更便宜 | 稍贵 |
QMK 内置对 EEPROM_I2C_MB85RC256V 和 EEPROM_I2C_24LC256 等芯片的预定义配置(完整列表见下方表格)。指定芯片宏后,所有参数(容量、页大小、写入时间、地址宽度等)均由驱动自动填充默认值,无需手动重复配置。唯一需要手动指定的通常是写保护引脚。
// rules.mk 必填:
EEPROM_DRIVER = i2c
// config.h 二选一(按实际焊接):
//#define EEPROM_I2C_24LC256 // 24LC256 EEPROM(官方默认值)
#define EEPROM_I2C_MB85RC256V // MB85RC256V FRAM(官方默认值)
// 写保护引脚(强烈建议定义,否则悬空时初始化/写入可能失败):
#define EXTERNAL_EEPROM_WP_PIN C0 // 接 GPIO 写入,驱动会自动拉低使能
其他容量的 24LC 系列(24LC32/64/128)也可用官方预定义宏:EEPROM_I2C_24LC32A、EEPROM_I2C_24LC64、EEPROM_I2C_24LC128,以及 Cat/RM 系列。
| 模块 / 芯片 | 预定义宏 | 容量 | 页大小 | 写入时间 | 地址宽度 |
|---|---|---|---|---|---|
| 24LC256 EEPROM | EEPROM_I2C_24LC256 | 32KB | 64B | 5ms | 16 位 |
| MB85RC256V FRAM | EEPROM_I2C_MB85RC256V | 32KB | —(无页) | —(无等待) | 16 位 |
| 24LC128 EEPROM | EEPROM_I2C_24LC128 | 16KB | 64B | 5ms | 16 位 |
| 24LC64 EEPROM | EEPROM_I2C_24LC64 | 8KB | 32B | 5ms | 8 位 |
| 24LC32A EEPROM | EEPROM_I2C_24LC32A | 4KB | 16B | 5ms | 8 位 |
| CAT24C512 | EEPROM_I2C_CAT24C512 | 64KB | 64B | 5ms | 16 位 |
| RM24C512C | EEPROM_I2C_RM24C512C | 64KB | 64B | 5ms | 16 位 |
完整文档:QMK Config Options · 源文件:drivers/eeprom/eeprom_i2c.h
EEPROM_DRIVER = i2c
// 地址规划:0 号字节存初始化标志,1 号字节存音量(示例)
#define EE_MAGIC 0
#define EE_VOL 1
#define EE_MAGIC_VAL 0x5A // 空白芯片读出为 0xFF,写入标志区分"未初始化"
void keyboard_post_init_user(void) {
if (eeprom_read_byte((void*)EE_MAGIC) != EE_MAGIC_VAL) {
// 首次上电:写入默认值
eeprom_write_byte((void*)EE_MAGIC, EE_MAGIC_VAL);
eeprom_write_byte((void*)EE_VOL, 50);
}
uint8_t vol = eeprom_read_byte((void*)EE_VOL);
// 用 vol 初始化音量/亮度/层位等状态…
}
// 调整后写回;update 仅在值变化时写入,可减少磨损
void save_volume(uint8_t vol) {
eeprom_update_byte((void*)EE_VOL, vol);
}
- WP 引脚必定义: WP 接 GPIO 并定义
EXTERNAL_EEPROM_WP_PIN,驱动初始化会自动拉低使能写入;引脚悬空可能因上拉进入写保护,导致初始化或写入失败(实测经验)。 - 指定芯片宏后无需手动配参数: 使用
EEPROM_I2C_MB85RC256V或EEPROM_I2C_24LC256后,所有默认值(容量、页大小、写入时间、地址宽度)由 QMK 自动填充,只需额外定义 WP Pin。 - 未指定芯片宏时: 需手动设置
EXTERNAL_EEPROM_ADDRESS_SIZE(24LC64/32 用 8 位,24LC256/128 用 16 位)、EXTERNAL_EEPROM_PAGE_SIZE、EXTERNAL_EEPROM_WRITE_TIME等。 - 页写限制(仅 EEPROM): 24LC256 每页 64 字节,连续写跨页边界会地址回绕,需手动分页(QMK 的
eeprom_write_block已内置处理);FRAM 无此限制。 - 写周期(仅 EEPROM): 24LC256 每次写入约 5ms,期间不应答,写后勿立即读同一地址;FRAM 写入立即生效。
- 磨损寿命: EEPROM 约 100 万次擦写,频繁变化的数据用
eeprom_update_byte只在值改变时写入;FRAM 寿命 10¹⁰,基本无需担心。 - 首次上电: 空白芯片任何地址读出都是
0xFF,务必用校验字节区分"未初始化",直接读出的 0xFF 会被当成有效数据。
通过 ADC 读取滑动电位器(或旋转电位器)的模拟电压,可实现连续参数控制,如音量推子、亮度调节、导播台推子等。
硬件: 电位器两端接 VCC 和 GND,中间抽头接 ADC GPIO。推荐 10KΩ 线性电位器(B10K)。
Part 1:基础 ADC 功能调用
QMK 的 ADC 读取通过 analog 驱动实现。STM32 平台需要同时修改三个文件才能正确启用 ADC,少一步都读不到数据。
ANALOG_DRIVER_REQUIRED = yes // 启用 ADC 驱动
#pragma once
#include_next <halconf.h>
// 启用 ADC HAL 驱动
#undef HAL_USE_ADC
#define HAL_USE_ADC TRUE
#pragma once
#include_next <mcuconf.h>
// 启用 ADC1 外设
#undef STM32_ADC_USE_ADC1
#define STM32_ADC_USE_ADC1 TRUE
// STM32F103 可设置 12-bit 分辨率(默认 10-bit),不用白不用
#define ADC_RESOLUTION ADC_CFGR1_RES_12BIT
// ADC 引脚定义(analogReadPin 直接用引脚名读取,无需额外配置)
// 在代码中直接用 A0, A1, B0 等引脚名调用 analogReadPin()
// analogReadPin() 返回当前引脚的 ADC 原始值
// STM32F103(12-bit):0-4095
// RP2040(12-bit):0-4095
// Atmega32u4(10-bit):0-1023
void matrix_scan_user(void) {
uint16_t val = analogReadPin(A0); // 直接用引脚名读取
// val 即为当前电位器位置对应的模拟值
}
ADC 原始值会有抖动,直接使用会导致输出频繁跳变。实际应用中需要做三件事:
- 滤波:滑动平均滤波,消除瞬时抖动
- 映射:把 12-bit (0-4095) 映射到 7-bit MIDI (0-127),用右移 5 位比乘除法更高效
- 防抖:设置变化阈值,只有超过阈值才发送;同时处理端点到达
// 12-bit → 7-bit 映射:右移 5 位(4096 >> 5 = 128),比乘除法快得多
uint8_t midi_val = (uint8_t)(adc_val >> 5);
// 如果电位器安装方向与 MIDI 值方向相反,用反转:
uint8_t midi_val = 0x7F - (uint8_t)(adc_val >> 5);
滑动平均滤波是跨时间取平均,推子快速推动时输出滞后、手感发"肉"。更优方案:同一扫描周期内对同一通道连续采样 16 次取平均(噪声降为 1/4,不引入跨周期拖影),映射到 7-bit 后加死区(值不变不发送)消除毛刺。实测可做到 0~127 全覆盖、静止零毛刺。
// 单周期 16 次过采样:噪声降为 1/4,无跨周期拖影
uint16_t oversample16(uint8_t pin) {
uint32_t sum = 0;
for (uint8_t i = 0; i < 16; i++) sum += analogReadPin(pin);
return (uint16_t)(sum >> 4);
}
// 12-bit → 7-bit:右移 5 位(4096 >> 5 = 128),端点钳位
uint8_t to7bit(uint16_t v) {
uint8_t cc = (uint8_t)(v >> 5);
return (cc > 127) ? 127 : cc;
}
// 死区:7-bit 值真正变化才发送,静止时零毛刺
static uint8_t last_cc[NUM_SLIDERS];
void slider(void) {
for (int i = 0; i < NUM_SLIDERS; i++) {
uint16_t raw = oversample16(adcPins[i]);
uint8_t cc = to7bit(raw);
if (cc != last_cc[i]) {
midi_send_cc(&midi_device, 2, sliders[i].midiControlNumber, cc);
last_cc[i] = cc;
}
}
}
与 Part 2 的滑动平均方案对比:滑动平均适合噪声大、推动慢的场景;16x 过采样 + 死区适合推子(要求手感跟手 + 端点全覆盖 + 静止稳定)。两者可互换:过采样替代滤波窗口,死区替代变化阈值,端点检测逻辑仍建议保留。
Part 2:实战 — ADC 推子控制 MIDI 信号
把滑动电位器做成导播台 MIDI 推子:推动电位器时,实时发送对应通道的 MIDI CC 消息。相比机械编码器,电位器推子能直接定位到任意位置,更接近真实调音台的手感。
核心思路: 在 matrix_scan_user() 中周期性读取 ADC → 滑动平均滤波 → 阈值防抖 + 端点检测 → 右移映射到 0-127 → 发送 MIDI CC。需要配合 4.7 章节的 MIDI_ADVANCED 和 extern MidiDevice midi_device;。
以下代码来自实际 10 通道导播台固件,用结构体管理每个推子的全部状态(滤波缓冲区、当前值、上次发送值、CC 编号),比平行数组更清晰、更易扩展。
#include QMK_KEYBOARD_H
extern MidiDevice midi_device;
#define FILTER_WINDOW_SIZE 8 // 滑动平均滤波窗口大小
#define NUM_SLIDERS 10 // 推子数量
#define ADCVar 30 // 变化阈值(12-bit 下推荐 20-40)
// 用结构体管理每个推子的所有数据
typedef struct {
uint16_t adcReadings[FILTER_WINDOW_SIZE]; // 独立的滤波缓冲区
uint8_t readingIndex;
uint32_t readingSum;
uint16_t filteredValue;
uint16_t lastSentValue;
uint8_t midiControlNumber; // 为该推子指定 MIDI CC 编号
} SliderChannel;
// 初始化推子数组,每个推子指定不同的 CC 编号
SliderChannel sliders[NUM_SLIDERS] = {
{ {0}, 0, 0, 0, 0, 0x0A }, // CC#10
{ {0}, 0, 0, 0, 0, 0x09 }, // CC#9
{ {0}, 0, 0, 0, 0, 0x08 }, // CC#8
{ {0}, 0, 0, 0, 0, 0x07 }, // CC#7(主音量)
{ {0}, 0, 0, 0, 0, 0x06 }, // CC#6
{ {0}, 0, 0, 0, 0, 0x05 }, // CC#5
{ {0}, 0, 0, 0, 0, 0x04 }, // CC#4
{ {0}, 0, 0, 0, 0, 0x03 }, // CC#3
{ {0}, 0, 0, 0, 0, 0x02 }, // CC#2
{ {0}, 0, 0, 0, 0, 0x01 } // CC#1
};
// 对应的 ADC 引脚数组
const uint32_t adcPins[NUM_SLIDERS] = { A0, A1, A2, A3, A4, A5, A6, A7, B0, B1 };
// 滑动平均滤波:维护一个环形缓冲区,O(1) 更新
uint16_t movingAverageFilter(SliderChannel* channel, uint16_t newReading) {
channel->readingSum = channel->readingSum
- channel->adcReadings[channel->readingIndex]
+ newReading;
channel->adcReadings[channel->readingIndex] = newReading;
channel->readingIndex = (channel->readingIndex + 1) % FILTER_WINDOW_SIZE;
return (uint16_t)(channel->readingSum / FILTER_WINDOW_SIZE);
}
// 防抖判断:变化超过阈值 OR 到达端点(解决推到底/顶时抖动发不出信号)
int shouldSendMIDI(SliderChannel* channel) {
uint16_t change = abs(channel->filteredValue - channel->lastSentValue);
// 主要条件:变化超过阈值
if (change >= ADCVar) return 1;
// 辅助条件:确保能到达端点,但避免端点附近微小抖动反复发送
// 只有上次值离端点较远、当前值非常接近端点时才触发
if (channel->filteredValue < 5 && channel->lastSentValue > 10) return 1; // 接近 0
if (channel->filteredValue > 4090 && channel->lastSentValue < 4080) return 1; // 接近 4095
return 0;
}
void slider(void) {
for (int i = 0; i < NUM_SLIDERS; i++) {
// 1. 读取并滤波(每个通道独立滤波器)
uint16_t rawValue = analogReadPin(adcPins[i]);
sliders[i].filteredValue = movingAverageFilter(&sliders[i], rawValue);
// 2. 判断是否需要发送
if (shouldSendMIDI(&sliders[i])) {
// 3. 右移 5 位映射到 0-127,0x7F- 反转方向
uint8_t midiValue = 0x7F - (sliders[i].filteredValue >> 5);
// 4. 发送到通道 3(索引 2)
midi_send_cc(&midi_device, 2, sliders[i].midiControlNumber, midiValue);
sliders[i].lastSentValue = sliders[i].filteredValue;
}
}
}
void matrix_scan_user(void) {
slider();
}
- 三步启用 ADC:
rules.mk+halconf.h+mcuconf.h缺一不可,少一步analogReadPin()返回 0。 - 12-bit 分辨率: STM32F103 设
ADC_CFGR1_RES_12BIT后精度翻 4 倍,推子手感明显更顺滑。 - 滑动平均滤波: 窗口 8 足够稳定且延迟可接受;用环形缓冲区 + 增量求和实现 O(1),不要每次重新求和。
- 端点检测是关键: 推子推到底/顶时 ADC 值在端点附近抖动,可能始终达不到阈值导致发不出 0 或 127。
shouldSendMIDI里的端点辅助条件专门解决这个问题。 - 右移代替乘除:
>> 5比* 127 / 4095快得多,嵌入式上值得养成习惯。 - 方向反转: 电位器安装方向与 MIDI 值方向相反时,用
0x7F - (val >> 5)反转。 - RGB 干扰: 大量 RGB 灯效会引入电源噪声影响 ADC 精度,对推子精度要求高时可临时关闭 RGB 测试。
机械旋转编码器(EC11 系列)通过两路脉冲检测旋转方向和步数,常用于音量调节、菜单滚动。QMK 支持最多 4 个编码器。
硬件: A → GPIO_A,B → GPIO_B,C → GND。A/B 需启用内部上拉。按下中键作为一个独立按键。
ENCODER_ENABLE = yes
#define ENCODERS_PAD_A { B12 } // 第一个编码器的 A/B 引脚
#define ENCODERS_PAD_B { B13 }
#define ENCODER_RESOLUTION 4 // 每格 4 步(EC11 默认)
bool encoder_update_user(uint8_t index, bool clockwise) {
if (clockwise) {
tap_code(KC_VOLU); // 顺时针:音量+
} else {
tap_code(KC_VOLD); // 逆时针:音量-
}
return false;
}
QMK 内置 MIDI 设备驱动,键盘插上 USB 后会被操作系统识别为标准 USB MIDI 类设备,可直接控制各类 DAW 或导播台(如 vMix、OBS Studio 配合 MIDI 插件、ATEM 切换台、Stream Deck 替代方案等),无需额外硬件或转换器。
推荐方式:默认只使用自定义 MIDI 信号。即在 process_record_user() 中通过 midi_send_* API 手动发送目标 MIDI 消息,而不是使用 MI_* 基础键码。这样能把任意 MIDI 事件(Note On/Off、CC 控制变化、Pitch Bend 等)自由绑定到任意矩阵按键或机械编码器旋钮,实现导播场景下的:
- 按键:触发切换、播放/暂停、推杆上推/下推等离散动作
- 旋钮/推子:连续调节音量、推子位置、过渡进度、亮度等参数
MIDI_ENABLE = yes
// 定义高级模式,启用 midi_send_* API(自定义信号必需)
#define MIDI_ADVANCED
// 可选:调整发送力度等默认参数
#define MIDI_VELOCITY_MIN 0 // 最小力度(默认 0)
#define MIDI_VELOCITY_MAX 127 // 最大力度(默认 127)
#define MIDI_VELOCITY_DEFAULT 100 // 默认力度(默认 127)
按下发送 Note On 触发镜头切换,松开发送 Note Off 结束。导播场景中常用 Note 编号对应不同镜头 / 通道。
// 声明 QMK 全局 MIDI 设备实例(自定义信号必需)
extern MidiDevice midi_device;
enum custom_keycodes {
CAM1 = SAFE_RANGE, // 镜头 1
CAM2, // 镜头 2
};
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case CAM1:
if (record->event.pressed) {
// 按下:通道 1 发送 Note 60 (C3),力度 127
midi_send_noteon(&midi_device, 0, 60, 127);
} else {
// 松开:发送 Note Off 结束
midi_send_noteoff(&midi_device, 0, 60, 0);
}
return false;
case CAM2:
if (record->event.pressed) {
midi_send_noteon(&midi_device, 0, 61, 127);
} else {
midi_send_noteoff(&midi_device, 0, 61, 0);
}
return false;
}
return true;
}
用循环批量定义 8 个按键,每个对应一个 Note 编号。适合导播台的多通道切换、PGM/PVW 切换、快捷键组等场景。
// 8 路通道切换:Note 60-67 对应通道 1-8
enum custom_keycodes {
CHAN1 = SAFE_RANGE,
CHAN2, CHAN3, CHAN4,
CHAN5, CHAN6, CHAN7, CHAN8,
};
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
// 计算偏移:CHAN1=0, CHAN2=1, ..., CHAN8=7
if (keycode >= CHAN1 && keycode <= CHAN8) {
uint8_t offset = keycode - CHAN1; // 0-7
uint8_t note = 60 + offset; // 60-67
if (record->event.pressed) {
midi_send_noteon(&midi_device, 0, note, 127);
} else {
midi_send_noteoff(&midi_device, 0, note, 0);
}
return false;
}
return true;
}
机械编码器旋转时连续发送 CC 消息,模拟导播台推子的连续调节。CC 7 是标准 MIDI 主音量。
// 单个旋钮控制通道 1 主音量(CC 7)
static uint8_t chan1_volume = 64; // 当前音量值(0-127)
bool encoder_update_user(uint8_t index, bool clockwise) {
if (index == 0) { // 第一个编码器
if (clockwise) {
chan1_volume = (chan1_volume < 127) ? chan1_volume + 1 : 127;
} else {
chan1_volume = (chan1_volume > 0) ? chan1_volume - 1 : 0;
}
// 发送 CC 7(主音量)到通道 1
midi_send_cc(&midi_device, 0, 7, chan1_volume);
}
return false;
}
用数组管理多个推子的当前值,编码器索引对应不同通道。适合导播台的多路混音推子组。
// 4 路推子:编码器 0-3 分别控制通道 1-4 的音量(CC 7)
static uint8_t fader_values[4] = {64, 64, 64, 64};
bool encoder_update_user(uint8_t index, bool clockwise) {
if (index < 4) { // 只处理前 4 个编码器
if (clockwise) {
fader_values[index] = (fader_values[index] < 127) ? fader_values[index] + 1 : 127;
} else {
fader_values[index] = (fader_values[index] > 0) ? fader_values[index] - 1 : 0;
}
// 发送 CC 7(主音量)到对应通道(index 0-3 对应通道 1-4)
midi_send_cc(&midi_device, index, 7, fader_values[index]);
}
return false;
}
扩展思路: 实际导播台常需要不同 CC 编号控制不同参数(如 CC 7=音量、CC 10=声像、CC 74=滤波器截止)。可把 fader_values 改为二维数组 fader_values[4][N],每个编码器同时控制多个参数。
- 通道索引: QMK 的 MIDI API 中通道索引是从
0开始的(0 对应 MIDI 通道 1,15 对应通道 16)。 - 自定义键码: 自定义键码必须从
SAFE_RANGE开始分配,否则会与 QMK 内置键码冲突。 - 发送力度: 使用
midi_send_noteon()时第三个参数为力度(0-127),第四个参数为通道索引。 - 步进调整: 若觉得旋钮步进太慢(每格只 +1),可将步进值调整为较大值(如
+2或+4),让旋钮转一圈能扫完整个 0-127 范围。 - 双向通信: QMK 支持接收 DAW 发送的 MIDI 信号,你可以在
midi_input_callback中编写逻辑,例如用灯效显示 DAW 的节拍时钟(MIDI Clock)或弯音值。
本节只讲 QMK 官方支持的有线分体方案,将覆盖:
① 基本架构:左右手各一块 MCU,通过分体通信线交换矩阵状态,主手负责 USB 输出;
② 连接方式:QMK 串行分体通信、常用 TRRS 连接及其接线注意事项;
③ 固件配置:启用 split、设置分体通信引脚、左右手识别与主从分工;
④ 刷写与调试:两块 MCU 分别刷写的方法,以及单侧按键无反应、左右识别颠倒时的排查;
⑤ 外设协同:分体状态下 RGB、OLED、编码器等功能的同步与资源占用。
官方对照:QMK Split Keyboard
NKRO(N-Key Rollover)让键盘能同时上报任意多个按键。QMK 的做法是把「8 个修饰键 + 240 个键位图」压进一个 32 字节的 HID 报告(report_id 1 字节 + mods 1 字节 + 位图 30 字节);普通模式则是 8 字节的「修饰键 + 保留位 + 6 个键槽」。
但「QMK 支持 NKRO」和「你的键盘插上就是 NKRO」是两件事。这里有两个彼此独立的开关,很多人只开了一个,然后奇怪为什么按 NK_TOGG 没反应:
- 编译层:
features.nkro(等价于旧的NKRO_ENABLE = yes)。不开,固件里根本没有 NKRO 代码,键位里写NK_TOGG也是死的。 - 运行时默认值:
host.default.nkro(等价于#define NKRO_DEFAULT_ON true)。不设,开机就是 6KRO,必须手动按一次NK_TOGG才切过去;而且这个状态会写进 EEPROM,重启后保留。
两个都开了,才是「插上即 NKRO」。反过来说:什么都不写,就是永远的 6KRO——键位里放 NK_TOGG 也救不回来,因为那段代码根本没被编进固件。
| 对比项 | 6KRO(默认) | NKRO |
|---|---|---|
| HID 报告 | 8 字节mods + reserved + 6 键 | 32 字节report_id + mods + 30 字节位图 |
| 上限 | 6 个普通键 + 8 个修饰键 | 240 个键位(keycode 0~239)任意组合 |
| 多出来的键 | 塞满 6 个后触发 ErrorRollOver,主机收到的键位不可信 | 没有这个上限 |
| BIOS / UEFI | 原生支持(boot protocol 就是按 8 字节定义的) | QMK 会自动降级成 6KRO,前提是主机发了 SET_PROTOCOL(0)(见下) |
| 走哪个端点 | 键盘 IN 端点 | shared 端点(与鼠标、多媒体键共用) |
| 主要代价 | 同时按超过 6 个普通键会丢键 | 多占一个端点名额、报告更大、个别老主机不认 |
第一步:先确定你要的是哪一种「开」
「打开 NKRO」在 QMK 里有三种不同的含义,对应的改法完全不一样。先对号入座,再抄下面的文件:
| 你的需求 | 要改什么 | 结果 |
|---|---|---|
| 完全不需要全键无冲 | 什么都不改 | 永远 6KRO。键位里写 NK_TOGG 也毫无反应(固件里没有这段代码) |
| 偶尔需要,平时 6KRO (给客户、通用键盘) | 只开编译层features.nkro | 固件里有了 NKRO,但开机仍是 6KRO;按 NK_TOGG 随时切过去,切回来也一样。推荐给大多数键盘 |
| 开机就要全键无冲 (自用、导播台、打游戏) | 编译层 + 默认值features.nkro + host.default.nkro | 插上就是 NKRO,不用按任何键;仍可用 NK_TOGG 临时切回 6KRO 进 BIOS |
features.nkro 决定「固件里有没有 NKRO」,host.default.nkro 决定「开机时是不是 NKRO」。只开前者 = 有开关但默认关着;两个都开 = 默认就是开着的。第二步:keyboard.json 怎么写(新工程,推荐)
QMK 现在的标准做法是所有配置都写在 keyboard.json,rules.mk 只留板级那几行。NKRO 相关的就两个字段,加在 JSON 顶层:
{
"manufacturer": "YourName",
"keyboard_name": "mykeyboard",
"maintainer": "YourName",
// ① 主控与 Bootloader:只在这里定义,rules.mk 里不要再写
"processor": "STM32F411",
"bootloader": "apm32-dfu",
// ② 编译层:固件里要不要编入 NKRO 代码
"features": {
"nkro": true,
"extrakey": true, // 其余功能照原样保留,不要整块覆盖
"mousekey": false
},
// ③ 运行时默认值:开机是否直接进 NKRO(不要这一块 = 开机 6KRO)
"host": {
"default": { "nkro": true }
},
"usb": {
"device_version": "1.0.0",
"pid": "0x0001",
"vid": "0xFEED"
},
"matrix_pins": {
"cols": ["B1", "B0", "A7", "A6", "A5"],
"rows": ["B15", "A8", "B3", "A15"]
},
"layouts": {
"LAYOUT": { "layout": [ /* 键位坐标,按你自己的填 */ ] }
}
}
如果只是给现成的键盘加 NKRO,不用重写整个文件——在上面那个 JSON 里补两块就行,其余内容一个字都别动:
// 加在顶层(和 "processor" 同级)
"features": { "nkro": true },
// 只想「可切换」就到此为止;要「开机即 NKRO」再加这一段
"host": { "default": { "nkro": true } }
| 字段 | 说明 |
|---|---|
features.nkro | 布尔值。true = 编译时定义 NKRO_ENABLE,把 NKRO 代码编进固件。这是前提,没有它后面全白搭 |
host.default.nkro | 布尔值。true = 编译时定义 NKRO_DEFAULT_ON,EEPROM 首次初始化时把「NKRO 开」写成默认状态 |
host.default 下面只能有 nkro 一个键。QMK 的 schema 对 host.default 设了 "additionalProperties": false,多写一个 "mouse": true 之类的字段会直接报 Additional properties are not allowed,编译不通过。另外 host 是 JSON 顶层属性,不要塞进 features 里。第三步:rules.mk / config.h 怎么写(老工程)
如果你的键盘还是老的 rules.mk 体系(没有 keyboard.json,或者 keyboard.json 里只放矩阵),那就按下面写。两套体系选一套,别混着来——原因见下一个标题。
# 板级定义
MCU = STM32F411
BOARD = GENERIC_STM32_F411XE
MCU_LDSCRIPT = STM32F411xE
BOOTLOADER = apm32-dfu
# 编译层开关:等价于 keyboard.json 的 "features": {"nkro": true}
NKRO_ENABLE = yes
// 等价于 keyboard.json 的 "host": {"default": {"nkro": true}}
// 注意:这一行只能写在 config.h,不能写在 rules.mk(rules.mk 只认变量,不认 C 宏)
#define NKRO_DEFAULT_ON true
rules.mk 里照样是 NKRO_ENABLE = yes,只是记得那 1.5K 上拉必须外接(见「USB 硬件设计」4.2)——NKRO 开着但枚举不上,那是硬件问题,不是 NKRO 的问题。坑:同一个开关别写两处,rules.mk 会静默赢
这是最容易让人怀疑人生的地方:keyboard.json 里明明写着 "nkro": true,编出来的固件却还是没有 NKRO。原因是 QMK 在合并配置时有一套优先级,而且冲突时只打一条警告、不报错:
| 同时出现在哪两处 | 谁说了算 | QMK 的警告原文 |
|---|---|---|
features.nkro(JSON)+ NKRO_ENABLE(rules.mk) | rules.mk | Feature nkro is specified in both info.json (…) and rules.mk (…). The rules.mk value wins. |
| 任何 JSON 字段 + 对应 rules.mk 变量 | rules.mk | X in rules.mk is overwriting Y in info.json |
| 任何 JSON 字段 + 对应 config.h 宏 | config.h | X in config.h is overwriting Y in info.json |
所以最坏的情况是:keyboard.json 写 "nkro": true,而某个从旧工程抄来的 rules.mk 里还留着 NKRO_ENABLE = no —— 编译不报错,只在你滚动编译日志时才会看到一行 warning,最终NKRO 代码根本没编进去,你对着 JSON 查一晚上也查不出原因。
keyboard.json,rules.mk 里不要再出现 NKRO_ENABLE、NKRO_DEFAULT_ON;老工程全部写在 rules.mk / config.h,keyboard.json 里就不要出现 features 和 host。改完务必回看一遍编译日志里的 warning,这类冲突是以警告形式出现的,很容易被刷过去。坑:改完没生效?先清 EEPROM
「我明明写了 host.default.nkro = true,刷完固件开机还是 6KRO」——这几乎全是同一个原因:默认值不等于当前值。
host.default.nkro只在eeconfig_init()时被写入一次,也就是 EEPROM 为空 / 无效 / 被主动清空 的那一刻(新板第一次上电、清过 EEPROM)。- 此后
keymap_config.nkro的真实状态一直存在 EEPROM 里。你上次按过一次NK_TOGG切回 6KRO,这个「关」就永久覆盖了编译期的默认值。 - 刷固件不会清 EEPROM。DFU / UF2 只写 Flash 的固件区,EEPROM 模拟区原封不动——所以「重新刷一遍」并不能把 NKRO 恢复成默认开。
三种清法,任选一种:
| 做法 | 说明 |
|---|---|
键位里放 EE_CLR(宏名 QK_CLEAR_EEPROM) | 最方便。烧进去后按一下,键盘自动重启,所有配置回到固件默认值。建议常驻在 Layer 2 之类的隐蔽层,以后调试也用得上 |
| QMK Toolbox 的 Clear EEPROM 按钮 | 键盘先进 bootloader(通常是按 RESET 键),Toolbox 里点 Clear EEPROM。不依赖键位 |
| 换一块新板 / 首次烧录 | 全新 MCU 的 EEPROM 是空白的,此时默认值一定生效,可用来验证「配置本身对不对」 |
EE_HANDS 左右手标记等全部运行时配置。清完需要重新按需设置一次——所以别把它当成日常操作,只在「改了默认值却不生效」时用。验证:怎么确认 NKRO 真的编进去了
- 看编译输出:
qmk compile -kb <你的键盘> -km <键位>成功之后执行qmk info -kb <你的键盘>,在Features一段里能看到nkro,说明features.nkro已生效。 - 按键实测:烧进去后打开任意在线按键测试页(keyboard tester),同时按住 10 个以上字母键,全部亮起 = NKRO 生效;只亮 6 个、其余闪烁或无反应 = 还是 6KRO。
- 切换实测:按一次
NK_TOGG(或LShift + RShift + N),重复上一步。如果切换前后行为完全一致且都只能 6 键,说明编译层没开,回去查features.nkro有没有被 rules.mk 覆盖。
FORCE_NKRO 了。它从 2025-05 的 breaking changes 起被标记废弃(keyboard.json 里的 usb.force_nkro 一并废弃,schema 里已注明 “Deprecated: use host.default.nkro instead”),后续版本会移除。原因是它写进 EEPROM 后强制了状态,导致 NK_TOGG 这类键码行为与预期不符——你以为按一下是切换,实际状态早被钉死了。要「开机即 NKRO」就写 host.default.nkro,语义是「默认值」而不是「强制」,运行时照样能切回 6KRO 进 BIOS。运行时切换:NK_TOGG、NK_ON / NK_OFF,以及 Magic N
开了编译开关之后,下面三种方式都能在设备上临时切换,不需要重刷:
NK_TOGG—— 切换(推荐放在键位里,比如 Layer 1 的某个角键)NK_ON/NK_OFF—— 分别强制开 / 关,适合做成「按住 Fn 才 6KRO」这种- Magic N —— 默认
LShift + RShift + N,不改键位也能切(MAGIC_ENABLE默认就是开的)
切换结果写进 EEPROM,掉电保留。所以「昨天还好好的,今天一插就变成只能按 6 个键」不是坏了,是上次顺手按了 Magic N。
BIOS / UEFI 里到底能不能用
能,但要看主板的脸色。QMK 里有一道明确的自动保护,逻辑就三行(quantum/action_util.c):
void send_keyboard_report(void) {
#ifdef NKRO_ENABLE
if (host_can_send_nkro() && keymap_config.nkro) {
send_nkro_report();
return;
}
#endif
send_6kro_report();
}
而 host_can_send_nkro() 只做一件事(tmk_core/protocol/host.c):
bool host_can_send_nkro(void) {
return usb_device_state_get_protocol() == USB_PROTOCOL_REPORT;
}
翻译过来:只要主机把键盘接口切到了 boot protocol,QMK 立刻停发 NKRO,改发 8 字节标准报告。usb_main.c 里 send_keyboard() 在 boot 模式下还会把 report_id 也剥掉,只发 &report->mods 起的 8 个字节——因为 boot 报告不允许带 report ID。
所以能不能在 BIOS 里打字,取决于主板会不会发 SET_PROTOCOL(0):
| 主板行为 | 结果 |
|---|---|
UEFI 正常发 SET_PROTOCOL(0)(绝大多数现代主板) | 正常用。QMK 自动降级成 6KRO 的 8 字节报告,BIOS 认得,只是同时按超过 6 个键会丢 |
老 BIOS 不发 SET_PROTOCOL | 完全没反应。固件停在 report protocol,发的还是 32 字节 NKRO 报告,BIOS 解析不了——设备管理器里连键盘都看得见,但按什么都没用 |
开机时按的是 NK_TOGG 之后的 6KRO 状态 | 无论如何都能用(此时根本不走 NKRO 分支) |
docs/usb_nkro.txt(2010 年)把根因说得更直白:「Some BIOS doesn't send SET_PROTOCOL request, a keyboard can't switch to boot protocol mode.」——十六年过去,机制一点没变。实用的保命做法:键位里一定放一个 NK_TOGG(或记住 Magic N)。进 BIOS 前先在系统里切回 6KRO,再重启进 Setup;万一忘了,用另一把普通键盘进去关掉「USB 快速启动 / Fast Boot」,通常就能恢复。
APM32 上有什么不一样:几乎没有,但要数端点
协议层完全一致。NKRO 是 USB 描述符与报告格式的事,跟哪家 MCU、哪条产线无关。APM32F405 / F411 在 QMK 里按 processor = STM32F405 / STM32F411 编译,用的就是 STM32 那套 ChibiOS OTG 驱动和 QMK 自己生成的描述符,行为与 ST 原厂芯片逐字节相同。极海自家的 SDK、DFU bootloader 都不参与这个过程,所以不存在「APM32 的 NKRO 要特殊配置」。
真正会卡的是端点数量。OTG_FS 一共只有 4 个 IN 端点,EP0 占掉一个,能分给功能的只剩 3 个。NKRO 本身不额外要端点(它挤在 shared 端点里,与鼠标、多媒体键共用),但 shared 端点自己要占一个名额。典型占用:
| 功能组合 | IN 端点 | 结果 |
|---|---|---|
| 键盘 + NKRO(+ 鼠标/多媒体) | 2 | 正常,还剩 1 个 |
键盘 + NKRO + 控制台日志 CONSOLE_ENABLE | 3 | 刚好用满,可用 |
| 键盘 + NKRO + MIDI + 虚拟串口 | > 3 | 编译直接失败,报 There are not enough available endpoints |
报错原文会点名要你砍功能:「Please disable one or more of the following: Mouse Keys, Extra Keys, Console, NKRO, MIDI, Serial, Steno」。做导播键盘这类「MIDI + 键盘一体化」的产品时最容易撞上,取舍一般是砍掉控制台日志或虚拟串口。
排查:NKRO 相关的四个典型现象
| 现象 | 原因与处理 |
|---|---|
按 NK_TOGG 毫无反应 | 编译层没开。features.nkro 或 NKRO_ENABLE = yes 必须为真,否则该键码在固件里是空操作 |
| 系统里能同时按很多键,但一进 BIOS 就失灵 | 主板不发 SET_PROTOCOL(0)。先切 6KRO 再进 Setup;或换用 PS/2 转接、开 Fast Boot 兼容选项 |
明明设了 host.default.nkro,刷完还是 6KRO | EEPROM 里存着上次手动切回 6KRO 的状态——默认值只在 EEPROM 首次初始化时写入,刷固件不会清它。按一次 NK_TOGG,或按 EE_CLR 清 EEPROM(详见 改完没生效) |
| 编译报端点不足 | 见上一节,砍掉 MIDI / 串口 / 控制台其中之一;也可考虑 KEYBOARD_SHARED_EP 让键盘报告也挤进 shared 端点(代价是 6KRO 报告变成 9 字节,带 report ID) |
源码依据(2026-10 核对 QMK 主线):action_util.c 的 send_keyboard_report() 与 host_can_send_nkro() 调用、host.c 的 protocol 判断、report.h 的报告结构(NKRO_REPORT_BITS 30)、eeconfig.c 的 NKRO_DEFAULT_ON 默认值 false。官方说明:FAQ — NKRO Doesn't work · 2025-05 Breaking Changes(FORCE_NKRO 废弃)
🧰 九、故障排查速查
按症状定位,先查最常见原因,再跳对应章节细读。本表持续补充。
| 症状 | 优先检查 | 相关章节 |
|---|---|---|
| USB 插上完全没反应 | ① 先看系列:F1 / E103 的 D+ 必须有 1.5KΩ 上拉,F0 / F3 / F4 内置、外接反而违规(RP2040 则是各串 27Ω)② PA10 是否给了确定电平(22KΩ 上拉 3.3V;悬空会被 ROM Bootloader 误判成串口烧录,DFU 直接不枚举)③ 晶振频率是否与 QMK 预设一致(F103/F411/F407=8MHz、F405=12MHz、F072 可省、32U4=16MHz);套了 BLACKPILL 板级则预设是 25MHz,与自画板的 8MHz 晶振必冲突(见 4.3) | USB 硬件设计 · 芯片支持速查 |
| BIOS / UEFI 里按键没反应 | ① 多半是当前处于 NKRO 模式,而主板没发 SET_PROTOCOL(0):先在系统里按 NK_TOGG 或 LShift+RShift+N 切回 6KRO,再重启进 Setup ② 确认 features.nkro 是否真的编进去了(没编进去的话 NK_TOGG 是空操作)③ 关掉主板 Fast Boot,或改用 PS/2 转接 | 全键无冲 NKRO |
| 编译报错 / 找不到文件 | ① rules.mk 里 MCU_LDSCRIPT 与 ld 文件名是否一致(不含 .ld 后缀)② 自建脚本是否放在键盘目录 ld/ 或官方 boards 的 ld/ ③ 环境是否完整(qmk doctor) | F103 配置 · F4 配置 |
| 刷写失败 / 进不了 DFU | ① 进入 Bootloader 的方式是否正确(Bootmagic 按住 (0,0) 键插 USB / QK_BOOT / BOOT0 跳线)② F103 是否已先烧 stm32duino ③ APM32 是否用 apm32-dfu 配置编译 | 烧录 Bootloader · 烧录固件 |
| 兼容芯片插上后 Toolbox 里看不到设备 | Toolbox 只认它内置了设备类的那几个 VID:PID。「兼容 STM32」不等于「能被 USB 刷」:ROM DFU 的 ID 不在名单里就永远不显示。改用 qmk flash(必要时在 rules.mk 自填 DFU_ARGS / DFU_SUFFIX_ARGS),或者直接走 SWD | 兼容芯片能否直接烧 |
dfu-util 报 File ID … does not match device | 固件里写的 BOOTLOADER 与板上实际芯片不一致(典型:APM32 固件刷 STM32 板)。没砖、也没写进去:改成对应芯片的 bootloader 重编译即可;应急可 dfu-suffix --delete 去掉后缀再刷。详见 7.1 | 7.1 固件身份 |
| 刷完 Bootloader 后彻底不识别 | bootloader 被覆盖了。ST-Link 直写和 ST 系统 DFU(0483:DF11)都落在 0x08000000,而 QMK 固件是链接到 0x08002000 的——等于把 bootloader 冲掉、固件又落到错误地址。USB 上一个设备都不会出现,按 QK_BOOT 也无效。回 6.2 重烧一次 bootloader 即可 | 6.3 三个 DFU 别混用 |
| 整行或整列按键失灵 | ① 该行/列 GPIO 定义与实际接线是否一致 ② 排针/焊点虚焊 ③ 该行/列引脚是否被 RGB/OLED 等功能复用占用 | 矩阵硬件设计 |
| 按键乱触发 / 幽灵键 | ① 二极管方向是否全部一致(COL2ROW / ROW2COL 是否接反)② 是否漏焊二极管 ③ 消抖参数是否过小 | 矩阵硬件设计 |
| 刷完固件按键无反应 | ① 链接脚本容量是否选错(程序跑飞)② 晶振与预设不符导致 USB 时钟异常 ③ keymap 的 LAYOUT 与矩阵定义是否匹配 | F4 配置 · 矩阵硬件设计 |
📦 十、本站资源下载汇总
STM32F103xC_stm32duino.ld — F103RCT6 用,256KB Flash / 48KB SRAM
STM32F103xE_stm32duino.ld — F103RET6 用,512KB / 64KB
STM32F103xG_stm32duino.ld — F103RGT6 用,1MB / 96KB
APM32E103xE_stm32duino.ld — APM32E103CET6/RET6 用,512KB / 128KB
STM32F407xG.ld — F407VGT6 用,1MB Flash(官方仅有 512KB 的 xE)
用法:放入键盘目录 ld/,rules.mk 写 MCU_LDSCRIPT = 文件名(不含 .ld)。详见 F103 配置 / F4 配置。
QMK Toolbox — 图形化刷写工具(.hex/.bin 一键刷入)
qmk_firmware — QMK 固件源码仓库(编译环境见「起步」章)
QMK 官方文档 — 本指南各章末尾均附对应官方页面,便于对照深挖