m65 — 5×13 LEGO Ortholinear Keyboard

The m65 is a 5-row, 13-column ortholinear mechanical keyboard designed to accommodate all standard keycaps from an ISO layout.
In its later iterations (Rev 7–Rev 11), it departs from a traditional direct-pin matrix and uses the tiny Seeed Studio XIAO RP2040 or Seeed Studio XIAO nRF52840 BLE (SAMD21 may also work). Because the XIAO only exposes 11 GPIO pins, rows are scanned via a 74HC589 (or 74HC165) input shift register while columns are driven by two chained 74HC595 output shift registers over hardware SPI (sharing clock and latch pins), leaving pins free for an I²C OLED / SPI e-ink screen and a clickable rotary encoder (making Rev 9–11 technically a 14-column matrix, later spun off into the m66 line).
Seeed Studio Sponsorship
For **Rev 7**, Seeed Studio kindly sponsored the 5×13 version with PCBs and XIAO microcontrollers as part of the [Seeed Fusion DIY XIAO Mechanical Keyboard Contest](https://www.seeedstudio.com/seeed-fusion-diy-xiao-mechanical-keyboard-contest.html) and their [Fusion PCB Assembly service](https://www.seeedstudio.com/pcb-assembly.html).Status: Rev 11 Done and Tested
* [x] Gerbers designed
* [x] Firmware (QMK & ZMK)
* [ ] Breadboard tested
* [x] Gerbers printed
* [x] Board tested
Revision History
* **Rev 11**: XIAO RP2040 with 3 shift registers (2× `74HC595` + 1× `74HC589`), OLED / e-ink (`SSD1680` WeAct), and push rotary encoder with QMK (actively exploring ZMK support for RP2040 and nRF52840)
* **Rev 10**: XIAO RP2040 with 3 shift registers (2× `74HC595` + 1× `74HC589`), OLED, and push rotary encoder with QMK
* **Rev 9**: XIAO RP2040 with 3 shift registers (2× `74HC595` + 1× `74HC165`), OLED, and push rotary encoder with QMK
* **Rev 7/8**: XIAO RP2040 and nRF52840 with 2× `74HC595` shift registers, rotary encoder, no OLED (both QMK and ZMK firmware supported)
* **Rev 5/6**: `RP2040` support (Raspberry Pi Pico, WeAct RP2040, and compatible clones), non-push encoder, OLED
* **Rev 4**: `STM32F401`/`F411` BlackPill, non-push encoder, OLED
* **Rev 3**: `STM32F401`/`F411` BlackPill, non-push encoder
* **Rev 2**: `GD32F303` aka [BluePill Plus](https://github.com/WeActStudio/BluePill-Plus) (USB-C `F103` equivalent)
* **Rev 1**: `STM32F103` / `APM32F103` or `STM32F303`
BOM
Microcontrollers
MCU Selection Guide
Using off-the-shelf development boards makes DIY keyboard design much simpler because all high-frequency crystal and USB protection circuitry lives on the MCU daughterboard. While I have built handwired boards with AVR (`ATmega32U4`), I strongly advise against using AVR for new designs.
* **Rev 1**: [`STM32F103C8T6` BluePill](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill.html) or [RobotDyn `STM32F303CCT6` BlackPill](https://stm32-base.org/boards/STM32F303CCT6-RobotDyn-Black-Pill.html) *(do not confuse with the WeAct F401/F411 BlackPill)*. Micro-USB; requires flashing an `stm32duino` bootloader.
* **Rev 2**: `GD32F303` [BluePill Plus](https://github.com/WeActStudio/BluePill-Plus) with USB Type-C.
* **Rev 3 / Rev 4**: WeAct [`STM32F401`/`F411` MiniSTM32F4x BlackPill](https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x) with USB Type-C.
* **Rev 5 / Rev 6**: `RP2040` ([Raspberry Pi Pico](https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html), [Waveshare RP2040-Plus](https://www.waveshare.com/wiki/RP2040-Plus), or USB-C compatible clones — verify pinout compatibility).
* **Rev 7 / Rev 9 / Rev 10 / Rev 11**: [Seeed Studio XIAO RP2040](https://wiki.seeedstudio.com/XIAO-RP2040/) and [Seeed Studio XIAO BLE nRF52840](https://wiki.seeedstudio.com/XIAO_BLE/). Ultra-compact footprint using SPI shift registers for matrix scanning.
* **Need more pins?**: Consider the [WeAct `STM32G431` CoreBoard](https://github.com/WeActStudio/WeActStudio.STM32G431CoreBoard), already supported in QMK.
Component Lists by Revision
Rev 11
Revision 11 BOM
* **LEGO Case**: ~46× 1×2 or 1×4 plates (use full bricks if housing a battery), 1× 16×32 double-sided baseplate or 2× 16×16 plates (gray compatible plates minimize PCB flexing), optional 4× 1×1 tiles/eyes and 4× 2×2 corner plates
* **Diodes**: 66× `1N4148` signal diodes (DO-35 through-hole or SOD-123 SMD)
* **Resistors**: 5× 10 kΩ (THT or SMD 1206) and 1× 5.1 kΩ (THT or SMD 1206)
* **MCU**: Seeed Studio XIAO RP2040 (or nRF52840) + recommended female socket headers
* **Shift Registers**: 2× `74HC595` (SOIC-16 SMD) and 1× `74HC589` (SOIC-16 SMD)
* **MOSFET**: 1× `2N7002` FET (SOT-23 SMD)
* **Capacitors (optional)**: 3× 100 nF decoupling capacitors (THT, SMD, or integrated socket)
* **Battery / Wireless (optional)**: Pogo pins, LiPo battery, 2-pin PH 2.0 battery socket, and 1P2T 4 mm micro slide switch
* **Encoder**: Clickable EC11 rotary encoder (Bourns 24-pulse incremental encoder with 6 mm flat shaft)
* **Displays (optional)**:
* 128×32 I²C OLED (4-pin) + 1×4 DIL socket
* WeAct SSD1680 e-ink module + 2×4 header and socket
Rev 10
Revision 10 BOM (Superseded by Rev 11)
* Identical to **Rev 11** above, with 128×32 I²C OLED (4-pin) + 1×4 DIL socket (prior to the dedicated 2×4 e-ink header added in Rev 11).
Rev 9
Revision 9 BOM
* **LEGO Case**: 16×32 double-sided plate (or 2× 16×16) + ~46× 1×2 or 1×4 plates
* **Diodes**: 66× `1N4148` signal diodes (DO-35 or SOD-123)
* **Resistors**: 5× 10 kΩ THT resistors
* **MCU**: Seeed Studio XIAO RP2040 (or nRF52840) + female headers
* **Shift Registers**: 2× `74HC595` (DIP-16 THT + sockets) and 1× `74HC165` (DIP-16 THT + socket)
* **Capacitors (optional)**: 3× 100 nF decoupling capacitors
* **Battery / Encoder**: Pogo pins, LiPo battery, 2-pin PH 2.0 socket, 1P2T 4 mm micro switch, EC11 clickable rotary encoder
Rev 7/8
Revision 7/8 BOM
* **LEGO Case**: 16×32 double-sided plate (or 2× 16×16) + ~46× 1×2 or 1×4 plates
* **Diodes**: 65× `1N4148` signal diodes (DO-35 or SOD-123)
* **MCU**: Seeed Studio XIAO RP2040 or XIAO nRF52840 BLE + female headers
* **Shift Registers**: 2× `74HC595` (DIP-16 THT + sockets)
* **Capacitors (optional)**: 2× 100 nF decoupling capacitors
* **Encoder & Battery**: EC11 rotary encoder, optional pogo pins and LiPo battery
Rev 5/6
Revision 5/6 BOM
* **LEGO Case**: 16×32 double-sided plate + ~46× 1×2 or 1×4 plates
* **Diodes**: 65× `1N4148` signal diodes (DO-35 or SOD-123)
* **MCU**: Raspberry Pi Pico (`RP2040`), WeAct RP2040, Teenstar, or compatible clone + 2×20 round pin headers & DIL sockets
* **LEDs & Resistors**: 2× LEDs + 2× resistors (`R1`, `R2` — 510 Ω works well for green/yellow LEDs; 1 kΩ for white LEDs)
* **Switches & Peripherals**: 65× 5-pin PCB-mount MX switches, EC11 rotary encoder, optional 128×32 I²C OLED (with 1×4 DIL socket), optional 3-pin horizontal JST header for LED strip
Rev 4
Revision 4 BOM (and Rev 1–3)
* **LEGO Case**: 16×32 double-sided plate + ~46× 1×2 or 1×4 plates
* **Diodes**: 65× `1N4148` signal diodes (DO-35 or SOD-123)
* **MCU**: WeAct `STM32F401` or `STM32F411` BlackPill + 2×20 round pin headers and 40-way DIL socket (15.24 mm)
* **Resistors & LEDs**:
* `R1`, `R2`: 2× 510 Ω (green/yellow) or 1 kΩ (white) + 2× indicator LEDs
* `R3`: 1× 100 kΩ pull-up resistor (ensures reliable `STM32F4x1` bootloader entry)
* **Switches & Peripherals**: 65× 5-pin PCB-mount switches, EC11 rotary encoder, 128×32 I²C OLED (with 1×4 socket), optional 3-pin horizontal JST header for LED strip
* *(For Rev 1–3, use the same BOM as Rev 4 with the corresponding MCU module.)*
Case

Assembly
Follow the illustrated Assembly Guide for soldering diodes, resistors, sockets, and switches.
Thickness Profiles
Depending on how many intermediate LEGO layers you stack, the enclosure can be built ultra-slim or deep enough for JST headers and acoustic foam:
-

8.0 mm — Slim Profile
1× baseplate (single or double-sided, flat or angled) + 1× intermediate plate (3.2 mm) + PCB (1.6 mm). Leaves room for a thin sheet of sound-dampening foam.
-

11.2 mm — Dual-Plate Profile
1× double-sided baseplate + 2× intermediate plates + PCB. Fits thicker foam or a hand-soldered LED strip (without a JST connector).
-

14.4 mm — Full Brick Profile
1× double-sided baseplate + 1× full LEGO brick + PCB. Provides clearance for a 3-pin JST header or LiPo battery.
3D Renders
Rev 11

Rev 10

Rev 9

Rev 7/8

Rev 5/6

Rev 4

Rev 2

Rev 1

Schematics
Rev 11

Rev 10

Rev 9

Rev 7

Rev 5

Rev 4

PCBs
Rev 11

Rev 10

Rev 9

Rev 7/8

Rev 5/6

Rev 4

Rev 3

Rev 2

Rev 1

Gerbers & KiCad Source
Production-ready Gerbers for JLCPCB:
Full KiCad project files are available in m-lego/m65 on GitLab. Note that the default branch HEAD is always work-in-progress:
KiCad Checkout
Rev 11
git clone https://gitlab.com/m-lego/m65.git
cd m65
git checkout revision11
Rev 10
git clone https://gitlab.com/m-lego/m65.git
cd m65
git checkout revision10
Rev 9
git clone https://gitlab.com/m-lego/m65.git
cd m65
git checkout revision9
Rev 7
git clone https://gitlab.com/m-lego/m65.git
cd m65
git checkout revision7
Rev 5/6
git clone https://gitlab.com/m-lego/m65.git
cd m65
git checkout revision5
Rev 4
git clone https://gitlab.com/m-lego/m65.git
cd m65
git checkout revision4
Firmware
Both QMK and ZMK are supported. You can download prebuilt .uf2 binaries below or build from source.
ZMK
Revisions 7, 5, and 4 support ZMK:
- firmware/mlego5x13_rev7-seeeduino_xiao_ble-zmk.uf2
- firmware/mlego5x13_rev7-seeeduino_xiao_rp2040-zmk.uf2
- firmware/mlego5x13_rev4-blackpill_f401ce-zmk.uf2
- firmware/mlego5x13_rev5-rpi_pico-zmk.uf2
Latest automated ZMK builds are available in GitHub Actions (mlego-zmk) under the Build mlego workflow artifacts.
ZMK Shield Sources:
- Main
mlego-zmkRepository - Rev 4 —
STM32F401BlackPill Shield - Rev 5 — Raspberry Pi Pico
RP2040Shield - Rev 7/8 — Seeed XIAO BLE (Sense) Shield
- Rev 7 — Seeed XIAO
RP2040Shield
QMK
- firmware/mlego_m65_rev11_default_knob.uf2
- firmware/mlego_m65_rev11_uk_knob.uf2
- firmware/mlego_m65_rev11_uk_knob_eink.uf2
- firmware/mlego_m65_rev10_default_knob.uf2
- firmware/mlego_m65_rev10_uk_knob.uf2
- firmware/mlego_m65_rev9_default_knob.uf2
- firmware/mlego_m65_rev9_uk_knob.uf2
- firmware/mlego_m65_rev7_default.uf2
- firmware/mlego_m65_rev7_uk.uf2
- firmware/mlego_m65_rev6_default.uf2
- firmware/mlego_m65_rev6_uk.uf2
- firmware/mlego_m65_rev5_default.uf2
- firmware/mlego_m65_rev5_uk.uf2
- firmware/mlego_m65_rev4_default.uf2
- firmware/mlego_m65_rev4_uk.uf2
- firmware/mlego_m65_rev3_default.uf2
- firmware/mlego_m65_rev3_uk.uf2
Latest automated QMK builds can be downloaded from GitHub Actions.
QMK Build Instructions
Rev 11
# OLED version
git clone --recurse-submodules -b mlego_dev https://github.com/alinelena/qmk_firmware.git qmk-alin
cd qmk-alin
qmk compile -kb mlego/m65/rev11 -km uk_knob
qmk compile -kb mlego/m65/rev11 -km default_knob
# E-Ink version
git clone --recurse-submodules -b mlego_eink https://github.com/alinelena/qmk_firmware.git qmk-alin
cd qmk-alin
qmk compile -kb mlego/m65/rev11 -km uk_knob
Rev 10
git clone --recurse-submodules -b mlego_dev https://github.com/alinelena/qmk_firmware.git qmk-alin
cd qmk-alin
qmk compile -kb mlego/m65/rev10 -km uk_knob
qmk compile -kb mlego/m65/rev10 -km default_knob
Rev 9
git clone --recurse-submodules -b mlego_dev https://github.com/alinelena/qmk_firmware.git qmk-alin
cd qmk-alin
qmk compile -kb mlego/m65/rev9 -km uk_knob
qmk compile -kb mlego/m65/rev9 -km default_knob
Rev 7
git clone --recurse-submodules -b mlego_dev https://github.com/alinelena/qmk_firmware.git qmk-alin
cd qmk-alin
qmk compile -kb mlego/m65/rev7 -km uk
qmk compile -kb mlego/m65/rev7 -km default
Rev 5/6
git clone --recurse-submodules -b mlego_dev https://github.com/alinelena/qmk_firmware.git qmk-alin
cd qmk-alin
qmk compile -kb mlego/m65/rev5 -km uk
qmk compile -kb mlego/m65/rev6 -km uk
Rev 4
git clone --recurse-submodules -b mlego https://github.com/alinelena/qmk_firmware.git qmk-alin
cd qmk-alin
qmk compile -kb mlego/m65/rev4 -km uk
Rev 3
git clone --recurse-submodules https://github.com/qmk/qmk_firmware.git
cd qmk_firmware
make mlego/m65/rev3:uk
make mlego/m65/rev3:uk:flash
Rev 2
# GD32F303 WeAct BluePill Plus
git clone --recurse-submodules https://github.com/qmk/qmk_firmware.git
cd qmk_firmware
make mlego/m65/rev2:uk
make mlego/m65/rev2:uk:flash
Rev 1
git clone --recurse-submodules https://github.com/qmk/qmk_firmware.git
cd qmk_firmware
make mlego/m65/rev1:uk
make mlego/m65/rev1:uk:flash
Build Gallery
By Hardware Revision
Rev 11

Rev 10

Rev 9

Rev 7 & 8

Rev 5 & 6

Rev 1–4

Switch & Keycap Showcase (#1–#15)
-

Bare Frame & Switches
Assembled PCB and switches mounted on the LEGO baseplate prior to keycaps.
-

Keyboard #1 — MT3
/dev/ttyBleachedKailh Crystal Royal tactile switches with MT3
/dev/ttyBleached keycaps (alt view). -

Keyboard #2 — MT3 Susuwatari
Kailh Crystal Jade clicky switches paired with double-shot ABS MT3 Susuwatari.
-

Keyboard #3 — MT3
/dev/ttyKailh Crystal Royal switches (home view 1, home view 2).
-

Keyboard #4 — NovelKeys Blueberry
Re-sprung NovelKeys Blueberry tactile switches with MT3
/dev/tty. -

Keyboard #5 — Gateron Ink v2
Gateron Ink v2 Yellow and Red linear switches prepared for a DK ISO layout.
-

Keyboard #6 — "Linear" Crystal Jades
Kailh Crystal Jades with clickbars removed, paired with MT3
/dev/tty+ Godspeed & Japanese alphas (switches 1, switches 2, build 1). -

Keyboard #7 — Gateron Whites & Godspeed
Gateron White switches with MT3
/dev/ttyand Godspeed modifiers (bare, without mods). -

Keyboard #8 — Frankenswitch Linears
Custom frankenswitch linears with MT3 Camillo, Godspeed, and Susuwatari accents.
-

Keyboard #9 — MT3 Extended 2048
MT3 Extended 2048 icon legends with colorful accent modifiers.
-

Keyboard #10 — MT3 Dasher
Two-tone blue MT3 Dasher keycap set.
-

Keyboard #11 — MT3 MiTo Cyber
High-contrast purple/magenta/yellow MT3 Cyber set.
-

Keyboard #12 — MT3 Matt3o 3277
IBM 3277 terminal-inspired dark gray and turquoise keycaps.
-

Keyboard #13 — MT3 Fairlane
Pastel mint and cream MT3 Fairlane keycaps.
-

Keyboard #14 — Commodore C64 Harvest
Original harvested Commodore 64 keycaps mounted via 3D-printed MX stem adapters.
-

Keyboard #15 — MT3 Camillo & Eva-02
MT3 Camillo alphas with Evangelion Unit-02 novelty accents.
Keymaps & Layout
Default QMK layers for the 5×13 ortholinear layout:

Pinouts by Revision
Rev 9–11 (XIAO RP2040)
(Note: Rev 9–11 correspond to m66 Rev 1–3 where the clickable encoder forms Column 13.)
ShiftRegister SN74HC595N (#1 & #2) ShiftRegister 74HC165N / 74HC589 (#3)
QB |1 16| VCC nPL |1 16| VCC
QC |2 15| QA CP |2 15| nCE
QD |3 14| SER data D4 |3 14| D3
QE |4 13| OE D5 |4 13| D2
QF |5 12| RCLK latch D6 |5 12| D1
QG |6 11| SRCLK clock D7 |6 11| D0
QH |7 10| SRCLR nQ7 |7 10| DS
G |8 9| QH* G |8 9| Q7
| Rows | C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | C13 | Row Pin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| R0 | Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | Bksp | MUTE | 3D0 |
| R1 | Tab | q | w | e | r | t | y | u | i | o | p | [ | ] | X* | 3D1 |
| R2 | # | a | s | d | f | g | h | j | k | l | ; | ' | Enter | X* | 3D2 |
| R3 | Shift | \ | z | x | c | v | b | n | m | , | . | Up | / | X* | 3D3 |
| R4 | Ctrl | Menu | Lower | Alt | Raise | Space | Space | Space | AltGr | Shift | Left | Down | Right | X* | 3D4 |
| Col | 2QB |
2QC |
2QD |
2QE |
2QF |
2QG |
2QH |
2QA |
1QH |
1QG |
1QF |
1QE |
1QA |
1QD |
(X NC)* |
- Encoders:
Pad_A = GP27,Pad_B = GP26 - OLED (I²C):
SCL = GP7,SDA = GP6 74HC595N:MOSI = GP3(pin 11),Latch = GP1(pin 8),Clock = GP2(pin 9)74HC165N:Latch = GP1(pin 8),Clock = GP2(pin 9),MISO = GP4(pin 10),SPI_DIVISOR = 8- LEDs (RP2040):
CAPS_LOCK = GP25,Lower = GP16,Raise = GP17,RGB DI = GP12,RGB On = GP11
Rev 7/8 (XIAO RP2040 / BLE)
| Rows | C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | Row Pin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| R0 | Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | Bksp | D0 (GP26) |
| R1 | Tab | q | w | e | r | t | y | u | i | o | p | [ | ] | D1 (GP27) |
| R2 | # | a | s | d | f | g | h | j | k | l | ; | ' | Enter | D2 (GP28) |
| R3 | Shift | \ | z | x | c | v | b | n | m | , | . | Up | / | D3 (GP29) |
| R4 | Ctrl | Menu | Lower | Alt | Raise | Space | Space | Space | AltGr | Shift | Left | Down | Right | D4 (GP6) |
| Col | 2QH |
2QG |
2QF |
2QE |
2QD |
2QC |
2QB |
2QA |
1QH |
1QG |
1QF |
1QE |
1QA |
- Encoders:
Pad_A = D5 (GP7),Pad_B = D6 (GP0) 74HC595N:Data = D10 (GP3),Latch = D7 (GP1),Clock = D8 (GP2),SPI_DIVISOR = 32- LEDs (RP2040):
CAPS_LOCK = GP25,Lower = GP16,Raise = GP17,RGB DI = GP12,RGB On = GP11
Rev 5 & 6 (RP2040 Pico / WeAct)
| Rows | C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | Row Pin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| R0 | Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | Bksp | GP22 |
| R1 | Tab | q | w | e | r | t | y | u | i | o | p | [ | ] | GP16 |
| R2 | # | a | s | d | f | g | h | j | k | l | ; | ' | Enter | GP18 |
| R3 | Shift | \ | z | x | c | v | b | n | m | , | . | Up | / | GP19 |
| R4 | Ctrl | Menu | Lower | Alt | Raise | Space | Space | Space | AltGr | Shift | Left | Down | Right | GP20 |
| Col | GP1 |
GP6 |
GP7 |
GP8 |
GP9 |
GP15 |
GP14 |
GP13 |
GP12 |
GP11 |
GP10 |
GP17 |
GP21 |
- Encoders:
Pad_A = GP4,Pad_B = GP5 - OLED (I²C):
SDA = GP2,SCL = GP3 - LEDs (Rev 5 Pico):
NUM_LOCK = GP28,CAPS_LOCK = GP25,SCROLL_LOCK = GP27,RGB_DI = GP0 - LEDs (Rev 6 WeAct):
NUM_LOCK = GP29,CAPS_LOCK = GP25,SCROLL_LOCK = GP28,RGB_DI = GP0(same physical positions on PCB)
Rev 4 (`STM32F401`/`F411`)
| Rows | C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | Row Pin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| R0 | Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | Bksp | B10 |
| R1 | Tab | q | w | e | r | t | y | u | i | o | p | [ | ] | A5 |
| R2 | # | a | s | d | f | g | h | j | k | l | ; | ' | Enter | A6 |
| R3 | Shift | \ | z | x | c | v | b | n | m | , | . | Up | / | A7 |
| R4 | Ctrl | Menu | Lower | Alt | Raise | Space | Space | Space | AltGr | Shift | Left | Down | Right | B0 |
| Col | B14 |
A8 |
A10 |
A15 |
B3 |
B4 |
B5 |
B7 |
A1 |
A2 |
A3 |
A4 |
B1 |
- Encoders:
Pad_A = A0,Pad_B = B6 - OLED (I²C):
SDA = B9,SCL = B8 - LEDs:
NUM_LOCK = B12,CAPS_LOCK = C13,SCROLL_LOCK = B13,RGB_DI = B15
Rev 3 (`STM32F401`/`F411`)
| Rows | C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | Row Pin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| R0 | Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | Bksp | B10 |
| R1 | Tab | q | w | e | r | t | y | u | i | o | p | [ | ] | A5 |
| R2 | # | a | s | d | f | g | h | j | k | l | ; | ' | Enter | A6 |
| R3 | Shift | \ | z | x | c | v | b | n | m | , | . | Up | / | A7 |
| R4 | Ctrl | Menu | Lower | Alt | Raise | Space | Space | Space | AltGr | Shift | Left | Down | Right | B0 |
| Col | A10 |
A15 |
B3 |
B4 |
B5 |
B9 |
B8 |
B7 |
A1 |
A2 |
A3 |
A4 |
B1 |
- Encoders:
Pad_A = A0,Pad_B = B6 - LEDs:
NUM_LOCK = B12,CAPS_LOCK = C13,SCROLL_LOCK = B13,RGB_DI = B15
Rev 1 & 2 (`STM32F103` / `GD32F303`)
| Rows | C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | Row Pin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| R0 | Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | Bksp | B11 |
| R1 | Tab | q | w | e | r | t | y | u | i | o | p | [ | ] | B0 |
| R2 | # | a | s | d | f | g | h | j | k | l | ; | ' | Enter | B1 |
| R3 | Shift | \ | z | x | c | v | b | n | m | , | . | Up | / | A2 |
| R4 | Ctrl | Menu | Lower | Alt | Raise | Space | Space | Space | AltGr | Shift | Left | Down | Right | A3 |
| Col | A10 |
A15 |
B3 |
B4 |
B5 |
B9 |
B8 |
B7 |
B6 |
C15 |
A0 |
A7 |
B10 |
- Encoders:
Pad_A = A8,Pad_B = A9 - LEDs:
NUM_LOCK = B12,CAPS_LOCK = B2(Rev 2) /C13(Rev 1),SCROLL_LOCK = B13,RGB_DI = B15
Matrix Scan Frequencies & OLED Benchmarks
Matrix scan frequencies (in Hz) reported by QMK with DEBUG_MATRIX_SCAN_RATE enabled using the default keymap:
| MCU Platform | Revision | Freq 1 (Minimal)¹ [Hz] | Freq 2 (Full Features)² [Hz] |
|---|---|---|---|
Teensy 2.0 (ATmega32U4) |
Handwire | 1860 |
— |
APM32F103C8T6 (STM32F103) |
Rev 1 | 3427 |
— |
GD32F303CCT6 (BluePill Plus) |
Rev 2 | 3931 |
— |
STM32F401 BlackPill |
Rev 4 | 4681 |
1976 |
RP2040 Raspberry Pi Pico |
Rev 5 | 5277 |
2214 |
RP2040 WeAct |
Rev 6 | 5278 |
2202 |
RP2040 Seeed XIAO (2× 74HC595) |
Rev 7 | 6162 |
— |
RP2040 Seeed XIAO (2× 595 + 1× 165) |
Rev 9³ | 4366 |
2125 |
RP2040 Seeed XIAO (Unicode enabled) |
Rev 9 | 3986 |
1924 |
- Freq 1: OLED off, WPM off, RGBLight off, Mousekey and Extrakey off.
- Freq 2: OLED on with all standard features enabled.
- Rev 9: Includes an extra 14th column compared to earlier revisions;
SPI_DIVISORis8on Rev 9 vs32on Rev 7.
Impact of OLED_UPDATE_INTERVAL
Updating an I²C OLED display on every matrix scan causes a noticeable drop in scan rate. QMK provides OLED_UPDATE_INTERVAL (in milliseconds) to throttle display refreshes. Tested on Rev 9 (128×32 and 128×64 screens yield identical numbers; baselines with OLED off: 3760 Hz with WPM off, 2631 Hz with WPM on):
OLED_UPDATE_INTERVAL [ms] |
Matrix Scan Frequency [Hz] |
|---|---|
0 (QMK default) |
2125 |
1 |
3154 |
2 |
3629 |
3 |
3788 |
4 |
3868 |
5 |
3915 |
10 (100 FPS — recommended) |
4011 |
20 |
4058 |
30 |
4075 |
50 |
4087 |
100 |
4097 |
Takeaways
1. Setting `OLED_UPDATE_INTERVAL` to **`10` ms** (100 FPS) nearly doubles the effective matrix scan rate (`4011 Hz` vs `2125 Hz`) while remaining more than fast enough for any keyboard status OLED. 2. Curiously, throttling the OLED refresh achieves a higher peak scan rate (`4097 Hz`) than compiling with the OLED disabled (`3760 Hz`).