From 2c745388201b633ba04036b17157669632d1740a Mon Sep 17 00:00:00 2001 From: Daniel Shields Date: Tue, 11 Aug 2026 11:46:49 +0100 Subject: [PATCH] [Keyboard] Add Keyboardio Model 100 The Model 100 shares the Model 01's split hardware design: both halves are ATtiny key scanners the main MCU talks to over I2C, using the same wire protocol, scanner addresses, and 64-LED layout. The custom matrix and LED drivers are therefore adapted from the Model 01. The main MCU differs: the Model 01's ATmega32U4 (AVR) is replaced by a GD32F303CG (ARM Cortex-M4). This is built as an STM32F103 with the high-density board variant, following the mlego/m65/rev2 precedent, so no dedicated GD32 ChibiOS port is required. Scanner power moves to B9 (open-drain), power-sense to B14/B15, and the scanners hang off I2C1 (B6/B7). Pin assignments and the 8 MHz HXTAL / 48 MHz USB clock were cross-checked against the Kaleidoscope Model 100 hardware plugin and the ArduinoCore-GD32-Keyboardio keyboardio_model_100 variant. --- keyboards/keyboardio/model100/board.h | 22 ++ keyboards/keyboardio/model100/config.h | 42 +++ keyboards/keyboardio/model100/halconf.h | 21 ++ keyboards/keyboardio/model100/keyboard.json | 282 ++++++++++++++++++ .../model100/keymaps/default/keymap.c | 76 +++++ keyboards/keyboardio/model100/leds.c | 109 +++++++ keyboards/keyboardio/model100/leds.h | 21 ++ keyboards/keyboardio/model100/matrix.c | 203 +++++++++++++ keyboards/keyboardio/model100/mcuconf.h | 39 +++ keyboards/keyboardio/model100/model100.h | 35 +++ keyboards/keyboardio/model100/readme.md | 100 +++++++ keyboards/keyboardio/model100/rules.mk | 30 ++ .../model100/wire-protocol-constants.h | 53 ++++ 13 files changed, 1033 insertions(+) create mode 100644 keyboards/keyboardio/model100/board.h create mode 100644 keyboards/keyboardio/model100/config.h create mode 100644 keyboards/keyboardio/model100/halconf.h create mode 100644 keyboards/keyboardio/model100/keyboard.json create mode 100644 keyboards/keyboardio/model100/keymaps/default/keymap.c create mode 100644 keyboards/keyboardio/model100/leds.c create mode 100644 keyboards/keyboardio/model100/leds.h create mode 100644 keyboards/keyboardio/model100/matrix.c create mode 100644 keyboards/keyboardio/model100/mcuconf.h create mode 100644 keyboards/keyboardio/model100/model100.h create mode 100644 keyboards/keyboardio/model100/readme.md create mode 100644 keyboards/keyboardio/model100/rules.mk create mode 100644 keyboards/keyboardio/model100/wire-protocol-constants.h diff --git a/keyboards/keyboardio/model100/board.h b/keyboards/keyboardio/model100/board.h new file mode 100644 index 000000000000..e644d22f24da --- /dev/null +++ b/keyboards/keyboardio/model100/board.h @@ -0,0 +1,22 @@ +// Copyright 2025 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include_next + +/* The GD32F303CG is a high-density part with 1MB flash / 96KB RAM. The + * stm32duino board files default to the medium-density STM32F103xB; select the + * high-density variant so the correct memory map and peripherals are used. */ +#undef STM32F103xB +#define STM32F103xE + +/* The Model 100 gates its USB D+ pull-up with a GPIO (PA8, active-high, + * push-pull) rather than a fixed resistor — confirmed from Keyboardio's DAPBoot + * config (HAVE_USB_PULLUP_CONTROL, USB_PULLUP_GPIO_PIN GPIO8). The stm32duino + * board default only floats PA12 (assumes a fixed pull-up), so without this the + * host never sees the device. Drive PA8 to (dis)connect from the bus. */ +#undef usb_lld_connect_bus +#define usb_lld_connect_bus(usbp) palSetPadMode(GPIOA, 8, PAL_MODE_OUTPUT_PUSHPULL); palSetPad(GPIOA, 8) +#undef usb_lld_disconnect_bus +#define usb_lld_disconnect_bus(usbp) palClearPad(GPIOA, 8) diff --git a/keyboards/keyboardio/model100/config.h b/keyboards/keyboardio/model100/config.h new file mode 100644 index 000000000000..e3d61e80a529 --- /dev/null +++ b/keyboards/keyboardio/model100/config.h @@ -0,0 +1,42 @@ +/* +Copyright 2018 James Laird-Wah +Copyright 2025 QMK + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +/* key matrix size; rows are doubled for split */ +#define MATRIX_ROWS 8 +#define MATRIX_COLS 8 + +/* The two ATtiny key scanners hang off I2C1 (B6/B7). They implement clock + * stretching, so a generous timeout is needed. */ +#define I2C1_SCL_PIN B6 +#define I2C1_SDA_PIN B7 + +/* On-die flash EEPROM emulation for the GD32F303 (behaves as an STM32F103 + * high-density part). */ +#define FEE_PAGE_SIZE 0x800 +#define FEE_PAGE_COUNT 4 +#define FEE_MCU_FLASH_SIZE_IGNORE_CHECK +#define FEE_MCU_FLASH_SIZE 512 + +/* RGB matrix: a coloured animation at a visible brightness on a fresh EEPROM. + * (on/hue/sat are left at their defaults — true/0/255 — since setting them + * explicitly duplicates the built-ins and the strict linter rejects that.) */ +#define RGB_MATRIX_DEFAULT_MODE RGB_MATRIX_CYCLE_LEFT_RIGHT +#define RGB_MATRIX_DEFAULT_VAL 128 +#define RGB_MATRIX_MAXIMUM_BRIGHTNESS 200 diff --git a/keyboards/keyboardio/model100/halconf.h b/keyboards/keyboardio/model100/halconf.h new file mode 100644 index 000000000000..ee640f10b112 --- /dev/null +++ b/keyboards/keyboardio/model100/halconf.h @@ -0,0 +1,21 @@ +/* Copyright 2025 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#define HAL_USE_I2C TRUE + +#include_next diff --git a/keyboards/keyboardio/model100/keyboard.json b/keyboards/keyboardio/model100/keyboard.json new file mode 100644 index 000000000000..10b8c230e58d --- /dev/null +++ b/keyboards/keyboardio/model100/keyboard.json @@ -0,0 +1,282 @@ +{ + "keyboard_name": "Model 100", + "manufacturer": "Keyboardio", + "url": "https://keyboard.io", + "maintainer": "qmk", + "usb": { + "vid": "0x3496", + "pid": "0x0006", + "device_version": "0.0.1" + }, + "rgb_matrix": { + "animations": { + "alphas_mods": true, + "gradient_up_down": true, + "gradient_left_right": true, + "breathing": true, + "band_sat": true, + "band_val": true, + "band_pinwheel_sat": true, + "band_pinwheel_val": true, + "band_spiral_sat": true, + "band_spiral_val": true, + "cycle_all": true, + "cycle_left_right": true, + "cycle_up_down": true, + "rainbow_moving_chevron": true, + "cycle_out_in": true, + "cycle_out_in_dual": true, + "cycle_pinwheel": true, + "cycle_spiral": true, + "dual_beacon": true, + "rainbow_beacon": true, + "rainbow_pinwheels": true, + "raindrops": true, + "jellybean_raindrops": true, + "hue_breathing": true, + "hue_pendulum": true, + "hue_wave": true, + "pixel_rain": true, + "pixel_flow": true, + "pixel_fractal": true + }, + "driver": "custom", + "layout": [ + {"matrix": [3, 7], "x": 3, "y": 35, "flags": 4}, + {"matrix": [2, 7], "x": 0, "y": 26, "flags": 4}, + {"matrix": [1, 7], "x": 0, "y": 17, "flags": 4}, + {"matrix": [0, 7], "x": 0, "y": 6, "flags": 4}, + {"matrix": [0, 6], "x": 14, "y": 5, "flags": 4}, + {"matrix": [1, 6], "x": 15, "y": 16, "flags": 4}, + {"matrix": [2, 6], "x": 16, "y": 25, "flags": 4}, + {"matrix": [3, 6], "x": 17, "y": 34, "flags": 4}, + {"matrix": [3, 5], "x": 31, "y": 29, "flags": 4}, + {"matrix": [2, 5], "x": 31, "y": 19, "flags": 4}, + {"matrix": [1, 5], "x": 30, "y": 11, "flags": 4}, + {"matrix": [0, 5], "x": 30, "y": 1, "flags": 4}, + {"matrix": [0, 4], "x": 45, "y": 0, "flags": 4}, + {"matrix": [1, 4], "x": 45, "y": 8, "flags": 4}, + {"matrix": [2, 4], "x": 46, "y": 17, "flags": 4}, + {"matrix": [3, 4], "x": 46, "y": 27, "flags": 4}, + {"matrix": [3, 3], "x": 60, "y": 27, "flags": 4}, + {"matrix": [2, 3], "x": 60, "y": 18, "flags": 4}, + {"matrix": [1, 3], "x": 60, "y": 9, "flags": 4}, + {"matrix": [0, 3], "x": 60, "y": 0, "flags": 4}, + {"matrix": [0, 2], "x": 74, "y": 2, "flags": 4}, + {"matrix": [1, 2], "x": 74, "y": 11, "flags": 4}, + {"matrix": [2, 2], "x": 75, "y": 20, "flags": 4}, + {"matrix": [3, 2], "x": 74, "y": 28, "flags": 4}, + {"matrix": [2, 1], "x": 89, "y": 30, "flags": 4}, + {"matrix": [1, 1], "x": 89, "y": 19, "flags": 4}, + {"matrix": [0, 1], "x": 89, "y": 7, "flags": 4}, + {"matrix": [0, 0], "x": 70, "y": 38, "flags": 1}, + {"matrix": [1, 0], "x": 82, "y": 41, "flags": 1}, + {"matrix": [2, 0], "x": 93, "y": 45, "flags": 1}, + {"matrix": [3, 0], "x": 104, "y": 50, "flags": 1}, + {"matrix": [3, 1], "x": 74, "y": 64, "flags": 1}, + {"matrix": [7, 6], "x": 149, "y": 64, "flags": 1}, + {"matrix": [7, 7], "x": 119, "y": 50, "flags": 1}, + {"matrix": [6, 7], "x": 130, "y": 45, "flags": 1}, + {"matrix": [5, 7], "x": 141, "y": 41, "flags": 1}, + {"matrix": [4, 7], "x": 153, "y": 38, "flags": 1}, + {"matrix": [4, 6], "x": 134, "y": 7, "flags": 4}, + {"matrix": [5, 6], "x": 134, "y": 19, "flags": 4}, + {"matrix": [6, 6], "x": 134, "y": 30, "flags": 4}, + {"matrix": [7, 5], "x": 149, "y": 28, "flags": 4}, + {"matrix": [6, 5], "x": 148, "y": 20, "flags": 4}, + {"matrix": [5, 5], "x": 149, "y": 11, "flags": 4}, + {"matrix": [4, 5], "x": 149, "y": 2, "flags": 4}, + {"matrix": [4, 4], "x": 163, "y": 0, "flags": 4}, + {"matrix": [5, 4], "x": 163, "y": 9, "flags": 4}, + {"matrix": [6, 4], "x": 163, "y": 18, "flags": 4}, + {"matrix": [7, 4], "x": 163, "y": 27, "flags": 4}, + {"matrix": [7, 3], "x": 177, "y": 27, "flags": 4}, + {"matrix": [6, 3], "x": 177, "y": 17, "flags": 4}, + {"matrix": [5, 3], "x": 178, "y": 8, "flags": 4}, + {"matrix": [4, 3], "x": 178, "y": 0, "flags": 4}, + {"matrix": [4, 2], "x": 193, "y": 1, "flags": 4}, + {"matrix": [5, 2], "x": 193, "y": 11, "flags": 4}, + {"matrix": [6, 2], "x": 192, "y": 19, "flags": 4}, + {"matrix": [7, 2], "x": 192, "y": 29, "flags": 4}, + {"matrix": [7, 1], "x": 206, "y": 34, "flags": 4}, + {"matrix": [6, 1], "x": 207, "y": 25, "flags": 4}, + {"matrix": [5, 1], "x": 208, "y": 16, "flags": 4}, + {"matrix": [4, 1], "x": 209, "y": 5, "flags": 4}, + {"matrix": [4, 0], "x": 224, "y": 6, "flags": 4}, + {"matrix": [5, 0], "x": 223, "y": 17, "flags": 4}, + {"matrix": [6, 0], "x": 223, "y": 26, "flags": 4}, + {"matrix": [7, 0], "x": 220, "y": 35, "flags": 4} + ] + }, + "processor": "STM32F103", + "board": "STM32_F103_STM32DUINO", + "features": { + "bootmagic": false, + "mousekey": true, + "extrakey": true, + "console": true, + "rgb_matrix": true + }, + "debounce": 0, + "layouts": { + "LAYOUT": { + "layout": [ + {"matrix": [0, 7], "x": 0, "y": 0.4, "h": 1.3}, + {"matrix": [0, 6], "x": 1, "y": 0.2, "h": 1.5}, + {"matrix": [0, 5], "x": 2, "y": 0.1, "h": 1.1}, + {"matrix": [0, 4], "x": 3, "y": 0}, + {"matrix": [0, 3], "x": 4, "y": 0.1}, + {"matrix": [0, 2], "x": 5, "y": 0.3}, + + {"matrix": [4, 5], "x": 12.5, "y": 0.3}, + {"matrix": [4, 4], "x": 13.5, "y": 0.1}, + {"matrix": [4, 3], "x": 14.5, "y": 0}, + {"matrix": [4, 2], "x": 15.5, "y": 0.1, "h": 1.1}, + {"matrix": [4, 1], "x": 16.5, "y": 0.2, "h": 1.5}, + {"matrix": [4, 0], "x": 17.5, "y": 0.4, "h": 1.3}, + + {"matrix": [1, 7], "x": 0, "y": 1.7}, + {"matrix": [1, 6], "x": 1, "y": 1.7}, + {"matrix": [1, 5], "x": 2, "y": 1.2}, + {"matrix": [1, 4], "x": 3, "y": 1}, + {"matrix": [1, 3], "x": 4, "y": 1.1}, + {"matrix": [1, 2], "x": 5, "y": 1.3}, + {"matrix": [0, 1], "x": 6, "y": 0.6, "h": 1.5}, + + {"matrix": [4, 6], "x": 11.5, "y": 0.6, "h": 1.5}, + {"matrix": [5, 5], "x": 12.5, "y": 1.3}, + {"matrix": [5, 4], "x": 13.5, "y": 1.1}, + {"matrix": [5, 3], "x": 14.5, "y": 1}, + {"matrix": [5, 2], "x": 15.5, "y": 1.2}, + {"matrix": [5, 1], "x": 16.5, "y": 1.7}, + {"matrix": [5, 0], "x": 17.5, "y": 1.7}, + + {"matrix": [2, 7], "x": 0, "y": 2.7}, + {"matrix": [2, 6], "x": 1, "y": 2.7}, + {"matrix": [2, 5], "x": 2, "y": 2.2}, + {"matrix": [2, 4], "x": 3, "y": 2}, + {"matrix": [2, 3], "x": 4, "y": 2.1}, + {"matrix": [2, 2], "x": 5, "y": 2.3}, + {"matrix": [1, 1], "x": 6, "y": 2.1, "h": 1.3}, + + {"matrix": [5, 6], "x": 11.5, "y": 2.1, "h": 1.3}, + {"matrix": [6, 5], "x": 12.5, "y": 2.3}, + {"matrix": [6, 4], "x": 13.5, "y": 2.1}, + {"matrix": [6, 3], "x": 14.5, "y": 2}, + {"matrix": [6, 2], "x": 15.5, "y": 2.2}, + {"matrix": [6, 1], "x": 16.5, "y": 2.7}, + {"matrix": [6, 0], "x": 17.5, "y": 2.7}, + + {"matrix": [3, 7], "x": 0, "y": 3.7, "h": 1.2}, + {"matrix": [3, 6], "x": 1, "y": 3.7}, + {"matrix": [3, 5], "x": 2, "y": 3.2, "h": 1.15}, + {"matrix": [3, 4], "x": 3, "y": 3}, + {"matrix": [3, 3], "x": 4, "y": 3.1}, + {"matrix": [3, 2], "x": 5, "y": 3.3}, + {"matrix": [2, 1], "x": 6, "y": 3.4, "h": 1.1}, + + {"matrix": [6, 6], "x": 11.5, "y": 3.4, "h": 1.1}, + {"matrix": [7, 5], "x": 12.5, "y": 3.3}, + {"matrix": [7, 4], "x": 13.5, "y": 3.1}, + {"matrix": [7, 3], "x": 14.5, "y": 3}, + {"matrix": [7, 2], "x": 15.5, "y": 3.2, "h": 1.15}, + {"matrix": [7, 1], "x": 16.5, "y": 3.7}, + {"matrix": [7, 0], "x": 17.5, "y": 3.7, "h": 1.2}, + + {"matrix": [0, 0], "x": 4.75, "y": 4.3}, + {"matrix": [4, 7], "x": 12.75, "y": 4.3}, + + {"matrix": [1, 0], "x": 5.75, "y": 4.5}, + {"matrix": [5, 7], "x": 11.75, "y": 4.5}, + + {"matrix": [2, 0], "x": 6.75, "y": 4.7}, + {"matrix": [6, 7], "x": 10.75, "y": 4.7}, + + {"matrix": [3, 0], "x": 7.75, "y": 5.1}, + {"matrix": [7, 7], "x": 9.75, "y": 5.1}, + + {"matrix": [3, 1], "x": 6.25, "y": 6.1, "h": 1.4}, + {"matrix": [7, 6], "x": 11.25, "y": 6.1, "h": 1.4} + ] + }, + "LAYOUT_thumb_row": { + "layout": [ + {"matrix": [0, 7], "x": 0, "y": 0.4, "h": 1.3}, + {"matrix": [0, 6], "x": 1, "y": 0.2, "h": 1.5}, + {"matrix": [0, 5], "x": 2, "y": 0.1, "h": 1.1}, + {"matrix": [0, 4], "x": 3, "y": 0}, + {"matrix": [0, 3], "x": 4, "y": 0.1}, + {"matrix": [0, 2], "x": 5, "y": 0.3}, + + {"matrix": [4, 5], "x": 12.5, "y": 0.3}, + {"matrix": [4, 4], "x": 13.5, "y": 0.1}, + {"matrix": [4, 3], "x": 14.5, "y": 0}, + {"matrix": [4, 2], "x": 15.5, "y": 0.1, "h": 1.1}, + {"matrix": [4, 1], "x": 16.5, "y": 0.2, "h": 1.5}, + {"matrix": [4, 0], "x": 17.5, "y": 0.4, "h": 1.3}, + + {"matrix": [1, 7], "x": 0, "y": 1.7}, + {"matrix": [1, 6], "x": 1, "y": 1.7}, + {"matrix": [1, 5], "x": 2, "y": 1.2}, + {"matrix": [1, 4], "x": 3, "y": 1}, + {"matrix": [1, 3], "x": 4, "y": 1.1}, + {"matrix": [1, 2], "x": 5, "y": 1.3}, + {"matrix": [0, 1], "x": 6, "y": 0.6, "h": 1.5}, + + {"matrix": [4, 6], "x": 11.5, "y": 0.6, "h": 1.5}, + {"matrix": [5, 5], "x": 12.5, "y": 1.3}, + {"matrix": [5, 4], "x": 13.5, "y": 1.1}, + {"matrix": [5, 3], "x": 14.5, "y": 1}, + {"matrix": [5, 2], "x": 15.5, "y": 1.2}, + {"matrix": [5, 1], "x": 16.5, "y": 1.7}, + {"matrix": [5, 0], "x": 17.5, "y": 1.7}, + + {"matrix": [2, 7], "x": 0, "y": 2.7}, + {"matrix": [2, 6], "x": 1, "y": 2.7}, + {"matrix": [2, 5], "x": 2, "y": 2.2}, + {"matrix": [2, 4], "x": 3, "y": 2}, + {"matrix": [2, 3], "x": 4, "y": 2.1}, + {"matrix": [2, 2], "x": 5, "y": 2.3}, + {"matrix": [1, 1], "x": 6, "y": 2.1, "h": 1.3}, + + {"matrix": [5, 6], "x": 11.5, "y": 2.1, "h": 1.3}, + {"matrix": [6, 5], "x": 12.5, "y": 2.3}, + {"matrix": [6, 4], "x": 13.5, "y": 2.1}, + {"matrix": [6, 3], "x": 14.5, "y": 2}, + {"matrix": [6, 2], "x": 15.5, "y": 2.2}, + {"matrix": [6, 1], "x": 16.5, "y": 2.7}, + {"matrix": [6, 0], "x": 17.5, "y": 2.7}, + + {"matrix": [3, 7], "x": 0, "y": 3.7, "h": 1.2}, + {"matrix": [3, 6], "x": 1, "y": 3.7}, + {"matrix": [3, 5], "x": 2, "y": 3.2, "h": 1.15}, + {"matrix": [3, 4], "x": 3, "y": 3}, + {"matrix": [3, 3], "x": 4, "y": 3.1}, + {"matrix": [3, 2], "x": 5, "y": 3.3}, + {"matrix": [2, 1], "x": 6, "y": 3.4, "h": 1.1}, + + {"matrix": [6, 6], "x": 11.5, "y": 3.4, "h": 1.1}, + {"matrix": [7, 5], "x": 12.5, "y": 3.3}, + {"matrix": [7, 4], "x": 13.5, "y": 3.1}, + {"matrix": [7, 3], "x": 14.5, "y": 3}, + {"matrix": [7, 2], "x": 15.5, "y": 3.2, "h": 1.15}, + {"matrix": [7, 1], "x": 16.5, "y": 3.7}, + {"matrix": [7, 0], "x": 17.5, "y": 3.7, "h": 1.2}, + + {"matrix": [0, 0], "x": 4.75, "y": 4.3}, + {"matrix": [1, 0], "x": 5.75, "y": 4.5}, + {"matrix": [2, 0], "x": 6.75, "y": 4.7}, + {"matrix": [3, 0], "x": 7.75, "y": 5.1}, + + {"matrix": [7, 7], "x": 9.75, "y": 5.1}, + {"matrix": [6, 7], "x": 10.75, "y": 4.7}, + {"matrix": [5, 7], "x": 11.75, "y": 4.5}, + {"matrix": [4, 7], "x": 12.75, "y": 4.3}, + + {"matrix": [3, 1], "x": 6.25, "y": 6.1, "h": 1.4}, + + {"matrix": [7, 6], "x": 11.25, "y": 6.1, "h": 1.4} + ] + } + } +} diff --git a/keyboards/keyboardio/model100/keymaps/default/keymap.c b/keyboards/keyboardio/model100/keymaps/default/keymap.c new file mode 100644 index 000000000000..310dd1c486b9 --- /dev/null +++ b/keyboards/keyboardio/model100/keymaps/default/keymap.c @@ -0,0 +1,76 @@ +/* Copyright 2018 James Laird-Wah + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include QMK_KEYBOARD_H + +/* layer constants */ +enum { + DEF = 0, + NUM, + FUN, +}; + + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { +[DEF] = LAYOUT( + QK_BOOT , KC_1 , KC_2 , KC_3 , KC_4 , KC_5 , KC_6 , KC_7 , KC_8 , KC_9 , KC_0 , TG(NUM), + KC_GRV , KC_Q , KC_W , KC_E , KC_R , KC_T , RM_NEXT, _______, KC_Y , KC_U , KC_I , KC_O , KC_P , KC_EQL , + KC_PGUP, KC_A , KC_S , KC_D , KC_F , KC_G , KC_TAB , KC_ENT , KC_H , KC_J , KC_K , KC_L , KC_SCLN, KC_QUOT, + KC_PGDN, KC_Z , KC_X , KC_C , KC_V , KC_B , KC_ESC , _______, KC_N , KC_M , KC_COMM, KC_DOT , KC_SLSH, KC_MINS, + KC_LCTL, KC_RCTL, + KC_BSPC, KC_SPC , + KC_LGUI, KC_RALT, + KC_LSFT, KC_RSFT, + MO(FUN), MO(FUN) + ), +[NUM] = LAYOUT( + _______, _______, _______, _______, _______, _______, _______, KC_P7 , KC_P8 , KC_P9 , KC_PMNS, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_P4 , KC_P5 , KC_P6 , KC_PPLS, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_P1 , KC_P2 , KC_P3 , KC_PEQL, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_P0 , KC_PDOT, KC_PAST, KC_PSLS, KC_PENT, + _______, _______, + _______, _______, + _______, _______, + _______, _______, + _______, _______ + ), +[FUN] = LAYOUT( + _______, KC_F1 , KC_F2 , KC_F3 , KC_F4 , KC_F5 , KC_F6 , KC_F7 , KC_F8 , KC_F9 , KC_F10 , KC_F11 , + KC_TAB , _______, MS_UP, _______, MS_BTN3, _______, RM_TOGG, KC_MPRV, KC_MNXT, KC_LCBR, KC_RCBR, KC_LBRC, KC_RBRC, KC_F12 , + KC_HOME, MS_LEFT, MS_DOWN, MS_RGHT, MS_BTN1, _______, _______, KC_MPLY, KC_LEFT, KC_DOWN, KC_UP , KC_RGHT, _______, _______, + KC_END , KC_PSCR, KC_INS , _______, MS_BTN2, _______, _______, _______, KC_MUTE, KC_VOLD, KC_VOLU, _______, KC_BSLS, KC_PIPE, + _______, _______, + KC_DEL , KC_ENT , + _______, _______, + _______, _______, + _______, _______ + ) +}; + +/* template for new layouts: +LAYOUT( + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, + _______, _______, + _______, _______, + _______, _______, + _______, _______ + ) +*/ + +/* vim: set ts=2 sw=2 et: */ diff --git a/keyboards/keyboardio/model100/leds.c b/keyboards/keyboardio/model100/leds.c new file mode 100644 index 000000000000..408339cc5e21 --- /dev/null +++ b/keyboards/keyboardio/model100/leds.c @@ -0,0 +1,109 @@ +/* Copyright 2018 James Laird-Wah + * Copyright 2025 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "leds.h" +#include "rgb_matrix.h" +#include +#include "model100.h" + +/* LED writes go over the SAME I2C1 bus as the key scanners. QMK's ChibiOS DMA + * i2c_transmit does not work on the GD32F303 and mixing it with the polled key + * reads corrupts the bus, so use the shared polled i2c_poll_write (matrix.c). */ + +void set_all_leds_to(uint8_t r, uint8_t g, uint8_t b) { + uint8_t buf[] = { + TWI_CMD_LED_SET_ALL_TO, + b, g, r + }; + i2c_poll_write(I2C_ADDR(LEFT), buf, sizeof(buf)); + i2c_poll_write(I2C_ADDR(RIGHT), buf, sizeof(buf)); +} + +void set_led_to(int led, uint8_t r, uint8_t g, uint8_t b) { + uint8_t buf[] = { + TWI_CMD_LED_SET_ONE_TO, + led & 0x1f, + b, g, r + }; + int hand = (led >= 32) ? RIGHT : LEFT; + i2c_poll_write(I2C_ADDR(hand), buf, sizeof(buf)); +} + +#ifdef RGB_MATRIX_ENABLE + +static struct { + uint8_t b; + uint8_t g; + uint8_t r; +} __attribute__((packed)) led_state[64]; + +static void set_color(int index, uint8_t r, uint8_t g, uint8_t b) { + /* No gamma here: with USE_CIE1931_CURVE the CIE curve is already applied to + * the value in hsv_to_rgb (quantum/color.c) before rgb_matrix hands us these + * channels. Correcting again crushed the mids (over-contrast, e.g. digital + * rain trails dropping to black too fast). Match the Model 01 driver. */ + led_state[index].r = r; + led_state[index].g = g; + led_state[index].b = b; +} + +static void set_color_all(uint8_t r, uint8_t g, uint8_t b) { + for (int i=0; i. + */ +#pragma once + +#include + +void set_all_leds_to(uint8_t r, uint8_t g, uint8_t b); +void set_led_to(int led, uint8_t r, uint8_t g, uint8_t b); diff --git a/keyboards/keyboardio/model100/matrix.c b/keyboards/keyboardio/model100/matrix.c new file mode 100644 index 000000000000..0780a5b9b5ca --- /dev/null +++ b/keyboards/keyboardio/model100/matrix.c @@ -0,0 +1,203 @@ +/* Copyright 2018 James Laird-Wah + * Copyright 2025 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "matrix.h" +#include +#include "model100.h" +#include "ch.h" +#include "hal.h" + +#define ROWS_PER_HAND (MATRIX_ROWS / 2) + +/* --------------------------------------------------------------------------- + * Polled (non-DMA) I2C for the ATtiny key scanners. + * + * ChibiOS's STM32 I2Cv1 driver completes transfers via DMA. On the GD32F303 + * (which we build as an STM32F103) the DMA<->I2C wiring does not behave as the + * ST driver expects, so transfers never complete — i2c_receive/i2c_transmit + * just time out even though the peripheral, clock and pins are all configured + * correctly. Keyboardio's own DAPBoot bootloader talks to these exact scanners + * with a polled loop and works fine, so we do the same: drive I2C1 by polling + * the status registers, with no interrupt and no DMA. + * + * This is also shared with the LED driver (leds.c) via i2c_poll_write() — the + * LEDs sit on the same bus, so they must use the same working path; mixing in + * the broken ChibiOS DMA driver corrupts the bus for everyone. + * + * The I2C register bit masks (CR1/SR1/SR2) come from the CMSIS device header + * pulled in by hal.h; TXE is not always exposed, so define our own below. + * --------------------------------------------------------------------------- */ +#ifndef I2C_SR1_TXE +#define I2C_SR1_TXE (1U << 7) +#endif + +/* The scanners clock-stretch (hold SCL low) while they service a request, so + * the waits must tolerate that without being so long that an unresponsive hand + * stalls the whole scan. ~20000 loop iterations is a few I2C byte-times here. */ +#define POLL_TIMEOUT_LOOPS 20000U + +/* Bring up I2C1 for polled use by hand. We deliberately do NOT use ChibiOS's + * i2cStart(): it leaves the I2C event interrupt armed, and our manual START + * would then fire that ISR with no active transfer, halting the system. */ +static void i2c_poll_setup(void) { + rccEnableI2C1(true); + rccResetI2C1(); + + /* PB6 = SCL, PB7 = SDA, alternate-function open-drain. */ + palSetLineMode(B6, PAL_MODE_STM32_ALTERNATE_OPENDRAIN); + palSetLineMode(B7, PAL_MODE_STM32_ALTERNATE_OPENDRAIN); + + uint32_t pclk1_mhz = STM32_PCLK1 / 1000000U; + I2CD1.i2c->CR1 = 0; /* disable while configuring */ + I2CD1.i2c->CR2 = pclk1_mhz; /* FREQ = PCLK1 in MHz */ + /* Standard mode 100 kHz: CCR = PCLK1 / (2 * 100k); TRISE = PCLK1(MHz) + 1. */ + I2CD1.i2c->CCR = STM32_PCLK1 / (2U * 100000U); + I2CD1.i2c->TRISE = pclk1_mhz + 1U; + I2CD1.i2c->CR1 = I2C_CR1_PE; /* enable */ +} + +/* Wait until (SR1 & mask) == mask, or bail. Returns false on timeout. */ +static bool i2c_wait_sr1(uint32_t mask) { + for (uint32_t i = 0; i < POLL_TIMEOUT_LOOPS; i++) { + if ((I2CD1.i2c->SR1 & mask) == mask) return true; + } + return false; +} + +/* Wait for a just-issued STOP to complete (CR1.STOP self-clears) and BUSY to + * drop, so the bus is genuinely idle before the next hand's transaction. */ +static void i2c_wait_stop(void) { + for (uint32_t i = 0; i < POLL_TIMEOUT_LOOPS; i++) { + if (!(I2CD1.i2c->CR1 & I2C_CR1_STOP) && !(I2CD1.i2c->SR2 & I2C_SR2_BUSY)) return; + } +} + +/* Blocking, polled read of `n` bytes from a pre-shifted 8-bit address (QMK's + * convention). Returns the number of bytes actually read. */ +static int i2c_poll_read(uint8_t addr8, uint8_t *dst, int n) { + uint8_t addr7 = addr8 >> 1; + + /* ChibiOS gates the I2C1 clock when it thinks the peripheral is unused (we + * don't hold its driver), so re-assert it each transaction. */ + rccEnableI2C1(true); + I2CD1.i2c->CR1 |= I2C_CR1_PE | I2C_CR1_ACK; + + for (uint32_t i = 0; (I2CD1.i2c->SR2 & I2C_SR2_BUSY); i++) { + if (i >= POLL_TIMEOUT_LOOPS) return 0; + } + + I2CD1.i2c->CR1 |= I2C_CR1_START; + if (!i2c_wait_sr1(I2C_SR1_SB)) return 0; + + I2CD1.i2c->DR = (uint32_t)((addr7 << 1) | 1U); /* read */ + if (!i2c_wait_sr1(I2C_SR1_ADDR)) { I2CD1.i2c->CR1 |= I2C_CR1_STOP; i2c_wait_stop(); return 0; } + (void)I2CD1.i2c->SR2; /* clear ADDR */ + + int got = 0; + while (n > 0) { + if (n == 1) { + I2CD1.i2c->CR1 &= ~I2C_CR1_ACK; /* NACK the last byte */ + I2CD1.i2c->CR1 |= I2C_CR1_STOP; + if (!i2c_wait_sr1(I2C_SR1_RXNE)) { i2c_wait_stop(); return got; } + dst[got++] = (uint8_t)I2CD1.i2c->DR; + n = 0; + } else { + if (!i2c_wait_sr1(I2C_SR1_RXNE)) { I2CD1.i2c->CR1 |= I2C_CR1_STOP; i2c_wait_stop(); return got; } + dst[got++] = (uint8_t)I2CD1.i2c->DR; + n--; + } + } + i2c_wait_stop(); + return got; +} + +/* Blocking, polled write. Returns true on success. Shared with leds.c via + * model100.h so the LED driver uses the same working polled path. */ +bool i2c_poll_write(uint8_t addr8, const uint8_t *src, int n) { + uint8_t addr7 = addr8 >> 1; + + rccEnableI2C1(true); + I2CD1.i2c->CR1 |= I2C_CR1_PE; + + for (uint32_t i = 0; (I2CD1.i2c->SR2 & I2C_SR2_BUSY); i++) { + if (i >= POLL_TIMEOUT_LOOPS) return false; + } + + I2CD1.i2c->CR1 |= I2C_CR1_START; + if (!i2c_wait_sr1(I2C_SR1_SB)) return false; + + I2CD1.i2c->DR = (uint32_t)(addr7 << 1); /* write */ + if (!i2c_wait_sr1(I2C_SR1_ADDR)) { I2CD1.i2c->CR1 |= I2C_CR1_STOP; return false; } + (void)I2CD1.i2c->SR2; + + for (int i = 0; i < n; i++) { + if (!i2c_wait_sr1(I2C_SR1_TXE)) { I2CD1.i2c->CR1 |= I2C_CR1_STOP; return false; } + I2CD1.i2c->DR = src[i]; + if (!i2c_wait_sr1(I2C_SR1_BTF)) { I2CD1.i2c->CR1 |= I2C_CR1_STOP; i2c_wait_stop(); return false; } + } + I2CD1.i2c->CR1 |= I2C_CR1_STOP; + i2c_wait_stop(); + return true; +} + +static void i2c_read_hand(int hand, matrix_row_t current_matrix[]) { + uint8_t buf[5] = {0}; + if (i2c_poll_read(I2C_ADDR(hand), buf, sizeof(buf)) != (int)sizeof(buf)) { + return; + } + /* Only KEYDATA replies carry a fresh row snapshot; the scanner returns other + * reply types (or no-data) when nothing new is available — leave the matrix + * untouched in that case so held keys stay held. */ + if (buf[0] != TWI_REPLY_KEYDATA) { + return; + } + + int start_row = hand ? ROWS_PER_HAND : 0; + memcpy(¤t_matrix[start_row], &buf[1], ROWS_PER_HAND); +} + +static void i2c_set_keyscan_interval(int hand, int delay) { + uint8_t buf[] = {TWI_CMD_KEYSCAN_INTERVAL, (uint8_t)delay}; + i2c_poll_write(I2C_ADDR(hand), buf, sizeof(buf)); +} + +void matrix_init_custom(void) { + /* Turn on the switched 5V network that powers the scanners. On the Model 100 + * this is an open-drain output driven LOW to enable (unlike the Model 01's + * push-pull C7). The ATtiny scanners take ~65-76ms to boot after power-up + * (Keyboardio's DAPBoot notes it works at 76ms but not 75ms and waits 125ms); + * talk to them too soon and they never answer, so match DAPBoot's 125ms. */ + gpio_set_pin_output_open_drain(B9); + gpio_write_pin_low(B9); + wait_ms(125); + + i2c_poll_setup(); + + i2c_set_keyscan_interval(LEFT, 2); + i2c_set_keyscan_interval(RIGHT, 2); +} + +bool matrix_scan_custom(matrix_row_t current_matrix[]) { + matrix_row_t last_matrix[MATRIX_ROWS]; + memcpy(last_matrix, current_matrix, sizeof(last_matrix)); + + i2c_read_hand(LEFT, current_matrix); + i2c_read_hand(RIGHT, current_matrix); + + return memcmp(last_matrix, current_matrix, sizeof(last_matrix)) != 0; +} + +/* vim: set ts=2 sw=2 et: */ diff --git a/keyboards/keyboardio/model100/mcuconf.h b/keyboards/keyboardio/model100/mcuconf.h new file mode 100644 index 000000000000..3ca8d4aec837 --- /dev/null +++ b/keyboards/keyboardio/model100/mcuconf.h @@ -0,0 +1,39 @@ +/* Copyright 2025 QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include_next + +#undef STM32_I2C_USE_I2C1 +#define STM32_I2C_USE_I2C1 TRUE + +/* Run off the internal HSI RC oscillator with HSE disabled. Keyboardio's own + * DAPBoot for this board is built with USE_HSI (CLOCK_MHZ 48), i.e. it does not + * rely on an external crystal — so the Model 100 very likely has no usable HSE, + * and a ChibiOS HSE config would spin forever on HSERDY in stm32_clock_init. + * On STM32F103 the HSI PLL source is fixed at HSI/2 = 4 MHz; x12 = 48 MHz + * sysclk, USBPRE /1 = 48 MHz USB. */ +#undef STM32_HSE_ENABLED +#define STM32_HSE_ENABLED FALSE +#undef STM32_PLLSRC +#define STM32_PLLSRC STM32_PLLSRC_HSI +#undef STM32_PLLMUL_VALUE +#define STM32_PLLMUL_VALUE 12 +#undef STM32_USBPRE +#define STM32_USBPRE STM32_USBPRE_DIV1 +#undef STM32_RTCSEL +#define STM32_RTCSEL STM32_RTCSEL_NOCLOCK diff --git a/keyboards/keyboardio/model100/model100.h b/keyboards/keyboardio/model100/model100.h new file mode 100644 index 000000000000..8a5ba8314e65 --- /dev/null +++ b/keyboards/keyboardio/model100/model100.h @@ -0,0 +1,35 @@ +/* Copyright 2018 James Laird-Wah + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "quantum.h" + +#include "wire-protocol-constants.h" +#define I2C_ADDR_LEFT (0x58 << 1) +#define I2C_ADDR_RIGHT (I2C_ADDR_LEFT + 6) +#define I2C_ADDR(hand) ((hand) ? I2C_ADDR_RIGHT : I2C_ADDR_LEFT) +#define LEFT 0 +#define RIGHT 1 + +/* Polled (non-DMA) I2C for the scanners, implemented in matrix.c. Both the key + * matrix and the LED driver (leds.c) share this: QMK's ChibiOS DMA i2c_transmit + * does not work on the GD32F303, and mixing it with our polled reads corrupts + * the bus. addr8 is the pre-shifted 8-bit address (I2C_ADDR macro). */ +bool i2c_poll_write(uint8_t addr8, const uint8_t *src, int n); + +#include "leds.h" + +/* vim: set ts=2 sw=2 et: */ diff --git a/keyboards/keyboardio/model100/readme.md b/keyboards/keyboardio/model100/readme.md new file mode 100644 index 000000000000..7e2c2f277a40 --- /dev/null +++ b/keyboards/keyboardio/model100/readme.md @@ -0,0 +1,100 @@ +# Keyboardio Model 100 + +A split keyboard. + +* Keyboard Maintainer: QMK Community +* Hardware Supported: Keyboardio Model 100 +* Hardware Availability: [Keyboardio](https://shop.keyboard.io) + +Make example for this keyboard (after setting up your build environment): + + make keyboardio/model100:default + +Flashing example for this keyboard: + + make keyboardio/model100:default:flash + +See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. +Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs). + +## Bootloader + +The Model 100 ships Keyboardio's DAPBoot DFU bootloader. To enter it, press the +`Prog` key (upper left) or hold it while plugging the keyboard in. + +DAPBoot reserves the first 8 KB of flash and expects the application's vector +table at `0x08002000`. We link there by naming the stm32duino offset linker +script (`MCU_LDSCRIPT = STM32F103x8_stm32duino` in `rules.mk`) while declaring +`"board": "STM32_F103_STM32DUINO"` in `keyboard.json` — rather than setting +`"bootloader": "stm32duino"`, which would force the Maple-default DFU +identifiers and clobber ours. + +In DFU mode the Model 100 enumerates as `3496:0005` (the application enumerates +as `3496:0006`), so `rules.mk` sets `DFU_ARGS`/`DFU_SUFFIX_ARGS` accordingly. +DAPBoot is a plain-DFU (non-DfuSe) bootloader, so it takes no `-s ` +specifier; `make keyboardio/model100:default:flash` drives dfu-util as: + + dfu-util -d 3496:0005 -a 0 -R -D .bin + +It is *very* hard to brick a Model 100 short of overwriting the bootloader +region itself, which requires specialised hardware, so experimentation is safe. + +> **Note:** software-triggered bootloader entry via `QK_BOOT` issues +> `NVIC_SystemReset()`; DAPBoot has its own entry condition, so a bare reset is +> not guaranteed to drop into DFU. The physical `Prog`-key method (hold while +> plugging in) is the reliable fallback. + +## Porting notes + +The Model 100 is architecturally almost identical to the Model 01: both halves +are ATtiny key scanners that the main MCU talks to over I2C, using the same wire +protocol (`wire-protocol-constants.h`), the same scanner addresses, and the same +64-LED (4 banks x 8, per hand) layout. + +What differs is the **main MCU**: + +| Function | Model 01 (ATmega32U4) | Model 100 (GD32F303CG) | +| --------------------- | --------------------- | ---------------------- | +| MCU family | AVR | ARM Cortex-M4 | +| Scanner power enable | `C7`, push-pull, HIGH | `B9`, open-drain, LOW | +| Power-sense inputs | `B4` | `B14`, `B15` | +| I2C to scanners | AVR TWI | I2C1 = `B6` / `B7` | +| USB D+ pull-up | built-in | GPIO `A8` (active-high)| + +The GD32F303 is register-compatible enough with the STM32F103 high-density line +that ChibiOS's existing STM32F1 HAL drives it. We build it as `STM32F103` with +`board.h` selecting the high-density (`STM32F103xE`) variant, following the +precedent set by `keyboards/mlego/m65/rev2`. No dedicated GD32 ChibiOS port is +needed (the in-tree `GD32VF103` support is the unrelated RISC-V part). + +Three Model-100-specific details were needed to bring it up on real hardware: + +* **Clock:** the board has no usable HSE crystal (its DAPBoot runs on HSI), so + `mcuconf.h` runs the MCU from the internal HSI oscillator (HSI/2 x 12 = 48 MHz + sysclk, giving a valid 48 MHz USB clock). A stock HSE config hangs at boot + waiting for `HSERDY`. +* **USB pull-up:** the D+ pull-up is gated by GPIO `A8` (active-high), not a + fixed resistor, so `board.h` overrides `usb_lld_connect_bus`/`disconnect_bus` + to drive `A8`. Without it the host never sees the device. +* **Scanner I2C:** ChibiOS's STM32 I2C driver is DMA-based and does not work on + the GD32's I2C peripheral, so `matrix.c` drives I2C1 with a small polled + (register-level) implementation, which `leds.c` shares via `i2c_poll_write()`. + +Pin assignments and the DFU/HSI/pull-up details were taken from the Kaleidoscope +Model 100 hardware plugin (`kaleidoscope/device/keyboardio/Model100.cpp`), the +`ArduinoCore-GD32-Keyboardio` `keyboardio_model_100` variant, and Keyboardio's +DAPBoot fork (`gd32-bootloader-dfu-dapboot`). + +The `Prog` key is at matrix `[0, 7]` (upper left), the same position as on the +Model 01, and the default keymap places `QK_BOOT` there. + +## Features + +This implements the standard keymap, including mousekeys. + +It doesn't do cursor warping - QMK does not support absolute mouse positioning. + +RGB matrix is supported (per-key, both hands). LED colour data is sent to the +scanners bank-by-bank, interleaved between the two hands to avoid overrunning +the ATtiny controllers. Gamma compensation and the hardware's high-speed batch +LED update functions are not implemented. diff --git a/keyboards/keyboardio/model100/rules.mk b/keyboards/keyboardio/model100/rules.mk new file mode 100644 index 000000000000..72f40be3e27d --- /dev/null +++ b/keyboards/keyboardio/model100/rules.mk @@ -0,0 +1,30 @@ +CUSTOM_MATRIX = lite +I2C_DRIVER_REQUIRED = yes +SRC += leds.c \ + matrix.c + +# We deliberately do NOT set `bootloader: stm32duino` in keyboard.json. That +# would pull in platforms/chibios/bootloader.mk's stm32duino block, which +# hard-assigns (with `=`, not `?=`) the Maple defaults +# `DFU_ARGS = -d 1EAF:0003 -a 2 -R` / `DFU_SUFFIX_ARGS = -v 1EAF -p 0003` +# *after* this file is read, clobbering any override we set here. Instead we set +# the same flags the stm32duino block would have (board is declared in +# keyboard.json) and supply our own DFU identifiers. Same approach as +# keyboards/handwired/onekey/bluepill_f103c6. +OPT_DEFS += -DBOOTLOADER_STM32DUINO +BOOTLOADER_TYPE = stm32duino +# Because we don't set BOOTLOADER=stm32duino, the ld-script auto-selection in +# platforms/chibios/platform.mk (which appends `_$(BOOTLOADER)`) won't find the +# offset script, and we'd silently fall back to plain STM32F103x8.ld linking at +# 0x08000000 — the firmware then flashes but never runs, because DAPBoot hands +# control to 0x08002000. Name the stm32duino offset script explicitly so the +# app vector table lands at 0x08002000 (matches keyboards/mlego/m65/rev2). +MCU_LDSCRIPT = STM32F103x8_stm32duino + +# The Model 100 ships Keyboardio's DAPBoot DFU bootloader, which enumerates as +# 3496:0005 rather than the Maple default 1EAF:0003. DAPBoot is a plain-DFU +# (non-DfuSe) bootloader that writes to its own fixed app base (0x08002000) via +# alt setting 0, so we must NOT pass a DfuSe `-s :leave` specifier — some +# dfu-util builds error out parsing it. Just select the device + alt 0 and reset. +DFU_ARGS = -d 3496:0005 -a 0 -R +DFU_SUFFIX_ARGS = -v 3496 -p 0005 diff --git a/keyboards/keyboardio/model100/wire-protocol-constants.h b/keyboards/keyboardio/model100/wire-protocol-constants.h new file mode 100644 index 000000000000..90a528582a07 --- /dev/null +++ b/keyboards/keyboardio/model100/wire-protocol-constants.h @@ -0,0 +1,53 @@ +/* + * This file is taken from the KeyboardioScanner module: + * https://github.com/keyboardio/KeyboardioScanner/blob/master/wire-protocol-constants.h + * + * Copyright (C) 2015-2018 Keyboard.io, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#define TWI_CMD_NONE 0x00 +#define TWI_CMD_VERSION 0x01 +#define TWI_CMD_KEYSCAN_INTERVAL 0x02 +#define TWI_CMD_LED_SET_ALL_TO 0x03 +#define TWI_CMD_LED_SET_ONE_TO 0x04 +#define TWI_CMD_COLS_USE_PULLUPS 0x05 +#define TWI_CMD_LED_SPI_FREQUENCY 0x06 + +#define LED_SPI_FREQUENCY_4MHZ 0x07 +#define LED_SPI_FREQUENCY_2MHZ 0x06 +#define LED_SPI_FREQUENCY_1MHZ 0x05 +#define LED_SPI_FREQUENCY_512KHZ 0x04 +#define LED_SPI_FREQUENCY_256KHZ 0x03 +#define LED_SPI_FREQUENCY_128KHZ 0x02 +#define LED_SPI_FREQUENCY_64KHZ 0x01 +#define LED_SPI_OFF 0x00 + + +// 512KHZ seems to be the sweet spot in early testing +// so make it the default +#define LED_SPI_FREQUENCY_DEFAULT LED_SPI_FREQUENCY_512KHZ + + +#define TWI_CMD_LED_BASE 0x80 + +#define TWI_REPLY_NONE 0x00 +#define TWI_REPLY_KEYDATA 0x01