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:
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'
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)."); } }