Wednesday, December 19, 2018

Minimal WSPR transmit with Arduino and Si5351

Intrigued by the ZachTek pocket WSPR transmitter Peter, VK3YE, has reviewed, I wondered if I could get a simple beacon going using just an Arduino, an Si5351 clock generator and (you'll be pleased to hear) a low pass filter.

Here's the rig. The low pass filter is from the VK3YE Beach 40 design which I like because it uses off the shelf inductors.

The waveform isn't perfect but it's certainly a long way from the square wave the comes out of the clock generator.

I've taken this sketch, ripped out the lovely GPS time synchronisation, and turned it into a 1 shot beacon. You power on, or press reset, it transmits one message and then turns off RF. Of course you need to push the button at the beginning of a two minute cycle.

Here's how it decodes locally on my receiver with no antenna connected.

You can see that big signal drift earlier where I powered up with the antenna disconnected to let it get up to temperature. In the actual slot it reports a drift of -1 and decodes nicely (well... twice).

Hooked it up to the antenna just now and the little Arduino/sSi5351 transmitter is being heard quite widely.

The software uses some very handy modules:

Both very easy to use. Here's my hack of the code, I'm afraid Blogger doesn't like parts of it like angle brackets so no doubt there will be some things to fix up.

 * Minimal WSPR beacon using Si5351Arduino library
 * Based on code:
 * Copyright (C) 2015 - 2016 Jason Milldrum 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .

#include "si5351.h"
#include "Wire.h"
#include <JTEncode.h>
#include <int.h>

#include "Wire.h"

#define TONE_SPACING            146           // ~1.46 Hz
#define WSPR_CTC                10672         // CTC value for WSPR
#define CORRECTION              0             // Change this for your ref osc

#define TIME_HEADER             "T"           // Header tag for serial time sync message
#define TIME_REQUEST            7             // ASCII bell character requests a time sync message 

#define TX_LED_PIN              13
//#define SYNC_LED_PIN            13

Si5351 si5351;

JTEncode jtencode;
unsigned long freq = 7040500UL;                // Change this
char call[7] = "VK2TPM";                        // Change this
char loc[5] = "QF56";                           // Change this
uint8_t dbm = 10;
uint8_t tx_buffer[SYMBOL_COUNT];

// Global variables used in ISRs
volatile bool proceed = false;

// Timer interrupt vector.  This toggles the variable we use to gate
// each column of output to ensure accurate timing.  Called whenever
// Timer1 hits the count set below in setup().
    proceed = true;
    // Serial.println("timer fired");

// Loop through the string, transmitting one character at a time.
void encode()
    uint8_t i;
    jtencode.wspr_encode(call, loc, dbm, tx_buffer);

    // Reset the tone to 0 and turn on the output
    si5351.set_clock_pwr(SI5351_CLK0, 1);
    digitalWrite(TX_LED_PIN, HIGH);

    // Now do the rest of the message
    for(i = 0; i < SYMBOL_COUNT; i++)
      uint64_t frequency = (freq * 100) + (tx_buffer[i] * TONE_SPACING);
        si5351.set_freq(frequency, SI5351_CLK0);
        Serial.print("freq = ");
        proceed = false;
    Serial.println("message done");
    // Turn off the output
    si5351.set_clock_pwr(SI5351_CLK0, 0);
    digitalWrite(TX_LED_PIN, LOW);

void setup()
  // Use the Arduino's on-board LED as a keying indicator.
  pinMode(TX_LED_PIN, OUTPUT);

  digitalWrite(TX_LED_PIN, LOW);

  // Set time sync provider
  //setSyncProvider(requestSync);  //set function to call when sync required

  // Initialize the Si5351
  // Change the 2nd parameter in init if using a ref osc other
  // than 25 MHz
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, CORRECTION);

  // Set CLK0 output
  si5351.set_freq(freq * 100, SI5351_CLK0);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Set for max power
  si5351.set_clock_pwr(SI5351_CLK0, 0); // Disable the clock initially

  // Set up Timer1 for interrupts every symbol period.
  noInterrupts();          // Turn off interrupts.
  TCCR1A = 0;              // Set entire TCCR1A register to 0; disconnects
                           //   interrupt output pins, sets normal waveform
                           //   mode.  We're just using Timer1 as a counter.
  TCNT1  = 0;              // Initialize counter value to 0.
  TCCR1B = (1 << CS12) |   // Set CS12 and CS10 bit to set prescale
    (1 << CS10) |          //   to /1024
    (1 << WGM12);          //   turn on CTC
                           //   which gives, 64 us ticks
  TIMSK1 = (1 << OCIE1A);  // Enable timer compare interrupt.
  OCR1A = WSPR_CTC;       // Set up interrupt trigger count;
  interrupts();            // Re-enable interrupts.

  encode(); // transmit once and stop

void loop()
   // blink LED when we've finished the transmit
  digitalWrite(TX_LED_PIN, HIGH);
  digitalWrite(TX_LED_PIN, LOW);


Unknown said...


Great fun isn't it! I made up a GPS Clock a while back, which is an Uno and Ublox6M GPS receiver. the code just reads NMEA headers and extracts time, lat/long, nbr satellites etc and displays these. The other day I discovered the ZachTech guy had opensourced his code, and that Jason NT7s had the JTEncode lib. So I'm busy trying to turn my GPS clock into a WSPR beacon. So far the basic control loop is working, getting GPS time and it kicks in on the even minutes. But the JTEncode lib is quite large and I cannot get it to all fit into the Uno's memory. I'll spend some more time cutting away everything and anything I can, to get it to fit. I'm also using Jason's si5351 lib. Well done on your WSPR experiment, the small size of your code shows just how easy it is with libraries.

Paul VK3HN.

M0BMN said...

Thanks for this code, I have taken it, added a bit and made a couple of dual mode (WSPR/QRSS CW DFCW) transmitters. I Added a RTC that triggers a transmission every 10 mins , put a single FET amp on the output of the si5351 and can get about 500mW (more if pushed) into the antenna via a QRPLabs LPF, getting many spots on the wspr network and the DFCW is making it to grabbers.
makes a great little unit, Thanks for the nice simple code that has made my job much easier.
all the best
Paul M0BMN

T_Rex said...

Nice work. I love the simplicity of it. I'm building one as soon as I get an SI5351 module from Aliexpress. Thanks for the posting.

BrianC said...

I was able to compile and get the code to run on my breadboard system, I can see a signal on the wsjt-x waterfall that looks like a wspr signal and can start the transmission on time, but it is not getting decoded..Breadboard is arduino nano and si5153 breakout board from adafruit.
I do not have a low pass filter in the circuit. How critical is the LPF and am I too close to the radio which is on the same bench?

Brian K9WIS

Peter Marks said...

Hello BrianC,

I don't know why it isn't being decoded for you. Even without the low pass filter it should decode. Perhaps the signal is too strong for your receiver, try moving further away.
Does it take the right amount of time? Perhaps your Arduino is running at a different clock speed?

glene77is said...

Excelent ! K4KKQ

hamradioal said...

Many thanks for posting this code. It looks to be very useful indeed and compiled fine on a UNO, maybe overkill for this job but I had one handy. I'll bodge up a test system tomorrow to play with.
Best 73s from Scotland.