Saturday, 10 March 2018

Si5351 DDS VFO/BFO Example Software

This code works with Jason Mildrum Si5351 Etherkit library (see https://github.com/etherkit/Si5351Arduino)


#include <Wire.h>
#include <SPI.h>
#include <LiquidCrystal_I2C.h>
#include <si5351.h>

const uint32_t bandStart = 7000000;     // start of 40m
const uint32_t bandEnd =   7300000;     // end of 40m
const uint32_t bandInit =  7100000;     // where to initially set the frequency
volatile long oldfreq = 0;
volatile long currentfreq = 0;
volatile int updatedisplay = 0;

volatile uint32_t freq = bandInit ;     // this is a variable (changes) - set it to the beginning of the band
volatile uint32_t radix = 1000;         // how much to change the frequency by, clicking the rotary encoder will change this.
volatile uint32_t oldradix = 1;

volatile uint32_t BFO_freq = 8003000;

// Rotary encoder pins and other inputs
static const int pushPin = 9;
static const int rotBPin = 2;
static const int rotAPin = 3;

// Rotary encoder variables, used by interrupt routines
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;

// Instantiate the Objects
LiquidCrystal_I2C lcd(0x27, 16, 2);
Si5351 si5351;

void setup()
{
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);

  // Set up pull-up resistors on inputs
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);

  // Set up interrupt pins
  attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);

  // Initialize the display
  lcd.begin();
  lcd.backlight();
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(31830, SI5351_PLL_INPUT_XO);      // Set to specific Si5351 calibration number
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}


void loop()
{
  currentfreq = getfreq();                // Interrupt safe method to get the current frequency

  if (currentfreq != oldfreq)
  {
    UpdateDisplay();
    SendFrequency();
    oldfreq = currentfreq;
  }

  if (digitalRead(pushPin) == LOW)
  {
    delay(10);
    while (digitalRead(pushPin) == LOW)
    {
      if (updatedisplay == 1)
      {
        UpdateDisplay();
        updatedisplay = 0;
      }
    }
    delay(50);
  }
}


long getfreq()
{
  long temp_freq;
  cli();
  temp_freq = freq;
  sei();
  return temp_freq;
}


// Interrupt routines
void ISRrotAChange()
{
  if (digitalRead(rotAPin))
  {
    rotAval = 1;
    UpdateRot();
  }
  else
  {
    rotAval = 0;
    UpdateRot();
  }
}


void ISRrotBChange()
{
  if (digitalRead(rotBPin))
  {
    rotBval = 1;
    UpdateRot();
  }
  else
  {
    rotBval = 0;
    UpdateRot();
  }
}


void UpdateRot()
{
  switch (rotState)
  {

    case 0:                                         // Idle state, look for direction
      if (!rotBval)
        rotState = 1;                               // CW 1
      if (!rotAval)
        rotState = 11;                              // CCW 1
      break;

    case 1:                                         // CW, wait for A low while B is low
      if (!rotBval)
      {
        if (!rotAval)
        {
          // either increment radixindex or freq
          if (digitalRead(pushPin) == LOW)
          {
            updatedisplay = 1;
            radix = radix / 10;
            if (radix < 1)
              radix = 100000;
          }
          else
          {
            freq = freq + radix;
            if (freq > bandEnd)
              freq = bandEnd;
          }
          rotState = 2;                             // CW 2
        }
      }
      else if (rotAval)
        rotState = 0;                             // It was just a glitch on B, go back to start
      break;

    case 2:                                         // CW, wait for B high
      if (rotBval)
        rotState = 3;                               // CW 3
      break;

    case 3:                                         // CW, wait for A high
      if (rotAval)
        rotState = 0;                               // back to idle (detent) state
      break;

    case 11:                                        // CCW, wait for B low while A is low
      if (!rotAval)
      {
        if (!rotBval)
        {
          // either decrement radixindex or freq
          if (digitalRead(pushPin) == LOW)
          {
            updatedisplay = 1;
            radix = radix * 10;
            if (radix == 100000)
              radix = 1;
          }
          else
          {
            freq = freq - radix;
            if (freq < bandStart)
              freq = bandStart;
          }
          rotState = 12;                            // CCW 2
        }
      }
      else if (rotBval)
        rotState = 0;                             // It was just a glitch on A, go back to start
      break;

    case 12:                                        // CCW, wait for A high
      if (rotAval)
        rotState = 13;                              // CCW 3
      break;

    case 13:                                        // CCW, wait for B high
      if (rotBval)
        rotState = 0;                               // back to idle (detent) state
      break;
  }
}


void UpdateDisplay()
{
  lcd.setCursor(0, 0);
  lcd.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(0, 1);
  lcd.print("ZL2CTM");

  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    lcd.setCursor(9, 0);
    lcd.print("       ");
    lcd.setCursor(9, 0);
    if (radix == 1)
      lcd.print("   1 Hz");
    if (radix == 10)
      lcd.print("  10 Hz");
    if (radix == 100)
      lcd.print(" 100 Hz");
    if (radix == 1000)
      lcd.print("  1 kHz");
    if (radix == 10000)
      lcd.print(" 10 kHz");
    if (radix == 100000)
      lcd.print("100 kHz");
    oldradix = radix;
  }
}


void SendFrequency()
{
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}






***************************************************************
This code works with Jason Mildrum old Si5351 library



#include <Wire.h>                         // I2C comms
#include <LiquidCrystal_I2C.h>
#include <si5351.h>

// Define Constants and Vaviables
static const long bandStart = 1000000;     // start of 80m
static const long bandEnd =   30000000;    // end of 80m
static const long bandInit =  3690000;     // where to initially set the frequency
static const long LSB_IF_freq = 9001500;   // filter centre freq + 1500Hz
static const long LSB_BFO_freq = 9001500;  // filter centre freq + 1500Hz
volatile long oldfreq = 0;
volatile long freq = bandInit ;         
volatile long radix = 1000;                // how much to change the frequency by, clicking the rotary encoder will change this.
volatile int updatedisplay = 0;

// Rotary encoder pins and other inputs
static const int pushPin = 4;
static const int rotBPin = 3;
static const int rotAPin = 2;

// Rotary encoder variables, used by interrupt routines
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;

// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2);
Si5351 si5351;

void setup()
{
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);

  // Set up pull-up resistors on inputs
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);

  // Set up interrupt pins
  attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);

  // Initialize the display
  lcd.begin();
  lcd.backlight();
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  si5351.set_freq((freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
  si5351.set_freq((LSB_BFO_freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK2);
}


void loop()
{
  if (freq != oldfreq)
  {
    UpdateDisplay();
    SendFrequency();
    oldfreq = freq;
  }

  if (digitalRead(pushPin) == LOW)
  {
    delay(10);
    while (digitalRead(pushPin) == LOW)
    {
      if (updatedisplay == 1)
      {
        UpdateDisplay();
        updatedisplay = 0;
      }
    }
    delay(50);
  }
}


// Interrupt routines
void ISRrotAChange()
{
  if (digitalRead(rotAPin))
  {
    rotAval = 1;
    UpdateRot();
  }
  else
  {
    rotAval = 0;
    UpdateRot();
  }
}


void ISRrotBChange()
{
  if (digitalRead(rotBPin))
  {
    rotBval = 1;
    UpdateRot();
  }
  else
  {
    rotBval = 0;
    UpdateRot();
  }
}


void UpdateRot()
{
  switch (rotState)
  {

    case 0:                                         // Idle state, look for direction
      if (!rotBval)
        rotState = 1;                               // CW 1
      if (!rotAval)
        rotState = 11;                              // CCW 1
      break;

    case 1:                                         // CW, wait for A low while B is low
      if (!rotBval)
      {
        if (!rotAval)
        {
          // either increment radixindex or freq
          if (digitalRead(pushPin) == LOW)
          {
            updatedisplay = 1;
            if (radix == 1000000)
              radix = 100000;
            else if (radix == 100000)
              radix = 10000;
            else if (radix == 10000)
              radix = 1000;
            else if (radix == 1000)
              radix = 100;
            else if (radix == 100)
              radix = 10;
            else if (radix == 10)
              radix = 1;
            else
              radix = 1000000;
          }
          else
          {
            freq = (freq + radix);
            if (freq > bandEnd)
              freq = bandEnd;
          }
          rotState = 2;                             // CW 2
        }
      }
      else if (rotAval)
        rotState = 0;                               // It was just a glitch on B, go back to start
      break;

    case 2:                                         // CW, wait for B high
      if (rotBval)
        rotState = 3;                               // CW 3
      break;

    case 3:                                         // CW, wait for A high
      if (rotAval)
        rotState = 0;                               // back to idle (detent) state
      break;

    case 11:                                        // CCW, wait for B low while A is low
      if (!rotAval)
      {
        if (!rotBval)
        {
          // either decrement radixindex or freq
          if (digitalRead(pushPin) == LOW)
          {
            updatedisplay = 1;
            if (radix == 1)
              radix = 10;
            else if (radix == 10)
              radix = 100;
            else if (radix == 100)
              radix = 1000;
            else if (radix == 1000)
              radix = 10000;
            else if (radix == 10000)
              radix = 100000;
            else if (radix == 100000)
              radix = 1000000;
            else
              radix = 1;
          }
          else
          {
            freq = (freq - radix);
            if (freq < bandStart)
              freq = bandStart;
          }
          rotState = 12;                            // CCW 2
        }
      }
      else if (rotBval)
        rotState = 0;                               // It was just a glitch on A, go back to start
      break;

    case 12:                                        // CCW, wait for A high
      if (rotAval)
        rotState = 13;                              // CCW 3
      break;

    case 13:                                        // CCW, wait for B high
      if (rotBval)
        rotState = 0;                               // back to idle (detent) state
      break;
  }
}


void UpdateDisplay()
{
  lcd.setCursor(0, 0);
  lcd.print("        ");
  lcd.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(10, 0);
  lcd.print("ZL2CTM");
  lcd.setCursor(10, 1);
  lcd.print("80m");

  lcd.setCursor(0, 1);
  lcd.print("        ");
  lcd.setCursor(0, 1);
  if (freq > 9999999)
  {
    if (radix == 1)
      lcd.print("       -");
    if (radix == 10)
      lcd.print("      -");
    if (radix == 100)
      lcd.print("     -");
    if (radix == 1000)
      lcd.print("    -");
    if (radix == 10000)
      lcd.print("   -");
    if (radix == 100000)
      lcd.print("  -");
    if (radix == 1000000)
      lcd.print(" -");
  }
  if (freq <= 9999999)
  {
    if (radix == 1)
      lcd.print("      -");
    if (radix == 10)
      lcd.print("     -");
    if (radix == 100)
      lcd.print("    -");
    if (radix == 1000)
      lcd.print("   -");
    if (radix == 10000)
      lcd.print("  -");
    if (radix == 100000)
      lcd.print(" -");
    if (radix == 1000000)
      lcd.print("-");
  }
}


void SendFrequency()
{
  // VFO
  si5351.set_freq(((LSB_IF_freq - freq - 70) * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);

  // BFO
  si5351.set_freq((LSB_BFO_freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK2);
}

21 comments:

  1. Hi Charlie
    I tried this Ardunio sketch it come up with errors ,
    lcd.begin();
    // Initialize the DDS
    si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
    i am using Ardunio 1.8.5 version
    ??
    email us Charlie

    73's
    Dave

    ReplyDelete
    Replies
    1. lcd.init() instead of lcd.begin()

      si5351.init(SI5351_CRYSTAL_LOAD_8PF,0,0) - note the extra comma and zero

      Delete
    2. Thanks Russel. I'm using the Si5351 from Jason Mildrum. I did note that his library calls are different from some of the others, which makes sense. Looks like we have different LCD libraries too.

      Thanks again.

      Charlie

      Delete
  2. You should look at the si5351mcu library. It's smaller than the one from J. Mildrum, and doesn't have issues with clicks when you tune. Only disadvantage is that you can only use 2 or the 3 outputs at a given time (because the library must tune the PLL as well as the multi-sync).

    ReplyDelete
  3. In both of these sections of code, your increment and decrement routines are backwards for the radix as in a CW rotation (with key press) is decrementing the radix rather than the more logical incrementing, the opposite is occurring for the CCW rotation (with key press.) The increment needs the multiply by 10 and the decrement needs the division by 10.

    ReplyDelete
  4. Hi Nick. It should be correct. Push and rotate CW moves the cursor to right, i.e., make smaller freq increments. CCW is the opposite.

    Charlie

    ReplyDelete
  5. Charlie, in the March, 2018 script for your VFO (using the eitherkit library), I don't see any place to enter the IF frequency. I'm looking for simple DDS to use with a RX to begin with. Later I want to add TFT displays, RIT, whatever else I need for CW comm. Would this be a good script use as a learning tool?

    ReplyDelete
    Replies
    1. Hi Jack. It's up the top in the variable declaration poertion. Look for: volatile uint32_t BFO_freq = 8003000;

      I suggest you don't use this script, but instead use the one in the blog for the MC1350 SSB rig (on this site). That is the current clean version I'm using here in the shack.

      Charlie

      Delete
    2. Thanks, I have some serial LCDs on the way here. I assume you are using a Nano. your BFO is set for the center at 8003. Does that mean your IF is 8 mhz and you have a wide band filter. I will use my 40 meters RX for CW. Do I set the BFO for 9000.7 or so?

      I have a grandson and his wife right now cycling around Wellington. I think they are still there.

      Delete
    3. Gidday Jack. Yes, I have a mixture of Nanos and Pro Minis. For now I'm going to settle on the Nano. As for the BFO, it might pay to watch the YouTube video that explains why I set the BFO to what it is, noting that this is for a SSB rig (https://www.youtube.com/watch?v=tas0cHTS22E). For CW, I'd expect you would set it to be 700Hz off the centre freq for the narrow band CW filter.

      Charlie

      Delete
  6. Hi Charlie. It works great. Thank you

    ReplyDelete
  7. Hi Charlie thanks for the great inspiration to get building again.. I've been a dormant ham for 17 years!! .. anyhow I used a different encoder library and it works fine. my LCD display is working and I have an optical rotary encoder .. just so you know even though the encoder is spec'd for 5-12 volts .. it runs at 3.3 fine..and smooth as butter .. and no detent.. .. I've purchased a teensy 4.0 and rev d audio board and will be full on in the next 2 weeks..as I cut over from arduino to teensy.. cheers..!.

    ReplyDelete
    Replies
    1. I'm so pleased to hear that Keith. Fantastic. Keep us posted on what you build. 73s Charlie, ZL2CTM

      Delete
  8. I am kind of a newbie to arduino and have a question. In line 57 of the code, you have a statement that includes the phrase "frequency * 100ULL"
    My question is what ULL means? I want to use this project to control my 40m Phaser from midnightdesignsolutions.com, which uses a divide by 4 function to get the actual operating frequency. That being the case, would I just change the aforementioned phrase to be "frequency * 4" and leave out the ULL?
    Thanks es 73 de Arnie W8DU

    ReplyDelete
  9. Hi Arnie. The ULL suffix makes the freq integer a 'unsigned long long int'. In your case (assuming you are using the CL0 output) try:

    si5351.set_freq((freq * 4) * 100ULL, SI5351_CLK0);

    Charlie

    ReplyDelete
  10. Thanks Charlie. I appreciate the quick reply. Always more to learn!
    And thanks for the effort you obviously put into this website!
    73 de Arnie W8DU

    ReplyDelete
  11. I have another question about your setup....Are external pull-up resistors required on the SDA and SCL lines of the Arduino?
    Tnx de Arnie W8DU

    ReplyDelete
    Replies
    1. No, I have never used those on the SDA/SCL lines Arnie.

      Delete
    2. really late but since this comes up on google sometimes: the Arduino's atmega328p has pull-up resistors built into to it that can be turned on for this. The 5135 library sets the MCU to use those internal pull-up resistors.

      Delete

Note: only a member of this blog may post a comment.