Contents[Hide]

1. Don't use analogWrite() anymore

The Arduino has no DAC; to get analog output values, we can use the standard Arduino function analogWrite(). This creates a so called PWM DAC, a PWM signal which has to be filtered with a low-pass filter. However, analogWrite() has some disadvantages:

  • The resolution is just 8 bit.
  • The PWM frequency is very low: 490 Hz.

To reduce the ripple, a low pass filter with a large RC time is required. This not only increases the settling time, but also presents problems when using the PWM DAC in a control loop. For a simple DAC without ripple, see Simple 10 bit DAC for the Arduino.

2. Create a faster DAC with the TimerOne library

Luckily there is a better solution. By using the TimerOne library, the PWM DAC can be significantly improved. Don't use analogWrite() anymore! The advantages are:

  • The DAC is faster; the PWM frequency is 31250Hz instead of 490Hz for 8 bit resolution.
  • The maximum resolution is 10 bit.
  • The low pass filter requires no elco.

Super simple 10 bit DAC for the Arduino
Super simple 10 bit DAC for the Arduino
Super simple 10 bit DAC for the Arduino
Super simple 10 bit DAC for the Arduino

Download the TimerOne library HERE.

3. Setting the DAC

3.1. Output voltage

The DAC value is 10 bit, regardless of the resolution, thus 0 ... 1023.
For a 5V supply voltage, the output voltage is:
U = DACvalue * 5 / 1023

3.2. Resolution

The resolution depends on the period. For a resolution of 8 bit, set the period to 32:
Timer1.initialize(32).

period [us] DAC [bit] PWM [Hz]
1 3 1000000
2 4 500000
4 5 250000
8 6 125000
16 7 62500
32 8 31250
64 9 15625
128 10 7812.5

3.3. Output pins

Only PWM outputs which are connected to timer1 are supported; for the ATmega328, these are the output pins 9 and 10. Two independent DACs can be created simultaneously. To use other PWM output pins, take TimerThree; I have not tested this.

4. 8 bit DAC example program

The example program creates a sawtooth at pin 9:

#include <TimerOne.h>
 
const byte PWMDAC1pin = 9; // PWM DAC, only pins 9 and 10 are allowed
const byte PWMDAC2pin = 10; // example using second DAC
const byte period = 32; // for 8 bit DAC 
 
void setup(void) 
{ pinMode(PWMDAC1pin, OUTPUT);  
  Timer1.initialize(period); 
}
 
void loop(void) 
{ for(int i=0; i<1024; i++)
  { Timer1.pwm(PWMDAC1pin, i); // create a sawtooth 
    delay(1);
  }
}

 With a low pass filter of 10k / 100nF, the 8 bit PWM DAC of the example has these characteristics:

  • PWM frequency 31250Hz
  • Max ripple voltage 40mV
  • Cut-off frequency 160Hz
  • Settling time 0% ... 90% 2.3ms

5. Future improvements to the super simple 10 bit DAC

  • I would prefer to use a special DAC class, derived from TimerOne, with a more convenient interface.
  • Within the same DAC class, one has a choice between timer1 and timer3.
  • Do the calculations in compile time if possible.

7. Notes:

  • The function setPwmDuty() doesn't work here, so use pwm(). This bug has to be solved in the TimerOne library.
  • Timer0 is used for the functions millis() and delay(), so better don't use this timer for anything else.

Do you have any comments? Please let me know.
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.