MicoAir
Docs directory
Table of contents

MicoAir GPS Module — UBX Protocol Parsing Reference

MicoAir GPS modules ship with the UBX protocol, NAV-PVT message, and 115200 baud enabled by default.

UBX Protocol Manual (M10050)

For protocol details, refer to the official manual. The M10 (M10050) and M9 (M9140) protocols are almost identical, apart from a few specific differences.

The following reference implementation configures a u-blox module and parses NAV-PVT messages.

ublox.h

#ifndef __UBLOX_H
#define	__UBLOX_H

#include <stdint.h>

#define UBLOX_SYNC1	    0xB5
#define UBLOX_SYNC2	    0x62

#define UBLOX_NAV_CLASS	    0x01
#define UBLOX_RXM_CLASS	    0x02
#define UBLOX_ACK_CLASS     0x05
#define UBLOX_CFG_CLASS	    0x06
#define UBLOX_MON_CLASS	    0x0a
#define UBLOX_AID_CLASS	    0x0b
#define UBLOX_TIM_CLASS	    0x0d

#define UBLOX_NAV_POSLLH    0x02
#define UBLOX_NAV_DOP	    0x04
#define UBLOX_NAV_SOL       0x06
#define UBLOX_NAV_PVT       0x07
#define UBLOX_NAV_VALNED    0x12
#define UBLOX_NAV_TIMEUTC   0x21
#define UBLOX_NAV_SBAS	    0x32
#define UBLOX_NAV_SVINFO    0x30

#define UBLOX_ACK_NAK       0x00
#define UBLOX_ACK_ACK       0x01

#define UBLOX_AID_REQ	    0x00

#define UBLOX_RXM_RAW	    0x10
#define UBLOX_RXM_SFRB	    0x11

#define UBLOX_MON_VER	    0x04
#define UBLOX_MON_HW	    0x09

#define UBLOX_TIM_TP	    0x01

#define UBLOX_CFG_PRT       0x00
#define UBLOX_CFG_MSG	    0x01
#define UBLOX_CFG_TP	    0x07
#define UBLOX_CFG_RTATE	    0x08
#define UBLOX_CFG_CFG	    0x09
#define UBLOX_CFG_SBAS	    0x16
#define UBLOX_CFG_NAV5	    0x24

#define UBLOX_MAX_PAYLOAD   256

typedef struct {
    float time;		 // Time
    double latitude;	 // Latitude
    double longitude;    // Longitude
    float altitude;	 // Altitude
    float velN;		 // North velocity
    float velE;		 // East velocity
    float velD;		 // Down velocity
    float speed;         // Ground speed
    float heading;       // Heading
    float hAcc;		 // Horizontal position accuracy
    float vAcc;          // Vertical position accuracy
    float sAcc;          // Speed accuracy
    float cAcc;          // Heading accuracy
    uint8_t fixed;       // Fix status
    uint8_t numSV;	 // Number of satellites
} GPS_t;

enum
{
    UBLOX_WAIT_SYNC1 = 0,
    UBLOX_WAIT_SYNC2,
    UBLOX_WAIT_CLASS,
    UBLOX_WAIT_ID,
    UBLOX_WAIT_LEN1,
    UBLOX_WAIT_LEN2,
    UBLOX_PAYLOAD,
    UBLOX_CHECK1,
    UBLOX_CHECK2
};

enum
{
    PORTABLE = 0,
    STATIONARY = 2,
    PEDESTRIAN = 3,
    AUTOMOTIVE = 4,
    SEA = 5,
    AIRBORNE_1G = 6,
    AIRBORNE_2G = 7,
    AIRBORNE_4G = 8
};

#pragma pack (1)
typedef struct {
    uint32_t   iTOW;	    //GPS Millisecond Time of Week	ms
    uint16_t   year;        //Year, range 1999..2099 (UTC)
    uint8_t    month;		//Month, range 1..12 (UTC)
    uint8_t    day;		    //Day of Month, range 1..31 (UTC)
    uint8_t    hour;		//Hour of Day, range 0..23 (UTC)
    uint8_t    min;		    //Minute of Hour, range 0..59 (UTC)
    uint8_t    sec;		    //Seconds of Minute, range 0..59 (UTC)
    uint8_t    valid;       //Validity Flags
    uint32_t   tAcc;        //Time Accuracy Estimate
    int32_t    nano;        //Fraction of second, range -1e9 .. 1e9 (UTC)
    uint8_t    gpsFix;      //GPSfix Type, range 0..5 0: no fix 1: dead reckoning only 2: 2D-fix 3: 3D-fix 4: GNSS + dead reckoning combined 5: time only fix
    uint8_t    flags;       //Fix status flags
    uint8_t    flags2;      //Additional flags
    uint8_t    numSV;       //Number of SVs used in Nav Solution
    int32_t	   lon;         //Longitude
    int32_t	   lat;         //Latitude
    int32_t	   height;      //Height above Ellipsoid
    int32_t	   hMSL;        //Height above mean sea level
    uint32_t   hAcc;        //Horizontal Accuracy Estimate
    uint32_t   vAcc;        //Vertical Accuracy Estimate
    int32_t    velN;        //NED north velocity	cm/s
    int32_t    velE;        //NED east velocity		cm/s
    int32_t    velD;        //NED down velocity		cm/s
    int32_t    gSpeed;		//Ground Speed (2-D)	cm/s
    int32_t    heading;		//Heading 2-D			deg
    uint32_t   sAcc;		//Speed Accuracy Estimate	cm/s
    uint32_t   cAcc;		//Course / Heading Accuracy Estimate deg
    uint16_t   pDOP;        //Position DOP
    uint8_t    res1;        //reserved
    int32_t    headVeh;     //Heading of vehicle (2-D)
    uint8_t    res2;        //reserved
} UBLOX_PVT_t;

typedef struct {
    union
    {
        UBLOX_PVT_t     pvt;
        char other[UBLOX_MAX_PAYLOAD];
    } payload;

    uint8_t  state;
    uint16_t count;
    uint8_t  class;
    uint8_t  id;
    uint16_t length;
    uint16_t checksumErrors;

    uint8_t  ubloxRxCK_A;
    uint8_t  ubloxRxCK_B;

    uint8_t  ubloxTxCK_A;
    uint8_t  ubloxTxCK_B;
} UBLOX_RAW_t;

typedef struct {
    uint16_t   year;
    uint8_t    month;
    uint8_t    day;
    uint8_t    hour;
    uint8_t    min;
    uint8_t    sec;
} UTC_TIME_t;

#pragma pack ()

void ublox_init(void);

#endif

ublox.c

#include "ublox.h"

// Default GPS baud rate
#define GPS_DEFAULT_BAUDRATE 115200

GPS_t gps_data;
UTC_TIME_t time;

static void ublox_parse_char(uint8_t data);
static void ublox_enable_message(uint8_t class, uint8_t id, uint8_t rate);
static void ublox_cfg_rate(uint16_t rate);
static void ublox_cfg_port(uint32_t baudrate);
static void ublox_cfg_nav5(void);

// Send data over the serial port; implement this function for the target platform
void ublox_send_message(uint8_t *data, uint8_t length)
{
    // Implement serial transmission for the target platform
}

// u-blox data decoder; call it from the serial interrupt or serial-data handler and pass in each received byte
void ublox_decode(uint8_t data)
{
    ublox_parse_char(data);
}

// Millisecond delay; implement this function for the target platform
void delay_ms(uint32_t ms)
{
}

/**********************************************************************************************************
* Function: ublox_init
* Description: Initialize the u-blox configuration
* Parameters: None
* Return value: None
**********************************************************************************************************/
void ublox_init(void)
{
    // Implement serial-port initialization for the target platform; the MG90X default baud rate is 115200
    // usart_init(GPS_DEFAULT_BAUDRATE);
    
    // Wait 300 ms after powering the GPS module before configuring it
    delay_ms(300);
    
    // Configure the u-blox UART
    // Baud rate: 115200
    // Input protocols: NMEA + UBX + RTCM
    // Output protocol: UBX
    ublox_cfg_port(GPS_DEFAULT_BAUDRATE);
    delay_ms(5);
    
    // Configure the output rate
    // The input parameter is the interval in ms
    // At output rates >=10 Hz, the u-blox M9140 (MG-902/903) limits navigation to 16 satellites
    // (the 16 satellites with the best signal quality among those visible)
    // To use the maximum number of satellites for navigation (32 on M9), set the output rate to no more than 8 Hz
    ublox_cfg_rate(125); // 125 ms output interval
    delay_ms(5);
    
    // Configure navigation parameters
    // AIRBORNE_2G mode (recommended for drones)
    ublox_cfg_nav5();
    delay_ms(5);
    
    // Enable PVT message output
    // The PVT message contains latitude, longitude, time, velocity, and the other data required for navigation,
    // so outputting and parsing this message alone is sufficient
    ublox_enable_message(UBLOX_NAV_CLASS, UBLOX_NAV_PVT, 1);
}

/**********************************************************************************************************
* Function: ublox_payload_decode
* Description: Decode the u-blox payload
* Parameters: Input data
* Return value: None
**********************************************************************************************************/
static void ublox_payload_decode(UBLOX_RAW_t raw_data)
{
    if(raw_data.class == UBLOX_NAV_CLASS)
    {
        switch(raw_data.id)
        {
        case UBLOX_NAV_PVT:
            gps_data.time      = (float)raw_data.payload.pvt.iTOW / 1000;  
            gps_data.latitude  = (double)raw_data.payload.pvt.lat * (double)1e-7; // Unit: deg
            gps_data.longitude = (double)raw_data.payload.pvt.lon * (double)1e-7; // Unit: deg  
            gps_data.altitude  = (float)raw_data.payload.pvt.hMSL * 0.001f;       // Unit: m
            gps_data.hAcc      = (float)raw_data.payload.pvt.hAcc * 0.001f;       // Unit: m
            gps_data.vAcc      = (float)raw_data.payload.pvt.vAcc * 0.001f;       // Unit: m
            gps_data.velN      = (float)raw_data.payload.pvt.velN * 0.001f;       // Unit: m/s
            gps_data.velE      = (float)raw_data.payload.pvt.velE * 0.001f;       // Unit: m/s
            gps_data.velD      = (float)raw_data.payload.pvt.velD * 0.001f;       // Unit: m/s
            gps_data.speed     = (float)raw_data.payload.pvt.gSpeed * 0.001f;     // Unit: m/s
            gps_data.heading   = (float)raw_data.payload.pvt.heading * 1e-5f;     // Unit: deg
            gps_data.sAcc      = (float)raw_data.payload.pvt.sAcc * 0.001f;       // Unit: m/s
            gps_data.cAcc      = (float)raw_data.payload.pvt.cAcc * 1e-5f;        // Unit: deg
            gps_data.numSV     = raw_data.payload.pvt.numSV;
            gps_data.fixed     = raw_data.payload.pvt.gpsFix;
            time.year          = raw_data.payload.pvt.year;
            time.month         = raw_data.payload.pvt.month;
            time.day           = raw_data.payload.pvt.day;
            time.hour          = raw_data.payload.pvt.hour;
            time.min           = raw_data.payload.pvt.min;
            time.sec           = raw_data.payload.pvt.sec;
            break;

        default:
            break;
        }
    }
    else if(raw_data.class == UBLOX_ACK_CLASS)
    {
        if(raw_data.id == UBLOX_ACK_ACK)
        {
        }
    }
}

/**********************************************************************************************************
* Function: ublox_parse_char
* Description: Parse a u-blox protocol byte stream
* Parameters: Input data
* Return value: None
**********************************************************************************************************/
static void ublox_parse_char(uint8_t data)
{
    static UBLOX_RAW_t ublox_raw;

    switch (ublox_raw.state)
    {
    /* Frame header 1 */
    case UBLOX_WAIT_SYNC1:
        if (data == UBLOX_SYNC1)
            ublox_raw.state = UBLOX_WAIT_SYNC2;
        break;

    /* Frame header 2 */
    case UBLOX_WAIT_SYNC2:
        if (data == UBLOX_SYNC2)
            ublox_raw.state = UBLOX_WAIT_CLASS;
        else
            ublox_raw.state = UBLOX_WAIT_SYNC1;
        break;

    /* Message class */
    case UBLOX_WAIT_CLASS:
        ublox_raw.class = data;
        // Initialize the checksum
        ublox_raw.ubloxRxCK_A = 0;
        ublox_raw.ubloxRxCK_B = 0;
        // Calculate the checksum
        ublox_raw.ubloxRxCK_A += data;
        ublox_raw.ubloxRxCK_B += ublox_raw.ubloxRxCK_A;
        ublox_raw.state = UBLOX_WAIT_ID;
        break;

    /* Message ID */
    case UBLOX_WAIT_ID:
        ublox_raw.id = data;
        // Calculate the checksum
        ublox_raw.ubloxRxCK_A += data;
        ublox_raw.ubloxRxCK_B += ublox_raw.ubloxRxCK_A;
        ublox_raw.state = UBLOX_WAIT_LEN1;
        break;

    /* Low 8 bits of the message length */
    case UBLOX_WAIT_LEN1:
        ublox_raw.length = data;
        // Calculate the checksum
        ublox_raw.ubloxRxCK_A += data;
        ublox_raw.ubloxRxCK_B += ublox_raw.ubloxRxCK_A;
        ublox_raw.state = UBLOX_WAIT_LEN2;
        break;

    /* High 8 bits of the message length */
    case UBLOX_WAIT_LEN2:
        ublox_raw.length += (data << 8);
        // Calculate the checksum
        ublox_raw.ubloxRxCK_A += data;
        ublox_raw.ubloxRxCK_B += ublox_raw.ubloxRxCK_A;
        if (ublox_raw.length >= (UBLOX_MAX_PAYLOAD-1))
        {
            ublox_raw.length = 0;
            ublox_raw.state = UBLOX_WAIT_SYNC1;
        }
        else if (ublox_raw.length > 0)
        {
            ublox_raw.count = 0;
            ublox_raw.state = UBLOX_PAYLOAD;
        }
        else
        {
            ublox_raw.state = UBLOX_CHECK1;
        }
        break;

    /* Message payload */
    case UBLOX_PAYLOAD:
        *((char *)(&ublox_raw.payload) + ublox_raw.count) = data;
        if (++ublox_raw.count == ublox_raw.length)
        {
            ublox_raw.state = UBLOX_CHECK1;
        }
        // Calculate the checksum
        ublox_raw.ubloxRxCK_A += data;
        ublox_raw.ubloxRxCK_B += ublox_raw.ubloxRxCK_A;
        break;

    /* Compare the CKA checksum byte */
    case UBLOX_CHECK1:
        if (data == ublox_raw.ubloxRxCK_A)
        {
            ublox_raw.state = UBLOX_CHECK2;
        }
        else
        {
            ublox_raw.state = UBLOX_WAIT_SYNC1;
            ublox_raw.checksumErrors++;
        }
        break;

    /* Compare the CKB checksum byte */
    case UBLOX_CHECK2:
        ublox_raw.state = UBLOX_WAIT_SYNC1;
        if (data == ublox_raw.ubloxRxCK_B)
        {
            // Reception is complete; decode the payload
            ublox_payload_decode(ublox_raw);
        }
        else
        {
            ublox_raw.checksumErrors++;
        }
        break;

    default:
        break;
    }
}

/**********************************************************************************************************
* Function: ublox_enable_message
* Description: Enable u-blox message output
* Parameters: Message class, message ID, and output rate
*             (0 disables output; 1 outputs once per cycle, the fastest setting; larger values reduce the output rate)
* Return value: None
**********************************************************************************************************/
static void ublox_enable_message(uint8_t class, uint8_t id, uint8_t rate)
{
    uint8_t data[11];
    uint8_t msg_len = 11;
    uint8_t ck_a = 0, ck_b = 0;

    data[0] = UBLOX_SYNC1;     // Frame header 1
    data[1] = UBLOX_SYNC2;     // Frame header 2
    data[2] = UBLOX_CFG_CLASS; // Message class
    data[3] = UBLOX_CFG_MSG;   // Message ID
    data[4] = 0x03;            // Low 8 bits of the payload length
    data[5] = 0x00;            // High 8 bits of the payload length

    data[6] = class;           // Set the message class
    data[7] = id;              // Set the message ID
    data[8] = rate;            // Set the message output rate

    for(uint8_t i=2; i<msg_len-2; i++)
    {
        ck_a += data[i];
        ck_b += ck_a;
    }

    data[9] = ck_a;
    data[10] = ck_b;

    ublox_send_message(data, msg_len);
}

/**********************************************************************************************************
* Function: ublox_cfg_rate
* Description: Set the u-blox output rate
* Parameters: Output interval (unit: ms)
* Return value: None
**********************************************************************************************************/
static void ublox_cfg_rate(uint16_t rate)
{
    uint8_t data[14];
    uint8_t msg_len = 14;
    uint8_t ck_a = 0, ck_b = 0;

    data[0] = UBLOX_SYNC1;             // Frame header 1
    data[1] = UBLOX_SYNC2;             // Frame header 2
    data[2] = UBLOX_CFG_CLASS;         // Message class
    data[3] = UBLOX_CFG_RTATE;         // Message ID
    data[4] = 0x06;                    // Low 8 bits of the payload length
    data[5] = 0x00;                    // High 8 bits of the payload length

    data[6] = (uint8_t)rate;           // Low eight bits of the output interval
    data[7] = (uint8_t)(rate >> 8);    // High eight bits of the output interval
    data[8] = 0x01;                    // Low eight bits of the navigation-cycle count
    data[9] = 0x00;                    // High eight bits of the navigation-cycle count
    data[10] = 0x01;                    //timeRef 0:UTC, 1:GPS time
    data[11] = 0x00;                    // High eight bits

    for(uint8_t i=2; i<msg_len-2; i++)
    {
        ck_a += data[i];
        ck_b += ck_a;
    }

    data[12] = ck_a;
    data[13] = ck_b;

    ublox_send_message(data, msg_len);
}

/**********************************************************************************************************
* Function: ublox_cfg_port
* Description: Configure u-blox output
* Parameters: Baud rate
* Return value: None
**********************************************************************************************************/
static void ublox_cfg_port(uint32_t baudrate)
{
    uint8_t data[28];
    uint8_t msg_len = 28;
    uint8_t ck_a = 0, ck_b = 0;

    data[0] = UBLOX_SYNC1;               // Frame header 1
    data[1] = UBLOX_SYNC2;               // Frame header 2
    data[2] = UBLOX_CFG_CLASS;           // Message class
    data[3] = UBLOX_CFG_PRT;             // Message ID
    data[4] = 0x14;                      // Low 8 bits of the payload length
    data[5] = 0x00;                      // High 8 bits of the payload length

    data[6] = 0x01;                      // Port number, 1: UART1
    data[7] = 0x00;                      // Reserved
    data[8] = 0x00;                      // High eight bits of TX Ready
    data[9] = 0x00;                      // Low eight bits of TX Ready
    data[10] = (uint8_t)(0x08D0);         // Serial-port configuration
    data[11] = (uint8_t)(0x08D0 >> 8);    // 8 data bits
    data[12] = (uint8_t)(0x08D0 >> 16);   // 1 stop bit
    data[13] = (uint8_t)(0x08D0 >> 24);   // No parity
    data[14] = (uint8_t)(baudrate);       // Serial baud rate
    data[15] = (uint8_t)(baudrate >> 8);  //
    data[16] = (uint8_t)(baudrate >> 16); //
    data[17] = (uint8_t)(baudrate >> 24); //
    data[18] = 0x07;                      // Input-protocol configuration
    data[19] = 0x00;                      //ubx+nmea+rtcm
    data[20] = 0x01;                      // Output-protocol configuration
    data[21] = 0x00;                      //ubx
    data[22] = 0x00;                      // Low eight bits of flags
    data[23] = 0x00;                      // High eight bits of flags
    data[24] = 0x00;                      //reserved
    data[25] = 0x00;                      //reserved

    for(uint8_t i=2; i<msg_len-2; i++)
    {
        ck_a += data[i];
        ck_b += ck_a;
    }

    data[26] = ck_a;
    data[27] = ck_b;

    ublox_send_message(data, msg_len);
}

/**********************************************************************************************************
* Function: ublox_cfg_nav5
* Description: Configure navigation parameters
* Parameters: None
* Return value: None
**********************************************************************************************************/
static void ublox_cfg_nav5(void)
{
    uint8_t data[44];
    uint8_t msg_len = 44;
    uint8_t ck_a = 0, ck_b = 0;

    data[0] = UBLOX_SYNC1;               // Frame header 1
    data[1] = UBLOX_SYNC2;               // Frame header 2
    data[2] = UBLOX_CFG_CLASS;           // Message class
    data[3] = UBLOX_CFG_NAV5;            // Message ID
    data[4] = 0x24;                      // Low 8 bits of the payload length
    data[5] = 0x00;                      // High 8 bits of the payload length

    data[6] = 0xFF;                      // Mask
    data[7] = 0xFF;                      // Mask
    data[8] = AIRBORNE_2G;               // Dynamic platform model; AIRBORNE_2G is recommended for drones
    data[9] = 0x03;                      // position fixing mode 3=auto 2D/3D
    data[10] = (uint8_t)(0x00);          // Fixed altitude(mean sea level) for 2D fix mode
    data[11] = (uint8_t)(0x00 >> 8);   
    data[12] = (uint8_t)(0x00 >> 16);  
    data[13] = (uint8_t)(0x00 >> 24);  
    data[14] = (uint8_t)(10000);         // Fixed altitude variance for 2D mode
    data[15] = (uint8_t)(10000 >> 8);  
    data[16] = (uint8_t)(10000 >> 16); 
    data[17] = (uint8_t)(10000 >> 24);
    data[18] = 0x0A;                     // Minimum satellite elevation: 10°
    data[19] = 0x00;                     // drLimit
    data[20] = (uint8_t)(250);           // pDop
    data[21] = (uint8_t)(250 >> 8);     
    data[22] = (uint8_t)(250);           // tDop
    data[23] = (uint8_t)(250 >> 8);     
    data[24] = (uint8_t)(100);           // pAcc Mask
    data[25] = (uint8_t)(100 >> 8);     
    data[26] = (uint8_t)(350);           // tAcc Mask
    data[27] = (uint8_t)(350 >> 8);     
    data[28] = 0;                        // Static hold threshold
    data[29] = 60;                       // DGNSS timeout
    data[30] = 0;                        // cn0ThreshNumSVs
    data[31] = 25;                       // Minimum C/N0 required for a satellite to participate in navigation
    data[32] = 0;                        // pAccAdr
    data[33] = 0;                                 
    data[34] = 0;                        // staticHoldMaxDist     
    data[35] = 0;
    data[36] = 0;                        // utcStandard
    data[37] = 0;                        // reserved
    data[38] = 0;    
    data[39] = 0;    
    data[40] = 0;    
    data[41] = 0;    

    for(uint8_t i=2; i<msg_len-2; i++)
    {
        ck_a += data[i];
        ck_b += ck_a;
    }

    data[42] = ck_a;
    data[43] = ck_b;

    ublox_send_message(data, msg_len);
}