/** @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 } }