8-Bit function generator

Our world is largely digital today, comprised of 1s and 0s, and values are represented in binary. And for a lot of purposes, these two options are enough. But what about applications that require an analog output after digital processing? For example, when we play music from our phones or computers, it comes out as an analog signal. My digital function generator creates analog signals as well. But how can we convert two state binary has into the infinite possibilities of analog. To do such a thing, we need to use a DAC, a digital-to-analog-converter. You may be familiar with the term ADC or analog to digital converter, and they do essentially the same things, but in reverse order. But DACs are oftentimes left out of some microcontrollers, and you will have to find a way to implement one yourself. The best option oftentimes is to just buy a DAC ic. But what if you are curious on how to make one yourself? Well, in this video I will show you one of the most popular methods to create a DAC, and ultimately use it to create a function generator.

Before we get into the construction of the DAC, let’s quickly review how binary and digital logic work for those who are unfamiliar. There are only two states, meaning that we are using a base 2 counting system. Let me give you an example counting from 0 to 5. 0 is 0 and 1 is 1 of course. But we ran out of digits, so 2 is represented as 10. Three is 11, and to add another one, we need to add another digit, so it ends up as 100. Finally, 5 is 101. Really quite simple, but a little confusing if you are seeing this for the first time.

Anyways, one of the most popular ways to construct a DAC that will take those binary numbers and turn them into an analog signal is the R-2R ladder. Its name represents the values of resistors that we will be using to construct it. So, drawing a two bit R-2R ladder, we can see the pattern that it uses already. Each input is connected through a resistor that is twice as large as the ones that are in the column. The basic idea of how this works is that, using the binary input, we can generate a fraction of the input HIGH voltage. Let’s give an example using these two bits. Let’s input two or a 10 into the DAC. We should get a voltage that is about 2/3 of the HIGH voltage. Think of the DAC as a device that can convert binary fractions into analog fractions. So, using two bits, we have four possible different voltage outputs, all within the range between the LOW and HIGH voltages. The more bits you add, the more voltage steps you can make using the DAC. With 8 bits, you can make 256 different steps. With 10 bits, you can make 1024 steps and so on and so forth.

Now that we know the basic workings of the DAC, let’s build it up on a breadboard to see if it really works. I will be using 1k and 2k resistors. They are of a 1% tolerance so that we can get as accurate of an output voltage as possible. You can use higher tolerance resistors, just keep in mind that you may get varying output voltages. I built the DAC up to 8 bits, because AVR microcontrollers run 8 bit CPUs. Anyways, running a quick manual test, we can see that inputting 128, half of 255, gives us a voltage that is close to half of 5 volts. Anyways let’s connect the DAC to PORTD on the ATmega8a microcontroller. Now we can conveniently write numbers to the code and control the exact output of the DAC.

Now that we have the microcontroller controlling the DAC, let’s make it generate some functions for us. Starting off, I will make the simplest function that I can think of, the ramp oscillator. To generate this I simply just incremented PORTD over and over again, and the overflow causes it to go back to 0. More advanced is the triangle wave, which is the same as the ramp, but it also goes back down. The square wave is extremely simple, and doesn’t even requre a DAC, but I included it just because. Finally, I had one more function to generate, and the channel is named after it: a sine wave. Now this wave was more difficult to generate than the others because we needed to run some triginometry. To make this easy for the AVR to run, I precalculated a table of sine values on my PC. Basically, the values 0-255 were converted into radian values 0-2pi. Then we took the sine of that and added one to it, so that we didn’t have to deal with negative values. Finally, the resulting value was then converted back to values 0-255. These values were mapped to a table so that the AVR can lookup which angle corresponds to which output voltage. I know the process the generate the sine wave was a bit complicated, so I’ve included both the microcontroller and the PC code in the description.

Now, while this works properly, a proper function generate will allow us to alter the frequency. So I added a potentiometer to act as a voltage divider and connected it to the ADC on ADC0. This will now give us values 0-255 which we can use to determine what frequency to run the waves at. To make this work, I setup the internal timer which will slow down or speed up the functions as necessary. Basically, the functions work one step at a time, and will only progress when the timer allows them, and we can determine how quickly the timer executes by setting its initial value. Again, the code is in the description.

While all of this function generating works as expected, what use is it if we can’t attach a load. Currently, attaching a load, such as a speaker will completely breakdown the output because the currents flowing through the DAC have to also power the load. So, in order to preserve the output voltage, we need an op-amp buffer. In this case I will use the LM358. However, there is still one problem: the LM358 cannot go all the way up to the positive rail, meaning that the top half of our DAC output will be cut off. So, I divided the output of the DAC in half by using a resistor divider with very high value 100k resistors. Now our output is from 0 to 2.5 volts, beacuse the op amp is capable of dealing with these voltages with a 5 volt supply. One more step to make the op amp drive any load is an output npn transistor. The opamp will maintain the voltage and the transistor will power the load. The schematic is in the description if you are confused on how the circuit is wired up.

Anyways, we can now connect the speaker again and listen to our functions! I like the way the ramp sounds, and the triangle sounds like a digital sine wave. Now that we have the generator working, it is time to solder it together and make it a little more reliable. So after soldering all of the resistors onto the board along with the other components we can test it again, to see that it is working great. I also added a button to allow us to change the current function without modifying the code.

While this was a fun experiment, and taught us a lot about DACs and R-2R ladders, I would personally just use a dedicated DAC on more serious projects, for their bettery accuracy and higher bit count. If you enjoyed this video and found it helpful, please consider subscribing to see the other videos that I make. Have a good one.