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/>.
26 #if defined(USE_GPS) && (defined(USE_GPS_PROTO_NMEA))
28 #include "build/build_config.h"
29 #include "build/debug.h"
31 #include "common/axis.h"
32 #include "common/gps_conversion.h"
33 #include "common/maths.h"
34 #include "common/utils.h"
36 #include "drivers/serial.h"
37 #include "drivers/time.h"
39 #include "fc/config.h"
40 #include "fc/runtime_config.h"
42 #include "io/serial.h"
44 #include "io/gps_private.h"
46 #include "scheduler/protothreads.h"
48 /* This is a light implementation of a GPS frame decoding
49 This should work with most of modern GPS devices configured to output 5 frames.
50 It assumes there are some NMEA GGA frames to decode on the serial bus
51 Now verifies checksum correctly before applying data
53 Here we use only the following data :
56 - GPS fix is/is not ok
57 - GPS num sat (4 is enough to be +/- reliable)
59 - GPS altitude (for OSD displaying)
60 - 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
;
101 #define NMEA_BUFFER_SIZE 16
103 static bool gpsNewFrameNMEA(char c
)
105 static gpsDataNmea_t gps_Msg
;
108 static uint8_t param
= 0, offset
= 0, parity
= 0;
109 static char string
[NMEA_BUFFER_SIZE
];
110 static uint8_t checksum_param
, gps_frame
= NO_FRAME
;
121 if (param
== 0) { //frame identification
122 gps_frame
= NO_FRAME
;
123 if (strcmp(string
, "GPGGA") == 0 || strcmp(string
, "GNGGA") == 0) {
124 gps_frame
= FRAME_GGA
;
126 else if (strcmp(string
, "GPRMC") == 0 || strcmp(string
, "GNRMC") == 0) {
127 gps_frame
= FRAME_RMC
;
132 case FRAME_GGA
: //************* GPGGA FRAME parsing
134 // case 1: // Time information
137 gps_Msg
.latitude
= GPS_coord_to_degrees(string
);
140 if (string
[0] == 'S')
141 gps_Msg
.latitude
*= -1;
144 gps_Msg
.longitude
= GPS_coord_to_degrees(string
);
147 if (string
[0] == 'W')
148 gps_Msg
.longitude
*= -1;
151 if (string
[0] > '0') {
158 gps_Msg
.numSat
= grab_fields(string
, 0);
161 gps_Msg
.hdop
= grab_fields(string
, 1) * 10; // hdop
164 gps_Msg
.altitude
= grab_fields(string
, 1) * 10; // altitude in cm
168 case FRAME_RMC
: //************* GPRMC FRAME parsing
169 // $GNRMC,130059.00,V,,,,,,,110917,,,N*62
172 gps_Msg
.time
= grab_fields(string
, 2);
175 gps_Msg
.speed
= ((grab_fields(string
, 1) * 5144L) / 1000L); // speed in cm/s added by Mis
178 gps_Msg
.ground_course
= (grab_fields(string
, 1)); // ground course deg * 10
181 gps_Msg
.date
= grab_fields(string
, 0);
196 if (checksum_param
) { //parity checksum
197 uint8_t checksum
= 16 * ((string
[0] >= 'A') ? string
[0] - 'A' + 10 : string
[0] - '0') + ((string
[1] >= 'A') ? string
[1] - 'A' + 10 : string
[1] - '0');
198 if (checksum
== parity
) {
199 gpsStats
.packetCount
++;
203 gpsSol
.numSat
= gps_Msg
.numSat
;
205 gpsSol
.fixType
= GPS_FIX_3D
; // NMEA doesn't report fix type, assume 3D
207 gpsSol
.llh
.lat
= gps_Msg
.latitude
;
208 gpsSol
.llh
.lon
= gps_Msg
.longitude
;
209 gpsSol
.llh
.alt
= gps_Msg
.altitude
;
211 // EPH/EPV are unreliable for NMEA as they are not real accuracy
212 gpsSol
.hdop
= gpsConstrainHDOP(gps_Msg
.hdop
);
213 gpsSol
.eph
= gpsConstrainEPE(gps_Msg
.hdop
* GPS_HDOP_TO_EPH_MULTIPLIER
);
214 gpsSol
.epv
= gpsConstrainEPE(gps_Msg
.hdop
* GPS_HDOP_TO_EPH_MULTIPLIER
);
215 gpsSol
.flags
.validEPE
= false;
218 gpsSol
.fixType
= GPS_NO_FIX
;
221 // NMEA does not report VELNED
222 gpsSol
.flags
.validVelNE
= false;
223 gpsSol
.flags
.validVelD
= false;
226 gpsSol
.groundSpeed
= gps_Msg
.speed
;
227 gpsSol
.groundCourse
= gps_Msg
.ground_course
;
229 // This check will miss 00:00:00.00, but we shouldn't care - next report will be valid
230 if (gps_Msg
.date
!= 0 && gps_Msg
.time
!= 0) {
231 gpsSol
.time
.year
= (gps_Msg
.date
% 100) + 2000;
232 gpsSol
.time
.month
= (gps_Msg
.date
/ 100) % 100;
233 gpsSol
.time
.day
= (gps_Msg
.date
/ 10000) % 100;
234 gpsSol
.time
.hours
= (gps_Msg
.time
/ 1000000) % 100;
235 gpsSol
.time
.minutes
= (gps_Msg
.time
/ 10000) % 100;
236 gpsSol
.time
.seconds
= (gps_Msg
.time
/ 100) % 100;
237 gpsSol
.time
.millis
= (gps_Msg
.time
& 100) * 10;
238 gpsSol
.flags
.validTime
= true;
241 gpsSol
.flags
.validTime
= false;
254 if (offset
< (NMEA_BUFFER_SIZE
-1)) { // leave 1 byte to trailing zero
255 string
[offset
++] = c
;
257 // only checksum if character is recorded and used (will cause checksum failure on dropped characters)
265 static ptSemaphore_t semNewDataReady
;
267 STATIC_PROTOTHREAD(gpsProtocolReceiverThread
)
269 ptBegin(gpsProtocolReceiverThread
);
272 // Wait until there are bytes to consume
273 ptWait(serialRxBytesWaiting(gpsState
.gpsPort
));
275 // Consume bytes until buffer empty of until we have full message received
276 while (serialRxBytesWaiting(gpsState
.gpsPort
)) {
277 uint8_t newChar
= serialRead(gpsState
.gpsPort
);
278 if (gpsNewFrameNMEA(newChar
)) {
279 gpsSol
.flags
.validVelNE
= false;
280 gpsSol
.flags
.validVelD
= false;
281 ptSemaphoreSignal(semNewDataReady
);
290 STATIC_PROTOTHREAD(gpsProtocolStateThreadNMEA
)
292 ptBegin(gpsProtocolStateThreadNMEA
);
295 ptWait(isSerialTransmitBufferEmpty(gpsState
.gpsPort
));
296 if (gpsState
.gpsConfig
->autoBaud
!= GPS_AUTOBAUD_OFF
) {
297 // Cycle through available baud rates and hope that we will match GPS
298 serialSetBaudRate(gpsState
.gpsPort
, baudRates
[gpsToSerialBaudRate
[gpsState
.autoBaudrateIndex
]]);
299 gpsState
.autoBaudrateIndex
= (gpsState
.autoBaudrateIndex
+ 1) % GPS_BAUDRATE_COUNT
;
300 ptDelayMs(GPS_BAUD_CHANGE_DELAY
);
304 serialSetBaudRate(gpsState
.gpsPort
, baudRates
[gpsToSerialBaudRate
[gpsState
.baudrateIndex
]]);
307 // No configuration is done for pure NMEA modules
309 // GPS setup done, reset timeout
310 gpsSetProtocolTimeout(gpsState
.baseTimeoutMs
);
312 // GPS is ready - execute the gpsProcessNewSolutionData() based on gpsProtocolReceiverThread semaphore
314 ptSemaphoreWait(semNewDataReady
);
315 gpsProcessNewSolutionData();
321 void gpsHandleNMEA(void)
323 // Run the protocol threads
324 gpsProtocolReceiverThread();
325 gpsProtocolStateThreadNMEA();
327 // If thread stopped - signal communication loss and restart
328 if (ptIsStopped(ptGetHandle(gpsProtocolReceiverThread
)) || ptIsStopped(ptGetHandle(gpsProtocolStateThreadNMEA
))) {
329 gpsSetState(GPS_LOST_COMMUNICATION
);
333 void gpsRestartNMEA(void)
335 ptSemaphoreInit(semNewDataReady
);
336 ptRestart(ptGetHandle(gpsProtocolReceiverThread
));
337 ptRestart(ptGetHandle(gpsProtocolStateThreadNMEA
));