OverView
This project was kindly sponsored by PCBWay(https://www.pcbway.com/).
This project is an upgraded version of my earlier work, wich was originally posted on Hackster.io
Due to a mistake in the joystick PCB design, my earlier attempt to build a FreeCAD device was unsuccessful. I have now fixed the PCB design and ordered a new set of boads.
Circuit Design
The schematic is shown below.
This left hand device is build around the RP2040 microcontroller and consists of a piezpelectric speaker, three mechanical key switches, and one joystic. The key switches and joystick are mounted on independent PCBs and connected to the main control board using flat cables.
The circuit below is for the joystick.
The image below shows the previous design. In that version, Pad No.1 and Pad No.2 were crossed. I
I fixed the mistake in the PCB layout and ordered a new set of boards.
If you're using KiCad, ordering PCBs from PCBWay is extremely simple. Install the official PCBWay plugin, and a PCBWay icon will appear in the top toolbar of the PCB Editor. Just click the icon - it automatically generates the Gerber files and uploads your design to PCBWay.
Making a jig
I desugbed and #D-printing a custom jig to help accurately implement and align the joystick during assembly.
The assembly jig was 3D-printed in PETG using a Bambu Lab X1C.
The back of the joystic PCB hase several SMD resistors and a connector of flat cable. Without the jig, it is very difficult to align and solder the joystic.
Joystick Wiring and GPIO Mapping
The joystic connects to the RP2040 main control board using a flat cable, as shown below.
The joystic pins are connected to the RP2040 GPIO pins as shown below.
Programming the RP2040
FreeCAD has multiple mouse navigation style. I mainly use the CAD Navigation style shown below. You can switch navigation styles anytime using the dropdown in the bottom toolbar.
I mostly rely on Rotate View when using CAD Navigation.
The left-hand unit also features three mechanical key switches. Their GPIO assignments are as follows:
GPIO 0: Mouse Right Click
GPIO 1: Mouse Left Click
GPIO 2: Mpise Scroll Click
The firmware was developed in PlatformIO. I used the Keyboard and Mouse libraries to handle mouse movement, click and keyboard input.
#include <Arduino.h>
#include <Keyboard.h>
#include <Mouse.h>
struct MouseButton {
uint8_t pin;
uint8_t button;
bool wasPressed;
};
struct ShortcutButton {
uint8_t pin;
bool wasPressed;
};
struct MovementPins {
uint8_t up;
uint8_t down;
uint8_t left;
uint8_t right;
};
constexpr uint8_t MOUSE_UP_PIN = 4;
constexpr uint8_t MOUSE_DOWN_PIN = 9;
constexpr uint8_t MOUSE_LEFT_PIN = 6;
constexpr uint8_t MOUSE_RIGHT_PIN = 7;
constexpr uint8_t MOUSE_SCROLL_CLICK_PIN = 5;
constexpr int8_t MOUSE_STEP = 5;
constexpr uint16_t LOOP_DELAY_MS = 10;
constexpr MovementPins movementPins = {
MOUSE_UP_PIN,
MOUSE_DOWN_PIN,
MOUSE_LEFT_PIN,
MOUSE_RIGHT_PIN,
};
MouseButton mouseButtons[] = {
{0, MOUSE_RIGHT, false},
{1, MOUSE_LEFT, false},
{MOUSE_SCROLL_CLICK_PIN, MOUSE_MIDDLE, false},
};
ShortcutButton backButton = {2, false};
bool isPressed(uint8_t pin) {
return digitalRead(pin) == LOW;
}
void updateMouseButtons() {
for (MouseButton &mouseButton : mouseButtons) {
const bool pressed = isPressed(mouseButton.pin);
if (pressed && !mouseButton.wasPressed) {
Mouse.press(mouseButton.button);
} else if (!pressed && mouseButton.wasPressed) {
Mouse.release(mouseButton.button);
}
mouseButton.wasPressed = pressed;
}
}
void updateBackButton() {
const bool pressed = isPressed(backButton.pin);
if (pressed && !backButton.wasPressed) {
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(KEY_LEFT_ARROW);
} else if (!pressed && backButton.wasPressed) {
Keyboard.release(KEY_LEFT_ARROW);
Keyboard.release(KEY_LEFT_ALT);
}
backButton.wasPressed = pressed;
}
void updateMouseMovement() {
int8_t x = 0;
int8_t y = 0;
if (isPressed(movementPins.left)) {
x -= MOUSE_STEP;
}
if (isPressed(movementPins.right)) {
x += MOUSE_STEP;
}
if (isPressed(movementPins.up)) {
y -= MOUSE_STEP;
}
if (isPressed(movementPins.down)) {
y += MOUSE_STEP;
}
if (x != 0 || y != 0) {
Mouse.move(x, y, 0);
}
}
void setup() {
for (const MouseButton &mouseButton : mouseButtons) {
pinMode(mouseButton.pin, INPUT_PULLUP);
}
pinMode(backButton.pin, INPUT_PULLUP);
pinMode(movementPins.up, INPUT_PULLUP);
pinMode(movementPins.down, INPUT_PULLUP);
pinMode(movementPins.left, INPUT_PULLUP);
pinMode(movementPins.right, INPUT_PULLUP);
Keyboard.begin();
Mouse.begin();
}
void loop() {
updateMouseButtons();
updateBackButton();
updateMouseMovement();
delay(LOOP_DELAY_MS);
}I uploaded a demonstration video on YouTube.
The device functions well overall, but I found it slightly tiring during extended use. I will improve the joystic placement in the next version for better ergonomics.
© 2026 Xian DIY - All Rights Reserved.This project is proprietary. No commercial use, redistribution, or modification is permitted without permission.








Comments