Build your own Function Generator with Arduino and AD9833 DDS Function Generator Module

Published  January 6, 2021   3
AD9833 and Arduino Based Function Generator

If you are an electronic enthusiast like me who wants to tweak around with different electronic circuits, having a decent Function Generator sometimes becomes mandatory. But owning one is a problem because such basic equipment can cost a fortune. Building your own test equipment is not only cheaper but also a great way to improve your knowledge.

So in this article, we are going to building a simple Signal Generator with Arduino and AD9833 DDS Function Generator Module which can produce sine, square, and triangle waves with a maximum frequency of 12 MHz at the output. And finally, we are going to test the output frequency with the help of our oscilloscope. 

We have previously built a Simple Sine Wave Generator, a Square Wave Generator, and a Triangle Wave Generator with the help of basic analog circuits. You can check those out if you are looking for some basic Waveform Generator Circuits. Also, if you want to build a cheaper Arduino Function generator without using the AD9833 Module, you can check out the DIY Arduino Waveform Generator Project.

What is a DDS Function Generator?

As the name implies, a function generator is a device that can output a specific waveform with a specific frequency upon setting. For example, consider you have an LC filter for which you want to test your output frequency response, you can easily do that with the help of a function generator. All you need to do is set your desired output frequency and waveform, then you can crank it down or up in order to test the response. This was just one example, you can do more things with it as the list goes on.

DDS stands for Direct Digital Synthesis. It is a type of waveform generator that uses digital to analog converters (DAC) to build a signal from the ground up. This method is specifically used to generate a sine wave. But the IC we are using can produce Square or Triangular wave signals. The Operations that happened inside a DDS chip are digital so it can switch the frequency very fast or it can switch from one signal to another very rapidly. This device has a fine frequency resolution with a broad frequency spectrum.

Understand the Working of the AD9833 Function Generator IC

At the heart of our project is the AD9833 Programmable Waveform Generator IC which is designed and developed by analog devices. It is a low power, programmable waveform generator capable of producing sine, triangular, and square wave with a maximum frequency of 12 MHz. It’s a very unique IC that is capable of altering the output frequency and phase with just a software program. It has a 3 wire SPI interface which is why communicating with this IC becomes very simple and easy. The functional block diagram of this IC is shown below.

AD9833 Function Generator IC Working

The working of this IC is very simple. If we take a look at the functional block diagram above, we will observe that we have a Phase Accumulator whose job is to store all the possible digital values of a sine wave, starting from 0 to 2π. Next, we have the SIN ROM whose job is to convert the phase information that can later be directly mapped into amplitude. The SIN ROM uses the digital phase information as an address to a lookup table and converts the phase information into amplitude. And lastly, we have a 10-bit digital to analog converter whose job is to receives the digital data from SIN ROM and convert it into the corresponding analog voltages, that is what we get from the output. At the output, we also have a switch that we can turn on or off with just a little software code. We will talk about that later in the article. The details you see above is a very stripped down version of what is happening inside the IC, and Most of the details you see above are taken from the AD9833 datasheet, you can also check it out for further information.

Components Required to Build the AD9833 based Function Generator

The components required to build the AD9833 based function generator is listed below, we designed this circuit with very generic components, which makes the replication process very easy.

  • Arduino Nano - 1
  • AD9833 DDS Function Generator - 1
  • 128 X 64 OLED Display - 1
  • Generic Rotary Encoder - 1
  • DC Barrel Jack - 1
  • LM7809 Voltage Regulator - 1
  • 470uF Capacitor - 1
  • 220uF Capacitor - 1
  • 104pF Capacitor - 1
  • 10K Resistor - 6
  • Tactile Switches - 4
  • Screw Terminal 5.04mm - 1
  • Female Header - 1
  • 12V Power Source - 1

AD9833 Based Function Generator - Schematic Diagram

The complete circuit diagram for the AD9833 and Arduino Based Function Generator is shown below.

AD9833 Based Function Generator Schematic Diagram

We are going to use the AD9833 with Arduino to generate our desired frequency. And in this section, we will explain all the details with the help of the schematic; let me give you a brief overview of what is happening with the circuit. Let's start with the AD9833 module. The AD9833 module is the function generator module and it’s connected with the Arduino according to the schematic. To power the circuit, we are using an LM7809 voltage regulator IC, with a decent decoupling capacitor, this is necessary because the supply noise can interfere with the output signal resulting in unwanted output. As always, the Arduino is working as the brain for this project. To display the set frequency and other valuable information, we have connected a 128 X 64 OLED display module. To change the frequency range, we are using three switches. The first one sets the frequency to Hz, the second one sets the output frequency to KHz, and the third one sets the frequency to MHz, we also have another button that can be used to enable or disable the output. Finally, we have the rotary encoder,  and we have to attach some pull-up resistor with it otherwise those switches will not work because we are checking the button press event on the pooling method. The rotary encoder is used to change the frequency and the tactile switch inside the rotary encoder is used to select the set waveform.

Arduino based Signal Generator

AD9833 Based Function Generator - Arduino Code

The complete code used in this project can be found at the bottom of this page. After adding the required header files and source files, you should be able to directly compile the Arduino file. You can download the ad9833 Arduino library and other libraries from the link given below or else you can use the board manager method to install the library.

The explanation of the code in the ino. file is as follows. First, we start by including all the required libraries. The library for the AD9833 DDS module is first followed by the library for OLED and the math library is required for some of our calculations.

#include <AD9833.h>   // LIbrary for AD9833 Module
#include <Wire.h> // Wire Library for OLED
#include <Adafruit_GFX.h> // Support Library for OLED
#include <Adafruit_SSD1306.h> // OLED library
#include <math.h> // Math Library

Next, we define all the necessary input and output pins for the buttons, switch, rotary encoder, and OLEDs.

#define SCREEN_WIDATA_PINH 128 // OLED display Width in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define SET_FREQUENCY_HZ A2 // Pushbutton To Set Frequency in Hz
#define SET_FREQUENCY_KHZ A3 // Pushbutton To Set Frequency in Khz
#define SET_FREQUENCY_MHZ A6 // Pushbutton To Set Frequency in Mhz
#define ENABLE_DISABLE_OUTPUT_PIN A7 // Pushbutton To Enable/Disable the Output
#define FNC_PIN 4 // Fsync Required by the AD9833 Module
#define CLK_PIN 8 // Clock Pin of the Encoder
#define DATA_PIN 7 // Data Pin of the Encoder
#define BTN_PIN 9 // Internal Push Button on the Encoder

Thereafter, we define all the necessary variables which are required in this code. First, we define an integer variable counter that will store the rotary encoder value. The next two variables clockPin and clockPinState store the pin statue that is required to understand the encoder direction. We have a time variable that holds the current timer-counter values, this variable is used for button debouncing. Next, we have an unsigned long variable moduleFrequency that holds the calculated frequency which is going to be applied. Next, we have the debounce delay. This delay can be adjusted as required. Next, we have three boolean variables set_frequency_hz, set_frequency_Khz, and set_frequency_Mhz these three variables are used to determine the current setting of the module. We will talk about it in more detail later in the article. Next, we have the variable which stores the status of the output waveform, the default output waveform is a sine wave. And finally, we have the encoder_btn_count variable that holds the encoder-button count which is used to set the output waveform.

int counter = 1; // This Counter value will increase or decrease if when the rotary encoder is turned
int clockPin; // Placeholder for pin status used by the rotary encoder
int clockPinState; // Placeholder for pin status used by the rotary encoder
unsigned long time = 0; // Used for debouncing
unsigned long moduleFrequency; // used to set output frequency
long debounce = 220; // Debounce delay
bool btn_state; // used to enable disable output of the AD98333 Module
bool set_frequency_hz = 1; // Defult frequency of the AD9833 Module
bool set_frequency_khz;
bool set_frequency_mhz;
String waveSelect = "SIN"; // Startup waveform of the module
int encoder_btn_count = 0; // used to check encoder button press
Next, we have our two objects one is for the OLED display and another one is for the AD9833 module.
Adafruit_SSD1306 display(SCREEN_WIDATA_PINH, SCREEN_HEIGHT, &Wire, -1);
AD9833 gen(FNC_PIN);

Next, we have our setup() function, in that setup function, we start with enabling the Serial for debugging. We initialize the AD9833 module with the help of the begin() method. Next, we set all the assigned rotary encoder pins as Input. And we store the value of the clock pin in the clockPinState variable, this is a necessary step for the rotary encoder.

Next, we set all the button pins as input and enable the OLED display with the help of display.begin() method, and we also check for any errors with an if statement. When that is done, we clear the display and print a startup splash screen, we add a delay of 2 seconds which is also the delay for the splash screen, and finally, we call the update_display() function which clears the screen and updates the display once again. The details of the update_display() method will be discussed later in the article.

void setup() {
  Serial.begin(9600); // Enable Serial @9600 baud
  gen.Begin(); // This MUST be the first command after declaring the AD9833 object
  pinMode(CLK_PIN, INPUT); // Setting Pins as input
  pinMode(DATA_PIN, INPUT);
  pinMode(BTN_PIN, INPUT_PULLUP);
  clockPinState = digitalRead(CLK_PIN);
  pinMode(SET_FREQUENCY_HZ, INPUT);// Setting Pins as input
  pinMode(SET_FREQUENCY_KHZ, INPUT);
  pinMode(SET_FREQUENCY_MHZ, INPUT);
  pinMode(ENABLE_DISABLE_OUTPUT_PIN, INPUT);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  display.clearDisplay(); // Clear the Screen
  display.setTextSize(2); // Set text Size
  display.setTextColor(WHITE); // set LCD Colour
  display.setCursor(30, 0); // Set Cursor Position
  display.println("AD9833"); // Print the this Text
  display.setCursor(17, 20);  // Set Cursor Position
  display.println("Function"); // Print the this Text
  display.setCursor(13, 40); // Set Cursor Position
  display.println("Generator"); // Print the this Text
  display.display(); // Update the Display
  delay(2000); // Delay of 2 SEC
  update_display(); // Call update_display Function
}

Next, we have our loop() function, all the major functionalities are written in the loop section.

First, we read the Clock pin of the Rotary encoder and store it in the clockPin variable which we have declared earlier. Next, in the if statement, we check if the previous value of the pin and the current value of the pin is similar or not and we also check the current value of the pin. If it’s all true, we check for the data pin, if true that means the encoder is rotating counterclockwise and we decrement the counter value with the help of counter-- command. Else we increment the counter value with counter ++ command. Finally, we put another if statement to set the minimum value to 1. Next, we update the clockPinState with the current clockPin value for future use.

void loop()
{
  clockPin = digitalRead(CLK_PIN);
  if (clockPin != clockPinState  && clockPin == 1) {
    if (digitalRead(DATA_PIN) != clockPin) {
      counter --;
    }
    else {
      counter ++;// Encoder is rotating CW so increment
    }
    if (counter < 1 ) counter = 1;
    Serial.println(counter);
    update_display();
  }

Next, we have our code to detect a button press. In this section, we have detected the button inside the encoder with the help of some nested if statements, if (digitalRead(BTN_PIN) == LOW && millis() - time > denounce), in this statement, we first check if the button pin is low or not, if it’s low, then it’s pressed. Then again we check the timer value with the debounce delay, if both the statements are true, then we declare it a successful button press action if so we increment the encoder_btn_count value. Next, we declare another if statement to set the maximum counter value to 2, we need it because we are using it to set the output waveform. The consecutive three if statements do that, if the value is zero, sine waveform is selected, if it’s one, it's a square wave, and if the value is 2, it's a triangular wave. In all three of these if statements, we update the display with the update_display() function. And finally, we update the time variable with the current timer counter value.

//If we detect a LOW signal, the button is pressed
  if ( digitalRead(BTN_PIN) == LOW && millis() - time > debounce) {
    encoder_btn_count++; // Increment the values
    if (encoder_btn_count > 2) // if value is greater than 2 reset it to 0
    {
      encoder_btn_count = 0;
    }
    if (encoder_btn_count == 0) { // if the value is 0 sine wave is selected
      waveSelect = "SIN"; // update the string variable with sin value
      update_display(); // update the display
    }
    if (encoder_btn_count == 1) { // if the value is 1 square wave is selected
      waveSelect = "SQR"; // update the string variable with SQR value
      update_display(); // update the display
    }
    if (encoder_btn_count == 2) { // if the value is 1 Triangular wave is selected
      waveSelect = "TRI";  // update the string variable with TRI value
      update_display();// update the display
    }
    time = millis(); // update the time variable
  }

Next, we define all necessary code that is required to set up all the buttons with a debounce delay. As the buttons are connected to the analog pins of the Arduino, we are using the analog read command to identify a button press if the analog read value reaches below 30, then we detect its a successful button press, and we wait for 200 ms to check if it is an actual button press or a noise only. If this statement is true, we assign the boolean variables with values that are used to set the Hz, Khz, and Mhz values of the function generator. Next, we update the display and update the time variable. We do that for all the four buttons connected with the Arduino.

  if (analogRead(SET_FREQUENCY_HZ) < 30 && millis() - time > debounce) {   
    set_frequency_hz = 1;    //update boolean values
    set_frequency_khz = 0;
    set_frequency_mhz = 0;
    update_display();// update the display
    time = millis();// update the time variable
  }
  if (analogRead(SET_FREQUENCY_KHZ) < 30 && millis() - time > debounce){
    set_frequency_hz = 0;   //update boolean values
    set_frequency_khz = 1;
    set_frequency_mhz = 0;
    moduleFrequency = counter * 1000;
    update_display();// update the display
    time = millis();// update the time variable
  }
  if (analogRead(SET_FREQUENCY_MHZ) < 30 && millis() - time > debounce ) { // check analog pin with debounce delay
    set_frequency_hz = 0;  //update boolean values
    set_frequency_khz = 0;
    set_frequency_mhz = 1;
    moduleFrequency = counter * 1000000;
    update_display();// update the display
    time = millis();// update the time variable
  }
  if (analogRead(ENABLE_DISABLE_OUTPUT_PIN) < 30 && millis() - time > debounce ) {// check analog pin with debounce delay
    btn_state = ! btn_state; // Invert the button state
    gen.EnableOutput(btn_state); // Enable / Disable output of the function generator depending on button state
    update_display();// update the display
    time = millis();// update the time variable
  }
}

Finally, we have our update_display() function. In this function, we did a lot more than just updating this display because a certain portion of the display cannot be updated in an OLED. To update it, you have to repaint it with new values. This makes the coding process a lot more difficult.

Inside this function, we start with clearing the display. Next, we set our required text size. Thereafter, we set our cursor and printed Function Generator with the display.println("Function Function"); command. We again set the text size to 2, and the cursor to (0,20 ) with the help of display.setCursor(0, 20) function. 

This is where we print the information for what wave it is.

 display.clearDisplay(); // FIrst clear the display
  display.setTextSize(1); //set text Size
  display.setCursor(10, 0); // Set cursor position
  display.println("Function Generator"); //print the text
  display.setTextSize(2);//set text Size
  display.setCursor(0, 20);//Set cursor position

Next, we check the boolean variables for frequency details and update the value in the moduleFrequency variable. We do this for Hz, kHz, and MHz values. Next, we check the waveSelect variable and identify which wave is selected. Now, we have the values to set the wave type and frequency.

if (set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0 ) { // check if button for setting the frequency in Hz is pressed
    moduleFrequency = counter; //update the moduleFrequency variable with current counter value
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0 ) { // check if button for setting the frequency in KHz is pressed
    moduleFrequency = counter * 1000;//update the moduleFrequency variable with current counter value but we multiply 1000 to set it on KHZ
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1) { // check if button for setting the frequency in MHz is pressed
    moduleFrequency = counter * 1000000;
    if (moduleFrequency > 12000000)
    {
      moduleFrequency = 12000000; // do not let the frequency to be grater that 12Mhz
      counter = 12;
    }
  }

  if (waveSelect == "SIN") { // Sine wave is selected
    display.println("SIN");
    gen.ApplySignal(SINE_WAVE, REG0, moduleFrequency);
    Serial.println(moduleFrequency);
  }
  if (waveSelect == "SQR") {// Sqr wave is selected
    display.println("SQR");
    gen.ApplySignal(SQUARE_WAVE, REG0, moduleFrequency);
    Serial.println(moduleFrequency);
  }
  if (waveSelect == "TRI" ) {// Tri wave is selected
    display.println("TRI");
    gen.ApplySignal(TRIANGLE_WAVE, REG0, moduleFrequency); // update the AD9833 module.
    Serial.println(moduleFrequency);
  }

We set the cursor again and update the counter values. Again we check the boolean to update the frequency range on the display, we have to do this because the working principle of the OLED is very weird.

display.setCursor(45, 20);
  display.println(counter); // print the counter information on the display.
  if (set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0 ) {
    display.setCursor(90, 20);
    display.println("Hz"); // print Hz on the display
    display.display(); // when all set update the display
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0 ) {
    display.setCursor(90, 20);
    display.println("Khz");
    display.display(); // when all set update the display
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1) {
    display.setCursor(90, 20);
    display.println("Mhz");
    display.display(); // when all set update the display
  }

Next, we check the button press variable to print output on / output off to the OLED. Again this needs to be done because of the OLED module.

 if (btn_state) { 
    display.setTextSize(1);
    display.setCursor(65, 45);
    display.print("Output ON"); // print output on to the display
    display.display();
    display.setTextSize(2);
  }
  else {
    display.setTextSize(1);
    display.setCursor(65, 45);
    display.print("Output OFF"); // print output off to the display
    display.display();
    display.setTextSize(2);
  }

This marks the end of our coding process. If you are confused at this point, you can check the comments in the code for further understanding.

Testing the AD9833 Based Function Generator

AD9833 Based Function Generator

To test the circuit, the above setup is used. As you can see, we have connected a 12V DC power adapter to the DC barrel jack and we have connected the Hantek Oscilloscope to the output of the circuit. We have also connected the oscilloscope to the laptop to visualize and measure the output frequency.

Once this was done, we set the output frequency to 5Khz with the help of the rotary encoder and we test the output sine wave and sure enough, it is a 5Khz sine wave at the output.

AD9833 Based Function Generator Working

Next, we have changed the output waveform to a triangular wave but the frequency stayed the same, the output waveform is shown below.

Testing AD9833 Based Function Generator

Then we changed the output to a square wave and observed the output, and it was a perfect square wave.

AD9833 Function Generator Working

We also altered the frequency ranges and tested the output, and it was working well.

Further Enhancements

This circuit is only a proof of concept and needs further enhancements. First, we need a good quality PCB and some good quality BNC connector for the output otherwise we cannot obtain a higher frequency. The amplitude of the module is very low, so to enhance that, we need some op-amp circuits to amplify the output voltage. A potentiometer can be connected in order to vary the output amplitude. A switch for offsetting the signal can be connected; this is also a must-have feature. And further, the code needs a lot of improvement as it's a little buggy. Finally, OLED displays need to be changed otherwise it's impossible to write easily understandable code.

This marks the end of this tutorial, I hope you liked the article and learned something new. If you have any questions regarding the article, you can leave them in the comment section below or you can use our Electronics Forum

Code

#include <AD9833.h>   // LIbrary for AD9833 Module
#include <Wire.h> // Wire Library for OLED
#include <Adafruit_GFX.h> // Support Library for OLED
#include <Adafruit_SSD1306.h> // OLED library
#include <math.h> // Math Library
#define SCREEN_WIDATA_PINH 128 // OLED display Width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define SET_FREQUENCY_HZ A2 // Pushbutton To Set Frequency In Hz
#define SET_FREQUENCY_KHZ A3 // Pushbutton To Set Frequency In Khz
#define SET_FREQUENCY_MHZ A6 // Pushbutton To Set Frequency In Mhz
#define ENABLE_DISABLE_OUTPUT_PIN A7 // Pushbutton To Enable / Disable the Output
#define FNC_PIN 4 // Fsync Required by the AD9833 Module
#define CLK_PIN 8 // Clock Pin of the Encoder
#define DATA_PIN 7 // Data Pin of the Encoder
#define BTN_PIN 9 // Internal Push Button on the Encoder
int counter = 1; // This Counter value will increas or decreas if when the rotarty encoder is turned
int clockPin; // Placeholder por pin status used by the rotary encoder
int clockPinState; // Placeholder por pin status used by the rotary encoder
unsigned long time = 0; // Used for debouncing
unsigned long moduleFrequency; // used to set output frequency
In the
long debounce = 220; // Debounce delay
bool btn_state; // used to enable disable output of the AD98333 Module
bool set_frequency_hz = 1; // Defult frequency of the AD9833 Module
bool set_frequency_khz;
bool set_frequency_mhz;
String waveSelect = "SIN"; // Startup waveform of the module
int encoder_btn_count = 0; // used to check encoder button press
Adafruit_SSD1306 display(SCREEN_WIDATA_PINH, SCREEN_HEIGHT, &Wire, -1);
AD9833 gen(FNC_PIN);
void setup() {
  Serial.begin(9600);
  gen.Begin(); // This MUST be the first command after declaring the AD9833 object
  pinMode(CLK_PIN, INPUT);
  pinMode(DATA_PIN, INPUT);
  pinMode(BTN_PIN, INPUT_PULLUP);
  clockPinState = digitalRead(CLK_PIN);
  pinMode(SET_FREQUENCY_HZ, INPUT);
  pinMode(SET_FREQUENCY_KHZ, INPUT);
  pinMode(SET_FREQUENCY_MHZ, INPUT);
  pinMode(ENABLE_DISABLE_OUTPUT_PIN, INPUT);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  display.clearDisplay(); // Clear the Screen
  display.setTextSize(2); // Set text Size
  display.setTextColor(WHITE); // set LCD Colour
  display.setCursor(30, 0); // Set Cursor Position
  display.println("AD9833"); // Print the this Text
  display.setCursor(17, 20);  // Set Cursor Position
  display.println("Function"); // Print the this Text
  display.setCursor(13, 40); // Set Cursor Position
  display.println("Generator"); // Print the this Text
  display.display(); // Update the Display
  delay(2000); // Delay of 2 SEC
  update_display(); // Call update_display Function
}
void loop()
{
  clockPin = digitalRead(CLK_PIN);
  if (clockPin != clockPinState  && clockPin == 1) {
    if (digitalRead(DATA_PIN) != clockPin) {
      counter --;
    }
    else {
      counter ++;// Encoder is rotating CW so increment
    }
    if (counter < 1 ) counter = 1;
    Serial.println(counter);
    update_display();
  }
  clockPinState = clockPin; // Remember last CLK_PIN state
  //If we detect LOW signal, button is pressed
  if ( digitalRead(BTN_PIN) == LOW && millis() - time > debounce) {
    encoder_btn_count++; // Increment the values
    if (encoder_btn_count > 2) // if value is grater that 2 reset it to 0
    {
      encoder_btn_count = 0;
    }
    if (encoder_btn_count == 0) { // if the value is 0 sine wave is selected
      waveSelect = "SIN"; // update the string variable with sin value
      update_display(); // update the display
    }
    if (encoder_btn_count == 1) { // if the value is 1 square wave is selected
      waveSelect = "SQR"; // update the string variable with SQR value
      update_display(); // update the display
    }
    if (encoder_btn_count == 2) { // if the value is 1 Triangular wave is selected
      waveSelect = "TRI";  // update the string variable with TRI value
      update_display();// update the display
    }
    time = millis(); // update the time variable
  }
  // Check buttton press action with analogread method
  // Put in a slight delay to help debounce the reading
  if (analogRead(SET_FREQUENCY_HZ) < 30 && millis() - time > debounce) { // check analogpin with debounce delay
    //update boolean  values
    set_frequency_hz = 1;
    set_frequency_khz = 0;
    set_frequency_mhz = 0;
    update_display();// update the display
    time = millis();// update the time variable
  }
  if (analogRead(SET_FREQUENCY_KHZ) < 30 && millis() - time > debounce) { // check analogpin with debounce delay
    //update boolean  values
    set_frequency_hz = 0;
    set_frequency_khz = 1;
    set_frequency_mhz = 0;
    moduleFrequency = counter * 1000;
    update_display();// update the display
    time = millis();// update the time variable
  }
  if (analogRead(SET_FREQUENCY_MHZ) < 30 && millis() - time > debounce ) { // check analogpin with debounce delay
    //update boolean  values
    set_frequency_hz = 0;
    set_frequency_khz = 0;
    set_frequency_mhz = 1;
    moduleFrequency = counter * 1000000;
    update_display();// update the display
    time = millis();// update the time variable
  }
  if (analogRead(ENABLE_DISABLE_OUTPUT_PIN) < 30 && millis() - time > debounce ) {// check analogpin with debounce delay
    btn_state = ! btn_state; // Invert the button state
    gen.EnableOutput(btn_state); // Enable / Disable output of the function generator depending on button state
    update_display();// update the display

    time = millis();// update the time variable
  }
}
void update_display()
{
  display.clearDisplay(); // FIrst clear the display
  display.setTextSize(1); //set text Size
  display.setCursor(10, 0); // Set cursor position
  display.println("Function Generator"); //print the text
  display.setTextSize(2);//set text Size
  display.setCursor(0, 20);//Set cursor position
  if (set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0 ) { // check if button for setting the frequency in Hz is pressed
    moduleFrequency = counter; //updayte teh moduleFrequency variable with current counter value
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0 ) { // check if button for setting the frequency in KHz is pressed
    moduleFrequency = counter * 1000;//updayte teh moduleFrequency variable with current counter value but we multiply 1000 to set it on KHZ
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1) { // check if button for setting the frequency in MHz is pressed
    moduleFrequency = counter * 1000000;
    if (moduleFrequency > 12000000)
    {
      moduleFrequency = 12000000; // do not let the frequency to be grater that 12Mhz
      counter = 12;
    }
  }
  if (waveSelect == "SIN") { // Sine wave is selected
    display.println("SIN");
    gen.ApplySignal(SINE_WAVE, REG0, moduleFrequency);
    Serial.println(moduleFrequency);
  }
  if (waveSelect == "SQR") {// Sqr wave is selected
    display.println("SQR");
    gen.ApplySignal(SQUARE_WAVE, REG0, moduleFrequency);
    Serial.println(moduleFrequency);
  }
  if (waveSelect == "TRI" ) {// Tri wave is selected
    display.println("TRI");
    gen.ApplySignal(TRIANGLE_WAVE, REG0, moduleFrequency); // update the AD9833 module.
    Serial.println(moduleFrequency);
  }
  display.setCursor(45, 20);
  display.println(counter); // print the counter information on teh display.
  if (set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0 ) {
    display.setCursor(90, 20);
    display.println("Hz"); // print Hz on the display
    display.display(); // when all set update the display
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0 ) {
    display.setCursor(90, 20);
    display.println("Khz");
    display.display(); // when all set update the display
  }
  if (set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1) {
    display.setCursor(90, 20);
    display.println("Mhz");
    display.display(); // when all set update the display
  }
  if (btn_state) { 
    display.setTextSize(1);
    display.setCursor(65, 45);
    display.print("Output ON"); // print output on to the display
    display.display();
    display.setTextSize(2);
  }
  else {
    display.setTextSize(1);
    display.setCursor(65, 45);
    display.print("Output OFF"); // print output off to the display
    display.display();
    display.setTextSize(2);
  }
}

Video

Have any question realated to this Article?

Ask Our Community Members

Comments

Hi Debashis Das. I have been looking at your project so I thought I would use the code and make the Function Generator. It works as far as the display tells me everything it should but I cannot get the AD9833 to give an output. I have checked the 9833 with another bit of software so I know it works. I cannot see in your code how it talks to the AD9833. Is it serial? Could you please explain?

Thanks

Martin

Hello, thanks for the article.

There is an error in the schematic, the data and clk pins of the AD9833 are exchanged. The AD9833_DATA tag must be connected to pin D11 and the AD9833_CLK tag to pin D13 of the Arduino to code work.