// ========== ATS20+ (Si4732) by RA4NAL ver.2 - 01.10.2023 ===========

// Enconder PINs
#define ENCODER_PIN_A 3  // PD3
#define ENCODER_PIN_B 2  // PD2

// OLED Diaplay constants
#define I2C_ADDRESS 0x3C // 0x3C or 0x3D
#define RST_PIN -1       // Define proper RST_PIN if required.

// Reset pin Si4732
#define RESET_PIN 12 

// For read buttons in interrupts
volatile uint16_t StateBut = 0;      // Button state
volatile uint16_t IntTimePush = 0;   // Push button Time counter
// volatile uint16_t IntTimeFree = 0;   // Free button Time counter

uint16_t OldBut = 0;                 // Old button state
uint16_t TimePush, button;           // For button
uint16_t temp16;
// uint16_t TimeFree;

// Encoder control variables
int8_t encoderCount = 0;

// StateBut:
// 0 0 0 0 0 0 0  x   x     x     x   x    x   x    x      x
//               PC0 PD7   PD6   PD5 PD4  PB3 PB2  PB1    PB0
//               BFO VOL_D VOL_U BW  MODE AGC STEP BAND_D BAND_U
// Analog input for battery test - A2 (PC2)
// Digital output for on/off RF amplifier A1 (PC1) =0 for AM,SSB; =1 for FM

// avr-libc library includes
#include <avr/io.h>
#include <Wire.h>
#include <EEPROM.h>
#include "Rotary.h"
#include "Buttons.h"
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"

#include <SI4735.h>
#include <patch_ssb_compressed.h>    // Compressed SSB patch version (saving almost 1KB)

const uint16_t size_content = sizeof ssb_patch_content; // See ssb_patch_content.h
const uint16_t cmd_0x15_size = sizeof cmd_0x15;         // Array of lines where the 0x15 command occurs in the patch content.
 
#define FM_BAND_TYPE 0
#define MW_BAND_TYPE 1
#define SW_BAND_TYPE 2
#define LW_BAND_TYPE 3

#define FM 0
#define LSB 1
#define USB 2
#define AM 3

#define MIN_ELAPSED_RSSI_TIME 150

#define DEFAULT_VOLUME 40    // change it for your favorite sound volume
#define VOL_UP_SSB     5     // volume_ssb = volume + VOL_UP_SSB

#define BAND_USB       11    // if (bandIdx >= BAND_USB) --> USB, else --> LSB

const uint8_t app_id = 0x5A; // Useful to check the EEPROM content before processing useful data
const int eeprom_address = 0;
long elapsedBatt = millis();

const char *bandModeDesc[] = {"FM ", "LSB", "USB", "AM "};
uint8_t currentMode = FM;
uint8_t seekDirection = 1;

bool ssbLoaded = false;
bool fmStereo = false;
bool rdsON = true;
bool SSBmode = true;

int currentBFO = 0;

long elapsedRSSI = millis();

// Some variables to check the SI4735 status
uint16_t currentFrequency;
uint16_t previousFrequency;
uint8_t currentStep = 1;
uint8_t currentBFOStep = 1;  //100;
uint16_t realBFOStep = 1000; //100;

uint16_t realSSBFRQ;

// Datatype to deal with bandwidth on AM, SSB and FM in numerical order.
// Ordering by bandwidth values.   
typedef struct
{
  uint8_t idx;      // SI473X device bandwidth index value
  const char *desc; // bandwidth description
} Bandwidth;

int8_t bwIdxSSB = 4;
Bandwidth bandwidthSSB[] = {
  {4, "0.5"},  // 0  
  {5, "1.0"},  // 1
  {0, "1.2"},  // 2
  {1, "2.2"},  // 3
  {2, "3.0"},  // 4  - default
  {3, "4.0"}   // 5
}; // 3 = 4kHz

int8_t bwIdxAM = 4;
const int maxFilterAM = 15;
Bandwidth bandwidthAM[] = {
  {4, "1.0"},   // 0
  {5, "1.8"},   // 1
  {3, "2.0"},   // 2
  {6, "2.5"},   // 3
  {2, "3.0"},   // 4 - default 
  {1, "4.0"},   // 5
  {0, "6.0"}    // 6 
};
/*
 int8_t bwIdxFM = 0;
 Bandwidth bandwidthFM[] = {
  {0, "AUT"}, // Automatic - default
  {1, "110"}, // Force wide (110 kHz) channel filter.
  {1, " 84"},
  {2, " 60"},
  {3, " 40"}
 };
*/
// Atenuator and AGC
uint8_t agcIdx = 0;
uint8_t disableAgc = 0;
uint8_t agcNdx = 0;
// AVC
uint8_t avcIdx = 0;
uint8_t max_avc_gain = 48;
uint8_t avcNdx = 1; // max AVC

// Band data structure
typedef struct
{
  uint8_t bandType;     // Band type (FM, MW or SW)
  uint16_t minimumFreq; // Minimum frequency of the band
  uint16_t maximumFreq; // maximum frequency of the band
  uint16_t currentFreq; // Default frequency or current frequency
  uint16_t currentStep; // Default step (increment and decrement)
  int8_t  bandwidth;  // Bandwidth local table index.
} Band;

/*
   Band table
   To add a new band, all you have to do is insert a new line in the table below.
   No extra code will be needed.
   Remove or comment a line if you do not want a given band
   You have to RESET the eeprom after modiging this table. 
   Turn your receiver on with the encoder push button pressed at first time
   to RESET the eeprom content.
*/
Band band[] = {
  {FM_BAND_TYPE, 6400, 8400, 7000, 5, 0},  // FM from 64 to 84 MHz
  {FM_BAND_TYPE, 8400, 10800, 10570, 10, 0},
  {LW_BAND_TYPE, 150, 520, 300, 5, 4},
  {MW_BAND_TYPE, 520, 1720, 810, 5, 4},  
//  {MW_BAND_TYPE, 531, 1701, 783, 9, 4},   // MW for Europe, Africa and Asia
  {SW_BAND_TYPE, 1720, 3500, 1900, 1, 4}, // 160 meters
  {SW_BAND_TYPE, 3500, 4500, 3700, 1, 4}, // 80 meters
  {SW_BAND_TYPE, 4500, 5500, 4850, 5, 4},
  {SW_BAND_TYPE, 5500, 6300, 6000, 5, 4},
  {SW_BAND_TYPE, 6300, 7800, 7200, 1, 4}, // 40 meters
  {SW_BAND_TYPE, 7800, 10000, 9600, 5, 4},
  {SW_BAND_TYPE, 10000, 11200, 10100, 1, 4}, // 30 meters
  {SW_BAND_TYPE, 11200, 12500, 11940, 5, 4},
  {SW_BAND_TYPE, 12500, 13900, 13600, 5, 4},
  {SW_BAND_TYPE, 13900, 14500, 14200, 1, 4}, // 20 meters
  {SW_BAND_TYPE, 14500, 16000, 15300, 5, 4},
  {SW_BAND_TYPE, 16000, 18000, 17600, 5, 4},
  {SW_BAND_TYPE, 18000, 20000, 18100, 1, 4},  // 17 meters
  {SW_BAND_TYPE, 20000, 23000, 21200, 1, 4},  // 15 mters
  {SW_BAND_TYPE, 23000, 26200, 24940, 1, 4},  // 12 meters
  {SW_BAND_TYPE, 26200, 28000, 27500, 1, 4},  // CB band (11 meters)
  {SW_BAND_TYPE, 28000, 30000, 28400, 1, 4}   // 10 meters
}; 

const int lastBand = (sizeof band / sizeof(Band)) - 1;
int bandIdx = 1;

uint8_t rssi = 0;
uint8_t aux;
uint8_t volume = DEFAULT_VOLUME;  
uint8_t volume_ssb = 0 ;

// Devices class declarations
Rotary encoder = Rotary(ENCODER_PIN_A, ENCODER_PIN_B);
SI4735 si4735;
SSD1306AsciiWire oled;

// ===================== SETUP ====================================
void setup()
{
  InitButtons();
  
// Encoder interrupt
  attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), rotaryEncoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), rotaryEncoder, CHANGE);

  Wire.begin();
  Wire.setClock(400000L);

//++++ UNcomment for SSD1306, COMMEMT for SH1106 ++++

#if RST_PIN >= 0
  oled.begin(&Adafruit128x64, I2C_ADDRESS, RST_PIN);
#else // RST_PIN < 0
  oled.begin(&Adafruit128x64, I2C_ADDRESS);
#endif 

//++++ UNcomment for SH1106, COMMENT for SSD1306 ++++

//#if RST_PIN >= 0
//  oled.begin(&SH1106_128x64, I2C_ADDRESS, RST_PIN);
//#else // RST_PIN < 0
//  oled.begin(&SH1106_128x64, I2C_ADDRESS);
//#endif
//+++++ End SSD1306 or SH1106 +++++++

// Splash - Change it for your introduction text.
  oled.clear();
  oled.setFont(X11fixed7x14B);
  oled.setCursor(10, 0);
  oled.print("ATS-20 (Si4732)");
  oled.setCursor(20, 2);
  oled.set2X();
  oled.println("RA4NAL");
  oled.set1X(); 
  oled.setCursor(25, 6);
  oled.print("01.10.2023");
  delay(2000);
// end Splash

// If you want to reset the eeprom, keep the BFO_SWITCH button pressed during statup
 if (StateBut & BFO_SWITCH)
  {
    oled.clear();
    EEPROM.update(eeprom_address, 0);
    oled.setCursor(15, 1);
    oled.print("EEPROM RESETED");
    delay(2000);
  }
 while (StateBut); // wait for off all buttons
 oled.clear();
//End EEPROM reset

 si4735.getDeviceI2CAddress(RESET_PIN); // Looks for the I2C bus address and set it.  Returns 0 if error
 si4735.setup(RESET_PIN, MW_BAND_TYPE);

 delay(300);
// Checking the EEPROM content

 if (EEPROM.read(eeprom_address) == app_id)
  {
    readAllReceiverInformation();
  } 

// Set up the radio for the current band (see index table variable bandIdx )
  useBand();
  currentFrequency = previousFrequency = si4735.getFrequency();

//  si4735.setVolume(volume);
  if (currentMode == LSB || currentMode == USB)
    {
     si4735.setVolume(volume_ssb);
    }
    else
    {
      si4735.setVolume(volume);
    }

  oled.clear();
  showStatus(); 
  showBattery();
 
// ------ Test Print ------
/*
Serial.begin(115200);
Serial.println(band[bandIdx].currentFreq);
Serial.println(currentFrequency);
Serial.println(ReadVcc());
*/
// ------ End Test --------

}
// ===================== END of SETUP ===========================

void saveAllReceiverInformation()
 {
   int addr_offset;
   EEPROM.update(eeprom_address, app_id); // stores the app id;
//   EEPROM.update(eeprom_address + 1, si4735.getVolume()); // stores the current Volume
   EEPROM.update(eeprom_address + 1, volume);
   EEPROM.update(eeprom_address + 2, bandIdx); // Stores the current band
   EEPROM.update(eeprom_address + 3, currentMode); // Stores the current Mode (FM / AM / SSB)
   EEPROM.update(eeprom_address + 4, currentBFO >> 8);
   EEPROM.update(eeprom_address + 5, currentBFO &  0XFF);

   addr_offset = 6;
   band[bandIdx].currentFreq = currentFrequency;

   for (int i = 0; i <= lastBand; i++ )
    {
      EEPROM.update(addr_offset++, (band[i].currentFreq >> 8) );   // stores the current Frequency HIGH byte for the band
      EEPROM.update(addr_offset++, (band[i].currentFreq & 0xFF));  // stores the current Frequency LOW byte for the band
      EEPROM.update(addr_offset++, band[i].currentStep);          // Stores current step of the band
      EEPROM.update(addr_offset++, band[i].bandwidth);           // table index (direct position) of bandwidth
    }
}
//--------------------------------------------------------------

void readAllReceiverInformation()
{
  int addr_offset;
  int bwIdx;
  volume = EEPROM.read(eeprom_address + 1); // Gets the stored volume;
  volume_ssb = volume + VOL_UP_SSB;
  if ( volume_ssb > 63 ) volume_ssb = 63 ;
  bandIdx = EEPROM.read(eeprom_address + 2);
  currentMode = EEPROM.read(eeprom_address + 3);
  currentBFO = EEPROM.read(eeprom_address + 4) << 8;
  currentBFO |= EEPROM.read(eeprom_address + 5);

  addr_offset = 6;
  for (int i = 0; i <= lastBand; i++ )
  {
    band[i].currentFreq = EEPROM.read(addr_offset++) << 8;
    band[i].currentFreq |= EEPROM.read(addr_offset++);
    band[i].currentStep = EEPROM.read(addr_offset++);
    band[i].bandwidth = EEPROM.read(addr_offset++);
  }

  previousFrequency = currentFrequency = band[bandIdx].currentFreq;
  currentStep = band[bandIdx].currentStep;
  bwIdx = band[bandIdx].bandwidth;

  if (currentMode == LSB || currentMode == USB)
  {
      loadSSB();
      bwIdxSSB = (bwIdx > 5)? 5: bwIdx;
      si4735.setSSBAudioBandwidth(bandwidthSSB[bwIdxSSB].idx);
      // If audio bandwidth selected is about 2 kHz or below, it is recommended to set Sideband Cutoff Filter to 0.
      if (bandwidthSSB[bwIdxSSB].idx == 0 || bandwidthSSB[bwIdxSSB].idx == 4 || bandwidthSSB[bwIdxSSB].idx == 5)
          si4735.setSSBSidebandCutoffFilter(0);
       else
          si4735.setSSBSidebandCutoffFilter(1);
   }
   else if (currentMode == AM)
   {
        bwIdxAM = bwIdx;
        si4735.setBandwidth(bandwidthAM[bwIdxAM].idx, 1);
   } else
     {
//        bwIdxFM = 0;   // bwIdx;
        si4735.setFmBandwidth(1);      //110 kHz
        SetStereoMono();
//        si4735.setFmBandwidth(bandwidthFM[bwIdxFM].idx);
     }
} 
//-----------------------------------------------------------------

// Switch the radio to current band.
// The bandIdx variable points to the current band.
// This function change to the band referenced by bandIdx (see table band).
// Show some basic information on display
void showStatus()
{
  showFrequency();
  oled.setFont(X11fixed7x14B);
  showSTEP();
  showBandwidth();
  ShowAGC();
  showRSSI();
  showVolume();
}
//-------------------------------------------------------------------

// Converts a number to a char string and places leading zeros.
// It is useful to mitigate memory space used by sprintf or generic similar function
void convertToChar(uint16_t value, char *strValue, uint8_t len, uint8_t dot)
{
  char d;
  for (char i = (len - 1); i >= 0; i--)
  {
    d = value % 10;
    value = value / 10;
    strValue[i] = d + 48;
  }
  strValue[len] = '\0';
  if ( dot > 0 )
   {
     for (char i = len; i >= dot ; i-- )
      {
       strValue[i + 1] = strValue[i];
      }
     strValue[dot] = '.';
  }

  if (strValue[0] == '0')
   {
    strValue[0] = '-';
   }
}
//----------------------------------------------------------

// Show current frequency
void showFrequency()
{
  uint16_t tmp;
  uint8_t d;
  char *bandMode;
  char freqDisplay[10];

  oled.setFont(lcdnums14x24);
  if (band[bandIdx].bandType == FM_BAND_TYPE)
   {
    convertToChar(currentFrequency, freqDisplay, 5, 3);
    strcat (freqDisplay, "0-");
    oled.setCursor(0, 2);
    oled.print(freqDisplay);
   }
  else        // AM, LSB or USB
   {
     realSSBFRQ = currentFrequency;
     tmp = 0;
     if (currentMode == LSB || currentMode == USB)
      {
        if ( currentBFO > 0)
          {
            realSSBFRQ += currentBFO / 1000;
            tmp = abs (currentBFO) % 1000;
            tmp = tmp / 10;
          }
        else
          if ( currentBFO < 0)
            {
              realSSBFRQ -= (abs (currentBFO) - 1) / 1000 + 1;
              tmp = abs (currentBFO) % 1000;
              tmp = 100 - tmp / 10;
            }
      }
     convertToChar(realSSBFRQ, freqDisplay, 5, 0);
     oled.setCursor(0, 2);
     oled.print(freqDisplay);
     d = tmp % 10;
     tmp = tmp / 10;
     freqDisplay[2] = d + 48;
     d = tmp % 10;
     tmp = tmp / 10;
     freqDisplay[1] = d + 48;
     freqDisplay[0] = '.';
     freqDisplay[3] = '\0';
     oled.print(freqDisplay);
   }

  oled.setFont(X11fixed7x14B);
  bandMode = (char *) bandModeDesc[currentMode];
  oled.setCursor(0, 0);
  oled.print(bandMode);

  if (band[bandIdx].bandType != FM_BAND_TYPE)
    {
      oled.setCursor(113, 3);
      oled.print("  ");
    }
}
//----------------------------------------------------

// Show Step
void showSTEP()
 {
   oled.setCursor(35, 0);
   oled.print("    ");
   if (currentMode == AM)
    {
      oled.setCursor(35, 0);
      oled.print(currentStep);
      oled.print("k");
    }
   if (currentMode == LSB || currentMode == USB)
    {
      oled.setCursor(42, 0);
      oled.print("k");
      oled.setCursor(35, 0);
      oled.print(currentBFOStep);
    }
 }
//-----------------------------------------------------

// Show AGC Information
void ShowAGC()
 {
   if (currentMode == AM || currentMode == FM )
   {
    si4735.getAutomaticGainControl();
    oled.setCursor(0, 6);
    if (agcIdx == 0 )
     {
       oled.print("AGC");
     }
    else
     {
       oled.print('-');
       if (agcNdx < 10)
         oled.print('0');
       oled.print(agcNdx);
     }
  }

  if (currentMode == LSB || currentMode == USB )
   {
    oled.setCursor(0, 6);
    switch (avcNdx)
       {
         case 1:
          {
            oled.print("AVC");
            break;
          }
         case 2:
          {
            oled.print("-6 ");
            break;
          }
         case 3:
          {
            oled.print("-12");
            break;
          }
         case 4:
          {
            oled.print("-18");
            break;
          }
     }
   }

 }

//-----------------------------------------------

// Shows RSSI/RSN status
void showRSSI()
{
  uint8_t n, m;
  char strRSSI[3];
  
  if (currentMode == FM)
   {
    oled.setCursor(113, 3);
    if (si4735.getCurrentPilot() && fmStereo )
     { 
      oled.print("ST");
     }
    else
      if (fmStereo)  oled.print("  ");
      else  oled.print("MN");
  if (rdsON)
    {
      oled.setCursor(40, 6);
      oled.print("SNR ");
      m = rssi;
      n = m %10;
      m = m / 10;
      strRSSI[1] = n +48;
      n = m % 10;
      strRSSI[0] = n +48;
      strRSSI[2] = '\0';
      oled.print(strRSSI);
    }
  else
    {
      oled.setCursor(40, 6);
      oled.print("      ");
    }
   }
}
//--------------------------------------------

// Shows the volume level on OLED
void showVolume()
{
  if (volume < 10)
  {
   oled.setCursor(113, 0);
   //oled.print(si4735.getCurrentVolume());
   oled.print (volume);
   oled.print(' ');
  }
  else
  {
  oled.setCursor(113, 0);
  //oled.print(si4735.getCurrentVolume());
  oled.print (volume);
  }
}

// Shows battery on OLED
void showBattery()
{
  uint8_t n, m;
  char strBatt[5];

  m = ReadVcc();
  n = m %10;
  m = m / 10;
  strBatt[2] = n +48;
  n = m % 10;
  m = m / 10;
  strBatt[1] = n +48;
  n = m % 10;
  strBatt[0] = n +48;
  strBatt[3] = '%';
  strBatt[4] = '\0';
  if (strBatt[0] == '0')  strBatt[0] = ' ';
  oled.setCursor(100, 6);
  oled.print(strBatt);
}
//--------------------------------------------------

// SHow bandwidth on AM,SSB and FM mode
void showBandwidth()
{
  char bw[7];

  strcpy(bw, "[]");
  if (currentMode == LSB || currentMode == USB)
  {
   if (bwIdxSSB >5) bwIdxSSB = 5;
   strcat (bw, bandwidthSSB[bwIdxSSB].desc);
  }
  else if (currentMode == AM)
    {
      strcat(bw, bandwidthAM[bwIdxAM].desc);
    }
      else
       {
         strcpy(bw, "      ");
       }
  oled.setCursor(68, 0);
  oled.print(bw);
}
//--------------------------------------------------

void useBand()
{
  cleanBfoRdsInfo();

  if (band[bandIdx].bandType == FM_BAND_TYPE)
   {
    digitalWrite(A1, 1);   // A1 = PC1 = 1 - FM mode
    agcIdx = 0;
    disableAgc = 0;        // Turns AGC ON
    agcNdx = 0;
    currentMode = FM;
    si4735.setTuneFrequencyAntennaCapacitor(0);
    si4735.setFM(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq, band[bandIdx].currentFreq, band[bandIdx].currentStep);
    si4735.setSeekFmLimits(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq);
    si4735.setSeekFmSpacing(band[bandIdx].currentStep);
    si4735.setSeekFmRssiThreshold(6);  // Default 20
    si4735.setSeekFmSNRThreshold(2);   //default 3
    si4735.setFmSoftMuteMaxAttenuation(0);  //disable Soft Mute for FM
    ssbLoaded = false;
    si4735.setAutomaticGainControl(disableAgc, agcNdx);
    si4735.setRdsConfig(1, 2, 2, 2, 2);
//    bwIdxFM = 0;     // band[bandIdx].bandwidth; 
    si4735.setFmBandwidth(1);            // 110 kHz
    SetStereoMono();
    checkRDS();
//    si4735.setFmBandwidth(bandwidthFM[bwIdxFM].idx);    
   }
  else
   {
    digitalWrite(A1, 0);   // A1 = PC1 = 0 - AM, SSB mode
    oled.setCursor(40, 6);
    oled.print("      ");
    if (band[bandIdx].bandType == MW_BAND_TYPE || band[bandIdx].bandType == LW_BAND_TYPE)
      si4735.setTuneFrequencyAntennaCapacitor(0);
    else
      si4735.setTuneFrequencyAntennaCapacitor(1);
    if (ssbLoaded)
     {
      si4735.setSSB(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq, band[bandIdx].currentFreq, band[bandIdx].currentStep, currentMode);
      si4735.setSSBAutomaticVolumeControl(1);
      si4735.setSsbSoftMuteMaxAttenuation(0); // Disable Soft Mute for SSB
      si4735.setAutomaticGainControl(disableAgc, agcNdx);
      bwIdxSSB = band[bandIdx].bandwidth;
      if (bwIdxSSB > 5) bwIdxSSB = 5;
      si4735.setSSBAudioBandwidth(bandwidthSSB[bwIdxSSB].idx);
      si4735.setSSBBfo(-currentBFO);   
      si4735.setAvcAmMaxGain(max_avc_gain);             // 12...90dB (42)
    //================================
    si4735.setSsbIfAgcAttackRate(4);
    si4735.setSsbIfAgcReleaseRate(140);
    //================================
     }
    else
     {
      currentMode = AM;
      si4735.setAM(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq, band[bandIdx].currentFreq, band[bandIdx].currentStep);
      si4735.setAutomaticGainControl(disableAgc, agcNdx);
      si4735.setAmAgcReleaseRate(20);         // Default 140
      si4735.setAmSoftMuteMaxAttenuation(0);  // Disable Soft Mute for AM
      bwIdxAM = band[bandIdx].bandwidth;
      si4735.setBandwidth(bandwidthAM[bwIdxAM].idx, 1);
      si4735.setAvcAmMaxGain(40);             // default 48 (12...90)
     }
    si4735.setSeekAmLimits(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq); // Consider the range all defined current band
    si4735.setSeekAmSpacing((band[bandIdx].currentStep > 10) ? 10 : band[bandIdx].currentStep); // Max 10kHz for spacing
    si4735.setSeekAmRssiThreshold(6);   // Default 25
    si4735.setSeekAmSNRThreshold(2);    // Default 5
  }
  delay(100);
  currentFrequency = band[bandIdx].currentFreq;
  currentStep = band[bandIdx].currentStep;

  showStatus();
}
//---------------------------------------------------

// Goes to the next band (see Band table)
void bandUp()
{
  if ((currentMode == USB) || (currentMode == LSB))
  { 
    currentFrequency = realSSBFRQ;
    currentBFO = 0;
  }
  // save the current frequency for the band
  band[bandIdx].currentFreq = currentFrequency;
  band[bandIdx].currentStep = currentStep;

  if (bandIdx < lastBand)
  {
    bandIdx++;
  }
  else
  {
    bandIdx = 0;
  }
  if ((currentMode == LSB) && (bandIdx >= BAND_USB))
    {
      currentMode = USB;
      SSBmode = true;  
    }
  if ((currentMode == USB) && (bandIdx < BAND_USB))
    {
      currentMode = LSB;
      SSBmode = true;  
    }
  useBand();
}
//------------------------------------------------------
// Goes to the previous band (see Band table)
void bandDown()
{
  if ((currentMode == USB) || (currentMode == LSB))
  { 
    currentFrequency = realSSBFRQ;
    currentBFO = 0;
  }
  // save the current frequency for the band
  band[bandIdx].currentFreq = currentFrequency;
  band[bandIdx].currentStep = currentStep;
  if (bandIdx > 0)
  {
    bandIdx--;
  }
  else
  {
    bandIdx = lastBand;
  }
  if ((currentMode == LSB) && (bandIdx >= BAND_USB))
    {
      currentMode = USB;
      SSBmode = true;  
    }
  if ((currentMode == USB) && (bandIdx < BAND_USB))
    {
      currentMode = LSB;
      SSBmode = true;  
    }
  useBand();
}
//----------------------------------------------

// This function loads the contents of the ssb_patch_content array into the
// CI (Si4735) and starts the radio on SSB mode.
void loadSSB()
{
  oled.setCursor(24, 0);
  oled.print("Loading SSB ");
  //  agcIdx = 0;
  //  disableAgc = 0; // Turns AGC ON
  //  agcNdx = 0;
  //  si4735.setI2CFastModeCustom(850000); // It is working. Faster, but I'm not sure if it is safe.
  //  si4735.setI2CFastModeCustom(500000);
  si4735.queryLibraryId(); // Is it really necessary here? I will check it.
  si4735.patchPowerUp();
  delay(50);
  si4735.downloadCompressedPatch(ssb_patch_content, size_content, cmd_0x15, cmd_0x15_size);
  si4735.setSSBConfig(bandwidthSSB[bwIdxSSB].idx, 1, 0, 1, 0, 1);    
//  si4735.setI2CStandardMode();
  ssbLoaded = true;
  cleanBfoRdsInfo();
  currentBFOStep = 1;
  realBFOStep = 1000;
} 
//---------------------------------------------

char *stationName;
char bufferStatioName[20];
long rdsElapsed = millis();
char oldBuffer[15];

// Show the Station Name. 
void showRDSStation()
{
  char *po, *pc;
  int col = 24;
  
  po = oldBuffer;
  pc = stationName;
  while (*pc) {
    if ( *po != *pc )
     {
        oled.setCursor(col, 0);
        oled.print(*pc); 
     }
    *po = *pc;    
    po++;
    pc++;
    col+=10;
  }
  // strcpy(oldBuffer, stationName);
  delay(120);
}
//--------------------------------------------

// Checks the station name is available
void checkRDS()
{
  si4735.getRdsStatus();
  if (si4735.getRdsReceived())
  {
    if (si4735.getRdsSync() && si4735.getRdsSyncFound())
    {
      stationName = si4735.getRdsText0A();
      if (stationName != NULL && (millis() - rdsElapsed) > 10 ) {
        showRDSStation();
        rdsElapsed = millis();
      }
    }
  }
} 
//----------------------------------------------

//Clean RDS    
void cleanBfoRdsInfo()
{
  oled.setCursor(24, 0);
  oled.print("            ");
}
//-----------------------------------------------

//This function is called by the seek function process.
void showFrequencySeek(uint16_t freq)
  {
    currentFrequency = freq;
    showFrequency();
  }
//-----------------------------------------------

//   Checks the stop seeking criterias.
//   Returns true if the user press the touch or rotates the encoder.
bool checkStopSeeking()
  {
// Checks the BFO_SWITCH and encoder
    return (bool) (encoderCount || (StateBut & BFO_SWITCH));
  }
//-----------------------------------------------

void SetStereoMono()
  {
     if (fmStereo)
       {
         si4735.setFMDeEmphasis(1);                 // 50 mks
         si4735.setFmBlendStereoThreshold(0);
         si4735.setFmBlendMonoThreshold(0);
         si4735.setFmBlendRssiStereoThreshold(0);
         si4735.setFmBLendRssiMonoThreshold(0);
         si4735.setFmBlendSnrStereoThreshold(0);
         si4735.setFmBLendSnrMonoThreshold(0);
         si4735.setFmBlendMultiPathStereoThreshold(100);
         si4735.setFmBlendMultiPathMonoThreshold(100);
       }        
     else
       {
         si4735.setFmBlendStereoThreshold(127);
         si4735.setFmBlendMonoThreshold(127);
         si4735.setFmBlendRssiStereoThreshold(127);
         si4735.setFmBLendRssiMonoThreshold(127);
         si4735.setFmBlendSnrStereoThreshold(127);
         si4735.setFmBLendSnrMonoThreshold(127);
         si4735.setFmBlendMultiPathStereoThreshold(0);
         si4735.setFmBlendMultiPathMonoThreshold(0);
       }        
  }
//-------------------------------------------------------

// Use Rotary.h and Rotary.cpp implementation to process encoder via interrupt
  void rotaryEncoder()
  { // rotary encoder events
    uint8_t encoderStatus = encoder.process();
    if (encoderStatus)
     {
       if (encoderStatus == DIR_CW)
        {
         encoderCount = 1;
        }
       else
        {
         encoderCount = -1;
        }
    }
  }

// ===================== LOOP ====================================
void loop()
{
// Check if the encoder has moved.
  if (encoderCount != 0)
  {
   previousFrequency = 0;  // Forces eeprom update, etc
//   IntTimeFree = 0;
//   if (IntTimeFree != 0)  IntTimeFree = 0;

   if (ssbLoaded)
    {
      currentBFO = (encoderCount == 1) ? (currentBFO + realBFOStep) : (currentBFO - realBFOStep);
      if (currentBFO >= 16000)
        {
          currentBFO -= 16000;
          currentFrequency += 16;
          si4735.setFrequency(currentFrequency);
          si4735.setAutomaticGainControl(disableAgc, agcNdx);
        }
      if (currentBFO <= -16000)
        {
          currentBFO += 16000;
          currentFrequency -= 16;
          si4735.setFrequency(currentFrequency);
          si4735.setAutomaticGainControl(disableAgc, agcNdx);
        }
      si4735.setSSBBfo(-currentBFO);
    }
    else
    {
      if ((encoderCount == 1) && (currentFrequency < band[bandIdx].maximumFreq))
       {
        si4735.frequencyUp();
        seekDirection = 1;
       }
        if ((encoderCount == -1) && (currentFrequency > band[bandIdx].minimumFreq))
       {
        si4735.frequencyDown();
        seekDirection = 0;
       }
// Show the current frequency only if it has changed
      currentFrequency = si4735.getFrequency();
    }
      showFrequency();
      encoderCount = 0;
  }
// -------- Check buttons --------

  TimePush = IntTimePush;
  if (TimePush != IntTimePush)  TimePush = IntTimePush; // Interrupt took place
//  TimeFree = IntTimeFree;
//  if (TimeFree != IntTimeFree)  TimeFree = IntTimeFree; // Interrupt took place

  button = StateBut;
  temp16 = ((OldBut ^ button) & button);     // Check if any -|_

  OldBut = button;                       // Update
  if (TimePush > 150)                    // Auto repeat
    {
      IntTimePush = 0;
      TimePush = IntTimePush;
      if (TimePush != IntTimePush)  TimePush = IntTimePush; // Interrupt took place
      temp16 = button;
    }
//----------------------------------------------
// Check BAND_BUTTON_UP
  if (temp16 & BAND_BUTTON_UP)
       bandUp();
// Check BAND_BUTTON_DOWN
  if (temp16 & BAND_BUTTON_DOWN)
       bandDown(); 
//----------------------------------------------
// Check VOL_UP
  if (temp16 & VOL_UP)
    {
      IntTimePush = 100;      // Fast auto repeat
//      si4735.volumeUp();
//      volume = si4735.getVolume();
      volume++;
      if ( volume > 63 ) volume = 63 ;
       if (currentMode == AM || currentMode == FM) si4735.setVolume(volume);
      if (currentMode == LSB || currentMode == USB)
       {
	      volume_ssb = volume + VOL_UP_SSB ;
	      if ( volume_ssb > 63 ) volume_ssb = 63 ;
	      si4735.setVolume(volume_ssb);
       }
      showVolume();
    }
// Check VOL_DOWN
  if (temp16 & VOL_DOWN)
    {
      IntTimePush = 100;      // Fast auto repeat
//      si4735.volumeDown();
//      volume = si4735.getVolume();
      volume--;
      if ( volume <= 1 ) volume = 1 ;
       if (currentMode == AM || currentMode == FM) si4735.setVolume(volume);
      if (currentMode == LSB || currentMode == USB)
       {
	      volume_ssb = volume + VOL_UP_SSB ;
	      if ( volume_ssb > 63 ) volume_ssb = 63 ;
	      si4735.setVolume(volume_ssb);
       }
      showVolume();
    }
//-----------------------------------------------
// Check BANDWIDTH
  if (temp16 & BANDWIDTH_BUTTON)
    {
     if (currentMode == LSB || currentMode == USB)
      {
         bwIdxSSB++;
         if (bwIdxSSB > 5)
           bwIdxSSB = 0;
         band[bandIdx].bandwidth = bwIdxSSB;
         si4735.setSSBAudioBandwidth(bandwidthSSB[bwIdxSSB].idx);
// If audio bandwidth selected is about 2 kHz or below, it is recommended to set Sideband Cutoff Filter to 0.
         if (bandwidthSSB[bwIdxSSB].idx == 0 || bandwidthSSB[bwIdxSSB].idx == 4 || bandwidthSSB[bwIdxSSB].idx == 5)
           si4735.setSSBSidebandCutoffFilter(0);
         else
           si4735.setSSBSidebandCutoffFilter(1);
      }
     else
      if (currentMode == AM)
       {
         bwIdxAM++;
         if (bwIdxAM > 6)
           bwIdxAM = 0;
           band[bandIdx].bandwidth = bwIdxAM;
           si4735.setBandwidth(bandwidthAM[bwIdxAM].idx, 1);
       }      
      else   // FM
       {
         rdsON = !rdsON;
         cleanBfoRdsInfo();
       }
/*
       {
         bwIdxFM++;
         if (bwIdxFM > 4)
           bwIdxFM = 0;
         else
         if (bwIdxFM < 0)
           bwIdxFM = 4;
         band[bandIdx].bandwidth = bwIdxFM; 
         si4735.setFmBandwidth(bandwidthFM[bwIdxFM].idx);
      }
*/
      showStatus();
    } 
//---------------------------------------------------
// Check AGC
 if (temp16 & AGC_SWITCH)
  { 
    if (currentMode == AM || currentMode == FM )
    {
      agcIdx += 1;
      if (agcIdx >4)  agcIdx = 0;
      switch (agcIdx)
       {
         case 0:
          {
            disableAgc = 0; // Turns AGC ON
            agcNdx = 0;
            break;
          }
         case 1:
          {
            disableAgc = 1; // Turns AGC OFF
            agcNdx = 0;     // Sets minimum attenuation
            break;
          }
         case 2:
          {
            disableAgc = 1; // Turns AGC OFF
            agcNdx = 6;    // Increases the attenuation AM/SSB AGC Index  = 6
            break;
          }
         case 3:
          {
            disableAgc = 1; // Turns AGC OFF
            agcNdx = 12;    // Increases the attenuation AM/SSB AGC Index  = 12
            break;
          }
         case 4:
          {
            disableAgc = 1; // Turns AGC OFF
            agcNdx = 18;    // Increases the attenuation AM/SSB AGC Index  = 18
          }
       }
// Sets AGC on/off and gain
      si4735.setAutomaticGainControl(disableAgc, agcNdx);
      showStatus();
    }
// Check AVC for SSB
 if (currentMode == LSB || currentMode == USB)
  {
    avcIdx += 1;
    if (avcIdx > 3)  avcIdx = 0;
        switch (avcIdx)
       {
         case 0:
          {
            max_avc_gain = 48; 
            avcNdx = 1;
            break;
          }
         case 1:
          {
            max_avc_gain = 42;
            avcNdx = 2; 
            break;
          }
         case 2:
          {
            max_avc_gain = 36;
            avcNdx = 3; 
            break;
          }
         case 3:
          {
            max_avc_gain = 30;
            avcNdx = 4; 
            break;
          }
       }
// Sets AVC gain
      si4735.setAvcAmMaxGain(max_avc_gain);
      showStatus();
  }
 }
    
//----------------------------------------------------------
// Check STEP
  if (temp16 & STEP_SWITCH)
    {
// This command should work only for SSB mode
      if ((currentMode == LSB) || (currentMode == USB))
       {
         if (currentBFOStep == 50)
           {
             currentBFOStep = 100;
             realBFOStep = 100;
           }
         else if (currentBFOStep == 100)
           {
             currentBFOStep = 1;
             realBFOStep = 1000;
           }
         else if (currentBFOStep == 1)
           {
             currentBFOStep = 5;
             realBFOStep = 5000;
           }
         else
           {
             currentBFOStep = 50;
             realBFOStep = 50;
           }
       }
      else
       if (currentMode == AM)
        {
          if (currentStep == 1)
            currentStep = 5;
          else if (currentStep == 5)
            currentStep = 10;
          else if (currentStep == 10)
            currentStep = 50;
          else
            currentStep = 1;
          si4735.setFrequencyStep(currentStep);
          band[bandIdx].currentStep = currentStep;
          si4735.setSeekAmSpacing((band[bandIdx].currentStep > 10) ? 10 : band[bandIdx].currentStep); // Max 10kHz for spacing
        }
      if (currentMode != FM)
       showStatus();
    }
//----------------------------------------------------
// Check MODE
  if (temp16 & MODE_SWITCH)
    {
      if (currentMode != FM)
       {
         if (!SSBmode) 
          {
            currentMode = AM;
            ssbLoaded = false;
            currentFrequency = realSSBFRQ;
            currentBFO = 0;
            SSBmode = true;
          }
         else
           if (currentMode == AM)
            {
// If you were in AM mode, it is necessary to load SSB patch (avery time)
              loadSSB();
              if (bandIdx < BAND_USB)
                currentMode = LSB;
              else
                currentMode = USB;
            }
           else if ((currentMode == USB) && SSBmode)
            {
              currentMode = LSB;
              SSBmode = false;
            }
             else if ((currentMode == LSB) && SSBmode)
              {
                currentMode = USB;
                SSBmode = false;
              }
        band[bandIdx].currentFreq = currentFrequency;
        band[bandIdx].currentStep = currentStep;
        useBand();
      }
     else        // FM
      {
        fmStereo = !fmStereo;
        SetStereoMono();
        showRSSI();
       }
    } 
//----------------------------------------------------------
// Check BFO_SWITCH
 if (temp16 & BFO_SWITCH)
  {
    if (currentMode == LSB || currentMode == USB)
      {
        if (realBFOStep <= 100)
          {
            currentBFOStep = 1;
            realBFOStep = 1000;
          }
        else
          {
            currentBFOStep = 100;
            realBFOStep = 100;
          }
        showSTEP();
      }
      else if (currentMode == FM || currentMode == AM)
       {
// Jumps up or down one space
        if (seekDirection)
          si4735.frequencyUp();
        else
          si4735.frequencyDown();
          
        si4735.seekStationProgress(showFrequencySeek, checkStopSeeking, seekDirection);
        delay(100);                   // 30
        currentFrequency = si4735.getFrequency();
        showFrequency();
      }
    } 
//------------------------------------------------

  if (currentMode == FM)
 {
   if (currentFrequency != previousFrequency)
    {
      cleanBfoRdsInfo();
      previousFrequency = currentFrequency;
    }
   if (rdsON)
    {
      checkRDS();
// Show RSSI status only in FM and if this condition has changed
      if ((millis() - elapsedRSSI) > MIN_ELAPSED_RSSI_TIME * 9)
       {
         si4735.getCurrentReceivedSignalQuality();
//         aux = si4735.getCurrentRSSI();
         aux = si4735.getCurrentSNR();
         if (rssi != aux)
          {
            rssi = aux;
            showRSSI();
          }
         elapsedRSSI = millis();
       }
    }
 }
//-------------------------------------------------------------
// Update battery status every 2 minutes
  if ( (millis() - elapsedBatt ) > 120000)
  {
     showBattery();
     elapsedBatt = millis();
  }  
//-------------------------------------------------------------
// Check (VOL_UP + VOL_DOWN)
// Save all Receiver Information if pushed buttons (VOL_UP + VOL_DOWN)

  if ((temp16 & VOL_UP) && (temp16 & VOL_DOWN))
    {
      saveAllReceiverInformation();
      oled.invertDisplay(1);
      delay(500);                   // 0,5 sec
      oled.invertDisplay(0);
      while (StateBut);             // wait for off all buttons
    }
//-----------------------------------------------

// Save all Receiver Information if buttons not pushed up to 2 min
/*
  if ((TimeFree > 30000) && (TimeFree < 30080))
    {
      oled.invertDisplay(1);
      delay(1500);
      saveAllReceiverInformation();
      oled.invertDisplay(0);
    }
*/
  delay(10);
}

//================== End program ===============================
