⌨ QMK 键盘开发指南
汇总基于 QMK 固件开发自定义键盘的核心知识:主控芯片选型、Bootloader 与刷写工具链、RGB / OLED / 编码器 / MIDI 等常用外设的硬件接线与固件配置。面向键盘 DIY 爱好者,基于 QMK 官方文档与实践经验整理。
🧠 一、主控芯片选型对比
以下对比涵盖 STM32 原厂及国产 APM32 替代型号。关键指标:内核架构、主频、Flash/SRAM、ADC、USB 规格、封装及 QMK 默认晶振频率。
选型对比总表(22 款芯片)
表格横向可滚动。✔ = QMK 默认支持;◐ = 需适配。
| 型号 | 品牌 | 内核架构 | 主频 | Flash | SRAM | ADC 位/通道 | USB | 封装 | 默认晶振频率* | 支持 / 备注 |
|---|---|---|---|---|---|---|---|---|---|---|
| APM32F072RBT6 | 极海 | Cortex-M0 | 48 MHz | 128 KB | 16 KB | 12-bit / 16ch | FS | LQFP-64 | 内置 48MHz* | ✔ * |
| STM32F103CBT6 | ST | Cortex-M3 | 72 MHz | 128 KB | 20 KB | 12-bit / 10ch | FS | LQFP-48 | 8 MHz | ✔ * |
| APM32E103CET6 | 极海 | Cortex-M3 | 72 MHz* | 512 KB* | 128 KB* | 12-bit / 21ch | FS | LQFP-48 | 8 MHz | ◐ * |
| STM32F103RGT6 | ST | Cortex-M3 | 72 MHz | 1 MB | 96 KB | 12-bit / 16ch | FS | LQFP-64 | 8 MHz | ✔ * |
| APM32E103RET6 | 极海 | Cortex-M3 | 72 MHz* | 512 KB* | 128 KB* | 12-bit / 21ch | FS | LQFP-64 | 8 MHz | ◐ * |
| STM32F303CCU6 | ST | Cortex-M4F | 72 MHz | 256 KB | 40 KB | 12-bit / 15ch | FS | UFQFPN-48 | 8 MHz | ✔ * |
| STM32F303RET6 | ST | Cortex-M4F | 72 MHz | 512 KB | 64 KB | 12-bit / 18ch | FS | LQFP-64 | 8 MHz | ✔ * |
| STM32F401CEU6 | ST | Cortex-M4F | 84 MHz | 256 KB | 96 KB | 12-bit / 10ch | FS | UFQFPN-48 | 8 MHz | ✔ * |
| STM32F401RET6 | ST | Cortex-M4F | 84 MHz | 512 KB | 96 KB | 12-bit / 16ch | FS | LQFP-64 | 8 MHz | ✔ * |
| APM32F402RBT6 | 极海 | Cortex-M4F | 120 MHz | 128 KB* | 32 KB | 12-bit / 21ch | FS | LQFP-64 | 8 MHz | ✔ * |
| APM32F405RGT6 | 极海 | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 12 MHz* | ✔ * |
| STM32F405RGT6 | ST | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 12 MHz* | ✔ * |
| APM32F407RGT6 | 极海 | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 8 MHz | ✔ * |
| STM32F407VGT6 | ST | Cortex-M4F | 168 MHz | 1 MB | 192 KB | 12-bit / 24ch | FS+HS* | LQFP-100 | 8 MHz | ✔ * |
| STM32F411CEU6 | ST | Cortex-M4F | 96 MHz | 512 KB | 128 KB | 12-bit / 10ch | FS | UFQFPN-48 | 8 MHz | ✔ * |
| APM32F411CET6 | 极海 | Cortex-M4F | 120 MHz* | 512 KB | 128 KB | 12-bit / 16ch | FS | LQFP-48 | 8 MHz | ✔ * |
| STM32F411RET6 | ST | Cortex-M4F | 96 MHz | 512 KB | 128 KB | 12-bit / 16ch | FS | LQFP-64 | 8 MHz | ✔ * |
| APM32F446RET6 | 极海 | Cortex-M4F | 180 MHz | 512 KB | 128 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 8 MHz | ✔ * |
| STM32F446RET6 | ST | Cortex-M4F | 180 MHz | 512 KB | 128 KB | 12-bit / 24ch | FS+HS* | LQFP-64 | 8 MHz | ✔ * |
| STM32H723VGT6 | ST | Cortex-M7 | 550 MHz | 1 MB | 564 KB | 12-bit / 20ch | FS+HS* | LQFP-100 | 25 MHz* | ✔ * |
| RP2040 | 树莓派 | 双核 Cortex-M0+ | 133 MHz | 2 MB* | 264 KB | 12-bit / 4ch | FS | QFN-56 | 12 MHz | ✔ * |
| ATMEGA32U4 | Microchip | AVR 8-bit | 16 MHz | 32 KB* | 2.5 KB | 10-bit / 12ch | FS | TQFP-44 | 16 MHz | ✔ * |
推荐 & 建议
QMK 默认晶振适配: 各芯片 QMK 默认按固定晶振频率计算时钟(F405 按 12MHz、H723 按 25MHz),选板时留意即可,并非优劣之分。
国产 APM32 实测: APM32F072 / F4xx 全系列直接可用;APM32E103 已验证可复用 F103 配置,使用完整 Flash/SRAM 时需匹配对应链接脚本。
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)、STM32F411CEU6/RET6(M4F @ 96MHz,256KB / 128KB)。注:F411*E(如 RET6)为 512KB Flash / 128KB SRAM。参考价 ~¥20-30。国产替代 APM32F411CET6 直接兼容。
- Cortex-M4F 带 FPU,性能充裕,复杂固件毫无压力
- F411 版:CEU6/C: 256KB Flash,RET6/E: 512KB Flash,SRAM 均为 128KB,RGB+OLED+宏全开不紧张
- 内置 USB FS OTG,无需外挂 PHY
- QMK 官方支持完善,Black Pill 开发板便宜好买
- 开发板晶振规格不统一:部分廉价板使用 8MHz,部分使用 25MHz 等其他频率,买错板子无法直接用,需核对 QMK 默认 8MHz 配置
- CEU6 为 UFQFPN-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,模拟精度一般
⚙️ 快速配置向导
下拉选择实际型号后,页面会给出对应的 rules.mk 设置、现有配置文件、是否需要新建文件,以及新建文件的目录和内容。
型号写法遵循“系列 + 封装代号 + 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 才对应不同容量等级。
| 实际型号 | 板载 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 晶振 |
表中的“基本完整”表示 QMK 已有对应的板级配置、MCU 配置和链接脚本,但仍需确认实际开发板的晶振、USB 电路和引脚定义。
F401E / F411C:对应链接脚本虽然存在,但默认选择可能不是实际容量,建议在键盘的 rules.mk 中显式指定 MCU_LDSCRIPT。
F405 / F407:两者共用部分 F4 MCU 配置,但常见板子的晶振不同。F405 配置按 12MHz 晶振计算 168MHz;F407 配置按 8MHz 晶振计算 168MHz,不能直接互换。
CCM RAM:F405/F407 的 CCM RAM 虽然在链接脚本中定义,但不是普通 SRAM,不能默认当作所有 QMK 全局内存使用,必要时需要使用对应段属性。
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
F4 在 rules.mk 中显式选择容量
# 例如 STM32F401E:512KB Flash / 96KB SRAM
MCU = STM32F401
MCU_LDSCRIPT = STM32F401xE
# 例如 STM32F411C:256KB Flash / 128KB SRAM
MCU = STM32F411
MCU_LDSCRIPT = STM32F411xC
💾 二、烧录 Bootloader
仅 F103 / E103 需要手动烧录 Bootloader。其他芯片出厂自带(AVR 带 DFU、STM32 内置系统 DFU、RP2040 原生 UF2),无需额外操作。点击标题展开/折叠详细内容。
F103 出厂 Bootloader 不支持 USB 烧录。刷入 stm32duino(Maple Bootloader)后,后续可直接通过 USB 用 QMK Toolbox 一键刷写固件,无需额外硬件。APM32E103 同样适用,实测可用。
// rules.mk
MCU = STM32F103 // 或 STM32F103 对应容量型号
BOOTLOADER = stm32duino
BOARD = STM32_F103_STM32DUINO
通过 SWD 接口(SWDIO/SWCLK,即 PA13/PA14)烧录,只需两根线。
1. 硬件连接:
| ST-Link | 开发板 | 说明 |
|---|---|---|
| SWDIO / DIO | SWDIO / PA13 | 数据线 |
| SWCLK / CLK | SWCLK / PA14 | 时钟线 |
| 3.3V | 3.3V | 供电 |
| GND | GND | 共地 |
找不到 SWD 排针就直接接 PA13/PA14。
2. ST-Link 版本: 建议 V2.1(比 V2 多虚拟串口和文件拖拽)。V2 基础款也可用,V3 太贵不划算。Win10/11 自带驱动,黄色感叹号才需搜 STSW-LINK009 装驱动。
3. 部署工具链:
# 在 QMK MSYS 中执行
pacman -S mingw-w64-x86_64-stlink mingw-w64-x86_64-openocd
4. 验证识别:
st-info --probe
正常返回 chipid: 0x041x。
5. 烧录:
st-flash --reset --format binary write <路径> 0x08000000
# 示例:
st-flash --reset --format binary write E:/QMKKeyboard/F103bootloader.bin 0x08000000
文件为 generic-none_bootloader.bin(约 7KB),在 QMK 仓库 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 设备。
📥 三、烧录固件
Bootloader 就位后(或芯片自带),即可烧录 QMK 固件。所有芯片通用。点击标题展开/折叠详细内容。
下载 QMK Toolbox → 打开 → 选 .hex / .bin → 键盘进 Bootloader → 点击 Flash → 完成。
支持: Atmel DFU、Caterina、STM32 DFU、stm32duino、AVR ISP 等。
# 编译并自动刷写
qmk flash -kb my_keyboard -km my_keymap
# 仅编译不刷写
qmk compile -kb my_keyboard -km my_keymap
# 指定 Bootloader 类型
qmk flash -kb my_keyboard -km my_keymap -bl dfu-util
# 常用 -bl:avrdude(Atmel DFU/Caterina) / dfu-util(STM32) / uf2conv(RP2040/TinyUF2)
操作: 按住 BOOTSEL 插入 USB → 出现 RPI-RP2 U 盘 → 拖 .uf2 固件进去 → 自动重启 ✅
优点: 无需安装任何驱动或工具,跨平台开箱即用。
1. Bootmagic Reset: 默认矩阵 (0,0) 按键(通常左上角)。断开 USB,按住此键再插入即可。
2. 物理按钮: RP2040 的 BOOTSEL、部分 F103 开发板的 BOOT0 跳线/按钮。
3. Keycode QK_BOOT: 键位布局中绑定,按下即进入 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);
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)或弯音值。