FriendlyWire.com


beginner-friendly electronics tutorials and projects


Discover the joy of understanding electronics!

Understanding digital to analog converters

January 19, 2019 tutorial

In this tutorial we will learn how to use digital to analog converters (DACs) to create an analog voltage from a digital number. DACs are the opposite of analog to digital converters (ADCs) that we already talked about previously, and they are typically used as adjustable voltage references (rather than drivers). At the end, we will control the brightness of an LED via the DAC module of the PIC16F1455.

How does a digital to analog converter (DAC) work?

Because we already learned about the whole analog vs. digital stuff when we looked at analog to digital converters (ADCs), in this tutorial we will focus directly on DACs. In principle, they just consist of simple voltage dividers:

For DACs, the voltage Uin is usually fixed and called the reference voltage, meaning that the highest possible digital number (255 for an 8-bit DAC, 1023 for a 10-bit DAC, you get the idea) corresponds to that voltage. When you now think of the resistor R1 as a variable resistor, for Uout any voltage between 0 and Uin can be achieved.

There are two ways one can build DACs from that only uses resistors and switches:

  • The left version is called a string resistor ladder network and works with resistors of the same value all in series. Each resistor comes with a switch, and closing a switch excludes that resistor from the network. But it is clear that this DAC has only five different states. Also, the output state of a certain voltage can be generated by many different switch configurations. Therefore, this circuit is not really ideal because it uses a lot of components.

    One important property of this approach is that if you want to increase your resolution you need to have the same amount of resistors. For example: if you want to create an 8-bit DAC, you will need 256 resistors, which is quite a lot!

  • On the right you can see a so-called R-2R resistor network that makes use of resistors of a certain value as well as twice that value. The resistors are connected as a so-called current divider, and the output voltage is generated in binary! The above R-2R DAC has a resolution of 24 = 16, meaning 4 bit. S8 is the least significant bit (LSB, see also our article on binary numbers) and corresponds to either 0 or 1, S7 is the second bit (0 or 2), S6 can be either 0 or 4, and S5 contributes with either 0 or 8.

    You can see that the number of resistors required is only 8 for a resolution of 4 bit. For an 8-bit DAC with a resolution of 255 you would therefore only need 16 resistors. Compare that with the 256 resistors from the above example!

In practice there are of course no switches in digital to analog converters, but instead they make use of transistors. There are also a lot of additional components built into realistic DACs that stabilize the output and decouple it from the reference input. But I still hope that the above diagrams give you a good idea of how DACs work :)

Using the DAC module of a PIC microcontroller

Why should we care about all of this? Many PIC microcontrollers actually come with a DAC module that we can use! So it would be a good idea to figure out how to activate the DAC module of a concrete example controller, and I settled on the PIC16F1455 whose datasheet you can find in the resources box.

Here is our rather simple schematic:

LED1 is driven by the controller's DAC module and its brightness is increased and then decreased periodically.

The PIC16F1455 has a built-in 5-bit DAC, meaning that it can take values from 0 to 31. At a reference voltage of 5V, this corresponds to a resolution of around 0.15V. The PIC16F1455, see section 17 (page 163) of the datasheet has two DAC ports: RC2 and RC3. These are not independent, you can just select in the software which port should be used as the DAC, and it is also possible to use both.

The built-in 5-bit DAC is not meant as a power source, so I added the capacitor C1 as a buffer. Technically, see the following picture taken from page 164, this is not even enough to fully buffer the output.

Here we learn one important detail: microcontroller DACs are intended as voltage references and not as adjustable power supplies. This is probably obvious if you have some experience in electronics, but I find it worthwile to point that out because it surprised me the first time I came across DACs.

Okay, other than that the above schematic is pretty simple, so let us now take a look at the important bits of the source code (see the full code in the appendix, and download the main.c file as well as the compiled .hex-file in the resources box).

We configure the PIC16F1455 to run on its 4MHz internal oscillator so that we can save external components. Then we initialize the DAC module as follows:

	// turn off ADC settings for DAC pins
    TRISC2 = 0;
    TRISC3 = 0;
    ANSC2 = 0;
    ANSC3 = 0;
    
    // DAC source: VDD
    D1PSS0 = 0;
    D1PSS1 = 0;
    
    // enable DAC output 1, but NOT output 2
    DACOE1 = 1;
    DACOE2 = 0;
    
    // switch DAC on
    DACEN = 1;
  • In lines 46-47 we set the DAC pins RC2 and RC3 as outputs by clearing their TRISTATE registers. Then, we switch off their analog pin functions in lines 48-49. This is necessary because these pins are also part of the PIC16F1455's ADC module (see the candle project) and that function would interfere with their role in the DAC module.
  • Lines 51-53 set the reference voltage of the DAC to VDD, meaning that the maximum voltage the DAC module can create will be the operating voltage, which here corresponds to 5V. You could also set it to an internal reference or an external reference, see the datasheet in section 17.6, page 166. Using an external reference is a useful feature because the operating voltage of a circuit is not always guaranteed to be perfectly stable. But in our example, of course, we don't need that level of accuracy, because we just want to visualize the DAC functionality by varying the brightness of an LED.
  • In lines 55-57 we activate RC2 (which is the first DAC output) and disable RC3 (which is the second DAC output). This is completely up to you and depends on where you want your DAC voltage output to appear; especially with printed circuit boards this can be a very useful option because it allows more flexibility in the PCB layout and design. DACOE seemingly stands for “DAC Output Enable.”
  • And finally, in line 60, we turn the DAC module on by activating the DACEN bit (which stands for “DAC Enable”).

And then comes the main loop:

	// main loop
    while (1) {
        
        // increase or decrease DAC value
        dac_value += dir;
        if (dac_value >= 31) {
            dir = -1;
        } else if (dac_value == 0) {
            dir = 1;
        }
        
        // write DAC value into DAC register
        DACR0 = dac_value & 1;
        DACR1 = (dac_value & 2) >> 1;
        DACR2 = (dac_value & 4) >> 2;
        DACR3 = (dac_value & 8) >> 3;
        DACR4 = (dac_value & 16) >> 4;
        
        // wait 100ms
        __delay_ms(100);
        
    }
  • This main loop gets repeated over and over and over.
  • In lines 65-71 we increase or decrease the current DAC value that is stored in the variable dac_value, and the current direction (either increasing or decreasing) is stored in the variable dir (which is either 1 or -1).
  • Line 81 waits 100ms so that the increase and decrease of the output voltage of the DAC module does not happen all too fast. We want to see it with our own eyes, after all :)
  • Lines 73-78 contain the important parts: using binary operations, we decompose the current DAC value stored in the variable dac_value into its five bits. These five bits are then stored in the registers DACR0 to DACR4, where the DAC module can read them and adjust its output voltage accordingly.

And that's it! It is remarkably simple, I find, because the PIC16F1455 takes care of everything else. So, in the future when you need a voltage reference: use your microcontroller's DAC module! If you want to try it out yourself, you can find the full source code in the appendix, and you can download the .hex file in the resources box. Don't forget that you need to flash the .hex file onto the PIC microcontroller.

Final thoughts?

...and another tutorial bites the dust. I have to say, on a personal note, that I don't find digital to analog converters all that terribly useful in everyday electronics life. Their counterparts, ADCs, are much more important. But I also believe that a well-rounded electronics hobbyist should at least know about DACs, and now you can hopefully count yourselves among that truly distinguised group of people ;-)

But all kidding aside, I hope you found this tutorial interesting, and I thank you for your time! Let me know on social media if something was not clear enough, and I will do my best to improve this article!

Appendix: The full source code

Here you can find the full source code. The code (as well as the .hex file) are also available for download in the resources box.

/*
 * File:   main.c
 * Author: boos
 *
 * Created on January 18, 2020, 6:39 PM
 */

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover Mode (Internal/External Switchover Mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config CPUDIV = CLKDIV6 // CPU System Clock Selection Bit (CPU system clock divided by 6)
#pragma config USBLSCLK = 48MHz // USB Low SPeed Clock Selection bit (System clock expects 48 MHz, FS/LS USB CLKENs divide-by is set to 8.)
#pragma config PLLMULT = 3x     // PLL Multipler Selection Bit (3x Output Frequency Selected)
#pragma config PLLEN = ENABLED  // PLL Enable Bit (3x or 4x PLL Enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

#include 
#define _XTAL_FREQ 4000000

// auxiliary variables
char dac_value = 0;
char dir = 1;

void main(void) {
    
     // set internal oscillator to 4MHz
    IRCF0 = 1;
    IRCF1 = 0;
    IRCF2 = 1;
    IRCF3 = 1;
    
    // turn off ADC settings for DAC pins
    TRISC2 = 0;
    TRISC3 = 0;
    ANSC2 = 0;
    ANSC3 = 0;
    
    // DAC source: VDD
    D1PSS0 = 0;
    D1PSS1 = 0;
    
    // enable DAC output 1, but NOT output 2
    DACOE1 = 1;
    DACOE2 = 0;
    
    // switch DAC on
    DACEN = 1;
    
    // main loop
    while (1) {
        
        // increase or decrease DAC value
        dac_value += dir;
        if (dac_value >= 31) {
            dir = -1;
        } else if (dac_value == 0) {
            dir = 1;
        }
        
        // write DAC value into DAC register
        DACR0 = dac_value & 1;
        DACR1 = (dac_value & 2) >> 1;
        DACR2 = (dac_value & 4) >> 2;
        DACR3 = (dac_value & 8) >> 3;
        DACR4 = (dac_value & 16) >> 4;
        
        // wait 100ms
        __delay_ms(100);
        
    }
    
    return;
    
}

About FriendlyWire

Beginner-friendly electronics tutorials and projects. Discover the joy of electronics! Keep reading.

Let's build a community

How did you get interested in electronics? What do you want to learn? Connect and share your story!

Tag Cloud

  • tutorial
  • analog signals
  • digital to analog converter
  • DAC
  • string resistor ladder network
  • R-2R network
  • reference voltage
  • resolution
  • PIC16F1455
  • beginner-friendly
  • breadboard
  • schematics
  • microcontroller