Friday, 9 June 2017

BASIC Tech Group - MyNews - 46 THE PIXIE CHALLENGE

The PIXIE CHALLENGE

This should be fun! On eBay you will find lots of very low cost kits for a 40m Transceiver called the "Pixie". This is a simple two transistor - Oscillator and PA, and a receiver - using the PA transistor as an amplifier followed by a diode detector and LM386 amplifier IC. It is a cute and interesting design.

It took me about 3 hours to sort out the components and identify the resistors, capacitors and coils (looking like RF chokes), then to build the board. It needs a morse key, a headphone or external amplifier and loudspeaker, a 9-12V supply (I used 6 x AA batteries, you can also use a simple PP3) and an antenna. I attached a 2m length of wire as an antenna - as the challenge is intended to make a contact over a short distance - a few tens of metres. It is also better to have ground connection or radial.

Take note that from my measurements the TX on 7023kHz has lots of harmonics, for example the second harmonic is less than 30dB down, which is poor and probably not legal. The RX also seems to radiate a low level signal at the RIT higher frequency.

THE CHALLENGE

The members of the Banbury Amateur Radio Society (BARS) will be challenged to take two of our "Constructor" evenings to each build a Pixie, get it going and make a CW QSO - minimum exchange of call signs and reports with acknowledgements. First couple to make a QSO will get a prize. Simple QSO might be:

CQ DE G3YWX K
G3YWX DE G3QAB KN
G3QAB DE G3YWX UR 599 K
R UR RST 599 K
R 599 SK
To set up this challenge I purchased one of the Pixie kits here. and it arrived in a couple of weeks. The circuit is a xtal oscillator RIT tuneable a kHz or so from the XTAL frequency of 7023kHz by a varicap diode. I built it and first tested the RX using my Arduino AD9851 VFO on a frequency of 7023.00kHz.

IMG 1197

Battery (6 x AA), morse key connection, audio output and antenna. And my VFO in the small blackbox.

The RX seems to be reasonably sensitive, but an external audio amplifier is a good idea. Next I tested the TX, and connected the antenna output to my RF Meter capable of measuring RF power from a few mW to 10W.

IMG 1199

The output was around 780mW into a 50R dummy load.

Arduino keyer

Now I am lazy about morse code (and terrible at it, as are other members of BARS - thus the challenge), but I wrote a short sketch for an Arduino Uno to send a fixed short text message or a message you type in, automatically. The Arduino controls a relay from one of its outputs which in turn keys the Pixie TX.

IMG 1203

The reception was by my Elektor SDR feeding the HDSDR software, with its audio output fed to the Argo spectrum display software.

IMG 1205

IMG 1204

You can read the morse message in the Argo window.

Both software programs are running on my very low cost (£180) Windows 10 PC! I used a low cost 96kHz USB analog/digital convertor.

CODE
// PIXIE_MORSE - relay driver for sending morse message
// V1.1 9-5-17
// thanks to F0GOJ for some of the varicode
// Output to a relay, HIGH = TX
// board LED also on pin
// RELAY < PTT (5)

// relay pin
#define RELAY 5

//speed WPM
#define WPM 5

int repeat = 10000; // erpeat in 10 secs

// message to send
char msg[] = "SECRET MESSAGE GOES HERE";

// morse varicode MSB 1st, and length
byte morseVaricode[2][59] = {
  { 0, 212, 72, 0, 144, 0, 128, 120, 176, 180,
    0, 80, 204, 132, 84, 144, 248, 120, 56, 24,
    8, 0, 128, 192, 224, 240, 224, 168, 0, 136,
    0, 48, 104, 64, 128, 160, 128, 0, 32, 192,
    0, 0, 112, 160, 64, 192, 128, 224, 96, 208,
    64, 0, 128, 32, 16, 96, 144, 176, 192
  },
  { 7, 6, 5, 0, 4, 0, 4, 6, 5, 6,
    0, 5, 6, 6, 6, 5, 5, 5, 5, 5,
    5, 5, 5, 5, 5, 5, 6, 6, 0, 5,
    0, 6, 6, 2, 4, 4, 3, 1, 4, 3,
    4, 2, 4, 3, 4, 2, 2, 3, 4, 4,
    3, 3, 1, 3, 4, 3, 4, 4, 4
  }
};

void setup() {
  // relay output
  pinMode(RELAY, OUTPUT);

  // delay before start
  delay(repeat);
}

void loop() {
  sendMsg(msg);            // send CW message
  delay(repeat);           // repeat
}

// send message at wpm
void sendMsg(char *m) {
  bool val;
  byte c, n, ndx, bits, vCode;;
  int dotTime, dashTime;

  // calculate dot time
  dotTime = 1200 / WPM;                           // Duration of 1 dot
  dashTime = 3 * dotTime;                         // and dash

  //send msg in morse code
  c = 0;
  while (m[c] != '\0') {
    m[c] = toupper(m[c]);                        // u.c.just in case

    if (m[c] == ' ') {                           // catch ASCII SP
      delay(7 * dotTime);
    }
    else if (m[c] > ' ' && m[c] <= 'Z') {
      ndx = m[c] - ' ';                         // index to varicode 0-58

      vCode = morseVaricode[0][ndx];            // get CW varicode data
      bits = morseVaricode[1][ndx];             // get CW varicode length

      if (bits != 0) {                          // if not characters # % < >
        for (n = 7; n > (7 - bits); n--) {      // Send CW character, MSB(bit 7) 1st
                                                // 0 for dot, 1 for dash
          val = bitRead(vCode, n);              // look up varicode bit

          digitalWrite(RELAY, HIGH);            // send dot or dash
          if (val == 1) delay(dashTime);
          else delay(dotTime);
          digitalWrite(RELAY, LOW);

          delay(dotTime);                       // for 1 dot space between dots|dashes
        }
      }
      delay(dashTime);                          // 1 dash space between characters in a word
    }
    c++;                                        // next character in string
  }
}

The next code needs the Arduino to be connected to a serial terminal program, you can use the "serial monitor" of the Arduino IDE or your own terminal program - I use "iSerialTerm" on my MacBook.

MORE CODE
// PIXIE_MORSE_TEXT - relay driver for sending morse message
// V1.1 16-6-17
// thanks to F0GOJ for some of the varicode
// Output to a relay, HIGH = TX
// board LED also on pin
// RELAY 5 PTT

// relay pin
#define RELAY 5

//speed WPM
#define WPM 5

// message to send
char msg[40];

// morse varicode MSB 1st, and length
byte morseVaricode[2][59] = {
  { 0, 212, 72, 0, 144, 0, 128, 120, 176, 180,
    0, 80, 204, 132, 84, 144, 248, 120, 56, 24,
    8, 0, 128, 192, 224, 240, 224, 168, 0, 136,
    0, 48, 104, 64, 128, 160, 128, 0, 32, 192,
    0, 0, 112, 160, 64, 192, 128, 224, 96, 208,
    64, 0, 128, 32, 16, 96, 144, 176, 192
  },
  { 7, 6, 5, 0, 4, 0, 4, 6, 5, 6,
    0, 5, 6, 6, 6, 5, 5, 5, 5, 5,
    5, 5, 5, 5, 5, 5, 6, 6, 0, 5,
    0, 6, 6, 2, 4, 4, 3, 1, 4, 3,
    4, 2, 4, 3, 4, 2, 2, 3, 4, 4,
    3, 3, 1, 3, 4, 3, 4, 4, 4
  }
};

void setup() {
  // Serial
  Serial.begin(9600);

  // relay output
  pinMode(RELAY, OUTPUT);
}

void loop() {
  if (getMsg(msg) == true) {
    Serial.println(msg);
    sendMsg(msg);            // send CW message
  }
  clearBuf(msg);
}

// get input msg[] U.C.
bool getMsg(char *m)
{
  char ch;
  int n;

  n = 0;
  if (Serial.available() > 0) {      // if input
    delay(20);                       // let USB catch up
    while (Serial.available() > 0) { // get input
      ch = Serial.read();            // use upper case as input
      if (ch == '\n') ch = '\0';     // end of text
      m[n++] = ch;
      delay(20);                     // let USB catch up
    }
    return true;                     // got input
  }
  return false;                      // no input
}

// clear msg and buffer
void clearBuf(char *m) {
  m[0] = '\0';
  while (Serial.available() > 0) Serial.read();
}

// send message at wpm
void sendMsg(char *m) {
  bool val;
  byte c, n, ndx, bits, vCode;;
  int dotTime, dashTime;

  // calculate dot time
  dotTime = 1200 / WPM;                           // Duration of 1 dot
  dashTime = 3 * dotTime;                         // and dash

  //send msg in morse code
  c = 0;
  while (m[c] != '\0') {
    m[c] = toupper(m[c]);                        // u.c.just in case

    if (m[c] == ' ') {                           // catch ASCII SP
      delay(7 * dotTime);
    }
    else if (m[c] > ' ' && m[c] <= 'Z') {
      ndx = m[c] - ' ';                         // index to varicode 0-58

      vCode = morseVaricode[0][ndx];            // get CW varicode data
      bits = morseVaricode[1][ndx];             // get CW varicode length

      if (bits != 0) {                          // if not characters # % < >
        for (n = 7; n > (7 - bits); n--) {      // Send CW character, MSB(bit 7) 1st
          // 0 for dot, 1 for dash
          val = bitRead(vCode, n);              // look up varicode bit

          digitalWrite(RELAY, HIGH);            // send dot or dash
          if (val == 1) delay(dashTime);
          else delay(dotTime);
          digitalWrite(RELAY, LOW);

          delay(dotTime);                       // for 1 dot space between dots|dashes
        }
      }
      delay(dashTime);                          // 1 dash space between characters in a word
    }
    c++;                                        // next character in string
  }
}

Friday, 26 May 2017

BASIC Tech Group - MyNews - 45 Testing PA, WSPR, more on SWR

Finally the 20dB attenuator I ordered from China has arrived. I need this as even the lowest output setting of my VFO is too much drive power for the small 3W PA amplifier I have. So now I can turn it down to get the PA operating in its linear range. So I have been trying agin to send WSPR weak signals and am looking for a result on WSPRnet.org.

IMG 1095

Here's the set up, from right to left

- Windows PC (I am a Mac man, but this seems not so bad) running HDSDR and WSTJ software, using VB to pipe audio from one to the other.

- The SDR receiver, which is an Elektor SDR Arduino shield and an Arudino Uno.

- The VFO, which has an AD9851, RTC (using a DS3231) and a buffer amp. Software is loaded to run WSPR on 40m at 7040.1kHz, the 20dB attenuator is plugged in the back of the VFO.

The small PA amplifier module, from eBay, running 2W output from a supply of 13.8V

IMG 1097

More on SWR

In the last post I talked about an SWR meter using a normal bridge circuit. The output from the bridge FWD & REF RF was handled by two AD9307 log amplifiers and fed to the analog inputs of an Arduino Nano. This drove an OLED display showing FWD power and SWR. Fiddle as I might I could not get stable results, probably as a lot of the circuitry was on a plug-and-play breadboard. So I am having a rethink.

I have browsed the web for ages and this is what I have come up with. I will wire this up on a proper PCB to test it:

IMG 1096

I have abandoned the AD8307s and gone back to what most people use - a couple of germanium low drop diodes. The PA amplifier this will be used with is 2-3W output, so their should be enough volts to run the thing. Calibration can be done in software.

As you can see I want to retain the measurement of RF output - just the voltage on the line, which can be shown as power when the system is matched to a 50R load. For this I am using a tried and trusted AD8307 circuit which will display 10mW to 10W. This AD8307 output will also be used to trigger the auto RX/TX switching of the PA.

Wish me luck.

Monday, 15 May 2017

BASIC Tech Group - MyNews - 44 The SWR meter

As part of my planned QRP PA, giving 3.2W output on 40, 30 & 20m, I am including an SWR meter. These look simple but are far from it.

The circuit I am trying to build is based on an Arduino Nano to do the calculations and drive a OLED display, and a couple of AD8307 log detectors to measure the Forward and Reflected power outputted from a conventional circuit using tow 1:10 transformers to measure the load line current and voltage.

Screen Shot 2017 05 15 at 12 46 10

When a wave travels from TRX to ANT a forward voltage FWD is output, when a reflected wave travels from ANT to TRX a reflected voltage REF is output. That's the theory anyway. (see this plagiarism article) The actual circuit looks like this

Screen Shot 2017 05 14 at 14 42 57

So far so good.

PROGRESS SO FAR

What I have done so far is to buikd the AD8307s and Nano circuit,

IMG 1086

and write some software to display a couple of bars for FWD power, SWR, display the values and say which band I am operating on. This involves detecting (over an average of ten measurements) the AD8307 outputs in millivoltss, converting this to dBm according to the ICs slope of 25mV/dB, converting this to dBm and then milliwatts across the 50R

IMG 1085resistors.

I have found that individual calibration is needed to get the same sensitivity from each AD8307, see code below. The slopes seem to be equal, but the intercepts different. Anyway I am now getting roughly the correct SWR for a 20dB Return Loss.

And at the moment I am stuck as my SWR transformers do not seem to giving the right outputs. I am driving the TXR end from my AD9851 VFO buffer outputs with 10mW and using a load of 50R at the output, built-into my RF Meter box. And the results are WRONG - Negative SWR! Reflected power bigger than Forward power... Why?

Code

// PA_LPF_TRX_OLED
// V0.95 16-5-17 need h/w for testing/calibTFMRLOSSn
// to add TRX

#include "Oled_128X64_I2C.h"

// analog reference (mV), A/D count, read avg
// slope and TX/display trigger (mW)
#define AREF 3300
#define ADCOUNT 1023
#define READAVG 10
#define TXTRIG 2

// analog inputs, band button, band relays, TXRX relay (PTT)
#define FWDPIN A0
#define REFPIN A1
#define SW 4
#define BAND1 7
#define BAND2 6
#define PTT 5

// display variables
double mwFwd, mwRef, swr;

// display text for bands
char bandTxt[][4] = {
  "40m", "30m", "20m"
};

byte band;

bool txFlag;

void setup() {

  Serial.begin(9600);

  pinMode(SW, INPUT_PULLUP);
  pinMode(BAND1, OUTPUT);
  pinMode(BAND2, OUTPUT);
  pinMode(PTT, OUTPUT);

  // analog ref AREF
  analogReference(EXTERNAL);

  oled.begin();

  // start on 40m
  band = 0;

  // TX off
  txFlag = false;
}

void loop() {
  int aFwd, aRef, n;
  double mV, dBm;

  // read SWR bridge inputs and average
  aFwd = 0;
  aRef = 0;
  for (n = 0; n < READAVG; n++) {
    aFwd += analogRead(FWDPIN); // typ +20dB level, gives 0dB at AD8307, or 2.5V (775 d/a)
    delay(50);
    aRef += analogRead(REFPIN); // typ -10dB level, gives -30dB at AD8307, or 1.5V (465 d/a)
  }
  aFwd /= READAVG;
  aRef /= READAVG;

  // 1. Adj slope for 20dB drop (470/50R), plot
  // 2. Adj intercept for correct mW
  // aFwd to mW, slope & intercept
  mwFwd = convert(aFwd, 25.0, -86);
  mwRef = convert(aRef, 25.0, -88);

  Serial.print("F: ");
  Serial.print(aFwd);
  Serial.print("\t");
  Serial.print(mwFwd, 3);

  Serial.print("\t R: ");
  Serial.print(aRef);
  Serial.print("\t");
  Serial.println(mwRef, 3);


  // calc SWR
  swr = ( 1 + sqrt(mwRef / mwFwd) ) / (1 - sqrt(mwRef / mwFwd) ); // calc SWR

  // band change
  if (digitalRead(SW) == LOW) {
    while (!digitalRead(SW));
    if (band < 2) band++;
    else band = 0;
    bandSw();
  }

  // switch to TX at 100mW
  if (mwFwd > TXTRIG) {
    digitalWrite(PTT, HIGH); // TX = HIGH
    txFlag = true;
  }
  else {
    digitalWrite(PTT, LOW);
    txFlag = false;
  }

  dispUpdate();

  delay(50); // loop stability
}

// convert A/D count, to mW
double convert(int aIn, double sl, double cal) {
  double mV, dBm;

  mV = (double)(map(aIn, 0, ADCOUNT, 0, AREF));
  dBm = (mV / sl) + cal;
  return pow(10.0, (dBm / 10.0));

}

// band relays, wiring HIGH = relay ON
void bandSw() {
  switch (band) {
    case 0:
      digitalWrite(BAND1, HIGH);  // 40m
      digitalWrite(BAND2, HIGH);
      break;
    case 1:
      digitalWrite(BAND1, HIGH); // 30m
      digitalWrite(BAND2, LOW);
      break;
    case 2:
      digitalWrite(BAND1, LOW); // 20m
      digitalWrite(BAND2, LOW);
      break;
  }
}

//=====PICTURE LOOP
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(20, 0, "QRP POWER AMP"); // title

    dispMsgS(0, 15, "FWD");
    dispMsgS(0, 25, "SWR");
    dispMsg(50, 35, "FWD");
    dispMsg(50, 50, "SWR");
    dispMsgL(10, 40, bandTxt[band]); // band "40m" "30m" "20m" large font

    // values display active only on TX
    if (txFlag == false) {
      dispBar(20, 15, 5, 0); // blank bars
      dispBar(20, 25, 5, 0);
    }
    else {
      dispBar(20, 15, 5, mwFwd / 50 ); // 0-5000mW = 0-100bar
      dispBar(20, 25, 5, (swr - 1) * 25); // 1-5 = 0-100bar
      if (mwFwd > 1000) {
        dispNum(80, 35, mwFwd / 1000, 1);
        dispMsg(110, 35, "W");
      }
      else {
        dispNum(80, 35, mwFwd, 0);
        dispMsg(110, 35, "mW");
      }
      dispNum(80, 50, swr, 2);
    }
  } while (oled.nextPage());
}



Thursday, 11 May 2017

BASIC Tech Group - MyNews - 43 Next project, a QRP PA

The final box in my chain of QRP station items for digital transmissions is to be a 3.2W QRP PA. The block diagram for this is:

IMG 1082

I have found a 3.2W RF amplifier module on eBay which I will use. It has a gain of 35dB and works from a +12V supply (I tried another one to play with and it worked well, but was only 2W). To drive this from my VFO I will use an external 20dB attenuator and adjust the output with the variable gain buffer of the VFO.

Next in the chain are a set of 3 switched LPFs for 40, 30 & 20m followed by a SWR bridge. All is controlled by an Arduino Nano microcomputer. This switches the LPF relays, reads the SWR bridge and displays FWD forward power and SWR on an OLED display. RX/TX switching is automatic when ever the PA forward output is above 100mW. So far I have developed some provisional software, you can see the OLED display, and a wired up an SWR detection board. Now to breadboard the AD8307s for detection of FWD & REF outputs from the bridge, and drive the Nano analog inputs for the display...

IMG 1081

By the way in the background you can see my SDR-ELEKTOR, RF_POWER_METER and VFO running Hellschreiber S-MT software transmitting a HELL_S-MT beacon signal, displayed on the screen in Argo software:

Screen Shot 2017 05 11 at 14 31 28

Wednesday, 26 April 2017

BASIC Tech Group - MyNews - 42 Boxing them up

I have, finally, got to the point of putting my projects in boxes. Here you can see, from top to bottom,

The Elektor SDR, with OLED display and a rotary encoder to select 80, 40 & 20m centre frequencies in 50kHz steps.

The RF meter, displaying the RF output from the VFO, it can display 0.1uW up to 10W power and has a switchable 50R dummy load inside.

The VFO, which is programmable as a simple VFO 1-70MHz, in steps of 10Hz to 1MHz. But can also be programmed with various Arduino sketches as a Beacon for CW, PSK & RTTY, as a JT65 transmistter and a WSPR transmitter. The output is -5 to +15dBm adjustable.

IMG 1036 Box stack

Sketches

The current range of sketches I have are:

Screen Shot 2017 05 04 at 14 55 44

These sketches all use the OLED display, and are written for the AD9851 synthesiser - except the SDR which uses the Si5351 sythesiser.

From the top these are for

BCN_CW_OLED - a CW beacon transmitting a short programable message at regular selectable intervals, uses an OLED display

IMG 1055 Argo view on PC

BCN_PSK_OLED - same for PSK31

IMG 1076 PSK31 Beacon running on VFO

Screen Shot 2017 05 09 at 11 57 02 PSK reception on HDSDR on MacBook

Screen Shot 2017 05 09 at 11 56 26 PSK31 decode on MultiMode Cocoa on MacBook

BNC_RTTY_OLED - same for RTTY

DATE_TIME_OLED - a simple date and time display that runs on the Arduino in any of the boxes as they have the same OLED connections and display libraries.

DATE_TIME_SET_OLED - an important sketch to set the time of the RTC in the VFO, this has to be set to 1sec accuracy for WSPR & JT65 transmissions

HELL_FELD_7x14_OLED - Hellschreiber message transmitter, with a standard 7x14 font

HELL_S-MT_5x7_OLED - another version of Hellschreiber using sequencial multi tone transmission and a 5x7 font

IMG 1054 Argo on PC

JT65_ADS51_TEXT_OLED - JT65 transmission of simple messages, input via a serial comms program over USB

IMG 1058 VFO running JT65

IMG 1069 HDSDR on PC

IMG 1070 WSJT-X on PC (Mac version crashes... need to sort out memory shared size)

RF_METER_OLED - a sketch for the RF Meter box, displaying RF bar graph 0.1uW to 10W, mV, dBm and power (when internal 50R dummy load active)

SDR_ELEKTOR_TUNE_OLED - a sketch for the Si5351 synthesiser used on the Elektor SDR board, with frequency selection in 50kHz steps over the 80, 40 & 20m bands

VFO_ADS51_OLED - a general purpose VFO 1-70MHz in steps of 10Hz to 1MHz.

VOLT_OLED - not a sketch for these boxes, but a simple DC voltmeter (0-5V) usng the Arduino analog input A0, and displaying on the OLED display. It also calculates the dB of the input and reads dBm if measurements are made across a 50R load. Good for a very low cost voltmeter using an Arduino Nano...

WSPR_ADS51_OLED - programmable WSPR transmitter, currently set to 40m (7040.1kHz, dial 7038,6kHz reception).



That's a full 2 years on and off work but I am nearing completion of my station. Planned are two more boxes - a complete QRP SDR transceiver and a 2-3W PA with 80, 40, 20m LPF and SWR display.

All Code and Libraries download.

Wednesday, 19 April 2017

BASIC Tech Group - MyNews - 41 Moving from LCD to OLED displays - a new header file

I am planning to mount a number of my projects in some, quite small, instrument boxes. I will build a VFO, an RF METER, an SDR, and a PA/LPF/SWR. These boxes will use 1.3" OLED displays. (Note: works also for the smaller 0.96" OLEDs).

IMG 0992

I plan to tidy up the sketches i have for driving OLED displays and consolidate them in a new header file, I call Oled_128X64_I2C.h. This header includes six functions used for displaying the data I need across many applications. They are

OLED Functions dispBar - display a bar graph at x, y, height h and length l, with a surrounding box
dispFreq - display, in a larger font, the frequency at x, y, in d decimal places
dispMsg - display a general text message at x, y
dispNum - display a general number at x, y, d decimal places
dispDate- display the date day-of-week, day, month, year dispTime - display the time at x, y, hours, minute, seconds
dispStep - display the VFO frequency step when tuning, at x, y

The variables used are shown in the functions' outlines above.

Example

Here is an example of the use of the new header file

// test OLED_128x64_I2C with OLED SH1106 display

#include "Oled_128X64_I2C.h"

// display values - global values used to pass them to dispUpdate() function
double a = 7100000;   // freq
double b = 0;          // centi freq
unsigned int c = 1000; // step
byte hr = 12;          // time
byte mn = 3;
byte sc = 33;
double n = 345.6;      // a number
byte hT = 5;          // bar height and length
byte bL = 45;

void setup() {
  oled.begin();
}

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

// picture loop, display definition data, what, where
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(0, 0, "RF");
    dispBar(15, 3, hT, bL);
    dispFreq(10, 15, a, b, 2);
    dispNum(45, 35, n, 2);
    dispTime(10, 50 , hr, mn, sc);
    dispStep(80, 50, c);
  } while ( oled.nextPage() );
}

You can see how simple it makes writing the sketch. Every thing needed, including the constructor for the 1.3" I2C OLED display is in the header file.

IMG 0991 The result of the code above.

IMG 0983 My desk full of OLED displays for my projects.

Code
// oled_128X64_I2C.h
// V1.5 21-4-17 added small & large versions, and date display
// defines oled pins, creates "oled" object for 128x64 SH1106 display
// step now an unsigned int
// functions usage
// void dispBar(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t h, u8g2_uint_t l)
// void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d)
// void dispMsgS(u8g2_uint_t x, u8g2_uint_t y, char *m)
// void dispMsg(u8g2_uint_t x, u8g2_uint_t y, char *m)
// void dispMsgL(u8g2_uint_t x, u8g2_uint_t y, char *m)
// void dispNum(u8g2_uint_t x, u8g2_uint_t y, double n, uint8_t d)
// void dispNumL(u8g2_uint_t x, u8g2_uint_t y, double n, uint8_t d)
// void dispDate(u8g2_uint_t x, u8g2_uint_t y, byte dw, byte da, byte mo, byte yr)
// void dispTime(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s)
// void dispTimeL(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s)
// void dispStep(u8g2_uint_t x, u8g2_uint_t y, unsigned int s)


#include "U8g2lib.h"
#include "Wire.h"

// oled object, SH1106 controller, 128X64, HW I2C and normal orientation R0
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0);

//=====FUNCTIONS
// display bar at x, y, h)eight, l)ength (0-128 pixels)
void dispBar(u8g2_uint_t x, u8g2_uint_t y, byte h, byte l) {
  byte n;

  oled.drawFrame(x, y, 100, h+1);
  for ( n = 0; n < l; n++) {
    oled.drawLine(x + n, y, x  + n, y + h);
  }
}

// display freq at x, y, f (Hz), cf (cHz), d)ecimal places (max 3)
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, byte d) {

  // sets font, cursor position and displays freq
  oled.setFont(u8g2_font_10x20_tf); // font
  oled.setFontPosTop(); // origin top

  f = f / 1000.0;
  cf = cf / 100000.0;

  oled.setCursor(x, y);
  oled.print(f + cf, d);
  oled.print("kHz");
}

// fonts github.com/olikraus/u8g2/wiki/fntgrpx11

// display small message at at x), y), *m)essage
void dispMsgS(u8g2_uint_t x, u8g2_uint_t y, char *m) {
  // sets font, cursor position and displays message
  oled.setFont(u8g2_font_5x8_tf); // font
  oled.setFontPosTop();
  oled.setCursor(x, y);
  oled.print(m);
}

// display message at at x), y), *m)essage
void dispMsg(u8g2_uint_t x, u8g2_uint_t y, char *m) {
  // sets font, cursor position and displays message
  oled.setFont(u8g2_font_7x13_tf); // font
  oled.setFontPosTop();
  oled.setCursor(x, y);
  oled.print(m);
}
// display large message at at x), y), *m)essage
void dispMsgL(u8g2_uint_t x, u8g2_uint_t y, char *m) {
  // sets font, cursor position and displays message
  oled.setFont(u8g2_font_10x20_tf); // font
  oled.setFontPosTop();
  oled.setCursor(x, y);
  oled.print(m);
}

// display number at x), y), n)umber (double), d)ecimal places
void dispNum(u8g2_uint_t x, u8g2_uint_t y, double n, byte d) {
    // sets font, cursor position and displays number
  oled.setFont(u8g_font_7x14); // fix font for now
  oled.setFontPosTop();
  oled.setCursor(x, y);
  oled.print(n, d);
}

// display number large at x), y), n)umber (double), d)ecimal places
void dispNumL(u8g2_uint_t x, u8g2_uint_t y, double n, byte d) {
    // sets font, cursor position and displays number
  oled.setFont(u8g2_font_10x20_tf); // font
  oled.setFontPosTop();
  oled.setCursor(x, y);
  oled.print(n, d);
}

// display date
void dispDate(u8g2_uint_t x, u8g2_uint_t y, byte dw, byte da, byte mo, byte yr) {
 // sets font, cursor position and displays message
  oled.setFont(u8g_font_7x14); // fix font for now
  oled.setFontPosTop();
  oled.setCursor(x, y);
  
  switch (dw) {
    case 1:
      oled.print("Mon");
      break;
    case 2:
      oled.print("Tue");
      break;
    case 3:
      oled.print("Wed");
      break;
    case 4:
      oled.print("Thu");
      break;
    case 5:
      oled.print("Fri");
      break;
    case 6:
      oled.print("Sat");
      break;
    case 7:
      oled.print("Sun");
      break;
  }

  oled.print(" ");
  oled.print(da);

  oled.print(" ");
  switch (mo)
  {
    case 1:
      oled.print("Jan");
      break;
    case 2:
      oled.print("Feb");
      break;
    case 3:
      oled.print("Mar");
      break;
    case 4:
      oled.print("Apr");
      break;
    case 5:
      oled.print("May");
      break;
    case 6:
      oled.print("Jun");
      break;
    case 7:
      oled.print("Jul");
      break;
    case 8:
      oled.print("Aug");
      break;
    case 9:
      oled.print("Sep");
      break;
    case 10:
      oled.print("Oct");
      break;
    case 11:
      oled.print("Nov");
      break;
    case 12:
      oled.print("Dec");
      break;
  }
  oled.print(" ");
  oled.print("20");
  oled.print(yr);
}

// display time HH:MM:SS at x), y)
void dispTime(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s) {
  // sets font, cursor position and displays message
  oled.setFont(u8g_font_7x14); // fix font for now
  oled.setFontPosTop();
  oled.setCursor(x, y);
  if (h < 10)
    oled.print("0");
  oled.print(h);
  oled.print(":");
  if (m < 10)
    oled.print("0");
  oled.print(m);
  oled.print(":");
  if (s < 10)
    oled.print("0");
  oled.print(s);
}

// display time HH:MM:SS at x), y)
void dispTimeL(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s) {
  // sets font, cursor position and displays message
  oled.setFont(u8g2_font_10x20_tf); // font
  oled.setFontPosTop();
  oled.setCursor(x, y);
  if (h < 10)
    oled.print("0");
  oled.print(h);
  oled.print(":");
  if (m < 10)
    oled.print("0");
  oled.print(m);
  oled.print(":");
  if (s < 10)
    oled.print("0");
  oled.print(s);
}

// display step at x) y) s)tep
void dispStep(u8g2_uint_t x, u8g2_uint_t y, unsigned int s) {
  // set font, cursor position and display step
  oled.setFont(u8g_font_7x14); // fix font for now
  oled.setFontPosTop();
  
  oled.setCursor(x, y);
  switch (s) // display freqStep
  {
    case 10:
      oled.print(" 10Hz");
      break;
    case 100:
      oled.print("100Hz");
      break;
    case 1000:
      oled.print(" 1kHz");
      break;
    case 10000:
      oled.print(" 10kHz");
      break;
    case 100000:
      oled.print("100kHz");
      break;
    case 1000000:
      oled.print(" 1MHz");
      break;
  }
}

Sunday, 16 April 2017

BASIC Tech Group - MyNews 40 - VFO using AD9851 with OLED display

This code has been updated - see later posting!!

Here's the code for a VFO using the AD9851, up to 70MHz. The AD9851 code is here

// VFO_ADS51_OLED
// V1.0 16-4-17 VFO for the AD9851, 30MHz external xtal, x6 180MHz internal clock
// freq steps 10Hz, 100Hz, 1kHz, 10kHz, 100kHz, 1MHz
// AD9851
// RESET 8
// DATA 9
// FQ_UD 10
// W_CLK 11
// OLED 128x64
// SDA = A4
// SCL = A5
// rotary encoder pins
// CLK = 2
// DT = 3
// SW = 4


// OLED, AD9851, Rotary Encoder & I2C libraries
#include "Oled_128X64_I2C.h"
#include "ADS9851.h"
#include "Rotary.h"

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

// xtal calibration (30MHz external x6 REFCLK = 180MHz internal
#define CALIBRATE 180002300 // cal against SDR (cal at 7070 against CORRA)

// encoder
#define CLK 2
#define DT 3
#define SW 4

// ads (analog-output digital synthesiser) object
ADS9851 ads;

// Encoder object
Rotary enc = Rotary(DT, CLK);

// phase coding, 0-180 [0-5]
uint8_t ph[] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10};

// initial settings
volatile double freqHz = 7100000; // (Hz) start frequency 7.1MHz
volatile double freqChz = 0;  // (cHz) additional 0cHz
volatile double freqStep = 10; // (Hz) init 10Hz freqStep
uint8_t phase = ph[0]; // init phase

// freq change flag
volatile bool freqChange;

void setup() {
  // encoder, button pins
  pinMode(DT, INPUT_PULLUP);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(SW, INPUT_PULLUP);

  // setup interrupts from DT or CLK for tuning
  attachInterrupt(digitalPinToInterrupt(DT), freqTune, CHANGE);
  attachInterrupt(digitalPinToInterrupt(CLK), freqTune, CHANGE);
  interrupts(); // enable

  // oled init, sets I2C addr to 0x3C
  oled.begin();

  // init ads, executes down() to flush buffers
  ads.begin(W_CLK, FQ_UD, DATA, RESET);

  // calibrate to xtal actual frequency
  ads.calibrate(CALIBRATE);

  ads.setFreq(freqHz, freqChz, phase);

  freqChange = false;

  dispUpdate();
}

// check button & freq tune, update display
void loop() {
  if (button()) {
    dispUpdate();
  }

  if (freqChange) {
    // freq updated
    ads.setFreq(freqHz, freqChz, phase);
    freqChange = false;
    dispUpdate();
  }
}


// ISR - encoder interrupt service routine
void freqTune() {
  unsigned char result;

  result = enc.process();
  if (result == DIR_CW ) {
    freqHz += freqStep;
    freqChange = true;
  }
  else if (result == DIR_CCW) {
    freqHz -= freqStep;
    freqChange = true;
  }
}

// change freqStep, 10Hz to 1MHz
bool button() {
  if (digitalRead(SW) == LOW) { // button pressed?
    while (!digitalRead(SW)); // wait for release
    if (freqStep == 1000000) freqStep = 10; // back to 10Hz
    else freqStep = freqStep * 10; // or increase by x10
    return true;
  }
  else {
    return false;
  }
}

// picture loop, display init data
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(50, 0, "VFO");
    dispFreq(15, 25, freqHz, freqChz, 2);
    dispMsg(15, 50, "<--");
    dispStep(45, 50, freqStep);
    dispMsg(95, 50, "-->");

  } while ( oled.nextPage() );
}