You are on page 1of 79

Waveform generators (also called function generators) are useful for testing and debugging

circuits. I often use them to test the frequency response of electronics components like op amp
and sensors. This waveform generator is powered by an Arduino. It outputs four waveshapes:
sine, triangle, pulse, and saw, each waveshape ranges in frequency from 1Hz-50kHz. The
frequency, pulse width, and overall amplitude (gain) of the waveforms is controlled by three
potentiometers. I've also included (optional) indicator LEDs that let you know which type of
wave is currently being sent to the output.

Parts List:
(4x) Mini SPST 1.5-Amp Momentary Pushbutton Switch (2 packages) Radioshack #275-1556
(8x) 10K Ohm 1/4-Watt Carbon Film Resistor (2 packages) Radioshack #271-1335
(9x) 20K Ohm 1/4-Watt Carbon Film Resistor (2 packages)
(1x) 50K-Ohm Linear-Taper Potentiometer Radioshack #271-1716
(1x) 10K-Omh Audio-Taper Potentiometer Radioshack #271-1721
(1x) 10K-Ohm Audio Control Potentiometer with SPST Switch Radioshack #271-215
(1x) 1/8" Stereo In-Line Audio Jack Radioshack #274-274
(1x) 10.01f 50V Ceramic Disc Capacitor Radioshack #55047551
(1x) 4.7K Ohm 1/4-Watt Carbon Film Resistor Radioshack #271-1330
(1x) 8 Pin Socket Radioshack #276-1995
(1x) LM386 Low Voltage Audio Power Amplifier Radioshack #276-1731
(2x) 220F 35V 20% Radial-lead Electrolytic Capacitor (or anything between 200 and 300 uF)
Radioshack #272-1029
(1x) Arduino Uno REV 3 Radioshack #276-128
(1x) Arduino Proto Shield Radioshack #276-140

(4x) White Super-bright LED Indicator Radioshack #55050633


(4x) 740 ohm 1/4W 5% Carbon Film Resistor (1 package) Radioshack 271-1317
(1x) 300Ohm resistor
Additional Materials:
Heat Shrink Radioshack #278-1611
22 Gauge Wire Radioshack #278-1224
Solder Radioshack #64-013
Drill
Hot Glue
Glue
Black diffusor material (tissue paper, plastic, etc)
Remove these ads by Signing Up

Step 1: Prepare Arduino Proto Shield

The Arduino Proto Shields are a convenient way to attach circuits to an Arduino, but I like to
trim them down a little bit first so they do not take up so much room in the project enclosure.
Start by trimming the pins down with a pair of wire cutters. Next, cut off the six pin socket.
Finally, cut the sockets from the top of the board.

Step 2: Enclosure

I decided to laser cut a custom enclosure for my project. I designed the enclosure using
AutoCAD, Autodesk 123D Make, and Corel Draw, and I've included corel draw and adobe
illustrator 2D files as well as the STL, and DWG files from this process below. If you do not
have access to a laser cutter, you can use my 2D files a guide and drill the necessary holes in a
project enclosure of some kind. Figure 4 shows the holes that should be drilled on the front
panel:
(3x) 7mm holes for gain, freq, and PWM pots
(3x) 7mm holes for four push buttons- sin, saw, tri, and pulse
(1x) 10mm hole for audio out

I cut out shapes of all four waveforms in the front of the enclosure so that I could backlight them
with indicator LEDs, you may choose to just drill four 5mm holes for these LEDs in the front
panel of the enclosure, place one LED under each momentary switch.
Also include a rectangular (11mm tall, 12mm wide) cutout somewhere on the side of the
enclosure for the arduino's usb port.
I made my project enclosure out of wood, so I had to glue all the pieces (except the bottom)
together with wood glue. I will attach the bottom panel on later in this instructable.
enclosure.stl

enclosure.stl1 KB
function generator enclosure.ai32 KB
function generator enclosure.cdr23 KB
enclosure.dwg228 KB

Step 3: Solder Button Leads

Solder a 10kOhm resistor to one lead of each of the four push buttons. As shown in the second
image, solder a green wire to the junction between the button and the resistor and a red wire to
the other end of the resistor. Solder a black wire the the second lead of the push button. It's a
good idea to cover these connections with a bit of heat shrink to prevent short circuits (fig 2).

Step 4: Install Audio Jack

Unscrew the plastic casing from the audio jack. Solder a red wire to the two stereo out pins and
solder a black wire to the ground pin (fig 3). I used hot glue to prevent short circuiting the jack
and to give the soldering joints some extra support. Finally, mount the audio jack in the
enclosure with super glue.

Step 5: Install Buttons

Snap the top of the button off and fit them into the wooden enclosure. Secure with hot glue.
Once dried, snap the black button tops back on.

Step 6: R2R DAC on Arduino Shield: Part 1

Solder eight 20kOhm resistors to the arduino protoshield. One end of each resistor should
connect to digital pins 0-7.

Step 7: R2R DAC on Arduino Shield: Part 1

Solder 7 10kOhm resistors to the protoboard so that they bridge the leads of the 8 20kPhm
resistors you have just soldered.

Step 8: R2R DAC on Arduino Shield: Part 3

Solder a 20kOhm resistor to the protoshield so that one end is connected to the 10kOhm resistor
attached to digital pin 0 and the other end is connected to a jumper wire to ground.

Step 9: IC socket

It's a good idea to use sockets for your ICs, this way you won't risk burning the IC with your
soldering iron and you can easily replace the IC if it breaks. Solder an 8 pin socket to the
protoboard as shown in the image.

Step 10: Low Pass Filter

Use a resistor and capacitor in series to create a low pass filter. Low pass filters let low
frequencies pass through and silence (attenuate) high frequencies. Connecting a low pass filter
to the output from the dac will smooth out the steps in the wave.
Here's how I calculated the value of the components in my low pass filter:
corner frequency = 1/(2*pi*R*C)
According to Nyquist's Theorum, signals cannot contain frequencies higher than half their
sampling rate. If I used a sampling rate of 100kHz, then the highest frequency I can produce is
50kHz.

if I use a 300Ohm resistor and I want a corner frequency of 50kHz:


50000 = 1/(6.28*300*C)
C = 1.06*10^-8 F
round this to:
C = 0.01uF
Connect one end of the the 300Ohm resistor to the 10kOhm resistor connected to digital pin 7.
Connect the capacitor to the other end of the 300Ohm resistor. The other side of the cap should
connect to ground.

Step 11: Amplifier: Part 1

Connect the positive lead of the 220uF capacitor to the junction between the resistor and
capacitor of the low pass filter. The other end of the 220uF capacitor connects to a 20kOhm
resistor that is connected to pin 3 of the IC socket. A 4.7kOhm resistor bridges pins 3 and 4 of
the IC socket.

Step 12: Amplifier: Part 2

Connect ground to pin 4 of the IC socket.

Step 13: Amplifier: Part 3

Connect the positive lead of a second 200uF capacitor to pin 5 of the IC socket. The other end of
the cap will be connected to the gain pot in a later step.

Step 14: Amplifier: Part 4

Connect pin 6 of the IC socket to Vin, pin 2 to ground, and snap the IC into the socket.

Step 15: Wire Gain Pot

Volume or gain of the audio signal will be controlled with the 10k audio taper pot with switch.
Connect the audio out from the amplifier and ground to either side of the potentiometer as
indicated in the picture. The middle is audio out, it will be hooked up directly to the audio jack.
Also connect a wire to the bottom and left leads on the back of the pot (figure 2). This is the
switch that will be used to connect to power in the next step.

Step 16: Connect to Battery

Connect the black wire from the battery clip to ground on the Arduino Shield. Connect one lead
from the gain pot switch to the red wire from the battery clip and connect the other gain pot lead
to Vin on the Arduino Shield.
Leave the battery disconnected for now.

Step 17: Connect Output to Headphone Jack

Connect the output from the amplifier (the negative lead of the cap connected to the IC at pin 5)
to the red wire we attached to the gain potentiometer in an earlier step. Connect the green wire
from the amplitude pot to the red wire connected to the audio jack. Connect the black wire from
the audio jack and the black wire from the pot to ground on the Arduino Shield.

Step 18: Wire Buttons

Connect all read leads from the button to 5V and all the black wires to ground on the arduino
shield (fig 1). Connect the green wires to analog in 0-3 in the following order:
analog 0
analog 1
analog 2
analog 3

=
=
=
=

pulse
triangle
saw
sine

Step 19: Wire Frequency and PWM Pots

Connect a red, black, and green wire to the 10kOhm and 50kOhm potentiometers as shown in the
images. Connect the red lead to 5V and the black leads to ground on the arduino shield.
Connect the center green wires to analog pins 4 (PWM) and 5 (frequency).

Step 20: Install Pots

Remove the side tab on all of the pots before installing in the enclosure, this will allow them to
sit flush against the wood. Remove washer and nut from each of the pots, place pot through hole
in enclosure, and secure with nut. Install all three pots in the enclosure.

Step 21: Wire LEDs: Part 1

Attach a 470Ohm resistor to the cathode of each of the four LEDs. Solder a black wire to the
other end of the resisotr and a red wire to the anode of the LED. Cover these connections with
shrink wrap to prevent short circuiting.

Step 22: Wire LEDs: Part 2

Solder the black leads from all four LEDs to ground on the arduino shield. Solder the red leads
to digital pins 8-11.

Step 23: Black Diffuser

Glue a light diffusing material behind the wave cutouts in the front panel. I used a piece of a
black plastic garbage bag.

Step 24: Glue LEDs

Glue the LEDs in the enclosure so that they are each pointed towards one of the cutout symbols
on the front panel. Here is a table for reference:
digital 8
digital 9
digital 10
digital 11

=
=
=
=

pulse
triangle
saw
sine

Step 25: Firmware

Upload the code at the bottom of this step onto the Arduino. The code uses a timer interrupt at a
frequency of 100kHz to send new data out to the DAC. The rest of the code monitors the state of
the buttons and knobs and adjusts variables accordingly. Since the interrupts occur at such a
high frequency, I had to keep the interrupt routine, the piece of code encapsulated in the
ISR(TIMER1_COMPA_vect){} as short as possible. Time intensive operations like
mathematical operations with floats and using the sin() function take too much time to complete.
I used several work around to get by this:
For triangle and saw I created the variables sawByte, triByte, sawInc, and triInc. Every time the
frequency changed I calculated the amount that the triangle and saw function would have to
increment at a sampling rate of 100kHz:
triInc = 511/period;
if (triInc==0){
triInc = 1;
}
sawInc = 255/period;
if (sawInc==0){

sawInc = 1;
}
then all the needed to be done in the interrupt routine was some simple math:
case 1://triangle
if((period-t) > t);
if (t == 0){
triByte = 0;
}
else{
triByte += triInc;
}
}
else{
triByte -= triInc;
}
if (triByte>255){
triByte = 255;
}
else if (triByte<0){
triByte = 0;
}
wave = triByte;
break;
case 2://saw
if (t=0){
sawByte=0;
}
else{
sawByte+=sawInc;
}
wave = sawByte;
break;
For the sine function, I wrote a simple python script which outputs 20000 values of
127+127sin(x) for one complete cycle:
import math
for x in range(0, 20000):
print str(int(127+127*math.sin(2*math.pi*x*0.00005)),)+str(","),
I stored this array in the Arduino's memory called sine20000[] and recalled the values I needed to
send to the DAC. This is much faster than calculating the values individually.

function_generator.ino.zip3 KB

Step 26: Last Few Connections

Plug the Arduino into your shield. Connect a 9V battery to the battery clip. Secure these items
inside the enclosure. Make sure that the Arduino's usb port is accessible from the outside of the
enclosure. Upon startup you should see the sine wave LED light up.

Step 27: Screw Back Panel

Drill four holes in the back panel and secure with screws.

Step 28: Add Knobs

Screw knobs on the three potentiometers.

Step 29: Test

Turn up the gain knob to turn on the function generator. Plug an eighth inch jack into the output
and hook up the function generator to an oscilloscope. Test out each of the waveforms and
adjust the frequency and gain to make sure they are working properly. Switch the output to pulse
and check if the pulse width modulation knob works (figs 4-6).
You will notice that the pulse wave is the only wave which truly ranges from 1Hz to 50kHz.
Since the sampling rate is 100kHz, the sine, triangle, and saw waves start to become somewhat
unrecognizable at about 25kHz (they are only comprised of 4 samples per cycle100kHz/25kHz). The saw and triangle waves only go down to about 100Hz, this is because the
values of triInc and sawInc get so low that they are rounded to zero below this frequency. The
sine wave reaches all the way to 1 HZ but the resolution stays the same for anything under 5Hz,
since the Arduino only has enough memory to store about 20 thousand samples.

Arduino DDS Sinewave Generator

Arduino Sine wave Generator using the direct digital


synthesis Method
Here we describe how to generate sine waves with an Arduino board in a very accurate way.
Almost no additional hardware is required. The frequency range reaches form zero to 16 KHz
with a resolution of a millionth part of one Hertz! Distortions can be kept less than one percent
on frequencies up to 3 KHz. This technique is not only useful for music and sound generation
another range of application is test equipment or measurement instrumentation. Also in
telecommunication the DDS Method is useful for instance in frequency of phase modulation
(FSK PSK).

The DDS Method (digital direct synthesis)


To implement the DDS Method in software we need four components. An accumulator and a
tuning word which are in our case just two long integer variables, a sinewave table as a list of
numerical values of one sine period stored as constants, a digital analog converter which is
provided by the PWM (analogWrite) unit, and a reference clock derived by a internal hardware
timer in the atmega. To the accumulator , the tuning word is added, the most significant byte of
the accu is taken as address of the sinetable where the value is fetched and outputted as analog
value bye the PWM unit. The whole process is cycle timed by an interrupt process which acts as

the reference clock. Further details of the DDS Method are described in web of course. This
article published by Analog Devices is one of many good references.
MT-085: Fundamentals of Direct Digital Synthesis (DDS)

Software implementation
To run this software on an Arduino Diecimila or Duemilenove connect a potentiometer to +5Volt
and Ground and the wiper to analog 0. The frequency appears on pin 11 where you can connect
active speakers or an output filter described later.

/*
*
*
*
*
*
*
*

DDS Sine Generator mit ATMEGS 168


Timer2 generates the 31250 KHz Clock Interrupt
KHM 2009 / Martin Nawrath
Kunsthochschule fuer Medien Koeln
Academy of Media Arts Cologne

*/
#include "avr/pgmspace.h"
// table of 256 sine values / one sine period / stored in flash memory
PROGMEM prog_uchar sine256[] = {
127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,18
4,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,
231,233,234,236,238,239,240,
242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,25
4,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,
238,236,234,233,231,229,227,225,223,
221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,17
3,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,
111,108,105,102,99,96,93,90,87,84,81,78,
76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,

16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,
9,10,11,12,14,15,16,18,20,21,23,25,27,29,31,
33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,
102,105,108,111,115,118,121,124
};
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
int ledPin = 13;
int testPin = 7;
int t2Pin = 6;
byte bb;

// LED pin 7

double dfreq;
// const double refclk=31372.549; // =16MHz / 510
const double refclk=31376.6;
// measured
// variables used inside interrupt service declared as voilatile
volatile byte icnt;
// var inside interrupt
volatile byte icnt1;
// var inside interrupt
volatile byte c4ms;
// counter incremented all 4ms
volatile unsigned long phaccu;
// pahse accumulator
volatile unsigned long tword_m; // dds tuning word m
void setup()
{
pinMode(ledPin, OUTPUT);
Serial.begin(115200);
Serial.println("DDS Test");
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(11, OUTPUT);

// sets the digital pin as output


// connect to the serial port

// sets the digital pin as output


// sets the digital pin as output
// pin11= PWM output / frequency output

Setup_timer2();
// disable interrupts to avoid timing distortion
cbi (TIMSK0,TOIE0);
// disable Timer0 !!! delay() is now not
available
sbi (TIMSK2,TOIE2);
// enable Timer2 Interrupt
dfreq=1000.0;
tword_m=pow(2,32)*dfreq/refclk;
}
void loop()
{
while(1) {
if (c4ms > 250) {
c4ms=0;
dfreq=analogRead(0);
output frequency from 0..1023 Hz
cbi (TIMSK2,TOIE2);

// initial output frequency = 1000.o Hz


// calulate DDS new tuning word

// timer / wait fou a full second


// read Poti on analog pin 0 to adjust
// disble Timer2 Interrupt

tword_m=pow(2,32)*dfreq/refclk;
sbi (TIMSK2,TOIE2);

// calulate DDS new tuning word


// enable Timer2 Interrupt

Serial.print(dfreq);
Serial.print(" ");
Serial.println(tword_m);
}
sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
}
}
//******************************************************************
// timer2 setup
// set prscaler to 1, PWM mode to phase correct PWM, 16000000/510 = 31372.55
Hz clock
void Setup_timer2() {
// Timer2 Clock Prescaler to : 1
sbi (TCCR2B, CS20);
cbi (TCCR2B, CS21);
cbi (TCCR2B, CS22);
// Timer2 PWM Mode set to Phase Correct PWM
cbi (TCCR2A, COM2A0); // clear Compare Match
sbi (TCCR2A, COM2A1);
sbi (TCCR2A, WGM20);
cbi (TCCR2A, WGM21);
cbi (TCCR2B, WGM22);

// Mode 1

/ Phase Correct PWM

}
//******************************************************************
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec
// this is the timebase REFCLOCK for the DDS generator
// FOUT = (M (REFCLK)) / (2 exp 32)
// runtime : 8 microseconds ( inclusive push and pop)
ISR(TIMER2_OVF_vect) {
sbi(PORTD,7);
oscope

// Test / set PORTD,7 high to observe timing with a

phaccu=phaccu+tword_m; // soft DDS, phase accu with 32 bits


icnt=phaccu >> 24;
// use upper 8 bits for phase accu as frequency
information
// read value fron ROM sine table and send to PWM DAC
OCR2A=pgm_read_byte_near(sine256 + icnt);
if(icnt1++ == 125) {
c4ms++;
icnt1=0;
}
cbi(PORTD,7);
}

// increment variable c4ms all 4 milliseconds

// reset PORTD,7

Results
In the upper part of the picture you see PWM signal on pin 11 and in the lower part what the
filter makes out of it. The sinewave looks not so clean but thats mainly the limited resolution of
the digital oscilloscope.

The spectrogram shows a surprisingly good result. The big peak is the output frequency of about
1000 Hz. All unwanted distortions are below 50 dB which is roughly what we expect from a
signal what is generated by an 8-bit DAC. ( 1/256 = 48dB).

DDS Spreadsheet
dds_calc A little worksheet around the DDS formula to calculate the tuning word.

PWM Output lowpass Filter


For a start you can just connect the output pin 11 to active speakers. But usually you need
lowpass filter is to get rid of the 32KHz sampling frequency in the output signal. A Chebyshef
lowpass with a cutoff at 12 KHz build with standard component values is shown here.

PWM DDS dedicated Hardware


This software implementation of DDS has of course several drawbacks in case of signal purity
and frequency range due to the limited speed of the software algorithm and analog capabilities of
the atmega chip. DDS modules which are using dedicated DDS chips are the state of the art in
signal generation and offer a frequency coverage from zero up into the 100MHz range.

WSPR Application
WSPR Weak Signal Propagation Reporter pronounced Whisper is a system which reports the
propagation of very weak radio signals over the world. The DDS Method was used here to
generate a tone sequence where four frequencies (1497,8 1499,3 1500,7 1502,2 Hz) are used
code a message in a very robust manner. This message is transmitted with a low power radio
beacon and can be observed worldwide via wspr.net .
wspr_beacon_dds_dcf_f4

You might also like