Bachelor Assignment for delayed teleoperating systems
Dependencies: EthernetInterface FastPWM mbed-rtos mbed MODSERIAL
main.cpp@11:cb9bb3f0635d, 2018-06-06 (annotated)
- Committer:
- darth_bachious
- Date:
- Wed Jun 06 07:06:14 2018 +0000
- Revision:
- 11:cb9bb3f0635d
- Parent:
- 10:694de5b31fd6
- Child:
- 12:5c08ffe8ad1d
Serial output for debugging;
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
darth_bachious | 0:2f89dec3e2ab | 1 | #include "mbed.h" |
darth_bachious | 0:2f89dec3e2ab | 2 | #include "EthernetInterface.h" |
darth_bachious | 0:2f89dec3e2ab | 3 | #include "rtos.h" |
darth_bachious | 2:c27b0654cffd | 4 | #include <vector> |
darth_bachious | 5:4d5b077b3fe6 | 5 | #include "FastPWM.h" |
darth_bachious | 6:ccbbf4c77d35 | 6 | #include <cmath> |
darth_bachious | 9:16044ec419af | 7 | #include "MODSERIAL.h" |
darth_bachious | 0:2f89dec3e2ab | 8 | |
darth_bachious | 1:853939e38acd | 9 | //Master or Slave? 1=Master, 0=Slave |
darth_bachious | 7:984f363f5e87 | 10 | static const int identity = 0; |
darth_bachious | 0:2f89dec3e2ab | 11 | |
darth_bachious | 0:2f89dec3e2ab | 12 | //network config |
darth_bachious | 1:853939e38acd | 13 | static const char* master_ip = "192.168.1.101"; |
darth_bachious | 1:853939e38acd | 14 | static const char* slave_ip = "192.168.1.102"; |
darth_bachious | 1:853939e38acd | 15 | static const char* MASK = "255.255.255.0"; |
darth_bachious | 1:853939e38acd | 16 | static const char* GATEWAY = "192.168.1.1"; |
darth_bachious | 9:16044ec419af | 17 | static const char* laptop_IP = "192.168.1.103"; |
darth_bachious | 0:2f89dec3e2ab | 18 | static const int port = 865; |
darth_bachious | 0:2f89dec3e2ab | 19 | |
darth_bachious | 9:16044ec419af | 20 | |
darth_bachious | 0:2f89dec3e2ab | 21 | //declaration of interfaces |
darth_bachious | 0:2f89dec3e2ab | 22 | DigitalOut led(LED_GREEN); |
darth_bachious | 0:2f89dec3e2ab | 23 | DigitalOut led2(LED_RED); |
darth_bachious | 10:694de5b31fd6 | 24 | DigitalOut led3(LED_BLUE); |
darth_bachious | 0:2f89dec3e2ab | 25 | EthernetInterface eth; //network |
darth_bachious | 0:2f89dec3e2ab | 26 | Serial pc(USBTX, USBRX);//create PC interface |
darth_bachious | 0:2f89dec3e2ab | 27 | UDPSocket socket; //socket to receive data on |
darth_bachious | 0:2f89dec3e2ab | 28 | Endpoint client; //The virtual other side, not to send actual information to |
darth_bachious | 0:2f89dec3e2ab | 29 | Endpoint counterpart; //The actual other side, this is where the information should go to |
darth_bachious | 9:16044ec419af | 30 | Endpoint laptop; |
darth_bachious | 6:ccbbf4c77d35 | 31 | InterruptIn Button1(SW2); |
darth_bachious | 6:ccbbf4c77d35 | 32 | InterruptIn Button2(SW3); |
darth_bachious | 5:4d5b077b3fe6 | 33 | Ticker controllerloop; |
darth_bachious | 0:2f89dec3e2ab | 34 | Ticker mainloop; |
darth_bachious | 6:ccbbf4c77d35 | 35 | DigitalOut debug(D11); |
darth_bachious | 0:2f89dec3e2ab | 36 | |
darth_bachious | 4:610b5051182a | 37 | //Motor interfaces and variables |
darth_bachious | 3:376fccdc7cd6 | 38 | InterruptIn EncoderA(D2); |
darth_bachious | 6:ccbbf4c77d35 | 39 | DigitalIn EncoderB(D3); |
darth_bachious | 3:376fccdc7cd6 | 40 | DigitalOut M1_DIR(D4); |
darth_bachious | 5:4d5b077b3fe6 | 41 | FastPWM M1_pwm(D5); |
darth_bachious | 4:610b5051182a | 42 | AnalogIn measuredForce(A5); |
darth_bachious | 4:610b5051182a | 43 | |
darth_bachious | 4:610b5051182a | 44 | //Low pass filter filter coeffs, 2nd order, 50 Hz |
darth_bachious | 4:610b5051182a | 45 | double b[3] = {0.020083365564211, 0.020083365564211*2, 0.020083365564211}; |
darth_bachious | 4:610b5051182a | 46 | double a[3] = {1.00000000000000, -1.561018075, 0.64135153805}; |
darth_bachious | 4:610b5051182a | 47 | |
darth_bachious | 4:610b5051182a | 48 | //variables related to networking |
darth_bachious | 7:984f363f5e87 | 49 | char data[25]= {""}; |
darth_bachious | 9:16044ec419af | 50 | char output[25] = {""}; |
darth_bachious | 11:cb9bb3f0635d | 51 | char status[21] = {""}; |
darth_bachious | 0:2f89dec3e2ab | 52 | int size; |
darth_bachious | 7:984f363f5e87 | 53 | unsigned int counter = 1; |
darth_bachious | 7:984f363f5e87 | 54 | unsigned int counter_received = 1; |
darth_bachious | 7:984f363f5e87 | 55 | unsigned int check = 1; |
darth_bachious | 11:cb9bb3f0635d | 56 | unsigned int counter_not_missed = 0; |
darth_bachious | 11:cb9bb3f0635d | 57 | float percentage_received = 0.0; |
darth_bachious | 9:16044ec419af | 58 | |
darth_bachious | 2:c27b0654cffd | 59 | float input = 0.0; |
darth_bachious | 4:610b5051182a | 60 | |
darth_bachious | 4:610b5051182a | 61 | //measured variables |
darth_bachious | 2:c27b0654cffd | 62 | float angle = 0.0; |
darth_bachious | 5:4d5b077b3fe6 | 63 | int encoderPos = 0; |
darth_bachious | 5:4d5b077b3fe6 | 64 | int prev_encoderPos = 0; |
darth_bachious | 4:610b5051182a | 65 | float encoder_vel = 0.0; |
darth_bachious | 4:610b5051182a | 66 | float motor_vel = 0.0; |
darth_bachious | 4:610b5051182a | 67 | float force = 0.0; |
darth_bachious | 5:4d5b077b3fe6 | 68 | float control_torque = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 69 | float force_offset = 0.5; |
darth_bachious | 3:376fccdc7cd6 | 70 | |
darth_bachious | 4:610b5051182a | 71 | //Reference, used in admittance |
darth_bachious | 4:610b5051182a | 72 | float ref_angle = 0.0; |
darth_bachious | 4:610b5051182a | 73 | float ref_vel = 0.0; |
darth_bachious | 4:610b5051182a | 74 | float ref_acc = 0.0; |
darth_bachious | 9:16044ec419af | 75 | const float virtualInertia = 0.03; |
darth_bachious | 9:16044ec419af | 76 | const float virtualDamping = 0.1; |
darth_bachious | 10:694de5b31fd6 | 77 | const float arm = 0.085; |
darth_bachious | 9:16044ec419af | 78 | const float max_velocity = 0.8; |
darth_bachious | 4:610b5051182a | 79 | |
darth_bachious | 6:ccbbf4c77d35 | 80 | //Controller required variables |
darth_bachious | 5:4d5b077b3fe6 | 81 | bool controller_check = 0; |
darth_bachious | 11:cb9bb3f0635d | 82 | const float sample_frequency = 200; |
darth_bachious | 11:cb9bb3f0635d | 83 | float looptime = 1.0/(sample_frequency); //50Hz, for the controller |
darth_bachious | 11:cb9bb3f0635d | 84 | const float ADDMlooptime = 1.0/2500; //2.5KHz, for the admittance controller |
darth_bachious | 6:ccbbf4c77d35 | 85 | enum States {stateCalibration, stateHoming, stateOperation}; |
darth_bachious | 4:610b5051182a | 86 | int currentState = stateCalibration; |
darth_bachious | 6:ccbbf4c77d35 | 87 | int transparancy = 1; // 1=Pos-Pos, 2=Pos-Force, 3=Force-Pos |
darth_bachious | 6:ccbbf4c77d35 | 88 | int passivity =0; // 0=off, 1=on |
darth_bachious | 6:ccbbf4c77d35 | 89 | int received_transparancy = 1; |
darth_bachious | 6:ccbbf4c77d35 | 90 | int received_passivity = 0; |
darth_bachious | 4:610b5051182a | 91 | |
darth_bachious | 4:610b5051182a | 92 | //Controller parameters |
darth_bachious | 9:16044ec419af | 93 | const float Ktl = 4.5; //high level controller parameters |
darth_bachious | 3:376fccdc7cd6 | 94 | const float Dtl=0.1; |
darth_bachious | 9:16044ec419af | 95 | const float Kp = 100; //low level controller parameters |
darth_bachious | 10:694de5b31fd6 | 96 | const float Dp = 0.5; |
darth_bachious | 6:ccbbf4c77d35 | 97 | float u = 0.0; //serves as input variable for the motor; |
darth_bachious | 4:610b5051182a | 98 | |
darth_bachious | 6:ccbbf4c77d35 | 99 | //passivity layer constant |
darth_bachious | 11:cb9bb3f0635d | 100 | const float beta = 0.2041; |
darth_bachious | 11:cb9bb3f0635d | 101 | const float Hd = 0.2778; |
darth_bachious | 11:cb9bb3f0635d | 102 | const float alpha = 0.5711; |
darth_bachious | 6:ccbbf4c77d35 | 103 | |
darth_bachious | 6:ccbbf4c77d35 | 104 | //passivity layer variables |
darth_bachious | 6:ccbbf4c77d35 | 105 | float tank = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 106 | float prev_ref_angle = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 107 | float package_out = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 108 | float received_package = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 109 | float prev_torque = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 110 | |
darth_bachious | 4:610b5051182a | 111 | |
darth_bachious | 4:610b5051182a | 112 | //Constants |
darth_bachious | 0:2f89dec3e2ab | 113 | const float PI = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679; |
darth_bachious | 2:c27b0654cffd | 114 | const float RadsPerCount = (2 * PI)/(1024*10); |
darth_bachious | 4:610b5051182a | 115 | const float FORCESENSORGAIN = 15.134; |
darth_bachious | 4:610b5051182a | 116 | const int MAX_ENCODER_LEFT = 1333; |
darth_bachious | 4:610b5051182a | 117 | const int MAX_ENCODER_RIGHT = -1453; |
darth_bachious | 5:4d5b077b3fe6 | 118 | const float WORKSPACEBOUND = PI/6; |
darth_bachious | 2:c27b0654cffd | 119 | |
darth_bachious | 6:ccbbf4c77d35 | 120 | const int frequency_pwm = 20000; |
darth_bachious | 0:2f89dec3e2ab | 121 | |
darth_bachious | 4:610b5051182a | 122 | //Time delay related variables |
darth_bachious | 11:cb9bb3f0635d | 123 | float timedelay = 0.2; //SECONDS |
darth_bachious | 6:ccbbf4c77d35 | 124 | int delaysteps = timedelay/looptime; |
darth_bachious | 8:5c891d5ebe45 | 125 | std::vector<float> delayArrayINPUT(max(delaysteps,1),0.0); |
darth_bachious | 8:5c891d5ebe45 | 126 | std::vector<float> delayArrayMODE(max(delaysteps,1),0.0); |
darth_bachious | 8:5c891d5ebe45 | 127 | std::vector<float> delayArrayPASS(max(delaysteps,1),0.0); |
darth_bachious | 8:5c891d5ebe45 | 128 | std::vector<float> delayArrayENERGY(max(delaysteps,1),0.0); |
darth_bachious | 4:610b5051182a | 129 | Timer t; |
darth_bachious | 4:610b5051182a | 130 | |
darth_bachious | 3:376fccdc7cd6 | 131 | //FUNCTIONS START HERE |
darth_bachious | 3:376fccdc7cd6 | 132 | |
darth_bachious | 3:376fccdc7cd6 | 133 | void encoderFunctionA() |
darth_bachious | 3:376fccdc7cd6 | 134 | { |
darth_bachious | 3:376fccdc7cd6 | 135 | if ((bool)EncoderA == (bool)EncoderB) { //Increment or decrement encoder position during interrupt |
darth_bachious | 3:376fccdc7cd6 | 136 | encoderPos++; |
darth_bachious | 3:376fccdc7cd6 | 137 | } else { |
darth_bachious | 3:376fccdc7cd6 | 138 | encoderPos--; |
darth_bachious | 3:376fccdc7cd6 | 139 | } |
darth_bachious | 3:376fccdc7cd6 | 140 | } |
darth_bachious | 4:610b5051182a | 141 | double velocityFilter(float x) |
darth_bachious | 4:610b5051182a | 142 | { |
darth_bachious | 4:610b5051182a | 143 | double y = 0.0; //Output |
darth_bachious | 4:610b5051182a | 144 | static double y_1 = 0.0; //Output last loop |
darth_bachious | 4:610b5051182a | 145 | static double y_2 = 0.0; //Output 2 loops ago |
darth_bachious | 4:610b5051182a | 146 | static double x_1 = 0.0; //Input last loop |
darth_bachious | 4:610b5051182a | 147 | static double x_2 = 0.0; //Input two loops ago |
darth_bachious | 3:376fccdc7cd6 | 148 | |
darth_bachious | 4:610b5051182a | 149 | //Finite difference equation for 2nd order Butterworth low-pass |
darth_bachious | 4:610b5051182a | 150 | y = -a[1]*y_1 - a[2]*y_2 + x*b[0] + x_1*b[1] + x_2*b[2]; |
darth_bachious | 4:610b5051182a | 151 | y_2 = y_1; |
darth_bachious | 4:610b5051182a | 152 | y_1 = y; |
darth_bachious | 4:610b5051182a | 153 | x_2 = x_1; |
darth_bachious | 4:610b5051182a | 154 | x_1 = x; |
darth_bachious | 4:610b5051182a | 155 | return (float)y; |
darth_bachious | 4:610b5051182a | 156 | } |
darth_bachious | 0:2f89dec3e2ab | 157 | |
darth_bachious | 0:2f89dec3e2ab | 158 | void inet_eth(){ |
darth_bachious | 1:853939e38acd | 159 | if(identity==1) |
darth_bachious | 1:853939e38acd | 160 | { |
darth_bachious | 1:853939e38acd | 161 | eth.init(master_ip, MASK,GATEWAY); |
darth_bachious | 1:853939e38acd | 162 | eth.connect(); |
darth_bachious | 0:2f89dec3e2ab | 163 | |
darth_bachious | 1:853939e38acd | 164 | socket.bind(port); |
darth_bachious | 1:853939e38acd | 165 | counterpart.set_address(slave_ip,port); |
darth_bachious | 9:16044ec419af | 166 | laptop.set_address(laptop_IP,port); |
darth_bachious | 1:853939e38acd | 167 | } |
darth_bachious | 1:853939e38acd | 168 | else if(identity==0) |
darth_bachious | 1:853939e38acd | 169 | { |
darth_bachious | 1:853939e38acd | 170 | eth.init(slave_ip, MASK,GATEWAY); |
darth_bachious | 1:853939e38acd | 171 | eth.connect(); |
darth_bachious | 1:853939e38acd | 172 | |
darth_bachious | 1:853939e38acd | 173 | socket.bind(port); |
darth_bachious | 9:16044ec419af | 174 | counterpart.set_address(master_ip,port); |
darth_bachious | 9:16044ec419af | 175 | laptop.set_address(laptop_IP,port+1); |
darth_bachious | 1:853939e38acd | 176 | } |
darth_bachious | 1:853939e38acd | 177 | |
darth_bachious | 0:2f89dec3e2ab | 178 | } |
darth_bachious | 0:2f89dec3e2ab | 179 | |
darth_bachious | 0:2f89dec3e2ab | 180 | void inet_USB(){ |
darth_bachious | 2:c27b0654cffd | 181 | pc.baud(115200); |
darth_bachious | 0:2f89dec3e2ab | 182 | } |
darth_bachious | 0:2f89dec3e2ab | 183 | |
darth_bachious | 0:2f89dec3e2ab | 184 | |
darth_bachious | 0:2f89dec3e2ab | 185 | void end_eth(){ |
darth_bachious | 0:2f89dec3e2ab | 186 | socket.close(); |
darth_bachious | 0:2f89dec3e2ab | 187 | eth.disconnect(); |
darth_bachious | 0:2f89dec3e2ab | 188 | } |
darth_bachious | 0:2f89dec3e2ab | 189 | |
darth_bachious | 5:4d5b077b3fe6 | 190 | void controllertrigger() |
darth_bachious | 0:2f89dec3e2ab | 191 | { |
darth_bachious | 5:4d5b077b3fe6 | 192 | controller_check = 1; |
darth_bachious | 0:2f89dec3e2ab | 193 | } |
darth_bachious | 2:c27b0654cffd | 194 | |
darth_bachious | 4:610b5051182a | 195 | |
darth_bachious | 2:c27b0654cffd | 196 | float update_delay(std::vector<float>&array, float new_value) |
darth_bachious | 2:c27b0654cffd | 197 | { |
darth_bachious | 8:5c891d5ebe45 | 198 | float return_value = array[0]; |
darth_bachious | 3:376fccdc7cd6 | 199 | for (int i=0; i<array.size()-1; ++i) |
darth_bachious | 3:376fccdc7cd6 | 200 | { |
darth_bachious | 3:376fccdc7cd6 | 201 | array[i]=array[i+1]; |
darth_bachious | 3:376fccdc7cd6 | 202 | } |
darth_bachious | 3:376fccdc7cd6 | 203 | array.back() = new_value; |
darth_bachious | 3:376fccdc7cd6 | 204 | return return_value; |
darth_bachious | 3:376fccdc7cd6 | 205 | } |
darth_bachious | 3:376fccdc7cd6 | 206 | |
darth_bachious | 3:376fccdc7cd6 | 207 | void limit(float &x, float lower, float upper) |
darth_bachious | 3:376fccdc7cd6 | 208 | { |
darth_bachious | 3:376fccdc7cd6 | 209 | if (x > upper) |
darth_bachious | 3:376fccdc7cd6 | 210 | x = upper; |
darth_bachious | 3:376fccdc7cd6 | 211 | if (x < lower) |
darth_bachious | 3:376fccdc7cd6 | 212 | x = lower; |
darth_bachious | 3:376fccdc7cd6 | 213 | } |
darth_bachious | 3:376fccdc7cd6 | 214 | |
darth_bachious | 4:610b5051182a | 215 | void motor_update(float PWM) //angle required to safeguard it from crashing into its stops |
darth_bachious | 4:610b5051182a | 216 | { |
darth_bachious | 4:610b5051182a | 217 | limit(PWM,-1.0f,1.0f); |
darth_bachious | 4:610b5051182a | 218 | if(PWM >= 0.0f) |
darth_bachious | 4:610b5051182a | 219 | { |
darth_bachious | 4:610b5051182a | 220 | M1_DIR = false; |
darth_bachious | 4:610b5051182a | 221 | M1_pwm = PWM; |
darth_bachious | 4:610b5051182a | 222 | } else { |
darth_bachious | 4:610b5051182a | 223 | M1_DIR = true; |
darth_bachious | 4:610b5051182a | 224 | M1_pwm = -PWM; |
darth_bachious | 4:610b5051182a | 225 | } |
darth_bachious | 4:610b5051182a | 226 | } |
darth_bachious | 4:610b5051182a | 227 | |
darth_bachious | 4:610b5051182a | 228 | void sensorUpdate() |
darth_bachious | 4:610b5051182a | 229 | { |
darth_bachious | 4:610b5051182a | 230 | angle = encoderPos * RadsPerCount; |
darth_bachious | 11:cb9bb3f0635d | 231 | encoder_vel = (encoderPos - prev_encoderPos)/ADDMlooptime; //careful, this function should be called every 1/2500 seconds |
darth_bachious | 4:610b5051182a | 232 | motor_vel = velocityFilter(encoder_vel * RadsPerCount); |
darth_bachious | 4:610b5051182a | 233 | prev_encoderPos = encoderPos; |
darth_bachious | 6:ccbbf4c77d35 | 234 | force = -FORCESENSORGAIN*2.0f*(measuredForce - force_offset); //Measured force |
darth_bachious | 4:610b5051182a | 235 | } |
darth_bachious | 4:610b5051182a | 236 | |
darth_bachious | 4:610b5051182a | 237 | void doCalibration() |
darth_bachious | 4:610b5051182a | 238 | { |
darth_bachious | 8:5c891d5ebe45 | 239 | u = -0.15; |
darth_bachious | 4:610b5051182a | 240 | motor_update(u); |
darth_bachious | 6:ccbbf4c77d35 | 241 | led2=0; |
darth_bachious | 6:ccbbf4c77d35 | 242 | led=1; |
darth_bachious | 4:610b5051182a | 243 | //switching states |
darth_bachious | 6:ccbbf4c77d35 | 244 | if((abs(motor_vel)<0.001f)&& t.read()>3.0f) |
darth_bachious | 3:376fccdc7cd6 | 245 | { |
darth_bachious | 8:5c891d5ebe45 | 246 | encoderPos = MAX_ENCODER_RIGHT; |
darth_bachious | 4:610b5051182a | 247 | ref_angle = encoderPos * RadsPerCount; |
darth_bachious | 4:610b5051182a | 248 | currentState = stateHoming; |
darth_bachious | 4:610b5051182a | 249 | t.stop(); |
darth_bachious | 4:610b5051182a | 250 | } |
darth_bachious | 4:610b5051182a | 251 | } |
darth_bachious | 4:610b5051182a | 252 | |
darth_bachious | 4:610b5051182a | 253 | void doHoming() |
darth_bachious | 4:610b5051182a | 254 | { |
darth_bachious | 4:610b5051182a | 255 | led2=0; |
darth_bachious | 4:610b5051182a | 256 | led=0; |
darth_bachious | 8:5c891d5ebe45 | 257 | ref_vel = 0.2; |
darth_bachious | 8:5c891d5ebe45 | 258 | if(ref_angle < 0.0f) |
darth_bachious | 4:610b5051182a | 259 | { |
darth_bachious | 5:4d5b077b3fe6 | 260 | ref_angle += ref_vel*ADDMlooptime; //careful, this function should be called every 1/50 seconds |
darth_bachious | 4:610b5051182a | 261 | } |
darth_bachious | 4:610b5051182a | 262 | else |
darth_bachious | 4:610b5051182a | 263 | { |
darth_bachious | 4:610b5051182a | 264 | ref_angle = 0.0; |
darth_bachious | 4:610b5051182a | 265 | ref_vel = 0.0; |
darth_bachious | 4:610b5051182a | 266 | } |
darth_bachious | 4:610b5051182a | 267 | u = Kp*(ref_angle - angle) + Dp*(ref_vel - motor_vel); |
darth_bachious | 4:610b5051182a | 268 | motor_update(u); |
darth_bachious | 4:610b5051182a | 269 | |
darth_bachious | 4:610b5051182a | 270 | //switching states |
darth_bachious | 6:ccbbf4c77d35 | 271 | if ((abs(encoderPos)<20)&&abs((ref_vel - motor_vel)<0.02f)) |
darth_bachious | 4:610b5051182a | 272 | { |
darth_bachious | 4:610b5051182a | 273 | currentState = stateOperation; |
darth_bachious | 6:ccbbf4c77d35 | 274 | force_offset = measuredForce; |
darth_bachious | 4:610b5051182a | 275 | motor_update(0.0); |
darth_bachious | 5:4d5b077b3fe6 | 276 | led2=1; |
darth_bachious | 6:ccbbf4c77d35 | 277 | led=1; |
darth_bachious | 4:610b5051182a | 278 | } |
darth_bachious | 3:376fccdc7cd6 | 279 | |
darth_bachious | 4:610b5051182a | 280 | } |
darth_bachious | 4:610b5051182a | 281 | |
darth_bachious | 4:610b5051182a | 282 | void doOperation() |
darth_bachious | 4:610b5051182a | 283 | { |
darth_bachious | 9:16044ec419af | 284 | ref_acc = (force*arm + control_torque- virtualDamping*ref_vel)/virtualInertia; |
darth_bachious | 5:4d5b077b3fe6 | 285 | ref_vel += ref_acc*ADDMlooptime; |
darth_bachious | 9:16044ec419af | 286 | if(ref_vel>max_velocity) |
darth_bachious | 9:16044ec419af | 287 | ref_vel=max_velocity; |
darth_bachious | 9:16044ec419af | 288 | else if(ref_vel<-max_velocity) |
darth_bachious | 9:16044ec419af | 289 | ref_vel=-max_velocity; |
darth_bachious | 9:16044ec419af | 290 | |
darth_bachious | 9:16044ec419af | 291 | |
darth_bachious | 5:4d5b077b3fe6 | 292 | ref_angle += ref_vel*ADDMlooptime; |
darth_bachious | 5:4d5b077b3fe6 | 293 | if(ref_angle > WORKSPACEBOUND) |
darth_bachious | 5:4d5b077b3fe6 | 294 | { |
darth_bachious | 5:4d5b077b3fe6 | 295 | ref_vel = 0.0; |
darth_bachious | 5:4d5b077b3fe6 | 296 | ref_acc = 0.0; |
darth_bachious | 5:4d5b077b3fe6 | 297 | ref_angle = WORKSPACEBOUND; |
darth_bachious | 5:4d5b077b3fe6 | 298 | } else if(ref_angle < -WORKSPACEBOUND) |
darth_bachious | 5:4d5b077b3fe6 | 299 | { |
darth_bachious | 5:4d5b077b3fe6 | 300 | ref_vel = 0.0; |
darth_bachious | 5:4d5b077b3fe6 | 301 | ref_acc = 0.0; |
darth_bachious | 5:4d5b077b3fe6 | 302 | ref_angle = -WORKSPACEBOUND; |
darth_bachious | 5:4d5b077b3fe6 | 303 | } |
darth_bachious | 5:4d5b077b3fe6 | 304 | u = Kp*(ref_angle - angle) + Dp*(ref_vel - motor_vel); |
darth_bachious | 4:610b5051182a | 305 | motor_update(u); |
darth_bachious | 6:ccbbf4c77d35 | 306 | |
darth_bachious | 6:ccbbf4c77d35 | 307 | //no switching states for now |
darth_bachious | 5:4d5b077b3fe6 | 308 | } |
darth_bachious | 5:4d5b077b3fe6 | 309 | |
darth_bachious | 5:4d5b077b3fe6 | 310 | void loopfunction() |
darth_bachious | 5:4d5b077b3fe6 | 311 | { |
darth_bachious | 5:4d5b077b3fe6 | 312 | sensorUpdate(); |
darth_bachious | 5:4d5b077b3fe6 | 313 | switch(currentState) |
darth_bachious | 5:4d5b077b3fe6 | 314 | { |
darth_bachious | 5:4d5b077b3fe6 | 315 | case stateCalibration: |
darth_bachious | 5:4d5b077b3fe6 | 316 | doCalibration(); |
darth_bachious | 5:4d5b077b3fe6 | 317 | break; |
darth_bachious | 5:4d5b077b3fe6 | 318 | case stateHoming: |
darth_bachious | 5:4d5b077b3fe6 | 319 | doHoming(); |
darth_bachious | 5:4d5b077b3fe6 | 320 | break; |
darth_bachious | 5:4d5b077b3fe6 | 321 | case stateOperation: |
darth_bachious | 5:4d5b077b3fe6 | 322 | doOperation(); |
darth_bachious | 5:4d5b077b3fe6 | 323 | break; |
darth_bachious | 5:4d5b077b3fe6 | 324 | } |
darth_bachious | 4:610b5051182a | 325 | } |
darth_bachious | 0:2f89dec3e2ab | 326 | |
darth_bachious | 6:ccbbf4c77d35 | 327 | void updateTransparency() |
darth_bachious | 6:ccbbf4c77d35 | 328 | { |
darth_bachious | 6:ccbbf4c77d35 | 329 | transparancy++; |
darth_bachious | 6:ccbbf4c77d35 | 330 | if(transparancy>3) |
darth_bachious | 6:ccbbf4c77d35 | 331 | transparancy = 1; |
darth_bachious | 6:ccbbf4c77d35 | 332 | } |
darth_bachious | 6:ccbbf4c77d35 | 333 | |
darth_bachious | 6:ccbbf4c77d35 | 334 | void updatePassivity() |
darth_bachious | 6:ccbbf4c77d35 | 335 | { |
darth_bachious | 6:ccbbf4c77d35 | 336 | passivity++; |
darth_bachious | 6:ccbbf4c77d35 | 337 | if(passivity>1) |
darth_bachious | 6:ccbbf4c77d35 | 338 | passivity = 0; |
darth_bachious | 6:ccbbf4c77d35 | 339 | } |
darth_bachious | 6:ccbbf4c77d35 | 340 | |
darth_bachious | 6:ccbbf4c77d35 | 341 | float passivityLayer(float Ftl, float E_in) |
darth_bachious | 6:ccbbf4c77d35 | 342 | { |
darth_bachious | 6:ccbbf4c77d35 | 343 | tank = tank + E_in - prev_torque*(ref_angle-prev_ref_angle); |
darth_bachious | 6:ccbbf4c77d35 | 344 | if(tank>0.0f) |
darth_bachious | 6:ccbbf4c77d35 | 345 | { |
darth_bachious | 6:ccbbf4c77d35 | 346 | package_out = tank*beta; |
darth_bachious | 6:ccbbf4c77d35 | 347 | tank = tank - package_out; |
darth_bachious | 6:ccbbf4c77d35 | 348 | } else |
darth_bachious | 6:ccbbf4c77d35 | 349 | package_out = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 350 | |
darth_bachious | 6:ccbbf4c77d35 | 351 | float FMAX1 = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 352 | if(tank>0.0f) |
darth_bachious | 6:ccbbf4c77d35 | 353 | { |
darth_bachious | 8:5c891d5ebe45 | 354 | FMAX1 = abs(Ftl); |
darth_bachious | 6:ccbbf4c77d35 | 355 | } |
darth_bachious | 6:ccbbf4c77d35 | 356 | float FMAX2 = abs(tank/(ref_vel*looptime)); |
darth_bachious | 9:16044ec419af | 357 | float FMAX3 = 20.0; |
darth_bachious | 9:16044ec419af | 358 | if(identity==0) |
darth_bachious | 9:16044ec419af | 359 | { |
darth_bachious | 9:16044ec419af | 360 | FMAX3 = 0.7; |
darth_bachious | 9:16044ec419af | 361 | } |
darth_bachious | 6:ccbbf4c77d35 | 362 | prev_ref_angle = ref_angle; |
darth_bachious | 6:ccbbf4c77d35 | 363 | |
darth_bachious | 6:ccbbf4c77d35 | 364 | float Ftlc = 0.0; |
darth_bachious | 6:ccbbf4c77d35 | 365 | |
darth_bachious | 6:ccbbf4c77d35 | 366 | if((tank<Hd)&&(identity==1)) |
darth_bachious | 6:ccbbf4c77d35 | 367 | { |
darth_bachious | 6:ccbbf4c77d35 | 368 | Ftlc = - alpha*(Hd-tank)*ref_vel; |
darth_bachious | 6:ccbbf4c77d35 | 369 | } |
darth_bachious | 9:16044ec419af | 370 | //pc.printf("%f,%f,%f,%f\r\n",tank,Ftl,min(min(abs(Ftl),FMAX1),min(FMAX2,FMAX3)),Ftlc); |
darth_bachious | 6:ccbbf4c77d35 | 371 | //pc.printf("Ftlc: %f\r\n",Ftlc); |
darth_bachious | 6:ccbbf4c77d35 | 372 | //pc.printf("tank: %f\r\n",tank); |
darth_bachious | 6:ccbbf4c77d35 | 373 | if(Ftl>=0.0f) |
darth_bachious | 6:ccbbf4c77d35 | 374 | { |
darth_bachious | 6:ccbbf4c77d35 | 375 | prev_torque = min(min(abs(Ftl),FMAX1),min(FMAX2,FMAX3))+Ftlc; |
darth_bachious | 6:ccbbf4c77d35 | 376 | return min(min(abs(Ftl),FMAX1),min(FMAX2,FMAX3))+Ftlc; //min() only takes to arguments, so nested min()'s |
darth_bachious | 8:5c891d5ebe45 | 377 | //return 0.0; |
darth_bachious | 8:5c891d5ebe45 | 378 | } |
darth_bachious | 6:ccbbf4c77d35 | 379 | else |
darth_bachious | 6:ccbbf4c77d35 | 380 | { |
darth_bachious | 9:16044ec419af | 381 | prev_torque = -min(min(abs(Ftl),FMAX1),min(FMAX2,FMAX3))+Ftlc; |
darth_bachious | 8:5c891d5ebe45 | 382 | //return 0.0; |
darth_bachious | 9:16044ec419af | 383 | return -min(min(abs(Ftl),FMAX1),min(FMAX2,FMAX3))+Ftlc; |
darth_bachious | 6:ccbbf4c77d35 | 384 | } |
darth_bachious | 6:ccbbf4c77d35 | 385 | |
darth_bachious | 6:ccbbf4c77d35 | 386 | } |
darth_bachious | 6:ccbbf4c77d35 | 387 | |
darth_bachious | 7:984f363f5e87 | 388 | void generateOutput(float variable) |
darth_bachious | 7:984f363f5e87 | 389 | { |
darth_bachious | 7:984f363f5e87 | 390 | memcpy(&output[0],&counter,4); |
darth_bachious | 7:984f363f5e87 | 391 | memcpy(&output[4],&transparancy,4); |
darth_bachious | 7:984f363f5e87 | 392 | memcpy(&output[8],&passivity,4); |
darth_bachious | 7:984f363f5e87 | 393 | memcpy(&output[12],&variable,4); |
darth_bachious | 7:984f363f5e87 | 394 | memcpy(&output[16],&package_out,4); |
darth_bachious | 7:984f363f5e87 | 395 | //pc.printf("output:%i,%i,%i,%f,%f\r\n",counter,transparancy,passivity,variable,package_out); |
darth_bachious | 7:984f363f5e87 | 396 | |
darth_bachious | 7:984f363f5e87 | 397 | } |
darth_bachious | 9:16044ec419af | 398 | void generateStatus() |
darth_bachious | 9:16044ec419af | 399 | { |
darth_bachious | 9:16044ec419af | 400 | memcpy(&status[0],&counter,4); |
darth_bachious | 9:16044ec419af | 401 | memcpy(&status[4],&ref_angle,4); |
darth_bachious | 9:16044ec419af | 402 | memcpy(&status[8],&force,4); |
darth_bachious | 9:16044ec419af | 403 | memcpy(&status[12],&tank,4); |
darth_bachious | 11:cb9bb3f0635d | 404 | memcpy(&status[16],&percentage_received,4); |
darth_bachious | 9:16044ec419af | 405 | } |
darth_bachious | 7:984f363f5e87 | 406 | |
darth_bachious | 0:2f89dec3e2ab | 407 | void receiveUDP(void const *argument){ |
darth_bachious | 4:610b5051182a | 408 | while(true) |
darth_bachious | 4:610b5051182a | 409 | { |
darth_bachious | 0:2f89dec3e2ab | 410 | size = socket.receiveFrom(client, data, sizeof(data)); |
darth_bachious | 4:610b5051182a | 411 | if(size > 0) |
darth_bachious | 4:610b5051182a | 412 | { |
darth_bachious | 0:2f89dec3e2ab | 413 | data[size] = '\0'; |
darth_bachious | 7:984f363f5e87 | 414 | if(size>18) //first check, an minimum amount of data must have arrived |
darth_bachious | 4:610b5051182a | 415 | { |
darth_bachious | 7:984f363f5e87 | 416 | memcpy(&check,&data[0],4); |
darth_bachious | 7:984f363f5e87 | 417 | if(counter_received < check) //second check, data must be newer |
darth_bachious | 4:610b5051182a | 418 | { |
darth_bachious | 7:984f363f5e87 | 419 | counter_received=check; |
darth_bachious | 7:984f363f5e87 | 420 | memcpy(&received_transparancy,&data[4],4); |
darth_bachious | 7:984f363f5e87 | 421 | memcpy(&received_passivity,&data[8],4); |
darth_bachious | 7:984f363f5e87 | 422 | memcpy(&input,&data[12],4); |
darth_bachious | 10:694de5b31fd6 | 423 | memcpy(&received_package,&data[16],4); |
darth_bachious | 10:694de5b31fd6 | 424 | led3=!led3; |
darth_bachious | 11:cb9bb3f0635d | 425 | counter_not_missed ++; |
darth_bachious | 11:cb9bb3f0635d | 426 | percentage_received = (float) counter_not_missed/counter_received; |
darth_bachious | 11:cb9bb3f0635d | 427 | |
darth_bachious | 7:984f363f5e87 | 428 | //pc.printf("data:%i,%i,%i,%f,%f\r\n",counter_received,received_transparancy,received_passivity,input,received_package); |
darth_bachious | 4:610b5051182a | 429 | } |
darth_bachious | 0:2f89dec3e2ab | 430 | } |
darth_bachious | 0:2f89dec3e2ab | 431 | } |
darth_bachious | 0:2f89dec3e2ab | 432 | } |
darth_bachious | 4:610b5051182a | 433 | } |
darth_bachious | 0:2f89dec3e2ab | 434 | |
darth_bachious | 0:2f89dec3e2ab | 435 | osThreadDef(receiveUDP, osPriorityNormal, DEFAULT_STACK_SIZE); |
darth_bachious | 0:2f89dec3e2ab | 436 | |
darth_bachious | 0:2f89dec3e2ab | 437 | int main(){ |
darth_bachious | 10:694de5b31fd6 | 438 | osThreadCreate(osThread(receiveUDP), NULL); |
darth_bachious | 10:694de5b31fd6 | 439 | |
darth_bachious | 1:853939e38acd | 440 | inet_eth(); |
darth_bachious | 0:2f89dec3e2ab | 441 | inet_USB(); |
darth_bachious | 0:2f89dec3e2ab | 442 | |
darth_bachious | 0:2f89dec3e2ab | 443 | led2=1; |
darth_bachious | 0:2f89dec3e2ab | 444 | led=1; |
darth_bachious | 5:4d5b077b3fe6 | 445 | |
darth_bachious | 6:ccbbf4c77d35 | 446 | //Set all interrupt requests to 2nd place priority |
darth_bachious | 5:4d5b077b3fe6 | 447 | for(int n = 0; n < 86; n++) { |
darth_bachious | 5:4d5b077b3fe6 | 448 | NVIC_SetPriority((IRQn)n,1); |
darth_bachious | 5:4d5b077b3fe6 | 449 | } |
darth_bachious | 5:4d5b077b3fe6 | 450 | //Set out motor encoder interrupt to 1st place priority |
darth_bachious | 5:4d5b077b3fe6 | 451 | NVIC_SetPriority(PORTB_IRQn,0); |
darth_bachious | 6:ccbbf4c77d35 | 452 | |
darth_bachious | 5:4d5b077b3fe6 | 453 | controllerloop.attach(&controllertrigger,looptime); |
darth_bachious | 5:4d5b077b3fe6 | 454 | mainloop.attach(&loopfunction,ADDMlooptime); |
darth_bachious | 5:4d5b077b3fe6 | 455 | |
darth_bachious | 4:610b5051182a | 456 | M1_pwm.period(1.0/frequency_pwm); |
darth_bachious | 4:610b5051182a | 457 | EncoderA.rise(&encoderFunctionA); |
darth_bachious | 7:984f363f5e87 | 458 | |
darth_bachious | 7:984f363f5e87 | 459 | Button1.rise(&updateTransparency); |
darth_bachious | 7:984f363f5e87 | 460 | Button2.rise(&updatePassivity); |
darth_bachious | 6:ccbbf4c77d35 | 461 | |
darth_bachious | 4:610b5051182a | 462 | t.start(); |
darth_bachious | 3:376fccdc7cd6 | 463 | |
darth_bachious | 0:2f89dec3e2ab | 464 | while(true){ |
darth_bachious | 5:4d5b077b3fe6 | 465 | if(controller_check==1){ |
darth_bachious | 6:ccbbf4c77d35 | 466 | debug = 1; |
darth_bachious | 6:ccbbf4c77d35 | 467 | float received_input = update_delay(delayArrayINPUT,input); |
darth_bachious | 6:ccbbf4c77d35 | 468 | float current_transparancy = update_delay(delayArrayMODE,received_transparancy); |
darth_bachious | 6:ccbbf4c77d35 | 469 | float current_passivity = update_delay(delayArrayPASS,received_passivity); |
darth_bachious | 6:ccbbf4c77d35 | 470 | float package_in = update_delay(delayArrayENERGY,received_package); |
darth_bachious | 6:ccbbf4c77d35 | 471 | received_package = 0; //IMPORTANT, WILL EXPLODE OTHERWISE |
darth_bachious | 3:376fccdc7cd6 | 472 | |
darth_bachious | 6:ccbbf4c77d35 | 473 | if(identity==0) |
darth_bachious | 6:ccbbf4c77d35 | 474 | { |
darth_bachious | 6:ccbbf4c77d35 | 475 | transparancy = current_transparancy; |
darth_bachious | 6:ccbbf4c77d35 | 476 | passivity = current_passivity; |
darth_bachious | 6:ccbbf4c77d35 | 477 | if(transparancy==1) |
darth_bachious | 6:ccbbf4c77d35 | 478 | { |
darth_bachious | 6:ccbbf4c77d35 | 479 | float torque_tlc = Ktl*(received_input-ref_angle) - Dtl*ref_vel; |
darth_bachious | 6:ccbbf4c77d35 | 480 | if(current_passivity==1) |
darth_bachious | 6:ccbbf4c77d35 | 481 | control_torque = passivityLayer(torque_tlc,package_in); |
darth_bachious | 6:ccbbf4c77d35 | 482 | else |
darth_bachious | 6:ccbbf4c77d35 | 483 | control_torque = torque_tlc; |
darth_bachious | 7:984f363f5e87 | 484 | generateOutput(ref_angle); |
darth_bachious | 6:ccbbf4c77d35 | 485 | } |
darth_bachious | 6:ccbbf4c77d35 | 486 | else if(transparancy==2) |
darth_bachious | 6:ccbbf4c77d35 | 487 | { |
darth_bachious | 6:ccbbf4c77d35 | 488 | float torque_tlc = Ktl*(received_input-ref_angle) - Dtl*ref_vel; |
darth_bachious | 6:ccbbf4c77d35 | 489 | if(current_passivity==1) |
darth_bachious | 6:ccbbf4c77d35 | 490 | control_torque = passivityLayer(torque_tlc,package_in); |
darth_bachious | 6:ccbbf4c77d35 | 491 | else |
darth_bachious | 7:984f363f5e87 | 492 | control_torque = torque_tlc; |
darth_bachious | 7:984f363f5e87 | 493 | generateOutput(force); |
darth_bachious | 6:ccbbf4c77d35 | 494 | } |
darth_bachious | 6:ccbbf4c77d35 | 495 | else if(transparancy==3) |
darth_bachious | 6:ccbbf4c77d35 | 496 | { |
darth_bachious | 6:ccbbf4c77d35 | 497 | float torque_tlc = received_input*arm; |
darth_bachious | 6:ccbbf4c77d35 | 498 | if(current_passivity==1) |
darth_bachious | 6:ccbbf4c77d35 | 499 | control_torque = passivityLayer(torque_tlc,package_in); |
darth_bachious | 6:ccbbf4c77d35 | 500 | else |
darth_bachious | 7:984f363f5e87 | 501 | control_torque = torque_tlc; |
darth_bachious | 7:984f363f5e87 | 502 | generateOutput(ref_angle); |
darth_bachious | 6:ccbbf4c77d35 | 503 | } |
darth_bachious | 6:ccbbf4c77d35 | 504 | } |
darth_bachious | 6:ccbbf4c77d35 | 505 | else if(identity == 1) |
darth_bachious | 6:ccbbf4c77d35 | 506 | { |
darth_bachious | 6:ccbbf4c77d35 | 507 | if(transparancy==1) |
darth_bachious | 6:ccbbf4c77d35 | 508 | { |
darth_bachious | 6:ccbbf4c77d35 | 509 | float torque_tlc = Ktl*(received_input-ref_angle) - Dtl*ref_vel; |
darth_bachious | 6:ccbbf4c77d35 | 510 | if(current_passivity==1) |
darth_bachious | 6:ccbbf4c77d35 | 511 | control_torque = passivityLayer(torque_tlc,package_in); |
darth_bachious | 6:ccbbf4c77d35 | 512 | else |
darth_bachious | 7:984f363f5e87 | 513 | control_torque = torque_tlc; |
darth_bachious | 7:984f363f5e87 | 514 | generateOutput(ref_angle); |
darth_bachious | 6:ccbbf4c77d35 | 515 | } |
darth_bachious | 6:ccbbf4c77d35 | 516 | else if(transparancy==2) |
darth_bachious | 6:ccbbf4c77d35 | 517 | { |
darth_bachious | 6:ccbbf4c77d35 | 518 | float torque_tlc = received_input*arm; |
darth_bachious | 6:ccbbf4c77d35 | 519 | if(current_passivity==1) |
darth_bachious | 6:ccbbf4c77d35 | 520 | control_torque = passivityLayer(torque_tlc,package_in); |
darth_bachious | 6:ccbbf4c77d35 | 521 | else |
darth_bachious | 7:984f363f5e87 | 522 | control_torque = torque_tlc; |
darth_bachious | 7:984f363f5e87 | 523 | generateOutput(ref_angle); |
darth_bachious | 6:ccbbf4c77d35 | 524 | } |
darth_bachious | 6:ccbbf4c77d35 | 525 | else if(transparancy==3) |
darth_bachious | 6:ccbbf4c77d35 | 526 | { |
darth_bachious | 6:ccbbf4c77d35 | 527 | float torque_tlc = Ktl*(received_input-ref_angle) - Dtl*ref_vel; |
darth_bachious | 6:ccbbf4c77d35 | 528 | if(current_passivity==1) |
darth_bachious | 6:ccbbf4c77d35 | 529 | control_torque = passivityLayer(torque_tlc,package_in); |
darth_bachious | 6:ccbbf4c77d35 | 530 | else |
darth_bachious | 7:984f363f5e87 | 531 | control_torque = torque_tlc; |
darth_bachious | 7:984f363f5e87 | 532 | generateOutput(force); |
darth_bachious | 6:ccbbf4c77d35 | 533 | } |
darth_bachious | 6:ccbbf4c77d35 | 534 | } |
darth_bachious | 10:694de5b31fd6 | 535 | if(counter%2 >0) |
darth_bachious | 10:694de5b31fd6 | 536 | |
darth_bachious | 10:694de5b31fd6 | 537 | //pc.printf("force:%f, refpos:%f, pos:%f, tank:%f\r\n",force, ref_angle, angle, tank); |
darth_bachious | 7:984f363f5e87 | 538 | //pc.printf("received input: %i\r\n",received_input); |
darth_bachious | 11:cb9bb3f0635d | 539 | pc.printf("ticks: %i\r\n",encoderPos); |
darth_bachious | 5:4d5b077b3fe6 | 540 | socket.sendTo(counterpart, output, sizeof(output)); |
darth_bachious | 11:cb9bb3f0635d | 541 | socket.sendTo(counterpart, output, sizeof(output)); |
darth_bachious | 11:cb9bb3f0635d | 542 | counter ++; |
darth_bachious | 9:16044ec419af | 543 | generateStatus(); |
darth_bachious | 9:16044ec419af | 544 | socket.sendTo(laptop,status,sizeof(status)); |
darth_bachious | 10:694de5b31fd6 | 545 | led=0; |
darth_bachious | 6:ccbbf4c77d35 | 546 | if(current_passivity==1) |
darth_bachious | 6:ccbbf4c77d35 | 547 | led2=0; |
darth_bachious | 6:ccbbf4c77d35 | 548 | else |
darth_bachious | 10:694de5b31fd6 | 549 | led2=1; |
darth_bachious | 10:694de5b31fd6 | 550 | |
darth_bachious | 5:4d5b077b3fe6 | 551 | controller_check = 0; |
darth_bachious | 6:ccbbf4c77d35 | 552 | debug = 0; |
darth_bachious | 0:2f89dec3e2ab | 553 | } |
darth_bachious | 4:610b5051182a | 554 | osDelay(0.1); //this might be the most ugliest piece of code that somehow still works. BK |
darth_bachious | 0:2f89dec3e2ab | 555 | } |
darth_bachious | 0:2f89dec3e2ab | 556 | } |