/WSPR

A Single Band WSPR beacon using an ESP32. Published in Practical Wireless May/June 2024

Primary LanguageC++GNU General Public License v3.0GPL-3.0

A beginners guide to the Arduino IDE culminating in a single band WSPR beacon.

Reprinted with permission, and errors corrected. From Practical Wireless articles in the May and June 2024 editions.

It's been pointed out to me that pin outs in my Fritzing diagram are not in the same order as the pin outs in some of the ESP32 that users have bought. Unfortunately, there are a lot of variations of the ESP32. This isn't a problem if you use the same pins, no matter where they are located, as my sketch. The pins that I use are 3V3 and GND for power to the OLED, si5351 and real time clock. The I2C pins are SDA pin D21 and SCL pin D22. The ESP32 can be either 30 or 38 pin. I've tested with both.

Most boards have a built in LED and normally have a definition for LED_BUILTIN. However, I've recently come across some boards that don't have LED_BUILTIN defined and the LED is connected to pin 2. I've also come across ESP32 boards with no LEDs apart from the power indicator. If you have a board where LED_BUILTIN isn't defined, or there is no LED, the code will define LED_BUILTIN = 2 and allow the program to compile and load. If there is an LED it should come on when WSPR is transmitting. If the LED doesn't come on either there isn't an LED or it's connected to another pin, change LED_BUILTIN to the connected pin. ESP32's marked ESP32_Devkitc_V4 only have a power LED, there is no other LED. If the blink program fails to compile with the error :-   Compilation error: 'LED_BUILTIN' was not declared in this scope

Add the following before void setup(){

#ifndef LED_BUILTIN

#define LED_BUILTIN 2

#endif

If there is an LED on pin 2 it should blink. If there isn't an LED on your ESP32 nothing will happpen.

Fig 1  Block Diagram

ESP32_Single_Band_WSPR

Why Espressif ESP32?

The ESP32 has built in Wi-Fi to connect to your router and get to the internet. It is also very fast and has a lot of memory and input/output pins. The problem with them is that there are lots of different varieties and the input/output pins can be in different positions. Just to make things even more awkward, they come as 30 pins, 36 pins, as well as 38 pins and they are not breadboard friendly. If you want to use them on a breadboard you need to put 2 breadboards together and span the ESP32 across the boards. It’s now possible to get holders for them and these make things a lot easier. If you haven’t bought a specific ESP32, like one of the Adafruit versions, you probably won’t get documentation. To identify them they normally have some marking on them so you can select the one you have in the boards manager. If there is no marking and the seller didn’t tell you what one it is you’ll just need to guess. If you get it wrong, the program will probably run very slowly or not at all, it won’t damage the chip. My 38 pin ESP32 says “Node MCU ESP-32S”, my 30 pin has no markings but worked with “DOIT ESP32 DEVKIT V1”. When I tried it with “NodeMCU-32S” it sometimes worked very slowly and sometimes not at all. The ESP32 is static sensitive but the easiest way to kill it is to attach, or disconnect, something to it while it is powered on so always disconnect power before adding, or taking away, anything to the circuit. Espressif, the makers of the chips, have excellent documentation at https://docs.espressif.com/projects/arduino-esp32/en/latest/getting_started.html

If you are new to the Arduino, this project might seem rather daunting but stick with it because, once completed, you will be able to take advantage of the many other projects available on the internet. This step will show how to upload a program, or as the Arduino calls it a sketch, add your ESP32 to the IDE and upload a sketch, and add libraries. Libraries are extremely useful collections of routines that other people have written. As an example, the Network Time Protocol, NTP, library that we will use allows acquiring the date and time with 1 line of code. I’m not going to delve too deeply into the actual programming because that isn’t necessary. The sketches have all been tested so all you need to do is upload them. I will, however, explain some bits of the more obscure notation at the end of this article. My intention is not to teach you how to program, but how to use the Arduino with a microprocessor. The programs are functional but experts will find fault because I haven’t used best programming practice. One aspect of best practice is a minimum of global variables, variables that can be seen anywhere in the sketch, and instead passing variables between functions. If this series of articles whets your appetite you will find lots of information, and free books, on the internet that will teach you how to program. The programs in my GitHub repository only require you to change a few entries in each program to get them working. For most of the sketches you only need to enter your Wi-Fi ssid and password. Thanks to Arduino it’s now easier to develop software and transfer it to microprocessors. We can connect other hardware to the microprocessor to do useful things, like switch things on and off, or display readings from probes, temperature and humidity for example. The Arduino doesn’t only connect to Arduino boards, it can connect to other manufacturers boards as well. By the end of this section, you will be able to load the source code, a program written in “C/C++”, onto an Espressif ESP32 device and get the date and time from a network time protocol, NTP, server. Now you’re probably wondering what this has to do with Radio. Well, if you’re a licenced radio amateur, and stick to the end, you will have a single band WSPR transmitter that will cost you less than £50 for all the hardware and software. WSPR needs an accurate time source and that’s where the NTP server comes in. Be aware, however, that due to latency on your network, the time reported may be slightly out. It should be out by less than a second though and that’s fine for WSPR. I’ll start by programming a bare ESP32 and then add parts one by one until the WSPR transmitter is complete. By doing it this way, we can test each module and if it doesn’t produce the required result, you only need to check the last bit that you added. I’ll show you how to get the Arduino IDE up and running and then use that to upload a sketch, the “C/C++” code, to an ESP32 microprocessor. If you haven’t used the Arduino integrated development environment, IDE, before it can be quite a steep learning curve. The WSPR version I currently run is a multiband version that adds switching low pass filters for extra bands and different bands before and after sunset. The number of bands is only limited to the number of low pass filters available. You won’t need to learn how to code, just how to download the code from my GitHub repository, https://github.com/mm5agm , and upload it to the ESP32. Construction is easy because the devices only need power, supplied by the ESP32 from a phone charger, not the best filtered I know, and another 2 wires connected to the I2C bus. The I2C bus can be thought off as a road with houses. Just as each house has a different house number, devices on the I2C bus have different addresses associated with them. The ESP32 sends and receives individual instructions to each device via this bus.

Download the Arduino IDE

The first thing we need to do is download the Arduino IDE. You can do this from https://www.arduino.cc/en/software You will be asked if you want to contribute a sum of money for development costs but you can download it for free. My computer’s operating system is Windows 11, so I downloaded “Windows Win 10 and newer, 64 bits”. I don’t have a Linux or macOS computer so, if you have, you may need to alter some of the instructions in this article. Once you’ve downloaded the program, run it and just select all the defaults. You should end up with this screen

Fig 2 Arduino_Installed

Tick “Run Arduino IDE” and click Finish. This will give you the Arduino IDE screen.

Setup the Arduino IDE

The IDE will probably install some default Libraries.

Fig 3 Arduino_First_Run

At the top of the Arduino screen, you will see the sketch name you’re working on. In my case sketch_jan6a. The next line shows the top-level menu items, “File, Edit, Sketch, Tools, and Help”. Under each of these options there are other menus. The next line shows a tick, an arrow, a weird symbol, a box showing what device is connected and at the far right, a shortcut to the serial plotter and a shortcut to the serial monitor. The tick means get the compiler, a program inside the IDE that produces machine code from the text sketch, to check the sketch is ok and will produce machine code for the ESP32. The arrow is to compile and upload the sketch to your device, the serial plotter creates a graph of what’s arriving at the serial port and the serial monitor allows you to see what information is coming back from your device. You can use the serial monitor to print the values of variables as your sketch runs. So as not to fill this article with screenshots I’ll use the convention “Top Level Menu/Sub Menu1/Sub Menu2” to tell you where to go. As an example, to get to the “Blink” example mentioned later, I would use “File/Examples/01.Basics/Blink”. You will notice that, at the bottom right of the screen it says “No board selected”. We need to tell the IDE what board we are using and what serial communication port it is on. Move your mouse pointer out of the IDE and open windows “Device Manager” by right clicking the windows start symbol at the bottom left of the windows toolbar and selecting it. Plug your ESP32 into a USB port. In Device Manager expand the “Ports (Com and LPT)”

Fig 4 Device_Manager

You can see that my ESP32 is connected to COM3. If you have lots of devices and can’t work out what port your ESP32 is connected to, just unplug it, one of the ports will disappear, and then plug it in again. Device Manager will rescan the ports and put it back in. Now go back to the Arduino IDE and open the “Preferences” option “File/Preferences”

Fig 5 Preferences

Fill in the fields for Font size etc. Take a note of where the Sketchbook location is because any libraries you add will be in Sketchbook location/libraries. You will see that I have selected the “Dark” theme. That’s the one I prefer because it gives me a better contrast for the text. To get the ESP32 microprocessor boards into the IDE, you need to fill in the “Additional boards manager URLs” with

https://espressif.github.io/arduino-esp32/package_esp32_index.json

Compiler warnings can generate a lot that we’re not interested in so just set it to “Default”. If you set warnings to “More” or “All” the compiler will also report what it thinks are errors from the libraries and may not compile. Click “OK”. Then “File/Save” then “File/Quit”. The next time you start the IDE it will allow you to add the ESP32.

Installing the ESP32 into the Arduino IDE

Restart the Arduino IDE and select “Tools/Boards Manager”. In the “Filter your search” box, put ESP32. The next screenshot shows the screen before filling in the search box. I put it in because it can be quite difficult to see where the search box is, it’s the bit that says “Filter your sketch”.

Fig 6 Filter_Boards

Put ESP32 in the search box as below and select “Install” for the option “esp32 by Espressif Systems”

Fig 7 ESP32_Board_Espressif

The “Output” box at the bottom of the IDE will show that packages are being downloaded

Fig 8 Output_ESP32_Install

You will know that the boards have been installed when “INSTALL” changes to “REMOVE”. Now that we have the boards installed, we can go to “Select Board” and click the small down arrow. At “Select other board and port…” in the search box I typed “node” as I know my ESP32 is a NodeMCU-32S, and selected “NodeMCU-32S” in the drop-down list. Tick your ESP32 and tick the serial port it is on. If you have a "ESP32 DEVKIT1" select "DOIT ESP32 DEVKIT V1".

Fig 9 Select_Board

Save and close the Arduino IDE. You will be glad to know that your setup is complete.

Initial Test of Sketch and ESP32

Open the Arduino IDE and go to “File/Examples/01 Basic/Blink” and open it. This sketch blinks the built in ESP32 led once a second. To get it onto the ESP32 we need to compile it and upload. To do that we’ll use the upload button, the right pointing arrow. The upload button both compiles and uploads. At the start of the sketch, you will see “/”. This tells the compiler to ignore everything until it gets to the symbols “/”. It’s a handy way to put lines of comments in. Another way to put comments in is to put “//” in a line and the compiler will ignore everything else on that line only.

Open the serial monitor “Tools/Serial Monitor” so you can get feedback from your ESP32. Go to the extreme right of the Serial Monitor portion and check that the baud rate selected is 9600, the same as in the sketch. Click the upload button, the right pointing arrow, and when you see “Connecting....” in the output monitor, press and hold the “Boot” button, for a second or two. The “Boot” button is on the right-hand side of the USB port, when the USB port is at the bottom. This operation can be a bit pernicety, sometimes you don’t need to press boot and sometimes it takes a couple of presses. If the blink program fails to compile with the error :-  

Compilation error: 'LED_BUILTIN' was not declared in this scope

Add the following before void setup(){

#ifndef LED_BUILTIN

#define LED_BUILTIN 2

#endif

If there is an LED on pin 2 it should blink. If there isn't an LED on your ESP32 nothing will happpen.

Fig 10 Upload

If you get

Connecting......................................

A fatal error occurred: Failed to connect to ESP32:

Wrong boot mode detected (0x13)! The chip needs to be in download mode.

For troubleshooting steps visit:

https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html

Failed uploading: uploading error: exit status 2

Then check your ESP32 is on the serial port you have selected. Keep trying till it loads. If your ESP32 built in led blinks you’ve proved the setup is ok. If it doesn’t you’ve probably got an ESP32 with no built in led. I have one of these. You’ll find out if you’re ESP32 is connected correctly when you load the next sketch.

Anatomy of a Sketch

When you write a new sketch the IDE gives you a standard template. void setup(){

// put your setup code here, to run once:

void loop(){

// put your main code here, to run repeatedly:

} Void means that this function doesn’t send anything back to the function that called it.

Function code starts at the “{“ and ends at the “}”.

The “setup” function only runs once when your sketch starts and is used to tell the compiler what input/output pins you’re going to use, maybe to initialise some devices, connect to the internet for example.

The “loop ()” function runs continuously and when it gets to the end of the loop it starts again.

You don’t want any function to be large as it gets difficult to maintain so the trick is to break it into smaller logical units.

The setup() in my multiband version has initialiseGPIOpins(); initialiseDisplay(); initialiseWiFi(); initialiseRTC(); initialseSun();

all functions that do 1 thing only and this saves setup() from being huge. Using functions means you only need to write code once. You just call the function when needed. A function can return nothing, void, or a result. Now these microprocessors run very fast, the ESP32 can be set to run at 240MHz, which means 240 million clock cycles a second, so sometimes you need to put a delay to slow things down so you can read the serial monitor. “Delay(1000)” will stop the process for 1000ms, 1 second. The Arduino only needs 1 clock cycle to add two small whole numbers, designated integers, but can take several hundred to add two big numbers with decimal points, designated a float. Listed above “setup()” is where we declare what libraries we are going to use and any global variables. A global variable can be seen at any part of the sketch so it’s handy in some respects but not in others. If you want a counter inside a function, it’s best to declare it there. That way the counter is only seen inside the function and it’s discarded when you leave the function. A common counter to use is “int i”. This is a throwback to mathematicians who used i and j to denote rows and columns in an array. Because it’s used all through the program you may think it should be global, but if it was, it would hold its value so if you have a function that uses it and changes its value, and it calls another function that uses it, you’d need to be extremely careful as to what its value it has when it moves between functions. Far easier to make it local so that when the other function uses it, we’re sure of its starting value.

Download my GitHub sketches

Let’s get started on a program, or in Arduino language, a sketch, that we can build on to get a working WSPR transmitter. Go to my repository https://github.com/mm5agm/WSPR and download the code as a zip file.

Fig 11 GitHub MM5AGM download

Click on the <> Code and select “Download ZIP” and save as WSPR_Examples.zip in your sketches folder. Unzip this into your sketches folder, it will call the folder WSPR-master so rename that folder WSPR_Examples. Inside WSPR_Examples you will get the License agreement for use of the code as well as the programs/sketches. I’ve used GPL V3.0 license which basically means that it’s free to use but if you use any of the code in a project that project must also be under the GPL. This "copyleft" approach guarantees that the software, and any derivatives of it, remain free for all users. It’s a way to ensure that the community benefits from improvements and that projects remain a collective endeavour. I also say that the programs come without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. This is so I can’t be sued no matter what happens. Open up the Arduino IDE and go to “File/Open” and navigate to where you unzipped WSPR_Examples and select “WSPR_Examples/ NTP_Basic /NTP_Basic.ino” If you get this message when you open it

Fig 12 Moving

Just click on OK. When you’re back at the IDE, select your board and port as before. Now before we attempt to compile the sketch, we need to download an NTP library because there are functions that are in that library that the sketch needs to access. The one I use works with the ESP32, they don’t all, so in your browser, go to https://github.com/SensorsIot/NTPtimeESP and download the zip file just as you did in my GitHub repository. Rename it NTPtimeESP.zip, in other words get rid of the -master. I always edit the folder names to get rid of the -master. Don’t unzip it, the IDE wants an actual zip file. You can put it anywhere, just remember where it is. In the IDE open the menu “Sketch/Include Library/Add .zip library” , it’s second top, and navigate to the folder where you saved the download. Select the zip file and click open. You will get a message in the IDE Output saying “Library installed”. If you go to you sketches folder, you will see a new folder called “libraries” and inside that there will be a folder called “NTPtimeESP”. If it has -master and like me you think that’s untidy, you can rename it without the -master. Now, in the IDE, navigate to where you unzipped WSPR_Examples and select “WSPR_Examples/NTP_Basic/ NTP_Basic.ino”, upload “NTP_Basic.ino” to your ESP32 the same way you uploaded the “Blink” sketch, right arrow symbol and hold the “boot” switch down momentarily. Click on “Tools” “Serial Monitor” so you will be able to see what’s happening. If the serial monitor displays gobbledygook, you’ve probably got it set to the wrong baud rate, set it to what’s used in the sketch, 9600. In the serial monitor you will see that your ESP32 keeps attempting to connect to your Wi-Fi but gives up. The message eventually says

Attempts to connect = 61

attempts = 61

Are you sure you have the correct ssid and password?

If you didn’t get a chance to see the message press the “En”, enable key on the left-hand side of the USB connector and your ESP32 will restart. Now go into the code and change the ssid and password to yours. Keep the quote marks around them. Now when you upload the sketch it should run and you should get something like

Connecting to Wi-Fi

Using ssid “Your SSID” Password “Your Password”

Attempts to connect = 1

Attempts to connect = 2

  • Connected to: Your SSID
  • IP address: 192.168.0.79

8/1/2024 16:33:56

8/1/2024 16:34:9

If you’re seeing the correct time and date everything is working. Note that there are no leading zeros and the time only updates every 10 seconds. Attempts to connect will give you an indication of how easy it is to connect to your router/hub. You’ve now connected to the internet, you’ve queried an NTP server, and you’ve printed the date and time. If it didn’t work for you maybe you’re in a dead spot for your home Wi-Fi. If your home Wi-Fi is OK, have a look at the error messages. Typical faults are wrong SSID, wrong Password – the sketch will tell you, or using an NTP server that doesn’t exist. If you’re in the UK check that you have the line

const char* NTP_Server = "uk.pool.ntp.org"; // pick an ntp server in your area

char* means this is a pointer – it points to a memory location. NTP_Server is a variable and it’s equal to "uk.pool.ntp.org” and “//” tells the compiler to ignore what’s after “//” as it’s just a comment.”

If you’re not in the UK go to https://timetoolsltd.com/information/public-ntp-server/ and you will find a server near you.

Add Real Time Clock

The RTC is a DS3231 available from many outlets. First off, make up your circuit as in the following diagram: -

Fig 13 ESP32_RTC

Red = +ve 3.3V Black = ground -ve Green = GPIO21, SDA and Yellow = GPIO22, SCL

You don’t need the battery in the RTC, in fact there’s some debate on the internet as to which battery you should use. Some RTCs are supplied with a CR2032, non rechargeable, and some with an LR44, a rechargeable battery. Some of the RTCs have no charging circuit, some a 3.3v, and some a 5V. Mine came with a rechargeable battery but I decided not to use it because if the power fails to my circuit it doesn’t matter, the RTC will reset to the correct time when the power is re-connected. SDA is the data line and SCL the clock line. SCL is not the time, microprocessors do things in a uniform manner and at precise position on a square wave signal. The square wave signal is the SCL line.

Open the Arduino IDE and load the sketch “WSPR_Examples/I2C_Scanner/I2C_Scanner.ino”. This program scans all the addresses on the I2C bus and reports the number of devices it finds and their address. The address is a hexadecimal number. Now we humans use the decimal system 0,1,2,3 etc but computers use, as well as decimal (base 10), binary (base 2) 0,01,10,11,100 (0,1,2,3,4 decimal), octal (base 8) 0,1,2,3,4,5,6,7,10,11 (0,1,2,3,4,5,6,7,8,9 decimal) and hexadecimal 0,1,2,3,4,5,6,7,8,9,A,B,C (0,1,2,3,4,5,6,7,8,9,10,11,12). Hexadecimal 10, represented as 10x = 16 decimal. The reason for these different number systems is that it’s easier to manipulate individual bits of registers in the microprocessor when we use these notations. Just to complicate things further, as well as the number system we also have signed and unsigned, and different size allocations for different number types. This is important because the compiler needs to know how much memory to allocate for each of the variables in the program. There’s lots on the internet about this subject if you want to read more, suffice to say that my sketches use everything up to an unsigned long integer, 0 to 4,294,967,295. This is used for the frequency we want the si5351 to output, and since it has a resolution of 0.01Hz, we need really large numbers. 14Mz to the si5351 is 1,400,000,000.

When you upload the sketch and open the serial monitor your output could be something like this: -

Scanning...

I2C device found at address 0x57 !

I2C device found at address 0x68 !

Finished Scanning. Number of devices found = 2

Fresh scan starting in 5 seconds

Now you’re thinking, hold on, I only have 1 device plugged in. How come it shows 2? Well, the DS3231 real time clocks (RTC), also exposes the temperature probe inside the device that it uses for its TCXO, the temperature compensated crystal oscillator. If you get 2 addresses, the one you want is 0x68 for the clock, 0x57 is the temperature probe. Note that the temperature probe isn’t very accurate, it’s plus or minus 3⁰C. The library I use for the RTC actually knows the address for the clock, so you don’t need it. If you use another library, you probably will. Now that we’ve proved our circuit works, we’ll use an NTP server to update the RTC.

In the IDE go to “File/Open” navigate to “WSPR_Examples/NTP_Time_With_RTC/NTP_Time_With_RTC.ino”. When the sketch is in the IDE you will see that we have included another library, RTClib. We need to add this to our library list. This library is included with the Arduino so it’s just a case of loading it. No need to go to GitHub and download a zip. In the IDE go to “Tools/Manage Libraries ..” This will open the inbuilt library manager. In the search box type rtclib. This should bring up “RTClib by Adafruit” version 2.1.3, or later.

Click on install and a box will appear that informs you that this library has another dependency, “Adafruit BusIO” and asking if you also want to install this, we do, so click on “Install All”. The output box will show that the libraries are installed. Now navigate to where you unzipped WSPR_Examples and select “WSPR_Examples/NTP_Time_With_RTC/ NTP_Time_With_RTC.ino”. Change the ssid and password to yours and upload. The serial monitor should show the RTC being updated every 2 minutes, and the time from the RTC every second with that slight delay.

There can be a problem when using external libraries and that is they may define the same thing differently. This is the case with the NTP and RTC libraries I’ve used. They each use their own structures for the date and time and internally define them differently. The NTP library has days Sunday to Saturday numbered 1 to 7 whereas the RTC library has the days numbered 0 to 6. Before I spotted this, weekDays[6] was reported as “Sun” by RTC but as my password by NTP. That’s because weekDays[7], Sunday in NTP, overflows the array and the next memory location just happened to be my password.

Adding the OLED

I included the OLED because I wanted the WSPR transmitter to be stand alone. When it’s stand alone you don’t have access to the serial monitor in the IDE so can’t see what time it thinks it is etc. Some of these OLEDs have yellow writing for the top of the screen. You don’t want one of these for my program. You want one that has white text all over. If you inadvertently get the yellow topped one, you will need to change the mainScreen() function to stop the second line being squashed up.

Disconnect the ESP32 from power and add your OLED to the breadboard and connect it to the power lines and piggy back the SCL and SDA lines from your RTC. Power the ESP32 and load “WSPR_Examples/I2C_Scanner/I2C_Scanner.ino” and you should find that there are now 3 devices. Make a note of the addresses. You will remember the ones you had before so the extra one will be the OLED.

In the IDE open “WSPR_Examples/NTP_Time_RTC_OLED/NTP_Time_RTC_OLED.ino”.

You’ll see that we have included another 2 libraries, “Adafruit_SSD1306” and “Adafruit_GFX”. These are libraries known to Arduino so you add them using its inbuilt library manager the same way you added the RTC library. In the IDE go to “Tools/Manage Libraries ..” and open the inbuilt library manager. In the search box type ssd1306. This will give you a choice, scroll down to Adafruit SSD 1306 and click “Install”. You will get a popup telling you there are dependencies, select “Install All”. This will also install the Adafruit_GFX_Library. One thing I find annoying with this OLED library is that when you write to the display, with for example display.println("Something”), it doesn’t appear on the screen until the next time the instruction display.display() is sent to it. After adding the libraries upload your sketch to the ESP32. The OLED should show your ssid on line 1, IP address on line 2 and the time on line 3. Remember, the time displayed will be slightly behind real time because of the 1 second delay in loop(). I’ll get rid of that in the final WSPR program.

Add the si5351 square wave generator

Adding the si5351 is similar to adding the RTC. Disconnect the ESP32 from power and piggyback the SDA and SDC lines from the RTC or the OLED and connect VCC and ground. Power the ESP32 and open and load the sketch “WSPR_Examples/I2C_Scanner/I2C_Scanner.ino” to your ESP32. If you’ve wired the si5251 correctly you should see another device, at address 0x60. The si5351 has a crystal oscillator, either 25 or 27MHz with 25MHz being the most common. On my si5351 the board says “25MHz xtal” to the left of the crystal. It’s not temperature controlled so you do get some drift. We need a library to control the si5351 and the one I use is si5351 by Jason Mildrum and Paul Warren. If you don’t have a 25MHz crystal you’ll need to read Jason’s notes on how to initialise it. This library is known to Arduino so add it using its inbuilt library manager the same way you added the RTC library. In the IDE go to “Tools/Manage Libraries. .” and open the inbuilt library manager. In the search box type si5351. Select and Install “Etherkit Si5351” by Jason Milldrum. You need an 8 digit frequency counter to do the calibration but you can use a receiver if you don’t have access to one. I used my receiver and the excellent WSJT software from Professor Joe Taylor et al. If you don’t have WSJT, download it from “https://sourceforge.net/projects/wsjt/” the instructions for the programs are at https://wsjt.sourceforge.io/wsjtx.html . There is a calibration sketch in the “Examples” section of this library as well as sketches that explain how to use the library. Upload “Files/Examples/Etherkit Si5351/si5351_calibration” to your ESP32. As I was using the WSPR program in WSJT I modified this sketch very slightly to use the 30Mtr WSPR frequency. The si5351 frequencies are indicated in units of 0.01 Hz so if you are doing the same, go to line 32 and change target_freq from 1000000000ULL to 10140200000ULL. You can change the target_freq to anything you want but it alters the amount of deviation the program adds during the calibration process. For example, if you calibrate at the 28MHz frequency and input “k” then the enter key, your frequency will go down not 100Hz but nearly 300. The calibration factor the program works out will be the same however. In the serial monitor you should see the instructions for the sketch, if you don’t, press the enable button on you ESP32 or unplug and plug it back into the USB port.

Set up WSJT-X

If you already have WSJT-X working, for FT8 say, there’s nothing you need to do apart from set up the waterfall. Open WSJT-X and select mode WSPR. If you haven’t set it up before, go to “File/Settings/Radio/Rig” in WSJT-X and set it to “None”. If you don’t do that, you will need to set up cat control. Now go to “File/Settings/Audio/Soundcard” and tell it what input device to use. Available devices will be in the dropdown lists. Next go to “View” and select “Waterfall”. In the waterfall display, set “Bins/Pixel” to 2 and “Start” at 100Hz. Leave everything else as is. The si5351 has, as well as the SMA connector, an output on its board marked “0” for clock 0. You will see others marked 1 and 2 for clock1 and clock2. I use clock 0. If you stick a short bit of wire, 6 inches or so, into the breadboard at “0” it will act as an aerial but you might get a lot of harmonics. When the waterfall was checked I could see my transmission was 1633Hz so I changed the “Bins/Pixel” to 1 and “Start” to 1100. This gives a wider span for 1400 to 1600Hz, the area that’s decoded in WSPR. As well as my signal you can see some wavy lines between 1400 and 1600Hz. These are WSPR transmissions.

Fig 14 Calibrate

I needed to come down 133Hz. In the IDE, just under where it says “Output Serial Monitor x”, there’s a box that you can put the commands into. To adjust my signal to get to 1500Hz, I entered “k” in this box which brought me down to 1533 and then a few “j” down 10Hz and “h” down 1Hz. This gave a calibration factor of 13100. When your happy you’re at 1500 on the 30Mtr frequency enter “q” in the program and it will quit and tell you the calibration factor. Make a note of the calibration factor as it’s required in the final WSPR sketch. If you don’t see, or hear the tone on your receiver, you’ll need to hunt for it. Do this by changing your receiver frequency. You should eventually find it. Make a note if you had to go up or down in frequency and in the calibration sketch move in big steps until you see the trace when at the 30Mtr dial frequency. The calibration factor can vary considerably between si5351’s, I have 2 si5351’s and the factors are 13100 and 131800.

The Single Band WSPR sketch

Upload “WSPR_Examples/WSPR_Single_Band /WSPR_Single_Band.ino”
At the start of the sketch you will see #define DEBUG // Comment this line to supress debugging to the serial port

With this you will see lots of information about the initialisation of Wi-Fi, OLED, and real time clock in the serial monitor. You’ll also see the variables you’ve changed and the time. The time is slightly delayed in the serial monitor but you’ll know it is accurate by the fact that others are decoding your signal. If you change

#define DEBUG

to

// #define DEBUG

This will stop the information being printed out on the serial monitor.

We need 2 more libraries for this, the final sketch. We need “Time by Michael Margolis” and “Etherkit JTEncode by Jason Milldrum”. Both of these libraries are accessible through the IDE. So, as before, “Tools/Manage Libraries ..” and search for “jtencode” and “Time” and install them. Note that for this sketch I have changed the baud rate for the serial monitor to 115200 so you should do the same. There are some variables you will need to change. Change “callsign”, “locator” first 4 characters,” ssid”, “password”, and “cal_factor”. Leave the “randomChange” at zero, this is a random value that’s applied to the TX frequency each time you transmit and is used to avoid being on the same frequency as someone else all the time. Since everyone will change frequency at the next transmit time by a random amount it is more likely you’ll get a clear slot. Leaving it at 0 will allow you to see where others claim your transmit frequency is and will give a guide as to how well the calibration was done. Since you have no idea how accurate these other receivers are you can only use them as a guide. Once you’re happy that you are in band you can change “randomChange” from 0 to anything from 0 to 100. I’d recommend less than 80 just in case the drift on your si5351 is excessive. In saying that, if it is excessive, no one will decode you. The writers of WSPR recommend transmitting for 20% of the time and as there are 30 timeslots an hour, we’re looking to transmit in 6 slots, in other words, with 10 minutes between transmissions. Some slot numbers are not possible. For example, to get 12 slots you would need to have 5 minutes between transmissions, and since that leads to transmission starting on an odd minute, it can’t be used. My sketch only allows valid transmit times. There are 2 sets of periods you can transmit in in each hour. These start 0,4,8,12 etc minutes and 2,6,10,14 etc minutes. In the sketch you will see a detailed analysis of this showing that, if you go with the recommended transmitting 20% of the time, 6 slots, you will have 3 transmits in each period which is ideal. Change “DST_OFFSET” and “TIME_ZONE” to yours if you want to display local time instead of UTC. If you’re not in UK change “NTP_Server” to one nearer to you. Finally, uncomment the frequency you want to use, making sure that only 1 frequency is uncommented. I used WSJT-X on WSPR to check my frequency over a period of a day as I know my receiver is accurate. I saw how much my frequency varied and took it from there. The following table shows how others reported my frequency with randomChange = 0

Call MHz SNR Drift Pwr Reporter RGrid km # Spots M5AGM 18.106117 -17 -3 0.01 LX1DQ JN39cq 968 9 M5AGM 18.106037 -24 -3 0.01 F1EYG JN18ar 897 3 M5AGM 18.106110 -26 -3 0.01 DL0LU N49cm 078 4 M5AGM 18.106138 -24 -3 0.01 PE0MJX JO31ai 826 2 M5AGM 18.106101 -25 -4 0.01 DC5AL-R JO31lk 868 1

Programming Notes A Boolean variable is either true or false. In the “c” programming language false = 0, and true is any other value, so -24 and 6 would both be true.

“X=5” means set x to 5. “X==5” means does x = 5?.

“number++” means use the number then increase it by 1. “++number” means increase the number by 1 then use it. “number- -” and “- -number” do the same but subtract 1.

“! =” not equal

“<” less than

“>” greater than

“<=” is less than or equal to

“>=” is greater than or equal to.

The Modulus operator,” %” returns the remainder after the division, for example “10%6” will return 4. This is handy for things like finding if you are at an even minute to transmit WSPR. To test if a number is “even” you would use “minute%2 == 0”.

Boolean operators.

“&&” is logical AND

“||” is logical OR “!” is logical NOT and results in true if the operand is false and vice versa. So “!true” = false.

There are lots of different looping structures, “for” “while” etc, and information on how to use these can easily be found on the internet. A gotcha that catches many programmers is testing for equality with a floating point number, a number with a decimal point, called a float in programming languages. Because microprocessors hold number values in multiple continuous memory locations, a float is nearly always an approximation to the real value. Floating-point numbers can be positive or negative and can be huge. They are stored as 32 bits (4 bytes) of information in the Arduino. Now because they are an approximation, you may have worked out that your answer = 3.4, but the Arduino may have stored it as 3.4000000000001. Now to a computer, these numbers are not the same so testing as equal will return false. In this example you would need to use something like

“(x<3.4001) && (x>3.3999)” assuming you’re happy with 4 decimal places.

I hope this project will encourage you to go further with Arduino. I can be contacted at mm5agm@outlook.com