Yeat another AD9833 Function Generator

1. Features

The function generator is based on Direct Digital Synthesis (DDS) realized with an AD9833 module which also incorporates an output amplifier AD8051 whose output amplitude is controlled by a digital potentiometer MCP41010.

An ESP32 microcontroller controls all the functions of the DDS module and also realizes the user interface comprising an I2C LCD and a rotary encoder with axial pushbutton.

The various functions of the generator are choosen by clockwise (cw) or counterclockwise (ccw) rotating the rotary encoders shaft and by clicking (click, or longclick) its pusbutton.

2. Specification

Frequency
Frequency Range
0.1 Hz - 9'999'999.9 Hz
Minimum Frequency Increment
0.1 Hz
Waveforms
Sine, Triangle, Square
Output Voltage Range
0.0 - 3.2 Vpp (Sine, Triangle)
adjustable with Digital Potentiometer in 255 Steps
3.2 Vpp (Square)


Sweep
min. Start Frequency
0.1 Hz
max Stop Frequency
9‘999‘999.9 Hz
min. Frequency Step
0.1 Hz
max. Frequency Step
99‘999.9 Hz
min. Frequency Step Duration
1 ms
max Frequency Step Duration
99‘999 ms


3. Parts

Parts needed for the AD9833 Function Generator (except breadboard)
parts

4. Wiring

 
                     USB                             .----------------. 
               .-----I I-----.                  27 --o CLK            |
               o 3V3 `¨´ Vin o-- LCD Vcc        26 --o DT   Rotary    |
               o GND     GND o-- LCD GND        25 --o SW   Encoder   |
               o 15       13 o                3.3V --o +    with      | 
     DPOT CS --o 2        12 o                 GND --o GND  Pusbutton | 
               o 4        14 o                       `----------------´    
               o RX2      27 o                       .--------------------.  
               o TX2      26 o                   2 --o CS   DDS           |
   AD9833 CS --o 5        25 o                  23 --o DAT  Breakout Board| 
  AD9833 CLK --o 18       33 o                  18 --o CLK  with          |
               o 19       32 o                   5 --o FSY  AD9833 DDS    |
     LCD SDA --o 21       35 o                 GND --o GND  MCP41010 DPOT |
               o RX0      34 o                3.3V --o Vcc  AD8051 OPAMP  |
               o TX0      VN o                       `--------------------´    
     LCD SCL --o 22       VP o                       .--------------.  
  AD9833 DAT --o 23       EN o                 GND --o GND  4 x 20  |
               `-------------´                 Vin --o VCC  Liquid  |
               ESP32 DevKit V1                  21 --o SDA  Crystal |
                                                22 --o SCL  Display | 
                                                     `--------------´

		

5. User Interface and Operation

Initial Display

On power up Screen-0 is displayed. The settings of both channels are shown. The active channel is marked, here as ⟨Chan 0⟩. We see the selected wave forms, the settings of the digital potentiometer and and the actual frequencies of each channel.

Channel 0 is active
Screen-0 : Channel 0 is active and its signal is fed to output
LongClick toggles output signal on/off.
Click switchs to other channel and back.
Channel 1 is active
Screen-0 : Channel 1 is active and its signal is fed to output
Channel 1 is set to triangle wave form. The output amplitude is set to 65 units of the digital potentiometer corresponding to aproximatly 1000 mVpp and the frequency is set to 554.4 Hz.
Rotating CW/CCW navigates to Screen-1, Screen-2, Screen-3 or backwards.

Channelsettings

The values of both channels can be set independently. The set values are not remembered between power ups. That is left for further enhacement and could be done by storing the settings in ESP32 flash memory.

Settings chan0
Screen-1 : Settings of channel 0
Longclick enters or quits Input Mode. This is indicated by a blinking cursor on Mode.
Click moves blinking cursor from line to line.
Rotating CW/CCW changes values.
Settings chan1
Screen-2 : Settings of channel 1
The cursor blinks on 10 kHz digit to change frequency by rotating encoder shaft.

Sweep

The sweep frequency starts at the frequency set for channel 0 and stops at the frequency set for channel 1. The possible modes are shown below.

Sweep rising slope
Screen-3 : Sweep settings – rising slope
Sweep : Is stopped
Mode : Rising slope from freq of channel 0 to freq of channel 1, sweep is executed once
Time : Duration of one frequency step 10 ms
Step : Frequency change from step to step is 1 Hz
Sweep falling slope
Screen-3 : Sweep settings – falling slope
Mode : Falling slope from freq of channel 1 to freq of channel 0
Sweep rising then falling slope
Screen-3: Sweep settings – rising then falling slope
Mode : Rising then falling slope increasing from freq of channel 0 to freq of channel 1 and returning to freq of channel 0
Sweep falling then rising slope
Screen-3: Sweep settings – falling then rising slope
Mode : Falling then rising slope decreasing from freq of channel 1 to freq of channel 0 and returning to freq of channel 1
Sweep rising slope cyclic
Screen-3: Sweep settings - rising slope, repeating
Sweep : Is running
Mode : Rising slope, repeatedly executed.

6. Example Waveforms

Waveform pictures are all taken with amplitude set to 133 units of the digital potentiometer, corresponding to 2000 mVpp. We see clearly the decreasing amplitude and degradation of the waveform at higher frequencies.

Sine 1kHz
Sine 10kHz
Sine 100kHz
Sine 1MHz
Sine 2MHz
Sine 3MHz
Sine 4MHz
Sine 5MHz
Triangle 1kHz
Triangle 10kHz
Triangle 100kHz
Triangle 1MHz
Square 1kHz
Square 10kHz
Square 100kHz
Square 1MHz

7. Statechart

The statechart, created with the free tool YAKINDU™, shows the basic operation of the frequency generator.

Rotating the shaft of the rotary encoder cycles through the 4 screens.

Finite State Machine

The next statechart shows the settings of channel 0 in more detail. The individual digits of the frequency are reached by clicking and then changing value by rotating the rotary encoders shaft.

Duration and frequency step for sweep mode are set in an analog way.

Finite Statechart Chn0

8. Program Code

My programming environment is not the native Arduino™ IDE but PlatformIO™ on top of Microsoft's Visual Studio Code™. This combination offers many advantages and allows a much better structuring of the code into several modules especially when we adopt The Object Oriented way.

The code below shows the main program. It mostly consists of comments. The setup() function in turn calls setup() of the classes AD9833FuncGen and ControlKnob and initializes the LCD. Astonishingly the main loop() consists of a single line namely the call of the loop() function of the class ControlKnob. This class realizes the Finite State Machine and handles the 4 actions onClick(), onLongClick(), onCW() and onCCW().

  #include "AD9833FunctionGenerator.h"
  
  hw_timer_t * timer = NULL;
  volatile SemaphoreHandle_t timerSemaphore;
  portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
  
  void IRAM_ATTR onTimer()
  {
    //portENTER_CRITICAL_ISR(&timerMux);
    // Critical code here
    //portEXIT_CRITICAL_ISR(&timerMux);
    // Give a semaphore that we can check in the loop
    xSemaphoreGiveFromISR(timerSemaphore, NULL);
  }

  void setup() 
  {
    Serial.begin(COMPORT_SPEED);
    
    ad9833FuncGen.setup();

    lcd.init();
    lcd.createChar(0, topLine);
    lcd.createChar(1, backslash);
    lcd.createChar(2, roundArrow);
    lcd.backlight();
    lcd.clear();
    lcd.print("   LCD-I2C 4 x 20   "); lcd.setCursor(0,1);
    lcd.print("--------------------"); lcd.setCursor(0,2);
    lcd.print("01234567890123456789"); lcd.setCursor(0,3);
    lcd.print("AaBbCcDdEeFfGgHhIiJj"); 
    lcd.display();
      
    cntrlKnob.setup(); // Shows initial values on LCD

    timerSemaphore = xSemaphoreCreateBinary();
    timer = timerBegin(0, 8000, true);  // Use 1st timer of 4 (counted from zero).
                                        // Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more info)
                                        // 80   --> clock = 1.0 usec
                                        // 8000 --> clock = 0.1 msec
      
      
    timerAttachInterrupt(timer, &onTimer, true);  // Attach onTimer function to our timer
                                                  // Set alarm to call onTimer function every 0.1 ms
                                                  // Repeat the alarm (third parameter)
  }

  void loop() 
  {
    cntrlKnob.loop();  // All the functionallity is handled in the control knobs loop
  }                       
			

Interested? Please download the entire program code. The zip-file contains the complete PlatformIO project.

My programming environment is not the native Arduino™ IDE but PlatformIO™ on top of Microsoft's Visual Studio Code™. This combination offers many advantages and allows a much better structuring of the code into several modules especially when we adopt The Object Oriented way.