#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);
}
Hi Charlie
ReplyDeleteI 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
lcd.init() instead of lcd.begin()
Deletesi5351.init(SI5351_CRYSTAL_LOAD_8PF,0,0) - note the extra comma and zero
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.
DeleteThanks again.
Charlie
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).
ReplyDeleteIn 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.
ReplyDeleteHi Nick. It should be correct. Push and rotate CW moves the cursor to right, i.e., make smaller freq increments. CCW is the opposite.
ReplyDeleteCharlie
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?
ReplyDeleteHi Jack. It's up the top in the variable declaration poertion. Look for: volatile uint32_t BFO_freq = 8003000;
DeleteI 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
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?
DeleteI have a grandson and his wife right now cycling around Wellington. I think they are still there.
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.
DeleteCharlie
Hi Charlie. It works great. Thank you
ReplyDeleteExcellent. Glad to hear.
ReplyDeleteHi 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..!.
ReplyDeleteI'm so pleased to hear that Keith. Fantastic. Keep us posted on what you build. 73s Charlie, ZL2CTM
DeleteI 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"
ReplyDeleteMy 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
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:
ReplyDeletesi5351.set_freq((freq * 4) * 100ULL, SI5351_CLK0);
Charlie
Thanks Charlie. I appreciate the quick reply. Always more to learn!
ReplyDeleteAnd thanks for the effort you obviously put into this website!
73 de Arnie W8DU
I have another question about your setup....Are external pull-up resistors required on the SDA and SCL lines of the Arduino?
ReplyDeleteTnx de Arnie W8DU
No, I have never used those on the SDA/SCL lines Arnie.
Deletereally 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.
DeleteCheers Scott.
Delete