/* This program written by Alastair GW0AJU. The code presented here for an all HF single band setting digital vfo. The code will automatically set the correct local oscillator frequency irrespective should the single band setting be below or above the 9MHz I.F. design. Ardunio board used = Ardiuno Uno original date 14th Jan 2016 update 28th jan 2016 update 1st Feb 2016 update 13th March 2017 */ //********** liquid display setup ********* #include #include #include #define I2C_ADDR 0x27 // <<- Add your address here. #define Rs_pin 0 #define Rw_pin 1 #define En_pin 2 #define BACKLIGHT_PIN 3 #define D4_pin 4 #define D5_pin 5 #define D6_pin 6 #define D7_pin 7 LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin); //*********** arduino uno eeprom setup ************* #include long address=0; //*********** rotary encoder dial setup ************** int clk_location = 2; int dt_location = 3; //********* logic switch for vfo step change ***************** int vfo_step_DA = 5; // logic pin d0 for vfo step change int vfo_step_DB = 6; // logic pin d1 for vfo step change //******************** morse key and tx_rx relay ************** int cw_key = 4; // pin 4 for cw key connection int tx_rx_key_relay = 13; // pin 13 tx/rx antenna relay, also keys pin 13 LED //*********** dds vfo setup ************** #define DDS_CLOCK 125E6 #define CLOCK 9 // W_CLK = Pin 9 - connect to AD9850 module word load clock pin (CLK) #define LOAD 10 // FQ_UD = Pin 10 - connect to freq update pin (FQ_UD) #define DATA 11 // DATA = Pin 11 - connect to serial data load pin (AD9850 D7 = DATA) #define RESET 12 // RESET = Pin 12 - connect to DDS reset line int run_once = 1; float result = 0; unsigned long IF_LO; unsigned long freq; long dial_step; long carrier_centre; float dial_post = 0; int move_dial = 0; int dial_delay_clk = 0; int dial_delay_anti_clk = 0; int dial_delay_movement = 0; //********** start of arduino DDS VFO control program ********* //This function will write a 4 byte (32bit) long to the eeprom at //the specified address to address + 3. void EEPROMWritelong(int address, long value) { //Decomposition from a long to 4 bytes by using bitshift. //One = Most significant -> Four = Least significant byte byte four = (value & 0xFF); byte three = ((value >> 8) & 0xFF); byte two = ((value >> 16) & 0xFF); byte one = ((value >> 24) & 0xFF); //Write the 4 bytes into the eeprom memory. EEPROM.write(address, four); EEPROM.write(address + 1, three); EEPROM.write(address + 2, two); EEPROM.write(address + 3, one); } //This function will return a 4 byte (32bit) long from the eeprom //at the specified address to address + 3. long EEPROMReadlong(long address) { //Read the 4 bytes from the eeprom memory. long four = EEPROM.read(address); long three = EEPROM.read(address + 1); long two = EEPROM.read(address + 2); long one = EEPROM.read(address + 3); //Return the recomposed long by using bitshift. return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF); } void setup() { Serial.begin(9600); // error correction in program code use only pinMode(cw_key, INPUT); // pin 4 morse key pinMode(tx_rx_key_relay, OUTPUT); // pin 7 ptt antenna relay pinMode(vfo_step_DA, INPUT); // DA = pin 5 logic pin d0 for vfo step change pinMode(vfo_step_DB, INPUT); // DB = pin 6 logic pin d1 for vfo step change pinMode(clk_location, INPUT); // pin 2 rotary encoder pinMode(dt_location, INPUT); // pin 3 rotary encoder pinMode(CLOCK, OUTPUT); pinMode(LOAD, OUTPUT); pinMode(DATA, OUTPUT); pinMode(RESET, OUTPUT); AD9850_reset(); AD9850_init(); // ********* start up display messages ************ lcd.begin (16,2); // <<-- our LCD is a 20x4, change for your LCD if needed // LCD Backlight ON lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE); lcd.setBacklight(HIGH); lcd.home(); // go home on LCD lcd.setCursor(0,0); // first line of display lcd.print("HF single band"); lcd.setCursor(0,1); lcd.print("Digital VFO"); delay(1500); lcd.clear(); lcd.home(); lcd.setCursor(0,0); // first line of display lcd.print("program code by"); lcd.setCursor(0,1); lcd.print("Alastair GW0AJU"); delay(1500); lcd.clear(); lcd.home(); // *** set start-up carrier frequency *** carrier_centre = 7023000; //Starting at the first byte on the eeprom. long address=0; if (EEPROMReadlong(0) > 7000000 && EEPROMReadlong(0)< 7200000) { carrier_centre = EEPROMReadlong(0); } else { //Writing first long. EEPROMWritelong(address, carrier_centre); address+=4; carrier_centre = EEPROMReadlong(0); } dial_frequency(); lcd.setCursor(2,1); lcd.print("Rx 40m CW"); // set message for band operation // *** the arduino code will automatically set the correct L.O for the Rx vfo setting *** } //************* void loop *********************** void loop() { getEncoderTurn(); // rotary encoder sub-routine for rotary movement of dial setting Tx_Rx_mode(); } //*************** start of program code sub-routines **************** void vfo_step_change() { int DA = digitalRead(vfo_step_DA); int DB = digitalRead(vfo_step_DB); if (DA == HIGH && DB == LOW) { dial_step = 5000; // 5KHz vfo increment dial_delay_movement = 50; } else if (DA == HIGH && DB == HIGH) { dial_step = 100; // 100Hz vfo increment dial_delay_movement = 35; } else if (DA == LOW && DB == HIGH) { dial_step = 10; // 10Hz vfo increment dial_delay_movement = 17; } } //******** detection of rotary dial movement and change of carrier frequency ************ void getEncoderTurn() // rotary encoder sub-routine for rotary movement { vfo_step_change(); // routine for change the vfo step 10Hz, 100Hz, or 1KHz if (digitalRead(clk_location) == HIGH) // anti clock wise direction { dial_delay_clk = 0; delay(3); if (digitalRead(clk_location) == LOW) { dial_delay_anti_clk = dial_delay_anti_clk + 1; if ( dial_delay_anti_clk > dial_delay_movement ) // change the number 18 to a higher value for smoother operation { move_dial = 1; result = -1; // down band movement carrier_centre = carrier_centre + (result * dial_step); EEPROMWritelong(address, carrier_centre); address+=4; dial_frequency(); dial_delay_anti_clk = 0; } } } else if (digitalRead(dt_location) == HIGH) // clock wise direction { dial_delay_anti_clk = 0; delay(3); if (digitalRead(dt_location) == LOW) { dial_delay_clk = dial_delay_clk + 1; if ( dial_delay_clk > dial_delay_movement ) // change the number 18 to a higher value for smoother operation { move_dial = 1; result = 1; // up band movement carrier_centre = carrier_centre + (result * dial_step); EEPROMWritelong(address, carrier_centre); address+=4; dial_frequency(); dial_delay_clk = 0; } } } } //********** processes for transmit / receive mode *************** void Tx_Rx_mode() // cw transmission sense for full cw break in mode { if (digitalRead(cw_key) == LOW ) { digitalWrite(tx_rx_key_relay, LOW); // ptt antenna relay low for Tx mode if ( run_once == 1 || move_dial == 1) { sendFrequency(carrier_centre); // DDS direct Tx carrier output for transmit frequency run_once = 0; move_dial = 0; lcd.home(); lcd.setCursor(2,1); lcd.print("Tx 40m CW"); // alter here for the display message for band operation } } else if (digitalRead(cw_key) == HIGH ) { digitalWrite(tx_rx_key_relay, HIGH); // ptt antenna relay high for Rx mode if ( run_once == 0 || move_dial == 1) { IF_LO = 9E6 + (carrier_centre); // Rx L.O. output high for 9MHz I.F. sendFrequency(IF_LO); run_once = 1; move_dial = 0; lcd.home(); lcd.setCursor(2,1); lcd.print("Rx 40m CW"); // alter here for the display message for band operation } } } // **************************************************************************************** // ************* frequency VFO control from dial change ***************** void AD9850_reset() { //reset sequence is: // CLOCK & LOAD = LOW // Pulse RESET high for a few uS (use 5 uS here) // Pulse CLOCK high for a few uS (use 5 uS here) // Set DATA to ZERO and pulse LOAD for a few uS (use 5 uS here) // data sheet diagrams show only RESET and CLOCK being used to reset the device, but I see no output unless I also // toggle the LOAD line here. digitalWrite(CLOCK, LOW); digitalWrite(LOAD, LOW); digitalWrite(RESET, LOW); delayMicroseconds(5); digitalWrite(RESET, HIGH); //pulse RESET delayMicroseconds(5); digitalWrite(RESET, LOW); delayMicroseconds(5); digitalWrite(CLOCK, LOW); delayMicroseconds(5); digitalWrite(CLOCK, HIGH); //pulse CLOCK delayMicroseconds(5); digitalWrite(CLOCK, LOW); delayMicroseconds(5); digitalWrite(DATA, LOW); //make sure DATA pin is LOW digitalWrite(LOAD, LOW); delayMicroseconds(5); digitalWrite(LOAD, HIGH); //pulse LOAD delayMicroseconds(5); digitalWrite(LOAD, LOW); // Chip is RESET now } void AD9850_init() { digitalWrite(RESET, LOW); digitalWrite(CLOCK, LOW); digitalWrite(LOAD, LOW); digitalWrite(DATA, LOW); } // *********** frequency control of the AD9850 DDS device for TX / Rx vfo ********* void sendFrequency(unsigned long frequency) { unsigned long tuning_word = (frequency * pow(2, 32)) / DDS_CLOCK; digitalWrite (LOAD, LOW); shiftOut(DATA, CLOCK, LSBFIRST, tuning_word); shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 8); shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 16); shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 24); shiftOut(DATA, CLOCK, LSBFIRST, 0x00); digitalWrite(LOAD, HIGH); } void dial_frequency() // to calculate the local oscillator frequency for 9MHz first I.F. using AD9850 DDS { if (digitalRead(cw_key) == LOW ) { sendFrequency(carrier_centre); // Tx carrier directly on output frequency for CW mode operation } else { sendFrequency(IF_LO); // Rx carrier mode offset for 9MHz I.F. setting } dial_post = carrier_centre; // display show centre channel frequency of Radio set operation lcd.home(); lcd.setCursor(0,0); lcd.print("vfo="); lcd.setCursor(4,0); lcd.print(dial_post/1E3,3); lcd.print("KHz"); } //************************************************************************** //**************************** end of program code ****************************