Mouth-Operated Adaptive Game Controller Using Arduino Leonardo

by liamdann_ in Design > 3D Design

1 Views, 0 Favorites, 0 Comments

Mouth-Operated Adaptive Game Controller Using Arduino Leonardo

F14B4865-FDC1-41CC-83CE-293F16691D1F_1_105_c.jpeg
C4E09022-66BA-4B14-8E46-5CD27C0203B8_1_105_c.jpeg

This is a custom-built adaptive controller designed for someone with limited movement in one hand. It allows the user to control a computer cursor and trigger clicks using their mouth and their one functional hand — opening up access to video games, computer navigation, and everyday computing without a standard mouse or keyboard.


The controller is split into two 3D printed housings:


Housing 1 (Mouth-Operated): A joystick and sip & puff mouthpiece unit. Moving the straw with your mouth moves the cursor. Sipping triggers one mouse-click function, puffing triggers another. Housing 1 also features a threaded mount on the side, compatible with a standard mic stand or adjustable arm, allowing the user to position the mouthpiece at exactly the right height and angle.


Housing 2 (Hand-Operated): A pyramid-shaped enclosure operated with the user's hand. It contains an IR proximity sensor, a micro switch, and a push button. When the IR sensor detects the hand nearby, it lights up an LED as visual feedback. The micro switch and push button provide additional click or key inputs.


All components are wired and soldered into a breadboard, which connects to an Arduino Leonardo. The Leonardo acts as a USB mouse — no special drivers needed. Works on Windows, Mac, and with the Xbox Adaptive Controller.


This Video serves as a demonstration of all the functions of our controller: https://youtube.com/shorts/Rul0QIRoDyw?feature=share


Supplies

ELECTRONIC COMPONENTS:

- Arduino Leonardo (x1)

- Breadboard (x1) — all components are soldered into this, which then connects to the Arduino

- Analog joystick module (x1)

- Sip and puff pressure sensor (x1)

- IR proximity sensor (x1) — triggers the LED when hand is detected

- Micro switch (x1)

- Two-pin push button (x1)

- LED (x1) — lights up when IR sensor detects proximity

- Resistors (220Ω for LED, pull-downs as needed)

- Jumper wires

- USB-A to Micro-USB cable (x1)


HARDWARE & HOUSING:

-3D printed Housing 1 (joystick + sip/puff enclosure) — STL file linked below

-3D printed Housing 2 (pyramid enclosure) — STL file linked below

-Heat-Shrink tubing for the mouthpiece

-M2/M3 screws and heat-set inserts

-Hot glue or epoxy

-Mic stand or adjustable arm mount (optional but recommended for comfortable positioning)

-Threaded metal jack/mount insert (x1) — sits in the side of Housing 1 and allows attachment to a mic stand or arm


TOOLS:

- Soldering iron, flux, and solder wire

- Wire strippers and cutters

- 3D printer (PLA recommended)

- Multimeter

- Arduino IDE (free download at arduino.cc)

- Heat shrink tubing

Printing Housing

E559E538-C678-4FAF-BCE6-4973DD0AB9B1_1_105_c.jpeg
037468D6-618E-408D-962D-324D82B2345E_1_105_c.jpeg

Print both housings before starting assembly.


HOUSING 1 — Mouth-Operated (Joystick & Sip/Puff):

This housing mounts the joystick inside so the straw attaches directly to the thumbstick and extends out through a port for the user's mouth. The user moves the straw with their mouth to control the cursor direction. The side of Housing 1 includes a threaded jack/mount so it can be attached to a mic stand or arm for hands-free positioning.


HOUSING 2 — Hand-Operated (Pyramid Enclosure):

This pyramid-shaped housing is designed to be used with one hand. The IR sensor, micro switch, and push button are accessible on the outer faces. The breadboard and Arduino Leonardo sit inside the base with the majority of the wiring routed internally.


Print Settings for Both:

- Material: PLA

- Layer height: 0.2mm

- Infill: 20%

- Supports: ON


POST-PRINT: MELTING HOLES WITH A SOLDERING IRON
Once both housings are printed, use the tip of your soldering iron to carefully melt any holes needed for wiring and hardware:
1. LED hole (Housing 2): Melt a hole on the outer face of the pyramid sized to fit your LED snugly. 2. Threaded mic jack hole (Housing 1): Melt a hole on the side of Housing 1 sized to fit your threaded metal insert. Press the insert in while the plastic is still warm so it seats flush and holds firmly.
Go slowly — melt a little at a time and test the fit frequently. It's easier to make a hole bigger than to fix one that's too large.


Wiring and Soldering the Breadboard

IMG_9715.JPG

All components are wired and soldered into a breadboard. The breadboard then connects to the Arduino Leonardo. This keeps the wiring organized and permanent inside the housing.


Start by wiring everything with jumper wires to test before soldering.


JOYSTICK (mouth-operated cursor movement):

- VCC → 5V rail on breadboard

- GND → GND rail on breadboard

- VRx → A0 on Arduino

- VRy → A1 on Arduino


SIP & PUFF SENSOR (mouth-operated clicks):

- VCC → 3.3V or 5V (check your sensor's spec)

- GND → GND rail

- OUT → A2 on Arduino


IR PROXIMITY SENSOR (triggers LED when hand is near):

- VCC → 5V rail

- GND → GND rail

- OUT → D3 on Arduino


MICRO SWITCH (hand-operated click):

- One terminal → D4 on Arduino

- Other terminal → GND rail


PUSH BUTTON (hand-operated click):

- One terminal → D5 on Arduino

- Other terminal → GND rail

- Use INPUT_PULLUP in code


LED (lights up when IR sensor detects hand):

- Anode → 220Ω resistor → D6 on Arduino

- Cathode → GND rail

- The LED is wired to respond specifically to the IR sensor input


Once all connections are tested and confirmed working, solder every connection permanently into the breadboard.


SOLDERING TIPS:

1. Tin both the wire and the pad before joining

2. Heat the joint 2–3 seconds, then apply solder — never to the iron itself

3. Let each joint cool before moving the wire

4. Good joints are shiny and conical — dull or blobby = cold joint, redo it

5. Cover exposed connections with heat shrink tubing

6. Use a multimeter to test continuity on every connection when done


Important: Solder before placing components into the housing. PLA melts easily.

Uploading the Code

The Arduino Leonardo runs a sketch that reads all sensor inputs and sends mouse movements and clicks to your computer over USB.


SETUP:

1. Download and install the Arduino IDE from arduino.cc

2. Plug the Arduino Leonardo into your computer via USB

3. Go to Tools > Board > Arduino Leonardo

4. Go to Tools > Port and select the correct port

5. Copy and paste the code below into a new sketch (or open the .ino file from the Google Drive link)

6. Click Upload (the right arrow button)

7. Open Tools > Serial Monitor to view debug output while testing


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CODE:

/*

Adaptive Technology Controller

Arduino Leonardo HID


INPUTS:

D7 -> Tactile button -> "w"

D4 -> Microswitch -> "a"

D6 -> IR sensor -> "s"

D3 -> Sip/Puff switch -> "d"

D2 -> Joystick SW button -> LEFT MOUSE CLICK

A0 -> Joystick X axis -> mouse left/right

A1 -> Joystick Y axis -> mouse up/down


JOYSTICK SAFETY:

- Calibrates center when Arduino starts

- Uses a dead zone to prevent drifting

- Uses low sensitivity so movement is controlled

*/


#include <Keyboard.h>

#include <Mouse.h>


// Digital inputs

const int tactileButtonPin = 7;

const int microSwitchPin = 4;

const int irSensorPin = 6;

const int sipPuffPin = 3;

const int joystickSWPin = 2;


// Joystick analog inputs

const int joyXPin = A0;

const int joyYPin = A1;


// IR sensor

const int IR_BLOCKED = LOW;


// Button states

bool lastTactileState = HIGH;

bool lastMicroState = HIGH;

bool lastIRState = HIGH;

bool lastSipPuffState = HIGH;

bool lastJoystickSW = HIGH;


// Joystick calibration

int centerX = 512;

int centerY = 512;


// Bigger number = less floating/drift

const int deadZone = 90;


// Bigger number = slower mouse

const int sensitivity = 120;


void setup() {

Keyboard.begin();

Mouse.begin();


pinMode(tactileButtonPin, INPUT_PULLUP);

pinMode(microSwitchPin, INPUT_PULLUP);

pinMode(sipPuffPin, INPUT_PULLUP);

pinMode(joystickSWPin, INPUT_PULLUP);


// IR worked better as INPUT in your earlier version

pinMode(irSensorPin, INPUT);


// Let joystick settle

delay(500);


// Calibrate joystick center at startup

centerX = analogRead(joyXPin);

centerY = analogRead(joyYPin);

}


void loop() {

// -----------------------------

// Read digital inputs

// -----------------------------

bool tactileState = digitalRead(tactileButtonPin);

bool microState = digitalRead(microSwitchPin);

bool irState = digitalRead(irSensorPin);

bool sipPuffState = digitalRead(sipPuffPin);

bool joystickSW = digitalRead(joystickSWPin);


// -----------------------------

// Keyboard inputs

// -----------------------------

if (tactileState == LOW && lastTactileState == HIGH) {

Keyboard.write('w');

delay(30);

}


if (microState == LOW && lastMicroState == HIGH) {

Keyboard.write('a');

delay(30);

}


if (irState == IR_BLOCKED && lastIRState == HIGH) {

Keyboard.write('s');

delay(30);

}


if (sipPuffState == LOW && lastSipPuffState == HIGH) {

Keyboard.write('d');

delay(30);

}


// -----------------------------

// Joystick SW -> left click

// -----------------------------

if (joystickSW == LOW && lastJoystickSW == HIGH) {

Mouse.click(MOUSE_LEFT);

delay(30);

}


// -----------------------------

// Joystick movement -> mouse

// -----------------------------

int rawX = analogRead(joyXPin);

int rawY = analogRead(joyYPin);


int xDifference = rawX - centerX;

int yDifference = rawY - centerY;


int moveX = 0;

int moveY = 0;


// Only move if outside dead zone

if (abs(xDifference) > deadZone) {

moveX = xDifference / sensitivity;

}


if (abs(yDifference) > deadZone) {

moveY = yDifference / sensitivity;

}


// Move mouse only when needed

if (moveX != 0 || moveY != 0) {

Mouse.move(moveX, moveY, 0);

}


// Save previous button states

lastTactileState = tactileState;

lastMicroState = microState;

lastIRState = irState;

lastSipPuffState = sipPuffState;

lastJoystickSW = joystickSW;


// Small delay smooths movement

delay(10);

}


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


HOW THE CODE WORKS:

- Joystick X/Y axes → Mouse.move() for cursor control

- Puff (pressure above threshold) → left click

- Sip (pressure below threshold) → right click

- IR sensor (proximity detected) → lights the LED + triggers input

- Micro switch (pressed) → button or key input

- Push button (pressed) → button or key input


Note: The Mouse and Keyboard libraries are built into the Arduino IDE — no extra installs needed. If you see a Mouse.h error, make sure you selected Leonardo, not Uno.

Testing

Before closing the housings, test every input individually. Open a text editor or watch your cursor on screen while you test.


MOUTH CONTROLS (Housing 1):

1. Joystick: Move the straw in all four directions — cursor should move smoothly and stop when centered.

2. Sip: Gently sip through the mouthpiece — should trigger the expected click.

3. Puff: Gently puff — should trigger the other click.


HAND CONTROLS (Housing 2):

4. IR sensor: Slowly move your hand toward the sensor — the LED should light up and an input should register.

5. Micro switch: Press it — input should register.

6. Push button: Press it — input should register.


If the cursor drifts when the joystick is untouched, add a deadzone in the code (ignore joystick values within ±50 of center, around 512 on a 0–1023 scale).

Assembly

C4E09022-66BA-4B14-8E46-5CD27C0203B8_1_105_c.jpeg
F5B7AB0B-1651-4163-AF97-C412A1510C7B_1_105_c.jpeg
65E9DF68-997B-42AA-B5FF-3765C1145821_1_105_c.jpeg

HOUSING 1 — Mouth-Operated:

1. Route all wires through the base of Housing 1.

2. Seat the joystick in its mount. Attach the straw to the thumbstick so it extends out the mouthpiece port.

3. Position the sip/puff sensor inline with the straw.

4. Tuck wiring inside neatly and close with screws.

5. Thread the mic stand mount into the jack on the side of Housing 1. Attach to a mic stand or adjustable arm and position the mouthpiece at a comfortable height for the user.


HOUSING 2 — Hand-Operated:

1. Place the breadboard and Arduino Leonardo inside the base of the pyramid. The majority of the wiring lives inside this housing.

2. Mount the IR sensor so it faces outward and can detect an approaching hand.

3. Install the micro switch and push button in their holes on the outer faces.

4. Insert the LED so it is clearly visible to the user from the outside.

5. Secure all internal wiring with hot glue or cable ties to keep everything tidy.

6. Close with screws.


CONNECTING THE TWO HOUSINGS:

Route the wire bundle from Housing 1 into Housing 2 where the breadboard and Arduino live.

Plug the USB cable from the Arduino into your PC or Xbox Adaptive Controller.

Using Controller/remapping

BASIC USE:

1. Plug the USB cable from the Arduino into your PC or Xbox Adaptive Controller.

2. No drivers needed — it shows up automatically as a USB mouse.

3. Position the mouthpiece comfortably. Move it to control the cursor.

4. Sip = left click. Puff = right click (or as configured in your code).

5. Hold your hand near the IR sensor to trigger the LED and that input.

6. Use the micro switch and push button with your hand for additional inputs.


REMAPPING:

To change what any input does:

- Edit the Arduino code and re-upload (most flexible)