/** * 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 "ubitx.h" #include extern void stopTx(void); extern void startTx(byte txMode, byte isDisplayUpdate = 0); extern unsigned long sideTone; extern int cwSpeed; // extern long CW_TIMEOUT; extern long cwTimeout; #define CW_TIMEOUT (cwTimeout) extern volatile bool inTx; // extern volatile int ubitx_mode; extern char isUSB; extern char cwMode; extern volatile unsigned char keyerControl; // extern volatile unsigned char keyerState; volatile unsigned char keyerState = IDLE; // extern unsigned volatile char IAMBICB; // 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; extern volatile bool txCAT; /** * 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) { static volatile bool i_am_running = false; bool continue_loop = true; if (i_am_running) return; i_am_running = true; // process if CW modes // if( (ubitx_mode == MODE_CW)||(ubitx_mode == MODE_CWR)){ if (cwMode > 0) { // 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 & IAMBICB) { 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 (cwMode == 0) { 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) && (txCAT == false)) { last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; stopTx(); } else last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; } i_am_running = false; } void Connect_Interrupts(void) { keyerControl = 0; cli(); TIMSK1 |= (1 << TOIE1); sei(); } /* #define N_MORSE (sizeof(morsetab)/sizeof(morsetab[0])) // Morse table struct t_mtab { char c, pat; } ; struct t_mtab morsetab[] = { {'.', 106}, {',', 115}, {'?', 76}, {'/', 41}, {'A', 6}, {'B', 17}, {'C', 21}, {'D', 9}, {'E', 2}, {'F', 20}, {'G', 11}, {'H', 16}, {'I', 4}, {'J', 30}, {'K', 13}, {'L', 18}, {'M', 7}, {'N', 5}, {'O', 15}, {'P', 22}, {'Q', 27}, {'R', 10}, {'S', 8}, {'T', 3}, {'U', 12}, {'V', 24}, {'W', 14}, {'X', 25}, {'Y', 29}, {'Z', 19}, {'1', 62}, {'2', 60}, {'3', 56}, {'4', 48}, {'5', 32}, {'6', 33}, {'7', 35}, {'8', 39}, {'9', 47}, {'0', 63} }; /////////////////////////////////////////////////////////////////////////////////////////// // CW generation routines for CQ message void key(int LENGTH){ if( !inTx ) startTx(TX_CW); cwKeydown(); delay(LENGTH*2); cwKeyUp(); delay(cwSpeed*2); } void send(char c){ int i ; if (c == ' ') { delay(7*cwSpeed) ; return ; } for (i=0; i