/** \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 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); 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 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.IambicMode = 1; bConfig.FrequencySidetone = 600; bConfig.SidetoneEnabled = 1; bConfig.TRX = 0; bConfig.Weight = 50; bConfig.WpMBpM = 1; bConfig.WpM = 12; bConfig.Ratio = 30; bConfig.Reverse = 0; bConfig.RiseTime = 6; bConfig.RiseTimeCounter = 6; bConfig.DebounceTime = 5; bConfig.WinkeyerEnabled = 0; 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< 0) OCR2A = (ocr2a >> StateRiseTime); else OCR2A = ocr2a; if(icnt == SINEWAVELENGTH) { icnt = 0; if(state_sidetoneoff == 0) { if(StateRiseTime > 0) { StateRiseTimeCounter++; if(StateRiseTimeCounter > bConfig.RiseTimeCounter) StateRiseTime--; } } if(state_sidetoneoff == 1) { if(StateRiseTime < bConfig.RiseTime) { StateRiseTimeCounter++; if(StateRiseTimeCounter > bConfig.RiseTimeCounter) StateRiseTime++; } else { OCR2A = 0; cbi(TIMSK1,OCIE1A); } } } } /** \brief 8 Bit Timer 0 * * The Timer 0 CTC interrupt * Dieser Interrupt wird jede Millisekunde erzeugt * */ ISR(TIMER0_COMPA_vect) { ms++; // allgemeiner Millisekundenzähler StoreEEprom++; // Zähler für Zeitablauf speichern EEprom MenuCtrlTimer++; // Zähler für Zeitablauf Einstellungen EncoderTimer++; // Zähler für 5ms Drehencoder Timer mselement++; // element length of dit or dat t_wait++; l_timer++; if(l_timer >= L_WAIT) { l_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; SendSerialString("Encoder Button pressed short\r\n"); } if(lastButton == ButtonPressed_Long) { bMenuCtrl.buttonPressedLong = 1; SendSerialString("Encoder Button pressed long\r\n"); } } // WpM verändert? Nach einer Sekunde im EEPROM Speichern if((StoreEEprom > 1000) && (bMerker.WpMChanged)) { bMerker.WriteWpMEEprom = 1; bMerker.WpMChanged = 0; } // Softwareentprellung für StraightKey TimerStraightKeyPressed++; if(StateStraightKeyPressed == 1) { if(TimerStraightKeyPressed > bConfig.DebounceTime) StateStraightKeyPressed = 2; } // 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); } 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); lcd_puts(CLEARLINE); lcd_gotoxy(0,0); sprintf(line,"%s - %i","Einstellungen", 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.IambicMode == 1) sprintf(line,"[%s]", IambicA); else sprintf(line," %s ", IambicB); lcd_puts(line); break; case M_IAMBICB: lcd_gotoxy(0,3); if(bConfig.IambicMode == 2) sprintf(line,"[%s]", IambicB); else sprintf(line," %s ", IambicB); lcd_puts(line); break; case M_REVERSE: lcd_gotoxy(0,3); if(bConfig.Reverse == 0) sprintf(line,"%s", ReverseLR); else sprintf(line,"%s", ReverseRL); 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 %i:1", "Ratio", bConfig.Ratio/10); lcd_puts(line); break; case M_TON_FREQ: lcd_gotoxy(0,3); if(bConfig.FrequencySidetone == 650) sprintf(line,"%s", "Ton 650Hz"); else sprintf(line,"%s %uHz", "Ton", bConfig.FrequencySidetone); lcd_puts(line); break; case M_TON: lcd_gotoxy(0,3); if(bConfig.SidetoneEnabled == 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; case M_RISETIME: lcd_gotoxy(0,3); sprintf(line,"%s %ims",RiseTime, bConfig.RiseTime); lcd_puts(line); break; case M_DEBOUNCE: lcd_gotoxy(0,3); sprintf(line,"%s %ims",DebounceTime, bConfig.DebounceTime); 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; bMenuCtrl.Update = 1; } 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.IambicMode == 1) sprintf(line,"%s", IambicA); if(bConfig.IambicMode == 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; // neuen Wert in bConfig speichern bMerker.WpMChanged = 1; // Merker für Timer 0 setzen bMenuCtrl.Update = 1; // Merker für UpdateDisplay setzen EncoderPos = st; // neuen Werte in EncoderPos speichern WpM = bConfig.WpM; // neuen Wert in WpM übernehmen cli(); StoreEEprom = 0; // Timer 0 Variable löschen für Update EEprom sei(); } } if((bMenuCtrl.buttonPressed == 1) && (bMenuCtrl.Config == 0)) { bMenuCtrl.Config = 1; MenuCtrlTimer = 0; bMenuCtrl.buttonPressed = 0; EncoderPos = EncoderRead(1); bMenuCtrl.CurMenue = EncoderPosConfig; EncoderWrite(EncoderPosConfig); bMenuCtrl.Update = 1; } if((bMenuCtrl.buttonPressedLong == 1) && (bMenuCtrl.Config == 1)) { bMenuCtrl.Config = 0; bMenuCtrl.Update = 1; bMenuCtrl.buttonPressedLong = 0; bMenuCtrl.buttonPressed = 0; bMenuCtrl.m_buttonPressed = 0; MenuCtrlTimer = 0; EncoderPosConfig = EncoderRead(1); EncoderWrite(EncoderPos); bMenuCtrl.ClrScr = 1; Beep(); } if(bMenuCtrl.Config == 1) { if(!bMenuCtrl.buttonPressed) { EncoderMinMax(1,M_MAX); st = EncoderRead(1); sprintf(sdebug,"Encoder %i\r\n",st); if(last != st) { SendSerialString(sdebug); 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.IambicMode = 1; bMenuCtrl.m_buttonPressed = 0; break; case M_IAMBICB: bConfig.IambicMode = 2; bMenuCtrl.m_buttonPressed = 0; break; case M_REVERSE: if(bConfig.Reverse == 1) bConfig.Reverse = 0; else bConfig.Reverse = 1; bMenuCtrl.m_buttonPressed = 0; if(bConfig.Reverse == 1) PaddleMode = PADDLE_REVERSE; else PaddleMode = PADDLE_NORMAL; bMenuCtrl.Update = 1; 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(); 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); SideToneOff(); delayms(1000); MachineMode = NORMAL; KeyerMode = IAMBIC_A; SendSerialString(CLRSCR); SendSerialString("BJ-Keyer V1.00\r\n"); SendSerialString("Ready!\r\n"); ReadEEpromWpM(); WpM = bConfig.WpM; EncoderPos = bConfig.WpM; EncoderWrite(bConfig.WpM); EncoderPosConfig = 1; SetFrequency(600); while(1) { Drehencoder(); // Wenn Geschwindigkeit verändert und Zeit abgelaufen, // dann im EEprom speichern und Merker löschen. if(bMerker.WriteWpMEEprom) { sprintf(" %i WpM in EEprom speichern\r\n", bConfig.WpM); WriteEEpromWpM(); bMerker.WriteWpMEEprom = 0; } UpdateDisplay(); if(MachineMode == NORMAL) { CheckStraightKey(); CheckPaddles(); DoMorse(); } if(MachineMode == COMMAND) { } } }