Some problems with UBX parser

To receive data from the neo-6m GPS module using the UBX protocol, I needed a library.

But neither ublox nor sparkfun wanted to work with my module.
I rewrote the example code, from video, to parse data directly from the serial port.
To receive two messages NAV-VELNED and NAV-VELNED.
Ran the example code on my GPS receiver NEO-6M with MCU stm32f411 .
For starting I configured the module to output at 10 Hz.

#define SERIAL_BAUD 115200
#define GNSS_BAUD 115200

// #define POSLLH
#define VELNED

HardwareSerial Serial2(USART2); // PA2(TX2), PA3(RX2)

const unsigned char UBX_HEADER[] = {0xB5, 0x62};

struct NAV_POSLLH {
  unsigned char cls;
  unsigned char id;
  unsigned short len;
  unsigned long iTOW;
  long lon;
  long lat;
  long height;
  long hMSL;
  unsigned long hAcc;
  unsigned long vAcc;
} posllh;

struct NAV_VELNED {
  unsigned char cls;
  unsigned char id;
  unsigned short len;
  unsigned long iTOW; // U4
  long velN; // I4
  long velE;
  long velD;
  unsigned long speed;
  unsigned long gSpeed;
  long heading;
  unsigned long sAcc;
  unsigned long cAcc;
} velned;

void setup() 
{
  Serial.begin(SERIAL_BAUD);
  Serial2.begin(GNSS_BAUD);
}

void loop() {

#ifdef POSLLH

  bool GPS_POSLLH = processGPSData(sizeof(NAV_POSLLH), UBX_HEADER, &posllh);

  if ( GPS_POSLLH ) {

  Serial.println("GPS_POSLLH");
  Serial.print(" cls: "); Serial.print(posllh.cls);
  Serial.print(" id: "); Serial.print(posllh.id);
  Serial.print(" len: "); Serial.print(posllh.len);
  Serial.print(" iTOW:");      Serial.print(posllh.iTOW);
  Serial.print(" lat/lon: "); Serial.print(posllh.lat/10000000.0f); 
  Serial.print(","); 
  Serial.print(posllh.lon/10000000.0f);
  Serial.print(" height: ");  Serial.print(posllh.height/1000.0f);
  Serial.print(" hMSL: ");    Serial.print(posllh.hMSL/1000.0f);
  Serial.print(" hAcc: ");    Serial.print(posllh.hAcc/1000.0f);
  Serial.print(" vAcc: ");    Serial.print(posllh.vAcc/1000.0f);
  Serial.println();

  }

	#endif

	#ifdef VELNED

  bool GPS_VELNED = processGPSData(sizeof(NAV_VELNED), UBX_HEADER, &velned);
    
  if ( GPS_VELNED ) {

  Serial.println("GPS_VELNED");
  Serial.print(" cls: "); Serial.print(velned.cls);
  Serial.print(" id: "); Serial.print(velned.id);
  Serial.print(" len: "); Serial.print(velned.len);
  Serial.print(" iTOW: "); Serial.print(velned.iTOW); // GPS Millisecond Time of Week
  Serial.print(" velN: "); Serial.print(velned.velN * 1e-2f); // m/s
  Serial.print(" velE: "); Serial.print(velned.velE * 1e-2f); // m/s
  Serial.print(" velD: "); Serial.print(velned.velD * 1e-2f); // m/s
  Serial.print(" Spd (3D): "); Serial.print(velned.speed * 1e-2f); // m/s
  Serial.print(" GndSpd (2D): "); Serial.print(velned.gSpeed * 1e-2f); // m/s
  Serial.print(" Hdg (2D): "); Serial.print(velned.heading * 1e-5f); // same that divide on 100000.0f
  Serial.print(" Spd Acc: "); Serial.print(velned.sAcc * 1e-2f);
  Serial.print(" Crs/Hdg Acc: "); Serial.print(velned.cAcc/100000.0f);
  Serial.println();

  }

	#endif

}

void calcChecksum(unsigned char* CK, const void* INIT, size_t size) {
  memset(CK, 0, 2);
  const unsigned char* data = (const unsigned char*)INIT;
  for (size_t i = 0; i < size; i++) {
    CK[0] += data[i];
    CK[1] += CK[0];
  }
}

bool processGPSData(const int payloadSize, const unsigned char* header, void* dataStruct) {
  static int fpos = 0;
  static unsigned char checksum[2];

  while (Serial2.available()) {
    byte c = Serial2.read();
    if (fpos < 2) {
      if (c == header[fpos])
        fpos++;
      else
        fpos = 0;
    } else {
      if ((fpos - 2) < payloadSize)
        ((unsigned char*)dataStruct)[fpos - 2] = c;

      fpos++;

      if (fpos == (payloadSize + 2)) {
        calcChecksum(checksum, dataStruct, payloadSize);
      } else if (fpos == (payloadSize + 3)) {
        if (c != checksum[0])
          fpos = 0;
      } else if (fpos == (payloadSize + 4)) {
        fpos = 0;
        if (c == checksum[1]) {
          return true;
        }
      } else if (fpos > (payloadSize + 4)) {
        fpos = 0;
      }
    }
  }
  return false;
}

If compile code with

#define POSLLH
// #define VELNED

Output to serial is fast

iTOW:58672700 lat/lon: 49.51,8.76 height: 188.49 hMSL: 175.28 hAcc: 2.30 vAcc: 1.94
iTOW:58672800 lat/lon: 49.51,8.76 height: 188.51 hMSL: 175.29 hAcc: 2.30 vAcc: 1.94
iTOW:58672900 lat/lon: 49.51,8.76 height: 188.52 hMSL: 175.30 hAcc: 2.30 vAcc: 1.94
iTOW:58673000 lat/lon: 49.51,8.76 height: 188.54 hMSL: 175.31 hAcc: 2.30 vAcc: 1.94

If compile code with

// #define POSLLH
#define VELNED

Output to serial is fast also

iTOW: 67772500 velN: -0.04 velE: 0.53 velD: 0.18 Spd (3D): 0.54 GndSpd (2D): 0.54 Hdg (2D): 97.34 Spd Acc: 0.28 Crs/Hdg Acc: 25.05
iTOW: 67772600 velN: -0.07 velE: 0.52 velD: 0.17 Spd (3D): 0.57 GndSpd (2D): 0.53 Hdg (2D): 97.33 Spd Acc: 0.28 Crs/Hdg Acc: 25.05
iTOW: 67772700 velN: -0.05 velE: 0.53 velD: 0.19 Spd (3D): 0.58 GndSpd (2D): 0.54 Hdg (2D): 97.33 Spd Acc: 0.28 Crs/Hdg Acc: 25.05

When I choose to parse both messages, I get problems with slow message output, or rather the parsing itself is slow.
I don't know what the problem might be.
The only thing is that for some reason the ID is VELNED at 12, but 18.
I looked at 3 datasheets for neo-6 modules and everywhere it should be 0x12.

NAV_VELNED
cls: 1 id: 18 len: 36
NAV_POSLLH
cls: 1 id: 2 len: 28

Has anyone encountered something similar?

There are well known libraries that do work with Ublox GPSs.

Maybe explain what you are actually trying to do and what is needed for a library to 'work' with your GPS ?

1 Like

I would love to do this on my own, but the libraries don't work with my module.
Before this, I wrote code for another module GN02D, it issues the NMEA protocol, so I found a lot of libraries for it and everything worked almost right away.
I need speeds and coordinates
velN ; velE; velD;
and
lon; lat; hMSL;
I can’t get these params on a module with the NMEA protocol... I can’t yet.
I am sure that to obtain these velocity vectors, you just need to calculate the values of the velocities and directions of the vectors using algebraic formulas, but there is no time for this yet.
That's why I took up the UBX protocol.
But as I already wrote, the libraries do not work with my module, perhaps an old version, perhaps HardwareSerial in conjunction with the core, which I use to build the firmware in the arduino-cli ... I don’t know.
The code I wrote is simple.
But it only works well when I receive the packages individually.
And of course I need to receive both packages at the same time.

A few years ago I made a sketch that takes VELNED, POSLLH and SOL at 2.5Hz and airborne settings and outputs to a telemetry receiver (FR-Sky S-port) in my model aircraft. I also wanted to measure the 3D speed.

Not as nicely parsed as your code, but it worked just fine.

And i just looked in my code and I am testing for 0x12 to get VELNED

void loop() {
  if (Serial.available()) { // If GPS data is available
    byte GPS_info_char = Serial.read();  // Read it and store as HEX char
    if (GPS_info_char == 0xb5) { // ublox B5 record found
      message_started = true;
      received_char = 0;
    } // end ublox B5 record found
    else if (message_started == true) { // the message is already started and I got a new character
      if (received_char < string_length) { // buffer not full
        GPS_info_buffer[received_char] = GPS_info_char;
        if (GPS_info_buffer[2] == 0x12) string_length = 42;  //VELNED lenght is 36 + 6
        if (GPS_info_buffer[2] == 0x02) string_length = 34;  //POSLLH lenght is 28 + 6
        if (GPS_info_buffer[2] == 0x06) string_length = 58;  //SOL lenght is 52 + 6
        received_char++;
      } // end buffer not full
      else { // final character received
...................
1 Like

I'm confused why I get 0x18 from my parser :roll_eyes:
This is probably not a problem associated with the low speed of the parser for two messages, but still - why does it produce such a strange number that is not even in the datasheet?

I think you may have decimal and hexadecimal somewhere crossed

0x12 = 18 (decimal)

1 Like

Which module did you test your code with?
Have you configured the module to output messages other than 1 Hz?

Yes yes!
I'm a little stupid...
This code
Serial.print(" id: "); Serial.print(velned.id, HEX);
Outputs this
id: 12
Yes, that's not the point.
I'm also thinking about adding a port serial buffer to put the read values into.
Serial2.read();
But I'm not very sure that this will help.

i think it was the NEO6M , because when I moved to the NEO8M (or the Beitian 220) I started to use the PVT sentence as it has everything I need in one UBLOX sentence.

Yes, that is what the hex command strings are for, that you can see in my code. First I turn off all NMEA and then I write some command strings to the GPS to get it at 2.5HZ (or 5 Hz I forgot) and set it to airborne mode for allowing higher G-manouvres with my plane.
I had a china clone/counterfeit GPS and they don't have a functional memory, so you need to reconfigure them after every startup.

/*
       Sensor class       |  Physical ID | Application ID |        #define        |      Data type       | Poll time [ms]
  ------------------------+--------------+----------------+-----------------------+----------------------+-----------------
  FrSkySportSensorAss     |     ID10     |     0x0A00     | ASS_SPEED_DATA_ID     | air speed            | 500
  ------------------------+--------------+----------------+-----------------------+----------------------+-----------------
  FrskySportSensorGps     |     ID4      |     0x0800     | GPS_LAT_LON_DATA_ID   | latitude/longitude   | 1000
                          |              |     0x0820     | GPS_ALT_DATA_ID       | altitude             | 500
                          |              |     0x0830     | GPS_SPEED_DATA_ID     | ground speed         | 500
                          |              |     0x0840     | GPS_COG_DATA_ID       | COG                  | 500
                          |              |     0x0850     | GPS_DATE_TIME_DATA_ID | date/time            | 10000
  ------------------------+--------------+----------------+-----------------------+----------------------+-----------------
*/

#include <SPort.h>                           // Include the SPort library
SPortHub hub(0x12, 2);                       // Hardware ID 0x12, Software serial pin 2
SimpleSPortSensor gps_lat_lon(0x0800);       // GPS latitude / longitude
SimpleSPortSensor gps_alt(0x0820);           // GPS altitude
SimpleSPortSensor gps_speed(0x0830);         // GPS speed
SimpleSPortSensor gps_cog(0x0840);           // GPS course over ground
//SimpleSPortSensor gps_date_time(0x0850);     // GPS date / time
SimpleSPortSensor ass_airspeed(0x0A00);      // ASS airspeed


//format hex command strings for sending to UBLOX
const static char velned[] = {0xb5, 0x62, 0x06, 0x01, 0x03, 0x00, 0x01, 0x12, 0x01, 0x1e, 0x67};
const static char posllh[] = {0xb5, 0x62, 0x06, 0x01, 0x03, 0x00, 0x01, 0x02, 0x01, 0x0e, 0x47};
const static char sol[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0x01, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x17, 0xDa};
const static char airborne[] = {0xb5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xff, 0xff, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27,
                                0x00, 0x00, 0x05, 0x00, 0xfa, 0x00, 0xfa, 0x00, 0x64, 0x00, 0x2c, 0x01, 0x00, 0x3c, 0x00, 0x00,
                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0b, 0xb5, 0x62, 0x06, 0x24,
                                0x00, 0x00, 0x2a, 0x84
                               };
const static char rate_a[] = {0xb5, 0x62, 0x06, 0x08, 0x06, 0x00, 0x90, 0x01, 0x01, 0x00, 0x01, 0x00, 0xa7, 0x1f}; // 2.5 Hz
const static char rate_b[] = {0xb5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x0e, 0x30}; // 2.5 Hz



#define GPS_INFO_BUFFER_SIZE 60 // enough for both the strings (SOL is only 52 long)
byte GPS_info_buffer[GPS_INFO_BUFFER_SIZE];
byte string_length = 58;  //VELNED lenght is 42, POSLLH lenght is 34, SOL length is 58. Initialize with longest string.
unsigned int received_char;
int i = 0; // counter
bool message_started = false;

unsigned long velocity_3d; // speed in centimeter / second
float spd_kts; //speed in knots
long longitude ;//longitude
long latitude ;//latitude
long height_above_sea_level;//height above main sea level
unsigned long ground_speed ;
float heading;

//CHECKSUM CALCULATION VARIABLE
unsigned char CK_A = 0;
unsigned char CK_B = 0;

byte sendlat = 0; // toggle to send either latitude or longitude in same field
byte fixok = 0; // flag to indicate a valid GPS fix was received.

void setup() {

  pinMode(13, OUTPUT);
  hub.registerSensor(gps_lat_lon);      //Add sensor to the hub
  hub.registerSensor(gps_alt);          //Add sensor to the hub
  hub.registerSensor(gps_speed);        //Add sensor to the hub
  hub.registerSensor(gps_cog);          //Add sensor to the hub
//  hub.registerSensor(gps_date_time);    //Add sensor to the hub
  hub.registerSensor(ass_airspeed);     //Add sensor to the hub
  hub.begin();                          //Start listening
  delay(2000); // wait for GPS to boot.
  // Configure the telemetry serial port and sensors (remember to use & to specify a pointer to sensor)

  Serial.begin(9600);
  delay(100);
  Serial.println(F("$PUBX,40,RMC,0,0,0,0*47")); //RMC OFF
  delay(100);
  Serial.println(F("$PUBX,40,VTG,0,0,0,0*5E")); //VTG OFF
  delay(100);
  Serial.println(F("$PUBX,40,GGA,0,0,0,0*5A")); //GGA OFF
  delay(100);
  Serial.println(F("$PUBX,40,GSA,0,0,0,0*4E")); //GSA OFF
  delay(100);
  Serial.println(F("$PUBX,40,GSV,0,0,0,0*59")); //GSV OFF
  delay(100);
  Serial.println(F("$PUBX,40,GLL,0,0,0,0*5C")); //GLL OFF
  delay(100);

  Serial.write(airborne, sizeof(airborne)); //set GPS mode to airborne < 4g
  delay(100);
  Serial.write(rate_a, sizeof(rate_a)); //set GPS update rate to 4Hz (1st string)
  delay(100);
  Serial.write(rate_b, sizeof(rate_b)); //set GPS update rate to 4Hz (2nd string)
  delay(100);
  Serial.write(velned, sizeof(velned)); // VELNED (VELocity, North, East, Down) message ON
  delay(100);
  Serial.write(posllh, sizeof(posllh)); // POSLLH (POSition Latitude Longitude Height) message ON
  delay(100);
  Serial.write(sol, sizeof(sol)); // POSLLH (POSition Latitude Longitude Height) message ON
} // end setup

void loop() {
  if (Serial.available()) { // If GPS data is available
    byte GPS_info_char = Serial.read();  // Read it and store as HEX char
    if (GPS_info_char == 0xb5) { // ublox B5 record found
      message_started = true;
      received_char = 0;
    } // end ublox B5 record found
    else if (message_started == true) { // the message is already started and I got a new character
      if (received_char < string_length) { // buffer not full
        GPS_info_buffer[received_char] = GPS_info_char;
        if (GPS_info_buffer[2] == 0x12) string_length = 42;  //VELNED lenght is 36 + 6
        if (GPS_info_buffer[2] == 0x02) string_length = 34;  //POSLLH lenght is 28 + 6
        if (GPS_info_buffer[2] == 0x06) string_length = 58;  //SOL lenght is 52 + 6
        received_char++;
      } // end buffer not full
      else { // final character received
        GPS_info_buffer[received_char] = GPS_info_char;
        UBX_calculate_checksum(GPS_info_buffer, 1, (string_length - 2)); // calculates checksum

        switch (string_length) {
          case 42: // VELNED string received
            if (CK_A == GPS_info_buffer[41] && CK_B == GPS_info_buffer[42]) { // confirm received and calculated checksums are the same
              digitalWrite(13, LOW);
              // convert the GPS buffer array bytes 24,23,22,21 into one "unsigned long" (4 byte) field
              velocity_3d   = (long)GPS_info_buffer[24] << 24;
              velocity_3d  += (long)GPS_info_buffer[23] << 16;
              velocity_3d  += (long)GPS_info_buffer[22] << 8;
              velocity_3d  += (long)GPS_info_buffer[21];
              spd_kts = (((float)velocity_3d * 1.94381) / 100);

              // convert the GPS buffer array bytes 28,27,26,25 into one "unsigned long" (4 byte) field
              ground_speed  = (long)GPS_info_buffer[28] << 24;
              ground_speed += (long)GPS_info_buffer[27] << 16;
              ground_speed += (long)GPS_info_buffer[26] << 8;
              ground_speed += (long)GPS_info_buffer[25];
              ground_speed = ((float)ground_speed / 100);

              // convert the GPS buffer array bytes 32,31,30,29 into one "unsigned long" (4 byte) field
              heading  = (long)GPS_info_buffer[32] << 24;
              heading += (long)GPS_info_buffer[31] << 16;
              heading += (long)GPS_info_buffer[30] << 8;
              heading += (long)GPS_info_buffer[29];
              heading = ((float)heading / 100000);
            }
            else {// when checksum error set speed = 0
              spd_kts = 0;
              ground_speed = 0;
              heading = 0;
            }

            gps_speed.value = ground_speed;               //in 0.001 kts
            gps_cog.value = heading;                  //in 0.01 degrees
//            gps_date_time.value = 0;                //t.b.d. difficult to understand formula/mechanism
            ass_airspeed.value = spd_kts;            //in 0.1 kts

            break;
          case 34: //POSLLH string received

            if (CK_A == GPS_info_buffer[33] && CK_B == GPS_info_buffer[34]) { // confirm received and calculated checksums are the same
              // convert the GPS buffer array bytes into one "long" (4 byte) field
              longitude   = (long)GPS_info_buffer[12] << 24;
              longitude  += (long)GPS_info_buffer[11] << 16;
              longitude  += (long)GPS_info_buffer[10] << 8;
              longitude  += (long)GPS_info_buffer[9];

              latitude   = (long)GPS_info_buffer[16] << 24;
              latitude  += (long)GPS_info_buffer[15] << 16;
              latitude  += (long)GPS_info_buffer[14] << 8;
              latitude  += (long)GPS_info_buffer[13];

              height_above_sea_level   = (long)GPS_info_buffer[24] << 24;
              height_above_sea_level  += (long)GPS_info_buffer[23] << 16;
              height_above_sea_level  += (long)GPS_info_buffer[22] << 8;
              height_above_sea_level  += (long)GPS_info_buffer[21];
              height_above_sea_level  = (height_above_sea_level / 1000);
            }
            else {
              longitude = 0;
              latitude = 0;
              height_above_sea_level = 0;
            }
            // Set GPS data

            if (sendlat == 0) {
              gps_lat_lon.value = (( ((((uint32_t)( longitude < 0 ? -longitude : longitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF)  | 0x80000000;
              if (longitude < 0) gps_lat_lon.value |= 0x40000000;
              sendlat = 1;
            }
            else {
              gps_lat_lon.value = ((  ((((uint32_t)( latitude < 0 ? -latitude : latitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF ) ;
              if (latitude < 0) gps_lat_lon.value |= 0x40000000;
              sendlat = 0;
            }
            gps_alt.value = height_above_sea_level;                //in cm but per 10cm displayed
            break;
          case 58: // SOL string received
            if (CK_A == GPS_info_buffer[57] && CK_B == GPS_info_buffer[58]) { // confirm received and calculated checksums are the same
              fixok = (( GPS_info_buffer[11 + 5] & B00000001) && ( GPS_info_buffer[10 + 5]) == 0x03); // fix status flag = 1 AND a 3Dfix
              digitalWrite(13, fixok);
            }
          break;
        }
        message_started = false; // ready for the new message
      } // end final character received
    } // end the message is already started and I got a new character
  } // end If GPS data is available

  hub.handle();                         //Handle new data

} // end loop

// CALCULATES THE UBX CHECKSUM OF A CHAR ARRAY
void UBX_calculate_checksum(uint8_t* array, unsigned int array_start, unsigned int array_length) {
  CK_A = 0;
  CK_B = 0;
  unsigned int i;
  for (i = 0; i < array_length; i++) {
    CK_A = CK_A + array[array_start + i];
    CK_B = CK_B + CK_A;
  }
}
1 Like

That's how I made the sketch. The GPS is connected to the hardware serial port (promini only has one serial port) and the telemetry outputs on a software serial anyhow (as it is an inverted serial signal).

1 Like

My neo-6m module seems to have no problems saving settings, even after turning off the power, everything is saved.

Settings for module directly from the firmware - that's cool!
What license are you writing your code under?

Free to use for non commercial purposes. That's also inherited from the Telemetry library I used.

1 Like

I was able to run your code.
Your messages are displayed in telemetry using
SimpleSPortSensor gps_lat_lon(0x0800);
I just rewrote it for serial port.

lat_lon: -2344867749
height: 176
gSpeed: 0
heading: 93
speed: 1
lat_lon: 64345780
height: 176
gSpeed: 0
heading: 95
speed: 1

Latitude and longitude are displayed one at a time for some reason, apparently this is intended.
The latitude/longitude values turn out strange, although the code seems to be doing division.

if (sendlat == 0) {
//   gps_lat_lon.value = (( ((((uint32_t)( longitude < 0 ? -longitude : longitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF)  | 0x80000000;
  nav.lat_lon = (( ((((uint32_t)( longitude < 0 ? -longitude : longitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF)  | 0x80000000;
//   if (longitude < 0) gps_lat_lon.value |= 0x40000000;
  if (longitude < 0) nav.lat_lon |= 0x40000000;
  sendlat = 1;
  Serial.print(" lat_lon: "); Serial.println(nav.lat_lon/10000000.0f);
}
else {
//   gps_lat_lon.value = ((  ((((uint32_t)( latitude < 0 ? -latitude : latitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF ) ;
  nav.lat_lon = ((  ((((uint32_t)( latitude < 0 ? -latitude : latitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF ) ;
//   if (latitude < 0) gps_lat_lon.value |= 0x40000000;
  if (latitude < 0) nav.lat_lon |= 0x40000000;
  sendlat = 0;
  Serial.print(" lat_lon: "); Serial.println(nav.lat_lon/10000000.0f);
}

Previously in my code I was getting the latitude and longitude separately and dividing the resulting values by 10 million.

Serial.print(" lat/lon: ");
Serial.print(posllh.lat/10000000.0f);
Serial.print(",");
Serial.print(posllh.lon/10000000.0f);

Could you explain the logic behind getting the fields from the message?
For example, you get the heading value like this:

 heading = (long)GPS_info_buffer[32] << 24;
 heading += (long)GPS_info_buffer[31] << 16;
 heading += (long)GPS_info_buffer[30] << 8;
 heading += (long)GPS_info_buffer[29];
 heading = ((float)heading / 100000);


The VELNED packet is 32 bytes long, for some reason you add 6 to it, I don’t understand why?
The heading has OFFSET = 24, but I need to get the velN, velE and velD parameters.
How can I do this, I can’t understand the logic?

Yes, the FRSky telemetry protocol for latitude and longitude gives you headache. It uses the same datafield for lat & lon and does not want a negative value. So the lat and lon values get processed in a way that eliminates negatives and creates a number range for lat and a number range for lon. After that number has been transmitted by telemetry to my RC-transmitter, it is decrypted again in the reverse order to get lat and lon again.
Must be to save telemetry bandwidth or some other reason, but it's complicated.

You don't need all of that and just take the "longitude" and "latitude" fields and work with those directly.

The heading is in the buffer from 29 - 32 as 4 separate signed bytes ("I4" in "number format" column in the definition of the VELNED sentence. It also has 5 decimal points (see column "scaling")
So I need to "glue" those four bytes together into one 4 byte field.
Now first I had to know if these 4 bytes were Big or Little Endian
You can find in the datasheet that they are Little Endian.

Using the definition of Little Endianness
increasing numeric significance with increasing memory addresses (or increasing time), known as little-endian.
I can bit-shift them in place into the "heading" field.
And in the end I need to divide by 10.000 because that was defined as the scaling.
Which is also the reason why I declared "heading" as a float instead of a long.

I do not understand where you got the 32 from. That's wrong, and I am also wrong.
The VELNED payload is 36 long
The VELNED Header is 2 long
The VELNED ID is 2 long
The VELNED checksum is 2 long

So in total it is 42 long

You see me adding 36 + 6 = 42

But..... I forgot that the "length" field is also a 2 byte field that is between ID and Payload

So in fact VELNED is 44 long.
which is why my checksum calculation work while I tell it the checksum fields are at position [41] and [42] in my buffer. Not storing the "0xB5" in the array and observing the fact that the first position is [0], it works in a ugly way.

You will have to parse the velN, velE and velD fields separately in a similar way I parse the "speed" field.

Would you mind sharing your complete code? I would like to understand how you used the
struct . I never used that, nor know the concept, so may be able to learn that from you.

I need to clean up my code. I do not store the 0xB5 value, I have the package lenghts wrong and I use the (wrongly defined) string length in my case statement, while there is a predefined "ID" field in the UBX sentence. I am having trouble understanding my own code from the past, so probably time to tidy things up.

1 Like

I have mistake, right lenght 36 bytes for nav VELNED packet.

I put the code for sending binary settings to the GPS module into define and commented it out.
My module freezes when receiving data NOT from the u-center, so I configure it only from a program from a PC, and not in the microcontroller code.

#define SERIAL_BAUD 115200
#define GNSS_BAUD 115200

#define STATUS_LED PC13

HardwareSerial Serial2(USART2); // PA2(TX2), PA3(RX2)

//format hex command strings for sending to UBLOX
const static char velned[] = {0xb5, 0x62, 0x06, 0x01, 0x03, 0x00, 0x01, 0x12, 0x01, 0x1e, 0x67};
const static char posllh[] = {0xb5, 0x62, 0x06, 0x01, 0x03, 0x00, 0x01, 0x02, 0x01, 0x0e, 0x47};
const static char sol[] = {0xb5, 0x62, 0x06, 0x01, 0x08, 0x00, 0x01, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x17, 0xDa};
const static char airborne[] = {0xb5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xff, 0xff, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27,
                                0x00, 0x00, 0x05, 0x00, 0xfa, 0x00, 0xfa, 0x00, 0x64, 0x00, 0x2c, 0x01, 0x00, 0x3c, 0x00, 0x00,
                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0b, 0xb5, 0x62, 0x06, 0x24,
                                0x00, 0x00, 0x2a, 0x84
                               };
const static char rate_a[] = {0xb5, 0x62, 0x06, 0x08, 0x06, 0x00, 0x90, 0x01, 0x01, 0x00, 0x01, 0x00, 0xa7, 0x1f}; // 2.5 Hz
const static char rate_b[] = {0xb5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x0e, 0x30}; // 2.5 Hz



// #define SETUP

#define GPS_INFO_BUFFER_SIZE 60 // enough for both the strings (SOL is only 52 long)
byte GPS_info_buffer[GPS_INFO_BUFFER_SIZE];
byte string_length = 58;  //VELNED lenght is 42, POSLLH lenght is 34, SOL length is 58. Initialize with longest string.
unsigned int received_char;
int i = 0; // counter
bool message_started = false;

unsigned long iTOW; // time of week
long velN; // speed in centimeter / second
long velE; // speed in centimeter / second
long velD; // speed in centimeter / second
unsigned long velocity_3d; // speed in centimeter / second
float spd_kts; //speed in knots
long longitude ;//longitude
long latitude ;//latitude
long height_above_sea_level;//height above main sea level
unsigned long ground_speed ;
float heading;

//CHECKSUM CALCULATION VARIABLE
unsigned char CK_A = 0;
unsigned char CK_B = 0;

// byte sendlat = 0; // toggle to send either latitude or longitude in same field
byte fixok = 0; // flag to indicate a valid GPS fix was received.

struct NAV {
  // posllh
  unsigned char cls_posllh;
  unsigned char id_posllh;
  unsigned short len_posllh;
  unsigned long iTOW_posllh;
  long lon;
  long lat;
  long lat_lon;
  long height;
  long hMSL;
  unsigned long hAcc;
  unsigned long vAcc;
  // velned
  unsigned char cls_velned;
  unsigned char id_velned;
  unsigned short len_velned;
  unsigned long iTOW_velned; // U4
  long velN; // I4
  long velE;
  long velD;
  unsigned long speed; // Spd (3D)
  unsigned long gSpeed;
  long heading;
  unsigned long sAcc;
} nav;

void setup() {

  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, LOW); // stm32f411 led ON
//   hub.registerSensor(gps_lat_lon);      //Add sensor to the hub
//   hub.registerSensor(gps_alt);          //Add sensor to the hub
//   hub.registerSensor(gps_speed);        //Add sensor to the hub
//   hub.registerSensor(gps_cog);          //Add sensor to the hub
//  hub.registerSensor(gps_date_time);    //Add sensor to the hub
//   hub.registerSensor(ass_airspeed);     //Add sensor to the hub
//   hub.begin();                          //Start listening
  delay(2000); // wait for GPS to boot.
  // Configure the telemetry serial port and sensors (remember to use & to specify a pointer to sensor)

    Serial.begin(SERIAL_BAUD);
    Serial2.begin(GNSS_BAUD);

#ifdef SETUP

  delay(100);
  Serial.println(F("$PUBX,40,RMC,0,0,0,0*47")); //RMC OFF
  delay(100);
  Serial.println(F("$PUBX,40,VTG,0,0,0,0*5E")); //VTG OFF
  delay(100);
  Serial.println(F("$PUBX,40,GGA,0,0,0,0*5A")); //GGA OFF
  delay(100);
  Serial.println(F("$PUBX,40,GSA,0,0,0,0*4E")); //GSA OFF
  delay(100);
  Serial.println(F("$PUBX,40,GSV,0,0,0,0*59")); //GSV OFF
  delay(100);
  Serial.println(F("$PUBX,40,GLL,0,0,0,0*5C")); //GLL OFF
  delay(100);

  Serial.write(airborne, sizeof(airborne)); //set GPS mode to airborne < 4g
  delay(100);
  Serial.write(rate_a, sizeof(rate_a)); //set GPS update rate to 4Hz (1st string)
  delay(100);
  Serial.write(rate_b, sizeof(rate_b)); //set GPS update rate to 4Hz (2nd string)
  delay(100);
  Serial.write(velned, sizeof(velned)); // VELNED (VELocity, North, East, Down) message ON
  delay(100);
  Serial.write(posllh, sizeof(posllh)); // POSLLH (POSition Latitude Longitude Height) message ON
  delay(100);
  Serial.write(sol, sizeof(sol)); // POSLLH (POSition Latitude Longitude Height) message ON

#endif

} // end setup

void loop() {
  if (Serial2.available()) { // If GPS data is available
    byte GPS_info_char = Serial2.read();  // Read it and store as HEX char
    if (GPS_info_char == 0xb5) { // ublox B5 record found
      message_started = true;
      received_char = 0;
    } // end ublox B5 record found
    else if (message_started == true) { // the message is already started and I got a new character
      if (received_char < string_length) { // buffer not full
        GPS_info_buffer[received_char] = GPS_info_char;
        if (GPS_info_buffer[2] == 0x12) string_length = 42;  //VELNED lenght is 36 + 6
        if (GPS_info_buffer[2] == 0x02) string_length = 34;  //POSLLH lenght is 28 + 6
        if (GPS_info_buffer[2] == 0x06) string_length = 58;  //SOL lenght is 52 + 6
        received_char++;
      } // end buffer not full
      else { // final character received
        GPS_info_buffer[received_char] = GPS_info_char;
        UBX_calculate_checksum(GPS_info_buffer, 1, (string_length - 2)); // calculates checksum

        switch (string_length) {
          case 42: // VELNED string received
            if (CK_A == GPS_info_buffer[41] && CK_B == GPS_info_buffer[42]) { // confirm received and calculated checksums are the same
              digitalWrite(STATUS_LED, HIGH); // stm32f411 led OFF
              // convert the GPS buffer array bytes 5,6,7,8 into one "unsigned long" (4 byte) field
              iTOW   = (long)GPS_info_buffer[8] << 24;
              iTOW  += (long)GPS_info_buffer[7] << 16;
              iTOW  += (long)GPS_info_buffer[6] << 8;
              iTOW  += (long)GPS_info_buffer[5];
              nav.iTOW_velned = iTOW;
              Serial.print(" iTOW: "); Serial.println(nav.iTOW_velned);
              // convert the GPS buffer array bytes 9,10,11,12 into one "unsigned long" (4 byte) field
              velN   = (long)GPS_info_buffer[12] << 24;
              velN  += (long)GPS_info_buffer[11] << 16;
              velN  += (long)GPS_info_buffer[10] << 8;
              velN  += (long)GPS_info_buffer[9];
              nav.velN = velN;
              Serial.print(" velN: "); Serial.println(nav.velN * 1e-2f);
              // convert the GPS buffer array bytes 13,14,15,16 into one "unsigned long" (4 byte) field
              velE   = (long)GPS_info_buffer[16] << 24;
              velE  += (long)GPS_info_buffer[15] << 16;
              velE  += (long)GPS_info_buffer[14] << 8;
              velE  += (long)GPS_info_buffer[13];
              nav.velE = velE;
              Serial.print(" velE: "); Serial.println(nav.velE * 1e-2f);
              // convert the GPS buffer array bytes 17,18,19,20 into one "unsigned long" (4 byte) field
              velD   = (long)GPS_info_buffer[20] << 24;
              velD  += (long)GPS_info_buffer[19] << 16;
              velD  += (long)GPS_info_buffer[18] << 8;
              velD  += (long)GPS_info_buffer[17];
              nav.velD = velD;
              Serial.print(" velD: "); Serial.println(nav.velD * 1e-2f);

              // convert the GPS buffer array bytes 24,23,22,21 into one "unsigned long" (4 byte) field
              velocity_3d   = (long)GPS_info_buffer[24] << 24;
              velocity_3d  += (long)GPS_info_buffer[23] << 16;
              velocity_3d  += (long)GPS_info_buffer[22] << 8;
              velocity_3d  += (long)GPS_info_buffer[21];
              spd_kts = (((float)velocity_3d * 1.94381) / 100);

              // convert the GPS buffer array bytes 28,27,26,25 into one "unsigned long" (4 byte) field
              ground_speed  = (long)GPS_info_buffer[28] << 24;
              ground_speed += (long)GPS_info_buffer[27] << 16;
              ground_speed += (long)GPS_info_buffer[26] << 8;
              ground_speed += (long)GPS_info_buffer[25];
              ground_speed = ((float)ground_speed / 100);

              // convert the GPS buffer array bytes 32,31,30,29 into one "unsigned long" (4 byte) field
              heading  = (long)GPS_info_buffer[32] << 24;
              heading += (long)GPS_info_buffer[31] << 16;
              heading += (long)GPS_info_buffer[30] << 8;
              heading += (long)GPS_info_buffer[29];
              heading = ((float)heading / 100000);
            }
            else {// when checksum error set speed = 0
              spd_kts = 0;
              ground_speed = 0;
              heading = 0;
            }

            // gps_speed.value = ground_speed;               //in 0.001 kts
            nav.gSpeed = ground_speed;               //in 0.001 kts
            Serial.print(" gSpeed: "); Serial.println(nav.gSpeed);
            // gps_cog.value = heading;                  //in 0.01 degrees
            nav.heading = heading;                  //in 0.01 degrees
            Serial.print(" heading: "); Serial.println(nav.heading);
//            gps_date_time.value = 0;                //t.b.d. difficult to understand formula/mechanism
            // ass_airspeed.value = spd_kts;            //in 0.1 kts
            nav.speed = spd_kts;            //in 0.1 kts
            Serial.print(" speed: "); Serial.println(nav.speed);

            break;
          case 34: //POSLLH string received

            if (CK_A == GPS_info_buffer[33] && CK_B == GPS_info_buffer[34]) { // confirm received and calculated checksums are the same
              // convert the GPS buffer array bytes into one "long" (4 byte) field
              longitude   = (long)GPS_info_buffer[12] << 24;
              longitude  += (long)GPS_info_buffer[11] << 16;
              longitude  += (long)GPS_info_buffer[10] << 8;
              longitude  += (long)GPS_info_buffer[9];

              Serial.print(" lat/lon: ");
              Serial.print(longitude/10000000.0f);

              latitude   = (long)GPS_info_buffer[16] << 24;
              latitude  += (long)GPS_info_buffer[15] << 16;
              latitude  += (long)GPS_info_buffer[14] << 8;
              latitude  += (long)GPS_info_buffer[13];

              Serial.print(","); 
              Serial.println(latitude/10000000.0f);

              height_above_sea_level   = (long)GPS_info_buffer[24] << 24;
              height_above_sea_level  += (long)GPS_info_buffer[23] << 16;
              height_above_sea_level  += (long)GPS_info_buffer[22] << 8;
              height_above_sea_level  += (long)GPS_info_buffer[21];
              height_above_sea_level  = (height_above_sea_level / 1000);

              nav.height = height_above_sea_level;                //in cm but per 10cm displayed
              Serial.print(" height: "); Serial.println(nav.height);
            }
            else {
              longitude = 0;
              latitude = 0;
              height_above_sea_level = 0;
            }
            break;
            // Set GPS data
            // if (sendlat == 0) {
            //   gps_lat_lon.value = (( ((((uint32_t)( longitude < 0 ? -longitude : longitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF)  | 0x80000000;
              // // nav.lat_lon = (( ((((uint32_t)( longitude < 0 ? -longitude : longitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF)  | 0x80000000;
            //   if (longitude < 0) gps_lat_lon.value |= 0x40000000;
              // if (longitude < 0) nav.lat_lon |= 0x40000000;
              // sendlat = 1;
              // Serial.print(" lat_lon: "); Serial.println(nav.lat_lon);
            // }
            // else {
            //   gps_lat_lon.value = ((  ((((uint32_t)( latitude < 0 ? -latitude : latitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF ) ;
              // // nav.lat_lon = ((  ((((uint32_t)( latitude < 0 ? -latitude : latitude)) / 10 ) * 6 ) / 10 ) & 0x3FFFFFFF ) ;
            //   if (latitude < 0) gps_lat_lon.value |= 0x40000000;
              // if (latitude < 0) nav.lat_lon |= 0x40000000;
              // sendlat = 0;
              // Serial.print(" lat_lon: "); Serial.println(nav.lat_lon);e);
            // }
            // gps_alt.value = height_above_sea_level;                //in cm but per 10cm displayed
          case 58: // SOL string received
            if (CK_A == GPS_info_buffer[57] && CK_B == GPS_info_buffer[58]) { // confirm received and calculated checksums are the same
              fixok = (( GPS_info_buffer[11 + 5] & B00000001) && ( GPS_info_buffer[10 + 5]) == 0x03); // fix status flag = 1 AND a 3Dfix
              digitalWrite(STATUS_LED, !fixok);
            }
          break;
        }
        // ubxPrint();
        message_started = false; // ready for the new message
      } // end final character received
    } // end the message is already started and I got a new character
  } // end If GPS data is available
//   hub.handle();                         //Handle new data
} // end loop

// CALCULATES THE UBX CHECKSUM OF A CHAR ARRAY
void UBX_calculate_checksum(uint8_t* array, unsigned int array_start, unsigned int array_length) {
  CK_A = 0;
  CK_B = 0;
  unsigned int i;
  for (i = 0; i < array_length; i++) {
    CK_A = CK_A + array[array_start + i];
    CK_B = CK_B + CK_A;
  }
}

void ubxPrint(){

//   Serial.println("GPS_UBX");
//   Serial.print(" cls: "); Serial.print(velned.cls);
  // Serial.print(" id: "); Serial.print(velned.id);
//   Serial.print(" id: "); Serial.print(velned.id, HEX);
//   Serial.print(" len: "); Serial.print(velned.len);
//   Serial.print(" iTOW: "); Serial.print(velned.iTOW); // GPS Millisecond Time of Week
//   Serial.print(" velN: "); Serial.print(velned.velN * 1e-2f); // m/s
//   Serial.print(" velE: "); Serial.print(velned.velE * 1e-2f); // m/s
//   Serial.print(" velD: "); Serial.print(velned.velD * 1e-2f); // m/s
//   Serial.print(" Spd (3D): "); Serial.print(velned.speed * 1e-2f); // m/s
  Serial.print(" height: "); Serial.print(nav.height); // m/s
  Serial.print(" lat_lon: "); Serial.print(nav.lat_lon); // m/s
  Serial.print(" speed: "); Serial.print(nav.speed); // m/s
  Serial.print(" gSpeed: "); Serial.print(nav.gSpeed); // m/s
  Serial.print(" heading: "); Serial.print(nav.heading); // m/s
//   Serial.print(" GndSpd (2D): "); Serial.print(velned.gSpeed * 1e-2f); // m/s
//   Serial.print(" Hdg (2D): "); Serial.print(velned.heading * 1e-5f); // same that divide on 100000.0f
//   Serial.print(" Spd Acc: "); Serial.print(velned.sAcc * 1e-2f);
//   Serial.print(" Crs/Hdg Acc: "); Serial.print(velned.cAcc/100000.0f);
  Serial.println();

}

You have a lot of extra variables, one type is converted to another.
Float to long and back.
This is an extra load on the microcontroller.
To get one field from a message package, you can use only two variables.
One to receive data from the buffer, and the second directly to receive the value, and put everything into a structure.
But in general, your code works quickly, thanks for your help.
The libraries could not get the data from my module.
I'm now interested in getting velN, velE and velD from the NMEA package.
There, you will probably need to mathematically calculate the velocity vectors from the cardinal directions and velocities using the right triangle formula.

1 Like

Yes. I did that for the NeoM8, where I only parse the UBLOX PVT sentence. That does not have the 3D speed in one parameter.

That goes like this

              velN   = (long)GPS_info_buffer[51+5] << 24;
              velN  += (long)GPS_info_buffer[50+5] << 16;
              velN  += (long)GPS_info_buffer[49+5] << 8;
              velN  += (long)GPS_info_buffer[48+5];

              velE   = (long)GPS_info_buffer[55+5] << 24;
              velE  += (long)GPS_info_buffer[54+5] << 16;
              velE  += (long)GPS_info_buffer[53+5] << 8;
              velE  += (long)GPS_info_buffer[52+5];

              velD   = (long)GPS_info_buffer[59+5] << 24;
              velD  += (long)GPS_info_buffer[58+5] << 16;
              velD  += (long)GPS_info_buffer[57+5] << 8;
              velD  += (long)GPS_info_buffer[56+5];
              if (fixcount >8) spd_kts = (((float)(sqrt(sq(velN) + sq(velE) + sq(velD))) * 1.94381) / 100);
              else spd_kts = 0;

Ignore the "fixcount". I want to have 8 valid GPS fixes before I acknowledge the speed. The model airplanes make 12 G turns, which is waaaay more than the 4G the GPS can accomodate. I envy the military GPS's, that I suspect do not have this limitation.

I calculate the speed in knots, as it arrives in cm/s from the GPS

And of course the arraypositions, where you find the velN, E, D are different

1 Like

Have you tried getting the speed and direction of these speeds from NMEA messages?
I needed to get the numSV - GSV parameter and it is contained only in the NMEA message.
Those. In order to work with UBX, I turned off all NMEA messages, and I have no idea what to do now.

No, I have them from the UBLOX PVT messaage (does not exist on Neo-M6N), but they are also in the VELNED message. See in the NAV-VELNED payload table. They are at offset 4, 8 and 12.

Why do you need the 3 separate speed vectors, if you intend to do the trigonomy to calculate the 3D speed? While you have the 3D speed already calculated for you by the GPS in the "Speed" field. I am confused about your application.

e.g. if you want the speed vector, you take heading and 3D speed and you have it. If you only want ground speed you ignore the velD. and calculate it from velN and velE.

1 Like

You misunderstood, I have two GPS modules NEO-6M and GN02D.
GN02D can only send NMEA messages.
And for the application I need to obtain 3D speeds, so for now I cannot use the GN02D module for my purposes, I am not satisfied with the UBX protocol because of its complexity and redundancy.

If you have to parse NMEA anyhow and are already familiar with it, I'd stick to that.

The reason I used UBX protocol is because it is compact, so I can get a high navigation refresh rate. This especially counts for the PVT sentence that come out of the NEO-M7N and higher. All I need in 92 bytes.

1 Like

I also like the UBX protocol for its speed, but I don’t like it because I couldn’t get all the libraries to work with the NEO-6M module :grinning:


Now I am getting the number of satellites from the SOL message.
satInView: 6
You also got the 3D-FIX value 0x03 from it.
I receive the number of satellites and everything works fine, but why is there a difference in parsing SOL messages from the same VELNED?
For example, to get the longitude value you need to write the following code:

longitude   = (long)GPS_info_buffer[12] << 24;
longitude  += (long)GPS_info_buffer[11] << 16;
longitude  += (long)GPS_info_buffer[10] << 8;
longitude  += (long)GPS_info_buffer[9];
nav.lon = longitude;

And to get a message about the number of satellites, you need to write the following code:

satInView = GPS_info_buffer[53]; // 47 + 5
nav.numSV = satInView;

I just do what you do, but I still can’t understand the logic.
It seems to me that you moved the registers to the side, counted them and brought them back to normal.