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