// include library code here #include // LCD display routines #include // On board non-volatile memory - used for the calibration factor #include // the encoder library I used from here * http://www.pjrc.com/teensy/td_libs_Encoder.html // declare routine prototypes here - not sure it's really necessary void Display_Header(void); void init_dds(void); void reset_dds(void); void dds(unsigned long freq); void start_message(void); void vfo(void); void calibrate(void); // variables and constants here const unsigned long VFOStartFrequency = 17100000; // constant 17.1 MHz in Hz for calling the default DDS frequency const unsigned long IFFrequency = 10000000; // This is my proposed IF frequency - we subtract this from the VFO to get the displayed frequency const unsigned long UpperVFOLimit = 17200000; // These two define the upper and lower limits of the VFO Tuning Range const unsigned long LowerVFOLimit = 17000000; float clock_frequency = 125000000; // this is the on-board clock frequency of my AD9850 board float twoE32 = pow (2,32); // this is 2 to the power of 32 (which is quite a lot) me thinks unsigned long VFOFrequency = VFOStartFrequency; // variable initialised to 17.1 MHz in Hz for calling the DDS int DDSStep = 1000; // 1000Hz per click as a staring point int step_count = 3; // 3 signifies a 1000 Hz step as a starting point unsigned long DisplayMHz = 0; // these two variables are used to get the bits before and after the decimal point in the display of the frequency unsigned long DisplayDecimal = 0; byte RESET = 6; // DDS RESET pin is connected to Arduino pin 6 byte DATA = 5; // DDS DATA pin is connected to Arduino pin 5 byte CLOCK = 4; // DDS CLOCK pin is connected to Arduino pin 4 byte LOAD = 3; // DDS LOAD pin is connected to Arduino pin 3 byte ENC_A = A0; // Encoder A This is bit 0 of PORT C which is also Arduino pin A0 byte ENC_B = A1; // Encoder B This is bit 1 of PORT C which is also Arduino pin A1 byte ENC_SW = A2; // Encoder Switch is connected to Arduino pin A2 byte ledpin = 13; // on board LED is always pin 13 (unlucky for some) byte calset = A3; // Calibrate set - tie this pin low to call calibration function byte calup = A4; // ground to increment calibration factor byte caldown = A5; // ground to decrement calibration factor // my EEPROM 4 word data structure here // this is a 4 character long data structure that // is read and written in the non-volatile on board memory // we do this so the calfactor isn't lost on power down struct CalFact { union { long value; struct { unsigned char b1; unsigned char b2; unsigned char b3; unsigned char b4; }__attribute__((packed)); }__attribute__((packed)); }__attribute__((packed)); struct CalFact CalFactor; // declare LCD as listed pins below LiquidCrystal lcd(12, 11, 10, 9, 8, 7); // declare the Encoder Encoder DDSEncoder(A0,A1); // initiatisation routine void setup() { // LCD 20 columns and 4 rows lcd.begin(20,4); lcd.clear(); // Rotary Encoder Pins // all with internal pull up pinMode(ENC_A, INPUT); digitalWrite(ENC_A, HIGH); pinMode(ENC_B, INPUT); digitalWrite(ENC_B, HIGH); pinMode(ENC_SW, INPUT); digitalWrite(ENC_SW, HIGH); // DDS pins for data, clock, reset and load pinMode (DATA, OUTPUT); // DDS pins as output pinMode (CLOCK, OUTPUT); pinMode (LOAD, OUTPUT); pinMode (RESET, OUTPUT); digitalWrite(DATA, LOW); // internal pull-down digitalWrite(CLOCK, LOW); digitalWrite(LOAD, LOW); digitalWrite(RESET, LOW); // Watchdog LED pin pinMode (ledpin, OUTPUT); // needs to be used as a watchdog digitalWrite (ledpin, HIGH); // turn it on so at least we know the software is running! // Calibration pins pinMode (calset, INPUT); // calibration related pins are inputs pinMode (calup, INPUT); pinMode (caldown, INPUT); digitalWrite(calset, HIGH); // internal pull-up enabled digitalWrite(calup, HIGH); digitalWrite(caldown, HIGH); // initialise CalFactor.value from EEPROM // reading the values one character at a time from the on-board non-volatile memory // if you find you have some wild numbr in here 1st time ever you may need to do a once off initialiation of the values CalFactor.b1 = EEPROM.read(50); CalFactor.b2 = EEPROM.read(51); CalFactor.b3 = EEPROM.read(52); CalFactor.b4 = EEPROM.read(53); // give the AD9850 2 seconds (2000 ms) after power on delay (2000); // call the initialisaion init_dds(); // then reset it reset_dds(); // then start it dds(VFOStartFrequency); // initial frequency setup } // main loop void loop() { // Calibration process follows: // if the calset pin is LOW then call the calibrate routine if(digitalRead(calset) == LOW) { calibrate(); } // display some Bad Magic (because I can) start_message(); // now call the VFO routine vfo(); // this never actually returns, so it could be a while before we are back here.... } // now we declare the routines to satisfy the prototypes above // DDS initialisation routine // jusr sets the four interface pins LOW void init_dds() { digitalWrite(RESET, LOW); digitalWrite(CLOCK, LOW); digitalWrite(LOAD, LOW); digitalWrite(DATA, LOW); }// end routine // reset DDS routine void reset_dds() { // reset sequence is: // CLOCK & LOAD = LOW // Pulse RESET high for a few uS (I use 5 mS here) // Pulse CLOCK high for a few uS (I use 5 mS here) // Set DATA to ZERO and pulse LOAD for a few uS (I use 5 mS here) // We could probably use 1ms delay each time - but who's rushing? digitalWrite(CLOCK, LOW); digitalWrite(LOAD, LOW); digitalWrite(RESET, LOW); delay(5); digitalWrite(RESET, HIGH); //pulse RESET delay(5); digitalWrite(RESET, LOW); delay(5); digitalWrite(CLOCK, LOW); delay(5); digitalWrite(CLOCK, HIGH); //pulse CLOCK delay(5); digitalWrite(CLOCK, LOW); delay(5); digitalWrite(DATA, LOW); //make sure DATA pin is LOW digitalWrite(LOAD, LOW); delay(5); digitalWrite(LOAD, HIGH); //pulse LOAD delay(5); digitalWrite(LOAD, LOW); }// end routine // dds instruction write - takes unsigned long frequency in Hz void dds(unsigned long freq) { int last8; unsigned long DDSLong; unsigned long Bitmask32 = 1; // 32 bit bit mask '0000 0000 0000 0000 0000 0000 0000 0001' byte Bitmask8 = 1; // 8 bit bit mask '0000 0001' // we shift these bitmasks left 1 bit at a time and "roll" the 1 from the far right to the left.... and AND them bitwise with a value to simply // determine if an unknown individual bit within a data value is a 1 or a 0 DDSLong = (twoE32 * freq)/ (clock_frequency+(CalFactor.value*50)); // this calculates the first 32 bits of the 40 bit DDS instruction // now we itterate through the first 32 bits one at a time, determine if the individual bits are 1 or 0 and write a HIGH or LOW as appropriate for (Bitmask32 = 1; Bitmask32 > 0; Bitmask32 <<= 1) { // iterate through 32 bits of DDSLong if (DDSLong & Bitmask32) // if bitwise AND resolves to true digitalWrite(DATA,HIGH); else // if bitwise AND resolves to false digitalWrite(DATA,LOW); // after every single bit we toggle the clock pin for the DDS to receive the data bit digitalWrite(CLOCK,HIGH); // Clock data in by setting clock pin high then low delayMicroseconds(1); digitalWrite(CLOCK,LOW); }// end for // now send the final 8 bits to complete the 40 bit instruction // the AD9850 datasheet explains this well, but we need 0000 0000 for (Bitmask8 = 1; Bitmask8 > 0; Bitmask8 <<= 1) { // iterate through last 8 bits of 40 bit instruction to DDS digitalWrite(DATA,LOW); // after every single bit we toggle the clock pin for the DDS to receive the data bit digitalWrite(CLOCK,HIGH); // Clock data in by setting clock pin high then low delayMicroseconds(1); digitalWrite(CLOCK,LOW); }// end for // and once all 40 bits have been sent // finally we toggle the load bit to say we are done // and let the AD9850 do its stuff digitalWrite (LOAD, HIGH); // Pulse DDS update delayMicroseconds(1); digitalWrite (LOAD, LOW); // to execute previous instruction set }// end routine void start_message() { lcd.clear(); delay(100); lcd.print(" G0MGX BITX DDS "); lcd.setCursor(0,2); lcd.print(" Bad Magic ...."); delay(2000); }// end routine void Display_Header() { lcd.clear(); lcd.print(" G0MGX BITX DDS "); }// end routine // this is really the main routine // it loops round reading the encoder and updating the DDS as appropriate // luckily I managed to change it so it only updates the DDS when it needs to // previously it was calling the dds routine every time even if the frequency hadn't changed // the generated frequency wasn't accurate (not suprisingly) void vfo() { // encoder stuff long EncoderPosition = 0; long newEncoderPosition; // set ut the LCD display lcd.clear(); lcd.print(" G0MGX BITX DDS"); lcd.setCursor(0,2); lcd.print("VFO : "); // now we loop while 1 is true so that will be for quite a while... while (1) { // read the current position of the encoder newEncoderPosition = DDSEncoder.read(); // if it has changed since last time we checked then do stuff if (newEncoderPosition != EncoderPosition) { // the VFO frequency will be a +ve or -ve from the start point VFOFrequency = VFOStartFrequency + (DDSStep * newEncoderPosition); // have we hit the lockstops if (VFOFrequency > UpperVFOLimit || VFOFrequency < LowerVFOLimit) { if (VFOFrequency > UpperVFOLimit) { VFOFrequency = UpperVFOLimit; } if (VFOFrequency < LowerVFOLimit) { VFOFrequency = LowerVFOLimit; } // if we have hit the lockstops write the old position back to the encoder DDSEncoder.write(EncoderPosition); } else { // if we haven't hit the lockstops update the encoder position EncoderPosition = newEncoderPosition; } dds(VFOFrequency); } // now we work out what to display on the LCD DisplayMHz = VFOFrequency - IFFrequency; DisplayMHz = DisplayMHz/1000000; DisplayDecimal = VFOFrequency % 1000000; lcd.setCursor(6, 2); lcd.print(DisplayMHz); lcd.print("."); // do leading zero stuff if (DisplayDecimal < 100000) lcd.print("0"); if (DisplayDecimal < 10000) lcd.print("0"); if (DisplayDecimal < 1000) lcd.print("0"); if (DisplayDecimal < 100) lcd.print("0"); if (DisplayDecimal < 10) lcd.print("0"); lcd.print(DisplayDecimal); // now figure out if we need to change the DDSStep // so is the encoder push button pressed? if (digitalRead(ENC_SW) == LOW) { delay(100); // a very crude debounce // the switch on the encoder has been pressed // so we increment my step count variable step_count = step_count + 1; // then we AND it with 3 to make that the maximum result (it will always be 0 to 3) step_count = step_count & 0x03; // now change the step as appropriate switch (step_count) { case 0: DDSStep = 100; break; case 1: DDSStep = 10; break; case 2: DDSStep = 1; break; case 3: DDSStep = 1000; break; } //end switch } if (digitalRead(ENC_SW) == LOW) delay(500); // delay 1/2 second to ensure the single press is complete // now update the step on the LCD display lcd.setCursor(15,2); if (DDSStep == 1000) lcd.print("1KHz"); if (DDSStep == 100) lcd.print("100Hz"); if (DDSStep == 10) lcd.print("10Hz"); if (DDSStep == 1) lcd.print("1Hz"); lcd.print(" "); } // end while }// end routine // hoopey calibrate routine follows void calibrate() { unsigned long FreqLong; unsigned long TempWord; // start by setting the DDS to 17.1 MHz dds(VFOStartFrequency); // display calibration text lcd.clear(); lcd.setCursor(0,0); lcd.print("Adjust to 17.1 MHz"); lcd.setCursor(0,1); lcd.print("Cal Factor= "); lcd.setCursor(12,1); lcd.print(CalFactor.value); lcd.print(" "); // remain in the loop while the calset is low while(digitalRead(calset) == LOW) { if (digitalRead(calup) == LOW) { delay(500); //crude debounce delay CalFactor.value++; dds(VFOStartFrequency); }//end if if (digitalRead(caldown) == LOW) { delay(500); //crude debounce delay CalFactor.value--; dds(VFOStartFrequency); }//end if lcd.setCursor(12,1); lcd.print(CalFactor.value); lcd.print(" "); }//end while // Now we're done fiddling, write the Calfactor value back to EEPROM EEPROM.write(50,CalFactor.b1); EEPROM.write(51,CalFactor.b2); EEPROM.write(52,CalFactor.b3); EEPROM.write(53,CalFactor.b4); }// end routine