BJ-Keyer/Source/main.c
Tom, DL7BJ 463869f62f Das Probem mit dem Iambic Mode lag bei der Funktion der Entprellung der
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.
2023-08-21 22:40:04 +02:00

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();
}
}