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

Getting it straight - AD985x IC Arduino library "ADS9850 & 51"

I’ve seen a lot of libraries on the web for the AD9850 and AD9851 Analog Digital Synthesisers. And as far as I can see none of them meet the data sheet description of how to program the devices.

Most people use the AD985x devices mounted on modules made in China.

IMG 0993

These all seem to have the same circuit diagram. The device is wired for either parallel or serial data inputs, and two sine and two square wave outputs.

Screen Shot 2017 04 17 at 11 26 00

Large board

Screen Shot 2017 04 17 at 11 25 09

Small board

But ONLY one sine output has a filter to filter the DAC jitters, a 70MHz LPF, this is on the IOUT pin. So this is the output you should use.

ARDUINO

Lets start with the Arduino, and assume we are going to send data to the device in serial mode. We need 4 pins for this, for the signals W_CLK, FQ_UP, DATA and RESET. So we set up 4 Arduino pins in OUTPUT mode. On the Arduino this defaults the output to LOW level, which is what we want.

The first thing to do is to reset the device, this is a bit difficult as it starts in parallel mode and could cause a problem. With the device in parallel mode a reset (a LOW-HIGH-LOW pulse on RESET) will zero the phase accumulator & offset, set the output IOUT to zero mA, set the internal address pointer to the first of the five programming bytes, set power down PD off and, for the AD9851 set the internal x6 xtal frequency REFCLK  multiplier off. But it won’t clear the frequency registers or the control and phase set registers. So they could contain random bits…  this is chance you have to take. They will anyway be overwritten by the following code, but could briefly put the device  in an illegal factory control mode...

The large modules from China are hard wired with pins D0 & D1 to VCC and D2 via a jumper J1 to GND, but the small modules should be wired externally to be safe. This gives the input code required to the parallel input D0-D7 of xxxxx011 which will switch the device to serial mode by a pulse on W_CLK followed by FQ_UD.

Next thing to do for the AD985x is to place the device in power down mode, which will clear all the registers, and for the AD9851 set the PD and the REFCLK registers. We need the REFCLK set to ‘1’ for the AD9851 to enable the x6 xtal frequency multiplier, as all AD9851 modules from China seem to come with a lower cost 30MHz xtal, not an external 180MHz one. But we need an internal 180MHz for correct frequency outputs. (This seems to be a BIG mistake many users make as AD9850 modules come with a 125MHz xtal...).

So the first call is to the library function “begin":

// init calFreq, pins, reset & serial mode
void ADS9850::begin(int W_CLK, int FQ_UD, int DATA, int RESET) {
	_W_CLK = W_CLK;
	_FQ_UD = FQ_UD;
	_DATA = DATA;
	_RESET = RESET;

	_calFreq = ADS_XTAL;

	pinMode(_W_CLK, OUTPUT); // outputs default to LOW
	pinMode(_FQ_UD, OUTPUT);
	pinMode(_DATA, OUTPUT);
	pinMode(_RESET, OUTPUT);

	pulse(_RESET); // reset, parallel mode, ptr to W0
        
        pulse(_W_CLK); // switch to serial mode, xxxxx011 wired on d0-d2
        pulse(_FQ_UD);

        down(); // clear freq and phase registers, REFCLK=1 (x6 en), PD=1 (pwd dn)
}

To set an output frequency we need to calculate the 32 bit value to serially transfer into the first 4 bytes of the 40 bit control register. This we do like this for the AD9850:

// calculate 4 freq bytes, convert double to to uint32_t
void ADS9850::setFreq(double f, double cf, uint8_t p) {
        uint32_t delta;

	delta = (uint32_t)((f + cf/100.0) * 4294967296.0 / _calFreq);
	p = p << 3; // = ppppp000
	update(delta, p);
}
or for the AD9851
// calculate 4 freq bytes, convert double to to uint32_t
void ADS9851::setFreq(double f, double cf, uint8_t p) {
     uint32_t delta;

	delta = (uint32_t)((f + cf/100.0) * 4294967296.0 / _calFreq);
	p = p << 3; // PD off = ppppp000
    setBit(p, 0); // REFCLK on, = ppppp001
	update(delta, p);
}

We start with frequencies f (Hz) and cf (cHz) in double floating point, calculate the "delta" value then convert the result to a 32 bit uint32_t value. And we calculate the phase register contents, setting the REFCLK flag for the AD9851

Next we have to transfer the uint32_t frequency, and the byte p for the control and phase of the ouptut. This is done by the function update().

The update is made like this (same for the AD9851):

// load the 4 delta & the control/phase registers
void ADS9850::update(uint32_t d, uint8_t c) {
    for (int i=0; i <4 ; i++, d >>= 8) {
       shiftOut(_DATA, _W_CLK, LSBFIRST, d); // output freq byte
    }
    shiftOut(_DATA, _W_CLK, LSBFIRST, c); // output control & phase byte

    pulse(_FQ_UD);
}

First "shiftOut" the four delta frequency bytes then the control/phase byte with the previous settings for PD and REFCLK. The function "shiftOut" generates the W_CLK signal for the transfer.

So here are the complete libraries.

Study them both to understand the differences between the AD9850 and 51.

Monday 10 April 2017

BASIC Tech Group - MyNews 39 - Updated VFO with OLED display

Here’s the updated sketch for my AD9850 based VFO, to use an OLED display. The VFO tunes in 10Hz to 1MHz steps. I have in development a buffer amplifier - probably it will be based on an MMIC (ERA2-SM)maybe  to give up to +10dBm output.

Screen Shot 2017 04 11 at 17 02 50

Code

// VFO_ADS_OLED
// V1.5 9-4-17 update to U8g2lib
// output G0
// freq steps 10Hz, 100Hz, 1kHz, 10kHz, 100kHz, 1MHz
// AD9850
// W_CLK 8
// FQ_UD 9
// DATA 10
// RESET 11
// OLED 128x64
// SDA = A4
// SCL = A5
// rotary encoder pins
// DT = 2
// CLK = 3
// SW = 4


// ADS9850, OLED and Rotary Encoder, I2C libraries
#include "ADS9850.h"
#include "U8g2lib.h"
#include "Rotary.h"
#include "Wire.h"

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

// chose pin 2,3 or 3,2 for encoder
#define DT 2
//#define DT 3
#define CLK 3
//#define CLK 2
#define SW 4

// xtal calibration
#define CALIBRATE 125000000

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

// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

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

// phase coding, 0-180 [0-5] in 11.25deg steps
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 AD9850
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(40, 0, "VFO_ADS");
dispFreq(15, 25, freqHz, freqChz, 2);
dispMsg(30, 50, "Step");
dispStep(70, 50, freqStep);

} while ( oled.nextPage() );
}

// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
double fd;
char buf[100];

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

fd = f + cf / 100; // calc freq

oled.setCursor(x, y);
oled.print(fd / 1000, d);
oled.print("kHz");
}

void dispStep(u8g2_uint_t x, u8g2_uint_t y, uint64_t s) {
// set font, cursor position and display step
oled.setFont(u8g2_font_7x13_tf); // font
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;
}
}

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

BASIC Tech Group - MyNews 38 - Updated SDR ELEKTOR with OLED display

Here’s the code for an update to the sketch for the Elektor SDR RX. It updates the display to OLED and adds a tuning capability to select 50kHZ segments of 80, 40 & 20m bands.

Code

// SDR_ELEKTOR_OLED_TUNE
// V4.5 9-4-17 update to U8g2lib
// Output 80, 40, 20m in 50kHz steps x4 CLK1
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// OLED 128x64
// SDA = A4
// SCL = A5

// Si5351 V2, LCD libraries
#include "si5351.h"
#include "U8g2lib.h"
#include "Rotary.h"

// chose pin 2,3 or 3,2 for encoder
#define DT 2
//#define DT 3
#define CLK 3
//#define CLK 2
#define SW 4

// xtal correction for Elektor DDS
#define CORRE 180000

// dds object
Si5351 dds;

// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

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

// table of tuning frequencies
uint64_t freqTable[] = {
350000000, 355000000, 360000000, 365000000, 370000000, 375000000,
705000000, 710000000, 715000000,
1405000000, 1410000000, 1415000000, 1420000000, 1425000000
};

// index into freq table, startup freq
byte ndx = 7;

// initial frequency
uint64_t freq = freqTable[ndx];

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

// enable interrupts
interrupts();

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

// init dds si5351 module, "0" = default 25MHz XTAL, correction
dds.init(SI5351_CRYSTAL_LOAD_8PF, 0, CORRE);

// set 8mA output drive
dds.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);

// enable VFO output CLK1, disable CLK0 & 2
dds.output_enable(SI5351_CLK0, 0);
dds.output_enable(SI5351_CLK1, 1);
dds.output_enable(SI5351_CLK2, 0);

freqOut(freq); // cHz, output freq
freqChange = false;

dispUpdate();
}

void loop() {
if (freqChange) {
freqOut(freq);
freqChange = false;
dispUpdate();
}
}

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

result = enc.process();
if (result == DIR_CW && ndx < 13) {
freq = freqTable[++ndx];
freqChange = true;
}
else if (result == DIR_CCW && ndx > 0) {
freq = freqTable[--ndx];;
freqChange = true;
}
}

// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(25, 0, "SDR_ELEKTOR");
dispFreq(25, 25, 0, freq, 0); // freq is in cHz
dispMsg(20, 50, "<-- Freq -->");
} while ( oled.nextPage() );
}

// Output Freq for Elektor SDR, f (cHz) x 4 on CLK1
void freqOut(uint64_t f) {
dds.set_freq(f * 4ULL, SI5351_CLK1);
}

// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
double fd;
char buf[100];

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

fd = f + cf / 100; // calc freq

oled.setCursor(x, y);
oled.print(fd / 1000, d);
oled.print("kHz");
}

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

BASIC Tech Group - MyNews 37 - Updated JT65 with OLED display

Here’s the code for JT65 with OLED display and text input via a USB connected terminal program, such as MacOS iSerialTerm or the Arduino IDE Serial Monitor window.

Code

// JT65_ADS_TEXT_OLED
// V2.5 9-4-17 serial input/output, update to U8g2lib
// Code based on Feld Hell beacon for Arduino by K6HX
// Timer setup code by LA3PNA.
// TX 7060000, Dial 7074830
// AD9850
// W_CLK 8
// FQ_UD 9
// DATA 10
// RESET 11
// OLED 128x64
// SDA = A4
// SCL = A5
// RTC I2C bus
// SDA = A4
// SCL = A5

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

// RTC address
#define RTCADDR 0x68

// Stuff specific to the general (integer) version of the Reed-Solomon codecs
#define MODNN(x) modnn(rs,x)
#define MM (rs->mm)
#define NN (rs->nn)
#define ALPHA_TO (rs->alpha_to)
#define INDEX_OF (rs->index_of)
#define GENPOLY (rs->genpoly)
#define NROOTS (rs->nroots)
#define FCR (rs->fcr)
#define PRIM (rs->prim)
#define IPRIM (rs->iprim)
#define PAD (rs->pad)
#define A0 (NN)

#define TONE_SPACING            269           // ~2.6917 Hz
#define SUBMODE_A               5812         // CTC value for JT65A
#define SYMBOL_COUNT            126

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

typedef unsigned int data_t;

/* Reed-Solomon codec control block */
struct rs {
  int mm;              /* Bits per symbol */
  int nn;              /* Symbols per block (= (1<<mm)-1) */
  data_t *alpha_to;     /* log lookup table */
  data_t *index_of;     /* Antilog lookup table */
  data_t *genpoly;      /* Generator polynomial */
  int nroots;     /* Number of generator roots = number of parity symbols */
  int fcr;        /* First consecutive root, index form */
  int prim;       /* Primitive element, index form */
  int iprim;      /* prim-th root of 1, index form */
  int pad;        /* Padding bytes in shortened block */
};

// RTC time
byte sec, mns, hrs;

// trigger TX (sec)
uint8_t trigger;
bool tx;

// Frequency variables
double freqHz = 7076000; // nominal dial is 1270Hz lower
double freqChz = 0;
uint8_t phase = 0;

// message to send
char message[20] = "";

// encode variables
uint8_t tx_buffer[SYMBOL_COUNT];
static void *rs;

// timer flag
volatile bool proceed = false;

// AD9850 object
ADS9850 ads;

// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

void setup()
{
  Serial.begin(9600);
  
  // init I2C
  Wire.begin();

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

  // init AD9850, init freq and off to start
  ads.begin(W_CLK, FQ_UD, DATA, RESET);
  ads.setFreq(freqHz, freqChz, phase);
  ads.down();

  // Set up Timer1 for interrupts every symbol period.
  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 << CS12) |   // Set CS12 and CS10 bit to set prescale
           (1 << CS10) |          //   to /1024
           (1 << WGM12);          //   turn on CTC
  //   which gives, 64us ticks
  TIMSK1 = (1 << OCIE1A);  // Enable timer compare interrupt.
  OCR1A = SUBMODE_A;          // Set up interrupt trigger count;
  interrupts();            // Re-enable interrupts.

  trigger = 1; // trigger on each minute
  tx = false;

  // Initialize the Reed-Solomon encoder
  rs = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0);
}

void loop() {
  // get time
  getRTC();
  dispUpdate();

  // message?
  if (getMsg(message) == true) {
    // echo message
    Serial.println(message);
    // display message & time, trigger time?
    do {
      getRTC();
      dispUpdate();
    } while (mns % trigger != 0 || sec != 0);
    // send
    tx = true; // indicate TX on display
    encode(message);
    tx = false;
    // clear message & buffer
    clearBuf(message);
  }
}

// picture loop, display init data
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(15, 0, "JT65_ADS_TEXT");
    dispMsg(15, 15, message);
    dispFreq(15, 30, freqHz, freqChz, 2);
    if (tx == false) dispTime(30, 50);
    else dispMsg(40, 50, "TX");
  } while ( oled.nextPage() );
}

// Loop through the string, transmitting one character at a time.
void encode(char * tx_string)
{
  uint8_t i;

  // encode the message
  jt65_encode(tx_string, tx_buffer);

  // Now do the rest of the message
  for (i = 0; i < SYMBOL_COUNT; i++)
  {
    ads.setFreq(freqHz, freqChz + (tx_buffer[i] * TONE_SPACING), phase);
    proceed = false;
    while (!proceed);
  }

  // Turn off the output
  ads.down();
}

static inline int modnn(struct rs *rs, int x) {
  while (x >= rs->nn) {
    x -= rs->nn;
    x = (x >> rs->mm) + (x & rs->nn);
  }
  return x;
}


uint8_t jt_code(char c)
{
  /* Validate the input then return the proper integer code */
  // Return 255 as an error code if the char is not allowed

  if (isdigit(c))
  {
    return (uint8_t)(c - 48);
  }
  else if (c >= 'A' && c <= 'Z')
  {
    return (uint8_t)(c - 55);
  }
  else if (c == ' ')
  {
    return 36;
  }
  else if (c == '+')
  {
    return 37;
  }
  else if (c == '-')
  {
    return 38;
  }
  else if (c == '.')
  {
    return 39;
  }
  else if (c == '/')
  {
    return 40;
  }
  else if (c == '?')
  {
    return 41;
  }
  else
  {
    return 255;
  }
}

// timer interrupt veector
ISR(TIMER1_COMPA_vect)
{
  proceed = true;
}

// Reed Soloman encoder by KA9Q
void encode_rs_int(void *p, data_t *data, data_t *parity)
{
  struct rs *rs = (struct rs *)p;

#undef A0
#define A0 (NN) /* Special reserved value encoding zero in index form */


  int i, j;
  data_t feedback;

  memset(parity, 0, NROOTS * sizeof(data_t));

  for (i = 0; i < NN - NROOTS - PAD; i++) {
    feedback = INDEX_OF[data[i] ^ parity[0]];
    if (feedback != A0) {    /* feedback term is non-zero */
#ifdef UNNORMALIZED
      /* This line is unnecessary when GENPOLY[NROOTS] is unity, as it must
         always be for the polynomials constructed by init_rs()
      */
      feedback = MODNN(NN - GENPOLY[NROOTS] + feedback);
#endif
      for (j = 1; j < NROOTS; j++)
        parity[j] ^= ALPHA_TO[MODNN(feedback + GENPOLY[NROOTS - j])];
    }
    /* Shift */
    memmove(&parity[0], &parity[1], sizeof(data_t) * (NROOTS - 1));
    if (feedback != A0)
      parity[NROOTS - 1] = ALPHA_TO[MODNN(feedback + GENPOLY[0])];
    else
      parity[NROOTS - 1] = 0;
  }
}


void free_rs_int(void *p)
{
  struct rs *rs = (struct rs *)p;

  free(rs->alpha_to);
  free(rs->index_of);
  free(rs->genpoly);
  free(rs);
}

// init RS enc, symb size, poly coeff, first root, primative, no roots, padding
void *init_rs_int(int symsize, int gfpoly, int fcr, int prim, int nroots, int pad) {
  struct rs *rs;

  //#undef NULL
  //#define NULL ((void *)0)

  int i, j, sr, root, iprim;

  rs = ((struct rs *)0);
  /* Check parameter ranges */
  if (symsize < 0 || symsize > 8 * sizeof(data_t)) {
    goto done;
  }

  if (fcr < 0 || fcr >= (1 << symsize))
    goto done;
  if (prim <= 0 || prim >= (1 << symsize))
    goto done;
  if (nroots < 0 || nroots >= (1 << symsize))
    goto done; /* Can't have more roots than symbol values! */
  if (pad < 0 || pad >= ((1 << symsize) - 1 - nroots))
    goto done; /* Too much padding */

  rs = (struct rs *)calloc(1, sizeof(struct rs));
  if (rs == NULL)
    goto done;

  rs->mm = symsize;
  rs->nn = (1 << symsize) - 1;
  rs->pad = pad;

  rs->alpha_to = (data_t *)malloc(sizeof(data_t) * (rs->nn + 1));
  if (rs->alpha_to == NULL) {
    free(rs);
    rs = ((struct rs *)0);
    goto done;
  }
  rs->index_of = (data_t *)malloc(sizeof(data_t) * (rs->nn + 1));
  if (rs->index_of == NULL) {
    free(rs->alpha_to);
    free(rs);
    rs = ((struct rs *)0);
    goto done;
  }

  /* Generate Galois field lookup tables */
  rs->index_of[0] = A0; /* log(zero) = -inf */
  rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */
  sr = 1;
  for (i = 0; i < rs->nn; i++) {
    rs->index_of[sr] = i;
    rs->alpha_to[i] = sr;
    sr <<= 1;
    if (sr & (1 << symsize))
      sr ^= gfpoly;
    sr &= rs->nn;
  }
  if (sr != 1) {
    /* field generator polynomial is not primitive! */
    free(rs->alpha_to);
    free(rs->index_of);
    free(rs);
    rs = ((struct rs *)0);
    goto done;
  }

  /* Form RS code generator polynomial from its roots */
  rs->genpoly = (data_t *)malloc(sizeof(data_t) * (nroots + 1));
  if (rs->genpoly == NULL) {
    free(rs->alpha_to);
    free(rs->index_of);
    free(rs);
    rs = ((struct rs *)0);
    goto done;
  }
  rs->fcr = fcr;
  rs->prim = prim;
  rs->nroots = nroots;

  /* Find prim-th root of 1, used in decoding */
  for (iprim = 1; (iprim % prim) != 0; iprim += rs->nn)
    ;
  rs->iprim = iprim / prim;

  rs->genpoly[0] = 1;
  for (i = 0, root = fcr * prim; i < nroots; i++, root += prim) {
    rs->genpoly[i + 1] = 1;

    /* Multiply rs->genpoly[] by  @**(root + x) */
    for (j = i; j > 0; j--) {
      if (rs->genpoly[j] != 0)
        rs->genpoly[j] = rs->genpoly[j - 1] ^ rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[j]] + root)];
      else
        rs->genpoly[j] = rs->genpoly[j - 1];
    }
    /* rs->genpoly[0] can never be zero */
    rs->genpoly[0] = rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[0]] + root)];
  }
  /* convert rs->genpoly[] to index form for quicker encoding */
  for (i = 0; i <= nroots; i++)
    rs->genpoly[i] = rs->index_of[rs->genpoly[i]];
done:;

  return rs;
}

uint8_t gray_code(uint8_t c)
{
  return (c >> 1) ^ c;
}

void rs_encode(uint8_t * data, uint8_t * symbols)
{
  unsigned int dat1[12];
  unsigned int b[51];
  unsigned int i;

  // Reverse data order for the Karn codec.
  for (i = 0; i < 12; i++)
  {
    dat1[i] = data[11 - i];
  }

  // Compute the parity symbols
  encode_rs_int(rs, dat1, b);

  // Move parity symbols and data into symbols array, in reverse order.
  for (i = 0; i < 51; i++)
  {
    symbols[50 - i] = b[i];
  }

  for (i = 0; i < 12; i++)
  {
    symbols[i + 51] = dat1[11 - i];
  }
}

void jt65_encode(char * message, uint8_t symbols[SYMBOL_COUNT])
{
  uint8_t i, j, k;

  // Convert all chars to uppercase
  for (i = 0; i < 13; i++)
  {
    if (islower(message[i]))
    {
      message[i] = toupper(message[i]);
    }
  }

  // Collapse multiple spaces down to one

  // Pad the message with trailing spaces
  uint8_t len = strlen(message);
  if (len < 13)
  {
    for (i = len; i < 13; i++)
    {
      message[i] = ' ';
    }
  }

  // Bit packing
  // -----------
  uint8_t c[12];
  uint32_t n1, n2, n3;

  // Find the N values
  n1 = jt_code(message[0]);
  n1 = n1 * 42 + jt_code(message[1]);
  n1 = n1 * 42 + jt_code(message[2]);
  n1 = n1 * 42 + jt_code(message[3]);
  n1 = n1 * 42 + jt_code(message[4]);

  n2 = jt_code(message[5]);
  n2 = n2 * 42 + jt_code(message[6]);
  n2 = n2 * 42 + jt_code(message[7]);
  n2 = n2 * 42 + jt_code(message[8]);
  n2 = n2 * 42 + jt_code(message[9]);

  n3 = jt_code(message[10]);
  n3 = n3 * 42 + jt_code(message[11]);
  n3 = n3 * 42 + jt_code(message[12]);


  // Pack bits 15 and 16 of N3 into N1 and N2,
  // then mask reset of N3 bits
  n1 = (n1 << 1) + ((n3 >> 15) & 1);
  n2 = (n2 << 1) + ((n3 >> 16) & 1);
  n3 = n3 & 0x7fff;

  // Set the freeform message flag
  n3 += 32768;

  c[0] = (n1 >> 22) & 0x003f;
  c[1] = (n1 >> 16) & 0x003f;
  c[2] = (n1 >> 10) & 0x003f;
  c[3] = (n1 >> 4) & 0x003f;
  c[4] = ((n1 & 0x000f) << 2) + ((n2 >> 26) & 0x0003);
  c[5] = (n2 >> 20) & 0x003f;
  c[6] = (n2 >> 14) & 0x003f;
  c[7] = (n2 >> 8) & 0x003f;
  c[8] = (n2 >> 2) & 0x003f;
  c[9] = ((n2 & 0x0003) << 4) + ((n3 >> 12) & 0x000f);
  c[10] = (n3 >> 6) & 0x003f;
  c[11] = n3 & 0x003f;

  // Reed-Solomon encoding
  // ---------------------
  uint8_t s[63];
  k = 0;

  rs_encode(c, s);

  // Interleaving
  // ------------
  uint8_t d[63];
  uint8_t d1[7][9];

  // Fill temp d1 array
  for (i = 0; i < 9; i++)
  {
    for (j = 0; j < 7; j++)
    {
      d1[i][j] = s[(i * 7) + j];
    }
  }

  // Interleave and translate back to 1D destination array
  for (i = 0; i < 7; i++)
  {
    for (j = 0; j < 9; j++)
    {
      d[(i * 9) + j] = d1[j][i];
    }
  }

  // Gray Code
  // ---------
  uint8_t g[63];

  for (i = 0; i < 63; i++)
  {
    g[i] = gray_code(d[i]);
  }

  // Merge with sync vector
  // ----------------------
  const uint8_t sync_vector[126] =
  { 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0,
    0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1,
    0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1,
    0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
    0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1,
    1, 1, 1, 1, 1, 1
  };

  j = 0;

  for (i = 0; i < 126; i++)
  {
    if (sync_vector[i])
    {
      symbols[i] = 0;
    }
    else
    {
      symbols[i] = g[j] + 2;
      j++;
    }
  }

}

// get time from RTC, convert bcd to decimal
void getRTC()
{
  // Reset the register pointer
  Wire.beginTransmission(RTCADDR);
  byte zero = 0x00;
  Wire.write(zero);
  Wire.endTransmission();

  // request 1st 3 bytes from the RTC address
  Wire.requestFrom(RTCADDR, 3);

  // get the s/m/h time data
  sec = bcdToDec(Wire.read());
  mns = bcdToDec(Wire.read());
  hrs = bcdToDec(Wire.read() & 0b111111); // mask 24 hour time bit
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val / 16 * 10) + (val % 16) );
}

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

  n = 0;
  if (Serial.available() > 0) {      // if input
    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();
}

// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
  double fd;
  char buf[100];

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

  fd = f + cf / 100; // calc freq

  oled.setCursor(x, y);
  oled.print(fd / 1000, d);
  oled.print("kHz");
}

// 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 time HH:MM:SS at x), y)
void dispTime(u8g2_uint_t x, u8g2_uint_t y) {
  // sets font, cursor position and displays message
  oled.setFont(u8g_font_7x14); // fix font for now
  oled.setFontPosTop();
  oled.setCursor(x, y);
  if (hrs < 10)
    oled.print("0");
  oled.print(hrs);
  oled.print(":");
  if (mns < 10)
    oled.print("0");
  oled.print(mns);
  oled.print(":");
  if (sec < 10)
    oled.print("0");
  oled.print(sec);
}

BASIC Tech Group - MyNews 36 - Updated RF METER with OLED display

I have updated the code for my RF Meter to use an OLED display. This is to make it possible to mount the system in a small instrument case...

Screen Shot 2017 04 10 at 10 20 08

Code

// RF_METER_OLED
// V1.5 9-4-17 update to U8g2lib
// RF Meter with OLED display
// inputs
// VIN A3
// AREF 3v3

#include "U8g2lib.h"

// 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 3300.0
#define BARSCALE (100 / 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 length
u8g2_uint_t bL;

double vR, dB, wA; // display values
uint8_t vD, dD, wD; // decimal places
double mW, mV, dBm, Vrms; // calculations

// oled object, OELDADDR 0x3C
//U8GLIB_SH1106_128X64 oled(U8G_I2C_OPT_NONE);
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

//======SETUP
void setup() {
// oled init, sets I2C addr to 0x3C
oled.begin();

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

//======LOOP
void loop() {
int Ain; // int = signed 16 bit


// 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, 0dBm = 1mW
Vrms = sqrt((mW / 1000.0) * IMP); // in double, out double,

bL = (dBm + 40); // -40 to +40dBm

// setup display values
// Volts

dispUpdate(); // display bar, Vrms, dBm and Pwr
}

//=====PICTURE LOOP, layout display positions and contant
// global variables, bH, bL, vR, dB, wA
void dispUpdate() {
oled.firstPage();
do {
dispBar(15, 5, 10, bL);
//--VOLTS--
if (Vrms < 1.0) { // millivolts
vR = Vrms * 1000.0;
vD = 0;
dispMsg(75, 20, "mV");
}
else { // volts
vR = Vrms;
vD = 1;
dispMsg(75, 20, "V");
}
dispNum(30, 20, vR, vD);

//--DBM--
dB = dBm; //dBm
dD = 0;
dispNum(30, 35, dB, dD);
dispMsg(75, 35, "dBm");

//--PWR--
if (mW > 1000) { // watts
wA = mW / 1000;;
wD = 1;
dispMsg(75, 50, "W");
}
else { // milliwatts
wA = mW;
wD = 2;
dispMsg(75, 50, "mW");
}
dispNum(30, 50, wA, wD);

 


} while (oled.nextPage());
}

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

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


// 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_8x13B_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, uint8_t d) {
// sets font, cursor position and displays number
oled.setFont(u8g2_font_8x13B_tf); // font
oled.setFontPosTop();
oled.setCursor(x, y);
oled.print(n, d);
}

Converting to OLED displays

I am at a point where I have 3-4 projects operational and ready to be boxed. Looking around the web I find there are virtually no folded aluminium boxes on the market - what happened to them? Maplin have a few old stock remaining, but no one else makes any!

Screen Shot 2017 04 17 at 12 11 24 Screen Shot 2017 04 17 at 12 11 30 Screen Shot 2017 04 17 at 12 11 33
But I did find some nice, small "instrument cases" on Amazon and bought 4 of them. What I found was that the 16x2 LCD displays that I have been using are too large... and I have chosen to switch to 1.3" OLED displays. Lot's of new learning about U8g2lib.h library and re-writing of code... but I have my wide band SDR, my wide range VFO and my RF power meter ready to go.

I now have to cut the rectanglar holes  for the Arduino UNO USB connector and te 1.3" OLED displays. Fortunately my son is production director at a small engineering company and can do this using his CNC milling machines. So there is hope.
 
Code
 
As well as converting code to use OLED displays I have also upgraded from the U8glib to the new, future supported, u8g2 library. New code will be published on this blog showing the use of this new display library. And a post will be made where you can download both my new (and old) ACTIVE sketches and the libraries that support them.
 



 

Monday 3 April 2017

BASIC Tech Group - MyNews 35 - OLED display for Elektor SDR

This is a version of the simple Arduino sketch which displays a single coded SDR centre frequency on an OLED 1.3" display. To use it install the library "u8g2" using the Arduino Library Manager (Sketch > Include Library > Manage Libraries ... and search for “u8g2", then select the latest version and Install).



Screen Shot 2017 04 10 at 10 16 33



Code

// SDR_ELEKTOR_OLED_TUNE
// V4.5 9-4-17 update to U8g2lib
// Output 80, 40, 20m in 50kHz steps x4 CLK1
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// OLED 128x64
// SDA = A4
// SCL = A5

// Si5351 V2, LCD libraries
#include "si5351.h"
#include "U8g2lib.h"
#include "Rotary.h"

// chose pin 2,3 or 3,2 for encoder
#define DT 2
//#define DT 3
#define CLK 3
//#define CLK 2
#define SW 4

// xtal correction for Elektor DDS
#define CORRE 180000

// dds object
Si5351 dds;

// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

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

// table of tuning frequencies
uint64_t freqTable[] = {
350000000, 355000000, 360000000, 365000000, 370000000, 375000000,
705000000, 710000000, 715000000,
1405000000, 1410000000, 1415000000, 1420000000, 1425000000
};

// index into freq table, startup freq
byte ndx = 7;

// initial frequency
uint64_t freq = freqTable[ndx];

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

// enable interrupts
interrupts();

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

// init dds si5351 module, "0" = default 25MHz XTAL, correction
dds.init(SI5351_CRYSTAL_LOAD_8PF, 0, CORRE);

// set 8mA output drive
dds.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);

// enable VFO output CLK1, disable CLK0 & 2
dds.output_enable(SI5351_CLK0, 0);
dds.output_enable(SI5351_CLK1, 1);
dds.output_enable(SI5351_CLK2, 0);

freqOut(freq); // cHz, output freq
freqChange = false;

dispUpdate();
}

void loop() {
if (freqChange) {
freqOut(freq);
freqChange = false;
dispUpdate();
}
}

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

result = enc.process();
if (result == DIR_CW && ndx < 13) {
freq = freqTable[++ndx];
freqChange = true;
}
else if (result == DIR_CCW && ndx > 0) {
freq = freqTable[--ndx];;
freqChange = true;
}
}

// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(25, 0, "SDR_ELEKTOR");
dispFreq(25, 25, 0, freq, 0); // freq is in cHz
dispMsg(20, 50, "<-- Freq -->");
} while ( oled.nextPage() );
}

// Output Freq for Elektor SDR, f (cHz) x 4 on CLK1
void freqOut(uint64_t f) {
dds.set_freq(f * 4ULL, SI5351_CLK1);
}

// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
double fd;
char buf[100];

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

fd = f + cf / 100; // calc freq

oled.setCursor(x, y);
oled.print(fd / 1000, d);
oled.print("kHz");
}

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