Skip to content

Add rgeorge/model_c keyboard#26341

Open
rgeorge1926-oss wants to merge 4 commits into
qmk:masterfrom
rgeorge1926-oss:add-model-c-keyboard
Open

Add rgeorge/model_c keyboard#26341
rgeorge1926-oss wants to merge 4 commits into
qmk:masterfrom
rgeorge1926-oss:add-model-c-keyboard

Conversation

@rgeorge1926-oss

@rgeorge1926-oss rgeorge1926-oss commented Jul 18, 2026

Copy link
Copy Markdown

Description

Adds the Model-C keyboard — a custom IBM Model M compatible keyboard controller using QMK firmware.

Hardware

  • ATmega32U4 (Arduino Pro Micro) microcontroller
  • Two MCP23S17 SPI port expanders for 8x16 matrix scanning
  • Standard IBM Model M connector pinout compatibility
  • Three status LEDs (caps lock, scroll lock, num lock)
  • German QWERTZ layout with umlaut support via unicode layer

Implementation

Uses CUSTOM_MATRIX=lite with MCP23S17 port expanders connected via hardware SPI. The matrix scanning uses high-impedance column deselection to avoid sneak paths in the diodeless matrix.

Checklist

  • I have read the Keyboard Templates and Keyboard Guidelines
  • This keyboard does not appear in the QMK Hardware Database
  • I have maintained a changelog
  • I have completed all of the primary items on the Keyboard Submission Checklist
  • I have tested the keyboard and it is working correctly
  • I have pointed the maintainer entry in the keyboard readme.md to myself and only myself

@rgeorge1926-oss
rgeorge1926-oss force-pushed the add-model-c-keyboard branch 3 times, most recently from 6dbbe29 to b09c38e Compare July 18, 2026 16:31
@sigprof

sigprof commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

This keyboard should be under ibm/model_m/ (see #14996 and other issues linked there).

Also https://github.com/rgeorge/model-c does not seem to be accessible at the moment.

Comment thread keyboards/rgeorge/model_c/config.h Outdated
Comment on lines +44 to +52
/* Matrix pins are driven by MCP23S17 port expanders via SPI - NOT direct GPIO */
#define MATRIX_ROW_PINS \
{ NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN }
#define MATRIX_COL_PINS \
{ NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN }

/* COL2ROW, ROW2COL */
#define DIODE_DIRECTION ROW2COL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These definitions are not needed (only the default matrix implementation needs them, but your custom matrix implementation does not use them).

Suggested change
/* Matrix pins are driven by MCP23S17 port expanders via SPI - NOT direct GPIO */
#define MATRIX_ROW_PINS \
{ NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN }
#define MATRIX_COL_PINS \
{ NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN }
/* COL2ROW, ROW2COL */
#define DIODE_DIRECTION ROW2COL

Comment thread keyboards/rgeorge/model_c/config.h Outdated
Comment on lines +53 to +56
/* Bootmagic Lite config */
#define BOOTMAGIC_LITE_ROW 0
#define BOOTMAGIC_LITE_COLUMN 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bootmagic config should be moved to JSON (set bootmagic.matrix).

Suggested change
/* Bootmagic Lite config */
#define BOOTMAGIC_LITE_ROW 0
#define BOOTMAGIC_LITE_COLUMN 5

Comment thread keyboards/rgeorge/model_c/config.h Outdated
Comment on lines +64 to +68

/* LED pins - active low (sinking current to VDD) */
#define LED1_PIN E6 /* Arduino D7 -> PE6 -> QMK E6 */
#define LED2_PIN B4 /* Arduino D8 -> PB4 -> QMK B4 */
#define LED3_PIN B5 /* Arduino D9 -> PB5 -> QMK B5 */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LEDs also should be configured in JSON (indicators.{caps_lock,num_lock,scroll_lock,on_state}).

Suggested change
/* LED pins - active low (sinking current to VDD) */
#define LED1_PIN E6 /* Arduino D7 -> PE6 -> QMK E6 */
#define LED2_PIN B4 /* Arduino D8 -> PB4 -> QMK B4 */
#define LED3_PIN B5 /* Arduino D9 -> PB5 -> QMK B5 */

Comment thread keyboards/rgeorge/model_c/keyboard.json Outdated
Comment on lines +7 to +8
"vid": "0x04D8",
"pid": "0xED10",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This VID belongs to Microchip Technology Inc.; did you actually request and receive a PID from them? If not, the common convention used in QMK is to select a random unregistered VID value above 0x4000 which does not collide with anything in https://yanfali.github.io/qmk_usb_usage/ and https://www.the-sz.com/products/usbid/, and any PID value you like. Alternatively, there are some places which can hand out presumably unique USB VID:PID combinations for Open Hardware projects.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom matrix implementation should be placed in a file named matrix.c.

Any other custom code should be placed in a *.c file with exactly the same name as the last component of the keyboard name (for the currently used name it would be model_c.c, not model-c.c); this file should not be mentioned in SRC (it would be used automatically). However, you may not need such a file at all (the code which inits the matrix pins could be moved to matrix_init_custom(), and the custom LED code won't be needed if you move the LED pin definitions into JSON).

Comment thread keyboards/rgeorge/model_c/rules.mk Outdated

CUSTOM_MATRIX = lite

SRC += model-c.c

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SRC += model-c.c
SRC += matrix.c

The custom matrix implementation should be moved to matrix.c, and even if you end up needing <keyboard>.c, it does not need to be mentioned in SRC.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.qmk.fm/pr_checklist#keyboard-prs:

default keymaps should be "pristine"

So you should not have your Unicode setup in the default keymap — just make a plain QWERTY keymap.

And also your LAYOUT macro included from model-c.h is completely wrong — you should use #include QMK_KEYBOARD_H to get the generated macro (which would expect key mappings in the same order as defined in JSON, which should roughly match the physical keyboard layout, not the electrical one).

Comment thread keyboards/rgeorge/model_c/keyboard.json Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use qmk format-json to reformat keyboard.json, so that the file (especially the layout section) would be more readable.

Comment thread keyboards/rgeorge/model_c/keyboard.json Outdated
"bootmagic": true,
"command": true,
"extrakey": true,
"ghost": false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"ghost": false,

features.ghost does not exist, so this definition just does nothing. The proper JSON key is matrix_pins.ghost, and I suspect that you actually need to set it to true (because the ghosting behavior when pressing multiple keys at once would be present in a diodeless matrix; the high-impedance deselection which you use only avoids overloading the chip outputs when multiple outputs get connected together).

Comment thread keyboards/rgeorge/model_c/keyboard.json Outdated
"extrakey": true,
"ghost": false,
"mousekey": true,
"nkro": true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably useless if your hardware is actually 2KRO.

Comment thread keyboards/rgeorge/model_c/keyboard.json Outdated
Comment on lines +11 to +12
"processor": "atmega32u4",
"bootloader": "caterina",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"processor": "atmega32u4",
"bootloader": "caterina",
"development_board": "promicro",

This will set the controller and bootloader, and setup compatibility with converters.

IBM Model M compatible keyboard controller using ATmega32U4
(Arduino Pro Micro) with two MCP23S17 SPI port expanders
to scan the 8x16 key matrix.

@zvecr zvecr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The folder structure here is incorrect. The files need to be inside a sub-folder of keyboards/ibm/model_m and the keymaps need to be within a keymaps folder.

// Custom Matrix Implementation
//------------------------------------------------------------------------------

void matrix_init_custom(void) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void matrix_init_custom(void) {
void matrix_init_custom(void) {
// Initialize SPI master
spi_init();
// Set chip select pins as output
gpio_set_pin_output(MCP23S17_CS1_PIN);
gpio_set_pin_output(MCP23S17_CS2_PIN);
// Deselect both chips (active low, so write HIGH)
gpio_write_pin_high(MCP23S17_CS1_PIN);
gpio_write_pin_high(MCP23S17_CS2_PIN);

Comment on lines +51 to +65
void keyboard_pre_init_kb(void) {
// Initialize SPI master
spi_init();

// Set chip select pins as output
gpio_set_pin_output(MCP23S17_CS1);
gpio_set_pin_output(MCP23S17_CS2);

// Deselect both chips (active low, so write HIGH)
gpio_write_pin_high(MCP23S17_CS1);
gpio_write_pin_high(MCP23S17_CS2);

keyboard_pre_init_user();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void keyboard_pre_init_kb(void) {
// Initialize SPI master
spi_init();
// Set chip select pins as output
gpio_set_pin_output(MCP23S17_CS1);
gpio_set_pin_output(MCP23S17_CS2);
// Deselect both chips (active low, so write HIGH)
gpio_write_pin_high(MCP23S17_CS1);
gpio_write_pin_high(MCP23S17_CS2);
keyboard_pre_init_user();
}

Comment on lines +43 to +44
#define MCP23S17_CS1_PIN C6 /* D5 -> PC6 (CS1 - Expander A for columns) */
#define MCP23S17_CS2_PIN D7 /* D6 -> PD7 (CS2 - Expander B for rows) */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesnt really add value as the only file thats ever going to use it is the keyboards matrix.c

Suggested change
#define MCP23S17_CS1_PIN C6 /* D5 -> PC6 (CS1 - Expander A for columns) */
#define MCP23S17_CS2_PIN D7 /* D6 -> PD7 (CS2 - Expander B for rows) */


// MCP23S17 chip select pins (for two expanders)
#define MCP23S17_CS1 (uint8_t) MCP23S17_CS1_PIN // CS for expander A (columns), address 0x01
#define MCP23S17_CS2 (uint8_t) MCP23S17_CS2_PIN // CS for expander B (rows), address 0x00

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#define MCP23S17_CS2 (uint8_t) MCP23S17_CS2_PIN // CS for expander B (rows), address 0x00
#define MCP23S17_CS1_PIN C6 /* D5 -> PC6 (CS1 - Expander A for columns) */
#define MCP23S17_CS2_PIN D7 /* D6 -> PD7 (CS2 - Expander B for rows) */

Comment on lines +42 to +44
static void mcp23s17_init_expander(uint8_t cs_pin, uint8_t addr, uint8_t iodir_a, uint8_t iodir_b);
static void mcp23s17_write_reg(uint8_t cs_pin, uint8_t addr, uint8_t reg_addr, uint8_t data);
static uint8_t mcp23s17_read_reg(uint8_t cs_pin, uint8_t addr, uint8_t reg_addr);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void mcp23s17_init_expander(uint8_t cs_pin, uint8_t addr, uint8_t iodir_a, uint8_t iodir_b);
static void mcp23s17_write_reg(uint8_t cs_pin, uint8_t addr, uint8_t reg_addr, uint8_t data);
static uint8_t mcp23s17_read_reg(uint8_t cs_pin, uint8_t addr, uint8_t reg_addr);
static void mcp23s17_init_expander(pin_t cs_pin, uint8_t addr, uint8_t iodir_a, uint8_t iodir_b);
static void mcp23s17_write_reg(pin_t cs_pin, uint8_t addr, uint8_t reg_addr, uint8_t data);
static uint8_t mcp23s17_read_reg(pin_t cs_pin, uint8_t addr, uint8_t reg_addr);

Comment on lines +190 to +198
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRA, 0xFF);
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRB, 0xFF);

if (col < 8) {
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_GPIOA, ~(1 << col));
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRA, ~(1 << col));
} else {
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_GPIOB, ~(1 << (col - 8)));
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRB, ~(1 << (col - 8)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRA, 0xFF);
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRB, 0xFF);
if (col < 8) {
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_GPIOA, ~(1 << col));
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRA, ~(1 << col));
} else {
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_GPIOB, ~(1 << (col - 8)));
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRB, ~(1 << (col - 8)));
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_IODIRA, 0xFF);
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_IODIRB, 0xFF);
if (col < 8) {
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_GPIOA, ~(1 << col));
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_IODIRA, ~(1 << col));
} else {
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_GPIOB, ~(1 << (col - 8)));
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_IODIRB, ~(1 << (col - 8)));

Comment on lines +76 to +89
mcp23s17_write_reg(MCP23S17_CS2, 0x00, MCP23S17_IOCON, MCP23S17_IOCON_HAEN);
mcp23s17_write_reg(MCP23S17_CS1, 0x00, MCP23S17_IOCON, MCP23S17_IOCON_HAEN);

// Expander B (CS2, addr 0x00) - Short 8-pin connector (Rows), all inputs
mcp23s17_init_expander(MCP23S17_CS2, 0x00, 0xFF, 0xFF);
// Enable pull-ups on row expander GPA0-GPA7
mcp23s17_write_reg(MCP23S17_CS2, 0x00, MCP23S17_GPPUA, 0xFF);

// Expander A (CS1, addr 0x01) - Long 16-pin connector (Columns)
// First init as all outputs with OLAT=0xFF (HIGH) so the output latches are set,
// then switch to all inputs (high-impedance) for diodeless-safe scanning.
mcp23s17_init_expander(MCP23S17_CS1, 0x01, 0x00, 0x00);
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRA, 0xFF);
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRB, 0xFF);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mcp23s17_write_reg(MCP23S17_CS2, 0x00, MCP23S17_IOCON, MCP23S17_IOCON_HAEN);
mcp23s17_write_reg(MCP23S17_CS1, 0x00, MCP23S17_IOCON, MCP23S17_IOCON_HAEN);
// Expander B (CS2, addr 0x00) - Short 8-pin connector (Rows), all inputs
mcp23s17_init_expander(MCP23S17_CS2, 0x00, 0xFF, 0xFF);
// Enable pull-ups on row expander GPA0-GPA7
mcp23s17_write_reg(MCP23S17_CS2, 0x00, MCP23S17_GPPUA, 0xFF);
// Expander A (CS1, addr 0x01) - Long 16-pin connector (Columns)
// First init as all outputs with OLAT=0xFF (HIGH) so the output latches are set,
// then switch to all inputs (high-impedance) for diodeless-safe scanning.
mcp23s17_init_expander(MCP23S17_CS1, 0x01, 0x00, 0x00);
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRA, 0xFF);
mcp23s17_write_reg(MCP23S17_CS1, 0x01, MCP23S17_IODIRB, 0xFF);
mcp23s17_write_reg(MCP23S17_CS2_PIN, 0x00, MCP23S17_IOCON, MCP23S17_IOCON_HAEN);
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x00, MCP23S17_IOCON, MCP23S17_IOCON_HAEN);
// Expander B (CS2, addr 0x00) - Short 8-pin connector (Rows), all inputs
mcp23s17_init_expander(MCP23S17_CS2_PIN, 0x00, 0xFF, 0xFF);
// Enable pull-ups on row expander GPA0-GPA7
mcp23s17_write_reg(MCP23S17_CS2_PIN, 0x00, MCP23S17_GPPUA, 0xFF);
// Expander A (CS1, addr 0x01) - Long 16-pin connector (Columns)
// First init as all outputs with OLAT=0xFF (HIGH) so the output latches are set,
// then switch to all inputs (high-impedance) for diodeless-safe scanning.
mcp23s17_init_expander(MCP23S17_CS1_PIN, 0x01, 0x00, 0x00);
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_IODIRA, 0xFF);
mcp23s17_write_reg(MCP23S17_CS1_PIN, 0x01, MCP23S17_IODIRB, 0xFF);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be removed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be removed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants