A driver for the MAX8U GPS by uBlox. Provides core functionality. Communicates through I2C.

MAX8U Driver

This driver supports a wide range of functions provided by the MAX8U chip. Most of the essential features are supported. It lays a framework to add more commands easily, with only a little extra work.

Driver was originally made for the USC Rocket Propulsion Lab by Adhyyan Sekhsaria and Jamie Smith

Features

Currently supports:

  • Getting coordinates in latitude, longitude, accuracy...
  • Velocity measurements in each axis
  • Individual satellite information
  • Time of last message
  • Antenna Power Status
  • GPS Fix Quality
  • Save configuration in memory

Documentation

Full documentation is available here

Usage

Code Structure:

In factory settings, the module is not configured to send any messages. For the GPS to give updates, we need to configure it by sending messages using setMessageEnabled() which internally calls sendMessage() and waitForAck().

calling configure() once will enable a few useful messages, and save this configuration in the MAX8U non-volatile memory.

update() to be called periodically by the user. Processes and reads all the messages currently stored in the GPS. Calls processMessage() on every message. processMessage(): processes the message currently in the buffer by calling the respective functions. Each message is read, and its relevant information is then stored in the public variables.

By default, the GPS sends messages in NMEA format (but we reconfigure it to UBX format), so readNextMessage() has to determine which format the message is.

define the macro MAX8U_DEBUG to enable printing out to message information to the debug port. If not defined, will only print error messages to the debug port.

Example

Outputting Coordinates, Velocity and Time from GPS

#include "MAX8U.h"

int main(){
    MAX8U gps(&pc, PIN_I2C_SDA, PIN_I2C_SCL, p25);
    bool booted = gps.begin();

    if(!booted){
        //handle error
    }
    booted = gps.configure();
    if(!booted){
        //handle error
    }

    while(true){
        bool newMessageRcvd = gps.update();
        pc.printf(">Position: %.06f %c, %.06f %c, Height %.02f m\r\n",
                  std::abs(gps.latitude), gps.latitude > 0 ? 'N' : 'S',
                  std::abs(gps.longitude), gps.longitude > 0 ? 'E' : 'W',
                  gps.height);

        // velocity
        pc.printf(">Velocity: %.02f m/s N, %.02f m/s E, %.02f m/s Down\r\n",
                  gps.northVel * .02f,
                  gps.eastVel * .02f,
                  gps.downVel * .02f);

        // accuracy
        pc.printf(">Fix: Quality: %" PRIu8 ", Num Satellites: %" PRIu8 ", Position Accuracy: %.02f m\r\n",
                static_cast<uint8_t>(gps.fixQuality), gps.numSatellites, gps.posAccuracy);

        // time
        pc.printf(">Time: %" PRIu8 "/%" PRIu8"/%" PRIu16" %" PRIu8":%" PRIu8 ":%" PRIu8 "\r\n",
                gps.month,
                gps.day,
                gps.year,
                gps.hour,
                gps.minute,
                gps.second);
    }
}

MAX8U.h

Committer:
adhyyan
Date:
2020-01-07
Revision:
3:b51460af3259
Parent:
2:4d2c874f386e

File content as of revision 3:b51460af3259:

#ifndef MAX8U_H
#define MAX8U_H


#include "mbed.h"
#include "MAX8UConstants.h"

#define MAX8U_I2C_DEF_ADDRESS 0x42 // this is the default address given in the databook


/**
 * Class to use the MAX8U
 */ 
class MAX8U
{
public:

    /**
     *  U-Blox GPS Fix Values
     */ 
    enum class GPSFix : uint8_t
    {
        NONE = 0,
        DEAD_RECKONING = 1,
        FIX_2D = 2,
        FIX_3D = 3,
        FIX_GPS_DEAD_RECKONING = 4,
        TIME_ONLY = 5
    };

    /**
     * U-Blox GNSS identifier
     */
    enum class GNSSID : uint8_t
    {
        GPS = 0,
        SBAS = 1,
        Galileo = 2,
        BeiDou = 3,
        IMES = 4,
        QZSS = 5,
        GLONASS = 6
    };

    /**
     * Used for AntennaPowerStatus, in the UBX_MON_HW message
     */
    enum class AntennaPowerStatus : uint8_t
    {
        OFF = 0,
        ON = 1,
        DONT_KNOW = 2,
        NO_MESSAGE_RCVD = 3
    };

    /**
     * Info about each sattelite, obtained from getSatelliteInfo()
     */
    struct SatelliteInfo
    {
        /**
         * GNSS that this satellite is from.
         */
        GNSSID gnss;

        /**
         * ID of this satellite in its GNSS
         */
        uint8_t satelliteID;

        /**
         * Carrier-noise ratio in dbHz
         */
        uint8_t signalStrength;

        /**
         * Signal quality indicator (see U-Blox protocol description section 32.17.17.1)
         */
         uint8_t signalQuality;

         /**
          * Get the string name of this satellite's GNSS.
          * @return
          */
         const char* getGNSSName();

         /**
          * True if this satellite is beign used to do navigation
          */
          bool svUsed;
    };

private:

    Serial* _debugPort;


    /// buffer stores the data rcvd from reeading the stream on the MAX8U
    const static int bufferMaxLen = 256;
    char buffer[bufferMaxLen + 1];

    /// length of the message currently in the buffer.
    size_t currMessageLength = 0;



    /**
     * I2C port object.  Provides physical layer communications with the chip.
     */
    I2C _i2cPort;

    /// i2c address of IMU (7 bits)
    uint8_t _i2cAddress;

    /// user defined port speed
    int  _i2cPortSpeed;

    /// Reset pin -- resets GPS when held low.
    DigitalOut _rst;

    /**
     * sends header and data in the correct UBX protocol (adding sync chars, and checksums)
     * @returns true if command was sent sucessfully
     *
     */
    bool sendCommand(uint8_t messageClass, uint8_t messageID, uint8_t *data, uint16_t dataLen);


public:

    // Data read from GPS

    /// Current latitude in decimal degrees.
    double latitude = 0;

    /// Current longitude in decimal degrees.
    double longitude = 0;

    /// Current height above earths surface (above ellipsoid) in meters
    float height = 0;

    /// Quality of the current fix
    GPSFix fixQuality = GPSFix::NONE;

    /// Estimate of the accuracy of the current position in meters
    float posAccuracy = 0;

    /// current number of sats used for navigation
    uint8_t numSatellites = 0;

    // Variables for Velocity Solution in NED, units: cm/s
    
    /// North Velocity Solution in NED, units: cm/s
    int32_t northVel = 0;
    
    /// East Velocity Solution in NED, units: cm/s
    int32_t eastVel = 0;
    
    /// Down Velocity Solution in NED, units: cm/s
    int32_t downVel = 0;
    
    /// 3D Speed Solution in NED, units: cm/s
    uint32_t speed3D = 0;

    //time as of the last message
    
    ///Month: time as of the last message
    uint8_t month;
    
    ///Day: time as of the last message
    uint8_t day;
    
    ///Hour: time as of the last message
    uint8_t hour;
    
    ///Minute: time as of the last message
    uint8_t minute;
    
    ///Second: time as of the last message
    uint8_t second;
    
    ///Year: time as of the last message
    uint16_t year;

    ///  is true, if the last msg was following the NMEA protocol, otherwise UBX
    bool isNMEASentence;

    /**
     * Construct a MAX8U, providing pins and parameters.
     *
     * This doesn't actually initialize the chip, you will need to call begin() for that.
     * @param debugPort DebugPort used to output debug information. Pass nullpntr otherwise.
     * @param user_SDApin Hardware I2C SDA pin connected to the MAX8
     * @param user_SCLpin Hardware I2C SCL pin connected to the MAX8
     * @param user_RSTPin Output pin connected to NRST
     * @param i2cAddress I2C address.  The MAX8 defaults to 0x42
     * @param i2cPortSpeed I2C frequency.  
     */
    MAX8U(Serial *debugPort, PinName user_SDApin, PinName user_SCLpin, PinName user_RSTPin,
          uint8_t i2cAddress = MAX8U_I2C_DEF_ADDRESS, int i2cPortSpeed = 100000);





    /**
     * resets the GPS. Configures the basic i2c settings. 
     * TODO: Verifies that it's connected, and reads out its version
     *
     * @return whether or not initialization was successful
     */
    bool begin();

    /**
     * Get the last message recieved from the GPS.  Could be binary or a string
     * depending on the protocol in use.
     * @return
     */
    char const * getLastMessageBuffer() { return buffer; }

    /**
     * If there is data to be read, then the function will read all queued data.
     * New data will be store in buffer array
     * @return true if got new data, false if there was an error or stream was empty
     */
    bool update();

    /**
     * Configure the GPS with the appropriate communications and message settings
     * for this driver.  Unless the driver has changed, you only need to call this once
     * for each new GPS (the config will be saved on the GPS)
     * @return
     */
    bool configure();

    /**
     * Receives a single MON_HW message and returns the power status
     * @return the status of the power of the antenna
     */
    AntennaPowerStatus antennaPowerStatus();

    /**
     * Reads and prints the current enabled GNSS Constellations and prints out the IDS for them
     */
    void readGNSSConfig();

    /**
     * Reads information from the GPS about all the satellites it can see and
     * fills it into the given array.
     * @param satelliteInfos Array of SatelliteInfos that the caller allocates.
     * @param infoLen Length of the info struct array.
     * @return The number of satellites the GPS returned info about.  If negative, there was
     * an error.  If <= the size of the array, then it's the number of valid array entries.
     * If greater than the size of the array, then the max number of array elements were filled in.
     */
    ssize_t readSatelliteInfo(SatelliteInfo * satelliteInfos, size_t infoLen);

    /**
     * Configures the time pulse. If enabled, it will send a pulse at the frequncy
     * @param timepulseId, 0 = timepulse, 1 = timepulse2
     * @param pulseLenRatio duty cycle.
     * @return
     */
    bool configureTimePulse(bool enabled, uint8_t timepulseId, uint32_t freq, uint32_t pulseLenRatio);



private:

    /**
     * Helper function to read form the data buffer.
    * Numerical values can only be read from aligned memory addresses, but sometimes we need to not do that.
    * This function takes an address and an optional offset, and reads a value of the template type.
    */
    template<typename T>
    T readUnalignedValue(char* data, size_t offset = 0)
    {
        T value;
        memcpy(&value, data + offset, sizeof(value));
        return value;
    }

    /**
     * Tells the GPS to enable the message indicated by messageClass and messageID
     * This is done by sending a message to the GPS and then waiting for an ack message
     * @return true if an ack message has been recieved from the gps
     */

    bool setMessageEnabled(uint8_t messageClass, uint8_t messageID, bool enabled);

    /**
     * Processes the message currently in the buffer.
     * If this is data we are processing, parses it and stores it in class variables.
     * Otherwise, does nothing.
     */
    void processMessage();

    /**
     * Process a NAV_POSLLH message loaded into the buffer.
     */
    void processNAV_POSLLH();

    /**
     * Process a NAV_SOL message loaded into the buffer.
     */
    void processNAV_SOL();

    /**
     * Process a NAV_TIMEUTC message loaded into the buffer.
     */
    void processNAV_TIMEUTC();

    /**
     * Process a NAV_VELNED message loaded into the buffer.
     */
    void processNAV_VELNED();

    /**
     * Wait for a specific message to be received.
     * If we get another message that is not the one we're looking for during this time, then
     * we process that message using processMessage()
     * If the message is not received before the timeout, returns false.
     *
     *
     * @param messageClass Class of the message to wait for
     * @param messageID ID of the of the message to wait for
     * @param timeout How long to wait for the message.
     * @return
     */
    bool waitForMessage(uint8_t messageClass, uint8_t messageID, float timeout = 1.0f);





    /**
     * It will wait for a message of a specific kind.
     * NOTE: we assume that we wait for an ACK before sending another message,
     * so there should never be two ACKs in play at once.
     * @param sentMessageClass Class of the message expecting an ACK for
     * @param sentMessageID ID of the message expecting an ACK for
     * @param timeout How long to wait for the message
     * @return true if a correct ACK was received
     */

    bool waitForACK(uint8_t sentMessageClass, uint8_t sentMessageID, float timeout = 1.0f);

    /**
     * Configure the port settings appropriately for this driver.
     * @return
     */
    bool configureCommSettings();

    /**
     * Save all the current settings to the GPS's flash memory so that they will
     * be loaded when it boots.
     * @return
     */
    bool saveSettings();

    /**
     * Check the software version.  If not in debug mode, this will just return true if
     * it was able to talk to the device.  In debug mode, this will print all version info
     * to the console.
     * @return
     */
    bool checkVersion();

    /**
     * Reads exactly one command in the buffer, by accessing the 0xff register.
     * Stores the data in a global arr, buffer.
     */
    bool readNextMessage();


    /**
     * Returns length of buffer in the GPS module by reading the registers
     * @returns length of message. -1 if no ack is rcvd.
     */
    int32_t readLen();
};
#endif