Skip to content

Commit 6c196a6

Browse files
committed
Conversion between LLA and ECEF. New example sketch to set survey fixed mode using LLA.
1 parent b722f4b commit 6c196a6

File tree

6 files changed

+205
-1
lines changed

6 files changed

+205
-1
lines changed

USERGUIDE.md

+6
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,12 @@ This library provides an interface for controlling and configuring an LG290P GNS
335335
- **`uint32_t getEcefAgeMs();`**
336336
Retrieves the age of the last RTCM 1005 report in milliseconds.
337337

338+
- **`void geodeticToEcef(double lat, double lon, double alt, double &xOut, double &yOut, double &zOut)`**
339+
Converts the geodetic position coordinate given by (lat/long/altitude) into an ECEF format coordinate
340+
341+
- **`void ecefToGeodetic(double x, double y, double z, double &latOut, double &lonOut, double &altOut);`**
342+
Converts the ECEF position coordinate given by (x, y, z) into a geodetic (lat/long/altitude) format
343+
338344
## EPE Error Domain
339345

340346
- **`double getNorthError();`**

examples/15.Survey.Fixed.Mode/15.Survey.Fixed.Mode.ino

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ void setup()
6060
verify(myGNSS.setModeBase(false)); // don't reset, because setSurveyInMode() is going to do it
6161

6262
Serial.println("Setting 'Survey' Mode fixed to top of Eiffel Tower");
63-
verify(myGNSS.setSurveyFixedMode(4200944.016, 168364.025, 4780802.825));
63+
double eiffelEcefX = 4201152.7587, eiffelEcefY = 168331.7945, eiffelEcefZ = 4780461.5607;
64+
verify(myGNSS.setSurveyFixedMode(eiffelEcefX, eiffelEcefY, eiffelEcefZ));
6465
if (!myGNSS.isConnected())
6566
{
6667
Serial.println("reconnection failed; halting");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Setting "Survey Fixed" Mode
3+
By: Nathan Seidle + Mikal Hart
4+
SparkFun Electronics
5+
Date: 29 September 2024
6+
License: MIT. Please see LICENSE.md for more information.
7+
8+
This example shows how enable the fixed survey mode, where you define the BASE station's
9+
location by supplying setSurveyFixedMode with Latitude/Longitude/Altitude coordinates.
10+
11+
These examples are targeted for an ESP32 platform but any platform that has multiple
12+
serial UARTs should be compatible.
13+
14+
Feel like supporting open source hardware?
15+
Buy a board from SparkFun!
16+
SparkFun Quadband GNSS RTK Breakout - LG290P (GPS-26620) https://www.sparkfun.com/products/26620
17+
18+
Hardware Connections:
19+
Connect RX3 (green wire) of the LG290P to pin 14 on the ESP32
20+
Connect TX3 (orange wire) of the LG290P to pin 13 on the ESP32
21+
To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240
22+
Note: Almost any ESP32 pins can be used for serial.
23+
Connect a multi-band GNSS antenna: https://www.sparkfun.com/products/21801
24+
*/
25+
26+
#include <SparkFun_LG290P_GNSS.h> // Click here to get the library: http://librarymanager/All#SparkFun_LG290P
27+
28+
// Adjust these values according to your configuration
29+
int pin_UART1_TX = 14;
30+
int pin_UART1_RX = 13;
31+
int gnss_baud = 460800;
32+
33+
LG290P myGNSS;
34+
HardwareSerial SerialGNSS(1); // Use UART1 on the ESP32
35+
int mode;
36+
37+
void setup()
38+
{
39+
Serial.begin(115200);
40+
delay(250);
41+
Serial.println();
42+
Serial.println("SparkFun Survey In Mode example");
43+
Serial.println("Initializing device...");
44+
45+
// We must start the serial port before using it in the library
46+
// Increase buffer size to handle high baud rate streams
47+
SerialGNSS.setRxBufferSize(1024);
48+
SerialGNSS.begin(gnss_baud, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX);
49+
50+
// myGNSS.enableDebugging(Serial); // Print all debug to Serial
51+
if (myGNSS.begin(SerialGNSS) == false) // Give the serial port over to the library
52+
{
53+
Serial.println("LG290P failed to respond. Check ports and baud rates. Freezing...");
54+
while (true);
55+
}
56+
Serial.println("LG290P detected!");
57+
58+
// You MUST be in BASE mode to set Survey In Mode
59+
Serial.println("Setting base station mode");
60+
verify(myGNSS.setModeBase(false)); // don't reset, because setSurveyInMode() is going to do it
61+
62+
double eiffelLat = 48.8584, eiffelLong = 2.2945, eiffelAlt = 365;
63+
double ecefX, ecefY, ecefZ;
64+
65+
// Convert Eiffel Tower lat/long/alt to ECEF coordinates
66+
LG290P::geodeticToEcef(eiffelLat, eiffelLong, eiffelAlt, ecefX, ecefY, ecefZ);
67+
68+
Serial.println("Setting 'Survey' Mode fixed to top of Eiffel Tower");
69+
Serial.printf("(%.3f,%.3f,%.3f)\r\n", ecefX, ecefY, ecefZ);
70+
verify(myGNSS.setSurveyFixedMode(ecefX, ecefY, ecefZ));
71+
if (!myGNSS.isConnected())
72+
{
73+
Serial.println("reconnection failed; halting");
74+
while (true);
75+
}
76+
Serial.println("Online!");
77+
78+
verify(myGNSS.getMode(mode));
79+
}
80+
81+
unsigned long lastUpdate = 0;
82+
83+
void loop()
84+
{
85+
myGNSS.update(); // Regularly call to parse any new data
86+
if (millis() - lastUpdate >= 1000)
87+
{
88+
lastUpdate = millis();
89+
int svinStatus = myGNSS.getSurveyInStatus();
90+
const char *status = svinStatus == 1 ? "In Progress" : svinStatus == 2 ? "Valid" : "Unknown/Invalid";
91+
Serial.printf("%02d:%02d:%02d: Mode = '%s' Status = '%s' (%d/%d) (%.4f,%.4f,%.4f) Mean Accuracy = %.4f\r\n",
92+
myGNSS.getHour(), myGNSS.getMinute(), myGNSS.getSecond(), mode == 2 ? "BASE" : "ROVER", status,
93+
myGNSS.getSurveyInObservations(), myGNSS.getSurveyInCfgDuration(),
94+
myGNSS.getSurveyInMeanX(), myGNSS.getSurveyInMeanY(), myGNSS.getSurveyInMeanZ(),
95+
myGNSS.getSurveyInMeanAccuracy());
96+
}
97+
}
98+
99+
void verify(bool event)
100+
{
101+
if (!event)
102+
{
103+
Serial.println("Operation failed: freezing!");
104+
while (true);
105+
}
106+
}

keywords.txt

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ getEcefZ KEYWORD2
127127

128128
getGeodeticAgeMs KEYWORD2
129129
getEcefAgeMs KEYWORD2
130+
geodeticToEcef KEYWORD2
131+
ecefToGeodetic KEYWORD2
130132

131133
getNorthError KEYWORD2
132134
getEastError KEYWORD2

src/SparkFun_LG290P_GNSS.cpp

+80
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,86 @@ uint32_t LG290P::getGeodeticAgeMs()
13421342
return millis() - lastUpdatePvtDomain;
13431343
}
13441344

1345+
/* static */
1346+
void LG290P::geodeticToEcef(double lat, double lon, double alt, double &xOut, double &yOut, double &zOut)
1347+
{
1348+
const double WGS84_A = 6378137; // https://geographiclib.sourceforge.io/html/Constants_8hpp_source.html
1349+
const double WGS84_E = 0.081819190842622; // http://docs.ros.org/en/hydro/api/gps_common/html/namespacegps__common.html
1350+
// and https://gist.github.com/uhho/63750c4b54c7f90f37f958cc8af0c718
1351+
double clat = cos(lat * DEG_TO_RAD);
1352+
double slat = sin(lat * DEG_TO_RAD);
1353+
double clon = cos(lon * DEG_TO_RAD);
1354+
double slon = sin(lon * DEG_TO_RAD);
1355+
1356+
double N = WGS84_A / sqrt(1.0 - WGS84_E * WGS84_E * slat * slat);
1357+
1358+
xOut = (N + alt) * clat * clon;
1359+
yOut = (N + alt) * clat * slon;
1360+
zOut = (N * (1.0 - WGS84_E * WGS84_E) + alt) * slat;
1361+
}
1362+
1363+
// Convert ECEF to LLH (geodetic)
1364+
// From: https://danceswithcode.net/engineeringnotes/geodetic_to_ecef/geodetic_to_ecef.html
1365+
/* static */
1366+
void LG290P::ecefToGeodetic(double x, double y, double z, double &latOut, double &lonOut, double &altOut)
1367+
{
1368+
double a = 6378137.0; // WGS-84 semi-major axis
1369+
double e2 = 6.6943799901377997e-3; // WGS-84 first eccentricity squared
1370+
double a1 = 4.2697672707157535e+4; // a1 = a*e2
1371+
double a2 = 1.8230912546075455e+9; // a2 = a1*a1
1372+
double a3 = 1.4291722289812413e+2; // a3 = a1*e2/2
1373+
double a4 = 4.5577281365188637e+9; // a4 = 2.5*a2
1374+
double a5 = 4.2840589930055659e+4; // a5 = a1+a3
1375+
double a6 = 9.9330562000986220e-1; // a6 = 1-e2
1376+
1377+
double zp, w2, w, r2, r, s2, c2, s, c, ss;
1378+
double g, rg, rf, u, v, m, f, p;
1379+
1380+
zp = abs(z);
1381+
w2 = x * x + y * y;
1382+
w = sqrt(w2);
1383+
r2 = w2 + z * z;
1384+
r = sqrt(r2);
1385+
lonOut = atan2(y, x); // Lon (final)
1386+
1387+
s2 = z * z / r2;
1388+
c2 = w2 / r2;
1389+
u = a2 / r;
1390+
v = a3 - a4 / r;
1391+
if (c2 > 0.3)
1392+
{
1393+
s = (zp / r) * (1.0 + c2 * (a1 + u + s2 * v) / r);
1394+
latOut = asin(s); // Lat
1395+
ss = s * s;
1396+
c = sqrt(1.0 - ss);
1397+
}
1398+
else
1399+
{
1400+
c = (w / r) * (1.0 - s2 * (a5 - u - c2 * v) / r);
1401+
latOut = acos(c); // Lat
1402+
ss = 1.0 - c * c;
1403+
s = sqrt(ss);
1404+
}
1405+
1406+
g = 1.0 - e2 * ss;
1407+
rg = a / sqrt(g);
1408+
rf = a6 * rg;
1409+
u = w - rg * c;
1410+
v = zp - rf * s;
1411+
f = c * u + s * v;
1412+
m = c * v - s * u;
1413+
p = m / (rf / g + f);
1414+
latOut = latOut + p; // Lat
1415+
altOut = f + m * p / 2.0; // Altitude
1416+
if (z < 0.0)
1417+
{
1418+
latOut *= -1.0; // Lat
1419+
}
1420+
1421+
latOut *= RAD_TO_DEG; // Convert to degrees
1422+
lonOut *= RAD_TO_DEG;
1423+
}
1424+
13451425
uint16_t LG290P::getYear()
13461426
{
13471427
ensurePvtEnabled();

src/SparkFun_LG290P_GNSS.h

+9
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,15 @@ class LG290P
959959
*/
960960
double getSurveyInMeanAccuracy() { ensureSvinStatusEnabled(); return svinStatusDomain.meanAcc; }
961961

962+
// Convert LLH (geodetic) to ECEF
963+
// From: https://stackoverflow.com/questions/19478200/convert-latitude-and-longitude-to-ecef-coordinates-system
964+
static void geodeticToEcef(double lat, double lon, double alt, double &xOut, double &yOut, double &zOut);
965+
966+
// Convert ECEF to LLH (geodetic)
967+
// From: https://danceswithcode.net/engineeringnotes/geodetic_to_ecef/geodetic_to_ecef.html
968+
static void ecefToGeodetic(double x, double y, double z, double &latOut, double &lonOut, double &altOut);
969+
970+
962971
#if false // TODO
963972

964973
float getLatitudeDeviation();

0 commit comments

Comments
 (0)