Hi!

I have just started using rtl-sdr in my application, SvxLink: http://svxlink.org/. Thanks for your work on the RTL code. It opens up the world of wideband receivers to everyone. The SvxLink RTL code is in the rtl-branch on GitHub right now if anyone is interested:

    https://github.com/sm0svx/svxlink/tree/rtl

I was missing some simple utility to calibrate the frequency error for a dongle. The simplest way I came up with was to modify the rtl_fm utility since it is very easy to find out the frequency error of an FM signal. To get a measure of the frequency error, all that have to be done in the FM demodulator is:

    samp_rate * angle / (2*PI)

The angle is already compensated with PI in the current demodulator so I only had to do the rest in my added calibration code.

I first filed this patch as a pull request on GitHub (https://github.com/steve-m/librtlsdr/pull/9) but then saw that contributions should be mailed to this mailing list.

The rtl_fm utility may now be used to calibrate the frequency error of a DVB-T dongle using the "-c" command line switch:

|   [-c] Do frequency error calibration
      Frequency error calibration will only work for FM.
      Use a higher sample rate, like -s170k, to handle
      large frequency errors. A strong and noise free
      signal is needed for good results. For example, use a
      broadcast FM station for calibration. Let the
      calibration run for some minutes until the values
      have stabilized. Rerun with -p option using the
      suggested ppm_error. Try this on a couple of stations.
      Repeat until the error is stable at about zero.
      Example: rtl_fm -f99.3M -s170k -r48k -c - | aplay -r48k -fS16_LE -
|

Regards,
Tobias

diff --git a/src/rtl_fm.c b/src/rtl_fm.c
index 0f7ac38..a58fb24 100644
--- a/src/rtl_fm.c
+++ b/src/rtl_fm.c
@@ -144,6 +144,9 @@ struct demod_state
 	pthread_cond_t ready;
 	pthread_mutex_t ready_m;
 	struct output_state *output_target;
+	double   cal_sum;
+	int      cal_cnt;
+	int      calibrate;
 };
 
 struct output_state
@@ -198,6 +201,17 @@ void usage(void)
 		//"\t    for fm squelch is inverted\n"
 		//"\t[-o oversampling (default: 1, 4 recommended)]\n"
 		"\t[-p ppm_error (default: 0)]\n"
+		"\t[-c] Do frequency error calibration\n"
+		"\t    Frequency error calibration will only work for FM.\n"
+		"\t    Use a higher sample rate, like -s170k, to handle\n"
+		"\t    large frequency errors. A strong and noise free\n"
+		"\t    signal is needed for good results. For example, use a\n"
+		"\t    broadcast FM station for calibration. Let the\n"
+		"\t    calibration run for some minutes until the values\n"
+		"\t    have stabilized. Rerun with -p option using the\n"
+		"\t    suggested ppm_error. Try this on a couple of stations.\n"
+		"\t    Repeat until the error is stable at about zero.\n"
+		"\t    Example: rtl_fm -f99.3M -s170k -r48k -c - | aplay -r48k -fS16_LE -\n"
 		"\t[-E enable_option (default: none)]\n"
 		"\t    use multiple -E to enable multiple options\n"
 		"\t    edge:   enable lower edge tuning\n"
@@ -727,6 +741,28 @@ void arbitrary_resample(int16_t *buf1, int16_t *buf2, int len1, int len2)
 	}
 }
 
+void calc_fm_fq_offset(struct demod_state *fm)
+{
+	int i;
+	for (i=0; i<fm->result_len; ++i)
+	{
+		/* Correct demodulated FM using rate_in * pcm / 2 and remove 
+		 * integer multiplier */
+		fm->cal_sum += fm->rate_in * (double)fm->result[i] / (1 << 15);
+		if (++fm->cal_cnt >= fm->rate_in)
+		{
+			double fq_offset = fm->cal_sum / fm->cal_cnt;
+			double fq_corr = -1000000.0 * fq_offset / dongle.freq;
+			int ppm_error = (int)round(fq_corr);
+			fprintf(stderr, "fq_offset=%+.0fHz ppm_error=%+d\n",
+					fq_offset, ppm_error);
+			fm->cal_sum = 0.0;
+			fm->cal_cnt = 0;
+		}
+	}
+
+}
+
 void full_demod(struct demod_state *d)
 {
 	int i, ds_p;
@@ -763,6 +799,12 @@ void full_demod(struct demod_state *d)
 	if (d->mode_demod == &raw_demod) {
 		return;
 	}
+
+	if (d->calibrate)
+	{
+		calc_fm_fq_offset(d);
+	}
+
 	/* todo, fm noise squelch */
 	// use nicer filter here too?
 	if (d->post_downsample > 1) {
@@ -975,6 +1017,9 @@ void demod_init(struct demod_state *s)
 	pthread_cond_init(&s->ready, NULL);
 	pthread_mutex_init(&s->ready_m, NULL);
 	s->output_target = &output;
+	s->cal_sum = 0.0;
+	s->cal_cnt = 0;
+	s->calibrate = 0;
 }
 
 void demod_cleanup(struct demod_state *s)
@@ -1047,7 +1092,7 @@ int main(int argc, char **argv)
 	output_init(&output);
 	controller_init(&controller);
 
-	while ((opt = getopt(argc, argv, "d:f:g:s:b:l:o:t:r:p:E:F:A:M:h")) != -1) {
+	while ((opt = getopt(argc, argv, "d:f:g:s:b:l:o:t:r:p:E:F:A:M:ch")) != -1) {
 		switch (opt) {
 		case 'd':
 			dongle.dev_index = verbose_device_search(optarg);
@@ -1142,6 +1187,9 @@ int main(int argc, char **argv)
 				demod.deemph = 1;
 				demod.squelch_level = 0;}
 			break;
+		case 'c':
+			demod.calibrate = 1;
+			break;
 		case 'h':
 		default:
 			usage();
@@ -1149,6 +1197,13 @@ int main(int argc, char **argv)
 		}
 	}
 
+	if (demod.calibrate && demod.mode_demod != &fm_demod)
+	{
+		fprintf(stderr, "Error: Frequency calibration can only "
+				"be done using the FM demodulator\n");
+		exit(1);
+	}
+
 	/* quadruple sample_rate to limit to Δθ to ±π/2 */
 	demod.rate_in *= demod.post_downsample;
 

Reply via email to