G0ETP WSPR Audio Exciter for Standard Transceivers (WSPR for Linux and embedded micros)

A friend was recently conducting some HF antenna measurements using WSPR to estimate its performance compared to a reference antenna. I had not looked at WSPR before so decided to give it a go; I set about looking from some Linux freeware to drive my existing HF transmitter via the PC sould card but only really found the full-blown WSJT-X program, which I use already for FT8. Good though this is, I like small programs that lend themselves to scripting, and being a professional signal-processing engineer, I set about writing my own as a learning/fun exercise.

Having looked online for a description of the WSPR encoding process, I have to extend my thanks to Andy Talbot G4JNT for the article he published back in 2009, wspr_coding_process.pdf. This is an almost complete presentation of the process and whilst slightly ambiguous in places, was enough to get me to a working solution. The other resource I used to verify my WSPR symbol coding was correct was Easy WSPR Encoder.

Coding Approach

I set out to write a pure C program that would take the CALLSIGN LOCATOR and POWER as inputs, construct the 162 x 2-bit symbol vector, then generate a sample stream that represents the 4FSK modulation for sending to the PC sound card. For simplicity, I decided to stream the sound samples to STDOUT and use the standard Linux utility aplay to pass them on to the sound device - why write sound API code when the 'UNIX way' is to bolt together separate programs?

Having got the main C program working, I ended up writing the 2-minute WSPR time-slot scheduler in a Bash shell script. Reasons for doing this include easy access to rigctl commands to key and de-key the transceiver and a way to avoid the WSPR time slots from drifting with respect to the PC time clock, which I have locked to NTP: If I were to keep the sound device active during the entire WSPR session, silent samples would have to be generated between active WSPR transmissions. Now, if the audio sample rate is even slightly off-frequency, the WSPR time slots will gradually slip with respect to the (NTP locked) PC clock. Sure it would be possible to check and correct this time slip within the C program but this would require a control loop to insert/delete audio samples to counter the slip. I felt it would be easier to simply stop the sound path after every WSPR transmission and allow the Bash shell script to schedule transmissions according to the accurate time clock. Sadly this results in the C program re-generating the WSPR symbols each time a transmission is started but it is very quick and un-noticeable.

Existing WSPR programs already provide the ability to transmit only in a user-selectable percentage of available WSPR slots to limit band utilisation and reduce interference to others. I felt it would also be beneficial to add a frequency-dither, so that my WSPR transmissions are not always on the same frequency within the 200Hz WSPR sub-band. This WSPR modulator can therefore apply a random frequency offset to the nominal 1500Hz audio-FSK signal. The span of the randomisation is user selectable.

Code Download

The C code can be downloaded here. Compile with gcc -o wspr_g0etp -O3 wspr_g0etp_main.c -lm

The Bash launch script can be downloaded here. Place a symbolic link to the wspr_g0etp executable alongside this script, or copy wspr_g0etp to /usr/local/bin

Operation

Edit the script to set your CALLSIGN LOCATOR and POWER in dBm. Run the script with ./wspr_run.sh. If not specified, the Tx slot probability will default to 0.2 (20%). You can specify a different percentage as a decimal after the script: E.g. ./wspr_run.sh 1 will Tx on every slot and is useful for setting up transmitter drive level. (I use pavucontrol for full control of sound streams under Linux. Be careful that there are not other programs generating audio as this will also be fed to your transmitter!)

Rig Tx/Rx Control

The Bash script expects you to have rigctld running on your machine. In my case, I have a Kenwood TS450S located on USB serial port ttyUSB0. I have another script that I use to launch rigctld as a background task. The contents of my script is:

rigctld -m 203 -r /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FT51324Z-if00-port0 -s 4800 -C serial_handshake=Hardware -C stop_bits=2 &


Last updated: 22.4.2024