Testing a RTL-SDR with FSK on HF

There’s a lot of discussion about ADC resolution and SDRs. I’m trying to develop a simple HF data system that uses RTL-SDRs in “Direct Sample” mode. This blog post describes how I measured the Minimum Detectable Signal (MDS) of my 100 bit/s 2FSK receiver, and a spreadsheet model of the receiver that explains my results.

Noise in a receiver comes from all sorts of places. There are two sources of concern for this project – HF band noise and ADC quantisation noise. On lower HF frequencies (7MHz and below) I’m guess-timating signals weaker than -100dBm will be swamped by HF band noise. So I’d like a receiver that has a MDS anywhere under that. The big question is, can we build such a receiver using a low cost SDR?

Experimental Set Up

So I hooked up the experimental setup in the figure below:

The photo shows the actual hardware. It was spaced apart a bit further for the actual test:

Rpitx is sending 2FSK at 100 bit/s and about 14dBm Tx power. It then gets attenuated by some fixed and variable attenuators to beneath -100dBm. I fed the signal into a RTL-SDR plugged into my laptop, demodulated the 2FSK signal, and measured the Bit Error Rate (BER).

I tried a command line receiver:

rtl_sdr -s 1200000 -f 7000000 -D 2 - | csdr convert_u8_f | csdr shift_addition_cc `python -c "print float(7000000-7177000)/1200000"` | csdr fir_decimate_cc 25 0.05 HAMMING | csdr bandpass_fir_fft_cc 0 0.1 0.05 | csdr realpart_cf | csdr convert_f_s16 | ~/codec2-dev/build_linux/src/fsk_demod 2 48000 100 - - | ~/codec2-dev/build_linux/src/fsk_put_test_bits -

and also gqrx, using this configuration:

with the very handy UDP output option sending samples to the FSK demodulator:

$ nc -ul 7355 | ./fsk_demod 2 48000 100 - - | ./fsk_put_test_bits -

Both versions demodulate the FSK signal and print the bit error rate in real time. I really love the csdr tools, and gqrx is also great for a more visual look at the signal and the ability to monitor the audio.

For these tests the gqrx receiver worked best. It attenuated nearby interferers better (i.e. better sideband rejection) and worked at lower Rx signal levels. It also has a “hardware AGC” option that I haven’t worked out how to enable in the command line tools. However for my target use case I’ll eventually need a command line version, so I’ll have to improve the command line version some time.

The RF Gods are smiling on me today. This experimental set up actually works better than previous bench tests where we needed to put the Tx in another room to get enough isolation. I can still get 10dB steps from the attenuator at -120dBm (ish) with the Tx a few feet from the Rx. It might be the ferrites on the cable to the switched attenuator.

I tested the ability to get solid 10dB steps using a CW (continuous sine wave) signal using the “test” utility in rpitx. FSK bounces about too much, especially with the narrow spec an settings I need to measure weak signals. The configuration of the Rigol DSA815 I used to see the weak signals is described at the end of this post on the SM2000.

The switched attenuator just has 10dB steps. I am getting zero bit errors at -115dBm, and the modem fell over on the next step (-125dBm). So the MDS is somewhere in between.


This spreadsheet (click for the file) models the receiver:

By poking the RTL-SDR with my signal generator, and plotting the output waveforms, I worked out that it clips at around -30dBm (a respectable S9+40dB). So that’s the strongest signal it can handle, at least using the rtl_sdr command line options I can find. Even though it’s an 8 bit ADC I figure there are 7 magnitude bits (the samples are unsigned chars). So we get 6dB per bit or 42dB dynamic range.

This lets us work out the the power of the quantisation noise (42dB beneath -30dBm). This noise power is effectively spread across the entire bandwidth of the ADC, a little bit of noise power for each Hz of bandwidth. The bandwidth is set by the sample rate of the RTL-SDRs internal ADC (28.8 MHz). So now we can work out No (N-nought), the power/unit Hz of bandwidth. It’s like a normalised version of the receiver “noise floor”. An ADC with more bits would have less quantisation noise.

There follows some modem working which gives us an estimate of the MDS for the modem. The MDS of -117.6dBm is between my two measurements above, so we have a good agreement between this model and the experimental results. Cool!

Falling through the Noise Floor

The “noise floor” depends on what you are trying to receive. If you are listening to wide 10kHz wide AM signal, you will be slurping up 10kHz of noise, and get a noise power of:

-146.6+10*log10(10000) = -106.6 dBm

So if you want that AM signal to have a SNR of 20dB, you need a received signal level of -86.6dBm to get over the quantisation noise of the receiver.

I’m trying to receive low bit rate FSK which can handle a lot more noise before it falls over, as indicated in the spreadsheet above. So it’s more robust to the quantisation noise and we can have a much lower MDS.

The “noise floor” is not some impenetrable barrier. It’s just a convention, and needs to be considered relative to the bandwidth and type of the signal you want to receive.

One area I get confused about is noise bandwidth. In the model above I assume the noise band width is the same as the ADC sample rate. Please feel free to correct me if that assumption is wrong! With IQ signals we have to consider negative frequencies, complex to real conversions, which all affects noise power. I muddle through this when I play with modems but if anyone has a straight forward explanation of the noise bandwidth I’d love to hear it!

Blocking Tests

At the suggestion of Mark, I repeated the MDS tests with a strong CW interferer from my signal generator. I adjusted the Sig Gen and Rx levels until I could just detect the FSK signal. Here are the results, all in dBm:

Sig Gen 2FSK Rx MDS Difference
-51 -116 65
-30 -96 66

The FSK signal was at 7.177MHz. I tried the interferer at 7MHz (177 kHz away) and 7.170MHz (just 7 kHz away) with the same blocking results. I’m pretty impressed that the system can continue to work with a 65dB stronger signal just 7kHz away.

So the interferer desensitises the receiver somewhat. When listening to the signal on gqrx, I can hear the FSK signal get much weaker when I switch the Sig Gen on. However it often keeps demodulating just fine – FSK is not sensitive to amplitude.

I can also hear spurious tones appearing; the quantisation noise isn’t really white noise any more when a strong signal is present. Makes me feel like I still have a lot to learn about this SDR caper, especially direct sampling receivers!

As with the MDS results – my blocking results are likely to depend on the nature of the signal I am trying to receive. For example a SSB signal or higher data rate might have different blocking results.

Still, 65dB rejection on a $27 radio (at least for my test modem signal) is not too shabby. I can get away with a S9+40dB (-30dBm) interferer just 7kHz away with my rx signal near the limits of where I want to detect (-96dBm).


So I figure for the lower HF bands this receivers performance is OK – the ADC quantisation noise isn’t likely to impact performance and the strong signal performance is good enough. An overload of -30dBm (S9+40dB) is also acceptable given the use case is remote communications where there is unlikely to be any nearby transmitters in the input filter passband.

The 100 bit/s signal is just a starting point. I can use that as a reference to help me understand how different modems and bit rates will perform. For example I can increase the bit rate to say 1000 bit/s 2FSK, increasing the MDS by 10dB, and still be well beneath my -100dBm MDS target. Good.

If it does falls over in the real world due to MDS performance, overload or blocking, I now have a good understanding of how it works so it will be possible to engineer a solution.

For example a pre-amp with X dB gain would lower the quantisation noise power by X dB and allow us to detect weaker signals but then the Rx would overload at -30-X dB. If we have strong signal problems but our target signal is also pretty strong we can insert an attenuator. If we drop in another SDR I can recompute the quantisation noise from it’s specs, and estimate how well it will perform.

Reading Further

Rpitx and 2FSK, first part in this series.
Spreadsheet used to do the working for the quantisation noise.

4 thoughts on “Testing a RTL-SDR with FSK on HF”

  1. Hi David,

    WOW! Again most impressive!!!

    I am thinking of trying to replicate what you are doing. Not sure I have enough brains though :).

    Your question about noise bandwidth is a very good question. At one point I built two radically different receivers for Olivia 32-1000 trying to ask and answer the question you ask. Other things came up, but the hardware still works. So far I have come to no real conclusions. Each receiver had its own more or less isolated antenna (vertical) on different sides of the factory.

    I think a ping mode similar to FT8 might be a big help. The ping response would give received level, noise and BER and perhaps other data like strong interferers. You could send that command repetitively so you can study your ‘real’ antenna’s directional pattern, etc.

    I have been looking at FT8 ‘signal strength’ from ‘local’ signals. The data is somewhat stable with occasional large deviations??

    I have yet to receive any of your signals. But I am a long ways away :). I have been able to compile your code so that is one big step already taken. And I can feed in your recorded live data and all seems OK.

    Lots of fun :).

    John – Concord, NH USA

  2. Hi, very nice work here. I think one of the hardiest hf bfsk modems is the GTOR implementation you might look into, I’m afraid 5N1 Baudot or 8N1 ASCII BFSK is going to force you to run low bitrates in actual ota use as hf is replete with multipath and fading as well as qrn/m. This is cart before horse but you get the idea, the ber on hf is why most HAMs run 45Bd rtty and not 300Bd rtty. That being said, I’ve often though hfdl would make for a lovely short messaging mode for HAM use, but hfdl isn’t BFSK.

    Keep up the good work!

  3. Hi David,

    I’m not sure the analysis you do about ADC quantization noise and sample rate is correct. The ADC might run at 28.8MHz, but then there is some DSP chain (mixing to IQ, filtering and decimation) and after this, IQ samples are sent to the host at 2.4Msps using still 8 bits. Therefore, the quantization noise power spectral density should be calculated using these 2.4MHz instead of the full 28.8MHz (which really should be read as 14.4MHz, since this is a real signal, so the bandwidth is half the sample rate).

    As a general remark, once you start decimating you should increase the number of bits to maintain the same dynamic range. That’s the reason why 16 bits instead of 8 are used in the 48kHz audio interface between GQRX and the FSK demodulator.

    1. Hi Daniel,

      You make some really good points – thanks. You have me thinking; SDR and quantisation is surprisingly complex under the surface. Here some initial thoughts on your comments:

      1. In my latest work, the RTL-SDR is running at just 240 ksps (to save CPU), but curiously – the MDS is about the same as 2.4 Msps (between -114 and -124 dBm). This suggests no great change in performance with reduced sample rate (however I may have some experimental error, easy to do with RF!).

      2. My signal processing model of the complex mixer, filter, and down sampler is a filter that extracts a chunk of the original sampled spectrum. “No” (the noise spectral density) is unchanged, although the total quantisation noise power is reduced as the bandwidth is reduced. The actual sample rate at the output is not important, as sampling theory says we can reproduce any band limited signal perfectly.

      3. So my model is the 28.8MHz ADC (which introduces quantisation noise), a noiseless mixer/filter/resampler, and a second, 8 bit quantiser at the output, that feeds IQ samples to the Host CPU.

      4. This second quantiser does indeed introduce additional noise, but no much (perhaps 3dB), as it’s resolution and hence quantisation noise is the same as the input ADC. So it’s like adding noise from two noise sources of equal power.

      5. This is like a cascaded noise figure problem in radio designs. The ADC has a NF of around 27dB, we then have some noiseless signal processing, and quantiser with a similar noise figure. After that we have high resolution samples or floating point so the DSP stages on the Host CPU inject no noise into the “radio”. The general rule of thumb with radios is the front end sets the Noise Figure.

      However there are probably other models we can use, and under some circumstances (e.g. strong signals) the quantisation noise model may break down.

      6. Of course this is all very waveform dependant. My little FSK waveform at 100 bit/s integrates over long time periods which averages out a lot of noise.

      Must admit I’m still feeling my way on all this. I am not an expert on radio and especially direct sampling architectures. I think I need some more simulation and real world tests to confirm my ideas.

      Thanks – great discussion 🙂

Comments are closed.