Saturday 19 August 2017

New! ADS9851 library update, QRSS & QRSS DFCW for AD9851 VFO

Been a bit busy today with some updates and a couple of re-writes. First off I noticed a problem with the library ADS9851 which I wrote for the AD9851 Analog Digital Synthesiser. When the "down" function was called it did not completely turn off the output! It continued to output a low level signal 500Hz below the programmed one... I think this was due to the reset pulse applied being too short - it has to be at least 5 system cycles long. So I have included a small 2us delay in the "pulse" function to give a longer LOW-HIGH-LOW pulse. The same pulse is used for "init" so will serve both functions.

Here's the ".h" and ".cpp" code for the updated library,

CODE for ADS9851.h
// Arduino Library for AD9851 frequency synthesiser module, with 30MHz clock
// V1.1 18-8-17 Antony Watts, M0IFA
// frequency in Hz and cHz
// W_CLK, FQ_UD, DATA, RESET to any pins
// void begin(int W_CLK, int FQ_UD, int DATA, int RESET); intialise pins and reset AD9850
// void setFreq(double Hz, double Chz, uint8_t p); set frequency(Hz) and centi-freq(Chz)
// void calibrate(double calHz); change xtal frequency from standard 125MHz to new value
// void down(); power down, power up with setFreq()
// phase coding, 0-180 in 11.25deg steps 0x00, 0x01, 0x02, 0x04, 0x08, 0x10
// REFCLK = 1 for x6 multiplier

#ifndef ADS9851_H
#define ADS9851_H

#include "Arduino.h"

#define ADS_XTAL 180000000.0

class ADS9851 {

	public:
		ADS9851();

		void begin(int W_CLK, int FQ_UD, int DATA, int RESET);
		void setFreq(double Hz, double Chz, uint8_t phase);
		void calibrate(double calHz);
                void down();

	private:
		int _W_CLK;
		int _FQ_UD;
		int _DATA;
		int _RESET;

		double _calFreq;

                void update(uint32_t d, uint8_t p);
		void pulse(int _pin);
                void init();

};

#endif


And the CODE for ADS9851.cpp
// Arduino Library for AD9851 frequency synthesiser module, with 30MHz clock
// V1.1 19-8-17 Antony Watts, M0IFA, pulser code update
// frequency in Hz and cHz
// W_CLK, FQ_UD, DATA, RESET to any pins
// void begin(int W_CLK, int FQ_UD, int DATA, int RESET); init, reset, serial mode
// void setFreq(double Hz, double Chz, uint8_t p); set f(Hz) and cHz(Chz), phase
// void calibrate(double calHz); change xtal frequency from standard 180MHz (30MHz x6)
// void down(); power down, power up with setFreq()
// phase in steps 0x00, 0x01, 0x02, 0x04, 0x08, 0x10 (11.5, 22.5, 45. 90, 180deg)
// REFCLK = 1 for x6 multiplier

#include "Arduino.h"
#include "ADS9851.h"

// constructor
ADS9851::ADS9851() {

}

// init calFreq, pins, reset & serial mode
void ADS9851::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 d2-d0
        pulse(_FQ_UD);

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

void ADS9851::update(uint32_t fW, uint8_t cP) {
    for (int i=0; i <4 ; i++, fW >>= 8) {
       shiftOut(_DATA, _W_CLK, LSBFIRST, fW); // output freq byte
    }
    shiftOut(_DATA, _W_CLK, LSBFIRST, cP); // output control & phase byte

    pulse(_FQ_UD);
}

// 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
        bitSet(p, 0); // REFCLK on, = ppppp001
	update(delta, p);
}

// turn off, zero freq
void ADS9851::down() {
        update(0, 0);
}

// clear freq to zero, set PD bit, set REFCLK bit
void ADS9851::init() {

	pulse(_FQ_UD);
	update(0, 0b00000101); // fW=0, PD=1 (pwr dwn), REFCLK=1 (on)
}

// pulse a pin LOW-HIGH-LOW
void ADS9851::pulse(int _pin) {
        digitalWrite(_pin, LOW);
	digitalWrite(_pin, HIGH);
        delayMicroseconds(2); // 2us pulse for init & down
	digitalWrite(_pin, LOW);
}

// load a new value for _calFreq
void ADS9851::calibrate(double calXtal) {
	_calFreq = calXtal;
}


NEW QRSS_CW and QRSS_DFCW sketches

Two very interesting slow CW modes are QRSS and QRSS DFCW. QRSS sends morse code at a very low speed, in this case it is 3sec/dot known as QRSS3. The DFCW mode sends both dots and dashes with the same lenght, but at different frequencies, in this case 5Hz apart, dash higher, dot lower.

CODE for QRSS_CW
// QRSS_CW, sends 40m QRSS3 message from the KB
// V1.0 18-8-17
// AD9851
// RESET 8
// DATA 9
// FQ_UD 10
// W_CLK 11
// OLED 128x64
// SDA = A4
// SCL = A5


// OLED, AD9851 libraries
#include "Oled_128X64_I2C.h"
#include "ADS9851.h"

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

// 3sec dot time
#define DOT 3000

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

// ASCII input
char msg[30];

// frequency settings - fixed for now
volatile double freqHz = 7000800; // (Hz) start frequency 7000.8kHz
volatile double freqChz = 0;      // (cHz) additional 0cHz
uint8_t phase = 0;                // init phase

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

// morse code strings, _ = dot space, 0-9 numbers, 10-36 A..Z
// table from 0 - 36
char morse[][8] = {
  "-----_", // 0
  ".----_", // 1-9
  "..---_",
  "...--_",
  "....-_",
  "....._",
  "-...._",
  "--..._",
  "---.._",
  "----._",
  ".-_",   // A
  "-..._", // B
  "-.-._", // C
  "-.._",  // D
  "._",    // E
  "..-._", // F
  "--._",  // G
  "...._", // H
  ".._",   // I
  ".---_", // J
  "-.-_",  // K
  ".-.._", // L
  "--_",   // M
  "-._",   // N
  "---_",  // O
  ".--._", // P
  "--.-_", // Q
  ".-._",  // R
  "..._",  // S
  "-_",    // T
  "..-_",  // U
  "...-_", // V
  ".--_",  // W
  "-..-_", // X
  "-.--_", // Y
  "--.._", // Z
  "__",    // word space
};

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

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

  msg[0] = '\0'; // init message
  
  dispUpdate(); //  init display
}

void loop()
{
  // get message, send
  if (getMsg(msg)) {
    qrssOut(msg);
  }
}

// get input message
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();            // get char
      if (ch == '\n' || ch == '\r') ch = '\0';     // end of text
      m[n++] = ch;
      delay(20);                     // let USB catch up
    }
    return true;                     // got input
  }
  return false;                      // no input
}

// look up morse string, send char by char
void qrssOut(char *m)
{
  static byte ndx;
  byte n;
  char c;

  dispUpdate(); // display msg

  // step along msg chraracters
  ndx = 0;
  while (m[ndx] != '\0')
  {
    // convert SPACE
    if (m[ndx] == 32)
      c = m[ndx] + 4;

    // convert ASCII
    else if (m[ndx] >= 48 && m[ndx] <= 57) // table 0-9
      c = m[ndx] - 48;
    else if (m[ndx] >= 65 && m[ndx] <= 90) // table A-Z (uc)
      c = m[ndx] - 55;
    else if (msg[ndx] >= 97 && msg[ndx] <= 122) // table a-z (lc)
      c = m[ndx] - 87;

    // output morse, up to SPACE
    n = 0;
    while (morse[c][n] != '_')
    {
      if (morse[c][n] == '.')  dotOut(); // dot out
      else if (morse[c][n] == '-')  dashOut(); // dash out
      n++;
    }
    spaceOut(); // end of char
    ndx++;
  }

  m[0] = '\0'; // clear message
  dispUpdate();
}

// send a dot for DOT time
void dotOut()
{
  unsigned long t;
  ads.setFreq(freqHz, freqChz, phase);
  t = millis();
  while (millis() < t + DOT);
  ads.down();
  t = millis();
  while (millis() < t + DOT);
}

// send a dash for 3* DOT time
void dashOut()
{
  unsigned long t;
  ads.setFreq(freqHz, freqChz, phase);
  t = millis();
  while (millis() < t + DOT * 3);
  ads.down();
  t = millis();
  while (millis() < t + DOT);
}

// word space for 2 * DOT time (each character has its own one DOT space
void spaceOut()
{
  unsigned long t;
  t = millis();
  while (millis() < t + DOT * 2);
}

// picture loop, display init data
void dispUpdate() {
  oled.firstPage();
  do {
      dispMsg(50, 0, "QRSS3");
      dispFreq(15, 20, freqHz, freqChz, 2);
      dispMsg(5, 40, msg);
  } while ( oled.nextPage() );
}


And the CODE for QRSS_DFCW
// QRSS_DFCW sends a DFCW3 message from the KB
// V1.0 18-8-17 like QRSS but with equal time for dot and dash
// with DFCW dot/dash spacing of 10Hz
// tunable in 10Hz steps
// 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 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

// encoder
#define CLK 2
#define DT 3

// 3sec dot time
#define DOT 3000

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

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

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

// frequency settings
volatile double freqHz = 7000800; // (Hz) start frequency 7000.8kHz
volatile double dotChz = 0;       // (cHz) zero
volatile double dashChz = 500;    // (cHz) additional 5Hz
volatile double freqStep = 10;    // (Hz) freqHz +/- step
uint8_t phase = 0;                // init phase

// ASCII input
char msg[30];

// morse code strings, _ = dot space, 0-9 numbers, 10-36 A..Z
// table from 0 - 36
char morse[][8] = {
  "-----_", // 0
  ".----_", // 1-9
  "..---_",
  "...--_",
  "....-_",
  "....._",
  "-...._",
  "--..._",
  "---.._",
  "----._",
  ".-_",   // A
  "-..._", // B
  "-.-._", // C
  "-.._",  // D
  "._",    // E
  "..-._", // F
  "--._",  // G
  "...._", // H
  ".._",   // I
  ".---_", // J
  "-.-_",  // K
  ".-.._", // L
  "--_",   // M
  "-._",   // N
  "---_",  // O
  ".--._", // P
  "--.-_", // Q
  ".-._",  // R
  "..._",  // S
  "-_",    // T
  "..-_",  // U
  "...-_", // V
  ".--_",  // W
  "-..-_", // X
  "-.--_", // Y
  "--.._", // Z
  "__",    // word space
};

// interrupt freq change flag
bool freqChange;

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

  // for msg input/display
  Serial.begin(9600);

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

  msg[0] = '\0'; // init message

  dispUpdate();
}

void loop()
{
  if (freqChange) {
    freqChange = false;
    dispUpdate();
  }
  if (getMsg(msg)) {
    dfcwOut(msg);
  }
}

// get input message
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();            // get char
      if (ch == '\n' || ch == '\r') ch = '\0';     // end of text
      m[n++] = ch;
      delay(20);                     // let USB catch up
    }
    return true;                     // got input
  }
  return false;                      // no input
}

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

// look up morse string, send char by char
void dfcwOut(char *m)
{
  static byte ndx;
  byte n;
  char c;

  dispUpdate();

  // step along msg chraracters
  ndx = 0;
  while (m[ndx] != '\0')
  {
    // convert to position in morse table
    // convert SPACE
    if (msg[ndx] == 32)
      c = m[ndx] + 4;

    // convert ASCII
    else if (m[ndx] >= 48 && m[ndx] <= 57) // table 0-9
      c = m[ndx] - 48;
    else if (m[ndx] >= 65 && m[ndx] <= 90) // table A-Z (uc)
      c = m[ndx] - 55;
    else if (m[ndx] >= 97 && m[ndx] <= 122) // table a-z (lc)
      c = m[ndx] - 87;

    // output morse, up to SPACE
    n = 0;
    while (morse[c][n] != '_')
    {
      if (morse[c][n] == '.')  dotOut(); // dot out
      else if (morse[c][n] == '-')  dashOut(); // dash out
      n++;
    }
    spaceOut(); // end of char
    ndx++;
  }
  m[0] = '\0';
  dispUpdate();
}


// send a dot for DOT time
void dotOut()
{
  unsigned long t;

  ads.setFreq(freqHz, dotChz, phase); //  send dot
  t = millis();
  while (millis() < t + DOT);
  ads.down();                             // small space
  t = millis();
  while (millis() < t + DOT / 2);
}

// send a dash for 3* DOT time
void dashOut()
{
  unsigned long t;

  ads.setFreq(freqHz, dashChz, phase); // send dash, 3x dot
  t = millis();
  while (millis() < t + DOT);
  ads.down();                              // small space
  t = millis();
  while (millis() < t + DOT / 2);
}

// word space for 2 * DOT time (each character has its own one DOT space
void spaceOut()
{
  unsigned long t;
  t = millis();
  while (millis() < t + DOT * 2);
}

// picture loop, display init data
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(50, 0, "DFCW3");
    dispFreq(15, 20, freqHz, dotChz, 2);
    dispMsg(5, 40, msg);
  } while ( oled.nextPage() );
}


This is the transmitter and the received signal in DFCW using the ARGO software,

IMG 1434

IMG 1433

No comments: