Tuesday, 28 April 2015

WSPR symbol generator

WSPR requires a set of 162 symbols 0-3, each representing one of four frequencies of transmission - see previous post. The encoding take place in four steps. Encode the call, encode the locator, this builds the first 81 bit word. This is then interleaved to generate a 162 bit output which is combined with the sync vector to produce the final output symbols 0-3.

Until now the way most people have generated the symbol table was to use a Windows program WSPR.exe. There seems to be no Mac version of this, and anyway I wanted to do it on the Arduino.

So here it is. To use it edit the sketch code to put in your person call, locator and TX power (in dBm - must end with 0, 3 or 7). In this example my call is M6KWH, my locator IO92 and my TX power 100mW or 20dBm. The call sign must be entered so that the third character is a number - so mine is "[SP]M6KWH". Upload the program, then open the IDE monitor window and hit RETURN, this will output the sync vector and symbols as below:

Screen Shot 2015 05 12 at 11 32 08

Cut and paste these into your WSPR transmit program (I am working on a WSPR TX program using my Universal_VFO design).

Code

// WSPR_symbol_generator input coded, output on monitor
// based on code from Martin Nawrath, Acedemy of Media Arts, Cologne

const char SyncVec[162] = {
  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
};

unsigned long n1;    // encoded callsign
unsigned long m1;    // encodes locator

byte c[11];                // encoded message
byte sym[170];             // symbol table 162
byte symt[170];            // symbol table temp

// put your data here
char call[] = " M6KWH";    // default values, 6 chars. 3rd numeric
char locator[] = "IO92";   // default value 4 chars
byte power = 20;           // default value 2 numberic

int ii,bb;

void setup()
{
  Serial.begin(9600);        // connect to the serial port
}

void loop()
{
  while(Serial.available() == 0);
  Serial.println("WSPR beacon");
  Serial.flush();
    
  encode_call();
  
  Serial.print("Call: ");
  Serial.print(call);
  Serial.print(" ");
//  Serial.print(n1,HEX);
  Serial.println(" ");

  encode_locator();
  
  Serial.print("Locator: ");
  Serial.print(locator);
  Serial.print(" ");
//  Serial.print(m1 << 2,HEX);
  Serial.println(" ");

//  for (bb=0;bb<=10;bb++)
//  {
//    Serial.print(c[bb],HEX);
//    Serial.print(",");
//  }
//  Serial.println("");
  
  encode_conv();

  Serial.println("");

  for (bb=0;bb<162 ;bb++)
  {
    Serial.print(symt[bb],DEC);
    Serial.print(",");
    if ( (bb+1) %32 == 0) Serial.println("");
  }
  Serial.println("");

  interleave_sync();

  for (bb=0;bb<162 ;bb++)
  {
    Serial.print(sym[bb],DEC);
    Serial.print(",");
    if ((bb+1) %32 == 0) Serial.println("");
  }
  Serial.println("");
  
  while(Serial.available() > 0) Serial.read();
}


// encode sequence
void encode() 
{
  encode_call();
  encode_locator();
  encode_conv();
  interleave_sync();
};

// normalize characters 0..9 A..Z Space in order 0..36
char chr_normf(char bc ) 
{
  char cc=36;
  
  if (bc >= '0' && bc <= '9') cc=bc-'0';
  if (bc >= 'A' && bc <= 'Z') cc=bc-'A'+10;
  if (bc == ' ' ) cc=36;

  return(cc);
}

// encode call sign
void encode_call()
{
  unsigned long t1;

  n1=chr_normf(call[0]);
  n1=n1*36+chr_normf(call[1]);
  n1=n1*10+chr_normf(call[2]);
  n1=n1*27+chr_normf(call[3])-10;
  n1=n1*27+chr_normf(call[4])-10;
  n1=n1*27+chr_normf(call[5])-10;

  // merge coded callsign into message array c[]
  t1=n1;
  c[0]= t1 >> 20;
  t1=n1;
  c[1]= t1 >> 12;
  t1=n1;
  c[2]= t1 >> 4;
  t1=n1;
  c[3]= t1 << 4;
}

// encode locator
void encode_locator()
{
  unsigned long t1;
  
  // coding of locator
  m1=179-10*(chr_normf(locator[0])-10)-chr_normf(locator[2]);
  m1=m1*180+10*(chr_normf(locator[1])-10)+chr_normf(locator[3]);
  m1=m1*128+power+64;

  // merge coded locator and power into message array c[]
  t1=m1;
  c[3]= c[3] + ( 0x0f & t1 >> 18);
  t1=m1;
  c[4]= t1 >> 10;
  t1=m1;
  c[5]= t1 >> 2;
  t1=m1;
  c[6]= t1 << 6;
}

void encode_conv()
{
  int bc=0;
  int cnt=0;
  int cc;
  unsigned long sh1=0;

  cc=c[0];

  for (int i=0; i < 81;i++) 
  {
    if (i % 8 == 0 ) 
    {
      cc=c[bc];
      bc++;
    }
    if (cc & 0x80) sh1=sh1 | 1;

    symt[cnt++]=parity(sh1 & 0xF2D05351);
    symt[cnt++]=parity(sh1 & 0xE4613C47);

    cc=cc << 1;
    sh1=sh1 << 1;
  }
}

// calculate parity
byte parity(unsigned long li)
{
  byte po = 0;
  while(li != 0)
  {
    po++;
    li&= (li-1);
  }
  return (po & 1);
}

// interleave reorder the 162 data bits and and merge table with the sync vector
void interleave_sync()
{
  int ii,ij,b2,bis,ip;
  ip=0;

  for (ii=0;ii<=255;ii++) 
  {
    bis=1;
    ij=0;
    
    for (b2=0;b2 < 8 ;b2++) 
    {
      if (ii & bis) ij= ij | (0x80 >> b2);
      bis=bis << 1;
    }
    
    if (ij < 162 ) 
    {
      sym[ij]= SyncVec[ij] +2*symt[ip];
      ip++;
    }
  }
}

Tuesday, 21 April 2015

More serious look at QRSS & WSPR

QRSS is a slow morse sending method, used at very low powers and narrow bandwidths. WSPR is a special signal coding carrying data, which is received and decoded by distant stations and give an idea of your propagation.

QRSS

To send QRSS you need a simple CW transmitter. For example the Universal VFO and the PA shields I have described for the Arduino UNO. The PA shield may be is a bit overkill and a lower output maybe should be used. An interesting design is a PA using a 74HC240 with out of phase signals fed to it from the VFO and the outputs connected to a balanced 1:4 transformer and LPF.

Screen Shot 2015 04 27 at 13 11 38

QRSS can be sent in basically three ways:

1 As simple CW, using dot = 3sec + 1 sec space, dash = 9sec + 1 sec space, word space = 2sec (1 space from each char + 2 extra = 3sec)

2 As frequency shift keying, with the same timing as #1 but on two frequencies, known as FSKCW. The frequencies are typically 2-5Hz apart, with the dot or dash being the higher frequency.

3 As two frequencies representing dot and dash, with a slightly longer gap for character spacing. The dot & dash are the same length. This is known as DFCW.

For example the letter 'C':

Screen Shot 2015 04 29 at 13 01 48

Different speeds have been adopted, but the most common is the one above called QRSS3, FSKCW3 or DFCW3. A transmitter must have better than 5Hz stability.

Reception is made by feeding the output of the RX audio (which can be from a simple direct conversion RX or an SDR RX) into your computer, and using an audio spectrum FFT display to show the frequency of the received audio.

Most HF QRSS activity is on 30m at 10140kHz.

Typical messages

Typical messages are very short, and replies are usually on a slightly different frequency to transmissions, for example:

CQ M6KWH K - M6KWH calling CQ

M6KWH ON7YD K - ON7YD replies

YD XDV 000 K - ON7YD call truncated, received, send report

XVD YD TU 73 K - received ON7YD back to you

YD XVD CL 73 SK - ON7YD received clear 73



WSPR

WSPR transmissions are a lot more complicated. As the message is encoded is a series of symbols/tones, each of which is transmitted on one of 4 frequencies (4FSK). For 30m the nominal "Dial freq" of the receiver is set to 10138.7kHZ USB. This transmits a WSPR message at 10140.2kHz, USB.

Screen Shot 2015 05 13 at 17 10 31

The symbols are transmitted as one of 4 tones like this

Screen Shot 2015 05 13 at 17 10 38

Where tones 0-3 are used for encoding the 4 tone FSK messages. The tone spacing is 1.46Hz (officially 1.465Hz but my DDS cannot resolve the last digit!). This uses a BW of just 6Hz and is transmitted at 1.4648baud, for the whole message the transmission time is 110.6sec. Transmissions are synchronised to start within 0-1 sec at 2 minute intervals based on UTC, that is for example at 00:00-00:01, 00:20-00:21 etc.

Messages

The WSPR message sent is a standard format which must be adhered to

Call Sign - Locator - power (in dBm, must end in 0, 3 or 7)

E.g. _M6KWH_IO92_37, where "_" is a space character - the third character MUST be numeric so there's a space at the front for my callsign

Their is a very good program called WSPR available on the web which runs under all OSs. This program both decodes and generates audio WSPR signals, including generating the symbol data from your information and outputting a modulated audio signal.

Screen Shot 2015 05 13 at 17 12 01

Your SDR radio tuned to dial 10138.7kHz USB will look like this

Screen Shot 2015 04 20 at 16 22 22

The WSPR program will display a narrow band of audio input from 100 - 300Hz around 1500Hz The display will look like this

Screen Shot 2015 04 20 at 16 22 59

It will read the signals being sent in the correct time slots (your PC clock must be accurate!) and decode them like this

Screen Shot 2015 04 20 at 16 23 07

the interesting parts which show the time (UTC), the frequency received, the call sign, locator and the power.

By uploading these received signals to a data base at WSPRnet you can plot them on a Google map and see either where you are receiving them from, or how far your signal is reaching.

Setup

There are two ways to set up a WSPR transmitter.

1 Use a program WSPR.exe to encode your symbols. Or use my Arduino based program described above, which tunes your transmitter with a sketch to shift the frequency of the DDS by the correct tone frequency.

2 Use an SDR TX shield and modulate your transmission with the audio output of the PC based WSPR program audio signals.

I will be investigating both or these methods and the transmission of QRSS in the future.