-
Notifications
You must be signed in to change notification settings - Fork 0
/
CamDo_LCD_GPS.ino
553 lines (447 loc) · 17.3 KB
/
CamDo_LCD_GPS.ino
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
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
/*
14th August 2017
CamDo Solutions Inc.
Arduino sketch to activate the GoPro camera based on the distance travelled
calculated from an attached GPS.
Requires the CamDo Blink time lapse controller, Arduino and external GPS module.
See cam-do.com/blog for details.
Uses the TinyGPS libary - make sure to use V1.3 or later.
as versions 1.22 and earlier gave lat/long in 100,000ths of a degree. V1.3 reports in millionths of a degree.
Careful not to use the TinyGPS++ library.
*/
#include <TinyGPS.h>
#include <LiquidCrystal.h>
/******** LCD driver pins ********
note these pins are different to the defaults in the Arduino LiquidCrystal library examples.
you may need to change back to the Arduino defaults depending on the model LCD 16x2 display you get.
**********************************/
// uncomment the following line if you are having trouble with your display (Arduino defaults)
// const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
// comment out the following line if you go back to the Arduino defaults above.
const int rs = 8, en = 9, d4 = 4, d5 = 5, d6 = 6, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7); // initialise the display library
/* LCD variable */
const int led = LED_BUILTIN; //typically pin13 but constant means we don't have to worry about that
boolean displayOn = 1;
int buttonVoltage = 0;
int buttonPressed = 0;
/* the backlight is controlled by PWM on D10. Therefore set D10 as a PWM output, then */
/*analogWrite (backLight, fadeValue); */
int backLight = 10;
int fadeValue = 125;
int noPhotos = 0; // stores the number of photos triggered
/* GPS parameters */
TinyGPS gps;
long lat, lon;
float fLat, fLon, otherfLat, otherfLon, fkmph = 0;
unsigned long fix_age, time, date, speed, course;
int year;
byte month, day, hour, minute, second, hundredths;
unsigned long chars;
unsigned short sentences, failed_checksum;
int distanceInMeters = 0;
float tempDistance = 0;
int triggerDistance = 50; // distance in metres between photos. Set to a low number for testing (50m). Can be changed here and also via LCD buttons (left and right)
const float pauseSpeed = 2.5; // in kmph - below which the distance measured is paused to help remove some of the GPS noise when standing still.
const long timeInterval = 5000; // 5 sec time interval for smoothing GPS data (milliseconds)
const long displayInterval = 1000; // 1 sec time to show each message on the LCD for before the next message rolls through
int deg;
int min1;
int min2;
/* CamDo Blink interface pins */
const int cameraTriggerPin = 2; // connected to digital pin 2
const int cameraStatusPin = 3; // connected to digital pin 3
int cameraState = LOW; // cameraState used to store camera status (on/off). Not being used in this project.
unsigned long previousMillis = 0; // will store last time update
unsigned long currentMillis = 0;
unsigned long previousMillis1 = 0; // will store last time distance was updated
unsigned long currentMillis1 = 0;
static const float SYDNEY_LAT = -33.80364, SYDNEY_LON = 151.289797;
boolean firstBoot;
/* Initialisation */
void setup()
{
pinMode(cameraTriggerPin, OUTPUT); // set cameraTriggerPin as an output
pinMode(cameraStatusPin, INPUT); // sets cameraStatusPin as an input
pinMode(led, OUTPUT); // sets the led as an output
Serial.begin(9600); // sets the GPS baud rate.
//updateGPSSettings(); // update refresh rate if desired - default is 1 Hz so not generally necessary
firstBoot = true;
tempDistance = 0;
previousMillis = millis(); // store current time for display timing
previousMillis1 = millis(); // store current time for GPS distance smooting
startLCD();
}
/*********************/
/* Main program loop */
/*********************/
void loop()
{
bool newData = false;
for (unsigned long start = millis(); millis() - start < 1000;)
{
while (Serial.available())
{
digitalWrite(led, HIGH);
int c = Serial.read(); // Read the GPS data
if (gps.encode(c)) // Did a new valid sentence come in?
{
newData = true;
}
}
}
digitalWrite(led, LOW);
if (newData)
{
gps.get_position(&lat, &lon, &fix_age); // retrieves +/- lat/long in millionths of a degree
gps.f_get_position(&fLat, &fLon, &fix_age); // the float version which helps for distance calcs
// note - make sure to be using V1.3 or greater of TinyGPS. Earlier versions return in 100,000th's of a degree.
gps.get_datetime(&date, &time, &fix_age); // time in hhmmsscc, date in ddmmyy
if (fix_age == TinyGPS::GPS_INVALID_AGE){ // check the data status
noGPSFix();
}
if (fix_age > 5000){
staleGPSData();
}
fkmph = gps.f_speed_kmph();
// Uncomment the following if you want to use the date / time parameters for anything
// Date/time cracking - convert the date time into easier to use variables.
// gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &fix_age);
// check if distance threshold has been met and trigger camera as appropriate
// update the time
currentMillis = millis();
// we check timing and display the messages on the LCD screen.
// need to have initial threshold around 3000 ms (ie 3 x displayInterval) to allow GPS time for processing in loop above.
// we measure the time and take an action rather than just setting a delay for each LCD message to avoid blocking the processor.
if ((currentMillis - previousMillis) <= 3*displayInterval) {
if (checkDistance()){
triggerCamera();
}
printDistancePhotos();
}
else if ((currentMillis - previousMillis) <= 4*displayInterval) {
printLat(); // update the LCD screen with lat/long position. Not required if you don't have an LCD.
printLon();
}
else if ((currentMillis - previousMillis) <= 5*displayInterval) {
printSatellitesSpeed(); // As above, have to balance update of info on screen with buttonpress responsiveness.
}
// keep adding else if statements in here and increase the displayInterval multiplier for each extra if statement.
else {
// we've dealt with all of the displays and reset our counter.
previousMillis = millis();
}
}
else {
noGPSFix();
}
// check if buttons pressed on LCD display and do something.
// see checkButtonPress() routine for explanations.
checkButtonPress();
} // end loop
/*******************************/
/******* Routines **************/
/*******************************/
// routine to update the UBLOX baud rate, etc. Should not have to use this.
// not called by default.
void updateGPSSettings(){
const char UBLOX_INIT[] PROGMEM = {
// Refer pg 14 / Chp 8 in manual for available settings.
// Reset to manufacturer defaults
0xB5,0x62,0x06,0x09,0x0D,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x07,0x1F,0x9E,
// Update Rate to 10 Hz
0xB5,0x62,0x06,0x08,0x06,0x00,0x64,0x00,0x01,0x00,0x01,0x00,0x7A,0x12
};
// write to GPS module
for(int i = 0; i < sizeof(UBLOX_INIT); i++){
Serial.write( pgm_read_byte(UBLOX_INIT+i) );
delay(5); // simulating a 38400baud pace (or less), otherwise commands are not accepted by the device.
}
}
/* Print the latitude */
void printLat(){
deg=lat/1000000;
min1=(lat/10000)%100;
min1=abs(min1); // take the absolute value since deg is signed. Note, do not combine mathematical operations inside abs() function.
min2=lat%10000;
min2 = abs(min2); // take the absolute value since deg is signed.
lcd.setCursor(0,0); // set the LCD cursor position
lcd.print("LAT:");
lcd.print(deg);
lcd.print(".");
lcd.print(min1);
lcd.print(min2);
lcd.write(0xDF); // write the degrees symbol
lcd.write(" ");
}
/* Print the longitude */
void printLon(){
deg=lon/1000000;
min1=lon/10000%100;
min1=abs(min1); // take the absolute value since deg is signed.
min2=lon%10000;
min2=abs(min2); // take the absolute value since deg is signed.
lcd.setCursor(0,1); // set the LCD cursor position
lcd.print("LON:");
lcd.print(deg);
lcd.print(".");
lcd.print(min1);
lcd.print(min2);
lcd.write(0xDF); // write the degrees symbol
lcd.write(" ");
}
/* report to the user there is no GPS fix */
void noGPSFix(){
lcd.setCursor(0,0); // set the LCD cursor position
lcd.print("Waiting: ");
lcd.setCursor(0,1); // set the LCD cursor position
lcd.print("No GPS fix yet! ");
}
/* Print number of satellites and current speed in km/h */
void printSatellitesSpeed(){
lcd.setCursor(0,0); // set the LCD cursor position
lcd.print("Satellites: ");
lcd.print(gps.satellites());
lcd.print(" ");
lcd.setCursor(0,1); // set the LCD cursor position
lcd.print("Speed km/h: ");
lcd.print(fkmph);
lcd.print(" ");
}
/* report to the user the GPS NMEA data may be stale */
void staleGPSData(){
lcd.setCursor(0,0); // set the LCD cursor position
lcd.print("Warning:Possible");
lcd.setCursor(0,1); // set the LCD cursor position
lcd.print("! Stale data !");
}
/* report the time in seconds since the system booted on the LCD */
void printTime () {
lcd.setCursor(0, 1);
// print the number of seconds since reset:
lcd.print(millis() / 1000);
}
/* report the current speed on the LCD */
void printSpeed () {
lcd.setCursor(0,0); // set the LCD cursor position
lcd.print("Speed km/h: ");
lcd.setCursor(0,1); // set the LCD cursor position
lcd.print(fkmph);
lcd.print(" ");
// other speed / position / altitude commands for reference
// returns +/- latitude/longitude in degrees
// gps.f_get_position(&fLat, &fLon, &fix_age);
// float falt = gps.f_altitude(); // +/- altitude in meters
// float fc = gps.f_course(); // course in degrees
// float fk = gps.f_speed_knots(); // speed in knots
// float fmph = gps.f_speed_mph(); // speed in miles/hr
// float fmps = gps.f_speed_mps(); // speed in m/sec
// float fkmph = gps.f_speed_kmph(); // speed in km/hr
}
/* check the distance travelled against our preset and see if we need to trigger the camera */
boolean checkDistance() {
// we need to check the distance travelled from previous trigger location
// it is actual distance travelled (recorded every 5 seconds), not line of sight.
// return true if threshold reached.
// check to see if we have just booted up and store coords.
if (firstBoot){
otherfLat = fLat;
otherfLon = fLon;
distanceInMeters = 0;
firstBoot = false;
}
// only update distance every few seconds to smooth out GPS a little.
// can be set in global variables at top of sketch (default 5 secs)
currentMillis1 = millis();
if ((currentMillis1 - previousMillis1) >= timeInterval) {
// save the new time interval
previousMillis1 = currentMillis1;
// gps.f_get_position(&fLat, &fLon, &fix_age); // the float version which helps for distance calcs
// update distance travelled
tempDistance = gps.distance_between(fLat, fLon, otherfLat, otherfLon);
// remove the spurious nature of GPS if standing still
// if speed less than the pause speed and distance less than 3m then skip
// can comment out the distance / speed check when testing so you can test on your bench without moving around!
if(tempDistance > 3 && fkmph > pauseSpeed)
{
distanceInMeters = distanceInMeters + tempDistance;
}
if (distanceInMeters >= triggerDistance) {
// reset distance travelled and update coords
distanceInMeters = 0;
otherfLat = fLat;
otherfLon = fLon;
return true;
}
else
return false;
}
else
return false;
}
/* display the distance travelled on the LCD*/
void printDistancePhotos (){
lcd.clear();
lcd.setCursor(0,0); // set the LCD cursor position
lcd.print("Dist(m): ");
lcd.print(distanceInMeters);
lcd.print(" ");
lcd.setCursor(0,1); // set the LCD cursor position
lcd.print("Photos: ");
lcd.print(noPhotos);
lcd.print(" ");
//delay(1000);
}
/* trigger the GoPro using CamDo Blink controller*/
void triggerCamera () {
digitalWrite(cameraTriggerPin, HIGH); // send a high signal to Blink to trigger the camera
delay(1000); // hold the trigger for 1000 milliseconds
digitalWrite(cameraTriggerPin, LOW); // send a low signal to Blink to finish trigger
cameraState = digitalRead(cameraStatusPin); // read the status of the camera from Blink. Not doing anything with it at this stage.
noPhotos++; // increment the number of photos taken
}
/* initialises the LCD and prints a welcome message */
void startLCD () {
lcd.begin(16, 2); // start the LCD library, setup with 16 x 2 characters
lcd.setCursor(0,0); // set the LCD cursor position (Column 0, Line 0)
lcd.print("CamDo Solutions"); // print to LCD
delay(200);
lcd.setCursor(0,1); // set the LCD cursor position (Column 0, Line 1 (second row))
char* welcome = "GPS PhotoTrigger";
for (int cnt = 0; cnt <= 16; cnt++) {
lcd.print(welcome[cnt]);
delay(100);
}
delay (2000);
lcd.clear();
}
/* check the integer length to help line text up on the LCD */
/* NOT BEING USED */
int checkIntLen(int someValue) {
int valLen = 0;
if(someValue > 9999)
valLen = 5;
else if(someValue > 999)
valLen = 4;
else if(someValue > 99)
valLen = 3;
else if(someValue > 9)
valLen = 2;
else
valLen = 1;
return valLen;
}
/* Check if one of the LCD buttons has been pressed and do something */
/* thanks to the DX.com forum for the outline */
/* http://club.dx.com/forums/Forums.dx/threadid.1194279 */
/* the backlight is controlled by PWM on D10 (var backLight). Set D10 as a PWM output, then */
/* analogWrite (backLight, fadeValue); */
void checkButtonPress() {
// pressing UP or DOWN will fade the display backLight up / down.
// pressing SELECT will turn the screen ON / OFF.
// pressing LEFT or RIGHT will increase or decrease the camera trigger distance.
// distance is in metres.
buttonVoltage = analogRead(0);
buttonPressed = buttonVoltage >> 7; //this removes least significant bits to get more stable reads.
switch (buttonPressed)
{
case 0: // RIGHT button.
//Increase triggerDistance by varying degrees depending on current setting.
lcd.setCursor(0,0);
lcd.print ("Distance(m): ");
lcd.setCursor(0,1);
lcd.print(triggerDistance);
lcd.print(" ");
delay(100);
if (triggerDistance >= 800)
{
triggerDistance = triggerDistance + 100;
}
else if(triggerDistance > 300)
{
triggerDistance = triggerDistance + 50;
}
else
{
triggerDistance = triggerDistance + 20; //Decrease triggerDistance by 20m at a time.
}
lcd.setCursor(0,0);
lcd.print ("Distance(m): ");
lcd.setCursor(0,1);
lcd.print(triggerDistance);
lcd.print(" ");
//lcd.scrollDisplayRight() ;
break;
case 1: // UP button
fadeValue = fadeValue +5;
if (fadeValue >= 254)
{
fadeValue = 255;
}
analogWrite (backLight, fadeValue);
break;
case 2: // DOWN button.
fadeValue = fadeValue -5;
if (fadeValue <= 1)
{
fadeValue = 0;
}
analogWrite (backLight, fadeValue);
break;
case 3: // LEFT button.
lcd.setCursor(0,0);
lcd.print ("Distance(m): ");
lcd.setCursor(0,1);
lcd.print(triggerDistance);
lcd.print(" ");
delay(100);
// make the distance scroll a bit quicker when numbers are larger
if (triggerDistance >= 800)
{
triggerDistance = triggerDistance - 100;
}
else if(triggerDistance > 300)
{
triggerDistance = triggerDistance - 50;
}
else
{
triggerDistance = triggerDistance - 20; //Decrease triggerDistance by 20m at a time.
}
lcd.setCursor(0,0);
lcd.print ("Distance(m): ");
lcd.setCursor(0,1);
if (triggerDistance <= 1)
{
triggerDistance = 0;
lcd.print("!! Disabled !!");
}
else
{
lcd.print(triggerDistance);
lcd.print(" ");
}
//lcd.scrollDisplayRight() ;
break;
case 5: // SELECT button
lcd.print ("Select");
if (displayOn)
{
displayOn=false;
lcd.noDisplay();
analogWrite (backLight, 0);
}
else
{
displayOn=true;
lcd.display();
analogWrite (backLight, fadeValue);
}
delay(500);
break;
case 7: // no button press
//lcd.print ("None ");
break;
}
delay(100); // delay for button press usability
}