2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
25 #include "build_config.h"
28 #include "common/maths.h"
29 #include "common/axis.h"
30 #include "common/utils.h"
32 #include "drivers/system.h"
33 #include "drivers/serial.h"
34 #include "drivers/serial_uart.h"
36 #include "io/serial.h"
38 #include "io/gps_private.h"
40 #include "flight/gps_conversion.h"
42 #include "config/config.h"
43 #include "config/runtime_config.h"
45 #if defined(GPS) && defined(GPS_PROTO_NMEA)
47 /* This is a light implementation of a GPS frame decoding
48 This should work with most of modern GPS devices configured to output 5 frames.
49 It assumes there are some NMEA GGA frames to decode on the serial bus
50 Now verifies checksum correctly before applying data
52 Here we use only the following data :
55 - GPS fix is/is not ok
56 - GPS num sat (4 is enough to be +/- reliable)
58 - GPS altitude (for OSD displaying)
59 - GPS speed (for OSD displaying)
67 static uint32_t grab_fields(char *src
, uint8_t mult
)
68 { // convert string to uint32
71 for (i
= 0; src
[i
] != 0; i
++) {
80 if (src
[i
] >= '0' && src
[i
] <= '9')
83 return 0; // out of bounds
88 typedef struct gpsDataNmea_s
{
95 uint16_t ground_course
;
99 static bool gpsNewFrameNMEA(char c
)
101 static gpsDataNmea_t gps_Msg
;
104 static uint8_t param
= 0, offset
= 0, parity
= 0;
105 static char string
[15];
106 static uint8_t checksum_param
, gps_frame
= NO_FRAME
;
107 static uint8_t svMessageNum
= 0;
108 uint8_t svSatNum
= 0, svPacketIdx
= 0, svSatParam
= 0;
119 if (param
== 0) { //frame identification
120 gps_frame
= NO_FRAME
;
121 if (string
[0] == 'G' && string
[1] == 'P' && string
[2] == 'G' && string
[3] == 'G' && string
[4] == 'A')
122 gps_frame
= FRAME_GGA
;
123 if (string
[0] == 'G' && string
[1] == 'P' && string
[2] == 'R' && string
[3] == 'M' && string
[4] == 'C')
124 gps_frame
= FRAME_RMC
;
125 if (string
[0] == 'G' && string
[1] == 'P' && string
[2] == 'G' && string
[3] == 'S' && string
[4] == 'V')
126 gps_frame
= FRAME_GSV
;
130 case FRAME_GGA
: //************* GPGGA FRAME parsing
132 // case 1: // Time information
135 gps_Msg
.latitude
= GPS_coord_to_degrees(string
);
138 if (string
[0] == 'S')
139 gps_Msg
.latitude
*= -1;
142 gps_Msg
.longitude
= GPS_coord_to_degrees(string
);
145 if (string
[0] == 'W')
146 gps_Msg
.longitude
*= -1;
149 if (string
[0] > '0') {
156 gps_Msg
.numSat
= grab_fields(string
, 0);
159 gps_Msg
.hdop
= grab_fields(string
, 1) * 10; // hdop (assume GPS is reporting it in meters)
162 gps_Msg
.altitude
= grab_fields(string
, 1) * 10; // altitude in cm
166 case FRAME_RMC
: //************* GPRMC FRAME parsing
169 gps_Msg
.speed
= ((grab_fields(string
, 1) * 5144L) / 1000L); // speed in cm/s added by Mis
172 gps_Msg
.ground_course
= (grab_fields(string
, 1)); // ground course deg * 10
179 // Total number of messages of this type in this cycle
183 svMessageNum
= grab_fields(string
, 0);
186 // Total number of SVs visible
187 gpsSol
.numCh
= grab_fields(string
, 0);
193 svPacketIdx
= (param
- 4) / 4 + 1; // satellite number in packet, 1-4
194 svSatNum
= svPacketIdx
+ (4 * (svMessageNum
- 1)); // global satellite number
195 svSatParam
= param
- 3 - (4 * (svPacketIdx
- 1)); // parameter number for satellite
197 if(svSatNum
> GPS_SV_MAXSATS
)
203 gpsSol
.svInfo
[svSatNum
- 1].chn
= svSatNum
;
204 gpsSol
.svInfo
[svSatNum
- 1].svid
= grab_fields(string
, 0);
207 // Elevation, in degrees, 90 maximum
210 // Azimuth, degrees from True North, 000 through 359
213 // SNR, 00 through 99 dB (null when not tracking)
214 gpsSol
.svInfo
[svSatNum
- 1].cno
= grab_fields(string
, 0);
215 gpsSol
.svInfo
[svSatNum
- 1].quality
= 0; // only used by ublox
230 if (checksum_param
) { //parity checksum
231 uint8_t checksum
= 16 * ((string
[0] >= 'A') ? string
[0] - 'A' + 10 : string
[0] - '0') + ((string
[1] >= 'A') ? string
[1] - 'A' + 10 : string
[1] - '0');
232 if (checksum
== parity
) {
233 gpsStats
.packetCount
++;
237 gpsSol
.numSat
= gps_Msg
.numSat
;
239 gpsSol
.flags
.fix3D
= 1;
240 gpsSol
.llh
.lat
= gps_Msg
.latitude
;
241 gpsSol
.llh
.lon
= gps_Msg
.longitude
;
242 gpsSol
.llh
.alt
= gps_Msg
.altitude
;
244 // EPH/EPV are unreliable for NMEA as they are not real accuracy
245 gpsSol
.eph
= gpsConstrainEPE(gps_Msg
.hdop
);
246 gpsSol
.epv
= gpsConstrainEPE(gps_Msg
.hdop
);
247 gpsSol
.flags
.validEPE
= 0;
250 gpsSol
.flags
.fix3D
= 0;
253 // NMEA does not report VELNED
254 gpsSol
.flags
.validVelNE
= 0;
255 gpsSol
.flags
.validVelD
= 0;
258 gpsSol
.groundSpeed
= gps_Msg
.speed
;
259 gpsSol
.groundCourse
= gps_Msg
.ground_course
;
271 string
[offset
++] = c
;
278 static bool gpsReceiveData(void)
280 bool hasNewData
= false;
282 if (gpsState
.gpsPort
) {
283 while (serialRxBytesWaiting(gpsState
.gpsPort
)) {
284 uint8_t newChar
= serialRead(gpsState
.gpsPort
);
285 if (gpsNewFrameNMEA(newChar
)) {
286 gpsSol
.flags
.gpsHeartbeat
= !gpsSol
.flags
.gpsHeartbeat
;
287 gpsSol
.flags
.validVelNE
= 0;
288 gpsSol
.flags
.validVelD
= 0;
297 static bool gpsInitialize(void)
299 gpsSetState(GPS_CHANGE_BAUD
);
303 static bool gpsChangeBaud(void)
305 gpsFinalizeChangeBaud();
309 bool gpsHandleNMEA(void)
312 bool hasNewData
= gpsReceiveData();
315 switch(gpsState
.state
) {
319 case GPS_INITIALIZING
:
320 return gpsInitialize();
322 case GPS_CHANGE_BAUD
:
323 return gpsChangeBaud();
326 case GPS_CHECK_VERSION
:
328 // No autoconfig, switch straight to receiving data
329 gpsSetState(GPS_RECEIVING_DATA
);
332 case GPS_RECEIVING_DATA
: