Jump to content

My KerbalController


hugopeeters

Recommended Posts

It might have been a year ago that I first saw someones custom controller for KSP on the internet. I thought: "man, I would really like to be able to build one of those, but I am lacking some serious skills".

After tinkering with Arduino's for a few months and learning how to solder from YouTube videos, I thought: "hey, maybe I can create a Kerbal Controller!"

Now that it is almost completed, I would like to share my build process with you all, including parts used, design drawings and Arduino code. I hope it will make it easier for some other KSP fans to get into building one.

[UPDATE]: Instructable here: https://www.instructables.com/id/KerbalController-a-Custom-Control-Panel-for-Rocket

[UPDATE]: I have uploaded all files to my github page here: https://github.com/hugopeeters

[UPDATE]: It is done! This is the finished product:

eYSQ8WG.jpg

 

But first, back to how it all started. My first test was to use a slide potentiometer to control the throttle. I did this using an Arduino UNO with UnoJoy. It took some trial and error to get it to run on my mac. I ended up not using UnoJoy in my final build. I'll explain later.

giphy.gif

 

Step two was to think about the number and type of buttons and switches I would like to use. I created a prototype layout in Sketchup.

WJJC6OT.jpg

 

I ordered a bunch of parts.

LGSxSOu.jpg

 

And a soldering iron.

9kJYlZ0.jpg

 

Trying the layout in real life.

RMSYzSi.jpg

 

First buttons installed.

giphy.gif

 

Testing out the LCD display.

giphy.gif

With the LCD display, I started using KSPSerialIO (props to @zitronen and @stibbons and any other people that worked on this plugin!). The hardest part of getting this to work, is variable type conversions. The plugin defines that the apoapsis in this example is a float. SO you have to do this trick to convert the value to a string or array of chars that can be sent to the display. This is the code I used for the test.

//Apoapsis
    char bufferAP[17];
    String strApo = "AP: ";
    if (VData.AP < 10000 && VData.AP > -10000) {
      strApo += String(VData.AP,0);
      strApo += "m ";
    } else if ((VData.AP >= 10000 && VData.AP < 10000000) || (VData.AP <= -10000 && VData.AP > -10000000)) {
      strApo += String((VData.AP / 1000),0);
      strApo += "km ";
    } else if ((VData.AP >= 10000000 && VData.AP < 10000000000) || (VData.AP <= -10000000 && VData.AP > -10000000000)) {
      strApo += String((VData.AP / 1000000),0);
      strApo += "Mm ";
    } else {
      strApo += String((VData.AP / 1000000000),0);
      strApo += "Gm ";
    }
    strApo.toCharArray(bufferAP,17);
    writeLCD(bufferAP);

 

This is the shoebox prototype with all the parts I tested with installed.

ilAkM83.jpg

 

And here is the mess of wires inside. I didn't want to have to desolder everything when moving to the final faceplate, so I used a breadboard for temporary connections.

DyalfkG.jpg

 

Enough info for today. In the next part, I'll show how I moved from the shoebox to a lasercut MDF faceplate and changed out those big arcade buttons for something way nicer! Stay tuned.

Edited by hugopeeters
Added instructable
Link to comment
Share on other sites

On 12/29/2017 at 1:58 AM, hugopeeters said:

First buttons installed.

Click-click-click-click-click-click-click-click-click-click-click-click-click-click!!! :D
addictive, innit! So much more fun than some silly old keyboard! It's what drives us! :sticktongue:

Seriously though, lookin' great!

Edited by richfiles
Autocorrect, stahp tryin' ta fix mah slang!
Link to comment
Share on other sites

55 minutes ago, stibbons said:

Nice work! Looks like a fun build. 

Pretty sure my mac port is a couple of revisions out of date, sorry. :( I'll make sure I get a build compiled against the most recent ksp available as soon as I can when I'm home from holidays. 

Yes, I am running KSP 1.2.2. I tried to compile the source code myself, but I run into the issue that KSP.UI does not appear to be present in the Assembly-CSharp.dll I copied from the KSP install I am running. I am not too concerned running an older version, but it would be awesome to be able to use my controller when the new Making History expansion comes out.

Link to comment
Share on other sites

1 hour ago, Freshmeat said:

That is a tight build. I like how you have focused on the absolute quality of life improvements, and hats off for learning everything from scratch. The LCD is multi-functional, judging from the toggles to the left of it?

Thank you. Yes, the X/Y/Z switches are used to toggle between different modes. I'll explain the details later, when I share more of the code.

Link to comment
Share on other sites

Here's part two of the build process.

After getting the inputs and outputs to work separately, I needed to think about integrating everything. I had used UnoJoy for the inputs and KSPSerialIO for the outputs. I realised using two Arduino's would not work very well, because I might want to make an output (LCD/LED) respond directly to an input (button press/toggle switch) or make the input depend on an output (crucial for using momentary buttons as toggles (if the lights are off, turn them on, if they are on, turn them off). So I really wanted to use a single Arduino. Because of the number of inputs and outputs required, I went with an Arduino Mega.

Aside from the fact that I could not get MegaJoy running on the mega, that's only good for inputs anyway. So the next step was to get inputs working using KSPSerialIO. It's a bit more complex than UnoJoy/MegaJoy, but it is soooo powerful! After reverse engineering the demo code, I have reorganised the code  in a way that makes sense to me and I started testing. Adding more and more code as I went along.

At the same time, I felt it was time to move on from the shoebox model and move to something more final. I looked into a company that creates full production grade button boxes. The mass production costs were fine (around 50 buck per item), but the first prototype would have cost 600+ dollars! A bit too much for me. Then, I looked into laser cutting. Multiple companies offer this service in my region (The Netherlands), at a price of around 40 - 50 dollars. Great! The only hurdle was I had no experience creating a vector drawing. I played around with a trial of Adobe Illustrator, but didn't feel like spending 20 buck per month after that. Inkscape is free, but I ended up with Affinity Designer. At a one-time price of 50 euros (60-ish dollars?) I liked the product. I watched basically all their video tutorials and drew this:

XdWvXDh.png

I'll add a mini-tutorial on how to draw for laser cutting / laser engraving later.

Here's the product I received:

p3fk2c3.jpg

jsNhpVR.jpg

 

I had only made two mistakes on this first try! No blocking issues however, so on with the build! The keep observer can spot the first mistake. Stay tuned for the rest of the build!

 

Link to comment
Share on other sites

Pretty awesome! You've managed to get up the steep learning curve.

You can use dtostrf() to format numbers for fixed width display, and I suggest including Time to AP on your LCD,  really useful for launch efficiency.

Link to comment
Share on other sites

Thank you! I like getting up learning curves. The view is great from up here :wink:

Okay, I'll look into dtostrf(). I am also struggling a bit with converting the byte for maxoverheat. I added TAp and TPe. 

Here are the display modes I came up with:

  //LCD Display Modes
  // 0 xyz TakeOff Mode:     Suface Velocity / Acceleration (G)
  // 1 Xyz Orbit Mode:       Apoapsis + Time to Apoapsis / Periapsis + Time to Periapsis
  // 2 xYz Maneuver Mode:    Time to next maneuver node / Remaining Delta-V for next maneuver node
  // 3 XYz Rendezvouz Mode:  Distance to target / Velocity relative to target
  // 4 xyZ Re-Entry Mode:    Percentage overheating (max) /   Deceleration (G)
  // 5 XyZ Flying Mode:      Altitude / Mach number
  // 6 xYZ Landing Mode:     Radar Altitude / Vertical Velocity
  // 7 XYZ Extra Mode:       not implemented (yet)

 

Link to comment
Share on other sites

My first mistake with the laser engraving was to include the KSP logo as a bitmap image. The laser shop requires everything to be vector based, so I should have traced the image in my vector drawing program to get it engraved. Ah well, I can stick on a sticker with a logo later.

The second mistake was worse. When measuring the joysticks, I looked at the available screw holes and the room needed for the stick to move. I forgot to measure the size of the joystick handles and think about fitting them. The handle has to go through the hole, but I made the holes too small!

SF2Xhi1.jpg

The odd thing is that the base of the joysticks has 4 additional screw holes. Those are not required to fit the joystick to the faceplate. The outer holes are fine. You screw through the ring that masks off the rubber skirt straight into the base of the joystick, so it all becomes securely fastened. Luckily, the rubber skirt and the ring will cover the hole, so nobody will see the hack job of me enlarging the holes with totally inappropriate tools.

c6BYfdy.jpg

It fits!

pKPEZ9I.jpg

 

Link to comment
Share on other sites

As you can see, I replaced the big arcade buttons with smaller, momentary push buttons with integrated LED's.

giphy.gif

I initially programmed some of them as toggles and others as momentary buttons. However, later I found out that Custom Action Groups are already handled as toggles. I am not sure whether that is native KSP behavior, or a feature of the KSPSerialIO plugin. Maybe @zitronen can shed some light on this? I also learned that the plugin already does a sort of edge detection to prevent unintentional repeated presses, so I could omit that from my own arduino code. Strange behavior still: if I toggle an Action group using my Controller, I cannot toggle it again from the game. Although my controller LED does follow the in-game status. Some more debugging to do there.... 

Link to comment
Share on other sites

@zitronen, I am digging around in your source code of KSPSerialIO to try and explain the behavior I am seeing. Take for example the lights. When a change in input is detected, you toggle the lights, like this:

ActiveVessel.ActionGroups.SetGroup(KSPActionGroup.Light, KSPSerialPort.VControls.Lights);

You basically keep track of the state of the lights with the boolean VesselControls.Lights. But you don't update that value based on what you get out of the game. You store that separately in KSPSerialPort.ControlStatus. I think that causes the unexpected behavior I am seeing. Is there any reason I am overlooking why you cannot stay in sync with the games (for example when  the lights button in the GUI is pressed) by sending something like this upon button presses?

ActiveVessel.ActionGroups.SetGroup(KSPActionGroup.Light, !(ActiveVessel.ActionGroups[KSPActionGroup.Light]));

 

Edited by hugopeeters
Link to comment
Share on other sites

Nice work, and a lot of thought well spent. I have found it quite useful to know how much dV i need to circularize when I reach AP, it helps me time my burn. Relevant bits of code:

Spoiler

float dVHohmann(float ap) { // calculates dV to circularize at apsis
	float dV, r, sgp;
	r = r_SOIBody(VData.SOINumber);
	sgp = sgp_SOIBody(VData.SOINumber);
	if ((r - ap) > 0) 	dV = sqrt(sgp / (ap + r)) - sqrt(sgp*(2 / (ap + r) - 1 / VData.SemiMajorAxis));
	else dV = -1;
	return dV;
}


float r_SOIBody(byte SOI) {
	float r;
	switch (SOI)
	{
	case 100: //kerbol
		r = 600000;
		break;
	case 110: // moho
		r = 200000;
		break;
	case 120: // eve
		r = 700000;
		break;
	case 121: // gilly
		r = 13000;
		break;
	case 130: // kerbin
		r = 600000;
		break;
	case 131: // mun
		r = 200000;
		break;
	case 132: // minmus
		r = 60000;
		break;
	case 140: // duna
		r = 320000;
		break;
	case 141: // ike
		r = 130000;
		break;


	}
	return r;
}

float sgp_SOIBody(byte SOI) {
	float sgp;
	switch (SOI)
	{
	case 100: //kerbol
		sgp = 3531600000000;
		break;
	case 110: // moho
		sgp = 65138398000;
		break;
	case 120: // eve
		sgp = 8171730200000;
		break;
	case 121: // gilly
		sgp = 8289449.8;
		break;
	case 130: // kerbin
		sgp = 3531600000000;
		break;
	case 131: // mun
		sgp = 65138398000;
		break;
	case 132: // minmus
		sgp = 1765800000;
		break;
	case 140: // duna
		sgp = 301363210000;
		break;
	case 141: // ike
		sgp = 18568369000;
		break;


	}
	return sgp;
}

 

I call it once every second, as square roots are expensive stuff

Edited by Freshmeat
typo
Link to comment
Share on other sites

8 minutes ago, Freshmeat said:

Nice work, and a lot of thought well spent. I have found it quite useful to know how much dV i need to circularize when I reach AP, it helps me time my burn. Relevant bits of code:

  Reveal hidden contents


float dVHohmann(float ap) { // calculates dV to circularize at apsis
	float dV, r, sgp;
	r = r_SOIBody(VData.SOINumber);
	sgp = sgp_SOIBody(VData.SOINumber);
	if ((r - ap) > 0) 	dV = sqrt(sgp / (ap + r)) - sqrt(sgp*(2 / (ap + r) - 1 / VData.SemiMajorAxis));
	else dV = -1;
	return dV;
}


float r_SOIBody(byte SOI) {
	float r;
	switch (SOI)
	{
	case 100: //kerbol
		r = 600000;
		break;
	case 110: // moho
		r = 200000;
		break;
	case 120: // eve
		r = 700000;
		break;
	case 121: // gilly
		r = 13000;
		break;
	case 130: // kerbin
		r = 600000;
		break;
	case 131: // mun
		r = 200000;
		break;
	case 132: // minmus
		r = 60000;
		break;
	case 140: // duna
		r = 320000;
		break;
	case 141: // ike
		r = 130000;
		break;


	}
	return r;
}

float sgp_SOIBody(byte SOI) {
	float sgp;
	switch (SOI)
	{
	case 100: //kerbol
		sgp = 3531600000000;
		break;
	case 110: // moho
		sgp = 65138398000;
		break;
	case 120: // eve
		sgp = 8171730200000;
		break;
	case 121: // gilly
		sgp = 8289449.8;
		break;
	case 130: // kerbin
		sgp = 3531600000000;
		break;
	case 131: // mun
		sgp = 65138398000;
		break;
	case 132: // minmus
		sgp = 1765800000;
		break;
	case 140: // duna
		sgp = 301363210000;
		break;
	case 141: // ike
		sgp = 18568369000;
		break;


	}
	return sgp;
}

 

I call it once every second, as square roots are expensive stuff

Wow, great stuff! Thanks for sharing your code!

It's too bad there is no vessel mass output by the plugin, to calculate remaining dV.

Once I am able to compile the plugin myself I might do some digging in the API...

Link to comment
Share on other sites

Check this mod out out. I spoke with Snark, the person behind it, asking if it was possible to make it's variables visible to other mods. Stibbons Is far more knowledgable on how the software side of things works, and clarified what was needed. I'm still deep into hardware, and even deeper into being overworked, ;.; so I'm not even remotely close to messing with software, but it seems like the mod has a lot of useful ∆v and time data, relating to maneuver nodes, surface impacts, and atmospheric interfaces. The update that mentions the data availability is located on page 12 of the thread.

Link to comment
Share on other sites

You might wanna ask Stibbons about his LCD navball too. Like seriously, he makes this stuff look almost easy! :D
Not terribly expensive or hard to find the needed hardware. An LCD, a controller for it, and as far as I'm aware, you just feed it the required data.

If you're concerned about room on your panel? Well, you can always expand to a second panel! :sticktongue:

I'm the one going with a real navball. So much work! :confused: I managed to get my hands on an FDAI (Flight Director/Attitude Indicator) from an old Israeli P-4 Phantom flight simulator. It's quite literally older than I am, and I sure do hope I can make it work. I'm building a mostly hardware solution to control it, with the software side only needing to command the hardware to do the real work. You'll notice I'm more of a hardware guy. I kinda refer to others far more knowledgable than I am right now, when software is on the line. :rolleyes:

Link to comment
Share on other sites

That's you?! Man, that's great!

I love how everybody on here is creating something, be it in hardware or in software. To me, the process is more fun than the product. Ok, showing off the product is pretty cool too :D

I guess the odds are pretty high I'll add a second module to the controller once it's done.

With the controller working mostly as desired so far:

pAHiScS.jpg

it's time to add the final components: the fuel gauges!

I am using these 10 LED bar modules. Some have all LEDs in one colour, others have a gradient of colours from green to red. On the former, I would like to light up all LEDs up to the current fuel level. On the latter, I want to light up only one LED at a time, so you get the visual cue of the level turning red.

At first I was prototyping with a LM3914 driver chip. It's works fine when you connect it to a potentiometer. But then I found out it needs an analog input and won't work with a PWM signal from an Arduino.

On to plan B: the shift register. In order to reduce 50 pins to only 3 we can string 7 HC595 chips together. All we have to do is convert the different fuel levels into the appropriate bytes to send into the chips.

After the prototype on a breadboard seemed to work, it's on to planning the soldered boards:

5wyEdkn.png

Work in progress:

fj4tHb0.jpg

 

All done except for the 50 leads from the drivers to the LEDs and the LEDs, via resistors to ground:

iDZ2TK1.jpg

 

Now I'm waiting for the last 2 LED modules to arrive from China and I'll do the final soldering.

Link to comment
Share on other sites

Delivery from China! Best get soldering!

Step 1: 50 wires to go from the shift registers to teh LED bars

Lt8oYwU.jpg

UkjstAc.jpg

I put the wires on the back of the boards. You can see the copper leads looping back to be soldered in in the back. This way should help prevent breaking the solder points when I pull on the wires.

 

Step 2: 50 current-limiting resistors to prevent the LEDs from burning out. I used larger value resistors on the green LEDs because they look brighter.

8rNjsDw.jpg

Standing up at the back of the board because there is no room on the front on this board.

2nxQBMQ.jpg

The one on the right is nice and tidy. When I did the other two, I soldered them down before bending the resistors down and now they won't budge :( I broke 4 off while trying and had to desolder and replace them. Very frustrating!

I guess they'll be recessed a mm more then I woud have liked. Ah well...

vyGChUH.jpg

Test fitting... You can tell the right ones are recessed.

 

Last steps to be completed: connecting the 50 wires I attached to the shift registers to the LED bars and writing up the code.

Link to comment
Share on other sites

1 hour ago, hugopeeters said:

Delivery from China! Best get soldering!
Step 1: 50 wires to go from the shift registers to the LED bars

Getting that package in the mail is like a little mini Christmas! The gift of tinkering! :D
It's especially a joy when the package arrives on a Friday... and a bummer when tracking shows it'll miss your weekend and show the next Monday. :/

Hehe... Mass wiring can be a pain, but that end result is so worth it! My solution was three dimensional. I brought my decoder circuitry right to the back of the LEDs. Complex, but compact. Bus wiring is tried and true and reliable! I really like your strain relief efforts on all the wires. Extra effort, but well worth it to make the ends more reliable. I do that often when I wire wrap boards.
 

1 hour ago, hugopeeters said:

The one on the right is nice and tidy. When I did the other two, I soldered them down before bending the resistors down and now they won't budge :( I broke 4 off while trying and had to desolder and replace them. Very frustrating!

Yeah. It can be fairly important to have a tiny bit of extra lead length between the board and the component for many parts. With those resistors, you snapped them from leverage forces pulling at the wire when the edges of the resistor body hit the board and couldn't budge any further. I always leave at least a millimeter or so of space under a part, just to avoid scenarios like this. I actually had to break my own rule doing some of my recent work, and it left me very worried about defects. I managed to not break anything, but it was not ideal.

One simple trick if you soldier it too close to the board... Reheat the solder joint as you bend over the part (the solder joint that's too close to the part is the one to reheat). It lets the lead pull up, giving it enough slack to bend. As long as you don't pull the lead entirely out of the solder joint, you should still have a reasonable connection. That ought to fix the problem of the right LED bars being recessed!

Another tip, for the broken ones. If you pre-trim a new resistor with a short lead, and pre-tin the lead with solder (if the part isn't already pre-tinned), then you should be able to just heat the pad and insert the part into the hole. The old busted lead may or may not fall out. Doesn't even matter, as long as it doesn't come out and short anything. If it pokes out partially, you can just trim it.

Great work on this! You're moving a a lightning pace, and doing a great job in the process! :cool:

Edited by richfiles
Link to comment
Share on other sites

There is so much I am learning by making these kinds of mistakes. I don't think you can learn these things beforehand. I guess knowledge comes from learning, but wisdom from experience :wink: 

All in all, I am very pleased with my results so far. Sometimes I imagine starting over and doing everything perfectly. But then I think: hey, it works and it looks fine, so why bother?

Thank you for the compliment! I'll try your suggestion for bending the resistors while heating up the connections. Carefully!

The last package of parts has already been delivered, but to one of my neighbors and I don't know which one! I'll go knocking on their doors tonight maybe. It's the M2, M2.5 and M3 nuts, bolts and spacers to fix the LCD and LED bars in place properly. Until then: duct (duck?) tape!

Link to comment
Share on other sites

Gah! The mysterious neighbor delivery... Curious, did they mistakenly deliver, or did they leave you a "left with a neighbor" notice? Either way, hope you find it!

Also, consider a tweezer, small plier, toothpick, or any suitable tool when doing the reflow + bend, incase the part gets quickly too hot to handle. The nice part of that technique, is since you only reheat the one side, the solder joint on the other side holds the part stable. Don't have to have that much of a grip or anything, since it won't fall off. Good luck!

Edited by richfiles
Link to comment
Share on other sites

1 hour ago, richfiles said:

Gah! The mysterious neighbor delivery... Curious, did they mistakenly deliver, or did they leave you a "left with a neighbor" notice? Either way, hope you find it!

It's horrible! I live in an apartment building. I buzzed the mailman in and heard him use the elevator. But he never arrived on my floor! He apparently delivered the package on the wrong floor and I have no idea which one.

At least it's only the bolts and not a crucial electrical component, so I can keep tinkering.

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...