├── .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 |
--------------------------------------------------------------------------------