Build a GNSS evaluation board – Part 2

Welcome back to my blog! Today, we’re diving into Part 2 of our GNSS evaluation board series. This time, we’ll focus on the firmware side of things. I’ll walk you through how to interface an ESP32-S3 with the module and gather essential information like location, local time, and satellite data. Whether you’re a seasoned coder or just getting started, this guide will help you get the most out of your GNSS module. If you haven’t already, check out Part 1. Let’s get started!

The MAX-M10S module has two interfaces for receiving position information: UART and I2C. The UART interface is the most popular, and almost all GNSS modules have it. As soon as the device is powered on, it starts sending information via the serial port using the NMEA protocol. As satellites are connected and positioning data is calculated, this information becomes available on the UART at a default frequency of 1 Hz.

To start, I connected a USB to serial converter to the module, and this is what I see on my serial terminal.

The way the data is presented is described by a standard called NMEA. Here’s a link for reference. The encoding is in ASCII, making it clearly readable by humans. Each row begins with a code that identifies the information contained in the line and ends with a checksum. All fields are comma-separated.

Let’s look at this screenshot from a random NMEA reference manual from google:

The GGA — Global Positioning System Fixed Data line contains information about time, position, and fix for the receiver. It also tells us which constellation is being used for positioning. This module supports GPS, GLONASS, Galileo, and BeiDou. Based on the constellation, we will see prefixes like GNGGA for multi-constellation, GPGGA for GPS, GLGGA for GLONASS, BDGGA for BeiDou, and GAGGA for Galileo. Besides location and time, the HDOP field indicates positioning accuracy: less than 2 is excellent, up to 5 is good, but it depends on the application.

Reading information with i2c and an ESP32-S3

With the I2C protocol, you can receive information from the uBlox module in two ways: using the NMEA standard or the proprietary UBX standard. Here, I’ll show the NMEA method, which also simplifies porting from I2C to UART since the data packets are identical.
For development with the ESP32-S3, I use ESP-IDF combined with Eclipse (Link here).

First, I created a new ESP-IDF blank project. Since it runs on FreeRTOS, one option is to create a configuration function to initialize the I2C peripheral and a task to periodically query the GNSS module, read information, and send it to the terminal.

let’s start with the i2c configuration:

Now that we have I2C configured to work with the MAX-M10S module, we want to read data from it. To do this, we need to understand how data transmission works via the I2C interface, which is clearly explained in the IntegrationManual. In short, there are three registers available for reading: two that contain the number of bytes to read (0xFD and 0xFE) and one that buffers the message stream (0xFF). A simple way to read info from the module is to periodically query the number of bytes available and read until the buffer is exhausted.

In the code below, I request 2 bytes starting at address 0xFD of the internal register, then store the number of available bytes to read in a variable.

What I want to do at this point is read the data stream from the module and analyze it line by line to obtain the information that interests me.

I propose an example to read GGA packet and extract the UTC time from the GNSS module. In the video I show you the evaluation board connected with a board with an LED matrix that displays the time.

GNSS based clock

Below is the code I used inside the while(1) structure of my task:

As long as num_to_read is greater than 0, I read from the I2C device one byte at a time and append it to my gps_string_in[] string. If I receive a new line character, it means I have a line available to analyze. Using the strstr() function, I check whether the GGA identifier is present. If it is, I use the parseNMEA_GGA() parsing function to update my database. I then display the time on the LED matrix with specific functions for my device. Below in the post, you’ll find a version that prints the parsed data to the terminal instead.

Parsing NMEA data format into variables

There are many libraries available for NMEA parsing, but here I’ll show you a simple version from scratch to understand how it works. This method can be extended to all the information provided by the uBlox module (more generally from all GPS modules with NMEA data format output), allowing us to extract, display, and save the data, for example, on a memory card.

Suppose we want to parse information from the GGA data frame. We know from the previous table that the data is packed into the frame, so first, I create a data structure:

Since we know the structure of the string from the GNSS module, a simple way to extract the data fields from the string is using sscanf() function.

Checksum validation

To make it more robust, we can add a checksum control function to validate the received data and ensure it is not corrupted.

To do this, I implemented two functions: one to calculate the checksum value and another to compare and validate it.

Final Code

Below is the output of the test code I wrote and analyzed in this article. The information contained in the GNGGA data frames from the GNSS module is displayed on the terminal after being parsed.

Of course, this is just a starting point. You can add more features, checks, and error controls. It will be easy to add further parsing functions to store all the information you need from the navigation module.

Here, I leave the complete main.c file, and for the entire firmware, you can check out my dedicated GitHub repository.

Thank you for reading and supporting my blog! If you enjoyed this content and would like to help me continue creating more, please consider leaving a donation. Your generosity will help me cover the costs of materials and tools for future projects. Any amount is greatly appreciated! And remember to follow me on Instagram for more updates and behind-the-scenes content.