CQ Magazine WSPR Beacon

This page collects the materials related to the article published in the February 2014 issue of CQ Magazine (http://www NULL.cq-amateur-radio NULL.com/cq_highlights/2014_cq/2014_01_cq/PDF_NO_1_2014_02_CQ_TOC NULL.pdf) (page 36).

You will find here :

Corrected schematic

The schematic published in CQ Magazine had a slight error with the connection from the analog buttons (between R1 and R2) going to the A5 analog input of the MSP430G2553 (P1.5 port).

WSPR QRSS DDS beacon schematic corrected (http://xv4y NULL.radioclub NULL.asia/wp-content/uploads/2013/11/1_WSPR_Schema_corrected NULL.png)

DDS WSPR beacon firmware

/* WSPR DDS Generator for MSP430G2553
 * Code for Energia 009

 * By Yannick DEVOS - XV4Y - January 2014
    http://xv4y.radioclub.asia/

    Copyright 2012-2013-2014 Yannick DEVOS under GPL 3.0 license
    Any commercial use or inclusion in a kit is subject to author approval

 * Based on code by DH3JO and G4JNT for WSPR sequence encoding
 * Optimization by XV4Y to reduce memory usage

====
 * PTT_key output allows to turn on/off the TX PA while sending
 * Agile Frequency generation using AD9850/9851 DDS
 * Output to Nokia 5110 compatible LCD

====
Revision history :
v1.00    2012-12-31
         First release
v1.01    2013-01-05
         Added reading from analog multiplexed buttons
v1.02    2013-01-29
         A few last minute bugs corrected and NoLCD reincorporated in the code
v1.10    2013-03-09
         Added random shift enable/disable parameter
         Added WSPR-15 support (with better symbol spacing accuracy)
V1.11    2013-03-21
         Multiple QTH (Callsign and Locator) allowed and startup selectable
V1.12    2013-06-09
         Cycling throug sets of predefined frequencies

====
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You can download a copy of the GNU General Public License at <http://www.gnu.org/licenses/>
*/

// Here modify to your taste or needs

#define PTT_key          P2_2
//#define StartBtn         P1_3
#define ANALOG_BUTTONS   A5
#define LCD_OUT_INFO     // Comment if no 5110 display is connected

const char call[2][7] =     { "XV4Y  ", "XV4Y  "} ;     // Callsigns must be 6 characters, number is mandatory in 3rd position, pad with spaces if necessary
const char locator[2][5] =  { "OK20", "OK20"} ;

#define PWR_LEVEL       9            // Power in dBm (value 6 is 20 dBm)
#define DEF_TX_RATE     4            // TX rate for a random start optimizing frequency sharing (value 4 is 1/5)
#define DEFAULT_FREQ    4            // Value 8 in Frequencies array is 14097100
#define DEFAULT_SHIFT   146          // WSPR Symbol frequency separation in 1/100 of Hertz
#define DEFAULT_QTH     0            // The callsign / locator par you choose by default
#define DEF_CYCLING     -1            // Which band set we cycle through frequencies by default : -1 is cycling disabled, 0 is first band set, 1 second band set...
#define MAX_CYCLE       8            // Number of bands we will cycle

              // List of frequencies we will cycle through, for real frequencis see table in constant belows
                // First set is user defined (40, 20, 15, 10m)
                // Second set is low band (160, 80, 40, 30m)
                // Third set is high bands (20, 17, 15, 10m)
                // Fourth set is WARC band (60, 30, 17, 12m)
const byte band_cycling[4][MAX_CYCLE] = {{6, 6, 8, 8, 10, 10, 12, 12}, {3, 3, 4, 4, 6, 6, 7, 7}, {8, 8, 9, 9, 10, 10, 12, 12}, {6, 6, 7, 7, 9, 9, 11, 11}};

// **
// Here under don't touch anything unless you know what you do
// **

#include <legacymsp430.h>
#include <sRTC.h> // Library for RTC clock with MSP430
#include <AD9850.h> // Library for AD9850 control by MSP430

#ifdef LCD_OUT_INFO
#include <LCD_5110.h>
#endif

              // Power levels (in dBm) you can select
const byte powers[16] = {0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50};
              // Frequencies (1 Hz precision) you can select
const unsigned long int frequencies[] = {
  137500, 471700, 501500, 1838500, 3594100, 5288700, 7040100, 10140200,
  14097100, 18106100, 21096100, 24926100, 28126100, 50294500, 70092500};

const unsigned tx_rates[7] = {1,2,3,4,5,7,10};

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

const byte Analog_Noise = 20;    // Margins for reading analog buttons

boolean  force_send = 1;       // The first time the beacon is turned on it will send a sequence
byte     bb, begin_sec, wspr_shift=DEFAULT_SHIFT;
char     freq_select=DEFAULT_FREQ, rate_select=DEF_TX_RATE, qth=DEFAULT_QTH, cycle_select=0, band_set=DEF_CYCLING;
int      begin_chunk, random_shift=50;

char chaine_txt[6] = {' ', ' ', ' ', ' ', ' ', 0x00};

byte sym[162];             // symbol table 162

RealTimeClock myClock;

//AD9850 myDDS (P1_0, P1_1, P1_2, P1_4);  // Call the AD9850 Library, AD9850 pins for CLOCK, LOAD, DATA and RESET
AD9850 myDDS (P1_1, P1_2, P1_0, P1_4);  // Call the AD9850 Library, AD9851 pins for CLOCK, LOAD, DATA and RESET

#ifdef LCD_OUT_INFO
LCD_5110 myScreen(P2_3,    // Chip Select *
         P1_6,    // Serial Clock *
         P2_5,    // Serial Data *
         P2_4,    // Data/Command *
         NULL,    // Reset *
         P1_7,    // Backlight
         NULL);  // Push Button 0
#endif

//******************************************************************
// Defining pins mode
// Encoding the WSPR sequence

void setup() {
  pinMode(PTT_key, OUTPUT);

  myClock.begin();
  myDDS.begin();

#ifdef LCD_OUT_INFO
  pinMode(ANALOG_BUTTONS, INPUT);

  myScreen.begin();
  myScreen.setBacklight(1);

  myScreen.setFont(1);
  myScreen.text(0, 0, "WSPR");
  myScreen.text(0, 2, "Beacon");
  myScreen.setFont(0);
  myScreen.text(0, 4, "v1.12 - XV4Y");
  myScreen.text(0, 5, "Init...");

  randomSeed(analogRead(ANALOG_BUTTONS));

  // Set a random shift of +/- 50Hz around the central WSPR frequency
  random_shift = random(100)-50;

  setup_menu();   // We call the setup menu

  myDDS.reset();

#else
  pinMode(StartBtn, INPUT_PULLUP);
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, LOW);

  myDDS.reset();

#endif

  myDDS.SetFrequency( frequencies[freq_select], 0, true );  // Set frequency with DDS power down

  digitalWrite( PTT_key, LOW );

  prepare_sequence();  // We compute the the WSPR sequence

  begin_chunk = myClock.RTC_chunk;
  begin_sec = myClock.RTC_sec;

#if defined(LCD_OUT_INFO)
  myScreen.clear();
  myScreen.setBacklight(1);
  myScreen.setFont(1);
  myScreen.text(0, 0, "TIME");
  myScreen.setFont(0);
  myScreen.text(0, 2, "Press button");
  myScreen.text(0, 3, "to set time.");
  myScreen.text(0, 4, "Sending test");

  while(read_buttons()!=5) {

    // We also send a test sequence to help calibrate the transmitter
    if ((myClock.RTC_chunk-(begin_chunk+0))%240==0) 
      send_test(begin_sec);

   };
  myScreen.setBacklight(0);

#else

  while(digitalRead(StartBtn)) {
    // This is for blinking LED fast
    if ((myClock.RTC_chunk-(begin_chunk+15))%60==0)
      digitalWrite( RED_LED, HIGH );
    if ((myClock.RTC_chunk-(begin_chunk+30))%60==0)
      digitalWrite( GREEN_LED, HIGH );
    if ((myClock.RTC_chunk-(begin_chunk+45))%60==0)
      digitalWrite( RED_LED, LOW );
    if ((myClock.RTC_chunk-(begin_chunk+60))%60==0)
      digitalWrite( GREEN_LED, LOW );

    // We also send a test sequence to help calibrate the transmitter
    if ((myClock.RTC_chunk-(begin_chunk+0))%240==0) 
      send_test(begin_sec);

   };
#endif

  myClock.Set_Time(0,0,0);

};

//******************************************************************
// Here starts the actual sequence sending

void loop() {

  if (band_set >= 0) freq_select = band_cycling[band_set][cycle_select];

  digitalWrite( PTT_key, LOW );
  myDDS.SetFrequency( (frequencies[freq_select]+random_shift), 0, true );  // Set frequency with DDS power down

  begin_chunk = myClock.RTC_chunk;

#if defined(LCD_OUT_INFO)
  myScreen.clear();
  myScreen.setBacklight(0);
  myScreen.setFont(1);
  if (wspr_shift == 18) {
    myScreen.text(0, 0, "WSPR-15");
  } else if (wspr_shift == 146) {
    myScreen.text(0, 0, "WSPR");
  };

  myScreen.setFont(0);
  myScreen.text(0, 2, call[qth]);
  myScreen.text(8, 2, locator[qth]);
  myScreen.text(0, 4, "Waiting...");

  if (wspr_shift == 146) {    // Wait start for WSPR 2
    while (!(myClock.RTC_sec==1 && (myClock.RTC_min%2)==0)) {  // We start each first second of even minutes
      if (read_buttons()==5){
        force_send = 1;  // Pressing the button while in wait mode force the sequence to start next time
        myScreen.text(0, 4, "Send next slot");
        myScreen.setBacklight(1);
      };
      if (myClock.RTC_chunk==1 || myClock.RTC_chunk==128) {
        display_time();
      };
    };
  } else if (wspr_shift == 18) {    // Wait start for WSPR 15
    while (!(myClock.RTC_sec==1 && (myClock.RTC_min%15)==0)) {  // We start each first second of even minutes
      if (read_buttons()==5){
        force_send = 1;  // Pressing the button while in wait mode force the sequence to start next time
        myScreen.text(0, 4, "Send next slot");
        myScreen.setBacklight(1);
      };
      if (myClock.RTC_chunk==1 || myClock.RTC_chunk==128) {
        display_time();
      };
    };
  };

  if (random(tx_rates[rate_select]+1)==tx_rates[rate_select] || force_send) {  // In order to minimize band occupation, randomly start the transmission depending on tx_rate
    // Now we start the transmit procedure
    force_send = 0;
    // Set PTT high, turn on back light and set DDS
    digitalWrite( PTT_key, HIGH );
    myScreen.setBacklight(1);
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), 0, false );  // Set frequency with DDS and turn on DDS

    // Display the frequency
    display_freq (frequencies[freq_select]+random_shift, 4);
    // Display Power
    display_number (powers[PWR_LEVEL], 0, 3);
    myScreen.text(2, 3, "dBm");
    myScreen.text(0, 5, "Now sending   ");

    send_sequence();

    myScreen.text(0, 4, "End of sequence");
  } else {
    // If not, we wait around 1 sec so we don't go through this too early...
    digitalWrite( PTT_key, LOW );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), 0, true );  // Set frequency with DDS power down
    begin_chunk = myClock.RTC_chunk;
    while ( ((256+myClock.RTC_chunk-begin_chunk)%256)<220 ) {
      delay(0);
    };
  };

#else
  digitalWrite( RED_LED, LOW );
  digitalWrite( GREEN_LED, LOW );

  begin_chunk = myClock.RTC_chunk;

  if (wspr_shift == 146) {    // Wait start for WSPR 2
    while (!(myClock.RTC_sec==1 && (myClock.RTC_min%2)==0)) {  // We start each first second of even minutes
      if ((myClock.RTC_chunk-(begin_chunk+10))%20==0) digitalWrite( GREEN_LED, HIGH );
      if ((myClock.RTC_chunk-(begin_chunk+20))%20==0) digitalWrite( GREEN_LED, LOW );
      if (!digitalRead(StartBtn)) force_send = 1;  // Pressing the button while in wait mode force the sequence to start next time
    };
  } else if (wspr_shift == 18) {    // Wait start for WSPR 15
    while (!(myClock.RTC_sec==1 && (myClock.RTC_min%15)==0)) {  // We start each first second of even minutes
      if ((myClock.RTC_chunk-(begin_chunk+20))%40==0) digitalWrite( GREEN_LED, HIGH );
      if ((myClock.RTC_chunk-(begin_chunk+40))%40==0) digitalWrite( GREEN_LED, LOW );
      if (!digitalRead(StartBtn)) force_send = 1;  // Pressing the button while in wait mode force the sequence to start next time
    };
  };  

  if (random(tx_rates[rate_select]+1)==tx_rates[rate_select] || force_send) {  // In order to minimize band occupation, randomly start the transmission depending on tx_rate
    force_send = 0;
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), 0, false );  // Set frequency with DDS and turn on DDS
    send_sequence();
  } else {
    // If not, we wait around 1 sec so we don't go through this too early...
    digitalWrite( PTT_key, LOW );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), 0, true );  // Set frequency with DDS power down
    begin_chunk = myClock.RTC_chunk;
    while ( ((256+myClock.RTC_chunk-begin_chunk)%256)<220 ) {
      delay(0);
    };
  };
#endif

  if (band_set >= 0) cycle_select++;    // We increase the index through the band cycling table
  if (cycle_select == MAX_CYCLE) cycle_select = 0;

};

//******************************************************************
// 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);
}

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

//******************************************************************
// Compute and initialize the WSPR symbols sequence
void prepare_sequence() {

  unsigned long sh1=0;
  unsigned long t1;
  boolean symt[164];            // symbol table temp => A remplacer par table de boolean
  byte c[11]={0,0,0,0,0,0,0,0,0,0,0};                // encoded message
  int bc=0, cnt=0, cc;
  int ii, ij, b2, bis, ip=0;

  // Encode callsign
  t1=chr_normf(call[qth][0]);
  t1=t1*36+chr_normf(call[qth][1]);
  t1=t1*10+chr_normf(call[qth][2]);
  t1=t1*27+chr_normf(call[qth][3])-10;
  t1=t1*27+chr_normf(call[qth][4])-10;
  t1=t1*27+chr_normf(call[qth][5])-10;

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

  // Encode locator
  t1=179-10*(chr_normf(locator[qth][0])-10)-chr_normf(locator[qth][2]);
  t1=t1*180+10*(chr_normf(locator[qth][1])-10)+chr_normf(locator[qth][3]);
  t1=t1*128+powers[PWR_LEVEL]+64;  // Encore power

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

  // Intilialize tables to 0
  for (int i=0; i<163; i++) {
    sym[i] = 0;
    symt[i] = 0;
  }

  // convolutional encoding of message array c[] into a 162 bit stream

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

  // interleave reorder the 162 data bits and and merge table with the sync vector

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

//******************************************************************
// Function to send the test sequence
void send_test(char begin_sec) {
  if ((myClock.RTC_sec-(begin_sec+0))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), 0, false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "00");
    #endif
  }

  if ((myClock.RTC_sec-(begin_sec+6))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), (wspr_shift*1), false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "01");
    #endif
  }

  if ((myClock.RTC_sec-(begin_sec+12))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), (wspr_shift*2), false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "10");
    #endif
  }

  if ((myClock.RTC_sec-(begin_sec+18))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), (wspr_shift*3), false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "11");
    #endif
  }

  if ((myClock.RTC_sec-(begin_sec+24))%30==0) {
    digitalWrite( PTT_key, LOW );
    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), 0,  1 );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "--");
    myScreen.text(4, 5, "00");
    #endif
  }

};

//******************************************************************
// Function to send the sequence
void send_sequence() {
  for (bb = 0; bb < 162; bb++) {
    begin_chunk = myClock.RTC_chunk;
    for (byte i=0; i<6; i++) {
      chaine_txt[i] = ' ';
    };

    myDDS.SetFrequency( (frequencies[freq_select]+random_shift), (wspr_shift*sym[bb]), false );  // Change frequency with symbols

    #ifdef LCD_OUT_INFO
    display_number (sym[bb], 12, 5);
    #else
    digitalWrite( GREEN_LED, (sym[bb] >> 1) );

    digitalWrite( RED_LED, (sym[bb] & 1) );

    #endif

    for (byte i=0; i<8; i++) {  // In case of WSPR-15 we have to wait 8 times longer
      while ( ((256+myClock.RTC_chunk-begin_chunk)%256)<175 ) {
        #ifdef LCD_OUT_INFO
        if (myClock.RTC_chunk==1 || myClock.RTC_chunk==128) display_time();  // Display time only twice a second
        #else
        delay(0);
        #endif
      };
      begin_chunk = myClock.RTC_chunk;
      if (wspr_shift == 146) break;  // In case of WSPR-2 we can go out earlier
    };
  };
};

#if defined(LCD_OUT_INFO)
//******************************************************************
// Setup menu at startup in 2 parts

void setup_menu() {
  // Here we wait 5 seconds more and check if the user wants to change settings

  begin_sec = myClock.RTC_sec;

  while ( ((60 + myClock.RTC_sec - begin_sec)%60) < 5 ) {
    if ( read_buttons()==5 ) {
      break;
    };
    delay(10);
  }

  // If button pressed we enter setting
  if(read_buttons()==5) {
    while (read_buttons()==5) delay(0);  // Debounce
    setup_menu_1();
    while (read_buttons()==5) delay(0);  // Debounce
    setup_menu_2();
    while (read_buttons()==5) delay(0);  // Debounce
    setup_menu_3();
    while (read_buttons()==5) delay(0);  // Debounce
    setup_menu_3b();
    while (read_buttons()==5) delay(0);  // Debounce
    if (band_set == -1) {
      setup_menu_4();
      while (read_buttons()==5) delay(0);  // Debounce
    };
    setup_menu_5();
    while (read_buttons()==5) delay(0);  // Debounce
  }

}

void setup_menu_1() {
    // First we set the frequency

    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 1");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "to set Band");
    myScreen.text(0, 4, "center Freq");
    while (1) {
      display_freq (frequencies[freq_select], 5);
      if(read_buttons()==1) {            // Up we increase
        while (read_buttons()==1) delay(0);  // Debounce
        freq_select++;
        if (freq_select > 14) freq_select = 0;
      } else if (read_buttons()==3) {    // Down we decrease
        while (read_buttons()==3) delay(0);  // Debounce
        freq_select--;
        if (freq_select < 0) freq_select = 14;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

void setup_menu_2() {
    // Then we set the tx_rate

    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 2");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "set TX/idle");
    myScreen.text(0, 4, "ratio");

    while (1) {
      myScreen.text(0, 5, "1/");
      display_number (tx_rates[rate_select], 2, 5);

      if(read_buttons()==1) {            // Up we increase
        while (read_buttons()==1) delay(0);  // Debounce
        rate_select++;
        if (rate_select > 6) rate_select = 0;
      } else if (read_buttons()==3) {    // Down we decrease
        while (read_buttons()==3) delay(0);  // Debounce
        rate_select--;
        if (rate_select < 0) rate_select = 6;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

void setup_menu_3() {

    // Then we set the WSPR mode

    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 3");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "to toggle");
    myScreen.text(0, 4, "WSPR Mode");

    while (1) {
      if (wspr_shift == 146) {
        myScreen.text(0, 5, "WSPR-2  ");
      } else if (wspr_shift == 18) {
        myScreen.text(0, 5, "WSPR-15 ");
      };

      if(read_buttons()==1) {            // Up toggle modes
        while (read_buttons()==1) delay(0);  // Debounce
        if (wspr_shift == 18) {
          wspr_shift = 146;
          random_shift = random(100)-50;
        } else if (wspr_shift == 146) {
          wspr_shift = 18;
          random_shift = 80;
        };
      } else if (read_buttons()==3) {    // Down toggle modes
        while (read_buttons()==3) delay(0);  // Debounce
        if (wspr_shift == 18) {
          wspr_shift = 146;
          random_shift = random(100)-50;
        } else if (wspr_shift == 146) {
          wspr_shift = 18;
          random_shift = 80;
        };
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }

}

void setup_menu_3b() {

    // Then we decide if we cycle through frequencies

    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 3B");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "to set band");
    myScreen.text(0, 4, "cycling mode");

    while (1) {
      if (band_set == -1) {
        myScreen.text(0, 5, "Disabled ");
      } else if (band_set == 0) {
        myScreen.text(0, 5, "User     ");
      } else if (band_set == 1) {
        myScreen.text(0, 5, "Low bands ");
      } else if (band_set == 2) {
        myScreen.text(0, 5, "High bands ");
      } else if (band_set == 3) {
        myScreen.text(0, 5, "WARC bands ");
      };

      if(read_buttons()==1) {            // Up toggle modes
        while (read_buttons()==1) delay(0);  // Debounce
        band_set++;
        if (band_set > 3) band_set = 3;
      } else if (read_buttons()==3) {    // Down toggle modes
        while (read_buttons()==3) delay(0);  // Debounce
        band_set--;
        if (band_set < -1) band_set = -1;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

void setup_menu_4() {

    // Then we adjust frequency manually

    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 4");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "adjust TX Freq");
    myScreen.text(0, 4, "by 5Hz step");

    while (1) {
      display_freq (frequencies[freq_select]+random_shift, 5);

      if(read_buttons()==1) {            // Up we increase
        delay(200);  // Debounce
        random_shift+=5;
        if (random_shift > 150) random_shift = 150;
      } else if (read_buttons()==3) {    // Down we decrease
        delay(200);  // Debounce
        random_shift-=5;
        if (random_shift < -150) random_shift = -150;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

void setup_menu_5() {

    // Then we choose the QTH

    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 5");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "to choose QTH");

    while (1) {
      myScreen.text(0, 5, call[qth]);
      myScreen.text(8, 5, locator[qth]);

      if(read_buttons()==1) {            // Up we increase
        while (read_buttons()==1) delay(0);  // Debounce
        qth++;
        if (qth > 1) qth = 0;
      } else if (read_buttons()==3) {    // Down we decrease
        while (read_buttons()==3) delay(0);  // Debounce
        qth--;
        if (qth < 0) qth = 1;      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

//******************************************************************
// Display the frequency
void display_freq (unsigned long freq, char ligne) {
  myScreen.text(10, ligne, " Hz ");
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (((freq)/1)%10);
  chaine_txt[3] = 0x30 + (((freq)/10)%10);
  chaine_txt[2] = 0x30 + (((freq)/100)%10);
  chaine_txt[1] = '.';
  chaine_txt[0] = 0x30 + (((freq)/1000)%10);
  myScreen.text(5, ligne, chaine_txt);
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (((freq)/10000)%10);
  chaine_txt[3] = 0x30 + (((freq)/100000)%10);
  chaine_txt[2] = '.';
  chaine_txt[1] = 0x30 + (((freq)/1000000)%10);
  chaine_txt[0] = 0x30 + (((freq)/10000000)%10);
  myScreen.text(0, ligne, chaine_txt); 
}

//******************************************************************
// Display the time
void display_time () {
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (myClock.RTC_sec%10);
  chaine_txt[3] = 0x30 + ((myClock.RTC_sec/10)%10);
  chaine_txt[2] = ':';
  chaine_txt[1] = 0x30 + (myClock.RTC_min%10);
  chaine_txt[0] = 0x30 + ((myClock.RTC_min/10)%10);
  myScreen.text(7, 3, chaine_txt);
}

//******************************************************************
// Display a 2 digits number
void display_number (byte number, char column, char ligne) {
  chaine_txt[2] = 0x00;
  chaine_txt[1] = 0x30 + (number%10);
  chaine_txt[0] = 0x30 + ((number/10)%10);
  myScreen.text(column, ligne, chaine_txt);
}

//******************************************************************
// Return a button value depending on the analog reading
byte read_buttons () {
  int value = analogRead(ANALOG_BUTTONS);
  if ( value<(1+Analog_Noise) ) {
    return 1;
  } else if ( value<(78+Analog_Noise) ) {
    return 2;
  } else if ( value<(146+Analog_Noise) ) {
    return 3;
  } else if ( value<(205+Analog_Noise) ) {
    return 4;
  } else if ( value<(255+Analog_Noise) ) {
    return 5;
  } else {
    return 0;
  }
}
#endif

//******************************************************************
// Interruption for the RTC clock
interrupt(TIMER1_A0_VECTOR) Tic_Tac(void) {
  myClock++;		      // Update chunks
};

DDS QRSS beacon firmware

/* QRSS beacon with 10 minutes frame for MSP430G2553 driving an AD9850 DDS
 * Code for Energia 009
 
 * By Yannick DEVOS - XV4Y - January 2014
    http://xv4y.radioclub.asia/

    Copyright 2012-2013-2014 Yannick DEVOS under GPL 3.0 license
    Any commercial use or inclusion in a kit is subject to author approval
 
====
 * PTT_key output allows to turn on/off the TX PA while sending
 * Agile Frequency generation using AD9850/9851 DDS
 * Output to Nokia 5110 compatible LCD

====
Revision history :
v1.00    2013-06-13
         First release based on QRSS framed v1.02


====
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You can download a copy of the GNU General Public License at <http://www.gnu.org/licenses/>
*/

// **
// Here under don't touch anything unless you know what you do
// **

// Alphabet binary coding by G0UPL
// 0 = dot, 1 = dash
const int _A	=	0b11111001;
const int _B	=	0b11101000;
const int _C	=	0b11101010;
const int _D	=	0b11110100;
const int _E	=	0b11111100;
const int _F	=	0b11100010;
const int _G	=	0b11110110;
const int _H	=	0b11100000;
const int _I	=	0b11111000;
const int _J	=	0b11100111;
const int _K	=	0b11110101;
const int _L	=	0b11100100;
const int _M	=	0b11111011;
const int _N	=	0b11111010;
const int _O	=	0b11110111;
const int _P	=	0b11100110;
const int _Q	=	0b11101101;
const int _R	=	0b11110010;
const int _S	=	0b11110000;
const int _T	=	0b11111101;
const int _U	=	0b11110001;
const int _V	=	0b11100001;
const int _W	=	0b11110011;
const int _X	=	0b11101001;
const int _Y	=	0b11101011;
const int _Z	=	0b11101100;
const int _SPC	=       0b11101111;
const int _0	=	0b11011111;
const int _1	=	0b11001111;
const int _2	=	0b11000111;
const int _3	=	0b11000011;
const int _4	=	0b11000001;
const int _5	=	0b11000000;
const int _6	=	0b11010000;
const int _7	=	0b11011000;
const int _8	=	0b11011100;
const int _9	=	0b11011110;
const int _BRK	 =      0b11010010;
const int _WAIT  =	0b10000000;

// **
// PARAMETERS : Here modify to your taste or needs
// ** 

const int msg[] = {5, _X, _V, _4, _Y, _WAIT};  // Format, is lenght in decimal and then the character constants including the _WAIT for the end of the sequence

int vitesse = 6;    // Number of seconds per dot (QRSS6 = 6)

#define PTT_key          P2_2
//#define StartBtn         P1_3
#define ANALOG_BUTTONS   A5
#define LCD_OUT_INFO     // Comment if no 5110 display is connected

#define DEFAULT_FREQ    6            // Value 8 in Frequencies array is 14097100
#define DEFAULT_SHIFT   500          // QRSS FSK frequency separation in 1/100 of Hertz
#define DEF_CYCLING     -1            // Which band set we cycle through frequencies by default : -1 is cycling disabled, 0 is first band set, 1 second band set...
#define MAX_CYCLE       8            // Number of bands we will cycle

              // List of frequencies we will cycle through, for real frequencis see table in constant belows
                // First set is user defined (40, 20, 15, 10m)
                // Second set is low band (160, 80, 40, 30m)
                // Third set is high bands (20, 17, 15, 10m)
                // Fourth set is WARC band (60, 30, 17, 12m)
const byte band_cycling[4][MAX_CYCLE] = {{6, 6, 8, 8, 10, 10, 12, 12}, {3, 3, 4, 4, 6, 6, 7, 7}, {8, 8, 9, 9, 10, 10, 12, 12}, {6, 6, 7, 7, 9, 9, 11, 11}};

#include <legacymsp430.h>
#include <sRTC.h> // Library for RTC clock with MSP430
#include <AD9850.h> // Library for AD9850 control by MSP430

#ifdef LCD_OUT_INFO
#include <LCD_5110.h>
#endif

RealTimeClock myClock;

//AD9850 myDDS (P1_0, P1_1, P1_2, P1_4);  // Call the AD9850 Library, AD9850 pins for CLOCK, LOAD, DATA and RESET
AD9850 myDDS (P1_1, P1_2, P1_0, P1_4);  // Call the AD9850 Library, AD9851 pins for CLOCK, LOAD, DATA and RESET

#ifdef LCD_OUT_INFO
LCD_5110 myScreen(P2_3,    // Chip Select *
         P1_6,    // Serial Clock *
         P2_5,    // Serial Data *
         P2_4,    // Data/Command *
         NULL,    // Reset *
         P1_7,    // Backlight
         NULL);  // Push Button 0
#endif

// **
// Here under don't touch anything unless you know what you do
// **

              // Frequencies (1 Hz precision) you can select
const unsigned long int frequencies[] = {
  137600, 476175, 501500, 1843200, 3500890, 5288700, 7000800, 10140000,
  14000800, 18108900, 21000800, 24928900, 28000800, 50294400, 70092500};

const byte Analog_Noise = 20;    // Margins for reading analog buttons

boolean  force_send = 1;       // The first time the beacon is turned on it will send a sequence
byte     bb, begin_sec;
char     freq_select=DEFAULT_FREQ, cycle_select=0, band_set=DEF_CYCLING;
int      begin_chunk, random_shift=0, qrss_shift=DEFAULT_SHIFT;

// Déclaration et initilisation des variables
byte msgIndex = 1;
byte inc_bit = 8;
byte character = _SPC;
boolean start = false;

byte key = 0;
byte etat = 0;

char chaine_txt[6] = {' ', ' ', ' ', ' ', ' ', 0x00};


//******************************************************************
// Defining pins mode
// Encoding the WSPR sequence

void setup() {
  pinMode(PTT_key, OUTPUT);

  myClock.begin();
  myDDS.begin();
  
#ifdef LCD_OUT_INFO
  pinMode(ANALOG_BUTTONS, INPUT);

  myScreen.begin();
  myScreen.setBacklight(1);
    
  myScreen.setFont(1);
  myScreen.text(0, 0, "QRSS");
  myScreen.text(0, 2, "Beacon");
  myScreen.setFont(0);
  myScreen.text(0, 4, "v1.00 - XV4Y");
  myScreen.text(0, 5, "Init...");
  
  setup_menu();   // We call the setup menu

  myDDS.reset();

#else
  pinMode(StartBtn, INPUT_PULLUP);
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, LOW);

  myDDS.reset();

#endif
  
  myDDS.SetFrequency( frequencies[freq_select]+random_shift, 0, true );  // Set frequency with DDS power down

  digitalWrite( PTT_key, LOW );

  begin_chunk = myClock.RTC_chunk;
  begin_sec = myClock.RTC_sec;

#if defined(LCD_OUT_INFO)
  myScreen.clear();
  myScreen.setBacklight(1);
  myScreen.setFont(1);
  myScreen.text(0, 0, "TIME");
  myScreen.setFont(0);
  myScreen.text(0, 2, "Press button");
  myScreen.text(0, 3, "to set time.");
  myScreen.text(0, 4, "Sending test");

  while(read_buttons()!=5) {

    // We also send a test sequence to help calibrate the transmitter
    if ((myClock.RTC_chunk-(begin_chunk+0))%240==0) 
      send_test(begin_sec);

   };
  myScreen.setBacklight(0);

#else

  while(digitalRead(StartBtn)) {
    // This is for blinking LED fast
    if ((myClock.RTC_chunk-(begin_chunk+15))%60==0)
      digitalWrite( RED_LED, HIGH );
    if ((myClock.RTC_chunk-(begin_chunk+30))%60==0)
      digitalWrite( GREEN_LED, HIGH );
    if ((myClock.RTC_chunk-(begin_chunk+45))%60==0)
      digitalWrite( RED_LED, LOW );
    if ((myClock.RTC_chunk-(begin_chunk+60))%60==0)
      digitalWrite( GREEN_LED, LOW );
    
    // We also send a test sequence to help calibrate the transmitter
    if ((myClock.RTC_chunk-(begin_chunk+0))%240==0) 
      send_test(begin_sec);

   };
#endif

  myClock.Set_Time(0,0,0);

};

//******************************************************************
// Here starts the actual sequence sending

void loop() {
  
  if (band_set >= 0) freq_select = band_cycling[band_set][cycle_select];
  
  digitalWrite( PTT_key, LOW );
  myDDS.SetFrequency( (frequencies[freq_select])+random_shift, 0, true );  // Set frequency with DDS power down

  begin_chunk = myClock.RTC_chunk;

#if defined(LCD_OUT_INFO)
  myScreen.clear();
  myScreen.setBacklight(0);
  myScreen.setFont(1);
  myScreen.text(0, 0, "QRSS ");
  display_number (vitesse, 8, 0);

  myScreen.setFont(0);
  myScreen.text(0, 2, "");
  myScreen.text(8, 2, "");
  myScreen.text(0, 4, "Waiting...");


  digitalWrite( PTT_key, LOW );

  begin_chunk = myClock.RTC_chunk;
  
  while (!(myClock.RTC_sec==1 && (myClock.RTC_min%2)==0)) {  // We start each first second of even minutes
    if (read_buttons()==5){
      force_send = 1;  // Pressing the button while in wait mode force the sequence to start next time
      myScreen.text(0, 4, "Send next slot");
      myScreen.setBacklight(1);
    };
    if (myClock.RTC_chunk==1 || myClock.RTC_chunk==128) {
      display_time();
    };
  };

  // Now we start the transmit procedure
  force_send = 0;
  // Set PTT high, turn on back light and set DDS
  digitalWrite( PTT_key, HIGH );
  myScreen.setBacklight(1);
  myDDS.SetFrequency( (frequencies[freq_select])+random_shift, 0, false );  // Set frequency with DDS and turn on DDS

  // Display the frequency
  display_freq (frequencies[freq_select]+random_shift, 4);
  myScreen.text(0, 5, "Sending     ");
  
  send_sequence();

#else
  digitalWrite( RED_LED, LOW );
  digitalWrite( GREEN_LED, LOW );
  
  while (!(myClock.RTC_sec==1 && (myClock.RTC_min%2)==0)) {  // We start each first second of even minutes
    if ((myClock.RTC_chunk-(begin_chunk+10))%20==0) digitalWrite( GREEN_LED, HIGH );
    if ((myClock.RTC_chunk-(begin_chunk+20))%20==0) digitalWrite( GREEN_LED, LOW );
  };
  
  force_send = 0;
  digitalWrite( PTT_key, HIGH );
  myDDS.SetFrequency( (frequencies[freq_select])+random_shift, 0, false );  // Set frequency with DDS and turn on DDS
  send_sequence();

#endif
  if (band_set >= 0) cycle_select++;    // We increase the index through the band cycling table
  if (cycle_select == MAX_CYCLE) cycle_select = 0;
  
};

//******************************************************************
// Function to send the test sequence
void send_test(char begin_sec) {
  if ((myClock.RTC_sec-(begin_sec+0))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select])+random_shift, 0, false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "00");
    #endif
  }

  if ((myClock.RTC_sec-(begin_sec+6))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select])+random_shift, (qrss_shift/3), false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "01");
    #endif
  }

  if ((myClock.RTC_sec-(begin_sec+12))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select])+random_shift, ((qrss_shift/3)*2), false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "10");
    #endif
  }

  if ((myClock.RTC_sec-(begin_sec+18))%30==0) {
    digitalWrite( PTT_key, HIGH );
    myDDS.SetFrequency( (frequencies[freq_select])+random_shift, (qrss_shift), false );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "TX");
    myScreen.text(4, 5, "11");
    #endif
  }
  
  if ((myClock.RTC_sec-(begin_sec+24))%30==0) {
    digitalWrite( PTT_key, LOW );
    myDDS.SetFrequency( (frequencies[freq_select])+random_shift, 0,  1 );
    #ifdef LCD_OUT_INFO
    myScreen.text(0, 5, "--");
    myScreen.text(4, 5, "00");
    #endif
  }

};

  
//******************************************************************
// Function to send the sequence
void send_sequence() {
  
  while (character != _WAIT) {
    myDDS.SetFrequency( (frequencies[freq_select])+random_shift, 0, false );

    msgIndex = 1;
    
    while (msgIndex < msg[0]+1) {
      // We read each character in the array
      character = msg[msgIndex];
  
      inc_bit = 8;
      
      // Special characters
      if (character == _SPC) {  // For inter-words spacing, we need 5 dots, since we already have 1+2, we need 2 more
         begin_sec = myClock.RTC_sec;
         #ifdef LCD_OUT_INFO
         myScreen.text(0, 4, "Break");
         #endif
         while ( ((60+myClock.RTC_sec-begin_sec)%60)<(2*vitesse) ) {
            delay(0);
         };
         inc_bit = 0;
      };
      if (character == _WAIT) {  // For WAIT or end of sequence, we just skip sending
         #ifdef LCD_OUT_INFO
         myScreen.text(0, 4, "End of sequence");
         #endif
         inc_bit = 0;
      };
         
      while (inc_bit) {
  
        // We read each bit of the character
        etat = bitRead(character,inc_bit-1);
  
        if (start) {
          if (etat) {
            key=3; // If the bit is 1 then it is a dash
            #ifdef LCD_OUT_INFO
            myScreen.text(8, 5, "Dash ");
            #endif
          } else {
            key=1; // If the bit is 0 then it is a dot
            #ifdef LCD_OUT_INFO
            myScreen.text(8, 5, "Dot ");
            #endif
          }
  
          while (key) {
  
            myDDS.SetFrequency( (frequencies[freq_select]+random_shift), qrss_shift, false );
            #ifdef LCD_OUT_INFO
            #else
            digitalWrite( RED_LED, HIGH );
            digitalWrite( GREEN_LED, HIGH );
            #endif
  
            // We wait 1 second x VITESSE parameter
            begin_sec = myClock.RTC_sec;
            while ( ((60+myClock.RTC_sec-begin_sec)%60)<(1*vitesse) ) {
              #ifdef LCD_OUT_INFO
              if (myClock.RTC_chunk==1 || myClock.RTC_chunk==128) display_time();  // Display time only twice a second
              #else
              delay(0);
              #endif
            };
  
            key--;
          }
  
          myDDS.SetFrequency( (frequencies[freq_select])+random_shift, 0, false );
          #ifdef LCD_OUT_INFO
          myScreen.text(8, 5, "    ");
          #else
          digitalWrite( RED_LED, LOW );
          digitalWrite( GREEN_LED, LOW );
          #endif
            // We wait 1 second x VITESSE parameter
          begin_sec = myClock.RTC_sec;
          while ( ((60+myClock.RTC_sec-begin_sec)%60)<(1*vitesse) ) {
            #ifdef LCD_OUT_INFO
            if (myClock.RTC_chunk==1 || myClock.RTC_chunk==128) display_time();  // Display time only twice a second
            #else
            delay(0);
            #endif
          };
        }
  
        if (!etat && !start) start=true;
  
        inc_bit--;
        
      }
      // We add one space between each characteur (lenght is 3 dots, including the one in the here above loop)
      begin_sec = myClock.RTC_sec;
      #ifdef LCD_OUT_INFO
      myScreen.text(8, 5, "Space");
      #endif
      while ( ((60+myClock.RTC_sec-begin_sec)%60)<(2*vitesse) ) {
        #ifdef LCD_OUT_INFO
        if (myClock.RTC_chunk==1 || myClock.RTC_chunk==128) display_time();  // Display time only twice a second
        #else
        delay(0);
        #endif
      };
  
      start = false;
      msgIndex++;
    };
   };
   character = _SPC;
};

#if defined(LCD_OUT_INFO)
//******************************************************************
// Setup menu at startup in 2 parts

void setup_menu() {
  // Here we wait 5 seconds more and check if the user wants to change settings

  begin_sec = myClock.RTC_sec;
  
  while ( ((60 + myClock.RTC_sec - begin_sec)%60) < 5 ) {
    if ( read_buttons()==5 ) {
      break;
    };
    delay(10);
  }
  
  // If button pressed we enter setting
  if(read_buttons()==5) {
    while (read_buttons()==5) delay(0);  // Debounce
    setup_menu_1();
    while (read_buttons()==5) delay(0);  // Debounce
    setup_menu_2();
    while (read_buttons()==5) delay(0);  // Debounce
    setup_menu_3();
    while (read_buttons()==5) delay(0);  // Debounce
    if (band_set == -1) {
      setup_menu_4();
      while (read_buttons()==5) delay(0);  // Debounce
    };
  }

}

void setup_menu_1() {
    // First we set the frequency
    
    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 1");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "to set Band");
    myScreen.text(0, 4, "center Freq");
    while (1) {
      display_freq (frequencies[freq_select], 5);
      if(read_buttons()==1) {            // Up we increase
        while (read_buttons()==1) delay(0);  // Debounce
        freq_select++;
        if (freq_select > 14) freq_select = 0;
      } else if (read_buttons()==3) {    // Down we decrease
        while (read_buttons()==3) delay(0);  // Debounce
        freq_select--;
        if (freq_select < 0) freq_select = 14;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

void setup_menu_2() {

    // Then we set the shift widht mode
    
    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 2");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "to set QRSS");
    myScreen.text(0, 4, "shift width");

    while (1) {
      display_number (byte(qrss_shift/100), 0, 5);
      myScreen.text(2, 5, " Hz ");

      if(read_buttons()==1) {            // Up we increase
        delay(200);  // Debounce
        qrss_shift+=100;
        if (qrss_shift > 2000) qrss_shift = 2000;
      } else if (read_buttons()==3) {    // Down we decrease
        delay(200);  // Debounce
        qrss_shift-=100;
        if (qrss_shift < 100) qrss_shift = 100;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }


}


void setup_menu_3() {

    // Then we decide if we cycle through frequencies
    
    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 3");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "to set band");
    myScreen.text(0, 4, "cycling mode");

    while (1) {
      if (band_set == -1) {
        myScreen.text(0, 5, "Disabled ");
      } else if (band_set == 0) {
        myScreen.text(0, 5, "User     ");
      } else if (band_set == 1) {
        myScreen.text(0, 5, "Low bands ");
      } else if (band_set == 2) {
        myScreen.text(0, 5, "High bands ");
      } else if (band_set == 3) {
        myScreen.text(0, 5, "WARC bands ");
      };

      if(read_buttons()==1) {            // Up toggle modes
        while (read_buttons()==1) delay(0);  // Debounce
        band_set++;
        if (band_set > 3) band_set = 3;
      } else if (read_buttons()==3) {    // Down toggle modes
        while (read_buttons()==3) delay(0);  // Debounce
        band_set--;
        if (band_set < -1) band_set = -1;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

void setup_menu_4() {

    // Then we adjust frequency manually
    
    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    myScreen.text(0, 0, "SETUP 4");
    myScreen.setFont(0);
    myScreen.text(0, 2, "Press up/dwn");
    myScreen.text(0, 3, "adjust TX Freq");
    myScreen.text(0, 4, "by 5Hz step");

    while (1) {
      display_freq (frequencies[freq_select]+random_shift, 5);

      if(read_buttons()==1) {            // Up we increase
        delay(200);  // Debounce
        random_shift+=5;
        if (random_shift > 150) random_shift = 150;
      } else if (read_buttons()==3) {    // Down we decrease
        delay(200);  // Debounce
        random_shift-=5;
        if (random_shift < -150) random_shift = -150;
      } else if (read_buttons()==5) {    // OK we go out
        while (read_buttons()==5) delay(0);  // Debounce
        break;          
      }
    }
}

//******************************************************************
// Display the frequency
void display_freq (unsigned long freq, char ligne) {
  myScreen.text(10, ligne, " Hz ");
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (((freq)/1)%10);
  chaine_txt[3] = 0x30 + (((freq)/10)%10);
  chaine_txt[2] = 0x30 + (((freq)/100)%10);
  chaine_txt[1] = '.';
  chaine_txt[0] = 0x30 + (((freq)/1000)%10);
  myScreen.text(5, ligne, chaine_txt);
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (((freq)/10000)%10);
  chaine_txt[3] = 0x30 + (((freq)/100000)%10);
  chaine_txt[2] = '.';
  chaine_txt[1] = 0x30 + (((freq)/1000000)%10);
  chaine_txt[0] = 0x30 + (((freq)/10000000)%10);
  myScreen.text(0, ligne, chaine_txt); 
}

//******************************************************************
// Display the time
void display_time () {
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (myClock.RTC_sec%10);
  chaine_txt[3] = 0x30 + ((myClock.RTC_sec/10)%10);
  chaine_txt[2] = ':';
  chaine_txt[1] = 0x30 + (myClock.RTC_min%10);
  chaine_txt[0] = 0x30 + ((myClock.RTC_min/10)%10);
  myScreen.text(7, 3, chaine_txt);
}

//******************************************************************
// Display a 2 digits number
void display_number (byte number, char column, char ligne) {
  chaine_txt[2] = 0x00;
  chaine_txt[1] = 0x30 + (number%10);
  chaine_txt[0] = 0x30 + ((number/10)%10);
  myScreen.text(column, ligne, chaine_txt);
}

//******************************************************************
// Return a button value depending on the analog reading
byte read_buttons () {
  int value = analogRead(ANALOG_BUTTONS);
  if ( value<(1+Analog_Noise) ) {
    return 1;
  } else if ( value<(78+Analog_Noise) ) {
    return 2;
  } else if ( value<(146+Analog_Noise) ) {
    return 3;
  } else if ( value<(205+Analog_Noise) ) {
    return 4;
  } else if ( value<(255+Analog_Noise) ) {
    return 5;
  } else {
    return 0;
  }
}
#endif

//******************************************************************
// Interruption for the RTC clock
interrupt(TIMER1_A0_VECTOR) Tic_Tac(void) {
  myClock.Inc_chunk();		      // Update chunks
};

DDS Simple VFO firmware

/* Simple VFO with DDS for MSP430G2553
 * Code for Energia 009
 
 * By Yannick DEVOS - XV4Y - January 2014
    http://xv4y.radioclub.asia/

    Copyright 2012-2013-2014 Yannick DEVOS under GPL 3.0 license
    Any commercial use or inclusion in a kit is subject to author approval

====
 * Agile Frequency generation using AD9850/9851 DDS
 * Output to Nokia 5110 compatible LCD
 * Check if P2_2 has changed state and switch VFO (like when PTT is pressed to operate split)

Usage for short press of buttons :
- Up / Down      increase or decrease the frequency following the choosen digit
- Left / Right   increase or decrease the digit
- OK             validate the frequency and send it to the DDS
Long press of buttons :
- Left           Set the current VFO to the next band bottom frequency
- Right          VFO A = VFO B
- OK             Switch between VFO A and VFO B

====
Revision history :
v1.00    2013-03-18
         First release

====
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You can download a copy of the GNU General Public License at <http://www.gnu.org/licenses/>
*/

// Here modify to your taste or needs

#define PTT_key          P2_2
#define ANALOG_BUTTONS   A5
#define AUTO_VALID       // Uncomment if you want the DDS frequency to be udapted automatically after each value change

#define DEFAULT_FREQ    8            // Value 8 in Frequencies array is 14000000
// Here under don't touch anything unless you know what you do

#include <legacymsp430.h>
#include <AD9850.h> // Library for AD9850 control by MSP430

#include <LCD_5110.h>

              // Frequencies (1 Hz precision) you can select
const unsigned long int frequencies[] = {
  137000, 471000, 501000, 1830000, 3500000, 5200000, 7000000, 10100000,
  14000000, 18068000, 21000000, 24890000, 28000000, 50000000, 70000000};
  
const byte Analog_Noise = 5;    // Margins for reading analog buttons

boolean   vfo=0, saved_PTT;  // VFO A or B and PTT input state
char      multiplier, aff_multiplier, freq_select=DEFAULT_FREQ;
unsigned  long int frequency_A=frequencies[DEFAULT_FREQ], frequency_B=frequencies[DEFAULT_FREQ], debounce;

char chaine_txt[6] = {' ', ' ', ' ', ' ', ' ', 0x00};


AD9850 myDDS (P1_0, P1_1, P1_2, P1_4);  // Call the AD9850 Library, AD9850 pins for CLOCK, LOAD, DATA and RESET
//AD9850 myDDS (P1_1, P1_2, P1_0, P1_4);  // Call the AD9850 Library, AD9851 pins for CLOCK, LOAD, DATA and RESET

LCD_5110 myScreen(P2_3,    // Chip Select *
         P1_6,    // Serial Clock *
         P2_5,    // Serial Data *
         P2_4,    // Data/Command *
         NULL,    // Reset *
         P1_7,    // Backlight
         NULL);  // Push Button 0

//******************************************************************
// Defining pins mode and initializing hardware

void setup() {
  pinMode(PTT_key, INPUT_PULLUP);

  myDDS.begin();
  
   
  pinMode(ANALOG_BUTTONS, INPUT);

  myScreen.begin();
  myScreen.setBacklight(1);
    
  myScreen.setFont(1);
  myScreen.text(0, 0, "sVFO");
  myScreen.text(0, 2, "AD9850");
  myScreen.setFont(0);
  myScreen.text(0, 4, "v1.00 - XV4Y");
  myScreen.text(0, 5, "Init...");
  
  delay(1000);
  
  myDDS.reset();
  
  myDDS.SetFrequency( frequency_A, 0, false );

  digitalWrite( PTT_key, LOW );

};

//******************************************************************
// Here starts the actual sequence sending

void loop() {
    myScreen.clear();
    myScreen.setBacklight(1);
    myScreen.setFont(1);
    if (vfo == 0) {
      myScreen.text(0, 0, "VFO A");
      myDDS.SetFrequency( frequency_A, 0, false );
    } else {
      myScreen.text(0, 0, "VFO B");
      myDDS.SetFrequency( frequency_B, 0, false );
    };

    myScreen.setFont(0);
    display_freq (frequency_A, 3);
    display_freq (frequency_B, 5);

    while (read_buttons()==5 || read_buttons()==4 || read_buttons()==2) delay(10); // Debounce except for UP/DWN

    while (1) {
      // Update the frequencies display
      if (multiplier > 5) {
        aff_multiplier = multiplier + 2;
      } else if (multiplier > 2) {
        aff_multiplier = multiplier + 1;
      } else {
        aff_multiplier = multiplier;
      };
      myScreen.text(0, 4, "          ");
      if (vfo == 0) {
        myScreen.text(9-aff_multiplier, 4, "^");
      } else {
        myScreen.text(9-aff_multiplier, 4, "v");
      }
      display_freq (frequency_A, 3);
      display_freq (frequency_B, 5);
  
      // Read the analog buttons input
      if(read_buttons()==1) {            // Up we increase frequency
        delay(200);  // Debounce
        if (vfo == 0) {
          frequency_A = frequency_A + powf(10,(float)multiplier);
        } else {
          frequency_B = frequency_B + powf(10,(float)multiplier);
        };
#if defined AUTO_VALID
        break;
#endif

      } else if (read_buttons()==3) {    // Down we decrease frequency
        delay(200);  // Debounce
        if (vfo == 0) {
          frequency_A = frequency_A - powf(10,(float)multiplier);
        } else {
          frequency_B = frequency_B - powf(10,(float)multiplier);
        };
#if defined AUTO_VALID
        break;
#endif

      } else if (read_buttons()==2) {    // Left we increase multiplier
        debounce = millis();
        while (read_buttons()==2) {  //Debounce
          if ((millis()-debounce)>1000) {  // Long press we do "Band UP"
            freq_select++;
            if (freq_select > 14) freq_select = 0;
            if (vfo == 1) frequency_B = frequencies[freq_select]; else frequency_A=frequencies[freq_select];
            multiplier--;
            break;
          };
        };
        multiplier++;
        if (multiplier > 7) multiplier = 7;

      } else if (read_buttons()==4) {    // Right we decrease multiplier
        debounce = millis();
        while (read_buttons()==4) {  //Debounce
          if ((millis()-debounce)>1000) {  // Long press we do VFO A=B
            if (vfo == 1) frequency_A = frequency_B; else frequency_B=frequency_A;
            multiplier++;
            break;
          };
        };
        multiplier--;
        if (multiplier < 0) multiplier = 0;

      } else if (read_buttons()==5) {    // OK we go out
        debounce = millis();
        while (read_buttons()==5) {  //Debounce
          if ((millis()-debounce)>1000) {  // Long press we switch VFO A/B
            if (vfo == 1) vfo=0; else vfo=1;
            break;
          };
        };
        break;   // Short press we just leave the loop so the frequency is transmitted to the AD9850
      }

      // Check if we are transmitting split (momentaneous VFO A->B switch)
      if (saved_PTT != digitalRead(PTT_key)) {
        saved_PTT = digitalRead(PTT_key);
        if (vfo == 1) vfo=0; else vfo=1;
        break;
      };
    }

};


//******************************************************************
// Display the frequency
void display_freq (unsigned long freq, char ligne) {
  myScreen.text(10, ligne, " Hz ");
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (((freq)/1)%10);
  chaine_txt[3] = 0x30 + (((freq)/10)%10);
  chaine_txt[2] = 0x30 + (((freq)/100)%10);
  chaine_txt[1] = '.';
  chaine_txt[0] = 0x30 + (((freq)/1000)%10);
  myScreen.text(5, ligne, chaine_txt);
  chaine_txt[5] = 0x00;
  chaine_txt[4] = 0x30 + (((freq)/10000)%10);
  chaine_txt[3] = 0x30 + (((freq)/100000)%10);
  chaine_txt[2] = '.';
  chaine_txt[1] = 0x30 + (((freq)/1000000)%10);
  chaine_txt[0] = 0x30 + (((freq)/10000000)%10);
  myScreen.text(0, ligne, chaine_txt); 
}

//******************************************************************
// Display a 2 digits number
void display_number (byte number, char column, char ligne) {
  chaine_txt[2] = 0x00;
  chaine_txt[1] = 0x30 + (number%10);
  chaine_txt[0] = 0x30 + ((number/10)%10);
  myScreen.text(column, ligne, chaine_txt);
}

//******************************************************************
// Return a button value depending on the analog reading
byte read_buttons () {
  int value = analogRead(ANALOG_BUTTONS);
  if ( value<(1+Analog_Noise) ) {
    return 1;
  } else if ( value<(78+Analog_Noise) ) {
    return 2;
  } else if ( value<(146+Analog_Noise) ) {
    return 3;
  } else if ( value<(205+Analog_Noise) ) {
    return 4;
  } else if ( value<(255+Analog_Noise) ) {
    return 5;
  } else {
    return 0;
  }
}

3 comments for “CQ Magazine WSPR Beacon

  1. Jouko
    16 janvier 2014 at 16:39

    Bonjour,

    I am very interested in your developmets for Energia, exspecially for the new Tiva C.
    The WSPR beacon would be nice to get work on Tiva Lauchpad.
    I cannot find any downloads on your page.

    73 Jouko OH5RM

    • yan
      16 janvier 2014 at 19:30

      Hi Jouko,

      I have no more time to work on Energia unfortunately. However I think the code should be portable from MSP430 to Tiva without too much work.
      Right now, you can download the source code in the online shop. You have to register.

      Once CQ Magazine has published my article (february issue I think), I will update this page with all the related material.
      I am just waiting for them…

      73,
      Yan – XV4Y.

      • Jouko
        17 janvier 2014 at 15:26

        Hi Yan,

        Thanks for the info. Yes I will try to convert the code to Tiva. But downloading it is a problem.
        I did register myself but at checkout it keeps asking abt paying method (free) and let me not complete.

        73 Jouko OH5RM