GLMtop

WSPR from AD9850


Summary: Usual way to produce WSPR-signals is to generate WSPR-signal at audio frequency and inject this audio to ssb-transmitter.
Here AD9850-chip is used with arduino to generate WSPR-signal directly on tx-frequency.
For this purpose frequency of AD9850 must be set in sub-hertz steps.


Hardware work for this project was just connecting wires with screwterminal connector (on right side of red receiver board).Wires...not sure if this project can really be called as"wireless communication".
You can copy hardware connections between arduino and AD9850 from webshed of David Mills and modified driver code from here (original is from webshed), and use WSPR-transmit function on end of this page to make WSPR-beacon. You need to create some code for timing of your beacon, and perhaps some user interface too. It should be moderate work.


Finally get rid from (most of) hard(ware) work...


Purpose for this work is to test local NVIS-communication on very low power levels. Nothing to tell about that, I seldom go far enough from my home for NVIS-work...
But I have tried this on "normal" WSPR-use. Get some spots over 2000km distance, on 10 meter band, without any amplifier. Power level is about 10 mw. I just connected AD9850-board to general filter on General Learning Machine and to antenna. Nice. Also simple user interface was rapidly done because modular structure of GLM-source.
lidPhenomenon.png

It was nice to see my own call on map. But after all, I have feeling that this work is mostly done by Joe Taylor (main developer of WSPR) and nature.

For me the ultimate goal is reliable communication between two stations, not situation when something is put on the air to see if somebody (anybody) somewhere (anywhere) can hear you. And this is not statement against "usual" ham radio or WSPR or any other mode. it is just one try to explain my own area of interest, it is a little corner on area of aerial magic...




Frequency change on left beacontrace is caused by opening the case of GLM. Same happends every time.How can opening the lid affect to direct digital synthesis?This is not a problem, but another phenomenon which tell us that there is something to learn.


Sub-hertz tuning for AD9850


This code section is based on work on webshed. First function will add possibility for sub-hertz tuning. Rest of section is directly from webshed of David Mills, and is here only for clarity and easy copy-paste. Thanks David!

For clarity I should mention that this code will not increase frequency accuracy of AD9850. It will only give your ability to change fr
equency in sub-hertz steps.

For sub-hertz tuning you have to give two parameters to function SetFrequency(): Base freguency at hertzs and fine tuning with float, also in hertzs. Like 10000000 for base and 0.5 for fine tuning. Function will try to set output frequency then to 10000000.5 hz. These two parameters are simply added together for final frequency.

Usage:

SetFrequency(10000000); -will set frequency of AD9850 to 10 Mhz.

SetFrequency(10000000, 0.5); - will set frequency 0.5 hz upper from previous, to 10000000,5...I think...

SetFrequency(9999990, 10.5) - will also set frequency to 10000000,5.


In nutshell, you can use this code to drive AD9850 just like original from David. But you can also add second parameter for function call for fine tuning.

So why two parameters are needed here, why not only one float parameter? Like SetFrequency(10000000.5)? Reason is that in arduino-enviroment float have only 6-7 digits of precision, which means total number of digits. So "float ddsFreq = 10000000.5;" already have 9 digits and it will behave just like datatype long, it contains only integer...yes I have tried...float on arduino reference.


void SetFrequency(unsigned long hertz, float subhertz)
 
{
  unsigned long tuning_hertz = (hertz * pow(2, 32)) / DDS_CLOCK;
  unsigned long tuning_subhertz = (subhertz * pow(2, 32)) / DDS_CLOCK;
  unsigned long tuning_word = tuning_hertz + tuning_subhertz;
 
  digitalWrite (LOAD, LOW);
 
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 8);  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 16);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 24);
  shiftOut(DATA, CLOCK, LSBFIRST, 0x0);
  digitalWrite (LOAD, HIGH);
}
 
void SetFrequency(unsigned long frequency)
{
  unsigned long tuning_word = (frequency * pow(2, 32)) / DDS_CLOCK;
  digitalWrite (LOAD, LOW);
 
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 8);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 16);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 24);
  shiftOut(DATA, CLOCK, LSBFIRST, 0x0);
  digitalWrite (LOAD, HIGH);
}
void AD9850_init()
{
 
  digitalWrite(RESET, LOW);
  digitalWrite(CLOCK, LOW);
  digitalWrite(LOAD, LOW);
  digitalWrite(DATA, LOW);
}
 
void AD9850_reset()
{
  //reset sequence is:
  // CLOCK & LOAD = LOW
  //  Pulse RESET high for a few uS (use 5 uS here)
  //  Pulse CLOCK high for a few uS (use 5 uS here)
  //  Set DATA to ZERO and pulse LOAD for a few uS (use 5 uS here)
 
  // data sheet diagrams show only RESET and CLOCK being used to reset the device, but I see no output unless I also
  // toggle the LOAD line here.
 
  digitalWrite(CLOCK, LOW);
  digitalWrite(LOAD, LOW);
 
  digitalWrite(RESET, LOW);
  delay(5);
  digitalWrite(RESET, HIGH);  //pulse RESET
  delay(5);
  digitalWrite(RESET, LOW);
  delay(5);
  digitalWrite(CLOCK, LOW);
  delay(5);
  digitalWrite(CLOCK, HIGH);  //pulse CLOCK
  delay(5);
  digitalWrite(CLOCK, LOW);
  delay(5);
  digitalWrite(DATA, LOW);    //make sure DATA pin is LOW
 
    digitalWrite(LOAD, LOW);
  delay(5);
  digitalWrite(LOAD, HIGH);  //pulse LOAD
  delay(5);
  digitalWrite(LOAD, LOW);
  // Chip is RESET now
}


Actual WSPR-tranmit function

firstspots.png

Here is actual tx-function. It can be used with hardware connections shown at webshed and modified driver code shown upper at this page. It must be called from loop() at right moment, at start of even minute on UTC-time.
Tx-frequency will be about variable ddsFreq. AD9850 will work at all HF-bands. Frequency must be in hertzs. Like for 28 Mhz ddsFreq must set to 28000000. On example frequency is set to 1 khz, I think it will not produce any radio interference at this frequency and you can listen it on audio if you like :)

Webpage of George Smart was really helpful when I did this. George have been doing similar work with slightly different AD9851-board. Page have good information about WSPR-protocol. Thanks George!

WSPR_SYMBOLS presented here will produce WSPR-transmission "FA1SE CA11 10" so change right symbols here! These are only for example. (I checked WSPR-database, no FA1SE call there.) I produced symbols with WsprryPi-program because happend to have it on my RasPi. Just start Wsprrypi-transmission with your call, location and power-information,and copy-paste symbols from terminal to your source code. There are alternative way on George Smart's arduino-WSPR-page. (Or I can produce symbols for you, ask from myhamcall@gmail.com. I am sure you will find my hamcall from these pages.)

Finally, this script is not exactly same as one I use. I have removed GLM-specific content, mainly interaction with user interface away. So it is not really tested, but I quess it works :)
void wsprTx(){
 
    unsigned long ddsFreq = 1000; // TX-frequency in hertz (this example is 1 kilohertz)
 
    byte WSPR_SYMBOLS[] = {1,3,0,2,2,2,0,0,3,2,2,2,3,3,3,0,0,0,1,0,2,3,2,1,3,3,3,0,0,2,2,2,2,2,
    3,0,2,1,0,3,0,2,0,0,0,2,3,0,3,3,0,2,3,1,0,3,0,0,2,3,1,0,3,0,2,0,2,3,3,2,1,0,1,0,1,2,1,0,0,1,
    0,2,1,0,3,1,0,0,2,1,1,0,3,2,3,2,0,2,3,2,2,2,2,2,1,2,2,3,2,0,3,3,1,2,3,3,2,2,1,3,2,3,2,0,2,1,
    3,1,0,2,0,0,2,3,0,3,0,2,3,1,2,0,2,2,0,2,0,1,1,2,3,0,3,3,2,2,0,3,1,2,2,0,};
 
    int i = 0;
    unsigned long wsprTimer = millis();
 
    for (i=0;i<162;i++) {
      wsprTimer = wsprTimer + 683;
      float subhertz =  (WSPR_SYMBOLS[i] * 1.4648);
      SetFrequency(ddsFreq, subhertz);
 
      while (millis() < wsprTimer){
       //just waiting here...wasting cpu power..
      }
  }
 
}


m