You are on page 1of 5

Simple 10 bit DAC for the Arduino

1. 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.

For an even more simpler DAC, see Super simple 10 bit DAC for the Arduino.

2. 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.

3. Hardware

The output voltage range is 5V * (R4 + R5) / R5 and can be changed by R4 and R5. The TLC272CN is a high input
impedance precision opamp.
Because the opamp output isn’t rail to rail, the supply voltage must be higher than the maximum DAC output voltage. For
an output voltage of 10V the supply voltage is 12V. We can use a single 5V power supply when it is no problem that the
maximum DAC output voltage is limited.
Simple 10 bit Arduino dac schematic

4. Libraries needed

Unzip the Dac library folder and place it in the standard Arduino library subfolder \libraries\. The libraries Streaming.h and
Flash.h from Mikal Hart should also be installed. Download these libraries here: http://arduiniana.org.

5. Test software

Unzip the DacDemoproject file. It contains three files. The DacDemo project tests the Dac application and shows how to
use the Dac. For troubleshooting see HERE.

6. 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.

7. Dac.cpp

/* Simple 10 bit DAC for the Arduino


Version 1.0
write(): Write a value to the DAC. 0 = 0V, 1023 = 10V.
read(): Read the actual output voltage.
refresh(): Because of leakage current, refresh the DAC periodically (10 sec. for 1 LSB error).
The settling time is max. 20ms.
Don't touch C1 / R1 during run.
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():
dacUpdownPin(2), UDacPin(0), 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*)
}

You might also like