Skip to main content
Who wants to join the solarbike DIY club?

Integrating 10-bit DAC for the Arduino

Published: 17 September 2010
Last updated: 03 February 2022

DAC on the oscilloscopeDAC on the oscilloscope

This article is outdated

Although this DAC works well you better take the new designs: 16-bit DAC or the Fast RC DAC.

Intro

If you need a DAC for the Arduino, this 10 bit DAC is accurate, cheap and uses only few components. The Arduino processor, the ATmega328 / Atmega168, has ADC inputs but unfortunately no DAC outputs. Although the internal ADC contains a 10 bit DAC, this DAC can’t be used stand alone. Therefore I developed a 10 bit DAC, which is build with an integrator. In contrast to a PWM DAC, there is no ripple.

Details

  • Resolution and accuracy 10 bit
  • Output voltage 0 … 10V
  • Settling time 20ms max.
  • Uses 1 arbitrary digital I/O pin and 1 arbitrary analog I/O pin.

Hardware

The DAC is actually an integrator, built with U1, R1 and C2. The integrator has an offset of 2.5V. The output voltage is fed back to de Arduino 10 bit ADC. By steering the dacUpdownPin high or low, the output voltage will be adjusted until the output voltage is equal to the desired value. Finally, the dacUpdownPin is made high impedance.

The output voltage range is 5V * (R4 + R5) / R5 and can be changed by R4 and R5. Use the rail-to-rail opamp LMC6482 instead of the TLC272CN. Note that C2 must be an MKT capacitor, not ceramic, because of the internal resistance.

Integrating 10-bit DAC for the Arduino schematicIntegrating 10-bit DAC for the Arduino schematic

DAC library

Unzip the Dac library and place it in the standard Arduino library subfolder \libraries\. 

DAC example program

The example program creates a sawtooth:

#include <Arduino.h>
#include "Dac.h"
 
const int dacUpdownPin = 2;
const int UDacPin = 0;
 
Dac dac(dacUpdownPin, UDacPin);
 
void setup(void) 
}
 
void loop(void) 
{ for(int i=0; i<1024; i++) dac.write(i); // create a sawtooth 
}

For troubleshooting see HERE.

Test software

The DacDemo project tests the Dac application and shows how to use the Dac. Unzip the DacDemo project file, it contains three files, the libraries Streaming.h and Flash.h from Mikal Hart should also be installed;  download these libraries here: http://arduiniana.org.

Further improvements

Although the DAC works fine it should be possible to further improve it in size and speed. If you have any improvement ideas, please put a reply here.

Dac.cpp 

/* Simple 10 bit DAC for the Arduino
Version 21-3-2013
1. changed constructor
 
Copyright (C) 2012  Albert van Dalen http://www.avdweb.nl
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.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at http://www.gnu.org/licenses .
               C1 100nF 10% MKT  
                _____||____
               |     ||    |  TLC272CN (VDD=12V, GND=0V)
           R1  |  |\       |
   I/O 2--56k-----|-\      |
                  |  \ ____|____ DAC out 0 ... 10V
           R2     |  /    |     
     5V- -10k-----|+/     |
               |  |/  R4 10k
               |          |_____
           R3 10k         |     |
               |      R5 10k    |
               |          |     |
              GND        GND    |
    ADC 0-----------------------
         
            5V |      _       _
               |     | |     | |
  I/O 2        |     | |     | |
          2.5V |_____| |_____| |______
               |
               |
            0V |______________________
          
               |______
               |      \_______
 DAC out       |              \_______ 
 (not to scale)| 
               |______________________
*/
 
#include <Arduino.h>
#include "Dac.h"
 
Dac::Dac(int dacUpdownPin, int UDacPin):
dacUpdownPin(dacUpdownPin), UDacPin(UDacPin), overshoot(5)
{ write(512); // set to 2,5V 
  write(512); // the first conversion can be wrong 
}
 
bool Dac::write(int val)
{ targetVal = val;
  if(targetVal > 1023) targetVal = 1023;
  if(targetVal < 0) targetVal = 0;   
  
  if(abs(read() - targetVal) > overshoot) // avoid overshoot from setDac() for small value changes
    if(!setDac()) return false; 
  if(!fineTune()) return false;
  if(abs(read() - targetVal) > 1) return false; // final error check
  return true;
 
bool Dac::refresh()
{ if(!fineTune()) return false;
  return true;
 
int Dac::read() const// not inline
{ return analogRead(UDacPin);
}
 
inline int Dac::fastRead() const
{ return analogRead(UDacPin);
}
  
/* To increase the DAC speed, the overshoot is reduced by an overshoot value (5). 
   However, this mechanism has a small influence on the accuracy. The DAC error is 0 or 1. 
   When the overshoot value is changed to 0 the DAC error is mostly 0. This mechanism can perhaps be improved.
*/
 
bool Dac::setDac()  
{ const byte timeout1 (255); // maxSettlingTime1 = 195
  int targetCorr; 
 
  if(read() == targetVal) return true; 
  if(read() < targetVal) 
  { targetCorr = targetVal - overshoot; // reduce overshoot caused by adc delay
    dacUp();
    for(settlingTime1=0; settlingTime1 < timeout1; settlingTime1++) 
    { if(fastRead() >= targetCorr) 
      { dacHold(); 
        break;
      }
    }
  } else  
  { targetCorr = targetVal + overshoot;
    dacDown();
    for(settlingTime1=0; settlingTime1 < timeout1; settlingTime1++)
    { if(fastRead() <= targetCorr)  
      { dacHold(); 
        break;
      }
    }
  }
  dacHold(); // end always with hold, in case of timeout
  if(settlingTime1 >= timeout1) return false;
  else return true;
}
 
bool Dac::fineTune() // produces no overshoot 
{ const byte timeout2 (80); // maxSettlingTime2 ~ 20
  const byte halfLsbCorrection (1);
  
  if(read() == targetVal) return true; // avoid ripple at refresh()
  if(read() < targetVal) 
  { for(settlingTime2=0; settlingTime2 < timeout2; settlingTime2++)
    { dacUp(); dacHold(); // finetuning with short pulse 
      if(fastRead() >= targetVal) 
      { for(int i=0; i<halfLsbCorrection; i++) dacUp(); // reduce error to 0
        break;
      }
    }     
  } else  
  { for(settlingTime2=0; settlingTime2 < timeout2; settlingTime2++)
    { dacDown(); dacHold(); // finetuning with short pulse 
      if(fastRead() <= targetVal) 
      { for(int i=0; i<halfLsbCorrection; i++) dacDown(); // reduce error to 0
        break;
      }
    }
  }
  dacHold(); // end always with hold, in case of timeout
  if(settlingTime2 >= timeout2) return false;
  else return true;
}
 
void Dac::dacUp() const
{ digitalWrite(dacUpdownPin, LOW);
  pinMode(dacUpdownPin, OUTPUT); 
}
void Dac::dacDown() const
{ digitalWrite(dacUpdownPin, HIGH);
  pinMode(dacUpdownPin, OUTPUT);
}
 
void Dac::dacHold() const
{ pinMode(dacUpdownPin, INPUT); // high impedance tristate 
  digitalWrite(dacUpdownPin, LOW); // disable pull up resistor 1*)
}