Arduino Function Generator (Part 3)

In my previous posts in this series I looked at a couple of ways to use an Arduino to generate analogue waveforms.

In this third part I look at a much simpler, IC-based digital to analog (DAC) circuit to provide the waveforms, and look at ways of changing the frequency of the output.

Let’s start with the new circuit.

In part two of this series, I used an R-2R ladder resistor network built from discrete components. This time I’m going to use the same concept (a R-2R ladder), but rather than building it from discrete resistors, I’m going to use a pre-built IC. In this case I used the TI TLC7226, but the same principles will apply to most DACs. The 7226 actually has four 8-bit DACs within it – but we’ll only be using one of them here.

The circuit is very simple – and can basically be designed by looking at the data sheet. Essentially there are some some voltage inputs & grounds, the eight input bits, the four output, a two-bit address selection (to determine which of the four outputs to use), and a latch.

Given that we’re only interested in one output – we can simply pull both of the address bits (pins 16 & 17 – A0 & A1) high to select (in this case) output D. We also need to supply 5v to the Reference voltage pin (pin 4) and the positive supply voltage (Vdd – pin 18); to do this we can simply use the 5v supplied by the Arduino’s 5v out pin. We’ll connect the analog & digital grounds (pins 5 & 6), and the "negative" supply voltage to the Arduino’s ground.

This just leaves the eight data bits – which we’ll connect to digital pins 0 – 8 on the Arduino (as before) – and the latch. The latch acts as a switch so we can change the input voltages (and / or switch to a different DAC) without affecting the output. We’ll connect this to another of the Arduino’s digital pins (here, pin 9).

The code is almost identical to the code we used previously.

[code lang=”cpp”]
int sine[255];

void setup()
{
pinMode(0, OUTPUT);
pinMode(1, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(9, OUTPUT);

float x;
float y;

for(int i=0;i<255;i++)
{
x=(float)i;
y=sin((x/255)*2*PI);
sine[i]=int(y*128)+128;
}
}

void loop()
{
for (int i=0;i<255;i++)
{
digitalWrite(9, HIGH);
PORTD=sine[i];
digitalWrite(9, LOW);
delayMicroseconds(2);
}
}
[/code]

Creative Commons License


IC ADC sine code by AJP is licensed under a Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License.


The only difference, is the code opening and closing the latch.

Hooking it up to our oscilloscope shows a nice smooth sine wave – as before.

Be careful not to wire the data pins up in the wrong order: if you do, you’ll get something that looks more like this…

So, what about changing the frequency?

Well, it’s pretty simple: we just chance the value of the delay constant; just as we did with our square wave all the way back in part 1.

There’s a bit of a problem with this though. Even if we turn the delay off altogether – we still only get a frequency of 400 Hz: and that’s pretty slow, given the clock frequency of the ATMEGA 328 is measured in MHz…

Can we make things any faster?

Well, let’s try.

One potential candidate is the array access. Depending on just how well optimised a compiler is, array access can be slow – since it typically requires multiple operations (including multiplication) to decode the address of the data, from the array index. Using the fact that the Arduino code is (essentially) just C, and the fact that we want to access the data in the array sequentially – we can write a potentially more efficient loop to access the data in the array by using pointers.

All we need to do is add an int pointer to the global variables, set this to the address of the 0th element of the sine array at the end of the setup() function, and then replace the array lookup with an oration dereferencing the sum of the pointer and the value of i, to yield the data stored as the ith element.

[code lang=”cpp”]
int sine[255];
int *p;

void setup()
{
pinMode(0, OUTPUT);
pinMode(1, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(9, OUTPUT);

float x;
float y;

for(int i=0;i<255;i++)
{
x=(float)i;
y=sin((x/255)*2*PI);
sine[i]=int(y*128)+128;
}
p=&sine[0];
}

void loop()
{
for (int i=0;i<255;i++)
{
digitalWrite(9, HIGH);
PORTD=*(p+i);
digitalWrite(9, LOW);
}
}
[/code]

Creative Commons License


IC ADC sine code pointers by AJP is licensed under a Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License.


If we run this, we find (disappointingly) that it doesn’t make any difference. We’re still only getting around 400Hz.

With the restructured loop now only containing a (fast) pointer operation, and the two calls to digitalWrite(); and of course it’s these two function calls that’s slowing things up.

I’ve previously mentioned that the Arduino’s digitalWrite() function is very slow: so what else can we do?

Well we could substitute the digitalWrite operations with another direct access write to the port registers, as we do for the eight data pins.

To do this, we simply replace digitalWrite(9, HIGH); with PORTB |= B00000010;, and we replace digitalWrite(9, LOW); with PORTB &= ~B00000010;

We could simply write B00000010 (i.e. the decimal value 2) to the PORTB register – but bitwise operations are very fast, and OR-ing it with the existing contents of the PORTB register means that we will only change the bit governing the function of pin 9. Similarly we could write a 0 to the register when we want to set the pin LOW – but by AND-ing the complement of 2 with the register we again only change the state of "pin 9’s bit"

Running this, we discover that we get an order of magnitude improvement over the previous code (digitalWrite is REALLY slow!).

Is this the best we can do?

Well, no. We can go one better.

Recall that the latch is used to prevent the output of the DAC wandering as the data bits are set; but since we’re writing a full 8-bit word to the PORTD register, all the data pins will change simultaneously: so we don’t actually need to use the latch.

With a minor modification to the circuit we can eliminate the latch – and save ourselves even more time within the loop. All we need to do is connect the 7226’s latch pin (WR – pin 15) to ground, and the DAC will change the output voltage as the data bits change. Once we’ve done this we can then eliminate both of the operations to change pin 9 of the Arduino.

Doing this gives us an output frequency of around 4.8KHz as measured on my oscilloscope.

And that really is the best we can do!

How does our simple function generator compare with a professional piece of test equipment? Well, one of the major limitations is the frequency range. "Real" function generators (even cheap ones) typically have frequency ranges extending into the MHz – whereas ours is limited to a much more sedate 5 KHz. More seriously is the fact that it’s pretty tricky to alter the frequency to any arbitrary choice with the Arduino. Introducing a single microsecond delay drops the frequency from around 5KHz, to around 2KHz. The only other way to slow the output would be to introduce more than 256 samples into the array – doubling up some number of samples: though this would (of course) also have the effect of roughening the smoothness of the sine wave. A real function generator (especially a digital one) would let the user generate any arbitrary frequency they choose. There’s also the fact that an analog function generator would generate a real (perfectly smooth) sine wave – rather than our 8-bit approximation (which could be important in some applications) – and a real digital function generator would typically have far better than 8-bit resolution – 28-bit resolution would be more typical. Finally real function generators let the user modify the amplitude and DC offset of the output signal: although we could modify our circuit to provide both of these features.

That said this project uses the same basic principles as a direct digital synthesis function generators – and is perfectly usable for simple applications, when precise control of the output signal is not required. For example, if you wanted to play with RC filter circuits and wanted a simple source to test them with – this Arduino circuit would be ideal (and costs very much less than a function generator).

If you’ve enjoyed this series, then do let me know – and if you’ve any questions, then please feel free to ask them in the comments: and I’ll see what I can do to help.

2 thoughts on “Arduino Function Generator (Part 3)

  1. Very cool project. I’m doing my own version of it with a Mega 2560 and this was very helpful to get me started. Just one comment: pointer access to a specific memory location is actually slower than reading the same location in the form of an array. Arrays are compiled as single instruction access commands, while incremented pointers like in your example need several commands.

Leave a Reply

Your email address will not be published. Required fields are marked *