Ostereier suchen mit GPS (GPS / GSM SIM 808) - [Teil 2]

Part 2

in the first part of the blog series we supported the Easter bunny so that he can store the Easter eggs into exciting hiding, which we will only find with technical support. We had it equipped with a GPS receiver. Hiding and reading the position was a light game, because the values ​​were shown on our display and only had to be noted.

The search shows us this time with bigger challenges, because first we must determine our current position and then calculate course and distance to the destination. This requires a degree of mathematics knowledge, but in which we can make some simplifications in Germany or Central Europe due to short distances.

What do we need? As for the first part:

Number Component
1 Micro Controller, z.B. den Uno R3 kompatiblen MC
1 LCD, z.B. das LCD-Keypad-Shield
1 GPS/GSM Modul SIM 808 (im dritten Teil mit Sim-Karte)
Akku oder Batterie


First, we make a thought trial and again grab the picture of the shape of an orange similar to our earth. We peel the orange and try to push a great bit of shell  on the table. The result is known: The shell will tear in some places. But the smaller the piece of shell is, the better it will succeed and you will not notice the difference or hardly notice. We use this circumstance to the following considerations. Instead of calculating with the formulas for the large-scale navigation, which come from the spherical trigonometry (earth ball), I hereby deduce formulas that are enough accurate.

But completely without triangular calculations (trigonometry is the Greek word for Triangle measurement)  with Mr. Pythagoras, the angle functions sin, cos and tan and a little bit of theorem of intersecting lines we do not come out. Short repetition: We draw a unit circuit (radius = 1) in the Cartesian coordinate system (x, y-axis) and a straight line from the center to a point in the circle. In mathematics we count (unlike navigation) the angle (alpha) counterclockwise from the X-axis. In the next step, we draw a rectangular triangle with our straight line as a hypotenuse and the legs of the triangle each parallel to the axes. It is:

Set of Pythagoras: H² = A² + G² with H = Hypotenuse, A = adjacent side and G = opposite leg

sin = G / H        cos = A / H      tan  = G / A     and    tan = G '/ A '

At the unit circle, the length of H and A ' is 1. So we can take the respective values ​​for the angle functions directly from the drawing (e.g., on a circle with 10 cm radius).

It can be seen that the values ​​for sinus and cosine can be between -1 and +1, for tangents, the values ​​at angles less than 45 ° smaller than 1, above 45 ° greater than 1 and (beware :) at 90 ° and 270 ° Infinite. We have to consider that later.


We have to consider something further: There is not only the unit degree in the mathematics for the description of the angle. The circular arc from the point (1.0) on the X-axis to the point P is also a common value. The conversion is easy: The unit circle has a circumference of 2πr with r = 1, ie 2π corresponds to 360 °, reduced the conversion factor π / 180. In our programming language, it was determined that the calculation of the angle functions is based on radians instead of degrees. So if we need the cosine of the geographic width, we have to multiply the angle with π and divide by 180. If we then calculate the angle with the reverse function of tangent, from which we determine the course, we must multiply the value with 180 and divide by π.

We sum up: The earth is not a slice,

But a unwound cylinder coat (at least our map with Mercator projection). We can simplify our calculations because we determine in the temperate latitudes and course and distance for relatively short distances between the start point and destination , where the differences between Orthodrome (large circle) and Loxodrome (rhumb line) are negligible.

We only need the respective angular differences in geographic positions; In the program, we call these deltaPhi for the difference in the latitude and deltaLambda for the difference in longitude. From this we calculate the legs of our auxiliary triangle, because we need length units. This is easy with deltaPhi. From the first part we know that an arc minute on the meridian / large circle = 1 nautical mile = 1.852 km. With this multiplier, our opposite leg (Deltay) is calculated quickly. We also remember that the distance of the meridians becomes narrower towards the poles. Therefore, as a further multiplier, the cosine of the middle latitude, simplified cos of the starting point's latitude, ie deltaX = deltaPhi (MM) * 1,852 * cos (PHI).

With deltaY and deltaX I can now calculate the distance and the mathematical angle between our start point and the destination. The distance is the beeline, so certainly shorter than the way in the outdoors. And in the course calculation we have to convert the  above the calculated math. angle to the course angle. This is very simple: TCourse = 90 -alpha,  with alpha = 180 * atan2 (deltaY, deltaX) / π. The course should be between 000 and 360, so if necessary 360 has to be added or subtracted. By the way, for the ARCUS Tangens function, we need the library  math.h

In the sketch from the first part we had the very left button of our LCD-Keypad-shield was only used to display the text AZ-Delivery.com. We now take this button for the calculation and display of course and distance to the waypoints. As an example, I have inserted five waypoints in the sketch. With the SELECT button the display will then "be toggled" through the respective waypoints. Course and distance are always calculated from the current position to the respective waypoint that is selected via the index.

In order to use the sketch for the Easter egg search, you only need the coordinates mentioned by the Easter Bunny in the lines

FLOAT LATWP [] = {5354.0000,4850.0000,5231.0000,5237.0000,5541.0000};

FLOAT LONWP [] = {952.0000,1258.0000,1324.0000,973.0000,1235.0000};

Enter and set the number of waypoints at WayPointMax = 5.

The sketch:

 /****************************************************************************
GPS with LCD display
Based on library dfrobot_sim808 and example SIM808_GETGPS by jason.lung@dfrobot.com
Adapted for Uno, Nano and compatible McUS and LCD1602 Keypad Shield
Offset for mez = 1 hour, possibly customize for mesz -> offset = 2
Time and day will be adjusted shortly after midnight, but month and year not.

Connections:
TX of the GPS connect to A3 = GPIO 17 of the LCD Keypad
RX of the GPS connect to A4 = GPIO 18 of the LCD Keypad

The umlauts of the texts were replaced by the Escape sequences.
LCD.Print ("\ XE1"); // gives an Ä
LCD.Print ("\ XEF"); // gives an Ö
LCD.Print ("\ XF5"); // gives a Ü
lcd.print ("\ xe2"); // gives a ß
lcd.print ("\ xdf"); // outputs a °
lcd.print ("\ x22"); // gives an "out
lcd.print ("\ xe4"); // outputs a μ
lcd.print ("\ xf4"); // outputs an Ω
*****************************************************************************/

#include
#include
#define pin_tx 17
#define pin_rx 18

// LCD HAS NO I2C Adapter, Data Transfer With Pins D4 to D7
#include
// LCD Pin to Arduino
// const int pin_bl = 15;
const int pin_en = 9;
const int pin_rs = 8;
const int pin_d4 = 4;
const int pin_d5 = 5;
const int pin_d6 = 6;
const int pin_d7 = 7;

LiquidCrystal LCD (PIN_RS, PIN_EN, PIN_D4, PIN_D5, PIN_D6, PIN_D7);

// Offset for Time, here UTC zu MEZ / MESZ
// Summertime MESZ: 2, Wintertime MEZ: 1
#define Offset 2

SoftwareSerial mySerial(PIN_TX,PIN_RX);
DFRobot_SIM808 sim808(&mySerial); //Connect RX,TX,PWR

// unterbrechungsfreie Zeitsteuerung
unsigned long previousMillis = 0;
const long interval = 1000;

int MONTH = 0;
int DAY = 0;
int DAYLCL = 0;
int HOUR = 0;
int HOURLCL = 0;
int MINUTE = 0;
int SECOND = 0;
float LAT = 0.0;
int LATDD = 0;
float LATMMmmmm = 0.0;
int LATMM = 0;
int LATDDWP = 0;
float LATMMmmmmWP = 0.0;
float LATSSs = 0.0;
float deltaPhi = 0.0;
float deltaY = 0.0;
float LON = 0.0;
int LONDDD = 0;
float LONMMmmmm = 0.0;
int LONMM = 0;
int LONDDWP = 0;
float LONMMmmmmWP = 0.0;
float LONSSs = 0.0;
float deltaLambda = 0.0;
float deltaX = 0.0;
float SPEED = 0.0;

float LATWP[] = {5354.0000,4850.0000,5231.0000,5237.0000,5541.0000};
float LONWP[] = {952.0000,1258.0000,1324.0000,973.0000,1235.0000};
int waypointIndex = 0;
int waypointMAX = 5;

char dirSN = 'N';
char dirEW = 'E';
int tCourse;
float distance;

// Buttons
int buttonInput = -1;
int buttonSelect = 0;
int buttonInput_old = 0;
bool buttonHold = false;

void setup() {
mySerial.begin(9600);
Serial.begin(9600);
lcd.begin(16,2); // initialize the lcd
lcd.clear();
lcd.setCursor(0,0); //Zählung beginnt bei Null, erst Zeichen, dann Zeile
lcd.print("AZ-Delivery.com");
lcd.setCursor(0,1); // 0=Erstes Zeichen, 1=zweite Zeile

//******** Initialize sim808 module *************
while(!sim808.init()) {
delay(1000);
Serial.print("Sim808 init error\r\n");
  }

//************* Turn on the GPS power************
if( sim808.attachGPS())
Serial.println("Open the GPS power success");
else
Serial.println("Open the GPS power failure");
}

void loop() {

buttonInput = Button();
// Button muss losgelassen werden
if (!buttonHold) {
buttonInput_old = buttonInput;
if (buttonInput==4) waypointIndex+=1;
buttonHold = true;
  }
switch (buttonInput) {
case 0: Serial.println("0");buttonSelect=0; break;
case 1: Serial.println("1");buttonSelect=1; break;
case 2: Serial.println("2");buttonSelect=2; break;
case 3: Serial.println("3");buttonSelect=3; break;
case 4: Serial.println("4");

if (waypointIndex>waypointMAX-1) waypointIndex=0;
buttonSelect=4;
Serial.print("waypointIndex ");
Serial.println(waypointIndex); break;
default: break;
  }
//Button loslassen pruefen
if (buttonInput != buttonInput_old) {
buttonHold = false;
  }


if (millis() - previousMillis >= interval) {
//************** Get GPS data *******************
if (sim808.getGPS()) {
MONTH = sim808.GPSdata.month;
DAY = sim808.GPSdata.day;
DAYLCL = DAY;
HOUR = sim808.GPSdata.hour;
HOURLCL = HOUR + Offset;
MINUTE = sim808.GPSdata.minute;
SECOND = sim808.GPSdata.second;
if (HOURLCL>24) {
HOURLCL = HOURLCL-24;
DAYLCL = DAYLCL + 1; }
Serial.print(sim808.GPSdata.year);
Serial.print("/");
Serial.print(MONTH);
Serial.print("/");
Serial.print(DAY);
Serial.print(" ");
Serial.print(HOUR);
Serial.print(":");
Serial.print(MINUTE);
Serial.print(":");
Serial.println(SECOND);
LAT = sim808.GPSdata.lat;
LATDD = int(LAT);
LATMMmmmm = (LAT - LATDD)*100;
LATDDWP = int(LATWP[waypointIndex]/100);
LATMMmmmmWP = LATWP[waypointIndex] - 100*LATDDWP;
deltaPhi = 60*(LATDDWP - LATDD) + (LATMMmmmmWP - LATMMmmmm);
deltaY = deltaPhi * 1.852;
Serial.print("deltaPhi = ");
Serial.println(deltaPhi);
Serial.println(deltaY);

LON = sim808.GPSdata.lon;
LONDDD = int(LON);
LONMMmmmm = (LON - LONDDD)*100;
LONDDWP = int(LONWP[waypointIndex]/100);
LONMMmmmmWP = LONWP[waypointIndex] - 100*LONDDWP;
deltaLambda = 60*(LONDDWP - LONDDD) + (LONMMmmmmWP - LONMMmmmm);
deltaX = deltaLambda * 1.852 * cos(DEG_TO_RAD * LAT);
Serial.print("deltaLambda = ");
Serial.println(deltaLambda);
Serial.println(deltaX);
Serial.print("latitude: ");
Serial.print(LATDD);
Serial.print("°");
Serial.print(LATMMmmmm,4);
Serial.println("'N");
Serial.print("longitude: ");
if (LON<100.0) Serial.print("0");
if (LON<10.0) Serial.print("0");
Serial.print(LONDDD);
Serial.print("°");
Serial.print(LONMMmmmm,4);
Serial.println("'E");

if (deltaX>0) {
dirEW = "E";
Serial.println(dirEW);
    }

else if (deltaLambda<0) {
dirEW = "W";
Serial.println(dirEW);
    }

else {Serial.print("NSNS"); }
Serial.print("LON ");
Serial.println(LON);
Serial.print("LONDDD ");
Serial.println(LONDDD);
Serial.print("LONWP");
Serial.print(LONWP[waypointIndex]);
Serial.println("");
Serial.print("latitude: ");
Serial.print(LATDD);
Serial.print("°");
Serial.print(LATMMmmmm,4);
Serial.println("'N");

Serial.print("longitude: ");
if (LON<100.0) Serial.print("0");
if (LON<10.0) Serial.print("0");
Serial.print(LONDDD);
Serial.print("°");
Serial.print(LONMMmmmm,4);
Serial.println("'E");

if (abs(deltaY)<0.5) {
if (deltaX < 0.1 && deltaX > -0.1) {
tCourse = 1359;
distance = 0;
      }
else if (deltaX > 0) {
tCourse = 90;
distance = deltaX;
      }
else if (deltaX < 0) {
tCourse = 270;
distance = - deltaX;
      }
    }
 
else if (deltaY>0) {
if (deltaX < 0.1 && deltaX > -0.1) {
tCourse = 360;
distance = deltaY;
      }
else if (deltaX > 0) {
tCourse = (90 - RAD_TO_DEG*atan2(deltaY,deltaX));
distance = sqrt(square(deltaX) + square(deltaY));
      }
else if (deltaX < 0) {
tCourse = (90 - RAD_TO_DEG*atan2(deltaY,deltaX));
distance = sqrt(square(deltaX) + square(deltaY));
      }
    }
else if (deltaY<0) {
if (deltaX < 0.1 && deltaX > -0.1) {
tCourse = 180;
distance = -deltaY;
      }
else if (deltaX > 0) {
tCourse = (90 - RAD_TO_DEG*atan2(deltaY,deltaX));
distance = sqrt(square(deltaX) + square(deltaY));
      }
else if (deltaX < 0) {
tCourse = (90 - RAD_TO_DEG*atan2(deltaY,deltaX));
distance = sqrt(square(deltaX) + square(deltaY));
      }
    }
if (tCourse < 0) tCourse = tCourse+360;
if (tCourse > 360) tCourse = tCourse-360;
Serial.print("True Course = ");
Serial.println(tCourse);
Serial.print("Distance = ");
Serial.println(distance);




SPEED = sim808.GPSdata.speed_kph;
if (SPEED >= 3.0) {
Serial.print("speed_kph: ");
Serial.println(SPEED);
Serial.print("heading: ");
Serial.println(sim808.GPSdata.heading); }
else {
Serial.print("speed_kph :");
Serial.println("below 3");
Serial.print("heading :");
Serial.println("not determined"); }

if (buttonSelect==0) {
lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(LATDD);
      lcd.print("\xDF");
      LATMM = int(LATMMmmmm);
      LATSSs = (LATMMmmmm-LATMM)*60;
      lcd.print(LATMM);
      lcd.print("'");
      lcd.print(LATSSs,1);
      lcd.print("\x22\ N");
      lcd.setCursor(0,1);
      if (LON<100.0) lcd.print("0");
      if (LON<10.0) lcd.print("0");
      lcd.print(LONDDD);
      lcd.print("\xDF");
      LONMM = int(LONMMmmmm);
      LONSSs = (LONMMmmmm-LONMM)*60;
      if (LONMM<10) lcd.print("0");
      lcd.print(LONMM);
      lcd.print("'");
      if (LONSSs<10.0) lcd.print("0");
      lcd.print(LONSSs,1);
      lcd.print("\x22\ E"); }

else if (buttonSelect==1) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("EUR: ");
      if (DAYLCL<10) lcd.print("0");
      lcd.print(DAYLCL);
      lcd.print(".");
      if (MONTH<10) lcd.print("0");
      lcd.print(MONTH);
      lcd.print(".");
      lcd.print(sim808.GPSdata.year);
      lcd.setCursor(5,1);
      if (HOURLCL<10) lcd.print("0");
      lcd.print(HOURLCL);
      lcd.print(":");
      if (MINUTE<10) lcd.print("0");
      lcd.print(MINUTE);
      lcd.print(":");
      if (SECOND<10) lcd.print("0");
      lcd.print(SECOND);
    }
    else if (buttonSelect==2) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("UTC: ");
      lcd.print(sim808.GPSdata.year);
      lcd.print("/");
      if (MONTH<10) lcd.print("0");
      lcd.print(MONTH);
      lcd.print("/");
      if (DAY<10) lcd.print("0");
      lcd.print(DAY);
      lcd.setCursor(5,1);
      if (HOUR<10) lcd.print("0");
      lcd.print(HOUR);
      lcd.print(":");
      if (MINUTE<10) lcd.print("0");
      lcd.print(MINUTE);
      lcd.print(":");
      if (SECOND<10) lcd.print("0");
      lcd.print(SECOND);
    }
    else if (buttonSelect==3) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(LATDD);
      lcd.print("\xDF");
      lcd.print(LATMMmmmm,4);
      lcd.print("' N");
      lcd.setCursor(0,1);
      if (LON<100.0) lcd.print("0");
      if (LON<10.0) lcd.print("0");
      lcd.print(LONDDD);
      lcd.print("\xDF");
      if (LONMMmmmm<10.0) lcd.print("0");
      lcd.print(LONMMmmmm,4);
      lcd.print("' E"); }
      else if (buttonSelect==4) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("WP");
      lcd.print(waypointIndex);
      lcd.setCursor(5,0);
      lcd.print("Kurs ");
      lcd.print(tCourse);
      lcd.setCursor(0,1);
      lcd.print("Distanz ");
      lcd.print(distance);
    }
  //
// //************* Turn off the GPS power ************
// sim808.detachGPS();
previousMillis = millis();
    }
  }
}

int Button() {
int A0;
// all buttons are connected to A0 via voltage divider
// Values of ADC are between 0 and 1023
// if necessary, values must be changed slightly
A0 = analogRead(0); //
if (A0 < 60) {
return 0;
  }
else if (A0 >= 60 && A0 < 250) {
return 1;
  }
else if (A0 >= 250 && A0 < 450){
return 2;
  }
else if (A0 >= 450 && A0 < 700){
return 3;
  }
else if (A0 >= 700 && A0 < 900){
return 4;
  }
else {
buttonHold = false;
return -1;
  }
} //end Button()


Now the Easter egg hunt with GPS shouldn't cause any more problems. We wish you beautiful and sunny Easter holidays in the great outdoors.


AmateurfunkDisplaysFor arduinoProjekte für fortgeschrittene

6 comments

Andreas S.

Andreas S.

Nach fundierter Hilfe bei der Fehleranalyse von AZ habe ich unkompliziert ein neues Modul bekommen, mit dem funktioniert nun alles einwandfrei.
Damit kann ich auch noch einen kleinen Nachtrag zu Teil1 liefern:
Wenn man anstelle von AT+CGNSTST=1 den Befehl AT+CGNSINF schickt, bekommt man anstelle den Rohdaten die bereits ausgewerteten Werte (einmalig) zurück.
Und mit AT+CGNSURC=5 bekommt man die ausgewerteten Daten im 5-Sekunden-Takt ( Den Takt kann man anpassen, ein Wert von 0 schaltet das wieder ab).
Das Modul bietet übrigens auch eine Bluetooth-Schnittstelle. Ich freue mich schon darauf, wenn die in einem eigenen Blog-Beitrag auch einmal aufgegriffen wird.

Bernd Albrecht

Bernd Albrecht

@ Carlo M.
Der Link für den Download des Sketches befindet sich in der Überschrift „Der Sketch“, die ich auf Ihren Kommentar hin in „Download Sketch“ geändert habe. Download des Beitrags als pdf habe ich am Ende eingefügt.
@ Ingo
Der Sonderfall „kurze Distanzen“ wurde eingefügt, um 1. eine Division durch 0 zu verhindern, und 2. „springende Werte“ für den Kurs aufgrund der Positionsungenauigkeit von nicht-militärischen GPS-Empfängern zu verhindern. Also Anzeige Kurs=999 und Distanz=0 bedeutet Nahbereich. Der Wert in der if-Anfrage kann selbstverständlich etwas kleiner (z.B. 0.02) gewählt werden. Auf den Sonderfall verzichten sollte man aber nicht.

Ingo

Ingo

Hallo Zusammen,
Wenn ich mich nicht total falsch liege (und ich habe es gerade getestet) wird mit dem Code das Eiersuchen echt zu einer Herausvorderung.
Durch die Codezeilen:
if (abs(deltaY)<0.5) {
if (deltaX < 0.1 && deltaX > -0.1) {
tCourse = 1359;
distance = 0;
}
Wird alles was Delta Y kleiner 100m und Delta Y kleiner 100 Meter auf ungültigen Winkel und Distanz 0 gesetzt. Dadurch gebit sich ein Bereich von 200m x200mum die Zielkoordinaten in dem das GPS nicht die Distanz und Richtung ausgiebt.
Hat das mal jemand getestet?
Gruß Ingo

Carlo M.

Carlo M.

An sich ein guter Beitrag, bei der Länge des Sketches aber recht verwirrend. Besonders am Anfang bei den Erklärungen ging es recht schnell zu. Ich vermisse ein Downloadangebot für den Sketch und den Artikel selbst, wie beim ersten Teil.

Andreas S.

Andreas S.

Interessanter Beitrag, nur leider scheitere ich bereits beim Versuch, das Modul mit einem Terminalprogramm anzusprechen. Es gab weder GPS-Ausgaben, noch reagierte es auf AT-Kommandos bei 6900 oder bei 34800 Baud. (GND, TX und RX an Serial/USB-Konverter (z.B. den FT232), Stromversorgung über die Extra Buchse angeschlossen, Stromschalter an und auch Starttaste betätigt).
Muss man das Modul noch extra initialisieren ?
Etwas ungewöhnlich ist, dass auf der Steckerleiste laut Beschriftung über Kreuz je 2 Rx und Tx Anschlüsse vorhanden sind. Vielleicht würde ein funktionierendes Anschlussbild Unsicherheiten beseitigen.
Aus welchem Grund verwendet das Projekt nicht wie oft gesehen die digitalen Ports D7 und D8 für das Software Serial, sondern die Analogpins ?
Frohe Ostern
Andreas

Alain Tanguy

Alain Tanguy

Dans la version française, le programme C pour Arduino est traduit en français même les mots réservés du langage C. Pour accéder à la version originale allemande supprimer “/fr” dans le lien de la page française :
https://www.az-delivery.de/fr/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/ostereier…etc.

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. Install ESP32 now from the board manager
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP programming via WLAN