DDS-based Rasp Pi function generator

Pass the Hat

© Lead Image © lightwise,

© Lead Image © lightwise,

Article from Issue 225/2019

A touch display, a case, and a custom add-on board transform the humble Rasp Pi into a high-performance function generator that rivals expensive commercial offerings.

Modern laboratory instruments are a marvel of integration, combining hardware, software, and often mechanical components to produce versatile and highly functional units that bring real value to the engineers and technologists who use them. In this article, I aim to show how such an instrument might be put together by combining the Raspberry Pi with several elements to provide a low-cost and flexible function generator that can rival its more expensive commercial cousins on price and possibly beat them in terms of flexibility.

Function Generator

A signal or function generator is a versatile frequency source able to output a signal in a number of output waveshapes and at an adjustable amplitude and frequency from direct current (DC) up into the high-frequency electromagnetic radio frequency (RF) region, depending on the application. Sophisticated instruments can also include frequency, phase, or amplitude modulation useful for testing radio transmitters and receivers.

No engineer's workbench is complete without such an instrument. Together with an oscilloscope, it allows an engineer to characterize the frequency and phase response of an amplifier, to test and calibrate counters and frequency meters, and to replace built-in oscillators in devices under test or during development.

A variable frequency and amplitude signal is often required for uses beyond the purely electronic, such as for applications in mechanical and acoustic engineering for testing loudspeakers and driving vibration platforms and a plethora of other electronically excited mechanical devices. In the field of medicine, variable-frequency sources are used to test the response of the human ear, as well as to drive ultrasound devices used in imaging. The list goes on and on.

Many of the commercially available instruments cost upward of several hundred dollars. Because of their self-contained nature, modifying their function is not generally possible, and embedding them into a larger system can be difficult because of their size and power requirements. Modern analog integrated circuit technology makes available to the instrument designer a vast array of building blocks, many of them programmable, to help build a sophisticated instrument with a minimum of components. The addition of a microcontroller or other embedded computer enables the designer to add extra features and produce a fully featured, flexible instrument that is easy to use.

The instrument presented here is based on a Raspberry Pi, an excellent basis for personalized instrumentation, the official Pi 7-inch LCD screen and case, and a direct digital synthesis (DDS) function generator on a Hardware Attached on Top (HAT) board of my design (see the "Specifications" box).


  • 0.01Hz to 10MHz, adjustable in 0.01Hz steps
  • Sine, square, and triangle outputs
  • 50-ohm or low-impedance output with jumper option
  • Output adjustable from +/-1mV to +/-5V, peak-to-peak
  • Transistor-transistor logic (TTL) sync output
  • Ninth-order filter for sine purity
  • Linux drivers or access through the serial peripheral interface (SPI) bus
  • Current limit warning LED
  • Onboard supplies for analog section
  • SMA connector for output


Practically all modern signal or function generators are implemented by DDS (see the Direct Digital Synthesis box). Because DDS chips are readily available for a few dollars, you can easily build an instrument with similar performance and greater flexibility for a few tens of dollars. The maximum output frequency is limited by the semiconductor technology used to build the chip and the master or "clock" frequency (usually derived from a crystal oscillator) used to drive the chip. Because the generation is purely digital, the frequencies produced have no lower bound. For this project, I have chosen the AD9833BRMZ [1] DDS chip from Analog Devices, which has a range up to 12.5MHz. As a side benefit, this chip can also produce triangular and square wave outputs.

Direct Digital Synthesis

Direct digital synthesis is a well-known technique for generating repetitive waveforms from a single, high-frequency source such as a crystal oscillator. The concept is fairly simple. Samples of the required waveform are stored in RAM or ROM (in the case of a sine wave, only a quarter of a full cycle need be stored, and the rest inferred by symmetry). A counter is then used to access the stored samples sequentially, and the samples are passed to a digital-to-analog converter (DAC), which then outputs the desired waveform.

By varying the frequency fed to the counter, the frequency of the output may be varied. The resulting waveform is a stepped approximation of the desired waveform; an output filter is required to reconstruct the exact waveform. This filter is generally a high-order passive filter with a corner frequency set as half the reference clock frequency. You can find more information about DDS in an online tutorial [2].

A useful generator must also have a means to adjust output amplitude. Amplitude control can be achieved many ways, from simple, mechanical variable potentiometers (pots) to their semiconductor equivalents. Many of these approaches have limitations because of excessive stray capacitance, linearity, stability, and the ability to control the amplitude with software. An optimum solution is to use a multiplying DAC, wherein the reference signal is replaced by the signal to be controlled. These so-called multiplying DACs are available and can control amplitudes of frequencies into the tens of megahertz and beyond. The AD5443YRMZ [3] from Analog Devices is such a chip, providing 10-bit resolution of amplitude control.

Both of the chips selected have SPI interfaces, so they are ideal for interfacing to the Raspberry Pi. Additionally, both are supported by Analog Devices' Linux Industrial I/O Subsystem drivers (see the "IIO" box).


The Linux Industrial I/O Subsystem (IIO) is an attempt at a unified device driver interface for chips such as data converters, sensors, and frequency sources. The wiki [4] gives an excellent in-depth description. From the perspective of this article, the main thing to note is that, once loaded, a device driver for a particular device exposes readable and writable parameters as entries in the /sys tree of the filesystem that allow user programs to sample and modify the device's function by file semantics, with no low-level (I2C/SPI/serial/etc.) programming required. High-speed devices also support a streaming interface.

Because the generated signal is a stepwise approximation to the ideal sine wave output, the output must be filtered. The filter chosen for this design is a ninth-order elliptic low-pass filter. Filters of this sort are a complex topic beyond the scope of this article, but you can find a detailed explanation online [5]. A passive filter comprises only resistors, capacitors, and inductors.

A number of online resources (e.g., RF Tools [6]) offer simple design tools that allow you to dial in the required parameter and then deliver a schematic of the filter, the component values, and a graphical phase and frequency response. The filter for the current design has a corner cut-off frequency of around 12.5MHz, or half of the chosen crystal drive frequency of 25MHz. This filter is an anti-alias filter, and by attenuating the higher frequencies (greater than the Nyquist frequency), it prevents the aliased components from being output.

A wide-band power amplifier is employed to produce output that can be used to drive other instruments at useful power levels. The OPA564AIDWP from Texas Instruments [7] provides an output of +/-10V peak-to-peak at more than 1A across the 10MHz frequency range, so it is ideal for this application. It has built-in overcurrent protection and a thermal shutdown mode, so it is immune to the type of abuse that a bench instrument might encounter.

The remaining components that make up a practical design are connectors, op-amps for level shifting and buffering, potentiometers for calibration, power supplies, and filters. The power supply used to power the instrument must be of sufficient capacity to supply both the Rasp Pi and the DDS board. The official universal supply [8] is recommended. The block diagram in Figure 1 shows how the essential elements are connected to form the complete instrument.

Figure 1: DDS function generator block diagram.

The excellent open source schematic capture and printed circuit board (PCB) design tool KiCad [9] was used to capture the design and layout of the two-layer PCB. The schematic (Figure 2) is a complete implementation of a practical DDS function generator, with power supplies, filtering, synchronization output, level shifters between stages, and calibration potentiometers to set null the output offset in the power amplifier and to set the full scale gain to the value indicated in the GUI.

Figure 2: DDS function generator schematic.

Along with the schematic, KiCAD presents a 3D render of the fully laid out PCB (Figure 3). The KiCAD 3D models are invaluable, allowing designs to be incorporated into larger mechanical models to ensure the mechanical positions of connectors and other elements are correct and that no component fouls any nearby mechanical element, such as features of an enclosure.

Figure 3: KiCAD 3D PCB layout.

Mounted on a Raspberry Pi, the DDS board fits within the official 7-inch Rasp Pi display case [10]. A small modification to the case is required to allow the output connector to protrude through the case wall (Figure 4). Two vertical saw cuts toward the HDMI connector remove a small rectangle of plastic and do not in any way affect the structural integrity of the case.

Figure 4: A modified Rasp Pi case allows the board's connector to protrude.

The Software

Today's instruments have generally moved away from front panels loaded with knobs and switches to a more generic user interface with an LED or LCD screen and possibly a single multifunction control knob. The interface designed for the current instrument is intended to run on the standard Rasp Pi 7-inch touch LCD [11], so the user interface will be entirely touch-based, allowing a compact instrument to be built into the standard case. The whole setup is packaged as an instrument, with a Python-based GUI allowing the full control of frequency, amplitude, and waveshape that you would expect from a commercial instrument.

As already stated, many instruments use a multipurpose spinner, or control dial, to change operating parameters that are displayed on an LED or LCD screen. The current GUI uses the same paradigm (Figure 5), with a circular GUI dial that controls frequency and amplitude. Two buttons associated with the dial select the decade [12] currently being edited, and another two allow you to jog the setting up or down for precise control. The frequency of voltage being edited is displayed in the central area of the screen, with the voltage displayed below. A series of buttons selects the function of the dial, and other buttons control output waveform and frequency range.

Figure 5: The GUI for the DDS-based Raspberry Pi function generator. Table 1 describes the function of each region of the GUI.

Table 1

GUI Functions



Top left

Range selector

Top center

Frequency output indicator with cursor and units.

Top right

Left and right arrows select decade (the cursor under the '1' character in the frequency output indicator).


Plus and minus jog up or down 1 unit.


Dial scrolls frequency up or down. If you scroll past 9, the cursor moves to the left and continues to increment.

Bottom left

Waveform selector.

Bottom center

Voltage output indicator with cursor and units.

Bottom right

Selector for dial function.

The IIO subsystem drivers hide the details of the SPI interface that are used to communicate with the DDS and DAC chips. The parameters that can be varied, such as frequency, level, and waveshape appear as files in the /sys tree. Simply writing valid values to those files causes SPI messages to be sent to the chip. (Although it would be possible to bypass the IIO drivers, i.e., not load them at all, and generate SPI messages directly, it would require a detailed understanding of the SPI protocol used by each chip and, in my opinion, is a lot of hard work for no appreciable gain.)

The IIO driver takes care of scaling and all the low-level communication details, which makes interfacing with the chip from a high-level language such as Python extremely convenient. The core methods required to communicate with the DDS board are shown in Listing 1. The routines control the hardware through the IIO device drivers and their entries in the /sys tree and represent the least amount of code required to control the generator.

Listing 1

01 import sys
03 #
04 # This file provides routines to control the hardware via the iio
05 # device drivers and their entries in the /sys tree.
06 # This is the least amount of code required to control the generator;
07 # all the other files simply provide a GUI front end.
08 # If you want to design your own GUI or embed the generator in another system,
09 # this is all you need.
10 #
12 ## dds registers
13 frequency_register='/sys/bus/iio/devices/iio:device1/out_altvoltage0_frequency0'
14 wavetype_register='/sys/bus/iio/devices/iio:device1/out_altvoltage0_out0_wavetype'
15 enable_register='/sys/bus/iio/devices/iio:device1/out_altvoltage0_out_enable'
17 # dac registers
18 voltage_register='/sys/bus/iio/devices/iio:device0/out_voltage0_raw'
20 # disable calls to hardware in test mode
21 test=(len(sys.argv)>1)
23 # enable output
24 def enableOutput(enable):
25     if test==False:
26         fo=open(enable_register,'w')
27         if enable==True:
28             fo.write('1')
29         else:
30             fo.write('0')
32 # write frequency in Hz to DDS
33 def setFrequency(frequency):
34     output=str(int(frequency))
35     #print('F='+output)
36     if test==False:
37         fo=open(frequency_register,'w')
38         fo.write(output)
40 # write to DDS one of 'sine', 'square', 'triangle'
41 def setWavetype(wavetype):
42     #print('T='+wavetype)
43     if test==False:
44         fo=open(wavetype_register,"w")
45         fo.write(wavetype)
47 # DAC full scale is 4096 (2^12), so scale from
48 # 5000 millivolts 1.23=4095/10000
49 def setVoltage(voltage):
50     output=str(int(voltage*4095/10000))
51     #print('V='+output)
52     if test==False:
53         fo=open(voltage_register,'w')
54         fo.write(output)

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Csound

    The powerful Csound software provides an impressive set of features for audio production and processing. We walk you through the entire system.

  • FOSSPicks

    Graham looks at Graphite, retrogram~rtlsdr, Piano Forte, OneTrick SIMIAN, The Command Line Murders, and more!

  • Free Software Projects

    C2h lets people talk to dolphins and whales, and if you happen to be a jazz musician, you will find a creative helper in Impro-Visor.

  • Software-Defined Radio

    Armed with a US$ 20 hunk of hardware and a free software-defined radio tool, Konstantin starts the hunt for radio-transmitted data from a weather station.

  • FOSSPicks

    Sparkling gems and new releases from the world of Free and Open Source Software.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More