// Last updated July 10, 2025
// Copyright (c) 2025 K.Tomoyasu JO4MLG/JH4***
// https://agc.ne.jp/e-craft/qrp_cw_transceiver_v3/
// Version 2.5.5 Si5351a OLED IambicB_keyer equipped 40mBAND CW Transceiver.
#include <Wire.h>
#include <SSD1306AsciiAvrI2c.h> //https://github.com/greiman/SSD1306Ascii
#include <Rotary.h> // https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
#include <EEPROM.h>

// --- Global setup ---
const unsigned long DEFAULT_FREQ = 19796940UL;
const int EEPROM_ADDR = 0;
unsigned long lastFreqWriteTime = 0;
bool freqChanged = false;
bool freqWritten = true;
unsigned long eeprom_cached_FREQ;

// TX/RX State transition management
unsigned long txDelayStartTime = 0; // For measuring transmission start waiting time
bool txTransitionActive = false; // Flags in Transition
bool txActive = false;

unsigned long rxDelayStartTime = 0; // For measuring reception start waiting time
bool rxTransitionActive = false;

// interval control for bar graph
unsigned long previousMillis = 0;
const long interval = 100; // S meter rewrite cycle(mS)
int previousBarLength = -1;

SSD1306AsciiAvrI2c oled;
Rotary r = Rotary(2, 3);

// Si5351A definitions
#define Si5351A_ADDR 0x60
#define MSNA_ADDR 26
#define MSNB_ADDR 34
#define MS0_ADDR 42
#define MS1_ADDR 50
#define MS2_ADDR 58
#define CLK0_CTRL 16
#define CLK1_CTRL 17
#define CLK2_CTRL 18
#define OUTPUT_CTRL 3
#define XTAL_LOAD_C 183
#define PLL_RESET 177

// I/O pins
#define SW_STEP 5
#define TX_SW 9 // Transmission control input from Straight(manual) key
#define TX_ON 7
#define AF_mute 8 // AF Mute Control
#define SIDETONE_PIN 10 // SideTone output

// Frequency setup
const long LOW_FREQ = 19796940L;
const long HI_FREQ = 19996940L;
const long IF_FREQ = 12796940L;
unsigned long FREQ = DEFAULT_FREQ;
unsigned long FREQ_OLD = FREQ;
unsigned long txFreq_OLD = 0;
int STEP = 100;

// --- constant value ---
const int SIDETONE_FREQ = 800; // sidetone frequency (Hz)
const unsigned long TX_ON_DELAY_MS = 3; // Delay from clk1 on to TX_ON on (mS)
const unsigned long RX_ON_DELAY_MS = 3; // Delay from TX_ON OFF to AF_mute release (mS)

// Variable to hold the state of TX_SW (manual key)
bool manualTxSwitchPressed = false;
unsigned long lastManualTxReadTime = 0;
const unsigned long MANUAL_TX_DEBOUNCE_DELAY = 10; // Manual key debouncing

// --- keyer pin assignment ---
const int DIT_PIN = 11;
const int DAH_PIN = 12;
const int SPEED_PIN = A1;

// --- keyer function
void updatePaddleLatch();
void loadWPM();
void runKeyerLogic();

// keyer variables
uint8_t nextElementToKey = 0; // The next code to be sent by the keyer
#define DIT_L     0x01
#define DAH_L     0x02
uint8_t keyerControl = 0;
uint8_t keyerState;
enum KSTYPE {IDLE, KEYED_PREP, KEYED, INTER_ELEMENT};
unsigned long ditTime = 50;
unsigned long ktimer;
bool lastWasDit = false;

// Debounce Variables
unsigned long lastDitStateChangeTime = 0;
unsigned long lastDahStateChangeTime = 0;
const long KEYER_DEBOUNCE_DELAY = 5; // paddle debounce delay time (mS)


// --- function(Si5351A / OLED / VFO) ---
const long ppm_correction = 22; // Specify the deviation of the PLL reference oscillation in ppm units(1ppm 25Hz)
void rotary_encoder();
void Fnc_Stp();
void drawBar(int currentBarLength);
void Set_CLK_Enable(uint8_t clk0, uint8_t clk1, uint8_t clk2);
void Freq_Disp(unsigned long Fre);
void Step_Disp(int Stp);
void Si5351_write(byte Reg, byte Data);
void Si5351_init();
void PLL_Set(char pll, uint32_t freq);
void MS_Set(uint8_t clk_no);
void Parameter_write(uint8_t REG_ADDR, uint32_t Pa1, uint32_t Pa2, uint32_t Pa3);
void setTxOnState(bool on);

// --- Setup ---
void setup() {
    // Rotary Encoder setup
    r.begin();
    attachInterrupt(0, rotary_encoder, CHANGE);
    attachInterrupt(1, rotary_encoder, CHANGE);

    // Pin Modes
    pinMode(SW_STEP, INPUT_PULLUP);
    pinMode(TX_SW, INPUT_PULLUP); // TX_SW (for manual key)

    pinMode(DIT_PIN, INPUT_PULLUP);
    pinMode(DAH_PIN, INPUT_PULLUP);

    pinMode(TX_ON, OUTPUT);
    pinMode(AF_mute, OUTPUT);
    pinMode(SIDETONE_PIN, OUTPUT);

    setTxOnState(false);
    digitalWrite(AF_mute, HIGH);

    EEPROM.get(EEPROM_ADDR, FREQ);
    if (FREQ < LOW_FREQ || FREQ > HI_FREQ) FREQ = DEFAULT_FREQ;
    FREQ_OLD = FREQ;
    eeprom_cached_FREQ = FREQ;

    oled.begin(&Adafruit128x64, 0x3C); //For 0.96 inch 128x64, 1.3 inch OLED is (&SH1106_128x64, 0x3C)

    Si5351_init();

    PLL_Set('A', FREQ);
    MS_Set(0); // Multi-synthesizer setting for CLK0 (receive)

    unsigned long initialTxFreq = FREQ - IF_FREQ;
    PLL_Set('B', initialTxFreq);
    MS_Set(1); // Multi-synthesizer setting for CLK1 (transmit)
    txFreq_OLD = initialTxFreq; // Record the initial transmission frequency
    Si5351_write(CLK1_CTRL, 0x6D); // Set CLK1 to 4mA
    
    Freq_Disp(FREQ);
    Step_Disp(STEP);

    oled.setFont(font5x7);
    oled.setCursor(0, 4); // 5th line
    oled.write("1__3__5__7_9+20+40+60"); // For S meter

    Set_CLK_Enable(1, 0, 0);

    keyerState = IDLE;
    sei();
}

// --- Loop ---
void loop() {
    unsigned long now = millis();

    if (digitalRead(SW_STEP) == LOW) Fnc_Stp();
    bool currentRawManualTx = (digitalRead(TX_SW) == LOW);
    static bool rawManualTxState = false;
    static bool previousManualTxState = false; // Retain previous state of manual key

    if (currentRawManualTx != rawManualTxState) {
        lastManualTxReadTime = now;
        rawManualTxState = currentRawManualTx;
    }
    if (now - lastManualTxReadTime > MANUAL_TX_DEBOUNCE_DELAY) {
        manualTxSwitchPressed = rawManualTxState;
    }

    if (manualTxSwitchPressed && !previousManualTxState) { // The moment I was pushed
        tone(SIDETONE_PIN, SIDETONE_FREQ);
    } else if (!manualTxSwitchPressed && previousManualTxState) { // The moment we were separated
        noTone(SIDETONE_PIN);
    }
    previousManualTxState = manualTxSwitchPressed;

    loadWPM(); // Update Keyer Speed
    updatePaddleLatch(); // Paddle Debouncing
    runKeyerLogic(); // Executing keyer logic

    bool requestTx = manualTxSwitchPressed || (keyerState == KEYED_PREP) || (keyerState == KEYED);

    if (requestTx) {
        if (!txTransitionActive) {
            // Transition from receiving to sending begins
            txDelayStartTime = now;
            txTransitionActive = true;
            rxTransitionActive = false; // Reset Transition to Inbound
            digitalWrite(AF_mute, LOW); // AF Mute Cancel
            Set_CLK_Enable(0, 1, 0); // Enable CLK1 (transmit)
        }

        if (txTransitionActive && (now - txDelayStartTime >= TX_ON_DELAY_MS)) {
            if (keyerState == IDLE && manualTxSwitchPressed) {
                setTxOnState(true);
            }
            txActive = true; // Marks TX as active
        }

        oled.setFont(font5x7);
        oled.setCursor(0, 6);
        if (digitalRead(TX_ON) == HIGH) {
            oled.write("_Transmitting_"); // Alternative display as transmission indicator
        }

    } else { // If there is no request to send
        if (!rxTransitionActive) {
            rxDelayStartTime = now;
            rxTransitionActive = true;
            txTransitionActive = false; // Reset Transition to Send

            setTxOnState(false); // When there is no longer a transmission request, set TX_ON to LOW.
            txActive = false; // Mark TX inactive

            Set_CLK_Enable(1, 0, 0); // Enable CLK0 (receive)
        }
        
        oled.setCursor(0, 6);
        oled.clearToEOL();
        
        if (rxTransitionActive && (now - rxDelayStartTime >= RX_ON_DELAY_MS)) {
            digitalWrite(AF_mute, HIGH); // AF Mute Cancel
            rxTransitionActive = false; // Transition to reception complete
        }
    }

    // --- Frequency change detection ---
    if (FREQ != FREQ_OLD) {
        PLL_Set('A', FREQ);
        MS_Set(0); // Multi-synthesizer associated with CLK0 (receive)
    
        unsigned long txFreq_new = FREQ - IF_FREQ;
        PLL_Set('B', txFreq_new);
        MS_Set(1); // Multi-synthesizer associated with CLK1 (transmit)
        txFreq_OLD = txFreq_new; // Update transmit frequency
    
        Freq_Disp(FREQ);
        FREQ_OLD = FREQ;
        freqChanged = true;
        lastFreqWriteTime = now;
        freqWritten = false;
        eeprom_cached_FREQ = FREQ;
    }

    // --- bar graph ---
    if (now - previousMillis >= interval) {
        previousMillis = now;
        int sensorValue = analogRead(A0); // Read the AGC voltage
        const int MIN_ADC = 190;
        const int MAX_ADC = 588;
        sensorValue = constrain(sensorValue, MIN_ADC, MAX_ADC);
        int currentBarLength = map(sensorValue, MAX_ADC, MIN_ADC, 0, 128);

        if (currentBarLength != previousBarLength) {
            drawBar(currentBarLength);
            previousBarLength = currentBarLength;
        }
    }

    // --- EEPROM write process ---
    if (freqChanged && !freqWritten && (now - lastFreqWriteTime >= 2000)) {
        unsigned long storedFreq;
        EEPROM.get(EEPROM_ADDR, storedFreq);
        if (storedFreq != eeprom_cached_FREQ) {
            EEPROM.put(EEPROM_ADDR, eeprom_cached_FREQ);
        }
        freqWritten = true;
        freqChanged = false;
    }
}

//--- Displays bar graphs one at a time ---
void drawBar(int currentBarLength) {
    oled.setFont(font5x7);
    int targetDisplayLength = currentBarLength;
    int previousDisplayLength = previousBarLength;
    if (targetDisplayLength > previousDisplayLength) {
        for (int i = previousDisplayLength; i < targetDisplayLength; i++) {
            oled.setCursor(i, 5);
            if (i % 2 == 0) {
                oled.write("L");
            } else {
                oled.write(" ");
            }
        }
    }
    else if (targetDisplayLength < previousDisplayLength) {
        for (int i = targetDisplayLength; i < previousDisplayLength; i++) {
            oled.setCursor(i, 5);
            oled.write(" ");
        }
    }
}

void Set_CLK_Enable(uint8_t clk0, uint8_t clk1, uint8_t clk2) {
    uint8_t val = 0;
    if (!clk0) val |= (1 << 0);
    if (!clk1) val |= (1 << 1);
    if (!clk2) val |= (1 << 2);
    Si5351_write(OUTPUT_CTRL, val);
}

void Freq_Disp(unsigned long Fre) {
    unsigned long displayFreq = Fre - IF_FREQ;

    char buf[12];
    sprintf(buf, "%lu", displayFreq);
    oled.setFont(fixednums8x16);
    oled.setCursor(8, 1);
    oled.print(buf[0]); oled.print(".");
    oled.print(buf[1]); oled.print(buf[2]); oled.print(buf[3]); oled.print(".");
    oled.print(buf + 4);
}

void Step_Disp(int Stp) {
    oled.setFont(font5x7);
    oled.setCursor(100, 2);
    oled.print((Stp == 1000) ? " 1K" : (Stp == 100) ? "100" : " 10");
}

void Si5351_write(byte Reg, byte Data) {
    Wire.beginTransmission(Si5351A_ADDR);
    Wire.write(Reg);
    Wire.write(Data);
    Wire.endTransmission();
}

// --- Be careful with the Register settings ---
void Si5351_init() {
    Si5351_write(OUTPUT_CTRL, 0xFF); // All outputs disabled
    Si5351_write(CLK0_CTRL, 0x80);   // Power down CLK0
    Si5351_write(CLK1_CTRL, 0x80);// Power down CLK1
    Si5351_write(CLK2_CTRL, 0x80);// Power down CLK2
    Si5351_write(XTAL_LOAD_C, 0x92);// Set crystal load capacitance
    Si5351_write(PLL_RESET, 0xA0);// Reset all PLLs
    Si5351_write(CLK0_CTRL, 0x4D);// CLK0: PLLA, 4mA, (RX)
    Si5351_write(CLK1_CTRL, 0x6D);// CLK1: PLLB, 4mA, (TX)
    Si5351_write(CLK2_CTRL, 0x6C);// CLK2: PLLB, 2mA, (I won't use it so anything is fine)
    Si5351_write(OUTPUT_CTRL, 0xFC); // Enable CLK0 CLK1 at init
}

void PLL_Set(char pll, uint32_t freq) {
    uint8_t addr = (pll == 'A') ? MSNA_ADDR : MSNB_ADDR;
    uint64_t pll_freq = (uint64_t)freq * 32;
    pll_freq = (pll_freq * (1000000L + ppm_correction)) / 1000000L; // Added for frequency correction
    uint32_t mult = pll_freq / 25000000;
    uint32_t l = pll_freq % 25000000;
    uint32_t num = (uint64_t)l * 1048575 / 25000000;
    uint32_t denom = 1048575;
    uint32_t P1 = 128 * mult + ((128 * num) / denom) - 512;
    uint32_t P2 = (128 * num) % denom;
    uint32_t P3 = denom;
    Parameter_write(addr, P1, P2, P3);
}

void MS_Set(uint8_t clk_no) {
    uint8_t addr;
    if (clk_no == 0) addr = MS0_ADDR;
    else if (clk_no == 1) addr = MS1_ADDR;
    else addr = MS2_ADDR;

    uint32_t P1 = 128 * 32 - 512; // Divide by 32
    uint32_t P2 = 0;
    uint32_t P3 = 1;
    Parameter_write(addr, P1, P2, P3);
}

void Parameter_write(uint8_t REG_ADDR, uint32_t Pa1, uint32_t Pa2, uint32_t Pa3) {
    Si5351_write(REG_ADDR, (Pa3 >> 8) & 0xFF);
    Si5351_write(REG_ADDR + 1, Pa3 & 0xFF);
    Si5351_write(REG_ADDR + 2, (Pa1 >> 16) & 0x03);
    Si5351_write(REG_ADDR + 3, (Pa1 >> 8) & 0xFF);
    Si5351_write(REG_ADDR + 4, Pa1 & 0xFF);
    Si5351_write(REG_ADDR + 5, ((Pa3 >> 12) & 0xF0) | ((Pa2 >> 16) & 0x0F));
    Si5351_write(REG_ADDR + 6, (Pa2 >> 8) & 0xFF);
    Si5351_write(REG_ADDR + 7, Pa2 & 0xFF);
}

// Rotary encoder ISR
void rotary_encoder() {
    unsigned char result = r.process();
    if (result) {
        FREQ += (result == DIR_CW) ? STEP : -STEP;
        FREQ = constrain(FREQ, LOW_FREQ, HI_FREQ);
        freqChanged = true;
    }
}

void Fnc_Stp() {
    STEP = (STEP == 10) ? 1000 : ((STEP == 1000) ? 100 : 10); // 10 -> 1000 -> 100 -> 10 の順に切り替え
    delay(10);
    Step_Disp(STEP);
    while (digitalRead(SW_STEP) == LOW) delay(10);
}

// --- Helper function for controlling TX_ON ---
void setTxOnState(bool on) {
    if (on) {
        digitalWrite(TX_ON, HIGH);
    } else {
        digitalWrite(TX_ON, LOW);
    }
}

// --- keyer functions ---
void updatePaddleLatch() {
    unsigned long now = millis();
    bool ditState = (digitalRead(DIT_PIN) == LOW);
    bool dahState = (digitalRead(DAH_PIN) == LOW);
    if (ditState != ((keyerControl & DIT_L) > 0) && (now - lastDitStateChangeTime > KEYER_DEBOUNCE_DELAY)) {
        if (ditState) keyerControl |= DIT_L; else keyerControl &= ~DIT_L;
        lastDitStateChangeTime = now;
    }
    if (dahState != ((keyerControl & DAH_L) > 0) && (now - lastDahStateChangeTime > KEYER_DEBOUNCE_DELAY)) {
        if (dahState) keyerControl |= DAH_L; else keyerControl &= ~DAH_L;
        lastDahStateChangeTime = now;
    }
}

void loadWPM() {
    int wpm = map(analogRead(SPEED_PIN), 0, 1023, 12, 40);
    ditTime = 1200UL / wpm;
}

void runKeyerLogic() {
    unsigned long now = millis();
    bool ditPressed = (keyerControl & DIT_L);
    bool dahPressed = (keyerControl & DAH_L);
    switch (keyerState) {
        case IDLE:
            if (manualTxSwitchPressed) nextElementToKey = 0;
            else if (ditPressed) { nextElementToKey = DIT_L; keyerState = KEYED_PREP; }
            else if (dahPressed) { nextElementToKey = DAH_L; keyerState = KEYED_PREP; }
            else { noTone(SIDETONE_PIN); nextElementToKey = 0; }
            break;
        case KEYED_PREP:
            if (nextElementToKey & DIT_L) { lastWasDit = true; nextElementToKey &= ~DIT_L; }
            else if (nextElementToKey & DAH_L) { lastWasDit = false; nextElementToKey &= ~DAH_L; }
            else { keyerState = IDLE; setTxOnState(false); noTone(SIDETONE_PIN); return; }
            setTxOnState(true);
            tone(SIDETONE_PIN, SIDETONE_FREQ);
            ktimer = now;
            keyerState = KEYED;
            break;
        case KEYED:
            if (ditPressed && !lastWasDit) nextElementToKey |= DIT_L;
            if (dahPressed && lastWasDit) nextElementToKey |= DAH_L;
            if (now - ktimer >= (lastWasDit ? ditTime : ditTime * 3)) {
                setTxOnState(false); noTone(SIDETONE_PIN);
                ktimer = now; keyerState = INTER_ELEMENT;
            }
            break;
        case INTER_ELEMENT:
            if (now - ktimer >= ditTime) {
                if (nextElementToKey) keyerState = KEYED_PREP;
                else if (ditPressed && dahPressed) { nextElementToKey = lastWasDit ? DAH_L : DIT_L; keyerState = KEYED_PREP; }
                else if (ditPressed) { nextElementToKey = DIT_L; keyerState = KEYED_PREP; }
                else if (dahPressed) { nextElementToKey = DAH_L; keyerState = KEYED_PREP; }
                else keyerState = IDLE;
            }
            break;
    }
}