/** * 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, byte isDisplayUpdate); extern unsigned long sideTone; extern int cwSpeed; 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; extern unsigned volatile char IAMBIC; extern unsigned volatile char PDLSWAP; 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 PTT_HANDKEY_ACTIVE = false; volatile long last_interrupt_time = 20; extern bool txCAT; // KC4UPR: These are some temporary (maybe?) translation macros to translate // between the mode selection code in W0EB's software, versus the mode // selection code in the basic (and CEC) software. I may replace this is the // future, either by reworking the whole codebase to use the (superior) W0EB // method, or else by modifying the keyer code to use the stock mode selection // code. #define MODE_USB 0 #define MODE_LSB 1 #define MODE_CW 2 #define MODE_CWR 3 #define ubitx_mode (cwMode == 0 ? (isUSB == 0) : ((cwMode == 1) + 2)) /* KC4UPR: Temporary holding ground for definitions etc that may need to get moved to other files. */ //#define DIGITAL_PTT (A3) //#define DIGITAL_DOT (D11) //#define DIGITAL_DASH (D12) /* Arduino Pin MC Pin Interrupt Mask A3/D17 PC3 PCINT[11] PCMSK1/bit 3 D11 PB3 PCINT[3] PCMSK0/bit 3 D12 PB4 PCINT[4] PCMSK0/bit 4 */ /** * 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 = true; //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 = false; //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) == 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; // 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 = false; startTx(TX_CW, 0); } if (!keyDown) 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) == LOW) { // 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 = false; startTx(TX_CW, 0); } if (!keyDown) cwKeyDown(); PTT_HANDKEY_ACTIVE = true; Turn_Off_Carrier_Timer_Count = CW_TIMEOUT; } } else if (keyDown && PTT_HANDKEY_ACTIVE) { 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) { while (continue_loop) { switch (keyerState) { case IDLE: if ((digitalRead(DIGITAL_DOT) == LOW) || (digitalRead(DIGITAL_DASH) == LOW) || (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) { // 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) { // 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) == LOW) { // 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, 0); } } } else if (inTx && !txCAT) { last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; stopTx(); } else { last_interrupt_time = PTT_HNDKEY_DEBOUNCE_CT; } } } void Connect_Interrupts(void) { keyerControl = 0; cli(); PCMSK0 |- 0b00011000; // turn on dot/dash pins PB3/PB4, physical D11/D12 PCMSK1 |= 0b00001000; // turn on PTT and Handkey pin PC3, physical A3 PCICR |= 0b00000011; // turn on ports B and C TIMSK1 |= (1<