← 返回工具箱

⌨ QMK 键盘开发指南

汇总基于 QMK 固件开发自定义键盘的核心知识:主控芯片选型、Bootloader 与刷写工具链、RGB / OLED / 编码器 / MIDI 等常用外设的硬件接线与固件配置。面向键盘 DIY 爱好者,基于 QMK 官方文档与实践经验整理。

🧠 一、主控芯片选型对比

以下对比涵盖 STM32 原厂及国产 APM32 替代型号。关键指标:内核架构、主频、Flash/SRAM、ADC、USB 规格、封装、QMK 默认晶振频率及参考价格。

选型对比总表(25 款芯片)

表格横向可滚动。✔ = QMK 默认支持;◐ = 需适配。注意:本表只判断「能不能编译、跑不跑得动」,没判断「能不能 USB 刷」——后者看芯片 ROM DFU 的 VID:PID 在不在 QMK / Toolbox 名单里,见 兼容芯片能否直接烧。

型号品牌内核架构主频FlashSRAMADC 位/通道USB封装默认晶振频率*参考价格*支持 / 备注
ST 原版 · STM32
F072 系列 · Cortex-M0 · 48MHz
STM32F072CBT6STCortex-M048 MHz128 KB16 KB12-bit / 16chFSLQFP-48无(内置 HSI48)*~¥10-15✔ *
STM32F072RBT6STCortex-M048 MHz128 KB16 KB12-bit / 16chFSLQFP-64无(内置 HSI48)*~¥12-18✔ *
F103 系列 · Cortex-M3 · 72MHz
STM32F103CBT6STCortex-M372 MHz128 KB20 KB12-bit / 10chFSLQFP-488 MHz~¥10-15✔ *
STM32F103RGT6STCortex-M372 MHz1 MB96 KB12-bit / 16chFSLQFP-648 MHz~¥25-35◐ *
F303 系列 · Cortex-M4F · 72MHz
STM32F303CCT6STCortex-M4F72 MHz256 KB40 KB12-bit / 15chFSLQFP-488 MHz~¥21-26✔ *
STM32F303RET6STCortex-M4F72 MHz512 KB64 KB12-bit / 18chFSLQFP-648 MHz~¥25-35✔ *
F401 系列 · Cortex-M4F · 84MHz
STM32F401CCT6STCortex-M4F84 MHz256 KB*64 KB12-bit / 10chFSLQFP-488 MHz~¥15-22✔ *
STM32F401RET6STCortex-M4F84 MHz512 KB96 KB12-bit / 16chFSLQFP-648 MHz~¥18-25✔ *
F405 / F407 系列 · Cortex-M4F · 168MHz
STM32F405RGT6STCortex-M4F168 MHz1 MB192 KB12-bit / 24chFS+HS*LQFP-6412 MHz*~¥28-37✔ *
STM32F407VGT6STCortex-M4F168 MHz1 MB192 KB12-bit / 24chFS+HS*LQFP-1008 MHz~¥35-45✔ *
F411 / F446 系列 · Cortex-M4F
STM32F411CET6STCortex-M4F96 MHz512 KB128 KB12-bit / 10chFSLQFP-488 MHz~¥18-25✔ *
STM32F411RET6STCortex-M4F96 MHz512 KB128 KB12-bit / 16chFSLQFP-648 MHz~¥20-28✔ *
STM32F446RET6STCortex-M4F180 MHz512 KB128 KB12-bit / 24chFS+HS*LQFP-648 MHz~¥25-35✔ *
H7 系列 · Cortex-M7 · 550MHz
STM32H723VGT6STCortex-M7550 MHz1 MB564 KB12-bit / 20chFS+HS*LQFP-1008 MHz*~¥55-75✔ *
APM32 · 极海(国产替代)
E103 系列 · F103 兼容 · Cortex-M3
APM32E103CET6极海Cortex-M372 MHz*512 KB*128 KB*12-bit / 21chFSLQFP-488 MHz~¥7-10◐ *
APM32E103RET6极海Cortex-M372 MHz*512 KB*128 KB*12-bit / 21chFSLQFP-648 MHz~¥8-11◐ *
F072 / F402 · 低成本线
APM32F072CBT6极海Cortex-M0+48 MHz128 KB16 KB12-bit / 16chFSLQFP-48无(内置 HSI48)*~¥6-8✔ *
APM32F072RBT6极海Cortex-M0+48 MHz128 KB16 KB12-bit / 16chFSLQFP-64无(内置 HSI48)*~¥6-9✔ *
APM32F402RBT6极海Cortex-M4F120 MHz128 KB*32 KB12-bit / 21chFSLQFP-648 MHz~¥6-9✔ *
F405 / F407 系列 · Cortex-M4F · 168MHz
APM32F405RGT6极海Cortex-M4F168 MHz1 MB192 KB12-bit / 24chFS+HS*LQFP-6412 MHz*~¥15-22✔ *
APM32F407RGT6极海Cortex-M4F168 MHz1 MB192 KB12-bit / 24chFS+HS*LQFP-648 MHz~¥15-25✔ *
F411 / F446 系列 · Cortex-M4F
APM32F411CET6极海Cortex-M4F120 MHz*512 KB128 KB12-bit / 16chFSLQFP-488 MHz~¥11-14✔ *
APM32F446RET6极海Cortex-M4F180 MHz512 KB128 KB12-bit / 24chFS+HS*LQFP-648 MHz~¥15-25✔ *
其他架构
RP2040树莓派双核 Cortex-M0+133 MHz2 MB*264 KB12-bit / 4chFSQFN-5612 MHz~¥10 ⚠*✔ *
ATMEGA32U4MicrochipAVR 8-bit16 MHz32 KB*2.5 KB10-bit / 12chFSTQFP-4416 MHz~¥18 ⚠*✔ *

参考价格为 2026-10 散片单买口径(如 F405RGT6 立创 1+ ¥36.6 / 100+ ¥27.5、F411CET6 立创 1+ ¥14.0),同规格 APM32 约为 ST 一半价。
为什么 ST 推荐立创商城:小批量直接买立创现货散价——淘宝散片来源不透明,很多不知道是哪年的老片子,甚至可能是二手翻新片,翻车风险高;立创现货可追溯、散价也公道。
为什么 APM32 在淘宝「联科芯」买而不在立创:作者长期在该店购买,已向极海官方确认其为代理,货源可靠、价格实在——顺便算是给他们打个广告。
行情随时浮动,下单前以实时查询为准;标 ⚠ 的价格为倒挂 / 不适用口径,不要照它下单。

推荐 & 建议

推荐顺序: F103 / E103(入门性价比)→ F401 / F411(标准进阶)→ F405RGT6 / APM32F407RGT6(性能需求)→ RP2040(大 ROM 存储需求)→ ATMEGA32U4(不推荐)。
QMK 默认晶振适配: 各芯片 QMK 默认按固定晶振频率计算时钟(F405 按 12MHz、H723 按 25MHz),选板时留意即可,并非优劣之分。
国产 APM32 实测: APM32F072 / F4xx 全系列直接可用;APM32E103 已验证可复用 F103 配置,使用完整 Flash/SRAM 时需匹配对应链接脚本。
APM32 入选口径: ① QMK 下直接套用对应 STM32 预设即可达到对等性能——虽发挥不到纸面 100%,但对键盘应用绰绰有余;② 产品品类多、价格合适,国内购买方便——本站作者长期在淘宝「联科芯」购买,已经向极海官方确认其为代理之一,散片好买、价格实在;③ 工具链与开发生态成熟。三条不满足者不列入本表推荐。
作者立场: 本人多块自研键盘均采用 APM32——同性能更便宜、国内随手买得到、封装对手焊友好,是国产替代的忠实用户与推广者。
F103 / E103 系列首推 · 入门性价比

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 性能一般
F401 / F411 系列标准进阶

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 通道),模拟扩展弱
  • 对键盘性能略过剩
STM32F405RGT6 / APM32F407RGT6性能需求

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 占用面积多
RP2040(Raspberry Pi Pico)大 ROM 存储需求

双核 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 等模拟外设
Atmega32u4(Pro Micro / Elite-C)最菜,不推荐新项目

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 官方板级
STM32F042x6GENERIC_STM32_
F042X6
无晶振(HSI)48 / 48 满速x6 = 全系直用(32KB 已是最大)预设 HSE 关闭,HSI/2×12 直接满速;USB 由内部时钟供
STM32F072xBGENERIC_STM32_
F072XB
无晶振(内置 HSI48)48 / 48 满速xB = 最大容量(128KB),CBT6 / RBT6 直接编译同 F042;F0 全系内置 HSI48,不接晶振即可满速跑 USB
STM32F103xBSTM32_F103_
STM32DUINO
8 MHz(写死)72 / 72 满速⚠ 默认仅 xB(128KB):CBT6 直用;RCT6 / RET6 / RGT6 需另配 xC / xE / xG 链接脚本——官方 stm32duino 只有 x6/x8/xB,脚本本页可下载必须外接 8MHz 晶振
STM32F303xCGENERIC_STM32_
F303XC
QMK_PROTON_C
8 MHz72 / 72 满速xC(256KB)直用 → CCT6 直接编译;RET6(512KB)需在 rules.mk 显式 MCU_LDSCRIPT = STM32F303xE(ChibiOS 自带该脚本,无需自建)Proton-C 同款配置
STM32F401xCGENERIC_STM32_
F401XC
8 MHz84 / 84 满速xC(256KB)直用 → CCT6 直接编译;RET6(512KB)需显式 MCU_LDSCRIPT = STM32F401xE(官方 common/ld 已有,无需自建)别用 BLACKPILL 板级:预设写死 25MHz(只对正品 WeAct V3.1 成立,仿板/自画板多为 8MHz,见 USB 章)
STM32F405xGGENERIC_STM32_
F405XG
⚠ 12 MHz
(board.h 写死)
168 / 168 满速xG(1MB)→ RGT6 直接满配,无需任何额外文件⚠ 最经典的坑:预设按 12MHz 配,8MHz 板直接跑错;APM32F405 借用此配置 = 12MHz
STM32F407xEGENERIC_STM32_
F407XE
8 MHz
(board.h 写死)
168 / 168 满速xE(512KB)→ VET6 直接编译;VGT6(1MB)需自建 xG 脚本(官方无,本页可下载)⚠ 与 F405 不同(8MHz),两系列晶振别混用
STM32F411xEGENERIC_STM32_
F411XE
8 MHz96 / 100xE(512KB)→ CET6 / RET6 直接编译;CC(256KB)需显式 MCU_LDSCRIPT = STM32F411xC(官方 common/ld 已有)QMK 预设 96 略低于标称 100;别用 BLACKPILL 板级(25MHz 预设,见 USB 章)
STM32F446xEGENERIC_STM32_
F446XE
8 MHz180 / 180 满速xE(512KB)→ RET6 直接编译—
STM32H723xGGENERIC_STM32_
H723XG
8 MHz
(M=4/N=275/P=1)
550 / 550 满速xG(1MB)→ VGT6 直接满配⚠ 市售 H723 核心板多载 25MHz 晶振,官方配置直接编译会跑飞,需改 PLLM
STM32G0B1xBGENERIC_STM32_
G0B1XB
无晶振(HSI16)64 / 64 满速xB 全容量直接编译—
STM32G431 / G474GENERIC_STM32_
G431XB
G474XE
无晶振(HSI16)160 / 170XB / XE 均直接编译QMK 预设略低于标称
STM32L412 / L432 / L433GENERIC_STM32_
L412XB
L432XC / L433XC
无晶振(HSI16)80 / 80 满速XB / XC 均直接编译—
非 ST 的 ARM · 官方板级
GD32VF103(RISC-V)SIPEED_
LONGAN_NANO
8 MHz108 / 108 满速官方板级直接编译—
WB32F3G71xx / FQ95xxGENERIC_WB32_*12 MHz(写死)72 / 72 满速官方板级直接编译—
AT32F415(雅特力)GENERIC_AT32_
F415XX
8 MHz(写死)144 / 200官方板级直接编译QMK 预设(×18)远低于标称
其他架构
RP2040GENERIC_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_124 MHz600 / 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

选型阶段就必须知道的一条: 「寄存器兼容 / 能跑 QMK 固件」≠「能用 USB 刷 QMK 固件」。QMK 编译时会把 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 报什么 IDToolbox 能否识别能否直接 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 工具
第三种情况的唯一绕法:自己把 VID:PID 填进去
# 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 依然认不出设备)
一句话: ROM bootloader 是掩膜在芯片里的固件,各写各的;QMK / dfu-util / Toolbox 认的是那串 VID:PID,不是「Cortex-M4 + STM32 寄存器」。所以选型时若不想长期抱着 SWD 和厂商 ISP 工具,优先挑 QMK 已有 bootloader 档位的芯片(ST 原厂、APM32、AT32、WB32、GD32V),别赌「兼容」。真要上名单外的芯片,就把 SWD 当常规流程——量产阶段本来也更顺(见 7.5)。

🚀 二、起步:环境搭建与第一个键盘工程

本篇为纯新手路线:从空电脑到编译出第一个能刷进键盘的固件。章节框架已就位,正文逐步补全。

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 → 按芯片选择刷写方式(跳转到「烧录固件」章)→ 刷完按键无反应时的第一轮自检。

官方对照:Building Your First Firmware

⚙️ 三、快速配置向导

下拉选择实际型号后,页面会同时给出 keyboard.json 和 rules.mk:前者唯一声明 processor / bootloader,后者只保留板级、链接脚本等其余设置,避免同一选项在两个文件中重复定义。

型号写法遵循“系列 + 封装代号 + Flash 容量代号”,例如 STM32F401*C。

⚙️ F103 / E103 配置

QMK 的“主控型号”与“板级配置”不是一一对应的。

以 F103 / E103 为例:

现有 QMK 配置文件位置
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 配置文件完整使用条件未适配时的实际上限
STM32F103C8T664KB20KBSTM32F103x8_
stm32duino.ld
现有文件
✔ 可完整使用无需额外修改64KB Flash20KB SRAM72MHz
STM32F103CBT6128KB20KBSTM32F103xB_
stm32duino.ld
现有文件
✔ 可完整使用无需额外修改128KB Flash20KB SRAM72MHz
STM32F103RCT6256KB48KB新建 STM32F103xC_
stm32duino.ld
需改 Flash/RAM 容量
◐ 需新建链接脚本主频无需修改
仍为 72MHz
128KB Flash20KB SRAM72MHz直接套用 xB
STM32F103RET6512KB64KB新建 STM32F103xE_
stm32duino.ld
需改 Flash/RAM 容量
◐ 需新建链接脚本主频无需修改
仍为 72MHz
128KB Flash20KB SRAM72MHz直接套用 xB
STM32F103RGT61MB96KB新建 STM32F103xG_
stm32duino.ld
需改 Flash/RAM 容量
◐ 需新建链接脚本主频无需修改
仍为 72MHz
128KB Flash20KB SRAM72MHz直接套用 xB
APM32E103CET6512KB128KB新建 APM32E103xE_
stm32duino.ld
建议独立命名
◐ 需改多个文件链接脚本
MCU / 时钟 / Flash 配置
128KB Flash20KB SRAM72MHz复用 F103 xB 配置
APM32E103RET6512KB128KB新建 APM32E103xE_
stm32duino.ld
建议独立命名
◐ 需改多个文件链接脚本
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。

没有对应配置文件时如何新建

最稳妥的方法:

  1. 复制同目录下现有的 STM32F103xB_stm32duino.ld
  2. 只修改 Flash 和 SRAM 容量定义
  3. 不要直接修改公共文件 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 全频率配置(实验性)

测试状态:以下内容用于记录后续移植方向,当前尚未完成完整测试。现有 F103 兼容配置已经验证可运行,但不能据此认定 APM32E103 的高频、USB、Flash 等待周期和长期稳定性已经验证。

APM32E103 的“完整容量”和“全频率”是两个独立目标:

后续适配时需要重点检查以下文件:

键盘目录/
├─ 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 的配置由三部分共同决定:

实际型号板载
Flash
板载
SRAM
QMK 配置文件当前主频完整使用条件
STM32F401*C256KB64KBSTM32F401xC.ldF401_MCUCONF84MHz✔ 基本完整确认晶振;默认按 xC
STM32F401*E512KB96KBSTM32F401xE.ld需显式指定84MHz◐ 需指定链接脚本否则可能按 xC 使用
STM32F405*G1MB192KB + CCMSTM32F405xG.ldF405/F407 共用 MCU 配置168MHz◐ 确认 12MHz 晶振CCM RAM 需单独放置变量
STM32F407*E512KB192KB + CCMSTM32F407xE.ldF405/F407 共用 MCU 配置168MHz◐ 确认 8MHz 晶振CCM RAM 需单独放置变量
STM32F411*C256KB128KBSTM32F411xC.ld需显式指定96MHz◐ 需指定链接脚本否则可能按 xE 使用
STM32F411*E512KB128KBSTM32F411xE.ldF411_MCUCONF96MHz✔ 基本完整确认晶振和板级引脚
STM32F446*E512KB128KB + 备份 SRAMSTM32F446xE.ldF446_MCUCONF180MHz✔ 基本完整确认 8MHz 晶振
APM32F405RG1MB192KB + CCMSTM32F405xG.ld沿用 F405 配置
BOOTLOADER = apm32-dfu
168MHz◐ 确认 12MHz 晶振实测可用,无需新建文件
APM32F411CET6512KB128KBSTM32F411xE.ld沿用 F411 配置
BOOTLOADER = apm32-dfu
96MHz(额定 120MHz)✔ 基本完整实测可用;跑满 120MHz 需改时钟
F4 配置的判断方式:

表中的“基本完整”表示 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 全局内存使用,必要时需要使用对应段属性。

在 QMK 中编译 APM32F405 / APM32F411(实测可用)

国产极海 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
总结:STM32F4 的主频通常可以达到对应型号的官方上限:F401 为 84MHz、F405/F407 为 168MHz、F411 为 96MHz、F446 为 180MHz。真正需要重点确认的是晶振频率、链接脚本容量、板级引脚和 CCM RAM 的使用方式。

🔋 四、USB 硬件设计(F1 / F4 全系列)

这一章解决一个很具体的问题:自己画一块键盘板,USB 那几根线到底怎么接。之所以不能只给一套图,是因为各系列的 USB 控制器根本不是同一代东西——F1 没有内置 D+ 上拉,1.5K 电阻必须外接;F4 内置了,外接反而违规。同一张图抄错系列,轻则枚举时好时坏,重则电脑连"未知设备"都不弹。

下面按「先分类 → F1 体系 → F4 体系 → 其余芯片 → 自检清单」的顺序讲,每条结论都给依据(芯片手册、ST 应用笔记、QMK 源码或上游 issue),不做"据说"式的口口相传。QMK 与自研固件对硬件的要求完全一致,一份 PCB 通吃。

三句话记法:F1 的 1.5K 必须画,F4 的 1.5K 画了就错;STM32 数据线直连、RP2040 要串 27Ω;F4 的 PA10 一颗 22K 不能省。

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 驱动USBv1OTGv1
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 最稳妥(若极海新版数据手册标注了内置上拉,以手册为准)。

F103 没有 HSI48,也没有"无晶振 USB"这回事。ST 官方表的 Crystal-less 一行,F102 / F103 是空的,只有带 HSI48 的 F0 这类型号才打勾。看到"F103 可以省晶振"的说法,那是把 F0 / F4 的经验套错了对象。

F1 特有的两个坑

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+ 的 1.5KΩ 上拉:芯片内部已有,外接反而违规

原理:F4 的 OTG_FS 把这颗 1.5K 电阻集成在片内 PHY 里,而且是可编程的——由 USB 内核在准备好之后才挂上(soft-connect),固件可以自己决定何时"露面"。

外接的后果:片内 1.5K 与外接 1.5K 并联约 750Ω,D+ 静态电平偏离规范。轻则主机识别时序错乱、枚举时好时坏,重则速度协商失败直接不认设备。有人外接后"能用",是因为片内上拉在 soft-connect 之前还没挂上,属于两头都不对的巧合,不能当作可行方案。

结论:F4 / F5 / F7 的 D+ 上拉一根线都不要画。唯一例外是同一块板要兼容 F103 那类芯片(此时上拉必须能断开,用 0Ω 或跳线选择,F4 工作时必须断开)——这也是"F1 与 F4 混板"唯一值得做的兼容设计。

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-01rgoulter 复现:WeAct F401 上 PA9 做输入时内部上拉无效、始终读低;外接上拉就正常——首次把矛头指向"内部下拉没关掉"
2021-06-12jiaxin96 确认只出现在 STM32F4 系列(F401 / F411),并给出临时办法:自写 matrix.c 替代标准矩阵扫描
2022-01-22zhaqian12 定位根因:只置 NOVBUSSENS 不够,还必须清掉 VBUSBSEN,在 STM32F2 / F4 上验证通过;同日 jiaxin96 指出这是 ChibiOS 的 bug,正确写法应为 GCCFG_INIT_VALUE = (GCCFG_NOVBUSSENS | GCCFG_PWRDWN)
2022-08-14QMK 维护者 drashna 追问"上游修了吗"
2023-04-10仍有用户踩坑(做分体键盘烧了两天,一直怀疑自己焊错);jiaxin96 回复:上游尚未接受,只能自己打补丁,并贴出 ChibiOS 论坛的报告链接
2023-04-14ChibiOS 作者 Giovanni 在论坛回复「Change committed」——上游正式修复(forum topic 6226)
2024-10-13drashna 确认"已由上游打补丁并收录",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.cinit_pins 里先 setPinOutput(pin) 再 setPinInputHigh(pin)2020 年的临时招,部分板子无效
直接打补丁改 hal_usb_lld.c 的 GCCFG_INIT_VALUE升级 QMK 会被覆盖,需重新打

现在到底怎么接(默认不用 5V)

按实际情况选一行。默认推荐第一行——PA9、PA10 各一颗 22KΩ 上拉到 3.3V,其余全部不接,5V 一根线都不进 MCU。

你的情况PA9PA10
只做有线键盘
(推荐默认)
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
新板为什么彻底不用 5V:键盘是总线供电,插上就有电,“有没有插”本身没有信息量,QMK 所有 F4 板级也已经用 BOARD_OTG_NOVBUSSENS 把检测关掉了。而 5V 上拉有两个真实代价:① 倒灌——USB 断电瞬间 5V 轨先崩、3.3V 还有残压时,电流经上拉网络推回 5V 轨(back-powering,可让某些 hub 锁死);② 上电时序——VBUS 已有 5V 而 LDO 还没起来时,分压中点 2.5V 直接打在未上电的 MCU 引脚上,经 ESD 二极管灌进 3.3V 网络,表现为“平时正常、热插拔偶尔死机”。
只有 3.3V 供电的子板,最容易犯的错:把原来的"两个 100K 分别上拉到 5V、下拉到 GND"改成"两个 100K 从 3.3V 分压到 GND",得到 1.65V。这比不接更糟:F411 的 VIH 约为 0.7 × 3.3V ≈ 2.31V,1.65V 既不算可靠的"高",也毫无检测意义,只是白搭两颗电阻和一个漏电流路径。
正确做法见上表第三行:只留 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 节原文:

“It is recommended to keep the RX pins of unused bootloader interfaces (USART_RX, SPI_MOSI, CAN_RX and USB D+/D- lines if present) at a known (low or high) level at the startup of the bootloader (detection phase). Leaving these pins floating during the detection phase might lead to activating unused interfaces.”

翻译成人话: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 空闲为高的习惯。

和 QMK 的关系:零。这条要求来自芯片 ROM,STM32F405 / F411 与 APM32F405 / F411 完全一致——极海 ROM Bootloader 的接口集合与 ST 相同(USB: PA11/PA12;USART1: PA9/PA10;CAN 等),悬空风险一模一样。

型号对齐:谁和谁能共用一套图

结论先给:F4 / F5 / F7 的 USB 外围接法完全相同,差异只在板级配置文件放在哪、晶振频率、以及 VCAP 接法。下表按 LQFP-64 口径整理,封装不同时引脚号以数据手册为准。

芯片QMK 通用板级默认晶振VCAP备注
STM32F401GENERIC_STM32_F401XC8MHz单 VCAP(30 脚)只有 OTG_FS;LQFP-64 的 31 / 47 脚是 VSS,必须接地
STM32F411GENERIC_STM32_F411XE8MHz单 VCAP(30 脚)issue #7855 的原型芯片;板级 config.h 定义 BOARD_OTG_NOVBUSSENS 1
STM32F446GENERIC_STM32_F446XE8MHz双 VCAP(按手册)另有 OTG_HS(需外置 ULPI PHY),QMK 用不到;USB 部分按 OTG_FS 接
STM32F405GENERIC_STM32_F405XG12MHz双 VCAP(31 / 47 脚)板级 board.h 定义 BOARD_OTG_NOVBUSSENS;PA9 兼 USART1_TX
STM32F407GENERIC_STM32_F407XE8MHz双 VCAP(31 / 47 脚)与 F405 引脚兼容但晶振预设不同,混用时最容易翻车的一对
STM32F5 / F7按型号自建
或套用相近板级
按型号多为双 VCAP同为 OTG 体系,D+ / PA9 / PA10 结论通用;PLL 与引脚映射必须核对 mcuconf.h
APM32F411GENERIC_STM32_F411XE8MHz单 VCAP(30 脚)LQFP-64 与 STM32F411 一一对应,PCB 可共用;ROM 的 USART1 同样走 PA9 / PA10
APM32F405/407GENERIC_STM32_F405XGF405 = 12MHz
F407 = 8MHz
双 VCAP极海 ROM 支持 USB + USART + CAN,接口集合与 ST 一致
APM32 侧无法像 ST 那样逐行对照源码(极海 ROM 闭源),但接口集合相同,PA10 悬空的风险与 ST 一致。另外 APM32F405 有 ST 没有的片上高速 PHY(OTG_HS2),但 QMK 用不到,接线按全速 OTG_FS 走即可——详见 选型对比总表的 USB 列。

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官方在售备注
STM32F401CCU684MHz / 256K / 64K停售官方已停售,市面流通多为库存或仿板
STM32F401CEU684MHz / 512K / 96K停售同上
STM32F411CEU6100MHz / 512K / 128K在售(V3.1)25MHz + 32.768kHz、三个按键(NRST / BOOT0 / 用户键 PA0)、USB-C;出厂预烧 WeAct HID Bootloader
「可靠性问题」要分清来源:正品 vs 仿板

官方在 README 里点名说过这件事,值得照抄过来:

  • 背面没有 WeAct Studio Logo 和版本号的,都不是他们生产的——这条是唯一可靠的辨识方法,买到仿板「质量问题和技术问题请自行找卖家解决」。
  • 工艺:正品 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,但前提都是"喂给它的晶振正好是它期望的那一个":

板级期望 HSEPLL(M / N / P / Q)验算
BLACKPILL_STM32_F4xx25MHz25 / 384 / 4 / 825÷25=1MHz → ×384=384MHz → ÷4=96MHz、÷8=48MHz
GENERIC_STM32_F4xxx8MHz
(F405 为 12MHz)
4 / 96 / 2 / 48÷4=2MHz → ×96=192MHz → ÷2=96MHz、÷4=48MHz

错配之后会怎样:USB 死,CPU 还在跑

两种情况的表现完全一样:板子能跑、矩阵能扫描、串口可能还有输出,但插上电脑连"未知 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 板级还顺带带了一堆"开发板假设"

晶振只是最要命的一条,其它还有:

用在自己 PCB 上,等于"借别人的板子定义":能跑的时候无所谓,一旦出事,你根本分不清是哪条假设在作怪。

这句「吹热才能进 DFU」的出处:QMK 官方文档原话是「Due to the use of a 25MHz crystal, the controller may have issues entering the bootloader. Heating up the controller can help with this issue.」——说的是开发板自己的起振裕量,不是固件。你自画板若也这样,先查晶振负载电容与起振裕量。
买 Black Pill 刷 QMK,会「弄丢」出厂的 HID Bootloader(正常)

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Ω

三个必须记住的差异:

想用一套 PCB 同时兼容 F405 和 F411(做升级款或降本款时很常见):30 / 31 / 47 三个位置各留一组 0Ω 选择位——装 F405 时 31 / 47 接电容、30 脚按 GPIO 处理;装 F411 时 30 脚接电容、31 / 47 用 0Ω 短接到地。同时注意晶振也不同(12MHz vs 8MHz),要留两个焊盘或选可换的晶振封装。

供电部分:USB 座上的 VBUS(5V)经 LDO 降到 3.3V 供 MCU 与整板使用。3.3V 侧至少要有一颗 4.7µF 钽/陶瓷电容 + 若干 100nF 去耦(每个电源脚一颗),这是 F4 能稳定跑 USB 的底线。若做自供电(电池 / DC),VBUS 与 3.3V 之间需要做电源路径隔离(理想二极管或肖特基 + 分压检测),不要简单并联。

提醒:官方文档这段已经过时

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):上拉内置,晶振看型号

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,不装下拉
PA1022K 上拉 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 参考链接

ST DM00296349:STM32 USB 硬件与 PCB 设计指南 — 各系列「D+ 上拉 Embedded / 必须外接」的官方对照表(4.2 的核心依据)
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 是什么、什么情况下要调;
⑥ 直排/斜列矩阵:非规则布局(方向键区、旋钮位)如何折算矩阵。

官方对照:How a Keyboard Matrix Works · Debounce API

💾 六、烧录 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 / AT32ROM 里的系统 DFU,或原生 UF2(RP2040)不用,直接看 第七章 烧录固件
本章只解决「把 bootloader 装进去」这一件事。 装好之后的日常刷固件全在第七章(USB 为主);SWD 的其它用途——量产批量、无 bootloader 裸固件、救砖——统一放在 7.5,不和第 6 章混着讲。
6.1 为什么 F103 / E103 必须自己烧 Bootloader▼
出厂那层「系统引导」为什么不够用

F103 / E103 出厂带的是 ST / 极海写在 ROM 里的 System Bootloader。它有三个硬限制,放到键盘上条条要命:

  1. 必须把 BOOT0 拉高才进得去。键盘装进壳之后,这个跳线要么在壳里、要么根本没引出来——每更新一次固件就得拆一次壳。
  2. 只认整片 Flash,没有「应用区」。它眼里的起点永远是 0x08000000,不存在「只更新固件、保留前面那一段」的概念。
  3. 它在 ROM 里,改不了也删不掉。好处是永远不会丢;坏处是它不会配合你的固件做「跳进 DFU」这种动作。
换上 stm32duino 之后变成什么样

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 引导都进不去
一句话: F103 / E103 上这 8KB 不是「可选优化」,它是「这块板以后能不能用一根 USB 线升级」的分水岭。烧一次,之后一辈子走 USB。
6.2 怎么烧:四种方式(CubeProgrammer / ST-LINK Utility / st-flash / 串口)▼
先选一种方式
方式需要什么什么时候用
A · STM32CubeProgrammerST-Link + 一根 USB 线新手首选。图形界面,能看见「连接 → 选文件 → 填地址 → 开始」每一步,出问题也知道卡在哪
B · ST-LINK UtilityST-Link + 旧版工具老电脑、或 CubeProgrammer 装不上(要注册 ST 账号)。ST 已停止更新,但离线可用、体积小
C · st-flash 命令行ST-Link + QMK MSYS已经装了 QMK MSYS,想一条命令解决;适合反复烧同一块板
D · USB-TTL 串口USB-TTL 模块SWD 被锁、芯片翻新、手头没有 ST-Link 时的兜底方案
A / B / C 三种方式共用的接线

都是走 SWD 接口(SWDIO/SWCLK,即 PA13/PA14),四根线:

ST-Link开发板说明
SWDIO / DIOSWDIO / PA13数据线
SWCLK / CLKSWCLK / PA14时钟线
3.3V3.3V供电
GNDGND共地

找不到 SWD 排针就直接接 PA13/PA14。注意 ST-Link 的 3.3V 只用于给目标供电,别同时插着键盘的 USB 线。

ST-Link 版本: 建议 V2.1(比 V2 多虚拟串口和文件拖拽)。V2 基础款也可用,V3 太贵不划算。Win10/11 自带驱动,黄色感叹号才需搜 STSW-LINK009 装驱动。

方式 A — STM32CubeProgrammer(GUI,推荐新手)

这是 ST 现在主推的官方烧录工具,Windows / macOS / Linux 都有。下载要注册一个 ST 账号,也可在装 STM32CubeMX 时一起勾选安装。

先分清工具: CubeMX 本身只做引脚配置和生成代码,它不能把 .bin 写进芯片。真正干这件事的是同厂的 STM32CubeProgrammer——装 CubeMX 时勾上它,或者在 ST 官网单独下。

1. 连接芯片:

  1. 接好上面四根线,ST-Link 插电脑
  2. 打开 CubeProgrammer,右上角把 Port 选 SWD(默认就是)
  3. 点 Connect。成功时右侧信息栏会出现芯片型号、Flash 大小、UID
  4. 连不上就把 Reset mode 从 Normal 改成 Under reset 再点 Connect——这是 F103 最常见的连不上原因:芯片已经在跑用户程序、把 SWD 引脚挪作他用了,只有在复位期间才肯让调试器接管

2. 写入 bootloader:

  1. 左侧切到 Erasing & Programming 页
  2. File path 选 generic-none_bootloader.bin(约 7KB)
  3. Start address 必须填 0x08000000。.bin 是纯二进制、不带地址信息,工具不知道往哪写;.hex / .elf 自带地址,可以留空
  4. 勾上 Verify programming;Run after programming 可以不勾(bootloader 自己会跑)
  5. 点 Start Programming,等进度条走完,出现 File download complete / Download verified successfully 即成功

3. 验证:拔掉 ST-Link,用 USB 线插键盘。因为 0x08002000 之后还是空的,bootloader 会停在 DFU 模式等你刷固件——设备管理器里应该能看到 1EAF:0003 这个设备。看到它就说明 bootloader 活了。

方式 B — STM32 ST-LINK Utility(GUI,老工具)

ST 早已停止更新(最终版 v4.6.0,官网已归档),新项目建议直接用方式 A。但它在老机器上跑得动、不需要登录,手上已经装了的话完全可以继续用。

操作步骤:

  1. Target → Connect,下方日志出现 Device connected 和芯片型号即为连上。连不上同样在 Target → Settings 里把 Connect Under Reset 勾上再连
  2. File → Open File...,选 generic-none_bootloader.bin
  3. 弹出 Start address 对话框,填 0x08000000 → OK。这一步漏了或填错,后面就白烧
  4. Target → Program & Verify...,确认勾了 Verify after programming,点 Start
  5. 日志出现 Programmed successfully / Verification... OK 即完成

地址对话框是 ST-LINK Utility 最容易漏的一步:它不像 CubeProgrammer 把地址摆在界面上,而是打开文件时弹一次窗,关掉后就再也看不见当前写的是哪个地址。

方式 C — ST-Link V2 + QMK MSYS(命令行,一条命令)

1. 部署工具链:

# 在 QMK MSYS 中执行
pacman -S mingw-w64-x86_64-stlink mingw-w64-x86_64-openocd

2. 验证识别:

st-info --probe

正常返回 chipid: 0x041x。

无法识别: ① SWDIO 被禁用 → 改用串口方式;② BOOT0 未接地;③ 山寨/翻新芯片。

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! 即完成。

APM32E103 实测: 日志会报 libusb 警告(QMK 未明面支持 E103),但烧录正常,Flash written and verified! 即为成功。
方式 D — USB-TTL 串口烧录(最保险,SWD 锁死也能救)

适用于 SWD 被锁、芯片翻新、或无 ST-Link。

1. 硬件连接:

USB-TTL开发板说明
RXPA9 (TX)交叉
TXPA10 (RX)交叉
3.3V3.3V供电(⚠ 务必 3.3V)
GNDGND共地

BOOT0 跳线拨到 1(或接 3.3V),进入系统 Bootloader。

2. Flash Loader Demonstrator 操作:

  1. 选串口号 → Next,绿灯 Target is readable 正常
  2. APM32 首次: 可能提示锁定,先点「解锁」等十几秒(卡死就强制结束进程、重插 TTL、重开工具)
  3. Download to device → 选 generic-none_bootloader.bin → 勾 Global Erase + Verify after download → Next
  4. 绿色进度条 Download operation finished successfully 即完成

完成后断开 TTL,BOOT0 恢复 0(接地),插入电脑即可识别为 stm32duino 设备。

QMK 编译配置(刷完 Bootloader 后)
# rules.mk
MCU = STM32F103
BOOTLOADER = stm32duino

只写这两行就够。QMK 看到 BOOTLOADER = stm32duino 会自动把 BOARD 设为 STM32_F103_STM32DUINO、链接脚本换成 STM32F103x8_stm32duino 系列(固件从 0x08002000 开始)、DFU 参数设为 -d 1EAF:0003 -a 2。手写 BOARD 属于重复定义,写错了反而编译不过。

总结: stm32duino 只烧一次,之后更新固件一律走 USB,不要再碰 ST-Link 和串口。为什么这条必须守死,见 6.3。
6.3 刷完之后别混用:三个「DFU」其实是三条路▼
真实案例: 一位 F103 用户,先用 ST-Link 刷好 stm32duino bootloader,成功;接着没换 USB,继续用同一个 ST-Link 往 0x08000000 写了一份 QMK 固件。结果键盘彻底不动,QMK Toolbox 里再也看不到设备,插上电脑连「未知 USB 设备」都不弹。

这事看着像「手贱」,其实是被三套长得几乎一样的流程绕进去了。把 Flash 的地址摊开看,一眼就明白。

Flash 被切成两半

起始结束里面是什么谁来写
0x080000000x08001FFFstm32duino bootloader(8 KB)ST-Link / 串口,一辈子只写这一次
0x080020000x0800FFFFQMK 固件(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 DFU1EAF:00030x08002000(alt 2)F103 日常刷固件的唯一正确通道,QMK Toolbox 认的就是它
ST 系统 bootloader0483:DF110x08000000(整片)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 还在——它一旦被覆盖,这个标记就没人读了。

已经搞坏了怎么办

  1. 不要再试 USB,插多少次都不会有设备出现,bootloader 已经没了
  2. 回到 6.2,用 ST-Link 或串口重烧一次 bootloader 到 0x08000000
  3. 拔掉 ST-Link,插 USB,确认设备是 1EAF:0003
  4. 用 QMK Toolbox 刷固件。这一次,也只会有这一次用得上外部工具
一句话记住: ST-Link 只负责把 bootloader 送进 0x08000000,只干一次;之后所有固件都交给 USB 上的 1EAF:0003,它会把东西放到 0x08002000。两件事不要互相代劳。

顺带说一句:反过来「用 QMK Toolbox 刷 bootloader」也不行——Toolbox 里给 STM32 用的写入口就是 0483:DF11 那条,同样落到 0x08000000,但 bootloader 的 .bin 在 USB 上是刷不进去的(那时芯片还没跑 stm32duino,USB 根本不存在)。

6.4 ATmega32U4:同样要烧 Bootloader,但麻烦得多占位 · 待补▼

为什么它也要烧:ATmega32U4 出厂是空片——USB 控制器虽然在片内,但没有引导程序。不先烧 atmel-dfu(或 Caterina)进去,它就是一块没有 USB 的 8 位单片机,插上电脑什么都不会发生。

为什么说它麻烦(这才是重点):

  1. 通道不一样:AVR 走 ISP(SPI 六根线:MOSI / MISO / SCK / RESET / VCC / GND),和 STM32 那套 SWD 完全不通用,工具也另买(USBasp,或用另一块 Arduino 当 ISP)。
  2. 烧完 bootloader 还得配熔丝位(LFUSE / HFUSE / EFUSE):时钟源选外部晶振还是内部 RC、CKDIV8 是否 8 分频、BOOTRST 复位向量指向哪、BOOTSZ 划多大 boot 区。错一位就可能跑错频率(USB 直接不工作)或变砖,而熔丝改错常常只能上高压并行编程器才救得回来。
  3. 晶振必须对:QMK 的 AVR 全线按 16MHz 算(F_CPU),板子上贴 8MHz 就得连熔丝、F_CPU 一起改。
  4. 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 flashQMK 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
7.1 QMK Toolbox 图形化刷写(日常主力)▼
QMK Toolbox

下载 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,别点,先断电拨回 0
  • APM32 DFU(314B:0106)— APM32 芯片的出厂 DFU,正常
为什么这条要写在最前面: 两个名字只差几个字母,写入地址却差 8KB,后果是 bootloader 被覆盖、键盘彻底失联。详见 6.2。
它会拦下你:File ID 与设备不匹配

板子是 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 没被动过,板子也没砖——它是在保护你别把张家的户口本塞进李家。

固件里的「身份证」是哪来的
  1. rules.mk 的 BOOTLOADER 先翻译成 DFU_SUFFIX_ARGS(platforms/chibios/bootloader.mk):apm32-dfu → -v 314B -p 0106;stm32-dfu → -v 0483 -p DF11。
  2. 编译到 %.bin 那一步,builddefs/common_rules.mk 会跑 dfu-suffix -v 314B -p 0106 -a xx.bin,在 .bin 末尾追加 16 字节后缀(VID / PID / 版本 / CRC)。所以这个 .bin 里确实「写着」它是 APM32。
  3. 插上板子:Toolbox 按枚举到的 VID/PID 归类设备,再调用这个设备类硬编码的命令(Apm32DfuDevice.cs / Stm32DfuDevice.cs),形如 dfu-util -a 0 -d <VID:PID> -s 0x08000000:leave -D file.bin——这一步的 ID 取自设备,不读固件。
  4. dfu-util 写之前把文件后缀里的 VID/PID 和实际设备比对。dfu-suffix 手册原话:后缀就是给 host 工具「check that the device is matching」用的,而且不会被写进芯片。对不上就 errx() 退出。
rules.mk 的 BOOTLOADER后缀 VID:PIDToolbox 显示写入参数
stm32-dfu0483:DF11STM32 DFU-a 0 -s 0x08000000:leave
apm32-dfu314B:0106APM32 DFU-a 0 -s 0x08000000:leave
stm32duino1EAF:0003STM32Duino-a 2 -R(落在 0x08002000)
gd32v-dfu28E9:0189GD32V DFU-a 0 -s 0x08000000:leave
at32-dfu2E3C:DF11不显示(Toolbox 没有这个设备类)只能 qmk flash 或手敲 dfu-util
wb32-dfu无后缀WB32 DFUQMK 未定义 DFU_SUFFIX_ARGS,不会撞这个问题
撞上了怎么办
  1. 改配置重编译(正解)。 板上是什么芯片就写什么:真 STM32 用 stm32-dfu,APM32 用 apm32-dfu。重新 qmk compile,后缀自然跟着换。
  2. 应急:把后缀删掉再刷。 dfu-suffix --delete firmware.bin 之后 VID/PID 退回通配 0xFFFF,dfu-util 不再比对,可以写进去。代价是每次重编译都要再删一次,只能当一次性手段;想先看里面写了什么,用 dfu-suffix --check firmware.bin。
  3. 走 SWD 直写。 ST-LINK / CubeProgrammer 只搬二进制,根本不看 DFU 后缀,见 7.5。救急可以,同样别当日常流程。
补一句: 强刷进去(删后缀或走 SWD)固件本身是能跑的——stm32-dfu 和 apm32-dfu 在 QMK 里同属 BOOTLOADER_TYPE = stm32_dfu,链接地址、跳转逻辑完全一样,差的只是那串 VID/PID。但下次再刷同一个 .bin,同样的错还会再报一次:「能跑」不等于「配对」,该改的配置还得改。
7.2 命令行刷写(QMK CLI,用得偏少)▼

这条路不是主力:平时改个键,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)
刷写前要先让板子进 bootloader——命令行不会替你按 QK_BOOT。几秒内没找到设备就会报 No device found,重新让板子进 DFU 再跑一次即可。
7.3 UF2 拖 U 盘:RP2040 默认,STM32 也能(TinyUF2 / uf2boot)▼

这是一整类刷写方式,不是 RP2040 专属。共同点:板子以「U 盘」身份出现,固件是 .uf2,拖进去就刷完——没有驱动、没有工具、没有管理员权限问题,Windows / macOS / Linux 一个样。

A · RP2040:出厂就带,零配置

操作:按住 BOOTSEL 插 USB → 资源管理器里出现 RPI-RP2 盘 → 把 .uf2 拖进去 → 板子自动重启进固件。QMK 侧 BOOTLOADER = rp2040,产物就是 .uf2。

这是 RP2040 的默认方式:ROM 里自带这层引导,永远刷不坏,也永远不需要额外工具。

B · STM32F4:TinyUF2

TinyUF2 是 Adafruit 维护的一套 UF2 引导,覆盖部分 STM32F4 板。它的存在形式是先往芯片里装一份 TinyUF2(用 SWD 或 ROM DFU 装一次),之后这块板就变成「插上就是 U 盘」。

QMK 侧写 BOOTLOADER = tinyuf2,qmk compile 产出 .uf2,之后拖拽即可。前提是那块板真的装了 TinyUF2——没有它,.uf2 拖进去没有 U 盘可拖。

C · STM32F103:uf2boot

F103 也有一条 UF2 路线:BOOTLOADER = uf2boot(QMK 内部仍映射到 STM32_F103_STM32DUINO 板级,产物换成 .uf2)。同样需要先装对应的 UF2 引导,装法与 6.2 的 stm32duino 一样是「SWD/串口写一次」。

取舍:UF2 的优点是刷写体验最好;代价是引导占的 Flash 通常比 stm32duino 的 8KB 更大,且生态没有 stm32duino 那么普及。F103 上默认还是推荐 stm32duino + Toolbox。

怎么判断自己能不能用:插上板子(或按住 BOOTSEL / 对应键插上),看资源管理器里有没有多出一个小容量 U 盘(RPI-RP2 或其它名字)。有 → 直接用 UF2;没有 → 你这块板没装 UF2 引导,走 7.1 或 7.2。
7.4 进入 Bootloader 的三种通用方式▼

1. Bootmagic Reset: 默认矩阵 (0,0) 按键(通常左上角)。断开 USB,按住此键再插入即可。

2. 物理按钮: RP2040 的 BOOTSEL、部分 F103 开发板的 BOOT0 跳线/按钮。

3. Keycode QK_BOOT: 键位布局中绑定,按下即进入 Bootloader(无需插拔)。建议放不常用的层避免误触。

🔌 八、支持的功能

以下功能均列明硬件接线和 rules.mk / config.h / keymap.c 配置。完整文档见 QMK 官方文档。点击标题展开/折叠详细内容。

8.1 WS2812 RGB 矩阵▼
概述

最常见的可寻址 RGB LED,单线串联,QMK 内置完整灯效引擎(数十种预设动画)。

硬件接线: DIN → GPIO(串 100-470Ω),VDD → 5V(每颗全白 ~60mA,注意总电流),GND → 共地。建议 DIN 前加电阻、每颗旁并联 0.1μF 电容。

rules.mk
RGB_MATRIX_ENABLE = yes
RGB_MATRIX_DRIVER = ws2812              // 或 apa102 / is31fl3731 等
config.h
// 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
RP2040 注意: 使用 WS2812_PIO_USE_PIO1 可启用 PIO 驱动,CPU 零开销。
8.2 LED 指示灯▼
概述

QMK 支持为 Caps Lock / Num Lock / Scroll Lock / 层状态等设置独立 LED 指示灯。
硬件: LED 正极 → GPIO(串 220-1KΩ 限流电阻),负极 → GND。高电平点亮。

config.h
// 标准指示灯
#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=低电平点亮
keymap.c — 自定义层指示灯
bool led_update_user(led_t led_state) {
    writePin(B5, IS_LAYER_ON(1));  // Layer 1 亮灯
    return true;
}
8.3 I2C OLED 屏幕▼
概述

支持 SSD1306 驱动的 128x32 / 128x64 OLED,I2C 地址通常 0x3C。可显示当前层、按键状态、自定义动画等。
硬件: SCL → I2C 时钟,SDA → I2C 数据,VCC → 3.3V/5V,GND → 共地。建议 SCL/SDA 加 4.7KΩ 上拉。

rules.mk
OLED_ENABLE = yes
OLED_DRIVER = ssd1306
config.h
#define OLED_DISPLAY_128X64               // 或 OLED_DISPLAY_128X32
#define I2C_DRIVER I2CD1                   // STM32 示例
#define I2C1_SCL_PIN B6
#define I2C1_SDA_PIN B7
keymap.c — 自定义显示
bool oled_task_user(void) {
    oled_write_ln(get_u8_str(get_highest_layer(layer_state), ' '), false);
    return false;
}
8.4 I2C EEPROM▼
概述

外接 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 引脚开发板说明
SDAI2C SDASTM32: PB7 / RP2040: GP2
SCLI2C SCLSTM32: PB6 / RP2040: GP3
VCC3.3V / 5V24LC 系列供电范围 1.8-5.5V
GNDGND共地
WPGPIO(推荐)或 GND高电平禁止写入;建议接 GPIO 并定义 EXTERNAL_EEPROM_WP_PIN,驱动初始化会自动拉低使能写入,悬空可能因上拉进入写保护,导致初始化或写入失败
A0/A1/A2GND 或 VCC地址引脚,接地=0、接 VCC=1

建议 SCL/SDA 加 4.7KΩ 上拉;与 OLED 同挂一条总线时共用上拉即可。

I2C 地址

A0/A1/A2 三引脚组合出 8 个地址:全接地为 0x50,随引脚接 VCC 递增,全接 VCC 为 0x57。QMK 的 EXTERNAL_EEPROM_I2C_BASE_ADDRESS 按 8 位地址填写(7 位地址左移一位),默认值 0b10100000(= 0xA0)即对应 7 位地址 0x50。

芯片选择:24LC256(EEPROM) vs MB85RC256V(FRAM)

两芯片引脚兼容、容量同为 32KB、I2C 地址一致,PCB 可直接互换。MB85RC256V 是铁电存储器,写入无需等待、寿命长,适合频繁读写配置 / 宏 / via 动态改键的场景,做动态存储时优先推荐。

对比项24LC256(EEPROM)MB85RC256V(FRAM)
写入等待约 5ms 写周期,期间不可访问无需等待,写入立即生效
擦写寿命约 100 万次约 100 亿次(10¹⁰)
页写限制64B/页,跨页回绕无页概念,可任意逐字节写
接口 / 地址I2C,0x50-0x57I2C,0x50-0x57(引脚兼容)
价格更便宜稍贵
QMK 配置方式

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 系列。

QMK 官方预定义芯片列表
模块 / 芯片预定义宏容量页大小写入时间地址宽度
24LC256 EEPROMEEPROM_I2C_24LC25632KB64B5ms16 位
MB85RC256V FRAMEEPROM_I2C_MB85RC256V32KB—(无页)—(无等待)16 位
24LC128 EEPROMEEPROM_I2C_24LC12816KB64B5ms16 位
24LC64 EEPROMEEPROM_I2C_24LC648KB32B5ms8 位
24LC32A EEPROMEEPROM_I2C_24LC32A4KB16B5ms8 位
CAT24C512EEPROM_I2C_CAT24C51264KB64B5ms16 位
RM24C512CEEPROM_I2C_RM24C512C64KB64B5ms16 位

完整文档:QMK Config Options · 源文件:drivers/eeprom/eeprom_i2c.h

rules.mk
EEPROM_DRIVER = i2c
keymap.c — 保存 / 读取用户配置
// 地址规划: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 会被当成有效数据。
8.5 ADC 滑动电位器▼
概述

通过 ADC 读取滑动电位器(或旋转电位器)的模拟电压,可实现连续参数控制,如音量推子、亮度调节、导播台推子等。
硬件: 电位器两端接 VCC 和 GND,中间抽头接 ADC GPIO。推荐 10KΩ 线性电位器(B10K)。

精度差异: STM32F103 默认 10-bit(0-1023),但可配置为 12-bit(0-4095),精度更高。RP2040 ADC 固定 12-bit(0-4095,GPIO26-29)。Atmega32u4 为 10-bit。下面的实战示例按 12-bit 处理。

Part 1:基础 ADC 功能调用

启用 ADC 驱动(三步缺一不可)

QMK 的 ADC 读取通过 analog 驱动实现。STM32 平台需要同时修改三个文件才能正确启用 ADC,少一步都读不到数据。

rules.mk
ANALOG_DRIVER_REQUIRED = yes      // 启用 ADC 驱动
halconf.h — 启用 HAL ADC
#pragma once
#include_next <halconf.h>

// 启用 ADC HAL 驱动
#undef HAL_USE_ADC
#define HAL_USE_ADC TRUE
mcuconf.h — 启用 ADC1 外设(STM32)
#pragma once
#include_next <mcuconf.h>

// 启用 ADC1 外设
#undef STM32_ADC_USE_ADC1
#define STM32_ADC_USE_ADC1 TRUE
config.h — ADC 引脚与分辨率
// STM32F103 可设置 12-bit 分辨率(默认 10-bit),不用白不用
#define ADC_RESOLUTION ADC_CFGR1_RES_12BIT

// ADC 引脚定义(analogReadPin 直接用引脚名读取,无需额外配置)
// 在代码中直接用 A0, A1, B0 等引脚名调用 analogReadPin()
keymap.c — 读取 ADC 原始值
// 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 原始值会有抖动,直接使用会导致输出频繁跳变。实际应用中需要做三件事:

  1. 滤波:滑动平均滤波,消除瞬时抖动
  2. 映射:把 12-bit (0-4095) 映射到 7-bit MIDI (0-127),用右移 5 位比乘除法更高效
  3. 防抖:设置变化阈值,只有超过阈值才发送;同时处理端点到达
// 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);
进阶:16x 过采样 + 7-bit 死区(满精度定稿方案)

滑动平均滤波是跨时间取平均,推子快速推动时输出滞后、手感发"肉"。更优方案:同一扫描周期内对同一通道连续采样 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 通道 MIDI 推子(结构体管理)

以下代码来自实际 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();
}
实战经验(来自 10 通道固件):
  • 三步启用 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 测试。
8.6 机械编码器旋钮▼
概述

机械旋转编码器(EC11 系列)通过两路脉冲检测旋转方向和步数,常用于音量调节、菜单滚动。QMK 支持最多 4 个编码器。
硬件: A → GPIO_A,B → GPIO_B,C → GND。A/B 需启用内部上拉。按下中键作为一个独立按键。

rules.mk
ENCODER_ENABLE = yes
config.h
#define ENCODERS_PAD_A { B12 }            // 第一个编码器的 A/B 引脚
#define ENCODERS_PAD_B { B13 }
#define ENCODER_RESOLUTION 4               // 每格 4 步(EC11 默认)
keymap.c — 旋转处理
bool encoder_update_user(uint8_t index, bool clockwise) {
    if (clockwise) {
        tap_code(KC_VOLU);              // 顺时针:音量+
    } else {
        tap_code(KC_VOLD);              // 逆时针:音量-
    }
    return false;
}
8.7 MIDI 驱动▼
概述

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 等)自由绑定到任意矩阵按键或机械编码器旋钮,实现导播场景下的:

  • 按键:触发切换、播放/暂停、推杆上推/下推等离散动作
  • 旋钮/推子:连续调节音量、推子位置、过渡进度、亮度等参数
rules.mk
MIDI_ENABLE = yes
config.h
// 定义高级模式,启用 midi_send_* API(自定义信号必需)
#define MIDI_ADVANCED

// 可选:调整发送力度等默认参数
#define MIDI_VELOCITY_MIN 0             // 最小力度(默认 0)
#define MIDI_VELOCITY_MAX 127           // 最大力度(默认 127)
#define MIDI_VELOCITY_DEFAULT 100       // 默认力度(默认 127)
案例 1:单个 MIDI 按键(导播切换镜头)

按下发送 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;
}
案例 2:一组 MIDI 按键(导播 8 路通道切换)

用循环批量定义 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;
}
案例 3:单个 MIDI 推子(旋钮控制音量)

机械编码器旋转时连续发送 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:一组 MIDI 推子(4 路音量推子组)

用数组管理多个推子的当前值,编码器索引对应不同通道。适合导播台的多路混音推子组。

// 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],每个编码器同时控制多个参数。

参考: 完整 MIDI 功能文档见 QMK 官方 MIDI 文档。MIDI 功能需要占用 2 个 USB 端点,V-USB 控制器(如部分 AVR 方案)可能无法正常工作。
注意事项:
  • 通道索引: 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)或弯音值。
8.8 分体键盘(QMK 有线分体)框架 · 正文待补▼

本节只讲 QMK 官方支持的有线分体方案,将覆盖:
① 基本架构:左右手各一块 MCU,通过分体通信线交换矩阵状态,主手负责 USB 输出;
② 连接方式:QMK 串行分体通信、常用 TRRS 连接及其接线注意事项;
③ 固件配置:启用 split、设置分体通信引脚、左右手识别与主从分工;
④ 刷写与调试:两块 MCU 分别刷写的方法,以及单侧按键无反应、左右识别颠倒时的排查;
⑤ 外设协同:分体状态下 RGB、OLED、编码器等功能的同步与资源占用。

官方对照:QMK Split Keyboard

8.9 全键无冲(NKRO)▼
先给结论:QMK 默认是 6KRO,而且有两层开关

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 顶层:

keyboard.json —— 完整示例(以 STM32F411 + APM32 DFU 为例)
{
  "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 里只放矩阵),那就按下面写。两套体系选一套,别混着来——原因见下一个标题。

rules.mk —— 只管「编不编进来」
# 板级定义
MCU = STM32F411
BOARD = GENERIC_STM32_F411XE
MCU_LDSCRIPT = STM32F411xE
BOOTLOADER = apm32-dfu

# 编译层开关:等价于 keyboard.json 的 "features": {"nkro": true}
NKRO_ENABLE = yes
config.h —— 只管「开机开不开」
// 等价于 keyboard.json 的 "host": {"default": {"nkro": true}}
// 注意:这一行只能写在 config.h,不能写在 rules.mk(rules.mk 只认变量,不认 C 宏)
#define NKRO_DEFAULT_ON true
F103 / E103 用户不用改任何东西。NKRO 是 USB 描述符和报告格式的事,与主控型号无关。F103 的 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.mkFeature nkro is specified in both info.json (…) and rules.mk (…). The rules.mk value wins.
任何 JSON 字段
+ 对应 rules.mk 变量
rules.mkX in rules.mk is overwriting Y in info.json
任何 JSON 字段
+ 对应 config.h 宏
config.hX 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 是空白的,此时默认值一定生效,可用来验证「配置本身对不对」
清 EEPROM 会一起清掉:层状态、RGB 亮度与灯效、NKRO 开关、磁轴校准值、EE_HANDS 左右手标记等全部运行时配置。清完需要重新按需设置一次——所以别把它当成日常操作,只在「改了默认值却不生效」时用。

验证:怎么确认 NKRO 真的编进去了

  1. 看编译输出:qmk compile -kb <你的键盘> -km <键位> 成功之后执行 qmk info -kb <你的键盘>,在 Features 一段里能看到 nkro,说明 features.nkro 已生效。
  2. 按键实测:烧进去后打开任意在线按键测试页(keyboard tester),同时按住 10 个以上字母键,全部亮起 = NKRO 生效;只亮 6 个、其余闪烁或无反应 = 还是 6KRO。
  3. 切换实测:按一次 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 分支)
这条不是猜的,QMK 自己的 FAQ 就是这么写的:「In some situations NKRO doesn't work and you will need to switch to 6KRO mode, in particular when you are in BIOS.」而更早的 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_ENABLE3刚好用满,可用
键盘 + 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,刷完还是 6KROEEPROM 里存着上次手动切回 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.17.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 配置 · 矩阵硬件设计
官方排错文档: FAQ Build · FAQ Debug · FAQ Keymap。

📦 十、本站资源下载汇总

自建链接脚本(QMK 官方没有,本站补写,文件头含自写标注)

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 官方文档 — 本指南各章末尾均附对应官方页面,便于对照深挖