├── .gitignore ├── README.md ├── KerbalController ├── LCD.ino ├── serial_communication.ino ├── init.ino ├── define_controlpacket.ino ├── KerbalController.ino └── define_vesseldatadisplay.ino └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KerbalController 2 | 3 | Arduino Code for KerbalController using KSPSerialIO 4 | 5 | This is the Arduino Code I am running on the Arduino Mega in my KerbalController. It makes use of KSPSerialIO originally created by zitron () and made compatible for Mac by phardy (. 6 | 7 | This code is based on the KSPIODemo16 files by zitron () but rigorously rearranged to my own preferences. 8 | -------------------------------------------------------------------------------- /KerbalController/LCD.ino: -------------------------------------------------------------------------------- 1 | void clearLCD () 2 | { 3 | //clear the LCD by writing all spaces 4 | jumpToStart(); 5 | mySerial.write(" "); 6 | mySerial.write(" "); 7 | jumpToStart(); 8 | } 9 | 10 | void jumpToStart () 11 | { 12 | //jump to the start of the first line on the LCD 13 | mySerial.write(254); 14 | mySerial.write(128); 15 | } 16 | 17 | void jumpToLineTwo () 18 | { 19 | //jump to the start of the second line on the LCD 20 | mySerial.write(254); 21 | mySerial.write(192); 22 | } 23 | 24 | void writeLCD (char myText[]) 25 | { 26 | //write text to the LCD 27 | mySerial.write(myText); 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 hugopeeters 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /KerbalController/serial_communication.ino: -------------------------------------------------------------------------------- 1 | uint8_t rx_len; 2 | uint16_t * address; 3 | byte buffer[256]; //address for temporary storage and parsing buffer 4 | uint8_t structSize; 5 | uint8_t rx_array_inx; //index for RX parsing buffer 6 | uint8_t calc_CS; //calculated Chacksum 7 | 8 | struct HandShakePacket 9 | { 10 | byte id; 11 | byte M1; 12 | byte M2; 13 | byte M3; 14 | }; 15 | 16 | HandShakePacket HPacket; 17 | 18 | void Handshake(){ 19 | HPacket.id = 0; 20 | HPacket.M1 = 3; 21 | HPacket.M2 = 1; 22 | HPacket.M3 = 4; 23 | KSPBoardSendData(details(HPacket)); 24 | } 25 | 26 | void InitTxPackets() { 27 | HPacket.id = 0; 28 | CPacket.id = 101; 29 | } 30 | 31 | //This shit contains stuff borrowed from EasyTransfer lib 32 | boolean KSPBoardReceiveData() { 33 | if ((rx_len == 0)&&(Serial.available()>3)){ 34 | while(Serial.read()!= 0xBE) { 35 | if (Serial.available() == 0) 36 | return false; 37 | } 38 | if (Serial.read() == 0xEF){ 39 | rx_len = Serial.read(); 40 | id = Serial.read(); 41 | rx_array_inx = 1; 42 | 43 | switch(id) { 44 | case 0: 45 | structSize = sizeof(HPacket); 46 | address = (uint16_t*)&HPacket; 47 | break; 48 | case 1: 49 | structSize = sizeof(VData); 50 | address = (uint16_t*)&VData; 51 | break; 52 | } 53 | } 54 | 55 | //make sure the binary structs on both Arduinos are the same size. 56 | if(rx_len != structSize){ 57 | rx_len = 0; 58 | return false; 59 | } 60 | } 61 | 62 | if(rx_len != 0){ 63 | while(Serial.available() && rx_array_inx <= rx_len){ 64 | buffer[rx_array_inx++] = Serial.read(); 65 | } 66 | buffer[0] = id; 67 | 68 | if(rx_len == (rx_array_inx-1)){ 69 | //seem to have got whole message 70 | //last uint8_t is CS 71 | calc_CS = rx_len; 72 | for (int i = 0; i CONTROLREFRESH) { 8 | controlTimeOld = now; 9 | define_control_packet(); 10 | } 11 | } 12 | 13 | //define the structure of a control packet 14 | struct ControlPacket { 15 | //the following controls can be sent to the KSPSerialIO plugin (defined by the plugin) 16 | byte id; 17 | byte MainControls; //SAS RCS Lights Gears Brakes Precision Abort Stage (see enum) 18 | byte Mode; //0 = stage, 1 = docking, 2 = map 19 | unsigned int ControlGroup; //action groups 1-10 20 | byte NavballSASMode; //AutoPilot mode 21 | byte AdditionalControlByte1; 22 | int Pitch; //-1000 -> 1000 23 | int Roll; //-1000 -> 1000 24 | int Yaw; //-1000 -> 1000 25 | int TX; //-1000 -> 1000 26 | int TY; //-1000 -> 1000 27 | int TZ; //-1000 -> 1000 28 | int WheelSteer; //-1000 -> 1000 29 | int Throttle; // 0 -> 1000 30 | int WheelThrottle; // 0 -> 1000 31 | }; 32 | 33 | //Create an instance of a control packet 34 | ControlPacket CPacket; 35 | 36 | //macro used to generate the control packet (also used for the handshake packet) 37 | #define details(name) (uint8_t*)&name,sizeof(name) 38 | 39 | //Enumeration of MainControls 40 | #define SAS 7 41 | #define RCS 6 42 | #define LIGHTS 5 43 | #define GEARS 4 44 | #define BRAKES 3 45 | #define PRECISION 2 46 | #define ABORT 1 47 | #define STAGE 0 48 | 49 | //Main controls uses enum above, e.g. MainControls(SAS,true); 50 | void MainControls(byte n, boolean s) { 51 | if (s) 52 | CPacket.MainControls |= (1 << n); // forces nth bit of x to be 1. all other bits left alone. 53 | else 54 | CPacket.MainControls &= ~(1 << n); // forces nth bit of x to be 0. all other bits left alone. 55 | } 56 | 57 | //Control groups (action groups) uses an integer to refer to a custom action group, e.g. ControlGroup(5,true); 58 | void ControlGroups(byte n, boolean s) { 59 | if (s) 60 | CPacket.ControlGroup |= (1 << n); // forces nth bit of x to be 1. all other bits left alone. 61 | else 62 | CPacket.ControlGroup &= ~(1 << n); // forces nth bit of x to be 0. all other bits left alone. 63 | } 64 | 65 | //Enumeration of SAS Modes 66 | #define SMOFF 0 67 | #define SMSAS 1 68 | #define SMPrograde 2 69 | #define SMRetroGrade 3 70 | #define SMNormal 4 71 | #define SMAntinormal 5 72 | #define SMRadialIn 6 73 | #define SMRadialOut 7 74 | #define SMTarget 8 75 | #define SMAntiTarget 9 76 | #define SMManeuverNode 10 77 | 78 | //SAS mode uses enum above, e.g. setSASMode(SMPrograde); 79 | void setSASMode(byte m) { 80 | CPacket.NavballSASMode &= B11110000; 81 | CPacket.NavballSASMode += m; 82 | } 83 | 84 | //Enumeration of Navball Target Modes 85 | #define NAVBallIGNORE 0 86 | #define NAVBallORBIT 1 87 | #define NAVBallSURFACE 2 88 | #define NAVBallTARGET 3 89 | 90 | //Navball mode uses enum above, e.g. setNavBallMode(NAVBallSURFACE); 91 | void setNavballMode(byte m) { 92 | CPacket.NavballSASMode &= B00001111; 93 | CPacket.NavballSASMode += m << 4; 94 | } 95 | 96 | 97 | void define_control_packet() { 98 | if (Connected) { 99 | //here we define what controls to send when which pins are manipulated 100 | 101 | //toggle switches 102 | if(!digitalRead(pSAS)){MainControls(SAS, true);} else {MainControls(SAS, false);} 103 | if(!digitalRead(pRCS)){MainControls(RCS, true);} else {MainControls(RCS, false);} 104 | if(digitalRead(pABORT)){MainControls(ABORT, true);} else {MainControls(ABORT, false);} 105 | 106 | //momentary stage button 107 | if(!digitalRead(pSTAGE) && digitalRead(pARM)){MainControls(STAGE, true);} else {MainControls(STAGE, false);} 108 | if(digitalRead(pARM)){ 109 | now = millis(); 110 | stageLedTime = now - stageLedTimeOld; 111 | if (stageLedTime > stageLedBlinkTime) { 112 | stageLedTimeOld = now; 113 | stage_led_on = !stage_led_on; 114 | } 115 | } 116 | else {stage_led_on = false;} 117 | digitalWrite(pSTAGELED, stage_led_on); 118 | 119 | //toggle buttons 120 | if(!digitalRead(pLIGHTS)){MainControls(LIGHTS, !lights_on);} 121 | if(!digitalRead(pGEARS)){MainControls(GEARS, !gears_on);} 122 | if(!digitalRead(pBRAKES)){MainControls(BRAKES, !brakes_on);} 123 | if(!digitalRead(pACTION1)){ControlGroups(1, !action1_on);} 124 | if(!digitalRead(pACTION2)){ControlGroups(2, !action2_on);} 125 | if(!digitalRead(pACTION3)){ControlGroups(3, !action3_on);} 126 | if(!digitalRead(pACTION4)){ControlGroups(4, !action4_on);} 127 | if(!digitalRead(pLADDER)){ControlGroups(5, !ladder_on);} 128 | if(!digitalRead(pSOLAR)){ControlGroups(6, !solar_on);} 129 | if(!digitalRead(pCHUTES)){ControlGroups(7, !chutes_on);} 130 | 131 | //throttle 132 | CPacket.Throttle = constrain(map(analogRead(pTHROTTLE),30,990,0,1023),0,1000); 133 | 134 | //rotation joystick button toggles flight control modes 135 | if(!digitalRead(pRB) && !rb_prev){rb_on = !rb_on; rb_prev = true;} 136 | if(digitalRead(pRB) && rb_prev){rb_prev = false;} 137 | 138 | if(rb_on){ 139 | //rocket mode 140 | if(analogRead(pRX) >= 530){CPacket.Yaw = constrain(map(analogRead(pRX),1023,530,-1000,0),-1000,0);} 141 | else if(analogRead(pRX) <= 470){CPacket.Yaw = constrain(map(analogRead(pRX),470,0,0,1000),0,1000);} 142 | else {CPacket.Yaw = 0;} 143 | if(analogRead(pRY) >= 530){CPacket.Pitch = constrain(map(analogRead(pRY),530,1023,0,1000),0,1000);} 144 | else if(analogRead(pRY) <= 470){CPacket.Pitch = constrain(map(analogRead(pRY),0,470,-1000,0),-1000,0);} 145 | else {CPacket.Pitch = 0;} 146 | if(analogRead(pRZ) <= 40){CPacket.Roll = constrain(map(analogRead(pRZ),0,40,-1000,0),-1000,0);} 147 | else if(analogRead(pRZ) >= 60){CPacket.Roll = constrain(map(analogRead(pRZ),60,500,0,1000),0,1000);} 148 | else {CPacket.Roll = 0;} 149 | 150 | if(analogRead(pTX) >= 530){CPacket.TX = constrain(map(analogRead(pTX),1023,530,0,1000),0,1000);} 151 | else if(analogRead(pTX) <= 470){CPacket.TX = constrain(map(analogRead(pTX),470,0,-1000,0),-1000,0);} 152 | else {CPacket.TX = 0;} 153 | if(analogRead(pTY) >= 530){CPacket.TY = constrain(map(analogRead(pTY),1023,530,-1000,0),-1000,0);} 154 | else if(analogRead(pTY) <= 470){CPacket.TY = constrain(map(analogRead(pTY),470,0,0,1000),0,1000);} 155 | else {CPacket.TY = 0;} 156 | if(analogRead(pTZ) <= 60){CPacket.TZ = constrain(map(analogRead(pTZ),0,60,-1000,0),-1000,0);} 157 | else if(analogRead(pTZ) >= 90){CPacket.TZ = constrain(map(analogRead(pTZ),90,500,0,1000),0,1000);} 158 | else {CPacket.TZ = 0;} 159 | } 160 | else { 161 | //airplane mode 162 | if(analogRead(pRX) >= 530){CPacket.Roll = constrain(map(analogRead(pRX),1023,530,-1000,0),-1000,0);} 163 | else if(analogRead(pRX) <= 470){CPacket.Roll = constrain(map(analogRead(pRX),470,0,0,1000),0,1000);} 164 | else {CPacket.Yaw = 0;} 165 | if(analogRead(pRY) >= 530){CPacket.Pitch = constrain(map(analogRead(pRY),530,1023,0,1000),0,1000);} 166 | else if(analogRead(pRY) <= 470){CPacket.Pitch = constrain(map(analogRead(pRY),0,470,-1000,0),-1000,0);} 167 | else {CPacket.Pitch = 0;} 168 | if(analogRead(pTX) >= 530){CPacket.Yaw = constrain(map(analogRead(pTX),1023,530,-1000,0),-1000,0);} 169 | else if(analogRead(pTX) <= 470){CPacket.Yaw = constrain(map(analogRead(pTX),470,0,0,1000),0,1000);} 170 | else {CPacket.Yaw = 0;} 171 | CPacket.TX = 0; 172 | CPacket.TY = 0; 173 | CPacket.TZ = 0; 174 | } 175 | 176 | // //translation joystick button toggles between modes? 177 | // if(!digitalRead(pTB) && !tb_prev){tb_on = !tb_on; tb_prev = true;} 178 | // if(digitalRead(pTB) && tb_prev){tb_prev = false;} 179 | // if(tb_on){ 180 | // 181 | // } 182 | // else { 183 | // 184 | // } 185 | 186 | //send the control packet to the KSPSerialIO plugin 187 | KSPBoardSendData(details(CPacket)); 188 | } 189 | } 190 | 191 | -------------------------------------------------------------------------------- /KerbalController/KerbalController.ino: -------------------------------------------------------------------------------- 1 | #include 2 | SoftwareSerial mySerial(15,14); //pin 14 connected to LCD, 15 unconnected 3 | 4 | //analog pins 5 | const int pTHROTTLE = A0; //slide pot 6 | const int pTX = A1; //translation x-axis 7 | const int pTY = A2; //translation y-axis 8 | const int pTZ = A3; //translation z-axis 9 | const int pRX = A4; //rotation x-axis 10 | const int pRY = A5; //rotation y-axis 11 | const int pRZ = A6; //rotation z-axis 12 | 13 | //digital pins 14 | const int pPOWER = 2; //power switch 15 | const int pTB = 3; //translation joystick button 16 | const int pRB = 4; //rotation joystick button 17 | const int latchPin = 8; //ST_CP - green 18 | const int dataPin = 11; //DS - yellow 19 | const int clockPin = 12; //SH_CP - blue 20 | const int pMODE = 22; //mode switch (used for debug mode) 21 | const int pLCDx = 27; //toggle switch x (used for LCD display modes) 22 | const int pLCDy = 24; //toggle switch y (used for LCD display modes) 23 | const int pLCDz = 29; //toggle switch z (used for LCD display modes) 24 | const int pSAS = 26; //SAS switch 25 | const int pRCS = 31; //RCS switch 26 | const int pABORT = 28; //Abort switch (safety switch, active high) 27 | const int pARM = 30; //Arm switch (safety switch, active high) 28 | const int pSTAGE = 32; //Stage button 29 | const int pSTAGELED = 33; //Stage button LED 30 | const int pLIGHTS = 34; //Lights button 31 | const int pLIGHTSLED = 35; //Lights button LED 32 | const int pLADDER = 36; //Ladder button (action group 5) 33 | const int pLADDERLED = 37; //Ladder button LED 34 | const int pSOLAR = 38; //Solar button (action group 6) 35 | const int pSOLARLED = 39; //Solar button LED 36 | const int pCHUTES = 40; //Chutes button (action group 7) 37 | const int pCHUTESLED = 41; //Chutes button LED 38 | const int pGEARS = 42; //Gears button 39 | const int pGEARSLED = 43; //Gears button LED 40 | const int pBRAKES = 44; //Brakes button 41 | const int pBRAKESLED = 45; //Brakes button LED 42 | const int pACTION1 = 46; //Action Group 1 button 43 | const int pACTION1LED = 47; //Action Group 1 button LED 44 | const int pACTION2 = 48; //Action Group 2 button 45 | const int pACTION2LED = 49; //Action Group 2 button LED 46 | const int pACTION3 = 50; //Action Group 3 button 47 | const int pACTION3LED = 51; //Action Group 3 button LED 48 | const int pACTION4 = 52; //Action Group 4 button 49 | const int pACTION4LED = 53; //Action Group 4 button LED 50 | 51 | //in-game state used to update button LEDs 52 | bool lights_on = false; 53 | bool ladder_on = false; 54 | bool solar_on = false; 55 | bool gears_on = false; 56 | bool brakes_on = false; 57 | bool chutes_on = false; 58 | bool stage_on = false; 59 | bool action1_on = false; 60 | bool action2_on = false; 61 | bool action3_on = false; 62 | bool action4_on = false; 63 | 64 | //toggle button states 65 | bool tb_on = true; 66 | bool rb_on = true; 67 | 68 | //previous button state 69 | bool tb_prev = false; 70 | bool rb_prev = false; 71 | 72 | //stage LED state 73 | bool stage_led_on = false; 74 | 75 | //input value variables 76 | int throttle_value; 77 | int tx_value; 78 | int ty_value; 79 | int tz_value; 80 | int rx_value; 81 | int ry_value; 82 | int rz_value; 83 | 84 | //variables used for fuel guages 85 | byte inputBytes[7]; 86 | int vSF, vLF, vOX, vEL, vMP, SF, LF, OX, EL, MP; 87 | 88 | //debug variable 89 | bool debug = false; 90 | 91 | //timing 92 | const int IDLETIMER = 20000; //if no message received from KSP for more than 20s, go idle (default 2000) 93 | const int CONTROLREFRESH = 10; //send control packet every 10 ms (default 25) 94 | const int stageLedBlinkTime = 500; //blink staging LED when armed every 500 ms 95 | 96 | //variables used in timing 97 | unsigned long deadtime, deadtimeOld, controlTime, controlTimeOld, stageLedTime, stageLedTimeOld; 98 | unsigned long now; 99 | 100 | //variables used in serial communication 101 | boolean Connected = false; 102 | byte id; 103 | 104 | void setup() { 105 | 106 | Serial.begin(38400); //KSPSerialIO connection 107 | mySerial.begin(9600); //LCD connection 108 | delay(500); //wait for LCD boot 109 | 110 | //write to LCD 111 | clearLCD(); 112 | writeLCD("KerbalController"); 113 | jumpToLineTwo(); 114 | writeLCD("booting..."); 115 | delay(100); 116 | 117 | //Initialize 118 | controlsInit(); //set all pin modes 119 | testLEDS(50); //blink every LED once to test (with a delay of 10 ms) 120 | InitTxPackets(); //initialize the serial communication 121 | } 122 | 123 | void loop() { 124 | 125 | //check mode 126 | if(!digitalRead(pMODE)){debug = true;} else {debug = false;} 127 | 128 | if(debug){ 129 | //Debug mode does not communicate with KSPSerialIO, but simply displays the button states on the LCD. 130 | //Select analog input using LCDx/y/z. Xyz = Throttle. xYz = Translation. xyZ = Rotation. 131 | 132 | //previous state tracking only used in debug 133 | bool stage_prev = false; 134 | bool chutes_prev = false; 135 | bool action1_prev = false; 136 | bool action2_prev = false; 137 | bool action3_prev = false; 138 | bool action4_prev = false; 139 | bool lights_prev = false; 140 | bool ladder_prev = false; 141 | bool solar_prev = false; 142 | bool gears_prev = false; 143 | bool brakes_prev = false; 144 | 145 | //clear the LCD 146 | clearLCD(); 147 | 148 | //toggle switches 149 | if(!digitalRead(pSAS)){writeLCD("S");} else {writeLCD("s");} 150 | if(!digitalRead(pRCS)){writeLCD("R");} else {writeLCD("r");} 151 | if(digitalRead(pABORT)){writeLCD("A");} else {writeLCD("a");} //note abort switch is active high 152 | if(digitalRead(pARM)){writeLCD("A");} else {writeLCD("a");} //note arm switch is active high 153 | 154 | //in debug mode, handle all buttons as toggle buttons 155 | 156 | if(!digitalRead(pSTAGE) && !stage_prev){stage_on = !stage_on; stage_prev = true;} 157 | if(digitalRead(pSTAGE) && stage_prev){stage_prev = false;} 158 | if(stage_on){writeLCD("S");} else {writeLCD("s");} 159 | digitalWrite(pSTAGELED, stage_on); 160 | 161 | if(!digitalRead(pLIGHTS) && !lights_prev){lights_on = !lights_on; lights_prev = true;} 162 | if(digitalRead(pLIGHTS) && lights_prev){lights_prev = false;} 163 | if(lights_on){writeLCD("L");} else {writeLCD("l");} 164 | digitalWrite(pLIGHTSLED, lights_on); 165 | 166 | if(!digitalRead(pLADDER) && !ladder_prev){ladder_on = !ladder_on; ladder_prev = true;} 167 | if(digitalRead(pLADDER) && ladder_prev){ladder_prev = false;} 168 | if(ladder_on){writeLCD("L");} else {writeLCD("l");} 169 | digitalWrite(pLADDERLED, ladder_on); 170 | 171 | if(!digitalRead(pSOLAR) && !solar_prev){solar_on = !solar_on; solar_prev = true;} 172 | if(digitalRead(pSOLAR) && solar_prev){solar_prev = false;} 173 | if(solar_on){writeLCD("S");} else {writeLCD("s");} 174 | digitalWrite(pSOLARLED, solar_on); 175 | 176 | if(!digitalRead(pCHUTES) && !chutes_prev){chutes_on = !chutes_on; chutes_prev = true;} 177 | if(digitalRead(pCHUTES) && chutes_prev){chutes_prev = false;} 178 | if(chutes_on){writeLCD("C");} else {writeLCD("c");} 179 | digitalWrite(pCHUTESLED, chutes_on); 180 | 181 | if(!digitalRead(pGEARS) && !gears_prev){gears_on = !gears_on; gears_prev = true;} 182 | if(digitalRead(pGEARS) && gears_prev){gears_prev = false;} 183 | if(gears_on){writeLCD("G");} else {writeLCD("g");} 184 | digitalWrite(pGEARSLED, gears_on); 185 | 186 | if(!digitalRead(pBRAKES) && !brakes_prev){brakes_on = !brakes_on; brakes_prev = true;} 187 | if(digitalRead(pBRAKES) && brakes_prev){brakes_prev = false;} 188 | if(brakes_on){writeLCD("B");} else {writeLCD("b");} 189 | digitalWrite(pBRAKESLED, brakes_on); 190 | 191 | if(!digitalRead(pACTION1) && !action1_prev){action1_on = !action1_on; action1_prev = true;} 192 | if(digitalRead(pACTION1) && action1_prev){action1_prev = false;} 193 | if(action1_on){writeLCD("A");} else {writeLCD("a");} 194 | digitalWrite(pACTION1LED, action1_on); 195 | 196 | if(!digitalRead(pACTION2) && !action2_prev){action2_on = !action2_on; action2_prev = true;} 197 | if(digitalRead(pACTION2) && action2_prev){action2_prev = false;} 198 | if(action2_on){writeLCD("A");} else {writeLCD("a");} 199 | digitalWrite(pACTION2LED, action2_on); 200 | 201 | if(!digitalRead(pACTION3) && !action3_prev){action3_on = !action3_on; action3_prev = true;} 202 | if(digitalRead(pACTION3) && action3_prev){action3_prev = false;} 203 | if(action3_on){writeLCD("A");} else {writeLCD("a");} 204 | digitalWrite(pACTION3LED, action3_on); 205 | 206 | if(!digitalRead(pACTION4) && !action4_prev){action4_on = !action4_on; action4_prev = true;} 207 | if(digitalRead(pACTION4) && action4_prev){action4_prev = false;} 208 | if(action4_on){writeLCD("A");} else {writeLCD("a");} 209 | digitalWrite(pACTION4LED, action4_on); 210 | 211 | if(!digitalRead(pTB) && !tb_prev){tb_on = !tb_on; tb_prev = true;} 212 | if(digitalRead(pTB) && tb_prev){tb_prev = false;} 213 | if(tb_on){writeLCD("T");} else {writeLCD("t");} 214 | 215 | if(!digitalRead(pRB) && !rb_prev){rb_on = !rb_on; rb_prev = true;} 216 | if(digitalRead(pRB) && rb_prev){rb_prev = false;} 217 | if(rb_on){writeLCD("R");} else {writeLCD("r");} 218 | 219 | //analog inputs 220 | if(!digitalRead(pLCDx) && digitalRead(pLCDy) && digitalRead(pLCDz)){ 221 | throttle_value = analogRead(pTHROTTLE); 222 | char throttle_char[5]; 223 | itoa(throttle_value, throttle_char, 10); 224 | writeLCD(throttle_char); 225 | writeLCD(" "); 226 | } 227 | if(digitalRead(pLCDx) && !digitalRead(pLCDy) && digitalRead(pLCDz)){ 228 | tx_value = analogRead(pTX); 229 | char tx_char[5]; 230 | itoa(tx_value, tx_char, 10); 231 | writeLCD(tx_char); 232 | writeLCD(" "); 233 | ty_value = analogRead(pTY); 234 | char ty_char[5]; 235 | itoa(ty_value, ty_char, 10); 236 | writeLCD(ty_char); 237 | writeLCD(" "); 238 | tz_value = analogRead(pTZ); 239 | char tz_char[5]; 240 | itoa(tz_value, tz_char, 10); 241 | writeLCD(tz_char); 242 | writeLCD(" "); 243 | } 244 | 245 | if(digitalRead(pLCDx) && digitalRead(pLCDy) && !digitalRead(pLCDz)){ 246 | rx_value = analogRead(pRX); 247 | char rx_char[5]; 248 | itoa(rx_value, rx_char, 10); 249 | writeLCD(rx_char); 250 | writeLCD(" "); 251 | ry_value = analogRead(pRY); 252 | char ry_char[5]; 253 | itoa(ry_value, ry_char, 10); 254 | writeLCD(ry_char); 255 | writeLCD(" "); 256 | rz_value = analogRead(pRZ); 257 | char rz_char[5]; 258 | itoa(rz_value, rz_char, 10); 259 | writeLCD(rz_char); 260 | } 261 | //end of debug mode 262 | } 263 | else { 264 | 265 | //KSP mode 266 | 267 | //send and receive data 268 | get_vessel_data(); 269 | send_control_packet(); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /KerbalController/define_vesseldatadisplay.ino: -------------------------------------------------------------------------------- 1 | //grab info from KSP here (VData object) and write out results to the Arduino pins 2 | 3 | //connect to the KSPSerialIO plugin and receive data 4 | int get_vessel_data() { 5 | int returnValue = -1; 6 | now = millis(); 7 | 8 | if (KSPBoardReceiveData()){ 9 | //data is being received 10 | deadtimeOld = now; 11 | returnValue = id; 12 | switch(id) { 13 | case 0: //First packet is a handshake packet 14 | Handshake(); 15 | clearLCD(); 16 | writeLCD("KerbalController"); 17 | writeLCD("handshake..."); 18 | break; 19 | case 1: 20 | //subsequent packets are data from the plugin 21 | define_vessel_data_display(); //define what to do with the data below 22 | break; 23 | } 24 | Connected = true; 25 | } 26 | else 27 | { //if no message received for a while, go idle 28 | deadtime = now - deadtimeOld; 29 | if (deadtime > IDLETIMER) 30 | { 31 | deadtimeOld = now; 32 | Connected = false; 33 | clearLCD(); 34 | writeLCD("KerbalController"); 35 | writeLCD("idle..."); 36 | } 37 | } 38 | return returnValue; 39 | } 40 | 41 | //define the structure of a VesseData packet 42 | struct VesselData 43 | { 44 | //the following data is available in the packet received from the plugin (defined by the KSPSerialIO plugin) 45 | byte id; //1 packet id 46 | float AP; //2 apoapsis (m to sea level) 47 | float PE; //3 periapsis (m to sea level) 48 | float SemiMajorAxis; //4 orbit semi major axis (m) 49 | float SemiMinorAxis; //5 orbit semi minor axis (m) 50 | float VVI; //6 vertical velocity (m/s) 51 | float e; //7 orbital eccentricity (unitless, 0 = circular, > 1 = escape) 52 | float inc; //8 orbital inclination (degrees) 53 | float G; //9 acceleration (Gees) 54 | long TAp; //10 time to AP (seconds) 55 | long TPe; //11 time to Pe (seconds) 56 | float TrueAnomaly; //12 orbital true anomaly (degrees) 57 | float Density; //13 air density (presumably kg/m^3, 1.225 at sea level) 58 | long period; //14 orbital period (seconds) 59 | float RAlt; //15 radar altitude (m) 60 | float Alt; //16 altitude above sea level (m) 61 | float Vsurf; //17 surface velocity (m/s) 62 | float Lat; //18 Latitude (degree) 63 | float Lon; //19 Longitude (degree) 64 | float LiquidFuelTot; //20 Liquid Fuel Total 65 | float LiquidFuel; //21 Liquid Fuel remaining 66 | float OxidizerTot; //22 Oxidizer Total 67 | float Oxidizer; //23 Oxidizer remaining 68 | float EChargeTot; //24 Electric Charge Total 69 | float ECharge; //25 Electric Charge remaining 70 | float MonoPropTot; //26 Mono Propellant Total 71 | float MonoProp; //27 Mono Propellant remaining 72 | float IntakeAirTot; //28 Intake Air Total 73 | float IntakeAir; //29 Intake Air remaining 74 | float SolidFuelTot; //30 Solid Fuel Total 75 | float SolidFuel; //31 Solid Fuel remaining 76 | float XenonGasTot; //32 Xenon Gas Total 77 | float XenonGas; //33 Xenon Gas remaining 78 | float LiquidFuelTotS; //34 Liquid Fuel Total (stage) 79 | float LiquidFuelS; //35 Liquid Fuel remaining (stage) 80 | float OxidizerTotS; //36 Oxidizer Total (stage) 81 | float OxidizerS; //37 Oxidizer remaining (stage) 82 | uint32_t MissionTime; //38 Time since launch (s) 83 | float deltaTime; //39 Time since last packet (s) 84 | float VOrbit; //40 Orbital speed (m/s) 85 | uint32_t MNTime; //41 Time to next node (s) [0 when no node] 86 | float MNDeltaV; //42 Delta V for next node (m/s) [0 when no node] 87 | float Pitch; //43 Vessel Pitch relative to surface (degrees) 88 | float Roll; //44 Vessel Roll relative to surface (degrees) 89 | float Heading; //45 Vessel Heading relative to surface (degrees) 90 | uint16_t ActionGroups; //46 status bit order:SAS, RCS, Light, Gears, Brakes, Abort, Custom01 - 10 91 | byte SOINumber; //47 SOI Number (decimal format: sun-planet-moon e.g. 130 = kerbin, 131 = mun) 92 | byte MaxOverHeat; //48 Max part overheat (% percent) 93 | float MachNumber; //49 Mach number 94 | float IAS; //50 Indicated Air Speed 95 | byte CurrentStage; //51 Current stage number 96 | byte TotalStage; //52 TotalNumber of stages 97 | float TargetDist; //53 Distance to targeted vessel (m) 98 | float TargetV; //54 Target vessel relative velocity 99 | byte NavballSASMode; //55 Combined byte for navball target mode and SAS mode 100 | // First four bits indicate AutoPilot mode: 101 | // 0 SAS is off //1 = Regular Stability Assist //2 = Prograde 102 | // 3 = RetroGrade //4 = Normal //5 = Antinormal //6 = Radial In 103 | // 7 = Radial Out //8 = Target //9 = Anti-Target //10 = Maneuver node 104 | // Last 4 bits set navball mode. (0=ignore,1=ORBIT,2=SURFACE,3=TARGET) 105 | }; 106 | 107 | //create an instance of a VesselData object 108 | VesselData VData; 109 | 110 | //Enumeration of ActionGroups (includes main controls and custom action groups) 111 | #define AGSAS 0 112 | #define AGRCS 1 113 | #define AGLight 2 114 | #define AGGears 3 115 | #define AGBrakes 4 116 | #define AGAbort 5 117 | #define AGCustom01 6 118 | #define AGCustom02 7 119 | #define AGCustom03 8 120 | #define AGCustom04 9 121 | #define AGCustom05 10 122 | #define AGCustom06 11 123 | #define AGCustom07 12 124 | #define AGCustom08 13 125 | #define AGCustom09 14 126 | #define AGCustom10 15 127 | 128 | //get the current state of main controls and custom action groups using enumeration above, e.g. ControlStatus(AGBrakes); 129 | byte ControlStatus(byte n) 130 | { 131 | return ((VData.ActionGroups >> n) & 1) == 1; 132 | } 133 | 134 | //get the current SAS Mode. Can be compared to enum values, e.g. if(getSASMode() == SMPrograde){//do stuff} 135 | byte getSASMode() { 136 | return VData.NavballSASMode & B00001111; // leaves alone the lower 4 bits of; all higher bits set to 0. 137 | } 138 | 139 | //get the current navball mode. Can be compared to enum values, e.g. if (getNavballMode() == NAVBallTARGET){//do stuff} 140 | byte getNavballMode() { 141 | return VData.NavballSASMode >> 4; // leaves alone the higher 4 bits of; all lower bits set to 0. 142 | } 143 | 144 | //define what to do with the vessel data here, e.g. turn on LED's, display text on the LCD 145 | void define_vessel_data_display() { 146 | 147 | //Fuel LED bar charts - NEED TO USE A SHIFT REGISTER to drive the LED bar charts! 148 | // to be implemented 149 | 150 | //LCD Display Modes 151 | // 0 xyz TakeOff Mode: Suface Velocity / Acceleration (G) 152 | // 1 Xyz Orbit Mode: Apoapsis + Time to Apoapsis / Periapsis + Time to Periapsis 153 | // 2 xYz Maneuver Mode: Time to next maneuver node / Remaining Delta-V for next maneuver node 154 | // 3 XYz Rendezvouz Mode: Distance to target / Velocity relative to target 155 | // 4 xyZ Re-Entry Mode: Percentage overheating (max) / Deceleration (G) 156 | // 5 XyZ Flying Mode: Altitude / Mach number 157 | // 6 xYZ Landing Mode: Radar Altitude / Vertical Velocity 158 | // 7 XYZ Extra Mode: not implemented (yet) 159 | 160 | if(digitalRead(pLCDx) && digitalRead(pLCDy) && digitalRead(pLCDz)){ 161 | //MODE 0 : TakeOff Mode 162 | //Vsurf 163 | clearLCD(); 164 | char bufferVsurf[17]; 165 | String strVsurf = "Vsurf: "; 166 | strVsurf += String(VData.Vsurf, 0); 167 | strVsurf += " m/s"; 168 | strVsurf.toCharArray(bufferVsurf,17); 169 | writeLCD(bufferVsurf); 170 | //Acceleration (G) 171 | jumpToLineTwo(); 172 | char bufferGee[17]; 173 | String strGee = "Accel: "; 174 | strGee += String(VData.G, 0); 175 | strGee += " G"; 176 | strGee.toCharArray(bufferGee,17); 177 | writeLCD(bufferGee); 178 | } 179 | 180 | if(!digitalRead(pLCDx) && digitalRead(pLCDy) && digitalRead(pLCDz)){ 181 | //MODE 1: Orbit Mode 182 | clearLCD(); 183 | 184 | //Apoapsis 185 | char bufferAP[17]; 186 | String strApo = "AP: "; 187 | if (VData.AP < 10000 && VData.AP > -10000) { 188 | strApo += String(VData.AP,0); 189 | strApo += "m "; 190 | } else if ((VData.AP >= 10000 && VData.AP < 10000000) || (VData.AP <= -10000 && VData.AP > -10000000)) { 191 | strApo += String((VData.AP / 1000),0); 192 | strApo += "km "; 193 | } else if ((VData.AP >= 10000000 && VData.AP < 10000000000) || (VData.AP <= -10000000 && VData.AP > -10000000000)) { 194 | strApo += String((VData.AP / 1000000),0); 195 | strApo += "Mm "; 196 | } else { 197 | strApo += String((VData.AP / 1000000000),0); 198 | strApo += "Gm "; 199 | } 200 | strApo += String(VData.TAp); //time to apoapsis 201 | strApo += "s"; 202 | strApo.toCharArray(bufferAP,17); 203 | writeLCD(bufferAP); 204 | 205 | //Periapsis 206 | char bufferPE[17]; 207 | String strPeri = "PE: "; 208 | if (VData.PE < 10000 && VData.PE > -10000) { 209 | strPeri += String(VData.PE,0); 210 | strPeri += "m "; 211 | } else if ((VData.PE >= 10000 && VData.PE < 10000000) || (VData.PE <= -10000 && VData.PE > -10000000)) { 212 | strPeri += String((VData.PE / 1000.0),0); 213 | strPeri += "km "; 214 | } else if ((VData.PE >= 10000000 && VData.PE < 10000000000) || (VData.PE <= -10000000 && VData.PE > -10000000000)) { 215 | strPeri += String((VData.PE / 1000000.0),0); 216 | strPeri += "Mm "; 217 | } else { 218 | strPeri += String((VData.PE / 1000000000.0),0); 219 | strPeri += "Gm "; 220 | } 221 | strPeri += String(VData.TPe); //time to periapsis 222 | strPeri += "s"; 223 | strPeri.toCharArray(bufferPE,17); 224 | jumpToLineTwo(); 225 | writeLCD(bufferPE); 226 | } 227 | 228 | if(digitalRead(pLCDx) && !digitalRead(pLCDy) && digitalRead(pLCDz)){ 229 | //MODE 2: Maneuver Mode 230 | //MNTime 231 | clearLCD(); 232 | char t[10]; 233 | dtostrf(VData.MNTime,8,0,t); 234 | writeLCD("Tnode: "); 235 | writeLCD(t); 236 | writeLCD("s"); 237 | //MNDeltaV 238 | jumpToLineTwo(); 239 | char bufferMNDeltaV[17]; 240 | String strMNDeltaV = "dV: "; 241 | strMNDeltaV += String(VData.MNDeltaV, 0); 242 | strMNDeltaV += " m/s"; 243 | strMNDeltaV.toCharArray(bufferMNDeltaV,17); 244 | writeLCD(bufferMNDeltaV); 245 | } 246 | 247 | if(!digitalRead(pLCDx) && !digitalRead(pLCDy) && digitalRead(pLCDz)){ 248 | //MODE 3: Rendezvouz Mode 249 | //Target Distance 250 | clearLCD(); 251 | char bufferTargetDist[17]; 252 | String strTargetDist = "TDist: "; 253 | strTargetDist += String(VData.TargetDist, 0); 254 | strTargetDist += " m"; 255 | strTargetDist.toCharArray(bufferTargetDist,17); 256 | writeLCD(bufferTargetDist); 257 | //Target Velocity 258 | jumpToLineTwo(); 259 | char bufferTargetV[17]; 260 | String strTargetV = "TVel: "; 261 | strTargetV += String(VData.TargetV, 0); 262 | strTargetV += " m/s"; 263 | strTargetV.toCharArray(bufferTargetV,17); 264 | writeLCD(bufferTargetV); 265 | } 266 | 267 | if(digitalRead(pLCDx) && digitalRead(pLCDy) && !digitalRead(pLCDz)){ 268 | //MODE 4: Re-Entry Mode 269 | //MaxOverHeat 270 | clearLCD(); 271 | char t[3]; 272 | dtostrf(VData.MaxOverHeat,3,0,t); 273 | writeLCD("Heat: "); 274 | writeLCD(t); 275 | writeLCD("%"); 276 | //Acceleration (G) 277 | jumpToLineTwo(); 278 | char bufferGee[17]; 279 | String strGee = "Decel: "; 280 | strGee += String(VData.G, 0); 281 | strGee += " G"; 282 | strGee.toCharArray(bufferGee,17); 283 | writeLCD(bufferGee); 284 | } 285 | 286 | if(!digitalRead(pLCDx) && digitalRead(pLCDy) && !digitalRead(pLCDz)){ 287 | //MODE 5: Flying Mode 288 | //Alt 289 | clearLCD(); 290 | char bufferAtl[17]; 291 | String strAlt = "Alt: "; 292 | strAlt += String(VData.Alt, 0); 293 | strAlt += " m/s"; 294 | strAlt.toCharArray(bufferAtl,17); 295 | writeLCD(bufferAtl); 296 | //Mach Number 297 | jumpToLineTwo(); 298 | char bufferMachNumber[17]; 299 | String strMach = "Mach:"; 300 | strMach += String(VData.G, 0); 301 | strMach.toCharArray(bufferMachNumber,17); 302 | writeLCD(bufferMachNumber); 303 | } 304 | 305 | if(digitalRead(pLCDx) && !digitalRead(pLCDy) && !digitalRead(pLCDz)){ 306 | //MODE 6: Landing Mode 307 | //RAlt 308 | clearLCD(); 309 | char bufferRAtl[17]; 310 | String strRAlt = "RAlt: "; 311 | strRAlt += String(VData.RAlt, 0); 312 | strRAlt += " m/s"; 313 | strRAlt.toCharArray(bufferRAtl,17); 314 | writeLCD(bufferRAtl); 315 | //Vertical Velocity 316 | jumpToLineTwo(); 317 | char bufferVVI[17]; 318 | String strVVI = "VVI: "; 319 | strVVI += String(VData.VVI, 0); 320 | strVVI += " m/s"; 321 | strVVI.toCharArray(bufferVVI,17); 322 | writeLCD(bufferVVI); 323 | } 324 | 325 | if(!digitalRead(pLCDx) && !digitalRead(pLCDy) && !digitalRead(pLCDz)){ 326 | //MODE 7: Extra Mode 327 | clearLCD(); 328 | writeLCD("KerbalController"); 329 | } 330 | 331 | //get in-game status for updating the LED statuses on the controller 332 | lights_on = ControlStatus(AGLight); 333 | gears_on = ControlStatus(AGGears); 334 | brakes_on = ControlStatus(AGBrakes); 335 | action1_on = ControlStatus(AGCustom01); 336 | action2_on = ControlStatus(AGCustom02); 337 | action3_on = ControlStatus(AGCustom03); 338 | action4_on = ControlStatus(AGCustom04); 339 | ladder_on = ControlStatus(AGCustom05); 340 | solar_on = ControlStatus(AGCustom06); 341 | chutes_on = ControlStatus(AGCustom07); 342 | 343 | //update button LEDs based on in-game status 344 | digitalWrite(pLIGHTSLED, lights_on); 345 | digitalWrite(pGEARSLED, gears_on); 346 | digitalWrite(pBRAKESLED, brakes_on); 347 | digitalWrite(pACTION1LED, action1_on); 348 | digitalWrite(pACTION2LED, action2_on); 349 | digitalWrite(pACTION3LED, action3_on); 350 | digitalWrite(pACTION4LED, action4_on); 351 | digitalWrite(pLADDERLED, ladder_on); 352 | digitalWrite(pSOLARLED, solar_on); 353 | digitalWrite(pCHUTESLED, chutes_on); 354 | 355 | //Fuel Gauges 356 | vSF = 100 * VData.SolidFuel / VData.SolidFuelTot; //percentage of solid fuel remaining 357 | vLF = 100 * VData.LiquidFuelS / VData.LiquidFuelTotS; //percentage of liquid fuel remaining in current stage 358 | vOX = 100 * VData.OxidizerS / VData.OxidizerTotS; //percentage of oxidized remaining in current stage 359 | vEL = 100 * VData.ECharge / VData.EChargeTot; //percentage of electric charge remaining 360 | vMP = 100 * VData.MonoProp / VData.MonoPropTot; //percentage of monopropellant remaining 361 | 362 | //scale down to 0-9 for binary calculations 363 | SF = constrain(map(vSF, 100, 0, 0, 9), 0, 9); 364 | LF = constrain(map(vLF, 100, 0, 0, 9), 0, 9); 365 | OX = constrain(map(vOX, 100, 0, 0, 9), 0, 9); 366 | EL = constrain(map(vEL, 0, 100, 0, 9), 0, 9); //EL bar soldered wrong way around 367 | MP = constrain(map(vMP, 100, 0, 0, 9), 0, 9); 368 | 369 | //calculate the power of 2. Now each value in binary is all zeroes an a single 1. we can use that to light one LED in each LED bar (dot mode) 370 | int powOX = 0.1+pow(2,OX); 371 | int powEL = 0.1+pow(2,EL); 372 | int powMP = 0.1+pow(2,MP); 373 | int powSF = 0.1+pow(2,SF); 374 | int powLF = 0.1+pow(2,LF); 375 | 376 | //map the 8-bit 595 shift registers to the 10-bit LED bars, specific to the way I wired them 377 | inputBytes[0] = powSF >> 6; 378 | inputBytes[1] = (powSF << 2) | (powLF >> 8); 379 | inputBytes[2] = powLF; 380 | inputBytes[3] = powEL >> 3; 381 | bitWrite(inputBytes[3], 0, bitRead(powEL, 4)); //fix the skipped 595 pin 382 | inputBytes[4] = (powEL << 4) | (powMP >> 6); 383 | inputBytes[5] = (powMP << 2) | (powOX >> 8); 384 | inputBytes[6] = powOX; 385 | 386 | 387 | //prepare the shift register 388 | digitalWrite(dataPin, LOW); 389 | digitalWrite(clockPin, LOW); 390 | digitalWrite(latchPin, LOW); 391 | 392 | //loop through the input bytes 393 | for (int j=0; j<=6; j++){ 394 | byte inputByte = inputBytes[j]; 395 | Serial.println(inputByte); 396 | shiftOut(dataPin, clockPin, MSBFIRST, inputByte); 397 | } 398 | 399 | //latch the values in when done shifting 400 | digitalWrite(latchPin, HIGH); 401 | 402 | } 403 | --------------------------------------------------------------------------------