Category Archives: WSPR

Building and Testing the QRP Labs QDX Digital Transceiver

I recently built the QRP Labs QDX transceiver,  which is a remarkable little radio, designed for FSK modes (FT8, JS8, WSPR, etc.)  on the 80-20 meter ham bands.QDX-2This rig has some very clever design inside.  Instead of the standard SSB modulator and demodulator, this uses a USB interface for audio and CAT rig control (emulating a Kenwood TS-440), and measures the audio input tone, using that measured audio frequency and the set “carrier” frequency to program the internal Si5351 clock chip.  This way it directly generates the transmit frequency — no sideband modulator / filter, no linear amplifier, just the synthesizer and an efficient Class-D 5W power amplifier.

On receive, the radio uses a “Tayloe” mixer, using the Si5351 to generate the necessary quadrature clocks.  The I and Q mixer outputs are digitized and processed in a software SSB demodulator.  This digital audio is sent out the USB interface, to a program such as WSJTX.

There’s much worth studying in this design and Hans Summers (the designer) has provided some excellent documentation:

It took me about three hours to build this radio.  The kit comes with all the surface-mount components already loaded, so I only had to solder a handful of capacitors and wind some toroids.


There has been some discussion about the four power transistors — they do run a bit warm and people have burned them out trying for higher power (or due to bad SWR).  I ran the transmitter into a dummy load for about two minutes and measured the transistor temperatures.  This had stabilized at about 50 degrees C:

QDX 2 minutes TX


The signal transmitted by this radio is clean, with harmonics well under the FCC requirements (those close-in spurs may not actually be there.  The spectrum analyzer I was using has some spurious responses of its own):20m 1

After this I gave the radio a spin on 20 meters, using WSJTX running on a small, inexpensive linux box, the Inovato “QUADRA”.  This little computer is comparable to the Raspberry Pi-3, and only costs $30 (power supply and HDMI cable included).  I installed JS8CALL, WSJTX, and Direwolf using the command-line “sudo apt-get install [program name]“, and everything went without a hitch.  Running FT8 for a few minutes resulted in this:



A friend had noted that the QDX wasn’t perfectly generating some of the smaller frequency shifts used in some FSK modes.  Hans has acknowledged the problem and is working on a firmware update, but I decided to look into this.  First I used my “Signalhound USB-SA44B” spectrum analyzer, in the modulation analysis mode.  FT8 looked good:

QDX FT8 mod 1

Note the frequency dither, as the QDX can’t decide on which frequency to send.  This should have absolutely no impact on performance.  8-FSK FT8 uses a tone-spacing of 6.25 Hz.

WSPR was next, and there are issues here.  The 4-FSK WSPR tone spacing is a narrower 1.456 Hz, and the QDX has difficulty generating these evenly-spaced tones.  Here is the analyzer modulation display for 30 meter WSPR:

4FSK Measurement

Notice how the tone frequency steps aren’t even.  Tone 1 should be halfway between tone 0 and tone 2 (and it isn’t).  I wasn’t able to measure exact tone frequencies with the modulation analyzer mode, so I put together a different test setup:



This technique mixes the output of the transmitter with a fixed-frequency signal generator, and the resulting difference frequency can be measured by a frequency counter.  I used a Time Interval Counter of my own design that does “reciprocal” counting where the period of the input signal is measured with 10ns resolution, resulting in fast and accurate measurement, especially for low frequency inputs.  These measurements are sent to a PC where they can be plotted and analyzed.

Modulation Measurement Setup

I had all the pieces lying around except for my low-pass filter, for which I put a simple R-C netwotk (470 Ohms series, 0.1uF shunt) and some SMA connectors on a bit of circuit board.  That Altoids tin contains a simple Si5351 clock generator, a tiny controller, and a not-particularly-stable crystal oscillator.

So here are some measurement results:

Meas Screen

This was done with the mixing generator set about 40 Hz lower than the QDX WSPR transmission.  The counter is pre-dividing the 40Hz difference frequency by four, resulting in a measurement rate of about 10Hz.  This gives about 14 or 15 samples per WSPR symbol.  Here we can see the same problem with the QDX frequency setting — this time it’s Tone 2 that is shifted.  The specifics of the shift or frequency error depends on the actual audio frequency coming in to the QDX (sent my WSJTX).  Here is the modulation with the WSPR transmit offset at 1404Hz (I believe that slow frequency shift is the QDX as it warms up, but I should test that using a better frequency reference):



Note that the step error is at the low-frequency end.  Incrementing the audio frequencies by 1 Hz gives us this with the error at the upper frequency end:


I saw the same error-vs-audio-frequency behavior as when using the spectrum analyzer modulation test.  As another check, I used my drift-buoy controller/synthesizer as a WSPR source (here, generating random WSPR -4FSK tones, and the measurements put into a spreadsheet in order to zero-reference the frequencies):

Counter-BuoyThe tone frequencies are correct.

The QDX  displays this WSPR frequency issue on both 30 and 20 meters,  and I assume on the other bands as well.  I have used the QDX to successfully transmit WSPR, so this amount of error isn’t necessarily critical, but I look forward to enhanced performance in the near future.

For what it’s worth, the Time Interval Counter (TIC) I am using (and designed) is able to directly measure the WSPR modulation at the 10.140… MHz carrier frequency, without using the fancy mixing / down-conversion arrangement:

Counter direct div2M

Here the TIC has pre-divided the MHz input frequency by 2,000,000 which gives us about five measurements per second, about the slowest rate that will reasonably measure 1.456 Hz symbol rate of WSPR.  With the 10 ns resolution of the TIC, this  results in frequency measurements with about 1/2 Hz resolution.  This is good enough to show the modulation, but not good enough to accurately measure it.

The down-conversion method, by directly mixing the MHz signal down to about 100Hz, provides a single-cycle frequency resolution of about 0.0001 Hz.  Since I am measuring four cycles, that gives 0.000025 Hz resolution, certainly enough to accurately measure WSPR deviation!



Yet Another WSPR Presentation

Here’s a new presentation I gave to the San Juan County Amateur Radio Society:

This has less detail about Raspberry Pi direct frequency synthesis, but more info on other WSPR projects and some newer technology.  Some of this is going to be designed into a drift-buoy I am contemplating, and I discuss some of the related issues, including ham telemetry.

Weak Signal Reception

I noticed something interesting when I was testing my Raspberry Pi WSPR transmitter:  just how little signal is needed to receive WSPR! I was using a FUNcube Dongle Pro+ receiver to monitor my transmitted signal.  This neat SDR (Software Defined Radio) covers from 150 KHz to 1.9 GHz (with a gap between 240 and 420 MHz).  It has some nice built-in filters and performs well.

FUNcube Dongle Pro +

The FUNcube was feeding the WSPR WSJT-X program (WSJT-X), and connected to the RPi WSPR transmitter / antenna with a -50dB tap/attenuator:

-50dB tap / attenuator
-50dB tap / attenuator

I made this attenuator a while back when I was testing my 100-Watt ham HF transceiver.  This tap lets me connect the rig to a dummy load (or an antenna) and siphons off a low-level signal that I can connect to my spectrum analyzer or other measuring equipment.  With a 100W through-signal, the signal at the tap is 1mW (that’s -50dB).  With that 100W signal the 7886 Ohm resistor dissipates about 0.6W, so I paralleled four 1/4W resistors (3300 || 3300 || 3300 || 2700 Ohms) to get close to the calculated value.  The thing works well enough at HF frequencies, but I wouldn’t trust the accuracy at VHF.

Anyway, with this tap I could monitor my on-the-air 20-meter WSPR transmissions.  What really surprised me was when I started receiving WSPR signals from the K6SRO WSPR beacon through that 50dB attenuator!   K6SRO transmits at 5W at a distance of over 1,000 kilometers from my receiver.  The received signal-to-noise ratio was -17dB, which is at least 12dB better than needed for WSPR decoding.

I suppose this reminds us that on the HF bands the atmospheric background noise is usually the dominant factor, and when it comes to receiving our antennas are less critical that we might imagine.

Raspberry Pi WSPR Transmitter – Update

I was fairly astounded to see the difference in spurious signal generation between the two versions of the WsprryPi .  Here’s the output from Raw 1

And here is the significantly cleaner output from 1

What caused the reduction in spurious outputs?  I’ve been studying the programs, and while I still haven’t figured out a lot of the details, one significant difference is the addition of this bit of code:

while (n_pwmclk_transmitted<n_pwmclk_per_sym) {
// Number of PWM clocks for this iteration
long int n_pwmclk=PWM_CLOCKS_PER_ITER_NOMINAL;
    // Iterations may produce spurs around the main peak based on the iteration
    // frequency. Randomize the iteration period so as to spread this peak
    // around.
if (n_pwmclk_transmitted+n_pwmclk>n_pwmclk_per_sym) {


This code snippet is located at line 300 in this file:

There may be other relevant changes to the code, but this looks like the big one when it comes to output spur generation.  I will play with it, as well as digging into the rest of the code, and report back.


Raspberry Pi WSPR Transmitter

[Updated Dec 25 -- corrections and clarifications] WSPR is the ham radio “Weak Signal Propagation Reporter” system (   Here is my recent experience using a Raspberry Pi as a very simple stand-alone  WSPR transmitter. IMG_20151211_094241666

Shown above is a Raspberry Pi Model B, going through a four-pole 14MHz  bandpass filter, and then to a multiband dipole strung through the trees.  It transmits on approximately 14.0971 MHz, at a power of +10 dBm (0.01 Watt).

Raspberry Pi Versions and code compatability

There are several Raspberry Pi versions. I first tried using my new Raspberry Pi B+

Raspberry Pi 2 Model B+

I used this code:, which had been referenced by several people who had used the RPi for WSPR. It didn’t work on this version of the Raspberry Pi, and by inserting debugging “printf” statements could see that it was getting hung up when using DMA (a type of memory access).  A bit of Googling found that this was a known problem, but the program would run on the older RPi.  I tried it on my older Raspberry Pi-B, and it worked!

Raspberry Pi Model B

Here is the raw Pi output between 0-100 MHz : Raw 1

I used a capacitively-coupled -20dB pad for this measurement.  You don’t want to put a resistive 50-Ohm load on the RPi output, so I used a  0.1uF DC-blocking capacitor. Notice all the spurs being generated by the Raspberry Pi.  We see the typical strong odd-harmonics that we would expect from the squarewave output of the Pi, and weaker even-order harmonics since the output isn’t a pure squarewave (rise and fall times probably aren’t identical.)  There are also other spurious signals, -32dBc (dB below the carrier).  These are presumably sampling artifacts, caused because we are effectively sampling a 14 MHz squarewave with the internally-generated 250 MHz clock driving the Pi divider.  More on this later… So the RPi output is a bit too dirty to put on the air.  I’ve seen people use a Pi-network low-pass filter for this, but because there are lower-frequency spurs as well as harmonics, I decided to use a simple band-pass filter:

14 MHz Bandpass Filter
14 MHz Bandpass Filter

Note that the inductor values are calculated, not measured.  I wound them using #28 enameled wire on a T37-6 toroid core from Amidon.  This filter has a capacitive divider input and output so there is no issue with DC loading the RPi output.   Here is the filter response: filter

I have it a bit overcoupled to simplify tuning, so I lose a few dB at the 14.0971 MHz operating frequency.  If I get ambitious I might optimize it, but it works well enough for now.  Here is the RPi output after the filter:


You can see that there are still some close-in spurs that my simple filter isn’t going to take care of:F2a

Still, the spurs are about 30dB below the 10mw carrier, so I decided it was good enough and put it on the air.

But what about the Raspberry Pi 2 B+???

This code still wouldn’t run on the newer RPi 2 B+.  After a bit of searching I found this code update which had fixed the hardware dependencies that broke things on the new Pi: In addition, this version has more command-line options that are very useful for debugging and running WSPR.  I tried it first on the older RPi-B.

WOW!  What happened to the spurs???

The output was dramatically different with this new code.  Look below and notice how most of the non-harmonic spurious signals have disappeared!  We now have the fundamental, strong odd harmonics, weaker even harmonics, a few (presumably) sampling artifacts, and a wideband, low-level noise floor around -50dBc (I used a peak-hold setting to capture the worst-case noise-floor envelope):


Running it through my band-pass filter we get a really nice signal: Fn1

Zooming in we see a very clean carrier with just a hint of close-in noise:


Zooming in further we see what look like 60 and 120 Hz sidebands, presumably coming from the cheap wall-wart USB charger I am using to power the RPi.


Why does this new code generate a cleaner output, seeing that the hardware is identical?  I need to compare the two versions, but I suspect that the difference is in the choice of divisors and the configuration of the MASH noise-shaping.  Here’s a link to the RPi divider hardware that covers some of the details of operation (see section 6.3):

I will report back in a few weeks when I get a chance to dig further into this.  I haven’t even tried this on the new RPi 2 B+, but I will soon.  Until then, the RPi WSPR transmitter is running full-time from my Friday Harbor location:     The signal was being received so well by some friends in Sonoma County that I put the 20dB attenuator back in between the Pi and the filter, reducing the output to -10dBm (100 micro-Watts).  The signal was still received at a distance of 1121 km, giving us about 11 million kilometers per Watt!  I have since resumed transmitting at +10dBm.

10mW WSPR on 20 meters
10mW WSPR on 20 meters


More details to come: I will be writing about these topics sooner or later:

  • Links to other WSPR / Pi articles
  • Frequency stability with the tiny low-cost Raspberry Pi oscillator
  • WSPR timing accuracy using Network Time Server
  • GPS time and frequency (?) synchronization
  • Hardware connections
  • Remote control of Raspberry Pi over the internet
  • Operation on other frequencies
  • Analysis of Raspberry Pi frequency generation, especially spectral purity and noise-shaping
  • Trying the new RPi-Zero (the $5 model!)

Use This Code: WsprryPi code that runs on all (?) Raspberry Pi boards: