2016-05-12 14:46:25 -05:00
# include <EEPROM.h>
2016-03-03 11:27:40 -06:00
# include <Encoder.h>
# include <Wire.h>
# include <LiquidCrystal.h>
# include <si5351.h>
//-----------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 ;
2016-04-07 23:25:59 -05:00
//FOR CQ MODE:
char CQ [ ] = " -.-. --.- " ;
char DE [ ] = " -.. . " ;
char morseCallsign [ ] = " -.- -.- ----. .--- . ..-. " ;
int morseCallsignLength = 25 ;
long morseElementLength = 70 ; //ms
//FOR WSPR MODE
2016-05-12 14:46:25 -05:00
double correctionFactor = 0 ; //adjusts the offset of the Si5351, in parts per million
double prevCorrectionFactor = correctionFactor ; //allows us to track whether the CF has changed, to save on EEPROM writes
2016-04-07 23:25:59 -05:00
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 } ;
*/
2016-03-03 11:27:40 -06:00
//-----Enumerations of frequency steps and their labels for each mode----//
2016-05-12 14:46:25 -05:00
enum modes { mode_testing = 0 , mode_basic , mode_polyakov , mode_bfo , mode_WSPR , mode_CQ , mode_calibrate } ;
const int NUM_MODES = 7 ;
2016-03-03 11:27:40 -06:00
int currMode = mode_basic ;
2016-05-12 14:46:25 -05:00
char * modeNames [ NUM_MODES ] = { " TEST " , " VFO " , " POLYA " , " BFO " , " WSPR " , " CQ " , " CAL " } ;
2016-03-03 11:27:40 -06:00
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
2016-04-07 23:25:59 -05:00
{ 1000 , 100 , 10 , 1 } , //bfo
{ 5 } , //WSPR
2016-05-12 14:46:25 -05:00
{ 500 } , //CQ
{ 1 } //calibrate
2016-03-03 11:27:40 -06:00
} ;
const int NUM_STEP_OPTIONS [ NUM_MODES ] = {
10 , //testing
4 , //basic
4 , //polyakov
2016-04-07 23:25:59 -05:00
4 , //bfo
1 , //wspr
2016-05-12 14:46:25 -05:00
1 , //cq
1 //calibrate
2016-03-03 11:27:40 -06:00
} ;
char * stepNames [ ] [ 10 ] = {
2016-04-07 23:25:59 -05:00
{ " 10MHz " , " 5MHz " , " 1MHz " , " 500Khz " , " 100KHz " , " 10KHz " , " 1KHz " , " 100Hz " , " 10Hz " , " 1 Hz " } , //basic
{ " 10KHz " , " 1KHz " , " 100Hz " , " 10 Hz " } , //basic
2016-03-03 11:27:40 -06:00
{ " 1KHz " , " 100 Hz " , " 10 Hz " , " 1 Hz " } , //polyakov
2016-04-07 23:25:59 -05:00
{ " 1KHz " , " 100 Hz " , " 10 Hz " , " 1 Hz " } , //BFO
{ " 5 Hz " } , //WSPR
2016-05-12 14:46:25 -05:00
{ " 500Hz " } , //CQ
{ " 1 ppm " } //Calibrate
2016-03-03 11:27:40 -06:00
} ;
int stepIndex = 0 ; // holds the index of the currently selected step value
//-----AMATEUR BAND DEFININTIONS----------------//
//See function "getCurrentBand" below as well
2016-04-07 23:25:59 -05:00
const int NUM_BANDS = 10 ;
char * bandNames [ NUM_BANDS ] = { " 160m " , " 80m " , " 60m " , " 40m " , " 30m " , " 20m " , " 17m " , " 15m " , " 12m " , " 10m " } ;
2016-03-03 11:27:40 -06:00
char * OUT_OF_BAND_LABEL = " OOB " ;
long bandEdges [ NUM_BANDS ] [ 2 ] = {
{ 1800000 , 2000000 } , //160m
{ 3500000 , 4000000 } , //80m
2016-04-07 23:25:59 -05:00
{ 5288600 , 5288800 } ,
2016-03-03 11:27:40 -06:00
{ 7000000 , 7300000 } , //40m
{ 10100000 , 10150000 } , //30m
{ 14000000 , 14350000 } , //20m
{ 18068000 , 18168000 } , //17m
{ 21000000 , 21450000 } , //15m
{ 24890000 , 24990000 } , //12m
{ 28000000 , 29700000 } //10m
} ;
2016-04-07 23:25:59 -05:00
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
} ;
2016-03-03 11:27:40 -06:00
/*
* 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
2016-04-07 23:25:59 -05:00
5288600 , //60m
2016-03-03 11:27:40 -06:00
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 ;
2016-04-07 23:25:59 -05:00
//Onboard LED Steup
const int PIN_LED = 13 ;
2016-03-03 11:27:40 -06:00
//---------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 setup ( ) {
// inialize LCD, display welcome message
lcd . begin ( 20 , 4 ) ;
delay ( 250 ) ;
lcd . setCursor ( 4 , 1 ) ;
lcd . print ( " VFO STARTING " ) ;
2016-04-07 23:25:59 -05:00
pinMode ( PIN_LED , OUTPUT ) ;
digitalWrite ( PIN_LED , LOW ) ;
2016-03-03 11:27:40 -06:00
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 ) ;
2016-05-12 14:46:25 -05:00
lcd . clear ( ) ;
lcd . setCursor ( 0 , 1 ) ;
lcd . print ( " READING CALIBRATION " ) ;
double tempDouble ;
EEPROM . get ( 0 , correctionFactor ) ;
//if (tempDouble < 0.00001 && tempDouble > -0.00001){correctionFactor = tempDouble;}
//else {correctionFactor = 0.00f;}
lcd . setCursor ( 0 , 2 ) ;
lcd . print ( " * " ) ;
lcd . print ( tempDouble ) ;
lcd . setCursor ( 0 , 3 ) ;
lcd . print ( " = " ) ;
lcd . print ( correctionFactor ) ;
delay ( 1000 ) ;
si5351 . init ( SI5351_CRYSTAL_LOAD_8PF , 0 ) ;
si5351 . set_freq ( ( currFreq * ( 1 + correctionFactor ) ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
enableOutput ( ) ;
si5351 . drive_strength ( SI5351_CLK0 , SI5351_DRIVE_8MA ) ;
si5351 . output_enable ( SI5351_CLK1 , 0 ) ;
si5351 . output_enable ( SI5351_CLK2 , 0 ) ;
delay ( 300 ) ;
2016-03-03 11:27:40 -06:00
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 ;
2016-04-07 23:25:59 -05:00
//step up or down or change step size, for encoder turns
2016-05-12 14:46:25 -05:00
if ( currMode ! = mode_calibrate ) {
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 ; }
}
else {
if ( encoderChange > 0 ) { correctionFactor + = 0.0000001 ; setFrequency_5351 ( currFreq ) ; displayNeedsUpdate = true ; }
if ( encoderChange < 0 ) { correctionFactor - = 0.0000001 ; setFrequency_5351 ( currFreq ) ; displayNeedsUpdate = true ; }
}
2016-03-03 11:27:40 -06:00
2016-04-07 23:25:59 -05:00
//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 ) ;
}
2016-05-12 14:46:25 -05:00
else if ( currMode = = mode_calibrate ) {
correctionFactor = 0.00 ;
}
2016-04-07 23:25:59 -05:00
}
2016-03-03 11:27:40 -06:00
//pressing the mode button cycles through the available modes
2016-04-07 23:25:59 -05:00
if ( checkButtonPress ( PIN_BUTTON_MODE ) ) {
2016-05-12 14:46:25 -05:00
//if the correctionFacotr has changed and we're leaving calibration mode, write the new correction factor to the EEPROM
//Note that we do this check before actually advancing to the next mode
if ( currMode = = mode_calibrate & & correctionFactor ! = prevCorrectionFactor ) {
writeCF ( ) ;
//DEBUG
lcd . clear ( ) ;
lcd . setCursor ( 0 , 0 ) ;
lcd . print ( " CALIBRATION STORED " ) ;
delay ( 1000 ) ;
displayNeedsUpdate = true ;
double tempDouble ;
EEPROM . get ( 0 , tempDouble ) ;
if ( tempDouble < 0.00001 & & tempDouble > - 0.00001 ) { correctionFactor = tempDouble ; }
else correctionFactor = 0.00f ;
lcd . setCursor ( 0 , 2 ) ;
lcd . print ( " * " ) ;
lcd . print ( tempDouble ) ;
lcd . setCursor ( 0 , 3 ) ;
lcd . print ( " = " ) ;
lcd . print ( correctionFactor ) ;
delay ( 10000 ) ;
}
//actually change the mode, and reset the step index
2016-04-07 23:25:59 -05:00
currMode = ( currMode + 1 ) % NUM_MODES ;
stepIndex = 0 ;
2016-05-12 14:46:25 -05:00
//if entering calibration mode, make a note of the current correction factor so we can tell later if it changes
if ( currMode = = mode_calibrate ) {
prevCorrectionFactor = correctionFactor ;
}
2016-04-07 23:25:59 -05:00
if ( currMode = = mode_WSPR ) { //If entering WSPR mode, set the current freqency to the bottom of the WSPR band slice
currFreq = findWSPRBand ( ) ;
}
2016-03-03 11:27:40 -06:00
2016-04-07 23:25:59 -05:00
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
2016-03-03 11:27:40 -06:00
* 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 ; }
}
}
2016-04-07 23:25:59 -05:00
if ( currMode = = mode_WSPR ) { //WSPR mode behaves differntly from other modes
currFreq = WSPRbandEdges [ getCurrentBand ( ) ] [ 0 ] ;
setFrequency_5351 ( currFreq ) ;
}
2016-03-03 11:27:40 -06:00
displayNeedsUpdate = true ;
}
}
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 ] ) ;
2016-05-12 14:46:25 -05:00
//If we're in calibration mode, print current calibration factor on line 2:
if ( currMode = = mode_calibrate ) {
lcd . setCursor ( 0 , 1 ) ;
lcd . print ( " CORRECTION " ) ;
2016-03-03 11:27:40 -06:00
2016-05-12 14:46:25 -05:00
int correctionPPM = int ( correctionFactor * pow ( 10 , 6 ) ) ;
2016-03-03 11:27:40 -06:00
2016-05-12 14:46:25 -05:00
int correctionPad = 0 ;
if ( correctionPPM < 100 ) correctionPad + + ;
if ( correctionPPM < 10 ) correctionPad + + ;
if ( correctionPPM > 0 ) correctionPad + + ;
lcd . setCursor ( 9 , 1 ) ;
for ( int i = 0 ; i < correctionPad ; i + + ) lcd . print ( " " ) ;
lcd . print ( correctionPPM ) ;
}
2016-03-03 11:27:40 -06:00
}
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 :
2016-05-12 14:46:25 -05:00
si5351 . set_freq ( ( newFreq * ( 1 + correctionFactor ) ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
2016-03-03 11:27:40 -06:00
break ;
case mode_basic :
2016-05-12 14:46:25 -05:00
si5351 . set_freq ( ( newFreq * ( 1 + correctionFactor ) ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
2016-03-03 11:27:40 -06:00
break ;
case mode_polyakov :
2016-05-12 14:46:25 -05:00
si5351 . set_freq ( ( ( newFreq * ( 1 + correctionFactor ) ) / 2 ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
2016-03-03 11:27:40 -06:00
break ;
case mode_bfo :
2016-05-12 14:46:25 -05:00
si5351 . set_freq ( ( newFreq * ( 1 + correctionFactor ) ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
break ;
case mode_WSPR :
si5351 . set_freq ( ( newFreq * ( 1 + correctionFactor ) ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
break ;
case mode_CQ :
si5351 . set_freq ( ( newFreq * ( 1 + correctionFactor ) ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
break ;
case mode_calibrate :
si5351 . set_freq ( ( newFreq * ( 1 + correctionFactor ) ) * 100ULL , 0ULL , SI5351_CLK0 ) ;
2016-03-03 11:27:40 -06:00
break ;
}
}
2016-04-07 23:25:59 -05:00
void enableOutput ( ) {
si5351 . output_enable ( SI5351_CLK0 , 1 ) ;
digitalWrite ( PIN_LED , HIGH ) ;
}
void disableOutput ( ) {
si5351 . output_enable ( SI5351_CLK0 , 0 ) ;
digitalWrite ( PIN_LED , LOW ) ;
}
2016-03-03 11:27:40 -06:00
//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 ' ;
}
2016-04-07 23:25:59 -05:00
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.
}
2016-05-12 14:46:25 -05:00
void writeCF ( ) {
EEPROM . put ( 0 , correctionFactor ) ;
}