Here is the signal generator control software to provide advanced control of the Arduino and AD9850 synthesizer module, as well as the KY-040 optical encoder.
Over Christmas 2015, I moved from “prototype” to “production” in developing an Arduino sketch (program) to fully control my signal generator described previously. If you assemble the hardware and load this sketch into the Arduino UNO, you will be up and running in a few hours. For under $25, you will have a fully functional signal generator that works from 100 kHz to 30 MHz. The complete source code is provided below. For those who are interested in how the signal generator control software works, I will describe it in detail in later posts.
One of the challenges in writing this signal generator control software was to get a lot of functionality controlled by only five buttons: Select, Left-Right and Up-Down. The first trick was to get the software to understand a short button press versus a long button press (I defined long as greater than half a second.) The second trick was create a simple menu system that could be controlled by the buttons. Here is a description of how it works.
- A short press on the Select button enters the Menu Mode. The Left-Right buttons then scroll through three menu choices: Off-On-Sweep. A second press on the Select button then selects one of these modes.
- When the signal generator is On, the Up-Down buttons change the frequency by the increment shown on the right side of the display. The frequency increments range from 1 Hz to 1 MHz. You can change the increments with the Left-Right buttons. Normally, the Up-Down buttons change frequency by one increment per press. However, a long press on the Select button toggles in and out of a repeating feature when the frequency continues to change while you hold down and Up-Down button.
- When you select Sweep the signal generator will sweep a range of frequencies. The starting and ending frequencies to Sweep, the increments, the dwell time for each increment and the number of sweeps can be set up in advance. You can pause and resume the sweep with a short press on the Up button, or cancel the sweep with a long press on the Up button.
You can also use the optical encoder in place of the buttons for Menu selection and changing frequency. The switch on the optical encoder works the same as the Select button.
Finally, the signal generator control software provides remote control over the USB-based serial port. I will describe the serial control in a later article. It adds additional features such as changing the sweep parameters, calibrating the signal generator, and saving configuration information to the Arduino EEPROM.
Signal Generator Control Software Source Code
The source code consists of two parts. First is the Arduino sketch, which you just copy and paste into the Arduino IDE (Integrated Development Environment) to create the INO file. This is the main program. Second is the AD9850 library, which consists of AD9850B.H and AD9850B.CPP. Just add these to your Arduino Libraries in a folder called AD9850B.
The signal generator control software also needs two other libraries. One is called LiquidCrystal, which is already contained in Arduino’s libraries. The other is called EEPROMex which is needed to save and load configuration data from the long term memory on your Arduino board.
Arduino Sketch
#include <EEPROMVar.h> #include <EEPROMex.h> #include "AD9850B.h" #include <LiquidCrystal.h> /* * Arduino C program to control AD9850 Signal Generator * described on http://play.fallows.ca * Version 2.0 January 5, 2016 * */ /* * Definitions */ /* LCD 1602 Keypad Shield Buttons */ #define BUTTON_NONE 0 #define BUTTON_RIGHT 1 #define BUTTON_UP 2 #define BUTTON_DOWN 3 #define BUTTON_LEFT 4 #define BUTTON_SELECT 5 #define BUTTON_RIGHT_LONG 11 #define BUTTON_UP_LONG 12 #define BUTTON_DOWN_LONG 13 #define BUTTON_LEFT_LONG 14 #define BUTTON_SELECT_LONG 15 #define PRESS_LONG 500 //long presses > 500 ms #define PRESS_LONG_INC 10 //add to signify long press /* UNO Analog Pin Connection to read LDC Keypad Buttons */ #define LCD_BUTTON_PIN 0 /* UNO Digital Pin Connections to AD9850 (A1..A4) */ #define AD9850B_CLOCK 16 #define AD9850B_LOAD 17 #define AD9850B_DATA 18 #define AD9850B_RESET 19 /* UNO Digital Pin Connections for KY-040 Encoder */ #define KY_CLK 2 //Interrupt 0 #define KY_DT 12 #define KY_SW 11 /* Signal Generator Modes */ #define MODE_OFF 0 #define MODE_ON 1 #define MODE_SWEEP 2 /* Range limitations */ #define FREQ_LIMIT_LOW 100 #define FREQ_LIMIT_HIGH 30000000 #define STEP_INDEX_LIMIT_LOW 0 //1Hz #define STEP_INDEX_LIMIT_HIGH 6 //1MHz /* Serial related */ #define SERIAL_BUFFER_SIZE 64 #define SERIAL_BAUDRATE 9600 #define SERIAL_ERROR_NONE 0 #define SERIAL_ERROR_INVALID_COMMAND 1 #define SERIAL_ERROR_BAD_PARAMETER 2 #define SERIAL_ERROR_NOT_CONNECTED 3 #define SERIAL_COMMAND_COUNT 20 /* EEPROM related */ #define EEPROM_ADDRESS 0 //Arbitrary starting address #define EEPROM_VALID 900 // Arbitary magic # /* * Global Objects and Variables */ /* Objects */ LiquidCrystal lcd(8, 9, 4, 5, 6, 7); AD9850B dds(AD9850B_CLOCK, AD9850B_LOAD, AD9850B_DATA, AD9850B_RESET); /* Synthesizer Related Variables */ unsigned long Frequency; //for Synthesizer unsigned long FrequencyBeforeSweep; //remember old frequency unsigned long FrequencyStep; //for Synthesizer - frequency increment long FrequencyCal; //for calibrating clock in AD9850 long FrequencyOffset; //for using dds with an IF boolean Repeating; //forces slewed frequency incrementes int StepIndex; //selects step value SweepState sweepStatus = ssOff; //defined in AD9850B.H boolean sweepPaused = false; //temporarily pause a sweep unsigned long StepValues[7] = { 1, 10, 100, 1000, 10000, 100000, 1000000 }; char* StepDisplays[] = { "1H", "10H", "100H", "1K", "10K", "100K", "1M" }; /* Mode and Menu Related Variables */ int Mode = MODE_OFF; int ModeValues[3] = { MODE_OFF, MODE_ON, MODE_SWEEP}; char* ModeDisplays[] = { "Off", "On", "Sweep" }; boolean MenuOn = false; int tempMode; //new mode to be selected from Menu int lastMode; //remember last mode after changing /* KY-040 Related Variables */ volatile boolean TurnDetected; //KY040 interrupt service routine - encoder is turning volatile boolean Down; //KY040 interrupt service routine - turning direction const int kyCLK = KY_CLK; //(Interrupt 0) const int kyDT = KY_DT; const int kySW = KY_SW; /* Serial Remote Operation Related Variables */ char serialBuffer[SERIAL_BUFFER_SIZE]; boolean serialConnected = false; boolean serialVerbose = false; //enables verbose serial responses int serialIndex = 0; /* These are the commands recognized on the serial port, either lower or upper case. Separately documented */ char* SerialCommandStrings[] = { "M", "M?", "F", "F?", "I", "I?", "C", "C?", "O", "O?", "S", "L", "WP", "W", "W?" , "U", "D", "V", "+", "X" }; enum SerialCommandIDs { setMode, getMode, setFreq, getFreq, setStep, getStep, setCal, getCal, setOffset, getOffset, setEEPROM, getEEPROM, setSweepParam, setSweep, getSweep, setUP, setDOWN, setVerbose, setStart, setExit }; /* EEPROM and Configuration Store */ struct swConfig { unsigned long csweeplo; unsigned long csweephi; unsigned long csweepstep; int csweepdelay; int csweepcount; }; struct sgConfig { int cvalid; unsigned long cfreq; int cstepindex; long cfreqcal; long cfreqoff; swConfig csweep; } config; /* * Main Program */ void setup() { /* LCD 1602 Keypad */ lcd.begin(16, 2); lcd.clear(); /* Encoder KY-040 */ pinMode(kyCLK, INPUT); pinMode(kyDT, INPUT); attachInterrupt(0, isrEncoder, CHANGE); pinMode(kySW, INPUT); /* Synthesizer module AD9850 */ pinMode(AD9850B_CLOCK, OUTPUT); pinMode(AD9850B_LOAD, OUTPUT); pinMode(AD9850B_DATA, OUTPUT); pinMode(AD9850B_RESET, OUTPUT); dds.SetActive(true); /* Startup */ LoadConfig(); dds.SetCalibration(FrequencyCal); Mode = MODE_ON; MenuOn = false; SetFrequency(Frequency); ShowStep(StepIndex); Serial.begin(SERIAL_BAUDRATE); // always waiting for serial connection delay(100); ShowMenu(Mode, MenuOn); } void loop() { int btn = BUTTON_NONE; /* * Get the Inputs. Priority given to LCD Keypad buttons. * Turning encoder produces BUTTON_UP or BUTTON_DOWN * Pressing encoder switch produces BUTTON_SELECT or BUTTON_SELECTLONG */ btn = ReadLCDBtn(); if (btn == BUTTON_NONE) { btn = CheckEncoderTurning(); } if (btn == BUTTON_NONE) { btn = CheckEncoderSwitch(); } /* Deal with Menu activities, if any */ btn = ProcessMenu(btn); /* Deal with Serial Port commands, if any */ if (GetSerialData()) { int result = ProcessSerial(serialBuffer); switch (result) { case SERIAL_ERROR_NONE: Serial.println("OK"); break; case SERIAL_ERROR_INVALID_COMMAND: Serial.println("! Invalid Command"); break; case SERIAL_ERROR_BAD_PARAMETER: Serial.println("! Bad Parameter"); break; case SERIAL_ERROR_NOT_CONNECTED: Serial.println("! Serial Not Connected"); break; } } /* * At this point, we are ready to respond to button presses * and remote commands */ if ((Mode == MODE_ON) && (!serialConnected)) { if ((lastMode == MODE_SWEEP) && (FrequencyBeforeSweep != 0)) { SetFrequency(FrequencyBeforeSweep); FrequencyBeforeSweep = 0; } if (!dds.GetActive()) { dds.SetActive(true); SetFrequency(Frequency); } switch (btn) { case BUTTON_LEFT: StepIndex--; if (StepIndex < STEP_INDEX_LIMIT_LOW) StepIndex = STEP_INDEX_LIMIT_HIGH; ShowStep(StepIndex); FrequencyStep = StepValues[StepIndex]; break; case BUTTON_RIGHT: StepIndex++; if (StepIndex > STEP_INDEX_LIMIT_HIGH) StepIndex = STEP_INDEX_LIMIT_LOW; ShowStep(StepIndex); FrequencyStep = StepValues[StepIndex]; break; case BUTTON_UP: if ((Frequency + FrequencyStep) <= FREQ_LIMIT_HIGH) { Frequency += FrequencyStep; SetFrequency(Frequency); } break; case BUTTON_DOWN: if ((Frequency - FrequencyStep) >= FREQ_LIMIT_LOW) { Frequency -= FrequencyStep; SetFrequency(Frequency); } break; case BUTTON_SELECT_LONG: Repeating = !Repeating; ShowRepeating(Repeating); break; } } else if (Mode == MODE_OFF) { if ((lastMode == MODE_SWEEP) && (FrequencyBeforeSweep != 0)) { SetFrequency(FrequencyBeforeSweep); FrequencyBeforeSweep = 0; } if (dds.GetActive()) { dds.SetActive(false); } } else if (Mode == MODE_SWEEP) { if (sweepStatus == ssOff) sweepStatus = ProcessSweep(true); if (!serialConnected) { if (btn == BUTTON_UP) { if (sweepStatus == ssFinished) { sweepStatus = ssOff; } else if (sweepStatus == ssRunning) { sweepPaused = !sweepPaused; } } else if (btn == BUTTON_UP_LONG) { sweepStatus = ssFinished; sweepPaused = false; ShowSweepCounter(0); SetFrequency(FrequencyBeforeSweep); Mode = lastMode; ShowMenu(Mode, false); } } if (!sweepPaused) { switch (sweepStatus) { case ssOff: sweepStatus = ProcessSweep(true); break; case ssRunning: sweepStatus = ProcessSweep(false); break; case ssFinished: ShowSweepCounter(0); break; } } } delay(10); } /* * Support Functions - Buttons and Encoder Inputs */ int ReadLCDBtn() { /* * My current Button ADC values: N=1020, R=0, U=140, D=325, L=500, S=736 * Make sure your check yours and modify buttonValues[] if necessary!!! * Button values > 0 are only returned when the button is released. * This function is checked at beginning of each program loop. * Trigger is used to measure elapsed time since button was pressed. * Triggered=true means waiting for button released * */ static const int buttonValues[6] = { 900, 50, 200, 400, 650, 850 }; static int LastButton = BUTTON_NONE; static unsigned long Trigger = 0; static boolean Triggered = false; int Voltage; int Button; int i; boolean Release; Voltage = analogRead(LCD_BUTTON_PIN); /* Find which button was pressed, if any */ if (Voltage > buttonValues[0]) { Button = BUTTON_NONE; // Return immediately if this is not a button release if (!Triggered) return Button; } else { for (i = 1; i < 5; i++) if (Voltage < buttonValues[i]) break; Button = i; } if ((Repeating) && ((Button == BUTTON_UP) || (Button == BUTTON_DOWN))) { /* Return immediately if we are repeating frequency changes */ return Button; } /* New button press triggered. Trigger=time press started */ if ((Button > BUTTON_NONE) && (!Triggered)) { Triggered = true; Trigger = millis(); } /* Check to see if this loop is a button release */ Release = ((Button == BUTTON_NONE) && (Triggered)); if (Release) { Button = LastButton; // Each button press could be normal OR long if ((millis() - Trigger) > PRESS_LONG) Button += PRESS_LONG_INC; Triggered = false; Release = false; LastButton = BUTTON_NONE; return Button; } else { // Otherwise just store the button ID for later release LastButton = Button; return BUTTON_NONE; } } int CheckEncoderTurning() { int retval = BUTTON_NONE; if (TurnDetected) // set by isrEncoder { if (Down) { if (MenuOn) { retval = BUTTON_LEFT; } else { retval = BUTTON_DOWN; } } else { if (MenuOn) { retval = BUTTON_RIGHT; } else { retval = BUTTON_UP; } } TurnDetected = false; } return retval; } int CheckEncoderSwitch() { int retval = BUTTON_NONE; static unsigned long startTime; if (digitalRead(kySW) == LOW) { startTime = millis(); while (digitalRead(kySW) == LOW) {} //wait until released if ((millis() - startTime) >= PRESS_LONG) { retval = BUTTON_SELECT_LONG; //Toggle Repeating } else { retval = BUTTON_SELECT; //Toggle MenuOn } } return retval; } void isrEncoder() { /* Interrupt Service Routine for Encoder turns */ Down = (digitalRead(kyCLK) == digitalRead(kyDT)); TurnDetected = true; } /* Support Functions - Menu and Sweep */ int ProcessMenu(int btn) { static int nextMode; if (btn == BUTTON_SELECT) { if (MenuOn) { lastMode = Mode; Mode = nextMode; // This is where Mode changes MenuOn = false; } else { MenuOn = true; // This is where Menu activates nextMode = Mode; } ShowMenu(Mode, MenuOn); return BUTTON_NONE; } /* * If Menu On, process all buttons as menu changes * else process buttons according to Mode */ if (MenuOn) { switch (btn) { case BUTTON_RIGHT: nextMode++; if (nextMode > 2) nextMode = 0; ShowMenu(nextMode, MenuOn); return BUTTON_NONE; break; case BUTTON_LEFT: nextMode--; if (nextMode < 0) nextMode = 2; ShowMenu(nextMode, MenuOn); return BUTTON_NONE; break; } } return btn; } SweepState ProcessSweep(bool reset) { static unsigned long lastClock; static unsigned long f = 0; static int counter = 0; /* State machine to run the sweep */ if (reset) { FrequencyBeforeSweep = Frequency; lastClock = millis(); counter = config.csweep.csweepcount; f = config.csweep.csweeplo; SetFrequency(f); ShowSweepCounter(counter); return ssRunning; } if ((millis() - lastClock) >= config.csweep.csweepdelay) { f += config.csweep.csweepstep; if (f > config.csweep.csweephi) { f = config.csweep.csweeplo; counter--; ShowSweepCounter(counter); } if (counter > 0) { SetFrequency(f); lastClock = millis(); return ssRunning; } else { ShowSweepCounter(counter); return ssFinished; } } } /* Support Functions - EEPROM */ void SaveConfig() { /* * EEPROM_VALID is a magic number to tell the program * it is reading a previously stored configuration. * Uses EEPROMex.h library updateblock function. * Update prefered to write as it only stores values * which have actually changed. In this program version * SaveConfig is only called as a Serial Command. */ config.cvalid = EEPROM_VALID; config.cfreq = Frequency; config.cstepindex = StepIndex; config.cfreqcal = FrequencyCal; config.cfreqoff = FrequencyOffset; EEPROM.updateBlock(EEPROM_ADDRESS, config); config.cvalid = 0; /* See http://thijs.elenbaas.net/2012/07/extended-eeprom-library-for-arduino/ for more info */ } void LoadConfig() { /* Called every time Arduino resets, and can be called * as a Serial Command */ EEPROM.readBlock(EEPROM_ADDRESS, config); if (config.cvalid != EEPROM_VALID) { /* Set defaults if EEPROM config empty */ config.cfreq = 10000000; config.cfreqcal = 0; config.cfreqoff = 0; config.cstepindex = 0; config.csweep.csweeplo = 10000000; config.csweep.csweephi = 10000100; config.csweep.csweepstep = 1; config.csweep.csweepdelay = 5; config.csweep.csweepcount = 5; } /* Load Synthesizer variables */ Frequency = config.cfreq; StepIndex = config.cstepindex; FrequencyStep = StepValues[StepIndex]; FrequencyCal = config.cfreqcal; FrequencyOffset = config.cfreqoff; } /* Support Functions - LCD Display */ void ShowStep(int index) { char buf[5] = ""; sprintf(buf, "%4s", StepDisplays[index]); lcd.setCursor(12, 0); lcd.print(buf); } void ShowFrequency(unsigned long f) { char sepChar = ','; int bufSize = 11; char buffer[] = " "; char * buf = buffer; buf += bufSize--; *(--buf) = '\0'; if (f==0) { *(--buf) = '0'; } else { uint8_t digCnt = 0; // general case: possibly multiple digits with thousands separators while ((f != 0) && bufSize) { // add a thousands separator every three digits if (sepChar && (digCnt >= 3) && (bufSize > 1)) { *(--buf) = sepChar; bufSize--; digCnt = 0; } // add another digit to the buffer *(--buf) = (char)(f % 10) | '0'; digCnt++; bufSize--; // prepare for producing the next digit f /= 10; } } lcd.setCursor(0, 0); lcd.print(buffer); /* Formatting routine adapted from Don Kinzer post * http://www.avrfreaks.net/forum/formatting-big-numbers-commas */ } void SetFrequency(unsigned long f) { unsigned long actualFrequency; /* Limit the frequency range*/ if (f < FREQ_LIMIT_LOW) { f = FREQ_LIMIT_LOW; } else if (f > FREQ_LIMIT_HIGH) { f = FREQ_LIMIT_HIGH; } actualFrequency = f; if (FrequencyOffset != 0) actualFrequency += FrequencyOffset; Frequency = f; dds.SetFrequency(actualFrequency); ShowFrequency(f); } void ShowMenu(int inx, boolean hilite) { char buf[8] = ""; if (hilite) { sprintf(buf, "<%-5s>", ModeDisplays[inx]); } else { sprintf(buf, " %-5s ", ModeDisplays[inx]); } lcd.setCursor(2, 1); lcd.print(buf); } void ShowRepeating(bool param) { lcd.setCursor(10, 0); if (param) { lcd.print( char(165));//small square symbol } else lcd.print(' '); } void ShowSweepCounter(int param) { char buf[3] = ""; sprintf(buf, "%02d", param); lcd.setCursor(14, 1); if (param > 0) { lcd.print(buf); } else { lcd.print(" "); } } /* Support Functions - Serial Communication */ void SetSerialConnected(boolean param) { serialConnected = param; lcd.setCursor(0, 1); if (serialConnected) { lcd.print('R'); } else { lcd.print(' '); } } boolean GetSerialData() { boolean lineFound = false; char charBuffer = 0; /* Checks for serial input every loop. Build incoming ASCII into * into serialBuffer until a line feed arrives. Then tell the * main loop a new line has been found for processing */ while (Serial.available() > 0) { charBuffer = Serial.read(); if (charBuffer == '\n') { serialBuffer[serialIndex] = 0; lineFound = (serialIndex > 0); serialIndex = 0; } else if (charBuffer == '\r') { // ignore } else if (serialIndex < SERIAL_BUFFER_SIZE && lineFound == false) { serialBuffer[serialIndex++] = toupper(charBuffer); } } return lineFound; /* See tutorials on serial command processing from http://www.thebreadboard.ca/ for excellent material from Peter Oakes, which I found very helpful, including this routine */ } int ProcessSerial(char * commandBuffer) { char * Command; char * Parameter; char * pch; unsigned long ParamValue = 0; unsigned long f; int i; int serialCommandIndex = -1; //* Parse serial string into Command and Param */ pch = strchr(commandBuffer, '='); if (pch == NULL) { Command = strtok(commandBuffer, ""); Parameter = NULL; } else { Command = strtok(commandBuffer, "="); Parameter = strtok(NULL, "="); } for (i = 0; i < SERIAL_COMMAND_COUNT; i++) { if (strcmp(Command, SerialCommandStrings[i]) == 0) { serialCommandIndex = i; break; } } if (serialCommandIndex == -1) return SERIAL_ERROR_INVALID_COMMAND; if ((!serialConnected) && (serialCommandIndex != setStart)) return SERIAL_ERROR_NOT_CONNECTED; if (CheckSerialParameter(serialCommandIndex, Parameter) != SERIAL_ERROR_NONE) return SERIAL_ERROR_BAD_PARAMETER; if (IsNumeric(Parameter)) { ParamValue = atol(Parameter); } switch (serialCommandIndex) { case setMode: if ((ParamValue >= MODE_OFF) && (ParamValue <= MODE_SWEEP)) { Mode = ParamValue; ShowMenu(Mode, false); } break; case getMode: if (serialVerbose) { Serial.print("Mode = "); Serial.println(ModeDisplays[Mode]); } else { Serial.print("M="); Serial.println(Mode); } break; case setFreq: if ((ParamValue >= FREQ_LIMIT_LOW) && (ParamValue <= FREQ_LIMIT_HIGH)) { SetFrequency(ParamValue); } else { return SERIAL_ERROR_BAD_PARAMETER; } break; case getFreq: if (serialVerbose) { Serial.print("Frequency = "); } else { Serial.print("F="); } Serial.println(Frequency); break; case setStep: if (ParamValue >= STEP_INDEX_LIMIT_LOW && ParamValue <= STEP_INDEX_LIMIT_HIGH) { StepIndex = ParamValue; FrequencyStep = StepValues[StepIndex]; ShowStep(StepIndex); } else { return SERIAL_ERROR_BAD_PARAMETER; } break; case getStep: if (serialVerbose) { Serial.print("Frequency Step "); Serial.print(StepDisplays[StepIndex]); Serial.print(" Index="); } else { Serial.print("I="); } Serial.println(StepIndex); break; case setCal: FrequencyCal = ParamValue; dds.SetCalibration(FrequencyCal); SetFrequency(Frequency); break; case getCal: if (serialVerbose) { Serial.print("Cal = "); } else { Serial.print("C="); } Serial.println(FrequencyCal); break; case setOffset: FrequencyOffset = ParamValue; SetFrequency(Frequency); break; case getOffset: if (serialVerbose) { Serial.print("Offset = "); } else { Serial.print("O="); } Serial.println(FrequencyOffset); break; case setEEPROM: SaveConfig(); break; case getEEPROM: LoadConfig(); break; case setSweepParam: if (!CheckSweepParameters(Parameter, true)) { return SERIAL_ERROR_BAD_PARAMETER; } break; case setSweep: switch (ParamValue) { case 0: if (sweepStatus > ssOff) { sweepStatus = ssFinished; sweepPaused = false; ShowSweepCounter(0); SetFrequency(FrequencyBeforeSweep); Mode = lastMode; ShowMenu(Mode, false); } break; case 1: if ((sweepStatus == ssOff) || (sweepStatus == ssFinished)) { sweepStatus = ProcessSweep(true); lastMode = Mode; Mode = MODE_SWEEP; } break; case 2: if (sweepStatus == ssRunning) sweepPaused = true; break; case 3: if (sweepStatus == ssRunning) sweepPaused = false; break; } break; case getSweep: if (serialVerbose) { Serial.print("Sweep Status = "); switch (sweepStatus) { case ssOff: Serial.println("Off"); break; case ssRunning: Serial.println("Running"); break; case ssFinished: Serial.println("Finished"); break; } } else { Serial.print("W="); Serial.println(sweepStatus); } break; case setUP: Frequency += FrequencyStep; SetFrequency(Frequency); break; case setDOWN: Frequency -= FrequencyStep; SetFrequency(Frequency); break; case setVerbose: serialVerbose = (ParamValue == 1); if (serialVerbose) { Serial.println("Verbose On"); } break; case setStart: SetSerialConnected(true); if (serialVerbose) Serial.println("Serial ON"); break; case setExit: SetSerialConnected(false); if (serialVerbose) Serial.println("Serial OFF"); break; } return SERIAL_ERROR_NONE; } /* Support Functions - Input Validation and Error Checking */ boolean CheckSweepParameters(char * params, boolean StoreInConfig) { swConfig t = { 0,0,0,0,0 }; int i = 1; while (i <= 5) { switch (i) //includes basic error checking { case 1: t.csweeplo = atol(strtok(params, ",")); if ((t.csweeplo < FREQ_LIMIT_LOW) || (t.csweeplo > FREQ_LIMIT_HIGH)) return false; break; case 2: t.csweephi = atol(strtok(NULL, ",")); if ((t.csweephi <= t.csweeplo) || (t.csweephi > FREQ_LIMIT_HIGH)) return false; break; case 3: t.csweepstep = atol(strtok(NULL, ",")); if ((t.csweepstep < 1) || (t.csweepstep >(t.csweephi - t.csweeplo))) return false; break; case 4: t.csweepdelay = atoi(strtok(NULL, ",")); if ((t.csweepdelay < 1) || (t.csweepdelay > 10)) return false; break; case 5: t.csweepcount = atoi(strtok(NULL, ",")); if ((t.csweepcount < 1) || (t.csweepcount > 100)) return false; break; } i++; } if (StoreInConfig) { config.csweep = t; } return true; } int CheckSerialParameter(int index, char * param) { int retval = SERIAL_ERROR_NONE; switch (index) { case setSweepParam: // errors caught later when command processed break; default: if (!IsNumeric(param)) retval = SERIAL_ERROR_BAD_PARAMETER; break; } return retval; } boolean IsNumeric(const char * param) { int counter = 0; while (*param) { if (!isDigit(*param)) { if ((counter == 0) && (*param == '-')) { // ignore error if first character is "-" } else { return false; } } param++; counter++; } return true; /* Source: Peter Oakes. Modified to accept negative value. */ }
AD9850B Library
// AD9850B.h #ifndef _AD9850B_h #define _AD9850B_h #if defined(ARDUINO) && ARDUINO >= 100 #include "arduino.h" #else #include "WProgram.h" #endif #define DDS_CLOCK 125000000 #define FREQ_FACTOR 4294967296 typedef enum SweepState { ssOff, ssRunning, ssFinished }; class AD9850B { private: int lineCLOCK; int lineLOAD; int lineDATA; int lineRESET; unsigned long _frequency = 10000000; double _calfactor = 0; boolean _active = false; void PowerDown(); void Reset(); public: AD9850B(int gW_CLK, int gFQ_UD, int gDATA, int gRESET); void Init(); boolean GetActive(); void SetActive(boolean param); unsigned long GetFrequency(); void SetCalibration(long param); void SetFrequency(unsigned long param); }; extern AD9850B dds; #endif /* Library Class to control AD9850 Synthesizer Module AD9850.CPP January 5, 2015 Sources of ideas: http://webshed.org/wiki/AD9850_Arduino https://github.com/F4GOJ/AD9850 https://github.com/gonya707/ARDUINO_AD9850_Library */ #include "AD9850B.h" #define pulseHigh(pin) {digitalWrite(pin, HIGH); delayMicroseconds(10); digitalWrite(pin, LOW); } AD9850B::AD9850B(int gW_CLK, int gFQ_UD, int gDATA, int gRESET) { lineCLOCK = gW_CLK; lineLOAD = gFQ_UD; lineDATA = gDATA; lineRESET = gRESET; } void AD9850B::PowerDown() { int PDword = 0x04; int i; pulseHigh(lineLOAD); for (i = 0; i<8; i++) { if ((PDword >> i) & 0x01) digitalWrite(lineDATA, HIGH); else digitalWrite(lineDATA, LOW); pulseHigh(lineCLOCK); } pulseHigh(lineLOAD); _frequency = 0; } void AD9850B::Reset() { digitalWrite(lineCLOCK, LOW); digitalWrite(lineLOAD, LOW); pulseHigh(lineRESET); pulseHigh(lineCLOCK); digitalWrite(lineDATA, LOW); pulseHigh(lineLOAD); } void AD9850B::Init() { digitalWrite(lineRESET, LOW); digitalWrite(lineCLOCK, LOW); digitalWrite(lineLOAD, LOW); digitalWrite(lineDATA, LOW); } boolean AD9850B::GetActive() { return _active; } void AD9850B::SetActive(boolean param) { if (_active != param) { switch (param) { case true: Init(); Reset(); SetFrequency(_frequency); _active = true; break; case false: PowerDown(); _active = false; break; } } } unsigned long AD9850B::GetFrequency() { return _frequency; } void AD9850B::SetFrequency(unsigned long param) { unsigned long tuning_word = (param * FREQ_FACTOR) / (DDS_CLOCK + _calfactor); digitalWrite(lineLOAD, LOW); digitalWrite(lineCLOCK, LOW); shiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word); shiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word >> 8); shiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word >> 16); shiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word >> 24); shiftOut(lineDATA, lineCLOCK, LSBFIRST, 0x0); pulseHigh(lineLOAD); _frequency = param; } void AD9850B::SetCalibration(long param) { _calfactor = param; }
#include directives are causing a circular reference.
remove #include “AD9850B.h” file in ad9850.h
You need to separate the second block on the web page into two files: AD9850B.h and AD9850B.cpp. Install as library. Then, #include AD9850.h in your Arduino sketch and it should work ok. If you want to contact me directly at play@fallows.ca I can send you the source by email. Cheers, John