commit b2345cf54e396917c0aa18c196935a06f4a7abe2 Author: mattbk Date: Thu Jan 25 09:31:20 2024 -0600 Init new project with variations on KK9JEF's code. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cad7657 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.configureOnOpen": false +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e6c2f0 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# DDS_VFO +Code for the arduino-controlled VFO based on the Si5351 chip diff --git a/Vfo Wiring.fzz b/Vfo Wiring.fzz new file mode 100644 index 0000000..a93c2cb Binary files /dev/null and b/Vfo Wiring.fzz differ diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..a2a4692 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:metro] +platform = atmelavr +board = metro +framework = arduino +lib_deps = + etherkit/Etherkit Si5351@^2.1.4 + paulstoffregen/Encoder@^1.4.4 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3d3b03e --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,395 @@ +//----------- History --------------- +/* +/* This code has been modified from that written by Jeff Glass (KK9JEF) and documented in the following locations: +/* - https://kk9jef.wordpress.com/2015/11/09/40m-direction-conversion-receiver-in-the-polyakov-style/ +/* - https://github.com/JeffersGlass/DDS_VFO +/* + */ + +#include +#include +#include +//#include +#include + + +//----------- Variables & Declarations --------------- +/* + * The current and desired LISTENING FREQUENCY, which is not always the frequency being output by the Si5351. + * In 'testing' and 'basic' modes, the output freqeuncy is equal to currFreq + * In 'polyakov' mode, the output frequency is half of curFreq + * In BFO mode, ......... + * These adjustments are mode in the setFrequency_5351 function depending on the current mode held in currMode + */ + +long currFreq = 1800000; //in HZ +long ifFreq = 8865000; //in HZ + +//-----Enumerations of frequency steps and their labels for each mode----// + +enum modes{mode_testing = 0, mode_basic, mode_polyakov, mode_bfo, mode_if}; +const int NUM_MODES = 5; +int currMode = mode_basic; + +const char* modeNames[NUM_MODES] = {"TEST", "VFO", "POLYA", "BFO", "IF"}; + +long steps[][10] = { //don't forget to update the MAX_STEPS_INDEX 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} //IF Mode +}; + +const int NUM_STEP_OPTIONS[NUM_MODES] = { + 10, //testing + 4, //basic + 4, //polyakov + 4, //bfo + 4 //if +}; +const 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"} //IF +}; + +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; +const char* bandNames[NUM_BANDS] = {"160m", "80m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"}; +const char* OUT_OF_BAND_LABEL = "OOB"; + +long bandEdges[NUM_BANDS][2] = { + {1800000, 2000000}, //160m + {3500000, 4000000}, //80m + {7000000, 7300000}, //40m + {10100000, 10150000}, //30m + {14000000, 14350000}, //20m + {18068000, 18168000}, //17m + {21000000, 21450000}, //15m + {24890000, 24990000}, //12m + {28000000, 29700000} //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. + * If the used has scrolled outside of a defined band and then presses the BAND button, they will + * still be advanced to the next band, but the band-return location will not be updated + */ + +long lastBandFreq[NUM_BANDS] = { + 1800000, //160m + 3500000, //80m + 7000000, //40m + 10100000, //30m + 14000000, //20m + 18068000, //17m + 21000000, //15m + 24890000, //12m + 28000000 //10m +}; + +/*Information on bandplan permissions and recommended communication modes is contained in the + * methods getPermission and getBandplanModes below + */ + +//--------------------------------------------- + +long lastButtonPress[] = {0,0,0,0,0,0,0}; //holds the last timestamp, from millis(), that a pin changed state. Directly references the arduino output pin numbers, length may need to be increased +boolean buttonActive[] = {false, false, false, false, false, false, false}; + +long encoderPosition = 0; +boolean displayNeedsUpdate; + +const long MIN_FREQ = 8500; +const long MAX_FREQ = 150000000; + +//---------LCD SETUP-------// +// int PIN_RS = 7; +// int PIN_EN = 8; +// int PIN_DB4 = 9; +// int PIN_DB5 = 10; +// int PIN_DB6 = 11; +// int PIN_DB7 = 12; +//LiquidCrystal lcd(PIN_RS, PIN_EN, PIN_DB4, PIN_DB5, PIN_DB6, PIN_DB7); + +//--------Si5351 Declaration---------------// + +Si5351 si5351; +//SDA is on pin A4 for Arduino Uno +//SCL is on pin A5 for Arduino Uno + +//--------Tuning Knob Interrupt Pins-------// +//Encoder knob(2, 3), pushbutton on 1 + +Encoder encoder(2, 3); +const int PIN_BUTTON_ENCODER = 1; + +//Button Pins// +const int PIN_BUTTON_MODE = 4; +const int PIN_BUTTON_BAND = 0; +const int BUTTON_DEBOUNCE_TIME = 10; //milliseconds + +//SWR Sensor Pins +const int PIN_SWR_FORWARD = A1; +const int PIN_SWR_REVERSE = A0; + + + +// void displayInfo(){ +// lcd.clear(); + +// // frequency information be centeredw within 11 spaces on the second line: +// if (currFreq >= 100000000) lcd.setCursor(3, 0); +// else if (currFreq > 10000000) lcd.setCursor(4, 0); +// else lcd.setCursor(5, 0); +// int mhz = int(currFreq/ 1000000); +// int khz = int((currFreq - (mhz*1000000)) / 1000); +// int hz = int(currFreq % 1000); + +// int khzPad = 0; +// if (khz < 100) khzPad++; +// if (khz < 10) khzPad++; + +// int hzPad = 0; +// if (hz < 100) hzPad++; +// if (hz < 10) hzPad++; + +// lcd.print(mhz); +// lcd.print("."); +// for (int i = 0; i < khzPad; i++) lcd.print("0"); +// lcd.print(khz); +// lcd.print("."); +// for (int i = 0; i < hzPad; i++) lcd.print("0"); +// lcd.print(hz); + +// //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); +// } + +// //The license needed to operate on this frequency (ARRL, USA ONLY) is printed just below the band label +// lcd.setCursor (19, 1); +// lcd.print(getPermission()); + +// //Step Information should take the middle 11 spaces on the 3nd line +// //The first 5 symbols are "STEP:", leaving 6 chars for step info. +// lcd.setCursor(4, 2); +// lcd.print("STEP:"); +// lcd.print(stepNames[currMode][stepIndex]); + +// //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(20-strlen(modeNames[currMode]), 3); +// lcd.print(modeNames[currMode]); + +// //DEBUG +// //lcd.setCursor(0,0); +// //lcd.print(getCurrentBand()); + +// /*float fwd = analogRead(PIN_SWR_FORWARD); +// float rev = analogRead(PIN_SWR_REVERSE); +// float gamma = rev/fwd; +// float swr = (1 + abs(gamma)) / (1 - abs(gamma)); + +// lcd.setCursor(0, 1); +// lcd.print(int(fwd)); +// lcd.setCursor(4, 1); +// lcd.print(int(rev)); +// lcd.setCursor(8, 1); +// lcd.print(gamma); +// lcd.setCursor(14, 1); +// lcd.print(swr);*/ + +// } + +boolean checkButtonPress(int pin){ + long time = millis(); + if (buttonActive[pin] && digitalRead(pin) == HIGH){ + buttonActive[pin] = false; + lastButtonPress[pin] = time; + } + else if (digitalRead(pin) == LOW && !buttonActive[pin] && time > lastButtonPress[pin] + BUTTON_DEBOUNCE_TIME){ + buttonActive[pin] = true; + lastButtonPress[pin] = time; + return true; + } + return false; +} + +void setFrequency_5351(long newFreq){ + switch (currMode){ + case mode_testing: + si5351.set_freq(newFreq * 100ULL, SI5351_CLK0); + break; + case mode_basic: + si5351.set_freq(newFreq * 100ULL, SI5351_CLK0); + break; + case mode_polyakov: + si5351.set_freq((newFreq / 2) * 100ULL, SI5351_CLK0); + break; + case mode_bfo: + si5351.set_freq(newFreq * 100ULL, SI5351_CLK0); + break; + case mode_if: + si5351.set_freq((newFreq + ifFreq) * 100UL, SI5351_CLK0); //VFO+IF + //VFO-IF + //IF-VFO + break; + } +} + +//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 +//returns -3 if out of band and higher than the highest defined band +int getCurrentBand(){ + if (currFreq < bandEdges[0][0]) return -2; //we are lower than the lower edge of the lowest defined band + if (currFreq > bandEdges[NUM_BANDS-1][1]) return -3; //We are higher than the upper edge of the highest defined band + for (int i = 0; i < NUM_BANDS; i++){ + if (currFreq >= bandEdges[i][0] && currFreq <= bandEdges[i][1]){return i;} //We are within a band + } + return -1; +} + +char getPermission(){ + if (getCurrentBand() < 0) return ' '; + + //160m + if (currFreq >= 1800000 && currFreq <= 2000000) return 'G'; + + //80m + if (currFreq >= 3525000 && currFreq <= 3600000) return 'T'; + if ((currFreq >= 3525000 && currFreq <= 3600000) || (currFreq >= 3800000 && currFreq <= 4000000)) return 'G'; + if ((currFreq >= 3525000 && currFreq <= 3600000) || (currFreq >= 3700000 && currFreq <= 4000000)) return 'A'; + if (currFreq >= 3500000 && currFreq <= 4000000) return 'E'; + + //40m + if (currFreq >= 7025000 && currFreq <= 7125000) return 'T'; + if ((currFreq >= 7025000 && currFreq <= 7125000) || (currFreq >= 7175000 && currFreq <= 7300000)) return 'G'; + if (currFreq >= 7025000 && currFreq <= 7300000) return 'A'; + if (currFreq >= 7000000 && currFreq <= 7300000) return 'E'; + + //30m + if (currFreq >= 10100000 && currFreq <= 10150000) return 'G'; + + //20m + if ((currFreq >= 14025000 && currFreq <= 14150000) || (currFreq >= 14225000 && currFreq <= 14350000)) return 'G'; + if ((currFreq >= 14025000 && currFreq <= 14150000) || (currFreq >= 14175000 && currFreq <= 14350000)) return 'A'; + if (currFreq >= 14000000 && currFreq <= 14350000) return 'E'; + + //17m + if (currFreq >= 18068000 && currFreq <= 18168000) return 'G'; + + //15m + if (currFreq >= 21025000 && currFreq <= 21200000) return 'T'; + if ((currFreq >= 21025000 && currFreq <= 21200000) || (currFreq >= 21275000 && currFreq <= 21450000)) return 'G'; + if ((currFreq >= 21025000 && currFreq <= 21200000) || (currFreq >= 21225000 && currFreq <= 21450000)) return 'A'; + if (currFreq >= 21000000 && currFreq <= 21450000) return 'E'; + + //12m + if (currFreq >= 24890000 && currFreq <= 24990000) return 'G'; + + //10m + if (currFreq >= 28000000 && currFreq <= 28500000) return 'T'; + if (currFreq >= 28000000 && currFreq <= 29700000) return 'G'; + + return 'X'; +} + +void setup(){ + // inialize LCD, display welcome message + //lcd.begin(20, 4); + //delay(250); + //lcd.setCursor(4, 1); + //lcd.print("VFO STARTING"); + + si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); + si5351.set_freq(currFreq * 100ULL, SI5351_CLK0); + si5351.output_enable(SI5351_CLK0, 1); + 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_BUTTON_ENCODER, INPUT); + digitalWrite(PIN_BUTTON_ENCODER, HIGH); + + pinMode(PIN_BUTTON_MODE, INPUT); + digitalWrite(PIN_BUTTON_MODE, HIGH); + pinMode(PIN_BUTTON_BAND, INPUT); + digitalWrite(PIN_BUTTON_BAND, HIGH); + + pinMode(PIN_SWR_FORWARD, INPUT); + pinMode(PIN_SWR_REVERSE, INPUT); + + //lcd.clear(); + //lcd.setCursor(2, 7); + //lcd.print("WELCOME!"); + //delay(500); + //displayInfo(); +} + +void loop(){ + //if (displayNeedsUpdate) {displayInfo();} + //delay(80); + + //detect whether encoder has changed position + long reading = encoder.read(); + long encoderChange = reading - encoderPosition; + encoderPosition = reading; + + displayNeedsUpdate = false; + + //step up or down or change step size, for either button presses or 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 mode button cycles through the available modes + if (checkButtonPress(PIN_BUTTON_MODE)){currMode = (currMode+1) % NUM_MODES; stepIndex = 0; setFrequency_5351(currFreq); displayNeedsUpdate = true;} + + /*The mode 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 + */ + if (checkButtonPress(PIN_BUTTON_BAND)){ + int currBand = getCurrentBand(); + if (currBand >= 0){ + lastBandFreq[currBand] = currFreq; + currFreq = lastBandFreq[(getCurrentBand() + 1) % NUM_BANDS]; + setFrequency_5351(currFreq); + } + else if (currBand == -2 || currBand == -3){ + currFreq = lastBandFreq[0]; + setFrequency_5351(currFreq); + } + else if (currBand == -1){ + for (int i = 0; i < NUM_BANDS; i++){ + if (currFreq < lastBandFreq[i]){currFreq = lastBandFreq[i]; setFrequency_5351(currFreq); break;} + } + } + displayNeedsUpdate = true; + } +} \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/vfo_oled.code-workspace b/vfo_oled.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/vfo_oled.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file