Arduino AD9850 WSPR

Een WSPR TX met de Arduino en AD9850 maken.

Met de Arduino en de AD9850 DDS chip is eenvoudig een 'Weak Signal Propagation Reporter (WSPR) baken zender te maken.

De AD9850 DDS chip is zoals beschreven eenvoudig te besturen zoals op de pagina "Arduino AD9850 DDS". Deze chip kan natuurlijk ook voor andere doeleinden gebruikt worden. Denk hierbij aan het maken van een signaalgenerator, middenfrequent oscillator voor een ontvanger, enzovoort.

De AD9850 chip heeft een minimale stap grote van 0.0291 Hz. Met het genereren van een WSPR signaal moeten de draaggolf een shift hebben van  12000/8191 = 1,46484375 Hz. Dit is geen enkel probleem voor de AD9850 DDS chip.

Voldoen aan de eis 'De uitzending moet 1 seconde na de even minuut starten'  is met een stand-alone microprocessor wat lastiger. De kunt de software klok handmatig gelijk te zetten, maar deze zal na verloop van tijd toch gaan verlopen. De klok frequentie van de CPU zal toch een kleine afwijking hebben. Oplossingen hiervoor kunnen zijn:

  • Een nauwkeurige klok chip toevoegen.
  • Tijd regelmatig synchroniseren vanaf het internet via het Network Time Protocol (NTP).
  • Een GPS module toevoegen en hieruit de tijd uitlezen.

In mijn oplossing heb ik voor het toevoegen van een GPS chip gekozen. De reden hiervan is dat internet niet overal beschikbaar is en een externe klok chip zal af en toe toch ook gelijk gezet moeten worden.

De configuratie in dit geval bestaat uit:

  • Arduino microprocessor.
  • DDS chip AD9850
  • GPS chip 6MV2
  • Eventueel een eindtrapje.

De opstelling ziet er als volgt uit:

Arduino WSPR test opstelling

Onderaan de pagina staat de gehele listing van de WSPR code voor de Arduino. Deze kan zeker nog verder uitgebouwd worden. Eerst een toelichting op de belangrijkste stukjes code:

Het programma laad bij het starten de AltSoftSerial.h en de TinyGPS library. De AltSoftSerial library is nodig omdat de GPS module met 9600bps communiceert en de default Arduino serial poort geeft bij uitlezen soms buffer overflows wat verminking van de GPS data tot gevolg heeft. Heb je een GPS module die met 4800 Bps communiceert, dan heb je hier geen probleem mee.

De data die uitgezonden moet worden staat in een array opgeslagen. Dit is gegenereerd met het programma wspr wat beschikbaar in onder Linux en Windows (DOS box).

Bijvoorbeeld:  (Windows executable op Linux onder Wine emulator)

$ ./wspr.exe Tx 0 0.0015 0 PA3HFN JO21 17 11

Details van het wspr.exe programma zijn hier te vinden.

Om de data direct te formateren kun je het volgende commando in linux geven:

$ ./wspr.exe Tx 0 0.0015 PA3HFN JO21 17 11 | awk '{print $2}'| sed ':a;N;$!ba;s/\n/,/g'

 

WSPR data genereren

 

 

 

 

 

 

In het onderstaande stukje code wordt de DDS op 10,140100MHz ingesteld. Dit is het onderste gedeelte van het band segment wat voor digitale modes in de 30 meter  band is toegekend.

 unsigned long WSPR_TX = 10.140100e6;  // this is the bottom of the band.

Door hier een random getal bij op te tellen met een maximum upper limit zal elke uitzending op een iets andere frequentie binnen het WSPR bandsegment plaats vinden. Je kunt dan voorkomen dat als er al een station zit je deze niet continue interfereert.

 WSPR_TXF = (WSPR_TX+DDS_OSET) + random(0, 190);

Een eenvoudige uitbreiding zou kunnen zijn door meer toegewezen banden toe te voegen zodat elke 2 minuten de uitzending in een andere toegewezen band kan plaats vinden. Je moet dan wel een om schakelbaar band filter achter de eindtrap mee schakelen.

Voordat er gestart wordt met een uitzending wordt er eerst een check gedaan of de GPS module een correcte tijd afgeeft.

 if (gps.time.isValid()) {

Is dat het geval, dan wordt er gewacht tot er een 'even' minuut verschijnt en er 1 seconde na de volle minuut is gevonden.

 // Looking for a transmit window 
 if ((gps.time.minute() % 2 == 0) && (gps.time.second() == 1)) {  // start transmission

Als het transmit window bereikt is dan wordt de DDS gestart. De functie wsprTX() zorgt ervoor dat de 162 bits uit het array uitgezonden gaan worden. (element 0 - 161 in het array).

// Data Tx function  
void wsprTX() { 
int i = 0; 
for (i=0;i<162;i++) { 
     wsprTXtone( WSPR_DATA[i] ); 
     delay(683); 
  } 
dds.setFrequency(0); 
}

De functie wsprTXtone berekend de offset van de draaggolf en zet de DDS op de juiste frequentie.

// Set DDS frequency
void wsprTXtone(int t) { 
  if ((t >= 0) && (t <= 3) ) { 
      dds.setFrequency((WSPR_TXF + (t * 1.4648)));
  }

De delay(683)   (8192/12000 = 0.682666667) is afgerond naar boven.

Hieronder de code voor een eenvoudige WSPR zender.

// WSPR beacon transmitter 30 meter band , 10Mhz.
// Berto van Oorspronk
// PA3HFN
// Januari 2015
//
//DDS Library
// http://github.com/m0xpd/DDS
// include the DDS Library:
#include <DDS.h>

// Alternative Serial Library: (no buffer overflow @9600bps).
// http://github.com/mikalhart/TinyGPSPlus/releases
//#include <SoftwareSerial.h>
#include <AltSoftSerial.h>
#include <TinyGPS++.h>

//======================================
// AD9850 Module....

// Set pin numbers for DDS:
const int W_CLK = 2;
const int FQ_UD = 3;
const int DATA = 5;
const int RESET = 6;
double freq = 10000000;
// Instantiate the DDS...
DDS dds(W_CLK, FQ_UD, DATA, RESET);

// Set pin numbers for GPS:
// The serial connection to the GPS device, PIN 8 and 9
static const int RXPin = 8, TXPin = 9;
static const uint32_t GPSBaud = 9600;
// The TinyGPS++ object
TinyGPSPlus gps;
// The serial connection to the GPS device
AltSoftSerial ss(RXPin, TXPin);


// LED
const int LED = 13;

unsigned long WSPR_TXF = 0;
/*  DDS TX frequency in Hz
    Band Dial freq (MHz) Tx freq (MHz)
    160m 1.836600   1.838000 - 1.838200
    80m 3.592600    3.594000 - 3.594200
    60m 5.287200    5.288600 - 5.288800
    40m 7.038600    7.040000 - 7.040200
    30m 10.138700  10.140100 - 10.140300
    20m 14.095600  14.097000 - 14.097200
    17m 18.104600  18.106000 - 18.106200
    15m 21.094600  21.096000 - 21.096200
    12m 24.924600  24.926000 - 24.926200
    10m 28.124600  28.126000 - 28.126200
    6m 50.293000   50.294400 - 50.294600
    2m 144.488500 144.489900 - 144.490100
*/
unsigned long WSPR_TX = 10.140100e6;  // this is the bottom of the band.
                                      // The station moves up with WSPR_TX+DDS_OSET) + random(0, 190) .
// DDS Offset in Hz
const int DDS_OSET = 105;  //DDS #2

int year;
byte month, day, hour, minute, second, hundredths, Nsatellites, ret, duty;
int WSPR_DUTY = 3;// transmit every N slices.

int WSPR_DATA[] = {3,3,0,0,2,2,0,2,3,0,0,0,1,3,3,2,0,2,1,0,0,1,
                    0,1,1,3,3,0,0,2,2,2,2,0,1,2,0,1,0,1,0,0,2,2,
                    0,2,1,0,1,1,2,0,3,3,0,3,0,0,0,3,3,2,1,2,0,2,
                    0,1,3,2,3,2,3,0,1,2,3,0,2,3,0,0,1,2,3,3,2,0,
                    0,1,1,2,1,0,3,2,0,0,3,0,0,0,2,2,3,0,2,3,2,2,
                    3,3,1,2,3,3,2,2,3,1,0,3,0,0,2,3,1,3,2,0,2,2,
                    2,1,2,3,2,2,1,3,0,2,0,2,2,0,0,1,1,2,1,2,3,3,
                    2,2,2,3,3,2,2,0};


void setup() {
  Serial.begin(115200);
  ss.begin(GPSBaud);

  // start up the DDS...
  dds.init();
  // (Optional) trim if your xtal is not at 125MHz...
  dds.trim(125000000); // enter actual osc freq.
  // start the oscillator...
  dds.setFrequency(freq);
}


void loop() {
    while (ss.available() > 0)
    if (gps.encode(ss.read())) {
  
      if (gps.time.isValid()) {
          Serial.print("GPS is valid. Receive data from "); 
          Serial.print( gps.satellites.value()); 
          Serial.println(" Sats." );
          WSPR_TXF = (WSPR_TX+DDS_OSET) + random(0, 190); // always choose a frequency, 
                                                          // it mixes it all up a little with the pRNG.
            // Looking for a transmit window 
            if ((gps.time.minute() % 2 == 0) && (gps.time.second() == 1)) {  // start transmission 
                                                                             // between 1 and 4 seconds,
                                                                             // on every even minute
               if (duty % WSPR_DUTY == 0) {
                     digitalWrite (LED, HIGH);
                     Serial.print("Beginning WSPR Transmission on ");
                     Serial.print(WSPR_TXF-DDS_OSET);
                     Serial.println(" Hz.");
                     wsprTX();
                     Serial.println(" Transmission Finished.");
                     digitalWrite (LED, LOW);
                     duty++;
               } else {
                  // Not the correct TX window, duty not matched.
                  delay(5000); //Miss this TX window
                  duty++;
                  Serial.println("Time window not mached.");
               }
      
          } else {
             Serial.println( gps.satellites.value());
             Serial.println("Wait for next transmit window.");
          }
     } else {
       Serial.println("No valid GPS data.");
     }
  }
}

// Data Tx function  
void wsprTX() { 
int i = 0; 
for (i=0;i<162;i++) { 
     wsprTXtone( WSPR_DATA[i] ); 
     delay(683); 
  } 
dds.setFrequency(0); 
}


// Set DDS frequency
void wsprTXtone(int t) { 
  if ((t >= 0) && (t <= 3) ) { 
      dds.setFrequency((WSPR_TXF + (t * 1.4648)));
  }
  else {
    Serial.print("Tone #");
    Serial.print(t);
    Serial.println(" is not valid.  (0 <= t <= 3).");
  }
}