Raspberry Pi based PPK and RTK solutions with RTKLIB

It’s been over six years now since I published my last post on how to run RTKLIB on a Raspberry PI, so it’s more than time for an update. In my previous post, I described using a Pi Zero as a data logger for a u-blox M8N for PPK solutions. In this post I will work with a Pi Model 4 and a u-blox M8T to demonstrate both logging for PPK solutions and a real-time RTK solution. The good news is that this time no soldering is required since we are going to use the USB port on the Pi to connect the receiver. These instructions will work with any u-blox receiver that supports raw observations and any model Pi that has USB ports for peripherals. With minor modifications, they can be used with any receiver that has a USB or UART port and supports raw observations.

Here’s an image of the assembled setup. The Pi in the center, and the u-blox M8T receiver is on top. We will use a wireless connection to talk to the Pi from an external computer so there is no need for a keyboard or display.

Raspberry Pi with u-blox M8T receiver

Step 1: Configure the Pi

The first step is to configure the Pi in “headless” mode so that we can talk to it from an external computer. This is quite straightforward and well-explained in this post, so I will not describe how to do it here. Only steps 1 and 2 in the post are required for this exercise. If you plan to use this for RTK solutions, be aware that the Pi will rely on the wireless connection to the internet for base station observations. This means that if you don’t want to be limited to using it within range of your home wireless router, then you will probably want to connect to a hot spot from a cell phone. If you are just interested in collecting data for PPK solutions, then it doesn’t matter.

After you have completed steps 1 and 2 above, you should have a Putty window open and have logged into your Pi. The next step is to build and install the RTKLIB code. The commands below will clone the RTKLIB code from the Github repository, compile the stream server app (str2str) and the RTK solution app (rtkrcv), and copy the executables to a location where they can be accessed from any directory.

> sudo apt update
> sudo apt install git
> mkdir rtklib
> cd rtklib
> git clone https://github.com/rtklibexplorer/RTKLIB.git
> cd RTKLIB/app/consapp/str2str/gcc
> make
> sudo cp str2str /usr/local/bin/str2str
> cd ../../rtkrcv/gcc
> make
> sudo cp rtkrcv /usr/local/bin/rtkrcv
> cd ../../../../..

Step 2: Configure the receiver

Before we connect the u-blox receiver to the Pi, we will need to configure it to output raw observation and navigation messages. The easiest way to do this is from your computer with the u-center app which can be downloaded from the u-blox website. Connect the receiver to your computer with a USB cable, start u-center, and connect to the receiver using the “Connection” option in the “Receiver” tab as shown in the image below.

u-center: Connect to receiver

Next, use the “Messages View” window from the “View” menu to enable the RAWX and SFRBX messages as seen below. While you are in the messages view, you can also disable any unnecessary NMEA messages to save communication bandwidth.

u-center: Enable raw observation and navigation messages

Next, we will switch to the “Configuration View” window to configure any other desired settings and then save them to flash . I would recommend verifying that all constellations are enabled with the “GNSS” command and that the sample rate is set to the desired value with the “RATE” command. I usually set this to 5 Hz. I would also recommend disabling both UART ports with the “PORT” command if you are not using them. If the baud rates are set too low, they will limit bandwidth on all ports including the USB port, even if nothing is connected to those ports. Finally, use the “CFG” command to save the settings to flash as shown below.

u-center: Save settings to flash

Step 3: Verify the data stream(s)

Next, we will confirm that we are receiving data from the rover receiver and if running a real-time solution, also from the base receiver. This step is not absolutely essential, but it does verify that we have the individual pieces working before we put it all together, and also gives some practice using the RTKLIB str2str command.

Disconnect the rover receiver from the computer and connect it to the Pi using a USB cable as shown in the image at the top of this post. Enter the following commands into the Putty console to create a new folder and run the stream server. This will connect to the USB port on the Pi. If you are using a UART port, you will need to use the appropriate port name.

> mkdir data
> cd data
> str2str -in serial://ttyACM0

The output of the receiver should now scroll across the Putty console screen. If you have any NMEA messages enabled, you should be able to see them mixed in with a bunch of random characters from the binary messages. Once you’ve confirmed the data stream, hit Control C to stop it.

If we want to log the receiver output for a PPK solution, we just need to add a file name to the previous command to redirect the data stream from the screen to a file. The command below will do this, using keywords in the file name to create a name that includes the current month, day, hour, and minute.

> str2str -in serial://ttyACM0 -out rover_%m%d_%h%M.ubx

The image below shows the expected output of both commands.

Verification of receiver data stream

If you are using the Pi just to log receiver data then you are done at this point unless you want to configure the Pi to make it automatically start collecting data whenever it is turned on. There are several ways to do this, all described in this post. Modifying the rc.local file is the simplest method.

For those who would prefer to run an RTK solution rather than just log data for a PPK solution, the next step is to confirm the base data stream. We will use the “str2str” command again, but this time we will specify the input to be an NTRIP stream using the format:


In my case, the command looks like this: (with the username and password removed)

> str2str -in ntrip://username:password@rtgpsout.unavco.org:2101/P041_RTCM3: -out temp.log

If everything is working properly, you should see non-zero transfer numbers and no errors, as in example above, in which case you can use Control C again to stop.

Note that if your NTRIP provider is using a VRS (Virtual Reference Station), then things are a little more complicated. We will need to send our local position inside of a GGA message. For this to work, you must have enabled the NEMA GGA message when configuring the receiver. To route these GGA messages back to the NTRIP server we will need to connect the stream server output to the receiver and enable the relay back feature with the “-b” option. Here’s an example I used to connect to test this with a VRS NTRIP server.

str2str -in ntrip://username:password@na.l1l2.skylark.swiftnav.com:2101/CRS -b 1 -out serial://ttyACM0

Step 4: Run the RTK solution

OK, now that we’ve confirmed that we are getting data from base and rover, it’s time to generate an RTK solution. We will use the “rtkrcv” console app in RTKLIB to do this, which we installed in Step 1.

We will need a configuration file for rtkrcv. You can use the “rtknavi_example.conf” file included with the demo5 release as a starting point but you will need to edit the stream configuration settings. Below are the settings I changed as well as a few important ones worth verifying are correct for your configuration. I have it configured to write the output to a file in LLH format. If you want the output in NMEA messages you can either change output stream 1 to “nmea” format or enable output stream 2 to get both a file and a stream of NMEA messages.

pos1-posmode =kinematic  # (0:single,1:dgps,2:kin,3:static)
pos1-frequency =l1  # (1:l1,2:l1+l2,3:l1+l2+l5)
pos1-navsys =13  # (1:gps+2:sbas+4:glo+8:gal+16:qzs+32:comp)
pos2-armode =fix-and-hold # (0:off,1:cont,2:inst,3:fix-and-hold)
pos2-gloarmode =fix-and-hold # (0:off,1:on,2:autocal,3:fix-and-hold)
out-solformat =llh #    (0:llh,1:xyz,2:enu,3:nmea)
ant2-postype =rtcm # (0:llh,1:xyz,2:sing,3:file,4:rinex,5:rtcm)
inpstr1-type =serial (0:off,1:ser,2:file,3:,...,7:ntrip)
inpstr2-type =ntripcli # (0:off,1:ser,2:file,3:,...,7:ntrip)
inpstr1-path =ttyACM0
inpstr2-path =usrname:pwd@rtgpsout.unavco.org:2101/P041_RTCM3
inpstr1-format =ubx # (0:rtcm2,1:rtcm3, ...)
inpstr2-format =rtcm3 # (0:rtcm2,1:rtcm3,...)
inpstr2-nmeareq =single # (0:off,1:latlon,2:single)
outstr1-type =file # (0:off,1:serial,2:file, ...)
outstr2-type =off # (0:off,1:serial,2:file, ...)
outstr1-path =rtkrcv_%m%d_%h%M.pos
outstr2-path =
outstr1-format =llh # (0:llh,1:xyz,2:enu,3:nmea)
outstr2-format =nmea # (0:llh,1:xyz,2:enu,3:nmea)
logstr1-type =file # (0:off,1:serial,2:file, ...)
logstr2-type =file # (0:off,1:serial,2:file, ...)
logstr1-path =rover_%m%d_%h%M.ubx
logstr2-path =base_%m%d_%h%M.rtcm3

I like to use WinSCP for editing and transferring files between the Pi and external computer but there are many other ways to do this. When you are done, the edited configuration file needs to be in the current folder you will run rtkrcv from. For my example, I renamed it “rtkrcv_pi.conf”

To run rtkrcv with a configuration file named “rtkrcv_pi.conf”, use the following commands:

> rtkrcv -s -o rtkrcv_pi.conf
Β  >> statusΒ  1

If all is well, you should see a status screen updated every second that looks something like this:

I changed the Putty display defaults to make this a little easier to read. I’ve also highlighted in yellow some of the numbers to check to make sure they look OK. Make sure you are seeing base RTCM location messages (usually 1005). If you want to check the input streams in more detail, you can use control c to exit the status menu, then enter “?” to see some of the other rtkrcv commands. To exit rtkrcv, use the “shutdown” command.

If all of your inputs look good, your solution is not working, and it is not obvious why, you can rerun rtkrcv with a “-t 3” in the command line. This will enable trace mode which will create a trace debug file which may offer clues as to what is wrong.

This should be enough to get you started. To explore more configuration options, see the str2str and rtkrcv sections in Appendix A of the RTKLIB users manual.


29 thoughts on “Raspberry Pi based PPK and RTK solutions with RTKLIB”

  1. Hello,

    I think there’s an even simpler solution: https://github.com/Stefal/rtkbase
    There’s a ready to flash image for PI and over a web interface it logs all the .ubx files every 24 hours and it can even generate rinex via the web interface.
    Oh and it can auto detect / configure your GNSS receiver πŸ™‚


  2. Hello, thank you for your post.

    I was wondering if there are there similar functions that can be used to set up the NTRIP Client but inside of a C program and not in command line ? And more specifically, how can we process the corrected position once we have both data from the rover and the base, are there functions inside of RTKLIB that may help to achieve this ?


    1. I also had a little problem on step 4 :

      My NTRIP Server is a VRS, so when I used the command that you explained for this cas in step 3, it worked fine, the RTK LED was blinking so I assumed the RTK mode was enabled.
      However, when I try to run the solution with you config file, the status screen print all the position of the rover to 0, and only prints the position of the base. Also, the solution status is empty (‘-‘).

      I was wondering if this was linked to the fact that my NTRIP provider is a VRS, and if it’s the case, what should I modify in the conf file to run the RTK solution ?

      I’m sorry if these questions seem stupid, I’m really new to the GNSS RTK world and I didn’t understand everything for the moment. I would really appreciate if you could help me in any way.


      1. You will need to update the input string parameters at the end of the config file to match your inputs, including the NTRIP setup.

        If you enter a “?” instead of “status 1” to the rtkrcv input prompt, you will get a list of other output commands that can be useful for debug. These include “stream”, “satellite”,”observ”, “navidata”, and maybe most valuable “error”. If these don’t help, you can also add “-t 2” to the command line to create a trace debug file which will provide additional information.


    2. I’m not sure why you would want to configure the NTRIP client from within the C program, since this would require recompiling the code every time you change to a new NTRIP server. Maybe you meant a config file, in which case you can specify the NTRIP setup in the config file if you are using rtkrcv. I’m not sure what you mean by “process the corrected position” but if you mean generated the position solution from the raw rover and base data, then that is what Step 4 in the post describes.


  3. Thank you for this great article. I have a ZED – F9P and want to use its internal RTK solution. I have it hooked up to the USB of my Raspberry Pi 4B. In Step 3, Verify the Data Streams, I see the output of the receiver when I run the str2str -in serial://ttyACM0 command. My NTRIP provider is a VRS. When I run your example command (using my credentials and NTRIP server information), the console indicates that I am able to connect and receive data( or at least the bps are larger than zero). Since the -out option is specifying the USB which the receiver is connected to, is the NTRIP information being piped into the receiver, and should the ZED – F9P be able to use that information to directly enter RTK mode as the str2str command is running? When I ran this, the RTK LED never turned off to indicate the receiver was operating in RTK mode. Is there some configuration or setting that I need to do either on the ZED – F9P or in the str2str command in order for the receiver to use the information str2str is outputting to it and enter RTK mode?


    1. The most common reason for not getting data from a VRS NTRIP server is that you have not provided the rover location to the NTRIP server with a NMEA GGA message. Did you follow the instructions in the post for doing this? I have repeated them below:

      “Note that if your NTRIP provider is using a VRS (Virtual Reference Station), then things are a little more complicated. We will need to send our local position inside of a GGA message. For this to work, you must have enabled the NEMA GGA message when configuring the receiver. To route these GGA messages back to the NTRIP server we will need to connect the stream server output to the receiver and enable the relay back feature with the “-b” option. Here’s an example I used to connect to test this with a VRS NTRIP server.

      str2str -in ntrip://username:password@na.l1l2.skylark.swiftnav.com:2101/CRS -b 1 -out serial://ttyACM0


      1. Thank you very much for your reply. I did follow the instructions in the post and was successfully getting data from the VRS NTRIP server. The problem I was having was that the F9P was not entering float or fixed RTK mode, even though the str2str command is outputting the NTRIP data to the USB the F9P is attached to. The solution I found is to specify the baud rate in the -out parameter. Using your example command, it looks like this :
        str2str -in ntrip://username:password@na.l1l2.skylark.swiftnav.com:2101/CRS -b 1 -out serial://ttyACM0:115200:8:n:1
        I assume this means that the F9p was expecting a baud rate of 115200, and str2str was outputting at a different baud rate until I specified it.
        Thank you again for your help.


        1. Glad to hear you sorted it out. For anyone else having a similar issue, one suggestion is to temporarily change the output of str2str from the serial port to a file and then verify that the contents of the file look good. If it contains just a bunch of random characters then that usually means the baud rate is set incorrectly.


  4. Hi RTKLIB explorer,

    I have been looking at the documentation for RTKLIB demo 5 and think I may have found a typo. I believe that in the second condition for the thin ionosphere shell model equation the greater than symbol should be flipped. I may be wrong though, I’m not sure but thought I’d bring it up.

    As is in the documentation:
    if (πœ™π‘Ÿ > 70Β°π‘Žπ‘›π‘‘ π‘‘π‘Žπ‘› 𝛼 π‘π‘œπ‘  𝐴 π‘§π‘Ÿπ‘  > π‘‘π‘Žπ‘›( πœ‹/2 βˆ’ πœ™π‘Ÿ))
    or (πœ™π‘Ÿ < βˆ’70Β°π‘Žπ‘›π‘‘ βˆ’ π‘‘π‘Žπ‘› 𝛼 π‘π‘œπ‘  𝐴 π‘§π‘Ÿπ‘  > π‘‘π‘Žπ‘›( πœ‹/2 + πœ™π‘Ÿ)){
    longitude_pierce_point = ect;
    longitude_pierce_point = ect;

    suggested edit:
    if (πœ™π‘Ÿ > 70Β°π‘Žπ‘›π‘‘ π‘‘π‘Žπ‘› 𝛼 π‘π‘œπ‘  𝐴 π‘§π‘Ÿπ‘  > π‘‘π‘Žπ‘›( πœ‹/2 βˆ’ πœ™π‘Ÿ))
    or (πœ™π‘Ÿ < βˆ’70Β°π‘Žπ‘›π‘‘ βˆ’ π‘‘π‘Žπ‘› 𝛼 π‘π‘œπ‘  𝐴 π‘§π‘Ÿπ‘  < π‘‘π‘Žπ‘›( πœ‹/2 + πœ™π‘Ÿ)){
    longitude_pierce_point = ect;
    longitude_pierce_point = ect;


    1. To be honest, I have not gone through the atmospheric correction calculations in Appendix E in any detail and these are unchanged from the 2.4.2 RTKLIB user manual. Maybe someone else who has looked at this more closely can commment?


      1. Thanks, for taking the time to reply. In case some one who has more experience with this section can comment I will also include that there appears to be a mistake with the equation which calculates the Ionospheric delay. Looking at another source, namely the 2017 Springers Handbook of Global Navigation – chapter 19.3.1 , I think that there is a missing squared symbol on the carrier frequency. Also while correct I think it is more representative to show that the TEC count is typically multiplied by 1e16 to adjust for units.

        As in the documentation:
        𝐼_{π‘Ÿ,𝑖}^𝑠 =(1/π‘π‘œπ‘  𝑧′)*(40.3e16/𝑓𝑖)*𝑇𝐸𝐢(𝑑,πœ™πΌπ‘ƒπ‘ƒ, πœ†πΌπ‘ƒπ‘ƒ)

        suggested edit:
        𝐼{π‘Ÿ,𝑖}^𝑠 =(1/π‘π‘œπ‘  𝑧′)(40.3/(𝑓𝑖^2))𝑇𝐸𝐢(𝑑,πœ™πΌπ‘ƒπ‘ƒ, πœ†πΌπ‘ƒπ‘ƒ)*1e16
        as done in the springers book add a note that TEC is normally recorded as 10^16 electrons/m^2 and leave the equation as
        𝐼{π‘Ÿ,𝑖}^𝑠 =(1/π‘π‘œπ‘  𝑧′)(40.3/(𝑓𝑖^2))𝑇𝐸𝐢(𝑑,πœ™πΌπ‘ƒπ‘ƒ, πœ†πΌπ‘ƒπ‘ƒ)

        a public source that shows the adjusted version of the formula:
        O. Øvstedal: Absolute positioning with single-frequency GPS receivers, GPS Solutions 5(4), 33–44 (2002)


  5. Hi Tim, I recently use rtklib 2.4.3, it sometimes remains in float for a long time and cannot recover to fix. At the same time, demo5 does not have this problem. I’m very interested in this issue, but currently have no ideas. Do you have any suggestions? Thanks


    1. I suggest using the demo5 code rather than the 2.4.3 code if you are primarily interested in the performance. The demo5 code has been optimized for practical use and low cost receivers and has some additional features. The 2.4.3 code is a more pure implementation and may be better for theoretical and educational purposes.


  6. Hi Tim, can you suggest the minimum NMEA messages to record along with the RAWX and SFRBX UBX messages that will be used in converting to RINEX? I am working on using a serial logger connected to UART1 instead of a computer on the USB and the throughput seems to be lower on the UART (@115200). When I remove some NMEA messages, I am able to get the TX buffer down. I am hoping I don’t really need all of the NMEA messages to convert to RINEX and the process with RTKPOST. Thanks


    1. No NMEA messages are required to generate RINEX raw observation and navigation files, although if you are using a virtual base station (VRS) you will need the GGA message enabled to indicate the rover position to the NTRIP server.


  7. The str2str command-line used with a VRS NTRIP server works perfectly.
    How should I configure the rtkrcv_pi.conf file to also use the relay back feature?
    I tried adding “-b 1 -out serial://ttyACM0” to the ‘ inpstr2-path’ string in the config file but that was a bit to simple, it did not work.


  8. I would like to get 1cm accuracy with ublox m8t in static mode. I get a difference between GPS and GAL a few centimeters in the same measurement session. Why ?


    1. I have found the answer. The module ublox neo-m8t has the accuracy of cheap circuits and costs much more. In the best case it is in the range of 10 cm and usually much worse. The product is not worth buying. RTKLIB is constantly being improved because it doesn’t work properly. Such toys.


      1. Your experience is different from mine. I have found the u-blox modules in general to be higher performance than other options in their price range. If you have reasonably open sky views, are getting fixed status in your solutions, and using reasonable configuration settings and baseline length, then you should be getting better than 10 cm accuracy. I find 2 cm horizontal and 5 cm vertical accuracies to be reasonably easy to achieve, even with single frequency solutions.


    2. I can only imagine that this module will be sufficient for plowing the field because the positional error changes very slowly and the price of the system plays a role. There are usually no terrain obstacles in the fields. The farmer must also access or set up his own base station. Additionally, invest in the tractor control system. I would not risk such an investment with such poorly explained possibilities of using and operating such a system by the manufacturer. And in general, is the ublox neo-m8t a thing of the past?


      1. To a large extent, I do think the M8T has been replaced by the u-blox F9P. It support dual frequencies, so will be more reliable, and the internal RTK solution makes it easier to use for real-time solutions. The M8T is still a good choice though for someone who wants to get into precision GNSS at the lowest price point.


    3. A few centimeter difference between single frequency, single constellation solutions might not be unreasonable, especially if using default configuration settings and including vertical errors.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: