Skip to content

Commit

Permalink
Printable case, lower profile and improved base and case. 3d code reo…
Browse files Browse the repository at this point in the history
…rg and improved parametrization
  • Loading branch information
tloimu committed May 10, 2021
1 parent 5ffbe66 commit 34b8d62
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 371 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/gen
*.dsn.autosave
temp-printables.scad
63 changes: 36 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
# Yet-Another HOTAS Throttle (YAHT)
# Yet-Another HOTAS Throttle (YA-HOTAS-THROTTLE)

This is mostly 3D printable HOTAS throttle game controller for USB.

![Full Preview](img/full-preview.png)

**A photo of an actual build (don't mind the colors - this is the first fully working build)**

![Full Photo](img/full-photo.png)

# Features

This HOTAS throttle connects as a standard USB joystick to Windows without needing to install any software.
This 3D printable HOTAS throttle game controller. It connects as a standard USB joystick to Windows without needing to install any software.

It has:

Expand All @@ -23,6 +13,18 @@ It has:
- adjustable throttle handle angle (twist) for better ergonomics at different table positioning
- host additional USB serial connectivity for debugging and programming

**Complete Design Explosion View**

![Full Preview](img/full-preview.png)

**A photo of an actual build (don't mind the colors - this is a fully working prototype build)**

![Full Photo](img/full-photo.jpg)

**And when fully opened**

![Fully Opened Photo](img/fully-opened.jpg)

# Design

The design is based on these guidelines:
Expand All @@ -48,6 +50,8 @@ Some parts require or benefit from post-print processing:
- some screw holes may benefit from drilling for easier fit
- throttle center detent hole could be drilled bigger for stronger detent or cone shaped with a bit larger drill head for smoother detent exit/entry

Below is an illustration of all printable parts.

![Preview of all printed parts](img/printed-parts.png)

Connect the wires like this to match the code:
Expand All @@ -56,6 +60,17 @@ Connect the wires like this to match the code:

*Note: The components within the dashed region in the right of the circuit diagram are not currently in the physical build. They only exist in the code and are for future extensions.*

Below are some photos of the assembly

![Fully Opened Photo](img/fully-opened.jpg)

![Fitting Everything In the Handle](img/handle-insides.jpg)

The cable management is important to make the throttle handle move smoothly and not move on its own at any position due wires pushing it.

![Handle Cable Management](img/handle-cable.jpg)


## Firmware

Directory **firmware** contains the code for Arduino. I has been tested with Arduino 1.8.9 on Teensy LC with USB Type **Serial + Keyboard + Mouse + Joystick**. You also need to have/install **Bounce2** and **FastLED** libraries. Prebuilt hex-file for Teensy LC is also located in the directory.
Expand All @@ -79,25 +94,25 @@ This build does not use a keyboard diode matrix as Teensy LC has enough pins for

# Parts List

On top of the 3D printable components:
On top of the 3D printable components, you need

- 2 x linear motion rods 8mm x 168mm (get one longer and cut in two)
- 2 x linear bearings 8x15x45 (e.g. LM8LUU)
- 1 x Teensy LC (or another Arduino board with enough pins)
- 2 x 10kohm LIN pot (Bourns BI P160 with center detent e.g. KNOC-model)
- 1 x 100mm travel 10kohm lin slide pot (Bourns PS100 B 10K 0E)
- 1 x 100mm travel 10kohm LIN slide pot (Bourns PS100 B 10K 0E)
- 1 x multidirectional switch 8 dir + press (Alps 688RKJXL 100401V)
- 5 x switches (a tact switch with pins fitting the circuit board listed)
- 1 x slide switch (any generic model you can fit the case)
- 2 x linear motion rods 8mm x 168mm (get one longer and cut in two)
- 2 x linear bearings 8x15x45 (e.g. LM8LUU)
- some generic circuit board (0.1" / 2.54mm pin pitch)
- N x screws (2.5mm and 3mm) for plastic - for case cover, handle thumb section and internal parts
- 2 x screws 3mm for attaching the rods from one of their ends to the case
- 2 x screws 2mm for attaching the slide potentiometer
- 6 x zip ties for attaching the bearings and do cable management, small ones will do
- 1 x joystick case (Saitek X45 stick part - or print it yourself)
- wires
- 1 x RGB LED (WS2812 compatible)
- 1 x 9.5mm steel ball
- 1 x steel spring of e.g. 4.5x22mm (one from a regular ball point pen will work well)
- some generic circuit board (0.1" / 2.54mm pin pitch)
- wires
- N x screws (2.5mm and 3mm) for plastic
- 2 x screws 3mm for attaching the rods from one of their ends to the case

The Teensy LC can quite easily be replaced with any Arduino compatible device with USB connectivity and enough pins. With more coding effort any USB capable micro controller that fits the case could also do.

Expand All @@ -116,14 +131,10 @@ For simple buttons, the following logic could be made. Press and hold Lock-butto
**Document:**

- Add button module circuit board picture and cutting instructions
- Add cable management picture

**Fix:**

- Make printable bottom case (now reusing bottom part of Saitek X45 joystick case)
- Make case and rod supports to both ends with exact correct rod separation and height
- Add Lock-switch place to handle print
- Cable insert cut into the handle shaft
- Cleanup 3D code
- Turn the LEDs off when computer is sleeping
- Thicker handle case and slightly better interlocking edges between the main and thumb sections
Expand All @@ -135,8 +146,6 @@ For simple buttons, the following logic could be made. Press and hold Lock-butto
- Add two buttons to front and lower thumb - can be less accessible than the 5 current ones - handy for less frequently used functions in a game
- Add one or two more axis - can be in less accessible locations than the X/Y pots - these come handy in flight simulators
- Add a place for the lights so they can be seen
- Curvier case cover near to player for better ergonomics when the throttle is placed nearer to the table edge (in certain positions mid-arm may rub against the case cover)
- Lower design

**Other ideas:**

Expand Down
56 changes: 35 additions & 21 deletions commercial-parts.scad
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,43 @@ module TeensyPlusPlus()
cube ([9,8,4]);
}

module SlidePotentiometer100()
module SlidePotentiometer(range=100, leverWidth=8.0, leverHeight=20,
sliderEndLength=10, sliderHeight=6.4,
value=0.5, alignTop=false, alignPin=false, alignLever=false, alignValueMax=false)
{
lever_range = 100;
lever_width = 8.0;
lever_thickness = 1.0;
lever_height = 20;
slider_length = 128;
slider_length = range + leverWidth + 2 * sliderEndLength;
slider_width = 8.3;
slider_height = 6.4;
lever_offset = (slider_length - lever_range + lever_width) / 2;
// Slider base
difference ()
{
cube ([slider_length,slider_width,slider_height]);
translate ([lever_offset, (slider_width-lever_thickness)/2, 1])
cube ([lever_range,lever_thickness,slider_height]);
// Screw places
translate ([slider_length - 4, slider_width/2, slider_height - 3]) cylinder (r1=1.5, r2=1.5, h=4);
translate ([4, slider_width/2, slider_height - 3]) cylinder (r1=1.5, r2=1.5, h=3);
}
// Lever
translate ([lever_offset + lever_range/2, (slider_width-lever_thickness)/2, 0])
cube ([lever_width,lever_thickness,lever_height]);
lever_thickness = 1.0;
pin_length = 4.4;
lever_groove_offset = (slider_length - range - slider_width) / 2;
lever_offset = sliderEndLength + range * value;

alignZ = alignTop ? -sliderHeight : (alignPin ? pin_length : 0);
alignX = alignValueMax ? -sliderEndLength : 0;
alignY = alignLever ? -slider_width/2 : 0;
translate([alignX, alignY, alignZ])
{
// Slider base
difference ()
{
cube ([slider_length,slider_width,sliderHeight]);
translate ([lever_groove_offset, (slider_width-lever_thickness*2)/2, 1])
cube ([range + slider_width,lever_thickness*2,sliderHeight]);
// Screw places
translate ([slider_length - 4, slider_width/2, sliderHeight - 3]) cylinder (d=3, h=4);
translate ([4, slider_width/2, sliderHeight - 3]) cylinder (d=3, h=4);
}
// Lever
translate ([lever_offset, (slider_width-lever_thickness)/2, 0])
cube ([leverWidth,lever_thickness,leverHeight]);
// Pins
color("white") translate ([0, 0, -pin_length])
{
translate ([0, slider_width-1, 0]) cube ([0.5,1,pin_length]); // +V
translate ([slider_length-0.5, 0, 0]) cube ([0.5,1,pin_length]); // GND
translate ([slider_length-0.5, slider_width-1, 0]) cube ([0.5,1,pin_length]); // R
}
}
}

module PotentiometerBourns95A1()
Expand Down
26 changes: 26 additions & 0 deletions common.scad
Original file line number Diff line number Diff line change
Expand Up @@ -765,3 +765,29 @@ module spring(d,l)
{
%cylinder(d=d, h=l);
}

module cutRectRimZ(x, y, thickness, tolerance=0.0)
{
difference()
{
translate([-thickness, -thickness, -0.1])
cube([x + thickness * 2, y + thickness * 2, thickness + 0.2]);

translate([tolerance, tolerance, -1])
cube([x ,y, thickness + 2]);
}
}

module roundCube(d=3, size=[1, 1, 1], offset=[0, 0, 0], extended=0)
{
intersection()
{
children();
size2 = [size.x - 2*d + offset.x, size.y - 2*d + offset.y, size.z - 2*d + offset.z];
minkowski()
{
translate(-offset) translate([d, d, d]) cube(size2);
sphere(d + extended, $fn=50);
}
}
}
Binary file added img/full-photo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed img/full-photo.png
Binary file not shown.
Binary file modified img/full-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/fully-opened.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/handle-cable.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/handle-insides.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/printed-parts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions knobs.scad
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
include <common.scad>;

$fn = $preview ? 40 : 100;
include <throttle-parameters.scad>;

// Recommend 0.20mm layer on 0.4mm nozzle
module hatKnob()
Expand Down
103 changes: 46 additions & 57 deletions printed-parts.scad
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,77 @@ include <throttle-base.scad>;
include <throttle-handle.scad>;
include <throttle-case.scad>;

drawOtherParts = false;
separateHandle = true;

handleSeparation = separateHandle ? 15 : 0;

throttle_slider_height = 6.4;
throttle_slider_end_length = 10;
handle_radius = 30;
shaft_intrusion = caseThickness + 1;
shaft_length = 35;

// Lay out all printable parts on the same level
dist = 5;

// Parts inside
ShaftAndBase(shaft_length, shaft_intrusion);
moveX(60)
{
moveZ(main_throttle_lever_height) flipY() MainThrottleLever();
moveX(30)
{
moveZ(base_height) flipY() BasePrinted();
moveX(45)
{
moveZ(ThrottleAxisHolderOffsetX) turnY(-90) ThrottleAxisHolder();
moveX(20)
{
moveZ(ThrottleAxisHolderOffsetX) turnY(-90) ThrottleAxisHolder();
moveX(15)
{
moveZ(backstop_offset_z) turnY(90) Backstop();
moveX(30) moveZ(BallSpringPlungerBallPartOffsetZ) flipY()
BallSpringPlunger(springPart=false);
moveX(45) moveZ(BallSpringPlungerSpringPartOffsetZ)
BallSpringPlunger(ballPart=false);
}
}
}
}
}
// Unless otherwise mentioned, 0.30mm layer on 0.40mm nozzle is sufficient

// Case and handle
moveY(90)
module AllPrintableParts()
{
moveX(120)
CaseBottom();

moveY(case_outside.y/2 + dist)
flipY() moveZ(-case_depth-case_cover_height) CaseCover(right=true, left=false);

moveY(- case_outside.y/2 - dist)
flipY() moveZ(-case_depth-case_cover_height) CaseCover(right=false, left=true);

moveX(case_outside.x + dist)
{
moveZ(handle_case_print_offset_z) handleCase();
moveY(0) moveX(-50)
moveY(-base_width - handle_radius)
{
moveX(-45)
moveY(-20)
moveZ(thumb_print_offset_z)
turnX(thumbPlateAngle.x) turnY(180 - thumbPlateAngle.y)
handleThumbPart();
moveX(80)
flipY() moveZ(-base_height)
BasePrinted(leverDistance=throttle_lever_distance);

moveX(-base_length - dist)
ShaftAndBase(shaft_length, shaft_intrusion);

moveY(base_width)
moveX(-base_length)
moveZ(-base_height + 2)
{
ThrottleAxisHolder(sliderPart=true);
moveX(-2*dist) ThrottleAxisHolder(sliderPart=true);

moveX(-25) moveZ(case_bottom + base_height + 1)
flipY()
moveY(140)
moveZ(-2*case_cover_height - caseThickness/2)
{
CaseCover(left=true, right=false);
moveY(-130) moveX(150)
// Turn the right cover to print the correct way - your slicer may do this differently, so check it
turnZ(-45) CaseCover(left=false, right=true);
}
BallSpringPlunger2(ballPart=true, springPart=false);
}
}

moveX(-70) moveY(60)
PrintableKnobsAndButtons();

moveZ(handle_case_print_offset_z)
handleCase();

moveY(handle_radius*2 + dist)
moveZ(thumb_print_offset_z)
turnX(thumbPlateAngle.x) turnY(180 - thumbPlateAngle.y)
handleThumbPart();
}
}

// Knobs and buttons (Recommend 0.20mm layer on 0.40mm nozzle)
moveY(-50)
module PrintableKnobsAndButtons()
{
moveZ(12) flipY() XPotKnob();
moveX(30)
moveY(30)
moveZ(12) flipY() YPotKnob();
moveX(50)
moveX(20)
{
flipY() hatKnob();
moveX(15)
for (i=[0:1:4])
{
moveX(15*i)
moveY(15*i)
moveZ(SwitchButtonBallOffsetZ)
SwitchButtonBall();
}
}
}

AllPrintableParts();
Loading

0 comments on commit 34b8d62

Please sign in to comment.