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)
66 static uint32_t grab_fields(char *src
, uint8_t mult
)
67 { // convert string to uint32
70 for (i
= 0; src
[i
] != 0; i
++) {
79 if (src
[i
] >= '0' && src
[i
] <= '9')
82 return 0; // out of bounds
87 typedef struct gpsDataNmea_s
{
94 uint16_t ground_course
;
98 static bool gpsNewFrameNMEA(char c
)
100 static gpsDataNmea_t gps_Msg
;
103 static uint8_t param
= 0, offset
= 0, parity
= 0;
104 static char string
[15];
105 static uint8_t checksum_param
, gps_frame
= NO_FRAME
;
116 if (param
== 0) { //frame identification
117 gps_frame
= NO_FRAME
;
118 if (string
[0] == 'G' && string
[1] == 'P' && string
[2] == 'G' && string
[3] == 'G' && string
[4] == 'A')
119 gps_frame
= FRAME_GGA
;
120 if (string
[0] == 'G' && string
[1] == 'P' && string
[2] == 'R' && string
[3] == 'M' && string
[4] == 'C')
121 gps_frame
= FRAME_RMC
;
125 case FRAME_GGA
: //************* GPGGA FRAME parsing
127 // case 1: // Time information
130 gps_Msg
.latitude
= GPS_coord_to_degrees(string
);
133 if (string
[0] == 'S')
134 gps_Msg
.latitude
*= -1;
137 gps_Msg
.longitude
= GPS_coord_to_degrees(string
);
140 if (string
[0] == 'W')
141 gps_Msg
.longitude
*= -1;
144 if (string
[0] > '0') {
151 gps_Msg
.numSat
= grab_fields(string
, 0);
154 gps_Msg
.hdop
= grab_fields(string
, 1) * 10; // hdop
157 gps_Msg
.altitude
= grab_fields(string
, 1) * 10; // altitude in cm
161 case FRAME_RMC
: //************* GPRMC FRAME parsing
164 gps_Msg
.speed
= ((grab_fields(string
, 1) * 5144L) / 1000L); // speed in cm/s added by Mis
167 gps_Msg
.ground_course
= (grab_fields(string
, 1)); // ground course deg * 10
182 if (checksum_param
) { //parity checksum
183 uint8_t checksum
= 16 * ((string
[0] >= 'A') ? string
[0] - 'A' + 10 : string
[0] - '0') + ((string
[1] >= 'A') ? string
[1] - 'A' + 10 : string
[1] - '0');
184 if (checksum
== parity
) {
185 gpsStats
.packetCount
++;
189 gpsSol
.numSat
= gps_Msg
.numSat
;
191 gpsSol
.fixType
= GPS_FIX_3D
; // NMEA doesn't report fix type, assume 3D
193 gpsSol
.llh
.lat
= gps_Msg
.latitude
;
194 gpsSol
.llh
.lon
= gps_Msg
.longitude
;
195 gpsSol
.llh
.alt
= gps_Msg
.altitude
;
197 // EPH/EPV are unreliable for NMEA as they are not real accuracy
198 gpsSol
.hdop
= gpsConstrainHDOP(gps_Msg
.hdop
);
199 gpsSol
.eph
= gpsConstrainEPE(gps_Msg
.hdop
* GPS_HDOP_TO_EPH_MULTIPLIER
);
200 gpsSol
.epv
= gpsConstrainEPE(gps_Msg
.hdop
* GPS_HDOP_TO_EPH_MULTIPLIER
);
201 gpsSol
.flags
.validEPE
= 0;
204 gpsSol
.fixType
= GPS_NO_FIX
;
207 // NMEA does not report VELNED
208 gpsSol
.flags
.validVelNE
= 0;
209 gpsSol
.flags
.validVelD
= 0;
212 gpsSol
.groundSpeed
= gps_Msg
.speed
;
213 gpsSol
.groundCourse
= gps_Msg
.ground_course
;
225 string
[offset
++] = c
;
232 static bool gpsReceiveData(void)
234 bool hasNewData
= false;
236 if (gpsState
.gpsPort
) {
237 while (serialRxBytesWaiting(gpsState
.gpsPort
)) {
238 uint8_t newChar
= serialRead(gpsState
.gpsPort
);
239 if (gpsNewFrameNMEA(newChar
)) {
240 gpsSol
.flags
.gpsHeartbeat
= !gpsSol
.flags
.gpsHeartbeat
;
241 gpsSol
.flags
.validVelNE
= 0;
242 gpsSol
.flags
.validVelD
= 0;
251 static bool gpsInitialize(void)
253 gpsSetState(GPS_CHANGE_BAUD
);
257 static bool gpsChangeBaud(void)
259 gpsFinalizeChangeBaud();
263 bool gpsHandleNMEA(void)
266 bool hasNewData
= gpsReceiveData();
269 switch(gpsState
.state
) {
273 case GPS_INITIALIZING
:
274 return gpsInitialize();
276 case GPS_CHANGE_BAUD
:
277 return gpsChangeBaud();
280 case GPS_CHECK_VERSION
:
282 // No autoconfig, switch straight to receiving data
283 gpsSetState(GPS_RECEIVING_DATA
);
286 case GPS_RECEIVING_DATA
: