Sending RTCM data over I2C to NEO-M8P-2 from SparkFun Thing Plus via QWIIC

I have a smartphone app that transmits RTCM data over Bluetooth to a SparkFun Thing Plus - ESP32 WROOM board. I want to pipe this data to a NEO-M8P-W board that I’m borrowing from someone else. The most logical way is to send the data via I2C using the existing QWIIC connection, especially since I’m not able to solder anything to the NEO-M8P-2.

Thing Plus:

https://www.sparkfun.com/products/retired/14689

NEO-M8P-2:

https://www.sparkfun.com/products/15005 … 1561122775

I’ve modified SparkFun’s provided “Example3_GetPosition” sketch to add Bluetooth. I collect the incoming bytes of RTCM data in an array and send them out all at once after the entire message is received.

Original here; my code pasted below:

https://github.com/sparkfun/SparkFun_Ub … sition.ino

When using serial and jumping the TX pin on the Thing Plus to the RX pin on the NEO-M8P-2 using a wire jumper, this communication works. However, when using the same exact array over I2C, I can’t seem to get it working. According to SparkFun’s hookup guide, it should be possible to pipe the data over I2C.

Hookup guide here:

https://learn.sparkfun.com/tutorials/gp … -guide/all

I’ve seen in a couple places that adding a 0xFF byte before the data transmission is required. I tried this, but it doesn’t seem to make any difference.

Is there something I’m missing, or is this module not capable of receiving RTCM data over I2C?

/*
  Reading lat and long via UBX binary commands - no more NMEA parsing!
  By: Nathan Seidle
  SparkFun Electronics
  Date: January 3rd, 2019
  License: MIT. See license file for more information but you can
  basically do whatever you want with this code.

  This example shows how to query a Ublox module for its lat/long/altitude. We also
  turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic 
  dramatically.

  Note: Long/lat are large numbers because they are * 10^7. To convert lat/long
  to something google maps understands simply divide the numbers by 10,000,000. We 
  do this so that we don't have to use floating point numbers.

  Leave NMEA parsing behind. Now you can simply ask the module for the datums you want!

  Feel like supporting open source hardware?
  Buy a board from SparkFun!
  ZED-F9P RTK2: https://www.sparkfun.com/products/15136
  NEO-M8P RTK: https://www.sparkfun.com/products/15005
  SAM-M8Q: https://www.sparkfun.com/products/15106

  Hardware Connections:
  Plug a Qwiic cable into the GPS and a BlackBoard
  If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425)
  Open the serial monitor at 115200 baud to see the output
*/

#include <Wire.h> //Needed for I2C to GPS

#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_Ublox_GPS
SFE_UBLOX_GPS myGPS;

#include "BluetoothSerial.h"
BluetoothSerial ESP_BT;

long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module.

void setup()
{
  Serial.begin(115200);
  while (!Serial); //Wait for user to open terminal
  Serial.println("SparkFun Ublox Example");

  Wire.begin();

  if (myGPS.begin() == false) //Connect to the Ublox module using Wire port
  {
    Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing."));
    while (1);
  }

  myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise)
  myGPS.saveConfiguration(); //Save the current settings to flash and BBR

  ESP_BT.begin("ESP32");
  Serial1.begin(9600);
}

void loop()
{
  //Query module only every second. Doing it more often will just cause I2C traffic.
  //The module only responds when a new position is available
  if (millis() - lastTime >= 1000)
  {
    if (ESP_BT.available()){ //check Bluetooth for incoming data
      int msgIdx = 0;
      byte msgIn[1023] = {}; 
      while (ESP_BT.available())
      {
        msgIn[msgIdx] = (byte)ESP_BT.read(); //insert new data into array
        msgIdx++; //keep track of position in array
      }
      Wire.beginTransmission(0x42);
      //Wire.write(0xFF);             //doesn't seem to make a difference
      Wire.write(msgIn, msgIdx);    //not working; unknown reason
      Wire.endTransmission();
      Serial1.write(msgIn, msgIdx); //works; pin 17 on WROOM to RX/MOSI on NEO-M8P-2
    }
    lastTime = millis(); //Update the timer
    
    long latitude = myGPS.getLatitude();
    //Serial.print(F("Lat: "));
    //Serial.print(latitude);

    long longitude = myGPS.getLongitude();
    //Serial.print(F(" Long: "));
    //Serial.print(longitude);
    //Serial.print(F(" (degrees * 10^-7)"));

    long altitude = myGPS.getAltitude();
    //Serial.print(F(" Alt: "));
    //Serial.print(altitude);
    //Serial.print(F(" (mm)"));

    byte SIV = myGPS.getSIV();
    //Serial.print(F(" SIV: "));
    //Serial.print(SIV);

    //Serial.println();

    double fLat = (double)latitude;
    double fLon = (double)longitude;

    Serial.print(fLat/10000000.0, 7);
    Serial.print(", ");
    Serial.print(fLon/10000000.0, 7);
    Serial.println();
  }
}

Has anyone had any success sending RTCM data over I2C?

Hello,

Let me first say that we can not guarantee or fully support custom code and modifications. That being said I will try to help as much as possible. If, at any point, you’d like to file a GitHub issue ticket on the Ublox libraries our engineers can try to take a look at the issue if they are able to, https://github.com/sparkfun/SparkFun_Ub … ry/issues/

There could be several explinations for your issue:

  • Arduino writes 8 bits at a time on the I2C line, only one Wire.write may not be sending the full packet

  • https://cdn.sparkfun.com/assets/2/f/6/8 … to,-74,793, Datasheet indicates that the RTCM messages are configured, by default, to communicate over UART. The module may have to be special configured for I2C to accept RTCM messages. Unfortunately, I can’t seem to find explicit documentation from Ublox for the process for this change.
  • Due to the specific nature of this issue, I would recommend reaching out to Ublox for their guidance on how to use I2C for RTCM packets.

    Hi Brandon, thanks for the reply.

    According to the Arduino documentation on the Wire library, the Wire.write() function does support sending an array of data. I’ve also tried sending the data one byte at a time, and that didn’t work either.

    https://www.arduino.cc/en/Reference/WireWrite

    I guess I’ll have to get in touch with u-blox and see if they can help.

    While I do appreciate all the work that went into the hookup guide and provided examples, I have to be completely honest here for a moment. It’s a little frustrating that it’s this hard to figure out, even though SparkFun’s guide mentions that this feature is available. That’s one of the reasons I went down this route.

    Anyway, thanks again for the reply.

    Hi, did you find a solution to ‘pipe’ RTCM messages to the rover over I2C? I have two ZED-F9P connected with qwiic cables to an Adafruit QTPY. I was hoping to simply ‘pipe’ the RTCM corrections from one unit to the other …but no success :frowning: .

    After reading the Hook up guide, I get the impression that this should be possible…

    “The processRTCM() function allows you to ‘pipe’ just the RTCM correction data to the channel of your choice.” … Simply push the RTCM bytes from your back channel into one of the ports (UART, SPI, I2C)

    If this is possible, I would apricate an example sketch.

    No, I never got the RTCM data working over I2C.

    I’m not even getting a full lock using UART. The RTK light flashes, indicating that it’s receiving the data, but it never shuts off to indicate that the RTK lock is established. It’s really frustrating.

    Hi --dd-- / NFT,

    Hidden in ZED-F9P Example5_RelativePositioningInformation is the code you need to (e.g.) receive RTCM data from a Base via Serial1 and push it to the Rover ZED over I2C:

    https://github.com/sparkfun/SparkFun_u- … #L148-L162

    Have a look at Example3_StartRTCMBase for the code you need to intercept the RTCM data from the Base ZED and transmit it on Serial1:

    https://github.com/sparkfun/SparkFun_u- … #L184-L187

    Best wishes,

    Paul

    Thanks again Paul, I’ll give it a try tomorrow.

    Thanks Paul.

    This example doesn’t seem to work for me. It may be because I’m using a NEO-M8P-2, not a ZED-F9P.

    When I try adapting this example to the code I’m using for the NEO-M8P-2, I get the following message: “‘class SFE_UBLOX_GPS’ has no member named ‘pushRawData’”. Is there an equivalent function for the SFE_UBLOX_GPS class?

    @NFT Are you using the new sparkfun GNSS library? Could that be the issue?

    https://github.com/sparkfun/SparkFun_u- … no_Library

    The function is there: https://github.com/sparkfun/SparkFun_u- … .cpp#L2959

    Hi NFT,

    Looks like you might be mixing up the old and new versions of the library?

    Old version:

    https://github.com/sparkfun/SparkFun_Ub … mation.ino

    New version:

    https://github.com/sparkfun/SparkFun_u- … mation.ino

    Both should work but please use the new version if you can.

    Best wishes,

    Paul

    OK, I’ve updated to the most recent version of the library and replaced my existing sketch with the one found here, as recommended:

    https://github.com/sparkfun/SparkFun_u- … #L148-L162

    I’ve replaced the “Serial1” code with code that uses “BluetoothSerial.h” and updated the rest of the code accordingly to use the BT link in place of Serial1. I also made sure to uncomment the #define that enables the communication.

    Here’s what my code looks like:

    #include <Wire.h> //Needed for I2C to GNSS
    
    #include <SparkFun_u-blox_GNSS_Arduino_Library.h> //http://librarymanager/All#SparkFun_u-blox_GNSS
    SFE_UBLOX_GNSS myGNSS;
    
    #include "BluetoothSerial.h"
    BluetoothSerial ESP_BT;
    
    #define USE_BT // Uncomment this line to push the RTCM data from ESP_BT to the module via I2C
    
    size_t numBytes = 0; // Record the number os bytes received from ESP_BT
    
    void setup()
    {
      Serial.begin(115200);
      while (!Serial); //Wait for user to open terminal
      Serial.println("u-blox Base station example");
    
    #ifdef USE_BT
      // If our board supports it, we can receive the RTCM data on ESP_BT
      ESP_BT.begin("ESP32");
    #endif
    
      Wire.begin();
      Wire.setClock(400000); //Increase I2C clock speed to 400kHz
    
      if (myGNSS.begin() == false) //Connect to the u-blox module using Wire port
      {
        Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing."));
        while (1);
      }
    
      // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate
      //myGNSS.factoryDefault(); delay(5000);
    
    #ifdef USE_BT
      Serial.print(F("Enabling UBX and RTCM input on I2C. Result: "));
      Serial.print(myGNSS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_RTCM3)); //Enable UBX and RTCM input on I2C
      myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR
    #endif
    }
    
    void loop()
    {
      // From v2.0, the data from getRELPOSNED (UBX-NAV-RELPOSNED) is returned in UBX_NAV_RELPOSNED_t packetUBXNAVRELPOSNED
      // Please see u-blox_structs.h for the full definition of UBX_NAV_RELPOSNED_t
      // You can either read the data from packetUBXNAVRELPOSNED directly
      // or can use the helper functions: getRelPosN/E/D; getRelPosAccN/E/D
      if (myGNSS.getRELPOSNED() == true)
      {
        Serial.print("relPosN: ");
        Serial.println(myGNSS.getRelPosN(), 4); // Use the helper functions to get the rel. pos. as m
        Serial.print("relPosE: ");
        Serial.println(myGNSS.getRelPosE(), 4);
        Serial.print("relPosD: ");
        Serial.println(myGNSS.getRelPosD(), 4);
    
        Serial.print("relPosLength: ");
        Serial.println(myGNSS.packetUBXNAVRELPOSNED->data.relPosLength);
        Serial.print("relPosHeading: ");
        Serial.println(myGNSS.packetUBXNAVRELPOSNED->data.relPosHeading);
    
        Serial.print("relPosHPN: ");
        Serial.println(myGNSS.packetUBXNAVRELPOSNED->data.relPosHPN);
        Serial.print("relPosHPE: ");
        Serial.println(myGNSS.packetUBXNAVRELPOSNED->data.relPosHPE);
        Serial.print("relPosHPD: ");
        Serial.println(myGNSS.packetUBXNAVRELPOSNED->data.relPosHPD);
        Serial.print("relPosHPLength: ");
        Serial.println(myGNSS.packetUBXNAVRELPOSNED->data.relPosHPLength);
    
        Serial.print("accN: ");
        Serial.println(myGNSS.getRelPosAccN(), 4); // Use the helper functions to get the rel. pos. accuracy as m
        Serial.print("accE: ");
        Serial.println(myGNSS.getRelPosAccE(), 4);
        Serial.print("accD: ");
        Serial.println(myGNSS.getRelPosAccD(), 4);
    
        Serial.print("gnssFixOk: ");
        if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.gnssFixOK == true)
          Serial.println("x");
        else
          Serial.println("");
    
        Serial.print("diffSolution: ");
        if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.diffSoln == true)
          Serial.println("x");
        else
          Serial.println("");
    
        Serial.print("relPosValid: ");
        if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.relPosValid == true)
          Serial.println("x");
        else
          Serial.println("");
    
        Serial.print("carrier Solution Type: ");
        if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.carrSoln == 0)
          Serial.println("None");
        else if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.carrSoln == 1)
          Serial.println("Float");
        else if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.carrSoln == 2)
          Serial.println("Fixed");
    
        Serial.print("isMoving: ");
        if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.isMoving == true)
          Serial.println("x");
        else
          Serial.println("");
    
        Serial.print("refPosMiss: ");
        if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.refPosMiss == true)
          Serial.println("x");
        else
          Serial.println("");
    
        Serial.print("refObsMiss: ");
        if (myGNSS.packetUBXNAVRELPOSNED->data.flags.bits.refObsMiss == true)
          Serial.println("x");
        else
          Serial.println("");
      }
      else
        Serial.println("RELPOS request failed");
    
      for (int i = 0; i < 500; i++)
      {
    #ifdef USE_BT
        uint8_t store[256];
        while ((ESP_BT.available()) && (numBytes < 256)) // Check if data has been received
        {
          store[numBytes++] = ESP_BT.read(); // Read a byte from ESP_BT and store it
        }
        if (numBytes > 0) // Check if data was received
        {
          //Serial.print("Pushing ");
          //Serial.print(numBytes);
          //Serial.println(" bytes via I2C");
          myGNSS.pushRawData(((uint8_t *)&store), numBytes); // Push the RTCM data via I2C
          numBytes = 0; // Reset numBytes
        }
    #endif
        delay(10);
      }
    }
    

    It doesn’t appear to work. The RTK light stays on. At least before with the UART link and the old library I was able to get the RTK light to flash. This doesn’t seem to respond to the RTCM data at all.

    I’m using an RTCM source provided by the State of Michigan through the NTRIP Client app on Android over a Bluetooth link from my phone, not my own u-blox base station.

    The output I’m getting from the serial port looks like this:

    relPosN: 0.0000
    relPosE: 0.0000
    relPosD: 0.0000
    relPosLength: 0
    relPosHeading: 0
    relPosHPN: 0
    relPosHPE: 0
    relPosHPD: 0
    relPosHPLength: 0
    accN: 0.0000
    accE: 0.0000
    accD: 0.0000
    gnssFixOk: 
    diffSolution: 
    relPosValid: 
    carrier Solution Type: None
    isMoving: 
    refPosMiss: 
    refObsMiss:
    

    This has been immensely frustrating.

    Hi NFT,

    Are you sure RELPOSNED is the correct message for your application? That gives you the relative position in North East Down coordinates of the rover with respect to the base. Your baseline is going to be mighty long if the base is the Michigan server? I only pointed you at that example because it shows how to use the push function.

    This application note from u-blox is very good:

    https://www.u-blox.com/sites/default/fi … 093%29.pdf

    Why not use standard Position Velocity Time instead? carrSoln will tell you if you have a floating or fixed solution.

    Best wishes,

    Paul

    I got it to work, just had to adjust the serial1 baud rate to match the base station and uncomment the ‘#define USE_SERIAL1’

    The use of the function ‘myGNSS.pushRawData()’ was exactly what I was looking for.

    relPosN: 0.4100
    relPosE: 0.0500
    relPosD: 0.2600
    relPosLength: 48
    relPosHeading: 729068
    relPosHPN: 4
    relPosHPE: 25
    relPosHPD: 6
    relPosHPLength: 89
    accN: 0.0571
    accE: 0.0615
    accD: 0.0710
    gnssFixOk: x
    diffSolution: x
    relPosValid: x
    carrier Solution Type: Float
    isMoving: 
    refPosMiss: 
    refObsMiss:
    

    PaulZC:
    Hi NFT,

    Are you sure RELPOSNED is the correct message for your application? That gives you the relative position in North East Down coordinates of the rover with respect to the base. Your baseline is going to be mighty long if the base is the Michigan server? I only pointed you at that example because it shows how to use the push function.

    This application note from u-blox is very good:

    https://www.u-blox.com/sites/default/fi … 093%29.pdf

    Why not use standard Position Velocity Time instead? carrSoln will tell you if you have a floating or fixed solution.

    Best wishes,

    Paul

    I have/had no idea what RELPOSNED is. I just used the linked example because adding the code from that example to my existing sketch didn’t work, so I figured I’d try to use the entire example sketch in case something else in my code was causing the problem.

    I’m located in Michigan, just a couple miles from the base station I’m trying to use. The fact that I’m not getting an RTK lock concerns me, regardless of which specific parameters I’m reading.