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/>.
27 #include "build/build_config.h"
29 #if defined(USE_GPS) && defined(USE_GPS_PROTO_UBLOX)
31 #include "build/debug.h"
34 #include "common/axis.h"
35 #include "common/typeconversion.h"
36 #include "common/gps_conversion.h"
37 #include "common/maths.h"
38 #include "common/utils.h"
40 #include "drivers/serial.h"
41 #include "drivers/time.h"
43 #include "fc/config.h"
44 #include "fc/runtime_config.h"
45 #include "fc/settings.h"
47 #include "io/serial.h"
49 #include "io/gps_private.h"
51 #include "scheduler/protothreads.h"
53 #include "gps_ublox.h"
54 #include "gps_ublox_utils.h"
57 // SBAS_AUTO, SBAS_EGNOS, SBAS_WAAS, SBAS_MSAS, SBAS_GAGAN, SBAS_NONE
58 // note PRNs last upadted 2023-08-10
59 // https://www.gps.gov/technical/prn-codes/L1-CA-PRN-code-assignments-2023-Apr.pdf
61 #define SBASMASK1_BASE 120
62 #define SBASMASK1_BITS(prn) (1 << (prn-SBASMASK1_BASE))
64 static const uint32_t ubloxScanMode1
[] = {
66 (SBASMASK1_BITS(121) | SBASMASK1_BITS(123) | SBASMASK1_BITS(126) | SBASMASK1_BITS(136) | SBASMASK1_BITS(150)), // SBAS_EGNOS
67 (SBASMASK1_BITS(131) | SBASMASK1_BITS(133) | SBASMASK1_BITS(135) | SBASMASK1_BITS(138)), // WAAS
69 (SBASMASK1_BITS(129) | SBASMASK1_BITS(137) | SBASMASK1_BITS(139)), // MSAS
71 (SBASMASK1_BITS(127) | SBASMASK1_BITS(128) | SBASMASK1_BITS(132)), // GAGAN
72 (SBASMASK1_BITS(122)), // SPAN
76 static const char * baudInitDataNMEA
[GPS_BAUDRATE_COUNT
] = {
77 "$PUBX,41,1,0003,0001,115200,0*1E\r\n", // GPS_BAUDRATE_115200
78 "$PUBX,41,1,0003,0001,57600,0*2D\r\n", // GPS_BAUDRATE_57600
79 "$PUBX,41,1,0003,0001,38400,0*26\r\n", // GPS_BAUDRATE_38400
80 "$PUBX,41,1,0003,0001,19200,0*23\r\n", // GPS_BAUDRATE_19200
81 "$PUBX,41,1,0003,0001,9600,0*16\r\n", // GPS_BAUDRATE_9600
82 "$PUBX,41,1,0003,0001,230400,0*1C\r\n", // GPS_BAUDRATE_230400
83 "$PUBX,41,1,0003,0001,460800,0*13\r\n", // GPS_BAUDRATE_460800
84 "$PUBX,41,1,0003,0001,921600,0*15\r\n" // GPS_BAUDRATE_921600
87 // Packet checksum accumulators
91 // State machine state
92 static bool _skip_packet
;
94 static uint8_t _msg_id
;
95 static uint16_t _payload_length
;
96 static uint16_t _payload_counter
;
98 static uint8_t next_fix_type
;
99 static uint8_t _class
;
100 static uint8_t _ack_state
;
101 static uint8_t _ack_waiting_msg
;
103 // do we have new position information?
104 static bool _new_position
;
106 // do we have new speed information?
107 static bool _new_speed
;
109 // Need this to determine if Galileo capable only
115 } ubx_capabilities
= { };
117 // Example packet sizes from UBlox u-center from a Glonass capable GPS receiver.
118 //15:17:55 R -> UBX NAV-STATUS, Size 24, 'Navigation Status'
119 //15:17:55 R -> UBX NAV-POSLLH, Size 36, 'Geodetic Position'
120 //15:17:55 R -> UBX NAV-VELNED, Size 44, 'Velocity in WGS 84'
121 //15:17:55 R -> UBX NAV-CLOCK, Size 28, 'Clock Status'
122 //15:17:55 R -> UBX NAV-AOPSTATUS, Size 24, 'AOP Status'
123 //15:17:55 R -> UBX 03-09, Size 208, 'Unknown'
124 //15:17:55 R -> UBX 03-10, Size 336, 'Unknown'
125 //15:17:55 R -> UBX NAV-SOL, Size 60, 'Navigation Solution'
126 //15:17:55 R -> UBX NAV, Size 100, 'Navigation'
127 //15:17:55 R -> UBX NAV-SVINFO, Size 328, 'Satellite Status and Information'
138 ubx_nav_posllh posllh
;
139 ubx_nav_status status
;
140 ubx_nav_solution solution
;
141 ubx_nav_velned velned
;
143 ubx_nav_svinfo svinfo
;
145 ubx_nav_timeutc timeutc
;
148 uint8_t bytes
[UBLOX_BUFFER_SIZE
];
151 bool gpsUbloxHasGalileo(void)
153 return (ubx_capabilities
.supported
& UBX_MON_GNSS_GALILEO_MASK
);
156 bool gpsUbloxHasBeidou(void)
158 return ubx_capabilities
.supported
& UBX_MON_GNSS_BEIDOU_MASK
;
161 bool gpsUbloxHasGlonass(void)
163 return ubx_capabilities
.supported
& UBX_MON_GNSS_GLONASS_MASK
;
166 bool gpsUbloxGalileoDefault(void)
168 return ubx_capabilities
.defaultGnss
& UBX_MON_GNSS_GALILEO_MASK
;
171 bool gpsUbloxBeidouDefault(void)
173 return ubx_capabilities
.defaultGnss
& UBX_MON_GNSS_BEIDOU_MASK
;
176 bool gpsUbloxGlonassDefault(void)
178 return ubx_capabilities
.defaultGnss
& UBX_MON_GNSS_GLONASS_MASK
;
181 bool gpsUbloxGalileoEnabled(void)
183 return ubx_capabilities
.enabledGnss
& UBX_MON_GNSS_GALILEO_MASK
;
186 bool gpsUbloxBeidouEnabled(void)
188 return ubx_capabilities
.enabledGnss
& UBX_MON_GNSS_BEIDOU_MASK
;
191 bool gpsUbloxGlonassEnabled(void)
193 return ubx_capabilities
.enabledGnss
& UBX_MON_GNSS_GLONASS_MASK
;
196 uint8_t gpsUbloxMaxGnss(void)
198 return ubx_capabilities
.capMaxGnss
;
201 timeMs_t
gpsUbloxCapLastUpdate(void)
203 return gpsState
.lastCapaUpdMs
;
206 static uint8_t gpsMapFixType(bool fixValid
, uint8_t ubloxFixType
)
208 if (fixValid
&& ubloxFixType
== FIX_2D
)
210 if (fixValid
&& ubloxFixType
== FIX_3D
)
215 static void sendConfigMessageUBLOX(void)
217 uint8_t ck_a
=0, ck_b
=0;
218 send_buffer
.message
.header
.preamble1
=PREAMBLE1
;
219 send_buffer
.message
.header
.preamble2
=PREAMBLE2
;
220 ublox_update_checksum(&send_buffer
.bytes
[2], send_buffer
.message
.header
.length
+4, &ck_a
, &ck_b
);
221 send_buffer
.bytes
[send_buffer
.message
.header
.length
+6] = ck_a
;
222 send_buffer
.bytes
[send_buffer
.message
.header
.length
+7] = ck_b
;
223 serialWriteBuf(gpsState
.gpsPort
, send_buffer
.bytes
, send_buffer
.message
.header
.length
+8);
225 // Save state for ACK waiting
226 _ack_waiting_msg
= send_buffer
.message
.header
.msg_id
;
227 _ack_state
= UBX_ACK_WAITING
;
230 static void pollVersion(void)
232 send_buffer
.message
.header
.msg_class
= CLASS_MON
;
233 send_buffer
.message
.header
.msg_id
= MSG_VER
;
234 send_buffer
.message
.header
.length
= 0;
235 sendConfigMessageUBLOX();
238 static void pollGnssCapabilities(void)
240 send_buffer
.message
.header
.msg_class
= CLASS_MON
;
241 send_buffer
.message
.header
.msg_id
= MSG_MON_GNSS
;
242 send_buffer
.message
.header
.length
= 0;
243 sendConfigMessageUBLOX();
247 static const uint8_t default_payload
[] = {
248 0xFF, 0xFF, 0x03, 0x03, 0x00, // CFG-NAV5 - Set engine settings (original MWII code)
249 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, // Collected by resetting a GPS unit to defaults. Changing mode to Pedistrian and
250 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x3C, 0x00, 0x00, 0x00, // capturing the data from the U-Center binary console.
251 0x00, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
254 #define GNSSID_SBAS 1
255 #define GNSSID_GALILEO 2
256 #define GNSSID_BEIDOU 3
257 #define GNSSID_GZSS 5
258 #define GNSSID_GLONASS 6
260 // M10 ublox protocol info:
261 // https://content.u-blox.com/sites/default/files/u-blox-M10-SPG-5.10_InterfaceDescription_UBX-21035062.pdf
262 static void ubloxSendSetCfgBytes(ubx_config_data8_payload_t
*kvPairs
, uint8_t count
)
264 ubx_config_data8_t cfg
= {};
266 ubloxCfgFillBytes(&cfg
, kvPairs
, count
);
268 serialWriteBuf(gpsState
.gpsPort
, (uint8_t *)&cfg
, cfg
.header
.length
+8);
269 _ack_waiting_msg
= cfg
.header
.msg_id
;
270 _ack_state
= UBX_ACK_WAITING
;
273 // Info on protocol used by M8-M9, check UBX-CFG-GNSS for gnss configuration
274 // https://content.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_UBX-13003221.pdf
275 // https://content.u-blox.com/sites/default/files/documents/u-blox-F9-HPG-1.32_InterfaceDescription_UBX-22008968.pdf
276 static int configureGNSS_SBAS(ubx_gnss_element_t
* gnss_block
)
278 gnss_block
->gnssId
= GNSSID_SBAS
;
279 gnss_block
->maxTrkCh
= 3;
280 gnss_block
->sigCfgMask
= 1;
281 if (gpsState
.gpsConfig
->sbasMode
== SBAS_NONE
) {
282 gnss_block
->enabled
= 0;
283 gnss_block
->resTrkCh
= 0;
285 gnss_block
->enabled
= 1;
286 gnss_block
->resTrkCh
= 1;
292 static int configureGNSS_GALILEO(ubx_gnss_element_t
* gnss_block
)
294 if (!gpsUbloxHasGalileo()) {
298 gnss_block
->gnssId
= GNSSID_GALILEO
;
299 gnss_block
->maxTrkCh
= 8;
301 // 0x01 = Galileo E1 (not supported for protocol versions less than 18.00)
302 // 0x10 = Galileo E5a // off by default
303 // 0x20 = Galileo E5b // off by default
304 gnss_block
->sigCfgMask
= 0x01;
305 if (gpsState
.gpsConfig
->ubloxUseGalileo
) {
306 gnss_block
->enabled
= 1;
307 gnss_block
->resTrkCh
= 4;
309 gnss_block
->enabled
= 0;
310 gnss_block
->resTrkCh
= 0;
316 static int configureGNSS_BEIDOU(ubx_gnss_element_t
* gnss_block
)
318 if (!gpsUbloxHasBeidou()) {
322 gnss_block
->gnssId
= GNSSID_BEIDOU
;
323 gnss_block
->maxTrkCh
= 8;
326 // 0x10 = BeiDou B2I // off by default
327 // 0x80 = BeiDou B2A // off by default
328 gnss_block
->sigCfgMask
= 0x01;
329 if (gpsState
.gpsConfig
->ubloxUseBeidou
) {
330 gnss_block
->enabled
= 1;
331 gnss_block
->resTrkCh
= 4;
333 gnss_block
->enabled
= 0;
334 gnss_block
->resTrkCh
= 0;
341 static int configureGNSS_GZSS(ubx_gnss_element_t * gnss_block)
343 if (!ubx_capabilities.capGzss) {
347 gnss_block->gnssId = GNSSID_GZSS;
348 gnss_block->maxTrkCh = 8;
352 gnss_block->sigCfgMask = 0x01 | 0x04;
353 gnss_block->enabled = 1;
354 gnss_block->resTrkCh = 4;
360 static int configureGNSS_GLONASS(ubx_gnss_element_t
* gnss_block
)
362 if(!gpsUbloxHasGlonass()) {
366 gnss_block
->gnssId
= GNSSID_GLONASS
;
367 gnss_block
->maxTrkCh
= 8;
369 // 0x10 = GLONASS L2 // off by default
370 gnss_block
->sigCfgMask
= 0x01;
371 if (gpsState
.gpsConfig
->ubloxUseGlonass
) {
372 gnss_block
->enabled
= 1;
373 gnss_block
->resTrkCh
= 4;
375 gnss_block
->enabled
= 0;
376 gnss_block
->resTrkCh
= 0;
382 static void configureGNSS10(void)
384 ubx_config_data8_payload_t gnssConfigValues
[] = {
386 {UBLOX_CFG_SIGNAL_SBAS_ENA
, gpsState
.gpsConfig
->sbasMode
== SBAS_NONE
? 0 : 1},
387 {UBLOX_CFG_SIGNAL_SBAS_L1CA_ENA
, gpsState
.gpsConfig
->sbasMode
== SBAS_NONE
? 0 : 1},
390 {UBLOX_CFG_SIGNAL_GAL_ENA
, gpsState
.gpsConfig
->ubloxUseGalileo
},
391 {UBLOX_CFG_SIGNAL_GAL_E1_ENA
, gpsState
.gpsConfig
->ubloxUseGalileo
},
394 {UBLOX_CFG_SIGNAL_BDS_ENA
, gpsState
.gpsConfig
->ubloxUseBeidou
},
395 {UBLOX_CFG_SIGNAL_BDS_B1_ENA
, gpsState
.gpsConfig
->ubloxUseBeidou
},
396 {UBLOX_CFG_SIGNAL_BDS_B1C_ENA
, 0},
398 // Should be enabled with GPS
399 {UBLOX_CFG_QZSS_ENA
, 1},
400 {UBLOX_CFG_QZSS_L1CA_ENA
, 1},
401 {UBLOX_CFG_QZSS_L1S_ENA
, 1},
404 {UBLOX_CFG_GLO_ENA
, gpsState
.gpsConfig
->ubloxUseGlonass
},
405 {UBLOX_CFG_GLO_L1_ENA
, gpsState
.gpsConfig
->ubloxUseGlonass
}
408 ubloxSendSetCfgBytes(gnssConfigValues
, 12);
411 static void configureGNSS(void)
414 send_buffer
.message
.header
.msg_class
= CLASS_CFG
;
415 send_buffer
.message
.header
.msg_id
= MSG_CFG_GNSS
; // message deprecated in protocol > 23.01, should use UBX-CFG-VALSET/UBX-CFG-VALGET
416 send_buffer
.message
.payload
.gnss
.msgVer
= 0;
417 send_buffer
.message
.payload
.gnss
.numTrkChHw
= 0; // read only, so unset
418 send_buffer
.message
.payload
.gnss
.numTrkChUse
= 0xFF; // If set to 0xFF will use hardware max
420 /* SBAS, always generated */
421 blocksUsed
+= configureGNSS_SBAS(&send_buffer
.message
.payload
.gnss
.config
[blocksUsed
]);
424 blocksUsed
+= configureGNSS_GALILEO(&send_buffer
.message
.payload
.gnss
.config
[blocksUsed
]);
427 blocksUsed
+= configureGNSS_BEIDOU(&send_buffer
.message
.payload
.gnss
.config
[blocksUsed
]);
430 blocksUsed
+= configureGNSS_GLONASS(&send_buffer
.message
.payload
.gnss
.config
[blocksUsed
]);
432 send_buffer
.message
.payload
.gnss
.numConfigBlocks
= blocksUsed
;
433 send_buffer
.message
.header
.length
= (sizeof(ubx_gnss_msg_t
) + sizeof(ubx_gnss_element_t
) * blocksUsed
);
434 sendConfigMessageUBLOX();
437 static void configureNAV5(uint8_t dynModel
, uint8_t fixMode
)
439 send_buffer
.message
.header
.msg_class
= CLASS_CFG
;
440 send_buffer
.message
.header
.msg_id
= MSG_CFG_NAV_SETTINGS
;
441 send_buffer
.message
.header
.length
= 0x24;
442 memcpy(send_buffer
.message
.payload
.bytes
, default_payload
, sizeof(default_payload
));
443 send_buffer
.message
.payload
.bytes
[2] = dynModel
;
444 send_buffer
.message
.payload
.bytes
[3] = fixMode
;
445 sendConfigMessageUBLOX();
448 static void configureMSG(uint8_t msg_class
, uint8_t id
, uint8_t rate
)
450 send_buffer
.message
.header
.msg_class
= CLASS_CFG
;
451 send_buffer
.message
.header
.msg_id
= MSG_CFG_SET_RATE
;
452 send_buffer
.message
.header
.length
= 3;
453 send_buffer
.message
.payload
.msg
.msg_class
= msg_class
;
454 send_buffer
.message
.payload
.msg
.id
= id
;
455 send_buffer
.message
.payload
.msg
.rate
= rate
;
456 sendConfigMessageUBLOX();
462 * timeRef 0 UTC, 1 GPS
464 static void configureRATE(uint16_t measRate
)
466 send_buffer
.message
.header
.msg_class
= CLASS_CFG
;
467 send_buffer
.message
.header
.msg_id
= MSG_CFG_RATE
;
468 send_buffer
.message
.header
.length
= 6;
469 send_buffer
.message
.payload
.rate
.meas
=measRate
;
470 send_buffer
.message
.payload
.rate
.nav
=1;
471 send_buffer
.message
.payload
.rate
.time
=1;
472 sendConfigMessageUBLOX();
477 static void configureSBAS(void)
479 send_buffer
.message
.header
.msg_class
= CLASS_CFG
;
480 send_buffer
.message
.header
.msg_id
= MSG_CFG_SBAS
;
481 send_buffer
.message
.header
.length
= 8;
482 send_buffer
.message
.payload
.sbas
.mode
=(gpsState
.gpsConfig
->sbasMode
== SBAS_NONE
?2:3);
483 send_buffer
.message
.payload
.sbas
.usage
=3;
484 send_buffer
.message
.payload
.sbas
.maxSBAS
=3;
485 send_buffer
.message
.payload
.sbas
.scanmode2
=0;
486 send_buffer
.message
.payload
.sbas
.scanmode1
=ubloxScanMode1
[gpsState
.gpsConfig
->sbasMode
];
487 sendConfigMessageUBLOX();
490 static void gpsDecodeProtocolVersion(const char *proto
, size_t bufferLength
)
492 if (bufferLength
> 13 && (!strncmp(proto
, "PROTVER=", 8) || !strncmp(proto
, "PROTVER ", 8))) {
495 float ver
= atof(proto
);
497 gpsState
.swVersionMajor
= (uint8_t)ver
;
498 gpsState
.swVersionMinor
= (uint8_t)((ver
- gpsState
.swVersionMajor
) * 100.0f
);
502 static uint32_t gpsDecodeHardwareVersion(const char * szBuf
, unsigned nBufSize
)
504 // ublox_5 hwVersion 00040005
505 if (strncmp(szBuf
, "00040005", nBufSize
) == 0) {
506 return UBX_HW_VERSION_UBLOX5
;
509 // ublox_6 hwVersion 00040007
510 if (strncmp(szBuf
, "00040007", nBufSize
) == 0) {
511 return UBX_HW_VERSION_UBLOX6
;
514 // ublox_7 hwVersion 00070000
515 if (strncmp(szBuf
, "00070000", nBufSize
) == 0) {
516 return UBX_HW_VERSION_UBLOX7
;
519 // ublox_M8 hwVersion 00080000
520 if (strncmp(szBuf
, "00080000", nBufSize
) == 0) {
521 return UBX_HW_VERSION_UBLOX8
;
524 // ublox_M9 hwVersion 00190000
525 if (strncmp(szBuf
, "00190000", nBufSize
) == 0) {
526 return UBX_HW_VERSION_UBLOX9
;
529 // ublox_M10 hwVersion 000A0000
530 if (strncmp(szBuf
, "000A0000", nBufSize
) == 0) {
531 return UBX_HW_VERSION_UBLOX10
;
534 return UBX_HW_VERSION_UNKNOWN
;
537 static bool gpsParseFrameUBLOX(void)
541 gpsSol
.llh
.lon
= _buffer
.posllh
.longitude
;
542 gpsSol
.llh
.lat
= _buffer
.posllh
.latitude
;
543 gpsSol
.llh
.alt
= _buffer
.posllh
.altitude_msl
/ 10; //alt in cm
544 gpsSol
.eph
= gpsConstrainEPE(_buffer
.posllh
.horizontal_accuracy
/ 10);
545 gpsSol
.epv
= gpsConstrainEPE(_buffer
.posllh
.vertical_accuracy
/ 10);
546 gpsSol
.flags
.validEPE
= true;
547 if (next_fix_type
!= GPS_NO_FIX
)
548 gpsSol
.fixType
= next_fix_type
;
549 _new_position
= true;
552 next_fix_type
= gpsMapFixType(_buffer
.status
.fix_status
& NAV_STATUS_FIX_VALID
, _buffer
.status
.fix_type
);
553 if (next_fix_type
== GPS_NO_FIX
)
554 gpsSol
.fixType
= GPS_NO_FIX
;
557 next_fix_type
= gpsMapFixType(_buffer
.solution
.fix_status
& NAV_STATUS_FIX_VALID
, _buffer
.solution
.fix_type
);
558 if (next_fix_type
== GPS_NO_FIX
)
559 gpsSol
.fixType
= GPS_NO_FIX
;
560 gpsSol
.numSat
= _buffer
.solution
.satellites
;
561 gpsSol
.hdop
= gpsConstrainHDOP(_buffer
.solution
.position_DOP
);
564 gpsSol
.groundSpeed
= _buffer
.velned
.speed_2d
; // cm/s
565 gpsSol
.groundCourse
= (uint16_t) (_buffer
.velned
.heading_2d
/ 10000); // Heading 2D deg * 100000 rescaled to deg * 10
566 gpsSol
.velNED
[X
] = _buffer
.velned
.ned_north
;
567 gpsSol
.velNED
[Y
] = _buffer
.velned
.ned_east
;
568 gpsSol
.velNED
[Z
] = _buffer
.velned
.ned_down
;
569 gpsSol
.flags
.validVelNE
= true;
570 gpsSol
.flags
.validVelD
= true;
574 if (UBX_VALID_GPS_DATE_TIME(_buffer
.timeutc
.valid
)) {
575 gpsSol
.time
.year
= _buffer
.timeutc
.year
;
576 gpsSol
.time
.month
= _buffer
.timeutc
.month
;
577 gpsSol
.time
.day
= _buffer
.timeutc
.day
;
578 gpsSol
.time
.hours
= _buffer
.timeutc
.hour
;
579 gpsSol
.time
.minutes
= _buffer
.timeutc
.min
;
580 gpsSol
.time
.seconds
= _buffer
.timeutc
.sec
;
581 gpsSol
.time
.millis
= _buffer
.timeutc
.nano
/ (1000*1000);
583 gpsSol
.flags
.validTime
= true;
585 gpsSol
.flags
.validTime
= false;
589 next_fix_type
= gpsMapFixType(_buffer
.pvt
.fix_status
& NAV_STATUS_FIX_VALID
, _buffer
.pvt
.fix_type
);
590 gpsSol
.fixType
= next_fix_type
;
591 gpsSol
.llh
.lon
= _buffer
.pvt
.longitude
;
592 gpsSol
.llh
.lat
= _buffer
.pvt
.latitude
;
593 gpsSol
.llh
.alt
= _buffer
.pvt
.altitude_msl
/ 10; //alt in cm
594 gpsSol
.velNED
[X
]=_buffer
.pvt
.ned_north
/ 10; // to cm/s
595 gpsSol
.velNED
[Y
]=_buffer
.pvt
.ned_east
/ 10; // to cm/s
596 gpsSol
.velNED
[Z
]=_buffer
.pvt
.ned_down
/ 10; // to cm/s
597 gpsSol
.groundSpeed
= _buffer
.pvt
.speed_2d
/ 10; // to cm/s
598 gpsSol
.groundCourse
= (uint16_t) (_buffer
.pvt
.heading_2d
/ 10000); // Heading 2D deg * 100000 rescaled to deg * 10
599 gpsSol
.numSat
= _buffer
.pvt
.satellites
;
600 gpsSol
.eph
= gpsConstrainEPE(_buffer
.pvt
.horizontal_accuracy
/ 10);
601 gpsSol
.epv
= gpsConstrainEPE(_buffer
.pvt
.vertical_accuracy
/ 10);
602 gpsSol
.hdop
= gpsConstrainHDOP(_buffer
.pvt
.position_DOP
);
603 gpsSol
.flags
.validVelNE
= true;
604 gpsSol
.flags
.validVelD
= true;
605 gpsSol
.flags
.validEPE
= true;
607 if (UBX_VALID_GPS_DATE_TIME(_buffer
.pvt
.valid
)) {
608 gpsSol
.time
.year
= _buffer
.pvt
.year
;
609 gpsSol
.time
.month
= _buffer
.pvt
.month
;
610 gpsSol
.time
.day
= _buffer
.pvt
.day
;
611 gpsSol
.time
.hours
= _buffer
.pvt
.hour
;
612 gpsSol
.time
.minutes
= _buffer
.pvt
.min
;
613 gpsSol
.time
.seconds
= _buffer
.pvt
.sec
;
614 gpsSol
.time
.millis
= _buffer
.pvt
.nano
/ (1000*1000);
616 gpsSol
.flags
.validTime
= true;
618 gpsSol
.flags
.validTime
= false;
621 _new_position
= true;
625 if (_class
== CLASS_MON
) {
626 gpsState
.hwVersion
= gpsDecodeHardwareVersion(_buffer
.ver
.hwVersion
, sizeof(_buffer
.ver
.hwVersion
));
627 if (gpsState
.hwVersion
>= UBX_HW_VERSION_UBLOX8
) {
628 if (_buffer
.ver
.swVersion
[9] > '2' || true) {
630 // after hw + sw vers; each is 30 bytes
632 for (int j
= 40; j
< _payload_length
&& !found
; j
+= 30)
634 // Example content: GPS;GAL;BDS;GLO
635 if (strnstr((const char *)(_buffer
.bytes
+ j
), "GAL", 30))
637 ubx_capabilities
.supported
|= UBX_MON_GNSS_GALILEO_MASK
;
640 if (strnstr((const char *)(_buffer
.bytes
+ j
), "BDS", 30))
642 ubx_capabilities
.supported
|= UBX_MON_GNSS_BEIDOU_MASK
;
645 if (strnstr((const char *)(_buffer
.bytes
+ j
), "GLO", 30))
647 ubx_capabilities
.supported
|= UBX_MON_GNSS_GLONASS_MASK
;
652 for (int j
= 40; j
< _payload_length
; j
+= 30) {
653 if (strnstr((const char *)(_buffer
.bytes
+ j
), "PROTVER", 30)) {
654 gpsDecodeProtocolVersion((const char *)(_buffer
.bytes
+ j
), 30);
662 if(_class
== CLASS_MON
) {
663 if (_buffer
.gnss
.version
== 0) {
664 ubx_capabilities
.supported
= _buffer
.gnss
.supported
;
665 ubx_capabilities
.defaultGnss
= _buffer
.gnss
.defaultGnss
;
666 ubx_capabilities
.enabledGnss
= _buffer
.gnss
.enabled
;
667 ubx_capabilities
.capMaxGnss
= _buffer
.gnss
.maxConcurrent
;
668 gpsState
.lastCapaUpdMs
= millis();
673 if ((_ack_state
== UBX_ACK_WAITING
) && (_buffer
.ack
.msg
== _ack_waiting_msg
)) {
674 _ack_state
= UBX_ACK_GOT_ACK
;
678 if ((_ack_state
== UBX_ACK_WAITING
) && (_buffer
.ack
.msg
== _ack_waiting_msg
)) {
679 _ack_state
= UBX_ACK_GOT_NAK
;
686 // we only return true when we get new position and speed data
687 // this ensures we don't use stale data
688 if (_new_position
&& _new_speed
) {
689 _new_speed
= _new_position
= false;
696 static bool gpsNewFrameUBLOX(uint8_t data
)
701 case 0: // Sync char 1 (0xB5)
702 if (PREAMBLE1
== data
) {
703 _skip_packet
= false;
707 case 1: // Sync char 2 (0x62)
708 if (PREAMBLE2
!= data
) {
717 _ck_b
= _ck_a
= data
; // reset the checksum accumulators
721 _ck_b
+= (_ck_a
+= data
); // checksum byte
724 case 4: // Payload length (part 1)
726 _ck_b
+= (_ck_a
+= data
); // checksum byte
727 _payload_length
= data
; // payload length low byte
729 case 5: // Payload length (part 2)
731 _ck_b
+= (_ck_a
+= data
); // checksum byte
732 _payload_length
|= (uint16_t)(data
<< 8);
733 if (_payload_length
> MAX_UBLOX_PAYLOAD_SIZE
) {
734 // we can't receive the whole packet, just log the error and start searching for the next packet.
739 // prepare to receive payload
740 _payload_counter
= 0;
741 if (_payload_length
== 0) {
746 _ck_b
+= (_ck_a
+= data
); // checksum byte
747 if (_payload_counter
< MAX_UBLOX_PAYLOAD_SIZE
) {
748 _buffer
.bytes
[_payload_counter
] = data
;
750 // NOTE: check counter BEFORE increasing so that a payload_size of 65535 is correctly handled. This can happen if garbage data is received.
751 if (_payload_counter
== _payload_length
- 1) {
759 _skip_packet
= true; // bad checksum
769 break; // bad checksum
772 gpsStats
.packetCount
++;
778 if (gpsParseFrameUBLOX()) {
786 static uint16_t hz2rate(uint8_t hz
)
791 STATIC_PROTOTHREAD(gpsConfigure
)
793 ptBegin(gpsConfigure
);
796 gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT
);
799 switch (gpsState
.gpsConfig
->dynModel
) {
800 case GPS_DYNMODEL_PEDESTRIAN
:
801 configureNAV5(UBX_DYNMODEL_PEDESTRIAN
, UBX_FIXMODE_AUTO
);
803 case GPS_DYNMODEL_AIR_1G
: // Default to this
805 configureNAV5(UBX_DYNMODEL_AIR_1G
, UBX_FIXMODE_AUTO
);
807 case GPS_DYNMODEL_AIR_4G
:
808 configureNAV5(UBX_DYNMODEL_AIR_4G
, UBX_FIXMODE_AUTO
);
811 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
813 // Disable NMEA messages
814 gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT
);
816 configureMSG(MSG_CLASS_NMEA
, MSG_NMEA_GGA
, 0);
817 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
819 configureMSG(MSG_CLASS_NMEA
, MSG_NMEA_GLL
, 0);
820 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
822 configureMSG(MSG_CLASS_NMEA
, MSG_NMEA_GSA
, 0);
823 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
825 configureMSG(MSG_CLASS_NMEA
, MSG_NMEA_GSV
, 0);
826 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
828 configureMSG(MSG_CLASS_NMEA
, MSG_NMEA_RMC
, 0);
829 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
831 configureMSG(MSG_CLASS_NMEA
, MSG_NMEA_VGS
, 0);
832 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
834 // Configure UBX binary messages
835 gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT
);
837 // M9N & M10 does not support some of the UBX 6/7/8 messages, so we have to configure it using special sequence
838 if (gpsState
.hwVersion
>= UBX_HW_VERSION_UBLOX9
) {
839 configureMSG(MSG_CLASS_UBX
, MSG_POSLLH
, 0);
840 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
842 configureMSG(MSG_CLASS_UBX
, MSG_STATUS
, 0);
843 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
845 configureMSG(MSG_CLASS_UBX
, MSG_VELNED
, 0);
846 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
848 configureMSG(MSG_CLASS_UBX
, MSG_TIMEUTC
, 0);
849 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
851 configureMSG(MSG_CLASS_UBX
, MSG_PVT
, 1);
852 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
854 configureMSG(MSG_CLASS_UBX
, MSG_NAV_SAT
, 0);
855 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
857 configureMSG(MSG_CLASS_UBX
, MSG_NAV_SIG
, 0);
858 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
860 if ((gpsState
.gpsConfig
->provider
== GPS_UBLOX7PLUS
) && (gpsState
.hwVersion
>= UBX_HW_VERSION_UBLOX7
)) {
861 configureRATE(hz2rate(gpsState
.gpsConfig
->ubloxNavHz
)); // default 10Hz
863 configureRATE(hz2rate(5)); // 5Hz
864 gpsConfigMutable()->ubloxNavHz
= SETTING_GPS_UBLOX_NAV_HZ_DEFAULT
;
866 ptWait(_ack_state
== UBX_ACK_GOT_ACK
|| _ack_state
== UBX_ACK_GOT_NAK
);
868 if(_ack_state
== UBX_ACK_GOT_NAK
) { // Fallback to safe 5Hz in case of error
869 configureRATE(hz2rate(5)); // 5Hz
870 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
874 // u-Blox 5/6/7/8 or unknown
875 // u-Blox 7-8 support PVT
876 if (gpsState
.hwVersion
>= UBX_HW_VERSION_UBLOX7
) {
877 configureMSG(MSG_CLASS_UBX
, MSG_POSLLH
, 0);
878 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
880 configureMSG(MSG_CLASS_UBX
, MSG_STATUS
, 0);
881 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
883 configureMSG(MSG_CLASS_UBX
, MSG_SOL
, 1);
884 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
886 configureMSG(MSG_CLASS_UBX
, MSG_VELNED
, 0);
887 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
889 configureMSG(MSG_CLASS_UBX
, MSG_TIMEUTC
, 0);
890 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
892 configureMSG(MSG_CLASS_UBX
, MSG_PVT
, 1);
893 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
895 configureMSG(MSG_CLASS_UBX
, MSG_SVINFO
, 0);
896 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
898 if ((gpsState
.gpsConfig
->provider
== GPS_UBLOX7PLUS
) && (gpsState
.hwVersion
>= UBX_HW_VERSION_UBLOX7
)) {
899 configureRATE(hz2rate(gpsState
.gpsConfig
->ubloxNavHz
)); // default 10Hz
902 configureRATE(hz2rate(5)); // 5Hz
904 ptWait(_ack_state
== UBX_ACK_GOT_ACK
|| _ack_state
== UBX_ACK_GOT_NAK
);
906 if(_ack_state
== UBX_ACK_GOT_NAK
) { // Fallback to safe 5Hz in case of error
907 configureRATE(hz2rate(5)); // 5Hz
908 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
911 // u-Blox 5/6 doesn't support PVT, use legacy config
912 // UNKNOWN also falls here, use as a last resort
914 configureMSG(MSG_CLASS_UBX
, MSG_POSLLH
, 1);
915 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
917 configureMSG(MSG_CLASS_UBX
, MSG_STATUS
, 1);
918 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
920 configureMSG(MSG_CLASS_UBX
, MSG_SOL
, 1);
921 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
923 configureMSG(MSG_CLASS_UBX
, MSG_VELNED
, 1);
924 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
926 configureMSG(MSG_CLASS_UBX
, MSG_TIMEUTC
, 10);
927 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
929 // This may fail on old UBLOX units, advance forward on both ACK and NAK
930 configureMSG(MSG_CLASS_UBX
, MSG_PVT
, 0);
931 ptWait(_ack_state
== UBX_ACK_GOT_ACK
|| _ack_state
== UBX_ACK_GOT_NAK
);
933 configureMSG(MSG_CLASS_UBX
, MSG_SVINFO
, 0);
934 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
936 // Configure data rate to 5HZ
938 ptWait(_ack_state
== UBX_ACK_GOT_ACK
);
943 // If particular SBAS setting is not supported by the hardware we'll get a NAK,
944 // however GPS would be functional. We are waiting for any response - ACK/NACK
945 gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT
);
947 ptWaitTimeout((_ack_state
== UBX_ACK_GOT_ACK
|| _ack_state
== UBX_ACK_GOT_NAK
), GPS_CFG_CMD_TIMEOUT_MS
);
949 // Configure GNSS for M8N and later
950 if (gpsState
.hwVersion
>= UBX_HW_VERSION_UBLOX8
) {
951 gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT
);
952 if(gpsState
.hwVersion
>= UBX_HW_VERSION_UBLOX10
|| (gpsState
.swVersionMajor
>=23 && gpsState
.swVersionMinor
>= 1)) {
957 ptWaitTimeout((_ack_state
== UBX_ACK_GOT_ACK
|| _ack_state
== UBX_ACK_GOT_NAK
), GPS_CFG_CMD_TIMEOUT_MS
);
959 if(_ack_state
== UBX_ACK_GOT_NAK
) {
960 gpsConfigMutable()->ubloxUseGalileo
= SETTING_GPS_UBLOX_USE_GALILEO_DEFAULT
;
961 gpsConfigMutable()->ubloxUseBeidou
= SETTING_GPS_UBLOX_USE_BEIDOU_DEFAULT
;
962 gpsConfigMutable()->ubloxUseGlonass
= SETTING_GPS_UBLOX_USE_GLONASS_DEFAULT
;
969 static ptSemaphore_t semNewDataReady
;
971 STATIC_PROTOTHREAD(gpsProtocolReceiverThread
)
973 ptBegin(gpsProtocolReceiverThread
);
976 // Wait until there are bytes to consume
977 ptWait(serialRxBytesWaiting(gpsState
.gpsPort
));
979 // Consume bytes until buffer empty of until we have full message received
980 while (serialRxBytesWaiting(gpsState
.gpsPort
)) {
981 uint8_t newChar
= serialRead(gpsState
.gpsPort
);
982 if (gpsNewFrameUBLOX(newChar
)) {
983 ptSemaphoreSignal(semNewDataReady
);
992 STATIC_PROTOTHREAD(gpsProtocolStateThread
)
994 ptBegin(gpsProtocolStateThread
);
997 if (gpsState
.gpsConfig
->autoBaud
!= GPS_AUTOBAUD_OFF
) {
998 // 0. Wait for TX buffer to be empty
999 ptWait(isSerialTransmitBufferEmpty(gpsState
.gpsPort
));
1001 // Try sending baud rate switch command at all common baud rates
1002 gpsSetProtocolTimeout((GPS_BAUD_CHANGE_DELAY
+ 50) * (GPS_BAUDRATE_COUNT
));
1003 for (gpsState
.autoBaudrateIndex
= 0; gpsState
.autoBaudrateIndex
< GPS_BAUDRATE_COUNT
; gpsState
.autoBaudrateIndex
++) {
1004 if (gpsBaudRateToInt(gpsState
.autoBaudrateIndex
) > gpsBaudRateToInt(gpsState
.gpsConfig
->autoBaudMax
)) {
1005 // trying higher baud rates fails on m8 gps
1006 // autoBaudRateIndex is not sorted by baud rate
1009 // 2. Set serial port to baud rate and send an $UBX command to switch the baud rate specified by portConfig [baudrateIndex]
1010 serialSetBaudRate(gpsState
.gpsPort
, baudRates
[gpsToSerialBaudRate
[gpsState
.autoBaudrateIndex
]]);
1011 serialPrint(gpsState
.gpsPort
, baudInitDataNMEA
[gpsState
.baudrateIndex
]);
1013 // 3. Wait for serial port to finish transmitting
1014 ptWait(isSerialTransmitBufferEmpty(gpsState
.gpsPort
));
1016 // 4. Extra wait to make sure GPS processed the command
1017 ptDelayMs(GPS_BAUD_CHANGE_DELAY
);
1019 serialSetBaudRate(gpsState
.gpsPort
, baudRates
[gpsToSerialBaudRate
[gpsState
.baudrateIndex
]]);
1022 // No auto baud - set port baud rate to [baudrateIndex]
1023 // Wait for TX buffer to be empty
1024 ptWait(isSerialTransmitBufferEmpty(gpsState
.gpsPort
));
1026 // Set baud rate and reset GPS timeout
1027 serialSetBaudRate(gpsState
.gpsPort
, baudRates
[gpsToSerialBaudRate
[gpsState
.baudrateIndex
]]);
1030 // Reset protocol timeout
1031 gpsSetProtocolTimeout(MAX(GPS_TIMEOUT
, ((GPS_VERSION_RETRY_TIMES
+ 3) * GPS_CFG_CMD_TIMEOUT_MS
)));
1033 // Attempt to detect GPS hw version
1034 gpsState
.hwVersion
= UBX_HW_VERSION_UNKNOWN
;
1035 gpsState
.autoConfigStep
= 0;
1039 gpsState
.autoConfigStep
++;
1040 ptWaitTimeout((gpsState
.hwVersion
!= UBX_HW_VERSION_UNKNOWN
), GPS_CFG_CMD_TIMEOUT_MS
);
1041 } while (gpsState
.autoConfigStep
< GPS_VERSION_RETRY_TIMES
&& gpsState
.hwVersion
== UBX_HW_VERSION_UNKNOWN
);
1043 gpsState
.autoConfigStep
= 0;
1044 ubx_capabilities
.supported
= ubx_capabilities
.enabledGnss
= ubx_capabilities
.defaultGnss
= 0;
1045 // M6 and earlier will never get pass this step, so skip it (#9440)
1046 if (gpsState
.hwVersion
> UBX_HW_VERSION_UBLOX6
) {
1048 pollGnssCapabilities();
1049 gpsState
.autoConfigStep
++;
1050 ptWaitTimeout((ubx_capabilities
.capMaxGnss
!= 0), GPS_CFG_CMD_TIMEOUT_MS
);
1051 } while (gpsState
.autoConfigStep
< GPS_VERSION_RETRY_TIMES
&& ubx_capabilities
.capMaxGnss
== 0);
1053 // Configure GPS module if enabled
1054 if (gpsState
.gpsConfig
->autoConfig
) {
1056 ptSpawn(gpsConfigure
);
1059 // GPS setup done, reset timeout
1060 gpsSetProtocolTimeout(gpsState
.baseTimeoutMs
);
1062 // GPS is ready - execute the gpsProcessNewSolutionData() based on gpsProtocolReceiverThread semaphore
1064 ptSemaphoreWait(semNewDataReady
);
1065 gpsProcessNewSolutionData();
1067 if ((gpsState
.gpsConfig
->provider
== GPS_UBLOX
|| gpsState
.gpsConfig
->provider
== GPS_UBLOX7PLUS
)) {
1068 if ((millis() - gpsState
.lastCapaPoolMs
) > GPS_CAPA_INTERVAL
) {
1069 gpsState
.lastCapaPoolMs
= millis();
1071 if (gpsState
.hwVersion
== UBX_HW_VERSION_UNKNOWN
)
1074 ptWaitTimeout((_ack_state
== UBX_ACK_GOT_ACK
|| _ack_state
== UBX_ACK_GOT_NAK
), GPS_CFG_CMD_TIMEOUT_MS
);
1077 pollGnssCapabilities();
1078 ptWaitTimeout((_ack_state
== UBX_ACK_GOT_ACK
|| _ack_state
== UBX_ACK_GOT_NAK
), GPS_CFG_CMD_TIMEOUT_MS
);
1086 void gpsRestartUBLOX(void)
1088 ptSemaphoreInit(semNewDataReady
);
1089 ptRestart(ptGetHandle(gpsProtocolReceiverThread
));
1090 ptRestart(ptGetHandle(gpsProtocolStateThread
));
1093 void gpsHandleUBLOX(void)
1095 // Run the protocol threads
1096 gpsProtocolReceiverThread();
1097 gpsProtocolStateThread();
1099 // If thread stopped - signal communication loss and restart
1100 if (ptIsStopped(ptGetHandle(gpsProtocolReceiverThread
)) || ptIsStopped(ptGetHandle(gpsProtocolStateThread
))) {
1101 gpsSetState(GPS_LOST_COMMUNICATION
);