ZL2PD DDS Audio Function Generator
This powerful low-distortion audio function
generator covers from 1 Hz to above 65 kHz. It produces four different
waveforms at low distortion with output levels adjustable from a few
millivolts to 5 volts p-p. It's all done with a cheap ATtiny2313
microprocessor and a cheap CMOS 4015 IC.
been experimenting recently with digital filters. Narrow bandpass audio
filters, to be precise. When it came to testing these filters, it
quickly became obvious that I needed a sine wave generator with low
distortion, fine tuning, and possessing an accurate and repeatable tone
Figure 1 : The function generator
rotary encoder for
My existing test equipment had some major limitations. I have a simple
CMOS sine wave generator which is described elsewhere on this website,
but it only covered a limited range of
frequencies. Some initial tests also showed that its distortion,
although fine for general testing, was just too great for these tests.
I’m guessing it might be as
much as high as 5%. In addition, it could not be tuned accurately and
repeatably to a specific frequency, particularly within a few Hz.
My other, somewhat older, low distortion opamp-based Wien bridge audio
oscillator was also starting to show its age. It had no fine tuning
control, and the variable resistor used for tuning the oscillator was
noisy, and badly in need of replacement.
I was clearly in need of a good, accurate, sine wave generator. As I
considered the options, I decided that it was as probably going to be
about as easy to build a full function generator, with sine, square and
a few other waveforms, as it was to build just a basic low distortion
sinewave generator. So that was the direction I headed.
The next question was whether to design my own unit, build an existing
design, or purchase a function generator. I rapidly disposed of the
‘purchase’ idea. Instruments within my price range had limited
features, and those that I liked were way beyond my budget. Such is
So, my next question: Should I build an existing design, or design one
from scratch? Scanning my magazines and looking around the Web,
function generator designs seemed to fall into three categories. Older
designs seemed to use either a truckload of discrete components and a
bucketful of opamps, or they used a specialised chip, usually the Exar
XR2206 or the Maxim MAX038. The XR2206 can be hard to find and has some
limitations in common with my existing test equipment, while the MAX038
can be very expensive. Newer designs mostly use Direct Digital
Synthesis (DDS), either based on an Analog Design DDS chip such as the
AD9835 which is driven by a microprocessor, or they simply a fast
microprocessor running DDS software.
The microprocessor-based DDS software designs appealed to me, since I
have a few skills in this area. Most of the designs I found used the
fast Atmel AVR processor family. Yet, despite finding a number of
potential designs, most, if not all, had problems.
For example, one design I came across was a really nice looking function
generator kit from Altronics published in the June 2009 issue of
Silicon Chip. But it was limited to a maximum frequency of 30 kHz, and
it had a surprisingly high distortion of 4%. The method
used to control the device was something of an issue too. Up/Down pushbuttons were used to select the
required frequency. This is not very convenient to use in
practice, and to make matters worse, it had some unusual tuning steps.
For example, the generator is only able to tune in 500 Hz steps at
frequencies above 1kHz. If I wanted an output of 1250 Hz, that Altronics/Silicon Chip design
would simply not do the job.
So, ‘design my own’ was the decision, and this design is the result.
This generator will be used for a variety of audio test work on my
workbench, as well as for the specific digital audio filter testing
which kicked off this project originally. With this in mind, my aim was
to produce a function generator with low distortion, ideally less than
0.5%, covering at least 1 Hz to 50 kHz, with 1 Hz resolution. I also
wanted the generator to have a reasonable range of output levels to
allow testing of microphone inputs, which requires low mV levels, as
well as filters where several volts of output level is often necessary.
I also wanted a function generator that was reasonably compact, battery
powered, and low in weight. The frequency shown on the display also had
to accurately report the generator’s output frequency. Some
microprocessor-based designs have relied on the micro’s internal RC
clock rather than a more stable and accurate external crystal, and this
can lead to some significant errors in the generated tone frequency.
The features of my function generator design are listed in Table 1:
Table 1 : Function
All of the major design objectives and specifications were met or
exceeded, some by a considerable margin.
Table 2 compares my design with the original Altronics product in Silicon Chip magazine:
Table 2 :
DDS Function Generator Performance Comparison
My design outperforms the Silicon Chip/Altronics generator in
practically every single specification. I think my generator looks
The core of this design is a microprocessor,
the low cost Atmel ATtiny2313 from the Atmel AVR family. This
microprocessor is capable of high speed operation, vital for digital
waveform synthesis. It is clocked by a 16.934MHz external crystal. This
crystal might appear to be an unusual frequency, but these are a
commonly encountered crystal, and often used in CD players. Using a
crystal improves the accuracy and stability of the function generator
considerably over the alternative internal microprocessor oscillator
seen in some other designs.
Figure 2 : DDS Function
(Right-click on the diagram to see it full size)
There are several ways to generate the desired waveform. One way is to
use pulse-width modulation (PWM). In this case, the pulse width of a
high speed square wave, say about 1MHz or more, is varied using
software. If the pulse width changes are done at a rate equal to the
desired audio frequency, and then passed through a low pass filter,
then the resulting waveform will be just the audio frequency. The 1 MHz
square wave will have been filtered out. The low pass filter typically
cuts off around 100 kHz. The audio output can include sine, square or
The advantage of this approach is that it only requires the use of a
single pin on the microprocessor. Many microprocessors have PWM
functionality built into the chip these days. I spent some time working
on this approach before discarding it as impractical for this
application. Despite some extensive additional filtering, this method
could not deliver the performance I was seeking. (This work was not
wasted – It spun off into a miniature square wave oscillator with PWM,
sweep and fixed sine wave outputs which I also built for use with SMPS
The alternate method is more widely used. This uses an 8-bit port on
the chip to drive a passive resistor digital to analog (D/A) converter.
This simplifies the software to some extent but requires eight times
the pin resources of the PWM method. Since this delivered the required
performance, that was the method used in this design.
Such an approach does not require the use of 1% resistors, although
these are commonly found in this type of A/D converter. The only
requirement is for a precise 2:1 resistor ratio. These resistor values
must come from the E24 resistor range. Since E24 parts are typically
only available from suppliers in 1% high stability component ranges,
the impression has been wrongly given that this level of precision is
actually required in D/A R/2R ladders. Not so. If output distortion of,
say, 1% is acceptable, then both 2:1 ratio and 1% accuracy requirements
can be waived. However, 1% distortion was too high for this design, so
1% E24 2:1 ratio resistors are required for this design.
The function generator’s frequency is selected using a rotary encoder.
I’ve recovered quite a few of these from old car radios and defunct
stereos. If you need to purchase one of these, they are available from
the web, from RF Candy, for example, for around $5 each plus shipping.
(See http://www.rfcandy.biz/shop/ and look under Switches – General)
Figure 3 : A typical rotary
encoder with integral switch
These rotary encoders produce a pair of phase shifted square waves,
with the lead or lag in phase indicating the direction of rotation of
the knob. These encoders almost all come with an integrated switch,
activated by pressing down on the knob, and this is used in this design
to select the required tuning step rate. 1, 10 or 100 Hz tuning steps
can be selected anywhere within the generator’s range.
If you are unable to find one of these encoders, it is possible
one from an old (really old!) computer mouse. A Google search for "make
your own rotary encoder from a computer mouse" will produce a number of
links. Similarly, if you cannot find an encoder with an integrated
separate pushbutton can be used, similar to that used in this design to
select the desired output waveform.
LCD Display and Interface
The LCD display is a standard 2 line by 8 character alphanumeric
display. Most function generator designs use the larger 16 character
per line display, and then leave most of the display blank and unused.
I was aiming for a compact unit, so this smaller display was ideal,
although there is next to no cost savings over the larger 16 character
Interfacing this LCD requires at least six pins on the micro, but I’d
run out of pins since I needed eight pins for the D/A. With
insufficient pins available on the ATtiny2313, I could simply have used
a larger AVR microprocessor. However, I found the price for these
larger chips rose quite quickly. It turned out to be much cheaper to
use available components, in this case a CMOS 4015 shift register, to
resolve the problem.
Special thanks must go to Roman Black for the
idea behind the 2-pin drive to the LCD used in this design. You can find his website here. Actually, Roman used
a 74HC595, driven by just one pin. Inspired, I tried this same approach
using a cheap CD4015 CMOS shift register I had in my parts bin, plus my
own AVR code, rather than Roman’s C-language code which he uses for his
PIC microprocessor based designs. It all worked perfectly.
Figure 4 : LCD interface with CD4015 shift register
Roman’s clever idea was to use two different timed pulses to latch in
either a 0 or a 1 bit value into a shift register. In Figure 4, the logic level present on
the D-input to the 4015 is clocked in on the rising edge of each of these timed
pulses. A short 1uS pulse is too brief to change the high logic level
present on C8 (2n2) and so a ‘1’ is clocked in by the rising edge of
this pulse. However, the longer 15uS pulse is sufficient to discharge
C8, resulting in a “0” being clocked into the 4015 by this pulse's rising edge.
The following 30uS high logic level “tail” restores C8 to a high logic
As a result, up to eight bits can be shifted into the 4015. This can
take up to 400uS, but that’s nothing in the scheme of things. The LCD
display is much slower at responding to events than this.
The fifth bit sent to the 4015 is used to drive the R/S pin of the LCD
display. This sets the LCD display mode. The LCD is enabled with a
pin from the microprocessor. The software waits for the data on the
outputs to settle before enabling the LCD with this connection, and
this allows the new data at the 4015 outputs to
be read by the LCD.
A small jumper on the board (marked SW5 on the schematic) was used to turn on and off the backlight
LED. LED backlighting consumes a lot of current from the battery. I originally
thought it would be essential to have backlighting, but after a while
I turned it off and I’ve not used it since. The display has adequate
contract without the extra help from backlighting.
Also, while I used a 56 ohm resistor for the internal LCD display backlighting LED, and this
value is shown in the circuit diagram (It's marked R3 on the schematic), I found R3 could be increased to
150 ohms without a really noticeable change in backlight level.
However, LCD displays and backlighting will vary. If you need backlighting, you may
need to try different values. I do not recommend using anything lower in value
than 56 ohms for R3 however.
VR2 (10k) sets the LCD display contrast. When you first turn the unit
on, this will have to be adjusted so you can see the display clearly.
Try setting it around mid-position initially.
Function Generator Output
The output opamp circuitry is quite standard, delivering an in-phase
and an inverted pair of output signals. It does avoid the need for any
large value electrolytic capacitors, and for that reason, take care.
The outputs are not DC isolated(!!) Most inputs on equipment I test
already have DC isolation, so I don’t usually have to worry about that,
but you may need to consider this.
If necessary, add a large value capacitor in series with each output. A
22uF 16VDC electrolytic capacitor is suitable, although the output
level will roll off somewhat below 10 Hz, but larger capacitors are not
The first (in-phase) generator output is controlled continuously from
the variable front panel “Level” control, while the second (inverted)
output has an additional three step attenuator to allow lower output
levels to be reached. I use this output for testing more sensitive
circuitry, such as microphone inputs on transmitters. This output also
inverts the waveforms, but this is only of significance with the
sawtooth wave. With this waveform selected, one output has a rising
ramp and sharp fall while the second output has a falling ramp followed
by a sharp rise. The useful thing about this output design was that it
saved me having to code in both sawtooth waveforms into ROM.
I used a TL072 opamp here. It has better specifications than the more
common LM358 and it gave much better performance at the upper end of
the frequency range. Other modern opamps could be used here also.
The function generator is powered from a standard 9V battery. The
current is relatively high for this type of battery, so an alkaline
type should definitely be used. To date, this battery has proven to be
satisfactory, although the notes above about the backlight should be
borne in mind. Using the backlight will run down the battery fairly
quickly unless a 150 ohm resistor (or higher value) is used at R3.
To improve battery life still further, a low volt drop regulator
(LP2951) has been used. This allows the battery voltage to drop to
about 5.5V before I have to toss it away. You could use a 7805 or 78L05
type, but the battery life will be reduced because those regulators
require a couple of volts to be dropped across them.
All of the software has been written in assembler to deliver optimal
speed. There are four main sections of code:
Initialisation – Sets up the instrument at
Input code – Manages the rotary encoder and switches
Tone Generation – A tiny code segment that does the
bulk of the work
Display code – Code to drive the LCD and the 2-wire
LCD interface via the 4015 shift register
The initial code starts up the generator with a brief two-line
scrolling sign-on message, and then the function generator outputs a
1000Hz sinewave. The generator is initialized with a 1Hz tuning step
For most of the time, the microprocessor simply runs the short
generation code segment. Interrupts are used to handle the rotary
encoder and switches to allow the microprocessor to rapidly manage any
user-required changes, such as a new frequency or waveform. Diodes D1
and D2 detect switch closures and initiate the required interrupt
routine, while the rotary encoder activity is detected directly with a
second interrupt input.
The use of interrupts does cause a brief flicker on the output waveform
when a change is made to the generator’s setting. The processor is very
fast, but not swift enough to manage to read the new switch settings,
update the LCD and simultaneously keep the digitally generated waveform
going perfectly. This is typical of all of these designs, and of little
concern when the generator is actually being used.
Most of the LCD code is quite routine since most alphanumeric displays
use either the Hitachi HD44780 or a compatible variation of this
interface chip. My display used a compatible Chinese-made Sunplus
These chips or their compatible equivalents have been used for a number
of years, and rarely give any problems. However, my standard core LCD
code allows for a fairly wide variation in display LCD timing. This is
a problem with some of the less expensive displays from some suppliers,
and this has given rise to problems in other designs. My software
should be compatible with all of these cheap LCDs.
Some minor additions to this core LCD driver code provides the
necessary support for the unusual CMOS 4015 serial to parallel
interface used. The impact on the code size is minimal.
Each waveform is stored in 256 bytes of the ROM. These waveform “lookup
tables” must be located at specific 256 byte address boundaries. As a
result, it is a tight squeeze to get all of the code along with as many
waveforms as possible. I managed to get all of the code and the four
different waveforms into the small 2048 bytes (“2k”) of ROM space, with
just 2 bytes to spare!
I built the unit shown on prototyping board rather than designing a
specific PCB for the unit. This was a quick to build but this required
more time than usual to be spent on the enclosure. The resulting
circuitry didn’t fit in any standard off the shelf box.
The prototype used a 5mm thick composite wood base measuring about 100 x 85mm and a matching 1.5mm
thick aluminium front panel of identical size. Four
30mm long nylon hex spacers
are used to hold the front panel in place and parallel with the back
wooden base panel. This front panel was covered by the laser printed
panel artwork (Available in the Download area at the bottom of this
turn, this was covered in self-adhesive clear plastic to protect the
panel during use.
The sides of the box were made from 3mm thick mahogany strips. These
are made in 100mm x 1m long panels, and they are available
from model shops. I cut out a 40mm wide strip which was then cut into
the four pieces needed for the sides of the box, and glued to the base.
The required slots for the various switches were cut into two of the
side panels. One side was screwed into place to allow the 9V battery to
quickly and easily replaced.
All told, my prototype measures 105(W) x 90(H) x 40(D) mm, excluding
knobs, or 55 mm (D) including knobs, and weighs in at 235 grams. That's
just over 8 oz for those still using those odd units. A plastic
case would reduce this weight by 30%, I suspect.
Using the Function Generator
Turn the function generator on. The sign-on message will scroll across
the LCD. The display will then switch to the normal display of
frequency and waveform settings. The initial setting on power-up is a
1000 Hz sinewave. The tuning rate is also set to 1 Hz at power-up, so
rotating the Frequency knob should increase or decrease the output
frequency in 1 Hz steps.
Pressing down on the Frequency control activates the integral Step switch. Pushing this briefly allows the next tuning step size
to be selected, from 1 to 10 to 100 Hz, and then back to 1Hz steps again. This allows for faster or slower tuning as
Pressing the Wave button will select the required sine, square,
triangle or sawtooth waveform.
The output level can be set using the combination of the continuously
adjustable Level control and the Attenuator
switch. The Level control provides continuous adjustment of the output
levels up to the maximum selected with the Attenuator switch (50mV,
500mV or 5Vpp)
The two outputs can deliver levels ranging from low millivolts to more
than 5V peak to peak. The maximum output depends on battery voltage.
Take care when using the higher output levels. It is very easy to drive
systems into overload with such large voltage swings.
Warning: The audio outputs are NOT
DC isolated. While most inputs on filters and equipment which might be
tested with this generator are equipped with isolating capacitors,
that's not always the case. If you need outputs free from DC voltage,
add a suitable capacitor in series with the 560 ohm output resistor.
The output waveform distortion is low on all of the waveforms. Sinewave
distortion was measured at less than 0.2% on the prototype. Figure 5
shows the output as measured using a PC soundcard and SpectraPLUS© demo
software. The figure illustrates the clean output of the function
generator, although the distortion reported of 0.15% is probably better
than the actual case because of the sharp filters in the sound card which cut
off any input signals into the card which are above 11kHz.
Figure 5 : Performance of a 1 kHz sinewave was measured using PC
soundcard-based software and illustrates the low distortion output of
Other measurements across the range of output levels and frequencies
(within the measurement limitations of the PC and soundcard used)
suggests that distortion performance was typically less than 0.2%, and
in line with expectations.
Square wave outputs are excellent up to 35 kHz, although some
distortion and leading edge ringing can be visible above this frequency
depending on output amplifier wiring and construction, and the specific
op-amp chosen. I found that C11 took care of this problem in the
prototype, but values of C11 ranging from 2p2 to 8p2 were required
depending on the op-amp used, and my layout.
Some Final Thoughts
I could have addressed a couple of other minor items if I had just had
another hundred bytes of ROM available. But I faced the choice of
either writing code to support four different waveforms (or five, if
you count the inverted ramp waveform which results from the main and
inverted op-amp output ports) or to resolve a few minor ‘might be nice
to have’ features. Given the choice, I went with coding the four/five
Also, I've now been using this generator for almost four years, and
that experience has demonstrated, firstly, that it does meet all of the
goals I set for it at the outset, it's easy to use, and perhaps most
importantly, the 9V battery seems to last for many months of use. But
still, there are a few things I'd like to address if I was doing it all
So what are these minor issues?
Firstly, it’s not obvious which tuning increment (1, 10 or 100 Hz
tuning steps) is currently selected just by glancing at the LCD
display. Turning the tuning knob makes it obvious, but I would like to
have added an underline character to the frequency display or to use
the three free output pins on the 4015 to drive LEDs to indicate the
tuning step size.
Secondly, the function generator runs up to 65,535 Hz before rolling
over through zero to restart at 1 Hz again. That’s fine, but it would
have been nice to get up to 100 kHz, say, or even 200 kHz. But that
would have required a longer tuning word, a few more calculations, and,
yes, more ROM. So, for now, 65535 Hz is the upper limit of this
Thirdly, when you tune, there is a momentary glitch on the waveform as
the DDS software updates the required output frequency. Almost every
test I do is at a specific frequency, so I tune to the frequency and
make my measurement. With a quick sweep of frequencies, say from 500Hz
to 3 kHz, the series of glitches that occur during the tuning are
barely noticeable. These momentary disturbances in the output waveform
are due to the rotary encoder detection and DDS update interrupt
software. This is possible to fix if I had enough code space left, but
I don't. I have since done it on another design which needed that
glitch-free performance, but it confirmed I needed a lot of additional
code space to achieve it.
Despite these minor issues, and as I mentioned above, the objectives I had in mind when I started
to design this function generator have all been met. I have designed and built a very
compact, low cost, lightweight and accurate function generator with
wide range and low distortion which has proved ideal for testing a wide variety of
equipment. And I’ve found it a genuine delight to use on my workbench.
I hope others will build it and enjoy it just as much as me.
Source code : DDS_4015LCD_asm.zip (11kB)
Hex code : DDS_4015LCD_hex.zip (3kB)
Front panel artwork : panel.gif (29kB)
AVR Fuse Bit settings : ddsFcnGen_FuseBits.txt (2kB)
(Sorry – No PCB available)
Want to go back to the main page? Click