AB9IL.net: Programmatic RTL-SDR Frequency Claibration

Written and curated by Philip Collier / AB9IL
HOME Software Defined Radio WiFi Antennas Air and Space Radio Linux or Windows Digital Audio Liberation Tech Video Gallery Photo Gallery

Live Internet SDR List Radio Caroline BBC Radio 4 LW


RTL-SDRs are great devices. They are known for being small, inexpensive, light in power demand, operable from low frequencies through L band, and workable with numerous signal processing packages. They are NOT known for having precise and stable clock oscillators. Frequency errors of 30 to 50 parts per million are common. For users tuning VHF narrowband voice or digital modes, such an error is enough to cause signals to fall on the edge of the receiver passband. In some cases, signals may fall out of a narrow passband and be completely missed.

Airband Radio on the RTL-SDR
How to maximize RTL-SDR performance for aeronautical comms.

One solution is to only use RTL-SDRs sold with upgraded clock oscillators, such as the NESDR SMArt or RTL-SDR.com V3. Another way to put the RTL-SDR on frequency is to check it against a known signal and manually program the offset into applications controlling the dongle. That is often accurate enough, but still a broad approximation. A better solution, also applicable to upgraded devices, is to automate the process of checking against known signals and get a more precise measurement based on a time series of phase measurements. For that, there is a nifty application named Kalibrate-RTL.

Kalibrate-RTL uses mobile telephone calibration signals, which are derived from GNSS signals as a precise frequency and time reference. Also, it eliminates human factors in measuring offsets. In fact it takes multiple readings and averages them to work out a mean offset. That is good enough (quite good enough) for most users. For critical applications needing top notch, minimal drift, precision tuning, an SDR better than RTL dongles should be considered...

How well do you know your RTL dongle? If its capabilities or how it works seems a bit mysterious, check out our more detailed page about understanding the RTL-SDR.

Given below is a bash script for running Kalibrate-RTL. It was developed on a modest Ubuntu Linux system, and tested on run of the mill RTL-SDR hardware. It can get the PPM offset, then save it along with a gain setting and SoapySDR device string for other applications to use. For example, GQRX, OpenWebRX, or Dump1090 can configured with the help of a wrapper script to read the offset and gain before bringing up the SDR for signal collection.

#!/bin/bash

# Copyright (c) 2019 by Philip Collier, Radio AB9IL.
# This script is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version. There is NO warranty; not even for
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

# Measure and save rtl-sdr calibration information using GSM base stations.
# Edit the file referenced for gain setting by certain applications.
# Save SoapySDR device string and key for other uses.

Encoding=UTF-8
savedir="/usr/local/etc"

get_offset(){
echo "Measuring ppm offset on channel "$best_channel
offset=($(kal -c $best_channel 2>&1 | grep "absolute error" | grep -Po "\d*\." | awk '{printf "%.0f", $0}'))

if [[ -z "$offset" ]]; then
		notifyerror
fi

echo $offset > $savedir/sdr_offset  # save offset for general usage
sed -i "s/corr_freq=.*/corr_freq=${offset}000000/g" ~/.config/gqrx/default.conf # save offset for gqrx

	WINDOW=$(zenity --info --height 100 --width 350 \
	--title="Calibration and Gain" \
	--text="An offset of $offset ppm has been written to file $savedir/sdr_offset.");
}

notifyerror(){
	WINDOW=$(zenity --info --height 100 --width 350 \
	--title="Calibration and Gain" \
	--text="Something went wrong.");
	exit
}

scan_band(){
echo "Please stand by.  Scanning for $band stations..."
mapfile -t arr < <(kal -v -s $band -g 40 2>&1 | grep 'chan:' | awk '{printf $2" "$7"\n"}' | sort -nrk2)
echo "Chan Strength"
printf '%s\n' "${arr[@]}"
set -- ${arr[0]}
best_channel=$1
get_offset
}

set_device(){
OUTPUT=$(zenity --forms --title="SoapySDR Device Type" --width 400 --height 100 \
--text="Enter the SDR start-up parameters
reported by \"SoapySDRUtil --find\"." \
--separator="," \
--add-entry="SoapySDR Device Driver (e.g. rtlsdr):" \
--add-entry="SoapySDR Device Key (e.g. rtl=0):" \
);

if [[ "$?" -ne "0" ]]; then
	notifyerror
fi

driver=$(awk -F, '{print $1}' <<<$OUTPUT)
devkey=$(awk -F, '{print $2}' <<<$OUTPUT)

# write device driver data to the reference file
echo $driver > $savedir/sdr_driver

# write device key to the reference file
echo $devkey > $savedir/sdr_key

	WINDOW=$(zenity --info --height 100 --width 350 \
	--title="SoapySDR Device Type" \
	--text="Device driver $driver has been written to file $savedir/sdr_driver.
Device key $devkey has been written to file $savedir/sdr_key.");
}

set_gain(){
OUTPUT=$(zenity --forms --title="Calibration and Gain" --width 400 --height 100 \
--text="Enter the desired SDR gain." \
--add-entry="Gain:");

if [[ "$?" -ne "0" || -z "$?" ]]; then
    exit
fi

gain=$(awk -F, '{print $1}' <<<$OUTPUT)

# write gain setting to the reference file
echo $gain > $savedir/sdr_gain

	WINDOW=$(zenity --info --height 100 --width 350 \
	--title="Calibration and Gain" \
	--text="A gain of $gain has been written to file $savedir/sdr_gain.");
}

set_offset(){
OUTPUT=$(zenity --forms --title="Calibration and Gain" --width 400 --height 100 \
--text="Enter the desired SDR offset (ppm)." \
--add-entry="Offset:");

if [[ "$?" -ne "0" || -z "$?" ]]; then
    exit
fi

offset=$(awk -F, '{print $1}' <<<$OUTPUT)

echo $offset > $savedir/sdr_offset  # save offset for general usage
sed -i "s/corr_freq=.*/corr_freq=${offset}000000/g" ~/.config/gqrx/default.conf # save offset for gqrx

	WINDOW=$(zenity --info --height 100 --width 350 \
	--title="Calibration and Gain" \
	--text="An offset of $offset ppm has been written to file $savedir/sdr_offset.");
}

gui(){
ans=$(zenity  --list  --title "SDR Operating Parameters" --width=500 --height=290 \
--text "Manage RTL-SDR frequency calibration and gain.
1) Calibration uses measurements of GSM base stations.
2) Device gain is saved for reference by other applications." \
--radiolist  --column "Pick" --column "Action" \
TRUE "Scan for GSM 850 MHz base stations." \
FALSE "Scan for GSM 900 MHz base stations." \
FALSE "Scan for E-GSM base stations." \
FALSE "Manually program the SDR offset." \
FALSE "Manually program the SDR gain." \
FALSE "Manually program the SoapySDR device data.");

	if [  "$ans" = "Scan for GSM 850 MHz base stations." ]; then
		band='GSM850'
		scan_band

	elif [  "$ans" = "Scan for GSM 900 MHz base stations." ]; then
		band='GSM900'
		scan_band

	elif [  "$ans" = "Scan for E-GSM base stations." ]; then
		band='EGSM'
		scan_band

	elif [  "$ans" = "Manually program the SDR offset." ]; then
		set_offset

	elif [  "$ans" = "Manually program the SDR gain." ]; then
		set_gain

	elif [  "$ans" = "Manually program the SoapySDR device data." ]; then
		set_device

	fi
}

case "$1" in
	gui)
		gui
	;;
	gsm850)
		band='GSM850'
		scan_band
	;;
	gsm900)
		band='GSM900'
		scan_band
	;;
	egsm)
		band='EGSM'
		scan_band
	;;
	offset)
		if [[ "$2" -ne "0" || -z "$2" ]]; then
    		exit
		fi
		echo "$2" > $savedir/sdr_offset
	;;
	gain)
		if [[ "$2" -ne "0" || -z "$2" ]]; then
    		exit
		fi
		echo "$2" > $savedir/sdr_gain
	;;
	device)
		if [[ "$2" -ne "0" || -z "$2" ]]; then
    		exit
		fi
		echo "$2" > $savedir/sdr_driver
		if [[ "$3" -ne "0" || -z "$3" ]]; then
    		exit
		fi
		echo "$3" > $savedir/sdr_key
	;;
	*)
	echo "Usage: kal.sh  

			gui			Use the graphical interface to select a function.
			gsm850		Scan for GSM 850 MHz base stations.
			gsm900		Scan for GSM 900 MHz base stations.
			egsm		Scan for E-GSM base stations.
			offset		Manually program the SDR offset .
			gain		Manually program the SDR gain .
			device		Manually program the SoapySDR device data  .

" >&2
	exit 3
	;;
esac

It is a simple matter to calibrate a dedicated receiver at boot time, as the script can be executed from a system init script. Here is a snippet of code which would run from a launcher in /etc/xdg/autostart/ or the script /etc/init.d/rc.local:

# calibrate the rtl-sdr
sh -c "kal.sh gsm850" &

The above example could also be run regularly as a cron job, but be aware that some scripting would be necessary to stop anther process which may be using the device, then start it up again after the calibration finishes. Calibration of SDR devices with very large offsets may fail, as the cell tower signals may fall outside of the receiver passband. In that case, use your favorite SDR application to manually find the strongest GSM signals, then invoke kal manually to tune one of them.

kal -v -f  -g 

For more scrpts and snippets useful for SDR operating, see the Skywave Linux Github repository.




© 2005 - 2024 AB9IL.net, All Rights Reserved.
About Philip Collier / AB9IL, Commentaries and Op-Eds, Contact, Privacy Policy and Affiliate Disclosure, XML Sitemap.

This website is reader-supported. As an Amazon affiliate, I earn from qualifying purchases.