Monday, 22 June 2015

VFO with Band Plan display

A few weeks ago John, 2E1EFH (now M0RVJ - Rev John!), suggested that the VFO could display the band plans which changed as you tune across. So i have developed the code here to do just that. It means the VFO is a single band job, and I have used the button on the rotary encoder to select tuning steps of 100Hz, 1kHz and 10kHz. Thus making a lot easier to move across the band. The steps are indicated by "+" symbols on the top right of the display.

Photo 06 22 2015 18 13 48

Code for 16 x 2 display

// VFO_40m_bandplan
// 40m only version
// 16 x 4 LCD LCD
// button changes steps 100Hz ( )/1kHz( +)/10kHz(++)

// I2C, Si5351, LCD and rotary Encoder libraries
#include "Wire.h"
#include "si5351.h"
#include "LiquidCrystal_I2C.h"
#include "Rotary.h"

// LCD
#define LCDADDR 0x27
#define LCDCOLS 16
#define LCDROWS 2

// rotary Encoder pins 2 & 3 (DT & CLK), band change pin 4 (SW)
#define DT 2
#define CLK 3
#define SW 4

// Rx & Tx signals
#define RX 13
#define TX 12
#define KEY 8

// dds object
Si5351 dds;

// LCD object
LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDROWS);

// rotary Encoder object
Rotary rot = Rotary(DT, CLK);

uint32_t steps = 10000; // init 100Hz steps

// define sa structure
typedef struct{
  uint32_t lower;
  uint32_t upper;
  char alloc[30];
} plan;

#define PLANS 12

// define he array contents cHz/cHz/Text
plan bp[PLANS] = {
  {700000000, 700100000, "CW QRSS 7000.7  "},
  {700100000, 703990000, "CW QRP 7030     "},
  {703990000, 704690000, "NB WSPR 7040    "},
  {704600000, 704990000, "NB Auto         "},
  {704990000, 705290000, "ALL Auto        "},
  {705290000, 705990000, "ALL Digital     "},
  {705990000, 706990000, "ALL             "},
  {706990000, 707990000, "ALL HELL 7077   "},
  {707990000, 709990000, "ALL SSB QRP 7090"},
  {709990000, 712990000, "ALL EMGCY 7110  "},
  {712990000, 717490000, "ALL SSB CON 7165"},
  {717490000, 720010000, "ALL DX INTNL    "},
};

uint32_t freq = 700000000; // indicated frequency
 
void setup() {
   // init LCD & backlight on
  lcd.init();
  lcd.backlight();
  
  // init dds si5351 module, "0" = default 25MHz XTAL
  dds.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  
  // set 8mA output drive
  dds.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  
  // enable VFO output CLK0, disable CLK1 & 2
  dds.output_enable(SI5351_CLK0, 1);
  dds.output_enable(SI5351_CLK1, 0);
  dds.output_enable(SI5351_CLK2, 0);

  // encoder, button, RX, TX, band and KEY pins
  pinMode(DT, INPUT_PULLUP);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(SW, INPUT_PULLUP);

  pinMode(RX, OUTPUT);
  pinMode(TX, OUTPUT);
  pinMode(KEY, INPUT_PULLUP);

  xmit(digitalRead(KEY)); // RXTX mode
  
  freqOut(freq); // output freq

  dispFreq(4, 0, freq, 1); // display freq col 6 row 0
  dispMsg(0, 1, scanPlan()); // display band plan col 0 row 3
}

void loop() {
    stepit(); // change steps?
    tune(); // rotary encoder tuning?
    xmit(digitalRead(KEY)); // TX?
}

void tune() {
  unsigned char dir; // tuning direction CW/CCW
  
  // tune?
  dir = rot.process(); // read encoder
  if(dir != DIR_NONE) // turned?
  {
    if(dir == DIR_CW && freq < bp[PLANS-1].upper - steps) freq += steps; // increment freq +/- STEPS
    if(dir == DIR_CCW && freq >= bp[0].lower + steps) freq -= steps;

    freqOut(freq); // output freq
    dispFreq(4, 0, freq, 1); // update freq display
    dispMsg(0, 1, scanPlan()); // update band plan display
  }
}

// change step?
void stepit() {
  if(digitalRead(SW) == LOW) // button pressed?
  {
    while(!digitalRead(SW)); // wait for release
    if(steps == 1000000) steps = 10000; // reset
    else steps = steps * 10; // or increment by x10
    
    switch (steps) // display status
    {
      case 10000:
        dispMsg(14, 0, "  ");
        break;
      case 100000:
        dispMsg(14, 0, " +");
        break;
      case 1000000:
        dispMsg(14, 0, "++");
        break;
    }
  }
}

// search for band info
char *scanPlan() {
  for(int i = 0; i < 15; i++) {
    if (freq >= bp[i].lower && freq < bp[i].upper) { // find plan
      return bp[i].alloc; // return when found
    }
  }
}

// frequency (in cHz) for VFO, on CLK0
void freqOut(uint32_t f) {
    dds.set_freq(f, 0ULL, SI5351_CLK0); // converted to cHz
}

// KEY HIGH RX, LOW TX
void xmit(bool x)
{
  if(x == LOW) // TX
  {
    dispMsg(0, 0, "TX ");
    digitalWrite(RX, HIGH); // RX off
    digitalWrite(TX, LOW); // TX on
  }
  else
  {
    dispMsg(0, 0, "VFO");
    digitalWrite(RX, LOW); // RX on
    digitalWrite(TX, HIGH); // TX off
  }
}

// display char msg at col c, row r
void dispMsg(uint8_t c, uint8_t r, char *m) {
  lcd.setCursor(c, r);
  lcd.print(m);
}

// display freq in kHz,col c, row r, d decimal places
void dispFreq(uint8_t c, uint8_t r, uint32_t f, uint8_t d) {
  lcd.setCursor(c, r); // clear last freq display
  lcd.print((float)f / 100000, d); // convert to float & kHz for print function
  
  lcd.print("kHz"); // + trailing spaces to clear previous display
}


You will notice that I have also implimented the RX TX switching which enables the RX when pin D13 goes low, and the TX when pin D12 goes low.

Obviously by just changing the frequency generated to x4, the output and drive of the Si5351 to use CLK2 and the display to "SDR" you can get an SDR version of the software.

IMG 4860

Code for 20 x 4 display

// Universal_VFO_40m_BandPlan_lcd2004
// 40m only version
// 20 x 4 I2C LCD
// button changes steps 100Hz ( )/1kHz( +)/10kHz(++)

// I2C, Si5351, LCD and rotary Encoder libraries
#include "Wire.h"
#include "si5351.h"
#include "LiquidCrystal_I2C.h"
#include "Rotary.h"

// LCD 20 col x 4 rows
#define LCDADDR 0x27
#define LCDCOLS 20
#define LCDROWS 4

// rotary Encoder pins 2 & 3 (DT & CLK), band change pin 4 (SW)
#define DT 2
#define CLK 3
#define SW 4

// Rx & Tx signals
#define RX 13
#define TX 12
#define KEY 8

// dds object
Si5351 dds;

// LCD object
LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDROWS);

// rotary Encoder object
Rotary rot = Rotary(DT, CLK);

uint32_t steps = 10000; // init 100Hz steps

// define sa structure
typedef struct{
  uint32_t lower;
  uint32_t upper;
  char alloc1[21];
  char alloc2[21];
} plan;

#define PLANS 9

// define he array contents cHz/cHz/Text
plan bp[PLANS] = {
  {700000000, 703990000, "7000-7040 CW PSK31  ", "7000.8 QRSS 7030 QRP"},
  {704000000, 704690000, "7040-7047 Nrw Band  ", "7040 WSPR           "},
  {704700000, 704990000, "7047-7050 Nrw Auto  ", "Auto-controlled Data"},
  {705000000, 705290000, "7050-7053 Auto      ", "Unattended Data     "},
  {705300000, 705990000, "7053-7060 All       ", "Digital modes       "},
  {706000000, 709990000, "7060-7100 SSB Cont  ", "7077 HELL 7090 QRP  "},
  {710000000, 712990000, "7100-7130 SSB Cont  ", "7110 Emergency      "},
  {713000000, 717490000, "7130-7175 All       ", "7165 Images         "},
  {717500000, 720000000, "7175-7200 All       ", "Internat priority   "},
};

uint32_t freq = 700000000; // start frequency cHz
 
void setup() {
   // init LCD & backlight on
  lcd.init();
  lcd.backlight();
  
  // init dds si5351 module, "0" = default 25MHz XTAL
  dds.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  
  // set 8mA output drive
  dds.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  
  // enable VFO output CLK0, disable CLK1 & 2
  dds.output_enable(SI5351_CLK0, 1);
  dds.output_enable(SI5351_CLK1, 0);
  dds.output_enable(SI5351_CLK2, 0);

  // encoder, button, RX, TX, band and KEY pins
  pinMode(DT, INPUT_PULLUP);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(SW, INPUT_PULLUP);

  pinMode(RX, OUTPUT);
  pinMode(TX, OUTPUT);
  pinMode(KEY, INPUT_PULLUP);

  xmit(digitalRead(KEY)); // RX|TX
  
  freqOut(freq); // output freq, cHz/100

  dispMsg(0, 0, "VFO"); // ident of program
  dispFreq(7, 0, freq, 1); // display freq in kHz col 7 row 0
  dispMsg(0, 1, "Band   40m");
  dispMsg(0, 2, bp[scanPlan()].alloc1); // display band plan col 0 row 2
  dispMsg(0, 3, bp[scanPlan()].alloc2); //  and col 0 row 3
}

void loop() {
    stepchg(); // change steps?   / +/++ = 100Hz/1kHz/10kHz
    tune(); // rotary encoder tuning?
    xmit(digitalRead(KEY)); // RX|TX
}

void tune() {
  unsigned char dir; // tuning direction CW/CCW
  
  // tune?
  dir = rot.process(); // read encoder
  if(dir != DIR_NONE) // turned?
  {
    if(dir == DIR_CW && freq < bp[PLANS-1].upper - steps) freq += steps; // increment freq +/- STEPS
    if(dir == DIR_CCW && freq >= bp[0].lower + steps) freq -= steps;

  dispFreq(7, 0, freq, 1); // display freq in kHz col 7 row 0
  dispMsg(0, 2, bp[scanPlan()].alloc1); // display band plan col 0 row 2
  dispMsg(0, 3, bp[scanPlan()].alloc2); //  and col 0 row 3
  }
}

// change step?
void stepchg() {
  if(digitalRead(SW) == LOW) // button pressed?
  {
    while(!digitalRead(SW)); // wait for release
    if(steps == 1000000) steps = 10000; // reset
    else steps = steps * 10; // or increment by x10
    
    switch (steps) // display status
    {
      case 10000:
        dispMsg(18, 0, "  ");
        break;
      case 100000:
        dispMsg(18, 0, " +");
        break;
      case 1000000:
        dispMsg(18, 0, "++");
        break;
    }
  }
}

// search for band info
byte scanPlan() {
  for(int i = 0; i < 15; i++) {
    if (freq >= bp[i].lower && freq <= bp[i].upper) // find plan
      return i; // return when found
  }
}

// frequency (in cHz) for VFO, on CLK0
void freqOut(uint32_t f) {
    dds.set_freq(f, 0ULL, SI5351_CLK0);
}

// KEY HIGH RX, LOW TX
void xmit(bool x)
{
  if(x == LOW) // TX
  {
    dispMsg(0, 0, "TX ");
    digitalWrite(RX, HIGH); // RX off
    digitalWrite(TX, LOW); // TX on
  }
  else
  {
    dispMsg(0, 0, "VFO");
    digitalWrite(RX, LOW); // RX on
    digitalWrite(TX, HIGH); // TX off
  }
}

// display char msg at col c, row r
void dispMsg(uint8_t c, uint8_t r, char *m) {
  lcd.setCursor(c, r);
  lcd.print(m);
}

// display freq in kHz,col c, row r, d decimal places
void dispFreq(uint8_t c, uint8_t r, uint32_t f, uint8_t d) {
  lcd.setCursor(c, r);
  lcd.print((float)f / 100000, d); // convert to float & kHz for print function
  lcd.print(" kHz"); // + trailing spaces to clear previous display
}

Monday, 8 June 2015

Low Pass filters for 40, 30 & 20m

I now plan a separate board for the LPFs in my CONCEPT project, this will enable me to use a Universal PA design, i.e. wide band amplifier.

The schematic is:

Screen Shot 2015 06 10 at 18 10 24

I will probably change the output header to a coaxial socket...

LPFs for 40, 30 & 20m

40m s

40m p

30m s

30m p

20m s

20m p

Simulations on LTSpice.

Saturday, 6 June 2015

BARS introduces "Concept" Arduino Rigs

The Banbury Amateur Radio Society - G0BRA ran a course on Arduino last year, this covered the introduction to the microcomputer board and the code development system. It included example programs for sending morse code, displaying temperatures and was to include a program for a digital frequency generator, but the AD9850 modules did not arrive from China in time!

The course was a grand success and sparked a renewed interest in building amateur radio equipment, and so it will now be expanded and extended.

CONCEPT

The target is to build a number of Arduino shields to give amateurs a basis for discovering construction and operating at QRP levels in many different modes - under software control. The shields and modes are:

Concept S0 Intro 005

Building in stages a Universal VFO, an SDR receiver followed by an SDR transmitter and LPF. With operating modes for WSPR propagation beacons, QRSS low power slow morse, CW and SSB. The bands to be covered are 40, 30 or 20m. Further boards may be developed in the future for example a PA and LPF.

Screen Shot 2015 06 06 at 09 18 48

The course signup is by July 30, then proceeds in 8 weekly sessions in September/October 2015 at the club headquarters in Banbury. Meetings are to be held every Tuesday evening 19:00 starting Sept 8.

If any non-members of BARS wish to take part, they should contact the society (see web site), society membership will be essential for the course.