6 Getting Started with DAC

6.1 Introduction

Author: Victor Berzan, Microchip Technology Inc.

The Digital-to-Analog Converter (DAC) converts a digital value written to the Data (DAC.DATA) register to an analog voltage. The output can be connected to a physical pin or used internally. The conversion range is between GND and the selected internal voltage reference (VREF), provided by the Voltage Reference (VREF) peripheral module.

This technical brief describes how the DAC works on tinyAVR® 0- and 1-series, megaAVR® 0-series and AVR® Dx and AVR® DA microcontrollers (MCUs). It covers the following use cases:
  • Generating Constant Analog Signal:

    Illustrates how to initialize the DAC, set the voltage reference, set the DAC to output a specific constant voltage

  • Generating Sine Wave Signal:

    Initializes the DAC, sets the voltage reference, updates the DAC output inside the infinite loop to generate sine wave samples.

  • Reading the DAC Internally with the ADC:

    Shows how to initialize the DAC and ADC, set the voltage reference, configure the ADC to read the DAC output values. The DAC output voltage is incremented each step, and then it is read using the ADC.

  • Using DAC as Negative Input for AC:

    Initializes the DAC and AC, configures the AC to use the negative input provided by the DAC, configures the DAC output value. The AC will compare the voltage on its positive input pin with the DAC voltage, and set the output pin to high or low, according to the compare result.

Note: For the first three use cases described in this document, there are two code examples: One bare metal developed on ATtiny817, and one generated with MPLAB® Code Configurator (MCC) developed on AVR128DA48. For the last use case, there is one bare metal code example developed on ATtiny817.

6.2 Overview

The DAC converts the digital value written to the Data (DACn.DATA) register into an analog voltage that will be available on the DAC output. The conversion range is between GND and the selected voltage reference in the Voltage Reference (VREF) peripheral module. The DAC has one continuous-time output with high drive capabilities. The DAC conversion can be started from the application by writing to the Data (DACn.DATA) register.

Figure 6-66. DAC Block Diagram

6.3 Generating Constant Analog Signal

The DAC can be used to generate a constant analog signal. The Voltage Reference (VREF) peripheral module is used to provide a voltage reference for the DAC. The DAC output ranges from 0V to 255 × V R E F 256 .

The voltage reference VREF can be selected from a list of predefined values:

Figure 6-67. CTRLA.DAC0REFSEL Values and Associated VREF Voltages

The 4.34V was selected, to have the widest variation range:

VREF_CTRLA |= VREF_DAC0REFSEL_4V34_gc;
VREF_CTRLB |= VREF_DAC0REFEN_bm;

A 25 μs delay is recommended after configuring the VREF and enabling the voltage reference output. To implement this delay, the VREF_STARTUP_MICROS macro definition is used.

Figure 6-68. Internal Voltage Reference Characteristics
_delay_us(VREF_STARTUP_MICROS);

The DAC output can be used internally by other peripherals, or it can be linked to an output pin. For the ATtiny817, the DAC output is connected to pin A6 (see Figure 6-69).

Figure 6-69. PORT Function Multiplexing

The DAC output pin needs to have the digital input buffer and the pull-up resistor disabled to reduce its load.

PORTA.PIN6CTRL &= ~PORT_ISC_gm;
PORTA.PIN6CTRL |= PORT_ISC_INPUT_DISABLE_gc;
PORTA.PIN6CTRL &= ~PORT_PULLUPEN_bm;

The desired output for the DAC in this particular example is 0.5425V. To achieve it, the following equation is applied:

D A T A = V O U T × 256 / V R E F = 0.5425 V × 256 / 4.34 V = 32 = 0 x 20

Writing to the DAC0.DATA register at initialization is optional; however, it may be useful to make the DAC output a specific voltage from the beginning.

DAC0.DATA = 0x20;

Enabling DAC, Output Buffer and Run in Standby sleep mode:

DAC0.CTRLA = DAC_ENABLE_bm | DAC_OUTEN_bm | DAC_RUNSTDBY_bm;
Important: If Run in Standby sleep mode is enabled, the DAC will continue to run when the MCU is in Standby sleep mode.

Starting a Conversion

When the DAC is enabled (ENABLE = 1 in DAC.CTRLA), a conversion starts as soon as the Data (DAC.DATA) register is written.

DAC0.DATA = 0x20;

After conversion, the output keeps its value of D A T A × V R E F 256 until the next conversion, as long as the DAC is running. Any change in VREF selection will immediately change the DAC output (if enabled and running).

Tip: The full code example is also available in the Appendix section.

An MCC generated code example for AVR128DA48, with the same functionality as the one described in this section, can be found here:

6.4 Generating Sine Wave Signal

The DAC can be used to obtain a sine wave signal by generating a defined number of discrete samples of the desired analog signal. Each sample is characterized by a voltage level and by a time duration. This mechanism provides an approximation of the desired sine wave signal.

In this application, the number of steps used is 100. To obtain a signal frequency of 50 Hz (translated into a period of 20 ms), the time duration between two signal samples will be (1000000/Desired Frequency)/Number of Steps [µs] = (1000000/50 Hz)/100 (steps) [µs] = 200 µs.

To generate the “steps” that approximate the sine wave, the integer values to be written to the DAC0.DATA register will be computed using the formula below:

sin e W a v e [ i ] = D C O f f s e t + A sin ( i 2 π N u m b e r o f S t e p s )

,where i is the sample index and takes values from 0 to Number of Steps, A is the signal amplitude (the maximum possible value is the DAC reference voltage), and the Number of Steps represents the number of samples used to construct the sine wave signal.

To generate a sine wave signal between 0V and 4.34V, the DAC must convert integers ranged between 0 and 255. Therefore, because the DAC will only generate positive voltage values, the DC Offset will be 128 (this is also the mean value of the sine wave signal). To obtain the maximum of 4.34V, the amplitude must be 127. The data to be written to the DAC0.DATA register is presented on the graph below.

Figure 6-70. DAC Input Digital Values

The ideal voltage values that will be available on the DAC output are presented on the graph below.

Figure 6-71. DAC Output Analog Values

To avoid spending time inside the infinite loop by computing the signal samples in real-time, the samples are computed at the beginning of the application and stored in a Look-up Table. The function implementation for this computation is presented below.

for(uint16_t i = 0; i < SINE_WAVE_STEPS; i++)
{
    sineWave[i] = SINE_DC_OFFSET + SINE_AMPLITUDE * sin(i * M_2PI / SINE_WAVE_STEPS);
}

The data is then written to the DAC0.DATA register inside the infinite loop using a 200 µs delay, as described above.

while (1)
{
    DAC0.DATA = sineWave[i++];    
    i = i % SINE_WAVE_STEPS;        
    _delay_us(STEP_DELAY_MICROS);
}
Tip: The full code example is also available in the Appendix section.

An MCC generated code example for AVR128DA48, with the same functionality as the one described in this section, can be found here:

6.5 Reading the DAC Internally with the ADC

As seen in Figure 6-72, the ADC input can be connected to the DAC output. Therefore, the DAC conversion result (a digital value) can be transmitted directly to the ADC. Then, the ADC will convert the analog result provided by the DAC again, obtaining a digital result.

Figure 6-72. Analog-to-Digital Converter Block Diagram

The VREF is configured to provide the voltage reference for the ADC0 and DAC0, as presented below.

Figure 6-73. VREF.CTRLA Register

The VREF peripheral module is initialized as illustrated in the code snippet below.

VREF_CTRLA |= VREF_DAC0REFSEL_4V34_gc; 
VREF_CTRLB |= VREF_DAC0REFEN_bm;
VREF_CTRLA |= VREF_ADC0REFSEL_4V34_gc; 
VREF_CTRLB |= VREF_ADC0REFEN_bm;    
_delay_us(VREF_STARTUP_MICROS);

Then, the ADC is initialized as presented below.

ADC0.CTRLC = ADC_PRESC_DIV4_gc    
           | ADC_REFSEL_INTREF_gc  
           | ADC_SAMPCAP_bm;     

ADC0.CTRLA = ADC_ENABLE_bm        
           | ADC_RESSEL_10BIT_gc;

To read the DAC with the ADC, the MUXPOS register of the ADC must be set to 0x1C.

Figure 6-74. MUXPOS DAC Output Selection
ADC0.MUXPOS = ADC_MUXPOS_DAC0_gc;
The ADC conversion is started by writing the corresponding bit to the ADC0.COMMAND.
ADC0.COMMAND = ADC_STCONV_bm;
When the conversion is done, the Result Ready (RESRDY) flag in ADC0.INTFLAGS will be set by the hardware.
while (!(ADC0.INTFLAGS & ADC_RESRDY_bm))
{
    ;
}
The flag must be cleared by software, as presented below.
ADC0.INTFLAGS = ADC_RESRDY_bm;

The ADC conversion result is available in the ADC0.RES register.

The DAC output can be set to different values and read using the ADC inside the infinite loop.
while (1) 
{
    adcVal = ADC0_read();               
    dacVal++;
    DAC0_setVal(dacVal); 
}
Tip: The full code example is also available in the Appendix section.

An MCC generated code example for AVR128DA48, with the same functionality as the one described in this section, can be found here:

6.6 Using DAC as Negative Input for AC

As seen in Figure 6-75, the AC can use the DAC output as the negative input. Therefore, the user can generate the AC negative voltage reference by simply writing the corresponding digital value to the DATA register of the DAC peripheral module. Then, the DAC will generate the conversion result, a constant voltage, which will be directly connected to the AC input.

Figure 6-75. Analog Comparator Block Diagram

In this code example, the voltage reference for the DAC module is configured at 1.5V.

VREF.CTRLA |= VREF_DAC0REFSEL_1V5_gc;
VREF.CTRLB |= VREF_DAC0REFEN_bm;
_delay_us(VREF_STARTUP_MICROS);

The AC is configured to receive its negative input from DAC and positive input from pin PA7.

AC0.MUXCTRLA = AC_MUXNEG_DAC_gc
             | AC_MUXPOS_PIN0_gc;

Pin PA5 is configured as the AC output.

AC0.CTRLA = AC_ENABLE_bm
            | AC_OUTEN_bm
            | AC_RUNSTDBY_bm
            | AC_LPMODE_bm;

After initializing the VREF, DAC, and AC, the DAC output is set to 1.4V.

DAC0_setVal(DAC_DATA_1V4);

The AC will compare the voltage from PA7 to 1.4V and set its output according to the comparison result.

Tip: The full code example is also available in the Appendix section.

This application can be implemented on AVR128DA48 by using the internal reference voltage generator (DACREF) feature of the AC. There is no need to configure the DAC to provide a negative input for the AC on this architecture.

6.8 Appendix

Generating Constant Analog Signal Code Example

    /* 3.33 MHz (needed for delay) */
    #define F_CPU                         (3333333UL)
    /* DAC value */
    #define DAC_EXAMPLE_VALUE             (0x20)
    /* VREF Startup time */
    #define VREF_STARTUP_MICROS           (25)

    #include <avr/io.h>
    #include <util/delay.h>
    
    void VREF_init(void);
    void DAC0_init(void);
    void DAC0_setVal(uint8_t val);

    void VREF_init(void)
    {
        /* Voltage reference at 4.34V */
        VREF.CTRLA |= VREF_DAC0REFSEL_4V34_gc;
        /* DAC0/AC0 reference enable: enabled */
        VREF.CTRLB |= VREF_DAC0REFEN_bm;
        /* Wait VREF start-up time */
        _delay_us(VREF_STARTUP_MICROS);
    }

    void DAC0_init(void)
    {
        /* Disable digital input buffer */
        PORTA.PIN6CTRL &= ~PORT_ISC_gm;
        PORTA.PIN6CTRL |= PORT_ISC_INPUT_DISABLE_gc;
        /* Disable pull-up resistor */
        PORTA.PIN6CTRL &= ~PORT_PULLUPEN_bm;

        /* Enable DAC, Output Buffer, Run in Standby */
        DAC0.CTRLA = DAC_ENABLE_bm | DAC_OUTEN_bm | DAC_RUNSTDBY_bm;
    }

    void DAC0_setVal(uint8_t val)
    {
        DAC0.DATA = val;
    }

    int main(void)
    {
        VREF_init();
        DAC0_init();

        DAC0_setVal(DAC_EXAMPLE_VALUE);

        while (1) 
        {
            ;
        }
    }

Generating Sine Wave Signal Code Example

    /* 3.33 MHz (needed for delay) */
    #define F_CPU                            (3333333UL)
    /* VREF Startup time */
    #define VREF_STARTUP_MICROS              (25)
    /* Number of steps for a sine wave period */
    #define SINE_WAVE_STEPS                  (100)
    /* Sine wave amplitude */
    #define SINE_AMPLITUDE                   (127)
    /* Sine wave DC offset */
    #define SINE_DC_OFFSET                   (128)
    /* 2*PI */
    #define M_2PI                            (2 * M_PI)
    /* Frequency of the sine wave */
    #define OUTPUT_FREQ                      (50)
    /* Step delay for the synthesis loop */
    #define STEP_DELAY_MICROS                ((1000000 / OUTPUT_FREQ) / SINE_WAVE_STEPS)

    #include <avr/io.h>
    #include <util/delay.h>
    #include <math.h>

    /* Buffer to store the sine wave samples */
    uint8_t sineWave[SINE_WAVE_STEPS];
    
    void sineWaveInit(void);
    void VREF_init(void);
    void DAC0_init(void);
    void DAC0_setVal(uint8_t val);

    void sineWaveInit(void)
    {
        for(uint16_t i = 0; i < SINE_WAVE_STEPS; i++)
        {
            sineWave[i] = SINE_DC_OFFSET + SINE_AMPLITUDE * sin(i * M_2PI / SINE_WAVE_STEPS);
        }
    }

    void VREF_init(void)
    {
        /* Voltage reference at 4.34V */
        VREF.CTRLA |= VREF_DAC0REFSEL_4V34_gc;
        /* DAC0/AC0 reference enable: enabled */
        VREF.CTRLB |= VREF_DAC0REFEN_bm;
        /* Wait VREF start-up time */
        _delay_us(VREF_STARTUP_MICROS);
    }

    void DAC0_init(void)
    {
        /* Disable digital input buffer */
        PORTA.PIN6CTRL &= ~PORT_ISC_gm;
        PORTA.PIN6CTRL |= PORT_ISC_INPUT_DISABLE_gc;
        /* Disable pull-up resistor */
        PORTA.PIN6CTRL &= ~PORT_PULLUPEN_bm;

        /* default value */
        DAC0.DATA = SINE_DC_OFFSET;
        /* Enable DAC, Output Buffer, Run in Standby */
        DAC0.CTRLA = DAC_ENABLE_bm | DAC_OUTEN_bm | DAC_RUNSTDBY_bm;
    }

    void DAC0_setVal(uint8_t val)
    {
        DAC0.DATA = val;
    }

    int main(void)
    {
        uint16_t i = 0;

        VREF_init();
        DAC0_init();

        sineWaveInit();

        while (1)
        {
            DAC0_setVal(sineWave[i++]);    
            i = i % SINE_WAVE_STEPS;        
            _delay_us(STEP_DELAY_MICROS);
        }
    }

Reading DAC Internally With ADC Code Example

    /* 3.33 MHz (needed for delay) */
    #define F_CPU                         (3333333UL)  
    /* VREF Startup time */
    #define VREF_STARTUP_MICROS           (25)

    #include <avr/io.h>
    #include <util/delay.h>
    
    void VREF_init(void);
    void DAC0_init(void);
    void ADC0_init(void);
    uint16_t ADC0_read(void);
    void DAC0_setVal(uint8_t val);

    void VREF_init(void)
    {
        /* Voltage reference at 4.34V */
        VREF_CTRLA |= VREF_DAC0REFSEL_4V34_gc; 
        /* DAC0/AC0 reference enable: enabled */
        VREF_CTRLB |= VREF_DAC0REFEN_bm;
        /* Voltage reference at 4.34V */
        VREF_CTRLA |= VREF_ADC0REFSEL_4V34_gc; 
        /* ADC0 reference enable: enabled */
        VREF_CTRLB |= VREF_ADC0REFEN_bm;    
        /* Wait VREF start-up time */
        _delay_us(VREF_STARTUP_MICROS);
    }

    void DAC0_init(void)
    {
        /* Enable DAC */
        DAC0.CTRLA = DAC_ENABLE_bm;
    }

    void ADC0_init(void)
    {
        ADC0.CTRLC = ADC_PRESC_DIV4_gc      /* CLK_PER divided by 4 */
                    | ADC_REFSEL_INTREF_gc  /* VDD reference */
                    | ADC_SAMPCAP_bm;       /* Sample Capacitance Selection: enabled */

        ADC0.CTRLA = ADC_ENABLE_bm          /* ADC Enable: enabled */
                    | ADC_RESSEL_10BIT_gc;  /* 10-bit mode */

        /* Select ADC channel */
        ADC0.MUXPOS = ADC_MUXPOS_DAC0_gc;
    }

    uint16_t ADC0_read(void)
    {
        /* Start ADC conversion */
        ADC0.COMMAND = ADC_STCONV_bm;

        /* Wait until ADC conversion done */
        while ( !(ADC0.INTFLAGS & ADC_RESRDY_bm) )
        {
            ;
        }

        /* Clear the interrupt flag by writing 1: */
        ADC0.INTFLAGS = ADC_RESRDY_bm;

        return ADC0.RES;
    }

    void DAC0_setVal(uint8_t val)
    {
        DAC0.DATA = val;
    }

    int main(void)
    {
        uint8_t dacVal = 0;
        volatile uint16_t adcVal = 0;

        VREF_init();
        DAC0_init();
        ADC0_init();

        /* Wait VREF start-up time */
        _delay_us(VREF_STARTUP_MICROS);

        DAC0_setVal(dacVal);

        while (1) 
        {
            adcVal = ADC0_read();

            /* do something with the adcVal */

            dacVal++;
            DAC0_setVal(dacVal); 
        }
    }

Using DAC as Negative Input for AC Code Example

    /* 3.33 MHz (needed for delay) */
    #define F_CPU                         (3333333UL)
    /* 1.4V output @ VREF = 1.5V */
    #define DAC_DATA_1V4                  (239)
    /* VREF Startup time */
    #define VREF_STARTUP_MICROS           (25)

    #include <avr/io.h>
    #include <util/delay.h>
    
    void VREF_init(void);
    void DAC0_setVal(uint8_t val);
    void DAC0_init(void);
    void AC0_init(void);

    void VREF_init(void)
    {
        /* Voltage reference at 1.5V */
        VREF.CTRLA |= VREF_DAC0REFSEL_1V5_gc;
        /* DAC0/AC0 reference enable: enabled */
        VREF.CTRLB |= VREF_DAC0REFEN_bm;
        /* Wait VREF start-up time */
        _delay_us(VREF_STARTUP_MICROS);
    }

    void DAC0_setVal(uint8_t val)
    {
        DAC0.DATA = val;
    }

    void DAC0_init(void)
    {
        /* Disable digital input buffer */
        PORTA.PIN6CTRL &= ~PORT_ISC_gm;
        PORTA.PIN6CTRL |= PORT_ISC_INPUT_DISABLE_gc;
        /* Disable pull-up resistor */
        PORTA.PIN6CTRL &= ~PORT_PULLUPEN_bm;

        /* Enable DAC, Output Buffer, Run in Standby */
        DAC0.CTRLA = DAC_ENABLE_bm | DAC_OUTEN_bm | DAC_RUNSTDBY_bm;
    }

    void AC0_init(void)
    {
        /* Negative input from DAC0 */
        /* Positive input from pin PA7 */
        AC0.MUXCTRLA = AC_MUXNEG_DAC_gc
                    | AC_MUXPOS_PIN0_gc;

        /* Enable, Output on PA5, Low Power mode */
        AC0.CTRLA = AC_ENABLE_bm
                    | AC_OUTEN_bm
                    | AC_RUNSTDBY_bm
                    | AC_LPMODE_bm;
    }

    int main(void)
    {
        /* Voltage divider -> VDD/2 input on PA7 */
        /* AC output on LED on PA5 */
        /* LED turns OFF when battery is below 2.8V (PA7 below 1.4V) */

        VREF_init();
        DAC0_init();
        AC0_init();

        /* 1.4V output @ VREF = 1.5V */
        DAC0_setVal(DAC_DATA_1V4);

        while (1)
        {
            ;
        }
    }