/** \brief BJ-Keyer

:
  Morsekeyer von DL7BJ
  tom@dl7bj.de

  OLED functions from https://github.com/Sylaina/oled-display

@verbatim
 History
 --------------------------------------------------------------------------
 2012-05-24 DL7BJ	erste Version
 2013-05-10 DL7BJ	Generierung des Mithörtons als Sinus mit PWM/DDS
 2013-07-15 DL7BJ	Änderungen der Keyerfunktionen
 2013-07-19 DL7BJ	Beep/Boop (Spielkram)
 2013-10-20 DL7BJ   Änderungen der PWM Funktionen für besseren Sinus
 2022-04-10 DL7BJ   erste Leiterplatten für Prototyp (bisher Lochraster)
 2022-09-02 DL7BJ   viele Softwareänderungen, neuer Filter für PWM
 2022-09-11 DL7BJ   Encoder, LC-Display, Frontplatine "entsorgt"
 2023-06-28 DL7BJ   Port Anpassungen an neue Leiterplatte V1.01

 ATMEGA328(P)
                            ----------
 (PCINT14/_RESET)     PC6 -| 1      28|- PC5 (ADC5/SCL/PCINT13)
 (PCINT16/RXD)        PD0 -| 2      27|- PC4 (ADC4/SDA/PCINT12)
 (PCINT17/TXT)        PD1 -| 3      26|- PC3 (ADC3/PCINT11)
 (PCINT18/INT0)       PD2 -| 4      25|- PC2 (ADC2/PCINT10)
 (PCINT19/OC2B/INT1)  PD3 -| 5      24|- PC1 (ADC1/PCINT9)
 (PCINT20/XCK/T0)     PD4 -| 6      23|- PC0 (ADC0/PCINT8)
                      VCC -| 7      22|- GND
                      GND -| 8      21|- AREF
 (PCINT6/XTAL1/TOSC1) PB6 -| 9      20|- AVCC
 (PCINT7/XTAL2/TOSC2) PB7 -|10      19|- PB5 (SCK/PCINT5)
 (PCINT21/OC0B/T1)    PD5 -|11      18|- PB4 (MISO/PCINT4)
 (PCINT22/OC0A/AIN0)  PD6 -|12      17|- PB3 (MOSI/OC2A/PCINT3)
 (PCINT23/AIN1)       PD7 -|13      16|- PB2 (SS/OC1B/PCINT2)
 (PCINT0/CLK0/ICP1)   PB0 -|14      15|- PB1 (OC1A/PCINT1)
                            ----------

 Pin  1 - PC6  - Reset                  Pin 28 - PC5    - SCL Display
 Pin  2 - PD0  - RxD                    Pin 27 - PC4    - SDA Display
 Pin  3 - PD1  - TxD                    Pin 26 - PC3    - LED Key
 Pin  4 - PD2  - Left Paddle            Pin 25 - PC2    - TRX 2 Out
 Pin  5 - PD3  - Right Paddle           Pin 24 - PC1    - TRX 1 Out
 Pin  6 - PD4  - Straight Key           Pin 23 - PC0    - Mem 4
                                        Pin 19 - PB5    - Mem 5
 Pin 11 - PD5  - Mem 1                  Pin 18 - PB4    - _Audio SD
 Pin 12 - PD6  - Mem 2                  Pin 17 - OC2A   - Audio PWM output
 Pin 13 - PD7  - Mem 3                  Pin 16 - PB2    - Encoder Switch
 Pin 14 - PB0  - Encoder A              Pin 15 - PB1    - Encoder B


 Value 1   2   4   8  32  64  128  255
 Bit   1   2   3   4   5   6    7    8
 Pin   0   1   2   3   4   5    6    7
@endverbatim
*/

#include "bj-keyer.h"
// Additional files
#include "functions.c"

#define LENGTH 256
#define AMP 127
#define OFFSET 128
#define PI2 6.283185

/**
 * \brief Initialsieren der Timer
 *
 * Alle Parameter der Timer basieren auf 16MHz Systemtakt.
 *
 * Timer 0  - 8 Bit timer für 1ms
 * Timer 2  - 8 Bit timer für PWM zur Erzeugung des Sinustons
 * Timer 1A - 16 Bit timer zur Erzeugung der Hüllkurve 
 * Timer 1B - 16 Bit timer wird nicht benutzt
 *
 * T   - dot duration
 * wpm - Words per Minute based on PARIS
 * Formula T = 1200 / wpm
 * Minimum speed 10 wpm - dot duration 120ms
 * Maximum speed 99 wpm - dot duration 12ms
 *
*/
void InitTimer(void)
{
    cli();
    // Timer 2 PWM
    TCCR2A = 0;
    TCCR2B = 0;
    // No prescaling
    sbi(TCCR2B,CS20);
    // Clear OC2A on compare match
    sbi(TCCR2A,COM2A1);
    // Fast PWM Mode
    sbi(TCCR2A,WGM20);
    sbi(TCCR2A,WGM21);
    // Phase Correct PWM
    //sbi(TCCR2A,WGM22);
    //sbi(TCCR2A,WGM20);
    // Initial value
    OCR2A = 0x80;
    sbi(DDRB,PB3);
    
    // Timer 1 für die Sinus Hüllkurve
    TCCR1A = 0; TCCR1B = 0; TIMSK1 = 0; 
    // CTC Mode
    sbi(TCCR1B,WGM12);
    // Prescaling 8
    sbi(TCCR1B,CS11);
    // Output Compare Match Interrupt Enable
    OCR1A = 51; // 600Hz
    sbi(TIMSK1,OCIE1A);

    // Timer 0 1ms für diverse Zähler
    TCCR0A = 0; TCCR0B = 0; TCNT0 = 0;
    cbi(TCCR0A,WGM00);
    sbi(TCCR0A,WGM01);
    cbi(TCCR0B,WGM02);  // CTC Mode 2 Immediate
    cbi(TCCR0B,CS02);
    sbi(TCCR0B,CS01);
    sbi(TCCR0B,CS00);   // prescaler 64
    OCR0A = 249;        // CTC 1ms
    sbi(TIMSK0,OCIE0A); // Enable Timer 0 CTC
    sei();
}

void Init()
{
    cli(); // disable all interrupts

    MachineMode = NORMAL;
    SendStatus = SENDING_NOTHING;
    
    // PORTB
    DDRB = 0x00;
    // Interne PullUps einschalten
    sbi(PORTB,PB0);
    sbi(PORTB,PB1);
    sbi(PORTB,PB2);
    sbi(PORTB,PB3);
    sbi(PORTB,PB5);
    sbi(PORTB,AUDIO_EN);
    // Ein- und Ausgänge festlegen
    sbi(DDRB,PB3);      // PWM
    sbi(DDRB,AUDIO_EN);
    // Audio Verstärker abschalten
    //  cbi(PORTB,AUDIO_EN);
   
    // PORTC
    sbi(DDRC,MORSE_LED);

    // PORTD
    // Ein- und Ausgänge festlegen
    DDRD = 0x00;
    // Interne PullUps für die Eingänge abschalten
    cbi(PORTD,LEFT_PADDLE);
    cbi(PORTD,RIGHT_PADDLE);
    cbi(PORTD,STRAIGHT_KEY);

    t_element_length = (uint16_t)1200/bConfig.wpm;

    // Pin Change Interrupts Port D - Keys
    // PD4 - StraightKey  - PCINT20 - Pin Change Interrupt 20
    // PD3 - Right Paddle - PCINT19 - Pin Change Interrupt 19
    // PD2 - Left Paddle  - PCINT18 - Pin Change Interrupt 18
    sbi(PCICR,PCIE2);
    sbi(PCMSK2,PCINT18);
    sbi(PCMSK2,PCINT19);
    sbi(PCMSK2,PCINT20);

    // Init serial
    UBRR0=UBRR_VALUE;   // Set baud rate
    sbi(UCSR0B,TXEN0);  // Enable TX
    sbi(UCSR0B,RXEN0);  // Enable RX
    sbi(UCSR0B,RXCIE0); // RX complete interrupt
    sbi(UCSR0C,UCSZ01); // no parity, 1 stop bit
    sbi(UCSR0C,UCSZ01); // 8-bit data

    InitTimer();
    EncoderInit();

    // Initialisierung Menüvariablen
    bMenuCtrl.ClrScr = 1;
    bMenuCtrl.Update = 1;
    bMenuCtrl.Config = 0;
    bMenuCtrl.buttonPressed = 0;
    bMenuCtrl.WriteEEProm = 0;
    bMenuCtrl.buttonPressedLong = 0;
    // Initialisierung Konfiguration
    bConfig.iambic = 1;
    bConfig.sidetone_f = 600;
    bConfig.sidetone = 1;
    bConfig.trx = 0;
    bConfig.weight = 50;
    bConfig.wpmbpm = 1;
    bConfig.wpm = 15;
    bConfig.ratio = 30;
    bConfig.reverse = 0;
    bConfig.SinusRising = 6;
    sei(); // enable all interrupts
}
/** \brief 16 Bit Timer 1A
 *
 * Timer 1A interrupt
 * Overflow interrupt every 64µs
 *
 */
//ISR(TIMER1_OVF_vect)
//{
//    //sCurrentTimer += 0xffff;
//    PORTD ^= (1<<PD1);
//}
//
ISR(TIMER1_COMPA_vect)
{
    IntDisable();
    if(StateRising > 0)
    {
        OCR2A = (pgm_read_byte_near(sinewave+icnt) >> StateRising);
    }
    else
    {
        OCR2A = pgm_read_byte_near(sinewave+icnt);
    }
    icnt++;
    if(icnt > SINEWAVELENGTH - 1)
    {
        icnt = 0;
        if(StateRising > 0)
            StateRising--;
    }
    IntEnable();
}

/** \brief 8 Bit Timer 0
 *
 * The Timer 0 CTC interrupt
 * Dieser Interrupt wird jede Millisekunde erzeugt
 *
*/
ISR(TIMER0_COMPA_vect)
{
    ms++;
    StoreEEprom++;
    MenuCtrlTimer++;

    mselement++;    // element length of dit or dat

    t_wait++;
    l_timer++;
    encoder_timer++;

    if(l_timer >= L_WAIT){
        l_timer = 0;
    }
    // Alle 5ms den Drehencoder abfragen
    if(encoder_timer > 5) {
        EncoderPolling();
        // Schalter vom Drehencoder abfragen
        if(EncoderGetButtonState() == ButtonPressed_Short)
        {
            bMenuCtrl.buttonPressed = 1;
            SendSerialString("Button pressed short\r\n");
        }
        if(EncoderGetButtonState() == ButtonPressed_Long)
        {
            bMenuCtrl.buttonPressedLong = 1;
            SendSerialString("Button pressed long\r\n");
        }
    }
    // Wpm verändert?
    if((StoreEEprom > 1000) && (bMerker.WpMChanged))
    {
        StoreEEprom = 0;
        bMerker.WriteWpMEEProm = 1;
        bMerker.WpMChanged = 0;
    }
    // Konfiguration nach 3 Sekunden verlassen
    // if((MenuCtrlTimer > 3000) && (bMenuCtrl.Config == 1))
    // {
    //    bMenuCtrl.Config = 0;
    //    bMenuCtrl.Update = 1;
    //    bMenuCtrl.ClrScr = 1;
    //    BeepBoop();
    // }
}

/** \brief 8 Bit Timer 2
 *
 * Timer 2 overflow interrupt
 * Mit diesem Interrupt wird der nächste Wert für die
 * Erzeugung des Sinus für den Mithörton geladen.
 *
*/
ISR(TIMER2_OVF_vect)
{
//  phaccu = phaccu + tword_m;
//  icnt = phaccu >> 24;
//  OCR2A = pgm_read_byte_near(sinewave+icnt);

}

/** \brief Pin Change Interupts für Paddle und StraightKey
 *
 *  Pin Change Interrupt Vector für die Tasteneingänge
 *                    Str Pa2 Pa1          
 *  PORTD PD7 PD6 PD5 PD4 PD3 PD2 PD1 PD0
 *        x80 x40 x20 x10  x8  x4  x2  x1
 *
*/
ISR(PCINT2_vect)
{
  uint8_t changedbits;
  changedbits = PIND ^ keyhistory;
  keyhistory = PIND;

  if(changedbits & (1<<STRAIGHT_KEY))
  {
    if(PIND & (1<<STRAIGHT_KEY))
    {
      PORTC &= ~(1<<MORSE_LED);
      SideToneOff();
    } 
    else 
    {
      PORTC |= (1<<MORSE_LED);
      SideToneOn();
    }
  }
}

ISR(USART_RX_vect)
{
    unsigned char data;
    data = UDR0;
    SendSerialChar(data);
}

/** \brief ClearSendBuffer
 *
*/
void ClearSendBuffer(void)
{
    // sendbufferbytes = 0;
}

/** \brief CheckDitPaddle
 *
 *
*/
void CheckDitPaddle(void)
{
    uint8_t pinvalue = 0;
    uint8_t ditpaddle = 0;

    if(PaddleMode == PADDLE_NORMAL)	// no reverse paddle
        ditpaddle = LEFT_PADDLE;
    else
        ditpaddle = RIGHT_PADDLE;	// reverse paddle

    pinvalue = ReadKeyPin(ditpaddle);

    if(pinvalue == 0)
        DitBuffer = 1;
}
/*
 ** CheckDahPaddle
 */
void CheckDahPaddle(void)
{
    uint8_t pinvalue = 0;
    uint8_t dahpaddle = 0;

    if(PaddleMode == PADDLE_NORMAL)	// no reverse paddle
        dahpaddle = RIGHT_PADDLE;
    else
        dahpaddle = LEFT_PADDLE;	// reverse paddle

    pinvalue = ReadKeyPin(dahpaddle);

    if(pinvalue == 0) {
        if(DahBuffer == 0) {
            DahCounter++;
            DitCounter = 0;
        }
        DahBuffer = 1;
    }
}
/*
 ** DoMorse
 */
void DoMorse(void)
{
    if((KeyerMode == IAMBIC_A) || (KeyerMode == IAMBIC_B) || KeyerMode == SINGLE_PADDLE)
    {
        if((KeyerMode == IAMBIC_A) && (IambicFlag) && (ReadKeyPin(LEFT_PADDLE)))
        {
            IambicFlag = 0;
            DitBuffer = 0;
            DahBuffer = 0;
        }
        else
        {
            if(DitBuffer)
            {
                DitBuffer = 0;
                SendDit(MANUAL_SENDING);
            }
            if(DahBuffer)
            {
                DahBuffer = 0;
                SendDah(MANUAL_SENDING);
            }
        }
    }
    else
    {
        if(KeyerMode == STRAIGHT)
        {
            if(DitBuffer)
            {
                DitBuffer = 0;
                TXSidetoneKey(1,MANUAL_SENDING);
            }
            else
            {
                TXSidetoneKey(0,MANUAL_SENDING);
            }
            DitCounter = 0;
        }
    }
}

void ConfigMenue(void)
{
    char line[22];

    lcd_charMode(NORMAL);
    lcd_gotoxy(0,0);
    sprintf(line,"%s    - %i","Konfiguration", bMenuCtrl.CurMenue);
    lcd_puts(line);
    lcd_charMode(DOUBLESIZE);
    lcd_gotoxy(0,3);
    lcd_puts(CLEARLINE);
    switch(bMenuCtrl.CurMenue)
    {
        case M_TRX1:
            lcd_gotoxy(0,3);
            if((bConfig.trx == 1) || (bConfig.trx == 0))
                sprintf(line,"[%s]", "TRX 1");
            else
                sprintf(line,"%s", "TRX 1");
            lcd_puts(line);
        break;
        case M_TRX2:
            lcd_gotoxy(0,3);
            if((bConfig.trx == 2) || (bConfig.trx == 0))
                sprintf(line,"[%s]", "TRX 2");
            else
                sprintf(line,"%s", "TRX 2");
            lcd_puts(line);
        break;
        case M_IAMBICA:
            lcd_gotoxy(0,3);
            if(bConfig.iambic == 1)
                sprintf(line,"[%s]", "Iambic A");
            else
                sprintf(line,"%s", "Iambic A");
            lcd_puts(line);
        break;
        case M_IAMBICB:
            lcd_gotoxy(0,3);
            if(bConfig.iambic == 2)
                sprintf(line,"[%s]", "Iambic B");
            else
                sprintf(line,"%s", "Iambic B");
            lcd_puts(line);
        break;
        case M_REVERSE:
            lcd_gotoxy(0,3);
            if(bConfig.reverse == 0)
                sprintf(line,"%s", "L . R -");
            else
                sprintf(line,"%s", "L - R .");
            lcd_puts(line);
        break;
        case M_RATIO:
            lcd_gotoxy(0,3);
            if(bConfig.ratio == 30)
                sprintf(line,"%s", "Ratio 3:1");
            else
                sprintf(line,"%s %f:1", "Ratio", bConfig.ratio/10);
            lcd_puts(line);
        break;
        case M_TON_FREQ:
            lcd_gotoxy(0,3);
            if(bConfig.sidetone_f == 650)
                sprintf(line,"%s", "Ton 650Hz");
            else
                sprintf(line,"%s %uHz", "Ton", bConfig.sidetone_f);
            lcd_puts(line);
        break;
        case M_TON:
            lcd_gotoxy(0,3);
            if(bConfig.ratio == 1)
                sprintf(line,"%s", "Ton an");
            else
                sprintf(line,"%s", "Ton aus");
            lcd_puts(line);
        break;
        case M_WPMBPM:
            lcd_gotoxy(0,3);
            if(bConfig.wpmbpm == 0)
                sprintf(line,"%s", "WpM");
            else
                sprintf(line,"%s", "BpM");
            lcd_puts(line);
        break;
    }
    bMenuCtrl.Update = 0;
    lcd_charMode(NORMAL);
}
/** \brief UpdateDisplay
 *
 * Aktualisierung der Anzeigen auf dem Display je nach
 * aktueller Funktion.
 *
 *  DOUBLESIZE 4x10 character
 *  NORMALSIZE 8x21 character
*/
void UpdateDisplay(void)
{
    char line[22];
    if(bMenuCtrl.Update)
    {
        if(bMenuCtrl.ClrScr)
        {
            lcd_clrscr();
            bMenuCtrl.ClrScr = 0;
        }
        if(!(bMenuCtrl.Config))
        {
            lcd_charMode(DOUBLESIZE);
            lcd_gotoxy(4,3);
            if(bConfig.wpmbpm)
                sprintf(line,"%i WpM ",bConfig.wpm);
            else
                sprintf(line,"%i BpM ", bConfig.wpm*5);
            lcd_puts(line);
            lcd_charMode(NORMAL);
            lcd_gotoxy(13,0);
            if(bConfig.iambic == 1)
                sprintf(line,"%s", IambicA);
            if(bConfig.iambic == 2)
                sprintf(line,"%s", IambicB);
            lcd_puts(line);
            lcd_gotoxy(0,0);
            if(bConfig.trx == 1)
                sprintf(line, "%s", Trx1);
            if(bConfig.trx == 2)
                sprintf(line, "%s", Trx2);
            if(bConfig.trx == 0)
                sprintf(line, "%s %s", Trx1, Trx2);
            lcd_puts(line);
        }
        if(bMenuCtrl.Config)
        {
            ConfigMenue();
        }
        bMenuCtrl.Update = 0;
    }
}
void Drehencoder(void)
{
    int8_t st = 0;
    static int8_t last;

    if(!(bMenuCtrl.Config))
    {
        EncoderMinMax(5,50);
        st = EncoderRead(1);
        if(bConfig.wpm != st)
        {
            bConfig.wpm = st;
            bMerker.WpMChanged = 1;
            bMenuCtrl.Update = 1;
        }
    }

    if((bMenuCtrl.buttonPressed == 1) && (bMenuCtrl.Config == 0))
    {
        bMenuCtrl.Config = 1;
        MenuCtrlTimer = 0;
        bMenuCtrl.buttonPressed = 0;
    }

    if((bMenuCtrl.buttonPressedLong == 1) && (bMenuCtrl.Config == 1))
    {
        bMenuCtrl.Config = 0;
        bMenuCtrl.Update = 1;
        bMenuCtrl.buttonPressedLong = 0;
        bMenuCtrl.buttonPressed = 0;
        bMenuCtrl.m_buttonPressed = 0;
        bMenuCtrl.m_buttonPressed = 0;
        MenuCtrlTimer = 0;
    }

    if(bMenuCtrl.Config == 1)
    {
        if(!bMenuCtrl.buttonPressed)
        {
            EncoderMinMax(1,M_MAX);
            st = EncoderRead(1);
            sprintf(sdebug,"Encoder %i\r\n",st);
            SendSerialString(sdebug);
            if(last != st)
            {
                bMenuCtrl.CurMenue = st;
                bMenuCtrl.Update = 1;
            }
            last = st;
        }

        if(bMenuCtrl.buttonPressed)
        {
            bMenuCtrl.m_buttonPressed = 1;
            bMenuCtrl.buttonPressed = 0;
        }

        if(bMenuCtrl.m_buttonPressed == 1)
        {
            UpdateDisplay();
            switch(bMenuCtrl.CurMenue)
            {
                case M_TRX1:
                    if(bConfig.trx == 2)
                        bConfig.trx = 0;
                    else
                        bConfig.trx = 1;
                    bMenuCtrl.m_buttonPressed = 0;
                break;
                case M_TRX2:
                    if(bConfig.trx == 1)
                        bConfig.trx = 0;
                    else
                        bConfig.trx = 2;
                    bMenuCtrl.m_buttonPressed = 0;
                break;
                case M_IAMBICA:
                    bConfig.iambic = 1;
                    bMenuCtrl.m_buttonPressed = 0;
                break;
                case M_IAMBICB:
                    bConfig.iambic = 2;
                    bMenuCtrl.m_buttonPressed = 0;
                break;
                case M_REVERSE:
                    if(bConfig.reverse == 1)
                        bConfig.reverse = 0;
                    else
                        bConfig.reverse = 1;
                    bMenuCtrl.m_buttonPressed = 0;
                    break;
                case M_RATIO:
                    EncoderMinMax(15,30);
                    st = EncoderRead(1);
                    bConfig.ratio = st;
                    if(bConfig.ratio > 30) bConfig.ratio = 30;
                    if(bConfig.ratio < 15) bConfig.ratio = 15;
                    bMenuCtrl.Update = 1;
                break;
                case M_TON_FREQ:
                break;
            }
        }
    }
}

/*
 ** main
 */
int main(void)
{

    Init();
    SideToneOff();

    lcd_init(LCD_DISP_ON);
    lcd_charMode(DOUBLESIZE);
    lcd_home();
    lcd_puts(PRG);
    lcd_gotoxy(1,2);
    lcd_puts(VER);
    lcd_gotoxy(2,4);
    lcd_puts(CALL);
    delayms(1000);

    MachineMode = NORMAL;
    KeyerMode = IAMBIC_A;
    ReadEEProm_WpM();

    SendSerialString(CLRSCR);
    SendSerialString("BJ-Keyer V1.00\r\n");
    SendSerialString("Ready!\r\n");

    EncoderWrite(bConfig.wpm);
    BeepBoop();

    SetFrequency(600);

    while(1)
    {
        Drehencoder();

        if(bMerker.WriteWpMEEProm)
            WriteEEProm_WpM();

        UpdateDisplay();

   //     if(MachineMode == NORMAL)
   //     {
   //        CheckPaddles();
   //        DoMorse();
   //     }

        if(MachineMode == COMMAND)
        {

        }
    }
}