NEMA input commands

I have a Sparkfun MAX M10S which is outputting NEMA sentences via the UART set to 9600 BAUD. I see in the NEMA standards/Input Messages, that I should be able to send commands to this device and change many things including its BAUD. I have my Pro Micro’s UART RX connected to the TX of the GNSS and it works fine. I connected the Pro Micro’s UART TX to the RX of the GNSS and get no noticeable state changes. For example, I used the example from table 2-4 to change the output to binary. This should cause my software to not receive data anymore. Yet, I still get data. I also tried to change the BAUD but it had no effect. I double checked the CRC and it is correct. I used a CRC generator and verified that I got the same number for the example.

Has anyone been able to successfully send NEMA commands to this GNSS device?

I tried it on an older ublox GPS and it didn’t work there either.

Hi @rgsparber ,

Please tell us which M10 document you are looking at. I couldn’t find Table 2-4 that you mentioned.

Please also tell us which Pro Micro board you are using. There are several. Most are 3.3V, but some older ones are 5V. Please be careful. The u-blox MAX-M10S is 3.3V. You may hit problems if you are using a 5V Pro Micro.

I’ve put together a generic NMEA polling example (see below). It will set the rate of the standard NMEA messages to zero, using the UBX VALSET configuration interface. Then it polls GNGGA using the xxGNQ message. All of the bits you need are in there; there is code to calculate both the UBX and NMEA checksums (they are different).

I hope this helps,
Paul

// Communicate with the u-blox MAX-M10S over Serial (UART)
// Set the rate of the standard NMEA messages to zero
// Poll individual NMEA messages

#define myConsole Serial // Change to e.g. SerialUSB if needed
#define mySerial Serial1 // Change this if needed

void ubxWriteByteUpdateChecksum(uint8_t *csum1, uint8_t *csum2, uint8_t b)
{
  // Send a single UBX byte and update the checksums
  
  mySerial.write(b);
  *csum1 = *csum1 + b;
  *csum2 = *csum2 + *csum1;
}

void ubxValset(uint32_t key, uint8_t val_8)
{
  // Send a UBX CFG VALSET message with a 32-bit key and an 8-bit value
  
  uint8_t csum1 = 0;
  uint8_t csum2 = 0;
  
  mySerial.write(0xb5); // UBX sync character 1 - not in checksum
  mySerial.write(0x62); // UBX sync character 2 - not in checksum
  
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x06); // Class CFG
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x8a); // ID VALSET

  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x09); // Length LSB
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Length MSB

  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Version 0x00
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x01); // Layers : update configuration in RAM layer only
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Reserved
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Reserved

  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >>  0) & 0xFF); // key LSB
  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >>  8) & 0xFF); // key
  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >> 16) & 0xFF); // key
  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >> 24) & 0xFF); // key MSB
  
  ubxWriteByteUpdateChecksum(&csum1, &csum2, val_8); // value (8-bit)

  mySerial.write(csum1); // Checksum CK_A - not included in length
  mySerial.write(csum2); // Checksum CK_B - not included in length
}

void nmeaWriteByteUpdateChecksum(uint8_t *csum, uint8_t b)
{
  // Send a single NMEA byte and update the checksum
  
  mySerial.write(b);
  *csum = *csum ^ b;
}

void nmeaWriteBytesUpdateChecksum(uint8_t *csum, const char *b)
{
  // Loop through NMEA text, send each byte and update checksum
  
  for (int i = 0; i < strlen(b); i++)
  {
    nmeaWriteByteUpdateChecksum(csum, b[i]);
  }
}

void nmeaWriteChecksumCRLF(uint8_t csum)
{
  // Send the NMEA checksum as two ASCII characters, plus CR and LF
  
  uint8_t nibble = csum >> 4; // Get 4 MS bits
  nibble += 0x30; // Convert to ASCII 0-9
  if (nibble >= 0x3A)
    nibble += 0x41 - 0x3A; // Convert to ASCII A-F
  mySerial.write(nibble);
  
  nibble = csum & 0xF; // Get 4 LS bits
  nibble += 0x30; // Convert to ASCII 0-9
  if (nibble >= 0x3A)
    nibble += 0x41 - 0x3A; // Convert to ASCII A-F
  mySerial.write(nibble);

  mySerial.write(0x0D); // CR
  mySerial.write(0x0A); // LF
}

void pollNMEA(const char *msgID)
{
  // Poll a single standard NMEA message

  uint8_t csum = 0;

  mySerial.write('$'); // Dollar - not in checksum  
  nmeaWriteBytesUpdateChecksum(&csum, "EIGNQ"); // Poll a standard message (Talker ID GN)
  nmeaWriteByteUpdateChecksum(&csum, ','); // Comma
  nmeaWriteBytesUpdateChecksum(&csum, msgID); // message ID
  mySerial.write('*'); // Asterix - not in checksum
  nmeaWriteChecksumCRLF(csum); // Write 2-byte ASCII checksum and CR, LF
}

void setup()
{
  delay(1000);

  myConsole.begin(115200); // Begin Serial at 115200 baud
  myConsole.println("SparkFun MAX-M10S NMEA Example");

  mySerial.begin(9600); // Communicate with the GNSS at 9600 baud. Change this to 38400 if needed

  // Set the rate of NMEA GGA, GLL, GSA, GSV, RMC, VTG on UART1 to zero
  ubxValset(0x209100bb, 0); // CFG-MSGOUT-NMEA_ID_GGA_UART1
  ubxValset(0x209100ca, 0); // CFG-MSGOUT-NMEA_ID_GLL_UART1
  ubxValset(0x209100c0, 0); // CFG-MSGOUT-NMEA_ID_GSA_UART1
  ubxValset(0x209100c5, 0); // CFG-MSGOUT-NMEA_ID_GSV_UART1
  ubxValset(0x209100ac, 0); // CFG-MSGOUT-NMEA_ID_RMC_UART1
  ubxValset(0x209100b1, 0); // CFG-MSGOUT-NMEA_ID_VTG_UART1

  // Read and discard the acknowledgements for the UBX CFG VALSET messages
  delay(1000);
  while (mySerial.available())
  {
    mySerial.read();
  }
}

void loop()
{

  // Poll GNGGA
  
  pollNMEA("GGA");

  // NMEA could take a full second to arrive (if the navigation rate is 1Hz).
  // Echo everything from mySerial to myConsole until a LF (0x0A) is received
  // or two seconds have elapsed.
  
  unsigned long startTime = millis();
  uint8_t c = 0;
  while ((c != 0x0A) && (millis() < (startTime + 2000)))
  {
    if (mySerial.available())
    {
      c = mySerial.read();
      myConsole.write(c);
    }
  }

}

PS. That code example won’t work on older modules like the MAX-M8 as they don’t support the VALSET configuration interface. You would need to use UBX-CFG-MSG instead of UBX-CFG-VALSET. Let me know if you need help modifying the code for that.

Best,
Paul

What type of output are you getting in the serial monitor? Totally blank?

Hi @ahsrabrifat ,

Your AI-generated replies often miss the point completely. Please make sure the replies are actually useful before you post them.

Thank you,
Paul

2 Likes

Hello Paul,

Thanks for that additional info!

Is that code known to be working? I ask because the M10 Interface Description seems to indicate that the method to ‘poll a standard message (Talker ID GN)’ is to send ‘$EIGNQ,RMC3A\r\n’, which appears to be just like you are doing, but that doesn’t work for me. The doc. further states that the ‘EI’ is the ‘Talker ID of the device requesting the poll’. So, I tried sending 'GNGNQ,RMC21\r\n’, and got a GNRMC NMEA string in return. Interestingly, when I try the same technique for ‘GP’ Talker ID, the module returns an error.

This is the first time I’ve played with a GNSS module that can potentially spit out data from different satellite sources, so I’m just getting up to speed!

John

Hi John (@ezflyr ),

Yes, tried and tested on MAX-M10S. I’m using a SparkFun ESP32 Thing Plus C, but I tried to keep the code example as generic as possible so it would run on many platforms.

With no antenna attached, you should see this:

SparkFun MAX-M10S NMEA Example
$GNGGA,,,,,,0,00,99.99,,,,,,*56
$GNGGA,,,,,,0,00,99.99,,,,,,*56
$GNGGA,,,,,,0,00,99.99,,,,,,*56

With an antenna, the time and then position will be displayed as the GNSS acquires signals

You can check what the code is sending to the module by doing a “loop-back” test. Disconnect the GNSS and connect TX directly to RX on your microcontroller. You should see this repeating quickly:

$EIGNQ,GGA*39
$EIGNQ,GGA*39
$EIGNQ,GGA*39

I noticed that the “*” is missing in your messages. Please check you are including that.

The “EI” doesn’t really matter. There is a post discussing that here.

I have an early version of the MAX-M10S, so it may be responding slightly differently to yours.

If I try to request GPRMC using $EIGPQ,RMC*3A , I see $GNTXT,01,01,01,NMEA unknown msg*46. To fix this, I need to set the “Talker ID” to “GP” first. You can do this by adding:

ubxValset(0x20930031, 1); // CFG-NMEA-MAINTALKERID : 0:Auto, 1:GP, 2:GL, 3:GN, 4:GA, 5:GB

Then I see:

$GPRMC,,V,,,,,,,,,,N,V*29
$GPRMC,,V,,,,,,,,,,N,V*29
$GPRMC,,V,,,,,,,,,,N,V*29

Remember to set the Talker ID back to 0 (Auto) or 3 (GN) if you want to request GNRMC or GNGGA again.

The ubxValset code only changes the configuration in RAM, so the configuration will reset when you disconnect the power. If you want to set the configuration in battery-backed RAM too, you need to change the code to:

ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x03); // Layers : 1:RAM, 2:BBR, 4:Flash, 3:RAM+BBR

I’ll share the full updated code in my next post.

Enjoy!
Paul

@ezflyr : Here is the full updated example

Hope this comes in useful,
Paul

// Communicate with the u-blox MAX-M10S over Serial (UART)
// Set the rate of the standard NMEA messages to zero
// Set the NMEA Talker ID
// Poll individual NMEA messages

#define myConsole Serial // Change to e.g. SerialUSB if needed
#define mySerial Serial1 // Change this if needed

void ubxWriteByteUpdateChecksum(uint8_t *csum1, uint8_t *csum2, uint8_t b)
{
  // Send a single UBX byte and update the checksums
  
  mySerial.write(b);
  *csum1 = *csum1 + b;
  *csum2 = *csum2 + *csum1;
}

void ubxSendMessage(uint8_t CLASS, uint8_t ID, uint16_t LENGTH, const uint8_t *msg)
{
  // Send a user-defined UBX message. Add the sync chars and checksum.
  // Remember that UBX uses Little-Endian (Least Significant Byte first) formatting.
  // See "key LSB" - "key MSB" code in ubxValset below.
  // This code assumes the data in msg is correctly formatted.
  
  uint8_t csum1 = 0;
  uint8_t csum2 = 0;
  
  mySerial.write(0xb5); // UBX sync character 1 - not in checksum
  mySerial.write(0x62); // UBX sync character 2 - not in checksum
  
  ubxWriteByteUpdateChecksum(&csum1, &csum2, CLASS); // Class
  ubxWriteByteUpdateChecksum(&csum1, &csum2, ID); // ID

  ubxWriteByteUpdateChecksum(&csum1, &csum2, LENGTH & 0xFF); // Length LSB
  ubxWriteByteUpdateChecksum(&csum1, &csum2, LENGTH >> 8); // Length MSB

  for (uint16_t i = 0; i < LENGTH; i++)
  {
    ubxWriteByteUpdateChecksum(&csum1, &csum2, msg[i]);
  }

  mySerial.write(csum1); // Checksum CK_A - not included in length
  mySerial.write(csum2); // Checksum CK_B - not included in length
}

void ubxValset(uint32_t key, uint8_t val_8)
{
  // Send a UBX CFG VALSET message with a 32-bit key and an 8-bit value
  
  uint8_t csum1 = 0;
  uint8_t csum2 = 0;
  
  mySerial.write(0xb5); // UBX sync character 1 - not in checksum
  mySerial.write(0x62); // UBX sync character 2 - not in checksum
  
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x06); // Class CFG
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x8a); // ID VALSET

  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x09); // Length LSB
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Length MSB

  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Version 0x00
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x01); // Layers : 1:RAM, 2:BBR, 4:Flash, 3:RAM+BBR
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Reserved
  ubxWriteByteUpdateChecksum(&csum1, &csum2, 0x00); // Reserved

  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >>  0) & 0xFF); // key LSB
  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >>  8) & 0xFF); // key
  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >> 16) & 0xFF); // key
  ubxWriteByteUpdateChecksum(&csum1, &csum2, (key >> 24) & 0xFF); // key MSB
  
  ubxWriteByteUpdateChecksum(&csum1, &csum2, val_8); // value (8-bit)

  mySerial.write(csum1); // Checksum CK_A - not included in length
  mySerial.write(csum2); // Checksum CK_B - not included in length
}

void nmeaWriteByteUpdateChecksum(uint8_t *csum, uint8_t b)
{
  // Send a single NMEA byte and update the checksum
  
  mySerial.write(b);
  *csum = *csum ^ b;
}

void nmeaWriteBytesUpdateChecksum(uint8_t *csum, const char *b)
{
  // Loop through NMEA text, send each byte and update checksum
  
  for (int i = 0; i < strlen(b); i++)
  {
    nmeaWriteByteUpdateChecksum(csum, b[i]);
  }
}

void nmeaWriteChecksumCRLF(uint8_t csum)
{
  // Send the NMEA checksum as two ASCII characters, plus CR and LF
  
  uint8_t nibble = csum >> 4; // Get 4 MS bits
  nibble += 0x30; // Convert to ASCII 0-9
  if (nibble >= 0x3A)
    nibble += 0x41 - 0x3A; // Convert to ASCII A-F
  mySerial.write(nibble);
  
  nibble = csum & 0xF; // Get 4 LS bits
  nibble += 0x30; // Convert to ASCII 0-9
  if (nibble >= 0x3A)
    nibble += 0x41 - 0x3A; // Convert to ASCII A-F
  mySerial.write(nibble);

  mySerial.write(0x0D); // CR
  mySerial.write(0x0A); // LF
}

void pollNMEA(const char *msgID)
{
  // Poll a single standard NMEA message

  uint8_t csum = 0;

  mySerial.write('$'); // Dollar - not in checksum  
  nmeaWriteBytesUpdateChecksum(&csum, "EIGNQ"); // Poll a standard message (Talker ID GN)
  nmeaWriteByteUpdateChecksum(&csum, ','); // Comma
  nmeaWriteBytesUpdateChecksum(&csum, msgID); // message ID
  mySerial.write('*'); // Asterix - not in checksum
  nmeaWriteChecksumCRLF(csum); // Write 2-byte ASCII checksum and CR, LF
}

void sendNMEA(const char *message)
{
  // Send a NMEA message. Add the $,*,checksum,CR,LF

  uint8_t csum = 0;

  mySerial.write('$'); // Dollar - not in checksum  
  nmeaWriteBytesUpdateChecksum(&csum, message); // Send the message
  mySerial.write('*'); // Asterix - not in checksum
  nmeaWriteChecksumCRLF(csum); // Write 2-byte ASCII checksum and CR, LF  
}

void setup()
{
  delay(1000);

  myConsole.begin(115200); // Begin Serial at 115200 baud
  myConsole.println("SparkFun MAX-M10S NMEA Example");

  mySerial.begin(9600); // Communicate with the GNSS at 9600 baud. Change this to 38400 if needed

  // Set the rate of NMEA GGA, GLL, GSA, GSV, RMC, VTG on UART1 to zero using UBX-CFG-VALSET
  // Note: these will be NACK'd on the MAX-M8 as it does not support the configuration interface
  ubxValset(0x209100bb, 0); // CFG-MSGOUT-NMEA_ID_GGA_UART1
  ubxValset(0x209100ca, 0); // CFG-MSGOUT-NMEA_ID_GLL_UART1
  ubxValset(0x209100c0, 0); // CFG-MSGOUT-NMEA_ID_GSA_UART1
  ubxValset(0x209100c5, 0); // CFG-MSGOUT-NMEA_ID_GSV_UART1
  ubxValset(0x209100ac, 0); // CFG-MSGOUT-NMEA_ID_RMC_UART1
  ubxValset(0x209100b1, 0); // CFG-MSGOUT-NMEA_ID_VTG_UART1

//  // To disable the standard NMEA messages on the MAX-M8 we need to use UBX-CFG-MSG
//  const uint8_t NMEAGGARate[] = { 0xf0, 0x00, 0x00 }; // { CLASS:NMEA, ID:GGA, Rate:0 }
//  ubxSendMessage(0x06, 0x01, 0x03, NMEAGGARate ); // CLASS:CFG, ID:MSG, LENGTH:3
//  const uint8_t NMEAGLLRate[] = { 0xf0, 0x01, 0x00 }; // { CLASS:NMEA, ID:GLL, Rate:0 }
//  ubxSendMessage(0x06, 0x01, 0x03, NMEAGLLRate ); // CLASS:CFG, ID:MSG, LENGTH:3
//  const uint8_t NMEAGSARate[] = { 0xf0, 0x02, 0x00 }; // { CLASS:NMEA, ID:GSA, Rate:0 }
//  ubxSendMessage(0x06, 0x01, 0x03, NMEAGSARate ); // CLASS:CFG, ID:MSG, LENGTH:3
//  const uint8_t NMEAGSVRate[] = { 0xf0, 0x03, 0x00 }; // { CLASS:NMEA, ID:GSV, Rate:0 }
//  ubxSendMessage(0x06, 0x01, 0x03, NMEAGSVRate ); // CLASS:CFG, ID:MSG, LENGTH:3
//  const uint8_t NMEARMCRate[] = { 0xf0, 0x04, 0x00 }; // { CLASS:NMEA, ID:RMC, Rate:0 }
//  ubxSendMessage(0x06, 0x01, 0x03, NMEARMCRate ); // CLASS:CFG, ID:MSG, LENGTH:3
//  const uint8_t NMEAVTGRate[] = { 0xf0, 0x05, 0x00 }; // { CLASS:NMEA, ID:VTG, Rate:0 }
//  ubxSendMessage(0x06, 0x01, 0x03, NMEAVTGRate ); // CLASS:CFG, ID:MSG, LENGTH:3
  
  // Set the NMEA Talker ID
  ubxValset(0x20930031, 0); // CFG-NMEA-MAINTALKERID : 0:Auto, 1:GP, 2:GL, 3:GN, 4:GA, 5:GB

  // Read and discard the acknowledgements for the UBX CFG VALSET messages
  delay(1000);
  while (mySerial.available())
  {
    mySerial.read();
  }
}

void loop()
{

  // Poll GNGGA
  
  //pollNMEA("GGA");

  // Poll GNRMC using sendNMEA
  
  sendNMEA("EIGNQ,RMC");

  // NMEA could take a full second to arrive (if the navigation rate is 1Hz).
  // Echo everything from mySerial to myConsole until a LF (0x0A) is received
  // or two seconds have elapsed.
  
  unsigned long startTime = millis();
  uint8_t c = 0;
  while ((c != 0x0A) && (millis() < (startTime + 2000)))
  {
    if (mySerial.available())
    {
      c = mySerial.read();
      myConsole.write(c);
    }
  }

}
1 Like

Thanks for the superb coding example. Your comments and variable names make it very clear. Since I only need to send one string of characters and I’m short on memory, I would do the number crunching by hand and then do a simple print using any needed byte swaps plus CRC calculations.

The document I’m using is “SiRF NMEA Reference Manual”, Revision 2.1, December 2007 and the table is on page 2-3. I found no mention of byte swapping in there.

I’m using the Pro Micro 5V 16 MHz board. Until now, it was only receiving data from the 3.3V GPS or GNSS so there wasn’t an issues except a reduction in logic 1 noise margin. I forgot that when I transmit from the Pro Micro to the GPS or GNSS, there is a problem. I have since added a voltage divider (400 ohms on top and 600 ohms on the bottom) so the logic 1 seen by the GPS or GNSS is within spec. I might have damaged the receiver which adds an unknown here.

I do process NMEA sentences correctly by using

character = Serial1.read();

I can connect the Pro Micro’s TX and RX together and when I execute

Serial1.println(“$PSRF100,1,19200,8,1,0*39”);

I see the correct characters. Notice that I did not swap the lower and upper bytes of each character for either the read or print.

I connect the GPS or GNSS TX to the RX of the Pro Micro and all works fine. It is just at 9600 BAUD which is slower than I would like.

I connect the Pro Micro TX to my voltage divider and its output to the GPS or GNSS RX, and my scope says the levels are correct but I don’t loop get a change in BAUD. Maybe this is because I blew the GPS RX input. I hope to get a new GPS in a few days to finish this test.

Hi @rgsparber ,

Thanks for the update.

The “SiRF NMEA Reference Manual” is the manual for old GPS chips made by SiRF. In particular the “PSRF” message is Proprietary to SiRF.

For the u-blox MAX-M10S, you need to refer to their M10 Interface Description. Normally you would use the u-blox UBX binary protocol to configure the module. But the module does support some PUBX NMEA messages, Proprietary to UBloX. The CONFIG (PUBX,41) message allows you to set the baud rate. E.g.:

$PUBX,41,1,0007,0003,19200,0*25\r\n

Ah, OK, here’s something I didn’t know. There is a RATE (PUBX,40) message which can be used to set the NMEA message output rate. That helps a lot. So, in your case, you may not need to use the UBX protocol at all. Updated code is attached below.

I hope this helps,
Paul

// Communicate with the u-blox MAX-M10S over Serial (UART)
// Set the rate of the standard NMEA messages to zero
// Poll individual NMEA messages

#define myConsole Serial // Change to e.g. SerialUSB if needed
#define mySerial Serial1 // Change this if needed

void echoSerialTimeout(unsigned long timeout)
{
  // Echo serial data from mySerial to myConsole until a timeout occurs
  
  unsigned long startTime = millis();
  while (millis() < (startTime + timeout))
  {
    while (mySerial.available())
    {
      myConsole.write(mySerial.read());
    }
  }
}

void echoSerialTerminatorTimeout(uint8_t terminator, unsigned long timeout)
{
  // Echo serial data from mySerial to myConsole until terminator
  // is received or a timeout occurs
  
  unsigned long startTime = millis();
  uint8_t c = 0;
  while ((c != terminator) && (millis() < (startTime + timeout)))
  {
    if (mySerial.available())
    {
      c = mySerial.read();
      myConsole.write(c);
    }
  }
}

void nmeaWriteByteUpdateChecksum(uint8_t *csum, uint8_t b)
{
  // Send a single NMEA byte and update the checksum
  
  mySerial.write(b);
  *csum = *csum ^ b;
}

void nmeaWriteBytesUpdateChecksum(uint8_t *csum, const char *b)
{
  // Loop through NMEA text, send each byte and update checksum
  
  for (int i = 0; i < strlen(b); i++)
  {
    nmeaWriteByteUpdateChecksum(csum, b[i]);
  }
}

void nmeaWriteChecksumCRLF(uint8_t csum)
{
  // Send the NMEA checksum as two ASCII characters, plus CR and LF
  
  uint8_t nibble = csum >> 4; // Get 4 MS bits
  nibble += 0x30; // Convert to ASCII 0-9
  if (nibble >= 0x3A)
    nibble += 0x41 - 0x3A; // Convert to ASCII A-F
  mySerial.write(nibble);
  
  nibble = csum & 0xF; // Get 4 LS bits
  nibble += 0x30; // Convert to ASCII 0-9
  if (nibble >= 0x3A)
    nibble += 0x41 - 0x3A; // Convert to ASCII A-F
  mySerial.write(nibble);

  mySerial.write(0x0D); // CR
  mySerial.write(0x0A); // LF
}

void sendNMEA(const char *message)
{
  // Send a NMEA message. Add the $,*,checksum,CR,LF

  uint8_t csum = 0;

  mySerial.write('$'); // Dollar - not in checksum  
  nmeaWriteBytesUpdateChecksum(&csum, message); // Send the message
  mySerial.write('*'); // Asterix - not in checksum
  nmeaWriteChecksumCRLF(csum); // Write 2-byte ASCII checksum and CR, LF  
}

void setup()
{
  delay(1000);

  myConsole.begin(115200); // Begin Serial at 115200 baud
  myConsole.println("SparkFun MAX-M10S NMEA Example");

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

  myConsole.println("Beginning communication at 9600 baud");

  mySerial.begin(9600); // Communicate with the GNSS at 9600 baud

  echoSerialTimeout(1000); // Echo everything for 1 second
  
  myConsole.println("Setting baud rate to 38400");

  // Set port 1 (UART1) to: NMEA+UBX In, NMEA+UBX Out, 38400 baud, no autobauding
  sendNMEA("PUBX,41,1,0003,0003,38400,0");

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

  myConsole.println("Beginning communication at 38400 baud");

  mySerial.begin(38400); // Communicate with the GNSS at 38400 baud

  echoSerialTimeout(1000); // Echo everything for 1 second

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

  myConsole.println("Setting NMEA message rates to zero on all ports");

  sendNMEA("PUBX,40,GGA,0,0,0,0,0");
  sendNMEA("PUBX,40,GLL,0,0,0,0,0");
  sendNMEA("PUBX,40,GSA,0,0,0,0,0");
  sendNMEA("PUBX,40,GSV,0,0,0,0,0");
  sendNMEA("PUBX,40,RMC,0,0,0,0,0");
  sendNMEA("PUBX,40,VTG,0,0,0,0,0");

  echoSerialTimeout(1000); // Echo everything for 1 second to empty the buffer
}

void loop()
{
  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

  // Poll GNGGA
  
  myConsole.println("Polling GNGGA:");

  sendNMEA("EIGNQ,GGA");

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

  // NMEA could take a full second to arrive (if the navigation rate is 1Hz).
  // Echo everything from mySerial to myConsole until a LF (0x0A) is received
  // or two seconds have elapsed.
  
  echoSerialTerminatorTimeout(0x0a, 2000);

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
}

Paul,
I have been stymied by the 8-bit Fletcher checksum. I think all else would make sense if I could just get the correct value.

I had to give back the NEO 10 so am left with the 6. Different docs but the message format is the same.

In “u-blox 6 Receiver Description”, Document number GPS.G6-SW-10018-F 68000, 18 Apr 2013, Document status Revision for FW 7.03 (Public Release) on page 83 is the example

$PUBX,41,1,0007,0003,19200,0*25

As I understand it, I perform the checksum over

PUBX,41,1,0007,0003,19200,0

where all characters are treated as ASCII. That didn’t give me 2 5. So I went back to the description of the various fields and treated them as bytes and numeric. That didn’t work either.

I know this must be blindly obvious, but I don’t see it.

Thanks in advance,
Rick
PS: in studying your code, it appears that you are using a simple checksum and not the Fletcher 8bit version specified in the docs. I’m confused…

Hi Rick (@rgsparber ),

The u-blox UBX binary protocol uses a true Fletcher checksum: sum of bytes, sum of sums.

But the NMEA protocol uses a simple exclusive-OR checksum:

Ex-or all the bytes between the $ and *
Excluding the $ and *
The hexadecimal checksum is then appended, expressed as two ASCII characters (“0” - “9”, “A” - “F”).
Terminate with Carriage Return and Line Feed

P is 0x50
U is 0x55

0 is 0x30

0x50 ^ 0x55 ^ … ^ 0x30 = 0x25

I hope this helps,
Paul

Thank you so much! The fog has cleared!

Rick

1 Like

I’m making progress in understanding this subject but now find that I can’t duplicate the Fletcher results. I found this example:

UBX-CFG-CFG
B5 62 06 09 0D 00 FF FF 00 00 00 00 00 00 FF FF 00 00 03 1B 9A

As I understand it:
B5 62 is the header
06 is the class (CFG)
09 is the ID
0D 00 is the length, Little Endian so the value is 00 0D or decimal 13 bytes
I count off 13 bytes of payload and see that the last 2 bytes are 1B and 9A which should be CK_A and CK_B

I used a spreadsheet to calculate CK_A and CK_B over the class, ID, length, and payload but do not get 1B 9A.

Is there something wrong in my assumptions or do I have a bug in my spreadsheet?

Thanks,
Rick

Hi Rick,

You have a bug in your spreadsheet… :wink:

Please ensure you are duplicating the code in ubxWriteByteUpdateChecksum above correctly.

For each byte after the 0x62: sum the bytes, and sum the sums, all modulo-256.

Byte CKA CKB
0x06 0x06 0x06
0x09 0x0f 0x15
0x0d 0x1c 0x31

I hope this helps,
Paul

It sure did unstick me. I found the bug in my spreadsheet and things are making sense again. Hopefully this is the end of my confusion and I can get on to sending the gps commands.

I am writing this up from the viewpoint of a totally confused newbie. I will publish it on Rick.Sparber.org.

Thanks!
Rick

No worries Rick - thanks for the update,
Paul