diff --git a/keyboards/ploopyco/common/as5600.c b/keyboards/ploopyco/common/as5600.c
new file mode 100644
index 00000000000..4ded20c7f3d
--- /dev/null
+++ b/keyboards/ploopyco/common/as5600.c
@@ -0,0 +1,69 @@
+/* Copyright 2025 Colin Lam, Ploopy Corporation (contact@ploopy.co)
+ *
+ * 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 "as5600.h"
+#include "print.h"
+
+void as5600_init(void) {
+ i2c_init();
+}
+
+uint16_t get_rawangle(void) {
+ uint8_t data[] = {0, 0};
+ i2c_status_t s = i2c_read_register(AS5600_I2C_ADDRESS, REG_RAWANGLE, data, 2, 100);
+ if (s == I2C_STATUS_TIMEOUT) {
+ printf("Timeout on get_rawangle()\n");
+ } else if (s == I2C_STATUS_ERROR) {
+ printf("Error on get_rawangle()\n");
+ } else {
+ ;
+ }
+ uint16_t rawangle = data[0] << 8 | data[1];
+ return rawangle;
+}
+
+bool is_magnet_too_high(void) {
+ uint8_t data[] = {0};
+ i2c_read_register(AS5600_I2C_ADDRESS, REG_STATUS, data, 1, 100);
+ uint8_t v = (data[0] >> 3) & 0x1;
+ if (v == 1) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool is_magnet_too_low(void) {
+ uint8_t data[] = {0};
+ i2c_read_register(AS5600_I2C_ADDRESS, REG_STATUS, data, 1, 100);
+ uint8_t v = (data[0] >> 4) & 0x1;
+ if (v == 1) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool is_magnet_present(void) {
+ uint8_t data[] = {0};
+ i2c_read_register(AS5600_I2C_ADDRESS, REG_STATUS, data, 1, 100);
+ uint8_t v = (data[0] >> 5) & 0x1;
+ if (v == 1) {
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/keyboards/ploopyco/common/as5600.h b/keyboards/ploopyco/common/as5600.h
new file mode 100644
index 00000000000..608d272072d
--- /dev/null
+++ b/keyboards/ploopyco/common/as5600.h
@@ -0,0 +1,54 @@
+/* Copyright 2025 Colin Lam, Ploopy Corporation (contact@ploopy.co)
+ *
+ * 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
+#include
+#include "i2c_master.h"
+
+#define POINTING_DEVICE_AS5600_TICK_COUNT 128
+
+// 12 was found to be a good value experimentally, balancing good
+// responsiveness with low backlash.
+#define POINTING_DEVICE_AS5600_DEADZONE 12
+
+// The speed divisor decreases the speed. 1 is base speed; 2 is divided by 2,
+// 3 is divided by 3, and so forth. For best results, make sure that
+// POINTING_DEVICE_AS5600_SPEED_DIV is an integer divisor of
+// POINTING_DEVICE_AS5600_DEADZONE (i.e. 3 is an integer divisor of 12, but
+// 5 is not).
+#define POINTING_DEVICE_AS5600_SPEED_DIV 2
+
+#define AS5600_I2C_ADDRESS (0x36 << 1)
+
+#define REG_ZMCO 0x00
+#define REG_ZPOS 0x01
+#define REG_MPOS 0x03
+#define REG_MANG 0x05
+#define REG_CONF 0x07
+#define REG_RAWANGLE 0x0c
+#define REG_ANGLE 0x0e
+#define REG_STATUS 0x0b
+#define REG_AGC 0x1a
+#define REG_MAGNITUDE 0x1b
+#define REG_BURN 0xff
+
+void as5600_init(void);
+uint16_t get_rawangle(void);
+bool is_magnet_too_high(void);
+bool is_magnet_too_low(void);
+bool is_magnet_present(void);
diff --git a/keyboards/ploopyco/knob/config.h b/keyboards/ploopyco/knob/config.h
new file mode 100644
index 00000000000..9b7d9f5c1a2
--- /dev/null
+++ b/keyboards/ploopyco/knob/config.h
@@ -0,0 +1,29 @@
+/* Copyright 2025 Colin Lam (Ploopy Corporation)
+ *
+ * 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 POINTING_DEVICE_HIRES_SCROLL_ENABLE 0
+#define POINTING_DEVICE_AS5600_ENABLE true
+
+#define I2C_DRIVER I2CD1
+#define I2C1_SDA_PIN GP22
+#define I2C1_SCL_PIN GP23
+
+#define UNUSABLE_PINS \
+ { GP0, GP1, GP2, GP3, GP4, GP5, GP6, GP7, GP8, GP9, GP10, GP11, GP12, \
+ GP13, GP14, GP15, GP16, GP17, GP18, GP19, GP20, GP21, GP24, GP25, \
+ GP26, GP27, GP29 }
diff --git a/keyboards/ploopyco/knob/info.json b/keyboards/ploopyco/knob/info.json
new file mode 100644
index 00000000000..b1641f5772b
--- /dev/null
+++ b/keyboards/ploopyco/knob/info.json
@@ -0,0 +1,28 @@
+{
+ "keyboard_name": "Ploopy Knob",
+ "url": "www.ploopy.co",
+ "maintainer": "ploopyco",
+ "manufacturer": "Ploopy Corporation",
+ "processor": "RP2040",
+ "bootloader": "rp2040",
+ "usb": {
+ "vid": "0x5043",
+ "pid": "0x63C3",
+ "max_power": 100
+ },
+ "features": {
+ "extrakey": true,
+ "mousekey": true,
+ "nkro": true,
+ "pointing_device": true,
+ "console": true,
+ "os_detection": true
+ },
+ "layouts": {
+ "LAYOUT": {
+ "layout": [
+ {"x": 0, "y": 0, "matrix": [0, 0]}
+ ]
+ }
+ }
+}
diff --git a/keyboards/ploopyco/knob/keymaps/default/keymap.c b/keyboards/ploopyco/knob/keymaps/default/keymap.c
new file mode 100644
index 00000000000..93c23584e1f
--- /dev/null
+++ b/keyboards/ploopyco/knob/keymaps/default/keymap.c
@@ -0,0 +1,27 @@
+/* Copyright 2023 Colin Lam (Ploopy Corporation)
+ *
+ * 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
+
+// Dummy
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {{{ KC_NO }}};
+
+void keyboard_post_init_user(void) {
+ // Customise these values to desired behaviour
+ // debug_enable=true;
+ // debug_matrix=true;
+ // debug_keyboard=true;
+ // debug_mouse=true;
+}
diff --git a/keyboards/ploopyco/knob/post_rules.mk b/keyboards/ploopyco/knob/post_rules.mk
new file mode 100644
index 00000000000..15a5cab6548
--- /dev/null
+++ b/keyboards/ploopyco/knob/post_rules.mk
@@ -0,0 +1,4 @@
+VPATH += keyboards/ploopyco/common
+SRC += as5600.c
+I2C_DRIVER_REQUIRED = yes
+POINTING_DEVICE_DRIVER = custom
diff --git a/keyboards/ploopyco/knob/readme.md b/keyboards/ploopyco/knob/readme.md
new file mode 100644
index 00000000000..0489ba44197
--- /dev/null
+++ b/keyboards/ploopyco/knob/readme.md
@@ -0,0 +1,31 @@
+# Ploopyco Knob
+
+It's a DIY, QMK-powered knob!
+
+* Keyboard Maintainer: [PloopyCo](https://github.com/ploopyco)
+* Hardware Supported: RP2040
+* Hardware Availability: [Store](https://ploopy.co/knob), [GitHub](https://github.com/ploopyco)
+
+Make example for this keyboard (after setting up your build environment):
+
+ qmk compile -kb ploopyco/knob/rev1_001 -km default
+
+# Building Firmware
+
+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).
+
+# Triggering the Bootloader
+
+[Do you see those two golden holes in the board](https://ploopy.co/wp-content/uploads/2025/06/knob-vias.jpg)? Those are called **vias**. They act exactly like a switch does. Right now, that switch is OFF. However, if you take a paperclip or a pair of metal tweezers and touch those two vias, the two vias will form an electrical connection. Effectively, that switch turns ON.
+
+Go ahead and connect the two vias, and then (while the vias are connected) plug in the Knob board into your computer.
+
+The computer should recognise that a mass storage device was just plugged in. Once this is done, you should be able to drag and drop files onto the Knob board, as if the board was a USB drive. Feel free to remove the tweezers or paperclip at this point.
+
+If you want to upload a new firmware file (a ".uf2" file, like "knob_awesome_version.uf2" or something), just drag it into the folder, and it'll automatically install on the Knob board and restart itself, in normal operating mode. You're done!
+
+**TIP**: If your firmware is in some kind of strange state and uploading new firmware isn't fixing it, try uploading [a flash nuke](https://learn.adafruit.com/getting-started-with-raspberry-pi-pico-circuitpython/circuitpython#flash-resetting-uf2-3083182) to the Knob board before flashing the new firmware. It wipes the memory of the Knob board completely clean, which can help clear a few types of errors.
+
+# Customizing your Ploopy Knob
+
+You can find customziation options [here](../readme.md).
diff --git a/keyboards/ploopyco/knob/rev1_001/keyboard.json b/keyboards/ploopyco/knob/rev1_001/keyboard.json
new file mode 100644
index 00000000000..8c148ea974f
--- /dev/null
+++ b/keyboards/ploopyco/knob/rev1_001/keyboard.json
@@ -0,0 +1,10 @@
+{
+ "usb": {
+ "device_version": "1.0.0"
+ },
+ "matrix_pins": {
+ "direct": [
+ [null]
+ ]
+ }
+}
diff --git a/keyboards/ploopyco/knob/rev1_001/readme.md b/keyboards/ploopyco/knob/rev1_001/readme.md
new file mode 100644
index 00000000000..0c23bf73eda
--- /dev/null
+++ b/keyboards/ploopyco/knob/rev1_001/readme.md
@@ -0,0 +1,3 @@
+This is the R1.001 version of the Knob. Future versions may have other features.
+
+See the [main readme](../readme.md) for more details.
diff --git a/keyboards/ploopyco/ploopyco.c b/keyboards/ploopyco/ploopyco.c
index 57f2a26b8c3..72fad42aba0 100644
--- a/keyboards/ploopyco/ploopyco.c
+++ b/keyboards/ploopyco/ploopyco.c
@@ -19,6 +19,7 @@
#include "ploopyco.h"
#include "analog.h"
#include "opt_encoder.h"
+#include "as5600.h"
// for legacy support
#if defined(OPT_DEBOUNCE) && !defined(PLOOPY_SCROLL_DEBOUNCE)
@@ -58,6 +59,10 @@
# define ENCODER_BUTTON_COL 0
#endif
+#ifdef POINTING_DEVICE_AS5600_ENABLE
+uint16_t current_position = 0;
+#endif
+
keyboard_config_t keyboard_config;
uint16_t dpi_array[] = PLOOPY_DPI_OPTIONS;
#define DPI_OPTION_SIZE ARRAY_SIZE(dpi_array)
@@ -163,6 +168,38 @@ report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
mouse_report.y = 0;
}
+#ifdef POINTING_DEVICE_AS5600_ENABLE
+ // Get AS5600 rawangle
+ uint16_t ra = get_rawangle();
+ int16_t delta = (int16_t)(ra - current_position);
+
+ // Wrap into [-2048, 2047] to get shortest direction
+ if (delta > 2048) {
+ delta -= 4096;
+ } else if (delta < -2048) {
+ delta += 4096;
+ }
+
+ if (detected_host_os() == OS_WINDOWS || detected_host_os() == OS_LINUX) {
+ // Establish a deadzone to prevent spurious inputs
+ if (delta > POINTING_DEVICE_AS5600_DEADZONE || delta < -POINTING_DEVICE_AS5600_DEADZONE) {
+ current_position = ra;
+ mouse_report.v = delta / POINTING_DEVICE_AS5600_SPEED_DIV;
+ }
+ } else {
+ // Certain operating systems, like MacOS, don't play well with the
+ // high-res scrolling implementation. For more details, see:
+ // https://github.com/qmk/qmk_firmware/issues/17585#issuecomment-2325248167
+ if (delta >= POINTING_DEVICE_AS5600_TICK_COUNT) {
+ current_position = ra;
+ mouse_report.v = 1;
+ } else if (delta <= -POINTING_DEVICE_AS5600_TICK_COUNT) {
+ current_position = ra;
+ mouse_report.v = -1;
+ }
+ }
+#endif
+
return pointing_device_task_user(mouse_report);
}
@@ -204,7 +241,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
void keyboard_pre_init_kb(void) {
// debug_enable = true;
// debug_matrix = true;
- // debug_mouse = true;
+ //debug_mouse = true;
// debug_encoder = true;
/* Ground all output pins connected to ground. This provides additional
@@ -237,6 +274,14 @@ void pointing_device_init_kb(void) {
pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
}
+#ifdef POINTING_DEVICE_AS5600_ENABLE
+void keyboard_post_init_kb(void) {
+ // Init the AS5600 controlling the Dial
+ as5600_init();
+ current_position = get_rawangle();
+}
+#endif
+
void eeconfig_init_kb(void) {
keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT;
eeconfig_update_kb(keyboard_config.raw);