From 437025dcb1dd4064e940cd5dfb2bb06e8ae7b0f2 Mon Sep 17 00:00:00 2001 From: heerelectronics Date: Wed, 5 Aug 2026 11:16:16 +0530 Subject: [PATCH] Add HEER S1 macropad (#26373) * Add HEER S1 macropad * Address review feedback for HEER S1 - readme: add product image, correct maintainer link and availability URL - config.h: drop OLED_DISPLAY_ADDRESS, already the driver default - keyboard.json: drop the i2c block, which only feeds split transport - move the OLED to heer_s1.c as oled_task_kb - default keymap reduced to keymaps[] and encoder_map[] The default keymap also configured GP16/GP17 as GPIO inputs at boot. Those are the OLED's I2C SDA/SCL lines, so it tore down the display bus on every start. The encoder switches are on GP2/GP14. Removed along with the rest of the code. Co-Authored-By: Claude Opus 5 * Update keyboards/heer_s1/keymaps/default/keymap.c Co-authored-by: Joel Challis * Update keyboards/heer_s1/keymaps/default/keymap.c Co-authored-by: Joel Challis * Update keyboards/heer_s1/keymaps/default/keymap.c Co-authored-by: Joel Challis * Update keyboards/heer_s1/keymaps/default/keymap.c Co-authored-by: Joel Challis * Update keyboards/heer_s1/keyboard.json Co-authored-by: Joel Challis * Update keyboards/heer_s1/readme.md Co-authored-by: Joel Challis * Update keyboards/heer_s1/keymaps/default/keymap.c Co-authored-by: Joel Challis * Update keyboards/heer_s1/keyboard.json Co-authored-by: Joel Challis * Update keyboards/heer_s1/keyboard.json Co-authored-by: Joel Challis * Fix duplicated enumerators in default keymap The suggested enum replacement was applied alongside the original entries rather than in place of them, leaving L_BASE, L_MEDIA and L_EDIT declared twice. Co-Authored-By: Claude Opus 5 * Update keyboards/heer_s1/readme.md Co-authored-by: Joel Challis * Update keyboards/heer_s1/keyboard.json Co-authored-by: Joel Challis --------- Co-authored-by: HEER Co-authored-by: Claude Opus 5 Co-authored-by: Joel Challis --- keyboards/heer_s1/config.h | 32 +++++++++++++ keyboards/heer_s1/halconf.h | 8 ++++ keyboards/heer_s1/heer_s1.c | 51 ++++++++++++++++++++ keyboards/heer_s1/keyboard.json | 56 ++++++++++++++++++++++ keyboards/heer_s1/keymaps/default/keymap.c | 56 ++++++++++++++++++++++ keyboards/heer_s1/keymaps/default/rules.mk | 1 + keyboards/heer_s1/mcuconf.h | 9 ++++ keyboards/heer_s1/readme.md | 27 +++++++++++ 8 files changed, 240 insertions(+) create mode 100644 keyboards/heer_s1/config.h create mode 100644 keyboards/heer_s1/halconf.h create mode 100644 keyboards/heer_s1/heer_s1.c create mode 100644 keyboards/heer_s1/keyboard.json create mode 100644 keyboards/heer_s1/keymaps/default/keymap.c create mode 100644 keyboards/heer_s1/keymaps/default/rules.mk create mode 100644 keyboards/heer_s1/mcuconf.h create mode 100644 keyboards/heer_s1/readme.md diff --git a/keyboards/heer_s1/config.h b/keyboards/heer_s1/config.h new file mode 100644 index 00000000000..8935cb74afe --- /dev/null +++ b/keyboards/heer_s1/config.h @@ -0,0 +1,32 @@ +// Copyright 2026 Heer (@heer) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// ── OLED (SH1106 1.3", 128×64, I2C address 0x3C) ──────────────────────────── +#define OLED_DISPLAY_128X64 +#define OLED_IC OLED_IC_SH1106 +#define OLED_COLUMN_OFFSET 2 +#define OLED_BRIGHTNESS 180 +#define OLED_TIMEOUT 0 +#define OLED_UPDATE_INTERVAL 10 +// NOTE: do NOT raise OLED_UPDATE_PROCESS_LIMIT. The default of 1 keeps each I2C +// transaction to a single 32-byte block. +// +// TESTED, TWICE, ON HARDWARE: a limit of 4 — only 128 bytes, one page, the write +// size stock SSD1306/SH1106 drivers use — blanks this panel completely. The board +// keeps running (keys and encoders work) but the display never comes up. The +// reasoning that "a page is safe because everyone writes pages" is wrong for this +// module. Do not try it again. +// +// OLED_UPDATE_INTERVAL is a different knob and is safe to shorten: it changes how +// OFTEN a block is flushed, not how BIG the transaction is, which stays one +// 32-byte block. At the old 33ms, swapping the right-hand panel between the key +// grid and the encoder bars took ~21 blocks * 33ms = ~700ms to reach the glass — +// you would turn the knob and see the bar most of a second later. 10ms puts that +// at ~210ms. If the panel ever misbehaves, this is the first value to put back. + +// ── I2C (OLED on GP16=SDA, GP17=SCL via RP2040 I2C0) ──────────────────────── +#define I2C_DRIVER I2CD0 +#define I2C1_SDA_PIN GP16 +#define I2C1_SCL_PIN GP17 diff --git a/keyboards/heer_s1/halconf.h b/keyboards/heer_s1/halconf.h new file mode 100644 index 00000000000..c809c7e7126 --- /dev/null +++ b/keyboards/heer_s1/halconf.h @@ -0,0 +1,8 @@ +// Copyright 2026 Heer (@heer) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#define HAL_USE_I2C TRUE + +#include_next diff --git a/keyboards/heer_s1/heer_s1.c b/keyboards/heer_s1/heer_s1.c new file mode 100644 index 00000000000..8cd40340312 --- /dev/null +++ b/keyboards/heer_s1/heer_s1.c @@ -0,0 +1,51 @@ +// Copyright 2026 Heer (@heer) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" + +#ifdef OLED_ENABLE + +// The panel is mounted upside-down on the PCB, so the rotation belongs to the +// board rather than to any one keymap. +// +// This returns the rotation and nothing else. oled_driver.c does: +// +// oled_rotation = oled_init_user(oled_init_kb(rotation)); +// +// so the driver already chains the keymap's hook onto this one's result — +// calling oled_init_user() from in here would run it twice. +oled_rotation_t oled_init_kb(oled_rotation_t rotation) { + return OLED_ROTATION_180; +} + +// A keymap that draws its own screen returns false from oled_task_user, which +// stops the board's default display painting over it. This mirrors the weak +// default in oled_driver.c, which is simply `return oled_task_user();`. +bool oled_task_kb(void) { + if (!oled_task_user()) { + return false; + } + + oled_set_cursor(0, 0); + oled_write_P(PSTR(" HEER S1"), false); + + oled_set_cursor(0, 1); + oled_write_P(PSTR("---------------"), false); + + // Shown as a number, not a name: a board-level default cannot know what any + // given keymap calls its layers. + oled_set_cursor(0, 2); + oled_write_P(PSTR("LAYER "), false); + oled_write_char('0' + get_highest_layer(layer_state), false); + + oled_set_cursor(0, 3); + oled_write_P(PSTR("["), false); + for (uint8_t i = 0; i < 4; i++) { + oled_write_P(get_highest_layer(layer_state) == i ? PSTR("*") : PSTR("-"), false); + } + oled_write_P(PSTR("]"), false); + + return true; +} + +#endif // OLED_ENABLE diff --git a/keyboards/heer_s1/keyboard.json b/keyboards/heer_s1/keyboard.json new file mode 100644 index 00000000000..271d9ab3d65 --- /dev/null +++ b/keyboards/heer_s1/keyboard.json @@ -0,0 +1,56 @@ +{ + "manufacturer": "Heer Works", + "keyboard_name": "HEER S1", + "maintainer": "heer", + "bootloader": "rp2040", + "processor": "RP2040", + "usb": { + "vid": "0x4857", + "pid": "0x5331", + "device_version": "1.0.0" + }, + "matrix_pins": { + "masked": true, + "cols": ["GP8", "GP9", "GP10", "GP11"], + "rows": ["GP4", "GP5", "GP6", "GP7", "GP2", "GP14"] + }, + "diode_direction": "ROW2COL", + "encoder": { + "rotary": [ + {"pin_a": "GP0", "pin_b": "GP1"}, + {"pin_a": "GP12", "pin_b": "GP13"} + ] + }, + "features": { + "bootmagic": true, + "encoder": true, + "extrakey": true, + "mousekey": true, + "nkro": true, + "oled": true + }, + "layouts": { + "LAYOUT": { + "layout": [ + {"matrix": [4, 0], "x": 0, "y": 0, "encoder": 0}, + {"matrix": [5, 0], "x": 1, "y": 0, "encoder": 1}, + {"matrix": [0, 0], "x": 0, "y": 1}, + {"matrix": [0, 1], "x": 1, "y": 1}, + {"matrix": [0, 2], "x": 2, "y": 1}, + {"matrix": [0, 3], "x": 3, "y": 1}, + {"matrix": [1, 0], "x": 0, "y": 2}, + {"matrix": [1, 1], "x": 1, "y": 2}, + {"matrix": [1, 2], "x": 2, "y": 2}, + {"matrix": [1, 3], "x": 3, "y": 2}, + {"matrix": [2, 0], "x": 0, "y": 3}, + {"matrix": [2, 1], "x": 1, "y": 3}, + {"matrix": [2, 2], "x": 2, "y": 3}, + {"matrix": [2, 3], "x": 3, "y": 3}, + {"matrix": [3, 0], "x": 0, "y": 4}, + {"matrix": [3, 1], "x": 1, "y": 4}, + {"matrix": [3, 2], "x": 2, "y": 4}, + {"matrix": [3, 3], "x": 3, "y": 4} + ] + } + } +} diff --git a/keyboards/heer_s1/keymaps/default/keymap.c b/keyboards/heer_s1/keymaps/default/keymap.c new file mode 100644 index 00000000000..d106b23e774 --- /dev/null +++ b/keyboards/heer_s1/keymaps/default/keymap.c @@ -0,0 +1,56 @@ +// Copyright 2026 Heer (@heer) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include QMK_KEYBOARD_H + +enum layers { + L_BASE, + L_MEDIA, + L_EDIT, + L_SYS, +}; + +// The bottom-right key advances to the next layer and wraps, so all four are +// reachable using nothing but standard keycodes. +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + [L_BASE] = LAYOUT( + TO(L_MEDIA), TO(L_BASE), + KC_1, KC_2, KC_3, KC_4, + KC_5, KC_6, KC_7, KC_8, + KC_9, KC_0, KC_MINS, KC_EQL, + KC_BSPC, KC_ENT, KC_ESC, TO(L_MEDIA) + ), + + [L_MEDIA] = LAYOUT( + TO(L_EDIT), TO(L_BASE), + KC_MPLY, KC_MSTP, KC_MPRV, KC_MNXT, + KC_VOLD, KC_VOLU, KC_MUTE, _______, + KC_BRID, KC_BRIU, _______, _______, + _______, _______, _______, TO(L_EDIT) + ), + + [L_EDIT] = LAYOUT( + TO(L_SYS), TO(L_BASE), + LCTL(KC_Z), LCTL(KC_X), LCTL(KC_C), LCTL(KC_V), + LCTL(KC_A), LCTL(KC_S), LCTL(KC_F), LCTL(KC_H), + LCTL(KC_N), LCTL(KC_W), LCTL(KC_T), LCTL(KC_P), + KC_DEL, KC_HOME, KC_END, TO(L_SYS) + ), + + [L_SYS] = LAYOUT( + TO(L_BASE), TO(L_BASE), + KC_F1, KC_F2, KC_F3, KC_F4, + KC_F5, KC_F6, KC_F7, KC_F8, + KC_F9, KC_F10, KC_F11, KC_F12, + KC_PSCR, KC_SCRL, KC_PAUS, TO(L_BASE) + ), +}; + +#if defined(ENCODER_MAP_ENABLE) +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { + [L_BASE] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU), ENCODER_CCW_CW(KC_LEFT, KC_RGHT) }, + [L_MEDIA] = { ENCODER_CCW_CW(KC_MPRV, KC_MNXT), ENCODER_CCW_CW(KC_BRID, KC_BRIU) }, + [L_EDIT] = { ENCODER_CCW_CW(LCTL(KC_Z), LCTL(KC_Y)), ENCODER_CCW_CW(KC_UP, KC_DOWN) }, + [L_SYS] = { ENCODER_CCW_CW(KC_F11, KC_F12), ENCODER_CCW_CW(KC_PGDN, KC_PGUP) }, +}; +#endif diff --git a/keyboards/heer_s1/keymaps/default/rules.mk b/keyboards/heer_s1/keymaps/default/rules.mk new file mode 100644 index 00000000000..ee325681483 --- /dev/null +++ b/keyboards/heer_s1/keymaps/default/rules.mk @@ -0,0 +1 @@ +ENCODER_MAP_ENABLE = yes diff --git a/keyboards/heer_s1/mcuconf.h b/keyboards/heer_s1/mcuconf.h new file mode 100644 index 00000000000..0930d1538da --- /dev/null +++ b/keyboards/heer_s1/mcuconf.h @@ -0,0 +1,9 @@ +// Copyright 2026 Heer (@heer) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include_next + +#undef RP_I2C_USE_I2C0 +#define RP_I2C_USE_I2C0 TRUE diff --git a/keyboards/heer_s1/readme.md b/keyboards/heer_s1/readme.md new file mode 100644 index 00000000000..09293b7c1e4 --- /dev/null +++ b/keyboards/heer_s1/readme.md @@ -0,0 +1,27 @@ +# HEER S1 + +![HEER S1](https://i.imgur.com/rSxQHUi.png) + +A 4x4 RP2040 macropad with two EC11 rotary encoders and a 128x64 SH1106 OLED. + +* Keyboard Maintainer: [Heer Electronics](https://github.com/heerelectronics) +* Hardware Supported: HEER S1 PCB (RP2040 QFN-56) +* Hardware Availability: https://www.heerelectronics.com + +Make example for this keyboard (after setting up your build environment): + + make heer_s1:default + +Flashing example for this keyboard: + + make heer_s1: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 + +Enter the bootloader in 3 ways: + +* **Bootmagic reset**: Hold down the key at (0,0) in the matrix (the top left key) and plug in the keyboard +* **Physical reset button**: Briefly press the button on the back of the PCB +* **Keycode in layout**: Press the key mapped to `QK_BOOT` (bottom row of the SYSTEM layer)