Wednesday 22 February 2017

BASIC Tech Group - MyNews 26 - LCD Bar graph function.

I have been admiring all those LCD displays on the web showing a bar graph, but had no idea how to implement it. Until I stumbled across a French web site with a novel solution. And here it is.

This is a complete sketch in order to show the setup, but you can extract just the includes, defines and bar glyphs to use the function. The inputs to the function are the column and row at which to start the bar graph, the digital input (0 to1023 integer) and the length of the bar.

// RF_METER, voltage, dBm & Pwr if 50R load
// V2 1-3-17
// 4k7 input impedance. 40dB attn
// LCD I2C bus (16x2)
// SDA = A4
// SCL = A5

//LCD library
#include "LiquidCrystal_I2C.h"
#include "math.h"

// chose address of 3F or 27 for LCD
//#define LCDADDR 0x27
#define LCDADDR 0x3F
#define LCDCOLS 16
#define LCDROWS 2

// Analog input pin, ref (measured) mV
// AREF can vary between Arduino, measure the 3.3V line and put AREF value here.
// scale for 80dB range to fill bar 0-1023
#define DCIN A3
#define AREF 3242.0
#define BARSCALE (1023 / 80)


// intercept (dBm), slope (mw/dB), input impedance, attenuator (dB).
// input attenuator is 4k7/51R
#define INTERCEPT -84.0
#define SLOPE 25.0
#define IMP 50.0
#define ATTN 40.0

// bar glyphs, 8 rows (top to bottom) for each glyph, 5 bits per row
uint8_t bar0[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000};
uint8_t bar1[8] = {B00000, B10000, B10000, B10000, B10000, B10000, B00000, B00000};
uint8_t bar2[8] = {B00000, B11000, B11000, B11000, B11000, B11000, B00000, B00000};
uint8_t bar3[8] = {B00000, B11100, B11100, B11100, B11100, B11100, B00000, B00000};
uint8_t bar4[8] = {B00000, B11110, B11110, B11110, B11110, B11110, B00000, B00000};
uint8_t bar5[8] = {B00000, B11111, B11111, B11111, B11111, B11111, B00000, B00000};

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

void setup() {
  Serial.begin(9600);
  
  // init LCD
  lcd.begin();

  lcd.createChar(0, bar0);
  lcd.createChar(1, bar1);
  lcd.createChar(2, bar2);
  lcd.createChar(3, bar3);
  lcd.createChar(4, bar4);
  lcd.createChar(5, bar5);

  lcd.clear();

  // 3.3V connected to AREF
  analogReference(EXTERNAL);

  dispMsg(0, 0, "RF"); // 0-10V bar graph goes to right...
}

void loop() {
  // display dBm etc
  dispDbm();
  delay(100);
}

void dispDbm() {
  int Ain;                  // int = signed 16 bit
  double mW, mV, dBm, Vrms; // double = double 4 x bytes = 32bit


  // calculations for mV input, dBm, mW and Volts, AREF in mV
  Ain = analogRead(DCIN); // returns an int 0-1023
  mV = (double)(Ain * AREF / 1023); // AREF in mV, calculate & convert to double
  dBm = (mV / SLOPE) + INTERCEPT + ATTN; // in doubles
  mW = pow(10.0, (dBm / 10.0)); // in double, out double
  Vrms = sqrt((mW / 1000.0) * IMP); // in double, out double,

  dispBar(3, 0, (dBm + 40) * BARSCALE, 13); // offset dBm and scale
  
  lcd.setCursor(0, 1);
  if (Vrms < 1.0) {
    lcd.print(Vrms * 1000.0, 0);
    lcd.print("mV ");
  }
  else {
    lcd.print(Vrms, 1);
    lcd.print("V ");
  }

  // dBm
  lcd.print(dBm, 0);
  lcd.print("dBm ");

  // Watts
  if (mW < 1000.0) {
    lcd.print(mW, 0);
    lcd.print("mW  ");
  }
  else {
    lcd.print(mW / 1000.0, 1);
    lcd.print("W   ");
  }
}

// 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 bar at col, row, 0-1023, length
void dispBar(uint8_t c, uint8_t r, int d, int l) {
  uint8_t i, ncol;

  lcd.setCursor(c, r);

  ncol = map(d, 0, 1023, 0, l * 5);

  for (i = 0; i < l; ++i) {
    if(ncol == 0) {
      lcd.write(0);
    }
    else if (ncol >= 5) {
      lcd.write(5);
      ncol -= 5;
    }
    else {
      lcd.write(ncol);
      ncol = 0;
    }
  }
}
}
In action

And here is the bar graph in action on my RF METER hardware and Sketch,

IMG 0800

My Si5351 Digital Frequency Synthesiser is outputting RF to the RF METER board, note that here there is no 50R dummy load so its measuring voltage only, and the DDS anyway outputs square waves so it not accurate for that reason as well. When used to measure sine waves across a 50R dummy load the reading are correct.

The sketch below is using the bar graph to display the RF strength (dBm scale)

// RF_METER, voltage input sensing
// V2 1-3-17
// does not include dummy load, measures only voltage input,
// 4k7 input impedance. 40dB attn
// displays volts, watts, dBm
// LCD I2C bus (16x2)
// SDA = A4
// SCL = A5

//LCD library
#include "LiquidCrystal_I2C.h"
#include "math.h"

// chose address of 3F or 27 for LCD
//#define LCDADDR 0x27
#define LCDADDR 0x3F
#define LCDCOLS 16
#define LCDROWS 2

// Analog input pin, ref (measured) mV
// AREF can vary between Arduino, measure the 3.3V line and put AREF value here.
// scale for Vrms to fill bar (10V x 100 = 1000)
#define DCIN A3
#define AREF 3240.0
#define BARSCALE (1023 / 80)


// intercept (dBm), slope (mw/dB), input impedance, attenuator (dB).
// input attenuator is 4k7/51R
#define INTERCEPT -84.0
#define SLOPE 25.0
#define IMP 50.0
#define ATTN 40.0

// bar glyphs, 8 rows (top to bottom) for each glyph, 5 bits per row
uint8_t bar0[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000};
uint8_t bar1[8] = {B00000, B10000, B10000, B10000, B10000, B10000, B00000, B00000};
uint8_t bar2[8] = {B00000, B11000, B11000, B11000, B11000, B11000, B00000, B00000};
uint8_t bar3[8] = {B00000, B11100, B11100, B11100, B11100, B11100, B00000, B00000};
uint8_t bar4[8] = {B00000, B11110, B11110, B11110, B11110, B11110, B00000, B00000};
uint8_t bar5[8] = {B00000, B11111, B11111, B11111, B11111, B11111, B00000, B00000};

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

void setup() {
  Serial.begin(9600);
  
  // init LCD
  lcd.begin();

  lcd.createChar(0, bar0);
  lcd.createChar(1, bar1);
  lcd.createChar(2, bar2);
  lcd.createChar(3, bar3);
  lcd.createChar(4, bar4);
  lcd.createChar(5, bar5);

  lcd.clear();

  // 3.3V connected to AREF
  analogReference(EXTERNAL);

  dispMsg(0, 0, "RF"); // 0-10V bar graph goes to right...
}

void loop() {
  // display dBm etc
  dispDbm();
  delay(100);
}

void dispDbm() {
  int Ain;                  // int = signed 16 bit
  double mW, mV, dBm, Vrms; // double = double 4 x bytes = 32bit


  // calculations for mV input, dBm, mW and Volts, AREF in mV
  Ain = analogRead(DCIN); // returns an int 0-1023
  mV = (double)(Ain * AREF / 1023); // AREF in mV, calculate & convert to double
  dBm = (mV / SLOPE) + INTERCEPT + ATTN; // in doubles
  mW = pow(10.0, (dBm / 10.0)); // in double, out double
  Vrms = sqrt((mW / 1000.0) * IMP); // in double, out double,

  dispBar(3, 0, (dBm + 40) * BARSCALE, 13); // BARSCALE tbd
  
  lcd.setCursor(0, 1);
  if (Vrms < 1.0) {
    lcd.print(Vrms * 1000.0, 0);
    lcd.print("mV ");
  }
  else {
    lcd.print(Vrms, 1);
    lcd.print("V ");
  }

  // dBm
  lcd.print(dBm, 0);
  lcd.print("dBm ");

  // Watts
  if (mW < 1000.0) {
    lcd.print(mW, 0);
    lcd.print("mW  ");
  }
  else {
    lcd.print(mW / 1000.0, 1);
    lcd.print("W   ");
  }
}

// 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 bar at col, row, 0-1023, length
void dispBar(uint8_t c, uint8_t r, int d, int l) {
  uint8_t i, ncol;

  lcd.setCursor(c, r);

  ncol = map(d, 0, 1023, 0, l * 5);

  for (i = 0; i < l; ++i) {
    if(ncol == 0) {
      lcd.write(0);
    }
    else if (ncol >= 5) {
      lcd.write(5);
      ncol -= 5;
    }
    else {
      lcd.write(ncol);
      ncol = 0;
    }
  }
}

Tuesday 21 February 2017

BASIC Tech Group - MyNews 25 - AD8307 Power meter

There's a lot of hot wind about (as usual) on the internet about using the AD8307 as a power meter. The AD8307 is a very clever device implementing a logarithmic amplifier, with RF direct input up to above +10dBm (0.71Vrms, 10mW/50R) and with a 70-80dB range, and so able to measure down to better than -60dBm (0.225mVrms, 0.001uW/50R)!

Used with an Arduino to make the calculations and preceded by a 40dB attenuator it can make a good power meter for up to 100W/50R (70.1V) power measurements. It is connected across the 50R feed cable using a "T" connector. It can also serve as a general purpose RF voltage meter, when not reading across a 50R impedance. The input circuit is:

Screen Shot 2017 02 21 at 10 35 23

The 40dB attenuator is made up from standard value resistors 4k7 and 51R which give better than 1dB accuracy. For general RF voltage measurements its input impedance is around 5K.

FullSizeRender 2

Software

Here's where there are a lot of 'errors' on the web, mainly because when written in C++ for the Arduino not much care has been taken of the type of the variables and lots of hidden type conversions are made by the complier. A clear code is shown below.

// RF_METER, voltage, dBm & Pwr if 50R load
// V2 1-3-17
// 4k7 input impedance. 40dB attn
// LCD I2C bus (16x2)
// SDA = A4
// SCL = A5

//LCD library
#include "LiquidCrystal_I2C.h"
#include "math.h"

// chose address of 3F or 27 for LCD
//#define LCDADDR 0x27
#define LCDADDR 0x3F
#define LCDCOLS 16
#define LCDROWS 2

// Analog input pin, ref (measured) mV
// AREF can vary between Arduino, measure the 3.3V line and put AREF value here.
// scale for 80dB range to fill bar 0-1023
#define DCIN A3
#define AREF 3242.0
#define BARSCALE (1023 / 80)


// intercept (dBm), slope (mw/dB), input impedance, attenuator (dB).
// input attenuator is 4k7/51R
#define INTERCEPT -84.0
#define SLOPE 25.0
#define IMP 50.0
#define ATTN 40.0

// bar glyphs, 8 rows (top to bottom) for each glyph, 5 bits per row
uint8_t bar0[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000};
uint8_t bar1[8] = {B00000, B10000, B10000, B10000, B10000, B10000, B00000, B00000};
uint8_t bar2[8] = {B00000, B11000, B11000, B11000, B11000, B11000, B00000, B00000};
uint8_t bar3[8] = {B00000, B11100, B11100, B11100, B11100, B11100, B00000, B00000};
uint8_t bar4[8] = {B00000, B11110, B11110, B11110, B11110, B11110, B00000, B00000};
uint8_t bar5[8] = {B00000, B11111, B11111, B11111, B11111, B11111, B00000, B00000};

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

void setup() {
  Serial.begin(9600);
  
  // init LCD
  lcd.begin();

  lcd.createChar(0, bar0);
  lcd.createChar(1, bar1);
  lcd.createChar(2, bar2);
  lcd.createChar(3, bar3);
  lcd.createChar(4, bar4);
  lcd.createChar(5, bar5);

  lcd.clear();

  // 3.3V connected to AREF
  analogReference(EXTERNAL);

  dispMsg(0, 0, "RF"); // 0-10V bar graph goes to right...
}

void loop() {
  // display dBm etc
  dispDbm();
  delay(100);
}

void dispDbm() {
  int Ain;                  // int = signed 16 bit
  double mW, mV, dBm, Vrms; // double = double 4 x bytes = 32bit


  // calculations for mV input, dBm, mW and Volts, AREF in mV
  Ain = analogRead(DCIN); // returns an int 0-1023
  mV = (double)(Ain * AREF / 1023); // AREF in mV, calculate & convert to double
  dBm = (mV / SLOPE) + INTERCEPT + ATTN; // in doubles
  mW = pow(10.0, (dBm / 10.0)); // in double, out double
  Vrms = sqrt((mW / 1000.0) * IMP); // in double, out double,

  dispBar(3, 0, (dBm + 40) * BARSCALE, 13); // offset dBm and scale
  
  lcd.setCursor(0, 1);
  if (Vrms < 1.0) {
    lcd.print(Vrms * 1000.0, 0);
    lcd.print("mV ");
  }
  else {
    lcd.print(Vrms, 1);
    lcd.print("V ");
  }

  // dBm
  lcd.print(dBm, 0);
  lcd.print("dBm ");

  // Watts
  if (mW < 1000.0) {
    lcd.print(mW, 0);
    lcd.print("mW  ");
  }
  else {
    lcd.print(mW / 1000.0, 1);
    lcd.print("W   ");
  }
}

// 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 bar at col, row, 0-1023, length
void dispBar(uint8_t c, uint8_t r, int d, int l) {
  uint8_t i, ncol;

  lcd.setCursor(c, r);

  ncol = map(d, 0, 1023, 0, l * 5);

  for (i = 0; i < l; ++i) {
    if(ncol == 0) {
      lcd.write(0);
    }
    else if (ncol >= 5) {
      lcd.write(5);
      ncol -= 5;
    }
    else {
      lcd.write(ncol);
      ncol = 0;
    }
  }
}

Thursday 16 February 2017

BASIC Tech Group - MyNews 24 - Feld-Hell TX

I have already put on this blog sequential multi-frequency Hellschreiber (HELL-S/MT). The original system however uses pulses at a single frequency. It also uses 7x14 fonts (14 half-pixels). So I have a new Arduino sketch (below) with the 7x14 font and transmitting on a single frequency. NT7S has a version that uses timer interrupts running at 245Hz for half-pixels giving rate of 122.5 baud the official timing for FELD-HELL to send the pixels for 4.0816ms which I have used here.

The hardware is the same as before: an Arduino UNO and a Si5351 DDS frequency synthesiser.

IMG 0778

IMG 0779

Here is the reception (which is this case can be done on LSB or USB) on my simple DC RX,

IMG 0776

IMG 0777

and displayed using the Mac version of CocoaModem 2.0, a program able to receive many digital modes...

Screen Shot 2017 02 20 at 13 50 07

Screen Shot 2017 02 20 at 13 49 53

Due to the digital output of the Si5351 there are a lot of pulse sidebands and harmonics which would need to be filtered out.

// HELL_FELD_7x14
// ====== V3 20-2-17
// font is 7 cols x 14 rows of 1/2 pixels
// read from left to right, bottom up
// bottom 2 1/2 pixel rows and top 2 1/2 pixel rows are blank
// 2 x right cols are blank
// so a char occupies 5 cols x 10 half pixels
// characters are transmitted in 400ms, or 400/7 = 57.14ms per col,
// or 57.14/14 per row = 4.0816ms/pixel
// baud rate is 1/(2 x 4.0816) = 122.5 baud, throughput 2.5ch/sec or 25wpm
// =====
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// LCD I2C bus (16x2)
// SDA = A4
// SCL = A5

// Si5351, LCD I2C libraries
#include "si5351.h"
#include "LiquidCrystal_I2C.h"

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

// frequencies, base (cHz)
#define BASEFREQ 707700000  // freqs 7077-7084, 10137-10144, 14060QRP, 14063-14069kHz

// Si5351 module corrections
#define CORRA 18400
#define CORRB 20000

volatile bool proceed = false;

typedef struct glyph {
  char ch ;
  word col[7] ;
} Glyph ;

// table of glyphs 7x14 for each character
Glyph glyphtab[] = {
  {' ', {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'A', {0x07fc, 0x0e60, 0x0c60, 0x0e60, 0x07fc, 0x0000, 0x0000}},
  {'B', {0x0c0c, 0x0ffc, 0x0ccc, 0x0ccc, 0x0738, 0x0000, 0x0000}},
  {'C', {0x0ffc, 0x0c0c, 0x0c0c, 0x0c0c, 0x0c0c, 0x0000, 0x0000}},
  {'D', {0x0c0c, 0x0ffc, 0x0c0c, 0x0c0c, 0x07f8, 0x0000, 0x0000}},
  {'E', {0x0ffc, 0x0ccc, 0x0ccc, 0x0c0c, 0x0c0c, 0x0000, 0x0000}},
  {'F', {0x0ffc, 0x0cc0, 0x0cc0, 0x0c00, 0x0c00, 0x0000, 0x0000}},
  {'G', {0x0ffc, 0x0c0c, 0x0c0c, 0x0ccc, 0x0cfc, 0x0000, 0x0000}},
  {'H', {0x0ffc, 0x00c0, 0x00c0, 0x00c0, 0x0ffc, 0x0000, 0x0000}},
  {'I', {0x0000, 0x0000, 0x0ffc, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'J', {0x003c, 0x000c, 0x000c, 0x000c, 0x0ffc, 0x0000, 0x0000}},
  {'K', {0x0ffc, 0x00c0, 0x00e0, 0x0330, 0x0e1c, 0x0000, 0x0000}},
  {'L', {0x0ffc, 0x000c, 0x000c, 0x000c, 0x000c, 0x0000, 0x0000}},
  {'M', {0x0ffc, 0x0600, 0x0300, 0x0600, 0x0ffc, 0x0000, 0x0000}},
  {'N', {0x0ffc, 0x0700, 0x01c0, 0x0070, 0x0ffc, 0x0000, 0x0000}},
  {'O', {0x0ffc, 0x0c0c, 0x0c0c, 0x0c0c, 0x0ffc, 0x0000, 0x0000}},
  {'P', {0x0c0c, 0x0ffc, 0x0ccc, 0x0cc0, 0x0780, 0x0000, 0x0000}},
  {'Q', {0x0ffc, 0x0c0c, 0x0c3c, 0x0ffc, 0x000f, 0x0000, 0x0000}},
  {'R', {0x0ffc, 0x0cc0, 0x0cc0, 0x0cf0, 0x079c, 0x0000, 0x0000}},
  {'S', {0x078c, 0x0ccc, 0x0ccc, 0x0ccc, 0x0c78, 0x0000, 0x0000}},
  {'T', {0x0c00, 0x0c00, 0x0ffc, 0x0c00, 0x0c00, 0x0000, 0x0000}},
  {'U', {0x0ff8, 0x000c, 0x000c, 0x000c, 0x0ff8, 0x0000, 0x0000}},
  {'V', {0x0ffc, 0x0038, 0x00e0, 0x0380, 0x0e00, 0x0000, 0x0000}},
  {'W', {0x0ff8, 0x000c, 0x00f8, 0x000c, 0x0ff8, 0x0000, 0x0000}},
  {'X', {0x0e1c, 0x0330, 0x01e0, 0x0330, 0x0e1c, 0x0000, 0x0000}},
  {'Y', {0x0e00, 0x0380, 0x00fc, 0x0380, 0x0e00, 0x0000, 0x0000}},
  {'Z', {0x0c1c, 0x0c7c, 0x0ccc, 0x0f8c, 0x0e0c, 0x0000, 0x0000}},
  {'0', {0x07f8, 0x0c0c, 0x0c0c, 0x0c0c, 0x07f8, 0x0000, 0x0000}},
  {'1', {0x0300, 0x0600, 0x0ffc, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'2', {0x061c, 0x0c3c, 0x0ccc, 0x078c, 0x000c, 0x0000, 0x0000}},
  {'3', {0x0006, 0x1806, 0x198c, 0x1f98, 0x00f0, 0x0000, 0x0000}},
  {'4', {0x1fe0, 0x0060, 0x0060, 0x0ffc, 0x0060, 0x0000, 0x0000}},
  {'5', {0x000c, 0x000c, 0x1f8c, 0x1998, 0x18f0, 0x0000, 0x0000}},
  {'6', {0x07fc, 0x0c66, 0x18c6, 0x00c6, 0x007c, 0x0000, 0x0000}},
  {'7', {0x181c, 0x1870, 0x19c0, 0x1f00, 0x1c00, 0x0000, 0x0000}},
  {'8', {0x0f3c, 0x19e6, 0x18c6, 0x19e6, 0x0f3c, 0x0000, 0x0000}},
  {'9', {0x0f80, 0x18c6, 0x18cc, 0x1818, 0x0ff0, 0x0000, 0x0000}},
  {'*', {0x018c, 0x0198, 0x0ff0, 0x0198, 0x018c, 0x0000, 0x0000}},
  {'.', {0x001c, 0x001c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'?', {0x1800, 0x1800, 0x19ce, 0x1f00, 0x0000, 0x0000, 0x0000}},
  {'!', {0x1f9c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'(', {0x01e0, 0x0738, 0x1c0e, 0x0000, 0x0000, 0x0000, 0x0000}},
  {')', {0x1c0e, 0x0738, 0x01e0, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'#', {0x0330, 0x0ffc, 0x0330, 0x0ffc, 0x0330, 0x0000, 0x0000}},
  {'$', {0x078c, 0x0ccc, 0x1ffe, 0x0ccc, 0x0c78, 0x0000, 0x0000}},
  {'/', {0x001c, 0x0070, 0x01c0, 0x0700, 0x1c00, 0x0000, 0x0000}},
} ;

#define NGLYPHS (sizeof(glyphtab)/sizeof(glyphtab[0]))

// freq variable
uint64_t freq = BASEFREQ; // base or bottom freq of pixels

// message to send
char msg[] = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"; // max 16 char for LCD display, can transmit longer

// dds object
Si5351 dds;

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

void setup() {
  // init I2C Wire & lcd
  lcd.begin();

  // init dds
  dds.init(SI5351_CRYSTAL_LOAD_8PF, 0,CORRB); // corr shield "B"

  // set drive output 8mA (about 4dBm into 50R)
  dds.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);

  dds.set_freq(freq, SI5351_CLK0);                   // output freq

  // disable all outputs
  dds.output_enable(SI5351_CLK0, 0);
  dds.output_enable(SI5351_CLK1, 0);
  dds.output_enable(SI5351_CLK2, 0);

  // initial display
  dispMsg(0, 0, " Hellschreiber  ");        // title & clear line

  // Set up Timer1 for interrupts at 245 Hz = 2 x 122.5 baud, as using 1/2 rows
  // 245Hz equals 4.01816ms / row
  noInterrupts();          // Turn off interrupts.
  TCCR1A = 0;              // Set entire TCCR1A register to 0; disconnects
  //   interrupt output pins, sets normal waveform
  //   mode.  We're just using Timer1 as a counter.
  TCNT1  = 0;              // Initialize counter value to 0.
  TCCR1B = (1 << CS10) |   // Set CS10 bit; this specifies no pre-scaling
           (1 << WGM12);        //   of the system clock.
  TIMSK1 = (1 << OCIE1A);  // Enable timer compare interrupt.
  OCR1A = 0xFF1A;          // Set up interrupt trigger count;
  //   16MHz clock / 0xFF1A counts == 245.00045938Hz
  interrupts();            // Re-enable interrupts.

  delay(5000);
}

// main loop, look up index of each msg character in glyphtab, send
void loop() {
  unsigned int c;

  dispMsg(0, 0, "                 ");        // clear title
  dispMsg(0, 1, msg);                        // disp msg
  dispFreq(1, 0, freq, 1);                   // display freq
  dispMsg(11, 0, " TX  ");

  c = 0;                                     // start at character 0
  while (msg[c] != '\0') {                   // up to end of string '\0'
    sendChar(msg[c]);                   // look up index in glyphtab
    c++;                                     // next character
  }

  dispMsg(0, 1, "                ");         // clear line
  dispMsg(0, 0, " Hellschreiber  ");         // replace title

  delay(5000);
}

// Timer interrupt vector.  This toggles the variable we use to gate
// each column of output to ensure accurate timing.  Called whenever
// Timer1 hits the count set below in setup().
ISR(TIMER1_COMPA_vect) {
  proceed = true;
}

// find index of character in glyphtab then send the pixels
void sendChar(char c) {
  int ndx, x, y;

  for (ndx = 0; ndx < NGLYPHS; ndx++) {                // scan for index
    if (glyphtab[ndx].ch == c) {                       // if found, send
      for (x = 0; x < 7; x++) {                        // scan 7 cols 0 - 6
        for (y = 0; y < 14; y++) {                     // scan 14 rows 0 - 13
          if (glyphtab[ndx].col[x] & (1 << y)) {       // look for '1's
            dds.output_enable(SI5351_CLK0, 1);         // 1 = pixel on
          }
          else {
            dds.output_enable(SI5351_CLK0, 0);         // 0 = pixel off
          }
          
          // wait for timer interrupt
          while (!proceed); // timer interrupt set proceed = true
          
          // reset flag
          noInterrupts();   // Turn off interrupts; just good practice...
          proceed = false;  // reset the flag for the next character...
          interrupts();     // and re-enable interrupts.
        }
      }
    }
    
  }
}


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

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

Monday 13 February 2017

BASIC Tech Group - MyNews 23 - New Arduino library for AD9850

There are a bunch of libraries on the net for the AD9850 synthesiser. But I wanted a one that was simple, but included the possibility of setting sub hertz frequencies, for example for WSPR use. The ADIC 9850 is of most use as an analog output frequency generator so I called mine ADS9850 (Analog Digital Synthesiser).

i have combined the best of those out there and made my own. Call ADS9850, you can find it here. It has just three functions:

void begin(int W_CLK, int FQ_UD, int DATA, int RESET);
void setFreq(unsigned long Hz, float Chz);
void calibrate(unsigned long calHz);
Example code

TAKE CARE! - Not yet tested as I am waiting for my AD9850 module to be delivered. I will report later if it works, but it complies OK.
// using ADS9850 library
// output a frequency 7100001.46Hz

#include "ADS9850.h"

// pins
#define W_CLK 8
#define FQ_UD 9
#define DATA 10
#define RESET 11

// create ads object
ADS9850 ads;

void setup() {
  ads.begin(W_CLK, FQ_UD, DATA, RESET);

  ads.calibrate(124999000); // calibrated XTAL freq

  ads.setFreq(7100000, 146); // output 7100001.46 Hz
}

void loop() {
  // put your main code here, to run repeatedly:

}

Sunday 5 February 2017

BASIC Tech Group - MyNews 21 - TX received WSPR

I have been tinkering around with WSPR for a while, but with my less than useless antenna - a 4ft whip standing on my shack table! - I have not been received anywhere.

But now I have added an amplifier.

IMG 0830

On the right is the LCD display of the message being sent, the "20" is a guess of the ERP going out... next along is the stack of an Arduino UNO at the bottom, next up a DDS built around the Si5351 synthesiser plus an RTC built around a DS3231 and on top a new shield which has two parts, one is a LPF and buffer amp for the Si5351 output, to the left, on the right is an RF voltmeter built around the AD8307 device (not use for these transmissions).

Then next to the left is an MMIC linear amplifier, found on eBay, 1-900MHz they say. 1-2W out, 12V operation.

IMG 3358

IMG 5629

IMG 8821

This is the result this morning on WSPRnet

Screen Shot 2017 02 05 at 11 19 50