forked from reedbn/ubitxv6
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkeyer.cpp
226 lines (200 loc) · 7.65 KB
/
keyer.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#include "toneAC2/toneAC2.h"
#include "pin_definitions.h"
#include "settings.h"
#include "tuner.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;
/**
* 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(){
toneAC2(PIN_CW_TONE, globalSettings.cwSideToneFreq);
digitalWrite(PIN_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(){
noToneAC2();
digitalWrite(PIN_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(PIN_ANALOG_KEYER);
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 <= cwAdcDashTo)
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;
char tmpKeyControl = 0;
if((KeyerMode_e::KEYER_STRAIGHT == globalSettings.keyerMode)
|| (digitalRead(PIN_PTT) == 0)){//use the PTT as the key for tune up, quick QSOs
while(1){
tmpKeyControl = update_PaddleLatch(0) | (digitalRead(PIN_PTT)?0:DIT_L);
//Serial.println((int)tmpKeyControl);
if ((tmpKeyControl & DIT_L) == DIT_L) {
// if we are here, it is only because the key is pressed
if (!globalSettings.txActive){
startTx(TuningMode_e::TUNE_CW);
globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs;
}
cwKeydown();
while ( tmpKeyControl & DIT_L == DIT_L){
tmpKeyControl = update_PaddleLatch(0) | (digitalRead(PIN_PTT)?0:DIT_L);
//Serial.println((int)tmpKeyControl);
}
cwKeyUp();
}
else{
if (0 < globalSettings.cwExpirationTimeMs && globalSettings.cwExpirationTimeMs < millis()){
globalSettings.cwExpirationTimeMs = 0;
stopTx();
}
return;//Tx stop control by Main Loop
}
checkCAT();
} //end of while
}
else{//KEYER_IAMBIC_*
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){
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; // 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
}//end of KEYER_IAMBIC_*
}