If you’ve wondered how to produce a true Arduino DAC analog output — a clean, continuously variable voltage rather than the stuttery approximation of PWM — you’re in the right place. Digital-to-Analog Conversion (DAC) is essential for audio generation, signal synthesis, calibrated voltage sources, and any application where PWM’s switching noise is unacceptable. This guide covers every practical method from low-cost external ICs to native DAC hardware on advanced Arduino boards.
Table of Contents
- PWM vs True DAC: What’s the Difference?
- MCP4725: The Go-To External DAC for Arduino
- MCP4725 Wiring and Complete Code
- Native DAC on Arduino Due, Zero, and MKR
- Waveform Generation: Sine, Sawtooth, Triangle
- Arduino Audio Output with DAC
- DIY R-2R Ladder DAC
- Frequently Asked Questions
PWM vs True DAC: What’s the Difference?
When most Arduino tutorials say “analog output”, they mean PWM (Pulse-Width Modulation). PWM rapidly switches a digital pin between 0V and 5V, varying the on-time ratio (duty cycle) to produce an average voltage. An LED or motor responds to this average and behaves as if the voltage is variable. But the actual signal is a square wave switching at 490Hz or 980Hz — not a smooth voltage at all.
A true DAC (Digital-to-Analog Converter) outputs a genuine, steady DC voltage at any value within its range. The output is set digitally (a number written over a bus or to a register) and the DAC produces the corresponding voltage with no switching, no ripple, and no need for filtering. The differences matter enormously in practice:
- Audio circuits: PWM’s 490Hz switching frequency falls squarely in the audio band and creates audible noise. DAC output is clean.
- Sensor simulation: Testing an analog sensor input? PWM will interfere with the ADC’s auto-scaling. A DAC gives a stable reference.
- Precision control: Controlling a variable gain amplifier or an analog potentiometer digitally requires a true DC voltage, not a PWM average that ripples with load.
- Signal generation: Function generators and arbitrary waveform generators (AWG) require true DAC output to produce clean sinusoids and other waveforms.
MCP4725: The Go-To External DAC for Arduino
The MCP4725 from Microchip is a 12-bit, single-channel DAC that communicates over I2C. It is the most popular DAC breakout for Arduino, and for good reason: it costs little, needs only 4 wires, has a built-in non-volatile memory (EEPROM) to store a power-on default voltage, and covers 0–VCC output range with 12-bit resolution (4096 steps).
Key Specifications
- Resolution: 12-bit (4096 steps)
- Output range: 0V to VCC (0–3.3V or 0–5V depending on supply)
- Output current: 25mA max (buffer the output for higher currents)
- Interface: I2C (up to 400kHz fast mode)
- Supply voltage: 2.7V–5.5V
- I2C address: 0x60 or 0x61 (configurable via A0 pin)
- EEPROM: Stores power-on DAC value persistently
- Update rate: ~250kHz maximum (limited by I2C speed)
Voltage Resolution Calculation
With a 5V supply and 12-bit resolution: each step = 5V / 4095 ≈ 1.22mV. You can set any voltage from 0V to ~4.998V in 1.22mV increments. This is far better than PWM’s effective resolution (~8-bit in practice), and without the switching noise.
MCP4725 Wiring and Complete Code
Wiring
| MCP4725 Pin | Arduino Uno |
|---|---|
| VCC | 5V |
| GND | GND |
| SDA | A4 |
| SCL | A5 |
| OUT | To your analog load |
Basic Output Code (using Adafruit MCP4725 library)
#include <Wire.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac;
void setup() {
Serial.begin(115200);
// 0x60 is default addr; use 0x61 for second module
if (!dac.begin(0x60)) {
Serial.println("MCP4725 not found!");
while (1);
}
}
void loop() {
// Ramp output 0V to 5V
for (uint16_t v = 0; v <= 4095; v += 16) {
dac.setVoltage(v, false); // false = don't save to EEPROM
delay(10);
}
// Set specific voltage: 2.048V on a 5V supply → 4096 * 2.048/5 = 1678
dac.setVoltage(1678, false);
delay(2000);
// Save to EEPROM (power-on default)
dac.setVoltage(2048, true); // 2.5V default on power-up
delay(100);
}
Direct I2C for Maximum Speed
For the fastest possible update rate (e.g., waveform generation), skip the library and write directly:
void dac_write_fast(uint16_t value) {
Wire.beginTransmission(0x60);
// Fast write mode: C2=0, C1=1, C0=0
Wire.write(0x40); // Command: Write DAC register, no EEPROM
Wire.write(value >> 4); // Upper 8 bits
Wire.write((value & 0x0F) << 4); // Lower 4 bits
Wire.endTransmission();
}
With 400kHz I2C, each call takes about 60µs, giving ~16,000 updates per second — adequate for audio up to 8kHz sample rate.
Native DAC on Arduino Due, Zero, and MKR
Some Arduino boards include a built-in DAC with no external hardware required:
Arduino Due
The Due (SAM3X8E, 84MHz) has two 12-bit DAC channels on pins DAC0 and DAC1. Output range is 0.55V–2.75V (not full rail-to-rail). Use analogWrite() with values 0–4095:
// Due native DAC
analogWriteResolution(12); // Enable 12-bit writes
analogWrite(DAC0, 2048); // ~1.65V midpoint
The Due’s DAC can update at up to 500kHz — fast enough for high-quality audio output. This makes the Due genuinely useful as a waveform generator or audio player without any additional hardware.
Arduino Zero and MKR Series
The Zero (SAMD21) has one 10-bit DAC on pin A0. Update with:
analogWriteResolution(10);
analogWrite(A0, 512); // 0–1023 range, 0–3.3V output
Arduino Nano RP2040 Connect
The RP2040 does not have a hardware DAC. However, the MCP4725 over I2C or an R-2R ladder DAC via GPIO are both practical options. The RP2040’s two cores allow one core to handle DAC updates continuously while the other handles communication or processing.
Waveform Generation: Sine, Sawtooth, Triangle
Pre-compute a lookup table (LUT) for efficiency. Calculating sin() in real time is far too slow for audio-rate updates:
#include <Wire.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac;
#define SAMPLES 256
uint16_t sineLUT[SAMPLES];
void setup() {
dac.begin(0x60);
Wire.setClock(400000); // Fast I2C
// Pre-compute sine LUT (0 to 4095 range)
for (int i = 0; i < SAMPLES; i++) {
sineLUT[i] = (uint16_t)(2047.5 + 2047.5 * sin(2.0 * PI * i / SAMPLES));
}
}
void loop() {
// Output sine wave at ~62Hz (256 samples × 16kHz/256 ≈ limited by I2C)
for (int i = 0; i < SAMPLES; i++) {
dac_write_fast(sineLUT[i]);
}
}
void dac_write_fast(uint16_t value) {
Wire.beginTransmission(0x60);
Wire.write(0x40);
Wire.write(value >> 4);
Wire.write((value & 0x0F) << 4);
Wire.endTransmission();
}
For a sawtooth, replace the LUT generation with: sineLUT[i] = (uint16_t)(i * 4095.0 / (SAMPLES - 1));
For a triangle: sineLUT[i] = (i < SAMPLES/2) ? i * (4095 / (SAMPLES/2)) : 4095 - (i - SAMPLES/2) * (4095 / (SAMPLES/2));
Arduino Audio Output with DAC
Playing 8-bit audio samples through a DAC is a classic project. The key requirements are:
- Sample data stored in flash as a byte array (use the Python
audioconverttool or Audacity to export raw PCM) - A timer interrupt to output samples at a precise rate (8kHz = one sample every 125µs)
- A low-pass filter and small audio amplifier on the DAC output
// Using Arduino Due native DAC for 22kHz audio
#include "AudioSample.h" // Array of uint16_t samples
volatile uint32_t samplePos = 0;
void TC0_Handler() {
TC_GetStatus(TC0, 0); // Clear interrupt flag
if (samplePos < SAMPLE_COUNT) {
dacc_write_conversion_data(DACC, audioSamples[samplePos++]);
} else {
samplePos = 0; // Loop
}
}
void setup() {
analogWriteResolution(12);
// Configure Timer Counter 0 for 22050Hz interrupts
// (full setup code omitted for brevity — see Arduino Due audio examples)
}
For a complete audio player project on the Due, the ArduinoSound library abstracts much of this complexity and supports I2S output for even better quality.
DIY R-2R Ladder DAC
If you have no I2C DAC and need fast analog output right now, build an R-2R ladder from resistors. An 8-bit R-2R ladder uses 8 digital output pins and a network of R and 2R resistors (typically 10kΩ and 20kΩ) to produce 256 output levels.
The resistor network sums the binary-weighted voltages from each pin. Connect the ladder’s output to any analog circuit. This approach gives an 8-bit DAC with update rates limited only by the speed you can write to the output ports — using direct port manipulation, you can achieve output rates above 100kHz on an Arduino Uno.
// Direct port write for maximum speed (uses PORTD pins 0-7)
void r2r_write(uint8_t value) {
PORTD = value; // Writes all 8 bits atomically
}
void loop() {
for (uint16_t i = 0; i < 256; i++) {
r2r_write(i);
delayMicroseconds(100); // 10kHz sawtooth
}
}
Drawbacks: uses 8 GPIO pins, resistor matching tolerances affect linearity, and the output impedance is not negligible. For precision applications, use the MCP4725. For high-speed audio generation where you already have resistors on hand, the R-2R approach is hard to beat in simplicity.
Frequently Asked Questions
Does Arduino Uno have a DAC?
No. The Arduino Uno has no native DAC. It only has PWM outputs. For true analog output, add an external DAC like the MCP4725 (I2C) or build an R-2R ladder DAC. The Arduino Due and Zero/MKR boards include native DAC hardware.
What is the difference between analogWrite() and a real DAC?
analogWrite() on standard Arduinos outputs a PWM square wave, not a true analog voltage. The average voltage equals duty_cycle × VCC, but the actual signal switches rapidly between 0V and 5V. A true DAC outputs a steady DC voltage with no switching. For applications sensitive to noise (audio, precision analog, ADC reference), a real DAC is essential.
How many volts can the MCP4725 output?
The MCP4725 output range is 0V to VCC (the supply voltage). If powered from 5V, it outputs 0–5V. If powered from 3.3V, output is 0–3.3V. The output at full scale (DAC code 4095) is approximately VCC – 1 LSB, essentially full rail.
Can I use two MCP4725 modules at the same time?
Yes. The MCP4725 has two address options: 0x60 (default, A0=GND) and 0x61 (A0=VCC). This gives you two independent DAC channels on the same I2C bus. For more channels, use the MCP4728 which provides four 12-bit DAC outputs in a single chip.
What sample rate can I achieve for Arduino audio with the MCP4725?
With 400kHz I2C and the fast write method, approximately 16,000 samples per second (16kSPS). This allows audio bandwidth up to about 8kHz — adequate for voice and simple audio effects. For higher quality audio, use the Arduino Due’s native 12-bit DAC which updates far faster, or use an I2S DAC module.
Ready to generate real analog signals with Arduino? Shop our full range of Arduino boards and accessories at Zbotic — with fast shipping across India.
Add comment