During my recent dabble with a WSPR Beacon on the Arduino, I created a C++ class to create the WSPR symbols given the callsign, location and power. The class is suitable for use on all platforms including an Arduino which is just as well, as that is what I wanted it for.
Thanks go to Mark VandeWettering (K6HX) for his work and to Andy Talbot (G4INT) for his help in unraveling the WSPR specification. Most of Andy’s work is reproduced in the code comments.
The code is presented as a C++ class that can be added to any C++ project or simply dropped into an Arduino sketch as a new ‘tab’. It is somewhat over commented but the aim is to try and clarify the WSPR encoding process in the context of a working example.
Error checking is sparse, therefore, please make sure that the callsign location and power are of the appropriate form.
WsprMessage.h
#ifndef WSPR_MESSAGE_H_
#define WSPR_MESSAGE_H_
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <arduino.h>
#define MSG_SIZE 162
class WsprMessage {
private:
int getCharValue(unsigned char ch);
int calculateParity(uint32_t ch);
unsigned char reverseAddress(unsigned char &reverseAddressIndex);
unsigned char reverseBits(unsigned char b);
void generateSymbols(char * callsign, char * location, int power);
public:
WsprMessage(char * callsign, char * location, int power);
unsigned char * symbols;
int size = MSG_SIZE;
};
#endif
WsprMessage.cpp
#include "WsprMessage.h"
const unsigned char sync[] PROGMEM = {
1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0,
1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1,
0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1,
1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0
};
WsprMessage::WsprMessage(char * callsign, char * location, int power) {
generateSymbols(callsign, location, power);
}
void WsprMessage::generateSymbols(char * callsign, char * location, int power) {
symbols = new unsigned char[MSG_SIZE];
char call[6];
for (int i = 0; i < 6; i++) call[i] = ' ';
if (isdigit(callsign[1])) {
for (int i = 0; i < (int)strlen(callsign); i++)
call[1 + i] = callsign[i];
}
else if (isdigit(callsign[2])) {
for (int i = 0; i < (int)strlen(callsign); i++)
call[i] = callsign[i];
}
uint32_t N = getCharValue(call[0]);
N *= 36; N += getCharValue(call[1]);
N *= 10; N += getCharValue(call[2]);
N *= 27; N += getCharValue(call[3]) - 10;
N *= 27; N += getCharValue(call[4]) - 10;
N *= 27; N += getCharValue(call[5]) - 10;
uint32_t M1 = (179 - 10 * (location[0] - 'A') - (location[2] - '0')) * 180
+ (10 * (location[1] - 'A')) + (location[3] - '0');
uint32_t M = M1 * 128 + power + 64;
int i;
uint32_t reg = 0;
unsigned char reverseAddressIndex = 0;
for (i = 0; i < MSG_SIZE; i++) {
symbols[i] = pgm_read_byte(sync + i);
}
for (i = 27; i >= 0; i--) {
reg <<= 1;
if (N & ((uint32_t)1 << i)) reg |= 1;
symbols[reverseAddress(reverseAddressIndex)] += 2 * calculateParity(reg & 0xf2d05351L);
symbols[reverseAddress(reverseAddressIndex)] += 2 * calculateParity(reg & 0xe4613c47L);
}
for (i = 21; i >= 0; i--) {
reg <<= 1;
if (M & ((uint32_t)1 << i)) reg |= 1;
symbols[reverseAddress(reverseAddressIndex)] += 2 * calculateParity(reg & 0xf2d05351L);
symbols[reverseAddress(reverseAddressIndex)] += 2 * calculateParity(reg & 0xe4613c47L);
}
for (i = 30; i >= 0; i--) {
reg <<= 1;
symbols[reverseAddress(reverseAddressIndex)] += 2 * calculateParity(reg & 0xf2d05351L);
symbols[reverseAddress(reverseAddressIndex)] += 2 * calculateParity(reg & 0xe4613c47L);
}
}
int WsprMessage::getCharValue(unsigned char ch)
{
if (isdigit(ch)) return ch - '0';
if (isalpha(ch)) return 10 + toupper(ch) - 'A';
if (ch == ' ') return 36;
return 0;
}
unsigned char WsprMessage::reverseAddress(unsigned char &reverseAddressIndex) {
unsigned char result = reverseBits(reverseAddressIndex++);
while (result > 161) {
result = reverseBits(reverseAddressIndex++);
}
return result;
}
unsigned char WsprMessage::reverseBits(unsigned char b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
int WsprMessage::calculateParity(uint32_t x)
{
int even = 0;
while (x) {
even = 1 - even;
x = x & (x - 1);
}
return even;
}