From 39d0a14258cbd1dd640405cdbc806dadb01521a8 Mon Sep 17 00:00:00 2001 From: John Barbero Date: Thu, 9 Nov 2023 18:52:47 +0100 Subject: [PATCH] Add SNES Macropad keyboard (#22377) Co-authored-by: jack <0x6a73@protonmail.com> --- keyboards/snes_macropad/config.h | 12 ++ keyboards/snes_macropad/halconf.h | 8 + keyboards/snes_macropad/info.json | 79 ++++++++++ .../snes_macropad/keymaps/default/keymap.c | 75 +++++++++ .../snes_macropad/keymaps/jbarberu/keymap.c | 99 ++++++++++++ keyboards/snes_macropad/keymaps/test/keymap.c | 75 +++++++++ keyboards/snes_macropad/matrix.c | 146 ++++++++++++++++++ keyboards/snes_macropad/mcuconf.h | 18 +++ keyboards/snes_macropad/readme.md | 36 +++++ keyboards/snes_macropad/rules.mk | 4 + keyboards/snes_macropad/snes_macropad.c | 130 ++++++++++++++++ 11 files changed, 682 insertions(+) create mode 100644 keyboards/snes_macropad/config.h create mode 100644 keyboards/snes_macropad/halconf.h create mode 100644 keyboards/snes_macropad/info.json create mode 100644 keyboards/snes_macropad/keymaps/default/keymap.c create mode 100644 keyboards/snes_macropad/keymaps/jbarberu/keymap.c create mode 100644 keyboards/snes_macropad/keymaps/test/keymap.c create mode 100644 keyboards/snes_macropad/matrix.c create mode 100644 keyboards/snes_macropad/mcuconf.h create mode 100644 keyboards/snes_macropad/readme.md create mode 100644 keyboards/snes_macropad/rules.mk create mode 100644 keyboards/snes_macropad/snes_macropad.c diff --git a/keyboards/snes_macropad/config.h b/keyboards/snes_macropad/config.h new file mode 100644 index 00000000000..c5edeb55f1d --- /dev/null +++ b/keyboards/snes_macropad/config.h @@ -0,0 +1,12 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET +#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_LED GP25 +#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 500U + +#define I2C_DRIVER I2CD1 +#define I2C1_SDA_PIN GP14 +#define I2C1_SCL_PIN GP15 diff --git a/keyboards/snes_macropad/halconf.h b/keyboards/snes_macropad/halconf.h new file mode 100644 index 00000000000..3fcb2f4eb60 --- /dev/null +++ b/keyboards/snes_macropad/halconf.h @@ -0,0 +1,8 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#define HAL_USE_I2C TRUE + +#include_next diff --git a/keyboards/snes_macropad/info.json b/keyboards/snes_macropad/info.json new file mode 100644 index 00000000000..c54e9bb0b26 --- /dev/null +++ b/keyboards/snes_macropad/info.json @@ -0,0 +1,79 @@ +{ + "manufacturer": "JBarberU's", + "keyboard_name": "SNES Macropad", + "maintainer": "jbarberu", + "bootloader": "rp2040", + "diode_direction": "COL2ROW", + "features": { + "bootmagic": false, + "command": false, + "console": true, + "extrakey": true, + "mousekey": true, + "nkro": true, + "rgblight": true, + "oled": true + }, + "ws2812": { + "pin": "GP5", + "driver": "vendor" + }, + "processor": "RP2040", + "matrix_size": { + "cols": 4, + "rows": 6 + }, + "url": "", + "usb": { + "device_version": "1.0.0", + "pid": "0x0000", + "vid": "0xFEED" + }, + "layouts": { + "LAYOUT": { + "layout": [ + {"matrix": [0, 0], "x": 0, "y": 0}, + {"matrix": [0, 1], "x": 1, "y": 0}, + {"matrix": [0, 2], "x": 2, "y": 0}, + {"matrix": [0, 3], "x": 3, "y": 0}, + {"matrix": [1, 0], "x": 0, "y": 1}, + {"matrix": [1, 1], "x": 1, "y": 1}, + {"matrix": [1, 2], "x": 2, "y": 1}, + {"matrix": [1, 3], "x": 3, "y": 1}, + {"matrix": [2, 0], "x": 0, "y": 2}, + {"matrix": [2, 1], "x": 1, "y": 2}, + {"matrix": [2, 2], "x": 2, "y": 2}, + {"matrix": [2, 3], "x": 3, "y": 2}, + + {"matrix": [3, 0], "x": 0, "y": 3}, + {"matrix": [3, 1], "x": 1, "y": 3}, + {"matrix": [3, 2], "x": 2, "y": 3}, + {"matrix": [3, 3], "x": 3, "y": 3}, + {"matrix": [4, 0], "x": 0, "y": 4}, + {"matrix": [4, 1], "x": 1, "y": 4}, + {"matrix": [4, 2], "x": 2, "y": 4}, + {"matrix": [4, 3], "x": 3, "y": 4}, + {"matrix": [5, 0], "x": 0, "y": 5}, + {"matrix": [5, 1], "x": 1, "y": 5}, + {"matrix": [5, 2], "x": 2, "y": 5}, + {"matrix": [5, 3], "x": 3, "y": 5} + ] + } + }, + "rgblight": { + "led_count": 12, + "max_brightness": 80, + "animations": { + "alternating": true, + "breathing": true, + "christmas": true, + "knight": true, + "rainbow_mood": true, + "rainbow_swirl": true, + "rgb_test": true, + "snake": true, + "static_gradient": true, + "twinkle": true + } + } +} diff --git a/keyboards/snes_macropad/keymaps/default/keymap.c b/keyboards/snes_macropad/keymaps/default/keymap.c new file mode 100644 index 00000000000..34f4f6248a0 --- /dev/null +++ b/keyboards/snes_macropad/keymaps/default/keymap.c @@ -0,0 +1,75 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include QMK_KEYBOARD_H + +enum Layer { + L_Numpad = 0, + L_Symbols, + L_RGB, +}; + +// clang-format off +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + /* + * Macropad Button Order + * ┌───┬───┬───┬───┐ + * │ 7 │ 8 │ 9 │ - │ + * ├───┼───┼───┼───┤ + * │ 4 │ 5 │ 6 │ + │ + * ├───┼───┼───┼───┤ + * │ 1 │ 2 │ 3 │ 0 │ + * └───┴───┴───┴───┘ + * + * SNES Button Order + * ┌────────┬────────┬────────┬────────┐ + * │ LT │ RT │ START │ SELECT │ + * ├────────┼────────┼────────┼────────┤ + * │ UP │ DOWN │ LEFT │ RIGHT │ + * ├────────┼────────┼────────┼────────┤ + * │ A │ B │ X │ Y │ + * └────────┴────────┴────────┴────────┘ + * + */ + [L_Numpad] = LAYOUT( + KC_P7, KC_P8, KC_P9, TO(L_RGB) + , KC_P4, KC_P5, KC_P6, LT(L_Symbols, KC_PCMM) + , KC_P1, KC_P2, KC_P3, KC_P0 + + , KC_A, KC_S, KC_ENT, KC_BSPC + , KC_UP, KC_DOWN, KC_LEFT, KC_RIGHT + , KC_X, KC_Z, LSFT(KC_F1),KC_TAB + ), + [L_RGB] = LAYOUT( + RGB_M_P, RGB_M_B, RGB_TOG, KC_NO + , RGB_MOD, RGB_HUI, RGB_VAI, TO(L_Numpad) + , RGB_RMOD, RGB_HUD, RGB_VAD, KC_NO + + , KC_A, KC_B, KC_C, KC_D + , KC_E, KC_F, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ), + [L_Symbols] = LAYOUT( + KC_PPLS, KC_PMNS, KC_PEQL, KC_NO + , KC_PAST, KC_PSLS, KC_ENT, KC_TRNS + , KC_NUM, KC_NO, KC_NO, QK_BOOT + + , KC_A, KC_B, KC_C, KC_D + , KC_E, KC_F, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ) +}; +// clang-format on + +const char* get_layer_name_user(int layer) { + switch (layer) { + case L_Numpad: + return "Numpad"; + case L_RGB: + return "RGB Controls"; + case L_Symbols: + return "Symbols"; + default: + return "Undef"; + } +} diff --git a/keyboards/snes_macropad/keymaps/jbarberu/keymap.c b/keyboards/snes_macropad/keymaps/jbarberu/keymap.c new file mode 100644 index 00000000000..0fbe0fa6266 --- /dev/null +++ b/keyboards/snes_macropad/keymaps/jbarberu/keymap.c @@ -0,0 +1,99 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include QMK_KEYBOARD_H + +enum Layer { + L_Numpad = 0, + L_Symbols, + L_EasyEDA, + L_RGB, + L_Adjust +}; + +// clang-format off +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + /* + * Macropad Button Order + * ┌───┬───┬───┬───┐ + * │ 7 │ 8 │ 9 │ - │ + * ├───┼───┼───┼───┤ + * │ 4 │ 5 │ 6 │ + │ + * ├───┼───┼───┼───┤ + * │ 1 │ 2 │ 3 │ 0 │ + * └───┴───┴───┴───┘ + * + * SNES Button Order + * ┌────────┬────────┬────────┬────────┐ + * │ LT │ RT │ START │ SELECT │ + * ├────────┼────────┼────────┼────────┤ + * │ UP │ DOWN │ LEFT │ RIGHT │ + * ├────────┼────────┼────────┼────────┤ + * │ A │ B │ X │ Y │ + * └────────┴────────┴────────┴────────┘ + * + */ + [L_Numpad] = LAYOUT( + KC_P7, KC_P8, KC_P9, TO(L_EasyEDA) + , KC_P4, KC_P5, KC_P6, LT(L_Symbols, KC_PCMM) + , KC_P1, KC_P2, KC_P3, KC_P0 + + , KC_A, KC_S, KC_ENT, KC_BSPC + , KC_UP, KC_DOWN, KC_LEFT, KC_RIGHT + , KC_X, KC_Z, LSFT(KC_F1), KC_TAB + ), + [L_EasyEDA] = LAYOUT( + KC_COMM, KC_DOT, KC_K, TO(L_RGB) + , KC_LSFT, KC_M, KC_N, TO(L_Numpad) + , KC_LCTL, KC_SPC, KC_DEL, KC_BSPC + + , KC_A, KC_B, KC_C, KC_D + , QK_BOOT, KC_TRNS, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ), + [L_RGB] = LAYOUT( + RGB_M_P, RGB_M_B, RGB_TOG, TO(L_Adjust) + , RGB_MOD, RGB_HUI, RGB_VAI, TO(L_Numpad) + , RGB_RMOD, RGB_HUD, RGB_VAD, KC_NO + + , KC_A, KC_B, KC_C, KC_D + , KC_E, KC_F, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ), + [L_Adjust] = LAYOUT( + KC_NO, KC_P8, KC_NO, KC_NO + , KC_NO, RGB_HUD, KC_NO, TO(L_Numpad) + , RGB_HUI, KC_NO, KC_TRNS, KC_NO + + , KC_A, KC_B, KC_C, KC_D + , KC_E, KC_F, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ), + [L_Symbols] = LAYOUT( + KC_PPLS, KC_PMNS, KC_PEQL, KC_NO + , KC_PAST, KC_PSLS, KC_ENT, KC_TRNS + , KC_NUM, KC_NO, KC_NO, QK_BOOT + + , KC_A, KC_B, KC_C, KC_D + , KC_E, KC_F, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ) +}; +// clang-format on + +const char * get_layer_name_user(int layer) { + switch (layer) { + case L_Numpad: + return "Numpad"; + case L_EasyEDA: + return "EasyEDA"; + case L_RGB: + return "RGB Controls"; + case L_Adjust: + return "Adjust"; + case L_Symbols: + return "Symbols"; + default: + return "Undef"; + } +} diff --git a/keyboards/snes_macropad/keymaps/test/keymap.c b/keyboards/snes_macropad/keymaps/test/keymap.c new file mode 100644 index 00000000000..86dd6699657 --- /dev/null +++ b/keyboards/snes_macropad/keymaps/test/keymap.c @@ -0,0 +1,75 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include QMK_KEYBOARD_H + +enum Layer { + L_Numpad = 0, + L_Symbols, + L_RGB +}; + +// clang-format off +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + /* + * Macropad Button Order + * ┌───┬───┬───┬───┐ + * │ 7 │ 8 │ 9 │ - │ + * ├───┼───┼───┼───┤ + * │ 4 │ 5 │ 6 │ + │ + * ├───┼───┼───┼───┤ + * │ 1 │ 2 │ 3 │ 0 │ + * └───┴───┴───┴───┘ + * + * SNES Button Order + * ┌────────┬────────┬────────┬────────┐ + * │ LT │ RT │ START │ SELECT │ + * ├────────┼────────┼────────┼────────┤ + * │ UP │ DOWN │ LEFT │ RIGHT │ + * ├────────┼────────┼────────┼────────┤ + * │ A │ B │ X │ Y │ + * └────────┴────────┴────────┴────────┘ + * + */ + [L_Numpad] = LAYOUT( + KC_1, KC_2, KC_3, KC_4 + , KC_5, KC_6, KC_7, KC_8 + , KC_9, KC_0, KC_A, KC_S + + , KC_A, KC_S, KC_ENT, KC_BSPC + , KC_UP, KC_DOWN, KC_LEFT, KC_RIGHT + , KC_X, KC_Z, LSFT(KC_F1),KC_TAB + ), + [L_RGB] = LAYOUT( + RGB_M_P, RGB_M_B, RGB_TOG, KC_NO + , RGB_MOD, RGB_HUI, RGB_VAI, TO(L_Numpad) + , RGB_RMOD, RGB_HUD, RGB_VAD, KC_NO + + , KC_A, KC_B, KC_C, KC_D + , KC_E, KC_F, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ), + [L_Symbols] = LAYOUT( + KC_PPLS, KC_PMNS, KC_PEQL, KC_NO + , KC_PAST, KC_PSLS, KC_ENT, KC_TRNS + , KC_NUM, KC_NO, KC_NO, QK_BOOT + + , KC_A, KC_B, KC_C, KC_D + , KC_E, KC_F, KC_G, KC_H + , KC_I, KC_J, KC_K, KC_L + ) +}; +// clang-format on + +const char * get_layer_name_user(int layer) { + switch (layer) { + case L_Numpad: + return "Numpad"; + case L_RGB: + return "RGB Controls"; + case L_Symbols: + return "Symbols"; + default: + return "Undef"; + } +} diff --git a/keyboards/snes_macropad/matrix.c b/keyboards/snes_macropad/matrix.c new file mode 100644 index 00000000000..28d036aca9c --- /dev/null +++ b/keyboards/snes_macropad/matrix.c @@ -0,0 +1,146 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "matrix.h" +#include "gpio.h" +#include "wait.h" +#include "string.h" + +#define SNES_CLOCK GP0 +#define SNES_LATCH GP1 +#define SNES_D0 GP2 +#define SNES_D1 GP3 +#define SNES_IO GP4 + +#define KBD_ROW0 GP24 +#define KBD_ROW1 GP23 +#define KBD_ROW2 GP22 +#define KBD_NUM_ROWS 3 + +#define KBD_COL0 GP18 +#define KBD_COL1 GP19 +#define KBD_COL2 GP20 +#define KBD_COL3 GP21 +#define KBD_ROW_SETUP_DELAY_US 5 + +// The real snes will clock 16 bits out of the controller, but only really has 12 bits of data +#define SNES_DATA_BITS 16 +#define SNES_DATA_SETUP_DELAY_US 10 +#define SNES_CLOCK_PULSE_DURATION 10 + +static const int kbd_pin_map[] = { + KBD_ROW0, + KBD_ROW1, + KBD_ROW2 +}; + +void matrix_init_custom(void) { + // init snes controller + setPinInputHigh(SNES_D0); + // todo: look into protocol for other strange snes controllers that use D1 and IO + // setPinInputHigh(SNES_D1); + // setPinInputHigh(SNES_IO); + setPinOutput(SNES_CLOCK); + setPinOutput(SNES_LATCH); + writePinLow(SNES_CLOCK); + writePinLow(SNES_LATCH); + + // init rows + setPinOutput(KBD_ROW0); + setPinOutput(KBD_ROW1); + setPinOutput(KBD_ROW2); + writePinHigh(KBD_ROW0); + writePinHigh(KBD_ROW1); + writePinHigh(KBD_ROW2); + + // init columns + setPinInputHigh(KBD_COL0); + setPinInputHigh(KBD_COL1); + setPinInputHigh(KBD_COL2); + setPinInputHigh(KBD_COL3); +} + +static matrix_row_t readRow(size_t row, int setupDelay) { + const int pin = kbd_pin_map[row]; + + // select the row + setPinOutput(pin); + writePinLow(pin); + wait_us(setupDelay); + + // read the column data + const matrix_row_t ret = + (readPin(KBD_COL0) ? 0 : 1 << 0) + | (readPin(KBD_COL1) ? 0 : 1 << 1) + | (readPin(KBD_COL2) ? 0 : 1 << 2) + | (readPin(KBD_COL3) ? 0 : 1 << 3); + + // deselect the row + setPinOutput(pin); + writePinHigh(pin); + + return ret; +} + +static void readKeyboard(matrix_row_t current_matrix[]) { + for (size_t row = 0; row < KBD_NUM_ROWS; ++row) { + current_matrix[row] = readRow(row, KBD_ROW_SETUP_DELAY_US); + } +} + +static matrix_row_t getBits(uint16_t value, size_t bit0, size_t bit1, size_t bit2, size_t bit3) { + matrix_row_t ret = 0; + ret |= (value >> bit3) & 1; + ret <<= 1; + ret |= (value >> bit2) & 1; + ret <<= 1; + ret |= (value >> bit1) & 1; + ret <<= 1; + ret |= (value >> bit0) & 1; + return ret; +} + +static void readSnesController(matrix_row_t current_matrix[]) { + uint16_t controller = 0; + + writePinHigh(SNES_LATCH); + + for (size_t bit = 0; bit < SNES_DATA_BITS; ++bit) { + // Wait for shift register to setup the data line + wait_us(SNES_DATA_SETUP_DELAY_US); + + // Shift accumulated data and read data pin + controller <<= 1; + controller |= readPin(SNES_D0) ? 0 : 1; + // todo: maybe read D1 and IO here too + + // Shift next bit in + writePinHigh(SNES_CLOCK); + wait_us(SNES_CLOCK_PULSE_DURATION); + writePinLow(SNES_CLOCK); + } + + writePinLow(SNES_LATCH); + + controller >>= 4; + + // SNES button order is pretty random, and we'd like them to be a bit tidier + current_matrix[3] = getBits(controller, 1, 0, 8, 9); + current_matrix[4] = getBits(controller, 7, 6, 5, 4); + current_matrix[5] = getBits(controller, 3, 11, 2, 10); +} + +bool matrix_scan_custom(matrix_row_t current_matrix[]) { + const size_t MATRIX_ARRAY_SIZE = MATRIX_ROWS * sizeof(matrix_row_t); + + // create a copy of the current_matrix, before we read hardware state + matrix_row_t last_value[MATRIX_ROWS]; + memcpy(last_value, current_matrix, MATRIX_ARRAY_SIZE); + + // read hardware state into current_matrix + readKeyboard(current_matrix); + readSnesController(current_matrix); + + // check if anything changed + return memcmp(last_value, current_matrix, MATRIX_ARRAY_SIZE) != 0; +} diff --git a/keyboards/snes_macropad/mcuconf.h b/keyboards/snes_macropad/mcuconf.h new file mode 100644 index 00000000000..0bbd4fef628 --- /dev/null +++ b/keyboards/snes_macropad/mcuconf.h @@ -0,0 +1,18 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include_next + +#undef RP_PWM_USE_PWM0 +#define RP_PWM_USE_PWM0 TRUE + +#undef RP_PWM_USE_PWM4 +#define RP_PWM_USE_PWM4 TRUE + +#undef RP_I2C_USE_I2C0 +#define RP_I2C_USE_I2C0 FALSE + +#undef RP_I2C_USE_I2C1 +#define RP_I2C_USE_I2C1 TRUE diff --git a/keyboards/snes_macropad/readme.md b/keyboards/snes_macropad/readme.md new file mode 100644 index 00000000000..9ef5e041496 --- /dev/null +++ b/keyboards/snes_macropad/readme.md @@ -0,0 +1,36 @@ +# snes_macropad + +![Completed Build](https://i.imgur.com/WzzPJ3Yh.jpg) +*Completed Build* + +![Completed Build, closer with RGB off](https://i.imgur.com/D7ki7Kkh.jpg) +*Completed Build, closer with RGB off* + +![PCB and FR4 top/bottom plates](https://i.imgur.com/TgOev7lh.jpg) +*PCB and FR4 top/bottom plates* + +The SNES Macropad is, as it sounds, a macropad that features a SNES connector. In addition it has a qwiic connector and a 3.5mm jack for 3.3V I2C (not audio), allowing additional expansion. + +This QMK implementation exposes the SNES controller as a part of the keyboard, meaning you can map the controller to do anything a qmk keyboard can. The layout is thus a 4x6 keyboard logically, split with the 3 first rows being on the macro pad and the 3 following being buttons on the snes controller. + +* Keyboard Maintainer: [JBarberU](https://github.com/jbarberu) +* Hardware Supported: SNES Macropad Rev 1, with a Raspberry Pi Pico Lite (AliExpress clone of Raspberry Pico with fewer grounds and all GPIO's exposed on the headers) +* Hardware Availability: The SNES Macro pad can be found [here](https://www.tindie.com/products/jbarberu/snes-macropad/) either as a kit, partially built or fully built. + +Make example for this keyboard (after setting up your build environment): + + make snes_macropad:default + +Flashing example for this keyboard: + + make snes_macropad: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: + +* **Physical bootsel button**: Hold down the bootsel button on the RPi Pico while plugging in the keyboard, or while pressing the reset button +* **Physical reset button**: Quickly double press the reset button +* **Keycode in layout**: Press the key mapped to `QK_BOOT` if it is available diff --git a/keyboards/snes_macropad/rules.mk b/keyboards/snes_macropad/rules.mk new file mode 100644 index 00000000000..52cdd84a78b --- /dev/null +++ b/keyboards/snes_macropad/rules.mk @@ -0,0 +1,4 @@ +# Enable features +CUSTOM_MATRIX = lite + +SRC += matrix.c diff --git a/keyboards/snes_macropad/snes_macropad.c b/keyboards/snes_macropad/snes_macropad.c new file mode 100644 index 00000000000..a8e04c8c32a --- /dev/null +++ b/keyboards/snes_macropad/snes_macropad.c @@ -0,0 +1,130 @@ +// Copyright 2023 John Barbero Unenge (@jbarberu) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" + +// oled keylog rendering has been kindly borrowed from crkbd <3 + +char key_name = ' '; +uint16_t last_keycode; +uint8_t last_row; +uint8_t last_col; + +static const char PROGMEM code_to_name[60] = {' ', ' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'R', 'E', 'B', 'T', '_', '-', '=', '[', ']', '\\', '#', ';', '\'', '`', ',', '.', '/', ' ', ' ', ' '}; + +static void set_keylog(uint16_t keycode, keyrecord_t *record) { + last_row = record->event.key.row; + last_col = record->event.key.col; + + key_name = ' '; + last_keycode = keycode; + if (IS_QK_MOD_TAP(keycode)) { + if (record->tap.count) { + keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode); + } else { + keycode = 0xE0 + biton(QK_MOD_TAP_GET_MODS(keycode) & 0xF) + biton(QK_MOD_TAP_GET_MODS(keycode) & 0x10); + } + } else if (IS_QK_LAYER_TAP(keycode) && record->tap.count) { + keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); + } else if (IS_QK_MODS(keycode)) { + keycode = QK_MODS_GET_BASIC_KEYCODE(keycode); + } else if (IS_QK_ONE_SHOT_MOD(keycode)) { + keycode = 0xE0 + biton(QK_ONE_SHOT_MOD_GET_MODS(keycode) & 0xF) + biton(QK_ONE_SHOT_MOD_GET_MODS(keycode) & 0x10); + } + if (keycode > ARRAY_SIZE(code_to_name)) { + return; + } + + // update keylog + key_name = pgm_read_byte(&code_to_name[keycode]); +} + +static const char *depad_str(const char *depad_str, char depad_char) { + while (*depad_str == depad_char) { + ++depad_str; + } + return depad_str; +} + +static void oled_render_keylog(void) { + oled_write_char('0' + last_row, false); + oled_write("x", false); + oled_write_char('0' + last_col, false); + oled_write(", k", false); + const char *last_keycode_str = get_u16_str(last_keycode, ' '); + oled_write(depad_str(last_keycode_str, ' '), false); + oled_write(":", false); + oled_write_char(key_name, false); +} + +__attribute__((weak)) const char * get_layer_name_user(int layer) { + return "Unknown"; +} + +static void oled_render_layer(void) { + oled_write("Layer: ", false); + oled_write_ln(get_layer_name_user(get_highest_layer(layer_state)), false); +} + +bool oled_task_kb(void) { + if (!oled_task_user()) { + return false; + } + + oled_render_layer(); + oled_render_keylog(); + oled_advance_page(true); + return false; +} + +static void setupForFlashing(void) { + oled_clear(); + oled_write(" ", false); + oled_write(" In flash mode... ", false); + oled_write(" ", false); + oled_write(" ", false); + + // QMK is clever about only rendering a certain number of chunks per frame, + // but since the device will go into flash mode right after this call, + // we want to override this behavior and force all the chunks to be sent to + // the display immediately. + const size_t numIterations = OLED_DISPLAY_WIDTH * OLED_DISPLAY_HEIGHT / OLED_UPDATE_PROCESS_LIMIT; + for (size_t num = 0; num < numIterations; ++num) { + oled_render(); + } + // todo: Replace the above hack with this, once develop branch is merged at the end of November 2023 + // oled_render_dirty(true); + + // Set alternating backlight colors + const uint8_t max = 20; + rgblight_mode_noeeprom(RGBLIGHT_MODE_STATIC_LIGHT); + for (size_t i = 0; i < RGBLED_NUM; ++i) { + LED_TYPE *led_ = (LED_TYPE *)&led[i]; + switch (i % 2) { + case 0: + setrgb(max, 0, max, led_); + break; + case 1: + setrgb(0, max, max, led_); + break; + } + } + rgblight_set(); +} + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + if (record->event.pressed) { + set_keylog(keycode, record); + } + if (keycode == QK_BOOT) { + setupForFlashing(); + } + return process_record_user(keycode, record); +} + +void keyboard_post_init_kb(void) { + rgblight_enable_noeeprom(); + rgblight_sethsv_noeeprom(HSV_MAGENTA); + rgblight_mode_noeeprom(RGBLIGHT_MODE_RAINBOW_SWIRL); + keyboard_post_init_user(); +}