From f7349de7a80d7e4be0ac647c7de4ca9c6c96678c Mon Sep 17 00:00:00 2001 From: JeffersGlass Date: Thu, 7 Apr 2016 23:25:59 -0500 Subject: [PATCH] Add WSPR and CQ code to VFO. --- VFO/VFO.ino | 6 +- VFO_WSPR/VFO_WSPR.ino | 249 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 233 insertions(+), 22 deletions(-) diff --git a/VFO/VFO.ino b/VFO/VFO.ino index fe80cb1..9634016 100644 --- a/VFO/VFO.ino +++ b/VFO/VFO.ino @@ -43,7 +43,7 @@ char* stepNames[][10] = { {" 10MHz", " 5MHz", " 1MHz", "500Khz", "100KHz", " 10KHz", " 1KHz", " 100Hz", " 10Hz", " 1 Hz"}, //basic {" 10KHz", " 1KHz", " 100 Hz", " 10 Hz"}, //basic {" 1KHz", " 100 Hz", " 10 Hz", " 1 Hz"}, //polyakov - {" 1KHz", " 100 Hz", " 10 Hz", " 1 Hz"} //BFO + {" 1KHz", " 100 Hz", " 10 Hz", " 1 Hz"}, //BFO {" 1KHz", " 100 Hz", " 10 Hz", " 1 Hz"} //IF }; @@ -208,7 +208,7 @@ void loop(){ } } displayNeedsUpdate = true; - } + }s } void displayInfo(){ @@ -321,7 +321,7 @@ void setFrequency_5351(long newFreq){ si5351.set_freq((newFreq + ifFreq) * 100UL, 0ULL, SI5351_CLK0); //VFO+IF //VFO-IF //IF-VFO - beak; + break; } } diff --git a/VFO_WSPR/VFO_WSPR.ino b/VFO_WSPR/VFO_WSPR.ino index 15427c4..6661da4 100644 --- a/VFO_WSPR/VFO_WSPR.ino +++ b/VFO_WSPR/VFO_WSPR.ino @@ -3,7 +3,6 @@ #include #include - //-----------Variables & Declarations--------------- /* * The current and desired LISTENING FREQUENCY, which is not always the frequency being output by the Si5351. @@ -15,45 +14,80 @@ long currFreq = 1800000; +//FOR CQ MODE: +char CQ[] = "-.-. --.-"; +char DE[] = "-.. ."; +char morseCallsign[] = "-.- -.- ----. .--- . ..-."; +int morseCallsignLength = 25; +long morseElementLength = 70; //ms + +//FOR WSPR MODE + +int correctionFactor = 0; //adjusts the offset of the Si5351 +int WSPR_TRANSMISSION_DATA[] = { //KK9JEF EN61 30 + 3,3,2,0,0,0,0,0,3,0,0,2,1,1,1,0,0,2,1,2,2,1,0,3,1,3,1,2,2,0, + 0,0,2,2,1,0,2,3,0,1,2,0,2,2,2,2,3,2,3,1,2,0,1,3,2,3,2,0,2,1, + 1,0,1,0,0,2,2,1,1,0,1,0,3,0,1,2,1,0,2,3,0,0,1,0,1,1,2,2,2,3, + 1,0,1,2,3,2,2,0,1,2,2,0,2,0,1,2,2,3,0,2,1,1,1,0,1,3,2,2,3,1, + 0,1,0,2,2,1,1,1,2,0,0,0,0,3,0,1,0,2,3,1,2,2,2,2,0,2,2,3,1,0, + 1,2,1,3,2,0,2,3,3,2,0,2}; + +/* +int WSPR_TRANSMISSION_DATA[] = { //KK9JEF EN61 27 + 3,3,2,0,0,2,0,2,3,0,0,2,1,3,1,2,0,2,1,0,2,3,0,1,1,1,1,0,2,2, + 0,0,2,2,1,2,2,3,0,1,2,2,2,2,2,0,3,0,3,1,2,0,1,3,2,1,2,0,2,3, + 1,0,1,2,0,2,2,1,1,0,1,0,3,2,1,0,1,2,2,3,0,0,1,0,1,1,2,2,2,3, + 1,2,1,2,3,0,2,0,1,0,2,0,2,0,1,2,2,3,0,0,1,1,1,0,1,1,2,0,3,3, + 0,3,0,0,2,1,1,3,2,0,0,2,0,1,0,3,0,2,3,1,2,2,2,0,0,2,2,3,1,0, + 1,0,1,1,2,0,2,1,3,0,0,2}; +*/ + //-----Enumerations of frequency steps and their labels for each mode----// -enum modes{mode_testing = 0, mode_basic, mode_polyakov, mode_bfo}; -const int NUM_MODES = 4; +enum modes{mode_testing = 0, mode_basic, mode_polyakov, mode_bfo, mode_WSPR, mode_CQ}; +const int NUM_MODES = 6; int currMode = mode_basic; -char* modeNames[NUM_MODES] = {"TEST", "VFO", "POLYA", "BFO"}; +char* modeNames[NUM_MODES] = {"TEST", "VFO", "POLYA", "BFO", "WSPR", "CQ"}; long steps[][10] = { //don't forget to update the NUM_STEP_OPTIONS array below {10000000, 5000000, 1000000, 500000, 100000, 10000, 1000, 10, 1}, //testing {10000, 1000, 100, 10}, //basic {1000, 100, 10, 1}, //polyakov - {1000, 100, 10, 1} //bfo + {1000, 100, 10, 1}, //bfo + {5}, //WSPR + {500} //CQ }; const int NUM_STEP_OPTIONS[NUM_MODES] = { 10, //testing 4, //basic 4, //polyakov - 4 //bfo + 4, //bfo + 1, //wspr + 1 //cq }; char* stepNames[][10] = { - {" 10MHz", " 5MHz", " 1MHz", "500Khz", "100KHz", " 10KHz", " 1KHz", " 100Hz", " 10Hz", " 1 Hz"}, //basic - {" 10KHz", " 1KHz", " 100 Hz", " 10 Hz"}, //basic + {" 10MHz", " 5MHz", " 1MHz", "500Khz", "100KHz", " 10KHz", " 1KHz", " 100Hz", " 10Hz", " 1 Hz"}, //basic + {" 10KHz", " 1KHz", " 100Hz", " 10 Hz"}, //basic {" 1KHz", " 100 Hz", " 10 Hz", " 1 Hz"}, //polyakov - {" 1KHz", " 100 Hz", " 10 Hz", " 1 Hz"} //BFO + {" 1KHz", " 100 Hz", " 10 Hz", " 1 Hz"}, //BFO + {" 5 Hz"}, //WSPR + {" 500Hz"} //CQ }; int stepIndex = 0; // holds the index of the currently selected step value //-----AMATEUR BAND DEFININTIONS----------------// //See function "getCurrentBand" below as well -const int NUM_BANDS = 9; -char* bandNames[NUM_BANDS] = {"160m", "80m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"}; +const int NUM_BANDS = 10; +char* bandNames[NUM_BANDS] = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"}; char* OUT_OF_BAND_LABEL = "OOB"; long bandEdges[NUM_BANDS][2] = { {1800000, 2000000}, //160m {3500000, 4000000}, //80m + {5288600, 5288800}, {7000000, 7300000}, //40m {10100000, 10150000}, //30m {14000000, 14350000}, //20m @@ -63,6 +97,19 @@ long bandEdges[NUM_BANDS][2] = { {28000000, 29700000} //10m }; +long WSPRbandEdges[NUM_BANDS][2] = { + {1838000, 1838200}, //160m + {3594000, 3594200}, //80m + {5288600, 5288800}, //60m + {7040000, 7040200}, //40m + {10140100, 10140300}, //30m + {14097000, 14097200}, //20m + {18106000, 18106200}, //17m + {21096000, 21096200}, //15m + {24926000, 24926200}, //12m + {28126000, 28126200}, //10m +}; + /* * Holds the last-seen frequency within each band. The list below is also the default location at bootup. * This array is updated when the BAND button is used to change between bands. @@ -73,6 +120,7 @@ long bandEdges[NUM_BANDS][2] = { long lastBandFreq[NUM_BANDS] = { 1800000, //160m 3500000, //80m + 5288600, //60m 7000000, //40m 10100000, //30m 14000000, //20m @@ -97,6 +145,9 @@ boolean displayNeedsUpdate; const long MIN_FREQ = 8500; const long MAX_FREQ = 150000000; +//Onboard LED Steup +const int PIN_LED = 13; + //---------LCD SETUP-------// int PIN_RS = 7; int PIN_EN = 8; @@ -136,14 +187,16 @@ void setup(){ si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); si5351.set_freq(currFreq * 100ULL, 0ULL, SI5351_CLK0); - si5351.output_enable(SI5351_CLK0, 1); + enableOutput(); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); si5351.output_enable(SI5351_CLK1, 0); si5351.output_enable(SI5351_CLK2, 0); delay(750); - //knob.write(0); + pinMode(PIN_LED, OUTPUT); + digitalWrite(PIN_LED, LOW); + pinMode(PIN_BUTTON_ENCODER, INPUT); digitalWrite(PIN_BUTTON_ENCODER, HIGH); @@ -173,17 +226,52 @@ void loop(){ displayNeedsUpdate = false; - //step up or down or change step size, for either button presses or encoder turns + //step up or down or change step size, for encoder turns if ((encoderChange > 0)){currFreq += steps[currMode][stepIndex]; currFreq = min(currFreq, MAX_FREQ); setFrequency_5351(currFreq); displayNeedsUpdate = true;} if ((encoderChange < 0)){currFreq -= steps[currMode][stepIndex]; currFreq = max(currFreq, MIN_FREQ); setFrequency_5351(currFreq); displayNeedsUpdate = true;} - //pressing the encoder button increments through the possible step sizes for each mode - if (checkButtonPress(PIN_BUTTON_ENCODER)){stepIndex = (stepIndex + 1) % (NUM_STEP_OPTIONS[currMode]); displayNeedsUpdate = true;} + //pressing the encoder button increments through the possible step sizes for each mode; + //in WSPR or CQ modes, the encoder button triggers the transmission of WSPR or a CQ, respectively. + if (checkButtonPress(PIN_BUTTON_ENCODER)){ + if (currMode == mode_testing || currMode == mode_basic || currMode == mode_polyakov || currMode == mode_bfo) { + stepIndex = (stepIndex + 1) % (NUM_STEP_OPTIONS[currMode]); + displayNeedsUpdate = true; + } + else if (currMode == mode_WSPR){ + transmitWSPR(); + } + else if (currMode == mode_CQ){ + transmitMorseWord(CQ); + transmitSpace(); + transmitMorseWord(CQ); + transmitSpace(); + transmitMorseWord(DE); + transmitSpace(); + transmitMorseWord(morseCallsign); + transmitSpace(); + transmitMorseWord(morseCallsign); + } + } //pressing the mode button cycles through the available modes - if (checkButtonPress(PIN_BUTTON_MODE)){currMode = (currMode+1) % NUM_MODES; stepIndex = 0; setFrequency_5351(currFreq); displayNeedsUpdate = true;} + if (checkButtonPress(PIN_BUTTON_MODE)){ + currMode = (currMode+1) % NUM_MODES; + stepIndex = 0; + if (currMode == mode_WSPR){ //If entering WSPR mode, set the current freqency to the bottom of the WSPR band slice + currFreq = findWSPRBand(); + } - /*The mode button: if currFreq is inside an amateur band, save that frequency as the one to return to when + if (currMode == mode_WSPR || currMode == mode_CQ){ + disableOutput(); //In WSPR or CQ mode, the transmitter should be off until manually triggered + } + else{ + enableOutput(); //In all other modes, the output of the VFO is on by default + } + setFrequency_5351(currFreq); + displayNeedsUpdate = true; + } + + /*The band button: if currFreq is inside an amateur band, save that frequency as the one to return to when * the user returns to this band, and jump to the return frequency for the next higher band. Otherwise, * just jump to the next higher band */ @@ -203,6 +291,10 @@ void loop(){ if (currFreq < lastBandFreq[i]){currFreq = lastBandFreq[i]; setFrequency_5351(currFreq); break;} } } + if (currMode == mode_WSPR){ //WSPR mode behaves differntly from other modes + currFreq = WSPRbandEdges[getCurrentBand()][0]; + setFrequency_5351(currFreq); + } displayNeedsUpdate = true; } } @@ -302,7 +394,7 @@ boolean checkButtonPress(int pin){ void setFrequency_5351(long newFreq){ switch (currMode){ case mode_testing: - si5351.set_freq(newFreq * 100ULL, 0ULL, SI5351_CLK0); + si5351.set_freq((newFreq + correctionFactor) * 100ULL, 0ULL, SI5351_CLK0); break; case mode_basic: si5351.set_freq(newFreq * 100ULL, 0ULL, SI5351_CLK0); @@ -316,6 +408,16 @@ void setFrequency_5351(long newFreq){ } } +void enableOutput(){ + si5351.output_enable(SI5351_CLK0, 1); + digitalWrite(PIN_LED, HIGH); +} + +void disableOutput(){ + si5351.output_enable(SI5351_CLK0, 0); + digitalWrite(PIN_LED, LOW); +} + //Returns the index of the current amateur radio band based on currFreq. Does not include the 60m band //Returns -1 if out of band, but within the HF amateur turning range //returns -2 if out of band and lower than the lowest defined band @@ -374,3 +476,112 @@ char getPermission(){ return 'X'; } +void transmitWSPR(){ + long startTime = millis(); + enableOutput(); + for (int dataFrame = 0; dataFrame < 162; dataFrame++){ + si5351.set_freq((currFreq + correctionFactor) * 100ULL + (146*WSPR_TRANSMISSION_DATA[dataFrame]*1ULL), SI5351_PLL_FIXED, SI5351_CLK0); + displayWSPR(dataFrame); + while (millis() < startTime + 683*(dataFrame+1)){ + if (checkButtonPress(PIN_BUTTON_ENCODER) || dataFrame > 162){ + goto escape; + } + } + } + escape: + disableOutput(); + displayNeedsUpdate = true; +} + +void displayWSPR(int frame){ + lcd.clear(); + + lcd.setCursor(0,0); + long printFreq = currFreq * 100ULL + (146*WSPR_TRANSMISSION_DATA[frame]*1ULL); + lcd.print(printFreq); + + //current frame and data are printed on the 3rd line + lcd.setCursor(0, 2); + lcd.print("FRAME:"); + lcd.setCursor(6, 2); + lcd.print(frame); + lcd.setCursor(10, 2); + lcd.print("DATA:"); + lcd.setCursor(15, 2); + lcd.print(WSPR_TRANSMISSION_DATA[frame]); + + //The current amateur band is printed in the top-right corner + int currBand = getCurrentBand(); + if (currBand >= 0){ + char* currBandName = bandNames[currBand]; + lcd.setCursor(20-strlen(currBandName), 0); + lcd.print(currBandName); + } + else{ + lcd.setCursor(20-strlen(OUT_OF_BAND_LABEL), 0); + lcd.print(OUT_OF_BAND_LABEL); + } + + //Callsign is printed at the beginning of the 4th line + lcd.setCursor(0, 3); + lcd.print("KK9JEF"); + + //The mode is printed on the 4th line with no label + //lcd.setCursor(6, 3); + lcd.setCursor(16, 3); + lcd.print("WSPR"); +} + +//When switching into WSPR mode, the VFO jumps to the WSPR portion of the appropriate band; if not inside a band when switching to WSPR mode, +//This fucntion determines where to jump to. +//Currently always resets to the bottom of the lowest band +long findWSPRBand(){ + /*switch (getCurrentBand()){ + case -2: //Below the lowest defined band + currFreq = WSPRbandEdges[0][0]; //set frequency to the bottom edge of lowest band + break; + case -3: //Above the highest defined band + currFreq = WSPRbandEdges[NUM_BANDS-1][0]; //Set frequency to bottom edge of highest band + break; + case -1: //in between bands + break; + default: + currFreq = currFreq = (currFreq % 5); //round to the nearest multiple of 5 hz, for readability in WSPR mode + break; + } + */ + return WSPRbandEdges[0][0]; +} + +void transmitMorseWord(char singleWord[]){ + for (int i = 0; i < strlen(singleWord); i++){ + if (singleWord[i] == '-') transmitDash(); + else if (singleWord[i] == '.') transmitDot(); + else transmitIntracharacter(); + } +} + +void transmitDash(){ + enableOutput(); + delay(morseElementLength * 3); + disableOutput(); + delay(morseElementLength); +} + +void transmitDot(){ + enableOutput(); + delay(morseElementLength); + disableOutput(); + delay(morseElementLength); +} + +void transmitIntracharacter(){ + disableOutput(); + delay(morseElementLength*2); //each element naturally has a one-dot space built in +} + +void transmitSpace(){ + disableOutput(); + delay(morseElementLength*6); //each element naturally has a one-dot space built in that follows it. +} +