This chip right here, the DAC8831, is capable of generating voltages with near microvolt precision. All you need to do is give it a binary number, and you can dial that voltage in. And devices, like this one, are readily avaliable with multiple different options for resolution, precision, speed, and so on. And that got me thinking, what would it take to make a DAC yourself. Well as it turns out, it is simultaneously simple and complicated. The idea is somewhat simple, but the execution isn’t. Before I show you the results, let me show how I did it.
In case we aren’t on the same page, DAC is an acryonym for Digital-To-Analog converter. Basically it takes some sort of digital number and converts it into an analog voltage. The digital number you give it serves as a fraction. More specifically, fractional output between a known voltage, which we will call Vref, and zero. The more bits that you have, the more resolution, since there are more possible output states that you can specify. The digital number that you give the DAC is also called a code.
There are several different architectures that you can use to achieve this functionality. R-2R DACs use a pattern of resistors in a ladder shape to add or remove voltage to the output depending on the inputs. A delta-sigma DAC uses a low bit DAC and filtering to achieve precise analog outputs. There are quite a few more DACs, but there is one in particular that I’d like to focus on today.
Let me introduce to you, a string DAC with interpolation. Ok, before we talk about the interpolation part, let’s make sure that you understand what a string DAC is. A string DAC is probably the simplest DAC that there is. Let’s take a look at a resistor divider with the two resistors having equal values, and with two switches… Congratulations! You’ve just made a one bit string DAC. As you might know a resistor divider does as the name implies, it divides between the two voltages. Our two switches allow our bit to select which voltage we want. If the bit is zero, we select the lower switch and get zero volts on the output. If the bit is a one, we select the upper switch and get half of Vref.
Ok easy enough, what about a two bit string DAC? Well, you just build a resistor divider with more resistors and more switches. As you can see, we are still selecting a voltage between zero and Vref, but this time we are given more resolution. Now we can select 0, Vref/4, Vref/2, and 3/4 of Vref. If Vref was one volt, that would correspond to 0, 0.25, 0.5, and 0.75 volts.
And a three bit DAC, well we just continue the pattern with eight resistors and switches. Hopefully now you can see the pattern that each additional bit doubles the number of switches and resistors that we need. You can express this mathematically as 2 to the power of n, where n is the number of bits. If you wanted, say, a 16 bit DAC, then you’d need 65 thousand switches and resistors. So clearly, you want to avoid using this many bits with a single string DAC. Luckily there is a way that you can divide up the DAC into multiple parts, that way we can avoid the danger of those large exponential numbers.
Let’s turn this four-bit string into two two-bit strings. On the first two-bit string, instead of tapping into only one of the outputs, let’s tap into two outputs. Let’s name these two outputs Vlow and Vhigh. We will also have to add an additional switch at the top to allow for a pair to be formed at the highest input. As you can see, we can still move the output through the string, but this time we have two outputs. The difference between Vlow and Vhigh is always the same in an ideal DAC. For example, if Vref is four volts, then the difference between Vlow and Vhigh will always be one volt, no matter where they are placed in the string.
Here comes the interesting part, we can further divide up the difference between Vlow and Vhigh by placing them at the top and bottom of our secondary DAC. In order to prevent loading of the main string, we place analog voltage buffers. Now we can further divide between Vlow and Vhigh. And with this, we have reduced the component count from 16 resistors and switches down to just 9. If we were to do this with a sixteen bit dac, and divide it up into two eight bit dacs, then we’d reduce the component count from 65536 down to just 513 plus two buffers, which is a massive improvement.
Let’s design a string of our own. To keep the experiment somewhat manageable for myself, I made a six-bit string DAC. This was mostly because a larger number of bits would have made the string much too long. Anyway, a six-bit string with the dual output needs 64 resistors and 65 switches. For the resistors, I chose 0.5% 1kOhm resistors in a 0603 package. These resistors were cheap, coming in at 2 cents each. The 0603 package allowed me to save space on the PCB and the 0.5% tolerance allowed for greater accuracy.
Now, for the switches. I wanted to use discrete MOSFETs at first, since they would make for good analog switches. But I quickly remembered that discrete MOSFETs don’t come with a connection to the substrate. Instead, it is connected to the source internally, which creates this body diode. This diode is very undesireable to our situation. To fix the problem, you could put two transistors back-to-back in order to null the effect of the diodes. This creates a new problem, in that it doubles the number of transistors that would be needed. I did end up using this method for the highest switch, but for all of the others I found a different solution.
The 74HC4051 is an analog multiplexer. It functions in a similiar way as our switches from earlier, but it also comes in a more compact package and has internal decoding logic. Since it is an 8:1 multiplexer, it will use three of our six bits. This also means that we will need eight of them to stand in for all 64 of our switches. You may be wondering now, if we are using three of the bits on the multiplexer now, how will we use the other three to select the correct multiplexer and switch? Well, before we do that, I want to revisit our Vlow and Vhigh outputs again.
Before, I simply marked the outputs as either Vlow or Vhigh, but how would we route them to the correct place? We could use two sets of switches on each node and either connect it to Vlow or Vhigh, but that’s a bit inefficient. Instead, we can combine and divide the string outputs into two separate lines, which I termed Bus 1 and Bus 2. At any time, only one switch will power a bus at a time. And we can use some logic and switching later on to decide which bus is Vlow and which is Vhigh.
In fact, when using this system each bus will alternate between being Vlow and Vhigh. And increasing the input code will only toggle one switch at a time, alternating between busses. This reduces swtiching disturbances on the output and greatly reduces circuit component count. You will, however, have to spend some time figuring out how to decode the main input to each of these busses.
And after some time brainstorming in my notebook, I found the solution. You’ll notice that since we divided the switches up into two separate groups, we need one fewer bit for the switch codes. This is easily done for both groups by simply dividing the main code by two. And since we are using binary, a division by two is done with a singular bit shift to the right. You can do this in the schematic by remapping the bits to skip over bit zero.
But we aren’t done yet, earlier I mentioned how the busses should take turns incrementing. Well, we can use remainder of the division that we just completed. Simply add the remainder to bus 1. We can find the remainder in bit 0, which we initially discarded. This way, when the code is even, both busses have the same input, but when it is odd, bus 1 is a single code ahead. In this manner, bus 2 is always ‘catching up’ to bus 1, creating this alternating pattern. This addition of one means that we will need to reintroduce a sixth bit into the bus one code.
And finally, we can decide whether bus 1 and bus 2 is Vlow or Vhigh by using bit 0 again. When bit 0 is low, then bus 1 is Vlow and bus 2 is Vhigh. And vice versa for when bit 0 is high.
Now, let’s implement this into the schematic. Like I mentioned, the division by two is rather easy, simply exclude bit zero. The addition is a bit more complicated since we needed a full adder IC. The CD74HCT283 does the job well. I needed to use two of them since each one only adds four bits individually. Connecting the carry output and input allows you to cascade the adders.
We have the codes for each of the busses, now we just need to decode the codes into selecting an individual switch. Like I mentioned earlier, the multiplexers already decode the first three bits for us. We need to take care of the rest ourselves though. The multiplexers have an enable input, which allows us to turn all of the switches off if it is high. So, I used a group of inverters and an AND gate for each multiplexer. Now the multiplexer is only active if bit 3 and bit 4 are the correct value. The highest switch is easy, since it is only active if bit 5 on bus 1 is active.
The last couple of things that I added to the schematic were this binary buffer with pulldown resistors. This way, I didn’t have to worry about floating inputs. I also added a Vref for our string. It was the REF5020. This is a precise voltage reference that will output a consistent 2.048V. And, of course, I added many decoupling capacitors.
The PCB layout wasn’t the most crazy thing, it was just really long because of the resistor string. Other than the string, I placed the bus 2 multiplexers on the bottom of the board so as to not clutter the board up. All that was left to do was generate the gerber files and have these boards manufactured.
And luckily, today’s sponsor is perfect for this. JLCPCB is a really great choice for any PCB project. In addtion to PCB manufacture, they provide PCB assembly, SMT stencils, 3D-printing, CNC machining, and mechatronic parts. This makes JLCPCB not only a very convenient service, but also very fast and affordable for any electronics project. I’ve been using JLCPCB for quite a while now, and their quick manufacturing time along with high quality PCBs has made my life so much easier. It’s super easy to have JLCPCB make your board. Just drag and drop your gerber files and JLCPCB will give you a quote. Once I received the boards, they were very high quality. I was especially impressed by the six-layer board I received and that they were able to manufacture the string DAC board despite it being quite long. The stencils were also phenominal as well. If you’ve been on the fence about where to get your PCB boards manufactured, definetly give JLCPCB a try. They have a $30 coupon for six-layer boards right now, which means you can get a six-layer board starting at just $5.
Thank you JLCPCB for sponsoring this video. Let’s put it together. I’ll put the six layer board aside for now, since I will explain it’s purpose a bit later in the video. To assemble the string DAC, I held it in place with the duplicate PCBs and taped them down. I them aligned the stencil and taped that down. Afterwards, I applied a bunch of this solder paste and spread it out with a gift card. Afterwards, I removed the stencil and started placing all of the components. Once that was done, I placed the board on a hot plate. For these larger boards, I recommend holding them down at the corners to ensure even heat spreading.
Unfortunately, I could only use the hot plate method for the top of the board. For the bottom, I got to hand soldering it all together. Having a microscope helps a lot with SMD hand soldering, but it’s definetly doable without. Just make sure that you aren’t accidentally shorting pins together, especially on these fine spaced pins.
To clean off the board, I recommend using some sort of alcohol. I used 99% isopropyl alcohol. You can use a brush to clean everything off, but I had this cool new toy to play with. Introducing the ultrasonic bath. It works by passing high frequency sound waves through the bath, creating tiny bubbles which then collapse and remove contaminants from the PCB. It took three quarts of isopropyl to fill the bath, which is quite a bit. I then placed my PCBs and my tweezers in the bath for cleaning. I set the temperature to 105 fahrenheit and let it clean for 10 minutes. Make sure to put the lid on so that the alcohol stays in the bath. After it was done, I made sure to drain all of the alcohol from the bath into a separate container. The stuff isn’t the cheapest, so it’s a good idea to reuse it in future baths.
I was impressed by how clean the items were after the bath though. The sticky residue from the flux was completely removed. And, since it was isopropyl, the boards dried very quickly.
Now we can finally test the string DAC. Except, not yet since there are a lot of errors. I had to go back and find a bunch of tiny issues with my hot plate soldering, which was weird since hot plate soldering is usually the easy part. Nothing wrong with JLCPCB’s services, I think I just went a bit light in the solder paste in a few spots. After resoldering loose joints and disconnecting a couple of shorted pins, the board worked. Well almost.
Somehow, I overlooked what happens when the highest switch on bus 1 is activated in the DAC. You see, when bit 5 is active, so is the switch, which is correct. But the lowest multiplexer doesn’t take this into account and just sees the zero code. This means that the highest and lowest switches are active simultaneously when we are at the fullscale code. It was an easy-ish fix though. I found this MM74HC32N OR-gate IC in my parts bin. To get it to fit on my board, I bent all of the pins up. Since I was only going to use one of the OR gates, I clipped all of the other outputs. Luckily, I placed a GND test point in a convenient spot, so I was able to use that as an anchor point for the chip.
I then cut the trace between the inverter and the enable pin of the multiplexer. Basically, I want the multiplexer to function in the same way as before and also disable whenever bus 1’s bit 5 is active. The OR gate allows this since a high signal disables the multiplexer. I wired up the connections with copper wire and that solved the problem. Now we just need a way to test all of the inputs codes. We could do this manually, but I’d rather computerize the processes.
Before we test our DIY DAC, I’d first like to test a commercial DAC chip so we have a point of reference to compare it to. I’ll be using the DAC8831, which I mentioned in the intro. I will be creating a testing platform which will have a DAC code writer, the DAC itself, a voltage reference, and a measurement device, all controlled by a PC. And when I mention a PC, I really mean a Raspberry Pi since I’d rather not dedicate my main computer to this test.
For the code writer, I opted to go with an ATmega8A, which is my go-to microcontroller for easy experimentation. My idea is that the PC will send a command over serial. The ATmega8A will then decode that command and send it to the DAC itself. To allow control over serial, I added an FTDI board to convert from USB to serial. The ATmega8A then connects to the DAC using SPI. When writing code for the ATmega, I was able to copy the basics from other projects that I’ve made in the past. We need to be able to receive characters from the UART and to send 16-bit data to the DAC.
Sending 16-bit data was easy enough. Simply do two SPI writes, and keep CS low until both bytes have been written. Receiving characters was easy enough since I’ve already written code for that in the past. The hard part was receiving multiple characters and putting them together into a single string. To do that, I made a loop that would fill up a buffer until it was either full or a newline character was detected. We are able to tell if the buffer overflowed when NULL is returned. I then made the base of a kind of overkill command decoding system. Basically, it decodes a command in the form of code . Either way, I was able to get it working. I wrote a python script on the PC side that sends the command over serial, allowing me to select any arbritrary code.
But what good is being able to automate setting the code if we can’t automate reading it either? Well, that’s where this multimeter comes in. This is a Keithley 2010 7.5 digit multimeter, which makes it plenty accurate enough to read the precise voltage output of the DAC. And it comes with a serial port on the back of it aswell, allowing for remote reading and control. The serial interface then uses the SCPI protocol for sending commands. Luckily, SCPI isn’t all that difficult to understand.
The basic structure of an SCPI command is as follows: the command you’d like to execute potentially followed by a value for that argument and ended with a newline. Well, it isn’t always a newline, but that’s the way I set it up. Make sure to change your meter’s settings to terminate with a newline if you ever decide to follow along. The commands themselves also have an interesting structure. They name themselves in sort of hierarchy pattern, with each level separated by a colon. For example, to set the number of digits the multimeter reads, we first need to specify that we are measuring a voltage, and then further specify that we are measuring a DC voltage. Then we tell it how many digits we’d like to use.
It also looks really odd since the capitalization seems inconsistent. There is a reason for this though, and that is that the lowercase letters are actually optional, since the uppercase letters are enough for the meter to deduce which command you are talking about. I left in the lowercase letters to make it easier for us to read. A good way to test whether your system works is by sending the *IDN? command. If it was successful, the meter sends back information about itself.
by using the FUNCtion command. I then set it to measure of 10 PLCs. PLC stands for power-line cycle, and it represents the amount of time it takes for the AC mains input to complete one period. So 10 PLCs means that it takes the meter 10 cycles to complete one reading. A longer integration time allows for a more accurate reading, but it makes the test take longer. I also disabled auto ranging in order to ensure that any gain error remained consistent throughout the test. Of course, all 7.5 digits of the meter were used. The filter averages multiple readings together to get one average reading. I disabled it for this test though. And finally, I enabled autozero. To read from the meter, you send the READ? command and receive the data over serial.
With the SCPI protocol figured out, we are now able to script an all codes test. Basically, we want to determine the voltage output at every single input code. It’s really simple, all we have to do is run through a for-loop with 65536 iterations. Inside of the loop, we write the code, wait a second for the output to settle, and then read the voltage from the meter. Once the loop is complete, we save all of those readings into a csv file. Now all that’s left is to run the code.
Now I can finally explain to you why we’d want to know the output value of each code. The first is that we want to make sure that every code is close to what it’d ideally be. For example, hex 8000, which is half of the fullscale code, should output exactly half of the reference voltage. A graph that would display this would be an INL chart. INL stands for integral nonlinearity, and it shows us how much the output differs from ideal. For example, an ideal dac would have a perfect straight output line and the INL chart would have every single value at zero.
A less ideal DAC might have an output like this, where the middle codes are a great deal larger than they should be. And as you can see, the INL chart makes it easier to visualize exactly how much it deviates from ideal. Now let’s take a look at our DAC8831. As you can see, just by looking at the output vs the input code, it looks like an ideal DAC, aside from the beginning bit. This is where the INL chart really shines, since we can more easily zoom in on the error.
And my first attempt at chart didn’t really go well, and it had something to do with my measurement. I think the problem is that since the DAC8831 is an unbuffered DAC, my multimeter’s internal resistance was messing with the R-2R DAC structure at first. But the good news is that this problem disappears at higher codes. So I adjusted the chart to consider the INL from code 6000 to fullscale. As you can see, the output voltage stays very close to what it should ideally be. Staying within about -1.5 LSBs. In case you didn’t know, when I refer to an LSB, I mean the amount of voltage that increasing the code by one should be. So one LSB is equal to 31.2 microvolts. In other words, the DAC output stays within about 40 microvolts of what we should predict. Compared to the datasheet, our INL chart is decent, the differences could be attributed to my test setup or just differences in the device under test.
There is another kind of nonlinearity that we should take a look at: DNL. DNL stands for differential nonlinearity. DNL is different from INL since it doesn’t keep track of the overall output, but rather the difference between two adjacent codes. For example, if we start at code 7000 and increment to 7001, the DNL at that code would be the voltage difference between them minus 1 LSB. Again, we adjust voltage into our LSB unit and an ideal DNL chart would stay at zero. This means that every code should increase by one LSB exactly in an ideal DAC.
If, for example, the code increased by 1.5 LSB, then we would have an error of 0.5 LSB, since it is 0.5 higher than the ideal 1 LSB. If we take a look at the DNL chart of the DAC8831, we will find great performance with the error generally staying under +/- 0.25 LSBs. This data actually lines up pretty well with the datasheet’s. This chart is actually really important for a specific reason: you really don’t want the DNL to ever go below -1.
Why is this so important? Well, if the DNL goes below negative one, then that means that increasing the DAC code actually caused the output voltage to decrease. This can actually be a big problem for control systems. The ability to not do this, to always stay above -1 DNL, is called monotonicity. And since the DAC8831 never comes close to dipping below -1 DNL, we can call it a monotonic DAC.
This is one of the advantages of a string DAC actually, since it is literally impossible for a higher code to give you a lower voltage. That is unless you somehow have a negative resistance, but if that were true then you’d probably have bigger ideas than the monotonicity of a string DAC.
With that out of the way, let me show you how the DIY string DAC performed. Each graph I’ll show you will come in a pair, since we have a Vlow and a Vhigh output. I did this make sure that the behavior of each output is consistent with the other. Also remember that we are only dealing with six bits right now, so each LSB is 32mV. That’s why the data seems so impressive, because it’s really easy to hit a 32mV target compared to the 32uV target that we were looking at before. And while the string DAC makes some interesting shapes, it really isn’t the reason why I made this video. Let’s finally talk about what I meant when I said that the DAC in this video was made as a string DAC with interpolation.
Like I mentioned earlier, you can split up your string DAC into two pieces. This greatly reduces the number of resistors and switches, but it comes at the cost of needing two additional buffers. The circuit I’m about to show you not only eliminates the string buffers, but can also partially eliminate an output buffer, and it has the same output behavior as a second string.
To begin, let me show you an op-amp buffer. It’s a lot like the op-amp I’ve shown you in a previous video. I’d recommend watching that if you don’t know what I mean when I say “differential pair”. I’ve changed this design to use MOSFETs because they have much higher input impedance compared to BJTs, which is the thing that will allow us to eliminate the string buffers. Anyways, as you should know from the op-amp video, when we wire the inverting input to the output we buffer the output. In other words, the voltage on the non-inverting input is buffered onto the output. Let’s permanently keep this first input connected to Vlow.
The thing I’m about to show you is a bit unconventional: let’s parallel another differential pair to the first. This is going to look really odd the first time that you see it, but it’s still our op-amp buffer from before just with two inputs. If we connected this new input to Vlow, then really nothing changes since the output will still drive Vlow on the output. But what about if we connect it to Vhigh? Well, this is when things get interesting. The first differential pair wants the output to be Vlow, and the second pair wants it to be Vhigh. They will both split their currents according to what the goal of the pair is.
This splitting of currents causes the output to be exactly equal to half of Vlow and Vhigh. You may now notice that this is a one bit DAC. Placing a zero, or in this case Vlow, on the input gives us Vlow, and placing a one, in this case Vhigh, we get half of the difference between Vlow and Vhigh. And since we are using MOSFETs, Vlow and Vhigh aren’t loaded from the gate impedance.
Now how would we make a two bit DAC from this. Well, a two-bit DAC has four possible output states, so we need to distribute the current in four possible ways. This means we need four differential pairs. Again, the first pair is always Vlow. The second pair is bit zero, and the third and fourth pairs are bit one. At code zero, all pairs are Vlow, so we get Vlow. At fullscale, three of the four pairs are Vhigh, so we get 3/4 of Vhigh. At midscale, two of the pairs are Vhigh, so we get half the voltage on the output. At this point, you should be able to notice that this is the same behavior that we had when we were examining the two-bit string DAC at the beginning of the video.
You will also notice that we run into that same exponential problem. This is OK, since we are reducing the number of bits anyway by cascading DACs, but we can still improve on it. Instead of using two differential pairs with the same current for the second bit, we can use one differential pair with double the current. So, instead of using an exponential number of differential pairs we are using an increasing current. This is a nice trick to save space, but it does degrade linearity performance since the pairs are no longer identical. I found in my simulations, that a good balance between maintaining identical pairs and saving space is having at most 4x the current of the first differential pair. This approximately reduces the number of pairs that we need by four, which is great since we are going to be making this interpolator 8-bits long.
Before we build all of those differential pairs, let’s make a few improvements to the core op-amp first. The lower current mirror can easily be improved by giving it a cascode. This helps because changing the drain-source voltage on FETs changes the drain current a bit, and the cascode keeps that voltage more constant. This now causes another problem where we can’t go nearly as low on the output voltage. A few other iterations of this design had the same problem, so I decided to shift the whole headroom problem to the positive rail since we have plenty of voltage up there.
To do that, I added a folded cascode. You may be familiar with cascodes from my BJT video, and a folded cascode has the same properties as a normal cascode, with the exception that we use different polarity transistors, and in this case we go from the P-channel differential pair to the N-channel cascode transistors. We do need an additional current source for each cascode though. The bias voltage for the cascode is generated by a green LED, which gives us roughly 3V. We top off the cascode with a current mirror in the same way that you would with a traditional op-amp. The output stage is also the same, with a class-B configuration. Which means that we no longer need to add a dedicated output power buffer.
Let’s now talk about how we will translate those 1’s and 0’s into Vhigh and Vlow inputs, and it’s actually really simple. Remember that analog switch we talked about in the string DAC section of this video? Well, we can do that to either switch Vlow or Vhigh to each differential pair group depending on the value of the corresponding bit. All we need is a few of those MOSFETs and a few logic inverters.
This whole design works really well in the simulator, but it most definetly won’t in real life. Remember how I said that linearity is affected by how identical each pair is? Well, in real life, we have to worry about the matching of each MOSFET in each pair. This isn’t that much of a problem for integrated DACs, since they are all manufactured on the same silicon, using the same process, which means that matching errors will be minimal. But we will be making this out of discrete transistors, so we won’t have that advantage.
Let’s see how easily we can match each MOSFET by hand. I have a pair of these 2n7000 N-channel MOSFETs that we can compare. If you have one of these DCA analyzers it’s actually really easy to test. Simply connect a lead to each MOSFET pin, it doesn’t matter in which order since the tool will figure that out itself. Click test and you will get all that you need to know. But what if you don’t have one of these analyzers? Well, let’s take a look at some manual testing.
MOSFETs operate on a principle called transconductance, which basically means that a voltage on the gate will cause a current to flow through the drain. For our testing circuit, I will place a MOSFET in series with a 1k resistor. Our supply voltage will be 10V. The idea is that we will place specific voltages on the MOSFET’s gate, and we will measure the current through the MOSFET’s drain.
So I started the test by incrementing the voltage in roughly 100mV steps. A handheld multimeter was used to verify the gate voltage and the bench multimeter was used to measure the current. As expected, increasing the gate voltage increased the drain current. What’s interesting to note though, is that the relationship between the two is exponential. Up to a certain point, increasing the gate voltage doesn’t affect the current very much. But at the threshold voltage, it suddenly increases very quickly. The DCA tool defines the threshold voltage at the point where the drain current is equal to 5mA, so the equivalent here would be about 1.8V.
My data gets capped at about 10mA because of the 1k resistor. If I were to remove it, then we could get much larger currents. The datasheet actually has a plot for this same data themselves, and the results line up closely enough. It also shows us just how much current you can get through the device with enough of a gate voltage. I’d also like to point your attention to the threshold voltage figure on the datasheet. You’ll see that the difference between the minimum, typical, and maximum values has a huge range. That’s a 2.2 volt difference!
This is rather big problem for us when we are trying to make a precise analog circuit, since I’d rather not have to go through and match 100s of MOSFETs into pairs. To get a better feel for this, let’s make two current mirrors from random MOSFETs to function as current sources and see how well they are matched.
I’m aiming for 25mA through each current mirror, so I setup the reference MOSFET with a 100 ohm resistor. The other two should ideally be 25mA as well since they all share the same gate voltage. But here on the breadboard, the matching just isn’t there. The main FET has 27mA flowing through it. The 1st mirror has 23.7mA and the 2nd has 29.2mA. So as you can see, it’s using MOSFETs alone causes too massive a difference to be used reliably alone like this.
If you have watched my video on bipolar transistors, then you should be familiar with emitter degeneration. MOSFETs have a similar setup in the form of source degeneration. The larger the source resistor, the less the transconductance value of the MOSFET affects the transconductance value of the subcircuit as a whole. I tried out several different source resistors on our current mirror, and the current values did start to converge. At 22 ohms, they were 11.25mA and 9.71mA. At 220 ohms, they were 1.93mA and 2.1mA. And at 2k2 ohms, they were 264uA and 293uA.
This is definetely interesting because it demonstrates how increasing source degeneration greatly improves matching, but it comes at the cost of greatly reducing your gain. This shouldn’t be an issue though since we are aiming for an identical, but constant current from these devices. To get even better results we should also degenerate our reference transistor, but at this point I think we’ve outgrown the breadboard, since the currents keep jumping around a bit.
To get a proper demonstration of how well this degeneration improves matching, I got three of the MOSFETs that I will use in the final design, which were these small SMD types. I put all of them on these adapter boards and them put the circuit together on a prototyping board. This time, all of the transistors have 56 ohm degeneration resistors. The currents this time were 19mA for the reference current, and 18.6mA for the 1st mirror and 18.89mA for the second mirror. This is much better compared to before, but the currents are still somewhat off from each other. To combat this in the final design, I added trimpots to all currents that needed to be precise.
At this point, I also got to wondering whether source degeneration would help with the differential pairs and not just the current sources. Yes, the source degeneration kills our gain, but that’s ok since we don’t really need much gain anyways. It does essentially eliminate the differences in transconductance. The simulation stills works well even with all of these source degenerators, so I decided to go ahead and apply this to my schematic with the hope that I wouldn’t have to manually match these pairs.
Anyways, with the schematic complete, I went ahead and completed the layout using six-layers. Thanks again to JLCPCB for supplying these boards. They did a great job with both of them. The SMD soldering for this one was easier than the string since the vast majority of the components were on the top layer this time around. I was able to use a hot plate to solder everything on the top and then I hand soldered the resistors and capacitors on the back. The hard part for this board was soldering all of these 1x2 headers. Once I was done soldering, this board got a bath just like the other board did.
The purpose of the headers was to allow me to calibrate the current sources by measuring them with my multimeter. Once the current was at its appropriate value, I connected it to the circuit with a jumper. This also took a while to get all of the current sources at their correct values. But once that was done, I verified that the circuit was working by double checking that the output was buffering Vlow when all of the inputs were zero. There was a bit of a DC offset on the output, but I figured that it could be trimmed later if needed.
Let’s get into those linearity measurements. I hooked up the string DAC’s Vlow and Vhigh to the Vlow and Vhigh inputs on the interpolator circuit. The first thing that I did was set a constant fullscale on the string. I then set my tester to run through all eight bits of our interpolator. And, well, here’s the result. We get this repeating spiky pattern, which unfortunately means that this DAC is not monotonic. But why is it like this?
Well, remember our discussion on the matching of the differential pairs? Well, it occured to me after putting this together that while the degeneration solved the problems with matching transconductance, it did not solve the problems with matching the gate threshold voltage. You’ll also see that the pattern occurs pretty often. This means that the lower bits of the DAC are contributing most to it. The upper bits also have problems matching with each other, but they also have multiple pairs per bit, which means that the matching error gets averaged out.
The performance of the DAC is still somewhat encouraging for a DIY attempt. According to the INL chart, we are always within about +/- 3 LSB everywhere on the interpolator. In this case one LSB is 125uV, which makes 3 LSBs equal to 375uV. So we atleast have sub-milivolt precision.
With all of the interpolator’s testing complete, let’s test the DAC as a whole now. This is a 14-bit DAC, so that means we will have 16384 steps to get through. After a couple of hours, the test was complete. The DNL plot is what we would expect from the previous DNL plot of the interpolator, with a maximum of 2 LSB and a typical minimum of about -3.5 LSB. The INL plot was rather disappointing, with a peak of 130 LSB, which is equal to 16mV. Either way, this is a good demonstration of why these plots are so important, since the output voltage plot doesn’t really show us any of this behavior on its own.
For the entirety of this video, you may have been wondering why I was even bothering with this whole interpolator system when we could have just used a secondary string. Well like I mentioned earlier, it removes the need for all of those resistors, switches, those two buffers and potentially the output buffer as well. This sounds ridiculous when you look at the size of my board, but this is really just an educational demonstration for YouTube. In reality, this method only saves space if you are working on a semiconductor, where everything is much, much smaller. Remember there are no monolithic buffer ICs or multiplexers in nice, neat packages. Instead each transistor must be drawn at a similar scale to all of the others.
If we go by transistor count, my design still used fewer transistors than if I had gone with an equivalent dual string. The string DAC used 65 switches, which we will approximate to 65 transistors. There were six switches to direct bus 1 and bus 2 to Vlow and Vhigh. The interpolator used 245 transistors. Combined, my project had an approximate 316 transistors not counting the string DAC’s digital decoding logic. A 14-bit DAC made from a six-bit cascaded by an eight-bit DAC would use 321 on switches alone. This doesn’t account for the additional digital decoding needed for the second string nor does it account for the additional buffers nor does it account for the additional 256 resistors.
Hopefully you can now see why I have shown you this circuit, and why it’s important for DACs designed on a semiconductor. This design is great for saving space on the chip’s surface. It isn’t practical at all for discrete designs, and you would probably get better performance by just using a second string.
Well, that’s it for this video. While the DAC that we made didn’t fully match the commercial options that you’ll find on Digikey, I think that we were able to make good progress in understanding how these devices work. If you’ve enjoyed this video and learned something new, please consider subscribing. Also visit my buymeacoffee page, because with your support, I can keep making these videos. Thanks for watching, have a good one.