Tuesday, 3 January 2017

BASIC Tech Group - MyNews 15 - Elektor Arduino SDR

I rushed an order out for the new Elektor SDR as soon as I discovered it on the Web. It arrived this morning.

Note: If you read this blog before, revised VFO code V3 is listed below, now covers 2-30MHz and includes frequency corrections

IMG 7170

I have already designed and built an SDR for use with the Arduino (see github.com/M6KWH). This SDR had a custom sketch written for Ham use on 40m.

The system uses a rotary encoder KY-40 and an LCD 1602 to tune and display the frequency.

IMG 9259

IMG 1759

I am using HDSDR software running under Wine on a Macbook and this works fine.

Comments on the design:

The antenna connection would be better if it was laid out for an SMA connector. And a actual 50R input matching would be better. Connections could be added on the right side for a rotary encoder (5pins) and an LCD display (4 pins). Instead of the usual peak in the centre of the SDR display, usually caused by 50Hz mains hum, there is a dip! Clever design or a mistake, I don't know.

Now how about a complementary Transmitter board Elektor?

According to me the frequency is not correct, it is about 1.4kHz wrong, maybe the xtal is not exactly on 25MHz? Looking at the xtal spec it says 18pF load, the max of the Si5351 is 8pF... So a software error correction, a large one, has to be used. This is in the code below I have calibrated against one of the Si5351 modules from Adafruit or Kanga which I have found to be very accurate.

Code

// SDR_ELEKTOR
// V3 3-3-17
// Output 4 x 2-30MHz on CLK1, in 10kHz-1MHz steps
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// LCD I2C bus (16x2)
// SDA = A4
// SCL = A5
// rotary encoder pins
// DT = 2
// CLK = 3
// SW = 4

// Si5351 V2, LCD and Rotary Encoder libraries
#include "si5351.h"
#include "LiquidCrystal_I2C.h"
#include "Rotary.h"

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

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

// min freq 7.0MHz, max 7.2MHz (cHz)
#define FREQMIN 700000000
#define FREQMAX 720000000

// freq steps 100kHz max, 10kHz step (cHz)
#define FREQSTEPMAX 10000000
#define FREQSTEP 1000000

// dds object
Si5351 dds;

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

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

// start freq & freqStep (cHz)
volatile uint64_t freq = 710000000; // 7.1MHz
volatile uint64_t freqStep = 1000000; // 10kHz

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

  // init LCD
  lcd.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;

  dispMsg(2, 0, "SDR ELEKTOR");
  dispFreq(0, 1, freq, 0); // display freq
  dispfreqStep(10, 1, freqStep); // display freqStep xxxxHz|kHz col 10, row 1
}

void loop() {
  // freqStep?
  if (button()) {
    dispfreqStep(10, 1, freqStep); // display freq step
  }
  if (freqChange) {
    freqOut(freq);
    freqChange = false;
    dispFreq(0, 1, freq, 1); // display freq
  }
}

// ISR - encoder interrupt service routine
void freqTune() {
  unsigned char result;
  
  result = enc.process();
  if (result == DIR_CW && freq < FREQMAX) {
    freq += freqStep;
    freqChange = true;
  }
  else if (result == DIR_CCW && freq > FREQMIN) {
    freq -= freqStep;
    freqChange = true;
  }
}

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

// 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 c)ol, r)ow, f (cHz), to d decimal places (kHz)
void dispFreq(uint8_t c, uint8_t r, uint64_t f, uint8_t d) {
  lcd.setCursor(c, r);
  lcd.print((float)f / 100000, d); // convert to float & kHz
  lcd.print("kHz ");
}

// display at c)ol, r)ow freq step s (cHz)
void dispfreqStep(byte c, byte r, uint64_t s)
{
  lcd.setCursor(c, r);
  switch (s) // display freqStep
  {
    case 1000:
      lcd.print("10Hz     ");
      break;
    case 10000:
      lcd.print("100Hz    ");
      break;
    case 100000:
      lcd.print("1kHz     ");
      break;
    case 1000000:
      lcd.print("10kHz    ");
      break;
    case 10000000:
      lcd.print("100kHz   ");
      break;
    case 100000000:
      lcd.print("1MHz     ");
      break;
  }
}

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

1 comment:

Anonymous said...

The new board has a transmit output. Have you done a sketch that supports transmit? My Arduino coding is crap! Will try your sketch. 73