diff --git a/ubitx_20/ubitx_keyer.cpp b/ubitx_20/ubitx_keyer.cpp new file mode 100644 index 0000000..9b52a37 --- /dev/null +++ b/ubitx_20/ubitx_keyer.cpp @@ -0,0 +1,341 @@ +/** + * 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 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)) + +/** + * 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(); + TIMSK1 |= (1<