// This is Hilltopper 20 file _4SQRP-_released_code_V2.0.ino // LM: Modified to support single line specification of BAND-specific constants // Also, LCD (Hilltopper J6) and selected CAT commands (Hilltopper J5) // LCD is always enabled // Modified to support CAT Z-commands for displaying step and Morse speed via TFT // and for setting STEP via TFT-touch. December 2019. #define BAND 40 // **Required** Supported values are 20, 30, 40 #define CAT // Comment this line to disable CAT // https://www.arduinolibraries.info/libraries/etherkit-si5351 // Etherkit Si53551 version 2.0.1, 12/15/2016 // Current version not backward-compatible // The following multi-line comment is copied from the Hilltopper-30_V2.0.ino sketch /* * Declarations at the following line numbers must be * changed for each new band: * * 64, 65 : Default frequencies * 189: Freq calibration correction factor * 196: Call to band assignment * 332-334: Band-edge limits */ /* Default frequencies: * 20 meters: Opfreq1 = 1406000000; Opfreq2 = 1403000000; * 30 meters: Opfreq1 = 1010600000; Opfreq2 = 1011000000; * 40 meters: Opfreq1 = 703000000; Opfreq2 = 711000000; */ /* Si5351 correction factor: * 20 meters: FSCALE=(CORR*(-1422L)); * 30 meters: FSCALE=(CORR*(-1976L)); * 40 meters: FSCALE=(CORR*(-2845L)); // Based on comment in setup() */ /* Call to band assignment: * BAND20() * BAND30() * BAND40() * * Band limits - 20 meters: * low_band_limit = 1400000000; * high_band_limit = 1435000000; * - 30 meters: * low_band_limit = 1010000000; * high_band_limit = 1015000000; * - 40 meters: * low_band_limit = 700000000; * high_band_limit = 730000000; */ #include #include #include // LM: Frequency display, etc. /* Controls- functional description: 'Function' -Hold on power-up: allows selection of paddle reverse and Mode A or B. -Brief tap: outputs frequency readout -Hold (>0.5 sec): starts Tune mode. Tune mode aborted by tap on either paddle, or by 5-second timeout. 'Tune' pushbutton: -Hold on power-up: Frequency initialized to 14030 kHz. otherwise, the power-up default is 14060 kHz. - Brief tap: Toggles tuning steps between 20 and 100 Hz - Hold (>0.5 sec): Turns RIT ON (boo-beep) or OFF (bee-boop) Rotary encoder is Incremental (quadrature) type. 24 PPR. direct Tx freq output on CLK1 of Si5351 board Rx freq with IF offset on CLK0 of Si5351 board Pin function: D0- (ICSP)Serial interface D1 - (ICSP)Serial interface D2 - QSK D3 - ENC A (INT1) D4 - TX EN D5 - SIDETONE D6 - MUTE D7 - (unused) D8 - FUNCTION P/B D9 - ENC PB D10 - ENC B D11 - CAL D12 - SPARE D13 - SPARE A0 - SPARE A1 - POT A2 - Paddle Connection - RING A3 - Paddle Connection - TIP A4 - I2C interface - SDA A5 - I2C interface - SCK */ #include "Wire.h" Si5351 si5351; volatile int ditTime,dashTime,elementTime; // No. milliseconds per dit bool actFlag = 0; int PORflag =1;// flag allowing one-time execution. djb // LM: Collect all band-specific declarations in one place here - // Change default startup frequencies and band limits to constants #if (BAND == 20) const long OPFREQ1 = 1406000000; // default startup freq. djb const long OPFREQ2 = 1403000000; // alternate startup freq. djb const long LOW_BAND_LIMIT = 1400000000; // low limit, band tuning const long HIGH_BAND_LIMIT = 1435000000; // high limit, band tuning const long BANDCORR = -1422L; // This is a new constant (LM) #elif (BAND == 30) const long OPFREQ1 = 1010600000; // default startup freq. djb const long OPFREQ2 = 1011000000; // alternate startup freq. djb const long LOW_BAND_LIMIT = 1010000000; // low limit, band tuning const long HIGH_BAND_LIMIT = 1015000000; // high limit, band tuning const long BANDCORR = -1976L; // This is a new constant (LM) #elif (BAND == 40) const long OPFREQ1 = 703000000; // default startup freq. djb const long OPFREQ2 = 711000000; // alternate startup freq. djb const long LOW_BAND_LIMIT = 700000000; // low limit, band tuning const long HIGH_BAND_LIMIT = 730000000; // high limit, band tuning const long BANDCORR = -2845L; // This is a new constant (LM) #else #error "Required BAND definition not found!" #endif int duration; bool reverse; byte ritflag = 0; byte RITflag = 0; byte SKmode= 0; byte code = 0x01; byte mask = 1; bool activekey = 0; bool MODE; // 0= MODE A; 1= MODE B byte EncoderFlag = 0; int wpm, x; bool TIMEOUT = 0; //key-down timeout flag volatile bool DOTpending,DASHpending =0; byte character; int lookup; byte Morsetable[] = {15, 62, 60, 56, 48, 32, 33, 35, 39, 47, // characters 0-9 1,1,1,1,1,1,1, // characters (hex 3A-40) 6,17,21,9,2,20,11,16, // characters A-H 4,30,13,18,7,5,15,22, // characters I-P 27,10,8,3,12,24,14,25,29,19}; //characters Q-Z byte LSB; // least signif. bit of Morse character. const int PB = 8; const int ENCPB = 9; const int STN = 5; const int QSK = 2; const int Txen = 4; const int Mute = 6; const int CAL = 11; const int RING = A2; const int TIP = A3; const int analogInPin = A1; int POTVAL = 0; int SPSCALE = 10; // active EEPROM locations const int CORRLSBLOC = 0; //LS BYTE of SI5351 correction factor const int CORRMSBLOC = 1; //MS BYTE " " const int STATESLOC =2; byte STATES = 0; // bit 1= MODE, BIT 0 = paddle reverse bool TUNEmode = 0; long int Fstep10 = 1000 ;// 10 Hz step long int Fstep20 = 2000; // added 20 Hz step 12/17 djb long int Fstep100 = 10000; long int Fstep200 = 20000; // added 200 Hz djb long int Fstep500 = 50000; // LM: 500 Hz long int Fstep1K = 100000; //encoder const int enclk = 3; //define encoder pin A const int endata = 10; //define encoder pin B volatile int c = HIGH; // init state of pin A volatile int d = HIGH; // make data val low //frequency tuning int stepSize; // tuning rate pointer long int stepK; // temp storage of freq tuning step int state; // switch state register long int VFOfreq; long int PORfreq; unsigned long time1 = 0; unsigned long time0 = 0; long int OPfreq; long int RITtemp; long int RITresult; int REG = 0; int temp; long int IFtemp; int CORR; //********************************************** long int IFfreq = 518515000; // 5185.15 kHz. This is the // filter passband center- not the BFO frequency. //*********************************************** // registers for limit testing // LM: Moved to band-specific conditional compile (above) /* long int low_band_limit; //low limit, band tuning long int high_band_limit; //high limit, band tuning */ // LM: LCD stuff - const boolean lcdPresent = true; // Enable/disable LCD code const int ROWS = 2; // If 20x4 change ROWS to 4 const int COLS = 16; // If 20x4 change COLS to 20 LiquidCrystal_I2C lcd(0x27, COLS, ROWS); // Instantiate const unsigned long ONESEC = 1000L; const unsigned long TWOSEC = 2000L; long int lastOPfreq = 0; // Detect frequency change int lastWPM = 0; // Detect speed change // LM: CAT stuff - #ifdef CAT const byte EOC = 59; // Semi-colon command terminator char RxCmd[80]; char TxCmd[80]; int rIndex = 0; int xIndex = 0; long lastDelta = 0; boolean lastRIT = false; #endif void setup() { #ifdef CAT Serial.begin(9600); #endif pinMode(A0, INPUT_PULLUP); pinMode(A1, INPUT); pinMode(A2, INPUT_PULLUP); pinMode(A3, INPUT_PULLUP); pinMode(A4, INPUT_PULLUP); pinMode(A5, INPUT_PULLUP); pinMode(0, INPUT_PULLUP); pinMode(1, OUTPUT); pinMode(2, OUTPUT); pinMode(3, INPUT_PULLUP); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, INPUT_PULLUP); pinMode(8, INPUT_PULLUP); pinMode(9, INPUT_PULLUP); pinMode(10, INPUT_PULLUP); pinMode(11, INPUT_PULLUP); pinMode(12, INPUT_PULLUP); pinMode(13, INPUT_PULLUP); digitalWrite(Txen, LOW); digitalWrite(QSK, LOW); digitalWrite(Mute, LOW); if (lcdPresent) { lcd.init(); displayLCD(" W A 4 E F S"," Hilltopper-40"); // Substitute personalized text delay(TWOSEC); // Extend duration of splash screen } /* The lines below retrieve the stored value of CORR and scale * it to the correction factor needed to compensate for * inaccuracy in the Si5351 crystal clock. '-1422L' scales * the correction from '2000' 20 Hz@10.000 MHz)to 20Hz@14.060MHz. * (The Si5351 freq correction in library 'Si5351.h' uses 10MHz. */ long FSCALE; READCORR(); //Band-specific constant BANDCORR is defined in conditional compile near top of sketch FSCALE=(CORR*(BANDCORR)); si5351.init(SI5351_CRYSTAL_LOAD_6PF, 0, 0); //set PLL xtal load si5351.set_correction(FSCALE); stepK = Fstep100 ; stepSize = 3; //LM: No need for separate BANDxx() calls - Band limits are set in conditional compile BANDxx(); // This is now an alias for PLLwrite() that is duplicated below. PLLwrite(); attachInterrupt(digitalPinToInterrupt(3),ENCODER,FALLING); #ifdef CAT catListen(); #endif } void loop(){ if (PORflag){ state = digitalRead(ENCPB); if (!state) {OPfreq = OPFREQ2;} else {OPfreq = OPFREQ1;} STATES = EEPROM.read(STATESLOC);// retrieve Paddle Swap and MODE reverse = bitRead(STATES,0); MODE = bitRead(STATES,1); if (!digitalRead(PB)) {SETSTATES();} // If grounded on power-up, Paddle Swap, choose Morse A or B if (!digitalRead(RING)){SKmode = 1;} // check for grounded 'ring' at power-up // VFOfreq = OPfreq ; if (!digitalRead(CAL)) {CALMODE ();} PORflag = 0; displayfreq(); IFtemp=IFfreq; PLLwrite(); } // short press triggers morse readout // long press starts tune mode long duration=0; if (!digitalRead(PB)) { delay(20); // bounce filter if (!digitalRead(PB)) { time0 = millis(); while(!digitalRead(PB)) {} duration = millis() - time0; if ((duration <500) & (!TIMEOUT)) {displayfreq(); duration=0;} // calls Audio readout if ((duration <500) & (TIMEOUT)) {TIMEOUT=0;duration=0;} // clears Straight-Key timeout. if (duration >=1000) {TIMEOUT=0;TUNEmode=1;TxON();} // turns Tx ON for 5 sec for tuning up/autotuner sequence. }} if ((!digitalRead(TIP)) & (SKmode) & (!TIMEOUT)) {TxON(); } // entry to Straight Key operation if (!SKmode){ //Entry to Iambic Keyer if (!digitalRead(TIP)){keyer();} if (!digitalRead(RING)){keyer();} } // test encoder switch // short press changes tuning step size // long press toggles RIT on and off duration=0; state = digitalRead(ENCPB); if (!digitalRead(ENCPB)) { delay(20); // bounce filter if (!digitalRead(ENCPB)) { time0 = millis(); while(!digitalRead(ENCPB)) {} time1 = millis(); duration = time1 - time0; if (duration >500) { if (!RITflag){boobeep ();RITflag=1;} else{beeboop(); RITflag =0; IFtemp=IFfreq; PLLwrite();}} else {nextFstep();} duration = 0; }} if (EncoderFlag == 1) { Tune_UP(); } if (EncoderFlag == 2) { Tune_DWN(); } // LM: If LCD is present, update display when frequency or Morse speed changes if (lcdPresent) { ANspeed(); // Speed control is not otherwise sensed unless button pressed if ((OPfreq != lastOPfreq) || (wpm != lastWPM) || // Frequency or speed or (RITflag && (lastDelta != (IFtemp - IFfreq))) || // IF change (RIT) or ((RITflag != lastRIT) && !RITflag)) { // RIT has been exited displayLCDfrequency(); } } #ifdef CAT catListen(); if (OPfreq != lastOPfreq) { cmdFA(); writeAnswer(); } if (lastDelta != (IFtemp - IFfreq)) { cmdFB(); writeAnswer(); } #endif lastOPfreq = OPfreq; lastDelta = IFtemp - IFfreq; lastRIT = RITflag; // Detect change in RIT status, specifically when RIT is exited. lastWPM = wpm; } void Tune_UP() { EncoderFlag = 0; FREQ_increment(); } void Tune_DWN() { EncoderFlag = 0; FREQ_decrement(); } void FREQ_increment() { if (!RITflag) {IFtemp=IFfreq; OPfreq = OPfreq + stepK;} else {IFtemp = IFtemp + stepK;} if (OPfreq > HIGH_BAND_LIMIT) { FREQ_decrement(); //band tuning limits } PLLwrite(); } void FREQ_decrement() { if (!RITflag) {IFtemp=IFfreq; OPfreq = OPfreq - stepK; } else {IFtemp = IFtemp - stepK;} if (OPfreq < LOW_BAND_LIMIT) { FREQ_increment(); } PLLwrite(); } void nextFstep () { ++ stepSize ; if (stepSize == 5) { //(stepSize = 3); (stepSize = 2); // LM: Substitute 3-position switch } if (lcdPresent) displayLCDfrequency(); // LM: Display (frequency and) selected step switch (stepSize) { // case 1: // stepK = Fstep10K; // break; case 2: //stepK = Fstep1K; stepK = Fstep500; // LM: Substitute 500 Hz as largest step dit(); dit(); dit(); // LM: Announce the current step value break; case 3: stepK = Fstep100; dit(); dit(); // ditto break; case 4: stepK = Fstep20; dit(); // ditto break; } } // LM: low band limit and high band limit have values assigned in conditional compile (near top of sketch) void BANDxx() { // Alias for PLLwrite() PLLwrite(); } // Audio frequency annunicator djb void displayfreq() { ANspeed (); loadWPM(wpm); Wire.beginTransmission(0x60); //turn off Rx clock output Wire.write(REG + 3); Wire.write(0xff); Wire.endTransmission(); long int freq2 = OPfreq / 100; // divide the freq to remove fractional Hz long int tenk; long int tenskhz; int oneskhz; unsigned int digit3, digit4; tenk = freq2 % 1000000; // break down the decades to insert decimal points on the display int digit = tenk / 100000; if (digit != 0) { morseOut(digit); } tenskhz = tenk % 100000; digit = tenskhz / 10000; morseOut(digit); oneskhz = tenk % 10000; digit3 = oneskhz / 1000; digit = digit3; morseOut(digit); dit(); dah(); dit(); delay(dashTime); //send 'R' for decimal point int hundshz = tenk % 1000; digit4 = hundshz / 100; digit = digit4; morseOut(digit); delay(5); Wire.beginTransmission(0x60); //turn on Rx clock output Wire.write(REG + 3); Wire.write(0xfe); Wire.endTransmission(); } void keyer() { { digitalWrite(Mute, LOW); digitalWrite(QSK, LOW); Wire.beginTransmission(0x60); //turn off Rx clock output Wire.write(REG + 3); Wire.write(0xff); Wire.endTransmission(); delay(5); VFOfreq = OPfreq ; si5351.set_freq(VFOfreq, SI5351_CLK1); Wire.beginTransmission(0x60); //turn on Tx clock output Wire.write(REG + 3); Wire.write(0xfd); Wire.endTransmission(); } inkeyer(); {Wire.beginTransmission(0x60); //turn off Tx clock output Wire.write(REG + 3); Wire.write(0xfe); Wire.endTransmission(); digitalWrite(Mute, LOW); delay(5); //wait for output to decay Wire.beginTransmission(0x60); //turn off Tx clock output Wire.write(REG + 3); Wire.write(0xfe); Wire.endTransmission(); digitalWrite(Mute, LOW); // unmute Rx audio } } void inkeyer() { ANspeed (); loadWPM(wpm); //START PROBATION/ SEMI-BREAK-IN TIMEOUT long time0 =millis(); while (millis()-(time0) < 100) { if (digitalRead(RING) == LOW) { dash();time0 =millis(); DASHpending=0;} if (digitalRead(TIP) == LOW) {dot(); time0=millis();DOTpending=0;} delay(1); if (DASHpending==1){dash();time0 =millis();DASHpending=0;} if (DOTpending==1){dot();time0 =millis();DOTpending=0;} } }// END inkeyer // paddle reverse timing below // NOTE: reversed=0. The unprogrammed EEPROM state is 'all 1s' // Allows startup in the paddle configuration for right-handed users. void dash(){ if (reverse){elementTime = dashTime; element(); DASHpending=0;} else {elementTime = ditTime; element(); DASHpending=0;} } byte dot () { if (reverse){elementTime = ditTime; element();DOTpending=0;} else {elementTime = dashTime; element(); DOTpending=0;} } byte element() { ANspeed(); loadWPM(wpm); tone(STN, 800); digitalWrite(Txen, HIGH);digitalWrite(QSK, HIGH); digitalWrite(Mute, HIGH); long time1 = millis(); while ((millis()-time1)<= elementTime) { if (!digitalRead(TIP)) {DOTpending=1;} if (!digitalRead(RING)){DASHpending=1;} } digitalWrite(Txen, LOW); delay(5);digitalWrite(QSK, LOW); // allow time for envelope to decay digitalWrite(Mute, LOW); noTone(STN); //delay(ditTime); time1 = millis(); while ((millis()-time1)<=(ditTime-5)) { if (!digitalRead(TIP)) {DOTpending=1;} if (!digitalRead(RING)){DASHpending=1;} } if (!MODE) {DOTpending=0;DASHpending=0;} // if MODE A,} // clear early key closures } void loadWPM(int wpm) { ditTime = 1200 / wpm; dashTime = ditTime * 3; } //void TIMER1_SERVICE_ROUTINE() void ENCODER() { c = digitalRead(enclk); //read clock pin d = digitalRead(endata); // read data pin if ( d ) { EncoderFlag = 1; } else { EncoderFlag = 2; } } void TxON() { VFOfreq = OPfreq ; si5351.set_freq(VFOfreq, SI5351_CLK1); Wire.beginTransmission(0x60); //turn on Tx clock output Wire.write(REG + 3); Wire.write(0xfd); Wire.endTransmission(); digitalWrite(Mute, HIGH); digitalWrite(QSK, HIGH); digitalWrite(Txen, HIGH); tone(STN, 800); //turn on sidetone TIMEOUT=0; long time0 = millis(); if (SKmode&!TUNEmode){long time0 = millis(); //Straight Key 5 second timing if ((!digitalRead(TIP))) { while (((millis()- time0) <= 5000)& (digitalRead(TIP)==0)) {} //allow up to 5 seconds key-down if ((millis()- time0) >= 4999){TIMEOUT=1;} }} if (TUNEmode) //TUNE mode 5 second timing while (((millis()- time0) <= 5000) & digitalRead(TIP) & (digitalRead(RING)|SKmode)) // if TUNE is called while in SK mode, must ignore (always grounded)- ring. {TIMEOUT=1;} noTone(STN); //turn off sidetone digitalWrite(Txen, LOW); //turn off PA delay(5); //wait for output to decay Wire.beginTransmission(0x60); //turn off Tx clock output Wire.write(REG + 3); Wire.write(0xfe); Wire.endTransmission(); if (TIMEOUT & SKmode &!TUNEmode){while (digitalRead(PB)) {} } else {} TUNEmode=0; TIMEOUT=0; digitalWrite(QSK, 0); digitalWrite(Mute, LOW); // unmute Rx audio } void PLLwrite() { VFOfreq = OPfreq + IFtemp ; si5351.set_freq(VFOfreq, SI5351_CLK0); Wire.beginTransmission(0x60); //turn off the Tx output, which gets turned on when the chip is updated Wire.write(REG + 3); Wire.write(0xfe); Wire.endTransmission(); } int ANspeed () { POTVAL = analogRead(analogInPin); // expected range: 0 to 947 (10-bit result) int SPSCALE = POTVAL / 35 ; // scale to 0--> 27 wpm = SPSCALE + 8 ; // assign wpm, set minimum speed to 8 wpm, max is 35 wpm return wpm; } void morseOut(byte digit) { byte character = Morsetable[digit]; //byte character =byte(lookup); for (x=7; (x>0); x--){ LSB = character; LSB= (LSB & 0x01); // '&' is destructive if (LSB == 0x01 ) {dah();} else {dit();} character = character >> 1; //shift right if (character ==0x01) {break;}}// end-of-character reached delay(dashTime); } void dah() { ANspeed(); loadWPM(wpm); tone(STN, 800); delay(dashTime); noTone(STN); delay(ditTime); } void dit() { ANspeed(); loadWPM(wpm); tone(STN, 800); delay(ditTime); noTone(5); delay(ditTime); } void boobeep () {tone (STN,500,150);delay(150);tone (STN,900,150);} void beeboop () {tone (STN,900,150);delay (150);tone (STN,500,150);} byte SETMODE() { bitWrite(STATES,0,reverse); bitWrite(STATES,1,MODE); EEPROM.write(STATESLOC, STATES); } void CALMODE() { VFOfreq = OPfreq ; si5351.set_freq(VFOfreq, SI5351_CLK1); Wire.beginTransmission(0x60); //turn on Tx clock output Wire.write(REG + 3); Wire.write(0xfd); Wire.endTransmission(); digitalWrite(Mute, HIGH); digitalWrite(QSK, HIGH); digitalWrite(Txen, HIGH); tone(STN, 800); //turn on sidetone for (int x=0; x<1000; x++) {delay(5); // check for encoder activity // every 5 mS for 5 seconds if (EncoderFlag == 1) //tune up 20 Hz {OPfreq= OPfreq +2000; ++CORR; VFOfreq = OPfreq ; si5351.set_freq(VFOfreq, SI5351_CLK1); } if (EncoderFlag == 2) //tune down 20 Hz {OPfreq= OPfreq - 2000; --CORR; VFOfreq = OPfreq ; si5351.set_freq(VFOfreq, SI5351_CLK1); } EncoderFlag=0; } // Store CORR in EEPROM int val = CORR; EEPROM.write(CORRLSBLOC,val); // store LS byte in EEPROM loc. 0 val=CORR>>8; //shift upper byte into lower byte EEPROM.write(CORRMSBLOC,val); // store MS byte in EEPROM loc. 1 // ------------------------------------------------------------------ noTone(5); //turn off sidetone digitalWrite(Txen, LOW); //turn off PA delay(5); //wait for output to decay Wire.beginTransmission(0x60); //turn off Tx clock output Wire.write(REG + 3); Wire.write(0xfe); Wire.endTransmission(); digitalWrite(QSK, LOW); delay(500);digitalWrite(Mute, LOW); // unmute Rx audio } int READCORR() // Retrieves CORR from EEPROM { int val=EEPROM.read(CORRMSBLOC); CORR= (val<<8); // load MS byte of CORR val=EEPROM.read(CORRLSBLOC); CORR = CORR +val; // Add LS Byte return CORR; } void SETSTATES() { MODE=0;activekey=0; // set default mode to A character = 0b00001010;LTROut(); // letter 'R' character = 0b01001100;LTROut(); // '?' inputJonny5(); if (activekey) {reverse=!reverse;activekey=0;} // toggle state of reverse character = 0b00010001;LTROut(); // 'B' character = 0b01001100;LTROut(); // '?' inputJonny5(); if (activekey){MODE = 1; activekey = 0;}// sets mode B SETMODE(); if (MODE == 0){character = 0b00000110; LTROut();}// 'A'} if (MODE == 1) {character = 0b00010001; LTROut();}// 'B' delay(500); } byte LTROut() { for (x=7; (x>0); x--){ LSB = character; LSB= (LSB & 0x01); // '&' is destructive if (LSB == 0x01 ) {dah();} else {dit();} character = character >> 1; //shift right if (character ==0x01) {break;}}// end-of-character reached delay(dashTime); } void inputJonny5() { activekey = 0; time0 = millis(); while ((millis()-time0)<=2000) {if ((digitalRead(TIP)==0)||((!digitalRead(RING))|SKmode)){activekey = 1;} // disable RING input on SKmode- it's always grounded } } // LM: Experimental add-ons - void myClear() { if (lcdPresent) { lcd.clear(); lcd.setCursor(0,0); } } void displayLCD(String line1, String line2) { if (lcdPresent) { int row = ROWS/2 - 1; // Same as if..else myClear(); lcd.setCursor(0, row); lcd.print(line1); lcd.setCursor(0, row+1); lcd.print(line2); lcd.backlight(); } } void displayLCDfrequency() { if (lcdPresent) { String s = String(OPfreq); //String m = s.substring(0,2); // MHz //String k = s.substring(2,5); // KHz //String h = s.substring(5,8); // Hz String sStep; switch(stepSize) { case 2 : sStep = "500"; break; case 3 : sStep = "100"; break; case 4 : sStep = " 20"; break; default : sStep = ""; } // String sFreq = m + "." + k + "." + h + " s " + sStep; String sFreq; if (BAND == 40) // One digit shorter than 30 or 20 meter frequency sFreq = " " + s.substring(0,1) + "." + s.substring(1,4) + "." + s.substring(4,7) + " s " + sStep; else sFreq = s.substring(0,2) + "." + s.substring(2,5) + "." + s.substring(5,8) + " s " + sStep; String sWPM = " " + String(wpm) + " wpm"; // The usual display if (RITflag) { // If RIT mode, squeeze additional data into line 2 sWPM = ritText() + " Hz " + String(wpm) + " wpm"; } /* Alternative formatting options for RIT delta display 1234567890123456 ---------------- 12 wpm RIT +1234 12 wpm +1234 Hz 12 wpm R+1234 Hz RIT +1234 wpm 12 ==> +1234 Hz 12 wpm <== [Selected] R+1234 Hz 12 wpm */ displayLCD(sFreq,sWPM); delay(5); } } // LM: Late mod to display RIT offset on LCD (independent of CAT) - 2/28/2019 String ritText() { // Called from displayLCDfrequency() // Compute replacement for line 2 of LCD when RIT is active int delta = (IFtemp - IFfreq) / 100; if (delta < 0) delta = -delta; String sDelta = String(delta); while (sDelta.length() < 4) sDelta = "0" + sDelta; if (IFtemp < IFfreq) sDelta = "-" + sDelta; else sDelta = "+" + sDelta; return sDelta; } #ifdef CAT // Utilities void clearRxCmd() { RxCmd[0] = (byte) 0; rIndex = 0; } void clearTxCmd() { TxCmd[0] = (byte) 0; xIndex = 0; } void clearCAT() { clearRxCmd(); clearTxCmd(); } void catListen() { byte data_byte; while (Serial.available()) { data_byte = Serial.read(); if (data_byte > 31) { RxCmd[rIndex++] = (char) data_byte; if (data_byte == EOC) { processRxCmd(); if ((byte)TxCmd[0] != 0) // Not empty writeAnswer(); clearRxCmd(); } } } } void writeAnswer() { byte data_byte; xIndex = 0; while (true) { data_byte = (byte) TxCmd[xIndex++]; if (data_byte == 0) break; Serial.write(data_byte); if (data_byte == EOC) break; } clearTxCmd(); } // From HRD to Hilltoper - void processRxCmd() { String rxFreq; // More convenient than char[] for incoming if (RxCmd[0] == 'F' or RxCmd[0] == 'f') { if (RxCmd[1] == 'A' or RxCmd[1] == 'a') { if (RxCmd[2] == (char)EOC) { cmdFA(); } else { // Set VFO-A rxFreq = ""; for (int i=5; i<13; i++) rxFreq.concat(RxCmd[i]); // 8-digit frequency in Hz (string) setFreq(0,rxFreq); } } else if (RxCmd[1] == 'B' or RxCmd[1] == 'b') { if (RxCmd[2] == (char)EOC) { cmdFB(); } else { rxFreq = ""; for (int i=5; i<13; i++) rxFreq.concat(RxCmd[i]); // 8-digit frequency in Hz (string) setFreq(1,rxFreq); } } else clearRxCmd(); // Unimplemented command beginning 'F' return; ; } else if (RxCmd[0] == 'I' or RxCmd[0] == 'i') { if ((RxCmd[1] == 'F' or RxCmd[1] == 'f') and (RxCmd[2] == (char)EOC)) { cmdIF(); } else clearRxCmd(); // Unimplemented command beginning 'I' } else if (RxCmd[0] == 'L' or RxCmd[0] == 'l') { if ((RxCmd[1] == 'K' or RxCmd[1] == 'k') and (RxCmd[2] == (char)EOC)) { cmdLK(); } else clearRxCmd(); // Unimplemented command beginning 'L' } else if (RxCmd[0] == 'Z' or RxCmd[0] == 'z') { if ((RxCmd[1] == 'S' or RxCmd[1] == 's') and (RxCmd[2] == (char)EOC)) { cmdZS(); } else if ((RxCmd[1] == 'M' or RxCmd[1] == 'm') and (RxCmd[2] == (char)EOC)) { cmdZM(); } else clearRxCmd(); // Unimplemented command beginning 'Z' } // End Z-command else clearRxCmd(); // Unimplemented command } void setFreq(int vfo, String hz) { hz.concat("00"); // 10-digits long hrdFreq = hz.toInt(); if (isnan((float)hrdFreq)) return; // For now if (hrdFreq < LOW_BAND_LIMIT) hrdFreq = LOW_BAND_LIMIT; else if (hrdFreq > HIGH_BAND_LIMIT) hrdFreq = HIGH_BAND_LIMIT; if (vfo == 0) { // VFO-A (main) if (OPfreq != hrdFreq) { OPfreq = hrdFreq; if (RITflag) { RITflag = false; // HRD frequency adjustment resets RIT beeboop(); // Audio indicator of RIT off } IFtemp = IFfreq; // RIT only adjustable from Hilltopper PLLwrite(); // Frequency change would be detected in loop() ... displayLCDfrequency(); // Instead update display here lastOPfreq = OPfreq; // and prevent loop() update. (There's a reason.) } } else if (vfo == 1) { // VFO-B = (VFO-A +/- RIT) long ritDelta = hrdFreq - OPfreq; if (ritDelta == 0) { // VFO-B = VFO-A implies RIT off (but not really) RITflag = false; beeboop(); // Audio indicator of RIT off IFtemp = IFfreq; PLLwrite(); } else { if (!RITflag) { RITflag = true; boobeep(); // Audio warning RIT is on - Adjusting VFO-B again // will turn will reenable RIT } IFtemp = IFfreq + ritDelta; PLLwrite(); } displayLCDfrequency(); // Update line 2 to include RIT data or exit status lastRIT = RITflag; } } // From Hilltopper to HRD - void cmdFA() { for (int i=0; i < 14; i++) { if (BAND == 40) // Frequency is one digit less than for 30 or 20 meter bands TxCmd[i] = ("FA0000" + String(OPfreq).substring(0,7) + (char)EOC).charAt(i); else TxCmd[i] = ("FA000" + String(OPfreq).substring(0,8) + (char)EOC).charAt(i); } } void cmdFB() { /* long bFreq = OPfreq; if (RITflag == 1) bFreq = bFreq - IFfreq + IFtemp; */ long bFreq = OPfreq -IFfreq + IFtemp; for (int i=0; i < 14; i++) { if (BAND == 40) // Frequency is one digit less than for 30 or 20 meter bands TxCmd[i] = ("FB0000" + String(bFreq).substring(0,7) + (char)EOC).charAt(i); else TxCmd[i] = ("FB000" + String(bFreq).substring(0,8) + (char)EOC).charAt(i); } } void cmdIF() { String s; /* Parameters: P1 Specify the frequency in Hz (11-digit). The blank digits must be . // LM: ???? */ s = "IF000" + String(OPfreq).substring(0,8); /* P2 5 spaces for the TS-480. */ s = s + " "; /* P3 RIT/ XIT frequency in Hz */ String t = "+0000"; // The following delta computation appears to have no effect in HRD // Maybe divide by 100 (Remove fractional Hz) /* long delta = IFtemp - IFfreq; boolean deltaNeg = (delta < 0); if (deltaNeg) delta = -delta; if (delta > 9999) delta = 9999; t = String(delta); while (t.length() < 4) t = "0" + t; if (deltaNeg) t = "-" + t; else t = "+" + t; */ s = s + t; // RIT delta /* P4 0: RIT OFF, 1: RIT ON */ if (RITflag == 1) s = s + "1"; // RIT on else s = s + "0"; // RIT off /* P5 0: XIT OFF, 1: XIT ON */ s = s + "0"; // Placeholder for XIT on/off /* P6: Always 0 for the TS-480 (Memory channel bank number). */ s = s + "0"; // Constant /* P7: Memory channel number (00 ~ 99). */ s = s + "00"; // Hilltopper has no memories, except startup /* P8 0: RX, 1: TX */ s = s + "0"; // For CW mode, probably always Rx /* P9 Operating mode. Refer to the MD commands for details. */ s = s + "3"; // Always CW mode /* P10 See FR and FT commands. */ s = s + "0"; // Placeholder - VFO-A for now /* P11 Scan status. Refer to the SC command. */ s = s + "0"; // Scan not applicable /* P12 0: Simplex operation, 1: Split operation */ s = s + "0"; // Placeholder - Simplex for now /* P13 0: OFF, 1: TONE, 2: CTCSS */ s = s + "0"; // Tone not applicable /* P14 Tone number (00 ~ 42). Refer to the TN and CN command. */ s = s + "00"; // Tone number not applicable /* P15 A space character for the TS-480. */ s = s + " "; // Constant (Emulates TS-480) s = s + (char)EOC; for (int i=0; i < s.length(); i++) TxCmd[i] = s.charAt(i); } void cmdLK() { /* P1 0: Frequency lock function OFF 1: Frequency lock function ON P2 0: Tuning control lock function OFF 1: Tuning control lock function ON*/ String s = "LK00"; // Locks are normally off if (RITflag) // VFO-A should not change during RIT s = "LK10"; s = s + (char)EOC; for (int i=0; i < 5; i++) TxCmd[i] = s.charAt(i); } void cmdZS() { // Return currently selected step for RIT String s = "ZS"; String sStep; switch(stepSize) { case 2 : sStep = "500"; break; case 3 : sStep = "100"; break; case 4 : sStep = " 20"; break; default : sStep = ""; } s.concat(sStep); s.concat((char) EOC); for (int i=0; i < 6; i++) TxCmd[i] = s.charAt(i); } void cmdZM() { // Return current Morse speed String s = "ZM"; String w = String(wpm); if (w.length() < 2) s.concat(" "); s.concat(w); s.concat((char) EOC); for (int i=0; i < 5; i++) TxCmd[i] = s.charAt(i); } #endif