/** @file       main.c
 *  @date       2023-09-05
 *  @author     Tom, DL7BJ
 *  @version    1.03
 *  @brief      BJ-Keyer, ein elektronischer Morsekeyer
 *
 *              Morsekeyer für 3 Handtasten und 3 Paddle mit einem 
 *              Sinus Mithörton und Ausgängen für 2 Transceiver
 *
 *              OLED Funktionen von https://github.com/Sylaina/oled-display
 *              Encoder Funktionen von https://www.mikrocontroller.net/articles/Drehgeber
 *
 *

@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
 2023-08-25 DL7BJ   Code dokumentiert und aufgeräumt
 2023-09-03 DL7BJ   Funktionen für die Speichertasten hinzugefügt
 2023-09-04 DL7BK   Programmierung der Textspeicher über serielle Schnittstelle
 
 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

@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
};

/** @fn     void InitTimer(void)
 *  @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 
 *
 *  @param  none
 *  @retval none
 */
void InitTimer(void)
{
    cli();                                  // Interrupts verbieten
    TCCR2A = 0;                             // Timer 2 PWM
    TCCR2B = 0;                             // Pulswellenmodulation
    sbi(TCCR2B,CS20);                       // kein Prescaler
    sbi(TCCR2A,COM2A1);                     // Clear OC2A on compare match
    sbi(TCCR2A,WGM20);                      // Fast PWM Mode
    sbi(TCCR2A,WGM21);                      // Fast PWM Mode
    OCR2A = 0x80;                           // Initialer Wert
    sbi(DDRB,PB3);                          // Datenrichtungsregister PB3 Ausgang
    cbi(TIMSK2,OCIE0A);                     // Timer 2 nicht starten
    TCCR1A = 0; TCCR1B = 0; TIMSK1 = 0;     // Timer 1 für die Sinus Hüllkurve
    sbi(TCCR1B,WGM12);                      // CTC Mode
    sbi(TCCR1B,CS11);                       // Prescaling 8
    OCR1A = 51;                             // Initialer Wert für 600Hz Ton
    sbi(TIMSK1,OCIE1A);                     // Timer 1 Sinus Hüllkurve einschalten
    TCCR0A = 0; TCCR0B = 0; TCNT0 = 0;      // Timer 0 1ms für diverse Zähler
    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();                                  // Interrupts erlauben    
}
/** @fn     void Init(void)
 *  @brief  Initialisierung aller Variablen, Timer 
 *  @param  none
 *  @retval none
 */
void Init(void)
{
    cli(); // disable all interrupts
    bState.SendStatus = SENDING_NOTHING;
    // Alle Ports auf Eingang
    DDRB = 0x00; DDRD = 0x00; DDRC = 0x00;
    // Interne PullUps einschalten
    sbi(PORTB,PB0);         // Encoder A
    sbi(PORTB,PB1);         // Encoder B
    sbi(PORTB,PB2);         // Encoder Switch
    // Eingänge für Speichertasten
    sbi(PORTD,MEM1);        // PD5
    sbi(PORTD,MEM2);        // PD6
    sbi(PORTD,MEM3);        // PD7
    sbi(PORTC,MEM4);        // PC0
    sbi(PORTB,MEM5);        // PB5
    // Ausgänge festlegen
    // PORTB Ausgänge
    sbi(DDRB,PB3);          // PWM
    sbi(DDRB,AUDIO_EN);     // Audio Verstärker
    sbi(PORTB,AUDIO_EN);    // Audio Verstärker Enable
    // PORTC Ausgänge
    sbi(DDRC,MORSE_LED);    // LED
    sbi(DDRC,TRX1);         // Transceiver 1
    sbi(DDRC,TRX2);         // Transceiver 2
    // Serielle Schnittstelle
    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

    // Alle Timer Variablen rücksetzen
    StateRiseTimeCounter = 0;
    MenuCtrlTimer = 0;
    EncoderTimer = 0;
    StoreEEpromTimer = 0;
    t_elementlength = 0;
    t_delayms = 0;
    // Timer initialisieren
    InitTimer();
    // Encoder initialisieren
    EncoderInit();
    // Initialisierung Menüvariablen
    bMenuCtrl.ClrScr = 1;
    bMenuCtrl.Update = 1;
    bMenuCtrl.Config = 0;
    bMenuCtrl.buttonPressed = 0;
    bMenuCtrl.buttonPressedLong = 0;
    bState.WriteEEprom = 0;
    sei(); // enable all interrupts
}
/** @fn     ISR(TIMER1_COMPA_vect)
 *  @brief  8 Bit Timer 1 ISR routine
 *
 *          Der Timer 1 lädt die Sinuswerte für die PWM
 *
 *  @param  none
 *  @retval none
*/
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);
            }
        }
    }
}
/** @fn     ISR(TIMER0_COMPA_vect) 
 *  @brief  8 Bit Timer 0 ISR routine
 *
 *          Der Timer 0 mit CTC Interrupt läuft mit einem Takt
 *          von einer Millisekunde Es werden mehrere Werte innerhalb 
 *          des Timerinterrupts verarbeitet.
 *
 *  @param  none
 *  @retval none
 */
ISR(TIMER0_COMPA_vect)
{
    t_delayms++;            // 16Bit Zähler für Warteschleifen
    t_elementlength++;      // Länge eines Symbols
    StoreEEpromTimer++;     // Zähler für Zeitablauf speichern EEprom
    MenuCtrlTimer++;        // Zähler für Zeitablauf Einstellungen
    EncoderTimer++;         // Zähler für 5ms Drehencoder Timer

    // 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((StoreEEpromTimer > 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;
    }
    TimerButtonPressed++;
    if(StateButtonPressed == KEY_PRESSED_DEBOUNCE)
    {
        if(TimerButtonPressed > 250)
        {
            StateButtonPressed = KEY_PRESSED;
        }
    }
}
/** @fn     ISR(USART_RX_vect)
 *  @brief  Interrupt RX serielle Schnittstelle
 *
 *  @param  none
 *  @retval none
 */
ISR(USART_RX_vect)
{
    unsigned char data;
    data = UDR0;
    SerialWriteChar(data);
    SerialReceive(data);
}
/** @fn     void ReStart(void)
 *  @brief  Initialisierung bei Reset und Power On
 *  @param  none
 *  @retval none
 */
void ReStart(void)
{
    bState.SendStatus = SENDING_NOTHING;
    Init();
    DisplayVersion();
    ReadEEprom();
    WpM = bConfig.WpM;
    EncoderPos = bConfig.WpM;
    EncoderWrite(bConfig.WpM);
    EncoderPosConfig = 1;
    SetFrequency(bConfig.SidetoneFreq);
    KeyerMode = bConfig.KeyerMode;
    PaddleMode = bConfig.Reverse;
    bState.KeyTX = 1;
    bState.KeyState = 0;
    bState.Automatic = 0;
    bState.WriteMsgEEprom = 0;
    bState.WriteEEprom = 0;
    if(bConfig.SidetoneEnabled)
        SidetoneEnable();
    else
        SidetoneDisable();
    SetRatio();
    SetWeight();
    for(uint8_t i = 1; i < 6; i++)
        ReadMsgEEprom(i);
}
/** @fn     int main(void)
 *  @brief  One Infinite Loop
 *  @param  none
 *  @retval none
 */
int main(void)
{
    ReStart();
    SerialReset();
    while(1)
    {
        // Wenn Geschwindigkeit verändert und Zeit abgelaufen,
        // dann neue Geschwindigkeit im EEprom speichern und 
        // Merker löschen.
        if(bState.WriteWpMEEprom)
        {
            WriteEEpromWpM();
            bState.WriteWpMEEprom = 0;
        }
        // Wenn Einstellungen verändert wurden, diese im EEprom
        // speichern
        if(bState.WriteEEprom)
        {
            WriteEEprom();
            bState.WriteEEprom = 0;
        }
        if(bState.WriteMsgEEprom == 2)
        {
            for(uint8_t i = 1; i < 6; i++)
                WriteMsgEEprom(i);
            bState.WriteMsgEEprom = 0;
            SerialReset();
        }
        Drehencoder();          // Drehencoder abfragen
        UpdateDisplay();        // Display aktualisieren
        CheckStraightKey();     // Handtaste auf Betätigung prüfen
        CheckPaddles();         // Paddles auf Betätigung prüfen
        CheckButtons();         // Taster abfragen
   }
}