/************************************************************************************************************************************************
Amateur Radio HF ( 3 Mhz to 30 Mhz) VFO Generator with Si5351 and Arduino Uno. VU3GAO Oct/2022 - Version 2.0
Code derived from J. CesarSound - ver 1.0 - Dec/2020.
https://create.arduino.cc/projecthub/CesarSound/10khz-to-120mhz-vfo-rf-generator-with-si5351-and-arduino-3a7cad
https://www.hackster.io/CesarSound/10khz-to-225mhz-vfo-rf-generator-with-si5351-version-2-bfa619
https://github.com/prt459/Arduino_si5351_VFO_Controller_Keyer/blob/master/SP_VFO_Controller_Keyer.ino
https://lastminuteengineers.com/oled-display-arduino-tutorial/
**************************************************************************************************************************************************/
//Libraries
#include <Wire.h> //IDE Standard
#include <SSD1306Ascii.h> //https://github.com/greiman/SSD1306Ascii
#include "SSD1306AsciiWire.h"
#include <EEPROMex.h> //https://thijs.elenbaas.net/2012/07/extended-eeprom-library-for-arduino
#include <Rotary.h> //Ben Buxton https://gthub.com/brianlow/Rotary
#include <si5351.h> //Etherkit https://github.com/etherkit/Si5351Arduino
#include <EasyButton.h> //https://easybtn.earias.me/docs/introduction
#include <elapsedMillis.h> //https://github.com/pfeerick/elapsedMillis/wiki
#define fMax 30000000UL
#define fMin 3000000UL
#define ddsCal 151000 // Si5351 Calibration Value
#define encoderButtonPin 4 //Define the pin used by Encoder push button
#define vfoAddress 0 //EEPROM for saving vfo
unsigned long opsfreq, freqold, fstep;
unsigned long vfo = ((fMax - fMin)/2) + fMin;
byte stp;
bool memTag = false;
SSD1306AsciiWire display;
Rotary r = Rotary(3, 2); //Encoder defined for Interrupt Pin 3,2
Si5351 si5351(0x60); //Si5351 I2C Address 0x60
EasyButton encoderButton (encoderButtonPin, true); //Create encoderButton Object as input with internal pullup resistors
elapsedMillis memTimer;
ISR(PCINT2_vect)
{
char result = r.process();
if (result == DIR_CW)
{
set_frequency(1);
}
else if (result == DIR_CCW)
{
set_frequency(-1);
}
}
void set_frequency(short dir)
{
if (dir == 1) vfo = vfo + fstep;
if (vfo >= fMax) vfo = fMax; //Upper tuning limit
if (dir == -1) vfo = vfo - fstep;
if (vfo < fMin) vfo = fMin; //lower tuning limit
}
void setup()
{
r.begin(true); //Enable the Arduino's internal weak pull-ups for the rotary's pins external pullup
encoderButton.begin();
Wire.begin();
Wire.setClock(400000);
display.begin(&Adafruit128x64,0x3c, -1);
display.setFont(Adafruit5x7);
display.set2X();
display.clear();
display.setCursor(0,1);
displayBanner();
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, ddsCal); //Initialize Si5351, with 25Mhz Xtal
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA); //Output current 2MA, 4MA, 6MA or 8MA
si5351.output_enable(SI5351_CLK0, 1); //1 - Enable / 0 - Disable CLK
si5351.output_enable(SI5351_CLK1, 0);
si5351.output_enable(SI5351_CLK2, 0);
si5351.update_status();
cli();
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
sei(); // Enable all interrupts
freqold = EEPROM.readLong(vfoAddress);
if (freqold < fMin || freqold > fMax )
{
EEPROM.updateLong (vfoAddress,vfo);
}
else
{
vfo = freqold;
}
encoderButton.onPressed(setsteps);
stp = 4;
setsteps();
tunegen();
displayFreq();
displayRadix();
}
void loop()
{
encoderButton.read();
if (freqold != vfo)
{
memTimer = 0;
memTag = true;
tunegen();
freqold = vfo;
}
if (memTimer > 60000) save_vfo();
tunegen();
displayFreq();
displayRadix();
}
void setsteps()
{
switch (stp)
{
case 1: stp = 2; fstep = 1; break;
case 2: stp = 3; fstep = 10; break;
case 3: stp = 4; fstep = 100; break;
case 4: stp = 5; fstep = 1000; break;
case 5: stp = 6; fstep = 10000; break;
case 6: stp = 1; fstep = 1000000; break;
}
}
void tunegen()
{
display.set1X();
display.setCursor (0,5);
si5351.set_freq(vfo * 100, SI5351_CLK0); //Update operating frequency
display.print(F("VFO Mode >> CW"));
}
void displayFreq()
{
unsigned int m = vfo / 1000000;
unsigned int k = (vfo % 1000000) / 1000;
unsigned int h = (vfo % 1000) / 1;
display.setCursor(0,1);
display.set2X();
char buffer[15] = "";
if (m < 1)
{
display.setCursor(0,1); sprintf(buffer, "%003d.%003d", k, h);
}
else if (m < 100)
{
display.setCursor(0,1); sprintf(buffer, "%2d.%003d.%003d", m, k, h);
}
else if (m >= 100)
{
unsigned int h = (vfo % 1000) / 1;
display.setCursor(0,1); sprintf(buffer, "%2d.%003d.%02d", m, k, h);
}
display.print(buffer);
}
void displayRadix()
{
display.set1X();
display.setCursor(0,3);
display.print(F("--------------------"));
display.setCursor(0,7);
display.print(F("--------------------"));
display.setCursor(0,4);
display.print(F("VFO Dial >> Mhz"));
display.setCursor(0,6);
display.print(F("Step Size >>"));
if (stp == 2) display.print(F(" 1Hz")); if (stp == 3) display.print(F(" 10Hz")); if (stp == 4) display.print(F(" 100Hz"));
if (stp == 5) display.print(F(" 1KHz")); if (stp == 6) display.print(F(" 10KHz"));if (stp == 1) display.print(F(" 1MHz"));
}
void save_vfo()
{
memTimer = 0;
if (memTag)
{
memTag = false;
EEPROM.writeLong (vfoAddress,vfo);
}
}
void displayBanner()
{
display.println(F("< HF VFO >"));
display.setCursor(0,4);
display.set1X();
display.println(F("VU3GAO"));
display.println(F("Nitin William"));
display.println(F("3Mhz....30MHz"));
delay (5000);
display.clear();
}