Code used to localize a robot using IMU with PID control

Dependencies:   IMUfilter LSM9DS0 Motordriver mbed

Fork of Robot_feedback_ir by Adrian Winata

Committer:
cardenb
Date:
Tue Oct 20 19:13:05 2015 +0000
Revision:
1:e71cba217586
Parent:
0:98baffd99476
Codebase for the robot IMU localization

Who changed what in which revision?

UserRevisionLine numberNew contents of line
awinata 0:98baffd99476 1 #include "mbed.h"
awinata 0:98baffd99476 2 #include "motordriver.h"
awinata 0:98baffd99476 3 #include "LSM9DS0.h"
cardenb 1:e71cba217586 4 #include "IMUfilter.h"
cardenb 1:e71cba217586 5
cardenb 1:e71cba217586 6 #define RATE 0.1
cardenb 1:e71cba217586 7
cardenb 1:e71cba217586 8 // Callback to update the PID controller
cardenb 1:e71cba217586 9 Ticker pid_updater;
cardenb 1:e71cba217586 10 // Set a PID update rate of 10 Hz
cardenb 1:e71cba217586 11 const float PID_RATE = 0.1;
cardenb 1:e71cba217586 12 // Call back to update the IMU samples
cardenb 1:e71cba217586 13 Ticker imu_updater;
cardenb 1:e71cba217586 14 Ticker heading_reseter;
cardenb 1:e71cba217586 15 // Set a sample rate of 200 Hz for the IMU
cardenb 1:e71cba217586 16 const float IMU_RATE = 0.025;
cardenb 1:e71cba217586 17 const int NUM_IMU_SAMPLES = 4;
cardenb 1:e71cba217586 18 // Count the number of times the IMU is sampled to average every 100 samples.
cardenb 1:e71cba217586 19 int imu_count = 0;
cardenb 1:e71cba217586 20 Ticker dist_updater;
cardenb 1:e71cba217586 21 const float DIST_RATE = 0.1;
cardenb 1:e71cba217586 22 const float DIST_LIMIT = 0.5;
cardenb 1:e71cba217586 23
cardenb 1:e71cba217586 24 const float INITIAL_SPEED_LEFT = 0.7;
cardenb 1:e71cba217586 25 const float INITIAL_SPEED_RIGHT = 0.609;
cardenb 1:e71cba217586 26
cardenb 1:e71cba217586 27 const float TURN_TIME = 0.55;
awinata 0:98baffd99476 28
awinata 0:98baffd99476 29 /*/////// PID control gains
awinata 0:98baffd99476 30 / These values need to be adjusted in accordance with the individual
awinata 0:98baffd99476 31 / actuators (motors) either by trial and error or computation. The information
cardenb 1:e71cba217586 32 / here should help:
awinata 0:98baffd99476 33 *////////
cardenb 1:e71cba217586 34 /*
cardenb 1:e71cba217586 35 * Done with Ziegler-Nichols method
cardenb 1:e71cba217586 36
cardenb 1:e71cba217586 37 #define Kp 0.002
cardenb 1:e71cba217586 38 #define Ki 0.00002
cardenb 1:e71cba217586 39 #define Kd 0.075
cardenb 1:e71cba217586 40 */
cardenb 1:e71cba217586 41 /*
cardenb 1:e71cba217586 42 * Done with random method from stack overflow which is more upvoted than Ziegler-Nichols
cardenb 1:e71cba217586 43
cardenb 1:e71cba217586 44 #define Kp 0.192
cardenb 1:e71cba217586 45 #define Ki 0.0
cardenb 1:e71cba217586 46 #define Kd 0.048
cardenb 1:e71cba217586 47 */
awinata 0:98baffd99476 48
cardenb 1:e71cba217586 49 #define Kp 0.0001
cardenb 1:e71cba217586 50 #define Ki 0.0
cardenb 1:e71cba217586 51 #define Kd 0.00001
cardenb 1:e71cba217586 52 /*
cardenb 1:e71cba217586 53 */
awinata 0:98baffd99476 54 // SDO_XM and SDO_G are pulled up, so our addresses are:
awinata 0:98baffd99476 55 #define LSM9DS0_XM_ADDR 0x1D // Would be 0x1E if SDO_XM is LOW
awinata 0:98baffd99476 56 #define LSM9DS0_G_ADDR 0x6B // Would be 0x6A if SDO_G is LOW
awinata 0:98baffd99476 57
awinata 0:98baffd99476 58 // peripheral objects
awinata 0:98baffd99476 59 DigitalOut f(LED1); // forward
awinata 0:98baffd99476 60 DigitalOut b(LED2); // backward
awinata 0:98baffd99476 61 DigitalOut l(LED3); // left
awinata 0:98baffd99476 62 DigitalOut r(LED4); // right
awinata 0:98baffd99476 63 //See http://mbed.org/cookbook/Motor
awinata 0:98baffd99476 64 //Connections to dual H-brdige driver for the two drive motors
awinata 0:98baffd99476 65 Motor left(p21, p22, p23, 1); // pwm, fwd, rev, has brake feature
awinata 0:98baffd99476 66 Motor right(p26, p25, p24, 1);
awinata 0:98baffd99476 67 LSM9DS0 imu(p9, p10, LSM9DS0_G_ADDR, LSM9DS0_XM_ADDR); // imu sensor
awinata 0:98baffd99476 68 Serial pc(USBTX, USBRX); // usb serial for debugging
awinata 0:98baffd99476 69
awinata 0:98baffd99476 70 // function prototypes
awinata 0:98baffd99476 71 void setup();
awinata 0:98baffd99476 72 void initIMU();
awinata 0:98baffd99476 73 float getHeading();
cardenb 1:e71cba217586 74 void updatePID();
cardenb 1:e71cba217586 75 void updateIMU();
cardenb 1:e71cba217586 76 float GetAvgHeading();
cardenb 1:e71cba217586 77 void UpdateMotorSpeed(float pid_output);
cardenb 1:e71cba217586 78 void updateDist();
cardenb 1:e71cba217586 79 void headingReset();
cardenb 1:e71cba217586 80 void turnRight();
cardenb 1:e71cba217586 81 void turnLeft();
cardenb 1:e71cba217586 82 void calibrateAccel();
cardenb 1:e71cba217586 83 void turnToAngle(float angle);
awinata 0:98baffd99476 84
awinata 0:98baffd99476 85 // global variables
awinata 0:98baffd99476 86 int count; // counter for IR
cardenb 1:e71cba217586 87
awinata 0:98baffd99476 88 float calcHeading; // calculated heading
awinata 0:98baffd99476 89 float heading[1]; // index 0 = goal, index 1 = current
cardenb 1:e71cba217586 90 float heading_avg[NUM_IMU_SAMPLES]; // moving average array
awinata 0:98baffd99476 91 float sum;
cardenb 1:e71cba217586 92
awinata 0:98baffd99476 93 float previousError; // previous error
awinata 0:98baffd99476 94 float pidError; // error
awinata 0:98baffd99476 95 float steadyError; // steady-state error
cardenb 1:e71cba217586 96
awinata 0:98baffd99476 97 float P; // proportional error
awinata 0:98baffd99476 98 float I; // integral error
awinata 0:98baffd99476 99 float D; // derivative error
cardenb 1:e71cba217586 100
awinata 0:98baffd99476 101 float kp; // proportional constant
awinata 0:98baffd99476 102 float ki; // integral constant
awinata 0:98baffd99476 103 float kd; // derivative constant
cardenb 1:e71cba217586 104
awinata 0:98baffd99476 105 float output; // PID controller output
cardenb 1:e71cba217586 106
cardenb 1:e71cba217586 107 float dt; // update frequency
cardenb 1:e71cba217586 108
awinata 0:98baffd99476 109 float motorSpeed1; // motor speed for left motor
awinata 0:98baffd99476 110 float motorSpeed2; // motor speed for right motor
cardenb 1:e71cba217586 111 float dist[2]; // 0 = x, 1 = y
cardenb 1:e71cba217586 112 float vel[2]; // 0 = x, 1 = y
cardenb 1:e71cba217586 113 float accel[2]; // 0 = x, 1 = y
cardenb 1:e71cba217586 114 float cur_dist; // resultant distance
cardenb 1:e71cba217586 115
cardenb 1:e71cba217586 116
awinata 0:98baffd99476 117
awinata 0:98baffd99476 118 // function implementations
awinata 0:98baffd99476 119 void setup()
awinata 0:98baffd99476 120 {
awinata 0:98baffd99476 121 // Use the begin() function to initialize the LSM9DS0 library.
awinata 0:98baffd99476 122 // You can either call it with no parameters (the easy way):
awinata 0:98baffd99476 123 uint16_t status = imu.begin();
awinata 0:98baffd99476 124
cardenb 1:e71cba217586 125 // Make sure communication is working
awinata 0:98baffd99476 126 pc.printf("LSM9DS0 WHO_AM_I's returned: 0x%X\n", status);
awinata 0:98baffd99476 127 pc.printf("Should be 0x49D4\n\n");
cardenb 1:e71cba217586 128 initIMU();
cardenb 1:e71cba217586 129
cardenb 1:e71cba217586 130 // Initialize motor speeds
cardenb 1:e71cba217586 131 motorSpeed1 = INITIAL_SPEED_LEFT;
cardenb 1:e71cba217586 132 motorSpeed2 = INITIAL_SPEED_RIGHT;
cardenb 1:e71cba217586 133 // Attach the PID Update routine to a callback
cardenb 1:e71cba217586 134 pid_updater.attach(&updatePID, PID_RATE);
cardenb 1:e71cba217586 135 // Attach the IMU update routine to a callback to computer a moving average
cardenb 1:e71cba217586 136 imu_updater.attach(&updateIMU, IMU_RATE);
cardenb 1:e71cba217586 137 dist_updater.attach(&updateDist, DIST_RATE);
cardenb 1:e71cba217586 138 // set motor speeds
cardenb 1:e71cba217586 139 left.speed(motorSpeed1);
cardenb 1:e71cba217586 140 right.speed(motorSpeed2);
awinata 0:98baffd99476 141 }
awinata 0:98baffd99476 142
awinata 0:98baffd99476 143 // obtains the initial heading upon bootup
awinata 0:98baffd99476 144 void initIMU()
awinata 0:98baffd99476 145 {
awinata 0:98baffd99476 146 imu.readMag();
cardenb 1:e71cba217586 147 imu.readAccel();
cardenb 1:e71cba217586 148 imu.calcBias();
cardenb 1:e71cba217586 149 imu.readMag();
cardenb 1:e71cba217586 150 wait(0.1);
cardenb 1:e71cba217586 151 // initial heading
cardenb 1:e71cba217586 152 float sum = 0;
cardenb 1:e71cba217586 153 for( int i = 0; i < NUM_IMU_SAMPLES; ++i) {
cardenb 1:e71cba217586 154 sum += getHeading();
cardenb 1:e71cba217586 155 }
cardenb 1:e71cba217586 156 float initHeading = sum/NUM_IMU_SAMPLES;
cardenb 1:e71cba217586 157
awinata 0:98baffd99476 158 heading[0] = initHeading;
awinata 0:98baffd99476 159 heading[1] = initHeading;
awinata 0:98baffd99476 160 }
awinata 0:98baffd99476 161
awinata 0:98baffd99476 162 // obtains an updated heading
awinata 0:98baffd99476 163 float getHeading()
awinata 0:98baffd99476 164 {
awinata 0:98baffd99476 165 imu.readMag(); // reads current value
awinata 0:98baffd99476 166 calcHeading = imu.calcHeading(); // sets current value
awinata 0:98baffd99476 167
cardenb 1:e71cba217586 168 // pc.printf("Compass Heading (in degrees): ");
cardenb 1:e71cba217586 169 // pc.printf("%2f\r\n",calcHeading);
cardenb 1:e71cba217586 170 // pc.printf("%2f\r\n",heading[0]);
awinata 0:98baffd99476 171
awinata 0:98baffd99476 172 return calcHeading;
awinata 0:98baffd99476 173 }
awinata 0:98baffd99476 174
cardenb 1:e71cba217586 175 // PID update attached to a ticker.
cardenb 1:e71cba217586 176 void updatePID()
awinata 0:98baffd99476 177 {
cardenb 1:e71cba217586 178 float current = heading[1];
cardenb 1:e71cba217586 179 float target = heading[0];
cardenb 1:e71cba217586 180 float dt = PID_RATE;
cardenb 1:e71cba217586 181 // pc.printf("current=%0.2f, target=%0.2f, dt=%0.2f\r\n", current, target, dt);
awinata 0:98baffd99476 182 // calculate difference between actual and goal values
awinata 0:98baffd99476 183 pidError = target - current;
awinata 0:98baffd99476 184 if (pidError < -270) pidError += 360;
awinata 0:98baffd99476 185 if (pidError > 270) pidError -= 360;
awinata 0:98baffd99476 186
awinata 0:98baffd99476 187 // accumulates error over time
awinata 0:98baffd99476 188 steadyError += pidError*dt;
awinata 0:98baffd99476 189
awinata 0:98baffd99476 190 // integrator windup compensation (saturates to actuator's output)
awinata 0:98baffd99476 191 if (steadyError < -10.0) steadyError = -10.0;
awinata 0:98baffd99476 192 if (steadyError > 10.0) steadyError = 10.0;
awinata 0:98baffd99476 193
awinata 0:98baffd99476 194 P = kp*pidError; // proportional error
awinata 0:98baffd99476 195 I = ki*steadyError; // integral error
awinata 0:98baffd99476 196 D = kd*(pidError - previousError)/dt; // derivative error
awinata 0:98baffd99476 197
awinata 0:98baffd99476 198 // save current error
awinata 0:98baffd99476 199 previousError = pidError;
awinata 0:98baffd99476 200
awinata 0:98baffd99476 201 float pidOutput = P + I + D; // calculate the PID output
cardenb 1:e71cba217586 202 pidOutput /= 300.0; // scale it down to get it within the range of the actuator - probably needs tuning
awinata 0:98baffd99476 203 if(pidOutput < -0.1) pidOutput = -0.1;
awinata 0:98baffd99476 204 if(pidOutput > 0.1) pidOutput = 0.1;
awinata 0:98baffd99476 205
cardenb 1:e71cba217586 206 // pc.printf("P = %f | I = %f | D = %f | Output = %f\r\n",P,I,D,pidOutput);
cardenb 1:e71cba217586 207 UpdateMotorSpeed(pidOutput);
cardenb 1:e71cba217586 208 }
cardenb 1:e71cba217586 209
cardenb 1:e71cba217586 210 void UpdateMotorSpeed(float pid_output) {
cardenb 1:e71cba217586 211 // updates new motor speeds
cardenb 1:e71cba217586 212 motorSpeed1 -= pid_output;
cardenb 1:e71cba217586 213 motorSpeed2 += pid_output;
cardenb 1:e71cba217586 214
cardenb 1:e71cba217586 215 // pc.printf("Wheel speed difference = %0.2f\r\n", motorSpeed1-motorSpeed2);
cardenb 1:e71cba217586 216 // set motor speeds
cardenb 1:e71cba217586 217 left.speed(motorSpeed1);
cardenb 1:e71cba217586 218 right.speed(motorSpeed2);
cardenb 1:e71cba217586 219 }
cardenb 1:e71cba217586 220
cardenb 1:e71cba217586 221 float GetAvgHeading() {
cardenb 1:e71cba217586 222 float sum = 0;
cardenb 1:e71cba217586 223 for( int i = 0; i < NUM_IMU_SAMPLES; ++i ) {
cardenb 1:e71cba217586 224 sum += heading_avg[i];
cardenb 1:e71cba217586 225 }
cardenb 1:e71cba217586 226 return sum/NUM_IMU_SAMPLES;
cardenb 1:e71cba217586 227 }
cardenb 1:e71cba217586 228
cardenb 1:e71cba217586 229
cardenb 1:e71cba217586 230 // Get reach the minimum number of samples and then use a moving average filter
cardenb 1:e71cba217586 231 // Keep the count below 2*num_samples to prevent overflow, but keep at a minimum of 1*num_samples
cardenb 1:e71cba217586 232 void updateIMU() {
cardenb 1:e71cba217586 233 // Get current index into the ring buffer (heading_avg)
cardenb 1:e71cba217586 234 int moving_idx = imu_count % NUM_IMU_SAMPLES;
cardenb 1:e71cba217586 235 // Add the current heading into the buffer
cardenb 1:e71cba217586 236 heading_avg[moving_idx] = getHeading();
cardenb 1:e71cba217586 237
cardenb 1:e71cba217586 238 // Keep the IMU count between num_samples & 2*num_samples
cardenb 1:e71cba217586 239 // Get the average heading as long as the minimum number of samples have been reached.
cardenb 1:e71cba217586 240 if( imu_count >= NUM_IMU_SAMPLES && imu_count <= 2*NUM_IMU_SAMPLES ) {
cardenb 1:e71cba217586 241 if( imu_count == 2*NUM_IMU_SAMPLES ) {
cardenb 1:e71cba217586 242 imu_count = NUM_IMU_SAMPLES;
cardenb 1:e71cba217586 243 }
cardenb 1:e71cba217586 244 heading[1] = GetAvgHeading();
cardenb 1:e71cba217586 245 // pc.printf("av_heading=%0.2f\r\n", heading[1]);
cardenb 1:e71cba217586 246 }
cardenb 1:e71cba217586 247 imu_count += 1;
cardenb 1:e71cba217586 248 }
awinata 0:98baffd99476 249
cardenb 1:e71cba217586 250 // Update acceleration, velocity, and distance vectors.
cardenb 1:e71cba217586 251 void updateDist() {
cardenb 1:e71cba217586 252 // Get latest acceleration reading.
cardenb 1:e71cba217586 253 imu.readAccel();
cardenb 1:e71cba217586 254
cardenb 1:e71cba217586 255 // Get acceleration vector
cardenb 1:e71cba217586 256 accel[0] = imu.ax - imu.abias[0];
cardenb 1:e71cba217586 257 accel[1] = imu.ay - imu.abias[1];
cardenb 1:e71cba217586 258
cardenb 1:e71cba217586 259 // Get velocity vector
cardenb 1:e71cba217586 260 vel[0] += accel[0]*RATE;
cardenb 1:e71cba217586 261 vel[1] += accel[1]*RATE;
cardenb 1:e71cba217586 262
cardenb 1:e71cba217586 263 // Get distance vector
cardenb 1:e71cba217586 264 dist[0] += vel[0]*RATE;
cardenb 1:e71cba217586 265 dist[1] += vel[1]*RATE;
cardenb 1:e71cba217586 266
cardenb 1:e71cba217586 267 // Get resultant distance
cardenb 1:e71cba217586 268 // d = (dx^2 + dy^2)^(1/2)
cardenb 1:e71cba217586 269 cur_dist = sqrt(dist[0]*dist[0] + dist[1]*dist[1]);
cardenb 1:e71cba217586 270 if( cur_dist > DIST_LIMIT ) {
cardenb 1:e71cba217586 271 turnRight();
cardenb 1:e71cba217586 272 // Reset vel and dist to get accurate distance measurement to next move
cardenb 1:e71cba217586 273 vel[0] = 0;
cardenb 1:e71cba217586 274 vel[1] = 0;
cardenb 1:e71cba217586 275 dist[0] = 0;
cardenb 1:e71cba217586 276 dist[1] = 0;
cardenb 1:e71cba217586 277 cur_dist = 0;
cardenb 1:e71cba217586 278 pidError = 0;
cardenb 1:e71cba217586 279 previousError = 0;
cardenb 1:e71cba217586 280 }
cardenb 1:e71cba217586 281 // pc.printf("ax=%0.2f, ay=%0.2f\r\nvx=%0.2f, vy=%0.2f\r\ndx=%0.2f, dy=%0.2f, d=%0.2f\r\n", accel[0], accel[1], vel[0], vel[1], dist[0], dist[1], cur_dist);
cardenb 1:e71cba217586 282 }
cardenb 1:e71cba217586 283
cardenb 1:e71cba217586 284 void headingReset() {
cardenb 1:e71cba217586 285 motorSpeed1 = INITIAL_SPEED_LEFT;
cardenb 1:e71cba217586 286 motorSpeed2 = INITIAL_SPEED_RIGHT;
cardenb 1:e71cba217586 287
cardenb 1:e71cba217586 288 // set motor speeds
cardenb 1:e71cba217586 289 left.speed(motorSpeed1);
cardenb 1:e71cba217586 290 right.speed(motorSpeed2);
cardenb 1:e71cba217586 291
cardenb 1:e71cba217586 292 heading[0] = getHeading();
cardenb 1:e71cba217586 293 heading[1] = getHeading();
cardenb 1:e71cba217586 294 }
cardenb 1:e71cba217586 295
cardenb 1:e71cba217586 296 void turnRight() {
cardenb 1:e71cba217586 297
cardenb 1:e71cba217586 298 left.stop(0);
cardenb 1:e71cba217586 299 right.stop(0);
cardenb 1:e71cba217586 300 left.speed(-1);
cardenb 1:e71cba217586 301 right.speed(1);
cardenb 1:e71cba217586 302
cardenb 1:e71cba217586 303 /*
cardenb 1:e71cba217586 304 int turn_ang = heading[0] + 90;
cardenb 1:e71cba217586 305 turn_ang = turn_ang% 360;
cardenb 1:e71cba217586 306 turnToAngle(turn_ang);
cardenb 1:e71cba217586 307 */
cardenb 1:e71cba217586 308
cardenb 1:e71cba217586 309 wait(TURN_TIME);
cardenb 1:e71cba217586 310 headingReset();
cardenb 1:e71cba217586 311 }
cardenb 1:e71cba217586 312
cardenb 1:e71cba217586 313 void turnToAngle(float angle) {
cardenb 1:e71cba217586 314 bool hasTurned = false;
cardenb 1:e71cba217586 315 left.stop(0);
cardenb 1:e71cba217586 316 right.stop(0);
cardenb 1:e71cba217586 317 left.speed(-1);
cardenb 1:e71cba217586 318 right.speed(1);
cardenb 1:e71cba217586 319 while(!hasTurned) {
cardenb 1:e71cba217586 320 float cur_head = getHeading();
cardenb 1:e71cba217586 321 pc.printf("%0.2f, %0.2f\r\n", cur_head, angle);
cardenb 1:e71cba217586 322 if( cur_head > angle - 20 && cur_head < angle + 20 ) {
cardenb 1:e71cba217586 323 hasTurned = true;
cardenb 1:e71cba217586 324 heading[0] = cur_head;
cardenb 1:e71cba217586 325 left.stop(0);
cardenb 1:e71cba217586 326 right.stop(0);
cardenb 1:e71cba217586 327 }
cardenb 1:e71cba217586 328
cardenb 1:e71cba217586 329 }
cardenb 1:e71cba217586 330 }
cardenb 1:e71cba217586 331 void turnLeft() {
cardenb 1:e71cba217586 332 left.stop(0);
cardenb 1:e71cba217586 333 right.stop(0);
cardenb 1:e71cba217586 334 left.speed(1);
cardenb 1:e71cba217586 335 right.speed(-1);
cardenb 1:e71cba217586 336
cardenb 1:e71cba217586 337 /*
cardenb 1:e71cba217586 338 int turn_ang = heading[0] - 90;
cardenb 1:e71cba217586 339 turn_ang = turn_ang% 360;
cardenb 1:e71cba217586 340 turnToAngle(turn_ang);
cardenb 1:e71cba217586 341 */
cardenb 1:e71cba217586 342
cardenb 1:e71cba217586 343 wait(TURN_TIME);
cardenb 1:e71cba217586 344 headingReset();
awinata 0:98baffd99476 345 }
awinata 0:98baffd99476 346
awinata 0:98baffd99476 347 int main()
awinata 0:98baffd99476 348 {
awinata 0:98baffd99476 349 // initialization functions
awinata 0:98baffd99476 350 setup();
awinata 0:98baffd99476 351
awinata 0:98baffd99476 352 // variables
awinata 0:98baffd99476 353 dt = RATE;
awinata 0:98baffd99476 354 kp = Kp;
awinata 0:98baffd99476 355 ki = Ki;
awinata 0:98baffd99476 356 kd = Kd;
cardenb 1:e71cba217586 357
cardenb 1:e71cba217586 358 /*
cardenb 1:e71cba217586 359 // Attach the PID Update routine to a callback
cardenb 1:e71cba217586 360 pid_updater.attach(&updatePID, PID_RATE);
cardenb 1:e71cba217586 361 // Attach the IMU update routine to a callback to computer a moving average
cardenb 1:e71cba217586 362 imu_updater.attach(&updateIMU, IMU_RATE);
cardenb 1:e71cba217586 363 dist_updater.attach(&updateDist, DIST_RATE);
awinata 0:98baffd99476 364
cardenb 1:e71cba217586 365 */
cardenb 1:e71cba217586 366 // spin in the main loop. Updates will happen asychronously.
awinata 0:98baffd99476 367 while(1) {
awinata 0:98baffd99476 368 }
awinata 0:98baffd99476 369 }