diff --git a/keyboards/binepad/candypad/candypad_oled.c b/keyboards/binepad/candypad/candypad_oled.c new file mode 100644 index 00000000000..fa4f450023e --- /dev/null +++ b/keyboards/binepad/candypad/candypad_oled.c @@ -0,0 +1,193 @@ +// Copyright 2023 binepad (@binepad) +// SPDX-License-Identifier: GPL-2.0-or-later +// Portions of this code are based on [Andrew Kannan](https://github.com/awkannan1)'s +// work on the Satisfaction75 + +#include "quantum.h" +#include "keymap_introspection.h" + +#ifdef OLED_ENABLE + +static char *get_enc_mode(uint8_t encoder) { +# ifdef ENCODER_MAP_ENABLE + + static char s_u_d[4] = {24, 38, 25, 0}; // Up and down + static char s_l_r[4] = {27, 38, 26, 0}; // Left and right + + uint8_t layer = get_highest_layer(layer_state); + uint16_t keycode = KC_TRNS; + + while (keycode == KC_TRNS) { + keycode = keycode_at_encodermap_location(layer, encoder, true); // only reads ENCODER_CW + if (keycode == KC_TRNS) { + if (layer > 0) { + layer--; + } else { + keycode = KC_NO; + } + } + } + + switch (keycode) { + case KC_NO: + return " - "; + break; + + case KC_WH_U: + case KC_WH_D: + return "MWL"; + break; + + case KC_VOLD: + case KC_VOLU: + return "VOL"; + break; + + case KC_MEDIA_NEXT_TRACK: + case KC_MEDIA_PREV_TRACK: + return "MED"; + break; + + case KC_UP: + case KC_DOWN: + return (char *)&s_u_d; + break; + + case KC_LEFT: + case KC_RIGHT: + return (char *)&s_l_r; + break; + + default: + return "Usr"; + break; + } + +# else + + return "Err"; + +# endif +} + +static void __draw_line_h(uint8_t x, uint8_t y, uint8_t len, bool on) { + for (uint8_t i = 0; i < len; i++) { + oled_write_pixel(i + x, y, on); + } +} + +static void __draw_line_v(uint8_t x, uint8_t y, uint8_t len, bool on) { + for (uint8_t i = 0; i < len; i++) { + oled_write_pixel(x, i + y, on); + } +} + +// Weak so that user keymaps can make their own +__attribute__((weak)) bool candypad_render_default_user(void) { + return false; // Return true if your user keymap renders its own +} + +__attribute__((weak)) bool candypad_render_default_kb(void) { + if (candypad_render_default_user()) { + return true; // Was handled by user code + } + + bool on; + + // --- Show Layer --- + + oled_set_cursor(0, 1); + oled_write_P(PSTR("LAYER"), false); + + oled_set_cursor(6, 1); + oled_write_char(get_highest_layer(layer_state) + 0x30, true); + + __draw_line_h((OLED_FONT_WIDTH * 6) - 1, OLED_FONT_HEIGHT - 1, OLED_FONT_WIDTH + 1, true); + __draw_line_v((OLED_FONT_WIDTH * 6) - 1, OLED_FONT_HEIGHT, OLED_FONT_HEIGHT, true); + + // --- Show Encoder 1 --- + + oled_set_cursor(13, 1); + oled_write_P(PSTR("ENC1"), false); + + oled_set_cursor(18, 1); + oled_write(get_enc_mode(0), true); + + __draw_line_h((OLED_FONT_WIDTH * 18) - 1, OLED_FONT_HEIGHT - 1, (OLED_FONT_WIDTH * 3) + 1, true); + __draw_line_v((OLED_FONT_WIDTH * 18) - 1, OLED_FONT_HEIGHT, OLED_FONT_HEIGHT, true); + + // --- Show Encoder 2 --- + + oled_set_cursor(13, 3); + oled_write_P(PSTR("ENC2"), false); + + oled_set_cursor(18, 3); + oled_write(get_enc_mode(1), true); + + __draw_line_h((OLED_FONT_WIDTH * 18) - 1, (OLED_FONT_HEIGHT * 3) - 1, (OLED_FONT_WIDTH * 3) + 1, true); + __draw_line_v((OLED_FONT_WIDTH * 18) - 1, OLED_FONT_HEIGHT * 3, OLED_FONT_HEIGHT, true); + + // --- Keyboard Modifiers --- + + led_t led_state = host_keyboard_led_state(); + oled_set_cursor(4, 3); + if (led_state.caps_lock && !led_state.num_lock) { // Caps-Lock transcends keyboards, so show it if on if num-lock is off + oled_write_P(PSTR("CAP"), true); + } else { + oled_write_P(PSTR("NUM"), led_state.num_lock); + } + on = led_state.num_lock || led_state.caps_lock; + __draw_line_h((OLED_FONT_WIDTH * 4) - 1, (OLED_FONT_HEIGHT * 3) - 1, (OLED_FONT_WIDTH * 3) + 1, on); + __draw_line_v((OLED_FONT_WIDTH * 4) - 1, OLED_FONT_HEIGHT * 3, OLED_FONT_HEIGHT, on); + + // --- Show keymap matrix --- + +# define MXDS_X 2 +# define MXDS_Y (OLED_FONT_HEIGHT * 3) + + // matrix + for (uint8_t x = 0; x < MATRIX_ROWS - 1; x++) { + for (uint8_t y = 0; y < MATRIX_COLS; y++) { + on = (matrix_get_row(x) & (1 << y)) > 0; + oled_write_pixel(MXDS_X + y, MXDS_Y + x + 1, on); + } + } + + on = (matrix_get_row(1) & (1 << 3)) > 0; // `+` + oled_write_pixel(MXDS_X + 3, MXDS_Y + 1 + 2, on); + + on = (matrix_get_row(3) & (1 << 3)) > 0; // `Enter` + oled_write_pixel(MXDS_X + 3, MXDS_Y + 3 + 2, on); + + on = (matrix_get_row(4) & (1 << 0)) > 0; // `0` + oled_write_pixel(MXDS_X + 0 + 1, MXDS_Y + 4 + 1, on); + + on = (matrix_get_row(5) & (1 << 0)) > 0; // Enc 1 press + oled_write_pixel(MXDS_X + 2, MXDS_Y, on); + + on = (matrix_get_row(5) & (1 << 1)) > 0; // Enc 2 press + oled_write_pixel(MXDS_X + 3, MXDS_Y, on); + + // outline + __draw_line_h(MXDS_X - 2, MXDS_Y - 2, 8, true); + __draw_line_h(MXDS_X - 2, MXDS_Y + 7, 8, true); + __draw_line_v(MXDS_X - 2, MXDS_Y - 1, 8, true); + __draw_line_v(MXDS_X + 5, MXDS_Y - 1, 8, true); + + return true; // Was handled here +} + +void oled_request_repaint(void) { + oled_clear(); +} + +bool oled_task_kb(void) { + if (!oled_task_user()) { + return false; + } + + candypad_render_default_kb(); + return false; +} + +#endif // OLED_ENABLE diff --git a/keyboards/binepad/candypad/config.h b/keyboards/binepad/candypad/config.h new file mode 100644 index 00000000000..eefc84cd924 --- /dev/null +++ b/keyboards/binepad/candypad/config.h @@ -0,0 +1,33 @@ +// Copyright 2023 binepad (@binepad) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +/* ----- Encoder ----- */ +#define ENCODER_DEFAULT_POS 0x3 // enable 1:1 resolution + +/* ----- OLED ----- */ +#ifdef OLED_ENABLE +/* I2C (for OLED) */ +# define I2C1_SCL_PIN GP23 +# define I2C1_SDA_PIN GP22 +# define I2C_DRIVER I2CD1 + +/* Configure oled driver for the 128x32 oled */ +# define OLED_TIMEOUT (2 * 60 * 1000) // 2 minutes +# define OLED_UPDATE_INTERVAL 33 // ~30fps +#endif // OLED_ENABLE + +/* ---- Direct pins, for use in (custom) `matrix.c` (please read that file) ----- */ +// !! : DIRECT_PINS_CUSTOM must match the MATRIX_ROWS / MATRIX_COLS matrix structure +// clang-format off +#define DIRECT_PINS_CUSTOM \ + { \ + {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}, \ + {NO_PIN, NO_PIN, NO_PIN, NO_PIN}, \ + {GP26, GP3, NO_PIN, NO_PIN} \ + } +// clang-format on diff --git a/keyboards/binepad/candypad/keyboard.json b/keyboards/binepad/candypad/keyboard.json new file mode 100644 index 00000000000..76cfeeb5c9d --- /dev/null +++ b/keyboards/binepad/candypad/keyboard.json @@ -0,0 +1,59 @@ +{ + "manufacturer": "binepad", + "keyboard_name": "CandyPad", + "maintainer": "binepad", + "bootloader": "rp2040", + "diode_direction": "COL2ROW", + "encoder": { + "rotary": [ + {"pin_a": "GP28", "pin_b": "GP27"}, + {"pin_a": "GP1", "pin_b": "GP2"} + ] + }, + "features": { + "bootmagic": true, + "encoder": true, + "extrakey": true, + "mousekey": true, + "nkro": true, + "oled": true + }, + "matrix_pins": { + "custom": true, + "custom_lite": true, + "cols": ["GP15", "GP14", "GP13", "GP10"], + "rows": ["GP12", "GP9", "GP7", "GP6", "GP5", null] + }, + "processor": "RP2040", + "url": "https://candykeys.com/product/candypad-keyboard", + "usb": { + "device_version": "1.0.0", + "pid": "0x4350", + "vid": "0x4249" + }, + "layouts": { + "LAYOUT": { + "layout": [ + {"matrix": [5, 0], "x": 2, "y": 0, "encoder":0}, + {"matrix": [5, 1], "x": 3, "y": 0, "encoder":1}, + {"label": "Num Lock", "matrix": [0, 0], "x": 0, "y": 1}, + {"label": "/", "matrix": [0, 1], "x": 1, "y": 1}, + {"label": "*", "matrix": [0, 2], "x": 2, "y": 1}, + {"label": "-", "matrix": [0, 3], "x": 3, "y": 1}, + {"label": "7", "matrix": [1, 0], "x": 0, "y": 2}, + {"label": "8", "matrix": [1, 1], "x": 1, "y": 2}, + {"label": "9", "matrix": [1, 2], "x": 2, "y": 2}, + {"label": "+", "matrix": [1, 3], "x": 3, "y": 2, "h": 2}, + {"label": "4", "matrix": [2, 0], "x": 0, "y": 3}, + {"label": "5", "matrix": [2, 1], "x": 1, "y": 3}, + {"label": "6", "matrix": [2, 2], "x": 2, "y": 3}, + {"label": "1", "matrix": [3, 0], "x": 0, "y": 4}, + {"label": "2", "matrix": [3, 1], "x": 1, "y": 4}, + {"label": "3", "matrix": [3, 2], "x": 2, "y": 4}, + {"label": "Enter", "matrix": [3, 3], "x": 3, "y": 4, "h": 2}, + {"label": "0", "matrix": [4, 0], "x": 0, "y": 5, "w": 2}, + {"label": ".", "matrix": [4, 2], "x": 2, "y": 5} + ] + } + } +} diff --git a/keyboards/binepad/candypad/keymaps/default/keymap.json b/keyboards/binepad/candypad/keymaps/default/keymap.json new file mode 100644 index 00000000000..adbbbb35795 --- /dev/null +++ b/keyboards/binepad/candypad/keymaps/default/keymap.json @@ -0,0 +1,33 @@ +{ + "config": { + "features": { + "encoder_map": true + } + }, + "encoders": [ + [ + { + "ccw": "KC_WH_U", + "cw": "KC_WH_D" + }, + { + "ccw": "KC_VOLD", + "cw": "KC_VOLU" + } + ] + ], + "keyboard": "binepad/candypad", + "keymap": "default", + "layers": [ + [ + "MO(1)", "KC_MUTE", + "KC_NUM", "KC_PSLS", "KC_PAST", "KC_PMNS", + "KC_P7", "KC_P8", "KC_P9", "KC_PPLS", + "KC_P4", "KC_P5", "KC_P6", + "KC_P1", "KC_P2", "KC_P3", "KC_PENT", + "KC_P0", "KC_PDOT" + ] + ], + "layout": "LAYOUT", + "version": 1 +} diff --git a/keyboards/binepad/candypad/matrix.c b/keyboards/binepad/candypad/matrix.c new file mode 100644 index 00000000000..2670dfeca42 --- /dev/null +++ b/keyboards/binepad/candypad/matrix.c @@ -0,0 +1,162 @@ +// Copyright 2023 Silvino R. (@silvinor) +// Copyright 2012-2018 Jun Wako, Jack Humbert, Yiancar [portions from `qantum/matrix.c`] +// SPDX-License-Identifier: GPL-2.0-or-later + +/** + * This file is mostly a copy of `matrix.c` from the QMK core, however + * it assumes the matrix is always COL2ROW and that the KB has BOTH + * matrix and direct pins. + + * For direct pins define: + * `DIRECT_PINS_CUSTOM` + * This stucture must match exactly the MATRIX_ROWS / MATRIX_COLS matrix. + * + * NB!!: The ROW that supports direct pins **must** have the row set + * to NO_PIN in the matrix array. +*/ + +#include "quantum.h" +#include "matrix.h" +#include "atomic_util.h" + +// ---------- Interal defs and vars ---------- + +#ifndef MATRIX_INPUT_PRESSED_STATE +# define MATRIX_INPUT_PRESSED_STATE 0 +#endif + +static pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; +static pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; +static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS_CUSTOM; + +// ---------- Interal functions ---------- +// .......... Helpers .......... + +static inline void gpio_atomic_set_pin_output_low(pin_t pin) { + ATOMIC_BLOCK_FORCEON { + gpio_set_pin_output(pin); + gpio_write_pin_low(pin); + } +} + +static inline void gpio_atomic_set_pin_input_high(pin_t pin) { + ATOMIC_BLOCK_FORCEON { + gpio_set_pin_input_high(pin); + } +} + +static bool select_row(uint8_t row) { + pin_t pin = row_pins[row]; + if (pin != NO_PIN) { + gpio_atomic_set_pin_output_low(pin); + return true; + } + return false; +} + +static void unselect_row(uint8_t row) { + pin_t pin = row_pins[row]; + if (pin != NO_PIN) { + gpio_atomic_set_pin_input_high(pin); + } +} + +static void unselect_rows(void) { + for (uint8_t x = 0; x < MATRIX_ROWS; x++) { + unselect_row(x); + } +} + +// .......... Inits .......... + +void matrix_init_matrix_pins(void) { + unselect_rows(); + for (uint8_t x = 0; x < MATRIX_COLS; x++) { + if (col_pins[x] != NO_PIN) { + gpio_atomic_set_pin_input_high(col_pins[x]); + } + } +} + +void matrix_init_direct_pins(void) { + for (int row = 0; row < MATRIX_ROWS; row++) { + for (int col = 0; col < MATRIX_COLS; col++) { + pin_t pin = direct_pins[row][col]; + if (pin != NO_PIN) { + gpio_atomic_set_pin_input_high(pin); + } + } + } +} + +// .......... Scanners .......... + +static inline uint8_t readMatrixPin(pin_t pin) { + if (pin != NO_PIN) { + return (gpio_read_pin(pin) == MATRIX_INPUT_PRESSED_STATE) ? 0 : 1; + } else { + return 1; + } +} + +void matrix_scan_matrix_row(matrix_row_t row_buffer[], uint8_t row_to_scan) { + matrix_row_t scan_row_value = 0; + + if (!select_row(row_to_scan)) { + return; + } + matrix_output_select_delay(); + + matrix_row_t row_shifter = MATRIX_ROW_SHIFTER; + for (uint8_t col_to_scan = 0; col_to_scan < MATRIX_COLS; col_to_scan++, row_shifter <<= 1) { + uint8_t pin_state = readMatrixPin(col_pins[col_to_scan]); + + // Populate the matrix row with the state of the col pin + scan_row_value |= pin_state ? 0 : row_shifter; + } + + unselect_row(row_to_scan); + matrix_output_unselect_delay(row_to_scan, scan_row_value != 0); + + row_buffer[row_to_scan] = scan_row_value; +} + +void matrix_scan_direct_row(matrix_row_t row_buffer[], uint8_t row_to_scan) { + matrix_row_t scan_row_value = 0; + + matrix_row_t row_shifter = MATRIX_ROW_SHIFTER; + for (uint8_t col_to_scan = 0; col_to_scan < MATRIX_COLS; col_to_scan++, row_shifter <<= 1) { + pin_t pin = direct_pins[row_to_scan][col_to_scan]; + if (pin != NO_PIN) { + scan_row_value |= readMatrixPin(pin) ? 0 : row_shifter; + } + } + + row_buffer[row_to_scan] = scan_row_value; +} + +// ========== Core functions required by lite matrix driver ========== + +void matrix_init_custom(void) { + matrix_init_matrix_pins(); + matrix_init_direct_pins(); +} + +bool matrix_scan_custom(matrix_row_t current_matrix[]) { + matrix_row_t buff_matrix[MATRIX_ROWS] = {0}; + + for (uint8_t scan_row = 0; scan_row < MATRIX_ROWS; scan_row++) { + // if the row is a NO_PIN then assume it's on a direct pins matrix, else assume COL2ROW matrix + if (row_pins[scan_row] == NO_PIN) { + matrix_scan_direct_row(buff_matrix, scan_row); + } else { + matrix_scan_matrix_row(buff_matrix, scan_row); + } + } + + bool matrix_has_changed = memcmp(current_matrix, buff_matrix, sizeof(buff_matrix)) != 0; + if (matrix_has_changed) { + memcpy(current_matrix, buff_matrix, sizeof(buff_matrix)); + } + return matrix_has_changed; +} diff --git a/keyboards/binepad/candypad/mcuconf.h b/keyboards/binepad/candypad/mcuconf.h new file mode 100644 index 00000000000..050d9875e5c --- /dev/null +++ b/keyboards/binepad/candypad/mcuconf.h @@ -0,0 +1,12 @@ +// Copyright 2023 binepad (@binepad) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include_next + +#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/binepad/candypad/readme.md b/keyboards/binepad/candypad/readme.md new file mode 100644 index 00000000000..dd64ebbaf8f --- /dev/null +++ b/keyboards/binepad/candypad/readme.md @@ -0,0 +1,29 @@ +# CandyPad Number Pad + +![CandyPad Keyboard](https://i.imgur.com/W9cfNOm.png) + +*A numeric pad with 2x rotary encoders and OLED display, a joint venture by CandyKeys × BinePad* + +* Keyboard Maintainer: [binepad](https://github.com/binepad) +* Hardware Supported: CandyPad Keyboard +* Hardware Availability: + - [candykeys.com](https://candykeys.com/product/candypad-keyboard) + - [binepad.com](https://binepad.com/products/candypad) + +Make example for this keyboard (after setting up your build environment): + + make binepad/candypad:default + +Flashing example for this keyboard: + + make binepad/candypad: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 PCB button located on the back of the PCB +* **Keycode in layout**: Press the key mapped to `QK_BOOT` if it is available diff --git a/keyboards/binepad/candypad/rules.mk b/keyboards/binepad/candypad/rules.mk new file mode 100644 index 00000000000..a7b061422de --- /dev/null +++ b/keyboards/binepad/candypad/rules.mk @@ -0,0 +1,5 @@ +# Copyright 2023 binepad (@binepad) +# SPDX-License-Identifier: GPL-2.0-or-later + +SRC += matrix.c \ + candypad_oled.c