Skip to content

Instantly share code, notes, and snippets.

@NT7S
Last active June 1, 2023 00:06
Show Gist options
  • Save NT7S/24206e12342e4631e2a7 to your computer and use it in GitHub Desktop.
Save NT7S/24206e12342e4631e2a7 to your computer and use it in GitHub Desktop.
A simple VFO for the Si5351 for either LCD or OLED
/*
Si5351 VFO
By LA3PNA 27 March 2015
Modified by NT7S 25 April 2015
Modified to be Si5351 Arduino v2 compliant by NT7S 21 Nov 2016
This version uses the new version of the Si5351 library from NT7S.
see: http://arduino.cc/en/Reference/AttachInterrupt for what pins that have interrupts.
UNO and 328 boards: Encoder on pin 2 and 3. Center pin to GND.
Leonardo: Encoder on pin 0 and 1. Center pin to GND.
100nF from each of the encoder pins to gnd is used to debounce
The pushbutton goes to pin 11 to set the tuning rate.
Pin 12 is the RX/TX pin. Put this pin LOW for RX, open or high for TX.
Single transistor switch to +RX will work.
VFO will NOT tune in TX.
LCD connections for for the LinkSprite 16 X 2 LCD Keypad Shield for Arduino.
Change as necessary for your LCD.
IF frequency is positive for sum product (IF = RF + LO) and negative for diff (IF = RF - LO)
VFO signal output on CLK0, BFO signal on CLK2
*/
// Only leave one uncommented for the display you wish to use
#define OLED
//#define LCD
#include <si5351.h>
#include "Wire.h"
// Conditional includes based on which display is defined above
#if defined(LCD)
#include <LiquidCrystal.h>
LiquidCrystal lcd( 8, 9, 4, 5, 6, 7 );
#endif
#if defined(OLED)
#include "U8glib.h"
U8GLIB_SSD1306_128X64_2X u8g(U8G_I2C_OPT_NONE); // I2C / TWI
#endif
// Class instantiation
Si5351 si5351;
// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;
volatile unsigned long frequency = 7100000UL; // This will be the frequency it always starts on.
volatile int tx;
unsigned long iffreq = 0; // set the IF frequency in Hz.
const unsigned long freqstep[] = {50, 100, 500, 1000, 5000, 10000}; // set this to your wanted tuning rate in Hz.
int corr = 0; // this is the correction factor for the Si5351, use calibration sketch to find value.
unsigned int lastReportedPos = 1; // change management
static boolean rotating = false; // debounce management
int inData;
int txpin = 12;
int freqsteps = 1;
int stepbutton = 11;
#define arraylength (sizeof(freqstep) / sizeof(freqstep[0]))
// Define hardware pins based on platform
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
int encoderPinA = 0; // right
int encoderPinB = 1; // left
#endif
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
int encoderPinA = 2; // right
int encoderPinB = 3; // left
#endif
// Interrupt on A changing state
void doEncoderA()
{
// debounce
if (rotating) delay (1); // wait a little until the bouncing is done
// Test transition, did things really change?
if (digitalRead(encoderPinA) != A_set) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if (A_set && !B_set) {
if (!tx) {
frequency += freqstep[freqsteps]; // hehre is the amount to increase the freq
}
rotating = false; // no more debouncing until loop() hits again
}
}
}
// Interrupt on B changing state, same as A above
void doEncoderB()
{
if (rotating) delay (1);
if (digitalRead(encoderPinB) != B_set) {
B_set = !B_set;
// adjust counter - 1 if B leads A
if (B_set && !A_set) {
if (!tx) {
frequency -= freqstep[freqsteps]; // here is the amount to decrease the freq
}
rotating = false;
}
}
}
void sprintf_seperated(char *str, unsigned long num)
{
// We will print out the frequency as a fixed length string and pad if less than 100s of MHz
char temp_str[6];
int zero_pad = 0;
// MHz
if(num / 1000000UL > 0)
{
sprintf(str, "%3lu", num / 1000000UL);
zero_pad = 1;
}
else
{
strcat(str, " ");
}
num %= 1000000UL;
// kHz
if(zero_pad == 1)
{
sprintf(temp_str, ",%03lu", num / 1000UL);
strcat(str, temp_str);
}
else if(num / 1000UL > 0)
{
sprintf(temp_str, ",%3lu", num / 1000UL);
strcat(str, temp_str);
zero_pad = 1;
}
else
{
strcat(str, " ");
}
num %= 1000UL;
// Hz
if(zero_pad == 1)
{
sprintf(temp_str, ",%03lu", num);
strcat(str, temp_str);
}
else
{
sprintf(temp_str, ",%3lu", num);
strcat(str, temp_str);
}
strcat(str, " MHz");
}
#if defined(OLED)
void draw_oled(void)
{
char temp_str[21];
u8g.setFont(u8g_font_unifont);
//u8g.setFont(u8g_font_helvR12);
sprintf_seperated(temp_str, frequency);
u8g.drawStr(0, 32, temp_str);
u8g.setFont(u8g_font_unifont);
sprintf(temp_str, "Step: %5u", freqstep[freqsteps]);
u8g.drawStr(0, 56, temp_str);
}
#endif
#if defined(LCD)
void draw_lcd(void)
{
char temp_str[21];
sprintf_seperated(temp_str, frequency);
lcd.setCursor(0, 0);
lcd.print(temp_str);
lcd.setCursor(6, 1);
sprintf(temp_str, "%5u", freqstep[freqsteps]);
lcd.print(temp_str);
}
#endif
void setup()
{
Serial.begin(9600);
// Set GPIO
pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);
pinMode(stepbutton, INPUT);
pinMode(txpin, INPUT);
// Turn on pullup resistors
digitalWrite(encoderPinA, HIGH);
digitalWrite(encoderPinB, HIGH);
digitalWrite(stepbutton, HIGH);
digitalWrite(txpin, HIGH);
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
//Code in here will only be compiled if an Arduino Leonardo is used.
// encoder pin on interrupt 0 (pin 0)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 1)
attachInterrupt(1, doEncoderB, CHANGE);
#endif
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Code in here will only be compiled if an Arduino Uno (or older) is used.
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
#endif
// Initialize the display
#if defined(LCD)
lcd.begin(16, 2);
lcd.print("Si5351 VFO");
delay(2000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Step: ");
#endif
#if defined(OLED)
//U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE); // I2C / TWI
// Assign default color value
if (u8g.getMode() == U8G_MODE_R3G3B2)
{
u8g.setColorIndex(255); // white
}
else if (u8g.getMode() == U8G_MODE_GRAY2BIT)
{
u8g.setColorIndex(3); // max intensity
}
else if (u8g.getMode() == U8G_MODE_BW)
{
u8g.setColorIndex(1); // pixel on
}
else if (u8g.getMode() == U8G_MODE_HICOLOR)
{
u8g.setHiColorByRGB(255,255,255);
}
#endif
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, corr);
si5351.set_freq((frequency + iffreq) * 100ULL, SI5351_CLK0);
si5351.set_freq(iffreq * 100ULL, SI5351_CLK2);
}
void loop()
{
if(digitalRead(txpin))
{
tx = 0;
}
else
{
tx = 1;
}
rotating = true; // reset the debouncer
if (lastReportedPos != frequency)
{
lastReportedPos = frequency;
// Handle LCD
#if defined(LCD)
draw_lcd();
#endif
si5351.set_freq((frequency + iffreq) * 100ULL, SI5351_CLK0);
}
// Handle OLED
#if defined(OLED)
u8g.firstPage();
do
{
draw_oled();
} while(u8g.nextPage());
delay(50);
#endif
if (Serial.available() > 0) // see if incoming serial data:
{
inData = Serial.read(); // read oldest byte in serial buffer:
}
if (inData == 'F')
{
frequency = Serial.parseInt();
inData = 0;
}
if (digitalRead(stepbutton) == LOW )
{
delay(50); // delay to debounce
if (digitalRead(stepbutton) == LOW )
{
freqsteps = freqsteps + 1;
if (freqsteps > arraylength - 1 )
{
freqsteps = 0;
}
delay(50); //delay to avoid many steps at one
}
}
}
@caulktel
Copy link

Bump!
N6ALT

@ServerNinja
Copy link

I looked into this code and I believe this library is being used here, which is different from the one that Adafruit has put on their site for the Si5351. It seems to be geared towards radio projects, and seems easier to work with too: https://github.com/etherkit/Si5351Arduino

@diep916
Copy link

diep916 commented Nov 27, 2020

The variable "lastReportedPos" is declared as unsigned int. It should be declared as unsigned long. Else, the comparison of it against the variable "frequency", unsigned long, will always fail (line 276). The program works either way; it's just that the SI5351 is updated constantly (the intent is to update only when frequency is changed.)

@k7ilo
Copy link

k7ilo commented Dec 13, 2020

Im trying to set my calibration information and if I put correction factor of 93000 in line 59 "int corr = 93000; I get error \Si5351_VFO.ino:59:12: warning: overflow in implicit constant conversion [-Woverflow] but if I put my correction factor in line 258 as si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 93000); it works without any errors. From my limited programming history, I know in line 258, corr just calls out for the value in line 59. Is it that 93000 is too large of a number in the int corr = in line 59?

Thanks

@diep916
Copy link

diep916 commented Dec 13, 2020

Im trying to set my calibration information and if I put correction factor of 93000 in line 59 "int corr = 93000; I get error \Si5351_VFO.ino:59:12: warning: overflow in implicit constant conversion [-Woverflow] but if I put my correction factor in line 258 as si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 93000); it works without any errors. From my limited programming history, I know in line 258, corr just calls out for the value in line 59. Is it that 93000 is too large of a number in the int corr = in line 59?

Thanks

Line 59 above declares "corr" as int (16 bits, max is 65,536). On the other hand, the library etherkit si5351 uses 32 bit integer for "corr". Hence, the compiler objects when you put a value of 93000 in line 59 but it's ok to pass 93000 into the line 258 calling the library.

@k7ilo
Copy link

k7ilo commented Dec 13, 2020 via email

@k7ilo
Copy link

k7ilo commented Jan 8, 2021

So I have a Kenwood TS-520 which the VFO outputs 4.9Mhz (0khz) to 5.5Mhz (600khz). What code do i need to add/change to output this only but reflect the proper frequency on the display? Thanks

@diep916
Copy link

diep916 commented Jan 12, 2021

From my understanding of how a Kenwood TS-520 works, you have multiple bands and each band has a HET oscillator frequency (for example, 40 m 7 MHz band oscillator is 15.895 MHz). So frequency 7 MHz is converted to a 1st IF of 8.895 MHz (and 7.3 MHz to 8.595 MHz). The VFO frequency ranges from 5.5 MHz to 4.9 MHz to generate a 2nd and final IF frequency of 3.395 MHz.

So, for the 40m band, you should set "frequency" (line 54) to 7 MHz and "iffreq" (line 57) to 12.5 MHz, and you MUST change the frequency calculation formula everywhere the SI5351 frequency is set from (frequency + iffreq) to (iffreq - frequency). These take place in lines 259, 285.

Of course, for each band of your TS-520, you also need to change "frequency" and "iffreq" to the corresponding starting frequency of the band and appropriate . For example, for 20 m band, "frequency" should be changed to 14 MHz and "iffreq" to 19.5 MHz. The change will make the VFO to start at 5.5 MHz (which is the result of 19.5 minus 14) at the low end of the band and go up accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment