├── README.md ├── ubitx_cat.ino ├── ubitx_factory_alignment.ino ├── ubitx_keyer.in2 ├── ubitx_keyer.ino ├── ubitx_menu.ino ├── ubitx_si5351.ino ├── ubitx_ui.ino └── ubitx_v4.3_code.ino /README.md: -------------------------------------------------------------------------------- 1 | #ubitx4 2 | -------------------------------------------------------------------------------- /ubitx_cat.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The CAT protocol is used by many radios to provide remote control to comptuers through 3 | * the serial port. 4 | * 5 | * This is very much a work in progress. Parts of this code have been liberally 6 | * borrowed from other GPLicensed works like hamlib. 7 | * 8 | * WARNING : This is an unstable version and it has worked with fldigi, 9 | * it gives time out error with WSJTX 1.8.0 10 | */ 11 | 12 | static unsigned long rxBufferArriveTime = 0; 13 | static byte rxBufferCheckCount = 0; 14 | #define CAT_RECEIVE_TIMEOUT 500 15 | static byte cat[5]; 16 | static byte insideCat = 0; 17 | 18 | //for broken protocol 19 | #define CAT_RECEIVE_TIMEOUT 500 20 | 21 | #define CAT_MODE_LSB 0x00 22 | #define CAT_MODE_USB 0x01 23 | #define CAT_MODE_CW 0x02 24 | #define CAT_MODE_CWR 0x03 25 | #define CAT_MODE_AM 0x04 26 | #define CAT_MODE_FM 0x08 27 | #define CAT_MODE_DIG 0x0A 28 | #define CAT_MODE_PKT 0x0C 29 | #define CAT_MODE_FMN 0x88 30 | 31 | #define ACK 0 32 | 33 | unsigned int skipTimeCount = 0; 34 | 35 | byte setHighNibble(byte b,byte v) { 36 | // Clear the high nibble 37 | b &= 0x0f; 38 | // Set the high nibble 39 | return b | ((v & 0x0f) << 4); 40 | } 41 | 42 | byte setLowNibble(byte b,byte v) { 43 | // Clear the low nibble 44 | b &= 0xf0; 45 | // Set the low nibble 46 | return b | (v & 0x0f); 47 | } 48 | 49 | byte getHighNibble(byte b) { 50 | return (b >> 4) & 0x0f; 51 | } 52 | 53 | byte getLowNibble(byte b) { 54 | return b & 0x0f; 55 | } 56 | 57 | // Takes a number and produces the requested number of decimal digits, staring 58 | // from the least significant digit. 59 | // 60 | void getDecimalDigits(unsigned long number,byte* result,int digits) { 61 | for (int i = 0; i < digits; i++) { 62 | // "Mask off" (in a decimal sense) the LSD and return it 63 | result[i] = number % 10; 64 | // "Shift right" (in a decimal sense) 65 | number /= 10; 66 | } 67 | } 68 | 69 | // Takes a frequency and writes it into the CAT command buffer in BCD form. 70 | // 71 | void writeFreq(unsigned long freq,byte* cmd) { 72 | // Convert the frequency to a set of decimal digits. We are taking 9 digits 73 | // so that we can get up to 999 MHz. But the protocol doesn't care about the 74 | // LSD (1's place), so we ignore that digit. 75 | byte digits[9]; 76 | getDecimalDigits(freq,digits,9); 77 | // Start from the LSB and get each nibble 78 | cmd[3] = setLowNibble(cmd[3],digits[1]); 79 | cmd[3] = setHighNibble(cmd[3],digits[2]); 80 | cmd[2] = setLowNibble(cmd[2],digits[3]); 81 | cmd[2] = setHighNibble(cmd[2],digits[4]); 82 | cmd[1] = setLowNibble(cmd[1],digits[5]); 83 | cmd[1] = setHighNibble(cmd[1],digits[6]); 84 | cmd[0] = setLowNibble(cmd[0],digits[7]); 85 | cmd[0] = setHighNibble(cmd[0],digits[8]); 86 | } 87 | 88 | // This function takes a frquency that is encoded using 4 bytes of BCD 89 | // representation and turns it into an long measured in Hz. 90 | // 91 | // [12][34][56][78] = 123.45678? Mhz 92 | // 93 | unsigned long readFreq(byte* cmd) { 94 | // Pull off each of the digits 95 | byte d7 = getHighNibble(cmd[0]); 96 | byte d6 = getLowNibble(cmd[0]); 97 | byte d5 = getHighNibble(cmd[1]); 98 | byte d4 = getLowNibble(cmd[1]); 99 | byte d3 = getHighNibble(cmd[2]); 100 | byte d2 = getLowNibble(cmd[2]); 101 | byte d1 = getHighNibble(cmd[3]); 102 | byte d0 = getLowNibble(cmd[3]); 103 | return 104 | (unsigned long)d7 * 100000000L + 105 | (unsigned long)d6 * 10000000L + 106 | (unsigned long)d5 * 1000000L + 107 | (unsigned long)d4 * 100000L + 108 | (unsigned long)d3 * 10000L + 109 | (unsigned long)d2 * 1000L + 110 | (unsigned long)d1 * 100L + 111 | (unsigned long)d0 * 10L; 112 | } 113 | 114 | //void ReadEEPRom_FT817(byte fromType) 115 | void catReadEEPRom(void) 116 | { 117 | //for remove warnings 118 | byte temp0 = cat[0]; 119 | byte temp1 = cat[1]; 120 | /* 121 | itoa((int) cat[0], b, 16); 122 | strcat(b, ":"); 123 | itoa((int) cat[1], c, 16); 124 | strcat(b, c); 125 | printLine2(b); 126 | */ 127 | 128 | cat[0] = 0; 129 | cat[1] = 0; 130 | //for remove warnings[1] = 0; 131 | 132 | switch (temp1) 133 | { 134 | case 0x45 : // 135 | if (temp0 == 0x03) 136 | { 137 | cat[0] = 0x00; 138 | cat[1] = 0xD0; 139 | } 140 | break; 141 | case 0x47 : // 142 | if (temp0 == 0x03) 143 | { 144 | cat[0] = 0xDC; 145 | cat[1] = 0xE0; 146 | } 147 | break; 148 | case 0x55 : 149 | //0 : VFO A/B 0 = VFO-A, 1 = VFO-B 150 | //1 : MTQMB Select 0 = (Not MTQMB), 1 = MTQMB ("Memory Tune Quick Memory Bank") 151 | //2 : QMB Select 0 = (Not QMB), 1 = QMB ("Quick Memory Bank") 152 | //3 : 153 | //4 : Home Select 0 = (Not HOME), 1 = HOME memory 154 | //5 : Memory/MTUNE select 0 = Memory, 1 = MTUNE 155 | //6 : 156 | //7 : MEM/VFO Select 0 = Memory, 1 = VFO (A or B - see bit 0) 157 | cat[0] = 0x80 + (vfoActive == VFO_B ? 1 : 0); 158 | cat[1] = 0x00; 159 | break; 160 | case 0x57 : // 161 | //0 : 1-0 AGC Mode 00 = Auto, 01 = Fast, 10 = Slow, 11 = Off 162 | //2 DSP On/Off 0 = Off, 1 = On (Display format) 163 | //4 PBT On/Off 0 = Off, 1 = On (Passband Tuning) 164 | //5 NB On/Off 0 = Off, 1 = On (Noise Blanker) 165 | //6 Lock On/Off 0 = Off, 1 = On (Dial Lock) 166 | //7 FST (Fast Tuning) On/Off 0 = Off, 1 = On (Fast tuning) 167 | 168 | cat[0] = 0xC0; 169 | cat[1] = 0x40; 170 | break; 171 | case 0x59 : // band select VFO A Band Select 0000 = 160 M, 0001 = 75 M, 0010 = 40 M, 0011 = 30 M, 0100 = 20 M, 0101 = 17 M, 0110 = 15 M, 0111 = 12 M, 1000 = 10 M, 1001 = 6 M, 1010 = FM BCB, 1011 = Air, 1100 = 2 M, 1101 = UHF, 1110 = (Phantom) 172 | //http://www.ka7oei.com/ft817_memmap.html 173 | //CAT_BUFF[0] = 0xC2; 174 | //CAT_BUFF[1] = 0x82; 175 | break; 176 | case 0x5C : //Beep Volume (0-100) (#13) 177 | cat[0] = 0xB2; 178 | cat[1] = 0x42; 179 | break; 180 | case 0x5E : 181 | //3-0 : CW Pitch (300-1000 Hz) (#20) From 0 to E (HEX) with 0 = 300 Hz and each step representing 50 Hz 182 | //5-4 : Lock Mode (#32) 00 = Dial, 01 = Freq, 10 = Panel 183 | //7-6 : Op Filter (#38) 00 = Off, 01 = SSB, 10 = CW 184 | //CAT_BUFF[0] = 0x08; 185 | cat[0] = (sideTone - 300)/50; 186 | cat[1] = 0x25; 187 | break; 188 | case 0x61 : //Sidetone (Volume) (#44) 189 | cat[0] = sideTone % 50; 190 | cat[1] = 0x08; 191 | break; 192 | case 0x5F : // 193 | //4-0 CW Weight (1.:2.5-1:4.5) (#22) From 0 to 14 (HEX) with 0 = 1:2.5, incrementing in 0.1 weight steps 194 | //5 420 ARS (#2) 0 = Off, 1 = On 195 | //6 144 ARS (#1) 0 = Off, 1 = On 196 | //7 Sql/RF-G (#45) 0 = Off, 1 = On 197 | cat[0] = 0x32; 198 | cat[1] = 0x08; 199 | break; 200 | case 0x60 : //CW Delay (10-2500 ms) (#17) From 1 to 250 (decimal) with each step representing 10 ms 201 | cat[0] = cwDelayTime; 202 | cat[1] = 0x32; 203 | break; 204 | case 0x62 : // 205 | //5-0 CW Speed (4-60 WPM) (#21) From 0 to 38 (HEX) with 0 = 4 WPM and 38 = 60 WPM (1 WPM steps) 206 | //7-6 Batt-Chg (6/8/10 Hours (#11) 00 = 6 Hours, 01 = 8 Hours, 10 = 10 Hours 207 | //CAT_BUFF[0] = 0x08; 208 | cat[0] = 1200 / cwSpeed - 4; 209 | cat[1] = 0xB2; 210 | break; 211 | case 0x63 : // 212 | //6-0 VOX Gain (#51) Contains 1-100 (decimal) as displayed 213 | //7 Disable AM/FM Dial (#4) 0 = Enable, 1 = Disable 214 | cat[0] = 0xB2; 215 | cat[1] = 0xA5; 216 | break; 217 | case 0x64 : // 218 | break; 219 | case 0x67 : //6-0 SSB Mic (#46) Contains 0-100 (decimal) as displayed 220 | cat[0] = 0xB2; 221 | cat[1] = 0xB2; 222 | break; case 0x69 : //FM Mic (#29) Contains 0-100 (decimal) as displayed 223 | case 0x78 : 224 | if (isUSB) 225 | cat[0] = CAT_MODE_USB; 226 | else 227 | cat[0] = CAT_MODE_LSB; 228 | 229 | if (cat[0] != 0) cat[0] = 1 << 5; 230 | break; 231 | case 0x79 : // 232 | //1-0 TX Power (All bands) 00 = High, 01 = L3, 10 = L2, 11 = L1 233 | //3 PRI On/Off 0 = Off, 1 = On 234 | //DW On/Off 0 = Off, 1 = On 235 | //SCN (Scan) Mode 00 = No scan, 10 = Scan up, 11 = Scan down 236 | //ART On/Off 0 = Off, 1 = On 237 | cat[0] = 0x00; 238 | cat[1] = 0x00; 239 | break; 240 | case 0x7A : //SPLIT 241 | //7A 0 HF Antenna Select 0 = Front, 1 = Rear 242 | //7A 1 6 M Antenna Select 0 = Front, 1 = Rear 243 | //7A 2 FM BCB Antenna Select 0 = Front, 1 = Rear 244 | //7A 3 Air Antenna Select 0 = Front, 1 = Rear 245 | //7A 4 2 M Antenna Select 0 = Front, 1 = Rear 246 | //7A 5 UHF Antenna Select 0 = Front, 1 = Rear 247 | //7A 6 ? ? 248 | //7A 7 SPL On/Off 0 = Off, 1 = On 249 | 250 | cat[0] = (splitOn ? 0xFF : 0x7F); 251 | break; 252 | case 0xB3 : // 253 | cat[0] = 0x00; 254 | cat[1] = 0x4D; 255 | break; 256 | 257 | } 258 | 259 | // sent the data 260 | Serial.write(cat, 2); 261 | } 262 | 263 | void processCATCommand2(byte* cmd) { 264 | byte response[5]; 265 | unsigned long f; 266 | 267 | switch(cmd[4]){ 268 | /* case 0x00: 269 | response[0]=0; 270 | Serial.write(response, 1); 271 | break; 272 | */ 273 | case 0x01: 274 | //set frequency 275 | f = readFreq(cmd); 276 | setFrequency(f); 277 | updateDisplay(); 278 | response[0]=0; 279 | Serial.write(response, 1); 280 | //sprintf(b, "set:%ld", f); 281 | //printLine2(b); 282 | break; 283 | 284 | case 0x02: 285 | //split on 286 | splitOn = 1; 287 | break; 288 | case 0x82: 289 | //split off 290 | splitOn = 0; 291 | break; 292 | 293 | case 0x03: 294 | writeFreq(frequency,response); // Put the frequency into the buffer 295 | if (isUSB) 296 | response[4] = 0x01; //USB 297 | else 298 | response[4] = 0x00; //LSB 299 | Serial.write(response,5); 300 | //printLine2("cat:getfreq"); 301 | break; 302 | 303 | case 0x07: // set mode 304 | if (cmd[0] == 0x00 || cmd[0] == 0x03) 305 | isUSB = 0; 306 | else 307 | isUSB = 1; 308 | response[0] = 0x00; 309 | Serial.write(response, 1); 310 | setFrequency(frequency); 311 | //printLine2("cat: mode changed"); 312 | //updateDisplay(); 313 | break; 314 | 315 | case 0x08: // PTT On 316 | if (!inTx) { 317 | response[0] = 0; 318 | txCAT = true; 319 | startTx(TX_SSB); 320 | updateDisplay(); 321 | } else { 322 | response[0] = 0xf0; 323 | } 324 | Serial.write(response,1); 325 | updateDisplay(); 326 | break; 327 | 328 | case 0x88 : //PTT OFF 329 | if (inTx) { 330 | stopTx(); 331 | txCAT = false; 332 | } 333 | response[0] = 0; 334 | Serial.write(response,1); 335 | updateDisplay(); 336 | break; 337 | 338 | case 0x81: 339 | //toggle the VFOs 340 | response[0] = 0; 341 | menuVfoToggle(1); // '1' forces it to change the VFO 342 | Serial.write(response,1); 343 | updateDisplay(); 344 | break; 345 | 346 | case 0xBB: //Read FT-817 EEPROM Data (for comfirtable) 347 | catReadEEPRom(); 348 | break; 349 | 350 | case 0xe7 : 351 | // get receiver status, we have hardcoded this as 352 | //as we dont' support ctcss, etc. 353 | response[0] = 0x09; 354 | Serial.write(response,1); 355 | break; 356 | 357 | case 0xf7: 358 | { 359 | boolean isHighSWR = false; 360 | boolean isSplitOn = false; 361 | 362 | /* 363 | Inverted -> *ptt = ((p->tx_status & 0x80) == 0); <-- souce code in ft817.c (hamlib) 364 | */ 365 | response[0] = ((inTx ? 0 : 1) << 7) + 366 | ((isHighSWR ? 1 : 0) << 6) + //hi swr off / on 367 | ((isSplitOn ? 1 : 0) << 5) + //Split on / off 368 | (0 << 4) + //dummy data 369 | 0x08; //P0 meter data 370 | 371 | Serial.write(response, 1); 372 | } 373 | break; 374 | 375 | default: 376 | //somehow, get this to print the four bytes 377 | ultoa(*((unsigned long *)cmd), c, 16); 378 | itoa(cmd[4], b, 16); 379 | strcat(b, ">"); 380 | strcat(b, c); 381 | printLine2(b); 382 | response[0] = 0x00; 383 | Serial.write(response[0]); 384 | } 385 | 386 | insideCat = false; 387 | } 388 | 389 | int catCount = 0; 390 | void checkCAT(){ 391 | byte i; 392 | 393 | //Check Serial Port Buffer 394 | if (Serial.available() == 0) { //Set Buffer Clear status 395 | rxBufferCheckCount = 0; 396 | return; 397 | } 398 | else if (Serial.available() < 5) { //First Arrived 399 | if (rxBufferCheckCount == 0){ 400 | rxBufferCheckCount = Serial.available(); 401 | rxBufferArriveTime = millis() + CAT_RECEIVE_TIMEOUT; //Set time for timeout 402 | } 403 | else if (rxBufferArriveTime < millis()){ //Clear Buffer 404 | for (i = 0; i < Serial.available(); i++) 405 | rxBufferCheckCount = Serial.read(); 406 | rxBufferCheckCount = 0; 407 | } 408 | else if (rxBufferCheckCount < Serial.available()){ // Increase buffer count, slow arrive 409 | rxBufferCheckCount = Serial.available(); 410 | rxBufferArriveTime = millis() + CAT_RECEIVE_TIMEOUT; //Set time for timeout 411 | } 412 | return; 413 | } 414 | 415 | 416 | //Arived CAT DATA 417 | for (i = 0; i < 5; i++) 418 | cat[i] = Serial.read(); 419 | 420 | 421 | //this code is not re-entrant. 422 | if (insideCat == 1) 423 | return; 424 | insideCat = 1; 425 | 426 | /** 427 | * This routine is enabled to debug the cat protocol 428 | catCount++; 429 | 430 | if (cat[4] != 0xf7 && cat[4] != 0xbb && cat[4] != 0x03){ 431 | sprintf(b, "%d %02x %02x%02x%02x%02x", catCount, cat[4],cat[0], cat[1], cat[2], cat[3]); 432 | printLine2(b); 433 | } 434 | */ 435 | processCATCommand2(cat); 436 | insideCat = 0; 437 | } 438 | 439 | 440 | -------------------------------------------------------------------------------- /ubitx_factory_alignment.ino: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This procedure is only for those who have a signal generator/transceiver tuned to exactly 7.150 and a dummy load 4 | */ 5 | 6 | void btnWaitForClick(){ 7 | while(!btnDown()) 8 | active_delay(50); 9 | while(btnDown()) 10 | active_delay(50); 11 | active_delay(50); 12 | } 13 | 14 | /** 15 | * Take a deep breath, math(ematics) ahead 16 | * The 25 mhz oscillator is multiplied by 35 to run the vco at 875 mhz 17 | * This is divided by a number to generate different frequencies. 18 | * If we divide it by 875, we will get 1 mhz signal 19 | * So, if the vco is shifted up by 875 hz, the generated frequency of 1 mhz is shifted by 1 hz (875/875) 20 | * At 12 Mhz, the carrier will needed to be shifted down by 12 hz for every 875 hz of shift up of the vco 21 | * 22 | */ 23 | 24 | 25 | void factory_alignment(){ 26 | 27 | calibrateClock(); 28 | 29 | if (calibration == 0){ 30 | printLine2("Setup Aborted"); 31 | return; 32 | } 33 | 34 | //move it away to 7.160 for an LSB signal 35 | setFrequency(7170000l); 36 | updateDisplay(); 37 | printLine2("#2 BFO"); 38 | active_delay(1000); 39 | 40 | usbCarrier = 11994999l; 41 | menuSetupCarrier(1); 42 | 43 | if (usbCarrier == 11994999l){ 44 | printLine2("Setup Aborted"); 45 | return; 46 | } 47 | 48 | printLine2("#3:Test 3.5MHz"); 49 | isUSB = false; 50 | setFrequency(3500000l); 51 | updateDisplay(); 52 | 53 | while (!btnDown()){ 54 | checkPTT(); 55 | active_delay(100); 56 | } 57 | 58 | btnWaitForClick(); 59 | printLine2("#4:Test 7MHz"); 60 | 61 | setFrequency(7150000l); 62 | updateDisplay(); 63 | while (!btnDown()){ 64 | checkPTT(); 65 | active_delay(100); 66 | } 67 | 68 | btnWaitForClick(); 69 | printLine2("#5:Test 14MHz"); 70 | 71 | isUSB = true; 72 | setFrequency(14000000l); 73 | updateDisplay(); 74 | while (!btnDown()){ 75 | checkPTT(); 76 | active_delay(100); 77 | } 78 | 79 | btnWaitForClick(); 80 | printLine2("#6:Test 28MHz"); 81 | 82 | setFrequency(28000000l); 83 | updateDisplay(); 84 | while (!btnDown()){ 85 | checkPTT(); 86 | active_delay(100); 87 | } 88 | 89 | printLine2("Alignment done"); 90 | active_delay(1000); 91 | 92 | isUSB = false; 93 | setFrequency(7150000l); 94 | updateDisplay(); 95 | 96 | } 97 | 98 | -------------------------------------------------------------------------------- /ubitx_keyer.in2: -------------------------------------------------------------------------------- 1 | /** 2 | * CW Keyer 3 | * 4 | * The CW keyer handles either a straight key or an iambic / paddle key. 5 | * They all use just one analog input line. This is how it works. 6 | * The analog line has the internal pull-up resistor enabled. 7 | * When a straight key is connected, it shorts the pull-up resistor, analog input is 0 volts 8 | * When a paddle is connected, the dot and the dash are connected to the analog pin through 9 | * a 10K and a 2.2K resistors. These produce a 4v and a 2v input to the analog pins. 10 | * So, the readings are as follows : 11 | * 0v - straight key 12 | * 1-2.5 v - paddle dot 13 | * 2.5 to 4.5 v - paddle dash 14 | * 2.0 to 0.5 v - dot and dash pressed 15 | * 16 | * The keyer is written to transparently handle all these cases 17 | * 18 | * Generating CW 19 | * The CW is cleanly generated by unbalancing the front-end mixer 20 | * and putting the local oscillator directly at the CW transmit frequency. 21 | * The sidetone, generated by the Arduino is injected into the volume control 22 | */ 23 | 24 | 25 | // in milliseconds, this is the parameter that determines how long the tx will hold between cw key downs 26 | #define CW_TIMEOUT (600l) 27 | #define PADDLE_DOT 1 28 | #define PADDLE_DASH 2 29 | #define PADDLE_BOTH 3 30 | #define PADDLE_STRAIGHT 4 31 | 32 | //we store the last padde's character 33 | //to alternatively send dots and dashes 34 | //when both are simultaneously pressed 35 | static char lastPaddle = 0; 36 | static boolean isStraightKey = false; 37 | 38 | //reads the analog keyer pin and reports the paddle 39 | byte getPaddle(){ 40 | int paddle = analogRead(ANALOG_KEYER); 41 | 42 | if (paddle > 800) // above 4v is up 43 | return 0; 44 | 45 | if (paddle > 600) // 4-3v is dot 46 | return PADDLE_DASH; 47 | else if (paddle > 300) //1-2v is dash 48 | return PADDLE_DOT; 49 | else if (paddle > 50) 50 | return PADDLE_BOTH; //both are between 1 and 2v 51 | else 52 | return PADDLE_STRAIGHT; //less than 1v is the straight key 53 | } 54 | 55 | /** 56 | * Starts transmitting the carrier with the sidetone 57 | * It assumes that we have called cwTxStart and not called cwTxStop 58 | * each time it is called, the cwTimeOut is pushed further into the future 59 | */ 60 | void cwKeydown(){ 61 | keyDown = 1; //tracks the CW_KEY 62 | tone(CW_TONE, (int)sideTone); 63 | digitalWrite(CW_KEY, 1); 64 | cwTimeout = millis() + CW_TIMEOUT; 65 | } 66 | 67 | /** 68 | * Stops the cw carrier transmission along with the sidetone 69 | * Pushes the cwTimeout further into the future 70 | */ 71 | void cwKeyUp(){ 72 | keyDown = 0; //tracks the CW_KEY 73 | noTone(CW_TONE); 74 | digitalWrite(CW_KEY, 0); 75 | cwTimeout = millis() + CW_TIMEOUT; 76 | } 77 | 78 | /** 79 | * The keyer handles the straight key as well as the iambic key 80 | * This module keeps looping until the user stops sending cw 81 | * if the cwTimeout is set to 0, then it means, we have to exit the keyer loop 82 | * Each time the key is hit the cwTimeout is pushed to a time in the future by cwKeyDown() 83 | */ 84 | 85 | void cwKeyer(){ 86 | byte paddle; 87 | lastPaddle = 0; 88 | 89 | while(1){ 90 | paddle = getPaddle(); 91 | 92 | // do nothing if the paddle has not been touched, unless 93 | // we are in the cw mode and we have timed out 94 | if (!paddle){ 95 | if (0 < cwTimeout && cwTimeout < millis()){ 96 | cwTimeout = 0; 97 | keyDown = 0; 98 | stopTx(); 99 | } 100 | 101 | if (!cwTimeout) 102 | return; 103 | 104 | //if a paddle was used (not a straight key) we should extend the space to be a full dash 105 | //by adding two more dots long space (one has already been added at the end of the dot or dash) 106 | if (cwTimeout > 0 && lastPaddle != PADDLE_STRAIGHT) 107 | active_delay(cwSpeed * 2); 108 | 109 | // got back to the begining of the loop, if no further activity happens on the paddle or the straight key 110 | // we will time out, and return out of this routine 111 | active_delay(5); 112 | continue; 113 | } 114 | 115 | Serial.print("paddle:");Serial.println(paddle); 116 | // if we are here, it is only because the key or the paddle is pressed 117 | if (!inTx){ 118 | keyDown = 0; 119 | cwTimeout = millis() + CW_TIMEOUT; 120 | startTx(TX_CW); 121 | updateDisplay(); 122 | } 123 | 124 | // star the transmission) 125 | // we store the transmitted character in the lastPaddle 126 | cwKeydown(); 127 | if (paddle == PADDLE_DOT){ 128 | active_delay(cwSpeed); 129 | lastPaddle = PADDLE_DOT; 130 | } 131 | else if (paddle == PADDLE_DASH){ 132 | active_delay(cwSpeed * 3); 133 | lastPaddle = PADDLE_DASH; 134 | } 135 | else if (paddle == PADDLE_BOTH){ //both paddles down 136 | //depending upon what was sent last, send the other 137 | if (lastPaddle == PADDLE_DOT) { 138 | active_delay(cwSpeed * 3); 139 | lastPaddle = PADDLE_DASH; 140 | }else{ 141 | active_delay(cwSpeed); 142 | lastPaddle = PADDLE_DOT; 143 | } 144 | } 145 | else if (paddle == PADDLE_STRAIGHT){ 146 | while (getPaddle() == PADDLE_STRAIGHT) 147 | active_delay(1); 148 | lastPaddle = PADDLE_STRAIGHT; 149 | } 150 | cwKeyUp(); 151 | //introduce a dot long gap between characters if the keyer was used 152 | if (lastPaddle != PADDLE_STRAIGHT) 153 | active_delay(cwSpeed); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /ubitx_keyer.ino: -------------------------------------------------------------------------------- 1 | /** 2 | CW Keyer 3 | CW Key logic change with ron's code (ubitx_keyer.cpp) 4 | Ron's logic has been modified to work with the original uBITX by KD8CEC 5 | 6 | Original Comment ---------------------------------------------------------------------------- 7 | * The CW keyer handles either a straight key or an iambic / paddle key. 8 | * They all use just one analog input line. This is how it works. 9 | * The analog line has the internal pull-up resistor enabled. 10 | * When a straight key is connected, it shorts the pull-up resistor, analog input is 0 volts 11 | * When a paddle is connected, the dot and the dash are connected to the analog pin through 12 | * a 10K and a 2.2K resistors. These produce a 4v and a 2v input to the analog pins. 13 | * So, the readings are as follows : 14 | * 0v - straight key 15 | * 1-2.5 v - paddle dot 16 | * 2.5 to 4.5 v - paddle dash 17 | * 2.0 to 0.5 v - dot and dash pressed 18 | * 19 | * The keyer is written to transparently handle all these cases 20 | * 21 | * Generating CW 22 | * The CW is cleanly generated by unbalancing the front-end mixer 23 | * and putting the local oscillator directly at the CW transmit frequency. 24 | * The sidetone, generated by the Arduino is injected into the volume control 25 | */ 26 | 27 | //CW ADC Range 28 | int cwAdcSTFrom = 0; 29 | int cwAdcSTTo = 50; 30 | int cwAdcBothFrom = 51; 31 | int cwAdcBothTo = 300; 32 | int cwAdcDotFrom = 301; 33 | int cwAdcDotTo = 600; 34 | int cwAdcDashFrom = 601; 35 | int cwAdcDashTo = 800; 36 | //byte cwKeyType = 0; //0: straight, 1 : iambica, 2: iambicb 37 | 38 | byte delayBeforeCWStartTime = 50; 39 | 40 | 41 | 42 | 43 | // in milliseconds, this is the parameter that determines how long the tx will hold between cw key downs 44 | //#define CW_TIMEOUT (600l) //Change to CW Delaytime for value save to eeprom 45 | #define PADDLE_DOT 1 46 | #define PADDLE_DASH 2 47 | #define PADDLE_BOTH 3 48 | #define PADDLE_STRAIGHT 4 49 | 50 | //we store the last padde's character 51 | //to alternatively send dots and dashes 52 | //when both are simultaneously pressed 53 | char lastPaddle = 0; 54 | 55 | //reads the analog keyer pin and reports the paddle 56 | byte getPaddle(){ 57 | int paddle = analogRead(ANALOG_KEYER); 58 | 59 | if (paddle > 800) // above 4v is up 60 | return 0; 61 | 62 | if (paddle > 600) // 4-3v is dot 63 | return PADDLE_DASH; 64 | else if (paddle > 300) //1-2v is dash 65 | return PADDLE_DOT; 66 | else if (paddle > 50) 67 | return PADDLE_BOTH; //both are between 1 and 2v 68 | else 69 | return PADDLE_STRAIGHT; //less than 1v is the straight key 70 | } 71 | 72 | /** 73 | * Starts transmitting the carrier with the sidetone 74 | * It assumes that we have called cwTxStart and not called cwTxStop 75 | * each time it is called, the cwTimeOut is pushed further into the future 76 | */ 77 | void cwKeydown(){ 78 | keyDown = 1; //tracks the CW_KEY 79 | tone(CW_TONE, (int)sideTone); 80 | digitalWrite(CW_KEY, 1); 81 | 82 | //Modified by KD8CEC, for CW Delay Time save to eeprom 83 | //cwTimeout = millis() + CW_TIMEOUT; 84 | cwTimeout = millis() + cwDelayTime * 10; 85 | } 86 | 87 | /** 88 | * Stops the cw carrier transmission along with the sidetone 89 | * Pushes the cwTimeout further into the future 90 | */ 91 | void cwKeyUp(){ 92 | keyDown = 0; //tracks the CW_KEY 93 | noTone(CW_TONE); 94 | digitalWrite(CW_KEY, 0); 95 | 96 | //Modified by KD8CEC, for CW Delay Time save to eeprom 97 | //cwTimeout = millis() + CW_TIMEOUT; 98 | cwTimeout = millis() + cwDelayTime * 10; 99 | } 100 | 101 | //Variables for Ron's new logic 102 | #define DIT_L 0x01 // DIT latch 103 | #define DAH_L 0x02 // DAH latch 104 | #define DIT_PROC 0x04 // DIT is being processed 105 | #define PDLSWAP 0x08 // 0 for normal, 1 for swap 106 | #define IAMBICB 0x10 // 0 for Iambic A, 1 for Iambic B 107 | enum KSTYPE {IDLE, CHK_DIT, CHK_DAH, KEYED_PREP, KEYED, INTER_ELEMENT }; 108 | static unsigned long ktimer; 109 | unsigned char keyerState = IDLE; 110 | 111 | //Below is a test to reduce the keying error. do not delete lines 112 | //create by KD8CEC for compatible with new CW Logic 113 | char update_PaddleLatch(byte isUpdateKeyState) { 114 | unsigned char tmpKeyerControl = 0; 115 | 116 | int paddle = analogRead(ANALOG_KEYER); 117 | //diagnostic, VU2ESE 118 | //itoa(paddle, b, 10); 119 | //printLine2(b); 120 | 121 | if (paddle >= cwAdcDashFrom && paddle <= cwAdcDashTo) 122 | tmpKeyerControl |= DAH_L; 123 | else if (paddle >= cwAdcDotFrom && paddle <= cwAdcDotTo) 124 | tmpKeyerControl |= DIT_L; 125 | else if (paddle >= cwAdcBothFrom && paddle <= cwAdcBothTo) 126 | tmpKeyerControl |= (DAH_L | DIT_L) ; 127 | else 128 | { 129 | if (Iambic_Key) 130 | tmpKeyerControl = 0 ; 131 | else if (paddle >= cwAdcSTFrom && paddle <= cwAdcSTTo) 132 | tmpKeyerControl = DIT_L ; 133 | else 134 | tmpKeyerControl = 0 ; 135 | } 136 | 137 | if (isUpdateKeyState == 1) 138 | keyerControl |= tmpKeyerControl; 139 | 140 | return tmpKeyerControl; 141 | } 142 | 143 | /***************************************************************************** 144 | // New logic, by RON 145 | // modified by KD8CEC 146 | ******************************************************************************/ 147 | void cwKeyer(void){ 148 | lastPaddle = 0; 149 | bool continue_loop = true; 150 | unsigned tmpKeyControl = 0; 151 | 152 | if( Iambic_Key ) { 153 | while(continue_loop) { 154 | switch (keyerState) { 155 | case IDLE: 156 | tmpKeyControl = update_PaddleLatch(0); 157 | if ( tmpKeyControl == DAH_L || tmpKeyControl == DIT_L || 158 | tmpKeyControl == (DAH_L | DIT_L) || (keyerControl & 0x03)) { 159 | update_PaddleLatch(1); 160 | keyerState = CHK_DIT; 161 | }else{ 162 | if (0 < cwTimeout && cwTimeout < millis()){ 163 | cwTimeout = 0; 164 | stopTx(); 165 | } 166 | continue_loop = false; 167 | } 168 | break; 169 | 170 | case CHK_DIT: 171 | if (keyerControl & DIT_L) { 172 | keyerControl |= DIT_PROC; 173 | ktimer = cwSpeed; 174 | keyerState = KEYED_PREP; 175 | }else{ 176 | keyerState = CHK_DAH; 177 | } 178 | break; 179 | 180 | case CHK_DAH: 181 | if (keyerControl & DAH_L) { 182 | ktimer = cwSpeed*3; 183 | keyerState = KEYED_PREP; 184 | }else{ 185 | keyerState = IDLE; 186 | } 187 | break; 188 | 189 | case KEYED_PREP: 190 | //modified KD8CEC 191 | if (!inTx){ 192 | //DelayTime Option 193 | active_delay(delayBeforeCWStartTime * 2); 194 | 195 | keyDown = 0; 196 | cwTimeout = millis() + cwDelayTime * 10; //+ CW_TIMEOUT; 197 | startTx(TX_CW); 198 | } 199 | ktimer += millis(); // set ktimer to interval end time 200 | keyerControl &= ~(DIT_L + DAH_L); // clear both paddle latch bits 201 | keyerState = KEYED; // next state 202 | 203 | cwKeydown(); 204 | break; 205 | 206 | case KEYED: 207 | if (millis() > ktimer) { // are we at end of key down ? 208 | cwKeyUp(); 209 | ktimer = millis() + cwSpeed; // inter-element time 210 | keyerState = INTER_ELEMENT; // next state 211 | }else if (keyerControl & IAMBICB) { 212 | update_PaddleLatch(1); // early paddle latch in Iambic B mode 213 | } 214 | break; 215 | 216 | case INTER_ELEMENT: 217 | // Insert time between dits/dahs 218 | update_PaddleLatch(1); // latch paddle state 219 | if (millis() > ktimer) { // are we at end of inter-space ? 220 | if (keyerControl & DIT_PROC) { // was it a dit or dah ? 221 | keyerControl &= ~(DIT_L + DIT_PROC); // clear two bits 222 | keyerState = CHK_DAH; // dit done, check for dah 223 | }else{ 224 | keyerControl &= ~(DAH_L); // clear dah latch 225 | keyerState = IDLE; // go idle 226 | } 227 | } 228 | break; 229 | } 230 | 231 | checkCAT(); 232 | } //end of while 233 | } 234 | else{ 235 | while(1){ 236 | if (update_PaddleLatch(0) == DIT_L) { 237 | // if we are here, it is only because the key is pressed 238 | if (!inTx){ 239 | //DelayTime Option 240 | active_delay(delayBeforeCWStartTime * 2); 241 | 242 | keyDown = 0; 243 | cwTimeout = millis() + cwDelayTime * 10; //+ CW_TIMEOUT; 244 | startTx(TX_CW); 245 | } 246 | cwKeydown(); 247 | 248 | while ( update_PaddleLatch(0) == DIT_L ) 249 | active_delay(1); 250 | 251 | cwKeyUp(); 252 | } 253 | else{ 254 | if (0 < cwTimeout && cwTimeout < millis()){ 255 | cwTimeout = 0; 256 | keyDown = 0; 257 | stopTx(); 258 | } 259 | //if (!cwTimeout) //removed by KD8CEC 260 | // return; 261 | // got back to the beginning of the loop, if no further activity happens on straight key 262 | // we will time out, and return out of this routine 263 | //delay(5); 264 | //delay_background(5, 3); //removed by KD8CEC 265 | //continue; //removed by KD8CEC 266 | return; //Tx stop control by Main Loop 267 | } 268 | 269 | checkCAT(); 270 | } //end of while 271 | } //end of elese 272 | } 273 | 274 | 275 | -------------------------------------------------------------------------------- /ubitx_menu.ino: -------------------------------------------------------------------------------- 1 | /** Menus 2 | * The Radio menus are accessed by tapping on the function button. 3 | * - The main loop() constantly looks for a button press and calls doMenu() when it detects 4 | * a function button press. 5 | * - As the encoder is rotated, at every 10th pulse, the next or the previous menu 6 | * item is displayed. Each menu item is controlled by it's own function. 7 | * - Eache menu function may be called to display itself 8 | * - Each of these menu routines is called with a button parameter. 9 | * - The btn flag denotes if the menu itme was clicked on or not. 10 | * - If the menu item is clicked on, then it is selected, 11 | * - If the menu item is NOT clicked on, then the menu's prompt is to be displayed 12 | */ 13 | 14 | 15 | /** A generic control to read variable values 16 | */ 17 | int getValueByKnob(int minimum, int maximum, int step_size, int initial, char* prefix, char *postfix) 18 | { 19 | int knob = 0; 20 | int knob_value; 21 | 22 | while (btnDown()) 23 | active_delay(100); 24 | 25 | active_delay(200); 26 | knob_value = initial; 27 | 28 | strcpy(b, prefix); 29 | itoa(knob_value, c, 10); 30 | strcat(b, c); 31 | strcat(b, postfix); 32 | printLine2(b); 33 | active_delay(300); 34 | 35 | while(!btnDown() && digitalRead(PTT) == HIGH){ 36 | 37 | knob = enc_read(); 38 | if (knob != 0){ 39 | if (knob_value > minimum && knob < 0) 40 | knob_value -= step_size; 41 | if (knob_value < maximum && knob > 0) 42 | knob_value += step_size; 43 | 44 | printLine2(prefix); 45 | itoa(knob_value, c, 10); 46 | strcpy(b, c); 47 | strcat(b, postfix); 48 | printLine1(b); 49 | } 50 | checkCAT(); 51 | } 52 | 53 | return knob_value; 54 | } 55 | 56 | //# Menu: 1 57 | 58 | int menuBand(int btn){ 59 | int knob = 0; 60 | int band; 61 | unsigned long offset; 62 | 63 | // band = frequency/1000000l; 64 | // offset = frequency % 1000000l; 65 | 66 | if (!btn){ 67 | printLine2("Band Select \x7E"); 68 | return; 69 | } 70 | 71 | printLine2("Band Select:"); 72 | //wait for the button menu select button to be lifted) 73 | while (btnDown()) 74 | active_delay(50); 75 | active_delay(50); 76 | ritDisable(); 77 | 78 | while(!btnDown()){ 79 | 80 | knob = enc_read(); 81 | if (knob != 0){ 82 | /* 83 | if (band > 3 && knob < 0) 84 | band--; 85 | if (band < 30 && knob > 0) 86 | band++; 87 | if (band > 10) 88 | isUSB = true; 89 | else 90 | isUSB = false; 91 | setFrequency(((unsigned long)band * 1000000l) + offset); */ 92 | if (knob < 0 && frequency > 3000000l) 93 | setFrequency(frequency - 200000l); 94 | if (knob > 0 && frequency < 30000000l) 95 | setFrequency(frequency + 200000l); 96 | if (frequency > 10000000l) 97 | isUSB = true; 98 | else 99 | isUSB = false; 100 | updateDisplay(); 101 | } 102 | checkCAT(); 103 | active_delay(20); 104 | } 105 | 106 | while(btnDown()) 107 | active_delay(50); 108 | active_delay(50); 109 | 110 | printLine2(""); 111 | updateDisplay(); 112 | menuOn = 0; 113 | } 114 | 115 | // Menu #2 116 | void menuRitToggle(int btn){ 117 | if (!btn){ 118 | if (ritOn == 1) 119 | printLine2("RIT On \x7E Off"); 120 | else 121 | printLine2("RIT Off \x7E On"); 122 | } 123 | else { 124 | if (ritOn == 0){ 125 | //enable RIT so the current frequency is used at transmit 126 | ritEnable(frequency); 127 | printLine2("RIT is On"); 128 | 129 | } 130 | else{ 131 | ritDisable(); 132 | printLine2("RIT is Off"); 133 | } 134 | menuOn = 0; 135 | active_delay(500); 136 | printLine2(""); 137 | updateDisplay(); 138 | } 139 | } 140 | 141 | 142 | //Menu #3 143 | void menuVfoToggle(int btn){ 144 | 145 | if (!btn){ 146 | if (vfoActive == VFO_A) 147 | printLine2("VFO A \x7E B"); 148 | else 149 | printLine2("VFO B \x7E A"); 150 | } 151 | else { 152 | if (vfoActive == VFO_B){ 153 | vfoB = frequency; 154 | isUsbVfoB = isUSB; 155 | EEPROM.put(VFO_B, frequency); 156 | if (isUsbVfoB) 157 | EEPROM.put(VFO_B_MODE, VFO_MODE_USB); 158 | else 159 | EEPROM.put(VFO_B_MODE, VFO_MODE_LSB); 160 | 161 | vfoActive = VFO_A; 162 | // printLine2("Selected VFO A "); 163 | frequency = vfoA; 164 | isUSB = isUsbVfoA; 165 | } 166 | else { 167 | vfoA = frequency; 168 | isUsbVfoA = isUSB; 169 | EEPROM.put(VFO_A, frequency); 170 | if (isUsbVfoA) 171 | EEPROM.put(VFO_A_MODE, VFO_MODE_USB); 172 | else 173 | EEPROM.put(VFO_A_MODE, VFO_MODE_LSB); 174 | 175 | vfoActive = VFO_B; 176 | // printLine2("Selected VFO B "); 177 | frequency = vfoB; 178 | isUSB = isUsbVfoB; 179 | } 180 | 181 | ritDisable(); 182 | setFrequency(frequency); 183 | updateDisplay(); 184 | printLine2(""); 185 | //exit the menu 186 | menuOn = 0; 187 | } 188 | } 189 | 190 | // Menu #4 191 | void menuSidebandToggle(int btn){ 192 | if (!btn){ 193 | if (isUSB == true) 194 | printLine2("USB \x7E LSB"); 195 | else 196 | printLine2("LSB \x7E USB"); 197 | } 198 | else { 199 | if (isUSB == true){ 200 | isUSB = false; 201 | printLine2("LSB Selected"); 202 | active_delay(500); 203 | printLine2(""); 204 | } 205 | else { 206 | isUSB = true; 207 | printLine2("USB Selected"); 208 | active_delay(500); 209 | printLine2(""); 210 | } 211 | //Added by KD8CEC 212 | if (vfoActive == VFO_B){ 213 | isUsbVfoB = isUSB; 214 | } 215 | else { 216 | isUsbVfoB = isUSB; 217 | } 218 | updateDisplay(); 219 | menuOn = 0; 220 | } 221 | } 222 | 223 | //Split communication using VFOA and VFOB by KD8CEC 224 | //Menu #5 225 | void menuSplitToggle(int btn){ 226 | if (!btn){ 227 | if (splitOn == 0) 228 | printLine2("Split Off \x7E On"); 229 | else 230 | printLine2("Split On \x7E Off"); 231 | } 232 | else { 233 | if (splitOn == 1){ 234 | splitOn = 0; 235 | printLine2("Split ON"); 236 | } 237 | else { 238 | splitOn = 1; 239 | if (ritOn == 1) 240 | ritOn = 0; 241 | printLine2("Split Off"); 242 | } 243 | active_delay(500); 244 | printLine2(""); 245 | updateDisplay(); 246 | menuOn = 0; 247 | } 248 | } 249 | 250 | int menuCWSpeed(int btn){ 251 | int knob = 0; 252 | int wpm; 253 | 254 | wpm = 1200/cwSpeed; 255 | 256 | if (!btn){ 257 | strcpy(b, "CW: "); 258 | itoa(wpm,c, 10); 259 | strcat(b, c); 260 | strcat(b, " WPM \x7E"); 261 | printLine2(b); 262 | return; 263 | } 264 | 265 | /* 266 | printLine1("Press FN to Set"); 267 | strcpy(b, "5:CW>"); 268 | itoa(wpm,c, 10); 269 | strcat(b, c); 270 | strcat(b, " WPM"); 271 | printLine2(b); 272 | active_delay(300); 273 | 274 | while(!btnDown() && digitalRead(PTT) == HIGH){ 275 | 276 | knob = enc_read(); 277 | if (knob != 0){ 278 | if (wpm > 3 && knob < 0) 279 | wpm--; 280 | if (wpm < 50 && knob > 0) 281 | wpm++; 282 | 283 | strcpy(b, "5:CW>"); 284 | itoa(wpm,c, 10); 285 | strcat(b, c); 286 | strcat(b, " WPM"); 287 | printLine2(b); 288 | } 289 | //abort if this button is down 290 | if (btnDown()) 291 | //re-enable the clock1 and clock 2 292 | break; 293 | checkCAT(); 294 | } 295 | */ 296 | wpm = getValueByKnob(1, 100, 1, wpm, "CW: ", " WPM>"); 297 | 298 | printLine2("CW Speed set!"); 299 | cwSpeed = 1200/wpm; 300 | EEPROM.put(CW_SPEED, cwSpeed); 301 | active_delay(500); 302 | 303 | printLine2(""); 304 | updateDisplay(); 305 | menuOn = 0; 306 | } 307 | 308 | void menuExit(int btn){ 309 | 310 | if (!btn){ 311 | printLine2("Exit Menu \x7E"); 312 | } 313 | else{ 314 | printLine2("Exiting..."); 315 | active_delay(500); 316 | printLine2(""); 317 | updateDisplay(); 318 | menuOn = 0; 319 | } 320 | } 321 | 322 | /** 323 | * The calibration routines are not normally shown in the menu as they are rarely used 324 | * They can be enabled by choosing this menu option 325 | */ 326 | int menuSetup(int btn){ 327 | if (!btn){ 328 | if (!modeCalibrate) 329 | printLine2("Settings \x7E"); 330 | else 331 | printLine2("Settings \x7E Off"); 332 | }else { 333 | if (!modeCalibrate){ 334 | modeCalibrate = true; 335 | printLine2("Settings On"); 336 | } 337 | else { 338 | modeCalibrate = false; 339 | printLine2("Settings Off"); 340 | } 341 | 342 | while(btnDown()) 343 | active_delay(100); 344 | active_delay(500); 345 | printLine2(""); 346 | return 10; 347 | } 348 | return 0; 349 | } 350 | 351 | //this is used by the si5351 routines in the ubitx_5351 file 352 | extern int32_t calibration; 353 | extern uint32_t si5351bx_vcoa; 354 | 355 | int calibrateClock(){ 356 | int knob = 0; 357 | int32_t prev_calibration; 358 | 359 | 360 | //keep clear of any previous button press 361 | while (btnDown()) 362 | active_delay(100); 363 | active_delay(100); 364 | 365 | digitalWrite(TX_LPF_A, 0); 366 | digitalWrite(TX_LPF_B, 0); 367 | digitalWrite(TX_LPF_C, 0); 368 | 369 | prev_calibration = calibration; 370 | calibration = 0; 371 | 372 | isUSB = true; 373 | 374 | //turn off the second local oscillator and the bfo 375 | si5351_set_calibration(calibration); 376 | startTx(TX_CW); 377 | si5351bx_setfreq(2, 10000000l); 378 | 379 | strcpy(b, "#1 10 MHz cal:"); 380 | ltoa(calibration/8750, c, 10); 381 | strcat(b, c); 382 | printLine2(b); 383 | 384 | while (!btnDown()) 385 | { 386 | 387 | if (digitalRead(PTT) == LOW && !keyDown) 388 | cwKeydown(); 389 | if (digitalRead(PTT) == HIGH && keyDown) 390 | cwKeyUp(); 391 | 392 | knob = enc_read(); 393 | 394 | if (knob > 0) 395 | calibration += 875; 396 | else if (knob < 0) 397 | calibration -= 875; 398 | else 399 | continue; //don't update the frequency or the display 400 | 401 | si5351_set_calibration(calibration); 402 | si5351bx_setfreq(2, 10000000l); 403 | strcpy(b, "#1 10 MHz cal:"); 404 | ltoa(calibration/8750, c, 10); 405 | strcat(b, c); 406 | printLine2(b); 407 | } 408 | 409 | cwTimeout = 0; 410 | keyDown = 0; 411 | stopTx(); 412 | 413 | printLine2("Calibration set!"); 414 | EEPROM.put(MASTER_CAL, calibration); 415 | initOscillators(); 416 | setFrequency(frequency); 417 | updateDisplay(); 418 | 419 | while(btnDown()) 420 | active_delay(50); 421 | active_delay(100); 422 | } 423 | 424 | int menuSetupCalibration(int btn){ 425 | int knob = 0; 426 | int32_t prev_calibration; 427 | 428 | if (!btn){ 429 | printLine2("Setup:Calibrate\x7E"); 430 | return 0; 431 | } 432 | 433 | printLine1("Press PTT & tune"); 434 | printLine2("to exactly 10 MHz"); 435 | active_delay(2000); 436 | calibrateClock(); 437 | } 438 | 439 | void printCarrierFreq(unsigned long freq){ 440 | 441 | memset(c, 0, sizeof(c)); 442 | memset(b, 0, sizeof(b)); 443 | 444 | ultoa(freq, b, DEC); 445 | 446 | strncat(c, b, 2); 447 | strcat(c, "."); 448 | strncat(c, &b[2], 3); 449 | strcat(c, "."); 450 | strncat(c, &b[5], 1); 451 | printLine2(c); 452 | } 453 | 454 | void menuSetupCarrier(int btn){ 455 | int knob = 0; 456 | unsigned long prevCarrier; 457 | 458 | if (!btn){ 459 | printLine2("Setup:BFO \x7E"); 460 | return; 461 | } 462 | 463 | prevCarrier = usbCarrier; 464 | printLine1("Tune to best Signal"); 465 | printLine2("Press to confirm. "); 466 | active_delay(1000); 467 | 468 | usbCarrier = 11995000l; 469 | si5351bx_setfreq(0, usbCarrier); 470 | printCarrierFreq(usbCarrier); 471 | 472 | //disable all clock 1 and clock 2 473 | while (!btnDown()){ 474 | knob = enc_read(); 475 | 476 | if (knob > 0) 477 | usbCarrier -= 50; 478 | else if (knob < 0) 479 | usbCarrier += 50; 480 | else 481 | continue; //don't update the frequency or the display 482 | 483 | si5351bx_setfreq(0, usbCarrier); 484 | printCarrierFreq(usbCarrier); 485 | 486 | active_delay(100); 487 | } 488 | 489 | printLine2("Carrier set! "); 490 | EEPROM.put(USB_CAL, usbCarrier); 491 | active_delay(1000); 492 | 493 | si5351bx_setfreq(0, usbCarrier); 494 | setFrequency(frequency); 495 | updateDisplay(); 496 | printLine2(""); 497 | menuOn = 0; 498 | } 499 | 500 | void menuSetupCwTone(int btn){ 501 | int knob = 0; 502 | int prev_sideTone; 503 | 504 | if (!btn){ 505 | printLine2("Setup:CW Tone \x7E"); 506 | return; 507 | } 508 | 509 | prev_sideTone = sideTone; 510 | printLine1("Tune CW tone"); 511 | printLine2("PTT to confirm. "); 512 | active_delay(1000); 513 | tone(CW_TONE, sideTone); 514 | 515 | //disable all clock 1 and clock 2 516 | while (digitalRead(PTT) == HIGH && !btnDown()) 517 | { 518 | knob = enc_read(); 519 | 520 | if (knob > 0 && sideTone < 2000) 521 | sideTone += 10; 522 | else if (knob < 0 && sideTone > 100 ) 523 | sideTone -= 10; 524 | else 525 | continue; //don't update the frequency or the display 526 | 527 | tone(CW_TONE, sideTone); 528 | itoa(sideTone, b, 10); 529 | printLine2(b); 530 | 531 | checkCAT(); 532 | active_delay(20); 533 | } 534 | noTone(CW_TONE); 535 | //save the setting 536 | if (digitalRead(PTT) == LOW){ 537 | printLine2("Sidetone set! "); 538 | EEPROM.put(CW_SIDETONE, sideTone); 539 | active_delay(2000); 540 | } 541 | else 542 | sideTone = prev_sideTone; 543 | 544 | printLine2(""); 545 | updateDisplay(); 546 | menuOn = 0; 547 | } 548 | 549 | void menuSetupCwDelay(int btn){ 550 | int knob = 0; 551 | int prev_cw_delay; 552 | 553 | if (!btn){ 554 | printLine2("Setup:CW Delay \x7E"); 555 | return; 556 | } 557 | 558 | active_delay(500); 559 | prev_cw_delay = cwDelayTime; 560 | cwDelayTime = getValueByKnob(10, 1000, 50, cwDelayTime, "CW Delay>", " msec"); 561 | 562 | printLine1("CW Delay Set!"); 563 | printLine2(""); 564 | active_delay(500); 565 | updateDisplay(); 566 | menuOn = 0; 567 | } 568 | 569 | void menuSetupKeyer(int btn){ 570 | int tmp_key, knob; 571 | 572 | if (!btn){ 573 | if (!Iambic_Key) 574 | printLine2("Setup:CW(Hand)\x7E"); 575 | else if (keyerControl & IAMBICB) 576 | printLine2("Setup:CW(IambA)\x7E"); 577 | else 578 | printLine2("Setup:CW(IambB)\x7E"); 579 | return; 580 | } 581 | 582 | active_delay(500); 583 | 584 | if (!Iambic_Key) 585 | tmp_key = 0; //hand key 586 | else if (keyerControl & IAMBICB) 587 | tmp_key = 2; //Iambic B 588 | else 589 | tmp_key = 1; 590 | 591 | while (!btnDown()) 592 | { 593 | knob = enc_read(); 594 | if (knob < 0 && tmp_key > 0) 595 | tmp_key--; 596 | if (knob > 0) 597 | tmp_key++; 598 | 599 | if (tmp_key > 2) 600 | tmp_key = 0; 601 | 602 | if (tmp_key == 0) 603 | printLine1("Hand Key?"); 604 | else if (tmp_key == 1) 605 | printLine1("Iambic A?"); 606 | else if (tmp_key == 2) 607 | printLine1("Iambic B?"); 608 | } 609 | 610 | active_delay(500); 611 | if (tmp_key == 0) 612 | Iambic_Key = false; 613 | else if (tmp_key == 1){ 614 | Iambic_Key = true; 615 | keyerControl &= ~IAMBICB; 616 | } 617 | else if (tmp_key == 2){ 618 | Iambic_Key = true; 619 | keyerControl |= IAMBICB; 620 | } 621 | 622 | EEPROM.put(CW_KEY_TYPE, tmp_key); 623 | 624 | printLine1("Keyer Set!"); 625 | active_delay(600); 626 | printLine1(""); 627 | 628 | //Added KD8CEC 629 | printLine2(""); 630 | updateDisplay(); 631 | menuOn = 0; 632 | } 633 | 634 | void menuReadADC(int btn){ 635 | int adc; 636 | 637 | if (!btn){ 638 | printLine2("6:Setup>Read ADC>"); 639 | return; 640 | } 641 | delay(500); 642 | 643 | while (!btnDown()){ 644 | adc = analogRead(ANALOG_KEYER); 645 | itoa(adc, b, 10); 646 | printLine1(b); 647 | } 648 | 649 | printLine1(""); 650 | updateDisplay(); 651 | } 652 | 653 | void doMenu(){ 654 | int select=0, i,btnState; 655 | 656 | //wait for the button to be raised up 657 | while(btnDown()) 658 | active_delay(50); 659 | active_delay(50); //debounce 660 | 661 | menuOn = 2; 662 | 663 | while (menuOn){ 664 | i = enc_read(); 665 | btnState = btnDown(); 666 | 667 | if (i > 0){ 668 | if (modeCalibrate && select + i < 150) 669 | select += i; 670 | if (!modeCalibrate && select + i < 80) 671 | select += i; 672 | } 673 | if (i < 0 && select - i >= 0) 674 | select += i; //caught ya, i is already -ve here, so you add it 675 | 676 | if (select < 10) 677 | menuBand(btnState); 678 | else if (select < 20) 679 | menuRitToggle(btnState); 680 | else if (select < 30) 681 | menuVfoToggle(btnState); 682 | else if (select < 40) 683 | menuSidebandToggle(btnState); 684 | else if (select < 50) 685 | menuSplitToggle(btnState); 686 | else if (select < 60) 687 | menuCWSpeed(btnState); 688 | else if (select < 70) 689 | select += menuSetup(btnState); 690 | else if (select < 80 && !modeCalibrate) 691 | menuExit(btnState); 692 | else if (select < 90 && modeCalibrate) 693 | menuSetupCalibration(btnState); //crystal 694 | else if (select < 100 && modeCalibrate) 695 | menuSetupCarrier(btnState); //lsb 696 | else if (select < 110 && modeCalibrate) 697 | menuSetupCwTone(btnState); 698 | else if (select < 120 && modeCalibrate) 699 | menuSetupCwDelay(btnState); 700 | else if (select < 130 && modeCalibrate) 701 | menuReadADC(btnState); 702 | else if (select < 140 && modeCalibrate) 703 | menuSetupKeyer(btnState); 704 | else 705 | menuExit(btnState); 706 | } 707 | 708 | //debounce the button 709 | while(btnDown()) 710 | active_delay(50); 711 | active_delay(50); 712 | 713 | checkCAT(); 714 | } 715 | 716 | -------------------------------------------------------------------------------- /ubitx_si5351.ino: -------------------------------------------------------------------------------- 1 | // ************* SI5315 routines - tks Jerry Gaffke, KE7ER *********************** 2 | 3 | // An minimalist standalone set of Si5351 routines. 4 | // VCOA is fixed at 875mhz, VCOB not used. 5 | // The output msynth dividers are used to generate 3 independent clocks 6 | // with 1hz resolution to any frequency between 4khz and 109mhz. 7 | 8 | // Usage: 9 | // Call si5351bx_init() once at startup with no args; 10 | // Call si5351bx_setfreq(clknum, freq) each time one of the 11 | // three output CLK pins is to be updated to a new frequency. 12 | // A freq of 0 serves to shut down that output clock. 13 | 14 | // The global variable si5351bx_vcoa starts out equal to the nominal VCOA 15 | // frequency of 25mhz*35 = 875000000 Hz. To correct for 25mhz crystal errors, 16 | // the user can adjust this value. The vco frequency will not change but 17 | // the number used for the (a+b/c) output msynth calculations is affected. 18 | // Example: We call for a 5mhz signal, but it measures to be 5.001mhz. 19 | // So the actual vcoa frequency is 875mhz*5.001/5.000 = 875175000 Hz, 20 | // To correct for this error: si5351bx_vcoa=875175000; 21 | 22 | // Most users will never need to generate clocks below 500khz. 23 | // But it is possible to do so by loading a value between 0 and 7 into 24 | // the global variable si5351bx_rdiv, be sure to return it to a value of 0 25 | // before setting some other CLK output pin. The affected clock will be 26 | // divided down by a power of two defined by 2**si5351_rdiv 27 | // A value of zero gives a divide factor of 1, a value of 7 divides by 128. 28 | // This lightweight method is a reasonable compromise for a seldom used feature. 29 | 30 | 31 | #define BB0(x) ((uint8_t)x) // Bust int32 into Bytes 32 | #define BB1(x) ((uint8_t)(x>>8)) 33 | #define BB2(x) ((uint8_t)(x>>16)) 34 | 35 | #define SI5351BX_ADDR 0x60 // I2C address of Si5351 (typical) 36 | #define SI5351BX_XTALPF 2 // 1:6pf 2:8pf 3:10pf 37 | 38 | // If using 27mhz crystal, set XTAL=27000000, MSA=33. Then vco=891mhz 39 | #define SI5351BX_XTAL 25000000 // Crystal freq in Hz 40 | #define SI5351BX_MSA 35 // VCOA is at 25mhz*35 = 875mhz 41 | 42 | // User program may have reason to poke new values into these 3 RAM variables 43 | uint32_t si5351bx_vcoa = (SI5351BX_XTAL*SI5351BX_MSA); // 25mhzXtal calibrate 44 | uint8_t si5351bx_rdiv = 0; // 0-7, CLK pin sees fout/(2**rdiv) 45 | uint8_t si5351bx_drive[3] = {1, 1, 1}; // 0=2ma 1=4ma 2=6ma 3=8ma for CLK 0,1,2 46 | uint8_t si5351bx_clken = 0xFF; // Private, all CLK output drivers off 47 | int32_t calibration = 0; 48 | 49 | void i2cWrite(uint8_t reg, uint8_t val) { // write reg via i2c 50 | Wire.beginTransmission(SI5351BX_ADDR); 51 | Wire.write(reg); 52 | Wire.write(val); 53 | Wire.endTransmission(); 54 | } 55 | 56 | void i2cWriten(uint8_t reg, uint8_t *vals, uint8_t vcnt) { // write array 57 | Wire.beginTransmission(SI5351BX_ADDR); 58 | Wire.write(reg); 59 | while (vcnt--) Wire.write(*vals++); 60 | Wire.endTransmission(); 61 | } 62 | 63 | 64 | void si5351bx_init() { // Call once at power-up, start PLLA 65 | uint8_t reg; uint32_t msxp1; 66 | Wire.begin(); 67 | i2cWrite(149, 0); // SpreadSpectrum off 68 | i2cWrite(3, si5351bx_clken); // Disable all CLK output drivers 69 | i2cWrite(183, SI5351BX_XTALPF << 6); // Set 25mhz crystal load capacitance 70 | msxp1 = 128 * SI5351BX_MSA - 512; // and msxp2=0, msxp3=1, not fractional 71 | uint8_t vals[8] = {0, 1, BB2(msxp1), BB1(msxp1), BB0(msxp1), 0, 0, 0}; 72 | i2cWriten(26, vals, 8); // Write to 8 PLLA msynth regs 73 | i2cWrite(177, 0x20); // Reset PLLA (0x80 resets PLLB) 74 | // for (reg=16; reg<=23; reg++) i2cWrite(reg, 0x80); // Powerdown CLK's 75 | // i2cWrite(187, 0); // No fannout of clkin, xtal, ms0, ms4 76 | } 77 | 78 | void si5351bx_setfreq(uint8_t clknum, uint32_t fout) { // Set a CLK to fout Hz 79 | uint32_t msa, msb, msc, msxp1, msxp2, msxp3p2top; 80 | if ((fout < 500000) || (fout > 109000000)) // If clock freq out of range 81 | si5351bx_clken |= 1 << clknum; // shut down the clock 82 | else { 83 | msa = si5351bx_vcoa / fout; // Integer part of vco/fout 84 | msb = si5351bx_vcoa % fout; // Fractional part of vco/fout 85 | msc = fout; // Divide by 2 till fits in reg 86 | while (msc & 0xfff00000) { 87 | msb = msb >> 1; 88 | msc = msc >> 1; 89 | } 90 | msxp1 = (128 * msa + 128 * msb / msc - 512) | (((uint32_t)si5351bx_rdiv) << 20); 91 | msxp2 = 128 * msb - 128 * msb / msc * msc; // msxp3 == msc; 92 | msxp3p2top = (((msc & 0x0F0000) << 4) | msxp2); // 2 top nibbles 93 | uint8_t vals[8] = { BB1(msc), BB0(msc), BB2(msxp1), BB1(msxp1), 94 | BB0(msxp1), BB2(msxp3p2top), BB1(msxp2), BB0(msxp2) 95 | }; 96 | i2cWriten(42 + (clknum * 8), vals, 8); // Write to 8 msynth regs 97 | i2cWrite(16 + clknum, 0x0C | si5351bx_drive[clknum]); // use local msynth 98 | si5351bx_clken &= ~(1 << clknum); // Clear bit to enable clock 99 | } 100 | i2cWrite(3, si5351bx_clken); // Enable/disable clock 101 | } 102 | 103 | void si5351_set_calibration(int32_t cal){ 104 | si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + cal; // apply the calibration correction factor 105 | si5351bx_setfreq(0, usbCarrier); 106 | } 107 | 108 | void initOscillators(){ 109 | //initialize the SI5351 110 | si5351bx_init(); 111 | si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + calibration; // apply the calibration correction factor 112 | si5351bx_setfreq(0, usbCarrier); 113 | } 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /ubitx_ui.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The user interface of the ubitx consists of the encoder, the push-button on top of it 3 | * and the 16x2 LCD display. 4 | * The upper line of the display is constantly used to display frequency and status 5 | * of the radio. Occasionally, it is used to provide a two-line information that is 6 | * quickly cleared up. 7 | */ 8 | 9 | //returns true if the button is pressed 10 | int btnDown(){ 11 | if (digitalRead(FBUTTON) == HIGH) 12 | return 0; 13 | else 14 | return 1; 15 | } 16 | 17 | /** 18 | * Meter (not used in this build for anything) 19 | * the meter is drawn using special characters. Each character is composed of 5 x 8 matrix. 20 | * The s_meter array holds the definition of the these characters. 21 | * each line of the array is is one character such that 5 bits of every byte 22 | * makes up one line of pixels of the that character (only 5 bits are used) 23 | * The current reading of the meter is assembled in the string called meter 24 | */ 25 | 26 | 27 | char meter[17]; 28 | 29 | const byte PROGMEM s_meter_bitmap[] = { 30 | B00000,B00000,B00000,B00000,B00000,B00100,B00100,B11011, 31 | B10000,B10000,B10000,B10000,B10100,B10100,B10100,B11011, 32 | B01000,B01000,B01000,B01000,B01100,B01100,B01100,B11011, 33 | B00100,B00100,B00100,B00100,B00100,B00100,B00100,B11011, 34 | B00010,B00010,B00010,B00010,B00110,B00110,B00110,B11011, 35 | B00001,B00001,B00001,B00001,B00101,B00101,B00101,B11011, 36 | B10000,B11000,B11100,B11110,B11100,B11000,B10000,B00000, 37 | B00001,B00011,B00111,B01111,B00111,B00011,B00001,B00000 38 | }; 39 | 40 | 41 | 42 | // initializes the custom characters 43 | // we start from char 1 as char 0 terminates the string! 44 | void initMeter(){ 45 | lcd.createChar(1, s_meter_bitmap); 46 | lcd.createChar(2, s_meter_bitmap + 8); 47 | lcd.createChar(3, s_meter_bitmap + 16); 48 | lcd.createChar(4, s_meter_bitmap + 24); 49 | lcd.createChar(5, s_meter_bitmap + 32); 50 | lcd.createChar(6, s_meter_bitmap + 40); 51 | lcd.createChar(0, s_meter_bitmap + 48); 52 | lcd.createChar(7, s_meter_bitmap + 56); 53 | } 54 | 55 | 56 | /** 57 | * The meter is drawn with special characters. 58 | * character 1 is used to simple draw the blocks of the scale of the meter 59 | * characters 2 to 6 are used to draw the needle in positions 1 to within the block 60 | * This displays a meter from 0 to 100, -1 displays nothing 61 | */ 62 | 63 | void drawMeter(int8_t needle){ 64 | int16_t best, i, s; 65 | 66 | if (needle < 0) 67 | return; 68 | 69 | s = (needle * 4)/10; 70 | for (i = 0; i < 8; i++){ 71 | if (s >= 5) 72 | meter[i] = 1; 73 | else if (s >= 0) 74 | meter[i] = 2 + s; 75 | else 76 | meter[i] = 1; 77 | s = s - 5; 78 | } 79 | if (needle >= 40) 80 | meter[i-1] = 6; 81 | meter[i] = 0; 82 | } 83 | 84 | // The generic routine to display one line on the LCD 85 | void printLine(char linenmbr, char *c) { 86 | if (strcmp(c, printBuff[linenmbr])) { // only refresh the display when there was a change 87 | lcd.setCursor(0, linenmbr); // place the cursor at the beginning of the selected line 88 | lcd.print(c); 89 | strcpy(printBuff[linenmbr], c); 90 | 91 | for (byte i = strlen(c); i < 16; i++) { // add white spaces until the end of the 16 characters line is reached 92 | lcd.print(' '); 93 | } 94 | } 95 | } 96 | 97 | 98 | // short cut to print to the first line 99 | void printLine1(char *c){ 100 | printLine(1,c); 101 | } 102 | // short cut to print to the first line 103 | void printLine2(char *c){ 104 | printLine(0,c); 105 | } 106 | 107 | // this builds up the top line of the display with frequency and mode 108 | void updateDisplay() { 109 | // tks Jack Purdum W8TEE 110 | // replaced fsprint commmands by str commands for code size reduction 111 | 112 | memset(c, 0, sizeof(c)); 113 | memset(b, 0, sizeof(b)); 114 | 115 | ultoa(frequency, b, DEC); 116 | 117 | if (inTx){ 118 | if (cwTimeout > 0) 119 | strcpy(c, " CW:"); 120 | else 121 | strcpy(c, " TX:"); 122 | } 123 | else { 124 | if (ritOn) 125 | strcpy(c, "RIT "); 126 | else { 127 | if (isUSB) 128 | strcpy(c, "USB "); 129 | else 130 | strcpy(c, "LSB "); 131 | } 132 | if (vfoActive == VFO_A) // VFO A is active 133 | strcat(c, "A:"); 134 | else 135 | strcat(c, "B:"); 136 | } 137 | 138 | 139 | 140 | //one mhz digit if less than 10 M, two digits if more 141 | if (frequency < 10000000l){ 142 | c[6] = ' '; 143 | c[7] = b[0]; 144 | strcat(c, "."); 145 | strncat(c, &b[1], 3); 146 | strcat(c, "."); 147 | strncat(c, &b[4], 3); 148 | } 149 | else { 150 | strncat(c, b, 2); 151 | strcat(c, "."); 152 | strncat(c, &b[2], 3); 153 | strcat(c, "."); 154 | strncat(c, &b[5], 3); 155 | } 156 | 157 | if (inTx) 158 | strcat(c, " TX"); 159 | printLine(1, c); 160 | 161 | /* 162 | //now, the second line 163 | memset(c, 0, sizeof(c)); 164 | memset(b, 0, sizeof(b)); 165 | 166 | if (inTx) 167 | strcat(c, "TX "); 168 | else if (ritOn) 169 | strcpy(c, "RIT"); 170 | 171 | strcpy(c, " \xff"); 172 | drawMeter(meter_reading); 173 | strcat(c, meter); 174 | strcat(c, "\xff"); 175 | printLine2(c);*/ 176 | } 177 | 178 | int enc_prev_state = 3; 179 | 180 | /** 181 | * The A7 And A6 are purely analog lines on the Arduino Nano 182 | * These need to be pulled up externally using two 10 K resistors 183 | * 184 | * There are excellent pages on the Internet about how these encoders work 185 | * and how they should be used. We have elected to use the simplest way 186 | * to use these encoders without the complexity of interrupts etc to 187 | * keep it understandable. 188 | * 189 | * The enc_state returns a two-bit number such that each bit reflects the current 190 | * value of each of the two phases of the encoder 191 | * 192 | * The enc_read returns the number of net pulses counted over 50 msecs. 193 | * If the puluses are -ve, they were anti-clockwise, if they are +ve, the 194 | * were in the clockwise directions. Higher the pulses, greater the speed 195 | * at which the enccoder was spun 196 | */ 197 | 198 | byte enc_state (void) { 199 | return (analogRead(ENC_A) > 500 ? 1 : 0) + (analogRead(ENC_B) > 500 ? 2: 0); 200 | } 201 | 202 | int enc_read(void) { 203 | int result = 0; 204 | byte newState; 205 | int enc_speed = 0; 206 | 207 | long stop_by = millis() + 50; 208 | 209 | while (millis() < stop_by) { // check if the previous state was stable 210 | newState = enc_state(); // Get current state 211 | 212 | if (newState != enc_prev_state) 213 | delay (1); 214 | 215 | if (enc_state() != newState || newState == enc_prev_state) 216 | continue; 217 | //these transitions point to the encoder being rotated anti-clockwise 218 | if ((enc_prev_state == 0 && newState == 2) || 219 | (enc_prev_state == 2 && newState == 3) || 220 | (enc_prev_state == 3 && newState == 1) || 221 | (enc_prev_state == 1 && newState == 0)){ 222 | result--; 223 | } 224 | //these transitions point o the enccoder being rotated clockwise 225 | if ((enc_prev_state == 0 && newState == 1) || 226 | (enc_prev_state == 1 && newState == 3) || 227 | (enc_prev_state == 3 && newState == 2) || 228 | (enc_prev_state == 2 && newState == 0)){ 229 | result++; 230 | } 231 | enc_prev_state = newState; // Record state for next pulse interpretation 232 | enc_speed++; 233 | active_delay(1); 234 | } 235 | return(result); 236 | } 237 | 238 | 239 | -------------------------------------------------------------------------------- /ubitx_v4.3_code.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This source file is under General Public License version 3. 3 | * 4 | * This verision uses a built-in Si5351 library 5 | * Most source code are meant to be understood by the compilers and the computers. 6 | * Code that has to be hackable needs to be well understood and properly documented. 7 | * Donald Knuth coined the term Literate Programming to indicate code that is written be 8 | * easily read and understood. 9 | * 10 | * The Raduino is a small board that includes the Arduin Nano, a 16x2 LCD display and 11 | * an Si5351a frequency synthesizer. This board is manufactured by Paradigm Ecomm Pvt Ltd 12 | * 13 | * To learn more about Arduino you may visit www.arduino.cc. 14 | * 15 | * The Arduino works by starts executing the code in a function called setup() and then it 16 | * repeatedly keeps calling loop() forever. All the initialization code is kept in setup() 17 | * and code to continuously sense the tuning knob, the function button, transmit/receive, 18 | * etc is all in the loop() function. If you wish to study the code top down, then scroll 19 | * to the bottom of this file and read your way up. 20 | * 21 | * Below are the libraries to be included for building the Raduino 22 | * The EEPROM library is used to store settings like the frequency memory, caliberation data, 23 | * callsign etc . 24 | * 25 | * The main chip which generates upto three oscillators of various frequencies in the 26 | * Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet 27 | * from www.silabs.com although, strictly speaking it is not a requirment to understand this code. 28 | * Instead, you can look up the Si5351 library written by xxx, yyy. You can download and 29 | * install it from www.url.com to complile this file. 30 | * The Wire.h library is used to talk to the Si5351 and we also declare an instance of 31 | * Si5351 object to control the clocks. 32 | */ 33 | #include 34 | #include 35 | 36 | /** 37 | The main chip which generates upto three oscillators of various frequencies in the 38 | Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet 39 | from www.silabs.com although, strictly speaking it is not a requirment to understand this code. 40 | 41 | We no longer use the standard SI5351 library because of its huge overhead due to many unused 42 | features consuming a lot of program space. Instead of depending on an external library we now use 43 | Jerry Gaffke's, KE7ER, lightweight standalone mimimalist "si5351bx" routines (see further down the 44 | code). Here are some defines and declarations used by Jerry's routines: 45 | */ 46 | 47 | 48 | /** 49 | * We need to carefully pick assignment of pin for various purposes. 50 | * There are two sets of completely programmable pins on the Raduino. 51 | * First, on the top of the board, in line with the LCD connector is an 8-pin connector 52 | * that is largely meant for analog inputs and front-panel control. It has a regulated 5v output, 53 | * ground and six pins. Each of these six pins can be individually programmed 54 | * either as an analog input, a digital input or a digital output. 55 | * The pins are assigned as follows (left to right, display facing you): 56 | * Pin 1 (Violet), A7, SPARE 57 | * Pin 2 (Blue), A6, KEYER (DATA) 58 | * Pin 3 (Green), +5v 59 | * Pin 4 (Yellow), Gnd 60 | * Pin 5 (Orange), A3, PTT 61 | * Pin 6 (Red), A2, F BUTTON 62 | * Pin 7 (Brown), A1, ENC B 63 | * Pin 8 (Black), A0, ENC A 64 | *Note: A5, A4 are wired to the Si5351 as I2C interface 65 | * * 66 | * Though, this can be assigned anyway, for this application of the Arduino, we will make the following 67 | * assignment 68 | * A2 will connect to the PTT line, which is the usually a part of the mic connector 69 | * A3 is connected to a push button that can momentarily ground this line. This will be used for RIT/Bandswitching, etc. 70 | * A6 is to implement a keyer, it is reserved and not yet implemented 71 | * A7 is connected to a center pin of good quality 100K or 10K linear potentiometer with the two other ends connected to 72 | * ground and +5v lines available on the connector. This implments the tuning mechanism 73 | */ 74 | 75 | #define ENC_A (A0) 76 | #define ENC_B (A1) 77 | #define FBUTTON (A2) 78 | #define PTT (A3) 79 | #define ANALOG_KEYER (A6) 80 | #define ANALOG_SPARE (A7) 81 | 82 | /** 83 | * The Raduino board is the size of a standard 16x2 LCD panel. It has three connectors: 84 | * 85 | * First, is an 8 pin connector that provides +5v, GND and six analog input pins that can also be 86 | * configured to be used as digital input or output pins. These are referred to as A0,A1,A2, 87 | * A3,A6 and A7 pins. The A4 and A5 pins are missing from this connector as they are used to 88 | * talk to the Si5351 over I2C protocol. 89 | * 90 | * Second is a 16 pin LCD connector. This connector is meant specifically for the standard 16x2 91 | * LCD display in 4 bit mode. The 4 bit mode requires 4 data lines and two control lines to work: 92 | * Lines used are : RESET, ENABLE, D4, D5, D6, D7 93 | * We include the library and declare the configuration of the LCD panel too 94 | */ 95 | 96 | #include 97 | LiquidCrystal lcd(8,9,10,11,12,13); 98 | 99 | /** 100 | * The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory. 101 | * We have to be very careful with variables that are declared inside the functions as they are 102 | * created in a memory region called the stack. The stack has just a few bytes of space on the Arduino 103 | * if you declare large strings inside functions, they can easily exceed the capacity of the stack 104 | * and mess up your programs. 105 | * We circumvent this by declaring a few global buffers as kitchen counters where we can 106 | * slice and dice our strings. These strings are mostly used to control the display or handle 107 | * the input and output from the USB port. We must keep a count of the bytes used while reading 108 | * the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable. 109 | */ 110 | char c[30], b[30]; 111 | char printBuff[2][31]; //mirrors what is showing on the two lines of the display 112 | int count = 0; //to generally count ticks, loops, etc 113 | 114 | /** 115 | * The second set of 16 pins on the Raduino's bottom connector are have the three clock outputs and the digital lines to control the rig. 116 | * This assignment is as follows : 117 | * Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 118 | * GND +5V CLK0 GND GND CLK1 GND GND CLK2 GND D2 D3 D4 D5 D6 D7 119 | * These too are flexible with what you may do with them, for the Raduino, we use them to : 120 | * - TX_RX line : Switches between Transmit and Receive after sensing the PTT or the morse keyer 121 | * - CW_KEY line : turns on the carrier for CW 122 | */ 123 | 124 | #define TX_RX (7) 125 | #define CW_TONE (6) 126 | #define TX_LPF_A (5) 127 | #define TX_LPF_B (4) 128 | #define TX_LPF_C (3) 129 | #define CW_KEY (2) 130 | 131 | /** 132 | * These are the indices where these user changable settinngs are stored in the EEPROM 133 | */ 134 | #define MASTER_CAL 0 135 | #define LSB_CAL 4 136 | #define USB_CAL 8 137 | #define SIDE_TONE 12 138 | //these are ids of the vfos as well as their offset into the eeprom storage, don't change these 'magic' values 139 | #define VFO_A 16 140 | #define VFO_B 20 141 | #define CW_SIDETONE 24 142 | #define CW_SPEED 28 143 | 144 | //These are defines for the new features back-ported from KD8CEC's software 145 | //these start from beyond 256 as Ian, KD8CEC has kept the first 256 bytes free for the base version 146 | #define VFO_A_MODE 256 // 2: LSB, 3: USB 147 | #define VFO_B_MODE 257 148 | 149 | //values that are stroed for the VFO modes 150 | #define VFO_MODE_LSB 2 151 | #define VFO_MODE_USB 3 152 | 153 | // handkey, iambic a, iambic b : 0,1,2f 154 | #define CW_KEY_TYPE 358 155 | 156 | /** 157 | * The uBITX is an upconnversion transceiver. The first IF is at 45 MHz. 158 | * The first IF frequency is not exactly at 45 Mhz but about 5 khz lower, 159 | * this shift is due to the loading on the 45 Mhz crystal filter by the matching 160 | * L-network used on it's either sides. 161 | * The first oscillator works between 48 Mhz and 75 MHz. The signal is subtracted 162 | * from the first oscillator to arriive at 45 Mhz IF. Thus, it is inverted : LSB becomes USB 163 | * and USB becomes LSB. 164 | * The second IF of 12 Mhz has a ladder crystal filter. If a second oscillator is used at 165 | * 57 Mhz, the signal is subtracted FROM the oscillator, inverting a second time, and arrives 166 | * at the 12 Mhz ladder filter thus doouble inversion, keeps the sidebands as they originally were. 167 | * If the second oscillator is at 33 Mhz, the oscilaltor is subtracated from the signal, 168 | * thus keeping the signal's sidebands inverted. The USB will become LSB. 169 | * We use this technique to switch sidebands. This is to avoid placing the lsbCarrier close to 170 | * 12 MHz where its fifth harmonic beats with the arduino's 16 Mhz oscillator's fourth harmonic 171 | */ 172 | 173 | // the second oscillator should ideally be at 57 MHz, however, the crystal filter's center frequency 174 | // is shifted down a little due to the loading from the impedance matching L-networks on either sides 175 | #define SECOND_OSC_USB (56995000l) 176 | #define SECOND_OSC_LSB (32995000l) 177 | 178 | 179 | //these are the two default USB and LSB frequencies. The best frequencies depend upon your individual taste and filter shape 180 | #define INIT_USB_FREQ (11996500l) 181 | // limits the tuning and working range of the ubitx between 3 MHz and 30 MHz 182 | #define LOWEST_FREQ (100000l) 183 | #define HIGHEST_FREQ (30000000l) 184 | 185 | //we directly generate the CW by programmin the Si5351 to the cw tx frequency, hence, both are different modes 186 | //these are the parameter passed to startTx 187 | #define TX_SSB 0 188 | #define TX_CW 1 189 | 190 | char ritOn = 0; 191 | char vfoActive = VFO_A; 192 | int8_t meter_reading = 0; // a -1 on meter makes it invisible 193 | unsigned long vfoA=7150000L, vfoB=14200000L, sideTone=800, usbCarrier; 194 | char isUsbVfoA=0, isUsbVfoB=1; 195 | unsigned long frequency, ritRxFrequency, ritTxFrequency; //frequency is the current frequency on the dial 196 | unsigned long firstIF = 45000000L; 197 | 198 | //these are variables that control the keyer behaviour 199 | int cwSpeed = 100; //this is actuall the dot period in milliseconds 200 | extern int32_t calibration; 201 | byte cwDelayTime = 60; 202 | bool Iambic_Key = true; 203 | #define IAMBICB 0x10 // 0 for Iambic A, 1 for Iambic B 204 | unsigned char keyerControl = IAMBICB; 205 | 206 | 207 | /** 208 | * Raduino needs to keep track of current state of the transceiver. These are a few variables that do it 209 | */ 210 | boolean txCAT = false; //turned on if the transmitting due to a CAT command 211 | char inTx = 0; //it is set to 1 if in transmit mode (whatever the reason : cw, ptt or cat) 212 | char splitOn = 0; //working split, uses VFO B as the transmit frequency, (NOT IMPLEMENTED YET) 213 | char keyDown = 0; //in cw mode, denotes the carrier is being transmitted 214 | char isUSB = 0; //upper sideband was selected, this is reset to the default for the 215 | //frequency when it crosses the frequency border of 10 MHz 216 | byte menuOn = 0; //set to 1 when the menu is being displayed, if a menu item sets it to zero, the menu is exited 217 | unsigned long cwTimeout = 0; //milliseconds to go before the cw transmit line is released and the radio goes back to rx mode 218 | unsigned long dbgCount = 0; //not used now 219 | unsigned char txFilter = 0; //which of the four transmit filters are in use 220 | boolean modeCalibrate = false;//this mode of menus shows extended menus to calibrate the oscillators and choose the proper 221 | //beat frequency 222 | 223 | /** 224 | * Below are the basic functions that control the uBitx. Understanding the functions before 225 | * you start hacking around 226 | */ 227 | 228 | /** 229 | * Our own delay. During any delay, the raduino should still be processing a few times. 230 | */ 231 | 232 | void active_delay(int delay_by){ 233 | unsigned long timeStart = millis(); 234 | 235 | while (millis() - timeStart <= delay_by) { 236 | //Background Work 237 | checkCAT(); 238 | } 239 | } 240 | 241 | /** 242 | * Select the properly tx harmonic filters 243 | * The four harmonic filters use only three relays 244 | * the four LPFs cover 30-21 Mhz, 18 - 14 Mhz, 7-10 MHz and 3.5 to 5 Mhz 245 | * Briefly, it works like this, 246 | * - When KT1 is OFF, the 'off' position routes the PA output through the 30 MHz LPF 247 | * - When KT1 is ON, it routes the PA output to KT2. Which is why you will see that 248 | * the KT1 is on for the three other cases. 249 | * - When the KT1 is ON and KT2 is off, the off position of KT2 routes the PA output 250 | * to 18 MHz LPF (That also works for 14 Mhz) 251 | * - When KT1 is On, KT2 is On, it routes the PA output to KT3 252 | * - KT3, when switched on selects the 7-10 Mhz filter 253 | * - KT3 when switched off selects the 3.5-5 Mhz filter 254 | * See the circuit to understand this 255 | */ 256 | 257 | void setTXFilters(unsigned long freq){ 258 | 259 | if (freq > 21000000L){ // the default filter is with 35 MHz cut-off 260 | digitalWrite(TX_LPF_A, 0); 261 | digitalWrite(TX_LPF_B, 0); 262 | digitalWrite(TX_LPF_C, 0); 263 | } 264 | else if (freq >= 14000000L){ //thrown the KT1 relay on, the 30 MHz LPF is bypassed and the 14-18 MHz LPF is allowd to go through 265 | digitalWrite(TX_LPF_A, 1); 266 | digitalWrite(TX_LPF_B, 0); 267 | digitalWrite(TX_LPF_C, 0); 268 | } 269 | else if (freq > 7000000L){ 270 | digitalWrite(TX_LPF_A, 1); 271 | digitalWrite(TX_LPF_B, 1); 272 | digitalWrite(TX_LPF_C, 0); 273 | } 274 | else { 275 | digitalWrite(TX_LPF_A, 1); 276 | digitalWrite(TX_LPF_B, 1); 277 | digitalWrite(TX_LPF_C, 1); 278 | } 279 | } 280 | 281 | /** 282 | * This is the most frequently called function that configures the 283 | * radio to a particular frequeny, sideband and sets up the transmit filters 284 | * 285 | * The transmit filter relays are powered up only during the tx so they dont 286 | * draw any current during rx. 287 | * 288 | * The carrier oscillator of the detector/modulator is permanently fixed at 289 | * uppper sideband. The sideband selection is done by placing the second oscillator 290 | * either 12 Mhz below or above the 45 Mhz signal thereby inverting the sidebands 291 | * through mixing of the second local oscillator. 292 | */ 293 | 294 | void setFrequency(unsigned long f){ 295 | uint64_t osc_f, firstOscillator, secondOscillator; 296 | 297 | setTXFilters(f); 298 | 299 | if (isUSB){ 300 | si5351bx_setfreq(2, firstIF + f); 301 | si5351bx_setfreq(1, firstIF + usbCarrier); 302 | } 303 | else{ 304 | si5351bx_setfreq(2, firstIF + f); 305 | si5351bx_setfreq(1, firstIF - usbCarrier); 306 | } 307 | 308 | frequency = f; 309 | } 310 | 311 | /** 312 | * startTx is called by the PTT, cw keyer and CAT protocol to 313 | * put the uBitx in tx mode. It takes care of rit settings, sideband settings 314 | * Note: In cw mode, doesnt key the radio, only puts it in tx mode 315 | * CW offest is calculated as lower than the operating frequency when in LSB mode, and vice versa in USB mode 316 | */ 317 | 318 | void startTx(byte txMode){ 319 | unsigned long tx_freq = 0; 320 | 321 | digitalWrite(TX_RX, 1); 322 | inTx = 1; 323 | 324 | if (ritOn){ 325 | //save the current as the rx frequency 326 | ritRxFrequency = frequency; 327 | setFrequency(ritTxFrequency); 328 | } 329 | else 330 | { 331 | if (splitOn == 1) { 332 | if (vfoActive == VFO_B) { 333 | vfoActive = VFO_A; 334 | isUSB = isUsbVfoA; 335 | frequency = vfoA; 336 | } 337 | else if (vfoActive == VFO_A){ 338 | vfoActive = VFO_B; 339 | frequency = vfoB; 340 | isUSB = isUsbVfoB; 341 | } 342 | } 343 | setFrequency(frequency); 344 | } 345 | 346 | if (txMode == TX_CW){ 347 | //turn off the second local oscillator and the bfo 348 | si5351bx_setfreq(0, 0); 349 | si5351bx_setfreq(1, 0); 350 | 351 | //shif the first oscillator to the tx frequency directly 352 | //the key up and key down will toggle the carrier unbalancing 353 | //the exact cw frequency is the tuned frequency + sidetone 354 | if (isUSB) 355 | si5351bx_setfreq(2, frequency + sideTone); 356 | else 357 | si5351bx_setfreq(2, frequency - sideTone); 358 | } 359 | updateDisplay(); 360 | } 361 | 362 | void stopTx(){ 363 | inTx = 0; 364 | 365 | digitalWrite(TX_RX, 0); //turn off the tx 366 | si5351bx_setfreq(0, usbCarrier); //set back the cardrier oscillator anyway, cw tx switches it off 367 | 368 | if (ritOn) 369 | setFrequency(ritRxFrequency); 370 | else{ 371 | if (splitOn == 1) { 372 | //vfo Change 373 | if (vfoActive == VFO_B){ 374 | vfoActive = VFO_A; 375 | frequency = vfoA; 376 | isUSB = isUsbVfoA; 377 | } 378 | else if (vfoActive == VFO_A){ 379 | vfoActive = VFO_B; 380 | frequency = vfoB; 381 | isUSB = isUsbVfoB; 382 | } 383 | } 384 | setFrequency(frequency); 385 | } 386 | updateDisplay(); 387 | } 388 | 389 | /** 390 | * ritEnable is called with a frequency parameter that determines 391 | * what the tx frequency will be 392 | */ 393 | void ritEnable(unsigned long f){ 394 | ritOn = 1; 395 | //save the non-rit frequency back into the VFO memory 396 | //as RIT is a temporary shift, this is not saved to EEPROM 397 | ritTxFrequency = f; 398 | } 399 | 400 | // this is called by the RIT menu routine 401 | void ritDisable(){ 402 | if (ritOn){ 403 | ritOn = 0; 404 | setFrequency(ritTxFrequency); 405 | updateDisplay(); 406 | } 407 | } 408 | 409 | /** 410 | * Basic User Interface Routines. These check the front panel for any activity 411 | */ 412 | 413 | /** 414 | * The PTT is checked only if we are not already in a cw transmit session 415 | * If the PTT is pressed, we shift to the ritbase if the rit was on 416 | * flip the T/R line to T and update the display to denote transmission 417 | */ 418 | 419 | void checkPTT(){ 420 | //we don't check for ptt when transmitting cw 421 | if (cwTimeout > 0) 422 | return; 423 | 424 | if (digitalRead(PTT) == 0 && inTx == 0){ 425 | startTx(TX_SSB); 426 | active_delay(50); //debounce the PTT 427 | } 428 | 429 | if (digitalRead(PTT) == 1 && inTx == 1) 430 | stopTx(); 431 | } 432 | 433 | void checkButton(){ 434 | int i, t1, t2, knob, new_knob; 435 | 436 | //only if the button is pressed 437 | if (!btnDown()) 438 | return; 439 | active_delay(50); 440 | if (!btnDown()) //debounce 441 | return; 442 | 443 | doMenu(); 444 | //wait for the button to go up again 445 | while(btnDown()) 446 | active_delay(10); 447 | active_delay(50);//debounce 448 | } 449 | 450 | 451 | /** 452 | * The tuning jumps by 50 Hz on each step when you tune slowly 453 | * As you spin the encoder faster, the jump size also increases 454 | * This way, you can quickly move to another band by just spinning the 455 | * tuning knob 456 | */ 457 | 458 | 459 | void doTuning(){ 460 | int s; 461 | unsigned long prev_freq; 462 | 463 | s = enc_read(); 464 | if (s != 0){ 465 | prev_freq = frequency; 466 | 467 | if (s > 4) 468 | frequency += 10000l; 469 | else if (s > 2) 470 | frequency += 500; 471 | else if (s > 0) 472 | frequency += 50l; 473 | else if (s > -2) 474 | frequency -= 50l; 475 | else if (s > -4) 476 | frequency -= 500l; 477 | else 478 | frequency -= 10000l; 479 | 480 | if (prev_freq < 10000000l && frequency > 10000000l) 481 | isUSB = true; 482 | 483 | if (prev_freq > 10000000l && frequency < 10000000l) 484 | isUSB = false; 485 | 486 | setFrequency(frequency); 487 | updateDisplay(); 488 | } 489 | } 490 | 491 | /** 492 | * RIT only steps back and forth by 100 hz at a time 493 | */ 494 | void doRIT(){ 495 | unsigned long newFreq; 496 | 497 | int knob = enc_read(); 498 | unsigned long old_freq = frequency; 499 | 500 | if (knob < 0) 501 | frequency -= 100l; 502 | else if (knob > 0) 503 | frequency += 100; 504 | 505 | if (old_freq != frequency){ 506 | setFrequency(frequency); 507 | updateDisplay(); 508 | } 509 | } 510 | 511 | /** 512 | * The settings are read from EEPROM. The first time around, the values may not be 513 | * present or out of range, in this case, some intelligent defaults are copied into the 514 | * variables. 515 | */ 516 | void initSettings(){ 517 | byte x; 518 | //read the settings from the eeprom and restore them 519 | //if the readings are off, then set defaults 520 | EEPROM.get(MASTER_CAL, calibration); 521 | EEPROM.get(USB_CAL, usbCarrier); 522 | EEPROM.get(VFO_A, vfoA); 523 | EEPROM.get(VFO_B, vfoB); 524 | EEPROM.get(CW_SIDETONE, sideTone); 525 | EEPROM.get(CW_SPEED, cwSpeed); 526 | 527 | 528 | if (usbCarrier > 12000000l || usbCarrier < 11990000l) 529 | usbCarrier = 11997000l; 530 | if (vfoA > 35000000l || 3500000l > vfoA) 531 | vfoA = 7150000l; 532 | if (vfoB > 35000000l || 3500000l > vfoB) 533 | vfoB = 14150000l; 534 | if (sideTone < 100 || 2000 < sideTone) 535 | sideTone = 800; 536 | if (cwSpeed < 10 || 1000 < cwSpeed) 537 | cwSpeed = 100; 538 | 539 | /* 540 | * The VFO modes are read in as either 2 (USB) or 3(LSB), 0, the default 541 | * is taken as 'uninitialized 542 | */ 543 | 544 | EEPROM.get(VFO_A_MODE, x); 545 | 546 | switch(x){ 547 | case VFO_MODE_USB: 548 | isUsbVfoA = 1; 549 | break; 550 | case VFO_MODE_LSB: 551 | isUsbVfoA = 0; 552 | break; 553 | default: 554 | if (vfoA > 10000000l) 555 | isUsbVfoA = 1; 556 | else 557 | isUsbVfoA = 0; 558 | } 559 | 560 | EEPROM.get(VFO_B_MODE, x); 561 | switch(x){ 562 | case VFO_MODE_USB: 563 | isUsbVfoB = 1; 564 | break; 565 | case VFO_MODE_LSB: 566 | isUsbVfoB = 0; 567 | break; 568 | default: 569 | if (vfoA > 10000000l) 570 | isUsbVfoB = 1; 571 | else 572 | isUsbVfoB = 0; 573 | } 574 | 575 | //set the current mode 576 | isUSB = isUsbVfoA; 577 | 578 | /* 579 | * The keyer type splits into two variables 580 | */ 581 | EEPROM.get(CW_KEY_TYPE, x); 582 | 583 | if (x == 0) 584 | Iambic_Key = false; 585 | else if (x == 1){ 586 | Iambic_Key = true; 587 | keyerControl &= ~IAMBICB; 588 | } 589 | else if (x == 2){ 590 | Iambic_Key = true; 591 | keyerControl |= IAMBICB; 592 | } 593 | 594 | } 595 | 596 | void initPorts(){ 597 | 598 | analogReference(DEFAULT); 599 | 600 | //?? 601 | pinMode(ENC_A, INPUT_PULLUP); 602 | pinMode(ENC_B, INPUT_PULLUP); 603 | pinMode(FBUTTON, INPUT_PULLUP); 604 | 605 | //configure the function button to use the external pull-up 606 | // pinMode(FBUTTON, INPUT); 607 | // digitalWrite(FBUTTON, HIGH); 608 | 609 | pinMode(PTT, INPUT_PULLUP); 610 | pinMode(ANALOG_KEYER, INPUT_PULLUP); 611 | 612 | pinMode(CW_TONE, OUTPUT); 613 | digitalWrite(CW_TONE, 0); 614 | 615 | pinMode(TX_RX,OUTPUT); 616 | digitalWrite(TX_RX, 0); 617 | 618 | pinMode(TX_LPF_A, OUTPUT); 619 | pinMode(TX_LPF_B, OUTPUT); 620 | pinMode(TX_LPF_C, OUTPUT); 621 | digitalWrite(TX_LPF_A, 0); 622 | digitalWrite(TX_LPF_B, 0); 623 | digitalWrite(TX_LPF_C, 0); 624 | 625 | pinMode(CW_KEY, OUTPUT); 626 | digitalWrite(CW_KEY, 0); 627 | } 628 | 629 | void setup() 630 | { 631 | Serial.begin(38400); 632 | Serial.flush(); 633 | lcd.begin(16, 2); 634 | 635 | //we print this line so this shows up even if the raduino 636 | //crashes later in the code 637 | printLine2("uBITX v4.3"); 638 | //active_delay(500); 639 | 640 | // initMeter(); //not used in this build 641 | initSettings(); 642 | initPorts(); 643 | initOscillators(); 644 | 645 | frequency = vfoA; 646 | setFrequency(vfoA); 647 | updateDisplay(); 648 | 649 | if (btnDown()) 650 | factory_alignment(); 651 | } 652 | 653 | 654 | /** 655 | * The loop checks for keydown, ptt, function button and tuning. 656 | */ 657 | 658 | byte flasher = 0; 659 | void loop(){ 660 | 661 | cwKeyer(); 662 | if (!txCAT) 663 | checkPTT(); 664 | checkButton(); 665 | 666 | //tune only when not tranmsitting 667 | if (!inTx){ 668 | if (ritOn) 669 | doRIT(); 670 | else 671 | doTuning(); 672 | } 673 | 674 | //we check CAT after the encoder as it might put the radio into TX 675 | checkCAT(); 676 | } 677 | --------------------------------------------------------------------------------