/************************************************************************************************************************************************ 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(); }