First Commit

This commit is contained in:
Nigreon 2020-07-08 23:46:02 +02:00
commit d9897cb710
110 changed files with 21617 additions and 0 deletions

0
include/.empty Normal file
View File

10
lib/BMP180MI/README.md Normal file
View File

@ -0,0 +1,10 @@
Yet Another Arduino BMP085 / BMP180 Digital Pressure Sensor Library
home: https://bitbucket.org/christandlg/bmp180mi
sensor: https://www.bosch-sensortec.com/bst/products/all_products/bmp180
Features:
- Supports I2C via the Wire library
- Supports other I2C libraries via inheritance
- Never blocks or delays (except for convenience functions)

View File

@ -0,0 +1,86 @@
// BMP180_I2C.ino
//
// shows how to use the BMP180MI library with the sensor connected using I2C.
//
// Copyright (c) 2018 Gregor Christandl
//
// connect the BMP180 to the Arduino like this:
// Arduino - BMC180
// 5V ------ VCC
// GND ----- GND
// SDA ----- SDA
// SCL ----- SCL
#include <Arduino.h>
#include <Wire.h>
#include <BMP180MI.h>
#define I2C_ADDRESS 0x77
//create an BMP180 object using the I2C interface
BMP180I2C bmp180(I2C_ADDRESS);
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
//wait for serial connection to open (only necessary on some boards)
while (!Serial);
Wire.begin();
//begin() initializes the interface, checks the sensor ID and reads the calibration parameters.
if (!bmp180.begin())
{
Serial.println("begin() failed. check your BMP180 Interface and I2C Address.");
while (1);
}
//reset sensor to default parameters.
bmp180.resetToDefaults();
//enable ultra high resolution mode for pressure measurements
bmp180.setSamplingMode(BMP180MI::MODE_UHR);
}
void loop() {
// put your main code here, to run repeatedly:
delay(1000);
//start a temperature measurement
if (!bmp180.measureTemperature())
{
Serial.println("could not start temperature measurement, is a measurement already running?");
return;
}
//wait for the measurement to finish. proceed as soon as hasValue() returned true.
do
{
delay(100);
} while (!bmp180.hasValue());
Serial.print("Temperature: ");
Serial.print(bmp180.getTemperature());
Serial.println(" degC");
//start a pressure measurement. pressure measurements depend on temperature measurement, you should only start a pressure
//measurement immediately after a temperature measurement.
if (!bmp180.measurePressure())
{
Serial.println("could not start perssure measurement, is a measurement already running?");
return;
}
//wait for the measurement to finish. proceed as soon as hasValue() returned true.
do
{
delay(100);
} while (!bmp180.hasValue());
Serial.print("Pressure: ");
Serial.print(bmp180.getPressure());
Serial.println(" Pa");
}

View File

@ -0,0 +1,175 @@
// BMP180_otherInterfaces.ino
//
// shows how to use the BMP180MI library with interfaces other than the native I2C or SPI interfaces.
//
// Copyright (c) 2018 Gregor Christandl
//
// connect the bmp180 to the Arduino like this:
// Arduino - bmp180
// 5V ------ VCC
// GND ----- GND
// SDA ----- SDA
// SCL ----- SCL
#include <Arduino.h>
#include <Wire.h>
#include <BMP180MI.h>
#define I2C_ADDRESS 0x77
//class derived from BMP180MI that implements communication using a library other than the native I2C library.
class BMP180Wire1 : public BMP180MI
{
public:
//constructor of the derived class.
//@param address i2c address of the sensor.
BMP180Wire1(uint8_t i2c_address) :
address_(i2c_address) //initialize the BMP180Wire1 classes private member address_ to the i2c address provided
{
//nothing else to do here...
}
private:
//this function must be implemented by derived classes. it is used to initialize the interface. first time communication
//test are not necessary - these checks are done by the BMP180MI class
//@return true on success, false otherwise.
bool beginInterface()
{
Wire1.begin();
return true;
}
//this function must be implemented by derived classes. this function is responsible for reading data from the sensor.
//@param reg register to read.
//@return read data (1 byte).
uint8_t readRegister(uint8_t reg)
{
#if defined(ARDUINO_SAM_DUE)
//workaround for Arduino Due. The Due seems not to send a repeated start with the code above, so this
//undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560)
//see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0
Wire1.requestFrom(address_, 1, reg, 1, true);
#else
Wire1.beginTransmission(address_);
Wire1.write(reg);
Wire1.endTransmission(false);
Wire1.requestFrom(address_, static_cast<uint8_t>(1));
#endif
return Wire1.read();
}
//this function can be implemented by derived classes. implementing this function is optional.
//@param reg register to read.
//@param length number of registers to read (max: 4)
//@return read data. LSB = last register read.
uint32_t readRegisterBurst(uint8_t reg, uint8_t length)
{
if (length > 4)
return 0L;
uint32_t data = 0L;
#if defined(ARDUINO_SAM_DUE)
//workaround for Arduino Due. The Due seems not to send a repeated start with the code below, so this
//undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560)
//see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0
Wire1.requestFrom(address_, length, data, length, true);
#else
Wire1.beginTransmission(address_);
Wire1.write(reg);
Wire1.endTransmission(false);
Wire1.requestFrom(address_, static_cast<uint8_t>(length));
for (uint8_t i = 0; i < length; i++)
{
data <<= 8;
data |= Wire1.read();
}
#endif
return data;
}
//this function must be implemented by derived classes. this function is responsible for sending data to the sensor.
//@param reg register to write to.
//@param data data to write to register.
void writeRegister(uint8_t reg, uint8_t data)
{
Wire1.beginTransmission(address_);
Wire1.write(reg);
Wire1.write(data);
Wire1.endTransmission();
}
uint8_t address_; //i2c address of sensor
};
//create an bmp180 object using the I2C interface, I2C address 0x77 and IRQ pin number 2
BMP180Wire1 bmp180(I2C_ADDRESS);
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
//wait for serial connection to open (only necessary on some boards)
while (!Serial);
//begin() initializes the interface, checks the sensor ID and reads the calibration parameters.
if (!bmp180.begin())
{
Serial.println("begin() failed. check your BMP180 Interface and I2C Address.");
while (1);
}
//reset sensor to default parameters.
bmp180.resetToDefaults();
//enable ultra high resolution mode for pressure measurements
bmp180.setSamplingMode(BMP180MI::MODE_UHR);
//...
}
void loop() {
// put your main code here, to run repeatedly:
delay(1000);
//start a temperature measurement
if (!bmp180.measureTemperature())
{
Serial.println("could not start temperature measurement, is a measurement already running?");
return;
}
//wait for the measurement to finish. proceed as soon as hasValue() returned true.
do
{
delay(100);
} while (!bmp180.hasValue());
Serial.print("Temperature: ");
Serial.print(bmp180.getTemperature());
Serial.println("degC");
//start a pressure measurement. pressure measurements depend on temperature measurement, you should only start a pressure
//measurement immediately after a temperature measurement.
if (!bmp180.measurePressure())
{
Serial.println("could not start perssure measurement, is a measurement already running?");
return;
}
//wait for the measurement to finish. proceed as soon as hasValue() returned true.
do
{
delay(100);
} while (!bmp180.hasValue());
Serial.print("Pressure: ");
Serial.print(bmp180.getPressure());
Serial.println("Pa");
}

25
lib/BMP180MI/keywords.txt Normal file
View File

@ -0,0 +1,25 @@
#######################################
# Syntax Coloring Map For ExampleLibrary
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
BMP180MI KEYWORD1
BMP180I2C KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,10 @@
name=BMP180MI
version=0.1.0
author=Gregor Christandl <christandlg@yahoo.com>
maintainer=Gregor Christandl <christandlg@yahoo.com>
sentence=A library for the Bosch Sensortec BMP085 / BMP180 Digital Pressure Sensors.
paragraph=The library supports I2C (via the Wire Library) interfaces. Use of other I2C libraries (e.g. software I2C) is supported by inheritance. Does not block or delay (except for convenience functions) making it better suited for applications where non-blocking behaviour is preferred.
category=Sensors
url=https://bitbucket.org/christandlg/bmp180mi
architectures=*
includes=BMP180MI.h

View File

@ -0,0 +1,396 @@
//Multi interface Bosch Sensortec BMP280 pressure sensor library
// Copyright (c) 2018 Gregor Christandl <christandlg@yahoo.com>
// home: https://bitbucket.org/christandlg/BMP180
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "BMP180MI.h"
BMP180MI::BMP180MI() :
command_(0x00),
sampling_mode_(BMP180MI::MODE_ULP),
up_(0L),
ut_(0l),
B5_(0L)
{
cal_params_ = {
0, //cp_AC1_
0, //cp_AC2_
0, //cp_AC3_
0, //cp_AC4_
0, //cp_AC5_
0, //cp_AC6_
0, //cp_B1_
0, //cp_B2_
0, //cp_MB_
0, //cp_MC_
0, //cp_MD_
};
}
BMP180MI::~BMP180MI()
{
//nothing to do here...
}
bool BMP180MI::begin()
{
//return false if the interface could not be initialized.
if (!beginInterface())
return false;
//read sensor ID.
uint8_t id = readID();
//check sensor ID. return false if sensor ID does not match a BMP180 ID.
if (id != BMP180MI::BMP180_ID)
return false;
//read compensation parameters
cal_params_ = readCalibrationParameters();
return true;
}
bool BMP180MI::measurePressure()
{
//return false if a measurement is already running.
if (readRegisterValue(BMP180_REG_CTRL_MEAS, BMP180_MASK_SCO))
return false;
command_ = BMP180_CMD_PRESS;
//start a measurement. command includes the 'start of conversion' bit.
writeRegisterValue(BMP180_REG_CTRL_MEAS, BMP180_MASK_OSS | BMP180_MASK_SCO | BMP180_MASK_MCTRL, (sampling_mode_ << 6) | command_);
return true;
}
bool BMP180MI::measureTemperature()
{
//return false if a measurement is already running.
if (readRegisterValue(BMP180_REG_CTRL_MEAS, BMP180_MASK_SCO))
return false;
command_ = BMP180_CMD_TEMP;
//start a measurement. command includes the 'start of conversion' bit.
writeRegisterValue(BMP180_REG_CTRL_MEAS, BMP180_MASK_SCO | BMP180_MASK_MCTRL, command_);
return true;
}
bool BMP180MI::hasValue()
{
if (readRegisterValue(BMP180_REG_CTRL_MEAS, BMP180_MASK_SCO))
return false;
switch (command_)
{
case BMP180_CMD_PRESS:
up_ = static_cast<int32_t>(readRegisterValueBurst(BMP180_REG_OUT, BMP180_MASK_PRESS, 3));
up_ >>= (8 - sampling_mode_);
break;
case BMP180_CMD_TEMP:
ut_ = static_cast<int32_t>(readRegisterValueBurst(BMP180_REG_OUT, BMP180_MASK_TEMP, 2));
break;
default:
return false;
}
return true;
}
float BMP180MI::getPressure()
{
int32_t p = 0;
int32_t B6 = B5_ - 4000;
int32_t X1 = (static_cast<int32_t>(cal_params_.cp_B2_) * ((B6 * B6) >> 12)) >> 11;
int32_t X2 = (static_cast<int32_t>(cal_params_.cp_AC2_) * B6) >> 11;
int32_t X3 = X1 + X2;
int32_t B3 = ((((static_cast<int32_t>(cal_params_.cp_AC1_) << 2) + X3) << sampling_mode_) + 2) >> 2;
X1 = (static_cast<int32_t>(cal_params_.cp_AC3_) * B6) >> 13;
X2 = (static_cast<int32_t>(cal_params_.cp_B1_) * ((B6 * B6) >> 12)) >> 16;
X3 = (X1 + X2 + 2) >> 2;
uint32_t B4 = static_cast<uint32_t>(cal_params_.cp_AC4_) * (static_cast<uint32_t>(X3 + 32768)) >> 15;
uint32_t B7 = static_cast<uint32_t>(up_ - B3) * (50000 >> sampling_mode_);
if (B7 < 0x80000000)
p = (B7 << 1) / B4;
else
p = (B7 / B4) << 1;
X1 = (p >> 8) * (p >> 8);
X1 = (X1 * 3038) >> 16;
X2 = (-7357 * p) >> 16;
p = p + ((X1 + X2 + 3791) >> 4);
return static_cast<float>(p);
}
float BMP180MI::getTemperature()
{
int32_t X1 = ((ut_ - static_cast<int32_t>(cal_params_.cp_AC6_)) * static_cast<int32_t>(cal_params_.cp_AC5_)) >> 15;
int32_t X2 = (static_cast<int32_t>(cal_params_.cp_MC_) << 11) / (X1 + static_cast<int32_t>(cal_params_.cp_MD_));
B5_ = X1 + X2;
int32_t T = (B5_ + 8) >> 4;
return static_cast<float>(T) * 0.1;
}
float BMP180MI::readTemperature()
{
if (!measureTemperature())
return NAN;
do
{
delay(10);
} while (!hasValue());
return getTemperature();
}
float BMP180MI::readPressure()
{
if (isnan(readTemperature()))
return NAN;
if (!measurePressure())
return NAN;
do
{
delay(10);
} while (!hasValue());
return getPressure();
}
uint8_t BMP180MI::readID()
{
return readRegisterValue(BMP180_REG_ID, BMP180_MASK_ID);
}
BMP180MI::BMP180CalParams BMP180MI::readCalibrationParameters()
{
BMP180MI::BMP180CalParams cal_params = {
0, //cp_AC1_
0, //cp_AC2_
0, //cp_AC3_
0, //cp_AC4_
0, //cp_AC5_
0, //cp_AC6_
0, //cp_B1_
0, //cp_B2_
0, //cp_MB_
0, //cp_MC_
0, //cp_MD_
};
//read compensation parameters
cal_params.cp_AC1_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_AC1, BMP180_MASK_CAL, 2));
cal_params.cp_AC2_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_AC2, BMP180_MASK_CAL, 2));
cal_params.cp_AC3_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_AC3, BMP180_MASK_CAL, 2));
cal_params.cp_AC4_ = readRegisterValueBurst(BMP180_REG_CAL_AC4, BMP180_MASK_CAL, 2);
cal_params.cp_AC5_ = readRegisterValueBurst(BMP180_REG_CAL_AC5, BMP180_MASK_CAL, 2);
cal_params.cp_AC6_ = readRegisterValueBurst(BMP180_REG_CAL_AC6, BMP180_MASK_CAL, 2);
cal_params.cp_B1_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_B1, BMP180_MASK_CAL, 2));
cal_params.cp_B2_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_B2, BMP180_MASK_CAL, 2));
cal_params.cp_MB_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_MB, BMP180_MASK_CAL, 2));
cal_params.cp_MC_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_MC, BMP180_MASK_CAL, 2));
cal_params.cp_MD_ = static_cast<int16_t>(readRegisterValueBurst(BMP180_REG_CAL_MD, BMP180_MASK_CAL, 2));
return cal_params;
}
void BMP180MI::resetToDefaults()
{
writeRegister(BMP180_REG_RESET, BMP180_CMD_RESET);
}
uint8_t BMP180MI::getSamplingMode()
{
return sampling_mode_;
}
bool BMP180MI::setSamplingMode(uint8_t mode)
{
switch (mode)
{
case BMP180MI::MODE_ULP:
case BMP180MI::MODE_STD:
case BMP180MI::MODE_HR:
case BMP180MI::MODE_UHR:
sampling_mode_ = mode;
break;
default:
return false;
}
return true;
}
uint8_t BMP180MI::getMaskShift(uint8_t mask)
{
uint8_t return_value = 0;
//count how many times the mask must be shifted right until the lowest bit is set
if (mask != 0)
{
while (!(mask & 1))
{
return_value++;
mask >>= 1;
}
}
return return_value;
}
uint8_t BMP180MI::setMaskedBits(uint8_t reg, uint8_t mask, uint8_t value)
{
//clear mask bits in register
reg &= (~mask);
//set masked bits in register according to value
return ((value << getMaskShift(mask)) & mask) | reg;
}
uint8_t BMP180MI::readRegisterValue(uint8_t reg, uint8_t mask)
{
return getMaskedBits(readRegister(reg), mask);
}
void BMP180MI::writeRegisterValue(uint8_t reg, uint8_t mask, uint8_t value)
{
uint8_t reg_val = readRegister(reg);
writeRegister(reg, setMaskedBits(reg_val, mask, value));
}
uint32_t BMP180MI::readRegisterValueBurst(uint8_t reg, uint32_t mask, uint8_t length)
{
return getMaskedBits(readRegisterBurst(reg, length), mask);
}
uint32_t BMP180MI::readRegisterBurst(uint8_t reg, uint8_t length)
{
if (length > 4)
return 0L;
uint32_t data = 0L;
for (uint8_t i = 0; i < length; i++)
{
data <<= 8;
data |= static_cast<uint32_t>(readRegister(reg));
}
return data;
}
//-----------------------------------------------------------------------
//BMP180I2C
BMP180I2C::BMP180I2C(uint8_t i2c_address) :
address_(i2c_address)
{
//nothing to do here...
}
BMP180I2C::~BMP180I2C()
{
//nothing to do here...
}
bool BMP180I2C::beginInterface()
{
return true;
}
uint8_t BMP180I2C::readRegister(uint8_t reg)
{
#if defined(ARDUINO_SAM_DUE)
//workaround for Arduino Due. The Due seems not to send a repeated start with the code above, so this
//undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560)
//see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0
Wire.requestFrom(address_, 1, reg, 1, true);
#else
Wire.beginTransmission(address_);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(address_, static_cast<uint8_t>(1));
#endif
return Wire.read();
}
uint32_t BMP180I2C::readRegisterBurst(uint8_t reg, uint8_t length)
{
if (length > 4)
return 0L;
uint32_t data = 0L;
#if defined(ARDUINO_SAM_DUE)
//workaround for Arduino Due. The Due seems not to send a repeated start with the code below, so this
//undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560)
//see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0
Wire.requestFrom(address_, length, data, length, true);
#else
Wire.beginTransmission(address_);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(address_, static_cast<uint8_t>(length));
for (uint8_t i = 0; i < length; i++)
{
data <<= 8;
data |= Wire.read();
}
#endif
return data;
}
void BMP180I2C::writeRegister(uint8_t reg, uint8_t value)
{
Wire.beginTransmission(address_);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
}

266
lib/BMP180MI/src/BMP180MI.h Normal file
View File

@ -0,0 +1,266 @@
//Multi interface Bosch Sensortec BMP180 pressure sensor library
// Copyright (c) 2018 Gregor Christandl <christandlg@yahoo.com>
// home: https://bitbucket.org/christandlg/BMP180mi
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#ifndef BMP180MI_H_
#define BMP180MI_H_
#include <Arduino.h>
#include <Wire.h>
class BMP180MI
{
public:
//pressure oversampling modes
enum sampling_mode_t : uint8_t
{
MODE_ULP = 0, //1 sample, ~4.5ms conversion time
MODE_STD = 1, //2 samples, ~7.5ms conversion time
MODE_HR = 2, //4 samples, ~13.5ms conversion time
MODE_UHR = 3 //8 samples, ~25.5ms conversion time
};
//compensation parameters
struct BMP180CalParams {
int16_t cp_AC1_;
int16_t cp_AC2_;
int16_t cp_AC3_;
uint16_t cp_AC4_;
uint16_t cp_AC5_;
uint16_t cp_AC6_;
int16_t cp_B1_;
int16_t cp_B2_;
int16_t cp_MB_;
int16_t cp_MC_;
int16_t cp_MD_;
};
static const uint8_t BMP180_ID = 0x55;
BMP180MI();
virtual ~BMP180MI();
bool begin();
//starts a pressure measurement. must be called as soon as possible after a temperature measurement.
//returns false if a measurement is currently in progress.
//@return true on success, false otherwise.
bool measurePressure();
//starts a temperature measurement. returns false if a measurement
//is currently in progress.
//@return true on success, false otherwise.
bool measureTemperature();
//@return true if a measurement was completed, false otherwise.
bool hasValue();
//@return the last measured pressure, in Pa.
float getPressure();
//@return the last measured temperature, in deg C.
float getTemperature();
//triggers a temperature measurement and returns the measured temperature, in deg C.
//convenience function. blocks until temperature conversion is finished. do not use in time ciritical applications.
//@return temperature in deg C or NAN if the measurement failed.
float readTemperature();
//triggers a temperature measurement followed by a pressure measurement and returns the measured pressure, in Pa.
//convenience function. blocks until pressure conversion is finished. do not use in time ciritical applications.
//@return pressure in Pa or NAN if the measurement failed.
float readPressure();
//@return the ID of the BMP180. the sensor will always return 0x55, so this function
//can be used as a communication check.
uint8_t readID();
/*
@return BMP180CalParams struct containing BMP180 calibration parameters. */
BMP180CalParams readCalibrationParameters();
/*
resets all registers to default values. */
void resetToDefaults();
/*
@return sampling mode as sampling_mode_t. */
uint8_t getSamplingMode();
/*
@param sampling mode as sampling_mode_t.
@return true on success, false otherwise. */
bool setSamplingMode(uint8_t mode);
private:
enum BMP180_register_t : uint8_t
{
BMP180_REG_ID = 0xD0, //contains 0x55 after power on
BMP180_REG_RESET = 0xE0, //write 0xB6 to reset
BMP180_REG_STATUS = 0xF3, //bit 0: im_update, bit 3: measuring
BMP180_REG_CTRL_MEAS = 0xF4, //sets data acquisition options of device
BMP180_REG_CONFIG = 0xF5, //sets the rate, filter and interface options of the device.
BMP180_REG_OUT = 0xF6, //raw conversion results
BMP180_REG_CAL_AC1 = 0xAA, //2 bytes each. can never be 0x0000 or oxFFFF
BMP180_REG_CAL_AC2 = 0xAC,
BMP180_REG_CAL_AC3 = 0xAE,
BMP180_REG_CAL_AC4 = 0xB0,
BMP180_REG_CAL_AC5 = 0xB2,
BMP180_REG_CAL_AC6 = 0xB4,
BMP180_REG_CAL_B1 = 0xB6,
BMP180_REG_CAL_B2 = 0xB8,
BMP180_REG_CAL_MB = 0xBA,
BMP180_REG_CAL_MC = 0xBC,
BMP180_REG_CAL_MD = 0xBE,
};
enum BMP180_command_t : uint8_t
{
BMP180_CMD_TEMP = 0x2E, //start temperature conversion
BMP180_CMD_PRESS = 0x34, //start pressure conversion
};
enum BMP180_mask_t : uint8_t
{
//register 0xD0
BMP180_MASK_ID = 0b11111111,
//register 0xE0
BMP180_MASK_RESET = 0b11111111,
//register 0xF4
BMP180_MASK_OSS = 0b11000000,
BMP180_MASK_SCO = 0b00100000,
BMP180_MASK_MCTRL = 0b00011111,
};
enum BMP180_mask_32bit_t : uint32_t
{
//register 0xF6
BMP180_MASK_PRESS = 0x00FFFFFF, //20 bits
BMP180_MASK_TEMP = 0x0000FFFF, //16 bits
};
static const uint16_t BMP180_MASK_CAL = 0xFFFF;
static const uint8_t BMP180_CMD_RESET = 0xB6;
virtual bool beginInterface() = 0;
/*
@param mask
@return number of bits to shift value so it fits into mask. */
uint8_t getMaskShift(uint8_t mask);
/*
@param register value of register.
@param mask mask of value in register
@return value of masked bits. */
template <class T> T getMaskedBits(T reg, T mask)
{
//extract masked bits
return ((reg & mask) >> getMaskShift(mask));
};
/*
@param register value of register
@param mask mask of value in register
@param value value to write into masked area
@param register value with masked bits set to value. */
uint8_t setMaskedBits(uint8_t reg, uint8_t mask, uint8_t value);
/*
reads the masked value from the register.
@param reg register to read.
@param mask mask of value.
@return masked value in register. */
uint8_t readRegisterValue(uint8_t reg, uint8_t mask);
/*
sets values in a register.
@param reg register to set values in
@param mask bits of register to set value in
@param value value to set */
void writeRegisterValue(uint8_t reg, uint8_t mask, uint8_t value);
/*
reads the masked values from multiple registers. maximum read length is 4 bytes.
@param reg register to read.
@param mask mask of value.
@param length number of bytes to read
@return register content */
uint32_t readRegisterValueBurst(uint8_t reg, uint32_t mask, uint8_t length);
/*
reads a register from the sensor. must be overwritten by derived classes.
@param reg register to read.
@return register content*/
virtual uint8_t readRegister(uint8_t reg) = 0;
/*
reads a series of registers from the sensor. must be overwritten by derived classes.
@param reg register to read.
@param length number of bytes to read
@return register content*/
virtual uint32_t readRegisterBurst(uint8_t reg, uint8_t length);
/*
writes a register to the sensor. must be overwritten by derived classes.
this function is also used to send direct commands.
@param reg register to write to.
@param value value writeRegister write to register. */
virtual void writeRegister(uint8_t reg, uint8_t value) = 0;
BMP180CalParams cal_params_;
uint8_t command_;
uint8_t sampling_mode_;
int32_t up_;
int32_t ut_;
int32_t B5_;
};
class BMP180I2C : public BMP180MI
{
public:
BMP180I2C(uint8_t i2c_address);
virtual ~BMP180I2C();
private:
bool beginInterface();
uint8_t readRegister(uint8_t reg);
uint32_t readRegisterBurst(uint8_t reg, uint8_t length);
void writeRegister(uint8_t reg, uint8_t value);
uint8_t address_;
};
#endif /* BMP180MI_H_ */

17
lib/NeoGPS/.gitattributes vendored Normal file
View File

@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

43
lib/NeoGPS/.gitignore vendored Normal file
View File

@ -0,0 +1,43 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

674
lib/NeoGPS/LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it 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.
This program is distributed in the hope that it 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 program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

80
lib/NeoGPS/README.md Normal file
View File

@ -0,0 +1,80 @@
NeoGPS
======
This fully-configurable Arduino library uses _**minimal**_ RAM, PROGMEM and CPU time,
requiring as few as _**10 bytes of RAM**_, **866 bytes of PROGMEM**, and **less than 1mS of CPU time** per sentence.
It supports the following protocols and messages:
#### NMEA 0183
* GPGGA - System fix data
* GPGLL - Geographic Latitude and Longitude
* GPGSA - DOP and active satellites
* GPGST - Pseudo Range Error Statistics
* GPGSV - Satellites in View
* GPRMC - Recommended Minimum specific GPS/Transit data
* GPVTG - Course over ground and Ground speed
* GPZDA - UTC Time and Date
The "GP" prefix usually indicates an original [GPS](https://en.wikipedia.org/wiki/Satellite_navigation#GPS) source. NeoGPS parses *all* Talker IDs, including
* "GL" ([GLONASS](https://en.wikipedia.org/wiki/Satellite_navigation#GLONASS)),
* "BD" or "GB" ([BeiDou](https://en.wikipedia.org/wiki/Satellite_navigation#BeiDou)),
* "GA" ([Galileo](https://en.wikipedia.org/wiki/Satellite_navigation#Galileo)), and
* "GN" (mixed)
This means that GLRMC, GBRMC or BDRMC, GARMC and GNRMC from the latest GPS devices (e.g., ublox M8N) will also be correctly parsed. See discussion of Talker IDs in [Configurations](extras/doc/Configurations.md#enabledisable-the-talker-id-and-manufacturer-id-processing).
Most applications can be fully implemented with the standard NMEA messages above. They are supported by almost all GPS manufacturers. Additional messages can be added through derived classes (see ublox and Garmin sections below).
Most applications will use this simple, familiar loop structure:
```
NMEAGPS gps;
gps_fix fix;
void loop()
{
while (gps.available( gps_port )) {
fix = gps.read();
doSomeWork( fix );
}
}
```
For more information on this loop, see the [Usage](extras/doc/Data%20Model.md#usage) section on the [Data Model](extras/doc/Data%20Model.md) page.
(This is the plain Arduino version of the [CosaGPS](https://github.com/SlashDevin/CosaGPS) library for [Cosa](https://github.com/mikaelpatel/Cosa).)
Goals
======
In an attempt to be reusable in a variety of different programming styles, this library supports:
* resource-constrained environments (e.g., ATTINY targets)
* sync or async operation (reading in `loop()` vs interrupt processing)
* event or polling (deferred handling vs. continuous calls in `loop()`)
* coherent fixes (merged from multiple sentences) vs. individual sentences
* optional buffering of fixes
* optional floating point
* configurable message sets, including hooks for implementing proprietary NMEA messages
* configurable message fields
* multiple protocols from same device
* any kind of input stream (Serial, [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial), I2C, PROGMEM arrays, etc.)
Inconceivable!
=============
Don't believe it? Check out these detailed sections:
Section | Description
-------- | ------------
[License](LICENSE) | The Fine Print
[Installing](extras/doc/Installing.md) | Copying files
[Data Model](extras/doc/Data%20Model.md) | How to parse and use GPS data
[Configurations](extras/doc/Configurations.md) | Tailoring NeoGPS to your needs
[Performance](extras/doc/Performance.md) | 37% to 72% faster! Really!
[RAM requirements](extras/doc/RAM.md) | Doing it without buffers!
[Program Space requirements](extras/doc/Program.md) | Making it fit
[Examples](extras/doc/Examples.md) | Programming styles
[Troubleshooting](extras/doc/Troubleshooting.md) | Troubleshooting
[Extending NeoGPS](extras/doc/Extending.md) | Using specific devices
[ublox](extras/doc/ublox.md) | ublox-specific code
[Garmin](extras/doc/Garmin.md) | Garmin-specific code
[Tradeoffs](extras/doc/Tradeoffs.md) | Comparing to other libraries
[Acknowledgements](extras/doc/Acknowledgements.md) | Thanks!

View File

@ -0,0 +1,168 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEA.ino
//
// Description: This program uses the fix-oriented methods available() and
// read() to handle complete fix structures.
//
// When the last character of the LAST_SENTENCE_IN_INTERVAL (see NMEAGPS_cfg.h)
// is decoded, a completed fix structure becomes available and is returned
// from read(). The new fix is saved the 'fix' structure, and can be used
// anywhere, at any time.
//
// If no messages are enabled in NMEAGPS_cfg.h, or
// no 'gps_fix' members are enabled in GPSfix_cfg.h, no information will be
// parsed, copied or printed.
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
// 3) You know the default baud rate of your GPS device.
// If 9600 does not work, use NMEAdiagnostic.ino to
// scan for the correct baud rate.
// 4) LAST_SENTENCE_IN_INTERVAL is defined to be the sentence that is
// sent *last* in each update interval (usually once per second).
// The default is NMEAGPS::NMEA_RMC (see NMEAGPS_cfg.h). Other
// programs may need to use the sentence identified by NMEAorder.ino.
// 5) NMEAGPS_RECOGNIZE_ALL is defined in NMEAGPS_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
//-------------------------------------------------------------------------
// The GPSport.h include file tries to choose a default serial port
// for the GPS device. If you know which serial port you want to use,
// edit the GPSport.h file.
#include <GPSport.h>
//------------------------------------------------------------
// For the NeoGPS example programs, "Streamers" is common set
// of printing and formatting routines for GPS data, in a
// Comma-Separated Values text format (aka CSV). The CSV
// data will be printed to the "debug output device".
// If you don't need these formatters, simply delete this section.
#include <Streamers.h>
//------------------------------------------------------------
// This object parses received characters
// into the gps.fix() data structure
static NMEAGPS gps;
//------------------------------------------------------------
// Define a set of GPS fix information. It will
// hold on to the various pieces as they are received from
// an RMC sentence. It can be used anywhere in your sketch.
static gps_fix fix;
//----------------------------------------------------------------
// This function gets called about once per second, during the GPS
// quiet time. It's the best place to do anything that might take
// a while: print a bunch of things, write to SD, send an SMS, etc.
//
// By doing the "hard" work during the quiet time, the CPU can get back to
// reading the GPS chars as they come in, so that no chars are lost.
static void doSomeWork()
{
// Print all the things!
trace_all( DEBUG_PORT, gps, fix );
} // doSomeWork
//------------------------------------
// This is the main GPS parsing loop.
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
doSomeWork();
}
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEA.INO: started\n") );
DEBUG_PORT.print( F(" fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F(" gps object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifndef NMEAGPS_RECOGNIZE_ALL
#error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )
DEBUG_PORT.println( F("\nWARNING: No NMEA sentences are enabled: no fix data will be displayed.") );
#else
if (gps.merging == NMEAGPS::NO_MERGING) {
DEBUG_PORT.print ( F("\nWARNING: displaying data from ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.print ( F(" sentences ONLY, and only if ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(" is enabled.\n"
" Other sentences may be parsed, but their data will not be displayed.") );
}
#endif
DEBUG_PORT.print ( F("\nGPS quiet time is assumed to begin after a ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(" sentence is received.\n"
" You should confirm this with NMEAorder.ino\n") );
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,107 @@
#include <NMEAGPS.h>
NMEAGPS gps; // This parses the GPS characters
gps_fix fix; // This holds on to the latest values
//======================================================================
// Program: NMEAGSV.ino
//
// Description: Display satellites in view, as reported by the GSV sentences.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) NMEAGPS_PARSE_SATELLITES and NMEAGPS_PARSE_SATELLITE_INFO are
// enabled in NMEAGPS_cfg.h
// 3) The GSV sentence has been enabled in NMEAGPS_cfg.h.
// 4) Your device emits the GSV sentence (use NMEAorder.ino to confirm).
// 5) LAST_SENTENCE_IN_INTERVAL has been set to GSV (or any other enabled sentence)
// in NMEAGPS_cfg.h (use NMEAorder.ino).
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//-----------------
// Check configuration
#ifndef NMEAGPS_PARSE_GSV
#error You must define NMEAGPS_PARSE_GSV in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_PARSE_SATELLITES
#error You must define NMEAGPS_PARSE_SATELLITE in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_PARSE_SATELLITE_INFO
#error You must define NMEAGPS_PARSE_SATELLITE_INFO in NMEAGPS_cfg.h!
#endif
//-----------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!Serial)
;
DEBUG_PORT.print( F("NeoGPS GSV example started\n") );
gpsPort.begin(9600);
} // setup
//-----------------
void loop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
displaySatellitesInView();
}
} // loop
//-----------------
void displaySatellitesInView()
{
DEBUG_PORT.print( gps.sat_count );
DEBUG_PORT.print( ',' );
for (uint8_t i=0; i < gps.sat_count; i++) {
DEBUG_PORT.print( gps.satellites[i].id );
DEBUG_PORT.print( ' ' );
DEBUG_PORT.print( gps.satellites[i].elevation );
DEBUG_PORT.print( '/' );
DEBUG_PORT.print( gps.satellites[i].azimuth );
DEBUG_PORT.print( '@' );
if (gps.satellites[i].tracked)
DEBUG_PORT.print( gps.satellites[i].snr );
else
DEBUG_PORT.print( '-' );
DEBUG_PORT.print( F(", ") );
}
DEBUG_PORT.println();
} // displaySatellitesInView

View File

@ -0,0 +1,433 @@
#include <Arduino.h>
#include <NMEAGPS.h>
//======================================================================
// Program: NMEASDlog.ino
//
// Description: This program is an interrupt-driven GPS logger.
// It uses the alternative serial port libraries NeoHWSerial,
// NeoSWSerial, or NeoICSerial.
//
// Prerequisites:
// 1) You have completed the requirements for NMEA_isr.ino
// 2) You have connected an SPI SD card and verified it is working
// with other SD utilities.
// 3) For logging faster than the default 1Hz rate, you have
// identified the required commands for your GPS device.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//#include <Streamers.h>
//----------------------------------------------------------------
// Check configuration
#if !defined( GPS_FIX_TIME ) || !defined( GPS_FIX_LOCATION )
#error You must define TIME and LOCATION in GPSfix_cfg.h
#endif
#if !defined( NMEAGPS_PARSE_RMC )
#error You must define NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_INTERRUPT_PROCESSING
#error You must define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static const int LED = 13;
static NMEAGPS gps;
//----------------------------------------------------------------
// SD card includes and declarations
#include <SPI.h>
#include <SdFat.h>
SdFat SD;
const int chipSelect = 8;
//----------------------------------------------------------------
// For testing, it may be more convenient to simply print the
// GPS fix fields to the Serial Monitor. Simply uncomment
// this define to skip all SD operations. An SD card module
// does not have to be connected.
#define SIMULATE_SD
#ifdef SIMULATE_SD
auto &logfile = DEBUG_PORT;
#else
File logfile;
#endif
//----------------------------------------------------------------
// Utility to print a long integer like it's a float
// with 9 significant digits.
void printL( Print & outs, int32_t degE7 )
{
// Extract and print negative sign
if (degE7 < 0) {
degE7 = -degE7;
outs.print( '-' );
}
// Whole degrees
int32_t deg = degE7 / 10000000L;
outs.print( deg );
outs.print( '.' );
// Get fractional degrees
degE7 -= deg*10000000L;
// Print leading zeroes, if needed
if (degE7 < 10L)
outs.print( F("000000") );
else if (degE7 < 100L)
outs.print( F("00000") );
else if (degE7 < 1000L)
outs.print( F("0000") );
else if (degE7 < 10000L)
outs.print( F("000") );
else if (degE7 < 100000L)
outs.print( F("00") );
else if (degE7 < 1000000L)
outs.print( F("0") );
// Print fractional degrees
outs.print( degE7 );
}
//----------------------------------------------------------------
// Because the SD write can take a long time, GPSisr will store
// parsed data in the NMEAGPS 'buffer' array until GPSloop can get to it.
//
// The number of elements you should have in the array depends on
// two things: the speed of your SD card, and the update rate you
// have chosen.
//
// For an update rate of 10Hz (100ms period), two fixes are probably
// enough. Most cards take ~100ms to complete a write of 512 bytes.
// With FIX_MAX=2, two complete fixes can be stored, which were
// received in 200ms. A third fix can be started, giving a little
// more time before an overrun occurs, a total of about 250ms.
//
// If your card is slower or your update rate is faster, you should
// first build and run this program to determine the speed of your
// card. The time it takes to log one record is printed to the log
// file.
//
// After the program has run for a minute or two, remove the
// card and examine the loggingTimes. You may see that an interval
// was skipped, and you will also see an OVERRUN message on the
// DEBUG_PORT (usually Serial).
//
// You should calculate a new FIX_MAX from the maximum loggingTime
// you see in the log file:
//
// FIX_MAX = (max loggingTime)/(update period) + 1;
//
// For example, if the max loggingTime is 160ms, and the update period is
// 100ms (10Hz), then FIX_MAX = 160/100 + 1 = 2.
//
// Change the FIX_MAX value, build and run the program again. The
// SD log file should now contain all records, and no OVERRUN
// messages should have been printed on the DEBUG_PORT.
//
// If you do see an OVERRUN message, examine the loggingTime to see
// if it exceeded the maximum value from the previous build, and
// increase FIX_MAX.
//
// If you are also printing data to a Serial device, you could be
// printing too much information. In general, you must print less than
// (baudrate/10) characters per second. For example, if your baudrate
// is 9600, you must print less than 960 characters per second. And if
// the update rate is 10Hz, you must print no more than 96 characters
// per update.
//
// There are also restrictions on how much you should print to Serial in one
// section of code. If you print more than 64 characters (the output buffer
// size), then some prints will block until all characters can be stored in the
// output buffer.
//
// For example, if you try to print 80 characters, the first 64 characters
// will be immediately stored in the output buffer. However, the last 16
// characters must wait until the output buffer has room for 16 more
// characters. That takes 16 * (10/baudrate) milliseconds. At 9600 baud
// that will take 17ms. The loggingTimes will show no less than 17ms per
// record, and will occasionally include the longer SD write time of ~100ms.
//----------------------------------------------------------------
static void GPSloop()
{
if (gps.available()) {
gps_fix fix = gps.read();
// Log the fix information if we have a location and time
if (fix.valid.location && fix.valid.time) {
static uint16_t lastLoggingTime = 0;
uint16_t startLoggingTime = millis();
// If you like the CSV format implemented in Streamers.h,
// you could replace all these prints with
// trace_all( logFile, fix ); // uncomment include Streamers.h
printL( logfile, fix.latitudeL() );
logfile.print( ',' );
printL( logfile, fix.longitudeL() );
logfile.print(',');
if (fix.dateTime.hours < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.hours);
logfile.print( ':' );
if (fix.dateTime.minutes < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.minutes);
logfile.print( ':' );
if (fix.dateTime.seconds < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.seconds);
logfile.print( '.' );
if (fix.dateTime_cs < 10)
logfile.print( '0' ); // leading zero for .05, for example
logfile.print(fix.dateTime_cs);
logfile.print(',');
logfile.print( lastLoggingTime ); // write how long the previous logging took
logfile.println();
// flush() is used to empty the contents of the SD buffer to the SD.
// If you don't call flush, the data will fill up the SdFat buffer
// of 512bytes and flush itself automatically.
//
// To avoid losing data or corrupting the SD card file system, you must
// call flush() at least once (or close()) before powering down or pulling
// the SD card.
//
// It is *strongly* recommended that you use some external event
// to close the file. For example, staying within 50m of the moving
// average location for 1 minute, or using a switch to start and stop
// logging. It would also be good to provide a visual indication
// that it is safe to power down and/or remove the card, perhaps via
// the LED.
//
// To reduce the amount of data that may be lost by an abnormal shut down,
// you can call flush() periodically.
//
// Depending on the amount of data you are printing, you can save
// *a lot* of CPU time by not flushing too frequently. BTW, flushing
// every time at 5Hz is too frequent.
// This shows how to flush once a second.
static uint16_t lastFlushTime = 0;
if (startLoggingTime - lastFlushTime > 1000) {
lastFlushTime = startLoggingTime; // close enough
logfile.flush();
}
#ifdef SIMULATE_SD
// Simulate the delay of writing to an SD card. These times are
// very long. This is intended to show (and test) the overrun detection.
//
// On a 1Hz GPS, delaying more than 2 seconds here, or more than
// 2 seconds in two consecutive updates, will cause OVERRUN.
//
// Feel free to try different delays to simulate the actual behavior
// of your SD card.
uint16_t t = random(0,5); // 0..4
t += 3; // 3..7
t = t*t*t*t; // 81..2401ms
delay( t ); // cause an OVERRUN
#endif
// All logging is finished, figure out how long that took.
// This will be written in the *next* record.
lastLoggingTime = (uint16_t) millis() - startLoggingTime;
}
}
} // GPSloop
//----------------------------------------------------------------
void GPSisr( uint8_t c )
{
gps.handle( c );
} // GPSisr
//----------------------------------------------------------------
// This routine waits for GPSisr to provide
// a fix that has a valid location.
//
// The LED is slowly flashed while it's waiting.
static void waitForFix()
{
DEBUG_PORT.print( F("Waiting for GPS fix...") );
uint16_t lastToggle = millis();
for (;;) {
if (gps.available()) {
if (gps.read().valid.location)
break; // Got it!
}
// Slowly flash the LED until we get a fix
if ((uint16_t) millis() - lastToggle > 500) {
lastToggle += 500;
digitalWrite( LED, !digitalRead(LED) );
DEBUG_PORT.write( '.' );
}
}
DEBUG_PORT.println();
digitalWrite( LED, LOW );
gps.overrun( false ); // we had to wait a while...
} // waitForFix
//----------------------------------------------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
; // wait for serial port to connect.
DEBUG_PORT.println( F("NMEASDlog.ino started!") );
DEBUG_PORT.print( F("fix size = ") );
DEBUG_PORT.println( sizeof(gps_fix) );
DEBUG_PORT.print( NMEAGPS_FIX_MAX );
DEBUG_PORT.println( F(" GPS updates can be buffered.") );
if (gps.merging != NMEAGPS::EXPLICIT_MERGING)
DEBUG_PORT.println( F("Warning: EXPLICIT_MERGING should be enabled for best results!") );
gpsPort.attachInterrupt( GPSisr );
gpsPort.begin( 9600 );
// Configure the GPS. These are commands for MTK GPS devices. Other
// brands will have different commands.
gps.send_P( &gpsPort, F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") ); // RMC only for MTK GPS devices
gps.send_P( &gpsPort, F("PMTK220,100") ); // 10Hz update rate for MTK GPS devices
// Enable the LED for blinking feedback
pinMode( LED, OUTPUT );
initSD();
waitForFix();
} // setup
//----------------------------------------------------------------
void loop()
{
GPSloop();
if (gps.overrun()) {
gps.overrun( false );
DEBUG_PORT.println( F("DATA OVERRUN: fix data lost!") );
}
}
//----------------------------------------------------------------
void initSD()
{
#ifdef SIMULATE_SD
DEBUG_PORT.println( F(" Simulating SD.") );
#else
DEBUG_PORT.println( F("Initializing SD card...") );
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
DEBUG_PORT.println( F(" SD card failed, or not present") );
// don't do anything more:
// Flicker the LED
while (true) {
digitalWrite(LED,HIGH);
delay(75);
digitalWrite(LED,LOW);
delay(75);
}
}
DEBUG_PORT.println( F(" SD card initialized.") );
// Pick a numbered filename, 00 to 99.
char filename[15] = "data_##.txt";
for (uint8_t i=0; i<100; i++) {
filename[5] = '0' + i/10;
filename[6] = '0' + i%10;
if (!SD.exists(filename)) {
// Use this one!
break;
}
}
logfile = SD.open(filename, FILE_WRITE);
if (!logfile) {
DEBUG_PORT.print( F("Couldn't create ") );
DEBUG_PORT.println(filename);
// If the file can't be created for some reason this leaves the LED on
// so I know there is a problem
digitalWrite(LED,HIGH);
while (true) {}
}
DEBUG_PORT.print( F("Writing to ") );
DEBUG_PORT.println(filename);
// GPS Visualizer requires a header to identify the CSV fields.
// If you are saving other data or don't need this, simply remove/change it
logfile.println( F("latitude,longitude,time,loggingTime") );
//trace_header( logfile ); // and uncomment #include Streamers.h
#endif
} // initSD

View File

@ -0,0 +1,90 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEA_isr.ino
//
// Prerequisites:
// 1) NMEA.ino works with your device
//
// Description: This minimal program parses the GPS data during the
// RX character interrupt. The ISR passes the character to
// the GPS object for parsing. The GPS object will add gps_fix
// structures to a buffer that can be later read() by loop().
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
// Check configuration
#ifndef NMEAGPS_INTERRUPT_PROCESSING
#error You must define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static NMEAGPS gps;
//--------------------------
static void GPSisr( uint8_t c )
{
gps.handle( c );
} // GPSisr
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEA_isr.INO: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("NMEAGPS object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.attachInterrupt( GPSisr );
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
if (gps.available()) {
// Print all the things!
trace_all( DEBUG_PORT, gps, gps.read() );
}
if (gps.overrun()) {
gps.overrun( false );
DEBUG_PORT.println( F("DATA OVERRUN: took too long to print GPS data!") );
}
}

View File

@ -0,0 +1,265 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAaverage.ino
//
// Description: This program averages locations over time to compute
// a higher-accuracy *static* location. It also shows
// how to use the distance functions in the Location class.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) At least once sentence with a location field has been enabled
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_GGA ) && \
!defined( NMEAGPS_PARSE_GLL ) && \
!defined( NMEAGPS_PARSE_RMC )
#error You must uncomment at least one of NMEAGPS_PARSE_GGA, GGL or RMC in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_TIME )
#error You must uncomment GPS_FIX_TIME in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_DATE )
#error You must uncomment GPS_FIX_DATE in GPSfix_cfg.h!
#endif
//------------------------------------------------------------
static NMEAGPS gps; // This parses the GPS characters
static gps_fix fix; // This holds the latest GPS fix
//------------------------------------------------------------
using namespace NeoGPS;
static gps_fix first; // good GPS data
static clock_t firstSecs; // cached dateTime in seconds since EPOCH
static Location_t avgLoc; // gradually-calculated average location
static uint16_t count; // number of samples
static int32_t sumDLat, sumDLon; // accumulated deltas
static bool doneAccumulating; // accumulation completed
//------------------------------------------------------------
const char nCD [] PROGMEM = "N";
const char nneCD[] PROGMEM = "NNE";
const char neCD [] PROGMEM = "NE";
const char eneCD[] PROGMEM = "ENE";
const char eCD [] PROGMEM = "E";
const char eseCD[] PROGMEM = "ESE";
const char seCD [] PROGMEM = "SE";
const char sseCD[] PROGMEM = "SSE";
const char sCD [] PROGMEM = "S";
const char sswCD[] PROGMEM = "SSW";
const char swCD [] PROGMEM = "SW";
const char wswCD[] PROGMEM = "WSW";
const char wCD [] PROGMEM = "W";
const char wnwCD[] PROGMEM = "WNW";
const char nwCD [] PROGMEM = "NW";
const char nnwCD[] PROGMEM = "NNW";
const char * const dirStrings[] PROGMEM =
{ nCD, nneCD, neCD, eneCD, eCD, eseCD, seCD, sseCD,
sCD, sswCD, swCD, wswCD, wCD, wnwCD, nwCD, nnwCD };
const __FlashStringHelper *compassDir( uint16_t bearing ) // degrees CW from N
{
const int16_t directions = sizeof(dirStrings)/sizeof(dirStrings[0]);
const int16_t degreesPerDir = 360 / directions;
int8_t dir = (bearing + degreesPerDir/2) / degreesPerDir;
while (dir < 0)
dir += directions;
while (dir >= directions)
dir -= directions;
return (const __FlashStringHelper *) pgm_read_ptr( &dirStrings[ dir ] );
} // compassDir
//------------------------------------------------------------
static void doSomeWork()
{
static bool warned = false; // that we're waiting for a valid location
if (fix.valid.location && fix.valid.date && fix.valid.time) {
if (count == 0) {
// Just save the first good fix
first = fix;
firstSecs = (clock_t) first.dateTime;
count = 1;
} else {
// After the first fix, accumulate locations until we have
// a good average. Then display the offset from the average.
if (warned) {
// We were waiting for the fix to be re-acquired.
warned = false;
DEBUG_PORT.println();
}
DEBUG_PORT.print( count );
if (!doneAccumulating) {
// Enough time?
if (((clock_t)fix.dateTime - firstSecs) > 2 * SECONDS_PER_HOUR)
doneAccumulating = true;
}
int32_t dLat, dLon;
if (!doneAccumulating) {
// Use deltas from the first location
dLat = fix.location.lat() - first.location.lat();
sumDLat += dLat;
int32_t avgDLat = sumDLat / count;
dLon = fix.location.lon() - first.location.lon();
sumDLon += dLon;
int32_t avgDLon = sumDLon / count;
// Then calculated the average location as the first location
// plus the averaged deltas.
avgLoc.lat( first.location.lat() + avgDLat );
avgLoc.lon( first.location.lon() + avgDLon );
count++;
}
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( avgLoc.lat() );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( avgLoc.lon() );
DEBUG_PORT.print( ',' );
dLat = avgLoc.lat() - fix.location.lat();
DEBUG_PORT.print( dLat );
DEBUG_PORT.print( ',' );
dLon = avgLoc.lon() - fix.location.lon();
DEBUG_PORT.print( dLon );
// Calculate the distance from the current fix to the average location
float avgDistError = avgLoc.DistanceKm( fix.location );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( avgDistError * 100000.0 ); // cm
// Calculate the bearing from the current fix to the average location.
// NOTE: other libraries will have trouble with this calculation,
// because these coordinates are *VERY* close together. Naive
// floating-point calculations will not have enough significant
// digits.
float avgBearingErr = fix.location.BearingTo( avgLoc );
float bearing = avgBearingErr * Location_t::DEG_PER_RAD;
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( bearing, 6 );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( compassDir( bearing ) );
// Calculate a point that is 10km away from the average location,
// at the error bearing
Location_t tenKmAway( avgLoc );
tenKmAway.OffsetBy( 10.0 / Location_t::EARTH_RADIUS_KM, avgBearingErr );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( tenKmAway.lat() );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( tenKmAway.lon() );
// Calculate the bearing from the average location to that point.
// This should be very close to the avgBearingErr, and will
// reflect the calculation error. This is because the
// current fix is *VERY* close to the average location.
float tb = avgLoc.BearingToDegrees( tenKmAway );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( tb, 6 );
DEBUG_PORT.println();
}
} else {
if (!warned) {
warned = true;
DEBUG_PORT.print( F("Waiting for fix...") );
} else {
DEBUG_PORT.print( '.' );
}
}
} // doSomeWork
//------------------------------------------------------------
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
doSomeWork();
}
} // GPSloop
//------------------------------------------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAaverage.INO: started\n") );
DEBUG_PORT.print( F(" fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F(" gps object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.println( F("Comparing current fix with averaged location.\n"
"count,avg lat,avg lon,dlat,dlon,distance(cm),"
"bearing deg,compass,lat/lon 10km away & recalc bearing") );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//------------------------------------------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,105 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAbenchmark.ino
//
// Prerequisites:
//
// Description: Use GPGGA and GPRMC sentences to test
// the parser's performance.
//
// GSV sentences are tested if enabled.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <Streamers.h>
static NMEAGPS gps;
//--------------------------
static uint32_t time_it( const char *data )
{
const uint16_t ITERATIONS = 1024;
uint32_t start, end;
Serial.flush();
start = micros();
for (uint16_t i=ITERATIONS; i > 0; i--) {
char *ptr = (char *) data;
while (*ptr)
gps.decode( *ptr++ );
}
end = micros();
return (end-start)/ITERATIONS;
}
//--------------------------
void setup()
{
Serial.begin(9600);
Serial.println( F("NMEAbenchmark: started") );
Serial.print( F("fix object size = ") );
Serial.println( sizeof(gps.fix()) );
Serial.print( F(" gps object size = ") );
Serial.println( sizeof(gps) );
trace_header( Serial );
Serial.flush();
const char *gga =
"$GPGGA,092725.00,4717.11399,N,00833.91590,E,"
"1,8,1.01,499.6,M,48.0,M,,0*5B\r\n";
const char *gga_no_lat =
"$GPGGA,092725.00,,,00833.91590,E,"
"1,8,1.01,499.6,M,48.0,M,,0*0D\r\n";
Serial << F("GGA time = ") << time_it( gga ) << '\n';
trace_all( Serial, gps, gps.fix() );
Serial << F("GGA no lat time = ") << time_it( gga_no_lat ) << '\n';
trace_all( Serial, gps, gps.fix() );
const char *rmc =
"$GPRMC,083559.00,A,4717.11437,N,00833.91522,E,"
"0.004,77.52,091202,,,A*57\r\n";
Serial << F("RMC time = ") << time_it( rmc ) << '\n';
trace_all( Serial, gps, gps.fix() );
#ifdef NMEAGPS_PARSE_GSV
const char *gsv =
"$GPGSV,3,1,10,23,38,230,44,29,71,156,47,07,29,116,41,08,09,081,36*7F\r\n"
"$GPGSV,3,2,10,10,07,189,,05,05,220,,09,34,274,42,18,25,309,44*72\r\n"
"$GPGSV,3,3,10,26,82,187,47,28,43,056,46*77\r\n";
Serial << F("GSV time = ") << time_it( gsv ) << '\n';
trace_all( Serial, gps, gps.fix() );
#endif
}
//--------------------------
void loop() {}

View File

@ -0,0 +1,69 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAblink.ino
//
// Prerequisites:
// 1) NMEA.ino works with your device
//
// Description: This program will toggle the LED once per second,
// when the LAST_SENTENCE_IN_INTERVAL is received.
//
// Because no actual GPS data is used, you could disable all
// messages (except the LAST_SENTENCE) and all gps_fix members.
// It would still receive a 'fix' oncer per second, without
// without using any RAM or CPU time to parse or save
// the (unused) values. Essentially, this app uses the LAST_SENTENCE
// as a 1PPS signal.
//
// Note: Because this example does not use 'Serial', you
// could use 'Serial' for the gpsPort, like this:
//
// #define gpsPort Serial
//
// See GPSport.h for more information.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
static NMEAGPS gps;
static const int led = 13;
//--------------------------
void setup()
{
gpsPort.begin(9600);
pinMode(led, OUTPUT);
}
//--------------------------
void loop()
{
if (gps.available( gpsPort)) {
gps.read(); // don't really do anything with the fix...
digitalWrite( led, !digitalRead(led) ); // toggle the LED
}
}

View File

@ -0,0 +1,367 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAdiagnostic.ino
//
// Description: This program tries different baud rates until
// valid NMEA sentences are detected. Some GPS devices may
// have a binary mode that does not emit NMEA sentences. You
// may have to send a special command or use a utility program
// to configure it to emit NMEA sentences instead of binary messages.
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
// Check configuration
#ifndef NMEAGPS_RECOGNIZE_ALL
#error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
#endif
#ifdef NMEAGPS_IMPLICIT_MERGING
#error You must *undefine* NMEAGPS_IMPLICIT_MERGING in NMEAGPS_cfg.h! \
Please use EXPLICIT or NO_MERGING.
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static NMEAGPS gps ; // This parses received characters
static gps_fix all_data ; // A composite of all GPS data fields
static uint32_t last_rx = 0UL; // The last millis() time a character was
// received from GPS.
static uint32_t valid_sentence_received = 0UL;
static bool last_sentence_received = false;
static uint32_t baudStartTime = 0UL;
static uint8_t warnings = 0;
static uint8_t errors = 0;
//--------------------------
static void hang()
{
DEBUG_PORT.println( F("\n** NMEAdiagnostic completed **\n") );
if (warnings) {
DEBUG_PORT.print( warnings );
DEBUG_PORT.print( F(" warnings") );
}
if (warnings && errors)
DEBUG_PORT.print( F(" and ") );
if (errors) {
DEBUG_PORT.print( errors );
DEBUG_PORT.print( F(" errors") );
}
if (warnings || errors)
DEBUG_PORT.println();
DEBUG_PORT.flush();
for (;;)
;
} // hang
//--------------------------
// Baud rates to check
static long baud_table[] =
{ 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400,
57600, 115200 };
static const uint8_t num_bauds = sizeof(baud_table)/sizeof(baud_table[0]);
static const uint8_t INITIAL_BAUD_INDEX = 3; // 9600
static uint8_t baud_index = INITIAL_BAUD_INDEX;
static bool triedDifferentBaud = false;
//--------------------------
static void tryBaud()
{
long baud = baud_table[baud_index];
DEBUG_PORT.print( F("\n____________________________\n\nChecking ") );
DEBUG_PORT.print( baud );
DEBUG_PORT.print( F(" baud...\n") );
DEBUG_PORT.flush();
//if (baud == 9600) baud = 17000;
gpsPort.begin( baud );
baudStartTime = millis();
} // tryBaud
//--------------------------
static void tryAnotherBaudRate()
{
gpsPort.end();
while (gpsPort.available())
gpsPort.read();
if (baud_index == INITIAL_BAUD_INDEX) {
baud_index = 0;
} else {
baud_index++;
if (baud_index == INITIAL_BAUD_INDEX)
baud_index++; // skip it, we already tried it
if (baud_index >= num_bauds) {
baud_index = INITIAL_BAUD_INDEX;
DEBUG_PORT.print( F("\n All baud rates tried!\n") );
hang();
}
}
tryBaud();
triedDifferentBaud = true;
} // tryAnotherBaudRate
//------------------------------------
static const uint16_t MAX_SAMPLE = 256;
static uint8_t someChars[ MAX_SAMPLE ];
static uint16_t someCharsIndex = 0;
static void dumpSomeChars()
{
if (someCharsIndex > 0) {
DEBUG_PORT.print( F("Received data:\n") );
const uint16_t bytes_per_line = 32;
char ascii[ bytes_per_line ];
uint8_t *ptr = &someChars[0];
for (uint16_t i=0; i<someCharsIndex; ) {
uint16_t j;
for (j=0; (i<someCharsIndex) && (j<bytes_per_line); i++, j++) {
uint8_t c = *ptr++;
if (c < 0x10)
DEBUG_PORT.print('0');
DEBUG_PORT.print( c, HEX );
if ((' ' <= c) && (c <= '~'))
ascii[ j ] = c;
else
ascii[ j ] = '.';
}
uint16_t jmax = j;
while (j++ < bytes_per_line)
DEBUG_PORT.print( F(" ") );
DEBUG_PORT.print( ' ' );
for (j=0; j<jmax; j++)
DEBUG_PORT.print( ascii[ j ] );
DEBUG_PORT.print( '\n' );
}
DEBUG_PORT.flush();
someCharsIndex = 0;
}
} // dumpSomeChars
//----------------------------------------------------------------
void displaySentences()
{
// We received one or more sentences, display the baud rate
DEBUG_PORT.print( F("\n\n**** NMEA sentence(s) detected! ****\n") );
dumpSomeChars();
DEBUG_PORT << F("\nDevice baud rate is ") <<
baud_table[ baud_index ] << '\n';
DEBUG_PORT.print( F("\nGPS data fields received:\n\n ") );
trace_header( DEBUG_PORT );
DEBUG_PORT.print( F(" ") );
trace_all( DEBUG_PORT, gps, all_data );
if (!last_sentence_received) {
warnings++;
DEBUG_PORT.print( F("\nWarning: LAST_SENTENCE_IN_INTERVAL defined to be ") );
DEBUG_PORT.print( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(", but was never received.\n"
" Please use NMEAorder.ino to determine which sentences your GPS device sends, and then\n"
" use the last one for the definition in NMEAGPS_cfg.h.") );
}
} // displaySentences
//----------------------------------------------------------------
// Listen to see if the GPS device is correctly
// connected and functioning.
static void listenForSomething()
{
uint32_t current_ms = millis();
uint32_t ms_since_last_rx = current_ms - last_rx;
bool waited_long_enough = (current_ms - baudStartTime) > 1000UL;
if ((ms_since_last_rx > 5) && waited_long_enough) {
// The GPS device has not sent any characters for at least 5ms.
// See if we've been getting chars sometime during the last second.
// If not, the GPS may not be working or connected properly.
bool getting_chars = (someCharsIndex > 0);
// Try to diagnose the problem
static uint8_t tries = 1;
bool tryNext = false;
if (!getting_chars) {
if (tries++ >= 3) {
errors++;
DEBUG_PORT.println( F("\nCheck GPS device and/or connections. No data received.\n") );
tryNext = true;
}
} else if (valid_sentence_received) {
uint8_t s = valid_sentence_received/1000;
uint8_t ms = valid_sentence_received - s*1000;
DEBUG_PORT.print( F("Valid sentences were received ") );
DEBUG_PORT.print( s );
DEBUG_PORT.print( '.' );
if (ms < 100)
DEBUG_PORT.print( '0' );
if (ms < 10)
DEBUG_PORT.print( '0' );
DEBUG_PORT.print( ms );
DEBUG_PORT.println(
F(" seconds ago.\n"
" The GPS update rate may be lower than 1Hz,\n"
" or the connections may be bad." ) );
displaySentences();
hang();
} else {
DEBUG_PORT.println(
F("No valid sentences, but characters are being received.\n"
" Check baud rate or device protocol configuration.\n" ) );
dumpSomeChars();
delay( 2000 );
tryNext = true;
}
if (tryNext) {
tries = 1;
tryAnotherBaudRate();
valid_sentence_received = 0UL;
}
}
} // listenForSomething
//------------------------------------
static void GPSloop()
{
while (gpsPort.available()) {
last_rx = millis();
uint8_t c = gpsPort.read();
if (someCharsIndex < MAX_SAMPLE)
someChars[ someCharsIndex++ ] = c;
if (gps.decode( c ) == NMEAGPS::DECODE_COMPLETED) {
all_data |= gps.fix();
valid_sentence_received = last_rx;
if (gps.nmeaMessage == LAST_SENTENCE_IN_INTERVAL)
last_sentence_received = true;
DEBUG_PORT.print( F("Received ") );
DEBUG_PORT.println( gps.string_for( gps.nmeaMessage ) );
static uint8_t sentences_printed = 0;
bool long_enough = (millis() - baudStartTime > 3000);
if (long_enough ||
(
(sentences_printed++ >= 20) &&
(someCharsIndex >= MAX_SAMPLE)
) ) {
displaySentences();
hang();
}
}
}
if (!valid_sentence_received ||
(millis() - valid_sentence_received > 3000UL))
listenForSomething();
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAdiagnostic.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
if (sizeof(gps_fix) <= 2) {
warnings++;
DEBUG_PORT.print( F("\nWarning: no fields are enabled in GPSfix_cfg.h.\n Only the following information will be displayed:\n ") );
trace_header( DEBUG_PORT );
}
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )
warnings++;
DEBUG_PORT.println( F("\nWarning: no messages are enabled for parsing in NMEAGPS_cfg.h.\n No fields will be valid, including the 'status' field.") );
#endif
DEBUG_PORT.flush();
tryBaud();
} // setup
//--------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,82 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAdistance.ino
//
// Description: Display distance from a base location.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) GPS_FIX_LOCATION has been enabled.
// 3) A sentence that contains lat/long has been enabled (GGA, GLL or RMC).
// 4) Your device sends at least one of those sentences.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC ) & \
!defined( NMEAGPS_PARSE_GGA ) & \
!defined( NMEAGPS_PARSE_GLL )
#error You must uncomment at least one of NMEAGPS_PARSE_RMC, NMEAGPS_PARSE_GGA or NMEAGPS_PARSE_GLL in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
NMEAGPS gps;
// The base location, in degrees * 10,000,000
NeoGPS::Location_t base( -253448688L, 1310324914L ); // Ayers Rock, AU
void setup()
{
DEBUG_PORT.begin(9600);
DEBUG_PORT.println( F("NMEAdistance.ino started.") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
gpsPort.begin(9600);
} // setup
void loop()
{
while (gps.available( gpsPort )) {
gps_fix fix = gps.read(); // save the latest
// When we have a location, calculate how far away we are from the base location.
if (fix.valid.location) {
float range = fix.location.DistanceMiles( base );
DEBUG_PORT.print( F("Range: ") );
DEBUG_PORT.print( range );
DEBUG_PORT.println( F(" Miles") );
} else
// Waiting...
DEBUG_PORT.print( '.' );
}
} // loop

View File

@ -0,0 +1,183 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAloc.ino
//
// Description: This program only parses an RMC sentence for the lat/lon.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) The RMC sentence has been enabled.
// 3) Your device sends an RMC sentence (e.g., $GPRMC).
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC )
#error You must uncomment NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_TIME )
#error You must uncomment GPS_FIX_TIME in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_SPEED )
#error You must uncomment GPS_FIX_SPEED in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_SATELLITES )
#error You must uncomment GPS_FIX_SATELLITES in GPSfix_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------------
static NMEAGPS gps; // This parses the GPS characters
//----------------------------------------------------------------
// Print the 32-bit integer degrees *as if* they were high-precision floats
static void printL( Print & outs, int32_t degE7 );
static void printL( Print & outs, int32_t degE7 )
{
// Extract and print negative sign
if (degE7 < 0) {
degE7 = -degE7;
outs.print( '-' );
}
// Whole degrees
int32_t deg = degE7 / 10000000L;
outs.print( deg );
outs.print( '.' );
// Get fractional degrees
degE7 -= deg*10000000L;
// Print leading zeroes, if needed
int32_t factor = 1000000L;
while ((degE7 < factor) && (factor > 1L)){
outs.print( '0' );
factor /= 10L;
}
// Print fractional degrees
outs.print( degE7 );
}
static void doSomeWork( const gps_fix & fix );
static void doSomeWork( const gps_fix & fix )
{
// This is the best place to do your time-consuming work, right after
// the RMC sentence was received. If you do anything in "loop()",
// you could cause GPS characters to be lost, and you will not
// get a good lat/lon.
// For this example, we just print the lat/lon. If you print too much,
// this routine will not get back to "loop()" in time to process
// the next set of GPS data.
if (fix.valid.location) {
if ( fix.dateTime.seconds < 10 )
DEBUG_PORT.print( '0' );
DEBUG_PORT.print( fix.dateTime.seconds );
DEBUG_PORT.print( ',' );
// DEBUG_PORT.print( fix.latitude(), 6 ); // floating-point display
// DEBUG_PORT.print( fix.latitudeL() ); // integer display
printL( DEBUG_PORT, fix.latitudeL() ); // prints int like a float
DEBUG_PORT.print( ',' );
// DEBUG_PORT.print( fix.longitude(), 6 ); // floating-point display
// DEBUG_PORT.print( fix.longitudeL() ); // integer display
printL( DEBUG_PORT, fix.longitudeL() ); // prints int like a float
DEBUG_PORT.print( ',' );
if (fix.valid.satellites)
DEBUG_PORT.print( fix.satellites );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( fix.speed(), 6 );
DEBUG_PORT.print( F(" kn = ") );
DEBUG_PORT.print( fix.speed_mph(), 6 );
DEBUG_PORT.print( F(" mph") );
} else {
// No valid location data yet!
DEBUG_PORT.print( '?' );
}
DEBUG_PORT.println();
} // doSomeWork
//------------------------------------
static void GPSloop();
static void GPSloop()
{
while (gps.available( gpsPort ))
doSomeWork( gps.read() );
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAloc.INO: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("NMEAGPS object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifdef NMEAGPS_NO_MERGING
DEBUG_PORT.println( F("Only displaying data from xxRMC sentences.\n Other sentences may be parsed, but their data will not be displayed.") );
#endif
DEBUG_PORT.flush();
gpsPort.begin(9600);
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,117 @@
#include <Arduino.h>
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAlocDMS.ino
//
// Description: This program only parses an RMC sentence for the lat/lon.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) The RMC sentence has been enabled.
// 3) Your device sends an RMC sentence (e.g., $GPRMC).
//
// Serial is for trace output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC )
#error You must uncomment NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION_DMS )
#error You must uncomment GPS_FIX_LOCATION_DMS in GPSfix_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------------
static NMEAGPS gps; // This parses the GPS characters
static void doSomeWork( const gps_fix & fix );
static void doSomeWork( const gps_fix & fix )
{
// This is the best place to do your time-consuming work, right after
// the RMC sentence was received. If you do anything in "loop()",
// you could cause GPS characters to be lost, and you will not
// get a good lat/lon.
// For this example, we just print the lat/lon. If you print too much,
// this routine will not get back to "loop()" in time to process
// the next set of GPS data.
if (fix.valid.location) {
DEBUG_PORT << fix.latitudeDMS;
DEBUG_PORT.print( fix.latitudeDMS.NS() );
DEBUG_PORT.write( ' ' );
if (fix.longitudeDMS.degrees < 100)
DEBUG_PORT.write( '0' );
DEBUG_PORT << fix.longitudeDMS;
DEBUG_PORT.print( fix.longitudeDMS.EW() );
} else {
// No valid location data yet!
DEBUG_PORT.print( '?' );
}
DEBUG_PORT.println();
} // doSomeWork
//------------------------------------
static void GPSloop();
static void GPSloop()
{
while (gps.available( gpsPort ))
doSomeWork( gps.read() );
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAlocDMS.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.flush();
gpsPort.begin(9600);
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,247 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAorder.ino
//
// Description: This example program records the order of sentences
// received in each 1-second interval. The last sentence is
// important to know, as that will be used to determine when the
// GPS quiet time is starting (see NMEA.ino). It is safe to perform
// time-consuming operations at that point, and the risk of losing
// characters will be minimized (see comments in 'GPSloop').
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
// 3) You know the default baud rate of your GPS device
// Use NMEAdiagnostic.ino to scan for the correct baud rate.
// 4) NMEAGPS_RECOGNIZE_ALL must be enabled in NMEAGPS_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check configuration
#ifndef NMEAGPS_RECOGNIZE_ALL
#error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static NMEAGPS gps ; // This parses received characters
static uint32_t last_rx = 0L; // The last millis() time a character was
// received from GPS. This is used to
// determine when the GPS quiet time begins.
//------------------------------------------------------------
static NMEAGPS::nmea_msg_t last_sentence_in_interval = NMEAGPS::NMEA_UNKNOWN;
static uint8_t prev_sentence_count = 0;
static uint8_t sentence_count = 0;
static const uint8_t MAX_SENTENCES = 20; // per second
static NMEAGPS::nmea_msg_t sentences[ MAX_SENTENCES ];
static void recordSentenceTypes()
{
// Always save the last sentence, even if we're full
sentences[ sentence_count ] = gps.nmeaMessage;
if (sentence_count < MAX_SENTENCES-1)
sentence_count++;
} // recordSentenceTypes
//-----------------------------------------------------------
static void printSentenceOrder()
{
DEBUG_PORT.println( F("\nSentence order in each 1-second interval:") );
for (uint8_t i=0; i<sentence_count; i++) {
DEBUG_PORT.print( F(" ") );
DEBUG_PORT.println( gps.string_for( sentences[i] ) );
}
if (sentences[sentence_count-1] == LAST_SENTENCE_IN_INTERVAL) {
DEBUG_PORT.print( F("\nSUCCESS: LAST_SENTENCE_IN_INTERVAL is correctly set to NMEAGPS::NMEA_") );
} else {
DEBUG_PORT.print( F("\nERROR: LAST_SENTENCE_IN_INTERVAL is incorrectly set to NMEAGPS::NMEA_") );
DEBUG_PORT.print( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.print( F("!\n You must change this line in NMEAGPS_cfg.h:\n"
" #define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_") );
}
DEBUG_PORT.println( gps.string_for( sentences[sentence_count-1] ) );
DEBUG_PORT.println();
} // printSentenceOrder
//------------------------------------
static void GPSloop()
{
while (gpsPort.available()) {
last_rx = millis();
if (gps.decode( gpsPort.read() ) == NMEAGPS::DECODE_COMPLETED) {
if (last_sentence_in_interval == NMEAGPS::NMEA_UNKNOWN) {
// Still building the list
recordSentenceTypes();
DEBUG_PORT.print( '.' );
}
}
}
} // GPSloop
//----------------------------------------------------------------
// Determine whether the GPS quiet time has started.
//
// This is only needed in the example programs, which must work
// for *any* GPS device.
//
// It also "pretends" to have a quiet time once per
// second so that some debug messages are emitted. This allows
// beginners to see whether the GPS device is correctly
// connected and functioning.
static bool quietTimeStarted()
{
uint32_t current_ms = millis();
uint32_t ms_since_last_rx = current_ms - last_rx;
if (ms_since_last_rx > 5) {
// The GPS device has not sent any characters for at least 5ms.
// See if we've been getting chars sometime during the last second.
// If not, the GPS may not be working or connected properly.
bool getting_chars = (ms_since_last_rx < 1000UL);
static uint32_t last_quiet_time = 0UL;
bool just_went_quiet =
(((int32_t) (last_rx - last_quiet_time)) > 10L);
bool next_quiet_time =
((current_ms - last_quiet_time) >= 1000UL);
if ((getting_chars && just_went_quiet)
||
(!getting_chars && next_quiet_time)) {
last_quiet_time = current_ms; // Remember for next loop
// If we're not getting good data, make some suggestions.
bool allDone = false;
if (!getting_chars) {
DEBUG_PORT.println( F("\nCheck GPS device and/or connections. No characters received.\n") );
allDone = true;
} else if (sentence_count == 0) {
DEBUG_PORT.println( F("No valid sentences, but characters are being received.\n"
"Check baud rate or device protocol configuration.\n" ) );
allDone = true;
}
if (allDone) {
DEBUG_PORT.print( F("\nEND.\n") );
for (;;)
; // hang!
}
// No problem, just a real GPS quiet time.
return true;
}
}
return false;
} // quietTimeStarted
//----------------------------------------------------------------
// Figure out what sentence the GPS device sends
// as the last sentence in each 1-second interval.
static void watchForLastSentence()
{
if (quietTimeStarted()) {
if (prev_sentence_count != sentence_count) {
// We have NOT received two full intervals of sentences with
// the same number of sentences in each interval. Start
// recording again.
prev_sentence_count = sentence_count;
sentence_count = 0;
} else if (sentence_count > 0) {
// Got it!
last_sentence_in_interval = sentences[ sentence_count-1 ];
}
}
} // watchForLastSentence
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAorder.INO: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("NMEAGPS object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
GPSloop();
if (last_sentence_in_interval == NMEAGPS::NMEA_UNKNOWN)
watchForLastSentence();
else {
printSentenceOrder();
for (;;)
; // All done!
}
}

View File

@ -0,0 +1,259 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEArevGeoCache.ino
//
// Description: Activates a servo when the current location is
// close enough to the target location.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) The RMC sentence has been enabled.
// 3) Your device sends an RMC sentence (e.g., $GPRMC).
//
// Additional Hardware examples:
// 16x2 Character LCD: https://www.adafruit.com/products/181)
// Servo : TPro Micro SG90 https://www.adafruit.com/products/169
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// Credits:
// This is simplified version of bnordlund9's Geobox:
// http://www.youtube.com/watch?v=g0060tcuofg
//
// Engagement Box by Kenton Harris 11/12/2012
//
// Reverse Geocache idea by Mikal Hart of http://arduiniana.org/
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC ) & \
!defined( NMEAGPS_PARSE_GGA ) & \
!defined( NMEAGPS_PARSE_GLL )
#error You must uncomment at least one of NMEAGPS_PARSE_RMC, NMEAGPS_PARSE_GGA or NMEAGPS_PARSE_GLL in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
//------------------------------------------------------------
// Additional Hardware includes
#include <LiquidCrystal.h>
//#include <NewLiquidCrystal_emul.h>
#include <PWMServo.h>
PWMServo servoLatch;
const int servoLock = 180; // angle (deg) of "locked" servo
const int servoUnlock = 105; // angle (deg) of "unlocked" servo
using namespace NeoGPS;
NMEAGPS gps;
float range; // current distance from HERE to THERE
const float CLOSE_ENOUGH = 300.0; // miles, change for your needs
LiquidCrystal lcd(7, 8, 6, 10, 11, 12);
const int fixLEDPin = 4;
const int servoPin = 9;
// The target location, in degrees * 10,000,000
Location_t there( 384602500, -1098191667L );
// 38°27'36.2"N 109°49'15.4"W
void setup()
{
servoLatch.attach(SERVO_PIN_A);
servoLatch.write(servoLock);
delay(50);
lcd.begin(16, 2);
Serial.begin(9600);
Serial.println( F("Debug GPS Test:") );
gpsPort.begin(9600);
// Configure GPS (these are Adafruit GPS commands - your brand may be different)
gpsPort.print
( F( "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n" // RMC only...
"$PMTK220,1000*1F\r\n" ) ); // ...and 1 Hz update rate
} // setup
void loop()
{
static uint8_t warningState = 0;
static uint32_t lastFixTime, lastDotTime;
while (gps.available( gpsPort )) {
gps_fix fix = gps.read(); // save the latest
// Set the "fix" LED to on or off
bool gpsWasFixed = fix.valid.status && (fix.status >= gps_fix::STATUS_STD);
digitalWrite( fixLEDPin, gpsWasFixed );
// When we have a location, calculate how far away we are from "there".
if (fix.valid.location) {
lastFixTime = millis();
range = fix.location.DistanceMiles( there );
//for GPS debug
DEBUG_PORT.print( F("Here: ") );
//printDMS( DEBUG_PORT, fix.location );
printAsFloat( DEBUG_PORT, fix.location );
DEBUG_PORT.print( F(" There: ") );
//printDMS( DEBUG_PORT, there );
printAsFloat( DEBUG_PORT, there );
DEBUG_PORT.print( F("Range: ") );
DEBUG_PORT.print( range );
DEBUG_PORT.println( F(" Miles") );
// Are we there?
if (range < CLOSE_ENOUGH) {
lcd.setCursor(0,1);
lcd.println( F("Box Unlocking...") );
delay(500);
servoLatch.write(servoUnlock);
lcd.clear();
lcd.setCursor(0,1);
lcd.print( F("Happy Birthday") );
for (;;); // hang here
}
// Nope, just give a little feedback...
lcd.clear();
lcd.setCursor(0,0);
lcd.print( F("Signal Locked") );
lcd.setCursor(0,1);
lcd.print( range );
lcd.print( F(" Miles") );
warningState = 0; // restart in case we lose the fix later
}
}
// Display warnings when the GPS hasn't had a fix for a while
if (millis() - lastFixTime > 2000UL) {
if (warningState == 0) {
// First, show the warning message...
lcd.clear();
lcd.setCursor(0,0);
lcd.print( F("Acquiring Signal") );
warningState = 1;
} else if (warningState < 10) {
// ...then some dots, two per second...
if (millis() - lastDotTime > 500UL ) {
lastDotTime = millis();
lcd.setCursor( warningState-1, 1 );
lcd.print( '.' );
warningState++;
}
} else if (warningState == 10) {
// ... then tell them what to do.
lcd.setCursor(0,1);
lcd.println( F("Take box outside") );
// Don't display anything else until location is valid
warningState++; // 11
}
}
} // loop
//----------------------------------------------------------------
#include "DMS.h"
void printDMS( Print & outs, const Location_t & loc )
{
DMS_t dms;
dms.From( loc.lat() );
outs.print( dms.NS() );
outs << dms;
dms.From( loc.lon() );
outs.print( dms.EW() );
outs << dms;
} // printDMS
//----------------------------------------------------------------
void printL( Print & outs, int32_t degE7 )
{
// Extract and print negative sign
if (degE7 < 0) {
degE7 = -degE7;
outs.print( '-' );
}
// Whole degrees
int32_t deg = degE7 / 10000000L;
outs.print( deg );
outs.print( '.' );
// Get fractional degrees
degE7 -= deg*10000000L;
// Print leading zeroes, if needed
int32_t factor = 1000000L;
while ((degE7 < factor) && (factor > 1L)){
outs.print( '0' );
factor /= 10L;
}
// Print fractional degrees
outs.print( degE7 );
} // printL
//----------------------------------------------------------------
void printAsFloat( Print & outs, const Location_t &loc )
{
printL( outs, loc.lat() ); // display decimal degrees or...
// printDMS( outs, loc.lat() ); // ... display degrees minutes seconds
outs.print( F(", ") );
printL( outs, loc.lat() );
// printDMS( outs, loc.lat() );
}

View File

@ -0,0 +1,72 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAsimple.ino
//
// Description: This program shows simple usage of NeoGPS
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) At least one of the RMC, GGA or GLL sentences have been enabled in NMEAGPS_cfg.h.
// 3) Your device at least one of those sentences (use NMEAorder.ino to confirm).
// 4) LAST_SENTENCE_IN_INTERVAL has been set to one of those sentences in NMEAGPS_cfg.h (use NMEAorder.ino).
// 5) LOCATION and ALTITUDE have been enabled in GPSfix_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
NMEAGPS gps; // This parses the GPS characters
gps_fix fix; // This holds on to the latest values
void setup()
{
DEBUG_PORT.begin(9600);
while (!Serial)
;
DEBUG_PORT.print( F("NMEAsimple.INO: started\n") );
gpsPort.begin(9600);
}
//--------------------------
void loop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
DEBUG_PORT.print( F("Location: ") );
if (fix.valid.location) {
DEBUG_PORT.print( fix.latitude(), 6 );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( fix.longitude(), 6 );
}
DEBUG_PORT.print( F(", Altitude: ") );
if (fix.valid.altitude)
DEBUG_PORT.print( fix.altitude() );
DEBUG_PORT.println();
}
}

View File

@ -0,0 +1,771 @@
#include <NMEAGPS.h>
using namespace NeoGPS;
//======================================================================
// Program: NMEAtest.ino
//
// Prerequisites:
// 1) All NMEA standard messages and Satellite Information
// are enabled.
// 2) All 'gps_fix' members are enabled.
// 3) All validation options are enabled.
//
// Description: This test program uses one GPGGA sentence
// to test the parser's:
// 1) robustness WRT dropped, inserted, and mangled characters
// 2) correctness WRT values extracted from the input stream
//
// Some care in testing must be taken because
// 1) The XOR-style checksum is not very good at catching errors.
// 2) The '*' is a special character for delimiting the CRC. If
// it is changed, a CR/LF will allow the sentence to pass.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined(NMEAGPS_PARSE_GGA) | \
!defined(NMEAGPS_PARSE_GLL) | \
!defined(NMEAGPS_PARSE_GSA) | \
!defined(NMEAGPS_PARSE_GST) | \
!defined(NMEAGPS_PARSE_GSV) | \
!defined(NMEAGPS_PARSE_RMC) | \
!defined(NMEAGPS_PARSE_VTG) | \
!defined(NMEAGPS_PARSE_ZDA)
#error NMEAGPS_PARSE_GGA, GLL, GSA, GSV, RMC, VTG and ZDA must be defined in NMEAGPS_cfg.h!
#endif
#ifndef GPS_FIX_DATE
#error GPS_FIX_DATE must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_TIME
#error GPS_FIX_TIME must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_LOCATION
#error GPS_FIX_LOCATION must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_LOCATION_DMS
#error GPS_FIX_LOCATION_DMS must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_ALTITUDE
#error GPS_FIX_ALTITUDE must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_SPEED
#error GPS_FIX_SPEED must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_HEADING
#error GPS_FIX_HEADING must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_HDOP
#error GPS_FIX_HDOP must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_GEOID_HEIGHT
#error GPS_FIX_GEOID_HEIGHT must be defined in GPSfix_cfg.h!
#endif
static NMEAGPS gps;
//--------------------------
// Example sentences
struct LocVector_t
{
float range;
float bearing;
};
static Location_t AyersRock( -253448688L, 1310324914L );
// -25.3448688,131.0324914
// 2520.692128,S,13101.949484,E
// 25 20' 41.528" S 131 1' 56.969" E
static const LocVector_t NiihauToAyersRock = { 9078.681, 238.33972 };
const char validRMC[] __PROGMEM =
"$GPRMC,092725.00,A,2520.69213,S,13101.94948,E,"
"0.004,77.52,091202,,,A*43\r\n";
//...........................
static Location_t ubloxHQ( 472852369L, 85630763L ); // near Zurich, Switzerland
// 47.2852369, 8.5630763
// 47 17' 6.840" N 008 33' 54.954" E
static const LocVector_t NiihauToUblox = { 12248.67, 8.0625 };
const char validGGA[] __PROGMEM =
"$GPGGA,092725.00,4717.113993,N,00833.915904,E,"
"1,8,1.01,499.6,M,48.0,M,,0*5C\r\n";
//...........................
static Location_t MacchuPicchu( -131628050L, -725455080L );
// -13.162805, -72.545508
// 13.162805,S,72.545508,W
// 13 09' 46.098" S 72 32' 43.830" W
static const LocVector_t NiihauToMacchu = { 10315.93, 103.07306 };
const char validGGA2[] __PROGMEM =
"$GPGGA,162254.00,1309.7683,S,7232.7305,W,1,03,2.36,2430.2,M,-25.6,M,,*7E\r\n";
//...........................
static Location_t DexterMO( 367944050L, -899586550L );
// 36.794405, -89.958655
// 36.794405,N,89.958655,W
// 36 47' 39.858" N 89 57' 31.158" W
static const LocVector_t NiihauToDexter = { 6865.319, 58.85472 };
const char validRMC2[] __PROGMEM =
"$GPRMC,162254.00,A,3647.6643,N,8957.5193,W,0.820,188.36,110706,,,A*49\r\n";
//...........................
static Location_t NiihauHI( 218276210L, -1602448760L );
// 21.827621, -160.244876
// 21.827621,N,160.244876,W
// 21 49' 39.4356" N 160 14' 41.5536 W
static const LocVector_t NiihauToNiihau = { 0.0, 90.0 };
const char validRMC3[] __PROGMEM =
"$GPRMC,235959.99,A,2149.65726,N,16014.69256,W,8.690,359.99,051015,9.47,E,A*26\r\n";
// 218276212L, -1602448757L
// 21 49.65727' N 160 14.69254' W
// 21 49' 39.4362" N 160 14' 41.5524" W
const char validRMC4[] __PROGMEM =
"$GPRMC,235959.99,A,2149.65727,N,16014.69254,W,8.690,359.99,051015,9.47,E,A*25\r\n";
static const LocVector_t NiihauToNiihau2 = { 0.00003812513, 54.31585 };
//...........................
static Location_t JujaKenya( -10934552L, 370261835L );
// -1.0934552, 37.0261835
// 01 05' 36.458" S 037 01' 42.140" E
static const LocVector_t NiihauToJuja = { 17046.24, 318.6483 };
const char validGLL[] __PROGMEM =
"$GNGLL,0105.60764,S,03701.70233,E,225627.00,A,A*6B\r\n";
//--------------------------------
const char mtk1[] __PROGMEM =
"$GPGGA,064951.000,2307.1256,N,12016.4438,E,1,8,0.95,39.9,M,17.8,M,,*63\r\n";
const char mtk2[] __PROGMEM =
"$GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,3.05,W,A*2C\r\n";
const char mtk3[] __PROGMEM =
"$GPVTG,165.48,T,,M,0.03,N,0.06,K,A*36\r\n";
const char mtk4[] __PROGMEM =
"$GPGSA,A,3,29,21,26,15,18,09,06,10,,,,,2.32,0.95,2.11*00\r\n";
const char mtk5[] __PROGMEM =
"$GPGSV,3,1,09,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*7D\r\n";
const char mtk6[] __PROGMEM =
"$GPGSV,3,2,09,18,26,314,40,09,57,170,44,06,20,229,37,10,26,084,37*77\r\n";
const char mtk7[] __PROGMEM =
"$GPGSV,3,3,09,07,,,26*73\r\n";
const char mtk7a[] __PROGMEM =
"$GLGSV,1,1,4,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*5E\r\n";
const char mtk8[] __PROGMEM =
"$GNGST,082356.00,1.8,,,,1.7,1.3,2.2*60\r\n";
const char mtk9[] __PROGMEM =
"$GNRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A,V*33\r\n";
const char mtk10[] __PROGMEM =
"$GNGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*45\r\n";
const char mtk11[] __PROGMEM =
"$GLZDA,225627.00,21,09,2015,00,00*70\r\n";
const char fpGGA00[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816900,W,"
"1,8,1.01,499.6,M,48.0,M,,0*49\r\n";
const char fpGGA01[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816901,W,"
"1,8,1.01,499.6,M,48.0,M,,0*48\r\n";
const char fpGGA02[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816902,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4B\r\n";
const char fpGGA03[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816903,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4A\r\n";
const char fpGGA04[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816904,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4D\r\n";
const char fpGGA05[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816905,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4C\r\n";
const char fpGGA06[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816906,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4F\r\n";
const char fpGGA07[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816907,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4E\r\n";
const char fpGGA08[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816908,W,"
"1,8,1.01,499.6,M,48.0,M,,0*41\r\n";
const char fpGGA09[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816909,W,"
"1,8,1.01,499.6,M,48.0,M,,0*40\r\n";
const char fpGGA10[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816910,W,"
"1,8,1.01,499.6,M,48.0,M,,0*48\r\n";
//--------------------------
static bool parse_P( const char *ptr )
{
bool decoded = false;
char c;
gps.fix().init();
while ( (c = pgm_read_byte( ptr++ )) != '\0' ) {
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
decoded = true;
}
}
return decoded;
}
//--------------------------
static void traceSample( const char *ptr, bool init = true )
{
Serial << F("Input: ") << (const __FlashStringHelper *) ptr;
if (init)
gps.data_init();
bool decoded = parse_P( ptr );
if (decoded)
Serial << F("Results: ");
else
Serial << F("Failed to decode! ");
trace_all( Serial, gps, gps.fix() );
Serial << '\n';
}
//--------------------------
static uint8_t passed = 0;
static uint8_t failed = 0;
static void checkFix
( const char *msg, NMEAGPS::nmea_msg_t msg_type, gps_fix::status_t status,
int32_t lat, int32_t lon,
uint8_t latDeg, uint8_t latMin, uint8_t latSec, uint16_t latSecFrac, Hemisphere_t ns,
uint8_t lonDeg, uint8_t lonMin, uint8_t lonSec, uint16_t lonSecFrac, Hemisphere_t ew,
const LocVector_t & to )
{
const char *ptr = msg;
for (;;) {
char c = pgm_read_byte( ptr++ );
if (!c) {
Serial.print( F("FAILED to parse \"") );
Serial.print( (const __FlashStringHelper *) msg );
Serial.println( F("\"\n") );
failed++;
break;
}
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
bool ok = true;
const gps_fix & fix = gps.fix();
if (gps.nmeaMessage != msg_type) {
Serial.print( F("FAILED wrong message type ") );
Serial.println( gps.nmeaMessage );
failed++;
ok = false;
}
if (fix.status != status ) {
Serial.print( F("FAILED wrong status ") );
Serial.print( fix.status );
Serial.print( F(" (expected ") );
Serial.print( status );
Serial.println( ')' );
failed++;
ok = false;
}
if (fix.latitudeL() != lat) {
Serial.print( F("FAILED wrong latitude ") );
Serial.print( fix.latitudeL() );
Serial.print( F(" (expected ") );
Serial.print( lat );
Serial.println( ')' );
failed++;
ok = false;
}
if (fix.longitudeL() != lon) {
Serial.print( F("FAILED wrong longitude ") );
Serial.print( fix.longitudeL() );
Serial.print( F(" (expected ") );
Serial.print( lon );
Serial.println( ')' );
failed++;
ok = false;
}
if (fix.latitudeDMS.degrees != latDeg) {
Serial.print( F("FAILED wrong latitude degrees ") );
Serial.print( fix.latitudeDMS.degrees );
Serial.print( F(", expected ") );
Serial.println( latDeg );
failed++;
ok = false;
}
if (fix.latitudeDMS.minutes != latMin) {
Serial.print( F("FAILED wrong latitude minutes ") );
Serial.print( fix.latitudeDMS.minutes );
Serial.print( F(", expected ") );
Serial.println( latMin );
failed++;
ok = false;
}
if (fix.latitudeDMS.seconds_whole != latSec) {
Serial.print( F("FAILED wrong latitude seconds ") );
Serial.print( fix.latitudeDMS.seconds_whole );
Serial.print( F(", expected ") );
Serial.println( latSec );
failed++;
ok = false;
}
int8_t fracDiff = (int8_t)(fix.latitudeDMS.seconds_frac - latSecFrac);
const int8_t ieps = 1;
if (abs(fracDiff) > ieps) {
Serial.print( F("FAILED wrong latitude seconds fraction ") );
Serial.print( fix.latitudeDMS.seconds_frac );
Serial.print( F(", expected ") );
Serial.println( latSecFrac );
failed++;
ok = false;
}
if (fix.latitudeDMS.hemisphere != ns) {
Serial.print( F("FAILED wrong latitude NS ") );
Serial.println( fix.latitudeDMS.NS() );
failed++;
ok = false;
}
if (fix.longitudeDMS.degrees != lonDeg) {
Serial.print( F("FAILED wrong longitude degrees ") );
Serial.print( fix.longitudeDMS.degrees );
Serial.print( F(", expected ") );
Serial.println( lonDeg );
failed++;
ok = false;
}
if (fix.longitudeDMS.minutes != lonMin) {
Serial.print( F("FAILED wrong longitude minutes ") );
Serial.print( fix.longitudeDMS.minutes );
Serial.print( F(", expected ") );
Serial.println( lonMin );
failed++;
ok = false;
}
if (fix.longitudeDMS.seconds_whole != lonSec) {
Serial.print( F("FAILED wrong longitude seconds ") );
Serial.print( fix.longitudeDMS.seconds_whole );
Serial.print( F(", expected ") );
Serial.println( lonSec );
failed++;
ok = false;
}
fracDiff = (int8_t)(fix.longitudeDMS.seconds_frac - lonSecFrac);
if (abs(fracDiff) > ieps) {
Serial.print( F("FAILED wrong longitude seconds fraction ") );
Serial.print( fix.longitudeDMS.seconds_frac );
Serial.print( F(", expected ") );
Serial.println( lonSecFrac );
failed++;
ok = false;
}
if (fix.longitudeDMS.hemisphere != ew) {
Serial.print( F("FAILED wrong longitude EW ") );
Serial.println( fix.longitudeDMS.EW() );
failed++;
ok = false;
}
char floatChars[16];
float distance = NiihauHI.DistanceKm( fix.location );
float diff = abs( distance - to.range );
if ( (diff/to.range) > 0.000001 ) {
Serial.print( F("FAILED distance ") );
dtostre( distance, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.print( floatChars );
Serial.print( F(" != ") );
dtostre( to.range, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.println( floatChars );
failed++;
ok = false;
}
float courseTo = NiihauHI.BearingToDegrees( fix.location );
diff = abs( courseTo - to.bearing );
if ( diff > 0.005 ) {
Serial.print( F("FAILED bearing ") );
dtostre( courseTo, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.print( floatChars );
Serial.print( F(" != ") );
dtostre( to.bearing, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.print( floatChars );
failed++;
ok = false;
}
if (ok)
passed++;
break;
}
}
}
//--------------------------
void setup()
{
// Start the normal trace output
Serial.begin(9600);
Serial.print( F("NMEA test: started\n") );
Serial.print( F("fix object size = ") );
Serial.println( sizeof(gps_fix) );
Serial.print( F("gps object size = ") );
Serial.println( sizeof(NMEAGPS) );
// Some basic rejection tests
Serial.println( F("Test rejection of all byte values") );
for (uint16_t c=0; c < 256; c++) {
if (c != '$') {
if (NMEAGPS::DECODE_CHR_INVALID != gps.decode( (char)c )) {
Serial.print( F("FAILED to reject single character ") );
Serial.println( c );
failed++;
return;
}
}
}
passed++;
Serial.println( F("Test rejection of multiple $") );
for (uint16_t i=0; i < 256; i++) {
if (NMEAGPS::DECODE_COMPLETED == gps.decode( '$' )) {
Serial.print( F("FAILED to reject multiple '$' characters\n") );
failed++;
return;
}
}
passed++;
uint16_t validGGA_len = 0;
// Insert a ' ' at each position of the test sentence
Serial.println( F("Insert ' '") );
uint16_t insert_at = 1;
do {
const char *ptr = validGGA;
uint8_t j = 0;
for (;;) {
if (j++ == insert_at) {
NMEAGPS::decode_t result = gps.decode( ' ' );
if (gps.validateChars() || gps.validateFields()) {
if (result == NMEAGPS::DECODE_COMPLETED) {
Serial.print( F("FAILED incorrectly decoded ' ' @ pos ") );
Serial.println( insert_at );
failed++;
} else if (gps.nmeaMessage != NMEAGPS::NMEA_UNKNOWN) {
Serial.print( F("FAILED incorrectly accepted ' ' @ pos ") );
Serial.println( insert_at );
failed++;
}
}
}
char c = pgm_read_byte( ptr++ );
if (!c) {
if (validGGA_len == 0) {
validGGA_len = j-1;
Serial.print( F("Test string length = ") );
Serial.println( validGGA_len );
}
break;
}
if (gps.decode( c ) == NMEAGPS::DECODE_COMPLETED) {
Serial.print( F("FAILED incorrectly decoded @ pos ") );
Serial.println( insert_at );
failed++;
//return;
}
}
} while (++insert_at < validGGA_len-2);
passed++;
// Drop one character from each position in example sentence
Serial.println( F("Drop character") );
for (uint16_t i=0; i < validGGA_len-3; i++) {
const char *ptr = validGGA;
uint8_t firstInvalidPos = 0;
char dropped = 0;
for (uint8_t j = 0;; j++) {
char c = pgm_read_byte( ptr++ );
if (!c || (c == '*')) break;
if (j == i) {
dropped = c;
} else {
NMEAGPS::decode_t result = gps.decode( c );
if (result == NMEAGPS::DECODE_COMPLETED) {
Serial.print( F("FAILED decoded after dropping '") );
Serial << dropped;
Serial.print( F("' at pos ") );
Serial.println( i );
failed++;
} else if (gps.nmeaMessage == NMEAGPS::NMEA_UNKNOWN) {
if (firstInvalidPos != 0)
firstInvalidPos = j;
}
}
}
if (firstInvalidPos != 0) {
Serial.print( F(" /*") );
Serial.print( i );
Serial.print( F("*/ ") );
Serial.println( firstInvalidPos );
}
}
passed++;
// Mangle one character from each position in example sentence
Serial.println( F("Mangle one character") );
for (uint16_t i=0; i < validGGA_len-3; i++) {
const char *ptr = validGGA;
uint8_t j = 0;
char replaced = 0;
for (;;) {
char c = pgm_read_byte( ptr++ );
if (!c || (c == '*')) break;
if (j++ == i)
replaced = c++; // mangle means increment
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
Serial.print( F("FAILED replacing '") );
Serial << (uint8_t) replaced;
Serial.print( F("' with '") );
Serial << (uint8_t) (replaced+1);
Serial.print( F("' at pos ") );
Serial.println( i );
failed++;
break;
//return;
}
}
}
passed++;
// Verify that exact values are extracted
Serial.println( F("Verify parsed values") );
{
const char *ptr = validGGA;
for (;;) {
char c = pgm_read_byte( ptr++ );
if (!c) {
Serial.print( F("FAILED to parse \"") );
Serial.print( (str_P) validGGA );
Serial.println( F("\"\n") );
failed++;
break;
}
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
gps_fix expected;
expected.dateTime.parse( PSTR("2002-12-09 09:27:25") );
expected.dateTime_cs = 0;
if (gps.nmeaMessage != NMEAGPS::NMEA_GGA) {
Serial.print( F("FAILED wrong message type ") );
Serial.println( gps.nmeaMessage );
failed++;
break;
}
if ((gps.fix().dateTime.hours != expected.dateTime.hours ) ||
(gps.fix().dateTime.minutes != expected.dateTime.minutes) ||
(gps.fix().dateTime.seconds != expected.dateTime.seconds) ||
(gps.fix().dateTime_cs != expected.dateTime_cs)) {
Serial << F("FAILED wrong time ") << gps.fix().dateTime << '.' << gps.fix().dateTime_cs << F(" != ") << expected.dateTime << '.' << expected.dateTime_cs << '\n';
failed++;
break;
}
if (gps.fix().latitudeL() != 472852332L) {
Serial.print( F("FAILED wrong latitude ") );
Serial.println( gps.fix().latitudeL() );
failed++;
break;
}
if (gps.fix().longitudeL() != 85652651L) {
Serial.print( F("FAILED wrong longitude ") );
Serial.println( gps.fix().longitudeL() );
failed++;
break;
}
if (gps.fix().status != gps_fix::STATUS_STD) {
Serial.print( F("FAILED wrong status ") );
Serial.println( gps.fix().status );
failed++;
break;
}
if (gps.fix().satellites != 8) {
Serial.print( F("FAILED wrong satellites ") );
Serial.println( gps.fix().satellites );
failed++;
break;
}
if (gps.fix().hdop != 1010) {
Serial.print( F("FAILED wrong HDOP ") );
Serial.println( gps.fix().hdop );
failed++;
break;
}
if (gps.fix().altitude_cm() != 49960) {
Serial.print( F("FAILED wrong altitude ") );
Serial.println( gps.fix().longitudeL() );
failed++;
break;
}
break;
}
}
}
passed++;
checkFix( validRMC , NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
-253448688L, 1310324913L,
25, 20, 41, 528, SOUTH_H, 131, 1, 56, 969, EAST_H,
NiihauToAyersRock );
checkFix( validGGA , NMEAGPS::NMEA_GGA, gps_fix::STATUS_STD,
472852332L, 85652651L,
47, 17, 6, 840, NORTH_H, 8, 33, 54, 954, EAST_H,
NiihauToUblox );
if (gps.fix().geoidHeight_cm() != 4800) {
Serial.print( F("FAILED wrong geoid height for 48.00") );
Serial.println( gps.fix().geoidHeight_cm() );
failed++;
}
checkFix( validGGA2, NMEAGPS::NMEA_GGA, gps_fix::STATUS_STD,
-131628050L, -725455083L,
13, 9, 46, 98, SOUTH_H, 72, 32, 43, 830, WEST_H,
NiihauToMacchu );
if (gps.fix().geoidHeight_cm() != -2560) {
Serial.print( F("FAILED wrong geoid height for -25.60") );
Serial.println( gps.fix().geoidHeight_cm() );
failed++;
}
checkFix( validRMC2, NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
367944050L, -899586550L,
36, 47, 39, 858, NORTH_H, 89, 57, 31, 158, WEST_H,
NiihauToDexter );
checkFix( validRMC3, NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
218276210L, -1602448760L,
21, 49, 39, 436, NORTH_H, 160, 14, 41, 554, WEST_H,
NiihauToNiihau );
checkFix( validRMC4, NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
218276212L, -1602448757L,
21, 49, 39, 436, NORTH_H, 160, 14, 41, 552, WEST_H,
NiihauToNiihau2 );
checkFix( validGLL , NMEAGPS::NMEA_GLL, gps_fix::STATUS_STD,
-10934607L, 370283722L,
1, 5, 36, 458, SOUTH_H, 37, 1, 42, 140, EAST_H,
NiihauToJuja );
}
//--------------------------
void loop()
{
Serial.print( F("PASSED ") );
Serial << passed;
Serial.println( F(" tests.") );
if (failed) {
Serial.print( F("FAILED ") );
Serial << failed;
Serial.println( F(" tests.") );
} else {
Serial << F("------ Samples ------\nResults format:\n ");
trace_header( Serial );
Serial << '\n';
#ifdef NMEAGPS_STATS
gps.statistics.init();
#endif
traceSample( validGGA );
traceSample( validGGA2 );
traceSample( validRMC );
traceSample( validRMC2 );
traceSample( validRMC3 );
traceSample( validGLL );
traceSample( mtk1 );
traceSample( mtk2 );
traceSample( mtk3 );
traceSample( mtk4 );
traceSample( mtk5 );
traceSample( mtk6, false );
traceSample( mtk7, false );
traceSample( mtk7a, false );
traceSample( mtk8 );
traceSample( mtk9 );
traceSample( mtk10 );
traceSample( mtk11 );
if (!gps.fix().valid.date ||
(gps.fix().dateTime.date != 21) ||
(gps.fix().dateTime.month != 9) ||
(gps.fix().dateTime.year != 15) ||
!gps.fix().valid.time ||
(gps.fix().dateTime.hours != 22) ||
(gps.fix().dateTime.minutes != 56) ||
(gps.fix().dateTime.seconds != 27))
Serial << F("******** ZDA not parsed correctly **********\n");
/**
* This next section displays incremental longitudes.
* If you have defined USE_FLOAT in Streamers.cpp, this will show
* how the conversion to /float/ causes loss of accuracy compared
* to the /uint32_t/ values.
*/
Serial << F("--- floating point conversion tests ---\n\n");
traceSample( fpGGA00 );
traceSample( fpGGA01 );
traceSample( fpGGA02 );
traceSample( fpGGA03 );
traceSample( fpGGA04 );
traceSample( fpGGA05 );
traceSample( fpGGA06 );
traceSample( fpGGA07 );
traceSample( fpGGA08 );
traceSample( fpGGA09 );
traceSample( fpGGA10 );
}
for (;;);
}

View File

@ -0,0 +1,188 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAtimezone.ino
//
// Description: This program shows how to offset the GPS dateTime member
// into your specific timezone. GPS devices do not know which
// timezone they are in, so they always report a UTC time. This
// is the same as GMT.
//
// Prerequisites:
// 1) NMEA.ino works with your device
// 2) GPS_FIX_TIME is enabled in GPSfix_cfg.h
// 3) NMEAGPS_PARSE_RMC is enabled in NMEAGPS_cfg.h. You could use
// any sentence that contains a time field. Be sure to change the
// "if" statement in GPSloop from RMC to your selected sentence.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
static NMEAGPS gps; // This parses received characters
static gps_fix fix; // This contains all the parsed pieces
//--------------------------
// CHECK CONFIGURATION
#if !defined(GPS_FIX_TIME) | !defined(GPS_FIX_DATE)
#error You must define GPS_FIX_TIME and DATE in GPSfix_cfg.h!
#endif
#if !defined(NMEAGPS_PARSE_RMC) & !defined(NMEAGPS_PARSE_ZDA)
#error You must define NMEAGPS_PARSE_RMC or ZDA in NMEAGPS_cfg.h!
#endif
#include <GPSport.h>
//--------------------------
// Set these values to the offset of your timezone from GMT
static const int32_t zone_hours = -5L; // EST
static const int32_t zone_minutes = 0L; // usually zero
static const NeoGPS::clock_t zone_offset =
zone_hours * NeoGPS::SECONDS_PER_HOUR +
zone_minutes * NeoGPS::SECONDS_PER_MINUTE;
// Uncomment one DST changeover rule, or define your own:
#define USA_DST
//#define EU_DST
#if defined(USA_DST)
static const uint8_t springMonth = 3;
static const uint8_t springDate = 14; // latest 2nd Sunday
static const uint8_t springHour = 2;
static const uint8_t fallMonth = 11;
static const uint8_t fallDate = 7; // latest 1st Sunday
static const uint8_t fallHour = 2;
#define CALCULATE_DST
#elif defined(EU_DST)
static const uint8_t springMonth = 3;
static const uint8_t springDate = 31; // latest last Sunday
static const uint8_t springHour = 1;
static const uint8_t fallMonth = 10;
static const uint8_t fallDate = 31; // latest last Sunday
static const uint8_t fallHour = 1;
#define CALCULATE_DST
#endif
//--------------------------
void adjustTime( NeoGPS::time_t & dt )
{
NeoGPS::clock_t seconds = dt; // convert date/time structure to seconds
#ifdef CALCULATE_DST
// Calculate DST changeover times once per reset and year!
static NeoGPS::time_t changeover;
static NeoGPS::clock_t springForward, fallBack;
if ((springForward == 0) || (changeover.year != dt.year)) {
// Calculate the spring changeover time (seconds)
changeover.year = dt.year;
changeover.month = springMonth;
changeover.date = springDate;
changeover.hours = springHour;
changeover.minutes = 0;
changeover.seconds = 0;
changeover.set_day();
// Step back to a Sunday, if day != SUNDAY
changeover.date -= (changeover.day - NeoGPS::time_t::SUNDAY);
springForward = (NeoGPS::clock_t) changeover;
// Calculate the fall changeover time (seconds)
changeover.month = fallMonth;
changeover.date = fallDate;
changeover.hours = fallHour - 1; // to account for the "apparent" DST +1
changeover.set_day();
// Step back to a Sunday, if day != SUNDAY
changeover.date -= (changeover.day - NeoGPS::time_t::SUNDAY);
fallBack = (NeoGPS::clock_t) changeover;
}
#endif
// First, offset from UTC to the local timezone
seconds += zone_offset;
#ifdef CALCULATE_DST
// Then add an hour if DST is in effect
if ((springForward <= seconds) && (seconds < fallBack))
seconds += NeoGPS::SECONDS_PER_HOUR;
#endif
dt = seconds; // convert seconds back to a date/time structure
} // adjustTime
//--------------------------
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
// Display the local time
if (fix.valid.time && fix.valid.date) {
adjustTime( fix.dateTime );
DEBUG_PORT << fix.dateTime;
}
DEBUG_PORT.println();
}
} // GPSloop
//--------------------------
#ifdef NMEAGPS_INTERRUPT_PROCESSING
static void GPSisr( uint8_t c )
{
gps.handle( c );
}
#endif
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAtimezone.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME ) );
DEBUG_PORT.println( F("Local time") );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
#ifdef NMEAGPS_INTERRUPT_PROCESSING
gpsPort.attachInterrupt( GPSisr );
#endif
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,138 @@
#include <NeoGPS_cfg.h>
#include <Garmin/GrmNMEA.h>
#include <GPSTime.h>
//======================================================================
// Program: PGRM.ino
//
// Description: This program parses NMEA proprietary messages from
// Garmin devices. It is an extension of NMEA.ino.
//
// Prerequisites:
// 1) You have a Garmin GPS device
// 2) NMEA.ino works with your device
// 3) At least one NMEA standard or proprietary sentence has been enabled.
// 4) Implicit Merging is disabled.
//
// Serial is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST ) & \
!defined( GARMINGPS_PARSE_F )
#error No NMEA sentences enabled: no fix data available.
#endif
#if !defined( GARMINGPS_PARSE_F )
#error No PGRM messages enabled! You must enable one or more in PGRM.h!
#endif
#ifndef NMEAGPS_DERIVED_TYPES
#error You must "#define NMEAGPS_DERIVED_TYPES" in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_EXPLICIT_MERGING
#error You must define NMEAGPS_EXPLICIT_MERGING in NMEAGPS_cfg.h
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------------
static GarminNMEA gps; // This parses received characters
static gps_fix fix;
//----------------------------------------------------------------
static void doSomeWork()
{
// Print leap seconds, when it finally arrives.
static bool leapSecondsPrinted = false;
if (not leapSecondsPrinted and (GPSTime::leap_seconds != 0)) {
leapSecondsPrinted = true;
DEBUG_PORT.print( F("GPS leap seconds = ") );
DEBUG_PORT.println( GPSTime::leap_seconds );
}
// Print all the things!
trace_all( DEBUG_PORT, gps, fix );
} // doSomeWork
//------------------------------------
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
doSomeWork();
}
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("PGRM: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("GarminNMEA object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifdef GARMINGPS_PARSE_F
if (LAST_SENTENCE_IN_INTERVAL != (NMEAGPS::nmea_msg_t) GarminNMEA::PGRMF) {
DEBUG_PORT.println( F("ERROR! LAST_SENTENCE_IN_INTERVAL should be PGRMF") );
for(;;);
}
#endif
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.begin(9600);
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,157 @@
#include <NeoGPS_cfg.h>
#include <ublox/ubxNMEA.h>
//======================================================================
// Program: PUBX.ino
//
// Description: This program parses NMEA proprietary messages from
// ublox devices. It is an extension of NMEA.ino.
//
// Prerequisites:
// 1) You have a ublox GPS device
// 2) NMEA.ino works with your device
// 3) You have installed ubxNMEA.H and ubxNMEA.CPP
// 4) At least one NMEA standard or proprietary sentence has been enabled.
// 5) Implicit Merging is disabled.
//
// Serial is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST ) & \
!defined( NMEAGPS_PARSE_PUBX_00 ) & !defined( NMEAGPS_PARSE_PUBX_04 )
#error No NMEA sentences enabled: no fix data available.
#endif
#if !defined( NMEAGPS_PARSE_PUBX_00 ) & !defined( NMEAGPS_PARSE_PUBX_04 )
#error No PUBX messages enabled! You must enable one or more in PUBX_cfg.h!
#endif
#ifndef NMEAGPS_DERIVED_TYPES
#error You must "#define NMEAGPS_DERIVED_TYPES" in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_EXPLICIT_MERGING
#error You must define NMEAGPS_EXPLICIT_MERGING in NMEAGPS_cfg.h
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------------
static ubloxNMEA gps; // This parses received characters
static gps_fix fix;
//----------------------------------------------------------------
static void poll()
{
#if defined( NMEAGPS_PARSE_PUBX_00 )
gps.send_P( &gpsPort, F("PUBX,00") );
#endif
#if defined( NMEAGPS_PARSE_PUBX_04 )
gps.send_P( &gpsPort, F("PUBX,04") );
#endif
}
//----------------------------------------------------------------
static void doSomeWork()
{
// Print all the things!
trace_all( DEBUG_PORT, gps, fix );
// Ask for the proprietary messages again
poll();
} // doSomeWork
//------------------------------------
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
#if defined(GPS_FIX_VELNED) && defined(NMEAGPS_PARSE_PUBX_00)
fix.calculateNorthAndEastVelocityFromSpeedAndHeading();
#endif
doSomeWork();
}
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("PUBX: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("ubloxNMEA object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifdef NMEAGPS_PARSE_PUBX_04
if (LAST_SENTENCE_IN_INTERVAL != (NMEAGPS::nmea_msg_t) ubloxNMEA::PUBX_04) {
DEBUG_PORT.println( F("ERROR! LAST_SENTENCE_IN_INTERVAL should be PUBX_04") );
for(;;);
}
#else
if (LAST_SENTENCE_IN_INTERVAL != (NMEAGPS::nmea_msg_t) ubloxNMEA::PUBX_00) {
DEBUG_PORT.println( F("ERROR! LAST_SENTENCE_IN_INTERVAL should be PUBX_00") );
for(;;);
}
#endif
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.begin(9600);
// Ask for the special PUBX sentences
poll();
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@ -0,0 +1,137 @@
#include <NMEAGPS.h>
//======================================================================
// Program: SyncTime.ino
//
// Description: This program shows how to update the sub-second
// part of a clock's seconds. You can adjust the clock update
// interval to be as small as 9ms. It will calculate the
// correct ms offset from each GPS second.
//
// Prerequisites:
// 1) NMEA.ino works with your device
// 2) GPS_FIX_TIME is enabled in GPSfix_cfg.h
// 3) NMEAGPS_PARSE_RMC is enabled in NMEAGPS_cfg.h. You could use
// any sentence that contains a time field. Be sure to change the
// "if" statement in GPSloop from RMC to your selected sentence.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
static NMEAGPS gps;
static gps_fix fix;
#if !defined(GPS_FIX_TIME)
#error You must define GPS_FIX_TIME in GPSfix_cfg.h!
#endif
#if !defined(NMEAGPS_PARSE_RMC) & \
!defined(NMEAGPS_PARSE_GLL) & \
!defined(NMEAGPS_PARSE_GGA) & \
!defined(NMEAGPS_PARSE_GST)
#error You must define NMEAGPS_PARSE_RMC, GLL, GGA or GST in NMEAGPS_cfg.h!
#endif
#if !defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) & \
!defined(NMEAGPS_TIMESTAMP_FROM_PPS)
#error You must define NMEAGPS_TIMESTAMP_FROM_INTERVAL or PPS in NMEAGPS_cfg.h!
#endif
#if defined(NMEAGPS_TIMESTAMP_FROM_PPS)
#warning You must modify this sketch to call gps.UTCsecondStart at the PPS rising edge (see NMEAGPS.h comments).
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//----------------------------------------------------------------
static const uint32_t CLOCK_INTERVAL_MS = 100UL;
static uint32_t lastShowTime = CLOCK_INTERVAL_MS+1; // First time never matches
//----------------------------------------------------------------
static void showTime( uint16_t subs, uint16_t factor = 100 /* hundredths */ )
{
uint8_t showSeconds = fix.dateTime.seconds;
// Step by seconds until we're in the current UTC second
while (subs >= 1000UL) {
subs -= 1000UL;
if (showSeconds < 59)
showSeconds++;
else
showSeconds = 0;
//DEBUG_PORT.print( '+' );
}
DEBUG_PORT.print( showSeconds );
DEBUG_PORT.print( '.' );
// Leading zeroes
for (;;) {
factor /= 10;
if ((factor < 10) || (subs >= factor))
break;
DEBUG_PORT.print( '0' );
}
DEBUG_PORT.println( subs );
} // showTime
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("SyncTime.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.println( F("Local time seconds.milliseconds") );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
}
if (fix.valid.time) {
uint32_t UTCms = gps.UTCms();
if (((UTCms % CLOCK_INTERVAL_MS) == 0) && (UTCms != lastShowTime)) {
showTime( UTCms, 1000 );
lastShowTime = UTCms;
}
}
}

View File

@ -0,0 +1,198 @@
#include <NMEAGPS.h>
NMEAGPS gps;
//======================================================================
// Program: SyncTime.ino
//
// Description: This program displays all GPS fields in the default configuration
// in a tabular display. To be comparable to other libraries' tabular displays,
// you must also enable HDOP in GPSfix_cfg.h.
//
// Most NeoGPS examples display *all* configured GPS fields in a CSV format
// (e.g., NMEA.ino).
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) GPS_FIX_HDOP is defined in GPSfix_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//-----------------
// Check configuration
#ifndef GPS_FIX_HDOP
#error You must uncomment GPS_FIX_HDOP in GPSfix_cfg.h!
#endif
//-----------------
static const NeoGPS::Location_t London( 51.508131, -0.128002 );
void setup()
{
DEBUG_PORT.begin(9600);
DEBUG_PORT.println
(
F( "Testing NeoGPS library\n\n"
"Sats HDOP Latitude Longitude Date Time Alt Speed Heading -- To London -- Chars Sentences Errors\n"
" (deg) (deg) (m) Dist Dir\n" )
);
repeat( '-', 133 );
gpsPort.begin(9600);
}
//-----------------
void loop()
{
if (gps.available( gpsPort )) {
gps_fix fix = gps.read();
float bearingToLondon = fix.location.BearingToDegrees( London );
bool validDT = fix.valid.date & fix.valid.time;
print( fix.satellites , fix.valid.satellites, 3 );
print( fix.hdop/1000.0 , fix.valid.hdop , 6, 2 );
print( fix.latitude () , fix.valid.location , 10, 6 );
print( fix.longitude() , fix.valid.location , 11, 6 );
print( fix.dateTime , validDT , 20 );
print( fix.altitude () , fix.valid.altitude , 7, 2 );
print( fix.speed_kph() , fix.valid.speed , 7, 2 );
print( fix.heading () , fix.valid.heading , 7, 2 );
print( compassDir( fix.heading () ) , fix.valid.heading , 4 );
print( fix.location.DistanceKm( London ), fix.valid.location , 5 );
print( bearingToLondon , fix.valid.location , 7, 2 );
print( compassDir( bearingToLondon ) , fix.valid.location , 4 );
print( gps.statistics.chars , true, 10 );
print( gps.statistics.ok , true, 6 );
print( gps.statistics.errors, true, 6 );
DEBUG_PORT.println();
}
}
//-----------------
// Print utilities
static void repeat( char c, int8_t len )
{
for (int8_t i=0; i<len; i++)
DEBUG_PORT.write( c );
}
static void printInvalid( int8_t len )
{
DEBUG_PORT.write( ' ' );
repeat( '*', abs(len)-1 );
}
static void print( float val, bool valid, int8_t len, int8_t prec )
{
if (!valid) {
printInvalid( len );
} else {
char s[16];
dtostrf( val, len, prec, s );
DEBUG_PORT.print( s );
}
}
static void print( int32_t val, bool valid, int8_t len )
{
if (!valid) {
printInvalid( len );
} else {
char s[16];
ltoa( val, s, 10 );
repeat( ' ', len - strlen(s) );
DEBUG_PORT.print( s );
}
}
static void print( const __FlashStringHelper *str, bool valid, int8_t len )
{
if (!valid) {
printInvalid( len );
} else {
int slen = strlen_P( (const char *) str );
repeat( ' ', len-slen );
DEBUG_PORT.print( str );
}
}
static void print( const NeoGPS::time_t & dt, bool valid, int8_t len )
{
if (!valid) {
printInvalid( len );
} else {
DEBUG_PORT.write( ' ' );
Serial << dt; // this "streaming" operator outputs date and time
}
}
//------------------------------------------------------------
// This snippet is from NMEAaverage. It keeps all the
// compass direction strings in FLASH memory, saving RAM.
const char nCD [] PROGMEM = "N";
const char nneCD[] PROGMEM = "NNE";
const char neCD [] PROGMEM = "NE";
const char eneCD[] PROGMEM = "ENE";
const char eCD [] PROGMEM = "E";
const char eseCD[] PROGMEM = "ESE";
const char seCD [] PROGMEM = "SE";
const char sseCD[] PROGMEM = "SSE";
const char sCD [] PROGMEM = "S";
const char sswCD[] PROGMEM = "SSW";
const char swCD [] PROGMEM = "SW";
const char wswCD[] PROGMEM = "WSW";
const char wCD [] PROGMEM = "W";
const char wnwCD[] PROGMEM = "WNW";
const char nwCD [] PROGMEM = "NW";
const char nnwCD[] PROGMEM = "NNW";
const char * const dirStrings[] PROGMEM =
{ nCD, nneCD, neCD, eneCD, eCD, eseCD, seCD, sseCD,
sCD, sswCD, swCD, wswCD, wCD, wnwCD, nwCD, nnwCD };
const __FlashStringHelper *compassDir( uint16_t bearing ) // degrees CW from N
{
const int16_t directions = sizeof(dirStrings)/sizeof(dirStrings[0]);
const int16_t degreesPerDir = 360 / directions;
int8_t dir = (bearing + degreesPerDir/2) / degreesPerDir;
while (dir < 0)
dir += directions;
while (dir >= directions)
dir -= directions;
return (const __FlashStringHelper *) pgm_read_ptr( &dirStrings[ dir ] );
} // compassDir

View File

@ -0,0 +1,399 @@
#include <NeoGPS_cfg.h>
#include <ublox/ubxGPS.h>
//======================================================================
// Program: ublox.ino
//
// Prerequisites:
// 1) You have a ublox GPS device
// 2) PUBX.ino works with your device
// 3) You have installed the ubxGPS.* and ubxmsg.* files.
// 4) At least one UBX message has been enabled in ubxGPS.h.
// 5) Implicit Merging is disabled in NMEAGPS_cfg.h.
//
// Description: This program parses UBX binary protocal messages from
// ublox devices. It shows how to acquire the information necessary
// to use the GPS Time-Of-Week in many UBX messages. As an offset
// from midnight Sunday morning (GPS time), you also need the current
// UTC time (this is *not* GPS time) and the current number of GPS
// leap seconds.
//
// Serial is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#ifndef NMEAGPS_DERIVED_TYPES
#error You must "#define NMEAGPS_DERIVED_TYPES" in NMEAGPS_cfg.h!
#endif
#if !defined(UBLOX_PARSE_STATUS) & !defined(UBLOX_PARSE_TIMEGPS) & \
!defined(UBLOX_PARSE_TIMEUTC) & !defined(UBLOX_PARSE_POSLLH) & \
!defined(UBLOX_PARSE_DOP) & !defined(UBLOX_PARSE_PVT) & \
!defined(UBLOX_PARSE_VELNED) & !defined(UBLOX_PARSE_SVINFO) & \
!defined(UBLOX_PARSE_HNR_PVT)
#error No UBX binary messages enabled: no fix data available.
#endif
#ifndef NMEAGPS_RECOGNIZE_ALL
// Resetting the messages with ublox::configNMEA requires that
// all message types are recognized (i.e., the enum has all
// values).
#error You must "#define NMEAGPS_RECOGNIZE_ALL" in NMEAGPS_cfg.h!
#endif
//-----------------------------------------------------------------
// Derive a class to add the state machine for starting up:
// 1) The status must change to something other than NONE.
// 2) The GPS leap seconds must be received
// 3) The UTC time must be received
// 4) All configured messages are "requested"
// (i.e., "enabled" in the ublox device)
// Then, all configured messages are parsed and explicitly merged.
class MyGPS : public ubloxGPS
{
public:
enum
{
GETTING_STATUS,
GETTING_LEAP_SECONDS,
GETTING_UTC,
RUNNING
}
state NEOGPS_BF(8);
MyGPS( Stream *device ) : ubloxGPS( device )
{
state = GETTING_STATUS;
}
//--------------------------
void get_status()
{
static bool acquiring = false;
if (fix().status == gps_fix::STATUS_NONE) {
static uint32_t dotPrint;
bool requestNavStatus = false;
if (!acquiring) {
acquiring = true;
dotPrint = millis();
DEBUG_PORT.print( F("Acquiring...") );
requestNavStatus = true;
} else if (millis() - dotPrint > 1000UL) {
dotPrint = millis();
DEBUG_PORT << '.';
static uint8_t requestPeriod;
if ((++requestPeriod & 0x07) == 0)
requestNavStatus = true;
}
if (requestNavStatus)
// Turn on the UBX status message
enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_STATUS );
} else {
if (acquiring)
DEBUG_PORT << '\n';
DEBUG_PORT << F("Acquired status: ") << (uint8_t) fix().status << '\n';
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & \
defined(UBLOX_PARSE_TIMEGPS)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS ))
DEBUG_PORT.println( F("enable TIMEGPS failed!") );
state = GETTING_LEAP_SECONDS;
#else
start_running();
state = RUNNING;
#endif
}
} // get_status
//--------------------------
void get_leap_seconds()
{
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & \
defined(UBLOX_PARSE_TIMEGPS)
if (GPSTime::leap_seconds != 0) {
DEBUG_PORT << F("Acquired leap seconds: ") << GPSTime::leap_seconds << '\n';
if (!disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS ))
DEBUG_PORT.println( F("disable TIMEGPS failed!") );
#if defined(UBLOX_PARSE_TIMEUTC)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC ))
DEBUG_PORT.println( F("enable TIMEUTC failed!") );
state = GETTING_UTC;
#else
start_running();
#endif
}
#endif
} // get_leap_seconds
//--------------------------
void get_utc()
{
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & \
defined(UBLOX_PARSE_TIMEUTC)
lock();
bool safe = is_safe();
NeoGPS::clock_t sow = GPSTime::start_of_week();
NeoGPS::time_t utc = fix().dateTime;
unlock();
if (safe && (sow != 0)) {
DEBUG_PORT << F("Acquired UTC: ") << utc << '\n';
DEBUG_PORT << F("Acquired Start-of-Week: ") << sow << '\n';
start_running();
}
#endif
} // get_utc
//--------------------------
void start_running()
{
bool enabled_msg_with_time = false;
#if defined(UBLOX_PARSE_POSLLH)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_POSLLH ))
DEBUG_PORT.println( F("enable POSLLH failed!") );
enabled_msg_with_time = true;
#endif
#if defined(UBLOX_PARSE_PVT)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_PVT ))
DEBUG_PORT.println( F("enable PVT failed!") );
enabled_msg_with_time = true;
#endif
#if defined(UBLOX_PARSE_VELNED)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_VELNED ))
DEBUG_PORT.println( F("enable VELNED failed!") );
enabled_msg_with_time = true;
#endif
#if defined(UBLOX_PARSE_DOP)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_DOP ))
DEBUG_PORT.println( F("enable DOP failed!") );
else
DEBUG_PORT.println( F("enabled DOP.") );
enabled_msg_with_time = true;
#endif
#if defined(UBLOX_PARSE_SVINFO)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_SVINFO ))
DEBUG_PORT.println( F("enable SVINFO failed!") );
enabled_msg_with_time = true;
#endif
#if defined(UBLOX_PARSE_TIMEUTC)
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE)
if (enabled_msg_with_time &&
!disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC ))
DEBUG_PORT.println( F("disable TIMEUTC failed!") );
#elif defined(GPS_FIX_TIME) | defined(GPS_FIX_DATE)
// If both aren't defined, we can't convert TOW to UTC,
// so ask for the separate UTC message.
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC ))
DEBUG_PORT.println( F("enable TIMEUTC failed!") );
#endif
#endif
state = RUNNING;
trace_header( DEBUG_PORT );
} // start_running
//--------------------------
bool running()
{
switch (state) {
case GETTING_STATUS : get_status (); break;
case GETTING_LEAP_SECONDS: get_leap_seconds(); break;
case GETTING_UTC : get_utc (); break;
}
return (state == RUNNING);
} // running
} NEOGPS_PACKED;
// Construct the GPS object and hook it to the appropriate serial device
static MyGPS gps( &gpsPort );
#ifdef NMEAGPS_INTERRUPT_PROCESSING
static void GPSisr( uint8_t c )
{
gps.handle( c );
}
#endif
//--------------------------
static void configNMEA( uint8_t rate )
{
for (uint8_t i=NMEAGPS::NMEA_FIRST_MSG; i<=NMEAGPS::NMEA_LAST_MSG; i++) {
ublox::configNMEA( gps, (NMEAGPS::nmea_msg_t) i, rate );
}
}
//--------------------------
static void disableUBX()
{
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_VELNED );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_POSLLH );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_DOP );
}
//--------------------------
void setup()
{
// Start the normal trace output
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("ublox binary protocol example started.\n") );
DEBUG_PORT << F("fix object size = ") << sizeof(gps.fix()) << '\n';
DEBUG_PORT << F("ubloxGPS object size = ") << sizeof(ubloxGPS) << '\n';
DEBUG_PORT << F("MyGPS object size = ") << sizeof(gps) << '\n';
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.flush();
// Start the UART for the GPS device
#ifdef NMEAGPS_INTERRUPT_PROCESSING
gpsPort.attachInterrupt( GPSisr );
#endif
gpsPort.begin(9600);
// Turn off the preconfigured NMEA standard messages
configNMEA( 0 );
// Turn off things that may be left on by a previous build
disableUBX();
#if 0
// Test a Neo M8 message -- should be rejected by Neo-6 and Neo7
ublox::cfg_nmea_v1_t test;
test.always_output_pos = false; // invalid or failed
test.output_invalid_pos = false;
test.output_invalid_time= false;
test.output_invalid_date= false;
test.use_GPS_only = false;
test.output_heading = false; // even if frozen
test.__not_used__ = false;
test.nmea_version = ublox::cfg_nmea_v1_t::NMEA_V_4_0;
test.num_sats_per_talker_id = ublox::cfg_nmea_v1_t::SV_PER_TALKERID_UNLIMITED;
test.compatibility_mode = false;
test.considering_mode = true;
test.max_line_length_82 = false;
test.__not_used_1__ = 0;
test.filter_gps = false;
test.filter_sbas = false;
test.__not_used_2__= 0;
test.filter_qzss = false;
test.filter_glonass= false;
test.filter_beidou = false;
test.__not_used_3__= 0;
test.proprietary_sat_numbering = false;
test.main_talker_id = ublox::cfg_nmea_v1_t::MAIN_TALKER_ID_GP;
test.gsv_uses_main_talker_id = true;
test.beidou_talker_id[0] = 'G';
test.beidou_talker_id[1] = 'P';
DEBUG_PORT << F("CFG_NMEA result = ") << gps.send( test );
#endif
while (!gps.running())
if (gps.available( gpsPort ))
gps.read();
}
//--------------------------
void loop()
{
if (gps.available( gpsPort ))
trace_all( DEBUG_PORT, gps, gps.read() );
// If the user types something, reset the message configuration
// back to a normal set of NMEA messages. This makes it
// convenient to switch to another example program that
// expects a typical set of messages. This also saves
// putting those config messages in every other example.
if (DEBUG_PORT.available()) {
do { DEBUG_PORT.read(); } while (DEBUG_PORT.available());
DEBUG_PORT.println( F("Stopping...") );
configNMEA( 1 );
disableUBX();
gpsPort.flush();
gpsPort.end();
DEBUG_PORT.println( F("STOPPED.") );
for (;;);
}
}

View File

@ -0,0 +1,507 @@
#include <NeoGPS_cfg.h>
#include <ublox/ubxGPS.h>
#include <NeoTeeStream.h>
//======================================================================
// Program: ubloxRate.ino
//
// Description: This program sends ublox commands to enable and disable
// NMEA sentences, set the update rate, and set the
// baud rate to 9600 or 115200.
//
// Enter the following commands through the Serial Monitor window:
//
const char help[] PROGMEM = R"(
'?' - dislay this help message
'1' - send NMEA PUBX text command to enable all sentences
'0' - send NMEA PUBX text command to disable all sentences except GLL
'd' - send UBX binary command to disable all sentences except GLL
'r1' - send UBX binary command to set update rate to 1Hz
'r5' - send UBX binary command to set update rate to 5Hz
'r0' - send UBX binary command to set update rate to 10Hz
'r6' - send UBX binary command to set update rate to 16Hz
're' - send UBX binary command to reset the GPS device (cold start)
'5' - send NMEA PUBX text command to set baud rate to 115200
'7' - send NMEA PUBX text command to set baud rate to 57600
'3' - send NMEA PUBX text command to set baud rate to 38400
'9' - send NMEA PUBX text command to set baud rate to 9600
'e' - toggle echo of all characters received from GPS device.
't' - toggle tracing of parsed GPS fields.
)";
// CAUTION: If your Serial Monitor window baud rate is less
// than the GPS baud rate, turning echo ON will cause the
// sketch to lose some or all GPS data and/or fixes.
//
// NOTE: All NMEA PUBX text commands are also echoed to the debug port.
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
// 3) You know the default baud rate of your GPS device.
// If 9600 does not work, use NMEAdiagnostic.ino to
// scan for the correct baud rate.
// 4) LAST_SENTENCE_IN_INTERVAL is defined to be
// the following in NMEAGPS_cfg.h:
//
// #include <stdint.h>
// extern uint8_t LastSentenceInInterval; // a variable!
// #define LAST_SENTENCE_IN_INTERVAL \
// ((NMEAGPS::nmea_msg_t) LastSentenceInInterval)
//
// This is a replacement for the typical
//
// #define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
//
// This allows the sketch to choose the last sentence *at run time*, not
// compile time. This is necessary because this sketch can send
// configuration commands that change which sentences are enabled.
// The storage for the "externed" variable is below.
// 5) ublox.ino builds correctly (see its prequisites).
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2018, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *not* enable NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_DERIVED_TYPES
#error You must enable NMEAGPS_DERIVED_TYPES in NMEAGPS_cfg.h!
#endif
#if !defined(NMEAGPS_PARSE_GLL)
#error You must enable NMEAGPS_PARSE_GLL in NMEAGPS_cfg.h!
#endif
#if !defined(UBLOX_PARSE_STATUS) & !defined(UBLOX_PARSE_TIMEGPS) & \
!defined(UBLOX_PARSE_TIMEUTC) & !defined(UBLOX_PARSE_POSLLH) & \
!defined(UBLOX_PARSE_DOP) & !defined(UBLOX_PARSE_PVT) & \
!defined(UBLOX_PARSE_VELNED) & !defined(UBLOX_PARSE_SVINFO) & \
!defined(UBLOX_PARSE_HNR_PVT)
#error No UBX binary messages enabled: no fix data available.
#endif
//-----------------------------------------------------------------
static ubloxGPS gps( &gpsPort );
static gps_fix fix_data;
uint8_t LastSentenceInInterval = 0xFF; // storage for the run-time selection
static char lastChar; // last command char
static bool echoing = false;
static bool tracing = true;
// Use NeoTee to echo the NMEA text commands to the Serial Monitor window
Stream *both[2] = { &DEBUG_PORT, &gpsPort };
NeoTeeStream tee( both, sizeof(both)/sizeof(both[0]) );
//-------------------------------------------
// U-blox UBX binary commands
const unsigned char ubxRate1Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,0xE8,0x03,0x01,0x00,0x01,0x00 };
const unsigned char ubxRate5Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,200,0x00,0x01,0x00,0x01,0x00 };
const unsigned char ubxRate10Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,100,0x00,0x01,0x00,0x01,0x00 };
const unsigned char ubxRate16Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,50,0x00,0x01,0x00,0x01,0x00 };
// Disable specific NMEA sentences
const unsigned char ubxDisableGGA[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableGLL[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x01,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableGSA[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x02,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableGSV[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x03,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableRMC[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x04,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableVTG[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x05,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableZDA[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x08,0x00,0x00,0x00,0x00,0x00,0x01 };
static const uint8_t ubxReset[] __PROGMEM =
{ ublox::UBX_CFG, ublox::UBX_CFG_RST,
UBX_MSG_LEN(ublox::cfg_reset_t), 0, // word length MSB is 0
0,0, // clear bbr section
ublox::cfg_reset_t::CONTROLLED_SW_RESET_GPS_ONLY, // reset mode
0x00 // reserved
};
void resetGPS()
{
enum sendUBXmethod_t
{
FROM_RAM_STRUCT,
FROM_PROGMEM_BYTES,
FROM_PROGMEM_STRUCT
};
const sendUBXmethod_t WAY = FROM_PROGMEM_STRUCT; // pick one
switch (WAY) {
case FROM_RAM_STRUCT:
{
ublox::cfg_reset_t cfg_cold; // RAM
cfg_cold.clear_bbr_section =
// Hotstart
{ false };
// Warmstart
// { true, false };
// Coldstart
// { true, true, true, true, true, true, true, true, true,
// true, // reserved1 is 2 bits
// true, true, true,
// true, // reserved2 is 1 bit
// true };
cfg_cold.reset_mode = ublox::cfg_reset_t::CONTROLLED_SW_RESET_GPS_ONLY;
cfg_cold.reserved = 0;
gps.send_request( cfg_cold );
}
break;
case FROM_PROGMEM_BYTES:
sendUBX( ubxReset, sizeof(ubxReset) ); // just send the PROGMEM bytes
break;
case FROM_PROGMEM_STRUCT:
{
const ublox::cfg_reset_t *cfg_cold_ptr = (const ublox::cfg_reset_t *) ubxReset;
gps.send_request_P( *cfg_cold_ptr );
}
break;
}
} // resetGPS
//--------------------------
void sendUBX( const unsigned char *progmemBytes, size_t len )
{
gpsPort.write( 0xB5 ); // SYNC1
gpsPort.write( 0x62 ); // SYNC2
uint8_t a = 0, b = 0;
while (len-- > 0) {
uint8_t c = pgm_read_byte( progmemBytes++ );
a += c;
b += a;
gpsPort.write( c );
}
gpsPort.write( a ); // CHECKSUM A
gpsPort.write( b ); // CHECKSUM B
} // sendUBX
//-------------------------------------------
// U-blox NMEA text commands
const char disableRMC[] PROGMEM = "PUBX,40,RMC,0,0,0,0,0,0";
const char disableGLL[] PROGMEM = "PUBX,40,GLL,0,0,0,0,0,0";
const char disableGSV[] PROGMEM = "PUBX,40,GSV,0,0,0,0,0,0";
const char disableGSA[] PROGMEM = "PUBX,40,GSA,0,0,0,0,0,0";
const char disableGGA[] PROGMEM = "PUBX,40,GGA,0,0,0,0,0,0";
const char disableVTG[] PROGMEM = "PUBX,40,VTG,0,0,0,0,0,0";
const char disableZDA[] PROGMEM = "PUBX,40,ZDA,0,0,0,0,0,0";
const char enableRMC[] PROGMEM = "PUBX,40,RMC,0,1,0,0,0,0";
const char enableGLL[] PROGMEM = "PUBX,40,GLL,0,1,0,0,0,0";
const char enableGSV[] PROGMEM = "PUBX,40,GSV,0,1,0,0,0,0";
const char enableGSA[] PROGMEM = "PUBX,40,GSA,0,1,0,0,0,0";
const char enableGGA[] PROGMEM = "PUBX,40,GGA,0,1,0,0,0,0";
const char enableVTG[] PROGMEM = "PUBX,40,VTG,0,1,0,0,0,0";
const char enableZDA[] PROGMEM = "PUBX,40,ZDA,0,1,0,0,0,0";
const char baud9600 [] PROGMEM = "PUBX,41,1,3,3,9600,0";
const char baud38400 [] PROGMEM = "PUBX,41,1,3,3,38400,0";
const char baud57600 [] PROGMEM = "PUBX,41,1,3,3,57600,0";
const char baud115200[] PROGMEM = "PUBX,41,1,3,3,115200,0";
//--------------------------
const uint32_t COMMAND_DELAY = 250;
void changeBaud( const char *textCommand, unsigned long baud )
{
gps.send_P( &tee, (const __FlashStringHelper *) disableRMC );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGLL );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSV );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGGA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableVTG );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableZDA );
delay( 500 );
gps.send_P( &tee, (const __FlashStringHelper *) textCommand );
gpsPort.flush();
gpsPort.end();
DEBUG_PORT.print( F("All sentences disabled for baud rate ") );
DEBUG_PORT.print( baud );
DEBUG_PORT.println( F(" change. Enter '1' to reenable sentences.") );
delay( 500 );
gpsPort.begin( baud );
} // changeBaud
//------------------------------------
static void doSomeWork()
{
// Print all the things!
if (tracing)
trace_all( DEBUG_PORT, gps, fix_data );
} // doSomeWork
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.println( F("ubloxRate.INO: started\r\n"
"Looking for GPS device on " GPS_PORT_NAME "\r\n"
"Enter '?' for help.") );
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )
DEBUG_PORT.println( F("\nWARNING: No NMEA sentences are enabled: no fix data will be displayed.") );
#else
if (gps.merging == NMEAGPS::NO_MERGING) {
DEBUG_PORT.print ( F("\nWARNING: displaying data from ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.print ( F(" sentences ONLY, and only if ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(" is enabled.\n"
" Other sentences may be parsed, but their data will not be displayed.") );
}
#endif
// Make sure the run-time selectable LAST_SENTENCE is
// configured correctly in NMEAGPS_cfg.h
for (uint8_t i=0; i < 1; i++) {
if (LastSentenceInInterval != LAST_SENTENCE_IN_INTERVAL) {
DEBUG_PORT.println(
F("LAST_SENTENCE_IN_INTERVAL is not properly defined in NMEAGPS_cfg.h!\n"
" See Prerequisite 4 above") );
for (;;); // hang here!
}
LastSentenceInInterval ++;
}
LastSentenceInInterval = NMEAGPS::NMEA_GLL;
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
void loop()
{
// Check for commands
if (DEBUG_PORT.available()) {
char c = DEBUG_PORT.read();
switch (c) {
case '?':
DEBUG_PORT.print( (const __FlashStringHelper *) help );
echoing = false;
tracing = false;
DEBUG_PORT.print( F("Enter command> ") );
break;
case '0':
if (lastChar == 'r') {
sendUBX( ubxRate10Hz, sizeof(ubxRate10Hz) );
} else {
gps.send_P( &tee, (const __FlashStringHelper *) disableRMC );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGLL );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSV );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGGA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableVTG );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableZDA );
LastSentenceInInterval = NMEAGPS::NMEA_GLL;
}
break;
case '1':
if (lastChar == 'r') {
sendUBX( ubxRate1Hz, sizeof(ubxRate1Hz) );
} else {
gps.send_P( &tee, (const __FlashStringHelper *) enableRMC );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGLL );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGSV );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGSA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGGA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableVTG );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableZDA );
LastSentenceInInterval = NMEAGPS::NMEA_ZDA;
}
break;
case '3':
changeBaud( baud38400, 38400UL );
break;
case '5':
if (lastChar == 'r') {
sendUBX( ubxRate5Hz, sizeof(ubxRate5Hz) );
} else {
changeBaud( baud115200, 115200UL );
}
break;
case '6':
if (lastChar == 'r') {
sendUBX( ubxRate16Hz, sizeof(ubxRate16Hz) );
}
break;
case '7':
changeBaud( baud57600, 57600UL );
break;
case '9':
changeBaud( baud9600, 9600UL );
break;
case 'd':
sendUBX( ubxDisableRMC, sizeof(ubxDisableRMC) );
delay( COMMAND_DELAY );
//sendUBX( ubxDisableGLL, sizeof(ubxDisableGLL) );
sendUBX( ubxDisableGSV, sizeof(ubxDisableGSV) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableGSA, sizeof(ubxDisableGSA) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableGGA, sizeof(ubxDisableGGA) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableVTG, sizeof(ubxDisableVTG) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableZDA, sizeof(ubxDisableZDA) );
LastSentenceInInterval = NMEAGPS::NMEA_GLL;
break;
case 'e':
if (lastChar == 'r') {
resetGPS();
} else {
echoing = !echoing;
}
break;
case 't':
tracing = !tracing;
break;
default: break;
}
lastChar = c;
}
// Check for GPS data
static bool displayingHex = false;
if (echoing) {
// Use advanced character-oriented methods to echo received characters to
// the Serial Monitor window.
if (gpsPort.available()) {
char c = gpsPort.read();
if (((' ' <= c) && (c <= '~')) || (c == '\r') || (c == '\n')) {
DEBUG_PORT.write( c );
displayingHex = false;
} else {
if (!displayingHex) {
displayingHex = true;
DEBUG_PORT.print( F("0x") );
}
if (c < 0x10)
DEBUG_PORT.write( '0' );
DEBUG_PORT.print( (uint8_t) c, HEX );
DEBUG_PORT.write( ' ' );
}
gps.handle( c );
if (gps.available()) {
fix_data = gps.read();
if (displayingHex)
displayingHex = false;
DEBUG_PORT.println();
doSomeWork();
}
}
} else {
// Use the normal fix-oriented methods to display fixes
if (gps.available( gpsPort )) {
fix_data = gps.read();
doSomeWork();
displayingHex = false;
}
}
}

View File

@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
//#define GPS_FIX_ALTITUDE
//#define GPS_FIX_SPEED
//#define GPS_FIX_HEADING
//#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
#define GPS_FIX_HDOP
#define GPS_FIX_VDOP
#define GPS_FIX_PDOP
#define GPS_FIX_LAT_ERR
#define GPS_FIX_LON_ERR
#define GPS_FIX_ALT_ERR
#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
#define NMEAGPS_PARSE_GGA
#define NMEAGPS_PARSE_GLL
#define NMEAGPS_PARSE_GSA
#define NMEAGPS_PARSE_GSV
#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
#define NMEAGPS_PARSE_VTG
#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
#define NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
//#define GPS_FIX_DATE
//#define GPS_FIX_TIME
//#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
//#define GPS_FIX_ALTITUDE
//#define GPS_FIX_SPEED
//#define GPS_FIX_HEADING
//#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
//#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
//#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
//#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL (nmea_msg_t) NMEA_LAST_MSG+5 /* ubloxNMEA::PUBX_04 */
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
//#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
//#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
//#define GPS_FIX_DATE
//#define GPS_FIX_TIME
//#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
//#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
//#define GPS_FIX_HEADING
//#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
//#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
//#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@ -0,0 +1,5 @@
Acknowledgements
==========
Mikal Hart's [TinyGPS](https://github.com/mikalhart/TinyGPS) for the generic `decode` approach.
tht's [initial implementation](http://forum.arduino.cc/index.php?topic=150299.msg1863220#msg1863220) of a Cosa `IOStream::Device`.

View File

@ -0,0 +1,64 @@
Character-oriented methods
===========================
Sometimes, individual characters or sentences must be processed or filtered, long before a fix structure is completed (i.e., `available()`). In this advanced technique, your sketch should read each character and pass it to the character-oriented method `gps.decode()`:
```
void loop()
{
while (serial.available()) {
char c = serial.read();
if (gps.decode( c ) == DECODE_COMPLETED) {
... do something with gps.fix()...
}
```
As `gps` decodes those bytes, it will gradually fill out the pieces of its internal fix structure, `gps.fix()` (members described [here](Data%20Model.md). When you want to use some of the fix data, you can access it like this:
```
Serial.print( gps.fix().latitude() );
Serial.print( ',' );
Serial.println( gps.fix().longitude() );
```
However, you must wait for the sentence to be completely decoded. You can't access `gps.fix()` unless you know that it is COMPLETED. You must copy it to another fix variable if you need to access it at any time.
**IMPORTANT:** `gps.fix()` **IS ONLY VALID WHEN:**
- `gps.decode()` just returned `DECODE_COMPLETED`, or
- `gps.is_safe()`
This is because `gps.fix().speed` may be half-formed. You must either do all your accessing immediately after `gps.decode()` returns `DECODE_COMPLETED`:
```
void loop()
{
// At this point of the code, speed could be half-decoded.
if (gps.fix().speed <= 5) // NOT A GOOD IDEA!
Serial.println( F("Too slow!") );
while (serial.available()) {
char c = serial.read();
if (gps.decode( serial.read() ) == NMEAGPS::DECODE_COMPLETED) {
// Access any piece of gps.fix() in here...
if (gps.fix().speed <= 5) // OK!
Serial.println( F("Too slow!") );
if (gps.fix().lat ...
}
}
```
Or you must call `gps.is_safe()` before using `gps.fix()`. It is safest to copy `gps.fix()` into your own variable for use at any time:
```
gps_fix my_fix;
void loop()
{
while (serial.available()) {
char c = serial.read();
if (gps.decode( serial.read() ) == NMEAGPS::DECODE_COMPLETED) {
my_fix = gps.fix(); // save for later...
}
}
if (my_fix.speed <= 5) // OK
DigitalWrite( UNDERSPEED_INDICATOR, HIGH );
```
Although the character-oriented program structure gives you a finer granularity of control, you must be more careful when accessing `gps.fix()`.

View File

@ -0,0 +1,267 @@
# Choosing your configuration
There are only a few configurations provided by examples. If your application needs something slightly different, here is a general configuration process.
## What number do you want?
First, decide which data members of `gps_fix` and `NMEAGPS` you need (see [Data Model](Data Model.md) for member descriptions). Those members **must** be enabled in `GPSfix_cfg.h`.
Next, figure out what messages can fill out those members, because those messages **must** be enabled in `NMEAGPS_cfg.h`. Here is a table of the NMEA messages parsed by NeoGPS, and which data members they affect:
<table>
<tr>
<td><p align="right"><b>Message</b></p><p><b>Data Member</b></p></td>
<td><p>GGA</p><p><br></p></td>
<td><p>GLL</p><p><br></p></td>
<td><p>GSA</p><p><br></p></td>
<td><p>GST</p><p><br></p></td>
<td><p>GSV</p><p><br></p></td>
<td><p>RMC</p><p><br></p></td>
<td><p>VTG</p><p><br></p></td>
<td><p>ZDA</p><p><br></p></td>
<td><p align="center">PUBX<br>00<sup>1<sup></p><p><br></p></td>
<td><p align="center">PUBX<br>04<sup>1<sup></p><p><br></p></td>
</tr>
<tr><td><p><b>class gps_fix</b></p></td></tr>
<tr>
<td><p align="right">status</p></td>
<td>*</td>
<td>*</td>
<td>*</td>
<td> </td>
<td> </td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">date<sup>2</sup></p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
</tr>
<tr>
<td><p align="right">time<sup>2</sup></p></td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td>*</td>
<td>*</td>
</tr>
<tr>
<td><p align="right">lat/lon</p></td>
<td>*</td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">altitude</p></td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">speed</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">heading</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">satellites <sup>3</sup></p></td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">HDOP</p></td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">VDOP</p></td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">PDOP</p></td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td><p align="right">TDOP</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">Velocity North,<br>
East,<br>
Down</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>* <sup>4</sup><br>* <sup>4</sup><br>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">lat, lon, alt error</p></td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">speed error,<br>
heading error,<br>
time error <sup>5</sup></p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr><td><p><b>class NMEAGPS</b></p></td></tr>
<tr>
<td><p align="right">satellite IDs <sup>3</sup></p></td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td><p align="right">satellite azimuth,<br>&nbsp;&nbsp;elevation and<br>&nbsp;&nbsp;signal strength <sup>3</sup></p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</table>
This table illustrates the poor design of the NMEA message set: it requires multiple messages to deliver a complete fix (i.e., all members of `gps_fix`). This also explains why many manufacturers provide proprietary messages that *are* more complete. Above, you can see that the `$PUBX,00`<sup>1</sup> message contains all members except `date`.
While the manufacturer's specification will document all sentences supported for your device, you can also find general descriptions of many NMEA sentences [here](http://www.gpsinformation.org/dale/nmea.htm), [here](http://aprs.gids.nl/nmea/) or [here](http://www.catb.org/gpsd/NMEA.txt).
<hr>
<sub><sup>1</sup> The NMEA proprietary messages "PUBX" are only availble in the `ubloxNMEA` class. See [ublox-specific instructions](ublox.md) for adding this class to your configuration.</sub>
<sub><sup>2</sup> Date and time are both stored in one member of `gps_fix`, called `dateTime`. The `fix.dateTime` member is a C++ class that has both date-oriented members (Date, Month and Year) and time-oriented members (Hours, Minutes and Seconds). See [NeoTime.h](/src/NeoTime.h) for the complete description and capabilities of the `dateTime` member, such as date/time arithmetic and conversion to/from seconds since the epoch. Hundredths of a second are stored in a separate member of `gps_fix`, called `dateTime_cs`, and can also be accessed with the functions `dateTime_ms()` and `dateTime_us()`.</sub>
<sub><sup>3</sup> The `fix.satellites` member identifies how many of these satellites were used to calculate a fix. The number of satellites' information available in the `gps.satellites[]` array is stored in `gps.sat_count`. This the total number of satellites that may or may not be used for calculating a fix.
<sub><sup>4</sup> Only Velocity Down is provided by the PUBX,00 message. A utility routine is provided to *calculate* Velocity North and East from Speed and Heading, if enabled. See `calculateNorthAndEastVelocityFromSpeedAndHeading` in gps_fix.h.
<sub><sup>5</sup> These fields are only available from UBX binary messages.

View File

@ -0,0 +1,13 @@
Coherency
==========
Coherency guarantees that all members of a fix are from the same GPS time. For example, lat/long members may have been set by the newest sentence, but the altitude may be from the previous time interval. Most applications do not care that the fix members are not coherent. However, if you are controlling a drone or other autonomous vehicle, you may need coherency.
NeoGPS achieves coherency by detecting the "quiet" time between batches of sentences. When new data starts coming in, the fix will get emptied or initialized, and all new sentences will be accumulated in the internal fix.
If coherency is desired, **you must choose the correct LAST_SENTENCE_IN_INTERVAL.** If you're not sure which sentence is sent last (and therefore, when the quiet time begins), use NMEAorder.ino to analyze your GPS device.
You must also use **EXPLICIT_MERGING**. Implicit merging cannot be used with coherency is because a sentence has to be parsed to know its timestamp. If it were implicitly merged, the old data would not have been invalidated. Invalidating data from a previous update period must be performed _before_ the sentence parsing begins. That can only be accomplished with a second 'safe' copy of the fix data and explicit merging (i.e., FIX_MAX >= 1). With implicit merging, new data has already been mixed with old data by the time DECODE_COMPLETED occurs and timestamps can be checked.
When you have correctly chosen the LAST_SENTENCE_IN_INTERVAL *and* EXPLICIT_MERGING, the fix-oriented methods `available` and `read()` will return a coherent fix.
NOTE: If you use the [character-oriented methods](CharOriented.md) `decode`, `is_safe` and `fix()` to handle individual sentences, you must check `intervalComplete()` to know when the GPS update interval is completed, and the GPS quiet time has started.

View File

@ -0,0 +1,260 @@
Configuration
=============
All configuration items are conditional compilations: a `#define` controls an `#if`/`#endif` section.
Comment out any items to be disabled or excluded from your build.
Where possible, checks are performed to verify that you have chosen a "valid"
configuration: you may see `#error` messages in the build log. See also [Choosing Your Configuration](Choosing.md) and [Troubleshooting](Troubleshooting.md).
# class gps_fix
The following configuration items are near the top of GPSfix_cfg.h:
```
// Enable/Disable individual parts of a fix, as parsed from fields of a $GPxxx sentence
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
#define GPS_FIX_HDOP
#define GPS_FIX_VDOP
#define GPS_FIX_PDOP
#define GPS_FIX_LAT_ERR
#define GPS_FIX_LON_ERR
#define GPS_FIX_ALT_ERR
#define GPS_FIX_GEOID_HEIGHT
```
See the [Data Model](Data%20Model.md) page and `GPSfix.h` for the corresponding members that are enabled or disabled by these defines.
========================
# class NMEAGPS
The following configuration items are near the top of NMEAGPS_cfg.h.
#### Enable/Disable parsing the fields of a $GPxxx sentence
```
#define NMEAGPS_PARSE_GGA
#define NMEAGPS_PARSE_GLL
#define NMEAGPS_PARSE_GSA
#define NMEAGPS_PARSE_GSV
#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
#define NMEAGPS_PARSE_VTG
#define NMEAGPS_PARSE_ZDA
```
#### Select the last sentence in an update interval
This is used to determine when the GPS quiet time begins and when a batch of coherent sentences have been merged. It is crucial to know when fixes can be marked as available, and when you can perform some time-consuming operations.
```
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
```
You can use `NMEAorder.ino` to determine the last sentence sent by your device.
#### Enable/Disable No, Implicit, Explicit Merging
If you want NO merging, comment out both defines. Otherwise, uncomment the IMPLICIT or EXPLICIT define.
```
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
```
See [Merging](Merging.md) for more information.
#### Define the fix buffer size.
The NMEAGPS object will hold on to this many fixes before an overrun occurs. The buffered fixes can be obtained by calling `gps.read()`. You can specify zero, but you have to be sure to call `gps.read()` before the next sentence starts.
```
#define NMEAGPS_FIX_MAX 1
```
#### Enable/Disable interrupt-style processing
Define how fixes are dropped when the fix buffer is full.
```
#define NMEAGPS_KEEP_NEWEST_FIXES true
```
true = the oldest fix will be dropped, and the new fix will be saved.
false = the new fix will be dropped, and all old fixes will be saved.
#### Enable/Disable interrupt-style processing
If you are using one of the NeoXXSerial libraries to `attachInterrupt`, this must be uncommented to guarantee safe access to the buffered fixes with `gps.read()`. For normal polling-style processing, it must be commented out.
```
//#define NMEAGPS_INTERRUPT_PROCESSING
```
#### Enable/Disable the talker ID and manufacturer ID processing.
There are two kinds of NMEA sentences:
1. Standard NMEA sentences begin with "$ttccc", where
"tt" is the talker ID (e.g., GP), and
"ccc" is the variable-length sentence type (e.g., RMC).
For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
transmitted by talker "GP". This is the most common talker ID. Some devices
may report "$GNGLL,..." when a mix of GPS and non-GPS satellites have been
used to determine the GLL data (e.g., GLONASS+GPS).
2. Proprietary NMEA sentences (i.e., those unique to a particular manufacturer)
begin with "$Pmmmccc", where
"P" is the NMEA-defined prefix indicator for proprietary messages,
"mmm" is the 3-character manufacturer ID, and
"ccc" is the variable-length sentence type (it can be empty).
No validation of manufacturer ID and talker ID is performed in this
base class. For example, although "GP" is a common talker ID, it is not
guaranteed to be transmitted by your particular device, and it IS NOT REQUIRED.
If you need validation of these IDs, or you need to use the extra information
provided by some devices, you have two independent options:
1. Enable SAVING the ID: When `decode` returns DECODE_COMPLETED, the `talker_id`
and/or `mfr_id` members will contain ID bytes. The entire sentence will be
parsed, perhaps modifying members of `fix`. You should enable one or both IDs
if you want the information in all sentences *and* you also want to know the ID
bytes. This adds 2 bytes of RAM for the talker ID, and 3 bytes of RAM for the
manufacturer ID.
2. Enable PARSING the ID: The virtual `parse_talker_id` and `parse_mfr_id` will
receive each ID character as it is received. If it is not a valid ID, return
`false` to abort processing the rest of the sentence. No CPU time will be wasted
on the invalid sentence, and no `fix` members will be modified. You should
enable this if you want to ignore some IDs. You must override `parse_talker_id`
and/or `parse_mfr_id` in a derived class.
```
#define NMEAGPS_SAVE_TALKER_ID
#define NMEAGPS_PARSE_TALKER_ID
#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
```
#### Enable/Disable parsing of NMEA proprietary messages
If you are deriving a class from NMEAGPS to parse proprietary messages, you must uncomment this define:
```
//#define NMEAGPS_PARSE_PROPRIETARY
```
#### Enable/Disable tracking the current satellite array
You can also enable tracking the detailed information for each satellite, and how many satellites you want to track.
Although many GPS receivers claim to have 66 channels of tracking, 16 is usually the maximum number of satellites
tracked at any one time.
```
#define NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_PARSE_SATELLITE_INFO
#define NMEAGPS_MAX_SATELLITES (20)
```
#### Enable/disable gathering interface statistics:
Uncommenting this define will allow counting the number of sentences and characters received and the number of checksum (CS) errors. If the CS errors are increasing, you could be losing characters or the connection could be noisy.
```
#define NMEAGPS_STATS
```
#### Enable/disable UTC sub-second resolution
Fixes will be received at approximately 1-second intervals, but there can
be 30ms *or more* of variance in those intervals. If you need to know the exact UTC time at *any* time,
with sub-second accuracy, you must calculate the
offset between the Arduino micros() clock and the UTC time in a received fix.
There are two ways to do this:
**1. When the GPS quiet time ends and the new update interval begins.**
The timestamp will be set when the first character (the '$') of
the new batch of sentences arrives from the GPS device. This is fairly
accurate, but it will be delayed from the PPS edge by the GPS device's
fix calculation time (usually ~100us). There is very little variance
in this calculation time (usually < 30us), so all timestamps are
delayed by a nearly-constant amount.
```
#define NMEAGPS_TIMESTAMP_FROM_INTERVAL
```
NOTE: At update rates higher than 1Hz, the updates may arrive with
some increasing variance.
**2. From the PPS pin of the GPS module.**
It is up to the application developer to decide how to capture that event. For example, you could:
a) simply poll for it in loop and call UTCsecondStart(micros());
b) use attachInterrupt to call a Pin Change Interrupt ISR to save
the micros() at the time of the interrupt (see NMEAGPS.h), or
c) connect the PPS to an Input Capture pin. Set the
associated TIMER frequency, calculate the elapsed time
since the PPS edge, and add that to the current micros().
```
#define NMEAGPS_TIMESTAMP_FROM_PPS
```
#### Enable/Disable derived types
Although normally disabled, this must be enabled if you derive any classes from NMEAGPS.
```
//#define NMEAGPS_DERIVED_TYPES
```
The ublox-specific files require this define (see [ublox](ublox.md) section).
#### Enable/Disable guaranteed comma field separator
Some devices may omit trailing commas at the end of some sentences. This may prevent the last field from being parsed correctly, because the parser for some types keep the value in an intermediate state until the complete field is received (e.g., parseDDDMM, parseFloat and parseZDA).
Enabling this will inject a simulated comma when the end of a sentence is received and the last field parser indicated that it still needs one.
```
//#define NMEAGPS_COMMA_NEEDED
```
#### Enable/Disable recognizing all sentence types
Some applications may want to recognize a sentence type without actually parsing any of the fields. Uncommenting this define will allow the `gps.nmeaMessage` member to be set when *any* standard message is seen, even though that message is not enabled by a `#defineNMEAGPS_PARSE_xxx`. No valid flags will be true for disabled sentences.
```
#define NMEAGPS_RECOGNIZE_ALL
```
#### Enable/Disable parsing scratchpad
Sometimes, a little extra space is needed to parse an intermediate form. This define enables extra space. Parsers that require the scratchpad can either `#ifdef` an `#error` when the scratchpad is disabled, or let the compiler generate an error when attempting to use the `scratchpad` union (see NMEAGPS.h).
```
//#define NMEAGPS_PARSING_SCRATCHPAD
```
========================
# ublox-specific configuration items
See the [ublox](ublox.md) section.
========================
# Floating-point output.
Streamers.cpp is used by the example programs for printing members of `fix()`. It is not required for parsing the GPS data stream, and this file may be deleted. It is an example of checking validity flags and formatting the various members of `fix()` for textual streams (e.g., Serial prints or SD writes).
Streamers.cpp has one configuration item:
```
#define USE_FLOAT
```
This is local to this file, and is only used by the example programs. This file is _not_ required unless you need to stream one of these types: bool, char, uint8_t, int16_t, uint16_t, int32_t, uint32_t, F() strings, `gps_fix` or `NMEAGPS`.
Most example programs have a choice for displaying fix information once per day. (Untested!)
```
#define PULSE_PER_DAY
```
========================
# Platforms
The following configuration items are near the top of NeoGPS_cfg.h.
#### Enable/Disable packed bitfields
```
#define NEOGPS_PACKED_DATA
```
8-bit Arduino platforms can address memory by bytes or words. This allows passing data by reference or
address, as long as it is one or more bytes in length. The `gps_fix` class has some members which are
only one bit; these members cannot be passed by reference or address, only by value. NeoGPS uses an
appropriate passing technique, depending on the size of these members.
32-bit Arduino platforms require *all* memory accesses to be 32-bit aligned, which precludes passing
bitfield, byte, or word members by reference or address. Rather than penalize the 8-bit platforms with
unpacked classes and structs, the `NEOGPS_PACKED_DATA` can be disabled on 32-bit platforms. This
increases the RAM requirements, but these platforms typically have more available RAM.
========================
# Typical configurations
A few common configurations are defined as follows
**Minimal**: no fix members, no messages (pulse-per-second only)
**DTL**: date, time, lat/lon, GPRMC messsage only.
**Nominal**: date, time, lat/lon, altitude, speed, heading, number of
satellites, HDOP, GPRMC and GPGGA messages.
**Full**: Nominal plus talker ID, VDOP, PDOP, lat/lon/alt errors, satellite array with satellite info, all messages, and parser statistics.
**PUBX**: Nominal fix items, standard sentences _disabled_, proprietary sentences PUBX 00 and 04 enabled, and required PARSE_MFR_ID and DERIVED_TYPES
These configurations are available in the [configs](/extras/configs) subdirectory.
========================
# Configurations of other libraries
**TinyGPS** uses the **Nominal** configuration + a second `fix`.
**TinyGPSPlus** uses the **Nominal** configuration + statistics + a second fix + timestamps for each `fix` member.
**Adafruit_GPS** uses the **Nominal** configuration + geoid height and IGNORES CHECKSUM!

View File

@ -0,0 +1,265 @@
Data Model
==========
Rather than holding onto individual fields, the concept of a **fix** is used to group data members of the GPS acquisition into a C structure (a `struct` type called `gps_fix`). This also facilitates merging pieces received at different times (i.e., in separate sentences) into a single easy-to-use structure.
The main `NMEAGPS gps;` object you declare in your sketch parses received characters, gradually assembling a `fix`. Most programs will call `gps.read()` to obtain the completed fix structure (see [Usage](#Usage) below).
Given a variable declaration of type `gps_fix`:
```
gps_fix fix;
```
...this `fix` variable (or any other variable of type `gps_fix`) contains the following members:
* `fix.status`, a status code
* `enum` values STATUS_NONE, STATUS_EST, STATUS_TIME_ONLY, STATUS_STD or STATUS_DGPS
* a [location](Location.md) structure (i.e., latitude and longitude), accessed with
* `fix.latitudeL()` and `fix.longitudeL()` for the higher-precision integer degrees, scaled by 10,000,000 (10 significant digits)
* `fix.latitude()` and `fix.longitude()` for the lower-precision floating-point degrees (~7 significant digits)
* NOTE: these lat/lon values are
* positive for North or East degrees and negative for South or West degrees.
* stored in a 'fix.location' structure, like a 2D coordinate. The `location_t` class provides additional methods for distance, bearing and offset calculations, as described [here](Location.md).
* `fix.latitudeDMS` and `fix.latitudeDMS` are structures (see DMS.h) that each contain
* `fix.longitudeDMS.degrees` in integer degrees
* `fix.latitudeDMS.degrees`, in integer minutes
* `fix.longitudeDMS.seconds_whole`, in integer seconds
* `fix.latitudeDMS.seconds_frac`, in integer thousandths of a second
* `fix.latitudeDMS.secondsF()`, in floating-point seconds
* hemisphere indicator, accessed with
* `fix.longitudeDMS.hemisphere` (enum values NORTH_H, SOUTH_H, EAST_H or WEST_H)
* `fix.longitudeDMS.EW()` (char values `E` or `W`)
* `fix.latitudeDMS.NS()` (char values `N` or `S`)
* NOTE: An integer degree value (scaled by 10<sup>7</sup> can be used to set the DMS structure by using `fix.latitudeDMS.From( otherLatitude );`
* an altitude (above ellipsoid, aka Mean Sea Level), accessed with
* `fix.altitude_cm()`, in integer centimeters
* `fix.altitude()`, in floating-point meters
* `fix.alt.whole`, in integer meters
* `fix.alt.frac`, in integer centimeters, to be added to the whole part
* a speed, accessed with
* `fix.speed_kph()`, in floating-point kilometers per hour
* `fix.speed_mph()`, in floating-point miles per hour
* `fix.speed()`, in floating-point knots (nautical miles per hour)
* `fix.speed_mkn()`, in integer knots, scaled by 1000
* `fix.spd.whole`, in integer knots
* `fix.spd.frac`, in integer thousandths of a knot, to be added to the whole part
* a heading, accessed with
* `fix.heading_cd()`, in integer hundredths of a degree
* `fix.heading()`, in floating-point degrees
* velocity components in the North, East and Down directions, accessed with
* `fix.velocity_north`, in integer cm/s
* `fix.velocity_east`, in integer cm/s
* `fix.velocity_down`, in integer cm/s
* `fix.hdop`, `fix.vdop` and `fix.pdop`, in integer thousandths of the DOP.
* [Dilution of Precision](https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation)) is a unitless measure of the current satellite constellation geometry WRT how 'good' it is for determining a position. This is _independent_ of signal strength and many other factors that may be internal to the receiver. &nbsp;&nbsp;**It cannot be used to determine position accuracy in meters.** Instead, use the LAT/LON/ALT error in cm members, which are populated by GST sentences.
* latitude, longitude and altitude error, accessed with
* `fix.lat_err_cm`, `fix.lon_err_cm` and `fix.alt_err_cm`, in integer centimeters
* `fix.lat_err()`, `fix.lon_err()` and `fix.alt_err()`, in floating-point meters
* speed, heading and time **errors**, accessed with
* `fix.spd_err_mmps`, in integer mm/s
* `fix.hdg_errE5`, in integer degrees * 100000
* `fix.time_err_ns`, in integer nanoseconds<br>**or with**
* `fix.spd_err()` in floating-point m/s
* `fix.hdg_err()` in floating-point degrees
* `fix.time_err()` in floating-point seconds
* geoid height above ellipsoid (see [here](https://en.wikipedia.org/wiki/Geoid) for description), accessed with
* `fix.geoidHeight_cm`, in integer centimeters
* `fix.geoidHeight()`, in floating-point meters
* `fix.geoidHt.whole`, in integer meters
* `fix.geoidHt.frac`, in integer centimeters to be added to the whole part
* `fix.satellites`, a satellite count
* a date/time structure (see [Time.h](/src/Time.h)), accessed with
* `fix.dateTime.year`,
* `fix.dateTime.month`,
* `fix.dateTime.date`, the day-of-month,
* `fix.dateTime.hours`,
* `fix.dateTime.minutes`,
* `fix.dateTime.seconds`, and
* `fix.dateTime.day`, the day-of-the-week. This member is expensive to calculate, so it is *uninitialized* until you call the `set_day()` method. If you need the day-of-the-week, be sure to call `set_day` whenever the `year`, `month` or `date` members are changed. In general, call `fix.dateTime.set_day()` whenever `fix` is assigned (e.g., `fix = gps.read()`).<br><br>
`Time` operations allow converting to and from total seconds offset from a *de facto* starting time (e.g., an epoch date/time "origin"). There are constants in Time.h for NTP, POSIX and Y2K epochs. Simply change the `static` members `s_epoch_year` and `s_epoch_weekday` in Time.h, and all date/time operations will be based on that epoch. This does not affect GPS times, but it will allow you to easily convert a GPS time to/from an NTP or POSIX time value (seconds).<br><br>
The [NMEAtimezone.ino](/examples/NMEAtimezone/NMEAtimezone.ino) example program shows how to convert the GPS time (UTC) into a local time. Basically, a `Time` structure is converted to seconds (from the epoch start), then the time zone offset *in seconds* is added, and then the offset seconds are converted back to a time structure, with corrected day, month, year, hours and minutes members.
* `fix.dateTime_cs`, in integer hundredths of a second
* `fix.dateTime_ms()`, in milliseconds
* `fix.dateTime_us()`, in microseconds
* a collection of boolean `valid` flags for each of the above members, accessed with
* `fix.valid.status`
* `fix.valid.date` for year, month, day-of-month
* `fix.valid.time` for hours, minutes, seconds and centiseconds
* `fix.valid.location` for latitude and longitude
* `fix.valid.altitude`
* `fix.valid.speed`
* `fix.valid.heading`
* `fix.valid.hdop`, `fix.valid.vdop` and `fix.valid.pdop`
* `fix.valid.lat_err`, `fix.valid.lon_err` and `fix.valid.alt_err`
* `fix.valid.geoidHeight`
## Validity
Because the GPS device may *not* have a fix, each member of a `gps_fix` can be marked as valid or invalid. That is, the GPS device may not know the lat/long yet. To check whether the fix member has been received, test the corresponding `valid` flag (described above). For example, to check if lat/long data has been received:
```
if (my_fix.valid.location) {
Serial.print( my_fix.latitude() );
Serial.print( ',' );
Serial.println( my_fix.longitude() );
}
```
You should also know that, even though you have enabled a particular member (see [GPSfix_cfg.h](/src/GPSfix_cfg.h)), it **may not have a value** until the related NMEA sentence sets it. And if you have not enabled that sentence for parsing in `NMEAGPS_cfg.h`, it will **never** be valid.
## Other GPS-related information
There is additional information that is not related to a fix. Instead, it contains information about parsing or a [**G**lobal **N**avigation **S**atellite **S**ystem](https://en.wikipedia.org/wiki/Satellite_navigation). GNSS's currently include GPS (US), GLONASS (Russia), Beidou (China) and Galileo (EU). The main `NMEAGPS gps` object you declare in your sketch contains:
* `gps.UTCsecondStart()`, the Arduino `micros()` value when the current UTC second started
* `gps.UTCms()`, the number of milliseconds since the last received UTC time, calculated from `micros()` and `gps.UTCsecondStart`.
* `gps.UTCus()`, the number of microseconds since the last received UTC time, calculated from `micros()` and `gps.UTCsecondStart`.
* `gps.nmeaMessage`, the latest received message type. This is an ephemeral value, because multiple sentences are merged into one `fix` structure. If you only check this after a complete fix is received, you will only see the LAST_SENTENCE_IN_INTERVAL.
* enum values NMEA_GLL, NMEA_GSA, NMEA_GST, NMEA_GSV, NMEA_RMC, NMEA_VTG or NMEA_ZDA
* `gps.satellies[]`, an array of satellite-specific information, where each element contains
* `gps.satellies[i].id`, satellite ID
* `gps.satellies[i].elevation`, satellite elevation in 0-90 integer degrees
* `gps.satellies[i].azimuth`, satellite azimuth in 0-359 integer degrees
* `gps.satellies[i].snr`, satellite signal-to-noise ratio in 0-99 integer dBHz
* `gps.satellies[i].tracked`, satellite being tracked flag, a boolean
* `gps.sat_count`, the number of elements in the `gps.satellites[]` array
* `gps.talker_id[]`, talker ID, a two-character array (not NUL-terminated)
* `gps.mfr_id[]`, manufacturer ID, a three-character array (not NUL-terminated)
* an internal fix structure, `gps.fix()`. Most sketches **should not** use `gps.fix()` directly!
## Usage
First, declare an instance of `NMEAGPS`:
```
NMEAGPS gps;
```
Next, tell the `gps` object to handle any available characters on the serial port:
```
void loop()
{
while (gps.available( gps_port )) {
```
The `gps` object will check if there are any characters available, and if so, read them from the port and parse them into its internal fix. Many characters will have to be read before the current fix is complete, so `gps.available` will return `false` until the fix is complete; the body of `while` loop will be skipped many times, and the rest of `loop()` will be executed.
When a fix is finally completed, `gps.available` will return `true`. Now your sketch can "read" the completed fix structure from the `gps` object:
```
void loop()
{
while (gps.available( gps_port )) {
gps_fix fix = gps.read();
```
The local `fix` variable now contains all the GPS fields that were parsed from the `gps_port`. You can access them as described above:
```
void loop()
{
while (gps.available( gps_port )) {
gps_fix fix = gps.read();
if (fix.valid.time) {
...
```
Note that the `fix` variable is local to that `while` loop; it cannot be accessed elsewhere in your sketch. If you need to access the fix information elsewhere, you must declare a global fix variable:
```
gps_fix currentFix;
void loop()
{
while (gps.available( gps_port )) {
currentFix = gps.read();
if (currentFix.valid.time) {
...
```
Any part of your sketch can use the information in `currentFix`.
Please note that the fix structure is much smaller than the raw character data (sentences). A fix is nominally 1/4 the size of one sentence (~30 bytes vs ~120 bytes). If two sentences are sent during each update interval, a fix could be 1/8 the size required for buffering two sentences.
In this fix-oriented program structure, the methods `gps.available` and `gps.read` are manipulating entire `gps_fix` structures. Multiple characters and sentences are used internally to fill out a single fix: members are "merged" from sentences into one fix structure (described [here](Merging.md)).
That program structure is very similar to the typical serial port reading loop:
```
void loop()
{
while (serial.available()) {
char c = serial.read();
... do something with the character ...;
}
```
However, the fix-oriented methods operate on complete *fixes*, not individual characters, fields or sentences.
Note: If you find that you need to filter or merge data with a finer level of control, you may need to use a different [Merging option](Merging.md), [Coherency](Coherency.md), or the more-advanced [Character-Oriented methods](/doc/CharOriented.md).
## Examples
Some examples of accessing fix values:
```
gps_fix fix_copy = gps.read();
int32_t lat_10e7 = fix_copy.lat; // scaled integer value of latitude
float lat = fix_copy.latitude(); // float value of latitude
Serial.print( fix_copy.latDMS.degrees );
Serial.print( ' ' );
Serial.print( fix_copy.latDMS.minutes );
Serial.print( F("' " );
Serial.print( fix_copy.latDMS.seconds );
if (fix_copy.dateTime.month == 4) // test for the cruelest month
cry();
// Count how satellites are being received for each GNSS
for (uint8_t i=0; i < gps.sat_count; i++) {
if (gps.satellites[i].tracked) {
if (gps.satellites[i] . id <= 32)
GPS_satellites++;
if (gps.satellites[i] . id <= 64)
SBAS_satellites++;
if (gps.satellites[i] . id <= 96)
GLONASS_satellites++;
}
}
```
And some examples of accessing valid flags in a `fix` structure:
```
if (fix_copy.valid.location)
// we have a lat/long!
if (fix_copy.valid.time)
// the copy has hours, minutes and seconds
```
Here's an example for accessing the altitude
```
if (fix_copy.valid.altitude) {
z2 = fix_copy.altitude_cm();
vz = (z2 - z1) / dt;
z1 = z2;
// Note: if you only care about meters, you could also do this:
// z = fix_copy.alt.whole;
}
```
You can also check a collection of flags before performing a calculation involving
multiple members:
```
if (fix_copy.valid.altitude && fix_copy.valid.date && fix_copy.valid.time) {
dt = (clock_t) fix_copy.dateTime - (clock_t) fix_copy.dateTime;
dz = fix_copy.alt.whole - last_alt; // meters
vz = dz / dt; // meters per second vertical velocity
}
```
Bonus: The compiler will optimize this into a single bit mask operation.
The example printing utility file, [Streamers.cpp](/src/Streamers.cpp#L100) shows how to access each fix member and print its value.
## Options
Except for `status`, each of these `gps_fix` members is conditionally compiled; any, all, or *no* members can be selected for parsing, storing and merging. This allows you to configuring NeoGPS to use the minimum amount of RAM for the particular members of interest. See [Configurations](Configurations.md) for how to edit [GPSfix_cfg.h](/src/GPSfix_cfg.h) and [NMEAGPS_cfg.h](/src/NMEAGPS_cfg.h#L67), respectively.
## Precision
Integers are used for all members, retaining full precision of the original data.
```
gps_fix fix = gps.read();
if (fix.valid.location) {
// 32-bit ints have 10 significant digits, so you can detect very
// small changes in position:
d_lat = fix_copy.lat - last_lat;
}
```
Optional floating-point accessors are provided for many members.
```
if (fix_copy.valid.location) {
float lat = fix_copy.latitude();
// floats only have about 6 significant digits, so this
// computation is useless for detecting small movements:
foobar = (lat - target_lat);
```

View File

@ -0,0 +1,168 @@
Examples
======
In an attempt to be reusable in a variety of different programming styles, this library supports:
* sync or async operation (main `loop()` vs interrupt processing)
* fused or not fused (multiple reports into one fix)
* optional buffering of fixes
* optional floating point
* configurable message sets, including hooks for implementing proprietary NMEA messages
* configurable message fields
* multiple protocols from same device
These example programs demonstrate how to use the classes in the different programming styles:
* [NMEA](/examples/NMEA/NMEA.ino) - sync, single fix, standard NMEA only (RMC sentence only)
* [NMEA_isr](/examples/NMEA_isr/NMEA_isr.ino) - **async**, single fix, standard NMEA only (RMC sentence only)
* [NMEAblink](/examples/NMEAblink/NMEAblink.ino) - sync, single fix, standard NMEA only, minimal example, only blinks LED
* [NMEAloc](/examples/NMEAloc/NMEAloc.ino) - sync, single fix, minimal example using only standard NMEA RMC sentence
* [NMEAlocDMS](/examples/NMEAlocDMS/NMEAlocDMS.ino) - same as NMEAloc.ino, but displays location in Degrees, Minutes and Seconds
* [NMEAaverage](/examples/NMEAaverage/NMEAaverage.ino) - sync, single fix, averages a high-accuracy location over time
* [NMEAtimezone](/examples/NMEAtimezone/NMEAtimezone.ino) - same as NMEAloc.ino, but displays local time instead of UTC (GMT)
* [NMEASDlog](/examples/NMEASDlog/NMEASDlog.ino) - **async**, buffered fixes, standard NMEA only (RMC sentence only), logging to SD card
* [PUBX](/examples/PUBX/PUBX.ino) - sync, coherent fix, standard NMEA + ublox proprietary NMEA
* [ublox](/examples/ublox/ublox.ino) - sync or **async**, coherent fix, ublox binary protocol UBX
Preprocessor symbol `USE_FLOAT` can be used in [Streamers.cpp](/src/Streamers.cpp) to select integer or floating-point output.
See also important information in `NMEAorder.ino` below, and the [Installing](Installing.md), [Configurations](Configurations.md) and [Troubleshooting](Troubleshooting.md) sections.
##Diagnostics
Several programs are provided to help diagnose GPS device problems:
### Connection and Baud Rate
* [NMEAdiagnostic](/examples/NMEAdiagnostic/NMEAdiagnostic.ino)
This program listens for sentences and, if none are detected, tries a different baud rate. When sentences are detected, the correct baud rate is displayed. The received data may help you determine the problem (e.g., dropped characters or binary protocol).
See the [Troubleshooting](Troubleshooting.md) section for more details.
### Sentence order
* [NMEAorder](/examples/NMEAorder/NMEAorder.ino)
This program determines the order of NMEA sentences sent during each 1-second interval:
```
NMEAorder.INO: started
fix object size = 44
NMEAGPS object size = 72
Looking for GPS device on Serial1
.....................
Sentence order in each 1-second interval:
RMC
VTG
GGA
GSA
GSV
GSV
GSV
GSV
GLL
```
The last sentence is of particular interest, as it is used to determine when the quiet time begins. All example programs **depend** on knowing the last sentence (see [Quiet Time Interval](Troubleshooting#quiet-time-interval)).
### Self-test Program
* [NMEAtest](/examples/NMEAtest/NMEAtest.ino)
For this program, **No GPS device is required**. Test bytes are streamed from PROGMEM character arrays. Various strings are passed to `decode` and the expected pass or fail results are displayed. If **NeoGPS** is correctly configured, you should see this on your SerialMonitor:
```
NMEA test: started
fix object size = 44
NMEAGPS object size = 72
Test string length = 75
PASSED 11 tests.
------ Samples ------
Results format:
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,VDOP,PDOP,Lat err,Lon err,Alt err,Sats,[sat],
Input: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,8,1.01,499.6,M,48.0,M,,0*5B
Results: 3,2000-01-01 09:27:25.00,472852332,85652650,,,49960,1010,,,,,,8,[],
Input: $GPRMC,092725.00,A,2520.69213,S,13101.94948,E,0.004,77.52,091202,,,A*43
Results: 3,2002-12-09 09:27:25.00,-253448688,1310324913,7752,4,,,,,,,,,[],
Input: $GPRMC,162254.00,A,3647.6643,N,8957.5193,W,0.820,188.36,110706,,,A*49
Results: 3,2006-07-11 16:22:54.00,367944050,-899586550,18836,820,,,,,,,,,[],
Input: $GPRMC,235959.99,A,2149.65726,N,16014.69256,W,8.690,359.99,051015,9.47,E,A*26
Results: 3,2015-10-05 23:59:59.99,218276210,-1602448760,35999,8690,,,,,,,,,[],
Input: $GNGLL,0105.60764,S,03701.70233,E,225627.00,A,A*6B
Results: 3,2000-01-01 22:56:27.00,-10934607,370283722,,,,,,,,,,,[],
Input: $GPGGA,064951.000,2307.1256,N,12016.4438,E,1,8,0.95,39.9,M,17.8,M,,*63
Results: 3,2000-01-01 06:49:51.00,231187600,1202740633,,,3990,950,,,,,,8,[],
Input: $GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,3.05,W,A*2C
Results: 3,2006-04-26 06:49:51.00,231187600,1202740633,16548,30,,,,,,,,,[],
Input: $GPVTG,165.48,T,,M,0.03,N,0.06,K,A*36
Results: 3,,,,16548,30,,,,,,,,,[],
Input: $GPGSA,A,3,29,21,26,15,18,09,06,10,,,,,2.32,0.95,2.11*00
Results: 3,,,,,,,950,,2320,,,,,[],
Input: $GPGSV,3,1,09,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*7D
Results: ,,,,,,,,,,,,,9,[29,21,26,15,],
Input: $GPGSV,3,2,09,18,26,314,40,09,57,170,44,06,20,229,37,10,26,084,37*77
Results: ,,,,,,,,,,,,,9,[29,21,26,15,18,9,6,10,],
Input: $GPGSV,3,3,09,07,,,26*73
Results: ,,,,,,,,,,,,,9,[29,21,26,15,18,9,6,10,7,],
Input: $GNGST,082356.00,1.8,,,,1.7,1.3,2.2*60
Results: ,2000-01-01 08:23:56.00,,,,,,,,,170,130,,,[29,21,26,15,18,9,6,10,7,],
Input: $GNRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A,V*33
Results: 3,2002-12-09 08:35:59.00,472852395,85652537,7752,4,,,,,,,,,[29,21,26,15,18,9,6,10,7,],
Input: $GNGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*45
Results: 3,2000-01-01 09:27:25.00,472852332,85652650,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GLZDA,225627.00,21,09,2015,00,00*70
Results: ,2015-09-21 22:56:27.00,,,,,,,,,,,,,[29,21,26,15,18,9,6,10,7,],
--- floating point conversion tests ---
Input: $GPGGA,092725.00,3242.9000,N,11705.8169,W,1,8,1.01,499.6,M,48.0,M,,0*49
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969483,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8170,W,1,8,1.01,499.6,M,48.0,M,,0*41
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969500,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8171,W,1,8,1.01,499.6,M,48.0,M,,0*40
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969517,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8172,W,1,8,1.01,499.6,M,48.0,M,,0*43
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969533,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8173,W,1,8,1.01,499.6,M,48.0,M,,0*42
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969550,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8174,W,1,8,1.01,499.6,M,48.0,M,,0*45
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969567,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8175,W,1,8,1.01,499.6,M,48.0,M,,0*44
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969583,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8176,W,1,8,1.01,499.6,M,48.0,M,,0*47
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969600,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
```
### Benchmark
* [NMEAbenchmark](/examples/NMEAbenchmark/NMEAbenchmark.ino)
For this program, **No GPS device is required**. GGA, RMC and GSV sentences can be tested. Bytes are streamed from PROGMEM character arrays and parsed to determine execution times. All times are displayed in microseconds:
```
NMEAbenchmark: started
fix object size = 22
NMEAGPS object size = 29
GGA time = 844
GGA no lat time = 497
```

View File

@ -0,0 +1,52 @@
Extending NeoGPS
=========
Using features that are unique to your device fall into three categories:
#### 1. Configuring the device with special commands
Many devices allow you to configure which standard messages are emitted, or the rate at which they are emitted. It may be as simple as sending a proprietary command to the device. Simply use the NMEAGPS `send` or `send_P` method.
For example, to set the baudrate of the ublox NEO-6M gps device, send it a
`UBX,41` message:
```
gps.send_P( F("PUBX,41,1,0007,0003,19200,0") );
```
#### 2. Parsing additional message types
Some devices provide additional messages with extra information, or more efficient groupings. This will require deriving a class from `NMEAGPS`. The derived class needs to
* declare a PROGMEM table of the new message types,
* point that table back to the NMEAGPS table
* override the `parseField` method to extract information from each new message type
Please see ubxNMEA.h and .cpp for an example of adding two ublox-proprietary messages.
#### 3. Handling new protocols
Some devices provide additional protocols. They are frequently binary, which requires
fewer bytes than NMEA 0183. Because they can both be transmitted on the same port, it is
very likely that they can be distinguished at the message framing level.
For example, NMEA messages always start with a '$' and end with CR/LF. ublox messages start
with 0xB5 and 0x62 bytes, a message class and id, and a 2-byte message length. There is no
terminating character; the message completed when `length` bytes have been received.
This will require deriving a class from `NMEAGPS`. The derived class needs
to
* define new `rxState` values for the protocol state machine. These should
be unique from the NMEA state values, but they should share the IDLE state
value.
* override the `decode` method to watch for its messages. As bytes are
received, it may transition out of the IDLE state and into its own set of
state values. If the character is not valid for this protocol, it should
delegate it to the NMEAGPS base clase, which may begin processing an NMEAGPS
message. If the rxState is not one of the derived states (i.e., it is in
one of the NMEAGPS states), the character should be delegated to
NMEAGPS::decode.
* implement something like the `parseField` method if parse-in-place
behavior is desirable. This is not necessarily `virtual`, as it will only
be called from the derived `decode`.
* You are free to add methods and data members as required for handling the
protocol. Only `decode` must be overridden.
Please see ubxGPS.h and .cpp for an example of implementing the
ublox-proprietary protocol, UBX. The derived `ubloxGPS` class provides both
parse-in-place *and* buffered messages. See the `send` and `poll` methods.

View File

@ -0,0 +1,36 @@
# Garmin Proprietary messages
To use the Garmin Proprietary NMEA message parser, you must enable the following in `NMEAGPS_cfg.h`:
```
#define NMEAGPS_PARSE_MFR_ID
#define NMEAGPS_DERIVED_TYPES
```
**NeoGPS** implements the following additional NMEA messages:
* $PGRMF - Fix data
You may want to change the configured PGRM messages in `src/Garmin/PGRM_cfg.h`. It is currently configured to work with the example application, `PGRM.ino`. `PGRM_cfg.h` has the following configuration items:
```
#define GARMINGPS_PARSE_PGRMF
```
As shown in the PGRM.ino example program, you should use an instance of `GarminNMEA` instead of the typical `NMEAGPS`:
```
#include <NeoGPS_cfg.h>
#include <Garmin/GrmNMEA.h>
#include <GPSTime.h>
...
static GarminNMEA gps; // This parses received characters
static gps_fix fix;
```
A few different header files are included, but all other operations are identical.
Notice that the $PGRMF message sets the global variable, `GPSTime::leap_seconds`. As reported in
[Issue #90](https://github.com/SlashDevin/NeoGPS/issues/90), this is required for exact UTC
calculations that span dates with different GPS leap seconds
(see [Wikipedia article](http://en.wikipedia.org/wiki/Global_Positioning_System#Leap_seconds)).

View File

@ -0,0 +1,196 @@
Installing
==========
1. [Download the library](#1-download-the-library)
2. [Choose a serial port](#2-choose-a-serial-port)
3. [Connect the GPS device](#3-connect-the-gps-device)
4. [Review `GPSport.h`](#4-review-librariesneogpssrcgpsporth)
5. [Open the example](#5--open-the-example-sketch-nmeaino)
6. [Build and upload](#6--build-and-upload-the-sketch-to-your-arduino)
<hr>
### 1. Download the library
It is easiest to use the [Ardino IDE Library Manager](https://www.arduino.cc/en/Guide/Libraries#toc3) to automatically download and install NeoGPS. Select the menu **Sketch -> Include Library -> Manage Libraries**. Then type "NeoGPS" in the Search box.
If you need to perform a manual installation,:
* Download the [master ZIP file](https://github.com/SlashDevin/NeoGPS/archive/master.zip).
* Open the zip file and open the nested `NeoGPS-master` subdirectory.
* Select and copy all files in the `NeoGPS-master` subdirectory into a new `Arduino/Libraries/NeoGPS` directory, like most libraries. The `Arduino/Libraries/NeoGPS` directory should contain:<br>
```
extras
examples
src
library.properties
LICENSE
README.md
```
<hr>
### 2. Choose a serial port
**BEST**: The fastest, most reliable way to connect a GPS device is to use a HardwareSerial port.
On any Arduino board, you can connect the GPS device to the `Serial` pins (0 & 1). You can still print debug statements, and they will show up on the Serial Monitor window. The received GPS characters will not interfere with those prints, and you will not see those characters on the Serial Monitor window.
However, when you need to upload a new sketch to the Arduino over USB, **you must disconnect the GPS TX from the Arduino RX pin 0.** Otherwise, the GPS characters will interfere with the upload data. Some people put a switch in that connection to make it easy to upload without disturbing the wires.
For Mega, Due and Teensy boards, you can connect the GPS device to the `Serial1`, `Serial2` or `Serial3` pins.
For Micro and Leo (and other 32U4-based Arduinos), you can connect the GPS device to the `Serial1` pins.
**2nd Best**: If you can't connect the GPS device to a `HardwareSerial` port, you should download and install the [AltSoftSerial](https://github.com/PaulStoffregen/AltSoftSerial) or [NeoICSerial](https://github.com/SlashDevin/NeoICSerial) library. These libraries only work on two specific pins (8 & 9 on an UNO). This library is very efficient and reliable. It uses one of the hardware TIMERs, so it may conflict with libraries that use TIMERs or PWM output (e.g., servo).
**3rd Best**: If you can't use the pins required by `AltSoftSerial`, and your GPS device runs at 9600, 19200 or 38400 baud, you should download and install the [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial) library. This library is almost as efficient. It will help you avoid common timing problems caused by `SoftwareSerial`. It does not need an extra TIMER, so it can be used with most other libraries. It does use Pin Change Interrupts, but there is an option in the header file that allows you to coordinate other PCI usage with `NeoSWSerial`.
`NeoSWSerial` can be used with `AltSoftSerial` at the same time, allowing your sketch to have two extra serial ports.
**WORST**: `SoftwareSerial` is NOT RECOMMENDED, because it disables interrupts for long periods of time. This can interfere with other parts of your sketch, or with other libraries. It cannot transmit and receive at the same time, and your sketch can only receive from one `SoftwareSerial` instance at time.
<hr>
### 3. Connect the GPS device
Most GPS devices are 3.3V devices, and most Arduinos are 5V devices. Although many GPS modules are described as "3V & 5V compatible",
<p align=center><b>YOU SHOULD NOT CONNECT A 5V ARDUINO TRANSMIT PIN TO THE 3.3V GPS RX PIN</b></p>
This can damage the device, cause overheating, system power problems or decrease the lifetime of battery-operated systems. You must level-shift this connection with inexpensive level-shifting modules (best) or a resistor divider.
Connecting the 3.3V GPS TX pin to a 5V Arduino receive pin will not damage the GPS device, but it may not be reliable. This is because the GPS TX voltage is slightly lower than what the Arduino requires. It works in many situations, but if you are not able to receive GPS characters reliably, you probably need to use a level-shifting module (best) or a diode+resistor to "pull up" the GPS TX pin voltage.
<hr>
### 4. Review `Libraries/NeoGPS/src/GPSport.h`
This file declares a the serial port to be used for the GPS device. You can either:
* Use the default `GPSport.h` and connect your GPS device according to what it chooses; or
* Replace the entire contents of `GPSport.h` and insert your own declarations (see below and comments in `GPSport.h`).
#### Default choices for GPSport.h
By default, Mega, Leonardo, Due, Zero/MKR1000 and Teensy boards will use `Serial1`.
All other Boards will use [AltSoftSerial](https://github.com/PaulStoffregen/AltSoftSerial) on two specific pins (see table at linked page).
If you want to use a different serial port library (review step 2 above), you must edit these `#include` lines in `GPSport.h`:
```
//#include <NeoHWSerial.h> // NeoSerial or NeoSerial1 for INTERRUPT_PROCESSING
#include <AltSoftSerial.h> // <-- DEFAULT. Two specific pins required (see docs)
//#include <NeoICSerial.h> // AltSoftSerial with Interrupt-style processing
//#include <NeoSWSerial.h> // Any pins, only @ 9600, 19200 or 38400 baud
//#include <SoftwareSerial.h> // NOT RECOMMENDED!
```
Uncomment **one** of those include statements, and it will use that library for the GPS serial port.
If you uncomment the `NeoSWSerial.h` include, pins 3 and 4 will be used for the GPS. If your GPS is on different pins, you must edit these `#define` lines in `GPSport.h`:
#define RX_PIN 4
#define TX_PIN 3
#### Choosing your own serial port
If you know what serial port you want to use, you can **REPLACE EVERYTHING** in `GPSport.h' with the three declarations that are used by all example programs:
1. the `gpsPort` variable **(include its library header if needed)**;
2. the double-quoted C string for the `GPS_PORT_NAME` (displayed by all example programs); and
3. the `DEBUG_PORT` to use for Serial Monitor print messages (usually `Serial`).
All the example programs can use any of the following serial port types:
* HardwareSerial (built-in `Serial`, `Serial1` et al. STRONGLY recommended)
* [AltSoftSerial](https://github.com/PaulStoffregen/AltSoftSerial) **DEFAULT** (only works on one specific Input Capture pin)
* [NeoICSerial](https://github.com/SlashDevin/NeoICSerial) (only works on one specific Input Capture pin)
* [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial) (required for NMEA_isr and NMEASDlog on built-in serial ports)
* [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial) (works on most pins)
* SoftwareSerial (built-in, NOT recommended)
Be sure to download the library you have selected (NOTE: `HardwareSerial` and `SoftwareSerial` are pre-installed by the Arduino IDE and do not need to be downloaded).
For example, to make all examples use `Serial` for the GPS port **and** for Serial Monitor messages, `GPSport.h` should contain just these 3 declarations:
```
#ifndef GPSport_h
#define GPSport_h
#define gpsPort Serial
#define GPS_PORT_NAME "Serial"
#define DEBUG_PORT Serial
#endif
```
Or, to make all examples use `AltSoftSerial` for the GPS port and `Serial` for Serial Monitor messages, `GPSport.h` should contain just these statements:
```
#ifndef GPSport_h
#define GPSport_h
#include <AltSoftSerial.h>
AltSoftSerial gpsPort;
#define GPS_PORT_NAME "AltSoftSerial"
#define DEBUG_PORT Serial
#endif
```
<hr>
### 5. Open the example sketch NMEA.ino
In the Arduino IDE, select **File -> Examples -> NeoGPS -> NMEA**.
<hr>
### 6. Build and upload the sketch to your Arduino.
**Note:** If the sketch does not compile, please see the [Troubleshooting](Troubleshooting.md#configuration-errors) section.
When the sketch begins, you should see this:
```
NMEA.INO: started
fix object size = 31
gps object size = 84
Looking for GPS device on Serial1
GPS quiet time is assumed to begin after a RMC sentence is received.
You should confirm this with NMEAorder.ino
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,Sats,Rx ok,Rx err,Rx chars,
3,2016-05-24 01:21:29.00,472852332,85652650,,138,,,1,0,66,
3,2016-05-24 01:21:30.00,472852311,85652653,,220,24040,7,9,0,557,
3,2016-05-24 01:21:31.00,472852315,85652647,,449,24080,7,17,0,1048,
etc.
```
The default NeoGPS configuration is **Nominal**, as described [here](Configurations.md#typical-configurations). If you do not see this output, please review the [Troubleshooting](Troubleshooting.md#gps-device-connection-problems) section.
This output can be copy & pasted into a into a text editor for saving as a CSV file, which can then be imported into a spreadsheet program for graphing or analysis.
<img src="images/example.png"/>
<hr>
### The NMEA.ino example works!
Once you have verified the GPS device connection and build process with this first example, you should also verify your device's behavior with `NMEAorder.ino` (see [this section](Troubleshooting.md#quiet-time-interval)). This can avoid problems later on, when you start adding/merging other functions to do your "work".
[Other examples](Examples.md) include `NMEAloc.ino`, which shows how to use just the location fields of a fix, or `NMEAtimezone.ino`, which shows how to adjust the GPS time for your local time zone.
If you are logging information to an SD card, you should next try `NMEA_isr.ino`. It is identical to `NMEA.ino`, except that it handles the GPS characters during the RX char interrupt. Interrupt handling will require one of the NeoXXSerial libraries to be installed (e.g. [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial)).
If you are working on a drone or other autonomous system, you should read about [Coherency](Coherency.md) and the interrupt-driven technique in [NMEA_isr](/examples/NMEA_isr/NMEA_isr.ino).
You can also try other configurations. Please see [Choosing Your Configuration](Choosing.md) for more information, and then simply edit `GPSfix_cfg.h` and/or `NMEAGPS_cfg.h`, or select an [example configuration](../configs) and copy these three files into your application directory: `NeoGPS_cfg.h`, `GPSfix_cfg.h`, and `NMEAGPS_cfg.h`.
You can review and edit each of the copied configuration files to add or remove messages or fields, at any time.
**Note:** Not all configurations will work with all example applications. Compiler error messages are emitted for incompatible settings, or if an example requires certain configurations.
### I have a ublox GPS device
After you have tried all the standard NMEA examples, and you need the ublox-specific capabilities of NeoGPS, please see the [ublox](ublox.md) section. Try `PUBX.ino` first, then try `ublox.ino` if you *really* need the binary protocol.

View File

@ -0,0 +1,18 @@
### License:
**NeoGPS**
Copyright (C) 2014-2017, SlashDevin
NeoGPS is free software: you can redistribute it and/or modify
it 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.
NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,62 @@
Location
========
The `Location_t` class is a 2D point, containing a latitude and longitude in integer degrees * 10<sup>7</sup> (source [here](/src/Location.h)).
This class also provides geographic distance, bearing and offset functions. Furthermore, they all take advantage of the increased precision of the integer coordinates. Other libraries use floating-point coordinates, which have only 6 or 7 significant digits. By using integer math, calculations maintain their original accuracy as long as possible. For example, small distances can be calculated to millimeter accuracy.
The example program [NMEAaverage.ino](/examples/NMEAaverage/NMEAaverage.ino) shows several techniques for performing 2D calculations.
### Distance
To calculate the distance between a pre-determined point and the current fix,
```
NeoGPS::Location_t madrid( 404381311L, -38196229L ); // see https://www.google.com/maps/@40.4381311,-3.8196229,6z
gps_fix fix;
void loop()
{
while (gps.available( gps_port )) {
fix = gps.read();
float dist = fix.location.DistanceKm( madrid );
// or dist = NeoGPS::Location_t::DistanceKm( fix.location, madrid );
Serial.print( dist );
Serial.println( F(" km") );
```
`DistanceMiles` is also available
### Bearing
To calculate the bearing from one point to another (in radians, CW from North),
```
float bearing = fix.location.BearingToDegrees( madrid );
// or bearing = NeoGPS::Location_t::BearingToDegrees( fix.location, madrid );
```
Radians is returned by `BearingTo`.
### Offsetting a Location
To move a location by a specified distance, in a specified direction,
```
float bearing = fix.location.BearingToDegrees( madrid );
// or bearing = NeoGPS::Location_t::BearingToDegrees( fix.location, madrid );
// Step 10km closer to the destination
Location_t next_stop( fix.location );
next_stop.OffsetBy( bearing, 10 / NeoGPS::Location_t::EARTH_RADIUS_KM );
```
Notice that the distance is specified in *radians*. To convert from km to radians, divide by the Earth's radius in km. To convert from miles, divide the miles by the Earth's radius in miles.
### NeoGPS namespace
Because the `Location_t` is inside the `NeoGPS` namespace, any time you want to declare your own instance, use any of the constants in that class (anything that requires the `Location_t` name), you must prefix it with `NeoGPS::` (shown above). As with any C++ namespace, you can relax that requirement by putting this statement anywhere after the NeoGPS includes:
```
using namespace NeoGPS;
```
This technique is used in the **NMEAaverage.ino** sketch.
However, if you have any other libraries that declare their own `Location_t` (not likely), you could not use the `using` statement. `Time_t` is inside the `NeoGPS` namespace for the same reason: avoiding name collisions.

View File

@ -0,0 +1,90 @@
Merging
===========
Because different NMEA sentences contain different pieces of a fix, they have to be "merged" to determine a complete picture. Some sentences contain only date and time. Others contain location and altitude, but not speed and heading (see table [here](Choosing.md)).
There are several ways to use the GPS fix data: without merging, implicit merging, and **explicit merging ([the default](#3-explicit-merging))**. NeoGPS allows you to choose how you want multiple sentences to be merged:
### 1. NO MERGING
In this mode, `gps.read()` will return a fix that is populated from just one sentence. You will probably receive multiple sentences per update interval, depending on your GPS device.
If you are interested in just a few pieces of information, and those pieces can be obtained from one or two sentences, you can wait for that specific sentence to arrive, and then use one or more members of the fix at that time. Make sure that "no merging" is selected in NMEAGPS_cfg.h by commenting out these lines:
```
//#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
```
Then write your loop to wait for the specific sentence:
```
void loop()
{
while (gps.available( gps_port )) {
const gps_fix & rmc = gps.read();
if (gps.nmeaMessage == NMEAGPS::NMEA_RMC) {
// All GPS-related work is performed inside this if statement
if (rmc.valid.speed && (rmc.speed <= 5))
Serial.println( F("Too slow!") );
if (rmc.valid.location && ... or any rmc member
}
}
// Can't access rmc out here...
```
If you are interested in pieces of information that are grouped by some detailed criteria (e.g., field values), you must select "no merging" and then manually merge the fixes of interest. The `merged` copy will be safe to access at any time:
```
gps_fix merged;
void loop()
{
while (gps.available( gps_port )) {
const gps_fix & fix = gps.read();
if ((gps.nmeaMessage == NMEAGPS::NMEA_RMC) &&
(fix.valid.heading &&
((4500 <= fix.heading_cd()) && (fix.heading_cd() < 9000)))){
merged |= fix;
if (merged.valid.satellites && (rmc.satellites > 5))
Serial.println( F("Moar sats!") );
}
}
// Can't access 'fix' out here, but 'merged' can be used...
```
### 2. IMPLICIT MERGING
If you are interested in more pieces of information, perhaps requiring more kinds of sentences to be decoded, but don't really care about what time the pieces were received, you could enable implicit merging:
```
//#define NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_IMPLICIT_MERGING
```
As sentences are received, they are accumulated internally. Previous field values are retained until they are overwritten by another sentence. When `gps.available`, the accumulated fix can be obtained with `gps.read()`.
Note: The members in an implicitly-merged fix may not be coherent (see [Coherency](Coherency.md). Also, checksum errors can cause the internal fix to be completely reset. Be sure your sketch checks the [valid flags](Data%20Model.md#validity) before using any fix data.
### 3. EXPLICIT MERGING
This is the default setting. To enable explicit merging, make sure this is in NMEAGPS_cfg.h:
```
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_FIX_MAX 1
```
NMEAGPS_FIX_MAX must be at least one to use EXPLICIT merging.
As sentences are received, they are merged internally. When the LAST_SENTENCE_IN_INTERVAL is received, the internal fix is marked as "available", and it can be accessed by calling `gps.read()`. When the next sentence arrives, the internal fix structure will be emptied.
```
gps_fix_t currentFix;
void loop()
{
while (gps.available( gps_port ))
currentFix = gps.read();
check_position( currentFix );
```
Explicit merging is also required to implement [Coherency](Coherency.md).

View File

@ -0,0 +1,48 @@
Performance
===========
#### **NeoGPS** is **40% to 70% faster**.
For comparison, the following sentences were parsed by various [Configurations](/doc/Configurations.md) of **NeoGPS**, **TinyGPS** and **TinyGPSPlus** on a 16MHz Arduino Mega2560.
```
$GPGGA,092725.00,4717.11399,N,00833.91590,E,1,8,1.01,499.6,M,48.0,M,,0*5B
$GPRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*57
$GPGSV,3,1,10,23,38,230,44,29,71,156,47,07,29,116,41,08,09,081,36*7F
$GPGSV,3,2,10,10,07,189,,05,05,220,,09,34,274,42,18,25,309,44*72
$GPGSV,3,3,10,26,82,187,47,28,43,056,46*77
```
<table>
<tr><td>Configuration</td><td>Sentence</td><td>NeoGPS</td><td>TinyGPS<br>Time (% faster)</td><td>TinyGPS++<br>Time (% faster)</td><td>Adafrut_GPS<br>Time (%faster)</td></tr>
<tr><td>Minimal</td><td>GGA<br>RMC</td><td>329us<br>335us</td><td>- (78%)<br>- (77%)</td><td>- (78%)<br>- (77%)</td></tr>
<tr><td>DTL</td><td>GGA<br>RMC</td><td>780us<br>803us</td><td>- (46%)<br>- (44%)</td><td>- (47%)<br>- (44%)</td></tr>
<tr><td>Nominal</td><td>GGA<br>RMC</td><td>864us<br>883us</td><td>1448us (40%)<br>1435us (39%)</td><td>1473us (41%)<br>1442us (39%)</td><td>1358us (36%)<br>1535us (42%)</td></tr>
<tr><td>Full</td><td>GGA<br>RMC<br>GSV</td><td>908us<br>899us<br>2194us</td><td>- (37%)<BR>- (37%)<br>- (-)</td><td>1523us (40%)<br>1560us (42%)<br>6651us (67%)</td></tr>
</table>
#### Why is **NeoGPS** faster?
Most libraries use extra buffers to accumulate parts of the sentence so they
can be parsed all at once. For example, an extra field buffer may hold on
to all the characters between commas. That buffer is then parsed into a
single data item, like `heading`. Some libraries even hold on to the
*entire* sentence before attempting to parse it. In addition to increasing
the RAM requirements, this requires **extra CPU time** to copy the bytes and
index through them... again.
**NeoGPS** parses each character immediately into the data item. When the
delimiting comma is received, the data item has been fully computed *in
place* and is marked as valid.
Most libraries parse all fields of their selected sentences. Although most
people use GPS for obtaining lat/long, some need only time, or even just one
pulse-per-second.
**NeoGPS** configures each item separately. Disabled items are
conditionally compiled, which means they will not use any RAM, program space
or CPU time. The characters from those fields are simply skipped; they are
never copied into a buffer or processed.
While it is significantly faster and smaller than all NMEA parsers, these same improvements also make
NeoGPS faster and smaller than _binary_ parsers.

View File

@ -0,0 +1,13 @@
Program Space requirements
=======
The **Minimal** configuration requires 866 bytes.
The **DTL** configuration requires 2072 bytes.
The **Nominal** configuration requires 2800 bytes. TinyGPS uses about 2400 bytes. TinyGPS++ uses about 2700 bytes.
The **Full** configuration requires 3462 bytes.
(All configuration numbers include 166 bytes PROGMEM.)

View File

@ -0,0 +1,41 @@
RAM requirements
=======
#### **NeoGPS** requires **72% to 96% _less_ RAM, saving 140 to 1100 bytes.**
Because you can select what data members are stored, the RAM savings depends on the [configuration](Configurations.md):
<table>
<tr><td>Configuration</td><td>NeoGPS<br>Size</td><td>TinyGPS<br>Size (% smaller)</td><td>TinyGPS++<br>Size (% smaller)</td><td>Adafruit_GPS<br>Size (% smaller)</td></tr>
<tr><td>Minimal</td><td>10</td><td>- (95%)</td><td>- (96%)</td></tr>
<tr><td>DTL</td><td>25</td><td>- (86%)</td><td>- (90%)</td></tr>
<tr><td>Nominal</td><td>41</td><td>180 (72%)</td><td>240 (83%)</td><td>326 (87%)</td></tr>
<tr><td>Full</td><td>242</td><td>- (-)</td><td>~1400 (83%)</td></tr>
</table>
#### Why does **NeoGPS** use less RAM?
As data is received from the device, various portions of a `fix` are
modified. In fact, _**no buffering RAM is required**_. Each character
affects the internal state machine and may also contribute to a data member
(e.g., latitude).
If your application only requires an accurate one pulse-per-second, you
can configure it to parse *no* sentence types and retain *no* data members.
This is the **Minimal** configuration. Although the
`fix().status` can be checked, no valid flags are available. Even
though no sentences are parsed and no data members are stored, the
application will still receive an empty `fix` once per second:
```
while (gps.available( gpsPort )) {
gps_fix nothingButStatus = gps.read();
sentenceReceived();
}
```
The `ubloxNMEA` derived class doesn't use any extra bytes of RAM.
The `ubloxGPS` derived class adds 20 bytes to handle the more-complicated protocol,
plus 5 static bytes for converting GPS time and Time Of Week to UTC.

View File

@ -0,0 +1,40 @@
Tradeoffs
=========
There's a price for everything, hehe...
#### Configurability means that the code is littered with #ifdef sections.
I've tried to increase white space and organization to make it more readable, but let's be honest...
conditional compilation is ugly.
#### Accumulating parts means knowing which parts are valid.
Before accessing a part, you must check its `valid` flag. Fortunately, this adds only one bit per member. See [Streamers.cpp](/src/Streamers.cpp#L100) for an example of accessing every data member. That file also shows how to accommodate different builds: all references to 'gps_fix' members are wrapped with conditional compilation `#ifdef`/`#endif` statements. If you do not plan to support multiple configurations, you do not need to use `#ifdef`/`#endif` statements.
#### Parsing without buffers, or *in place*, means that you must be more careful about when you access data items.
In general, using the fix-oriented methods `available` and `read` are atomically safe. You can access any parts of your `fix` structure, at any time.
If you are using the advanced [character-oriented methods](/extras/doc/CharOriented.md):
* You must wait to access the internal `gps.fix()` until after the entire sentence has been parsed.
* Only 3 example programs use these methods: NMEAblink, NMEAorder and NMEAdiagnostic. These examples simply `decode` until a sentence is COMPLETED. See `GPSloop()` in [NMEAdiagnostic.ino](/examples/NMEAdiagnostoc/NMEAdiagnostic.ino).
* Member function `gps.is_safe()` can also be used to determine when it is safe to access the internal `gps.fix()`.
* Received data errors can cause invalid field values to be set in the internal fix *before* the CRC is fully computed. The CRC will
catch most of those, and the internal fix members will then be marked as invalid.
#### Accumulating parts into *one* fix means less RAM but more complicated code
By enabling one of the [merging](/extras/doc/Merging.md) methods, fixes will accumulate data from all received sentences. The code required to implement those different techniques is more complicated than simply setting a structure member.
You are not restricted from having other instances of fix; you can copy or merge a some or all of a new fix into another copy if you want.
#### Full C++ OO implementation is more advanced than most Arduino libraries.
You've been warned! ;)
#### "fast, good, cheap... pick two."
Although most of the RAM reduction is due to eliminating buffers, some of it is from trading RAM
for additional code (see **Nominal** Program Space above). And, as I mentioned, the readabilty (i.e., goodness) suffers from its configurability.

View File

@ -0,0 +1,275 @@
Troubleshooting
===============
Most problems are caused by these kinds of errors:
1. [Device connection](#gps-device-connection-problems) (use `NMEAdiagnostic.ino`)
2. [Poor reception](#poor-reception) (use `NMEA.ino`)
3. [Configuration](#configuration-errors) (use `NMEAtest.ino`)
4. [Quiet Time Interval](#quiet-time-interval) (use `NMEAorder.ino`)
5. [Trying to do too many things](#trying-to-do-too-many-things) at the same time
6. [When all else fails...](#when-all-else-fails)
__________________
## GPS device connection problems
The example program `NMEAdiagnostic.ino` can several kinds of problems:
1) The GPS device may not be correctly wired,
2) The GPS device is correctly wired but running at the wrong baud rate,
3) The GPS device is running at the correct baud rate, but it never sends the LAST_SENTENCE_IN_INTERVAL, as defined in NMEAGPS_cfg.h
To help diagnose the problem, use [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino). Create an NMEAdiagnostic subdirectory, copy the same NeoGPS files into that subdirectory, along with NMEAdiagnostic.ino. Build and upload the sketch.
##### 1) Not correctly wired
If the GPS device is not correctly connected, [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) will display:
```
NMEAdiagnostic.INO: started
Looking for GPS device on Serial1
____________________________
Checking 9600 baud...
Check GPS device and/or connections. No data received.
```
The example program [NMEA.ino](/examples/NMEA/NMEA.ino) will display the following, stopping after printing the header:
```
NMEA.INO: started
fix object size = 31
gps object size = 84
Looking for GPS device on Serial1
GPS quiet time is assumed to begin after a GST sentence is received.
You should confirm this with NMEAorder.ino
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,Sats,Rx ok,Rx err,Rx chars,
```
You may have the GPS device connected to the wrong pins (GPS RX should be connected to Arduino TX, and GPS TX should be connected to Arduino RX), or the .INO may be using the wrong serial object: review `GPSport.h` for the expected connections, or delete the include and declare your own serial port variable.
##### 2) Wrong baud rate
If the wrong baud rate is chosen, [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) will try each baud rate to determine the correct baud rate for your GPS device. For each baud rate, it will display:
```
Checking 4800 baud...
No valid sentences, but characters are being received.
Check baud rate or device protocol configuration.
Received data:
884A0C4C8C480C8808884A0C8C480C4A4CC80A0C027759495995A5084C0A4C88 .J.L.H....J..H.JL....wYIY...L.L.
```
If the example program [NMEA.ino](/examples/NMEA/NMEA.ino) is using the wrong baud rate, it will print lines of empty data:
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,Rx ok,Rx err,Rx chars,
,,,,,,,,0,3,181,
,,,,,,,,0,1,422,
```
Notice how the received character count (Rx chars) is increasing, but nothing was decoded successfully (Rx ok always 0), and a few sentences had errors. You should review your GPS device's documentation to find the correct baud rate, or use [NMEAdiagnostic](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) to auto-detect the correct baud rate.
##### 3) Wrong LAST_SENTENCE_IN_INTERVAL in NMEAGPS_cfg.h
It is very important that the correct LAST_SENTENCE_IN_INTERVAL is chosen. It is used to determine what sentences are in a particular interval (usually 1 second) and when the GPS quiet time begins (see below).
Choosing the wrong LAST_SENTENCE_IN_INTERVAL may cause GPS characters to be dropped (because the quiet time is assumed to begin at the wrong time), or prevent any update intervals from being completed (because the sketch waits for the wrong sentence to arrive).
If the wrong LAST_SENTENCE_IN_INTERVAL is chosen, [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) will display:
```
NMEAdiagnostic.INO: started
Looking for GPS device on Serial1
____________________________
Checking 9600 baud...
Received GSV
Received GSV
Received GLL
Received RMC
Received VTG
Received GGA
Received GSA
Received GSV
Received GSV
Received GSV
Received GLL
Received RMC
Received VTG
Received GGA
Received GSA
Received GSV
Received GSV
Received GSV
Received GLL
Received RMC
Received VTG
**** NMEA sentence(s) detected! ****
Received data:
47504753562C332C312C31312C30322C37302C3330322C32372C30352C33352C GPGSV,3,1,11,02,70,302,27,05,35,
3139372C32342C30362C34392C3035332C33312C30392C31372C3037332C3230 197,24,06,49,053,31,09,17,073,20
2A37360D0A2447504753562C332C322C31312C31322C35352C3236362C32352C *76..$GPGSV,3,2,11,12,55,266,25,
Device baud rate is 9600
GPS data fields received:
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,Sats,Rx ok,Rx err,Rx chars,
3,2016-05-24 01:56:45.00,456013948,-720168555,,816,25450,7,21,0,1317,
Warning: LAST_SENTENCE_IN_INTERVAL defined to be RMC, but was never received.
Please use NMEAorder.ino to determine which sentences your GPS device sends, and then
use the last one for the definition in NMEAGPS_cfg.h.
** NMEAdiagnostic completed **
1 warnings
```
As instructed, you should run the NMEAorder.ino sketch to determine which sentence is last. You may be able to see a slight pause in the "Received XXX" messages above, which would also identify the last sentence. Edit NMEAGPS_cfg.ino and change this line:
```
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
```
__________________
## Poor reception
If the GPS device is correctly connected, and the sketch is receiving complete NMEA sentences, without data errors, [NMEA.ino](/examples/NMEA/NMEA.ino) displays "mostly" empty fields:
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,Rx ok,Rx err,Rx chars,
,0,,,,,,,3,0,259,
,0,,,,,,,6,0,521,
```
Notice how "Rx ok" and "Rx chars" increase each second, and "Rx err" is usually zero. This means that the sketch is receiving complete NMEA sentences, without data errors.
Because the fields are all empty, the GPS device is not receiving signals from any satellites. Check the antenna connection and orientation, and make sure you have an unobstructed view of the sky: go outside for the best reception, although getting close to a window may help.
Although different GPS devices behave differently when they do not have a fix, you may be able to determine how many satellites are being received from what is being reported.
As soon as the GPS device receives a signal from just one satellite, the status may change to "1" and the date and time will be reported:
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,Rx ok,Rx err,Rx chars,
2015-09-14 15:07:30,1,2015-09-14 19:07:30.00,,,,,,3,0,259,
2015-09-14 15:07:31,1,2015-09-14 19:07:30.00,,,,,,3,0,521,
```
If it continues to report only date and time, you do not have an unobstructed view of the sky: only one satellite signal is being received.
When signals from three satellites are being received, the GPS device will start reporting latitude and longitude.
When signals from four or more satellites are being received, the GPS device will start reporting altitude.
You can also enable other `gps_fix` fields or NMEA sentences. In **GPSfix_cfg.h**, uncomment this line:
```
#define GPS_FIX_SATELLITES
```
In **NMEAGPS_cfg.h** uncomment these lines:
```
#define NMEAGPS_PARSE_GGA
#define NMEAGPS_PARSE_GSA
#define NMEAGPS_PARSE_GSV
#define NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_PARSE_SATELLITE_INFO
#define NMEAGPS_MAX_SATELLITES (20)
```
This will display additional fields for how many satellites are in view, whether they are being "tracked", and their individual signal strengths.
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,VDOP,PDOP,Lat err,Lon err,Alt err,Sats,[sat elev/az @ SNR],Rx ok,Rx err,Rx chars,
2015-09-14 16:03:07,3,2015-09-14 20:03:07.00,,,,,,,,,,,,2,[2 71/27@14,5 65/197@33,],8,0,476,
2015-09-14 16:03:08,3,2015-09-14 20:03:08.00,,,,,,,,,,,,2,[2 71/27@14,5 65/197@33,],16,0,952,
```
This shows that only two satellites are being tracked. You must move to a position with a better view of the sky.
__________________
## Quiet Time Interval
Because GPS devices send lots of data, it is possible for the Arduino input buffer to overflow. Many other libraries' examples will fail when modified to print too much information, or write to an SD card (see also [next section](#trying-to-do-too-many-things)).
NeoGPS examples are structured in a way that takes advantage of a "quiet time" in the data stream. GPS devices send a batch of sentences, once per second. After the last sentence in the batch has been sent, the GPS device will not send any more data until the next interval. The example programs watch for that last sentence. When it is received, it is safe to perform time-consuming operations.
It is **critical** to know the order of the sentences sent by your specific device. If you do not know what sentence is last in a batch, use the example program `NMEAorder.ino` to list the sentence order. When you know the last sentence, review `NMEAGPS_cfg.h` to confirm that the correct value on this line:
```
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
```
Most example programs depend on this statement. `NMEAdiagnostic.ino` will emit a warning if that sentence is never recevied. If `LAST_SENTENCE_IN_INTERVAL` is not correct for your device, the example programs will probably lose GPS data, especially on `SoftwareSerial` data ports. The example programs may behave like they are using the wrong baud rate: empty fields, increasing Rx Errors, and increasing Rx Chars. Basically, the example programs are [Trying To Do Too Much](#trying-to-do-too-many-things) at the wrong time. With the correct `LAST_SENTENCE_IN_INTERVAL`, the example programs will not lose GPS data.
## Configuration errors
Because there are so many configurable items, it is possible that your configuration prevents acquiring the desired GPS information.
The compiler **cannot** catch message set dependencies: the enum
`nmea_msg_t` is always available. So even though a `fix` member is enabled,
you may have disabled all messages that would have set its value.
`NMEAtest.ino` can be used to check some configurations.
For example, if your application needs altitude, you **must** enable the GGA sentence. No other sentence provides the altitude member (see [this table](Choosing.md)). If `NMEA_PARSE_GGA` is not defined, `gps.decode()` will return COMPLETED after a GGA is received, but no parts of the GGA sentence will have been parsed, and altitude will never be valid. NeoGPS will _recognize_ the GGA sentence, but it will not be parsed.
The compiler will catch any attempt to use parts of a `fix` that have been
configured out: you will see something like `gps_fix does not have member
xxx`.
There are also compile-time checks to make sure the configuration is valid. For example, if you enable `NMEAGPS_PARSE_SATELLITES` so that you can access the array of satellite information, you *must* enable `GPS_FIX_SATELLITES`. An error message will tell you to do that. Until you disable `NMEAGPS_PARSE_SATELLITES` **or** enable `GPS_FIX_SATELLITES`, it will not compile.
__________________
## Trying to do too many things
Many libraries and their examples, and I mean almost all of 'em, are not structured in a way that lets you do more than one thing in a sketch. The result: the example program works great, but adding anything to it breaks it.
#### Printing too much
Many programmers run into trouble because they try to print too much debug info. The Arduino `Serial.print` function will "block" until those output characters can be stored in a buffer. While the sketch is blocked at `Serial.print`, the GPS device is probably still sending data. The _input_ buffer on an Arduino is only 64 bytes, about the size of one NMEA sentence. After 64 bytes have been received stored, all other data is dropped! Depending on the GPS baud rate and the Serial Monitor baud rate, it may be very easy to lose GPS characters.
It is crucial to call `gps.available( gps_port )` or `serial.read()` frequently, and _never_ to call a blocking function that takes more than (64*11/baud) seconds. If the GPS is running at 9600, you cannot block for more than 70ms. If your debug `Serial` is also running at 9600, you cannot write more than 64 bytes consecutively (i.e., in less than 70ms).
#### Blocking operations
Most Arduino libraries are written in a blocking fashion. That is, if you call a library's function, it will not return from that function until the operation has been completed. If that operation takes a long time, GPS characters will be dropped.
Many programmers want to write GPS data to an SD card. This is completely reasonable to do, but an `SD.write` can block long enough to cause the input buffer to overflow. SD libraries have their own buffers, and when they are filled, the library performs SPI operations to "flush" the buffer to the SD card. While that is happening, the GPS device is _still_ sending data, and it will eventually overflow the serial input buffer.
This is a very common problem! Here are some diagrams to help explain the timing for the Adafruit_GPS library. First, lets look at how the incoming GPS data relates to reading and parsing it:
<img src="images/GPS%20Timing%200.jpg"/>
Note how loop calls GPS.read, and when it has read all the chars that have been received up to that point, it returns. loop may get called lots of times while it's waiting for the chars to come in. Eventually, the whole sentence is received, newNMEAreceived returns true, and your sketch can `parse` the new data (not needed for **NeoGPS**).
The problem is that if you try to do anything that takes "too long", `GPS.read` won't get called. The incoming chars stack up in the input buffer until it's full. After that, the chars will be dropped:
<img src="images/GPS%20Timing%201.jpg"/>
The next sentence, a GPRMC, continues to come in while `Serial.print` and `SD.write` are doing their thing... and data gets lost.
Fortunately, there are two ways to work around this:
#### **1)** Use an interrupt-driven approach
The received data could be handled by an **I**nterrupt **S**ervice **R**outine. The example program [NMEA_isr.INO](/examples/NMEA_isr/NMEA_isr.ino) shows how to handle the received GPS characters *during* the RX interrupt. This program uses one of the replacement **NeoXXSerial** libraries to attach an interrupt handler to the GPS serial port.
When a character is received, the ISR is called, where it is immediately decoded. Normally, the character is stored in an input buffer, and you have to call `available()` and then `read()` to retrieve the character. Handling it in the ISR totally avoids having to continuously call `Serial1.read()`, and is much more efficient. Your program does not have to be structured around the GPS quiet time.
The ISR can also save (i.e., "buffer") complete fixes as they are completed. This allows the main sketch to perform an operation that takes multiple update intervals. This is especially important if your GPS is sending updates 10 times per second (10Hz), and your SD card takes ~150ms to complete a logging write.
Normally, this would cause a loss of data. You can specify the number of fixes to be stored by the ISR for later use: simply set NMEAGPS_FIX_MAX to 2. This will allow 2 complete fixes, accumulated from several sentences each, to be stored until `loop()` can call `gps.read()`. This is referred to as "fix-oriented operation" in the Data Model description.
While interrupt handling is considered a more advanced technique, it is similar to the existing Arduino [attachInterrupt](https://www.arduino.cc/en/Reference/AttachInterrupt) function for detecting when pin change.
Which **NeoXXLibrary** should you use?
* If `Serial` or `Serial1` is connected to the GPS device, you can use [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial).
* If the Input Capture pin can be connected to the GPS device, as required for AltSoftSerial, you can use [NeoICSerial](https://github.com/SlashDevin/NeoICSerial).
* If neither of those connections is possible, you can [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial) on almost any pair of digital pins. It only supports a few baud rates, though.
#### **2)** Restructure `loop()` to do time-consuming operations during the GPS [quiet time](#quiet-time-interval).
All non-ISR example programs are structured this way. The "quiet time" is perfect for doing other things:
<img src="images/GPS%20Timing%202.jpg"/>
All you need to do is hold on to the GPS information (date, time, location, etc.) until the quiet time comes around. You'll need to take the same approach for each additional task. For additional sensors, hold on to the temperature, acceleration, whatever, until the quiet time comes around. *Then* perform the blocking operation, like `SD.write`, and no GPS data will be lost.
This is why NeoGPS uses a `fix` structure: it can be
* _populated_ as the characters are received,
* _copied/merged_ when a sentence is complete, and then
* _used_ anytime (for fast operations) or during the quiet time (for slow operations).
You do not have to call a "parse" function after a complete sentence has been received -- the data was parsed as it was received. Essentially, the processing time for parsing is spread out across the receipt of all characters. When the last character of the sentence is received (i.e. `gps.available()` or `gps.decode(c) == DECODE_COMPLETED`), the relevant members of `gps.fix()` have already been populated.
These example programs are structured so that the (relatively) slow printing operations are performed during the GPS quiet time. Simply replace those trace/print statements with your specific code.
__________________
## When all else fails
If you still do not have enough time to complete your tasks during the GPS quiet time, you can
* Increase the baud rate on the debug port (takes less time to print)
* Increase the baud rate on the GPS port (increases quiet time)
* Configure the GPS device to send fewer sentences (decreases parsing time, increases quiet time)
* Use a binary protocol for your specific device (decreases parsing time, increases quiet time)
* Watch for a specific message to be COMPLETED, then begin your specific processing. This may cause some sentences to lose characters, but they may not be necessary. See comments regarding `LAST_SENTENCE_IN_INTERVAL` above.
* Use the interrupt-driven approach, described above. It is *guaranteed* to work!

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@ -0,0 +1 @@
These .MD files are in github markdown format. They are intended to be read on the github website, as linked from the top repository page.

View File

@ -0,0 +1,67 @@
NeoGPS support for u-blox GPS devices
=================
To use either ublox-specific NMEA messages ($PUBX) or the UBX binary protocol, you must enable the following in `NMEAGPS_cfg.h`:
```
#define NMEAGPS_PARSE_MFR_ID
#define NMEAGPS_DERIVED_TYPES
```
## ublox-specific NMEA messages
**NeoGPS** implements the following additional NMEA messages:
# NMEA 0183 Proprietary Messages
* UBX,00 - Lat/Long Position Data
* UBX,04 - Time of Day and Clock Information
You may want to change the configured PUBX messages in `PUBX_cfg.h`. It is currently configured to work with the example application, `PUBX.ino`. `PUBX_cfg.h` has the following configuration items:
```
#define NMEAGPS_PARSE_PUBX_00
#define NMEAGPS_PARSE_PUBX_04
```
## ublox-specific binary protocol
**NeoGPS** implements the following messages in the UBX binary protocol:
# UBX Protocol Messages
<table>
<tr><td><b>Message</b></td><td><b>Description</b></td><td><b>NEO series</b></td></tr>
<tr><td>NAV_STATUS</td><td>Receiver Navigation Status</td><td><p align="center">6</p></td></tr>
<tr><td>NAV_TIMEGPS</td><td>GPS Time Solution</td><td><p align="center">6</p></td></tr>
<tr><td>NAV_TIMEUTC</td><td>UTC Time Solution</td><td><p align="center">6</p></td></tr>
<tr><td>NAV_POSLLH</td><td>Geodetic Position Solution</td><td><p align="center">6</p></td></tr>
<tr><td>NAV_DOP</td><td>Dilutions of precision</td><td><p align="center">6</p></td></tr>
<tr><td>NAV_PVT</td><td>Navigation Position Velocity Time Solution</td><td><p align="center">7</p></td></tr>
<tr><td>NAV_VELNED</td><td>Velocity Solution in NED (North/East/Down)</td><td><p align="center">6</p></td></tr>
<tr><td>NAV_SVINFO</td><td>Space Vehicle Information</td><td><p align="center">6</p></td></tr>
<tr><td>HNR_PVT</td><td>High Rate Output of PVT Solution</td><td><p align="center">8</p></td></tr>
</table>
You may want to change the configured UBX messages in `ubx_cfg.h`. It is currently configured to work with the example application `ublox.ino`.
The configuration file `ubx_cfg.h` has the following configuration items near the top of the file:
```
#define UBLOX_PARSE_STATUS
#define UBLOX_PARSE_TIMEGPS
#define UBLOX_PARSE_TIMEUTC
#define UBLOX_PARSE_POSLLH
//#define UBLOX_PARSE_DOP
#define UBLOX_PARSE_VELNED
//#define UBLOX_PARSE_PVT
#define UBLOX_PARSE_SVINFO
//#define UBLOX_PARSE_HNR_PVT
```
**Note:** Disabling some of the UBX messages may prevent the `ublox.ino` example sketch from working. That sketch goes through a process of first acquiring the current GPS leap seconds and UTC time so that "time-of-week" milliseconds can be converted to a UTC time.
The POSLLH and VELNED messages use a Time-Of-Week timestamp. Without the TIMEGPS and TIMEUTC messages, that TOW timestamp cannot be converted to a UTC time.
* If your application does not need the UTC date and/or time, you could disable the TIMEGPS and TIMEUTC messages.
* If your application does not need latitude, longitude or altitude, you could disable the POSLLH message.
* If your application does not need speed or heading, you could disable the VELNED message.
* If your application does not need satellite information, you could disable the SVINFO message.

View File

@ -0,0 +1,10 @@
name=NeoGPS
version=4.2.9
author=SlashDevin
maintainer=SlashDevin
sentence=NMEA and ublox GPS parser, configurable to use as few as 10 bytes of RAM
paragraph=Faster and smaller than all other GPS parsers
category=Communication
url=https://github.com/SlashDevin/NeoGPS
architectures=avr,samd,sam,esp8266,arc32,esp32
includes=NMEAGPS.h

View File

@ -0,0 +1,34 @@
#ifndef COSACOMPAT_H
#define COSACOMPAT_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#ifdef __AVR__
#include <avr/pgmspace.h>
#else
#define PGM_P const char *
#endif
typedef PGM_P str_P;
#define __PROGMEM PROGMEM
#endif

121
lib/NeoGPS/src/DMS.cpp Normal file
View File

@ -0,0 +1,121 @@
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "DMS.h"
#include <Print.h>
//----------------------------------------------------------------
// Note that no division is used, and shifts are on byte boundaries. Fast!
void DMS_t::From( int32_t deg_1E7 )
{
const uint32_t E7 = 10000000UL;
if (deg_1E7 < 0) {
deg_1E7 = -deg_1E7;
hemisphere = SOUTH_H; // or WEST_H
} else
hemisphere = NORTH_H; // or EAST_H
const uint32_t div_E32 = 429; // 1e-07 * 2^32
degrees = ((deg_1E7 >> 16) * div_E32) >> 16;
uint32_t remainder = deg_1E7 - degrees * E7;
remainder *= 60; // to minutes * E7
minutes = ((remainder >> 16) * div_E32) >> 16;
remainder -= minutes * E7;
remainder *= 60; // to seconds * E7
uint32_t secs = ((remainder >> 16) * div_E32) >> 16;
remainder -= secs * E7;
const uint32_t div_1E4_E24 = 1677; // 0.00001 * 2^24
seconds_frac = (((remainder >> 8) * div_1E4_E24) >> 16); // thousandths
seconds_whole = secs;
// Carry if thousandths too big
if (seconds_frac >= 1000) {
seconds_frac -= 1000;
seconds_whole++;
if (seconds_whole >= 60) {
seconds_whole -= 60;
minutes++;
if (minutes >= 60) {
minutes -= 60;
degrees++;
}
}
}
} // From
//----------------------------------------------------------------
Print & operator << ( Print & outs, const DMS_t & dms )
{
if (dms.degrees < 10)
outs.write( '0' );
outs.print( dms.degrees );
outs.write( ' ' );
if (dms.minutes < 10)
outs.write( '0' );
outs.print( dms.minutes );
outs.print( F("\' ") );
if (dms.seconds_whole < 10)
outs.write( '0' );
outs.print( dms.seconds_whole );
outs.write( '.' );
if (dms.seconds_frac < 100)
outs.write( '0' );
if (dms.seconds_frac < 10)
outs.write( '0' );
outs.print( dms.seconds_frac );
outs.print( F("\" ") );
return outs;
} // operator <<
//----------------------------------------------------------------
void DMS_t::printDDDMMmmmm( Print & outs ) const
{
outs.print( degrees );
if (minutes < 10)
outs.print( '0' );
outs.print( minutes );
outs.print( '.' );
// Calculate the fractional minutes from the seconds,
// *without* using floating-point numbers.
uint16_t mmmm = seconds_whole * 166; // same as 10000/60, less .66666...
mmmm += (seconds_whole * 2 + seconds_frac/2 ) / 3; // ... plus the remaining .66666
// ... plus the seconds_frac, scaled by 10000/(60*1000) = 1/6, which
// is implemented above as 1/2 * 1/3
// print leading zeroes, if necessary
if (mmmm < 1000)
outs.print( '0' );
if (mmmm < 100)
outs.print( '0' );
if (mmmm < 10)
outs.print( '0' );
outs.print( mmmm );
}

55
lib/NeoGPS/src/DMS.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef DMS_H
#define DMS_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "NeoGPS_cfg.h"
#include <stdint.h>
class Print;
enum Hemisphere_t { NORTH_H = 0, SOUTH_H = 1, EAST_H = 0, WEST_H = 1 };
class DMS_t
{
public:
uint8_t degrees;
uint8_t minutes ;//NEOGPS_BF(6);
Hemisphere_t hemisphere ;//NEOGPS_BF(2); compiler bug!
uint8_t seconds_whole NEOGPS_BF(6);
uint16_t seconds_frac NEOGPS_BF(10); // 1000ths
void init() { degrees = minutes = seconds_whole = seconds_frac = 0;
hemisphere = NORTH_H; }
float secondsF() const { return seconds_whole + 0.001 * seconds_frac; };
char NS () const { return (hemisphere == SOUTH_H) ? 'S' : 'N'; };
char EW () const { return (hemisphere == WEST_H) ? 'W' : 'E'; };
//.............................................................................
// A utility function to convert from integer 'lat' or 'lon', scaled by 10^7
void From( int32_t deg_1E7 );
// Print DMS as the funky NMEA DDDMM.mmmm format
void printDDDMMmmmm( Print & outs ) const;
} NEOGPS_PACKED;
extern Print & operator << ( Print & outs, const DMS_t & );
#endif

View File

@ -0,0 +1,21 @@
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "GPSTime.h"
uint8_t GPSTime::leap_seconds = 0;
NeoGPS::clock_t GPSTime::s_start_of_week = 0;

85
lib/NeoGPS/src/GPSTime.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef GPSTIME_H
#define GPSTIME_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "NeoTime.h"
class GPSTime
{
GPSTime();
static NeoGPS::clock_t s_start_of_week;
public:
/**
* GPS time is offset from UTC by a number of leap seconds. To convert a GPS
* time to UTC time, the current number of leap seconds must be known.
* See http://en.wikipedia.org/wiki/Global_Positioning_System#Leap_seconds
*/
static uint8_t leap_seconds;
/**
* Some receivers report time WRT start of the current week, defined as
* Sunday 00:00:00. To save fairly expensive date/time calculations,
* the UTC start of week is cached
*/
static void start_of_week( NeoGPS::time_t & now )
{
now.set_day();
s_start_of_week =
(NeoGPS::clock_t) now -
(NeoGPS::clock_t) ((((now.day-1 ) * 24L +
now.hours ) * 60L +
now.minutes) * 60L +
now.seconds);
}
static NeoGPS::clock_t start_of_week()
{
return s_start_of_week;
}
/*
* Convert a GPS time-of-week to UTC.
* Requires /leap_seconds/ and /start_of_week/.
*/
static NeoGPS::clock_t TOW_to_UTC( uint32_t time_of_week )
{ return (NeoGPS::clock_t)
(start_of_week() + time_of_week - leap_seconds); }
/**
* Set /fix/ timestamp from a GPS time-of-week in milliseconds.
* Requires /leap_seconds/ and /start_of_week/.
**/
static bool from_TOWms
( uint32_t time_of_week_ms, NeoGPS::time_t &dt, uint16_t &ms )
{
//trace << PSTR("from_TOWms(") << time_of_week_ms << PSTR("), sow = ") << start_of_week() << PSTR(", leap = ") << leap_seconds << endl;
bool ok = (start_of_week() != 0) && (leap_seconds != 0);
if (ok) {
NeoGPS::clock_t tow_s = time_of_week_ms/1000UL;
dt = TOW_to_UTC( tow_s );
ms = (uint16_t)(time_of_week_ms - tow_s*1000UL);
}
return ok;
}
};
#endif

591
lib/NeoGPS/src/GPSfix.h Normal file
View File

@ -0,0 +1,591 @@
#ifndef GPSFIX_H
#define GPSFIX_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "NeoGPS_cfg.h"
#include "GPSfix_cfg.h"
#if defined( GPS_FIX_DATE ) | defined( GPS_FIX_TIME )
#include "NeoTime.h"
#endif
#ifdef GPS_FIX_LOCATION_DMS
#include "DMS.h"
#endif
#ifdef GPS_FIX_LOCATION
#include "Location.h"
#endif
/**
* A structure for holding a GPS fix: time, position, velocity, etc.
*
* Because GPS devices report various subsets of a coherent fix,
* this class tracks which members of the fix are being reported:
* each part has its own validity flag. Also, operator |= implements
* merging multiple reports into one consolidated report.
*
* @section Limitations
* Reports are not really fused with an algorithm; if present in
* the source, they are simply replaced in the destination.
*
*/
class gps_fix
{
public:
// Default Constructor
gps_fix() { init(); };
//------------------------------------------------------------------
// 'whole_frac' is a utility structure that holds the two parts
// of a floating-point number.
//
// This is used for Altitude, Heading and Speed, which require more
// significant digits than a 16-bit number.
//
// When parsing floating-point fields, the decimal point is used as
// a separator for these two parts. This is much more efficient
// than calling 'long' or 'floating-point' math subroutines.
//
// This requires knowing the exponent on the fraction when a simple type
// (e.g., float or int) is needed. For example, 'altitude()' knows that
// the whole part was stored as integer meters, and the fractional part
// was stored as integer centimeters.
//
// Unless you want the speed and precision of the two integer parts, you
// shouldn't have to use 'whole_frac'. Instead, use the
// accessor functions for each of the specific fields for
// Altitude, Heading and Speed.
struct whole_frac {
int16_t whole;
int16_t frac;
void init() { whole = 0; frac = 0; };
int32_t int32_00() const { return ((int32_t)whole) * 100L + frac; };
int16_t int16_00() const { return whole * 100 + frac; };
int32_t int32_000() const { return whole * 1000L + frac; };
float float_00() const { return ((float)whole) + ((float)frac)*0.01; };
float float_000() const { return ((float)whole) + ((float)frac)*0.001; };
} NEOGPS_PACKED;
//--------------------------------------------------------------
// Members of a GPS fix
//
// Each member is separately enabled or disabled by the CFG file
// by #ifdef/#endif wrappers.
// Each member has a storage declaration that depends on the
// precision and type of data available from GPS devices.
// Some members have a separate accessor function that converts the
// internal storage type to a more common or convenient type
// for use by an application.
// For example, latitude data is internally stored as a 32-bit integer,
// where the reported degrees have been multiplied by 10^7. Many
// applications expect a floating-point value, so a floating-point
// accessor is provided: 'latitude()'. This function converts the
// internal 32-bit integer to a floating-point value, and then
// divides it by 10^7. The returned value is now a floating-point
// degrees value.
#ifdef GPS_FIX_LOCATION
NeoGPS::Location_t location;
int32_t latitudeL() const { return location.lat (); };
float latitude () const { return location.latF(); }; // accuracy loss
int32_t longitudeL() const { return location.lon (); };
float longitude () const { return location.lonF(); }; // accuracy loss
#endif
#ifdef GPS_FIX_LOCATION_DMS
DMS_t latitudeDMS;
DMS_t longitudeDMS;
#endif
#ifdef GPS_FIX_ALTITUDE
whole_frac alt; // .01 meters
int32_t altitude_cm() const { return alt.int32_00(); };
float altitude () const { return alt.float_00(); };
float altitude_ft() const { return altitude() * 3.28084; };
#endif
#ifdef GPS_FIX_VELNED
int32_t velocity_north; // cm/s
int32_t velocity_east; // cm/s
int32_t velocity_down; // cm/s
void calculateNorthAndEastVelocityFromSpeedAndHeading()
{
#if defined( GPS_FIX_HEADING ) && defined( GPS_FIX_SPEED )
if (valid.heading && valid.speed && valid.velned) {
float course = heading() * NeoGPS::Location_t::RAD_PER_DEG;
float speed_cm_per_s = speed_metersph() * (100.0 / 3600.0);
velocity_north = round( speed_cm_per_s * cos( course ) );
velocity_east = round( speed_cm_per_s * sin( course ) );
// velocity_down has already been set.
}
#endif
}
#endif
#ifdef GPS_FIX_SPEED
whole_frac spd; // .001 nautical miles per hour
uint32_t speed_mkn () const { return spd.int32_000(); };
float speed () const { return spd.float_000(); };
// Utilities for speed in other units
CONST_CLASS_DATA float KM_PER_NMI = 1.852;
float speed_kph () const { return speed() * KM_PER_NMI; };
CONST_CLASS_DATA uint32_t M_PER_NMI = 1852;
uint32_t speed_metersph() const { return (spd.whole * M_PER_NMI) + (spd.frac * M_PER_NMI)/1000; };
CONST_CLASS_DATA float MI_PER_NMI = 1.150779;
float speed_mph() const { return speed() * MI_PER_NMI; };
#endif
#ifdef GPS_FIX_HEADING
whole_frac hdg; // .01 degrees
uint16_t heading_cd() const { return hdg.int16_00(); };
float heading () const { return hdg.float_00(); };
#endif
//--------------------------------------------------------
// Dilution of Precision is a measure of the current satellite
// constellation geometry WRT how 'good' it is for determining a
// position. This is _independent_ of signal strength and many
// other factors that may be internal to the receiver.
// It _cannot_ be used to determine position accuracy in meters.
// Instead, use the LAT/LON/ALT error in cm members, which are
// populated by GST sentences.
#ifdef GPS_FIX_HDOP
uint16_t hdop; // Horizontal Dilution of Precision x 1000
#endif
#ifdef GPS_FIX_VDOP
uint16_t vdop; // Vertical Dilution of Precision x 1000
#endif
#ifdef GPS_FIX_PDOP
uint16_t pdop; // Position Dilution of Precision x 1000
#endif
//--------------------------------------------------------
// Error estimates for lat, lon, altitude, speed, heading and time
#ifdef GPS_FIX_LAT_ERR
uint16_t lat_err_cm;
float lat_err() const { return lat_err_cm / 100.0; } // m
#endif
#ifdef GPS_FIX_LON_ERR
uint16_t lon_err_cm;
float lon_err() const { return lon_err_cm / 100.0; } // m
#endif
#ifdef GPS_FIX_ALT_ERR
uint16_t alt_err_cm;
float alt_err() const { return alt_err_cm / 100.0; } // m
#endif
#ifdef GPS_FIX_SPD_ERR
uint16_t spd_err_mmps;
float spd_err() const { return spd_err_mmps / 1000.0; } // m/s
#endif
#ifdef GPS_FIX_HDG_ERR
uint16_t hdg_errE5; // 0.00001 deg
float hdg_err() const { return hdg_errE5 / 1.0e5; } // deg
#endif
#ifdef GPS_FIX_TIME_ERR
uint16_t time_err_ns;
float time_err() const { return time_err_ns / 1.0e9; } // s
#endif
//--------------------------------------------------------
// Height of the geoid above the WGS84 ellipsoid
#ifdef GPS_FIX_GEOID_HEIGHT
whole_frac geoidHt; // .01 meters
int32_t geoidHeight_cm() const { return geoidHt.int32_00(); };
float geoidHeight () const { return geoidHt.float_00(); };
#endif
//--------------------------------------------------------
// Number of satellites used to calculate a fix.
#ifdef GPS_FIX_SATELLITES
uint8_t satellites;
#endif
//--------------------------------------------------------
// Date and Time for the fix
#if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME)
NeoGPS::time_t dateTime ; // Date and Time in one structure
#endif
#if defined(GPS_FIX_TIME)
uint8_t dateTime_cs; // The fix's UTC hundredths of a second
uint32_t dateTime_us() const // The fix's UTC microseconds
{ return dateTime_cs * 10000UL; };
uint16_t dateTime_ms() const // The fix's UTC millseconds
{ return dateTime_cs * 10; };
#endif
//--------------------------------------------------------
// The current fix status or mode of the GPS device.
//
// Unfortunately, the NMEA sentences are a little inconsistent
// in their use of "status" and "mode". Both fields are mapped
// onto this enumerated type. Be aware that different
// manufacturers interpret them differently. This can cause
// problems in sentences which include both types (e.g., GPGLL).
//
// Note: Sorted by increasing accuracy. See also /operator |=/.
enum status_t {
STATUS_NONE,
STATUS_EST,
STATUS_TIME_ONLY,
STATUS_STD,
STATUS_DGPS,
STATUS_RTK_FLOAT,
STATUS_RTK_FIXED,
STATUS_PPS // Precise Position System, *NOT* Pulse-per-second
};
status_t status NEOGPS_BF(8);
//--------------------------------------------------------
// Flags to indicate which members of this fix are valid.
//
// Because different sentences contain different pieces of a fix,
// each piece can be valid or NOT valid. If the GPS device does not
// have good reception, some fields may not contain any value.
// Those empty fields will be marked as NOT valid.
struct valid_t {
bool status NEOGPS_BF(1);
#if defined(GPS_FIX_DATE)
bool date NEOGPS_BF(1);
#endif
#if defined(GPS_FIX_TIME)
bool time NEOGPS_BF(1);
#endif
#if defined( GPS_FIX_LOCATION ) | defined( GPS_FIX_LOCATION_DMS )
bool location NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_ALTITUDE
bool altitude NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_SPEED
bool speed NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_VELNED
bool velned NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_HEADING
bool heading NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_SATELLITES
bool satellites NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_HDOP
bool hdop NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_VDOP
bool vdop NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_PDOP
bool pdop NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_LAT_ERR
bool lat_err NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_LON_ERR
bool lon_err NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_ALT_ERR
bool alt_err NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_SPD_ERR
bool spd_err NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_HDG_ERR
bool hdg_err NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_TIME_ERR
bool time_err NEOGPS_BF(1);
#endif
#ifdef GPS_FIX_GEOID_HEIGHT
bool geoidHeight NEOGPS_BF(1);
#endif
// Initialize all flags to false
void init()
{
uint8_t *all = (uint8_t *) this;
for (uint8_t i=0; i<sizeof(*this); i++)
*all++ = 0;
}
// Merge these valid flags with another set of valid flags
void operator |=( const valid_t & r )
{
uint8_t *all = (uint8_t *) this;
const uint8_t *r_all = (const uint8_t *) &r;
for (uint8_t i=0; i<sizeof(*this); i++)
*all++ |= *r_all++;
}
} NEOGPS_PACKED
valid; // This is the name of the collection of valid flags
//--------------------------------------------------------
// Initialize a fix. All configured members are set to zero.
void init()
{
#ifdef GPS_FIX_LOCATION
location.init();
#endif
#ifdef GPS_FIX_LOCATION_DMS
latitudeDMS.init();
longitudeDMS.init();
#endif
#ifdef GPS_FIX_ALTITUDE
alt.init();
#endif
#ifdef GPS_FIX_SPEED
spd.init();
#endif
#ifdef GPS_FIX_VELNED
velocity_north =
velocity_east =
velocity_down = 0;
#endif
#ifdef GPS_FIX_HEADING
hdg.init();
#endif
#ifdef GPS_FIX_HDOP
hdop = 0;
#endif
#ifdef GPS_FIX_VDOP
vdop = 0;
#endif
#ifdef GPS_FIX_PDOP
pdop = 0;
#endif
#ifdef GPS_FIX_LAT_ERR
lat_err_cm = 0;
#endif
#ifdef GPS_FIX_LON_ERR
lon_err_cm = 0;
#endif
#ifdef GPS_FIX_ALT_ERR
alt_err_cm = 0;
#endif
#ifdef GPS_FIX_SPD_ERR
spd_err_mmps = 0;
#endif
#ifdef GPS_FIX_HDG_ERR
hdg_errE5 = 0;
#endif
#ifdef GPS_FIX_TIME_ERR
time_err_ns = 0;
#endif
#ifdef GPS_FIX_GEOID_HEIGHT
geoidHt.init();
#endif
#ifdef GPS_FIX_SATELLITES
satellites = 0;
#endif
#if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME)
dateTime.init();
#endif
#if defined(GPS_FIX_TIME)
dateTime_cs = 0;
#endif
status = STATUS_NONE;
valid.init();
} // init
//-------------------------------------------------------------
// Merge valid fields from the right fix into a "fused" fix
// on the left (i.e., /this/).
//
// Usage: gps_fix left, right;
// left |= right; // explicit merge
gps_fix & operator |=( const gps_fix & r )
{
// Replace /status/ only if the right is more "accurate".
if (r.valid.status && (!valid.status || (status < r.status)))
status = r.status;
#ifdef GPS_FIX_DATE
if (r.valid.date) {
dateTime.date = r.dateTime.date;
dateTime.month = r.dateTime.month;
dateTime.year = r.dateTime.year;
}
#endif
#ifdef GPS_FIX_TIME
if (r.valid.time) {
dateTime.hours = r.dateTime.hours;
dateTime.minutes = r.dateTime.minutes;
dateTime.seconds = r.dateTime.seconds;
dateTime_cs = r.dateTime_cs;
}
#endif
#ifdef GPS_FIX_LOCATION
if (r.valid.location) {
location = r.location;
}
#endif
#ifdef GPS_FIX_LOCATION_DMS
if (r.valid.location) {
latitudeDMS = r.latitudeDMS;
longitudeDMS = r.longitudeDMS;
}
#endif
#ifdef GPS_FIX_ALTITUDE
if (r.valid.altitude)
alt = r.alt;
#endif
#ifdef GPS_FIX_HEADING
if (r.valid.heading)
hdg = r.hdg;
#endif
#ifdef GPS_FIX_SPEED
if (r.valid.speed)
spd = r.spd;
#endif
#ifdef GPS_FIX_VELNED
if (r.valid.velned) {
velocity_north = r.velocity_north;
velocity_east = r.velocity_east;
velocity_down = r.velocity_down;
}
#endif
#ifdef GPS_FIX_SATELLITES
if (r.valid.satellites)
satellites = r.satellites;
#endif
#ifdef GPS_FIX_HDOP
if (r.valid.hdop)
hdop = r.hdop;
#endif
#ifdef GPS_FIX_VDOP
if (r.valid.vdop)
vdop = r.vdop;
#endif
#ifdef GPS_FIX_PDOP
if (r.valid.pdop)
pdop = r.pdop;
#endif
#ifdef GPS_FIX_LAT_ERR
if (r.valid.lat_err)
lat_err_cm = r.lat_err_cm;
#endif
#ifdef GPS_FIX_LON_ERR
if (r.valid.lon_err)
lon_err_cm = r.lon_err_cm;
#endif
#ifdef GPS_FIX_ALT_ERR
if (r.valid.alt_err)
alt_err_cm = r.alt_err_cm;
#endif
#ifdef GPS_FIX_SPD_ERR
if (r.valid.spd_err)
spd_err_mmps = r.spd_err_mmps;
#endif
#ifdef GPS_FIX_HDG_ERR
if (r.valid.hdg_err)
hdg_errE5 = r.hdg_errE5;
#endif
#ifdef GPS_FIX_TIME_ERR
if (r.valid.time_err)
time_err_ns = r.time_err_ns;
#endif
#ifdef GPS_FIX_GEOID_HEIGHT
if (r.valid.geoidHeight)
geoidHt = r.geoidHt;
#endif
// Update all the valid flags
valid |= r.valid;
return *this;
} // operator |=
} NEOGPS_PACKED;
#endif

View File

@ -0,0 +1,56 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
//#define GPS_FIX_VELNED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
#define GPS_FIX_HDOP
#define GPS_FIX_VDOP
#define GPS_FIX_PDOP
#define GPS_FIX_LAT_ERR
#define GPS_FIX_LON_ERR
#define GPS_FIX_ALT_ERR
//#define GPS_FIX_SPD_ERR
//#define GPS_FIX_HDG_ERR
//#define GPS_FIX_TIME_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

271
lib/NeoGPS/src/GPSport.h Normal file
View File

@ -0,0 +1,271 @@
#ifndef GPSport_h
#define GPSport_h
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//-----------------------------------------------------------
// There are 2 ways this file can be used:
//
// I. AS IS, which tries to *guess* which port a beginner should use.
// If you include <SoftwareSerial.h>, <NeoSWSerial.h>, <AltSoftSerial.h>,
// <NeoICSerial.h> *OR* <NeoHWSerial.h> before this file (in the INO)
// or on line 152 below, one of those ports will be used for the GPS.
// This file cannot always guess what you want, so you may need to use it
// in the 2nd way...
//
// II. *REPLACE EVERYTHING* in this file to *specify* which port and
// which library you want to use for that port. Just declare the
// port here. You must declare three things:
//
// 1) the "gpsPort" variable (used by all example programs),
// 2) the string for the GPS_PORT_NAME (displayed by all example programs), and
// 3) the DEBUG_PORT to use for Serial Monitor print messages (usually Serial).
//
// The examples will display the device you have selected during setup():
//
// DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
//
// Choosing the serial port connection for your GPS device is probably
// the most important design decision, because it has a tremendous impact
// on the processor load. Here are the possible configurations,
// from BEST to WORST:
//
// --- The BEST choice is to use a HardwareSerial port. You could use Serial
// on any board, but you will have to disconnect the Arduino RX pin 0
// from the GPS TX pin to upload a new sketch over USB. This is a very
// reliable connection:
//
// #define gpsPort Serial
// #define GPS_PORT_NAME "Serial"
// #define DEBUG_PORT Serial
//
// If you have a Mega, Leo or Due board, you could use Serial1,
// Serial2 or Serial3:
//
// #define gpsPort Serial1
// #define GPS_PORT_NAME "Serial1"
// #define DEBUG_PORT Serial
//
// These extra ports do not have to be disconnected to upload new
// sketches over USB.
//
// Use NeoHWSerial if you want to handle GPS characters
// in an Interrupt Service Routine (see NMEA_isr.INO).
// Also uncomment NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h.
//
// #include <NeoHWSerial.h>
// #define gpsPort NeoSerial
// #define GPS_PORT_NAME "NeoSerial"
// #define DEBUG_PORT NeoSerial
//
// Note that the DEBUG_PORT must be NeoSerial, not Serial
// (see NeoHWSerial documentation for additional requirements).
//
// --- The SECOND BEST choice is AltSoftSerial. It uses 10x to 500x
// more processor time than a HardwareSerial port, but it is the BEST
// *software* serial port library. It only works on two specific pins
// (see the AltSoftSerial documentation).
//
// #include <AltSoftSerial.h>
// AltSoftSerial gpsPort; // 8 & 9 for an UNO
// #define GPS_PORT_NAME "AltSoftSerial"
// #define DEBUG_PORT Serial
//
// Use NeoICSerial if you want to handle GPS characters
// in an Interrupt Service Routine (see NMEA_isr.INO).
// Also uncomment NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h.
//
// #include <NeoICSerial.h>
// NeoICSerial gpsPort; // 8 & 9 for an UNO
// #define GPS_PORT_NAME "NeoICSerial"
// #define DEBUG_PORT Serial
//
// --- The THIRD BEST choice is NeoSWSerial. It is almost as efficient as
// AltSoftSerial, but it only supports baud rates 9600, 19200 or 38400.
//
// #include <NeoSWSerial.h>
// NeoSWSerial gpsPort(3, 2);
// #define GPS_PORT_NAME "NeoSWSerial(3,2)"
// #define DEBUG_PORT Serial
//
// NeoSWSerial supports handling GPS characters in an Interrupt Service
// Routine (see NMEA_isr.INO). If you want to do that, also uncomment
// NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h.
//
// --- The WORST choice is SoftwareSerial. IT IS NOT RECOMMENDED because
// it disables interrupts for the entire time a character is sent OR
// received. This interferes with other parts of your sketch or with
// other libraries. It prevents receiving or sending data on any other
// port. It cannot transmit and receive at the same time. 95% of the
// CPU time will be spent twiddling its thumbs. It may support 57600,
// but 115200 is rarely reliable.
//
// #include <SoftwareSerial.h>
// SoftwareSerial gpsPort(2,3);
// #define GPS_PORT_NAME "SoftwareSerial(8,9)"
// #define DEBUG_PORT Serial
//
// You should *seriously* consider using other pins or lower baud rates,
// so that you could use one of the other recommended ports.
//-----------------------------------------------------------
// DEFAULT file contents:
// *GUESS* which port should be used. If you know what port you want to use,
// *DELETE* the rest of this file and declare your own GPS port variable,
// GPS port name string, and debug print port variable (see above).
#if defined(SERIAL_PORT_HARDWARE_OPEN)
#if SERIAL_PORT_HARDWARE_OPEN == Serial1
// This Arduino has more than one hardware serial port,
// use Serial1 (or NeoSerial1).
#define NEOGPS_USE_SERIAL1
#endif
#endif
#ifdef NEOGPS_USE_SERIAL1
#if defined( NMEAGPS_INTERRUPT_PROCESSING )
#include <NeoHWSerial.h>
#define gpsPort NeoSerial1
#define GPS_PORT_NAME "NeoSerial1"
#else
#define gpsPort Serial1
#define GPS_PORT_NAME "Serial1"
#endif
#else
// Uncomment *zero* or *one* of the serial port library includes.
// If none of these includes are uncommented, Serial will be used.
//
//#include <NeoHWSerial.h> // NeoSerial or NeoSerial1 Interrupt-style processing
#include <AltSoftSerial.h> // <-- DEFAULT. Two specific pins required
//#include <NeoICSerial.h> // AltSoftSerial with Interrupt-style processing (see docs)
//#include <NeoSWSerial.h> // Any pins, only @ 9600, 19200 or 38400 baud
//#include <SoftwareSerial.h> // NOT RECOMMENDED!
#if defined( SoftwareSerial_h )
#define SS_TYPE SoftwareSerial
#warning SoftwareSerial is NOT RECOMMENDED. Use AltSoftSerial or NeoSWSerial instead.
#if defined( NMEAGPS_INTERRUPT_PROCESSING )
#error You cannot use SoftwareSerial with INTERRUPT_PROCESSING. Use NeoSWSerial or NeoICSerial instead.
#endif
#elif defined( NeoSWSerial_h )
#define SS_TYPE NeoSWSerial
#elif defined( AltSoftSerial_h )
#define SS_TYPE AltSoftSerial
#define RX_PIN -1 // doesn't matter because it only works...
#define TX_PIN -1 // ...on two specific pins
#if defined( NMEAGPS_INTERRUPT_PROCESSING )
#error You cannot use AltSoftSerial with INTERRUPT_PROCESSING. Use NeoICSerial instead.
#endif
#elif defined( NeoICSerial_h )
#define SS_TYPE NeoICSerial
#define RX_PIN -1 // doesn't matter because it only works...
#define TX_PIN -1 // ...on two specific pins
#elif defined( NeoHWSerial_h )
#define gpsPort NeoSerial
#define GPS_PORT_NAME "NeoSerial"
#warning Using Serial (actually NeoSerial) for GPS connection.
#else
// No serial library files were included before this file, just use Serial.
#define gpsPort Serial
#define GPS_PORT_NAME "Serial"
#warning Using Serial for GPS connection.
// You will see this warning if you want to use Serial for the GPS, because no
// software serial port libraries were included. That is a good choice.
//
// To use a different serial port for GPS device, you must include a serial header
// before "#include GPSport.h" in the INO. It may be simpler to replace the
// entire contents of this file with your own declarations. Follow the
// instructions above for declaring your own "gpsPort" variable. Everything else
// in this file should be deleted. See Installation instructions for more
// information.
#endif
#endif
#ifdef SS_TYPE
//---------------------------------------------------------------
// The current Board (an Uno?) does not have an extra serial port.
// Use a software serial library to listen to the GPS device.
// You should expect to get some RX errors, which may
// prevent getting fix data every second. YMMV.
// Default Arduino RX pin number that is connected to the GPS TX pin
#ifndef RX_PIN
#define RX_PIN 4
#endif
// Default Arduino TX pin number that is connected to the GPS RX pin
#ifndef TX_PIN
#define TX_PIN 3
#endif
SS_TYPE gpsPort( RX_PIN, TX_PIN );
// A little preprocessor magic to get a nice string
#define xstr(x) str(x)
#define str(x) #x
#define GPS_PORT_NAME \
xstr(SS_TYPE) "( RX pin " xstr(RX_PIN) \
", TX pin " xstr(TX_PIN) " )"
#ifdef NEOGPS_USE_SERIAL1
// If you *really* want to do this, or you just happened to include
// a software serial library header for something else, you're
// better off *not* including this file. Just declare
// your own gpsPort in your INO file.
#error You should be using Serial1 for the GPS device. \
Software serial libraries are very inefficient and unreliable when \
used for GPS communications!
#endif
#endif
//------------------------------------------------------------
// When NeoHWSerial is used, none of the built-in HardwareSerial
// variables can be used: Serial, Serial1, Serial2 and Serial3
// *cannot* be used. Instead, you must use the corresponding
// NeoSerial, NeoSerial1, NeoSerial2 or NeoSerial3. This define
// is used to substitute the appropriate Serial variable in
// all debug prints.
#ifdef NeoHWSerial_h
#define DEBUG_PORT NeoSerial
#else
#define DEBUG_PORT Serial // default for most sketches
#endif
// End of guessing game.
//------------------------
#endif

View File

@ -0,0 +1,110 @@
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "Garmin/GrmNMEA.h"
// Disable the entire file if derived types are not allowed,
// *or* if no PUBX messages are enabled.
#if defined( NMEAGPS_DERIVED_TYPES) & \
defined( GARMINGPS_PARSE_F )
#include "GPSTime.h"
//----------------------------------------------------------------
bool GarminNMEA::parseField( char chr )
{
if (nmeaMessage >= (nmea_msg_t) PGRM_FIRST_MSG) {
switch (nmeaMessage) {
case PGRMF: return parseF( chr );
default:
break;
}
} else
// Delegate
return NMEAGPS::parseField(chr);
return true;
} // parseField
//----------------------------------------------------------------
// Garmin Proprietary NMEA Sentence strings (alphabetical)
#if defined(GARMINGPS_PARSE_F) | defined(NMEAGPS_RECOGNIZE_ALL)
static const char garminF[] __PROGMEM = "F";
#endif
static const char * const garmin_nmea[] __PROGMEM =
{
#if defined(GARMINGPS_PARSE_F) | defined(NMEAGPS_RECOGNIZE_ALL)
garminF,
#endif
};
const NMEAGPS::msg_table_t GarminNMEA::garmin_msg_table __PROGMEM =
{
GarminNMEA::PGRM_FIRST_MSG,
(const msg_table_t *) &NMEAGPS::nmea_msg_table, // <-- link to standard message table
sizeof(garmin_nmea)/sizeof(garmin_nmea[0]),
garmin_nmea
};
//----------------------------------------------------------------
bool GarminNMEA::parseF( char chr )
{
#ifdef GARMINGPS_PARSE_F
switch (fieldIndex) {
case 3: return parseDDMMYY( chr );
case 4: return parseTime( chr );
case 5: return parseLeapSeconds( chr ); // <-- you will write this
PARSE_LOC(6);
//case 10: return parseFix( char ); // not needed, because next field sets status
case 11: return parseFix( chr );
case 12: return parseSpeedKph( chr );
case 13: return parseHeading( chr );
case 14: return parsePDOP( chr );
//case 15: return parseTDOP( chr ); // not yet supported
}
#endif
return true;
} // parseF
//----------------------------------------------------------------
bool GarminNMEA::parseLeapSeconds( char chr )
{
static uint8_t newLeapSeconds; // just used for parsing
if (NMEAGPS::parseInt( newLeapSeconds, chr )) {
GPSTime::leap_seconds = newLeapSeconds; // assign parsed value to global
}
return true;
} // parseGPSleapSeconds
#endif // DERIVED types and at least one PGRM message enabled

View File

@ -0,0 +1,101 @@
#ifndef _GRMNMEA_H_
#define _GRMNMEA_H_
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "NMEAGPS_cfg.h"
// Disable the entire file if derived types are not allowed.
#ifdef NMEAGPS_DERIVED_TYPES
#include "NMEAGPS.h"
#include "Garmin/PGRM_cfg.h"
#if !defined(GARMINGPS_PARSE_F)
// No Garmin Proprietary messages defined, ignore rest of file.
#else
#if !defined(NMEAGPS_PARSE_PROPRIETARY)
#error NMEAGPS_PARSE_PROPRIETARY must be defined in NMEAGPS_cfg.h in order to parse PGRM messages!
#endif
#if !defined(NMEAGPS_PARSE_MFR_ID)
#error NMEAGPS_PARSE_MFR_ID must be defined in NMEAGPS_cfg.h in order to parse PGRM messages!
#endif
//=============================================================
// NMEA 0183 Parser for Garmin GPS Modules.
//
// @section Limitations
// Very limited support for Garmin proprietary NMEA messages.
// Only NMEA messages of types F are parsed (i.e., $PGRMF).
//
class GarminNMEA : public NMEAGPS
{
GarminNMEA( const GarminNMEA & );
public:
GarminNMEA() {};
/** Garmin proprietary NMEA message types. */
enum grm_msg_t {
PGRM_BEGIN = NMEA_LAST_MSG,
#if defined(GARMINGPS_PARSE_F) | defined(NMEAGPS_RECOGNIZE_ALL)
PGRMF,
#endif
PGRM_END
};
static const nmea_msg_t PGRM_FIRST_MSG = (nmea_msg_t) (PGRM_BEGIN+1);
static const nmea_msg_t PGRM_LAST_MSG = (nmea_msg_t) (PGRM_END -1);
protected:
bool parseField( char chr );
bool parseMfrID( char chr )
{ bool ok;
switch (chrCount) {
case 1: ok = (chr == 'G'); break;
case 2: ok = (chr == 'R'); break;
case 3: ok = (chr == 'M'); break;
default: ok = false;
}
return ok;
};
bool parseF( char chr );
bool parseLeapSeconds( char chr );
static const msg_table_t garmin_msg_table __PROGMEM;
NMEAGPS_VIRTUAL const msg_table_t *msg_table() const
{ return &garmin_msg_table; };
} NEOGPS_PACKED;
#endif // at least one GRM message enabled
#endif // NMEAGPS_DERIVED_TYPES enabled
#endif

View File

@ -0,0 +1,13 @@
#ifndef PGRM_CFG_H
#define PGRM_CFG_H
//------------------------------------------------------------
// Enable/disable the parsing of specific proprietary NMEA sentences.
//
// Configuring out a sentence prevents its fields from being parsed.
// However, the sentence type may still be recognized by /decode/ and
// stored in member /nmeaMessage/. No valid flags would be available.
//#define GARMINGPS_PARSE_F
#endif

137
lib/NeoGPS/src/Location.cpp Normal file
View File

@ -0,0 +1,137 @@
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "Location.h"
using namespace NeoGPS;
//---------------------------------------------------------------------
// Calculate dLon with integers, less one bit to avoid overflow
// (360 * 1e7 = 3600000000, which is too big).
// Retains precision when points are close together.
int32_t safeDLon( int32_t p2, int32_t p1 )
{
int32_t dLonL;
int32_t halfDLon = (p2/2 - p1/2);
if (halfDLon < -1800000000L/2) {
dLonL = (p2 + 1800000000L) - (p1 - 1800000000L);
} else if (1800000000L/2 < halfDLon) {
dLonL = (p2 - 1800000000L) - (p1 + 1800000000L);
} else {
dLonL = p2 - p1;
}
return dLonL;
} // safeDLon
//---------------------------------------------------------------------
float Location_t::DistanceRadians
( const Location_t & p1, const Location_t & p2 )
{
int32_t dLonL = safeDLon( p2.lon(), p1.lon() );
int32_t dLatL = p2.lat() - p1.lat();
if ((abs(dLatL)+abs(dLonL)) < 1000) {
// VERY close together. Just use equirect approximation with precise integers.
// This is not needed for accuracy (that I can measure), but it is
// a quicker calculation.
return EquirectDistanceRadians( p1, p2 );
}
// Haversine calculation from http://www.movable-type.co.uk/scripts/latlong.html
float dLat = dLatL * RAD_PER_DEG * LOC_SCALE;
float haverDLat = sin(dLat/2.0);
haverDLat *= haverDLat; // squared
float dLon = dLonL * RAD_PER_DEG * LOC_SCALE;
float haverDLon = sin(dLon/2.0);
haverDLon *= haverDLon; // squared
float lat1 = p1.latF();
lat1 *= RAD_PER_DEG;
float lat2 = p2.latF();
lat2 *= RAD_PER_DEG;
float a = haverDLat + cos(lat1) * cos(lat2) * haverDLon;
float c = 2 * atan2( sqrt(a), sqrt(1-a) );
return c;
} // DistanceRadians
//---------------------------------------------------------------------
float Location_t::EquirectDistanceRadians
( const Location_t & p1, const Location_t & p2 )
{
// Equirectangular calculation from http://www.movable-type.co.uk/scripts/latlong.html
float dLat = (p2.lat() - p1.lat()) * RAD_PER_DEG * LOC_SCALE;
float dLon = safeDLon( p2.lon(), p1.lon() ) * RAD_PER_DEG * LOC_SCALE;
float x = dLon * cos( p1.lat() * RAD_PER_DEG * LOC_SCALE + dLat/2 );
return sqrt( x*x + dLat*dLat );
} // EquirectDistanceRadians
//---------------------------------------------------------------------
float Location_t::BearingTo( const Location_t & p1, const Location_t & p2 )
{
int32_t dLonL = safeDLon( p2.lon(), p1.lon() );
float dLon = dLonL * RAD_PER_DEG * LOC_SCALE;
int32_t dLatL = p2.lat() - p1.lat();
float lat1 = p1.lat() * RAD_PER_DEG * LOC_SCALE;
float cosLat1 = cos( lat1 );
float x, y, bearing;
if ((abs(dLatL)+abs(dLonL)) < 1000) {
// VERY close together. Just use equirect approximation with precise integers.
x = dLonL * cosLat1;
y = dLatL;
bearing = PI/2.0 - atan2(y, x);
} else {
float lat2 = p2.lat() * RAD_PER_DEG * LOC_SCALE;
float cosLat2 = cos(lat2);
y = sin(dLon) * cosLat2;
x = cosLat1 * sin(lat2) - sin(lat1) * cosLat2 * cos(dLon);
bearing = atan2(y, x);
}
if (bearing < 0.0)
bearing += TWO_PI;
return bearing;
} // BearingTo
//---------------------------------------------------------------------
void Location_t::OffsetBy( float distR, float bearingR )
{
float lat1 = lat() * RAD_PER_DEG * LOC_SCALE;
float newLat = asin( sin(lat1)*cos(distR) +
cos(lat1)*sin(distR)*cos(bearingR) );
float dLon = atan2( sin(bearingR)*sin(distR)*cos(lat1),
cos(distR)-sin(lat1)*sin(newLat));
_lat = (newLat / (RAD_PER_DEG * LOC_SCALE));
_lon += (dLon / (RAD_PER_DEG * LOC_SCALE));
} // OffsetBy

126
lib/NeoGPS/src/Location.h Normal file
View File

@ -0,0 +1,126 @@
#ifndef NEOGPS_LOCATION_H
#define NEOGPS_LOCATION_H
#include "NeoGPS_cfg.h"
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
class NMEAGPS;
namespace NeoGPS {
class Location_t
{
public:
CONST_CLASS_DATA float LOC_SCALE = 1.0e-7;
Location_t() {}
Location_t( int32_t lat, int32_t lon )
: _lat(lat), _lon(lon)
{}
Location_t( float lat, float lon )
: _lat(lat / LOC_SCALE), _lon(lon / LOC_SCALE)
{}
Location_t( double lat, double lon )
: _lat(lat / LOC_SCALE), _lon(lon / LOC_SCALE)
{}
int32_t lat() const { return _lat; };
void lat( int32_t l ) { _lat = l; };
float latF() const { return ((float) lat()) * LOC_SCALE; };
void latF( float v ) { _lat = v / LOC_SCALE; };
int32_t lon() const { return _lon; };
void lon( int32_t l ) { _lon = l; };
float lonF() const { return ((float) lon()) * LOC_SCALE; };
void lonF( float v ) { _lon = v / LOC_SCALE; };
void init() { _lat = _lon = 0; };
CONST_CLASS_DATA float EARTH_RADIUS_KM = 6371.0088;
CONST_CLASS_DATA float RAD_PER_DEG = PI / 180.0;
CONST_CLASS_DATA float DEG_PER_RAD = 180.0 / PI;
CONST_CLASS_DATA float MI_PER_KM = 0.621371;
//-----------------------------------
// Distance calculations
static float DistanceKm( const Location_t & p1, const Location_t & p2 )
{
return DistanceRadians( p1, p2 ) * EARTH_RADIUS_KM;
}
float DistanceKm( const Location_t & p2 ) const
{ return DistanceKm( *this, p2 ); }
static float DistanceMiles( const Location_t & p1, const Location_t & p2 )
{
return DistanceRadians( p1, p2 ) * EARTH_RADIUS_KM * MI_PER_KM;
}
float DistanceMiles( const Location_t & p2 ) const
{ return DistanceMiles( *this, p2 ); }
static float DistanceRadians( const Location_t & p1, const Location_t & p2 );
float DistanceRadians( const Location_t & p2 ) const
{ return DistanceRadians( *this, p2 ); }
static float EquirectDistanceRadians
( const Location_t & p1, const Location_t & p2 );
float EquirectDistanceRadians( const Location_t & p2 ) const
{ return EquirectDistanceRadians( *this, p2 ); }
static float EquirectDistanceKm( const Location_t & p1, const Location_t & p2 )
{
return EquirectDistanceRadians( p1, p2 ) * EARTH_RADIUS_KM;
}
float EquirectDistanceKm( const Location_t & p2 ) const
{ return EquirectDistanceKm( *this, p2 ); }
static float EquirectDistanceMiles( const Location_t & p1, const Location_t & p2 )
{
return EquirectDistanceRadians( p1, p2 ) * EARTH_RADIUS_KM * MI_PER_KM;
}
float EquirectDistanceMiles( const Location_t & p2 ) const
{ return EquirectDistanceMiles( *this, p2 ); }
//-----------------------------------
// Bearing calculations
static float BearingTo( const Location_t & p1, const Location_t & p2 ); // radians
float BearingTo( const Location_t & p2 ) const // radians
{ return BearingTo( *this, p2 ); }
static float BearingToDegrees( const Location_t & p1, const Location_t & p2 )
{ return BearingTo( p1, p2 ) * DEG_PER_RAD; }
float BearingToDegrees( const Location_t & p2 ) const // radians
{ return BearingToDegrees( *this, p2 ); }
//-----------------------------------
// Offset a location (note distance is in radians, not degrees)
void OffsetBy( float distR, float bearingR );
//private: //---------------------------------------
friend class NMEAGPS; // This does not work?!?
int32_t _lat; // degrees * 1e7, negative is South
int32_t _lon; // degrees * 1e7, negative is West
} NEOGPS_PACKED;
} // NeoGPS
#endif

2109
lib/NeoGPS/src/NMEAGPS.cpp Normal file

File diff suppressed because it is too large Load Diff

341
lib/NeoGPS/src/NMEAGPS.h Normal file
View File

@ -0,0 +1,341 @@
#ifndef NMEAGPS_H
#define NMEAGPS_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "CosaCompat.h"
#include <Arduino.h>
#ifdef __AVR__
#include <avr/interrupt.h>
#endif
#include "GPSfix.h"
#include "NMEAGPS_cfg.h"
//------------------------------------------------------
//
// NMEA 0183 Parser for generic GPS Modules.
//
// As bytes are received from the device, they affect the
// internal FSM and set various members of the current /fix/.
// As multiple sentences are received, they are (optionally)
// merged into a single fix. When the last sentence in a time
// interval (usually 1 second) is received, the fix is stored
// in the (optional) buffer of fixes.
//
// Only these NMEA messages are parsed:
// GGA, GLL, GSA, GST, GSV, RMC, VTG, and ZDA.
class NMEAGPS
{
NMEAGPS & operator =( const NMEAGPS & );
NMEAGPS( const NMEAGPS & );
public:
NMEAGPS();
//.......................................................................
// NMEA standard message types (aka "sentences")
enum nmea_msg_t {
NMEA_UNKNOWN,
#if defined(NMEAGPS_PARSE_GGA) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_GGA,
#endif
#if defined(NMEAGPS_PARSE_GLL) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_GLL,
#endif
#if defined(NMEAGPS_PARSE_GSA) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_GSA,
#endif
#if defined(NMEAGPS_PARSE_GST) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_GST,
#endif
#if defined(NMEAGPS_PARSE_GSV) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_GSV,
#endif
#if defined(NMEAGPS_PARSE_RMC) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_RMC,
#endif
#if defined(NMEAGPS_PARSE_VTG) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_VTG,
#endif
#if defined(NMEAGPS_PARSE_ZDA) | defined(NMEAGPS_RECOGNIZE_ALL)
NMEA_ZDA,
#endif
NMEAMSG_END // a bookend that tells how many enums there were
};
CONST_CLASS_DATA nmea_msg_t NMEA_FIRST_MSG = (nmea_msg_t) (NMEA_UNKNOWN+1);
CONST_CLASS_DATA nmea_msg_t NMEA_LAST_MSG = (nmea_msg_t) (NMEAMSG_END-1);
//=======================================================================
// FIX-ORIENTED methods: available, read, overrun and handle
//=======================================================================
// The typical sketch should have something like this in loop():
//
// while (gps.available( Serial1 )) {
// gps_fix fix = gps.read();
// if (fix.valid.location) {
// ...
// }
// }
//.......................................................................
// The available(...) functions return the number of *fixes* that
// are available to be "read" from the fix buffer. The GPS port
// object is passed in so a char can be read if port.available().
uint8_t available( Stream & port )
{
if (processing_style == PS_POLLING)
while (port.available())
handle( port.read() );
return _available();
}
uint8_t available() const volatile { return _available(); };
//.......................................................................
// Return the next available fix. When no more fixes
// are available, it returns an empty fix.
const gps_fix read();
//.......................................................................
// The OVERRUN flag is set whenever a fix is not read by the time
// the next update interval starts. You must clear it when you
// detect the condition.
bool overrun() const { return _overrun; }
void overrun( bool val ) { _overrun = val; }
//.......................................................................
// As characters are processed, they can be categorized as
// INVALID (not part of this protocol), OK (accepted),
// or COMPLETED (end-of-message).
enum decode_t { DECODE_CHR_INVALID, DECODE_CHR_OK, DECODE_COMPLETED };
//.......................................................................
// Process one character, possibly saving a buffered fix.
// It implements merging and coherency.
// This can be called from an ISR.
decode_t handle( uint8_t c );
//=======================================================================
// CHARACTER-ORIENTED methods: decode, fix and is_safe
//=======================================================================
//
// *** MOST APPLICATIONS SHOULD USE THE FIX-ORIENTED METHODS ***
//
// Using `decode` is only necessary if you want finer control
// on how fix information is filtered and merged.
//
// Process one character of an NMEA GPS sentence. The internal state
// machine tracks what part of the sentence has been received. As the
// sentence is received, members of the /fix/ structure are updated.
// This character-oriented method *does not* buffer any fixes, and
// /read()/ will always return an empty fix.
//
// @return DECODE_COMPLETED when a sentence has been completely received.
NMEAGPS_VIRTUAL decode_t decode( char c );
//.......................................................................
// Current fix accessor.
// *** MOST APPLICATIONS SHOULD USE read() TO GET THE CURRENT FIX ***
// /fix/ will be constantly changing as characters are received.
//
// For example, fix().longitude() may return nonsense data if
// characters for that field are currently being processed in /decode/.
gps_fix & fix() { return m_fix; };
// NOTE: /is_safe/ *must* be checked before accessing members of /fix/.
// If you need access to the current /fix/ at any time, you should
// use the FIX-ORIENTED methods.
//.......................................................................
// Determine whether the members of /fix/ are "currently" safe.
// It will return true when a complete sentence and the CRC characters
// have been received (or after a CR if no CRC is present).
// It will return false after a new sentence starts.
bool is_safe() const volatile { return (rxState == NMEA_IDLE); }
// NOTE: When INTERRUPT_PROCESSING is enabled, is_safe()
// and fix() could change at any time (i.e., they should be
// considered /volatile/).
//=======================================================================
// DATA MEMBER accessors and mutators
//=======================================================================
//.......................................................................
// Convert a nmea_msg_t to a PROGMEM string.
// Useful for printing the sentence type instead of a number.
// This can return "UNK" if the message is not a valid number.
const __FlashStringHelper *string_for( nmea_msg_t msg ) const;
//.......................................................................
// Most recent NMEA sentence type received.
nmea_msg_t nmeaMessage NEOGPS_BF(8);
//.......................................................................
// Storage for Talker and Manufacturer IDs
#ifdef NMEAGPS_SAVE_TALKER_ID
char talker_id[2];
#endif
#ifdef NMEAGPS_SAVE_MFR_ID
char mfr_id[3];
#endif
//.......................................................................
// Various parsing statistics
#ifdef NMEAGPS_STATS
struct statistics_t {
uint32_t ok; // count of successfully parsed sentences
uint32_t errors; // NMEA checksum or other message errors
uint32_t chars;
void init()
{
ok = 0L;
errors = 0L;
chars = 0L;
}
} statistics;
#endif
//.......................................................................
// SATELLITE VIEW array
#ifdef NMEAGPS_PARSE_SATELLITES
struct satellite_view_t
{
uint8_t id;
char talker_id;
#ifdef NMEAGPS_PARSE_SATELLITE_INFO
uint8_t elevation; // 0..99 deg
uint16_t azimuth; // 0..359 deg
uint8_t snr NEOGPS_BF(7); // 0..99 dBHz
bool tracked NEOGPS_BF(1);
#endif
} NEOGPS_PACKED;
satellite_view_t satellites[ NMEAGPS_MAX_SATELLITES ];
uint8_t sat_count; // in the above array
bool satellites_valid() const { return (sat_count >= m_fix.satellites); }
#endif
//.......................................................................
// Reset the parsing process.
// This is used internally after a CS error, or could be used
// externally to abort processing if it has been too long
// since any data was received.
void reset()
{
rxState = NMEA_IDLE;
}
//=======================================================================
// CORRELATING Arduino micros() WITH UTC.
//=======================================================================
#if defined(NMEAGPS_TIMESTAMP_FROM_PPS) | \
defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL)
protected:
uint32_t _UTCsecondStart;
#if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) & \
( defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME) )
uint32_t _IntervalStart; // quiet time just ended
#endif
public:
// The micros() value when the current UTC second started
uint32_t UTCsecondStart() const
{ lock();
uint32_t ret = _UTCsecondStart;
unlock();
return ret;
};
void UTCsecondStart( uint32_t us ) { _UTCsecondStart = us; };
// The elapsed time since the start of the current UTC second
uint32_t UTCus() const { return micros() - UTCsecondStart(); };
uint32_t UTCms() const { return UTCus() / 1000UL; };
// If you have attached a Pin Change interrupt routine to the PPS pin:
//
// const int PPSpin = 5;
// void PPSisr() { gps.UTCsecondStart( micros() ); };
// void setup()
// {
// attachInterrupt( digitalPinToInterrupt(PPSpin), PPSisr, RISING );
// }
//
// If you are using an Input Capture pin, calculate the elapsed
// microseconds since the capture time (based on the TIMER
// frequency):
//
// void savePPSus() // called as an ISR or from a test in loop
// {
// uint32_t elapsedUS = (currentCount - captureCount) * countUS;
// gps.UTCsecondStart( micros() - elapsedUS );
// }
#endif
//=======================================================================
// COMMUNICATING WITH THE GPS DEVICE: poll, send and send_P
//=======================================================================
//.......................................................................
// Request the specified NMEA sentence. Not all devices will respond.
static void poll( Stream *device, nmea_msg_t msg );
//.......................................................................
// Send a message to the GPS device.
// The '$' is optional, and the '*' and CS will be added automatically.
static void send( Stream *device, const char *msg );
static void send_P( Stream *device, const __FlashStringHelper *msg );
#include "NMEAGPSprivate.h"
} NEOGPS_PACKED;
#endif

View File

@ -0,0 +1,342 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "GPSfix_cfg.h"
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
#define NMEAGPS_PARSE_GSA
#define NMEAGPS_PARSE_GSV
#define NMEAGPS_PARSE_GST
//#define NMEAGPS_PARSE_RMC
#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GST
// NOTE: For PUBX-only, PGRM and UBX configs, use
// (NMEAGPS::nmea_msg_t)(NMEAGPS::NMEA_LAST_MSG+1)
// Otherwise, use one of the standard NMEA messages:
// NMEAGPS::NMEA_RMC
//
// ==> CONFIRM THIS WITH NMEAorder.INO <==
//
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Choose how multiple sentences are merged into a fix:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// Nothing is done to the fix at the beginning of every sentence...
#define NMEAGPS_INIT_FIX(m)
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating (not IMPLICIT), invalidate the entire fix
// at the beginning of every sentence...
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Define how fixes are dropped when the FIFO is full.
// true = the oldest fix will be dropped, and the new fix will be saved.
// false = the new fix will be dropped, and all old fixes will be saved.
#define NMEAGPS_KEEP_NEWEST_FIXES true
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
#define NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (72)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Becase the NMEA checksum is not very good at error detection, you can
// choose to enable additional validity checks. This trades a little more
// code and execution time for more reliability.
//
// Validation at the character level is a syntactic check only. For
// example, integer fields must contain characters in the range 0..9,
// latitude hemisphere letters can be 'N' or 'S'. Characters that are not
// valid for a particular field will cause the entire sentence to be
// rejected as an error, *regardless* of whether the checksum would pass.
#define NMEAGPS_VALIDATE_CHARS false
// Validation at the field level is a semantic check. For
// example, latitude degrees must be in the range -90..+90.
// Values that are not valid for a particular field will cause the
// entire sentence to be rejected as an error, *regardless* of whether the
// checksum would pass.
#define NMEAGPS_VALIDATE_FIELDS false
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
//------------------------------------------------------
// If you need to know the exact UTC time at *any* time,
// not just after a fix arrives, you must calculate the
// offset between the Arduino micros() clock and the UTC
// time in a received fix. There are two ways to do this:
//
// 1) When the GPS quiet time ends and the new update interval begins.
// The timestamp will be set when the first character (the '$') of
// the new batch of sentences arrives from the GPS device. This is fairly
// accurate, but it will be delayed from the PPS edge by the GPS device's
// fix calculation time (usually ~100us). There is very little variance
// in this calculation time (usually < 30us), so all timestamps are
// delayed by a nearly-constant amount.
//
// NOTE: At update rates higher than 1Hz, the updates may arrive with
// some increasing variance.
//#define NMEAGPS_TIMESTAMP_FROM_INTERVAL
// 2) From the PPS pin of the GPS module. It is up to the application
// developer to decide how to capture that event. For example, you could:
//
// a) simply poll for it in loop and call UTCsecondStart(micros());
// b) use attachInterrupt to call a Pin Change Interrupt ISR to save
// the micros() at the time of the interrupt (see NMEAGPS.h), or
// c) connect the PPS to an Input Capture pin. Set the
// associated TIMER frequency, calculate the elapsed time
// since the PPS edge, and add that to the current micros().
//#define NMEAGPS_TIMESTAMP_FROM_PPS
#if defined( NMEAGPS_TIMESTAMP_FROM_INTERVAL ) & \
defined( NMEAGPS_TIMESTAMP_FROM_PPS )
#error You cannot enable both TIMESTAMP_FROM_INTERVAL and PPS in NMEAGPS_cfg.h!
#endif
#endif

View File

@ -0,0 +1,409 @@
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
protected:
//.......................................................................
// Table entry for NMEA sentence type string and its offset
// in enumerated nmea_msg_t. Proprietary sentences can be implemented
// in derived classes by adding a second table. Additional tables
// can be singly-linked through the /previous/ member. The instantiated
// class's table is the head, and should be returned by the derived
// /msg_table/ function. Tables should be sorted alphabetically.
struct msg_table_t {
uint8_t offset; // nmea_msg_t enum starting value
const msg_table_t *previous;
uint8_t size; // number of entries in table array
const char * const *table; // array of NMEA sentence strings
};
static const msg_table_t nmea_msg_table __PROGMEM;
NMEAGPS_VIRTUAL const msg_table_t *msg_table() const
{ return &nmea_msg_table; };
//.......................................................................
// These virtual methods can accept or reject
// the talker ID (for standard sentences) or
// the manufacturer ID (for proprietary sentences).
// The default is to accept *all* IDs.
// Override them if you want to reject certain IDs, or you want
// to handle COMPLETED sentences from certain IDs differently.
#ifdef NMEAGPS_PARSE_TALKER_ID
NMEAGPS_VIRTUAL bool parseTalkerID( char ) { return true; };
#endif
#ifdef NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_MFR_ID
NMEAGPS_VIRTUAL bool parseMfrID( char ) { return true; };
#endif
#endif
public:
//.......................................................................
// Set all parsed data to initial values.
void data_init()
{
fix().init();
#ifdef NMEAGPS_PARSE_SATELLITES
sat_count = 0;
#endif
}
//.......................................................................
enum merging_t { NO_MERGING, EXPLICIT_MERGING, IMPLICIT_MERGING };
static const merging_t
merging = NMEAGPS_MERGING; // see NMEAGPS_cfg.h
enum processing_style_t { PS_POLLING, PS_INTERRUPT };
static const processing_style_t
processing_style = NMEAGPS_PROCESSING_STYLE; // see NMEAGPS_cfg.h
static const bool keepNewestFixes = NMEAGPS_KEEP_NEWEST_FIXES;
static const bool validateChars () { return NMEAGPS_VALIDATE_CHARS; }
static const bool validateFields() { return NMEAGPS_VALIDATE_FIELDS; }
//.......................................................................
// Control access to this object. This preserves atomicity when
// the processing style is interrupt-driven.
void lock() const
{
if (processing_style == PS_INTERRUPT)
noInterrupts();
}
void unlock() const
{
if (processing_style == PS_INTERRUPT)
interrupts();
}
protected:
//=======================================================================
// PARSING FINITE-STATE MACHINE
//=======================================================================
// Current fix
gps_fix m_fix;
// Current parser state
uint8_t crc; // accumulated CRC in the sentence
uint8_t fieldIndex; // index of current field in the sentence
uint8_t chrCount; // index of current character in current field
uint8_t decimal; // digits received after the decimal point
struct {
bool negative NEOGPS_BF(1); // field had a leading '-'
bool _comma_needed NEOGPS_BF(1); // field needs a comma to finish parsing
bool group_valid NEOGPS_BF(1); // multi-field group valid
bool _overrun NEOGPS_BF(1); // an entire fix was dropped
bool _intervalComplete NEOGPS_BF(1); // automatically set after LAST received
#if (NMEAGPS_FIX_MAX == 0)
bool _fixesAvailable NEOGPS_BF(1);
#endif
#ifdef NMEAGPS_PARSE_PROPRIETARY
bool proprietary NEOGPS_BF(1); // receiving proprietary message
#endif
} NEOGPS_PACKED;
#ifdef NMEAGPS_PARSING_SCRATCHPAD
union {
uint32_t U4;
uint16_t U2[2];
uint8_t U1[4];
} scratchpad;
#endif
bool comma_needed()
{
#ifdef NMEAGPS_COMMA_NEEDED
return _comma_needed;
#else
return false;
#endif
}
void comma_needed( bool value )
{
#ifdef NMEAGPS_COMMA_NEEDED
_comma_needed = value;
#endif
}
// Internal FSM states
enum rxState_t {
NMEA_IDLE, // Waiting for initial '$'
NMEA_RECEIVING_HEADER, // Parsing sentence type field
NMEA_RECEIVING_DATA, // Parsing fields up to the terminating '*'
NMEA_RECEIVING_CRC // Receiving two-byte transmitted CRC
};
CONST_CLASS_DATA uint8_t NMEA_FIRST_STATE = NMEA_IDLE;
CONST_CLASS_DATA uint8_t NMEA_LAST_STATE = NMEA_RECEIVING_CRC;
rxState_t rxState NEOGPS_BF(8);
//.......................................................................
uint8_t _available() const volatile { return _fixesAvailable; };
//.......................................................................
// Buffered fixes.
#if (NMEAGPS_FIX_MAX > 0)
gps_fix buffer[ NMEAGPS_FIX_MAX ]; // could be empty, see NMEAGPS_cfg.h
uint8_t _fixesAvailable;
uint8_t _firstFix;
uint8_t _currentFix;
#endif
//.......................................................................
// Indicate that the next sentence should initialize the internal data.
// This is useful for coherency or custom filtering.
bool intervalComplete() const { return _intervalComplete; }
void intervalComplete( bool val ) { _intervalComplete = val; }
//.......................................................................
// Identify when an update interval is completed, according to the
// most recently-received sentence. In this base class, it just
// looks at the nmeaMessage member. Derived classes may have
// more complex, specific conditions.
NMEAGPS_VIRTUAL bool intervalCompleted() const
{ return (nmeaMessage == LAST_SENTENCE_IN_INTERVAL); }
// see NMEAGPS_cfg.h
//.......................................................................
// When a fix has been fully assembled from a batch of sentences, as
// determined by the configured merging technique and ending with the
// LAST_SENTENCE_IN_INTERVAL, it is stored in the (optional) buffer
// of fixes. They are removed with /read()/.
void storeFix();
//=======================================================================
// PARSING METHODS
//=======================================================================
//.......................................................................
// Try to recognize an NMEA sentence type, after the IDs have been accepted.
decode_t parseCommand( char c );
decode_t parseCommand( const msg_table_t *msgs, uint8_t cmdCount, char c );
//.......................................................................
// Parse various NMEA sentences
bool parseGGA( char chr );
bool parseGLL( char chr );
bool parseGSA( char chr );
bool parseGST( char chr );
bool parseGSV( char chr );
bool parseRMC( char chr );
bool parseVTG( char chr );
bool parseZDA( char chr );
//.......................................................................
// Depending on the NMEA sentence type, parse one field of an expected type.
NMEAGPS_VIRTUAL bool parseField( char chr );
//.......................................................................
// Parse the primary NMEA field types into /fix/ members.
bool parseFix ( char chr ); // aka STATUS or MODE
bool parseTime ( char chr );
bool parseDDMMYY ( char chr );
bool parseLat ( char chr );
bool parseNS ( char chr );
bool parseLon ( char chr );
bool parseEW ( char chr );
bool parseSpeed ( char chr );
bool parseSpeedKph ( char chr );
bool parseHeading ( char chr );
bool parseAlt ( char chr );
bool parseGeoidHeight( char chr );
bool parseHDOP ( char chr );
bool parseVDOP ( char chr );
bool parsePDOP ( char chr );
bool parse_lat_err ( char chr );
bool parse_lon_err ( char chr );
bool parse_alt_err ( char chr );
bool parseSatellites ( char chr );
// Helper macro for parsing the 4 consecutive fields of a location
#define PARSE_LOC(i) case i: return parseLat( chr );\
case i+1: return parseNS ( chr ); \
case i+2: return parseLon( chr ); \
case i+3: return parseEW ( chr );
//.......................................................................
// Parse floating-point numbers into a /whole_frac/
// @return true when the value is fully populated.
bool parseFloat( gps_fix::whole_frac & val, char chr, uint8_t max_decimal );
//.......................................................................
// Parse floating-point numbers into a uint16_t
// @return true when the value is fully populated.
bool parseFloat( uint16_t & val, char chr, uint8_t max_decimal );
//.......................................................................
// Parse NMEA lat/lon dddmm.mmmm degrees
bool parseDDDMM
(
#if defined( GPS_FIX_LOCATION )
int32_t & val,
#endif
#if defined( GPS_FIX_LOCATION_DMS )
DMS_t & dms,
#endif
char chr
);
//.......................................................................
// Parse integer into 8-bit int
// @return true when non-empty value
bool parseInt( uint8_t &val, uint8_t chr )
{
negative = false;
bool is_comma = (chr == ',');
if (chrCount == 0) {
if (is_comma)
return false; // empty field!
if (((validateChars() || validateFields()) && (chr == '-')) ||
(validateChars() && !isdigit( chr )))
sentenceInvalid();
else
val = (chr - '0');
} else if (!is_comma) {
if (validateChars() && !isdigit( chr ))
sentenceInvalid();
else
val = (val*10) + (chr - '0');
}
return true;
}
//.......................................................................
// Parse integer into signed 8-bit int
// @return true when non-empty value
bool parseInt( int8_t &val, uint8_t chr )
{
bool is_comma = (chr == ',');
if (chrCount == 0) {
if (is_comma)
return false; // empty field!
negative = (chr == '-');
if (negative) {
comma_needed( true ); // to negate
val = 0;
} else if (validateChars() && !isdigit( chr )) {
sentenceInvalid();
} else {
val = (chr - '0');
}
} else if (!is_comma) {
val = (val*10) + (chr - '0');
} else if (negative) {
val = -val;
}
return true;
}
//.......................................................................
// Parse integer into 16-bit int
// @return true when non-empty value
bool parseInt( uint16_t &val, uint8_t chr )
{
negative = false;
bool is_comma = (chr == ',');
if (chrCount == 0) {
if (is_comma)
return false; // empty field!
if (((validateChars() || validateFields()) && (chr == '-')) ||
(validateChars() && !isdigit( chr )))
sentenceInvalid();
else
val = (chr - '0');
} else if (!is_comma) {
if (validateChars() && !isdigit( chr ))
sentenceInvalid();
else
val = (val*10) + (chr - '0');
}
return true;
}
//.......................................................................
// Parse integer into 32-bit int
// @return true when non-empty value
bool parseInt( uint32_t &val, uint8_t chr )
{
negative = false;
bool is_comma = (chr == ',');
if (chrCount == 0) {
if (is_comma)
return false; // empty field!
if (((validateChars() || validateFields()) && (chr == '-')) ||
(validateChars() && !isdigit( chr )))
sentenceInvalid();
else
val = (chr - '0');
} else if (!is_comma) {
if (validateChars() && !isdigit( chr ))
sentenceInvalid();
else
val = (val*10) + (chr - '0');
}
return true;
}
private:
void sentenceBegin ();
void sentenceOk ();
void sentenceInvalid ();
void sentenceUnrecognized();
void headerReceived ();

126
lib/NeoGPS/src/NeoGPS_cfg.h Normal file
View File

@ -0,0 +1,126 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------
// Enable/disable packed data structures.
//
// Enabling packed data structures will use two less-portable language
// features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
//
// Disabling packed data structures will be very portable to other
// platforms. NeoGPS configurations will use slightly more RAM, and on
// 8-bit AVRs, the speed is slightly slower, and the code is slightly
// larger. There may be no choice but to disable packing on processors
// that do not support packed structures.
//
// There may also be compiler-specific switches that affect packing and the
// code which accesses packed members. YMMV.
#include <Arduino.h>
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
//------------------------------------------------------------------------
// Accommodate C++ compiler and IDE changes.
//
// Declaring constants as class data instead of instance data helps avoid
// collisions with #define names, and allows the compiler to perform more
// checks on their usage.
//
// Until C++ 10 and IDE 1.6.8, initialized class data constants
// were declared like this:
//
// static const <valued types> = <constant-value>;
//
// Now, non-simple types (e.g., float) must be declared as
//
// static constexpr <nonsimple-types> = <expression-treated-as-const>;
//
// The good news is that this allows the compiler to optimize out an
// expression that is "promised" to be "evaluatable" as a constant.
// The bad news is that it introduces a new language keyword, and the old
// code raises an error.
//
// TODO: Evaluate the requirement for the "static" keyword.
// TODO: Evaluate using a C++ version preprocessor symbol for the #if.
// #if __cplusplus >= 201103L (from XBee.h)
//
// The CONST_CLASS_DATA define will expand to the appropriate keywords.
//
#if ( \
(ARDUINO < 10606) | \
((10700 <= ARDUINO) & (ARDUINO <= 10799 )) | \
((107000 <= ARDUINO) & (ARDUINO <= 107999)) \
) \
& \
!defined(ESP8266) // PlatformIO Pull Request #82
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
//------------------------------------------------------------------------
// The PROGMEM definitions are not correct for Zero, MKR1000 and
// earlier versions of Teensy boards
#if defined(ARDUINO_SAMD_MKRZERO) | \
defined(ARDUINO_SAMD_ZERO) | \
defined(ARDUINO_SAM_DUE) | \
defined(ARDUINO_ARCH_ARC32) | \
defined(__TC27XX__) | \
defined(NRF52) | \
(defined(TEENSYDUINO) && (TEENSYDUINO < 139))
#undef pgm_read_ptr
#define pgm_read_ptr(addr) (*(const void **)(addr))
#endif
#endif

208
lib/NeoGPS/src/NeoTime.cpp Normal file
View File

@ -0,0 +1,208 @@
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "NeoTime.h"
// For strtoul declaration
#include <stdlib.h>
#include <Print.h>
Print & operator<<( Print& outs, const NeoGPS::time_t& t )
{
outs.print( t.full_year( t.year ) );
outs.write( '-' );
if (t.month < 10) outs.write( '0' );
outs.print( t.month );
outs.write( '-' );
if (t.date < 10) outs.write( '0' );
outs.print( t.date );
outs.write( ' ' );
if (t.hours < 10) outs.write( '0' );
outs.print( t.hours );
outs.write( ':' );
if (t.minutes < 10) outs.write( '0' );
outs.print( t.minutes );
outs.write( ':' );
if (t.seconds < 10) outs.write( '0' );
outs.print( t.seconds );
return outs;
}
using NeoGPS::time_t;
bool time_t::parse(str_P s)
{
static size_t BUF_MAX = 32;
char buf[BUF_MAX];
strcpy_P(buf, s);
char* sp = &buf[0];
uint16_t value = strtoul(sp, &sp, 10);
if (*sp != '-') return false;
year = value % 100;
if (full_year() != value) return false;
value = strtoul(sp + 1, &sp, 10);
if (*sp != '-') return false;
month = value;
value = strtoul(sp + 1, &sp, 10);
if (*sp != ' ') return false;
date = value;
value = strtoul(sp + 1, &sp, 10);
if (*sp != ':') return false;
hours = value;
value = strtoul(sp + 1, &sp, 10);
if (*sp != ':') return false;
minutes = value;
value = strtoul(sp + 1, &sp, 10);
if (*sp != 0) return false;
seconds = value;
return (is_valid());
}
#ifdef TIME_EPOCH_MODIFIABLE
uint16_t time_t::s_epoch_year = Y2K_EPOCH_YEAR;
uint8_t time_t::s_epoch_offset = 0;
uint8_t time_t::s_epoch_weekday = Y2K_EPOCH_WEEKDAY;
uint8_t time_t::s_pivot_year = 0;
#endif
const uint8_t time_t::days_in[] __PROGMEM = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
time_t::time_t(clock_t c)
{
uint16_t dayno = c / SECONDS_PER_DAY;
c -= dayno * (uint32_t) SECONDS_PER_DAY;
day = weekday_for(dayno);
uint16_t y = epoch_year();
for (;;) {
uint16_t days = days_per( y );
if (dayno < days) break;
dayno -= days;
y++;
}
bool leap_year = is_leap(y);
y -= epoch_year();
y += epoch_offset();
while (y > 100)
y -= 100;
year = y;
month = 1;
for (;;) {
uint8_t days = pgm_read_byte(&days_in[month]);
if (leap_year && (month == 2)) days++;
if (dayno < days) break;
dayno -= days;
month++;
}
date = dayno + 1;
hours = c / SECONDS_PER_HOUR;
uint16_t c_ms;
if (hours < 18) // save 16uS
c_ms = (uint16_t) c - (hours * (uint16_t) SECONDS_PER_HOUR);
else
c_ms = c - (hours * (uint32_t) SECONDS_PER_HOUR);
minutes = c_ms / SECONDS_PER_MINUTE;
seconds = c_ms - (minutes * SECONDS_PER_MINUTE);
}
void time_t::init()
{
seconds =
hours =
minutes = 0;
date = 1;
month = 1;
year = epoch_year() % 100;
day = epoch_weekday();
}
time_t::operator clock_t() const
{
clock_t c = days() * SECONDS_PER_DAY;
if (hours < 18)
c += hours * (uint16_t) SECONDS_PER_HOUR;
else
c += hours * (uint32_t) SECONDS_PER_HOUR;
c += minutes * (uint16_t) SECONDS_PER_MINUTE;
c += seconds;
return (c);
}
uint16_t time_t::days() const
{
uint16_t day_count = day_of_year();
uint16_t y = full_year();
while (y-- > epoch_year())
day_count += days_per(y);
return (day_count);
}
uint16_t time_t::day_of_year() const
{
uint16_t dayno = date - 1;
bool leap_year = is_leap();
for (uint8_t m = 1; m < month; m++) {
dayno += pgm_read_byte(&days_in[m]);
if (leap_year && (m == 2)) dayno++;
}
return (dayno);
}
#ifdef TIME_EPOCH_MODIFIABLE
void time_t::use_fastest_epoch()
{
// Figure out when we were compiled and use the year for a really
// fast epoch_year. Format "MMM DD YYYY"
const char* compile_date = (const char *) PSTR(__DATE__);
uint16_t compile_year = 0;
for (uint8_t i = 7; i < 11; i++)
compile_year = compile_year*10 + (pgm_read_byte(&compile_date[i]) - '0');
// Temporarily set a Y2K epoch so we can figure out the day for
// January 1 of this year
epoch_year ( Y2K_EPOCH_YEAR );
epoch_weekday ( Y2K_EPOCH_WEEKDAY );
time_t this_year(0);
this_year.year = compile_year % 100;
this_year.set_day();
uint8_t compile_weekday = this_year.day;
epoch_year ( compile_year );
epoch_weekday( compile_weekday );
pivot_year ( this_year.year );
}
#endif

305
lib/NeoGPS/src/NeoTime.h Normal file
View File

@ -0,0 +1,305 @@
#ifndef TIME_H
#define TIME_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "NeoGPS_cfg.h"
#include "CosaCompat.h"
namespace NeoGPS {
//------------------------------------------------------
// Enable/disable run-time modification of the epoch.
// If this is defined, epoch mutators are available.
// If this is not defined, the epoch is a hard-coded constant.
// Only epoch accessors are available.
//#define TIME_EPOCH_MODIFIABLE
/**
* Number of seconds elapsed since January 1 of the Epoch Year,
* 00:00:00 +0000 (UTC).
*/
typedef uint32_t clock_t;
const uint8_t SECONDS_PER_MINUTE = 60;
const uint8_t MINUTES_PER_HOUR = 60;
const uint16_t SECONDS_PER_HOUR = (uint16_t) SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
const uint8_t HOURS_PER_DAY = 24;
const uint32_t SECONDS_PER_DAY = (uint32_t) SECONDS_PER_HOUR * HOURS_PER_DAY;
const uint8_t DAYS_PER_WEEK = 7;
/**
* Common date/time structure
*/
struct time_t {
enum weekday_t {
SUNDAY = 1,
MONDAY = 2,
TUESDAY = 3,
WEDNESDAY = 4,
THURSDAY = 5,
FRIDAY = 6,
SATURDAY = 7
};
// NTP epoch year and weekday (Monday)
static const uint16_t NTP_EPOCH_YEAR = 1900;
static const uint8_t NTP_EPOCH_WEEKDAY = MONDAY;
// POSIX epoch year and weekday (Thursday)
static const uint16_t POSIX_EPOCH_YEAR = 1970;
static const uint8_t POSIX_EPOCH_WEEKDAY = THURSDAY;
// Y2K epoch year and weekday (Saturday)
static const uint16_t Y2K_EPOCH_YEAR = 2000;
static const uint8_t Y2K_EPOCH_WEEKDAY = SATURDAY;
uint8_t seconds; //!< 00-59
uint8_t minutes; //!< 00-59
uint8_t hours; //!< 00-23
uint8_t day; //!< 01-07 Day of Week
uint8_t date; //!< 01-31 Day of Month
uint8_t month; //!< 01-12
uint8_t year; //!< 00-99
/**
* Constructor.
*/
time_t() {}
/**
* Construct from seconds since the Epoch.
* @param[in] c clock.
*/
time_t(clock_t c);
/**
* Initialize to January 1 of the Epoch Year, 00:00:00
*/
void init();
/**
* Convert to seconds.
* @return seconds from epoch.
*/
operator clock_t() const;
/**
* Offset by a number of seconds.
* @param[in] offset in seconds.
*/
void operator +=( clock_t offset )
{ *this = offset + operator clock_t(); }
/**
* Set day member from current value. This is a relatively expensive
* operation, so the weekday is only calculated when requested.
*/
void set_day()
{
day = weekday_for(days());
}
/**
* Convert to days.
* @return days from January 1 of the epoch year.
*/
uint16_t days() const;
/**
* Calculate day of the current year.
* @return days from January 1, which is day zero.
*/
uint16_t day_of_year() const;
/**
* Calculate 4-digit year from internal 2-digit year member.
* @return 4-digit year.
*/
uint16_t full_year() const
{
return full_year(year);
}
/**
* Calculate 4-digit year from a 2-digit year
* @param[in] year (4-digit).
* @return true if /year/ is a leap year.
*/
static uint16_t full_year( uint8_t year )
{
uint16_t y = year;
if (y < pivot_year())
y += 100 * (epoch_year()/100 + 1);
else
y += 100 * (epoch_year()/100);
return y;
}
/**
* Determine whether the current year is a leap year.
* @returns true if the two-digit /year/ member is a leap year.
*/
bool is_leap() const
{
return is_leap(full_year());
}
/**
* Determine whether the 4-digit /year/ is a leap year.
* @param[in] year (4-digit).
* @return true if /year/ is a leap year.
*/
static bool is_leap(uint16_t year)
{
if (year % 4) return false;
uint16_t y = year % 400;
return (y == 0) || ((y != 100) && (y != 200) && (y != 300));
}
/**
* Calculate how many days are in the specified year.
* @param[in] year (4-digit).
* @return number of days.
*/
static uint16_t days_per(uint16_t year)
{
return (365 + is_leap(year));
}
/**
* Determine the day of the week for the specified day number
* @param[in] dayno number as counted from January 1 of the epoch year.
* @return weekday number 1..7, as for the /day/ member.
*/
static uint8_t weekday_for(uint16_t dayno)
{
return ((dayno+epoch_weekday()-1) % DAYS_PER_WEEK) + 1;
}
/**
* Check that all members, EXCEPT FOR day, are set to a coherent date/time.
* @return true if valid date/time.
*/
bool is_valid() const
{
return
((year <= 99) &&
(1 <= month) && (month <= 12) &&
((1 <= date) &&
((date <= pgm_read_byte(&days_in[month])) ||
((month == 2) && is_leap() && (date == 29)))) &&
(hours <= 23) &&
(minutes <= 59) &&
(seconds <= 59));
}
/**
* Set the epoch year for all time_t operations. Note that the pivot
* year defaults to the epoch_year % 100. Valid years will be in the
* range epoch_year..epoch_year+99. Selecting a different pivot year
* will slide this range to the right.
* @param[in] y epoch year to set.
* See also /full_year/.
*/
#ifdef TIME_EPOCH_MODIFIABLE
static void epoch_year(uint16_t y)
{
s_epoch_year = y;
epoch_offset( s_epoch_year % 100 );
pivot_year( epoch_offset() );
}
#endif
/**
* Get the epoch year.
* @return year.
*/
static uint16_t epoch_year()
{
return (s_epoch_year);
}
static uint8_t epoch_weekday() { return s_epoch_weekday; };
#ifdef TIME_EPOCH_MODIFIABLE
static void epoch_weekday( uint8_t ew ) { s_epoch_weekday = ew; };
#endif
/**
* The pivot year determine the range of years WRT the epoch_year
* For example, an epoch year of 2000 and a pivot year of 80 will
* allow years in the range 1980 to 2079. Default 0 for Y2K_EPOCH.
*/
static uint8_t pivot_year() { return s_pivot_year; };
#ifdef TIME_EPOCH_MODIFIABLE
static void pivot_year( uint8_t py ) { s_pivot_year = py; };
#endif
#ifdef TIME_EPOCH_MODIFIABLE
/**
* Use the current year for the epoch year. This will result in the
* best performance of conversions, but dates/times before January 1
* of the epoch year cannot be represented.
*/
static void use_fastest_epoch();
#endif
/**
* Parse a character string and fill out members.
* @param[in] s PROGMEM character string with format "YYYY-MM-DD HH:MM:SS".
* @return success.
*/
bool parse(str_P s);
static const uint8_t days_in[] PROGMEM; // month index is 1..12, PROGMEM
protected:
static uint8_t epoch_offset() { return s_epoch_offset; };
#ifdef TIME_EPOCH_MODIFIABLE
static void epoch_offset( uint8_t eo ) { s_epoch_offset = eo; };
static uint16_t s_epoch_year;
static uint8_t s_pivot_year;
static uint8_t s_epoch_offset;
static uint8_t s_epoch_weekday;
#else
static const uint16_t s_epoch_year = Y2K_EPOCH_YEAR;
static const uint8_t s_pivot_year = s_epoch_year % 100;
static const uint8_t s_epoch_offset = s_pivot_year;
static const uint8_t s_epoch_weekday = Y2K_EPOCH_WEEKDAY;
#endif
} NEOGPS_PACKED;
}; // namespace NeoGPS
class Print;
/**
* Print the date/time to the given stream with the format "YYYY-MM-DD HH:MM:SS".
* @param[in] outs output stream.
* @param[in] t time structure.
* @return iostream.
*/
Print & operator <<( Print & outs, const NeoGPS::time_t &t );
#endif

View File

@ -0,0 +1,489 @@
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "Streamers.h"
#include "NMEAGPS.h"
//#define USE_FLOAT
Print& operator <<( Print &outs, const bool b )
{ outs.print( b ? 't' : 'f' ); return outs; }
Print& operator <<( Print &outs, const char c ) { outs.print(c); return outs; }
Print& operator <<( Print &outs, const uint16_t v ) { outs.print(v); return outs; }
Print& operator <<( Print &outs, const uint32_t v ) { outs.print(v); return outs; }
Print& operator <<( Print &outs, const int32_t v ) { outs.print(v); return outs; }
Print& operator <<( Print &outs, const uint8_t v ) { outs.print(v); return outs; }
Print& operator <<( Print &outs, const __FlashStringHelper *s )
{ outs.print(s); return outs; }
//------------------------------------------
const char gps_fix_header[] __PROGMEM =
"Status,"
#if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME)
"UTC "
#if defined(GPS_FIX_DATE)
"Date"
#endif
#if defined(GPS_FIX_DATE) & defined(GPS_FIX_TIME)
"/"
#endif
#if defined(GPS_FIX_TIME)
"Time"
#endif
#else
"s"
#endif
","
#ifdef GPS_FIX_LOCATION
"Lat,Lon,"
#endif
#ifdef GPS_FIX_LOCATION_DMS
"DMS,"
#endif
#if defined(GPS_FIX_HEADING)
"Hdg,"
#endif
#if defined(GPS_FIX_SPEED)
"Spd,"
#endif
#ifdef GPS_FIX_VELNED
"Vel N,E,D,"
#endif
#if defined(GPS_FIX_ALTITUDE)
"Alt,"
#endif
#if defined(GPS_FIX_HDOP)
"HDOP,"
#endif
#if defined(GPS_FIX_VDOP)
"VDOP,"
#endif
#if defined(GPS_FIX_PDOP)
"PDOP,"
#endif
#if defined(GPS_FIX_LAT_ERR)
"Lat err,"
#endif
#if defined(GPS_FIX_LON_ERR)
"Lon err,"
#endif
#if defined(GPS_FIX_ALT_ERR)
"Alt err,"
#endif
#if defined(GPS_FIX_SPD_ERR)
"Spd err,"
#endif
#if defined(GPS_FIX_HDG_ERR)
"Hdg err,"
#endif
#if defined(GPS_FIX_TIME_ERR)
"Time err,"
#endif
#if defined(GPS_FIX_GEOID_HEIGHT)
"Geoid Ht,"
#endif
#if defined(GPS_FIX_SATELLITES)
"Sats,"
#endif
;
//...............
#ifdef GPS_FIX_LOCATION_DMS
static void printDMS( Print & outs, const DMS_t & dms )
{
if (dms.degrees < 10)
outs.write( '0' );
outs.print( dms.degrees );
outs.write( ' ' );
if (dms.minutes < 10)
outs.write( '0' );
outs.print( dms.minutes );
outs.print( F("\' ") );
if (dms.seconds_whole < 10)
outs.write( '0' );
outs.print( dms.seconds_whole );
outs.write( '.' );
if (dms.seconds_frac < 100)
outs.write( '0' );
if (dms.seconds_frac < 10)
outs.write( '0' );
outs.print( dms.seconds_frac );
outs.print( F("\" ") );
} // printDMS
#endif
//...............
Print & operator <<( Print &outs, const gps_fix &fix )
{
if (fix.valid.status)
outs << (uint8_t) fix.status;
outs << ',';
#if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME)
bool someTime = false;
#if defined(GPS_FIX_DATE)
someTime |= fix.valid.date;
#endif
#if defined(GPS_FIX_TIME)
someTime |= fix.valid.time;
#endif
if (someTime) {
outs << fix.dateTime << '.';
uint16_t ms = fix.dateTime_ms();
if (ms < 100)
outs << '0';
if (ms < 10)
outs << '0';
outs << ms;
}
outs << ',';
#else
// Date/Time not enabled, just output the interval number
static uint32_t sequence = 0L;
outs << sequence++ << ',';
#endif
#ifdef USE_FLOAT
#ifdef GPS_FIX_LOCATION
if (fix.valid.location) {
outs.print( fix.latitude(), 6 );
outs << ',';
outs.print( fix.longitude(), 6 );
} else
outs << ',';
outs << ',';
#endif
#ifdef GPS_FIX_LOCATION_DMS
if (fix.valid.location) {
printDMS( outs, fix.latitudeDMS );
outs.print( fix.latitudeDMS.NS() );
outs.write( ' ' );
if (fix.longitudeDMS.degrees < 100)
outs.write( '0' );
printDMS( outs, fix.longitudeDMS );
outs.print( fix.longitudeDMS.EW() );
}
outs << ',';
#endif
#ifdef GPS_FIX_HEADING
if (fix.valid.heading)
outs.print( fix.heading(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_SPEED
if (fix.valid.speed)
outs.print( fix.speed(), 3 ); // knots
outs << ',';
#endif
#ifdef GPS_FIX_VELNED
if (fix.valid.velned)
outs.print( fix.velocity_north ); // cm/s
outs << ',';
if (fix.valid.velned)
outs.print( fix.velocity_east ); // cm/s
outs << ',';
if (fix.valid.velned)
outs.print( fix.velocity_down ); // cm/s
outs << ',';
#endif
#ifdef GPS_FIX_ALTITUDE
if (fix.valid.altitude)
outs.print( fix.altitude(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_HDOP
if (fix.valid.hdop)
outs.print( (fix.hdop * 0.001), 3 );
outs << ',';
#endif
#ifdef GPS_FIX_VDOP
if (fix.valid.vdop)
outs.print( (fix.vdop * 0.001), 3 );
outs << ',';
#endif
#ifdef GPS_FIX_PDOP
if (fix.valid.pdop)
outs.print( (fix.pdop * 0.001), 3 );
outs << ',';
#endif
#ifdef GPS_FIX_LAT_ERR
if (fix.valid.lat_err)
outs.print( fix.lat_err(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_LON_ERR
if (fix.valid.lon_err)
outs.print( fix.lon_err(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_ALT_ERR
if (fix.valid.alt_err)
outs.print( fix.alt_err(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_SPD_ERR
if (fix.valid.spd_err)
outs.print( fix.spd_err(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_HDG_ERR
if (fix.valid.hdg_err)
outs.print( fix.hdg_err(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_TIME_ERR
if (fix.valid.time_err)
outs.print( fix.time_err(), 2 );
outs << ',';
#endif
#ifdef GPS_FIX_GEOID_HEIGHT
if (fix.valid.geoidHeight)
outs.print( fix.geoidHeight(), 2 );
outs << ',';
#endif
#else
// not USE_FLOAT ----------------------
#ifdef GPS_FIX_LOCATION
if (fix.valid.location)
outs << fix.latitudeL() << ',' << fix.longitudeL();
else
outs << ',';
outs << ',';
#endif
#ifdef GPS_FIX_LOCATION_DMS
if (fix.valid.location) {
printDMS( outs, fix.latitudeDMS );
outs.print( fix.latitudeDMS.NS() );
outs.write( ' ' );
if (fix.longitudeDMS.degrees < 100)
outs.write( '0' );
printDMS( outs, fix.longitudeDMS );
outs.print( fix.longitudeDMS.EW() );
}
outs << ',';
#endif
#ifdef GPS_FIX_HEADING
if (fix.valid.heading)
outs << fix.heading_cd();
outs << ',';
#endif
#ifdef GPS_FIX_SPEED
if (fix.valid.speed)
outs << fix.speed_mkn();
outs << ',';
#endif
#ifdef GPS_FIX_VELNED
if (fix.valid.velned)
outs.print( fix.velocity_north ); // cm/s
outs << ',';
if (fix.valid.velned)
outs.print( fix.velocity_east ); // cm/s
outs << ',';
if (fix.valid.velned)
outs.print( fix.velocity_down ); // cm/s
outs << ',';
#endif
#ifdef GPS_FIX_ALTITUDE
if (fix.valid.altitude)
outs << fix.altitude_cm();
outs << ',';
#endif
#ifdef GPS_FIX_HDOP
if (fix.valid.hdop)
outs << fix.hdop;
outs << ',';
#endif
#ifdef GPS_FIX_VDOP
if (fix.valid.vdop)
outs << fix.vdop;
outs << ',';
#endif
#ifdef GPS_FIX_PDOP
if (fix.valid.pdop)
outs << fix.pdop;
outs << ',';
#endif
#ifdef GPS_FIX_LAT_ERR
if (fix.valid.lat_err)
outs << fix.lat_err_cm;
outs << ',';
#endif
#ifdef GPS_FIX_LON_ERR
if (fix.valid.lon_err)
outs << fix.lon_err_cm;
outs << ',';
#endif
#ifdef GPS_FIX_ALT_ERR
if (fix.valid.alt_err)
outs << fix.alt_err_cm;
outs << ',';
#endif
#ifdef GPS_FIX_SPD_ERR
if (fix.valid.spd_err)
outs.print( fix.spd_err_mmps );
outs << ',';
#endif
#ifdef GPS_FIX_HDG_ERR
if (fix.valid.hdg_err)
outs.print( fix.hdg_errE5 );
outs << ',';
#endif
#ifdef GPS_FIX_TIME_ERR
if (fix.valid.time_err)
outs.print( fix.time_err_ns );
outs << ',';
#endif
#ifdef GPS_FIX_GEOID_HEIGHT
if (fix.valid.geoidHeight)
outs << fix.geoidHeight_cm();
outs << ',';
#endif
#endif
#ifdef GPS_FIX_SATELLITES
if (fix.valid.satellites)
outs << fix.satellites;
outs << ',';
#endif
return outs;
}
//-----------------------------
static const char NMEAGPS_header[] __PROGMEM =
#if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) | defined(NMEAGPS_TIMESTAMP_FROM_PPS)
"micros(),"
#endif
#if defined(NMEAGPS_PARSE_SATELLITES)
"[sat"
#if defined(NMEAGPS_PARSE_SATELLITE_INFO)
" elev/az @ SNR"
#endif
"],"
#endif
#ifdef NMEAGPS_STATS
"Rx ok,Rx err,Rx chars,"
#endif
"";
void trace_header( Print & outs )
{
outs.print( (const __FlashStringHelper *) &gps_fix_header[0] );
outs.print( (const __FlashStringHelper *) &NMEAGPS_header[0] );
outs << '\n';
}
//--------------------------
void trace_all( Print & outs, const NMEAGPS &gps, const gps_fix &fix )
{
outs << fix;
#if defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) | defined(NMEAGPS_TIMESTAMP_FROM_PPS)
outs << gps.UTCsecondStart();
outs << ',';
#endif
#if defined(NMEAGPS_PARSE_SATELLITES)
outs << '[';
for (uint8_t i=0; i < gps.sat_count; i++) {
outs << gps.satellites[i].id;
#if defined(NMEAGPS_PARSE_SATELLITE_INFO)
outs << ' ' <<
gps.satellites[i].elevation << '/' << gps.satellites[i].azimuth;
outs << '@';
if (gps.satellites[i].tracked)
outs << gps.satellites[i].snr;
else
outs << '-';
#endif
outs << ',';
}
outs << F("],");
#endif
#ifdef NMEAGPS_STATS
outs << gps.statistics.ok << ','
<< gps.statistics.errors << ','
<< gps.statistics.chars << ',';
#endif
outs << '\n';
} // trace_all

View File

@ -0,0 +1,50 @@
#ifndef STREAMERS_H
#define STREAMERS_H
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it 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.
//
// NeoGPS is distributed in the hope that it 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 NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include <Arduino.h>
extern Print & operator <<( Print & outs, const bool b );
extern Print & operator <<( Print & outs, const char c );
extern Print & operator <<( Print & outs, const uint16_t v );
extern Print & operator <<( Print & outs, const uint32_t v );
extern Print & operator <<( Print & outs, const int32_t v );
extern Print & operator <<( Print & outs, const uint8_t v );
extern Print & operator <<( Print & outs, const __FlashStringHelper *s );
class gps_fix;
/**
* Print valid fix data to the given stream with the format
* "status,dateTime,lat,lon,heading,speed,altitude,satellites,
* hdop,vdop,pdop,lat_err,lon_err,alt_err"
* The "header" above contains the actual compile-time configuration.
* A comma-separated field will be empty if the data is NOT valid.
* @param[in] outs output stream.
* @param[in] fix gps_fix instance.
* @return iostream.
*/
extern Print & operator <<( Print &outs, const gps_fix &fix );
class NMEAGPS;
extern void trace_header( Print & outs );
extern void trace_all( Print & outs, const NMEAGPS &gps, const gps_fix &fix );
#endif

View File

@ -0,0 +1,14 @@
#ifndef PUBX_CFG_H
#define PUBX_CFG_H
//------------------------------------------------------------
// Enable/disable the parsing of specific proprietary NMEA sentences.
//
// Configuring out a sentence prevents its fields from being parsed.
// However, the sentence type may still be recognized by /decode/ and
// stored in member /nmeaMessage/. No valid flags would be available.
#define NMEAGPS_PARSE_PUBX_00
//#define NMEAGPS_PARSE_PUBX_04
#endif

Some files were not shown because too many files have changed in this diff Show More