Note that at least in the US the frequency accuracy of broadcast FM stations is no better than the transmission mode needs. NFM weather broadcasts are about as good as it gets for most things on VHF. Cellphone signals may well provide the best calibration sources. The towers have ppb oscillators in profusion and one presumes they use them.

{^_^}   Joanne/W6MKU

On 2014-07-31 01:23, Tobias wrote:
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


rtl_fm-cal.patch


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