{"id":762,"date":"2016-01-06T09:12:01","date_gmt":"2016-01-06T16:12:01","guid":{"rendered":"http:\/\/play.fallows.ca\/wp\/?p=762"},"modified":"2017-02-09T20:38:16","modified_gmt":"2017-02-10T03:38:16","slug":"signal-generator-control-software-ad9850","status":"publish","type":"post","link":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/","title":{"rendered":"Signal Generator Control Software for AD9850"},"content":{"rendered":"<p><a href=\"https:\/\/i0.wp.com\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control.jpg\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" class=\"alignnone\" style=\"border: 0px currentcolor\" title=\"SigGen Control\" src=\"https:\/\/i0.wp.com\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg?resize=536%2C304\" alt=\"signal generator control software\" width=\"536\" height=\"304\" border=\"0\" \/><\/a><\/p>\n<p>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.<\/p>\n<p><!--more--><\/p>\n<p>Over Christmas 2015, I moved from \u201cprototype\u201d to \u201cproduction\u201d in developing an Arduino sketch (program) to fully control my <a href=\"http:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/arduino-signal-generator-hardware-software\/\" target=\"_blank\">signal generator described previously<\/a>. 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.<\/p>\n<p>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.<\/p>\n<ul>\n<li>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.<\/li>\n<li>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.<\/li>\n<li>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.<\/li>\n<\/ul>\n<p>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.<\/p>\n<p>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.<\/p>\n<h2>Signal Generator Control Software Source Code<\/h2>\n<p>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.<\/p>\n<p>The signal generator control software also needs two other libraries. One is called <a href=\"https:\/\/www.arduino.cc\/en\/Reference\/LiquidCrystal\" target=\"_blank\" rel=\"nofollow\">LiquidCrystal<\/a>, which is already contained in Arduino\u2019s libraries. The other is called <a href=\"http:\/\/thijs.elenbaas.net\/2012\/07\/extended-eeprom-library-for-arduino\/\" target=\"_blank\" rel=\"nofollow\">EEPROMex<\/a> which is needed to save and load configuration data from the long term memory on your Arduino board.<\/p>\n<h3>Arduino Sketch<\/h3>\n<p>&nbsp;<\/p>\n<pre class=\"brush: cpp; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n#include &lt;EEPROMVar.h&gt;\r\n#include &lt;EEPROMex.h&gt;\r\n#include &quot;AD9850B.h&quot;\r\n#include &lt;LiquidCrystal.h&gt;\r\n\r\n\/*\r\n * Arduino C program to control AD9850 Signal Generator\r\n * described on http:\/\/play.fallows.ca\r\n * Version 2.0  January 5, 2016\r\n *\r\n *\/\r\n\r\n \/*\r\n  * Definitions\r\n  *\/\r\n  \/* LCD 1602 Keypad Shield Buttons *\/\r\n#define BUTTON_NONE 0\r\n#define BUTTON_RIGHT  1\r\n#define BUTTON_UP     2\r\n#define BUTTON_DOWN   3\r\n#define BUTTON_LEFT   4\r\n#define BUTTON_SELECT 5\r\n#define BUTTON_RIGHT_LONG  11\r\n#define BUTTON_UP_LONG     12\r\n#define BUTTON_DOWN_LONG   13\r\n#define BUTTON_LEFT_LONG   14\r\n#define BUTTON_SELECT_LONG 15\r\n#define PRESS_LONG 500\t\t\t\/\/long presses &gt; 500 ms\r\n#define PRESS_LONG_INC 10\t\t\/\/add to signify long press\r\n\/* UNO Analog Pin Connection to read LDC Keypad Buttons *\/\r\n#define LCD_BUTTON_PIN    0\r\n\/* UNO Digital Pin Connections to AD9850 (A1..A4) *\/\r\n#define AD9850B_CLOCK  16\r\n#define AD9850B_LOAD 17\r\n#define AD9850B_DATA  18\r\n#define AD9850B_RESET 19\r\n\/* UNO Digital Pin Connections for KY-040 Encoder *\/\r\n#define\t KY_CLK 2 \/\/Interrupt 0\r\n#define  KY_DT 12\r\n#define  KY_SW 11\r\n\/* Signal Generator Modes *\/\r\n#define MODE_OFF 0\r\n#define MODE_ON 1\r\n#define MODE_SWEEP 2\r\n\/* Range limitations *\/\r\n#define  FREQ_LIMIT_LOW 100\r\n#define FREQ_LIMIT_HIGH 30000000\r\n#define STEP_INDEX_LIMIT_LOW 0\t\t\/\/1Hz\r\n#define STEP_INDEX_LIMIT_HIGH 6\t\t\/\/1MHz\r\n\/* Serial related *\/\r\n#define SERIAL_BUFFER_SIZE 64\r\n#define SERIAL_BAUDRATE 9600\r\n#define SERIAL_ERROR_NONE 0\r\n#define SERIAL_ERROR_INVALID_COMMAND 1\r\n#define SERIAL_ERROR_BAD_PARAMETER 2\r\n#define SERIAL_ERROR_NOT_CONNECTED 3\r\n#define SERIAL_COMMAND_COUNT 20\r\n\/* EEPROM related *\/\r\n#define EEPROM_ADDRESS 0\t\/\/Arbitrary starting address\r\n#define EEPROM_VALID 900\t\/\/ Arbitary magic #\r\n\r\n\/*\r\n * Global Objects and Variables\r\n *\/\r\n \/* Objects *\/\r\nLiquidCrystal lcd(8, 9, 4, 5, 6, 7);\r\nAD9850B dds(AD9850B_CLOCK, AD9850B_LOAD, AD9850B_DATA, AD9850B_RESET);\r\n\r\n\/* Synthesizer Related Variables *\/\r\nunsigned long Frequency;      \/\/for Synthesizer\r\nunsigned long FrequencyBeforeSweep; \/\/remember old frequency\r\nunsigned long FrequencyStep;  \/\/for Synthesizer - frequency increment\r\nlong FrequencyCal;\t\t\/\/for calibrating clock in AD9850\r\nlong FrequencyOffset;\t\/\/for using dds with an IF\r\nboolean Repeating;\t\t\/\/forces slewed frequency incrementes\r\nint StepIndex;\t\t\t\/\/selects step value\r\nSweepState sweepStatus = ssOff;  \/\/defined in AD9850B.H\r\nboolean sweepPaused = false;\t \/\/temporarily pause a sweep\r\nunsigned long StepValues&#x5B;7] = {\r\n1, 10, 100, 1000, 10000, 100000, 1000000 };\r\nchar* StepDisplays&#x5B;] = {\r\n&quot;1H&quot;, &quot;10H&quot;, &quot;100H&quot;, &quot;1K&quot;, &quot;10K&quot;, &quot;100K&quot;, &quot;1M&quot; };\r\n\r\n\/* Mode and Menu Related Variables *\/\r\nint Mode = MODE_OFF;\r\nint ModeValues&#x5B;3] = { MODE_OFF, MODE_ON, MODE_SWEEP};\r\nchar*  ModeDisplays&#x5B;] = { &quot;Off&quot;, &quot;On&quot;, &quot;Sweep&quot; };\r\nboolean MenuOn = false;\r\nint tempMode;\t\/\/new mode to be selected from Menu\r\nint lastMode;\t\/\/remember last mode after changing\r\n\r\n\/* KY-040 Related Variables *\/\r\nvolatile boolean TurnDetected;  \/\/KY040 interrupt service routine - encoder is turning\r\nvolatile boolean Down;          \/\/KY040 interrupt service routine - turning direction\r\nconst int kyCLK = KY_CLK;\t\t\/\/(Interrupt 0)\r\nconst int kyDT = KY_DT;\r\nconst int kySW = KY_SW;\r\n\r\n\/* Serial Remote Operation Related Variables *\/\r\nchar serialBuffer&#x5B;SERIAL_BUFFER_SIZE];\r\nboolean serialConnected = false;\r\nboolean serialVerbose = false;\t\/\/enables verbose serial responses\r\nint serialIndex = 0;\r\n\/* These are the commands recognized on the serial port, either lower or upper case. Separately documented *\/\r\nchar* SerialCommandStrings&#x5B;] = { &quot;M&quot;, &quot;M?&quot;, &quot;F&quot;, &quot;F?&quot;, &quot;I&quot;, &quot;I?&quot;, &quot;C&quot;, &quot;C?&quot;, &quot;O&quot;, &quot;O?&quot;, &quot;S&quot;, &quot;L&quot;, &quot;WP&quot;, &quot;W&quot;, &quot;W?&quot; , &quot;U&quot;, &quot;D&quot;, &quot;V&quot;,  &quot;+&quot;, &quot;X&quot; };\r\nenum SerialCommandIDs\r\n{\r\n\tsetMode, getMode, setFreq, getFreq, setStep, getStep, setCal, getCal, setOffset, getOffset, setEEPROM, getEEPROM, setSweepParam, setSweep, getSweep, setUP, setDOWN, setVerbose, setStart, setExit\r\n};\r\n\r\n\/* EEPROM and Configuration Store *\/\r\nstruct swConfig {\r\n\tunsigned long csweeplo;\r\n\tunsigned long csweephi;\r\n\tunsigned long csweepstep;\r\n\tint csweepdelay;\r\n\tint csweepcount;\r\n};\r\nstruct sgConfig {\r\n\tint cvalid;\r\n\tunsigned long cfreq;\r\n\tint cstepindex;\r\n\tlong cfreqcal;\r\n\tlong cfreqoff;\r\n\tswConfig csweep;\r\n} config;\r\n\r\n\/*\r\n * Main Program\r\n *\/\r\nvoid setup()\r\n{\r\n\t\/* LCD 1602 Keypad *\/\r\n\tlcd.begin(16, 2);\r\n\tlcd.clear();\r\n\t\/* Encoder KY-040 *\/\r\n\tpinMode(kyCLK, INPUT);\r\n\tpinMode(kyDT, INPUT);\r\n\tattachInterrupt(0, isrEncoder, CHANGE);\r\n\tpinMode(kySW, INPUT);\r\n\t\/* Synthesizer module AD9850 *\/\r\n\tpinMode(AD9850B_CLOCK, OUTPUT);\r\n\tpinMode(AD9850B_LOAD, OUTPUT);\r\n\tpinMode(AD9850B_DATA, OUTPUT);\r\n\tpinMode(AD9850B_RESET, OUTPUT);\r\n\tdds.SetActive(true);\r\n\t\/* Startup *\/\r\n\tLoadConfig();\r\n\tdds.SetCalibration(FrequencyCal);\r\n\tMode = MODE_ON;\r\n\tMenuOn = false;\r\n\tSetFrequency(Frequency);\r\n\tShowStep(StepIndex);\r\n\tSerial.begin(SERIAL_BAUDRATE); \/\/ always waiting for serial connection\r\n\tdelay(100);\r\n\tShowMenu(Mode, MenuOn);\r\n}\r\n\r\nvoid loop()\r\n{\r\n\tint btn = BUTTON_NONE;\r\n\r\n\t\/*\r\n\t* Get the Inputs. Priority given to LCD Keypad buttons.\r\n\t* Turning encoder produces BUTTON_UP or BUTTON_DOWN\r\n\t* Pressing encoder switch produces BUTTON_SELECT or BUTTON_SELECTLONG\r\n\t*\/\r\n\tbtn = ReadLCDBtn();\r\n\tif (btn == BUTTON_NONE) {\r\n\t\tbtn = CheckEncoderTurning();\r\n\t}\r\n\tif (btn == BUTTON_NONE) {\r\n\t\tbtn = CheckEncoderSwitch();\r\n\t}\r\n\t\/* Deal with Menu activities, if any *\/\r\n\tbtn = ProcessMenu(btn);\r\n\t\/* Deal with Serial Port commands, if any *\/\r\n\tif (GetSerialData()) {\r\n\t\tint result = ProcessSerial(serialBuffer);\r\n\t\tswitch (result)\r\n\t\t{\r\n\t\tcase SERIAL_ERROR_NONE:\r\n\t\t\tSerial.println(&quot;OK&quot;);\r\n\t\t\tbreak;\r\n\t\tcase SERIAL_ERROR_INVALID_COMMAND:\r\n\t\t\tSerial.println(&quot;! Invalid Command&quot;);\r\n\t\t\tbreak;\r\n\t\tcase SERIAL_ERROR_BAD_PARAMETER:\r\n\t\t\tSerial.println(&quot;! Bad Parameter&quot;);\r\n\t\t\tbreak;\r\n\t\tcase SERIAL_ERROR_NOT_CONNECTED:\r\n\t\t\tSerial.println(&quot;! Serial Not Connected&quot;);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t\/*\r\n\t *\tAt this point, we are ready to respond to button presses\r\n\t *  and remote commands\r\n\t*\/\r\n\tif ((Mode == MODE_ON) &amp;&amp; (!serialConnected))\r\n\t{\r\n\t\tif ((lastMode == MODE_SWEEP) &amp;&amp; (FrequencyBeforeSweep != 0))\r\n\t\t{\r\n\t\t\tSetFrequency(FrequencyBeforeSweep);\r\n\t\t\tFrequencyBeforeSweep = 0;\r\n\t\t}\r\n\t\tif (!dds.GetActive()) {\r\n\t\t\tdds.SetActive(true);\r\n\t\t\tSetFrequency(Frequency);\r\n\t\t}\r\n\r\n\t\tswitch (btn)\r\n\t\t{\r\n\t\tcase BUTTON_LEFT:\r\n\t\t\tStepIndex--;\r\n\t\t\tif (StepIndex &lt; STEP_INDEX_LIMIT_LOW)\r\n\t\t\t\tStepIndex = STEP_INDEX_LIMIT_HIGH;\r\n\t\t\tShowStep(StepIndex);\r\n\t\t\tFrequencyStep = StepValues&#x5B;StepIndex];\r\n\t\t\tbreak;\r\n\r\n\t\tcase BUTTON_RIGHT:\r\n\t\t\tStepIndex++;\r\n\t\t\tif (StepIndex &gt; STEP_INDEX_LIMIT_HIGH)\r\n\t\t\t\tStepIndex = STEP_INDEX_LIMIT_LOW;\r\n\t\t\tShowStep(StepIndex);\r\n\t\t\tFrequencyStep = StepValues&#x5B;StepIndex];\r\n\t\t\tbreak;\r\n\r\n\t\tcase BUTTON_UP:\r\n\t\t\tif ((Frequency + FrequencyStep) &lt;= FREQ_LIMIT_HIGH)\r\n\t\t\t{\r\n\t\t\t\tFrequency += FrequencyStep;\r\n\t\t\t\tSetFrequency(Frequency);\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\r\n\t\tcase BUTTON_DOWN:\r\n\t\t\tif ((Frequency - FrequencyStep) &gt;= FREQ_LIMIT_LOW)\r\n\t\t\t{\r\n\t\t\t\tFrequency -= FrequencyStep;\r\n\t\t\t\tSetFrequency(Frequency);\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\r\n\t\tcase BUTTON_SELECT_LONG:\r\n\t\t\tRepeating = !Repeating;\r\n\t\t\tShowRepeating(Repeating);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\telse if (Mode == MODE_OFF)\r\n\t{\r\n\t\tif ((lastMode == MODE_SWEEP) &amp;&amp; (FrequencyBeforeSweep != 0))\r\n\t\t{\r\n\t\t\tSetFrequency(FrequencyBeforeSweep);\r\n\t\t\tFrequencyBeforeSweep = 0;\r\n\t\t}\r\n\t\tif (dds.GetActive())\r\n\t\t{\r\n\t\t\tdds.SetActive(false);\r\n\t\t}\r\n\r\n\t}\r\n\telse if (Mode == MODE_SWEEP)\r\n\t{\r\n\t\tif (sweepStatus == ssOff) sweepStatus = ProcessSweep(true);\r\n\t\tif (!serialConnected)\r\n\t\t{\r\n\t\t\tif (btn == BUTTON_UP)\r\n\t\t\t{\r\n\t\t\t\tif (sweepStatus == ssFinished)\r\n\t\t\t\t{\r\n\t\t\t\t\tsweepStatus = ssOff;\r\n\t\t\t\t}\r\n\t\t\t\telse if (sweepStatus == ssRunning)\r\n\t\t\t\t{\r\n\t\t\t\t\tsweepPaused = !sweepPaused;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (btn == BUTTON_UP_LONG)\r\n\t\t\t{\r\n\t\t\t\tsweepStatus = ssFinished;\r\n\t\t\t\tsweepPaused = false;\r\n\t\t\t\tShowSweepCounter(0);\r\n\t\t\t\tSetFrequency(FrequencyBeforeSweep);\r\n\t\t\t\tMode = lastMode;\r\n\t\t\t\tShowMenu(Mode, false);\t\t\t\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!sweepPaused)\r\n\t\t{\r\n\t\t\tswitch (sweepStatus)\r\n\t\t\t{\r\n\t\t\tcase ssOff:\r\n\t\t\t\tsweepStatus = ProcessSweep(true);\r\n\t\t\t\tbreak;\r\n\t\t\tcase ssRunning:\r\n\t\t\t\tsweepStatus = ProcessSweep(false);\r\n\t\t\t\tbreak;\r\n\t\t\tcase ssFinished:\r\n\t\t\t\tShowSweepCounter(0);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\t\t\t\r\n\t}\r\n\r\n\tdelay(10);\r\n}\r\n\r\n\/*\r\n * Support Functions - Buttons and Encoder Inputs\r\n *\/\r\nint ReadLCDBtn() {\r\n\t\/*\r\n\t * My current Button ADC values: N=1020, R=0, U=140, D=325, L=500, S=736\r\n\t * Make sure your check yours and modify buttonValues&#x5B;] if necessary!!!\r\n\t * Button values &gt; 0 are only returned when the button is released.\r\n\t * This function is checked at beginning of each program loop.\r\n\t * Trigger is used to measure elapsed time since button was pressed.\r\n\t * Triggered=true means waiting for button released\r\n\t *\r\n\t *\/\r\n\tstatic const int buttonValues&#x5B;6] = { 900, 50, 200, 400, 650, 850 };\r\n\tstatic int LastButton = BUTTON_NONE;\r\n\tstatic unsigned long Trigger = 0;\r\n\tstatic boolean Triggered = false;\r\n\tint Voltage;\r\n\tint Button;\r\n\tint i;\r\n\tboolean Release;\r\n\r\n\tVoltage = analogRead(LCD_BUTTON_PIN);\r\n\r\n\t\/* Find which button was pressed, if any *\/\r\n\tif (Voltage &gt; buttonValues&#x5B;0])\r\n\t{\r\n\t\tButton = BUTTON_NONE;\r\n\t\t\/\/ Return immediately if this is not a button release\r\n\t\tif (!Triggered) return Button;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tfor (i = 1; i &lt; 5; i++) if (Voltage &lt; buttonValues&#x5B;i]) break;\r\n\t\tButton = i;\r\n\t}\r\n\r\n\tif ((Repeating) &amp;&amp; ((Button == BUTTON_UP) || (Button == BUTTON_DOWN)))\r\n\t{\r\n\t\t\/* Return immediately if we are repeating frequency changes *\/\r\n\t\treturn Button;\r\n\t}\r\n\r\n\t\/* New button press triggered. Trigger=time press started *\/\r\n\tif ((Button &gt; BUTTON_NONE) &amp;&amp; (!Triggered)) {\r\n\t\tTriggered = true;\r\n\t\tTrigger = millis();\r\n\t}\r\n\t\/* Check to see if this loop is a button release *\/\r\n\tRelease = ((Button == BUTTON_NONE) &amp;&amp; (Triggered));\r\n\r\n\tif (Release)\r\n\t{\r\n\t\tButton = LastButton;\r\n\t\t\/\/ Each button press could be normal OR long\r\n\t\tif ((millis() - Trigger) &gt; PRESS_LONG) Button += PRESS_LONG_INC;\r\n\t\tTriggered = false;\r\n\t\tRelease = false;\r\n\t\tLastButton = BUTTON_NONE;\r\n\t\treturn Button;\r\n\t}\r\n\telse\r\n\t{\r\n\t\t\/\/ Otherwise just store the button ID for later release\r\n\t\tLastButton = Button;\r\n\t\treturn BUTTON_NONE;\r\n\t}\r\n}\r\n\r\nint CheckEncoderTurning() {\r\n\tint retval = BUTTON_NONE;\r\n\tif (TurnDetected)\t\/\/ set by isrEncoder\r\n\t{\r\n\t\tif (Down) {\r\n\t\t\tif (MenuOn)\r\n\t\t\t{\r\n\t\t\t\tretval = BUTTON_LEFT;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tretval = BUTTON_DOWN;\r\n\t\t\t}\r\n\r\n\t\t}\r\n\t\telse {\r\n\t\t\tif (MenuOn)\r\n\t\t\t{\r\n\t\t\t\tretval = BUTTON_RIGHT;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tretval = BUTTON_UP;\r\n\t\t\t}\r\n\r\n\t\t}\r\n\t\tTurnDetected = false;\r\n\t}\r\n\treturn retval;\r\n}\r\n\r\nint CheckEncoderSwitch() {\r\n\tint retval = BUTTON_NONE;\r\n\tstatic unsigned long startTime;\r\n\tif (digitalRead(kySW) == LOW)\r\n\t{\r\n\t\tstartTime = millis();\r\n\t\twhile (digitalRead(kySW) == LOW) {}  \/\/wait until released\r\n\t\tif ((millis() - startTime) &gt;= PRESS_LONG)\r\n\t\t{\r\n\t\t\tretval = BUTTON_SELECT_LONG;\t\/\/Toggle Repeating\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tretval = BUTTON_SELECT;\t\t\t\/\/Toggle MenuOn\r\n\t\t}\r\n\t}\r\n\treturn retval;\r\n}\r\n\r\nvoid isrEncoder() {\r\n\t\/* Interrupt Service Routine for Encoder turns *\/\r\n\tDown = (digitalRead(kyCLK) == digitalRead(kyDT));\r\n\tTurnDetected = true;\r\n}\r\n\r\n\/* Support Functions - Menu and Sweep *\/\r\nint ProcessMenu(int btn)\r\n{\r\n\tstatic int nextMode;\r\n\r\n\tif (btn == BUTTON_SELECT)\r\n\t{\r\n\t\tif (MenuOn)\r\n\t\t{\r\n\t\t\tlastMode = Mode;\r\n\t\t\tMode = nextMode; \/\/ This is where Mode changes\r\n\t\t\tMenuOn = false;\r\n\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tMenuOn = true; \/\/ This is where Menu activates\r\n\t\t\tnextMode = Mode;\r\n\t\t}\r\n\t\tShowMenu(Mode, MenuOn);\r\n\t\treturn BUTTON_NONE;\r\n\t}\r\n\t\/*\r\n\t* If Menu On, process all buttons as menu changes\r\n\t* else process buttons according to Mode\r\n\t*\/\r\n\tif (MenuOn)\r\n\t{\r\n\t\tswitch (btn)\r\n\t\t{\r\n\t\tcase BUTTON_RIGHT:\r\n\t\t\tnextMode++;\r\n\t\t\tif (nextMode &gt; 2) nextMode = 0;\r\n\t\t\tShowMenu(nextMode, MenuOn);\r\n\t\t\treturn BUTTON_NONE;\r\n\t\t\tbreak;\r\n\r\n\t\tcase BUTTON_LEFT:\r\n\t\t\tnextMode--;\r\n\t\t\tif (nextMode &lt; 0) nextMode = 2;\r\n\t\t\tShowMenu(nextMode, MenuOn);\r\n\t\t\treturn BUTTON_NONE;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\treturn btn;\r\n}\r\n\r\nSweepState ProcessSweep(bool reset)\r\n{\r\n\tstatic unsigned long lastClock;\r\n\tstatic unsigned long f = 0;\r\n\tstatic int counter = 0;\r\n\t\/* State machine to run the sweep *\/\r\n\tif (reset)\r\n\t{\r\n\t\tFrequencyBeforeSweep = Frequency;\r\n\t\tlastClock = millis();\r\n\t\tcounter = config.csweep.csweepcount;\r\n\t\tf = config.csweep.csweeplo;\r\n\t\tSetFrequency(f);\r\n\t\tShowSweepCounter(counter);\r\n\t\treturn ssRunning;\r\n\t}\r\n\r\n\tif ((millis() - lastClock) &gt;= config.csweep.csweepdelay)\r\n\t{\r\n\t\tf += config.csweep.csweepstep;\r\n\t\tif (f &gt; config.csweep.csweephi)\r\n\t\t{\r\n\t\t\tf = config.csweep.csweeplo;\r\n\t\t\tcounter--;\r\n\t\t\tShowSweepCounter(counter);\r\n\t\t}\r\n\t\tif (counter &gt; 0)\r\n\t\t{\r\n\t\t\tSetFrequency(f);\r\n\t\t\tlastClock = millis();\r\n\t\t\treturn ssRunning;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tShowSweepCounter(counter);\r\n\t\t\treturn ssFinished;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\/* Support Functions - EEPROM *\/\r\nvoid SaveConfig() {\r\n\t\/*\r\n\t * EEPROM_VALID is a magic number to tell the program\r\n\t * it is reading a previously stored configuration.\r\n\t * Uses EEPROMex.h library updateblock function.\r\n\t * Update prefered to write as it only stores values\r\n\t * which have actually changed. In this program version\r\n\t * SaveConfig is only called as a Serial Command.\r\n\t *\/\r\n\tconfig.cvalid = EEPROM_VALID;\r\n\tconfig.cfreq = Frequency;\r\n\tconfig.cstepindex = StepIndex;\r\n\tconfig.cfreqcal = FrequencyCal;\r\n\tconfig.cfreqoff = FrequencyOffset;\r\n\tEEPROM.updateBlock(EEPROM_ADDRESS, config);\r\n\tconfig.cvalid = 0;\r\n\t\/* See http:\/\/thijs.elenbaas.net\/2012\/07\/extended-eeprom-library-for-arduino\/ for more info *\/\r\n}\r\n\r\nvoid LoadConfig() {\r\n\t\/* Called every time Arduino resets, and can be called\r\n\t * as a Serial Command\r\n\t *\/\r\n\r\n\tEEPROM.readBlock(EEPROM_ADDRESS, config);\r\n\tif (config.cvalid != EEPROM_VALID)\r\n\t{\r\n\t\t\/* Set defaults if EEPROM config empty *\/\r\n\t\tconfig.cfreq = 10000000;\r\n\t\tconfig.cfreqcal = 0;\r\n\t\tconfig.cfreqoff = 0;\r\n\t\tconfig.cstepindex = 0;\r\n\t\tconfig.csweep.csweeplo = 10000000;\r\n\t\tconfig.csweep.csweephi = 10000100;\r\n\t\tconfig.csweep.csweepstep = 1;\r\n\t\tconfig.csweep.csweepdelay = 5;\r\n\t\tconfig.csweep.csweepcount = 5;\r\n\t}\r\n\t\/* Load Synthesizer variables *\/\r\n\tFrequency = config.cfreq;\r\n\tStepIndex = config.cstepindex;\r\n\tFrequencyStep = StepValues&#x5B;StepIndex];\r\n\tFrequencyCal = config.cfreqcal;\r\n\tFrequencyOffset = config.cfreqoff;\r\n}\r\n\r\n\/* Support Functions - LCD Display *\/\r\nvoid ShowStep(int index) {\r\n\tchar buf&#x5B;5] = &quot;&quot;;\r\n\tsprintf(buf, &quot;%4s&quot;, StepDisplays&#x5B;index]);\r\n\tlcd.setCursor(12, 0);\r\n\tlcd.print(buf);\r\n}\r\n\r\nvoid ShowFrequency(unsigned long f) {\r\n\tchar sepChar = ',';\r\n\tint bufSize = 11;\r\n\tchar buffer&#x5B;] = &quot;           &quot;;\r\n\tchar * buf = buffer;\r\n\r\n\tbuf += bufSize--;\r\n\t*(--buf) = '&#092;&#048;';\r\n\tif (f==0)\r\n\t{\r\n\t\t*(--buf) = '0';\r\n\t}\r\n\telse\r\n\t{\r\n\t\tuint8_t digCnt = 0;\r\n\r\n\t\t\/\/ general case: possibly multiple digits with thousands separators\r\n\t\twhile ((f != 0) &amp;&amp; bufSize)\r\n\t\t{\r\n\t\t\t\/\/ add a thousands separator every three digits\r\n\t\t\tif (sepChar &amp;&amp; (digCnt &gt;= 3) &amp;&amp; (bufSize &gt; 1))\r\n\t\t\t{\r\n\t\t\t\t*(--buf) = sepChar;\r\n\t\t\t\tbufSize--;\r\n\t\t\t\tdigCnt = 0;\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ add another digit to the buffer\r\n\t\t\t*(--buf) = (char)(f % 10) | '0';\r\n\t\t\tdigCnt++;\r\n\t\t\tbufSize--;\r\n\r\n\t\t\t\/\/ prepare for producing the next digit\r\n\t\t\tf \/= 10;\r\n\t\t}\r\n\t}\r\n\tlcd.setCursor(0, 0);\r\n\tlcd.print(buffer);\r\n\t\/* Formatting routine adapted from Don Kinzer post \r\n\t * http:\/\/www.avrfreaks.net\/forum\/formatting-big-numbers-commas *\/\r\n}\r\n\r\nvoid SetFrequency(unsigned long f) {\r\n\tunsigned long actualFrequency;\r\n\r\n\t\/* Limit the frequency range*\/\r\n\tif (f &lt; FREQ_LIMIT_LOW)\r\n\t{\r\n\t\tf = FREQ_LIMIT_LOW;\r\n\t}\r\n\telse if (f &gt; FREQ_LIMIT_HIGH)\r\n\t{\r\n\t\tf = FREQ_LIMIT_HIGH;\r\n\t}\r\n\r\n\tactualFrequency = f;\r\n\tif (FrequencyOffset != 0) actualFrequency += FrequencyOffset;\r\n\r\n\tFrequency = f;\r\n\tdds.SetFrequency(actualFrequency);\r\n\tShowFrequency(f);\r\n}\r\n\r\nvoid ShowMenu(int inx, boolean hilite) {\r\n\tchar buf&#x5B;8] = &quot;&quot;;\r\n\tif (hilite)\r\n\t{\r\n\t\tsprintf(buf, &quot;&lt;%-5s&gt;&quot;, ModeDisplays&#x5B;inx]);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tsprintf(buf, &quot; %-5s &quot;, ModeDisplays&#x5B;inx]);\r\n\t}\r\n\tlcd.setCursor(2, 1);\r\n\tlcd.print(buf);\r\n}\r\n\r\nvoid ShowRepeating(bool param)\r\n{\r\n\tlcd.setCursor(10, 0);\r\n\tif (param)\r\n\t{\r\n\t\tlcd.print( char(165));\/\/small square symbol\r\n\t}\r\n\telse\r\n\t\tlcd.print(' ');\r\n}\r\n\r\nvoid ShowSweepCounter(int param)\r\n{\r\n\tchar buf&#x5B;3] = &quot;&quot;;\r\n\tsprintf(buf, &quot;%02d&quot;, param);\r\n\tlcd.setCursor(14, 1);\r\n\tif (param &gt; 0)\r\n\t{\r\n\t\tlcd.print(buf);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tlcd.print(&quot;  &quot;);\r\n\t}\r\n}\r\n\r\n\/* Support Functions - Serial Communication *\/\r\nvoid SetSerialConnected(boolean param)\r\n{\r\n\tserialConnected = param;\r\n\tlcd.setCursor(0, 1);\r\n\tif (serialConnected)\r\n\t{\r\n\t\tlcd.print('R');\r\n\t}\r\n\telse\r\n\t{\r\n\t\tlcd.print(' ');\r\n\t}\r\n}\r\n\r\nboolean GetSerialData() {\r\n\tboolean lineFound = false;\r\n\tchar charBuffer = 0;\r\n\t\/* Checks for serial input every loop. Build incoming ASCII into\r\n\t * into serialBuffer until a line feed arrives. Then tell the\r\n\t * main loop a new line has been found for processing\r\n\t *\/\r\n\twhile (Serial.available() &gt; 0) {\r\n\t\tcharBuffer = Serial.read();\r\n\t\tif (charBuffer == '\\n') {\r\n\t\t\tserialBuffer&#x5B;serialIndex] = 0;\r\n\t\t\tlineFound = (serialIndex &gt; 0);\r\n\t\t\tserialIndex = 0;\r\n\t\t}\r\n\t\telse if (charBuffer == '\\r')\r\n\t\t{\r\n\t\t\t\/\/ ignore\t \r\n\t\t}\r\n\t\telse if (serialIndex &lt; SERIAL_BUFFER_SIZE &amp;&amp; lineFound == false) {\r\n\t\t\tserialBuffer&#x5B;serialIndex++] = toupper(charBuffer);\r\n\t\t}\r\n\t}\r\n\treturn lineFound;\r\n\t\/* See tutorials on serial command processing from http:\/\/www.thebreadboard.ca\/ for\r\n\texcellent material from Peter Oakes, which I found very helpful, including this routine *\/\r\n}\r\n\r\nint ProcessSerial(char * commandBuffer) {\r\n\tchar * Command;\r\n\tchar * Parameter;\r\n\tchar * pch;\r\n\tunsigned long ParamValue = 0;\r\n\tunsigned long f;\r\n\tint i;\r\n\tint serialCommandIndex = -1;\r\n\r\n\t\/\/* Parse serial string into Command and Param *\/\r\n\tpch = strchr(commandBuffer, '=');\r\n\tif (pch == NULL)\r\n\t{\r\n\t\tCommand = strtok(commandBuffer, &quot;&quot;);\r\n\t\tParameter = NULL;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tCommand = strtok(commandBuffer, &quot;=&quot;);\r\n\t\tParameter = strtok(NULL, &quot;=&quot;);\r\n\t}\r\n\r\n\tfor (i = 0; i &lt; SERIAL_COMMAND_COUNT; i++)\r\n\t{\r\n\t\tif (strcmp(Command, SerialCommandStrings&#x5B;i]) == 0) {\r\n\t\t\tserialCommandIndex = i;\r\n\t\t\tbreak;\r\n\t\t}\r\n\r\n\t}\r\n\tif (serialCommandIndex == -1) return SERIAL_ERROR_INVALID_COMMAND;\r\n\tif ((!serialConnected) &amp;&amp; (serialCommandIndex != setStart)) return SERIAL_ERROR_NOT_CONNECTED;\r\n\tif (CheckSerialParameter(serialCommandIndex, Parameter) != SERIAL_ERROR_NONE) return SERIAL_ERROR_BAD_PARAMETER;\r\n\tif (IsNumeric(Parameter))\r\n\t{\r\n\t\tParamValue = atol(Parameter);\r\n\t}\r\n\r\n\tswitch (serialCommandIndex)\r\n\t{\r\n\r\n\tcase setMode:\r\n\t\tif ((ParamValue &gt;= MODE_OFF) &amp;&amp; (ParamValue &lt;= MODE_SWEEP))\r\n\t\t{\r\n\t\t\tMode = ParamValue;\r\n\t\t\tShowMenu(Mode, false);\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase getMode:\r\n\t\tif (serialVerbose)\r\n\t\t{\r\n\t\t\tSerial.print(&quot;Mode = &quot;);\r\n\t\t\tSerial.println(ModeDisplays&#x5B;Mode]);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tSerial.print(&quot;M=&quot;);\r\n\t\t\tSerial.println(Mode);\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase setFreq:\r\n\t\tif ((ParamValue &gt;= FREQ_LIMIT_LOW) &amp;&amp; (ParamValue &lt;= FREQ_LIMIT_HIGH))\r\n\t\t{\r\n\t\t\tSetFrequency(ParamValue);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn SERIAL_ERROR_BAD_PARAMETER;\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase getFreq:\r\n\t\tif (serialVerbose)\r\n\t\t{\r\n\t\t\tSerial.print(&quot;Frequency = &quot;);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tSerial.print(&quot;F=&quot;);\r\n\t\t}\r\n\t\tSerial.println(Frequency);\r\n\t\tbreak;\r\n\r\n\tcase setStep:\r\n\t\tif (ParamValue &gt;= STEP_INDEX_LIMIT_LOW &amp;&amp; ParamValue &lt;= STEP_INDEX_LIMIT_HIGH)\r\n\t\t{\r\n\t\t\tStepIndex = ParamValue;\r\n\t\t\tFrequencyStep = StepValues&#x5B;StepIndex];\r\n\t\t\tShowStep(StepIndex);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn SERIAL_ERROR_BAD_PARAMETER;\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase getStep:\r\n\t\tif (serialVerbose)\r\n\t\t{\r\n\t\t\tSerial.print(&quot;Frequency Step &quot;);\r\n\t\t\tSerial.print(StepDisplays&#x5B;StepIndex]);\r\n\t\t\tSerial.print(&quot;  Index=&quot;);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tSerial.print(&quot;I=&quot;);\r\n\t\t}\r\n\t\tSerial.println(StepIndex);\r\n\t\tbreak;\r\n\r\n\tcase setCal:\r\n\t\tFrequencyCal = ParamValue;\r\n\t\tdds.SetCalibration(FrequencyCal);\r\n\t\tSetFrequency(Frequency);\r\n\t\tbreak;\r\n\r\n\tcase getCal:\r\n\t\tif (serialVerbose)\r\n\t\t{\r\n\t\t\tSerial.print(&quot;Cal = &quot;);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tSerial.print(&quot;C=&quot;);\r\n\t\t}\r\n\r\n\t\tSerial.println(FrequencyCal);\r\n\t\tbreak;\r\n\r\n\tcase setOffset:\r\n\t\tFrequencyOffset = ParamValue;\r\n\t\tSetFrequency(Frequency);\r\n\t\tbreak;\r\n\r\n\tcase getOffset:\r\n\t\tif (serialVerbose)\r\n\t\t{\r\n\t\t\tSerial.print(&quot;Offset = &quot;);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tSerial.print(&quot;O=&quot;);\r\n\t\t}\r\n\t\tSerial.println(FrequencyOffset);\r\n\t\tbreak;\r\n\r\n\tcase setEEPROM:\r\n\t\tSaveConfig();\r\n\t\tbreak;\r\n\r\n\tcase getEEPROM:\r\n\t\tLoadConfig();\r\n\t\tbreak;\r\n\r\n\tcase setSweepParam:\r\n\t\tif (!CheckSweepParameters(Parameter, true))\r\n\t\t{\r\n\t\t\treturn SERIAL_ERROR_BAD_PARAMETER;\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase setSweep:\r\n\t\tswitch (ParamValue)\r\n\t\t{\r\n\t\tcase 0:\r\n\t\t\tif (sweepStatus &gt; ssOff)\r\n\t\t\t{\r\n\t\t\t\tsweepStatus = ssFinished;\r\n\t\t\t\tsweepPaused = false;\r\n\t\t\t\tShowSweepCounter(0);\r\n\t\t\t\tSetFrequency(FrequencyBeforeSweep);\r\n\t\t\t\tMode = lastMode;\r\n\t\t\t\tShowMenu(Mode, false);\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 1:\r\n\t\t\tif ((sweepStatus == ssOff)  || (sweepStatus == ssFinished))\r\n\t\t\t{\r\n\t\t\t\tsweepStatus = ProcessSweep(true);\r\n\t\t\t\tlastMode = Mode;\r\n\t\t\t\tMode = MODE_SWEEP;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 2:\r\n\t\t\tif (sweepStatus == ssRunning) sweepPaused = true;\r\n\t\t\tbreak;\r\n\t\tcase 3:\r\n\t\t\tif (sweepStatus == ssRunning) sweepPaused = false;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase getSweep:\r\n\t\tif (serialVerbose)\r\n\t\t{\r\n\t\t\tSerial.print(&quot;Sweep Status = &quot;);\r\n\t\t\tswitch (sweepStatus)\r\n\t\t\t{\r\n\t\t\tcase ssOff:\r\n\t\t\t\tSerial.println(&quot;Off&quot;);\r\n\t\t\t\tbreak;\r\n\t\t\tcase ssRunning:\r\n\t\t\t\tSerial.println(&quot;Running&quot;);\r\n\t\t\t\tbreak;\r\n\t\t\tcase ssFinished:\r\n\t\t\t\tSerial.println(&quot;Finished&quot;);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tSerial.print(&quot;W=&quot;);\r\n\t\t\tSerial.println(sweepStatus);\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase setUP:\r\n\t\tFrequency += FrequencyStep;\r\n\t\tSetFrequency(Frequency);\r\n\t\tbreak;\r\n\r\n\tcase setDOWN:\r\n\t\tFrequency -= FrequencyStep;\r\n\t\tSetFrequency(Frequency);\r\n\t\tbreak;\r\n\r\n\tcase setVerbose:\r\n\t\tserialVerbose = (ParamValue == 1);\r\n\t\tif (serialVerbose)\r\n\t\t{\r\n\t\t\tSerial.println(&quot;Verbose On&quot;);\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase setStart:\r\n\t\tSetSerialConnected(true);\r\n\t\tif (serialVerbose) Serial.println(&quot;Serial ON&quot;);\r\n\t\tbreak;\r\n\r\n\tcase setExit:\r\n\t\tSetSerialConnected(false);\r\n\t\tif (serialVerbose) Serial.println(&quot;Serial OFF&quot;);\r\n\t\tbreak;\r\n\t}\r\n\r\n\treturn SERIAL_ERROR_NONE;\r\n}\r\n\r\n\/* Support Functions - Input Validation and Error Checking *\/\r\nboolean CheckSweepParameters(char * params, boolean StoreInConfig) {\r\n\tswConfig t = { 0,0,0,0,0 };\r\n\tint i = 1;\r\n\twhile (i &lt;= 5)\r\n\t{\r\n\t\tswitch (i)\t\/\/includes basic error checking\r\n\t\t{\r\n\t\tcase 1:\r\n\t\t\tt.csweeplo = atol(strtok(params, &quot;,&quot;));\r\n\t\t\tif ((t.csweeplo &lt; FREQ_LIMIT_LOW) || (t.csweeplo &gt; FREQ_LIMIT_HIGH)) return false;\r\n\t\t\tbreak;\r\n\t\tcase 2:\r\n\t\t\tt.csweephi = atol(strtok(NULL, &quot;,&quot;));\r\n\t\t\tif ((t.csweephi &lt;= t.csweeplo) || (t.csweephi &gt; FREQ_LIMIT_HIGH)) return false;\r\n\t\t\tbreak;\r\n\t\tcase 3:\r\n\t\t\tt.csweepstep = atol(strtok(NULL, &quot;,&quot;));\r\n\t\t\tif ((t.csweepstep &lt; 1) || (t.csweepstep &gt;(t.csweephi - t.csweeplo))) return false;\r\n\t\t\tbreak;\r\n\t\tcase 4:\r\n\t\t\tt.csweepdelay = atoi(strtok(NULL, &quot;,&quot;));\r\n\t\t\tif ((t.csweepdelay &lt; 1) || (t.csweepdelay &gt; 10)) return false;\r\n\t\t\tbreak;\r\n\t\tcase 5:\r\n\t\t\tt.csweepcount = atoi(strtok(NULL, &quot;,&quot;));\r\n\t\t\tif ((t.csweepcount &lt; 1) || (t.csweepcount &gt; 100)) return false;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\ti++;\r\n\t}\r\n\tif (StoreInConfig)\r\n\t{\r\n\t\tconfig.csweep = t;\r\n\t} \r\n\treturn true;\r\n}\r\n\r\nint CheckSerialParameter(int index, char * param)\r\n{\r\n\tint retval = SERIAL_ERROR_NONE;\r\n\r\n\tswitch (index)\r\n\t{\r\n\tcase setSweepParam:\r\n\t\t\/\/ errors caught later when command processed\r\n\t\tbreak;\r\n\tdefault:\r\n\t\tif (!IsNumeric(param)) retval = SERIAL_ERROR_BAD_PARAMETER;\r\n\t\tbreak;\r\n\t}\r\n\treturn retval;\r\n}\r\n\r\nboolean IsNumeric(const char * param)\r\n{\r\n\tint counter = 0;\r\n\twhile (*param)\r\n\t{\r\n\t\tif (!isDigit(*param)) {\r\n\t\t\tif ((counter == 0) &amp;&amp; (*param == '-')) {\r\n\t\t\t\t\/\/ ignore error if first character is &quot;-&quot;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t\tparam++;\r\n\t\tcounter++;\r\n\t}\r\n\treturn true;\r\n\t\/* Source: Peter Oakes. Modified to accept negative value. *\/\r\n}\r\n<\/pre>\n<h3>AD9850B Library<\/h3>\n<p>&nbsp;<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n\/\/ AD9850B.h\r\n\r\n#ifndef _AD9850B_h\r\n#define _AD9850B_h\r\n\r\n#if defined(ARDUINO) &amp;&amp; ARDUINO &gt;= 100\r\n\t#include &quot;arduino.h&quot;\r\n#else\r\n\t#include &quot;WProgram.h&quot;\r\n#endif\r\n\r\n#define DDS_CLOCK 125000000\r\n#define FREQ_FACTOR 4294967296\r\n\r\ntypedef enum SweepState { ssOff, ssRunning, ssFinished };\r\n\r\nclass AD9850B\r\n{\r\n private:\r\n\t int lineCLOCK;\r\n\t int lineLOAD;\r\n\t int lineDATA;\r\n\t int lineRESET;\r\n\t unsigned long _frequency = 10000000;\r\n\t double _calfactor = 0;\r\n\t boolean _active = false;\r\n\t void PowerDown();\r\n\t void Reset();\r\n\r\n public:\r\n\tAD9850B(int gW_CLK, int gFQ_UD, int gDATA, int gRESET);\r\n\tvoid Init();\r\n\tboolean GetActive();\r\n\tvoid SetActive(boolean param);\r\n\tunsigned long GetFrequency();\r\n\tvoid SetCalibration(long param);\r\n\tvoid SetFrequency(unsigned long param);\r\n\t\r\n};\r\n\r\nextern AD9850B dds;\r\n\r\n#endif\r\n\r\n\/*\r\nLibrary Class to control AD9850 Synthesizer Module AD9850.CPP\r\nJanuary 5, 2015\r\nSources of ideas:\r\n  http:\/\/webshed.org\/wiki\/AD9850_Arduino\r\n  https:\/\/github.com\/F4GOJ\/AD9850\r\n  https:\/\/github.com\/gonya707\/ARDUINO_AD9850_Library\r\n*\/ \r\n\r\n\r\n#include &quot;AD9850B.h&quot;\r\n\r\n#define pulseHigh(pin) {digitalWrite(pin, HIGH); delayMicroseconds(10); digitalWrite(pin, LOW); }\r\n\r\nAD9850B::AD9850B(int gW_CLK, int gFQ_UD, int gDATA, int gRESET)\r\n{\r\n\tlineCLOCK = gW_CLK;\r\n\tlineLOAD = gFQ_UD;\r\n\tlineDATA = gDATA;\r\n\tlineRESET = gRESET;\r\n}\r\n\r\nvoid AD9850B::PowerDown()\r\n{\r\n\tint PDword = 0x04;\r\n\tint i; \r\n\tpulseHigh(lineLOAD);\r\n\tfor (i = 0; i&lt;8; i++)\r\n\t{\r\n\t\tif ((PDword &gt;&gt; i) &amp; 0x01)\r\n\t\t\tdigitalWrite(lineDATA, HIGH);\r\n\t\telse\r\n\t\t\tdigitalWrite(lineDATA, LOW);\r\n\t\tpulseHigh(lineCLOCK);\r\n\t}\r\n\tpulseHigh(lineLOAD);\r\n\t_frequency = 0;\r\n}\r\n\r\nvoid AD9850B::Reset()\r\n{\r\n\tdigitalWrite(lineCLOCK, LOW);\r\n\tdigitalWrite(lineLOAD, LOW);\r\n\tpulseHigh(lineRESET);\r\n\tpulseHigh(lineCLOCK);\r\n\tdigitalWrite(lineDATA, LOW);\r\n\tpulseHigh(lineLOAD);\r\n}\r\n\r\nvoid AD9850B::Init()\r\n{\r\n\tdigitalWrite(lineRESET, LOW);\r\n\tdigitalWrite(lineCLOCK, LOW);\r\n\tdigitalWrite(lineLOAD, LOW);\r\n\tdigitalWrite(lineDATA, LOW);\r\n\r\n}\r\n\r\nboolean AD9850B::GetActive()\r\n{\r\n\treturn _active;\r\n}\r\n\r\nvoid AD9850B::SetActive(boolean param)\r\n{\r\n\tif (_active != param)\r\n\t{\r\n\t\tswitch (param)\r\n\t\t{\r\n\r\n\t\tcase true:\r\n\t\t\tInit();\r\n\t\t\tReset();\r\n\t\t\tSetFrequency(_frequency);\r\n\t\t\t_active = true;\r\n\t\t\tbreak;\r\n\r\n\t\tcase false:\r\n\t\t\tPowerDown();\r\n\t\t\t_active = false;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nunsigned long AD9850B::GetFrequency()\r\n{\r\n\treturn _frequency;\r\n}\r\n\r\nvoid AD9850B::SetFrequency(unsigned long param)\r\n{\r\n\tunsigned long tuning_word = (param * FREQ_FACTOR) \/ (DDS_CLOCK + _calfactor);\r\n\tdigitalWrite(lineLOAD, LOW);\r\n\tdigitalWrite(lineCLOCK, LOW);\r\n\tshiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word);\r\n\tshiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word &gt;&gt; 8);\r\n\tshiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word &gt;&gt; 16);\r\n\tshiftOut(lineDATA, lineCLOCK, LSBFIRST, tuning_word &gt;&gt; 24);\r\n\tshiftOut(lineDATA, lineCLOCK, LSBFIRST, 0x0); \r\n\tpulseHigh(lineLOAD);\r\n\t_frequency = param;\r\n}\r\n\r\nvoid AD9850B::SetCalibration(long param)\r\n{\r\n\t_calfactor = param;\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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.<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_share_on_mastodon":"0"},"categories":[11],"tags":[153,52],"series":[259],"class_list":["post-762","post","type-post","status-publish","format-standard","hentry","category-electronics-projects","tag-ad9850","tag-arduino","series-ad9850"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Signal Generator Control Software for AD9850 - Making It Up<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Signal Generator Control Software for AD9850 - Making It Up\" \/>\n<meta property=\"og:description\" content=\"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.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/\" \/>\n<meta property=\"og:site_name\" content=\"Making It Up\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/john.fallows.5\" \/>\n<meta property=\"article:published_time\" content=\"2016-01-06T16:12:01+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2017-02-10T03:38:16+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg\" \/>\n<meta name=\"author\" content=\"John VE6EY\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@VE6EY\" \/>\n<meta name=\"twitter:site\" content=\"@VE6EY\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"John VE6EY\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/\",\"url\":\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/\",\"name\":\"Signal Generator Control Software for AD9850 - Making It Up\",\"isPartOf\":{\"@id\":\"https:\/\/play.fallows.ca\/wp\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#primaryimage\"},\"thumbnailUrl\":\"http:\/\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg\",\"datePublished\":\"2016-01-06T16:12:01+00:00\",\"dateModified\":\"2017-02-10T03:38:16+00:00\",\"author\":{\"@id\":\"https:\/\/play.fallows.ca\/wp\/#\/schema\/person\/9750e0ab227030255d9806757525f945\"},\"breadcrumb\":{\"@id\":\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#primaryimage\",\"url\":\"https:\/\/i0.wp.com\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg?fit=536%2C304&ssl=1\",\"contentUrl\":\"https:\/\/i0.wp.com\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg?fit=536%2C304&ssl=1\",\"width\":536,\"height\":304},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/play.fallows.ca\/wp\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Projects\",\"item\":\"https:\/\/play.fallows.ca\/wp\/category\/projects\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Electronics Projects\",\"item\":\"https:\/\/play.fallows.ca\/wp\/category\/projects\/electronics-projects\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Signal Generator Control Software for AD9850\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/play.fallows.ca\/wp\/#website\",\"url\":\"https:\/\/play.fallows.ca\/wp\/\",\"name\":\"Making It Up\",\"description\":\"Enjoying Radio and Maker Hobbies\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/play.fallows.ca\/wp\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/play.fallows.ca\/wp\/#\/schema\/person\/9750e0ab227030255d9806757525f945\",\"name\":\"John VE6EY\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/play.fallows.ca\/wp\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e4048edfe09efff51033c48b4fb951e8ac0a4dc84a25c96b25e5ae9f5b7069a5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e4048edfe09efff51033c48b4fb951e8ac0a4dc84a25c96b25e5ae9f5b7069a5?s=96&d=mm&r=g\",\"caption\":\"John VE6EY\"},\"sameAs\":[\"https:\/\/x.com\/VE6EY\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Signal Generator Control Software for AD9850 - Making It Up","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/","og_locale":"en_US","og_type":"article","og_title":"Signal Generator Control Software for AD9850 - Making It Up","og_description":"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.","og_url":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/","og_site_name":"Making It Up","article_publisher":"https:\/\/www.facebook.com\/john.fallows.5","article_published_time":"2016-01-06T16:12:01+00:00","article_modified_time":"2017-02-10T03:38:16+00:00","og_image":[{"url":"http:\/\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg","type":"","width":"","height":""}],"author":"John VE6EY","twitter_card":"summary_large_image","twitter_creator":"@VE6EY","twitter_site":"@VE6EY","twitter_misc":{"Written by":"John VE6EY","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/","url":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/","name":"Signal Generator Control Software for AD9850 - Making It Up","isPartOf":{"@id":"https:\/\/play.fallows.ca\/wp\/#website"},"primaryImageOfPage":{"@id":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#primaryimage"},"image":{"@id":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#primaryimage"},"thumbnailUrl":"http:\/\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg","datePublished":"2016-01-06T16:12:01+00:00","dateModified":"2017-02-10T03:38:16+00:00","author":{"@id":"https:\/\/play.fallows.ca\/wp\/#\/schema\/person\/9750e0ab227030255d9806757525f945"},"breadcrumb":{"@id":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#primaryimage","url":"https:\/\/i0.wp.com\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg?fit=536%2C304&ssl=1","contentUrl":"https:\/\/i0.wp.com\/play.fallows.ca\/wp\/wp-content\/uploads\/sites\/4\/2016\/01\/SigGen-Control_thumb.jpg?fit=536%2C304&ssl=1","width":536,"height":304},{"@type":"BreadcrumbList","@id":"https:\/\/play.fallows.ca\/wp\/projects\/electronics-projects\/signal-generator-control-software-ad9850\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/play.fallows.ca\/wp\/"},{"@type":"ListItem","position":2,"name":"Projects","item":"https:\/\/play.fallows.ca\/wp\/category\/projects\/"},{"@type":"ListItem","position":3,"name":"Electronics Projects","item":"https:\/\/play.fallows.ca\/wp\/category\/projects\/electronics-projects\/"},{"@type":"ListItem","position":4,"name":"Signal Generator Control Software for AD9850"}]},{"@type":"WebSite","@id":"https:\/\/play.fallows.ca\/wp\/#website","url":"https:\/\/play.fallows.ca\/wp\/","name":"Making It Up","description":"Enjoying Radio and Maker Hobbies","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/play.fallows.ca\/wp\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/play.fallows.ca\/wp\/#\/schema\/person\/9750e0ab227030255d9806757525f945","name":"John VE6EY","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/play.fallows.ca\/wp\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e4048edfe09efff51033c48b4fb951e8ac0a4dc84a25c96b25e5ae9f5b7069a5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e4048edfe09efff51033c48b4fb951e8ac0a4dc84a25c96b25e5ae9f5b7069a5?s=96&d=mm&r=g","caption":"John VE6EY"},"sameAs":["https:\/\/x.com\/VE6EY"]}]}},"share_on_mastodon":{"url":"","error":""},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p6wNv5-ci","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/posts\/762","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/comments?post=762"}],"version-history":[{"count":4,"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/posts\/762\/revisions"}],"predecessor-version":[{"id":9953,"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/posts\/762\/revisions\/9953"}],"wp:attachment":[{"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/media?parent=762"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/categories?post=762"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/tags?post=762"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/play.fallows.ca\/wp\/wp-json\/wp\/v2\/series?post=762"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}