1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-17 05:15:25 +03:00
betaflight/src/main/io/gps.c
ctzsnooze 09ee27cd97 Refactor sat count checks and GPS trust code
single minimum GPS satellite setting
single required GPS satellite setting
CLI Baro vs GPS trust user interface
GPS trust refactoring
allow arming with GPS_FIX even if not enough sats
required sats must be present to arm
set required sat count to 8
add blackbox headers
2022-09-07 09:55:35 +10:00

1877 lines
64 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This file is part of Cleanflight and Betaflight.
*
* Cleanflight and Betaflight are free software. You can redistribute
* this software and/or modify this software under the terms of the
* GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Cleanflight and Betaflight are distributed in the hope that they
* will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include "platform.h"
#ifdef USE_GPS
#include "build/build_config.h"
#include "build/debug.h"
#include "common/axis.h"
#include "common/gps_conversion.h"
#include "common/maths.h"
#include "common/utils.h"
#include "config/feature.h"
#include "pg/pg.h"
#include "pg/pg_ids.h"
#include "drivers/light_led.h"
#include "drivers/time.h"
#include "io/beeper.h"
#include "io/dashboard.h"
#include "io/gps.h"
#include "io/serial.h"
#include "config/config.h"
#include "fc/runtime_config.h"
#include "flight/imu.h"
#include "flight/pid.h"
#include "flight/gps_rescue.h"
#include "scheduler/scheduler.h"
#include "sensors/sensors.h"
#define LOG_ERROR '?'
#define LOG_IGNORED '!'
#define LOG_SKIPPED '>'
#define LOG_NMEA_GGA 'g'
#define LOG_NMEA_RMC 'r'
#define LOG_UBLOX_SOL 'O'
#define LOG_UBLOX_STATUS 'S'
#define LOG_UBLOX_SVINFO 'I'
#define LOG_UBLOX_POSLLH 'P'
#define LOG_UBLOX_VELNED 'V'
#define DEBUG_SERIAL_BAUD 0 // set to 1 to debug serial port baud config (/100)
#define DEBUG_UBLOX_INIT 0 // set to 1 to debug ublox initialization
#define DEBUG_UBLOX_FRAMES 0 // set to 1 to debug ublox received frames
char gpsPacketLog[GPS_PACKET_LOG_ENTRY_COUNT];
static char *gpsPacketLogChar = gpsPacketLog;
// **********************
// GPS
// **********************
int32_t GPS_home[2];
uint16_t GPS_distanceToHome; // distance to home point in meters
uint32_t GPS_distanceToHomeCm;
int16_t GPS_directionToHome; // direction to home or hol point in degrees * 10
uint32_t GPS_distanceFlownInCm; // distance flown since armed in centimeters
int16_t GPS_verticalSpeedInCmS; // vertical speed in cm/s
int16_t nav_takeoff_bearing;
#define GPS_DISTANCE_FLOWN_MIN_SPEED_THRESHOLD_CM_S 15 // 0.54 km/h 0.335 mph
gpsSolutionData_t gpsSol;
uint32_t GPS_packetCount = 0;
uint32_t GPS_svInfoReceivedCount = 0; // SV = Space Vehicle, counter increments each time SV info is received.
uint8_t GPS_update = 0; // toogle to distinct a GPS position update (directly or via MSP)
uint8_t GPS_numCh; // Details on numCh/svinfo in gps.h
uint8_t GPS_svinfo_chn[GPS_SV_MAXSATS_M8N];
uint8_t GPS_svinfo_svid[GPS_SV_MAXSATS_M8N];
uint8_t GPS_svinfo_quality[GPS_SV_MAXSATS_M8N];
uint8_t GPS_svinfo_cno[GPS_SV_MAXSATS_M8N];
// GPS timeout for wrong baud rate/disconnection/etc in milliseconds (default 2.5second)
#define GPS_TIMEOUT (2500)
// How many entries in gpsInitData array below
#define GPS_INIT_ENTRIES (GPS_BAUDRATE_MAX + 1)
#define GPS_BAUDRATE_CHANGE_DELAY (200)
// Timeout for waiting ACK/NAK in GPS task cycles (0.25s at 100Hz)
#define UBLOX_ACK_TIMEOUT_MAX_COUNT (25)
static serialPort_t *gpsPort;
typedef struct gpsInitData_s {
uint8_t index;
uint8_t baudrateIndex; // see baudRate_e
const char *ubx;
const char *mtk;
} gpsInitData_t;
// NMEA will cycle through these until valid data is received
static const gpsInitData_t gpsInitData[] = {
{ GPS_BAUDRATE_115200, BAUD_115200, "$PUBX,41,1,0003,0001,115200,0*1E\r\n", "$PMTK251,115200*1F\r\n" },
{ GPS_BAUDRATE_57600, BAUD_57600, "$PUBX,41,1,0003,0001,57600,0*2D\r\n", "$PMTK251,57600*2C\r\n" },
{ GPS_BAUDRATE_38400, BAUD_38400, "$PUBX,41,1,0003,0001,38400,0*26\r\n", "$PMTK251,38400*27\r\n" },
{ GPS_BAUDRATE_19200, BAUD_19200, "$PUBX,41,1,0003,0001,19200,0*23\r\n", "$PMTK251,19200*22\r\n" },
// 9600 is not enough for 5Hz updates - leave for compatibility to dumb NMEA that only runs at this speed
{ GPS_BAUDRATE_9600, BAUD_9600, "$PUBX,41,1,0003,0001,9600,0*16\r\n", "" }
};
#define GPS_INIT_DATA_ENTRY_COUNT (sizeof(gpsInitData) / sizeof(gpsInitData[0]))
#define DEFAULT_BAUD_RATE_INDEX 0
#ifdef USE_GPS_UBLOX
enum {
PREAMBLE1 = 0xB5,
PREAMBLE2 = 0x62,
CLASS_NAV = 0x01,
CLASS_ACK = 0x05,
CLASS_CFG = 0x06,
MSG_ACK_NACK = 0x00,
MSG_ACK_ACK = 0x01,
MSG_POSLLH = 0x2,
MSG_STATUS = 0x3,
MSG_SOL = 0x6,
MSG_PVT = 0x7,
MSG_VELNED = 0x12,
MSG_SVINFO = 0x30,
MSG_SAT = 0x35,
MSG_CFG_MSG = 0x1,
MSG_CFG_PRT = 0x00,
MSG_CFG_RATE = 0x08,
MSG_CFG_SET_RATE = 0x01,
MSG_CFG_SBAS = 0x16,
MSG_CFG_NAV_SETTINGS = 0x24,
MSG_CFG_GNSS = 0x3E
} ubx_protocol_bytes;
#define UBLOX_MODE_ENABLED 0x1
#define UBLOX_MODE_TEST 0x2
#define UBLOX_USAGE_RANGE 0x1
#define UBLOX_USAGE_DIFFCORR 0x2
#define UBLOX_USAGE_INTEGRITY 0x4
#define UBLOX_GNSS_ENABLE 0x1
#define UBLOX_GNSS_DEFAULT_SIGCFGMASK 0x10000
#define UBLOX_DYNMODE_PEDESTRIAN 3
#define UBLOX_DYNMODE_AIRBORNE_1G 6
#define UBLOX_DYNMODE_AIRBORNE_4G 8
typedef struct {
uint8_t preamble1;
uint8_t preamble2;
uint8_t msg_class;
uint8_t msg_id;
uint16_t length;
} ubx_header;
typedef struct {
uint8_t gnssId;
uint8_t resTrkCh;
uint8_t maxTrkCh;
uint8_t reserved1;
uint32_t flags;
} ubx_configblock;
typedef struct {
uint8_t msgClass;
uint8_t msgID;
} ubx_poll_msg;
typedef struct {
uint8_t msgClass;
uint8_t msgID;
uint8_t rate;
} ubx_cfg_msg;
typedef struct {
uint16_t measRate;
uint16_t navRate;
uint16_t timeRef;
} ubx_cfg_rate;
typedef struct {
uint8_t mode;
uint8_t usage;
uint8_t maxSBAS;
uint8_t scanmode2;
uint32_t scanmode1;
} ubx_cfg_sbas;
typedef struct {
uint8_t msgVer;
uint8_t numTrkChHw;
uint8_t numTrkChUse;
uint8_t numConfigBlocks;
ubx_configblock configblocks[7];
} ubx_cfg_gnss;
typedef struct {
uint16_t mask;
uint8_t dynModel;
uint8_t fixMode;
int32_t fixedAlt;
uint32_t fixedAltVar;
int8_t minElev;
uint8_t drLimit;
uint16_t pDOP;
uint16_t tDOP;
uint16_t pAcc;
uint16_t tAcc;
uint8_t staticHoldThresh;
uint8_t dgnssTimeout;
uint8_t cnoThreshNumSVs;
uint8_t cnoThresh;
uint8_t reserved0[2];
uint16_t staticHoldMaxDist;
uint8_t utcStandard;
uint8_t reserved1[5];
} ubx_cfg_nav5;
typedef union {
ubx_poll_msg poll_msg;
ubx_cfg_msg cfg_msg;
ubx_cfg_rate cfg_rate;
ubx_cfg_nav5 cfg_nav5;
ubx_cfg_sbas cfg_sbas;
ubx_cfg_gnss cfg_gnss;
} ubx_payload;
typedef struct {
ubx_header header;
ubx_payload payload;
} __attribute__((packed)) ubx_message;
#endif // USE_GPS_UBLOX
typedef enum {
GPS_STATE_UNKNOWN,
GPS_STATE_INITIALIZING,
GPS_STATE_INITIALIZED,
GPS_STATE_CHANGE_BAUD,
GPS_STATE_CONFIGURE,
GPS_STATE_RECEIVING_DATA,
GPS_STATE_LOST_COMMUNICATION,
GPS_STATE_COUNT
} gpsState_e;
// Max time to wait for received data
#define GPS_MAX_WAIT_DATA_RX 30
gpsData_t gpsData;
PG_REGISTER_WITH_RESET_TEMPLATE(gpsConfig_t, gpsConfig, PG_GPS_CONFIG, 1);
PG_RESET_TEMPLATE(gpsConfig_t, gpsConfig,
.provider = GPS_UBLOX,
.sbasMode = SBAS_NONE,
.autoConfig = GPS_AUTOCONFIG_ON,
.autoBaud = GPS_AUTOBAUD_OFF,
.gps_ublox_use_galileo = false,
.gps_ublox_mode = UBLOX_AIRBORNE,
.gps_set_home_point_once = false,
.gps_use_3d_speed = false,
.sbas_integrity = false,
.gpsRequiredSats = GPS_REQUIRED_SAT_COUNT,
.gpsMinimumSats = GPS_MINIMUM_SAT_COUNT
);
static void shiftPacketLog(void)
{
uint32_t i;
for (i = ARRAYLEN(gpsPacketLog) - 1; i > 0 ; i--) {
gpsPacketLog[i] = gpsPacketLog[i-1];
}
}
static bool isConfiguratorConnected() {
return (getArmingDisableFlags() & ARMING_DISABLED_MSP);
}
static void gpsNewData(uint16_t c);
#ifdef USE_GPS_NMEA
static bool gpsNewFrameNMEA(char c);
#endif
#ifdef USE_GPS_UBLOX
static bool gpsNewFrameUBLOX(uint8_t data);
#endif
static void gpsSetState(gpsState_e state)
{
gpsData.lastMessage = millis();
sensorsClear(SENSOR_GPS);
gpsData.state = state;
gpsData.state_position = 0;
gpsData.state_ts = millis();
gpsData.ackState = UBLOX_ACK_IDLE;
}
void gpsInit(void)
{
gpsData.baudrateIndex = 0;
gpsData.errors = 0;
gpsData.timeouts = 0;
memset(gpsPacketLog, 0x00, sizeof(gpsPacketLog));
// init gpsData structure. if we're not actually enabled, don't bother doing anything else
gpsSetState(GPS_STATE_UNKNOWN);
gpsData.lastMessage = millis();
if (gpsConfig()->provider == GPS_MSP) { // no serial ports used when GPS_MSP is configured
gpsSetState(GPS_STATE_INITIALIZED);
return;
}
const serialPortConfig_t *gpsPortConfig = findSerialPortConfig(FUNCTION_GPS);
if (!gpsPortConfig) {
return;
}
while (gpsInitData[gpsData.baudrateIndex].baudrateIndex != gpsPortConfig->gps_baudrateIndex) {
gpsData.baudrateIndex++;
if (gpsData.baudrateIndex >= GPS_INIT_DATA_ENTRY_COUNT) {
gpsData.baudrateIndex = DEFAULT_BAUD_RATE_INDEX;
break;
}
}
portMode_e mode = MODE_RXTX;
#if defined(GPS_NMEA_TX_ONLY)
if (gpsConfig()->provider == GPS_NMEA) {
mode &= ~MODE_TX;
}
#endif
// no callback - buffer will be consumed in gpsUpdate()
gpsPort = openSerialPort(gpsPortConfig->identifier, FUNCTION_GPS, NULL, NULL, baudRates[gpsInitData[gpsData.baudrateIndex].baudrateIndex], mode, SERIAL_NOT_INVERTED);
if (!gpsPort) {
return;
}
// signal GPS "thread" to initialize when it gets to it
gpsSetState(GPS_STATE_INITIALIZING);
}
#ifdef USE_GPS_NMEA
void gpsInitNmea(void)
{
#if !defined(GPS_NMEA_TX_ONLY)
uint32_t now;
#endif
switch (gpsData.state) {
case GPS_STATE_INITIALIZING:
#if !defined(GPS_NMEA_TX_ONLY)
now = millis();
if (now - gpsData.state_ts < 1000) {
return;
}
gpsData.state_ts = now;
if (gpsData.state_position < 1) {
serialSetBaudRate(gpsPort, 4800);
gpsData.state_position++;
} else if (gpsData.state_position < 2) {
// print our FIXED init string for the baudrate we want to be at
serialPrint(gpsPort, "$PSRF100,1,115200,8,1,0*05\r\n");
gpsData.state_position++;
} else {
// we're now (hopefully) at the correct rate, next state will switch to it
gpsSetState(GPS_STATE_CHANGE_BAUD);
}
break;
#endif
case GPS_STATE_CHANGE_BAUD:
#if !defined(GPS_NMEA_TX_ONLY)
now = millis();
if (now - gpsData.state_ts < 1000) {
return;
}
gpsData.state_ts = now;
if (gpsData.state_position < 1) {
serialSetBaudRate(gpsPort, baudRates[gpsInitData[gpsData.baudrateIndex].baudrateIndex]);
gpsData.state_position++;
} else if (gpsData.state_position < 2) {
serialPrint(gpsPort, "$PSRF103,00,6,00,0*23\r\n");
gpsData.state_position++;
} else
#else
{
serialSetBaudRate(gpsPort, baudRates[gpsInitData[gpsData.baudrateIndex].baudrateIndex]);
}
#endif
gpsSetState(GPS_STATE_RECEIVING_DATA);
break;
}
}
#endif // USE_GPS_NMEA
#ifdef USE_GPS_UBLOX
static void ubloxSendByteUpdateChecksum(const uint8_t data, uint8_t *checksumA, uint8_t *checksumB)
{
*checksumA += data;
*checksumB += *checksumA;
serialWrite(gpsPort, data);
}
static void ubloxSendDataUpdateChecksum(const uint8_t *data, uint8_t len, uint8_t *checksumA, uint8_t *checksumB)
{
while (len--) {
ubloxSendByteUpdateChecksum(*data, checksumA, checksumB);
data++;
}
}
static void ubloxSendMessage(const uint8_t *data, uint8_t len)
{
uint8_t checksumA = 0, checksumB = 0;
serialWrite(gpsPort, data[0]);
serialWrite(gpsPort, data[1]);
ubloxSendDataUpdateChecksum(&data[2], len - 2, &checksumA, &checksumB);
serialWrite(gpsPort, checksumA);
serialWrite(gpsPort, checksumB);
// Save state for ACK waiting
gpsData.ackWaitingMsgId = data[3]; //save message id for ACK
gpsData.ackTimeoutCounter = 0;
gpsData.ackState = UBLOX_ACK_WAITING;
}
static void ubloxSendConfigMessage(ubx_message *message, uint8_t msg_id, uint8_t length)
{
message->header.preamble1 = PREAMBLE1;
message->header.preamble2 = PREAMBLE2;
message->header.msg_class = CLASS_CFG;
message->header.msg_id = msg_id;
message->header.length = length;
ubloxSendMessage((const uint8_t *) message, length + 6);
}
static void ubloxSendPollMessage(uint8_t msg_id)
{
ubx_message tx_buffer;
tx_buffer.header.preamble1 = PREAMBLE1;
tx_buffer.header.preamble2 = PREAMBLE2;
tx_buffer.header.msg_class = CLASS_CFG;
tx_buffer.header.msg_id = msg_id;
tx_buffer.header.length = 0;
ubloxSendMessage((const uint8_t *) &tx_buffer, 6);
}
static void ubloxSendNAV5Message(bool airborne) {
ubx_message tx_buffer;
tx_buffer.payload.cfg_nav5.mask = 0xFFFF;
if (airborne) {
#if defined(GPS_UBLOX_MODE_AIRBORNE_1G)
tx_buffer.payload.cfg_nav5.dynModel = UBLOX_DYNMODE_AIRBORNE_1G;
#else
tx_buffer.payload.cfg_nav5.dynModel = UBLOX_DYNMODE_AIRBORNE_4G;
#endif
} else {
tx_buffer.payload.cfg_nav5.dynModel = UBLOX_DYNMODE_PEDESTRIAN;
}
tx_buffer.payload.cfg_nav5.fixMode = 3;
tx_buffer.payload.cfg_nav5.fixedAlt = 0;
tx_buffer.payload.cfg_nav5.fixedAltVar = 10000;
tx_buffer.payload.cfg_nav5.minElev = 5;
tx_buffer.payload.cfg_nav5.drLimit = 0;
tx_buffer.payload.cfg_nav5.pDOP = 250;
tx_buffer.payload.cfg_nav5.tDOP = 250;
tx_buffer.payload.cfg_nav5.pAcc = 100;
tx_buffer.payload.cfg_nav5.tAcc = 300;
tx_buffer.payload.cfg_nav5.staticHoldThresh = 0;
tx_buffer.payload.cfg_nav5.dgnssTimeout = 60;
tx_buffer.payload.cfg_nav5.cnoThreshNumSVs = 0;
tx_buffer.payload.cfg_nav5.cnoThresh = 0;
tx_buffer.payload.cfg_nav5.reserved0[0] = 0;
tx_buffer.payload.cfg_nav5.reserved0[1] = 0;
tx_buffer.payload.cfg_nav5.staticHoldMaxDist = 200;
tx_buffer.payload.cfg_nav5.utcStandard = 0;
tx_buffer.payload.cfg_nav5.reserved1[0] = 0;
tx_buffer.payload.cfg_nav5.reserved1[1] = 0;
tx_buffer.payload.cfg_nav5.reserved1[2] = 0;
tx_buffer.payload.cfg_nav5.reserved1[3] = 0;
tx_buffer.payload.cfg_nav5.reserved1[4] = 0;
ubloxSendConfigMessage(&tx_buffer, MSG_CFG_NAV_SETTINGS, sizeof(ubx_cfg_nav5));
}
static void ubloxSetMessageRate(uint8_t messageClass, uint8_t messageID, uint8_t rate) {
ubx_message tx_buffer;
tx_buffer.payload.cfg_msg.msgClass = messageClass;
tx_buffer.payload.cfg_msg.msgID = messageID;
tx_buffer.payload.cfg_msg.rate = rate;
ubloxSendConfigMessage(&tx_buffer, MSG_CFG_MSG, sizeof(ubx_cfg_msg));
}
static void ubloxSetNavRate(uint16_t measRate, uint16_t navRate, uint16_t timeRef) {
ubx_message tx_buffer;
tx_buffer.payload.cfg_rate.measRate = measRate;
tx_buffer.payload.cfg_rate.navRate = navRate;
tx_buffer.payload.cfg_rate.timeRef = timeRef;
ubloxSendConfigMessage(&tx_buffer, MSG_CFG_RATE, sizeof(ubx_cfg_rate));
}
static void ubloxSetSbas() {
ubx_message tx_buffer;
//NOTE: default ublox config for sbas mode is: UBLOX_MODE_ENABLED, test is disabled
tx_buffer.payload.cfg_sbas.mode = UBLOX_MODE_TEST;
if (gpsConfig()->sbasMode != SBAS_NONE) {
tx_buffer.payload.cfg_sbas.mode |= UBLOX_MODE_ENABLED;
}
//NOTE: default ublox config for sbas mode is: UBLOX_USAGE_RANGE | UBLOX_USAGE_DIFFCORR, integrity is disabled
tx_buffer.payload.cfg_sbas.usage = UBLOX_USAGE_RANGE | UBLOX_USAGE_DIFFCORR;
if (gpsConfig()->sbas_integrity) {
tx_buffer.payload.cfg_sbas.usage |= UBLOX_USAGE_INTEGRITY;
}
tx_buffer.payload.cfg_sbas.maxSBAS = 3;
tx_buffer.payload.cfg_sbas.scanmode2 = 0;
switch (gpsConfig()->sbasMode) {
case SBAS_AUTO:
tx_buffer.payload.cfg_sbas.scanmode1 = 0;
break;
case SBAS_EGNOS:
tx_buffer.payload.cfg_sbas.scanmode1 = 0x00010048; //PRN123, PRN126, PRN136
break;
case SBAS_WAAS:
tx_buffer.payload.cfg_sbas.scanmode1 = 0x0004A800; //PRN131, PRN133, PRN135, PRN138
break;
case SBAS_MSAS:
tx_buffer.payload.cfg_sbas.scanmode1 = 0x00020200; //PRN129, PRN137
break;
case SBAS_GAGAN:
tx_buffer.payload.cfg_sbas.scanmode1 = 0x00001180; //PRN127, PRN128, PRN132
break;
default:
tx_buffer.payload.cfg_sbas.scanmode1 = 0;
break;
}
ubloxSendConfigMessage(&tx_buffer, MSG_CFG_SBAS, sizeof(ubx_cfg_sbas));
}
void gpsInitUblox(void)
{
uint32_t now;
// UBX will run at the serial port's baudrate, it shouldn't be "autodetected". So here we force it to that rate
// Wait until GPS transmit buffer is empty
if (!isSerialTransmitBufferEmpty(gpsPort))
return;
switch (gpsData.state) {
case GPS_STATE_INITIALIZING:
now = millis();
if (now - gpsData.state_ts < GPS_BAUDRATE_CHANGE_DELAY)
return;
if (gpsData.state_position < GPS_INIT_ENTRIES) {
// try different speed to INIT
baudRate_e newBaudRateIndex = gpsInitData[gpsData.state_position].baudrateIndex;
gpsData.state_ts = now;
if (lookupBaudRateIndex(serialGetBaudRate(gpsPort)) != newBaudRateIndex) {
// change the rate if needed and wait a little
serialSetBaudRate(gpsPort, baudRates[newBaudRateIndex]);
#if DEBUG_SERIAL_BAUD
debug[0] = baudRates[newBaudRateIndex] / 100;
#endif
return;
}
// print our FIXED init string for the baudrate we want to be at
serialPrint(gpsPort, gpsInitData[gpsData.baudrateIndex].ubx);
gpsData.state_position++;
} else {
// we're now (hopefully) at the correct rate, next state will switch to it
gpsSetState(GPS_STATE_CHANGE_BAUD);
}
break;
case GPS_STATE_CHANGE_BAUD:
serialSetBaudRate(gpsPort, baudRates[gpsInitData[gpsData.baudrateIndex].baudrateIndex]);
#if DEBUG_SERIAL_BAUD
debug[0] = baudRates[gpsInitData[gpsData.baudrateIndex].baudrateIndex] / 100;
#endif
gpsSetState(GPS_STATE_CONFIGURE);
break;
case GPS_STATE_CONFIGURE:
// Either use specific config file for GPS or let dynamically upload config
if (gpsConfig()->autoConfig == GPS_AUTOCONFIG_OFF) {
gpsSetState(GPS_STATE_RECEIVING_DATA);
break;
}
if (gpsData.ackState == UBLOX_ACK_IDLE) {
switch (gpsData.state_position) {
case 0:
gpsData.ubloxUsePVT = true;
gpsData.ubloxUseSAT = true;
ubloxSendNAV5Message(gpsConfig()->gps_ublox_mode == UBLOX_AIRBORNE);
break;
case 1: //Disable NMEA Messages
ubloxSetMessageRate(0xF0, 0x05, 0); // VGS: Course over ground and Ground speed
break;
case 2:
ubloxSetMessageRate(0xF0, 0x03, 0); // GSV: GNSS Satellites in View
break;
case 3:
ubloxSetMessageRate(0xF0, 0x01, 0); // GLL: Latitude and longitude, with time of position fix and status
break;
case 4:
ubloxSetMessageRate(0xF0, 0x00, 0); // GGA: Global positioning system fix data
break;
case 5:
ubloxSetMessageRate(0xF0, 0x02, 0); // GSA: GNSS DOP and Active Satellites
break;
case 6:
ubloxSetMessageRate(0xF0, 0x04, 0); // RMC: Recommended Minimum data
break;
case 7: //Enable UBLOX Messages
if (gpsData.ubloxUsePVT) {
ubloxSetMessageRate(CLASS_NAV, MSG_PVT, 1); // set PVT MSG rate
} else {
ubloxSetMessageRate(CLASS_NAV, MSG_SOL, 1); // set SOL MSG rate
}
break;
case 8:
if (gpsData.ubloxUsePVT) {
gpsData.state_position++;
} else {
ubloxSetMessageRate(CLASS_NAV, MSG_POSLLH, 1); // set POSLLH MSG rate
}
break;
case 9:
if (gpsData.ubloxUsePVT) {
gpsData.state_position++;
} else {
ubloxSetMessageRate(CLASS_NAV, MSG_STATUS, 1); // set STATUS MSG rate
}
break;
case 10:
if (gpsData.ubloxUsePVT) {
gpsData.state_position++;
} else {
ubloxSetMessageRate(CLASS_NAV, MSG_VELNED, 1); // set VELNED MSG rate
}
break;
case 11:
if (gpsData.ubloxUseSAT) {
ubloxSetMessageRate(CLASS_NAV, MSG_SAT, 5); // set SAT MSG rate (every 5 cycles)
} else {
ubloxSetMessageRate(CLASS_NAV, MSG_SVINFO, 5); // set SVINFO MSG rate (every 5 cycles)
}
break;
case 12:
ubloxSetNavRate(0x64, 1, 1); // set rate to 10Hz (measurement period: 100ms, navigation rate: 1 cycle) (for 5Hz use 0xC8)
break;
case 13:
ubloxSetSbas();
break;
case 14:
if ((gpsConfig()->sbasMode == SBAS_NONE) || (gpsConfig()->gps_ublox_use_galileo)) {
ubloxSendPollMessage(MSG_CFG_GNSS);
} else {
gpsSetState(GPS_STATE_RECEIVING_DATA);
}
break;
default:
break;
}
}
switch (gpsData.ackState) {
case UBLOX_ACK_IDLE:
break;
case UBLOX_ACK_WAITING:
if ((++gpsData.ackTimeoutCounter) == UBLOX_ACK_TIMEOUT_MAX_COUNT) {
gpsSetState(GPS_STATE_LOST_COMMUNICATION);
}
break;
case UBLOX_ACK_GOT_ACK:
if (gpsData.state_position == 14) {
// ublox should be initialised, try receiving
gpsSetState(GPS_STATE_RECEIVING_DATA);
} else {
gpsData.state_position++;
gpsData.ackState = UBLOX_ACK_IDLE;
}
break;
case UBLOX_ACK_GOT_NACK:
if (gpsData.state_position == 7) { // If we were asking for NAV-PVT...
gpsData.ubloxUsePVT = false; // ...retry asking for NAV-SOL
gpsData.ackState = UBLOX_ACK_IDLE;
} else {
if (gpsData.state_position == 11) { // If we were asking for NAV-SAT...
gpsData.ubloxUseSAT = false; // ...retry asking for NAV-SVINFO
gpsData.ackState = UBLOX_ACK_IDLE;
} else {
gpsSetState(GPS_STATE_CONFIGURE);
}
}
break;
}
break;
}
}
#endif // USE_GPS_UBLOX
void gpsInitHardware(void)
{
switch (gpsConfig()->provider) {
case GPS_NMEA:
#ifdef USE_GPS_NMEA
gpsInitNmea();
#endif
break;
case GPS_UBLOX:
#ifdef USE_GPS_UBLOX
gpsInitUblox();
#endif
break;
default:
break;
}
}
static void updateGpsIndicator(timeUs_t currentTimeUs)
{
static uint32_t GPSLEDTime;
if ((int32_t)(currentTimeUs - GPSLEDTime) >= 0 && STATE(GPS_FIX) && (gpsSol.numSat >= gpsConfig()->gpsRequiredSats)) {
GPSLEDTime = currentTimeUs + 150000;
LED1_TOGGLE;
}
}
void gpsUpdate(timeUs_t currentTimeUs)
{
static gpsState_e gpsStateDurationUs[GPS_STATE_COUNT];
timeUs_t executeTimeUs;
gpsState_e gpsCurrentState = gpsData.state;
// read out available GPS bytes
if (gpsPort) {
while (serialRxBytesWaiting(gpsPort)) {
if (cmpTimeUs(micros(), currentTimeUs) > GPS_MAX_WAIT_DATA_RX) {
// Wait 1ms and come back
rescheduleTask(TASK_SELF, TASK_PERIOD_HZ(TASK_GPS_RATE_FAST));
return;
}
gpsNewData(serialRead(gpsPort));
}
// Restore default task rate
rescheduleTask(TASK_SELF, TASK_PERIOD_HZ(TASK_GPS_RATE));
} else if (GPS_update & GPS_MSP_UPDATE) { // GPS data received via MSP
gpsSetState(GPS_STATE_RECEIVING_DATA);
onGpsNewData();
GPS_update &= ~GPS_MSP_UPDATE;
}
#if DEBUG_UBLOX_INIT
debug[0] = gpsData.state;
debug[1] = gpsData.state_position;
debug[2] = gpsData.ackState;
#endif
switch (gpsData.state) {
case GPS_STATE_UNKNOWN:
case GPS_STATE_INITIALIZED:
break;
case GPS_STATE_INITIALIZING:
case GPS_STATE_CHANGE_BAUD:
case GPS_STATE_CONFIGURE:
gpsInitHardware();
break;
case GPS_STATE_LOST_COMMUNICATION:
gpsData.timeouts++;
if (gpsConfig()->autoBaud) {
// try another rate
gpsData.baudrateIndex++;
gpsData.baudrateIndex %= GPS_INIT_ENTRIES;
}
gpsSol.numSat = 0;
DISABLE_STATE(GPS_FIX);
gpsSetState(GPS_STATE_INITIALIZING);
break;
case GPS_STATE_RECEIVING_DATA:
// check for no data/gps timeout/cable disconnection etc
if (millis() - gpsData.lastMessage > GPS_TIMEOUT) {
gpsSetState(GPS_STATE_LOST_COMMUNICATION);
#ifdef USE_GPS_UBLOX
} else {
if (gpsConfig()->autoConfig == GPS_AUTOCONFIG_ON) { // Only if autoconfig is enabled
switch (gpsData.state_position) {
case 0:
if (!isConfiguratorConnected()) {
if (gpsData.ubloxUseSAT) {
ubloxSetMessageRate(CLASS_NAV, MSG_SAT, 0); // disable SAT MSG
} else {
ubloxSetMessageRate(CLASS_NAV, MSG_SVINFO, 0); // disable SVINFO MSG
}
gpsData.state_position = 1;
}
break;
case 1:
if (STATE(GPS_FIX) && (gpsConfig()->gps_ublox_mode == UBLOX_DYNAMIC)) {
ubloxSendNAV5Message(true);
gpsData.state_position = 2;
}
if (isConfiguratorConnected()) {
gpsData.state_position = 2;
}
break;
case 2:
if (isConfiguratorConnected()) {
if (gpsData.ubloxUseSAT) {
ubloxSetMessageRate(CLASS_NAV, MSG_SAT, 5); // set SAT MSG rate (every 5 cycles)
} else {
ubloxSetMessageRate(CLASS_NAV, MSG_SVINFO, 5); // set SVINFO MSG rate (every 5 cycles)
}
gpsData.state_position = 0;
}
break;
}
}
#endif //USE_GPS_UBLOX
}
break;
}
if (sensors(SENSOR_GPS)) {
updateGpsIndicator(currentTimeUs);
}
if (!ARMING_FLAG(ARMED) && !gpsConfig()->gps_set_home_point_once) {
DISABLE_STATE(GPS_FIX_HOME);
}
#if defined(USE_GPS_RESCUE)
if (gpsRescueIsConfigured()) {
updateGPSRescueState();
}
#endif
static bool hasFix = false;
if (STATE(GPS_FIX_HOME)) {
if (gpsIsHealthy() && gpsSol.numSat >= gpsConfig()->gpsRequiredSats && !hasFix) {
// ready beep sequence on fix or requirements for gps rescue met.
beeper(BEEPER_READY_BEEP);
hasFix = true;
}
} else {
hasFix = false;
}
executeTimeUs = micros() - currentTimeUs;
if (executeTimeUs > gpsStateDurationUs[gpsCurrentState]) {
gpsStateDurationUs[gpsCurrentState] = executeTimeUs;
}
schedulerSetNextStateTime(gpsStateDurationUs[gpsData.state]);
}
static void gpsNewData(uint16_t c)
{
if (!gpsNewFrame(c)) {
return;
}
if (gpsData.state == GPS_STATE_RECEIVING_DATA) {
// new data received and parsed, we're in business
gpsData.lastLastMessage = gpsData.lastMessage;
gpsData.lastMessage = millis();
sensorsSet(SENSOR_GPS);
}
GPS_update ^= GPS_DIRECT_TICK;
#if DEBUG_UBLOX_INIT
debug[3] = GPS_update;
#endif
onGpsNewData();
}
bool gpsNewFrame(uint8_t c)
{
switch (gpsConfig()->provider) {
case GPS_NMEA: // NMEA
#ifdef USE_GPS_NMEA
return gpsNewFrameNMEA(c);
#endif
break;
case GPS_UBLOX: // UBX binary
#ifdef USE_GPS_UBLOX
return gpsNewFrameUBLOX(c);
#endif
break;
default:
break;
}
return false;
}
// Check for healthy communications
bool gpsIsHealthy()
{
return (gpsData.state == GPS_STATE_RECEIVING_DATA);
}
/* This is a light implementation of a GPS frame decoding
This should work with most of modern GPS devices configured to output 5 frames.
It assumes there are some NMEA GGA frames to decode on the serial bus
Now verifies checksum correctly before applying data
Here we use only the following data :
- latitude
- longitude
- GPS fix is/is not ok
- GPS num sat (4 is enough to be +/- reliable)
// added by Mis
- GPS altitude (for OSD displaying)
- GPS speed (for OSD displaying)
*/
#define NO_FRAME 0
#define FRAME_GGA 1
#define FRAME_RMC 2
#define FRAME_GSV 3
// This code is used for parsing NMEA data
/* Alex optimization
The latitude or longitude is coded this way in NMEA frames
dm.f coded as degrees + minutes + minute decimal
Where:
- d can be 1 or more char long. generally: 2 char long for latitude, 3 char long for longitude
- m is always 2 char long
- f can be 1 or more char long
This function converts this format in a unique unsigned long where 1 degree = 10 000 000
EOS increased the precision here, even if we think that the gps is not precise enough, with 10e5 precision it has 76cm resolution
with 10e7 it's around 1 cm now. Increasing it further is irrelevant, since even 1cm resolution is unrealistic, however increased
resolution also increased precision of nav calculations
static uint32_t GPS_coord_to_degrees(char *coordinateString)
{
char *p = s, *d = s;
uint8_t min, deg = 0;
uint16_t frac = 0, mult = 10000;
while (*p) { // parse the string until its end
if (d != s) {
frac += (*p - '0') * mult; // calculate only fractional part on up to 5 digits (d != s condition is true when the . is located)
mult /= 10;
}
if (*p == '.')
d = p; // locate '.' char in the string
p++;
}
if (p == s)
return 0;
while (s < d - 2) {
deg *= 10; // convert degrees : all chars before minutes ; for the first iteration, deg = 0
deg += *(s++) - '0';
}
min = *(d - 1) - '0' + (*(d - 2) - '0') * 10; // convert minutes : 2 previous char before '.'
return deg * 10000000UL + (min * 100000UL + frac) * 10UL / 6;
}
*/
// helper functions
#ifdef USE_GPS_NMEA
static uint32_t grab_fields(char *src, uint8_t mult)
{ // convert string to uint32
uint32_t i;
uint32_t tmp = 0;
int isneg = 0;
for (i = 0; src[i] != 0; i++) {
if ((i == 0) && (src[0] == '-')) { // detect negative sign
isneg = 1;
continue; // jump to next character if the first one was a negative sign
}
if (src[i] == '.') {
i++;
if (mult == 0) {
break;
} else {
src[i + mult] = 0;
}
}
tmp *= 10;
if (src[i] >= '0' && src[i] <= '9') {
tmp += src[i] - '0';
}
if (i >= 15) {
return 0; // out of bounds
}
}
return isneg ? -tmp : tmp; // handle negative altitudes
}
typedef struct gpsDataNmea_s {
int32_t latitude;
int32_t longitude;
uint8_t numSat;
int32_t altitudeCm;
uint16_t speed;
uint16_t hdop;
uint16_t ground_course;
uint32_t time;
uint32_t date;
} gpsDataNmea_t;
static bool gpsNewFrameNMEA(char c)
{
static gpsDataNmea_t gps_Msg;
uint8_t frameOK = 0;
static uint8_t param = 0, offset = 0, parity = 0;
static char string[15];
static uint8_t checksum_param, gps_frame = NO_FRAME;
static uint8_t svMessageNum = 0;
uint8_t svSatNum = 0, svPacketIdx = 0, svSatParam = 0;
switch (c) {
case '$':
param = 0;
offset = 0;
parity = 0;
break;
case ',':
case '*':
string[offset] = 0;
if (param == 0) { //frame identification
gps_frame = NO_FRAME;
if (0 == strcmp(string, "GPGGA") || 0 == strcmp(string, "GNGGA")) {
gps_frame = FRAME_GGA;
} else if (0 == strcmp(string, "GPRMC") || 0 == strcmp(string, "GNRMC")) {
gps_frame = FRAME_RMC;
} else if (0 == strcmp(string, "GPGSV")) {
gps_frame = FRAME_GSV;
}
}
switch (gps_frame) {
case FRAME_GGA: //************* GPGGA FRAME parsing
switch (param) {
// case 1: // Time information
// break;
case 2:
gps_Msg.latitude = GPS_coord_to_degrees(string);
break;
case 3:
if (string[0] == 'S')
gps_Msg.latitude *= -1;
break;
case 4:
gps_Msg.longitude = GPS_coord_to_degrees(string);
break;
case 5:
if (string[0] == 'W')
gps_Msg.longitude *= -1;
break;
case 6:
gpsSetFixState(string[0] > '0');
break;
case 7:
gps_Msg.numSat = grab_fields(string, 0);
break;
case 8:
gps_Msg.hdop = grab_fields(string, 1) * 100; // hdop
break;
case 9:
gps_Msg.altitudeCm = grab_fields(string, 1) * 10; // altitude in centimeters. Note: NMEA delivers altitude with 1 or 3 decimals. It's safer to cut at 0.1m and multiply by 10
break;
}
break;
case FRAME_RMC: //************* GPRMC FRAME parsing
switch (param) {
case 1:
gps_Msg.time = grab_fields(string, 2); // UTC time hhmmss.ss
break;
case 7:
gps_Msg.speed = ((grab_fields(string, 1) * 5144L) / 1000L); // speed in cm/s added by Mis
break;
case 8:
gps_Msg.ground_course = (grab_fields(string, 1)); // ground course deg * 10
break;
case 9:
gps_Msg.date = grab_fields(string, 0); // date dd/mm/yy
break;
}
break;
case FRAME_GSV:
switch (param) {
/*case 1:
// Total number of messages of this type in this cycle
break; */
case 2:
// Message number
svMessageNum = grab_fields(string, 0);
break;
case 3:
// Total number of SVs visible
GPS_numCh = grab_fields(string, 0);
if (GPS_numCh > GPS_SV_MAXSATS_LEGACY) {
GPS_numCh = GPS_SV_MAXSATS_LEGACY;
}
break;
}
if (param < 4)
break;
svPacketIdx = (param - 4) / 4 + 1; // satellite number in packet, 1-4
svSatNum = svPacketIdx + (4 * (svMessageNum - 1)); // global satellite number
svSatParam = param - 3 - (4 * (svPacketIdx - 1)); // parameter number for satellite
if (svSatNum > GPS_SV_MAXSATS_LEGACY)
break;
switch (svSatParam) {
case 1:
// SV PRN number
GPS_svinfo_chn[svSatNum - 1] = svSatNum;
GPS_svinfo_svid[svSatNum - 1] = grab_fields(string, 0);
break;
/*case 2:
// Elevation, in degrees, 90 maximum
break;
case 3:
// Azimuth, degrees from True North, 000 through 359
break; */
case 4:
// SNR, 00 through 99 dB (null when not tracking)
GPS_svinfo_cno[svSatNum - 1] = grab_fields(string, 0);
GPS_svinfo_quality[svSatNum - 1] = 0; // only used by ublox
break;
}
GPS_svInfoReceivedCount++;
break;
}
param++;
offset = 0;
if (c == '*')
checksum_param = 1;
else
parity ^= c;
break;
case '\r':
case '\n':
if (checksum_param) { //parity checksum
shiftPacketLog();
uint8_t checksum = 16 * ((string[0] >= 'A') ? string[0] - 'A' + 10 : string[0] - '0') + ((string[1] >= 'A') ? string[1] - 'A' + 10 : string[1] - '0');
if (checksum == parity) {
*gpsPacketLogChar = LOG_IGNORED;
GPS_packetCount++;
switch (gps_frame) {
case FRAME_GGA:
*gpsPacketLogChar = LOG_NMEA_GGA;
frameOK = 1;
if (STATE(GPS_FIX)) {
gpsSol.llh.lat = gps_Msg.latitude;
gpsSol.llh.lon = gps_Msg.longitude;
gpsSol.numSat = gps_Msg.numSat;
gpsSol.llh.altCm = gps_Msg.altitudeCm;
gpsSol.hdop = gps_Msg.hdop;
}
break;
case FRAME_RMC:
*gpsPacketLogChar = LOG_NMEA_RMC;
gpsSol.groundSpeed = gps_Msg.speed;
gpsSol.groundCourse = gps_Msg.ground_course;
#ifdef USE_RTC_TIME
// This check will miss 00:00:00.00, but we shouldn't care - next report will be valid
if(!rtcHasTime() && gps_Msg.date != 0 && gps_Msg.time != 0) {
dateTime_t temp_time;
temp_time.year = (gps_Msg.date % 100) + 2000;
temp_time.month = (gps_Msg.date / 100) % 100;
temp_time.day = (gps_Msg.date / 10000) % 100;
temp_time.hours = (gps_Msg.time / 1000000) % 100;
temp_time.minutes = (gps_Msg.time / 10000) % 100;
temp_time.seconds = (gps_Msg.time / 100) % 100;
temp_time.millis = (gps_Msg.time & 100) * 10;
rtcSetDateTime(&temp_time);
}
#endif
break;
} // end switch
} else {
*gpsPacketLogChar = LOG_ERROR;
}
}
checksum_param = 0;
break;
default:
if (offset < 15)
string[offset++] = c;
if (!checksum_param)
parity ^= c;
}
return frameOK;
}
#endif // USE_GPS_NMEA
#ifdef USE_GPS_UBLOX
// UBX support
typedef struct {
uint32_t time; // GPS msToW
int32_t longitude;
int32_t latitude;
int32_t altitude_ellipsoid;
int32_t altitudeMslMm;
uint32_t horizontal_accuracy;
uint32_t vertical_accuracy;
} ubx_nav_posllh;
typedef struct {
uint32_t time; // GPS msToW
uint8_t fix_type;
uint8_t fix_status;
uint8_t differential_status;
uint8_t res;
uint32_t time_to_first_fix;
uint32_t uptime; // milliseconds
} ubx_nav_status;
typedef struct {
uint32_t time;
int32_t time_nsec;
int16_t week;
uint8_t fix_type;
uint8_t fix_status;
int32_t ecef_x;
int32_t ecef_y;
int32_t ecef_z;
uint32_t position_accuracy_3d;
int32_t ecef_x_velocity;
int32_t ecef_y_velocity;
int32_t ecef_z_velocity;
uint32_t speed_accuracy;
uint16_t position_DOP;
uint8_t res;
uint8_t satellites;
uint32_t res2;
} ubx_nav_solution;
typedef struct {
uint32_t time;
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint8_t valid;
uint32_t tAcc;
int32_t nano;
uint8_t fixType;
uint8_t flags;
uint8_t flags2;
uint8_t numSV;
int32_t lon;
int32_t lat;
int32_t height;
int32_t hMSL;
uint32_t hAcc;
uint32_t vAcc;
int32_t velN;
int32_t velE;
int32_t velD;
int32_t gSpeed;
int32_t headMot;
uint32_t sAcc;
uint32_t headAcc;
uint16_t pDOP;
uint8_t flags3;
uint8_t reserved0[5];
int32_t headVeh;
int16_t magDec;
uint16_t magAcc;
} ubx_nav_pvt;
typedef struct {
uint32_t time; // GPS msToW
int32_t ned_north;
int32_t ned_east;
int32_t ned_down;
uint32_t speed_3d;
uint32_t speed_2d;
int32_t heading_2d;
uint32_t speed_accuracy;
uint32_t heading_accuracy;
} ubx_nav_velned;
typedef struct {
uint8_t chn; // Channel number, 255 for SVx not assigned to channel
uint8_t svid; // Satellite ID
uint8_t flags; // Bitmask
uint8_t quality; // Bitfield
uint8_t cno; // Carrier to Noise Ratio (Signal Strength) // dbHz, 0-55.
uint8_t elev; // Elevation in integer degrees
int16_t azim; // Azimuth in integer degrees
int32_t prRes; // Pseudo range residual in centimetres
} ubx_nav_svinfo_channel;
typedef struct {
uint8_t gnssId;
uint8_t svId; // Satellite ID
uint8_t cno; // Carrier to Noise Ratio (Signal Strength) // dbHz, 0-55.
int8_t elev; // Elevation in integer degrees
int16_t azim; // Azimuth in integer degrees
int16_t prRes; // Pseudo range residual in decimetres
uint32_t flags; // Bitmask
} ubx_nav_sat_sv;
typedef struct {
uint32_t time; // GPS Millisecond time of week
uint8_t numCh; // Number of channels
uint8_t globalFlags; // Bitmask, Chip hardware generation 0:Antaris, 1:u-blox 5, 2:u-blox 6
uint16_t reserved2; // Reserved
ubx_nav_svinfo_channel channel[GPS_SV_MAXSATS_M8N]; // 32 satellites * 12 byte
} ubx_nav_svinfo;
typedef struct {
uint32_t time; // GPS Millisecond time of week
uint8_t version;
uint8_t numSvs;
uint8_t reserved0[2];
ubx_nav_sat_sv svs[GPS_SV_MAXSATS_M9N];
} ubx_nav_sat;
typedef struct {
uint8_t clsId; // Class ID of the acknowledged message
uint8_t msgId; // Message ID of the acknowledged message
} ubx_ack;
enum {
FIX_NONE = 0,
FIX_DEAD_RECKONING = 1,
FIX_2D = 2,
FIX_3D = 3,
FIX_GPS_DEAD_RECKONING = 4,
FIX_TIME = 5
} ubs_nav_fix_type;
enum {
NAV_STATUS_FIX_VALID = 1,
NAV_STATUS_TIME_WEEK_VALID = 4,
NAV_STATUS_TIME_SECOND_VALID = 8
} ubx_nav_status_bits;
enum {
NAV_VALID_DATE = 1,
NAV_VALID_TIME = 2
} ubx_nav_pvt_valid;
// Packet checksum accumulators
static uint8_t _ck_a;
static uint8_t _ck_b;
// State machine state
static bool _skip_packet;
static uint8_t _step;
static uint8_t _msg_id;
static uint16_t _payload_length;
static uint16_t _payload_counter;
static bool next_fix;
static uint8_t _class;
// do we have new position information?
static bool _new_position;
// do we have new speed information?
static bool _new_speed;
// Example packet sizes from UBlox u-center from a Glonass capable GPS receiver.
//15:17:55 R -> UBX NAV-STATUS, Size 24, 'Navigation Status'
//15:17:55 R -> UBX NAV-POSLLH, Size 36, 'Geodetic Position'
//15:17:55 R -> UBX NAV-VELNED, Size 44, 'Velocity in WGS 84'
//15:17:55 R -> UBX NAV-CLOCK, Size 28, 'Clock Status'
//15:17:55 R -> UBX NAV-AOPSTATUS, Size 24, 'AOP Status'
//15:17:55 R -> UBX 03-09, Size 208, 'Unknown'
//15:17:55 R -> UBX 03-10, Size 336, 'Unknown'
//15:17:55 R -> UBX NAV-SOL, Size 60, 'Navigation Solution'
//15:17:55 R -> UBX NAV, Size 100, 'Navigation'
//15:17:55 R -> UBX NAV-SVINFO, Size 328, 'Satellite Status and Information'
// from the UBlox9 document, the largest payout we receive is the NAV-SAT and the payload size
// is calculated as 8 + 12*numCh. numCh in the case of a M9N is 42.
#define UBLOX_PAYLOAD_SIZE (8 + 12 * 42)
// Receive buffer
static union {
ubx_nav_posllh posllh;
ubx_nav_status status;
ubx_nav_solution solution;
ubx_nav_velned velned;
ubx_nav_pvt pvt;
ubx_nav_svinfo svinfo;
ubx_nav_sat sat;
ubx_cfg_gnss gnss;
ubx_ack ack;
uint8_t bytes[UBLOX_PAYLOAD_SIZE];
} _buffer;
void _update_checksum(uint8_t *data, uint8_t len, uint8_t *ck_a, uint8_t *ck_b)
{
while (len--) {
*ck_a += *data;
*ck_b += *ck_a;
data++;
}
}
static bool UBLOX_parse_gps(void)
{
uint32_t i;
*gpsPacketLogChar = LOG_IGNORED;
switch (_msg_id) {
case MSG_POSLLH:
*gpsPacketLogChar = LOG_UBLOX_POSLLH;
//i2c_dataset.time = _buffer.posllh.time;
gpsSol.llh.lon = _buffer.posllh.longitude;
gpsSol.llh.lat = _buffer.posllh.latitude;
gpsSol.llh.altCm = _buffer.posllh.altitudeMslMm / 10; //alt in cm
gpsSetFixState(next_fix);
_new_position = true;
break;
case MSG_STATUS:
*gpsPacketLogChar = LOG_UBLOX_STATUS;
next_fix = (_buffer.status.fix_status & NAV_STATUS_FIX_VALID) && (_buffer.status.fix_type == FIX_3D);
if (!next_fix)
DISABLE_STATE(GPS_FIX);
break;
case MSG_SOL:
*gpsPacketLogChar = LOG_UBLOX_SOL;
next_fix = (_buffer.solution.fix_status & NAV_STATUS_FIX_VALID) && (_buffer.solution.fix_type == FIX_3D);
if (!next_fix)
DISABLE_STATE(GPS_FIX);
gpsSol.numSat = _buffer.solution.satellites;
gpsSol.hdop = _buffer.solution.position_DOP;
#ifdef USE_RTC_TIME
//set clock, when gps time is available
if(!rtcHasTime() && (_buffer.solution.fix_status & NAV_STATUS_TIME_SECOND_VALID) && (_buffer.solution.fix_status & NAV_STATUS_TIME_WEEK_VALID)) {
//calculate rtctime: week number * ms in a week + ms of week + fractions of second + offset to UNIX reference year - 18 leap seconds
rtcTime_t temp_time = (((int64_t) _buffer.solution.week)*7*24*60*60*1000) + _buffer.solution.time + (_buffer.solution.time_nsec/1000000) + 315964800000LL - 18000;
rtcSet(&temp_time);
}
#endif
break;
case MSG_VELNED:
*gpsPacketLogChar = LOG_UBLOX_VELNED;
gpsSol.speed3d = _buffer.velned.speed_3d; // cm/s
gpsSol.groundSpeed = _buffer.velned.speed_2d; // cm/s
gpsSol.groundCourse = (uint16_t) (_buffer.velned.heading_2d / 10000); // Heading 2D deg * 100000 rescaled to deg * 10
_new_speed = true;
break;
case MSG_PVT:
*gpsPacketLogChar = LOG_UBLOX_SOL;
next_fix = (_buffer.pvt.flags & NAV_STATUS_FIX_VALID) && (_buffer.pvt.fixType == FIX_3D);
gpsSol.llh.lon = _buffer.pvt.lon;
gpsSol.llh.lat = _buffer.pvt.lat;
gpsSol.llh.altCm = _buffer.pvt.hMSL / 10; //alt in cm
gpsSetFixState(next_fix);
_new_position = true;
gpsSol.numSat = _buffer.pvt.numSV;
gpsSol.hdop = _buffer.pvt.pDOP;
gpsSol.speed3d = (uint16_t) sqrtf(powf(_buffer.pvt.gSpeed / 10, 2.0f) + powf(_buffer.pvt.velD / 10, 2.0f));
gpsSol.groundSpeed = _buffer.pvt.gSpeed / 10; // cm/s
gpsSol.groundCourse = (uint16_t) (_buffer.pvt.headMot / 10000); // Heading 2D deg * 100000 rescaled to deg * 10
_new_speed = true;
#ifdef USE_RTC_TIME
//set clock, when gps time is available
if (!rtcHasTime() && (_buffer.pvt.valid & NAV_VALID_DATE) && (_buffer.pvt.valid & NAV_VALID_TIME)) {
dateTime_t dt;
dt.year = _buffer.pvt.year;
dt.month = _buffer.pvt.month;
dt.day = _buffer.pvt.day;
dt.hours = _buffer.pvt.hour;
dt.minutes = _buffer.pvt.min;
dt.seconds = _buffer.pvt.sec;
dt.millis = (_buffer.pvt.nano > 0) ? _buffer.pvt.nano / 1000 : 0; //up to 5ms of error
rtcSetDateTime(&dt);
}
#endif
break;
case MSG_SVINFO:
*gpsPacketLogChar = LOG_UBLOX_SVINFO;
GPS_numCh = _buffer.svinfo.numCh;
// If we're getting NAV-SVINFO is because we're dealing with an old receiver that does not support NAV-SAT, so we'll only
// save up to GPS_SV_MAXSATS_LEGACY channels so the BF Configurator knows it's receiving the old sat list info format.
if (GPS_numCh > GPS_SV_MAXSATS_LEGACY)
GPS_numCh = GPS_SV_MAXSATS_LEGACY;
for (i = 0; i < GPS_numCh; i++) {
GPS_svinfo_chn[i] = _buffer.svinfo.channel[i].chn;
GPS_svinfo_svid[i] = _buffer.svinfo.channel[i].svid;
GPS_svinfo_quality[i] =_buffer.svinfo.channel[i].quality;
GPS_svinfo_cno[i] = _buffer.svinfo.channel[i].cno;
}
for (i = GPS_numCh; i < GPS_SV_MAXSATS_LEGACY; i++) {
GPS_svinfo_chn[i] = 0;
GPS_svinfo_svid[i] = 0;
GPS_svinfo_quality[i] = 0;
GPS_svinfo_cno[i] = 0;
}
GPS_svInfoReceivedCount++;
break;
case MSG_SAT:
*gpsPacketLogChar = LOG_UBLOX_SVINFO; // The logger won't show this is NAV-SAT instead of NAV-SVINFO
GPS_numCh = _buffer.sat.numSvs;
// We can receive here upto GPS_SV_MAXSATS_M9N channels, but since the majority of receivers currently in use are M8N or older,
// it would be a waste of RAM to size the arrays that big. For now, they're sized GPS_SV_MAXSATS_M8N which means M9N won't show
// all their channel information on BF Configurator. When M9N's are more widespread it would be a good time to increase those arrays.
if (GPS_numCh > GPS_SV_MAXSATS_M8N)
GPS_numCh = GPS_SV_MAXSATS_M8N;
for (i = 0; i < GPS_numCh; i++) {
GPS_svinfo_chn[i] = _buffer.sat.svs[i].gnssId;
GPS_svinfo_svid[i] = _buffer.sat.svs[i].svId;
GPS_svinfo_cno[i] = _buffer.sat.svs[i].cno;
GPS_svinfo_quality[i] =_buffer.sat.svs[i].flags;
}
for (i = GPS_numCh; i < GPS_SV_MAXSATS_M8N; i++) {
GPS_svinfo_chn[i] = 255;
GPS_svinfo_svid[i] = 0;
GPS_svinfo_quality[i] = 0;
GPS_svinfo_cno[i] = 0;
}
// Setting the number of channels higher than GPS_SV_MAXSATS_LEGACY is the only way to tell BF Configurator we're sending the
// enhanced sat list info without changing the MSP protocol. Also, we're sending the complete list each time even if it's empty, so
// BF Conf can erase old entries shown on screen when channels are removed from the list.
GPS_numCh = GPS_SV_MAXSATS_M8N;
GPS_svInfoReceivedCount++;
break;
case MSG_CFG_GNSS:
{
bool isSBASenabled = false;
bool isM8NwithDefaultConfig = false;
if ((_buffer.gnss.numConfigBlocks >= 2) &&
(_buffer.gnss.configblocks[1].gnssId == 1) && //SBAS
(_buffer.gnss.configblocks[1].flags & UBLOX_GNSS_ENABLE)) { //enabled
isSBASenabled = true;
}
if ((_buffer.gnss.numTrkChHw == 32) && //M8N
(_buffer.gnss.numTrkChUse == 32) &&
(_buffer.gnss.numConfigBlocks == 7) &&
(_buffer.gnss.configblocks[2].gnssId == 2) && //Galileo
(_buffer.gnss.configblocks[2].resTrkCh == 4) && //min channels
(_buffer.gnss.configblocks[2].maxTrkCh == 8) && //max channels
!(_buffer.gnss.configblocks[2].flags & UBLOX_GNSS_ENABLE)) { //disabled
isM8NwithDefaultConfig = true;
}
const uint16_t messageSize = 4 + (_buffer.gnss.numConfigBlocks * sizeof(ubx_configblock));
ubx_message tx_buffer;
memcpy(&tx_buffer.payload, &_buffer, messageSize);
if (isSBASenabled && (gpsConfig()->sbasMode == SBAS_NONE)) {
tx_buffer.payload.cfg_gnss.configblocks[1].flags &= ~UBLOX_GNSS_ENABLE; //Disable SBAS
}
if (isM8NwithDefaultConfig && gpsConfig()->gps_ublox_use_galileo) {
tx_buffer.payload.cfg_gnss.configblocks[2].flags |= UBLOX_GNSS_ENABLE; //Enable Galileo
}
ubloxSendConfigMessage(&tx_buffer, MSG_CFG_GNSS, messageSize);
}
break;
case MSG_ACK_ACK:
if ((gpsData.ackState == UBLOX_ACK_WAITING) && (_buffer.ack.msgId == gpsData.ackWaitingMsgId)) {
gpsData.ackState = UBLOX_ACK_GOT_ACK;
}
break;
case MSG_ACK_NACK:
if ((gpsData.ackState == UBLOX_ACK_WAITING) && (_buffer.ack.msgId == gpsData.ackWaitingMsgId)) {
gpsData.ackState = UBLOX_ACK_GOT_NACK;
}
break;
default:
return false;
}
// we only return true when we get new position and speed data
// this ensures we don't use stale data
if (_new_position && _new_speed) {
_new_speed = _new_position = false;
return true;
}
return false;
}
static bool gpsNewFrameUBLOX(uint8_t data)
{
bool parsed = false;
switch (_step) {
case 0: // Sync char 1 (0xB5)
if (PREAMBLE1 == data) {
_skip_packet = false;
_step++;
}
break;
case 1: // Sync char 2 (0x62)
if (PREAMBLE2 != data) {
_step = 0;
break;
}
_step++;
break;
case 2: // Class
_step++;
_class = data;
_ck_b = _ck_a = data; // reset the checksum accumulators
break;
case 3: // Id
_step++;
_ck_b += (_ck_a += data); // checksum byte
_msg_id = data;
#if DEBUG_UBLOX_FRAMES
debug[2] = _msg_id;
#endif
break;
case 4: // Payload length (part 1)
_step++;
_ck_b += (_ck_a += data); // checksum byte
_payload_length = data; // payload length low byte
break;
case 5: // Payload length (part 2)
_step++;
_ck_b += (_ck_a += data); // checksum byte
_payload_length += (uint16_t)(data << 8);
#if DEBUG_UBLOX_FRAMES
debug[3] = _payload_length;
#endif
if (_payload_length > UBLOX_PAYLOAD_SIZE) {
_skip_packet = true;
}
_payload_counter = 0; // prepare to receive payload
if (_payload_length == 0) {
_step = 7;
}
break;
case 6:
_ck_b += (_ck_a += data); // checksum byte
if (_payload_counter < UBLOX_PAYLOAD_SIZE) {
_buffer.bytes[_payload_counter] = data;
}
if (++_payload_counter >= _payload_length) {
_step++;
}
break;
case 7:
_step++;
if (_ck_a != data) {
_skip_packet = true; // bad checksum
gpsData.errors++;
}
break;
case 8:
_step = 0;
shiftPacketLog();
if (_ck_b != data) {
*gpsPacketLogChar = LOG_ERROR;
gpsData.errors++;
break; // bad checksum
}
GPS_packetCount++;
if (_skip_packet) {
*gpsPacketLogChar = LOG_SKIPPED;
break;
}
if (UBLOX_parse_gps()) {
parsed = true;
}
}
return parsed;
}
#endif // USE_GPS_UBLOX
static void gpsHandlePassthrough(uint8_t data)
{
gpsNewData(data);
#ifdef USE_DASHBOARD
if (featureIsEnabled(FEATURE_DASHBOARD)) {
dashboardUpdate(micros());
}
#endif
}
void gpsEnablePassthrough(serialPort_t *gpsPassthroughPort)
{
waitForSerialPortToFinishTransmitting(gpsPort);
waitForSerialPortToFinishTransmitting(gpsPassthroughPort);
if (!(gpsPort->mode & MODE_TX))
serialSetMode(gpsPort, gpsPort->mode | MODE_TX);
#ifdef USE_DASHBOARD
if (featureIsEnabled(FEATURE_DASHBOARD)) {
dashboardShowFixedPage(PAGE_GPS);
}
#endif
serialPassthrough(gpsPort, gpsPassthroughPort, &gpsHandlePassthrough, NULL);
}
float GPS_scaleLonDown = 1.0f; // this is used to offset the shrinking longitude as we go towards the poles
void GPS_calc_longitude_scaling(int32_t lat)
{
float rads = (fabsf((float)lat) / 10000000.0f) * 0.0174532925f;
GPS_scaleLonDown = cos_approx(rads);
}
////////////////////////////////////////////////////////////////////////////////////
// Calculate the distance flown and vertical speed from gps position data
//
static void GPS_calculateDistanceFlownVerticalSpeed(bool initialize)
{
static int32_t lastCoord[2] = { 0, 0 };
static int32_t lastAlt;
static int32_t lastMillis;
int currentMillis = millis();
if (initialize) {
GPS_distanceFlownInCm = 0;
GPS_verticalSpeedInCmS = 0;
} else {
if (STATE(GPS_FIX_HOME) && ARMING_FLAG(ARMED)) {
uint16_t speed = gpsConfig()->gps_use_3d_speed ? gpsSol.speed3d : gpsSol.groundSpeed;
// Only add up movement when speed is faster than minimum threshold
if (speed > GPS_DISTANCE_FLOWN_MIN_SPEED_THRESHOLD_CM_S) {
uint32_t dist;
int32_t dir;
GPS_distance_cm_bearing(&gpsSol.llh.lat, &gpsSol.llh.lon, &lastCoord[GPS_LATITUDE], &lastCoord[GPS_LONGITUDE], &dist, &dir);
if (gpsConfig()->gps_use_3d_speed) {
dist = sqrtf(sq(gpsSol.llh.altCm - lastAlt) + sq(dist));
}
GPS_distanceFlownInCm += dist;
}
}
GPS_verticalSpeedInCmS = (gpsSol.llh.altCm - lastAlt) * 1000 / (currentMillis - lastMillis);
GPS_verticalSpeedInCmS = constrain(GPS_verticalSpeedInCmS, -1500, 1500);
}
lastCoord[GPS_LONGITUDE] = gpsSol.llh.lon;
lastCoord[GPS_LATITUDE] = gpsSol.llh.lat;
lastAlt = gpsSol.llh.altCm;
lastMillis = currentMillis;
}
void GPS_reset_home_position(void)
{
if (!STATE(GPS_FIX_HOME) || !gpsConfig()->gps_set_home_point_once) {
if (STATE(GPS_FIX) && gpsSol.numSat >= gpsConfig()->gpsRequiredSats) {
// requires the full sat count to say we have a home fix
GPS_home[GPS_LATITUDE] = gpsSol.llh.lat;
GPS_home[GPS_LONGITUDE] = gpsSol.llh.lon;
GPS_calc_longitude_scaling(gpsSol.llh.lat);
// need an initial value for distance and bearing calcs, and to set ground altitude
ENABLE_STATE(GPS_FIX_HOME);
}
}
GPS_calculateDistanceFlownVerticalSpeed(true); //Initialize
}
////////////////////////////////////////////////////////////////////////////////////
#define DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR_IN_HUNDREDS_OF_KILOMETERS 1.113195f
#define TAN_89_99_DEGREES 5729.57795f
// Get distance between two points in cm
// Get bearing from pos1 to pos2, returns an 1deg = 100 precision
void GPS_distance_cm_bearing(int32_t *currentLat1, int32_t *currentLon1, int32_t *destinationLat2, int32_t *destinationLon2, uint32_t *dist, int32_t *bearing)
{
float dLat = *destinationLat2 - *currentLat1; // difference of latitude in 1/10 000 000 degrees
float dLon = (float)(*destinationLon2 - *currentLon1) * GPS_scaleLonDown;
*dist = sqrtf(sq(dLat) + sq(dLon)) * DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR_IN_HUNDREDS_OF_KILOMETERS;
*bearing = 9000.0f + atan2_approx(-dLat, dLon) * TAN_89_99_DEGREES; // Convert the output radians to 100xdeg
if (*bearing < 0)
*bearing += 36000;
}
void GPS_calculateDistanceAndDirectionToHome(void)
{
if (STATE(GPS_FIX_HOME)) {
uint32_t dist;
int32_t dir;
GPS_distance_cm_bearing(&gpsSol.llh.lat, &gpsSol.llh.lon, &GPS_home[GPS_LATITUDE], &GPS_home[GPS_LONGITUDE], &dist, &dir);
GPS_distanceToHome = dist / 100; // m/s
GPS_distanceToHomeCm = dist; // cm/sec
GPS_directionToHome = dir / 10; // degrees * 10 or decidegrees
} else {
// If we don't have home set, do not display anything
GPS_distanceToHome = 0;
GPS_distanceToHomeCm = 0;
GPS_directionToHome = 0;
}
}
void onGpsNewData(void)
{
if (!(STATE(GPS_FIX) && gpsSol.numSat >= gpsConfig()->gpsMinimumSats)) {
// if we don't have a 3D fix and the minimum sats, don't give data to GPS rescue
return;
}
GPS_calculateDistanceAndDirectionToHome();
if (ARMING_FLAG(ARMED)) {
GPS_calculateDistanceFlownVerticalSpeed(false);
}
#ifdef USE_GPS_RESCUE
rescueNewGpsData();
#endif
}
void gpsSetFixState(bool state)
{
if (state) {
ENABLE_STATE(GPS_FIX);
ENABLE_STATE(GPS_FIX_EVER);
} else {
DISABLE_STATE(GPS_FIX);
}
}
#endif