/** @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
 2023-07-29 DL7BJ   Menü für Einstellungen implementiert
 2023-08-06 DL7BJ   Quelltexte umstrukturiert, Aufteilung

 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 "main.h"

// Sinustabelle für PWM mit 64 Werten
const unsigned char sinewave[] PROGMEM = {
    0x00, 0x01, 0x02, 0x05, 0x0a, 0x0f, 0x15, 0x1d, 0x25, 0x2f, 0x39, 0x43, 0x4f, 0x5a, 0x67, 0x73, 
    0x80, 0x8c, 0x98, 0xa5, 0xb0, 0xbc, 0xc6, 0xd0, 0xda, 0xe2, 0xea, 0xf0, 0xf5, 0xfa, 0xfd, 0xfe, 
    0xff, 0xfe, 0xfd, 0xfa, 0xf5, 0xf0, 0xea, 0xe2, 0xda, 0xd0, 0xc6, 0xbc, 0xb0, 0xa5, 0x98, 0x8c, 
    0x80, 0x73, 0x67, 0x5a, 0x4f, 0x43, 0x39, 0x2f, 0x25, 0x1d, 0x15, 0x0f, 0x0a, 0x05, 0x02, 0x01
};

/**
 * \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);
    cbi(TIMSK2,OCIE0A);

    // 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 1 Sinus Hüllkurve einschalten

    // 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);     // Timer 0 1ms einschalten
    sei();
}

void Init()
{
    cli(); // disable all interrupts
    bState.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
    // 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);

    LengthOfElement = (uint16_t)1200/bConfig.WpM;

    // Pin Change Interrupts Port D - Keys
    // Alle Pins liegen im PCINT2 Vektor
    // 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);
    sbi(PCICR,PCIE0);
    sbi(PCMSK0,PCINT0);
    sbi(PCMSK0,PCINT1);
    sbi(PCMSK0,PCINT2);

    // 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.buttonPressedLong = 0;
    bMenuCtrl.WriteEEprom = 0;
    sei(); // enable all interrupts
}

ISR(TIMER1_COMPA_vect)
{
    ocr2a = pgm_read_byte_near(sinewave+icnt);
    icnt++;
    if(StateRiseTime > 0)
        OCR2A = (ocr2a >> StateRiseTime);
    else
        OCR2A = ocr2a;
    
    if(icnt == SINEWAVELENGTH)
    {
        icnt = 0;
        if(bState.SidetoneOff == 0)
        {
            if(StateRiseTime > 0)
            {
                StateRiseTimeCounter++;
                if(StateRiseTimeCounter > bConfig.RiseTimeCounter)
                    StateRiseTime--;
            }
        }
        if(bState.SidetoneOff == 1)
        {
            if(StateRiseTime < bConfig.RiseTime)
            {
                StateRiseTimeCounter++;
                if(StateRiseTimeCounter > bConfig.RiseTimeCounter)
                    StateRiseTime++;
            } else {
                OCR2A = 0;
                cbi(TIMSK1,OCIE1A);
            }
        }
    }
}

/** @brief 8 Bit Timer 0 ISR routine
 *
 *  The Timer 0 CTC interrupt will be fired every millisecond and 
 *  has a bundle of counter variables for different timings.
 *
 *  params: none
 *  return: none
 *
*/
ISR(TIMER0_COMPA_vect)
{
    t_delayms++;            // 16Bit counter for milliseconds
    t_elementlength++;      // Length of element dit/dah
    t_wait++;
    t_timer++;             
    StoreEEprom++;          // Zähler für Zeitablauf speichern EEprom
    MenuCtrlTimer++;        // Zähler für Zeitablauf Einstellungen
    EncoderTimer++;         // Zähler für 5ms Drehencoder Timer

    if(t_timer >= L_WAIT)
    {
        t_timer = 0;
    }
    // Alle 5ms den Drehencoder abfragen
    if(EncoderTimer > 5) 
    {
        EncoderTimer = 0;
        EncoderPolling();
        // Schalter vom Drehencoder abfragen
        lastButton = EncoderGetButtonState();
        if(lastButton == ButtonPressed_Short)
        {
            bMenuCtrl.buttonPressed = 1;
        }
        if(lastButton == ButtonPressed_Long)
        {
            bMenuCtrl.buttonPressedLong = 1;
            if(bMenuCtrl.Config == 1)
                bState.WriteEEprom = 1;
        }
    }
    // WpM verändert? Nach 5 Sekunden im EEPROM Speichern
    if((StoreEEprom > 1000) && (bState.WpMChanged))
    {
        bState.WriteWpMEEprom = 1;
        bState.WpMChanged = 0;
    }
    // Softwareentprellung für StraightKey
    TimerStraightKeyPressed++;
    if(StateStraightKeyPressed == KEY_PRESSED_DEBOUNCE)
    {
        if(TimerStraightKeyPressed > bConfig.DebounceTime) 
            StateStraightKeyPressed = KEY_PRESSED;
    }
    // Softwarentprellung für Paddle
    TimerPaddleDitKeyPressed++;
    if(StatePaddleDitKeyPressed == KEY_PRESSED_DEBOUNCE)
    {
        if(TimerPaddleDitKeyPressed > bConfig.DebounceTime)
            StatePaddleDitKeyPressed = KEY_PRESSED;
    }
    // Softwarentprellung für Paddle
    TimerPaddleDahKeyPressed++;
    if(StatePaddleDahKeyPressed == KEY_PRESSED_DEBOUNCE)
    {
        if(TimerPaddleDahKeyPressed > bConfig.DebounceTime)
            StatePaddleDahKeyPressed = KEY_PRESSED;
    }

}

/** \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)
{
    /* nothing */
}
/** @brief  Interrupt RX serielle Schnittstelle
 *  @param  none
 *  @return none
 */
ISR(USART_RX_vect)
{
    unsigned char data;
    data = UDR0;
    SerialWriteChar(data);
}
/** @brief  Pin Change Interrupt PORT C
 *  @param  none
 *  @return none
 */
ISR(PCINT2_vect)
{
}
/** @brief  Pin Change Interrupt PORT B
 *  @param  none
 *  @return none
 */
ISR(PCINT0_vect)
{
    SerialWriteChar("Encoder\r\n");
    bState.Encoder = 1;
}
/** @brief  Initialisierung bei Reset und Neustart
 *  @param  none
 *  @return none
 */
void ReStart(void)
{

    MenuCtrlTimer = 0;
    t_delayms = 0;
    bState.SendStatus = SENDING_NOTHING;
    
    Init();    
    DisplayVersion();
    ReadEEprom();
    WpM = bConfig.WpM;
    EncoderPos = bConfig.WpM;
    EncoderWrite(bConfig.WpM);
    EncoderPosConfig = 1;
    SetFrequency(bConfig.SidetoneFreq);
    SetRatio(0);
    KeyerMode = bConfig.KeyerMode;
    PaddleMode = bConfig.Reverse;
    bState.KeyTX = 1;
    bState.KeyState = 0;
    if(bConfig.SidetoneEnabled)
        SidetoneEnable();
    else
        SidetoneDisable();
    LastPINDState = 0xff;
}
/** @brief  main()
 *  @param  none
 *  @return none
 */
int main(void)
{
    ReStart();
    SerialAbout();
    SerialInfo();
    while(1)
    {
        // Wenn Geschwindigkeit verändert und Zeit abgelaufen,
        // dann im EEprom speichern und Merker löschen.
        if(bState.WriteWpMEEprom)
        {
            WriteEEpromWpM();
            bState.WriteWpMEEprom = 0;
        }
        if(bState.WriteEEprom)
        {
            WriteEEprom();
            bState.WriteEEprom = 0;
        }
        
        Drehencoder(); 
        UpdateDisplay();
        CheckStraightKey();
        CheckPaddles();

        if(bState.DitPressed || bState.DahPressed)
        {
            if(bState.DitPressed && !bState.DahPressed)
                SendDit();
            else if(bState.DahPressed && !bState.DitPressed)
                SendDah();
            else if(bState.DahPressed && bState.DitPressed)
                SendIambic();
        }
    }
}


