Integrating 10-bit DAC for the Arduino
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.
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*)
}
|