UM980 is relatively affordable chip allowing multi-constellation multi-frequency GNSS with capability to provide raw phase measurements and hence potentially suitable for use in OpenStreetMap environment for precise mapping susing RTK.
In this post (or potentially series of posts), I will document my experiences with using UM980 with RTKLIB and other open-source or free tools.
Hardware
- UM980 RTK GNSS USB Dongle with SMA antenna - ELT0222 (from gnss.store)
- EM-FD700 antenna (form AliExpress)
The module can easily be configured using UPrecise tool coming with UM980. It is advisable to have faster than default COM port rate
~~~
CONFIG COM1 230400
~~~
and use the signal group 2 (note that this leads to reset of the UM980 module
~~~
CONFIG SIGNALGROUP 2
~~~
If you need to stop the stream of measured data, you can use
~~~
UNLOG
~~~
You can also UNLOG just specific messages.
If you want to store your config of the module so that it survives unplugging and restarting the module, use
~~~
SAVECONFIG
~~~
Software setup on Android
RTKGPS+ - collecting raw phase measurements
This is to collect the raw data in real time. The last RTKGPS+ tool is release is unfortunately relatively old and hence does not support, so the native UniCore support, which is available in demo5_b34L version of RTKLIB, is not yet available in RTKGPS+. Hence it is mostly useful for collecting raw data, rather than providing real-time solutions. If you want to collect the data, choose BINEX as the format and just log the input stream from the USB. This is how to configure the module:
CONFIG PPP DISABLE
OBSVMB 1
GPSEPHB 120
BDSEPHB 120
BD3EPHB 120
GLOEPHB 120
GALEPHB 120
GPSIONB 120
BDSIONB 120
BD3IONB 120
GALIONB 120
If you want to have faster measurements, you can use
~~~
OBSVMB 0.2
~~~
But it may be then advisable to use at least 240kbaud rate of the COM port (which is generally well supported by RTKGPS+).
The input stream configuration is 230400/8-N-1 and format to store the data is BINEX.
RTKGPS+ - undocumented NavTel-compatible mode
Alternatively, one can attempt to use it using undocumented mode of UM980 to produce binary data compatible data:
~~~
RANGEB COM1 0.2
~~~
However, I had problem that only very poor solutions were found, with only using GLONASS and Galileo constellation - probably due to lack of ephemeris included (use of “CONFIG ALLEPHRTCM ENABLE” option is still to be explored).
Then the input format is NovAtel OEMV/4.
RTKGPS+ - recording the base data
It is possible to use the RTKGPS+ to log also the data from the base, a nearby coming from IGS or from EUREF (both requiring a free registration) and store the data in RTCM 3 format.
SW Maps - free software on Android to read NMEA data
This is an example how to use PPP on the UM980 module and collect the data using SW Maps.
~~~
CONFIG PPP ENABLE E6-HAS
CONFIG PPP DATUM WGS84
CONFIG PPP TIMEOUT 120
CONFIG PPP CONVERGE 15 30
GPGGA 0.2
GPGSA 1
GPGST 1
GPGSV 1
GPGLL 1
GPGNS 1
GPRMC 1
~~~
Use of RTKLIB on the Windows
This assumes use of demo5_b34L version of RTKLIB or later.
Once the data is captured using RTKLIB, one can process the data using convbin and rtkpost from RTKLIB to process the data
convbin -od -os -oi -ot rover_20250626042937.unc
convbin -od -os -oi -ot base_20250626042937.rtcm3
rtkpost config for postprocessing with output to pos file
# rtkpost options (2025/06/26 04:39:16, v.demo5 b34L)
pos1-posmode =kinematic # (0:single,1:dgps,2:kinematic,3:static,4:static-start,5:movingbase,6:fixed,7:ppp-kine,8:ppp-static,9:ppp-fixed)
pos1-frequency =l1+l2+l5+l6 # (1:l1,2:l1+l2,3:l1+l2+l5,4:l1+l2+l5+l6)
pos1-soltype =combined # (0:forward,1:backward,2:combined,3:combined-nophasereset)
pos1-elmask =15 # (deg)
pos1-snrmask_r =off # (0:off,1:on)
pos1-snrmask_b =off # (0:off,1:on)
pos1-snrmask_L1 =38,38,38,38,38,38,38,38,38
pos1-snrmask_L2 =0,0,0,0,0,0,0,0,0
pos1-snrmask_L5 =0,0,0,0,0,0,0,0,0
pos1-snrmask_L6 =0,0,0,0,0,0,0,0,0
pos1-dynamics =on # (0:off,1:on)
pos1-tidecorr =0 # (1:solid+2:otl+4:spole)
pos1-ionoopt =brdc # (0:off,1:brdc,2:sbas,3:dual-freq,4:est-stec,5:ionex-tec,6:qzs-brdc)
pos1-tropopt =saas # (0:off,1:saas,2:sbas,3:est-ztd,4:est-ztdgrad)
pos1-sateph =brdc # (0:brdc,1:precise,2:brdc+sbas,3:brdc+ssrapc,4:brdc+ssrcom)
pos1-posopt1 =off # (0:off,1:on)
pos1-posopt2 =off # (0:off,1:on)
pos1-posopt3 =off # (0:off,1:on,2:precise)
pos1-posopt4 =off # (0:off,1:on)
pos1-posopt5 =off # (0:off,1:on)
pos1-posopt6 =off # (0:off,1:on)
pos1-exclsats = # (prn ...)
pos1-navsys =47 # (1:gps+2:sbas+4:glo+8:gal+16:qzs+32:bds+64:navic)
pos2-armode =fix-and-hold # (0:off,1:continuous,2:instantaneous,3:fix-and-hold)
pos2-gloarmode =fix-and-hold # (0:off,1:on,2:autocal,3:fix-and-hold)
pos2-bdsarmode =off # (0:off,1:on)
pos2-arfilter =on # (0:off,1:on)
pos2-arthres =3
pos2-arthresmin =3
pos2-arthresmax =3
pos2-arthres1 =0.1
pos2-arthres2 =0
pos2-arthres3 =1e-09
pos2-arthres4 =1e-05
pos2-varholdamb =0.1 # (cyc^2)
pos2-gainholdamb =0.01
pos2-arlockcnt =0
pos2-minfixsats =4
pos2-minholdsats =5
pos2-mindropsats =10
pos2-arelmask =15 # (deg)
pos2-arminfix =100
pos2-armaxiter =1
pos2-elmaskhold =15 # (deg)
pos2-aroutcnt =20
pos2-maxage =30 # (s)
pos2-syncsol =off # (0:off,1:on)
pos2-slipthres =0.05 # (m)
pos2-dopthres =0 # (m)
pos2-rejionno =1 # (m)
pos2-rejcode =10 # (m)
pos2-niter =1
pos2-baselen =0 # (m)
pos2-basesig =0 # (m)
out-solformat =llh # (0:llh,1:xyz,2:enu,3:nmea)
out-outhead =on # (0:off,1:on)
out-outopt =on # (0:off,1:on)
out-outvel =off # (0:off,1:on)
out-timesys =gpst # (0:gpst,1:utc,2:jst)
out-timeform =hms # (0:tow,1:hms)
out-timendec =3
out-degform =deg # (0:deg,1:dms)
out-fieldsep =
out-outsingle =on # (0:off,1:on)
out-maxsolstd =0 # (m)
out-height =geodetic # (0:ellipsoidal,1:geodetic)
out-geoid =internal # (0:internal,1:egm96,2:egm08_2.5,3:egm08_1,4:gsi2000)
out-solstatic =all # (0:all,1:single)
out-nmeaintv1 =0 # (s)
out-nmeaintv2 =0 # (s)
out-outstat =residual # (0:off,1:state,2:residual)
stats-eratio1 =300
stats-eratio2 =300
stats-eratio5 =300
stats-eratio6 =300
stats-errphase =0.003 # (m)
stats-errphaseel =0.003 # (m)
stats-errphasebl =0 # (m/10km)
stats-errdoppler =1 # (Hz)
stats-snrmax =52 # (dB.Hz)
stats-errsnr =0 # (m)
stats-errrcv =0 # ( )
stats-stdbias =30 # (m)
stats-stdiono =0.03 # (m)
stats-stdtrop =0.3 # (m)
stats-prnaccelh =3 # (m/s^2)
stats-prnaccelv =1 # (m/s^2)
stats-prnbias =0.0001 # (m)
stats-prniono =0.001 # (m)
stats-prntrop =0.0001 # (m)
stats-prnpos =0 # (m)
stats-clkstab =5e-12 # (s/s)
ant1-postype =llh # (0:llh,1:xyz,2:single,3:posfile,4:rinexhead,5:rtcm,6:raw)
ant1-pos1 =0 # (deg|m)
ant1-pos2 =0 # (deg|m)
ant1-pos3 =0 # (m|m)
ant1-anttype =
ant1-antdele =0 # (m)
ant1-antdeln =0 # (m)
ant1-antdelu =0 # (m)
ant2-postype =single # (0:llh,1:xyz,2:single,3:posfile,4:rinexhead,5:rtcm,6:raw)
ant2-pos1 =0 # (deg|m)
ant2-pos2 =0 # (deg|m)
ant2-pos3 =0 # (m|m)
ant2-anttype =
ant2-antdele =0 # (m)
ant2-antdeln =0 # (m)
ant2-antdelu =0 # (m)
ant2-maxaveep =1
ant2-initrst =on # (0:off,1:on)
misc-timeinterp =off # (0:off,1:on)
misc-sbasatsel =0 # (0:all)
misc-rnxopt1 =
misc-rnxopt2 =-EPHALL
misc-pppopt =
file-satantfile =
file-rcvantfile =
file-staposfile =
file-geoidfile =
file-ionofile =
file-dcbfile =
file-eopfile =
file-blqfile =
file-tempdir =
file-geexefile =
file-solstatfile =
file-tracefile =
rtkpost config for postprocessing with output to NMEA file
# rtkpost options (2025/06/26 06:28:15, v.demo5 b34L)
pos1-posmode =kinematic # (0:single,1:dgps,2:kinematic,3:static,4:static-start,5:movingbase,6:fixed,7:ppp-kine,8:ppp-static,9:ppp-fixed)
pos1-frequency =l1+l2+l5+l6 # (1:l1,2:l1+l2,3:l1+l2+l5,4:l1+l2+l5+l6)
pos1-soltype =combined # (0:forward,1:backward,2:combined,3:combined-nophasereset)
pos1-elmask =15 # (deg)
pos1-snrmask_r =off # (0:off,1:on)
pos1-snrmask_b =off # (0:off,1:on)
pos1-snrmask_L1 =38,38,38,38,38,38,38,38,38
pos1-snrmask_L2 =0,0,0,0,0,0,0,0,0
pos1-snrmask_L5 =0,0,0,0,0,0,0,0,0
pos1-snrmask_L6 =0,0,0,0,0,0,0,0,0
pos1-dynamics =on # (0:off,1:on)
pos1-tidecorr =0 # (1:solid+2:otl+4:spole)
pos1-ionoopt =brdc # (0:off,1:brdc,2:sbas,3:dual-freq,4:est-stec,5:ionex-tec,6:qzs-brdc)
pos1-tropopt =saas # (0:off,1:saas,2:sbas,3:est-ztd,4:est-ztdgrad)
pos1-sateph =brdc # (0:brdc,1:precise,2:brdc+sbas,3:brdc+ssrapc,4:brdc+ssrcom)
pos1-posopt1 =off # (0:off,1:on)
pos1-posopt2 =off # (0:off,1:on)
pos1-posopt3 =off # (0:off,1:on,2:precise)
pos1-posopt4 =off # (0:off,1:on)
pos1-posopt5 =off # (0:off,1:on)
pos1-posopt6 =off # (0:off,1:on)
pos1-exclsats = # (prn ...)
pos1-navsys =47 # (1:gps+2:sbas+4:glo+8:gal+16:qzs+32:bds+64:navic)
pos2-armode =fix-and-hold # (0:off,1:continuous,2:instantaneous,3:fix-and-hold)
pos2-gloarmode =fix-and-hold # (0:off,1:on,2:autocal,3:fix-and-hold)
pos2-bdsarmode =off # (0:off,1:on)
pos2-arfilter =on # (0:off,1:on)
pos2-arthres =3
pos2-arthresmin =3
pos2-arthresmax =3
pos2-arthres1 =0.1
pos2-arthres2 =0
pos2-arthres3 =1e-09
pos2-arthres4 =1e-05
pos2-varholdamb =0.1 # (cyc^2)
pos2-gainholdamb =0.01
pos2-arlockcnt =0
pos2-minfixsats =4
pos2-minholdsats =5
pos2-mindropsats =10
pos2-arelmask =15 # (deg)
pos2-arminfix =100
pos2-armaxiter =1
pos2-elmaskhold =15 # (deg)
pos2-aroutcnt =20
pos2-maxage =30 # (s)
pos2-syncsol =off # (0:off,1:on)
pos2-slipthres =0.05 # (m)
pos2-dopthres =0 # (m)
pos2-rejionno =1 # (m)
pos2-rejcode =10 # (m)
pos2-niter =1
pos2-baselen =0 # (m)
pos2-basesig =0 # (m)
out-solformat =nmea # (0:llh,1:xyz,2:enu,3:nmea)
out-outhead =on # (0:off,1:on)
out-outopt =on # (0:off,1:on)
out-outvel =on # (0:off,1:on)
out-timesys =gpst # (0:gpst,1:utc,2:jst)
out-timeform =hms # (0:tow,1:hms)
out-timendec =3
out-degform =deg # (0:deg,1:dms)
out-fieldsep =
out-outsingle =on # (0:off,1:on)
out-maxsolstd =0 # (m)
out-height =geodetic # (0:ellipsoidal,1:geodetic)
out-geoid =internal # (0:internal,1:egm96,2:egm08_2.5,3:egm08_1,4:gsi2000)
out-solstatic =all # (0:all,1:single)
out-nmeaintv1 =0 # (s)
out-nmeaintv2 =0 # (s)
out-outstat =state # (0:off,1:state,2:residual)
stats-eratio1 =300
stats-eratio2 =300
stats-eratio5 =300
stats-eratio6 =300
stats-errphase =0.003 # (m)
stats-errphaseel =0.003 # (m)
stats-errphasebl =0 # (m/10km)
stats-errdoppler =1 # (Hz)
stats-snrmax =52 # (dB.Hz)
stats-errsnr =0 # (m)
stats-errrcv =0 # ( )
stats-stdbias =30 # (m)
stats-stdiono =0.03 # (m)
stats-stdtrop =0.3 # (m)
stats-prnaccelh =3 # (m/s^2)
stats-prnaccelv =1 # (m/s^2)
stats-prnbias =0.0001 # (m)
stats-prniono =0.001 # (m)
stats-prntrop =0.0001 # (m)
stats-prnpos =0 # (m)
stats-clkstab =5e-12 # (s/s)
ant1-postype =llh # (0:llh,1:xyz,2:single,3:posfile,4:rinexhead,5:rtcm,6:raw)
ant1-pos1 =0 # (deg|m)
ant1-pos2 =0 # (deg|m)
ant1-pos3 =0 # (m|m)
ant1-anttype =
ant1-antdele =0 # (m)
ant1-antdeln =0 # (m)
ant1-antdelu =0 # (m)
ant2-postype =single # (0:llh,1:xyz,2:single,3:posfile,4:rinexhead,5:rtcm,6:raw)
ant2-pos1 =0 # (deg|m)
ant2-pos2 =0 # (deg|m)
ant2-pos3 =0 # (m|m)
ant2-anttype =
ant2-antdele =0 # (m)
ant2-antdeln =0 # (m)
ant2-antdelu =0 # (m)
ant2-maxaveep =1
ant2-initrst =on # (0:off,1:on)
misc-timeinterp =off # (0:off,1:on)
misc-sbasatsel =0 # (0:all)
misc-rnxopt1 =
misc-rnxopt2 =-EPHALL
misc-pppopt =
file-satantfile =
file-rcvantfile =
file-staposfile =
file-geoidfile =
file-ionofile =
file-dcbfile =
file-eopfile =
file-blqfile =
file-tempdir =
file-geexefile =
file-solstatfile =
file-tracefile =
Using rtkpost
Simply select .obs and .nav data provided by your rover (generated using convbin above) and .obs data from your base (also generated using convbin above).
Next steps
- figure out if “CONFIG ALLEPHRTCM ENABLE” option has any impact
- explore if GPS/GNSS ephemeris can be used with RANGEB undocumented option
- update RTKGPS+ to demo5_b34L
- figure out if SW Maps can be used to feed RTCM3 data from base to feed the UM980 to compute RTK on the module in real time