Project showcase
A Simple Kerbal Space Program Arduino Leonardo Controller

A Simple Kerbal Space Program Arduino Leonardo Controller © CC BY-NC-SA

Just as the title says - with a 20-min gameplay video as proof.

  • 18 views
  • 0 comments
  • 0 respects

Components and supplies

Apps and online services

About this project


Yes, i've made a gameplay video, because I couldn't find anyone who actually "played" KSP on their custom controller.

Many people have created their own Kerbal Space Program controller, from the simplest buttons to full-size panels or cockpits with various readout displays. After playing KSP for a while, I thought I'd make something similar - something also simple - on my spare Arduino Leonardo.

The Leonardo and some of the Arduino microcontrollers can be used to "emulate" a keyboard and/or a mouse, by simply using the official Keyboard library to output keys (there are samples in the Arduino IDE). So I bought a bunch of cheap regular buttons/switches (couldn't find the fancy ones), install everything in a cardboard box and wired them up:

  • a 2-axis joystick with switch button
  • a 5-button board (using 1-wire to send different analog signal)
  • a rotary encoder with switch button
  • 3 push buttons
  • 2 switch
  • 4 LEDs
  • a speaker with very simple amplifier

In effect there are 18 different switches. Obviously only part of the keys can be set on the controller - then again, you still need the mouse to do some stuff in KSP. One of the switch is used to enable/disable keyboard output. The LEDs are used to indicate the status of certain switches (keyboard output, engine, RCS and SAS).

KSP default key bindings

Keyboard modifiers (special keys for Keyboard.h)

Since I wanted to keep it simple, I did not use any software plugins or mods to get data from the game. The good thing is that my controller will always be a plug-and-play device, no extra setup needed.

Oh, right. I changed throttle up/down keys in the KSP to left Ctrl and right Ctrl. My Win 10 OS went nuts when my rotary encoder tried to press a lot of left Shift in a short time.

I also used a speech library called Talkie, which is is a software implementation of Texas Instruments speech synthesizer on TI products and Atari arcade games, etc. I used Vocab_US_Large.h which are consisted of military/aircraft-related words. Not totally fit for KSP, but I always wanted to use it somewhere. (For example, when I press the map key, it would say "INFLIGHT AREA DISPLAY" :p there are just no "MAP" in the library. The voice also serves as delay for some buttons so that they would not be pressed too quickly.)

A small NPN transistor is used to amplify the sound for the speaker, but due to the power limitation of the microcontroller itself (and my lack of advanced knowledge of circuits) it is not loud. Just barely loud enough for the player.


Code

Kerbal ControllerArduino
#include "Keyboard.h"
#include "Talkie.h"
#include "Vocab_US_Large.h"

#define JOYSTICK_UP_KEY 'W'
#define JOYSTICK_DW_KEY 'S'
#define JOYSTICK_LT_KEY 'A'
#define JOYSTICK_RT_KEY 'D'
#define JOYSTICK_SW_KEY KEY_DELETE
#define BUTTON_1_KEY    'M'
#define BUTTON_2_KEY    'H'
#define BUTTON_3_KEY    'N'
#define BUTTON_4_KEY    'Q'
#define BUTTON_5_KEY    'E'
#define STAGE_KEY       ' '
#define ENGINE_ON_KEY   'Z'
#define ENGINE_OFF_KEY  'X'
#define ROTARY_UP_KEY   KEY_LEFT_CTRL // change throttle up key in the game
#define ROTARY_DW_KEY   KEY_RIGHT_CTRL // change throttle down key in the game
#define ROTARY_SW_KEY   'G'
#define SAS_KEY         'T'
#define RCS_KEY         'R'

#define JOYSTICK_X      A0
#define JOYSTICK_Y      A1
#define JOYSTICK_SW     A2
#define BUTTONS         A3
#define ENGINE          A4
#define POWER           A5
#define ROTARY_CLK      2
#define ROTARY_DTA      3
#define ROTARY_SW       4
#define STAGE           5
#define SAS             6
#define RCS             7
#define POWER_LED       8
#define ENGINE_LED      11
#define RCS_LED         12
#define SAS_LED         13
// the speaker is connected to pin 9 (+) and 10 (-)

#define BUTTON_G_V      4400
#define BUTTON_R_V      1000
#define BUTTON_W_V      1500
#define BUTTON_Y_V      500
#define BUTTON_B_V      2500

int joystick_x;
int joystick_y;
int joystick_sw;
int buttons;
int button_pressed;
int buttons_v[5] = {BUTTON_G_V, BUTTON_R_V, BUTTON_W_V, BUTTON_Y_V, BUTTON_B_V};
int rotary_clk = 1;
int rotary_clk_prev = 1;
int rotary_dta = 1;
int rotary_sw;
int rotary_dir;
int stage;
int enabled = 1;
int enabled_prev = 1;
int engine_on = 1;
int engine_on_prev = 1;
int rcs_on = 0;
int rcs_on_prev = 0;
int rcs = 0;
int rcs_prev = 0;
int sas_on = 0;
int sas_on_prev = 0;
int sas = 0;
int sas_prev = 0;

Talkie voice;

void setup() {

  Serial.begin(9600);
  Keyboard.begin();
  pinMode(JOYSTICK_SW, INPUT_PULLUP);
  pinMode(BUTTONS, INPUT_PULLUP);
  pinMode(POWER, INPUT_PULLUP);
  pinMode(ENGINE, INPUT_PULLUP);
  pinMode(ROTARY_CLK, INPUT_PULLUP);
  pinMode(ROTARY_DTA, INPUT_PULLUP);
  pinMode(ROTARY_SW, INPUT_PULLUP);
  pinMode(STAGE, INPUT_PULLUP);
  pinMode(SAS, INPUT_PULLUP);
  pinMode(RCS, INPUT_PULLUP);
  pinMode(POWER_LED, OUTPUT);
  pinMode(ENGINE_LED, OUTPUT);
  pinMode(RCS_LED, OUTPUT);
  pinMode(SAS_LED, OUTPUT);
  digitalWrite(POWER_LED, LOW);
  digitalWrite(ENGINE_LED, LOW);
  digitalWrite(RCS_LED, LOW);
  digitalWrite(SAS_LED, LOW);
  enabled = digitalRead(POWER);
  engine_on = digitalRead(ENGINE);
  rcs = digitalRead(RCS);
  sas = digitalRead(SAS);
  enabled_prev = enabled;
  engine_on_prev = engine_on;
  rcs_prev = rcs;
  sas_prev = sas;

  voice.say(sp5_FLIGHT_1);
  voice.say(sp4_CONTROL);
  voice.say(sp5_INSTRUMENTS);
  voice.say(sp3_START);
  voice.say(sp2_UP);
  voice.say(sp4_AND);
  voice.say(sp4_READY);

  delay(500);

}

void press_key(uint8_t key, int ms) {
  Serial.println("Pressed key: " + String(key));
  if (enabled) {
    Keyboard.write(key);
  }
  delay(ms);
}

void update_led() {
  digitalWrite(POWER_LED, enabled);
  digitalWrite(ENGINE_LED, engine_on);
  digitalWrite(RCS_LED, rcs_on);
  digitalWrite(SAS_LED, sas_on);
}

void loop() {

  joystick_x = analogRead(JOYSTICK_X);
  joystick_y = analogRead(JOYSTICK_Y);
  joystick_sw = !digitalRead(JOYSTICK_SW);
  buttons = analogRead(BUTTONS) * 10;
  button_pressed = -1;
  for (int i = 0; i < 5; i++) {
    if (abs(buttons - buttons_v[i]) < 250) {
      button_pressed = i;
      break;
    }
  }
  rotary_clk = digitalRead(ROTARY_CLK);
  rotary_dta = digitalRead(ROTARY_DTA);
  rotary_sw = !digitalRead(ROTARY_SW);
  rotary_dir = 0;
  if (rotary_clk_prev == 0 && rotary_clk == 1) {
    if (rotary_dta == 0) {
      rotary_dir = -1;
    } else {
      rotary_dir = 1;
    }
  }
  rotary_clk_prev = rotary_clk;

  enabled = !digitalRead(POWER);
  engine_on = !digitalRead(ENGINE);
  stage = !digitalRead(STAGE);
  rcs = digitalRead(RCS);
  sas = digitalRead(SAS);
  rcs_on = (rcs != rcs_prev) ? 1 - rcs_on : rcs_on;
  sas_on = (sas != sas_prev) ? 1 - sas_on : sas_on;

  update_led();

  if (joystick_x < 256) {
    press_key(JOYSTICK_LT_KEY, 5);
  } else if (joystick_x > 767) {
    press_key(JOYSTICK_RT_KEY, 5);
  }
  if (joystick_y < 256) {
    press_key(JOYSTICK_UP_KEY, 5);
  } else if (joystick_y > 767) {
    press_key(JOYSTICK_DW_KEY, 5);
  }
  if (joystick_sw) {
    press_key(JOYSTICK_SW_KEY, 5);
    voice.say(sp2_CONNECT);
    voice.say(sp5_LOCALIZER);
  }

  switch (button_pressed) {
    case 0:
      press_key(BUTTON_1_KEY, 5);
      voice.say(sp5_INFLIGHT);
      voice.say(sp4_AREA);
      voice.say(sp2_DISPLAY);
      break;
    case 1:
      press_key(BUTTON_2_KEY, 5);
      break;
    case 2:
      press_key(BUTTON_3_KEY, 5);
      break;
    case 3:
      press_key(BUTTON_4_KEY, 5);
      break;
    case 4:
      press_key(BUTTON_5_KEY, 5);
      break;
  }

  if (enabled && !enabled_prev) {
    voice.say(sp4_POWER);
    voice.say(sp4_ON);
  } else if (!enabled && enabled_prev) {
    voice.say(sp4_POWER);
    voice.say(sp4_OFF);
  }
  if (engine_on && !engine_on_prev) {
    press_key(ENGINE_ON_KEY, 5);
    voice.say(sp5_ENGINE);
    voice.say(sp5_IGNITE);
  } else if (engine_on_prev && !engine_on) {
    press_key(ENGINE_OFF_KEY, 5);
    voice.say(sp5_ENGINE);
    voice.say(sp3_STOP);
  }
  if (rotary_dir == -1) {
    digitalWrite(ENGINE_LED, !engine_on);
    press_key(ROTARY_UP_KEY, 5);
    digitalWrite(ENGINE_LED, !engine_on);
  } else if (rotary_dir == 1) {
    digitalWrite(ENGINE_LED, !engine_on);
    press_key(ROTARY_DW_KEY, 5);
    digitalWrite(ENGINE_LED, !engine_on);
  }
  if (rotary_sw) {
    press_key(ROTARY_SW_KEY, 5);
    voice.say(sp5_LANDING_GEAR);
  }
  if (stage) {
    press_key(STAGE_KEY, 5);
    voice.say(sp5_LAUNCH);
    voice.say(sp4_SEQUENCE);
  }
  if (rcs_on != rcs_on_prev) {
    press_key(RCS_KEY, 5);
    voice.say(sp4_VECTORS);
    voice.say(sp4_CONTROL);
  }
  if (sas_on != sas_on_prev) {
    press_key(SAS_KEY, 5);
    voice.say(sp5_AUTOPILOT);
  }

  enabled_prev = enabled;
  engine_on_prev = engine_on;
  rcs_on_prev = rcs_on;
  sas_on_prev = sas_on;
  rcs_prev = rcs;
  sas_prev = sas;
}

Comments

Similar projects you might like

A Simple Reflex Game

Project tutorial by rowan07

  • 1,687 views
  • 0 comments
  • 6 respects

ATmega Alien Themed Slot Machine

Project tutorial by Dan Murphy

  • 2,509 views
  • 6 comments
  • 25 respects

Arduino Simple Simon Says

Project tutorial by Metonymy

  • 8,058 views
  • 2 comments
  • 11 respects

Simple Programmable Robotic Arm

Project showcase by Ryan Chan

  • 96,156 views
  • 92 comments
  • 250 respects
Add projectSign up / Login