Tasteneingänge. Muss ein > 100€ Keyer überhaupt eine Entprellung haben? Überlegung, ob es nicht sinnvoller ist, die Tasteneingänge per Interrupt abzufragen. Das würde den Code auch noch erheblich vereinfachen, da nicht ständig an allen möglichen Stellen die Paddle abgefragt werden müssten.
383 Zeilen
11 KiB
C
383 Zeilen
11 KiB
C
/** @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);
|
|
|
|
// 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 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();
|
|
}
|
|
/** @brief main()
|
|
* @param none
|
|
* @return none
|
|
*/
|
|
int main(void)
|
|
{
|
|
ReStart();
|
|
SerialAbout();
|
|
SerialInfo();
|
|
while(1)
|
|
{
|
|
Drehencoder();
|
|
// Wenn Geschwindigkeit verändert und Zeit abgelaufen,
|
|
// dann im EEprom speichern und Merker löschen.
|
|
if(bState.WriteWpMEEprom)
|
|
{
|
|
sprintf(sdebug," %i WpM in EEprom speichern\r\n", bConfig.WpM);
|
|
SerialWriteString(sdebug);
|
|
WriteEEpromWpM();
|
|
bState.WriteWpMEEprom = 0;
|
|
}
|
|
if(bState.WriteEEprom)
|
|
{
|
|
sprintf(sdebug,"Einstellungen in EEprom speichern\r\n");
|
|
SerialWriteString(sdebug);
|
|
WriteEEprom();
|
|
bState.WriteEEprom = 0;
|
|
}
|
|
UpdateDisplay();
|
|
CheckStraightKey();
|
|
CheckPaddles();
|
|
DitDahBuffers();
|
|
}
|
|
}
|
|
|
|
|