DIY Waveform Generator

The DIY waveform generator was created as a tool to be used to assist in testing circuits. It can also be used for purely educational purposes. It is a circuit board with a RP2040 MCU and an AD9833 Analog Devices waveform generator IC that is USB powered and can be programmed the exact same way a Raspberry Pi Pico board is. It also includes debug LEDs and headers to breakout the AD9833 output and GPIO from the RP2040.
Low frequency waveform generators can easily be found from multiple test equipment companies. While these are nice devices that will reliably output a waveform for you, they are also expensive. Even the cheaper ones can set you back hundreds of dollars. The below table was taken from the Rigol website comparing multiple “lower end” offerings.

https://www.rigolna.com/products/waveform-generators/

As can be seen, you can expect to spend anywhere from $300 to $2,000 for a “lower quality” device. The reality is that these devices are not that great, and creating your own waveform generator for testing purposes is not that complicated. That is exactly what the following text will outline. A PCB was designed and assembled that can output waveforms up to 12MHz. Furthermore, JLCPCB will be used to manufacture and build the PCB. This is my first time using their assembly service, so I am happy to be able to share my experience.

AD9833 and RP204o

To begin, the AD9833 waveform generator IC from Analog Devices will be used to create my output signals. This is a low power, complete DDS on chip that is controlled through a 3-wire SPI interface.  It can generate sinusoidal outputs, triangle waves, and square waves. We will be clocking this using a 24MHz clock, so the maximum theoretical frequency we can get out of it is 12MHz. The datasheet is full of good information, so if interested in waveform generation it is a good read.

Next, the RP2040 microcontroller from the Raspberry Pi foundation will be used to interface to the AD9833. I have used the Arduino MCU for most of my projects in the past, however I have recently been interested in using the RP2040. It is cheaper, faster, has much more memory, easily available, has much more GPIO, and in general is an overall much more powerful device. Consequently, this is the MCU of choice for this project. The below pic is the key features of the RP2040 from the datasheet.

In addition, the below pic is the pinout of the Raspberry Pi Pico board from the datasheet.

The RP2040 and AD9833 are the two key components that will make this design possible. The rest of the components needed for the project such as the oscillators, USB connector, headers, resistors, and capacitors will be chosen based on cost and what is available through the JLCPCB assembly service.

JLCPCB EasyEDA Design

Overall, using the schematic editor and layout tools available from EasyEDA went smoothly. They were pretty simple to use and if you have used schematic and layout software before, everything will come easy. I will not review all the factors that went into choosing all the components, however I wanted to use 0402 SMD components for all the passive components and LEDs. A lot of influence here came from what was available as well as what parts were the lowest cost. The flash memory for the RP2040 is the same as the recommended part from the Pico datasheet. There is an LDO placed on the board that is a Texas Instruments part. This takes the 5V USB power and converts it to a clean 3.3V. The RP2040, 24MHz oscillator, flash, and AD9833 are all powered with 3.3V. Furthermore, there are series ferrite beads to the MCU and DDS part of the boards. These provide additional filtering and isolation but can also be replaced with precision resistors for measuring current consumption. The below screenshot shows the schematic put together using the EasyEDA tool.

The below screenshot shows all 4 layers from the PCB design. This design could have easily been done with a 2-layer board, however having complete GND planes in the PCB stack up is good practice. It also allows layer 3 to hold miscellaneous traces and power planes that are sandwiched between GND planes. It also so happens that a 4-layer board is not much more expensive than a 2-layer board from JLCPCB. So the added cost is minimal.

JLCPCB also has a pretty awesome feature that lets you view the PCB in 3D. This is completely unnecessary for any practical purposes I have, however I thought it was pretty cool. Below is some pics from the 3D rendering.

Lastly, the below screenshots show the final ordering of the PCBs with the assembly service.

PCB Unboxing

The PCBs arrived sooner than expected. The order was placed on a Monday night and the completed boards were delivered on a Wednesday, one week later. Thus, to manufacture the PCB, assemble and reflow the PCB, and deliver the boards from China to Chicago, Illinois took a total of about 9 days. Pretty impressive for the overall cost.

The below pics show the boxing and packaging the boards come shipped in:

Nothing special to note on the packaging. Styrofoam is placed in between the boards to keep them separated and keep the components safe during shipping. The below pics show one of the PCBs. The build quality and overall soldering is very good.

PCB Bring Up

The first things I am going to check out with the circuits boards are the clocks and making sure all the voltages are correct. There is only two clocks, and the main voltages on the board are the 5V USB power and the 3.3V supply after the LDO. If these look correct, we can then try to program the board with a simple LED blinking program.

The voltage out of the USB connector and into the LDO measured in at 5.06V. Out of the LDO, 3.28V was measured. Very close to the expected 3.3V and within the supply tolerance of all the components. In addition, there is components in series with the supplies for the RP2040 and the AD9833. Thus, it is important to double check the 3.3V supply close to the circuit’s inputs to ensure there is not an unnecessary voltage drop across the series component which could indicate excessive current draw from one of the circuits. These also measured 3.28V, so everything is in working order in regards to the voltage rails.

Next is checking the clocks to make sure they are oscillating and producing the correct frequencies. I have a Rigol DS1054 oscilloscope that will be used for this purpose. These are low-cost oscilloscopes that are pretty popular for home use. Screen captures of the 12MHz and 24MHz clocks are shown below.

12MHz Oscillator Measurement
24MHz Oscillator Measurement

The 12 MHz oscillator is a raw crystal. Meaning the output waveform should look sinusoidal. On the other hand, the 24Mhz oscillator features a CMOS output. Meaning the output waveform should look like a square wave. There is some ringing or ripple on the 24MHz clock, however this is probably due to the oscilloscope probe ground connection. A lower parasitic ground connection should be used for a precise measurement of the waveform. For the purposes of knowing if the clocks are working though this will suffice.

Coding Examples

Upon powering up these boards for the first time, the “Boot select” button should be held down while plugging it into a computer. This puts the device into USB mass storage mode and allows us to drag and drop a file onto the board to program it. With that said we are going to open up the Thonny IDE and install micropython on the device:

MicroPython can be installed by clicking on the bottom right hand area of the IDE

An additional window will pop up asking for the micropython family, variant of board, and version. I chose the latest version (which will populate automatically) and the RP2/Raspberry Pi Pico variant, since I have essentially copied the Pico board schematic.

pop-up window with MicroPython family and variant options



After choosing install we can now start programming the
device. Overall, the entire process should be identical to the process done on
a Raspberry Pi Pico board. The first program to test the board is simply
blinking LEDs. It will do it in a “chasing” pattern, such that one LED will
turn on, then the next LED will turn on while the last one turns off. There are
8 LEDs on the board and this program will exercise all of them. The code for
this example is shown below.

The above pic shows the PCB powered on with the blinking LED example. The two green LEDs are for power indication on the 5V and 3.3V rails. From the program, one of the blue LEDs will blink at a time.

The next step involved testing the DDS or waveform generator. The code for this took a bit of trial and error along with lots of google searches. However, after some time I was able to successfully get waveforms out of the DDS. The complete code for my initial testing is shown below.

import machine
import utime

# AD9833 Register Address
REG_FREQ0 = 0x4000
REG_FREQ1 = 0x8000
REG_PHASE0 = 0xC000
REG_PHASE1 = 0xE000
REG_CONTROL = 0x2000

# AD9833 Control Bits
BIT_RESET = 0x0100
BIT_B28 = 0x2000
BIT_HLB = 0x8000

# SPI Configuration
spi = machine.SPI(0, baudrate=1000000, polarity=1, phase=0)
cs = machine.Pin(17, machine.Pin.OUT)

# Function to send data to AD9833
def send_data(data):
    cs.value(0)             # Select AD9833
    spi.write(data.to_bytes(2, 'big'))
    cs.value(1)             # Deselect AD9833

# Function to set the frequency of AD9833
"""
def set_frequency(freq):
    freq_reg = int(freq * (2**28 / 25000000))
    send_data(REG_FREQ0 | (freq_reg & 0x3FFF))
    send_data(REG_FREQ1 | ((freq_reg >> 14) & 0x3FFF))
"""    
# Function to set the frequency of AD9833
def set_frequency(freq):
    freq_reg = int((freq * (2**28)) / 24000000)
    print('Freq_reg value:'+str(freq_reg))
    print('freq0 register value: '+str(REG_FREQ0 | (freq_reg & 0x3FFF)))
    print('freq1 register value: '+str(REG_FREQ0 | ((freq_reg >> 14) & 0x3FFF)))
    send_data(REG_FREQ0 | (freq_reg & 0x3FFF))
    send_data(REG_FREQ0 | ((freq_reg >> 14) & 0x3FFF))

# Function to set the phase of AD9833
def set_phase(phase):
    phase_reg = int(phase * (2**12 / 360))
    send_data(REG_PHASE0 | (phase_reg & 0x0FFF))
    send_data(REG_PHASE1 | ((phase_reg >> 14) & 0x0FFF))

# Function to reset AD9833
def reset():
    send_data(REG_CONTROL | BIT_RESET)
    utime.sleep_us(10)
    
def wakeUp():
    send_data(REG_CONTROL | 0x0000)

def setSine():
    send_data(REG_CONTROL | 0x0000)
    
def setTriangle():
    send_data(REG_CONTROL | 0x0002)
    
def setSquare():
    send_data(REG_CONTROL | 0x0028)

# Initialize AD9833
reset()
send_data(REG_CONTROL | BIT_HLB)  # Set control bits for AD9833

# Set frequency and phase
set_frequency(1000)  # Set frequency to 1000 Hz
set_phase(0)        # Set phase to 0 degrees
wakeUp()

The above example code sets the output frequency to 1KHz in the third from last line. This is a sinusoidal output from the DDS and is measured in the pic shown below:

Using the “setSquare()” and “setTriangle()” functions, the output can also be changed to a square wave and triangle waveform respectively.

The below pic shows a 20Hz and 1MHz output from the DDS. These are reaching the lower and upper operating frequency limits of the device.

As we approach the sampling rate of the device (24MHz) we can begin to see the discrete steps from the DAC output. This is normal and expected behavior. For example, a 4MHz output would be sampled 6 times. This can be easily seen in the image below.

Conclusion

Overall, the PCBs designed and created are working great. The build quality is awesome for the price paid and the turnaround time. In addition, working with the EasyEDA free schematic and layout tools was simple. Picking and choosing components available for the assembly service was also not much of a hassle. I will definitely be using JLCPCB again with their assembly service. I am also looking forward to working further with this PCB for future projects. The main reason I created it was to help with testing audio amplifiers. Additional topics I would like to explore using it involve further working with the RP2040 MCU. I would like to test out the analog to digital converters and see what the signal processing capabilities are. I would also like to create a CLI interface to the board. I am planning on posting these boards for sale through the Tindie website. Check back for more info.

Leave a comment