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