/*
see  http://www.sadarc.org/xenforo/upload/index.php?threads/ic22s-arduinoized.275/
  Originally, Frequ synth for TR-7200GII
  Denys VK3ZYZ (with help from Google and others)
  Fixes by Ray and Josh 20190728
  CTCSS tones added :)
  try to port it to OLED. (S,+,-)
  Add mode display
  Add CTCSS display *************** made it float. Check to see if tones still work! YES :)
  Invert frequ display on TX
  Had CTCSS on wrong channel Fixed!
  made display reset = -1

  Add CTCSS as per https://www.amateurradio.com.au/repeaters

  Max number of channels = 61 as RAM clashes.

  Adjust OLED freq display position.

  20211202 add calibration.

   Try to just use Aduino pins, no I2C.

   Move TRX from 2 to A2
   Move REV from 5 to A3
    from sheet printed for AU frequs...
   PLL N = (f -144.4)/.025
      CHANGE PTT SENSE TO ACTIVE LOW. 20220720
   20230308 CTCSS int x 10 to save space.
    Use old duplex switch to select other options.??????
*/

#define Version "IC22S_20230309"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// synthesiser default settings on start up.
unsigned long RXgenfrequency  = 146050000;
unsigned long TXgenfrequency  = 146050000;
unsigned long IFoffset        =  10700000;  // IF offset
unsigned long PLLoffset       = 131700000;  //133690000;    //check these frequs!
unsigned long chan_spacing    =     25000;  //15000;

float         txDisplayFreq   = 146.000;
float         rxDisplayFreq   = 146.000;
int           mode            = HIGH;        // HIGH = normal, LOW = REV
#define       RX                HIGH
int           TRXmode         = RX;          // HIGH = RX, LOW = TX
int           channel         = 2;           // start on channel number "31" (starts from 0)
int encoder;                                 // encoder direction: CCW=-1; NC=0; CW=1
int timer                     = 100;         // sub tone setting. The higher the number, the slower the timing.
int i ;
int flag = LOW;

// encoder definitions
#define ENCODER_A     A0
#define ENCODER_B     A1
#define TRX           A2           // PC2 input HIGH = TX, LOW = RX.
#define REV_SWITCH    A3           // PC3 Encoder Button  repeater reverse
// A4 = SDA
// A5 = SCL
#define ModeSwitch    A6           // extra modes selector
//                    A7           // not used yet

// Use Port D to drive the PLL pins.
// NOTE the Arduino 0 and 1 pins swap the port bit order.
//                     1           // PLL b0 PD0 (Serial TXD)
//                     0           // PLL b1 PD1 (Serial RXD)
//                     2           // PLL b2 PD2 
//                     3           // PLL b3 PD3 
//                     4           // PLL b4 PD4 
//                     5           // PLL b5 PD5 
//                     6           // PLL b6 PD6 
//                     7           // PLL b7 PD7 

/* ********************* PortB0-PB4 used to generate CTCSS tones via R2R nerwork.
#define spare2         8           // Sub Tone R2R b0 PB0
#define spare3         9           // Sub Tone R2R b1 PB1
#define spare4        10           // Sub Tone R2R b2 PB2
#define spare5        11           // Sub Tone R2R b3 PB3
#define spare6        12           // Sub Tone R2R b4 PB4
*/

#define Output2       13           // not used yet

#define ENCODER_INPUT_PORT PINC
#define PLL_Port PIND
#define On  1
#define Off 0
#define RefFrequ      500000       // 16Mhz / 32 steps for CTCSS sine wave generator
                                    
int     SineDivider = 4065;         // default sine wave divider
  
long int RXFrequs []      // Edit this for RX frequencies wanted.
{ 146000,
  146625,
  146650,
  146675,
  146700,
  146725,
  146750,
  146775,
  146800,
  146825,
  146850,
  146875,
  146900,
  146925,
  146950,
  146975,
  147000,
  146425,
  146450,
  146475,
  146500,
  146525,
  146550,
  146575,
  146600,
  147025,
  147050,
  147075,
  147100,
  147125,
  147150,
  147175,
  147200,
  147225,
  147250,
  147275,
  147300,
  147325,
  147350,
  147375,
  147400,
  147425,
  147450,
  147475,
  147500,
  147525,
  147550,
  147575,
  147600,   
  144700,   
  144725,
  144750,
  144775,
  144800,
  144825,
  144850,
  144875,
  144900,  
  145075,   
  145100,
  145125,   // 60 = chan 61
  145150,
  145175,
  145200,
  145225,
  145250,
  145275,
  145300,
  145325,
  145350,
  145375,
  145400,   // Chan = 72
};

int TXFrequs []      // Edit this for TX frequencies wanted. Added to RX frequs.
{ 0,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  -600,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  600,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,      
  0,      
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,    
  0,     
  0,    // 60 = chan 61
  0,
  0,
  0,  
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,      // Chan = 72
};

int SubTone []      // Edit this for CTCSS frequencies wanted.
{ 0,      // "0 ="  chan 1
  0,
  19,  //  chan 3
  0,
  10,   //  chan 5
  0,
  10,   //  chan 7
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  10,   // chan 15
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  19,    // chan 26
  0,
  0,
  10,   // chan 29
  10,   // chan 30
  0,
  0,
  0,
  0,
  0,
  10,   // chan 36
  0,
  0,
  0,
  10,   // chan 40
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,     
  0,     
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,    
  0, 
  0,    //"60"= Chan 61
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,    // Chan 72 
};

float CTCSS = 0;
int   CTCSS_Index = 0;
int   CTCSS_Tones []
//   0     1     2     3     4     5     6     7     8     9
{    0,  670,  693,  719,  744,  770,  797,  825,  854,  885,   //  +0
   915,  948,  974, 1000, 1035, 1072, 1109, 1148, 1188, 1230,   // +10
  1273, 1318, 1365, 1413, 1462, 1500, 1514, 1567, 1598, 1622,   // +20
  1655, 1679, 1713, 1773, 1799, 1835, 1862, 1899, 1928, 1966,   // +30
  1995, 2035, 2065, 2107, 2181, 2257, 2291, 2336, 2418, 2503,   // +40
  2541,                                                         // +50
};

int sinetable[] // one cycle sine wave via R2R network.
{ 0,1,2,3,5,7,10,13,16,19,22,25,27,28,29,30,31,30,29,28,27,25,22,19,16,13,10,7,5,3,2,1, };

void setup() {

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address may be 0x3D 
    for(;;); // Don't proceed, loop forever
  }
  display.clearDisplay();
  display.display();                  // Update screen
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(WHITE,BLACK);  // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F(Version));
  display.display(); // Update screen
  delay(1000);
  display.clearDisplay();
  display.display();                  // Update screen
  delay(100);
  display.setCursor(0,0);
  display.print("Ch                    ");
  display.display(); // Update screen

  // configure rotary encoder & push-button
  pinMode(ENCODER_A, INPUT_PULLUP);     // encoder leads
  pinMode(ENCODER_B, INPUT_PULLUP);
  pinMode(REV_SWITCH, INPUT_PULLUP);    // Hi = nomal, LOW =  Reverse
  pinMode(TRX, INPUT);            // Hi = TX, LOW =  RX
  DDRB  = B11111;                 // sets Arduino pins 8 to 12 as outputs.
  PORTB = B10000;                 // R2R out at rest to half level.
  DDRD  = B11111111;              // all bits outputs
  
  encoder = read_encoder();       // returns +1/0/-1 (cw/nc/ccw)
  mode=( digitalRead(REV_SWITCH));
  TRXmode=(digitalRead(TRX));
    set_synth(mode, TRXmode);
}

/******************************
  main loop
*******************************/
void loop() {

  if ( digitalRead(REV_SWITCH) != mode){
    mode=( digitalRead(REV_SWITCH));
    set_synth(mode, TRXmode);
  }
  if (digitalRead(TRX) !=TRXmode){
    TRXmode=(digitalRead(TRX));
    set_synth(mode, TRXmode);  
  }
  if (TRXmode==RX)
  {
      // check the frequency
      encoder = read_encoder();                       // returns +1/0/-1 (cw/nc/ccw)
      if (encoder != 0) {
        channel = channel + encoder;
        channel = constrain(channel, 0, 71);          // max 72 channels.
        set_synth(mode, TRXmode);
      }
      int temp2 =  analogRead(ModeSwitch);        // 2 readings the same?
      if (temp2 == analogRead(ModeSwitch))
      {
      if (temp2 >= 700)
        {if ( flag==LOW)
            { flag=HIGH;
            ShowCTCSS();      
            }
        }
       while (analogRead(ModeSwitch) >= 700)    // DupA >=300, spx = 0, DupB >=700 (461 and 839)
        { 
          encoder = read_encoder();
          if (encoder != 0) 
           { int temp = (SubTone[channel]);
              temp = temp + encoder;
              temp = constrain(temp, 0, 50);
              (SubTone[channel]) = temp;
              ShowCTCSS();
            } 
        }
          if ((analogRead(ModeSwitch) <= 10)&& (flag==HIGH))
          { flag=LOW;
            ShowCTCSS();      
          }
      }  
  }  
}

/******************************
  read encoder direction
*******************************/
int read_encoder() {
  // definitions
#define DELAY 100                               // number of polling-loops   
  static unsigned short integrator;               // encoder debounce

  /* the array values are determined by combining the previous and current A1,A0
    Gray Code bit-patterns to form an array-index. +1 is assigned to all clockwise
    (CW) indexes; -1 is assigned to all counter-clockwise (CCW) indexes; and 0 is
    assigned to the remaining positions to indicate no movement*/
  static char encoder[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; // SWAPPED PHASES
  static unsigned char encoder_index = 0;
  static long count = 0;                         // CW rotation = +1; CCW rotation = -1
  static long this_reading;
  static long last_reading;
  char value;                                    // return value

  /* check for encoder rotation. The absolute "count" value changes as the encoder rotates.
    Typically there are four states (counts) per click, but often more due to contact bounce.
    Bounce doesn't matter so long as the "count" decreases" for CCW rotation; remains constant
    when there is no rotation; and increases with CW rotation. Bounce can be completely
    eliminated if we look for the "detent" code and integrate as shown below. */
  encoder_index <<= 2;                           // shift previous bit-pattern left two places
  encoder_index |= (ENCODER_INPUT_PORT & 0x03);  // get current bit-pattern and combine with previous
  count += encoder[( encoder_index & 0x0f )];

  // debounce encoder
  if ((ENCODER_INPUT_PORT & 0x03) == 3) {        // encoder on an indent (3 decimal == 00000011 binary)
    if (integrator < DELAY) {
      integrator++;
    }
  } else {                                       // encoder not resting on indent
    if (integrator > 0) {
      integrator--;
    }
  }

  // prepare the "return" value
  if (integrator >= DELAY) {                     // encoder deemed to be stationary
    this_reading = count;
    if (this_reading < last_reading) value = -1;
    if (this_reading == last_reading) value = 0;
    if (this_reading > last_reading) value = 1;
    last_reading = this_reading;                 // both readings now hold current "count"
    integrator = 0;                              // reset integrator
  } else {
    value = 0;
  }
  return (value);
}

// set the display and PLL
void set_synth( int frmode, int trx )
{  
  display.setTextSize(1);     // Normal 1:1 pixel scale
  display.setCursor(18,0);    // write channel number to the LCD 60
  display.print("  ");        // clear the old number
  display.setCursor(18,0);    // write channel number to the LCD
  if (channel < 9)            // "9" = 10 as array starts at 0
  {
    display.print(" ");       // extra space for formalling a single digit.
  }
  display.print((channel + 1)); // display the channel number

  display.setCursor(34,0);   // write mode to the LCD
  if (TXFrequs[channel]== 0)
  {display.print(" SIM "); }
    if (TXFrequs[channel] == 600)
  {display.print("+600"); }
    if (TXFrequs[channel] ==-600)
  {display.print("-600"); }

  ShowCTCSS();


  
// Select FWD or REV frequencies
  if (frmode == HIGH)    // test for fwd or rev repeater...
  { RXgenfrequency = (RXFrequs[channel]); // normal operation
    TXgenfrequency = ((RXFrequs[channel]) + (TXFrequs[channel]));
  }
  else
  {
    TXgenfrequency = (RXFrequs[channel]); // reverse repeater operation
    RXgenfrequency = ((RXFrequs[channel]) + (TXFrequs[channel]));
  }

// Then display them
  if ( trx == RX) // =RX.
  {  
  rxDisplayFreq = float (RXgenfrequency)/1000;  // display the RX frequency
  display.setCursor(0,10);                      // was (10,10) for 6M. Start of second LCD line
  display.setTextSize(3);                       // Normal 1:1 pixel scale
  display.print(rxDisplayFreq, 3);
  }
   if ( trx == !RX) // =TX.
  {
  display.setTextColor(BLACK,WHITE);            // Invert Frequ Display
  display.setCursor(0,10);                      // was (10,10) for 6M
  display.setTextSize(3);                       // large text
  txDisplayFreq = float (TXgenfrequency)/1000;
  display.print(txDisplayFreq, 3);
  }
  display.display(); // Update screen
  display.setTextColor(WHITE,BLACK);            // back to normal text
  display.display(); // Update screen

// is it transmittig or receiving?

  if ( trx == RX)            // =RX.
  { 
    setPLL(RXgenfrequency);   
    TIMSK1 = 0;                // subtone off
  }
  else                         // =TX.
  { 
    setPLL(TXgenfrequency);
 // do we need subtones?   
    if (CTCSS_Index != 0)    
//    { SineDivider = (RefFrequ/(float)(CTCSS_Tones[CTCSS_Index]/10)); // Subtone table as Int = Float/10
      { SineDivider = (RefFrequ/CTCSS); 
    CTCSS_Tone();             // subtone on
    }
    else 
    {
    TIMSK1 = 0;              // subtone off
    }
  }
}

void CTCSS_Tone()
{
  cli();                    // interupsts off for setup
  TCCR1A = 0;               // 
  TCCR1B = 0; //B00000010;  //  no prescaler
   // set compare match register for 1hz increments
  OCR1A = SineDivider ;     // = (16*10^6) / (1*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 no prescaler
  TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  sei();                    // interupsts on      
}

ISR (TIMER1_COMPA_vect) {
  PORTB=(sinetable[i++]);   // write value from table to PORTB
  if(i>=32){
    i=0;
  }
}

// make this the drive for the diode matrix replacement.
void setPLL(unsigned long frq)
{  // from Oz ducumentation
    int PLLdivider =  ((frq - 144400)/25);
    PORTD=(PLLdivider); 
}

void ShowCTCSS(){
  if (flag==HIGH)
  { display.setTextColor(BLACK,WHITE); // invert text  
  }
  display.setTextSize(1);     // Normal 1:1 pixel scale
  display.setCursor(70,0);          // write CTCSS to the LCD
  display.print("         ");
  CTCSS_Index=(SubTone[channel]);
  if(CTCSS_Index!=0)  
  {display.setCursor(70,0); 
   display.print("tone");
    CTCSS = ((CTCSS_Tones[CTCSS_Index])/10);
    if(CTCSS<100)        // pad leading spacee if needed
  {  display.print(" ");}             
     display.print(CTCSS,1);  
  }
  display.display();
  display.setTextColor(WHITE,BLACK);            // back to normal text
}