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++;
    }
  }
}

No comments: