// include library code here #include // LCD display routines #include // On board non-volatile memory - used for the calibration factor #include // This is NON STANDARD and needs to be added to your Arduio Codebase on your PC - Just Google It! // declare routine prototypes here - not sure it's really necessary void init_dds(void); void reset_dds(void); void dds(unsigned long freq); void start_message(void); void menu(void); void fixed(void); void sweep(void); void vfo(void); void calibrate(void); int read_encoder(void); unsigned long get_frequency(char message[]); // variables and constants here unsigned long TenMHz = 10000000; // constant 10 MHz in Hz for calling the default DDS frequency #define ENC_PORT PINC // This is straight from the encoder example I copied, so hence its done like this here! byte RESET = 44; // DDS RESET pin is connected to Arduino pin 44 byte DATA = 42; // DDS DATA pin is connected to Arduino pin 42 byte CLOCK = 40; // DDS CLOCK pin is connected to Arduino pin 40 byte LOAD = 38; // DDS LOAD pin is connected to Arduino pin 38 byte ENC_A = 36; // Encoder A This is bit 0 of PORT C which is also Arduino pin 36 byte ENC_B = 37; // Encoder B This is bit 1 of PORT C which is also Arduino pin 37 byte ENC_SW = 48; // Encoder Switch is connected to Arduino pin 48 byte ledpin = 13; // on board LED is always pin 13 (unlucky for some) byte calset = 41; // Calibrate set - tie this pin low to call calibration function byte calup = 43; // increment calibration factor byte caldown = 45; // 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 isnt 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(32, 30, 28, 26, 24, 22); // declare the keypad structure // this is stolen from the on-line keypad.h tutorial const byte ROWS = 4; // Four rows const byte COLS = 3; // Three columns // Define the Keymap char keys[ROWS][COLS] = { {'1','2','3'}, {'4','5','6'}, {'7','8','9'}, {'#','0','*'} }; // Connect keypad ROW0, ROW1, ROW2 and ROW3 to these Arduino pins. byte rowPins[ROWS] = { 9, 8, 7, 6 }; // Connect keypad COL0, COL1 and COL2 to these Arduino pins. byte colPins[COLS] = { 12, 11, 10 }; // Create the Keypad Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); // initiatisation routine void setup() { // LCD 20 columns and 4 rows lcd.begin(20,4); // 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 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 CalFactor.b1 = EEPROM.read(50); CalFactor.b2 = EEPROM.read(51); CalFactor.b3 = EEPROM.read(52); CalFactor.b4 = EEPROM.read(53); // give the AD9851 2 seconds after power on delay (2000); init_dds(); reset_dds(); dds(TenMHz); // 10 MHz initial frequency } // 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 Mogic start_message(); // now loop forever calling the menu while (1) { menu(); } } // now we declare the routines to satisfy the prototypes above void init_dds() { digitalWrite(RESET, LOW); digitalWrite(CLOCK, LOW); digitalWrite(LOAD, LOW); digitalWrite(DATA, LOW); } void reset_dds() { //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); 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); // Chip is RESET now } // 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 AND them bitwise with a value to simply // determine if an unknown individual bit within a data structure is a 1 or a 0 byte FirstBit = 1; float clock_frequency = 180000000; // this is the on-board clock frequency of my AD9851 board float twoE32 = pow (2,32); // this is 2 to the power of 32 (which is quite a lot) DDSLong = ((twoE32 * (freq+CalFactor.value))/ clock_frequency); // 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); } // now send the final 8 bits to complete the 40 bit instruction // the AD9851 datasheet explains this well, but we need 1000 0000 because we want // to use the clock multiplier // so here we are going to look 8 times and send a 1 the first time round then 7 0s // 10 out of 10 for niftyness but 0 out of 10 for readability: for (Bitmask8 = 1; Bitmask8 > 0; Bitmask8 <<= 1) { // iterate through last 8 bits of 40 bit instruction to DDS if (Bitmask8 & FirstBit) // 1st bit of remaining 8 needs to be 1 to enable clock multiplier digitalWrite(DATA,HIGH); else 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); } // and once all 40 bits have been sent // finally we toggle the load bit to say we are done // and let the AD9851 do its stuff digitalWrite (LOAD, HIGH); // Pulse DDS update delayMicroseconds(1); digitalWrite (LOAD, LOW); // to execute previous instruction set return; } void start_message(void) { lcd.clear(); delay(100); lcd.print(" G0MGX DDS Sig Gen"); lcd.setCursor(0,2); lcd.print(" Bad Magic ...."); delay(2000); lcd.clear(); return; } void menu(void) { char key; lcd.clear(); delay(100); lcd.print(" G0MGX DDS Sig Gen"); lcd.setCursor(0,1); lcd.print(" 1. Fixed 2. Sweep"); lcd.setCursor(0,2); lcd.print(" 3. VFO"); lcd.setCursor(0,3); lcd.print("> "); key = keypad.waitForKey(); lcd.setCursor(2,3); lcd.print(key); if(key) // Check for a valid key. { switch (key) { case '1': fixed(); break; case '2': sweep(); break; case '3': vfo(); break; default: break; } } return; } void fixed(void) { dds(get_frequency("Frequency",10000000)); } void sweep(void) { unsigned long start_frequency; unsigned long end_frequency; unsigned long increments; unsigned long sweep_frequency; unsigned long display_frequency; start_frequency = get_frequency("Low",10000000); end_frequency = get_frequency("High",10000000); increments = get_frequency("Step",1000); lcd.clear(); lcd.print(" G0MGX DDS Sig Gen"); lcd.setCursor(0,1); lcd.print("Sweeping..."); while (1) { lcd.setCursor(0,3); lcd.print("Step : "); lcd.print(increments); for (sweep_frequency = start_frequency; sweep_frequency <= end_frequency; sweep_frequency = sweep_frequency + increments) { lcd.setCursor(0,2); lcd.print("Frequency : "); lcd.setCursor(12,2); display_frequency = sweep_frequency - (sweep_frequency % 1000); lcd.print(display_frequency); dds(sweep_frequency); } } } void vfo(void) { int vfo_increment = 1000; int step_count = 1; unsigned long frequency = get_frequency("Start ", 10000000); static uint8_t counter = 1; int8_t tmpdata; // start the DDS with the users frequency dds(frequency); // set ut the LCD display lcd.clear(); lcd.print(" G0MGX DDS Sig Gen"); lcd.setCursor(0,1); lcd.print("VFO"); lcd.setCursor(0,2); lcd.print("Frequency : "); lcd.print(frequency); lcd.setCursor(0,3); lcd.print("Step : "); lcd.print("1 KHz"); while (1) { // Read the encoder state tmpdata = read_encoder(); // in this crazyness here I need to find a way to somehow // only use one in four of the reads from the encoder // so I am relying on counter/4 returning 0 unless // counter itself is also 4 if (tmpdata) counter ++; // increment or decrement if appropriate if (tmpdata > 0) frequency = frequency + ((counter / 4) * vfo_increment); if (tmpdata < 0) frequency = frequency - ((counter / 4) * vfo_increment); // if it's changed send the new frequency to the dds if ((tmpdata > 0 || tmpdata < 0) && (counter / 4) == 1) dds(frequency); if (counter == 4) counter = 1; // update display lcd.setCursor(12,2); lcd.print(frequency); // now check for the step change switch on the encoder if (digitalRead(ENC_SW) == LOW) { delay (100); // pathetic debounce step_count++; step_count = step_count & 0x03; switch (step_count) { case 0: vfo_increment = 10; break; case 1: vfo_increment = 100; break; case 2: vfo_increment = 1000; break; case 3: vfo_increment = 1; break; } if (digitalRead(ENC_SW==LOW)) { delay (500); // delay for 1/2 second but after that assume we are changing the increment again } // update displayed vfo increment setting lcd.setCursor(7,3); if (vfo_increment == 1000) { lcd.print("1 KHz "); } else { lcd.print(vfo_increment); lcd.print(" Hz "); } } } } void calibrate(void) { unsigned long FreqLong; unsigned long TempWord; // no need to output 10MHz calibration signal on DDS // as we have just run setup() which does this already // display calibration text lcd.clear(); lcd.setCursor(0,0); lcd.print("Adjust to 10MHz"); lcd.setCursor(0,1); lcd.print("Cal Factor= "); lcd.setCursor(12,1); lcd.print(CalFactor.value); // remain in the loop while the calset is low while(digitalRead(calset) == LOW) { if (digitalRead(calup) == LOW) { delay(250); //crude debounce delay CalFactor.value++; dds(TenMHz); } if (digitalRead(caldown) == LOW) { delay(250); //crude debounce delay CalFactor.value--; dds(TenMHz); } lcd.setCursor(12,1); lcd.print(CalFactor.value); lcd.print(" "); } // Now 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); } unsigned long get_frequency(char message[], int long multiplier) { unsigned long ret_frequency = 0; char key; int user_key; lcd.clear(); lcd.print("Enter "); lcd.print(message); lcd.print(":"); lcd.setCursor (0,2); lcd.print("> "); while (multiplier > 0) { // Get keypress key = keypad.waitForKey(); // convert from char to integer user_key = key-48ul; // if the char entered is a * or # force bail out if (key == '*' || key == '#') multiplier = 0; // So we aren't about to bail out if this is true if (multiplier != 0) { // if we dont have a zero and the ret_frequency is no longer 0 it can't be a leading zero if (user_key == 0 && ret_frequency == 0 ){} else // dont print leading 0s lcd.print(user_key); } ret_frequency = ret_frequency + (user_key * multiplier); multiplier = multiplier / 10; } delay(200); if (ret_frequency == 0) ret_frequency = TenMHz; return ret_frequency; } int read_encoder() { static int enc_states[] = {0, -1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}; static int old_AB = 0; old_AB <<= 2; old_AB |= (ENC_PORT & 0x03); return (enc_states[(old_AB & 0x0f)]); }