diff --git a/ubitx_20/ubitx_keyer.cpp b/ubitx_20/ubitx_keyer.cpp new file mode 100644 index 0000000..58e77ae --- /dev/null +++ b/ubitx_20/ubitx_keyer.cpp @@ -0,0 +1,334 @@ +/** + * File name ubitx_keyer.cpp + * CW Keyer + * + * The CW keyer handles either a straight key or an iambic / paddle key. + * D12 for DOT Paddle and D11 for DASH Paddle and D* for PTT/Handkey + * + * Generating CW + * The CW is cleanly generated by unbalancing the front-end mixer + * and putting the local oscillator directly at the CW transmit frequency. + * The sidetone, generated by the Arduino is injected into the volume control + */ +#include +#include "ubitx.h" + +extern void stopTx(void); +extern void startTx(byte txMode); +extern void updateDisplay(void); +extern void printLine2(char *c); +extern void printLine3(char *c); +extern void printLine4(char *c); + +extern unsigned long sideTone; +extern int cwSpeed; +extern long CW_TIMEOUT; +extern volatile bool inTx; +extern volatile int ubitx_mode; + +extern volatile unsigned char keyerControl; +extern volatile unsigned char keyerState; +extern unsigned volatile char IAMBIC; +extern unsigned volatile char PDLSWAP; + +extern volatile unsigned long Ubitx_Voltage; +extern volatile int Ubitx_Voltage_Timer; + + +volatile bool keyDown = false; //in cw mode, denotes the carrier is being transmitted +volatile uint8_t Last_Bits = 0xFF;; + + +volatile bool Dot_in_Progress = false; +volatile unsigned long Dot_Timer_Count = 0; +volatile bool Dash_in_Progress = false; +volatile unsigned long Dash_Timer_Count = 0; +volatile bool Inter_Bit_in_Progress = false; +volatile unsigned long Inter_Bit_Timer_Count = 0; +volatile bool Turn_Off_Carrier_in_Progress = false; +volatile unsigned long Turn_Off_Carrier_Timer_Count = 0; +volatile bool Ubitx_Voltage_Act = false; +volatile bool PTT_HANDKEY_ACTIVE = false; +volatile long last_interrupt_time = 20; +extern bool Cat_Lock; +extern volatile bool TX_In_Progress; + +/** + * Starts transmitting the carrier with the sidetone + * It assumes that we have called cwTxStart and not called cwTxStop + * each time it is called, the cwTimeOut is pushed further into the future + */ +void cwKeydown(void){ + keyDown = 1; //tracks the CW_KEY + tone(CW_TONE, (int)sideTone); + digitalWrite(CW_KEY, 1); + #ifdef XMIT_LED + digitalWrite(ON_AIR, 0); // extinguish the LED on NANO's pin 13 + #endif + +} +/** + * Stops the CW carrier transmission along with the sidetone + * Pushes the cwTimeout further into the future + */ +void cwKeyUp(void){ + keyDown = 0; //tracks the CW_KEY + noTone(CW_TONE); + digitalWrite(CW_KEY, 0); + #ifdef XMIT_LED + digitalWrite(ON_AIR, 1); // extinguish the LED on NANO's pin 13 + #endif +} + + + +void update_PaddleLatch() { +// if (!digitalRead(DIGITAL_DOT) ) keyerControl |= DIT_L; +// if (!digitalRead(DIGITAL_DASH) ) keyerControl |= DAH_L; + if (digitalRead(DIGITAL_DOT) == LOW) { + if (keyerControl & PDLSWAP) keyerControl |= DAH_L; else keyerControl |= DIT_L; + } + if (digitalRead(DIGITAL_DASH) == LOW) { + if (keyerControl & PDLSWAP) keyerControl |= DIT_L; else keyerControl |= DAH_L; + } + +} + +////////////////////////////////////////////////////////////////////////////////////////// +// interupt handlers + +//// timers +ISR(TIMER1_OVF_vect) { + bool continue_loop = true; + + +// timer for voltage display + if ( Ubitx_Voltage_Timer <= 0 ){ + Ubitx_Voltage_Timer = Ubitx_Voltage_Timer_Count; + Ubitx_Voltage_Act = true; + }else Ubitx_Voltage_Timer = Ubitx_Voltage_Timer - 1; + +// process if CW modes +if( (ubitx_mode == MODE_CW)||(ubitx_mode == MODE_CWR)){ + + + // process DOT and DASH timing + if( (Dot_in_Progress)&&(Dot_Timer_Count > 0) ){ + if( !inTx ){ + keyDown = 0; + startTx(TX_CW); + } + if( keyDown == 0 ) cwKeydown(); + Dot_Timer_Count = Dot_Timer_Count - 1; + if( Dot_Timer_Count <= 0 ){ + Dot_Timer_Count = 0; + Dot_in_Progress = false; + cwKeyUp(); + } + } + + // process Inter Bit Timing + if( (Inter_Bit_in_Progress)&&(Inter_Bit_Timer_Count > 0) ){ + Inter_Bit_Timer_Count = Inter_Bit_Timer_Count - 1; + if( Inter_Bit_Timer_Count <= 0 ){ + Inter_Bit_Timer_Count = 0; + Inter_Bit_in_Progress = false; + } + } + + // process turning off carrier + if( (Turn_Off_Carrier_in_Progress)&&(Turn_Off_Carrier_Timer_Count > 0) ){ + Turn_Off_Carrier_Timer_Count = Turn_Off_Carrier_Timer_Count - 1; + if( Turn_Off_Carrier_Timer_Count <= 0 ){ + Turn_Off_Carrier_in_Progress = false; + Turn_Off_Carrier_Timer_Count = 0; + stopTx(); + } + } + + // process hand key + if( digitalRead(DIGITAL_KEY) == 0 ){ + // If interrupts come faster than 5ms, assume it's a bounce and ignore + last_interrupt_time = last_interrupt_time - 1; + if( last_interrupt_time <= 0) { + last_interrupt_time = 0; + if( !inTx ){ + keyDown = 0; + startTx(TX_CW); + } + if( keyDown == 0 ) cwKeydown(); + PTT_HANDKEY_ACTIVE = true; + Turn_Off_Carrier_Timer_Count = CW_TIMEOUT; + } + }else if( (keyDown == 1) && (PTT_HANDKEY_ACTIVE == true) ){ + cwKeyUp(); + Turn_Off_Carrier_Timer_Count = CW_TIMEOUT; + Turn_Off_Carrier_in_Progress = true; + last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; + PTT_HANDKEY_ACTIVE = false; + }else last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; + +if( PTT_HANDKEY_ACTIVE == false ){ + while(continue_loop){ + switch (keyerState) { + case IDLE: + if ( + (!digitalRead(DIGITAL_DOT)) || + (!digitalRead(DIGITAL_DASH))|| + (keyerControl & 0x03) + ) { + update_PaddleLatch(); + keyerState = CHK_DIT; + Dot_in_Progress = false; + Dot_Timer_Count = 0; + Turn_Off_Carrier_Timer_Count = 0; + Turn_Off_Carrier_in_Progress = false; + }else{ + continue_loop = false; + } + break; + + case CHK_DIT: + if (keyerControl & DIT_L) { + keyerControl |= DIT_PROC; + keyerState = KEYED_PREP; + Dot_Timer_Count = cwSpeed; + }else{ + keyerState = CHK_DAH; + } + break; + + case CHK_DAH: + if (keyerControl & DAH_L) { + keyerState = KEYED_PREP; + Dot_Timer_Count = cwSpeed*3; + }else{ + continue_loop = false; + keyerState = IDLE; + } + break; + + case KEYED_PREP: + keyerControl &= ~(DIT_L + DAH_L); // clear both paddle latch bits + keyerState = KEYED; // next state + Turn_Off_Carrier_Timer_Count = 0; + Turn_Off_Carrier_in_Progress = false; + Dot_in_Progress = true; + break; + + case KEYED: + if (Dot_in_Progress == false) { // are we at end of key down ? + Inter_Bit_in_Progress = true; + Inter_Bit_Timer_Count = cwSpeed; + keyerState = INTER_ELEMENT; // next state + }else if (keyerControl & IAMBIC) { + update_PaddleLatch(); // early paddle latch in Iambic B mode + continue_loop = false; + }else continue_loop = false; + break; + + case INTER_ELEMENT: + // Insert time between dits/dahs + update_PaddleLatch(); // latch paddle state + if (Inter_Bit_in_Progress == false) { // are we at end of inter-space ? + Turn_Off_Carrier_Timer_Count = CW_TIMEOUT; + Turn_Off_Carrier_in_Progress = true; + if (keyerControl & DIT_PROC) { // was it a dit or dah ? + keyerControl &= ~(DIT_L + DIT_PROC); // clear two bits + keyerState = CHK_DAH; // dit done, check for dah + }else{ + keyerControl &= ~(DAH_L); // clear dah latch + keyerState = IDLE; // go idle + } + }else continue_loop = false; + break; + } + } + } +} + + // process PTT + if( (ubitx_mode == MODE_USB)|| (ubitx_mode == MODE_LSB)){ + if( digitalRead(PTT) == 0 ){ + // If interrupts come faster than 5ms, assume it's a bounce and ignore + last_interrupt_time = last_interrupt_time - 1; + if( last_interrupt_time <= 0) { + last_interrupt_time = 0; + if( !inTx ) startTx(TX_SSB); + } + }else if( (inTx)&&(Cat_Lock == false )&&(TX_In_Progress == false)){ + last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; + stopTx(); + }else last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; + } + +} +void Connect_Interrupts(void){ + keyerControl = 0; + cli(); + TIMSK1 |= (1<