Digital to Analogue Converter (DAC) DAC Theory

A digital to analogue converter takes a series of digital inputs (a string of 1s and 0s, in our case there will be 8 of them like 10011001) and converts it into an analogue output. You see DACs in every digital audio device (MP3 players, CD players) as these all store music in digital form, but need to drive a speaker with an analogue signal. Hence the need to convert the digital data into an analogue signal.

Here’s an example of how digitization works (figures from the Wikipedia article on DACs). To digitize an analogue signal like a wave we sample it at a typically fixed frequency (taken to be sufficiently high so that we do not hear artifacts due

to the sampling) and save the samples in digital form:

The DAC does the reverse: given

the samples in digital form, re-create the analogue waveform:

Of course, this is

approximate. The steps will always be present, but as long as they are small enough, they may be smoothened out. To make them small enough we need to be able to sample accurately and use a large number of bits to represent the signal. For example, if we use only 4 bits to sample the waveform, we will have a resolution of only $2^4 = 16$ levels. With 8 bits the number of levels we can represent increases to $2^8 = 256$ levels. This is still not good enough for a commercial audio system, but it will be good enough for this experiment. Our goal is to build an 8-bit DAC that accepts sequences of 8 binary numbers (a byte) and outputs an analogue representation of that sequence:

Binary Numbers and BitsWe usually use Decimal numbers in our calculations. Computers use Binary numbers. Each binary number consists of a sequence of bits. These are 0s and 1s. A sequence of 8bits is called a Byte. In this experiment we will work with bytes. How do we count in binary? Here is a sequence of binary numbers and their decimal equivalents (I’ve used 4bit sequences for conveneince). The subscripts indicate the number system in use: $2$ means binary, and $10$ means decimal. \begin{eqnarray*} 0000_{2} & = & 0_{10} \\ 0001_{2} & = & 1_{10} \\ 0010_{2} & = & 2_{10} \\ 0011_{2} & = & 3_{10} \\ 0100_{2} & = & 4_{10} \\ 0101_{2} & = & 5_{10} \\ \end{eqnarray*} How do we convert from binary to decimal? Consider the binary number $(b_3 b_2 b_1 b_0)_{2}$. The rule for converting it to decimal is: \begin{eqnarray*} (b_3 b_2 b_1 b_0)_{2} & = & b_3 \times 2^3 + b_2 \times 2^2 + b_1 \times 2^1 + b_0 \times 2^0 \end{eqnarray*} Or, more generally, for the $n$-bit binary number: \begin{eqnarray*} (b_{n-1}\cdots b_2 b_1 b_0)_{2} & = & \sum_{i = 0}^{n-1} b_i \times 2^i \end{eqnarray*}

On a computer a $1$ is represented as a HIGH voltage (5V on the Arduino) and $0$ as a LOW voltage (0V on the Arduino, though on some systems all that may be necessary is a sufficiently low voltage.

For more on binary numbers see the Wikipedia article.

Cool fact: if you counted in binary using your fingers you’d be able to count from 0 to 1023.

In our setup, we will use the Arduino Uno to set the digital pin values. This will be done in software. The DAC will be made of resistors in what is known as an R-2R network. You will find more information about R-2R networks on the links provided below.

If you are interested in learning how an R-2R network works have a look at these links:

Parts

In the first part of the experiment we will use 5% resistors in the DAC. Subsequently we will re-construct it using 1% resistors.

• 9 20 kOhm resistors 5%/1% metal film
• 7 10 kOhm resistors 5%/1% metal film
• Arduino Uno
• wire jumpers

The actual values of the resistors may vary. What is important is that one set of resistors has resistances twice the other.

Circuit

Here is a breadboard view of the DAC circuit. The second breadboard is included as we will place components here as the circuit develops. Also, there is a thin wire from one of the 20K resistors to ground. Ignore it. Click on the images for a larger view.

We will first use 5% resistors to construct the DAC. Make sure you use these and not the more accurate, 1% varieties! The 5% resistors may be 10KOhm and 22KOhm. If so, the resulting network will not be exactly an R-2R network, but that is OK. You will see the point of using these 5% resistors below.
These circuit diagrams have been made with the Fritzing program. You can access the Fritzing file for the DAC here.

The Fritzing files (*.fzz) are editable. You can save these and edit them as you see fit. This could be useful for your own projects and for illustrating circuit diagrams for your report. But bear in mind that the edits are time-consuming so don’t try to do it during the lab!

Programming the Arduino

Having constructed the DAC what we now need to do is program the Arduino to send a byte (8 bits) of data to the eight inputs of the DAC. Here is a code which does this:

/*
DAC: Single byte
A. J. Misquitta
*/

void setup(){
//set digital pins 0-7 as outputs
for (int i=0; i<8; i++){
pinMode(i,OUTPUT);
}
}

void loop(){
digitalWrite(0, HIGH); // 1 : we start from the rightmost bit
digitalWrite(1, LOW);  // 0
digitalWrite(2, LOW);  // 0
digitalWrite(3, LOW);  // 0
digitalWrite(4, HIGH); // 1
digitalWrite(5, HIGH); // 1
digitalWrite(6, LOW);  // 0
digitalWrite(7, HIGH); // 1
}

For details of Arduino sketches see the Arduino Getting Started page. If you haven’t already gone over the material and examples on that page, please do so before proceeding. Let’s look at what this sketch is up to. Recall that every Arduino sketch has a setup part and a loop part. The instructions in the setup part are exectuted only once, but the instructions in the loop part are executed till the device is switched off.

The first bit of code sets up digital pins 0 to 7 as outputs. We have used a for loop to do this:

void setup(){
//set digital pins 0-7 as outputs
for (int i=0; i<8; i++){
pinMode(i,OUTPUT);
}
}

This bit of code is equivalent to

void setup(){
pinMode(0,OUTPUT);
pinMode(1,OUTPUT);
pinMode(2,OUTPUT);
...
pinMode(7,OUTPUT);
}

but the former is clearly a lot more compact!

Next we enter the loop part in which we write the byte 10110001 to the DAC input pins. This is done by setting appropriate pins to HIGH (= 1) and others to LOW (= 0):

void loop(){
digitalWrite(0, HIGH); // 1 : we start from the rightmost bit
digitalWrite(1, LOW);  // 0
digitalWrite(2, LOW);  // 0
digitalWrite(3, LOW);  // 0
digitalWrite(4, HIGH); // 1
digitalWrite(5, HIGH); // 1
digitalWrite(6, LOW);  // 0
digitalWrite(7, HIGH); // 1
}

The general form of the digitalWrite (the upper case ‘W’ is essential!) is

digitalWrite(pin,VALUE);

where VALUE = HIGH or LOW.

Question: What is the decimal value of the binary number 10110001?

This is all very well, but how would you read out the result of the DAC? We have a few options: we could use a multimeter to do the reading (do it), or an oscilloscope, or use the Arduino itself. Let’s now explore the latter option.

Using the Analogue Input pins on the Arduino

The Arduino Uno includes 6 analogue input pins labeled ‘A0’ through ‘A5’. Locate these on the board. Each of these can read and digitize an analogue signal from 0 to 5 Volts using a 10 bit Analogue to Digital converter (ADC). A 0V signal will correspond to Bin(0000000000) and a 5V signal to (11111111111). Notice that these are 10-bit binary numbers, so while a 0V signal will correspond to Dec(0), a 5V signal will be Dec(1023) ($1023 = 2^{10} – 1$). So what we will do now is to take the output of our 8-bit DAC and send it to port A0 on the Arduino. Then get the Arduino to read and digitize the input on port A0 and display it using the Serial Monitor. Here is the program that does this:

//DAC: Single byte
//A. J. Misquitta

/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
*/

void setup(){
//set digital pins 0-7 as outputs
for (int i=0;i<8;i++){
pinMode(i,OUTPUT);
}
pinMode(A0,INPUT);
Serial.begin(9600);   // Output to Serial
while (! Serial);     // Wait untilSerial is ready
Serial.println("DAC: Single Byte!");
}

void loop(){
digitalWrite(0, HIGH); // 1 : remember we start from the rightmost bit
digitalWrite(1, LOW);  // 0
digitalWrite(2, LOW);  // 0
digitalWrite(3, LOW);  // 0
digitalWrite(4, HIGH); // 1
digitalWrite(5, HIGH); // 1
digitalWrite(6, LOW);  // 0
digitalWrite(7, HIGH); // 1
//delayMicroseconds(50);//wait 50us if using an oscilloscope
//delay(100);//wait 100ms if using a multimeter/serial port
/*
Having written the byte to the digital pins, we will read the
output of the DAC using the analogue pin A0 of the Arduino Uno.
The analogue pins are Analogue to Digital converters (ADC).
Read the DAC output into variable DACout and write it to
the serial port.
*/
int DACout = analogRead(A0);
if (Serial.available())
{
Serial.println(DACout);
}
delay(100);
}

Set up the circuit to read the DAC output on A0 and run this code. You will need to start the serial monitor to see the output of the code. The serial monitor may not start till you hit a key. After that you will see a string of numbers. They will fluctuate slightly.

Task:Question: Does the output at A0 correspond to the decimal value of the 8-bit input to the DAC? In the example above, we input the byte 10110001 to our 8-bit DAC. Earlier you were asked to figure out the decimal value of this byte. Does the reading on the serial port correspond to this value? If not, why not?

READ  Arduino compatible Luna Mod Looper

Hint: Our DAC has an 8 bit output while the ADC on the Arduino is a 10 bit converter. You may need to do a bit of scaling to get the expected value.

Task:Measure the output voltage of the DAC using a DMM (Digital Multimeter). Supply the DAC with 10 input values spanning the range of possible inputs (i.e., from 00000000 to 11111111). Record the DAC output voltage for each of these inputs.

Task:Now re-construct your DAC using the more accurate 1% resistors.

Task:Repeat the 10 measurements using identical inputs. Record the DAC output voltages.

Using Thevenin’s theorem (see the article by Alan Wolke at Textronix) it can be shown that the output voltage of our DAC is given by \begin{equation} V^{\rm DAC} = 5 \times \left( \sum_{i = 0}^{7} b_i \left(\frac{1}{2}\right)^{(8-i)} \right) ~{\rm V} \end{equation} where the $b_i$ are the bits in the binary number. So, for example, for the binary number 10000011 we have \begin{equation} V^{\rm DAC}(10000011) = 5 \times \left( 1\times \left(\frac{1}{2}\right)^8 + 1\times \left(\frac{1}{2}\right)^7 + 0\times \left(\frac{1}{2}\right)^6 \\ ~~~~+ 0\times \left(\frac{1}{2}\right)^5 + 0\times \left(\frac{1}{2}\right)^4 \\ ~~~~+ 0\times \left(\frac{1}{2}\right)^3 + 0\times \left(\frac{1}{2}\right)^2 \\ ~~~~+ 1\times \left(\frac{1}{2}\right)^1 \right) ~ {\rm V} \end{equation} or $V^{\rm DAC}(10000011) = 5 \times ( 0.5117) ~ {\rm V} = 2.5586 ~ {\rm V}$

Task:Using the above formula, find the theoretical DAC voltages for your 10 sets of binary inputs. Compare these values with the two sets of results obtained above. Which set is closer to the theoretical values? Why do you think this might be the case?

Digital writes to PORTD

The following explanation of PORTD has been lifted verbatim from |Amanda’s article.

Now we know how to send a byte of data to the DAC and get an analogue output. From the introduction to digitization given above we know that to create a waveform all we need to do is send in a sequence of bytes to our DAC. Let’s see how we could send the DAC two bytes:

sequence 10110100 & 00001111

// 10110100
digitalWrite(0, HIGH); // 1 : remember we start from the rightmost bit
digitalWrite(1, LOW);  // 0
digitalWrite(2, LOW);  // 0
digitalWrite(3, LOW);  // 0
digitalWrite(4, HIGH); // 1
digitalWrite(5, HIGH); // 1
digitalWrite(6, LOW);  // 0
digitalWrite(7, HIGH); // 1
// 00001111
digitalWrite(0, LOW);  // 0 : remember we start from the rightmost bit
digitalWrite(1, LOW);  // 0
digitalWrite(2, LOW);  // 0
digitalWrite(3, LOW);  // 0
digitalWrite(4, HIGH); // 1
digitalWrite(5, HIGH); // 1
digitalWrite(6, HIGH); // 1
digitalWrite(7, HIGH); // 1

This would work, but to generate a sine wave we would need a lot of bytes of data (the more, the smoother the waveform). Clearly this approach to sending data to the DAC is of limited value.

READ  Linear applies switched-capacitor step-down topology for inductor-less DC/DC

An alternative is to loop over the bits in each byte:

seq = (1,0,1,1,0,0,0,1);
for (int i = 0, i < 8; i++)
{
digitalWrite(i,seq(i)); // seq(i) = 0 ==> LOW & seq(i) = 1 ==> HIGH
}
seq = (0,0,0,0,1,1,1,1);
for (int i = 0, i < 8; i++)
{
digitalWrite(i,seq(i)); // seq(i) = 0 ==> LOW & seq(i) = 1 ==> HIGH
}

Can you see how this works? It is shorter, and could have made it even shorter by using a function call. But there is an even simpler way of transmitting bytes to our DAC: The Arduino allows us to write an entire byte to pins 0 to 7 via PORTD.

PORTD is simply short-hand for pins 7,6,5,4,3,2,1,0 (in this order). We can write an 8-bit binary number (i.e., a Byte) directly to pins 0 to 7 like so:

Writing to PORTD

// Write the byte 10110001 to pins 76543210 in that order:
PORTD = B10110001;

Here the B in front of 10110001 says that the number is in binary form. We could write two bytes as follows:

Writing to PORTD in Binary

PORTD = B10110001;
PORTD = B00001111;

This is clearly a much better option! The code is shorter and also more readable. We can see the bytes we are writing to the DAC. Additionally, the Arduino Atmel328 chip is able to write to all eight pins simultaneously and not sequently as was done when we used the digitalWrite() commands. Since PORTD has 8 pins on it (digital pins 0-7) we can send it one of $2^8 = 256$ possible values ($0-255$) to control the pins.

We could send the data to the DAC (via PORTD) in decimal form as follows:

Writing to PORTD in Decimal

PORTD = 177; // decimal form of 10110001
PORTD = 15;  // decimal form of 00001111

In the following pieces of code we send a value between 0 and 255 to “PORTD” when we want to send data to the DAC, it looks like this:

PORTD = 125;//send data to DAC This is called addressing the port directly. On the Arduino, digital pins 0-7 are all on port D of the Atmel328 chip. The PORTD command lets us tells pins 0-7 to go HIGH or LOW in one line (instead of having to use digitalWrite() eight times). Not only is this easier to code, it’s much faster for the Arduino to process and it causes the pins to all change simultaneously instead of one by one (you can only talk to one pin at a time with digitalWrite()).

For example, if we wrote the following line:

PORTD = 0;

it would set pins 0-7 LOW. With the DAC set up on pins 0-7 this will output 0V. if we sent the following:

PORTD = 255;

it would set pins 0-7 HIGH. This will cause the DAC to output 5V. We can also send combinations of LOW and HIGH states to output a voltage between 0 and 5V from the DAC. For example:

PORTD = 125;

125 = 01111101 in binary. This sets pin 7 low (the most significnat bit (MSB) is 0), pins 6-2 high (the next five bits are 1), pin 1 low (the next bit is 0), and pin 0 high (the least significant bit (LSB) is 1). You can read more about how this works here. To calculate the voltage that this will output from the DAC, we use the following equation:

voltage output from DAC = [ (value sent to PORTD) / 255 ] * 5 V

so for PORTD = 125:

voltage output from DAC = ( 125 / 255 ) * 5V = 2.45V

The code below sends out several voltages between 0 and 5V and holds each for a short time to demonstrate the concepts described above. In the main loop() function:

For more detail: Digital to Analogue Converter (DAC) DAC Theory

This Post / Project can also be found using search terms:

• dac theory
• digital to analogue converter arduino
• digital to analogue converter theory
• nextqhq 