ubitxv6/keyer.cpp

235 lines
7.7 KiB
C++

#include <Arduino.h>
#include "settings.h"
#include "ubitx.h"
/**
CW Keyer
CW Key logic change with ron's code (ubitx_keyer.cpp)
Ron's logic has been modified to work with the original uBITX by KD8CEC
Original Comment ----------------------------------------------------------------------------
* The CW keyer handles either a straight key or an iambic / paddle key.
* They all use just one analog input line. This is how it works.
* The analog line has the internal pull-up resistor enabled.
* When a straight key is connected, it shorts the pull-up resistor, analog input is 0 volts
* When a paddle is connected, the dot and the dash are connected to the analog pin through
* a 10K and a 2.2K resistors. These produce a 4v and a 2v input to the analog pins.
* So, the readings are as follows :
* 0v - straight key
* 1-2.5 v - paddle dot
* 2.5 to 4.5 v - paddle dash
* 2.0 to 0.5 v - dot and dash pressed
*
* The keyer is written to transparently handle all these cases
*
* 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
*/
//CW ADC Range
//static const unsigned int cwAdcSTFrom = 0;
static const unsigned int cwAdcSTTo = 50;
static const unsigned int cwAdcBothFrom = cwAdcSTTo + 1;
static const unsigned int cwAdcBothTo = 300;
static const unsigned int cwAdcDotFrom = cwAdcBothTo + 1;
static const unsigned int cwAdcDotTo = 600;
static const unsigned int cwAdcDashFrom = cwAdcDotTo + 1;
static const unsigned int cwAdcDashTo = 800;
static const unsigned int delayBeforeCWStartTime = 50;
/**
* 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(){
tone(CW_TONE, globalSettings.cwSideToneFreq);
digitalWrite(CW_KEY, 1);
globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs;
}
/**
* Stops the cw carrier transmission along with the sidetone
* Pushes the cwTimeout further into the future
*/
void cwKeyUp(){
noTone(CW_TONE);
digitalWrite(CW_KEY, 0);
globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs;
}
//Variables for Ron's new logic
#define DIT_L 0x01 // DIT latch
#define DAH_L 0x02 // DAH latch
#define DIT_PROC 0x04 // DIT is being processed
#define PDLSWAP 0x08 // 0 for normal, 1 for swap
#define IAMBICB 0x10 // 0 for Iambic A, 1 for Iambic B
enum KSTYPE {IDLE, CHK_DIT, CHK_DAH, KEYED_PREP, KEYED, INTER_ELEMENT };
static unsigned long ktimer;
unsigned char keyerState = IDLE;
uint8_t keyerControl = 0;
//Below is a test to reduce the keying error. do not delete lines
//create by KD8CEC for compatible with new CW Logic
char update_PaddleLatch(bool isUpdateKeyState) {
unsigned char tmpKeyerControl = 0;
unsigned int paddle = analogRead(ANALOG_KEYER);
//use the PTT as the key for tune up, quick QSOs
if (digitalRead(PTT) == 0)
tmpKeyerControl |= DIT_L;
else if (paddle >= cwAdcDashFrom && paddle <= cwAdcDashTo)
tmpKeyerControl |= DAH_L;
else if (paddle >= cwAdcDotFrom && paddle <= cwAdcDotTo)
tmpKeyerControl |= DIT_L;
else if (paddle >= cwAdcBothFrom && paddle <= cwAdcBothTo)
tmpKeyerControl |= (DAH_L | DIT_L) ;
else{
if (KeyerMode_e::KEYER_STRAIGHT != globalSettings.keyerMode)
tmpKeyerControl = 0 ;
else if (paddle <= cwAdcSTTo)
tmpKeyerControl = DIT_L ;
else
tmpKeyerControl = 0 ;
}
if (isUpdateKeyState)
keyerControl |= tmpKeyerControl;
return tmpKeyerControl;
}
/*****************************************************************************
// New logic, by RON
// modified by KD8CEC
******************************************************************************/
void cwKeyer(void){
bool continue_loop = true;
unsigned tmpKeyControl = 0;
if(KeyerMode_e::KEYER_STRAIGHT != globalSettings.keyerMode){
while(continue_loop){
switch(keyerState){
case IDLE:
tmpKeyControl = update_PaddleLatch(0);
if((tmpKeyControl == DAH_L)//Currently dah
||(tmpKeyControl == DIT_L)//Currently dit
||(tmpKeyControl == (DAH_L | DIT_L))//Currently both
||( keyerControl & (DAH_L | DIT_L))){//Resolving either
update_PaddleLatch(true);
keyerState = CHK_DIT;
}
else{
if (0 < globalSettings.cwExpirationTimeMs && globalSettings.cwExpirationTimeMs < millis()){
globalSettings.cwExpirationTimeMs = 0;
stopTx();
}
continue_loop = false;
}
break;
case CHK_DIT:
if (keyerControl & DIT_L) {
keyerControl |= DIT_PROC;
ktimer = globalSettings.cwDitDurationMs;
keyerState = KEYED_PREP;
}else{
keyerState = CHK_DAH;
}
break;
case CHK_DAH:
if (keyerControl & DAH_L) {
ktimer = 3*globalSettings.cwDitDurationMs;
keyerState = KEYED_PREP;
}else{
keyerState = IDLE;
}
break;
case KEYED_PREP:
//modified KD8CEC
if (!globalSettings.txActive){
//DelayTime Option
active_delay(delayBeforeCWStartTime * 2);
globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs;
startTx(TuningMode_e::TUNE_CW);
}
ktimer += millis(); // set ktimer to interval end time
keyerControl &= ~(DIT_L + DAH_L); // clear both paddle latch bits
keyerState = KEYED; // next state
cwKeydown();
break;
case KEYED:
if (millis() > ktimer) { // are we at end of key down ?
cwKeyUp();
ktimer = millis() + (globalSettings.cwDitDurationMs / 10); // inter-element time
keyerState = INTER_ELEMENT; // next state
}
else if(KeyerMode_e::KEYER_IAMBIC_B == globalSettings.keyerMode){
update_PaddleLatch(1); // early paddle latch in Iambic B mode
}
break;
case INTER_ELEMENT:
// Insert time between dits/dahs
update_PaddleLatch(1); // latch paddle state
if (millis() > ktimer) { // are we at end of inter-space ?
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
}
}
break;
}
checkCAT();
} //end of while
}
else{//KEYER_STRAIGHT
while(1){
char state = update_PaddleLatch(0);
// Serial.println((int)state);
if (state == DIT_L) {
// if we are here, it is only because the key is pressed
if (!globalSettings.txActive){
startTx(TuningMode_e::TUNE_CW);
//DelayTime Option
active_delay(delayBeforeCWStartTime * 2);
globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwDitDurationMs;
}
cwKeydown();
while ( update_PaddleLatch(0) == DIT_L )
active_delay(1);
cwKeyUp();
}
else{
if (0 < globalSettings.cwExpirationTimeMs && globalSettings.cwExpirationTimeMs < millis()){
globalSettings.cwExpirationTimeMs = 0;
stopTx();
}
return;//Tx stop control by Main Loop
}
checkCAT();
} //end of while
}//end of else KEYER_STRAIGHT
}