forked from rihorn2/sumobots
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSumoCollisionDetect.ino
More file actions
471 lines (408 loc) · 12.8 KB
/
Copy pathSumoCollisionDetect.ino
File metadata and controls
471 lines (408 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
/* This example uses the accelerometer in the Zumo Shield's onboard LSM303DLHC with the LSM303 Library to
* detect contact with an adversary robot in the sumo ring.
*
* This example extends the BorderDetect example, which makes use of the onboard Zumo Reflectance Sensor Array
* and its associated library to detect the border of the sumo ring. It also illustrates the use of
* ZumoMotors, PushButton, and ZumoBuzzer.
*
* In loop(), the program reads the x and y components of acceleration (ignoring z), and detects a
* contact when the magnitude of the 3-period average of the x-y vector exceeds an empirically determined
* XY_ACCELERATION_THRESHOLD. On contact detection, the forward speed is increased to FULL_SPEED from
* the default SEARCH_SPEED, simulating a "fight or flight" response.
*
* The program attempts to detect contact only when the Zumo is going straight. When it is executing a
* turn at the sumo ring border, the turn itself generates an acceleration in the x-y plane, so the
* acceleration reading at that time is difficult to interpret for contact detection. Since the Zumo also
* accelerates forward out of a turn, the acceleration readings are also ignored for MIN_DELAY_AFTER_TURN
* milliseconds after completing a turn. To further avoid false positives, a MIN_DELAY_BETWEEN_CONTACTS is
* also specified.
*
* This example also contains the following enhancements:
*
* - uses the Zumo Buzzer library to play a sound effect ("charge" melody) at start of competition and
* whenever contact is made with an opposing robot
*
* - randomizes the turn angle on border detection, so that the Zumo executes a more effective search pattern
*
* - supports a FULL_SPEED_DURATION_LIMIT, allowing the robot to switch to a SUSTAINED_SPEED after a short
* period of forward movement at FULL_SPEED. In the example, both speeds are set to 400 (max), but this
* feature may be useful to prevent runoffs at the turns if the sumo ring surface is unusually smooth.
*
* - logging of accelerometer output to the serial monitor when LOG_SERIAL is #defined.
*
* This example also makes use of the public domain RunningAverage library from the Arduino website; the relevant
* code has been copied into this .ino file and does not need to be downloaded separately.
*/
#include <Wire.h>
#include <ZumoShield.h>
// #define LOG_SERIAL // write log output to serial port
#define LED 13
Pushbutton button(ZUMO_BUTTON); // pushbutton on pin 12
// Accelerometer Settings
#define RA_SIZE 3 // number of readings to include in running average of accelerometer readings
#define XY_ACCELERATION_THRESHOLD 2400 // for detection of contact (~16000 = magnitude of acceleration due to gravity)
// Reflectance Sensor Settings
#define NUM_SENSORS 6
unsigned int sensor_values[NUM_SENSORS];
// this might need to be tuned for different lighting conditions, surfaces, etc.
#define QTR_THRESHOLD 1500 // microseconds
ZumoReflectanceSensorArray sensors(QTR_NO_EMITTER_PIN);
// Motor Settings
ZumoMotors motors;
// these might need to be tuned for different motor types
#define REVERSE_SPEED 200 // 0 is stopped, 400 is full speed
#define TURN_SPEED 200
#define SEARCH_SPEED 200
#define SUSTAINED_SPEED 400 // switches to SUSTAINED_SPEED from FULL_SPEED after FULL_SPEED_DURATION_LIMIT ms
#define FULL_SPEED 400
#define STOP_DURATION 100 // ms
#define REVERSE_DURATION 200 // ms
#define TURN_DURATION 300 // ms
#define RIGHT 1
#define LEFT -1
enum ForwardSpeed { SearchSpeed, SustainedSpeed, FullSpeed };
ForwardSpeed _forwardSpeed; // current forward speed setting
unsigned long full_speed_start_time;
#define FULL_SPEED_DURATION_LIMIT 250 // ms
// Sound Effects
ZumoBuzzer buzzer;
const char sound_effect[] PROGMEM = "O4 T100 V15 L4 MS g12>c12>e12>G6>E12 ML>G2"; // "charge" melody
// use V0 to suppress sound effect; v15 for max volume
// Timing
unsigned long loop_start_time;
unsigned long last_turn_time;
unsigned long contact_made_time;
#define MIN_DELAY_AFTER_TURN 400 // ms = min delay before detecting contact event
#define MIN_DELAY_BETWEEN_CONTACTS 1000 // ms = min delay between detecting new contact event
// RunningAverage class
// based on RunningAverage library for Arduino
// source: https://playground.arduino.cc/Main/RunningAverage
template <typename T>
class RunningAverage
{
public:
RunningAverage(void);
RunningAverage(int);
~RunningAverage();
void clear();
void addValue(T);
T getAverage() const;
void fillValue(T, int);
protected:
int _size;
int _cnt;
int _idx;
T _sum;
T * _ar;
static T zero;
};
// Accelerometer Class -- extends the LSM303 Library to support reading and averaging the x-y acceleration
// vectors from the onboard LSM303DLHC accelerometer/magnetometer
class Accelerometer : public LSM303
{
typedef struct acc_data_xy
{
unsigned long timestamp;
int x;
int y;
float dir;
} acc_data_xy;
public:
Accelerometer() : ra_x(RA_SIZE), ra_y(RA_SIZE) {};
~Accelerometer() {};
void enable(void);
void getLogHeader(void);
void readAcceleration(unsigned long timestamp);
float len_xy() const;
float dir_xy() const;
int x_avg(void) const;
int y_avg(void) const;
long ss_xy_avg(void) const;
float dir_xy_avg(void) const;
private:
acc_data_xy last;
RunningAverage<int> ra_x;
RunningAverage<int> ra_y;
};
Accelerometer lsm303;
boolean in_contact; // set when accelerometer detects contact with opposing robot
// forward declaration
void setForwardSpeed(ForwardSpeed speed);
void setup()
{
// Initiate the Wire library and join the I2C bus as a master
Wire.begin();
// Initiate LSM303
lsm303.init();
lsm303.enable();
#ifdef LOG_SERIAL
Serial.begin(9600);
lsm303.getLogHeader();
#endif
randomSeed((unsigned int) millis());
// uncomment if necessary to correct motor directions
//motors.flipLeftMotor(true);
//motors.flipRightMotor(true);
pinMode(LED, HIGH);
buzzer.playMode(PLAY_AUTOMATIC);
waitForButtonAndCountDown(false);
}
void waitForButtonAndCountDown(bool restarting)
{
#ifdef LOG_SERIAL
Serial.print(restarting ? "Restarting Countdown" : "Starting Countdown");
Serial.println();
#endif
digitalWrite(LED, HIGH);
button.waitForButton();
digitalWrite(LED, LOW);
// play audible countdown
for (int i = 0; i < 3; i++)
{
delay(1000);
buzzer.playNote(NOTE_G(3), 50, 12);
}
delay(1000);
buzzer.playFromProgramSpace(sound_effect);
delay(1000);
// reset loop variables
in_contact = false; // 1 if contact made; 0 if no contact or contact lost
contact_made_time = 0;
last_turn_time = millis(); // prevents false contact detection on initial acceleration
_forwardSpeed = SearchSpeed;
full_speed_start_time = 0;
}
void loop()
{
if (button.isPressed())
{
// if button is pressed, stop and wait for another press to go again
motors.setSpeeds(0, 0);
button.waitForRelease();
waitForButtonAndCountDown(true);
}
loop_start_time = millis();
lsm303.readAcceleration(loop_start_time);
sensors.read(sensor_values);
if ((_forwardSpeed == FullSpeed) && (loop_start_time - full_speed_start_time > FULL_SPEED_DURATION_LIMIT))
{
setForwardSpeed(SustainedSpeed);
}
if (sensor_values[0] < QTR_THRESHOLD)
{
// if leftmost sensor detects line, reverse and turn to the right
turn(RIGHT, true);
}
else if (sensor_values[5] < QTR_THRESHOLD)
{
// if rightmost sensor detects line, reverse and turn to the left
turn(LEFT, true);
}
else // otherwise, go straight
{
if (check_for_contact()) on_contact_made();
int speed = getForwardSpeed();
motors.setSpeeds(speed, speed);
}
}
// execute turn
// direction: RIGHT or LEFT
// randomize: to improve searching
void turn(char direction, bool randomize)
{
#ifdef LOG_SERIAL
Serial.print("turning ...");
Serial.println();
#endif
// assume contact lost
on_contact_lost();
static unsigned int duration_increment = TURN_DURATION / 4;
// motors.setSpeeds(0,0);
// delay(STOP_DURATION);
motors.setSpeeds(-REVERSE_SPEED, -REVERSE_SPEED);
delay(REVERSE_DURATION);
motors.setSpeeds(TURN_SPEED * direction, -TURN_SPEED * direction);
delay(randomize ? TURN_DURATION + (random(8) - 2) * duration_increment : TURN_DURATION);
int speed = getForwardSpeed();
motors.setSpeeds(speed, speed);
last_turn_time = millis();
}
void setForwardSpeed(ForwardSpeed speed)
{
_forwardSpeed = speed;
if (speed == FullSpeed) full_speed_start_time = loop_start_time;
}
int getForwardSpeed()
{
int speed;
switch (_forwardSpeed)
{
case FullSpeed:
speed = FULL_SPEED;
break;
case SustainedSpeed:
speed = SUSTAINED_SPEED;
break;
default:
speed = SEARCH_SPEED;
break;
}
return speed;
}
// check for contact, but ignore readings immediately after turning or losing contact
bool check_for_contact()
{
static long threshold_squared = (long) XY_ACCELERATION_THRESHOLD * (long) XY_ACCELERATION_THRESHOLD;
return (lsm303.ss_xy_avg() > threshold_squared) && \
(loop_start_time - last_turn_time > MIN_DELAY_AFTER_TURN) && \
(loop_start_time - contact_made_time > MIN_DELAY_BETWEEN_CONTACTS);
}
// sound horn and accelerate on contact -- fight or flight
void on_contact_made()
{
#ifdef LOG_SERIAL
Serial.print("contact made");
Serial.println();
#endif
in_contact = true;
contact_made_time = loop_start_time;
setForwardSpeed(FullSpeed);
buzzer.playFromProgramSpace(sound_effect);
}
// reset forward speed
void on_contact_lost()
{
#ifdef LOG_SERIAL
Serial.print("contact lost");
Serial.println();
#endif
in_contact = false;
setForwardSpeed(SearchSpeed);
}
// class Accelerometer -- member function definitions
// enable accelerometer only
// to enable both accelerometer and magnetometer, call enableDefault() instead
void Accelerometer::enable(void)
{
// Enable Accelerometer
// 0x27 = 0b00100111
// Normal power mode, all axes enabled
writeAccReg(LSM303::CTRL_REG1_A, 0x27);
if (getDeviceType() == LSM303::device_DLHC)
writeAccReg(LSM303::CTRL_REG4_A, 0x08); // DLHC: enable high resolution mode
}
void Accelerometer::getLogHeader(void)
{
Serial.print("millis x y len dir | len_avg dir_avg | avg_len");
Serial.println();
}
void Accelerometer::readAcceleration(unsigned long timestamp)
{
readAcc();
if (a.x == last.x && a.y == last.y) return;
last.timestamp = timestamp;
last.x = a.x;
last.y = a.y;
ra_x.addValue(last.x);
ra_y.addValue(last.y);
#ifdef LOG_SERIAL
Serial.print(last.timestamp);
Serial.print(" ");
Serial.print(last.x);
Serial.print(" ");
Serial.print(last.y);
Serial.print(" ");
Serial.print(len_xy());
Serial.print(" ");
Serial.print(dir_xy());
Serial.print(" | ");
Serial.print(sqrt(static_cast<float>(ss_xy_avg())));
Serial.print(" ");
Serial.print(dir_xy_avg());
Serial.println();
#endif
}
float Accelerometer::len_xy() const
{
return sqrt(last.x*a.x + last.y*a.y);
}
float Accelerometer::dir_xy() const
{
return atan2(last.x, last.y) * 180.0 / M_PI;
}
int Accelerometer::x_avg(void) const
{
return ra_x.getAverage();
}
int Accelerometer::y_avg(void) const
{
return ra_y.getAverage();
}
long Accelerometer::ss_xy_avg(void) const
{
long x_avg_long = static_cast<long>(x_avg());
long y_avg_long = static_cast<long>(y_avg());
return x_avg_long*x_avg_long + y_avg_long*y_avg_long;
}
float Accelerometer::dir_xy_avg(void) const
{
return atan2(static_cast<float>(x_avg()), static_cast<float>(y_avg())) * 180.0 / M_PI;
}
// RunningAverage class
// based on RunningAverage library for Arduino
// source: https://playground.arduino.cc/Main/RunningAverage
// author: Rob.Tillart@gmail.com
// Released to the public domain
template <typename T>
T RunningAverage<T>::zero = static_cast<T>(0);
template <typename T>
RunningAverage<T>::RunningAverage(int n)
{
_size = n;
_ar = (T*) malloc(_size * sizeof(T));
clear();
}
template <typename T>
RunningAverage<T>::~RunningAverage()
{
free(_ar);
}
// resets all counters
template <typename T>
void RunningAverage<T>::clear()
{
_cnt = 0;
_idx = 0;
_sum = zero;
for (int i = 0; i< _size; i++) _ar[i] = zero; // needed to keep addValue simple
}
// adds a new value to the data-set
template <typename T>
void RunningAverage<T>::addValue(T f)
{
_sum -= _ar[_idx];
_ar[_idx] = f;
_sum += _ar[_idx];
_idx++;
if (_idx == _size) _idx = 0; // faster than %
if (_cnt < _size) _cnt++;
}
// returns the average of the data-set added so far
template <typename T>
T RunningAverage<T>::getAverage() const
{
if (_cnt == 0) return zero; // NaN ? math.h
return _sum / _cnt;
}
// fill the average with a value
// the param number determines how often value is added (weight)
// number should preferably be between 1 and size
template <typename T>
void RunningAverage<T>::fillValue(T value, int number)
{
clear();
for (int i = 0; i < number; i++)
{
addValue(value);
}
}