DAC

Parts

Circuit

arduino_8bit_dac_amp_circuit_1.jpg

Taken from Amanda Aghassaei's page (check license!) The DAC is the first part only. Just the resistors.

DAC Theory

Useful information can be found here:

Digital writes to PORTD

The following explanation of PORTD has been lifted verbatim from Amanda's article.

We will soon been sending sequences of 8 bits (1 byte) to our DAC. From what we have already learnt about the digital pins of the Arduino, we know that we can write out these 8 bits, 1 to each of the digital pins 0 to 7 using commands like

  digitalWrite(pin, HIGH);

where, to send the sequence 10110001, we'd need the lines

sequence 10110100

  digitalWrite(0, HIGH); // 1 : remember we start from the rightmost bit
  digitalWrite(1, LOW);  // 0
  digitalWrite(2, LOW);  // 0
  digitalWrite(3, LOW);  // 0
  digitalWrite(4, HIGH); // 1
  digitalWrite(5, HIGH); // 1
  digitalWrite(6, LOW);  // 0
  digitalWrite(7, HIGH); // 1

Of course, we could have saved many lines by looping over the bits in a byte:

  seq = (1,0,1,1,0,0,0,1);
  for (int i = 0, i < 8; i++)
  {
    digitalWrite(i,seq(i)); // seq(i) = 0 ==> LOW & seq(i) = 1 ==> HIGH
  }

But there is another, more convenient way of doing this: The Arduino allows us to write an entire byte to pins 0 to 7 via PORTD.

In the following pieces of code we send a value between 0 and 255 to "PORTD" when we want to send data to the DAC, it looks like this:

PORTD = 125;//send data to DAC

This is called addressing the port directly. On the Arduino, digital pins 0-7 are all on port d of the Atmel328 chip. The PORTD command lets us tells pins 0-7 to go HIGH or LOW in one line (instead of having to use digitalWrite() eight times). Not only is this easier to code, it's much faster for the Arduino to process and it causes the pins to all change simultaneously instead of one by one (you can only talk to one pin at a time with digitalWrite()). Since port d has eight pins on it (digital pins 0-7) we can send it one of 2^8 = 256 possible values (0-255) to control the pins. For example, if we wrote the following line:

PORTD = 0;

it would set pins 0-7 LOW. With the DAC set up on pins 0-7 this will output 0V. if we sent the following:

PORTD = 255;

it would set pins 0-7 HIGH. This will cause the DAC to output 5V. We can also send combinations of LOW and HIGH states to output a voltage between 0 and 5V from the DAC. For example:

PORTD = 125;

125 = 01111101 in binary. This sets pin 7 low (the msb is 0), pins 6-2 high (the next five bits are 1), pin 1 low (the next bit is 0), and pin 0 high (the lsb is 1). You can read more about how this works here. To calculate the voltage that this will output from the DAC, we use the following equation:

voltage output from DAC = [ (value sent to PORTD) / 255 ] * 5V so for PORTD = 125: voltage output from DAC = ( 125 / 255 ) * 5V = 2.45V

The code below sends out several voltages between 0 and 5V and holds each for a short time to demonstrate the concepts described above. In the main loop() function:

  PORTD = 0;//send (0/255)*5 = 0V out DAC
  delay(1);//wait 1ms
  PORTD = 127;//send (127/255)*5 = 2.5V out DAC
  delay(2);//wait 2ms
  PORTD = 51;//send (51/255)*5 = 1V out DAC
  delay(1);//wait 1ms
  PORTD = 255;//send (255/255)*5 = 5V out DAC
  delay(3);//wait 3ms

The output is should be visualised on an oscilloscope or sent to the Serial display using the technique used for the sine wave below.

DAC Practical

Creating the 8 bit DAC is rather simple. Probably best to use 1% tolerance resistances as the 5 or 10% varieties would introduce a lot of noise in the DAC.

This experiment needs an oscilloscope, but failing this, we can use the serial output of the Arduino and a short code to visualise it.

Square waves

The first example is a simple square waveform generation code:

//Analog out
//by Amanda Ghassaei
//http://www.instructables.com/id/Arduino-Audio-Output/
//Sept 2012

/*
 * 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.
 *
*/

void setup(){
  //set digital pins 0-7 as outputs
  for (int i=0;i<8;i++){
    pinMode(i,OUTPUT);
  }
}

void loop(){
  PORTD = 0;//send (0/255)*5 = 0V out DAC
  delay(2000);//wait 1ms
  PORTD = 127;//send (127/255)*5 = 2.5V out DAC
  delay(2000);//wait 2ms
  PORTD = 51;//send (51/255)*5 = 1V out DAC
  delay(2000);//wait 1ms
  PORTD = 255;//send (255/255)*5 = 5V out DAC
  delay(2000);//wait 3ms
}

I've increased the //delay//s to allow voltage measurement using a multimeter. If using the oscilloscope use the ms timings listed in the comments.

Ramp Wave

Next try getting a ramp waveform:

//Ramp out
//by Amanda Ghassaei
//http://www.instructables.com/id/Arduino-Audio-Output/
//Sept 2012

/*
 * 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.
 *
*/

void setup(){
  //set digital pins 0-7 as outputs
  for (int i=0;i<8;i++){
    pinMode(i,OUTPUT);
  }
  pinMode(A0,INPUT);
  Serial.begin(9600);                        // Output to Serial
  while (! Serial); // Wait untilSerial is ready
  Serial.println("Waveform generation!");
}

void loop(){
  for (int a=0;a<256;a++){
    PORTD = a;//send out ramp to digital pins 0-7
    //delayMicroseconds(50);//wait 50us if using an oscilloscope
    delay(100);//wait 100ms if using a multimeter/serial port
    int DACout = analogRead(A0); 
    if (Serial.available())
    {
      Serial.println(DACout);
    }
  }
}

Sine wave and Serial display

Now for a sine wave. I've modified Amanda's code quite a lot for this one. Since I don't have an oscilloscope, we will use the serial display to visualise the wave. The display code has been based on the BOE Bot serial display exercise.

I modified the circuit to feed the output of the DAC (top pin of the resistor network) to pin A0 on the Arduino. This allows the analogue output to be read and analysed/visualised.

//Sine wave
//based on code by  Amanda Ghassaei
//http://www.instructables.com/id/Arduino-Audio-Output/
//Sept 2012
// Modified by A.J.Misquitta to include Serial output, visualization of the wave
// and period calculation.

/*
 * 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.
 *
*/

void setup(){
  //set digital pins 0-7 as outputs
  for (int i=0;i<8;i++){
    pinMode(i,OUTPUT);
  }
  pinMode(A0,INPUT);
  Serial.begin(115200);                        // Output to Serial
  while (! Serial); // Wait untilSerial is ready
  Serial.println("Waveform generation!");
}

void loop(){
  boolean up = true;
  unsigned long time_up = 0;
  unsigned long time_down = 0;
  int crossover = 512; // timers activate at this value
  int samples = 100;  // number of samples to use
  
  for (int t=0; t < samples;t++){//increment "t"
    //send sine wave to DAC, centered around (127/255)*5 = 2.5V
    PORTD = 127+127*sin(2*3.1415926*t/100);
    // The period of the wave can be controlled using the delay. 
    // But other functions in the loop also effect the period!
    // The delay should be linked to the samples in a sensible way:
    delayMicroseconds(5000/samples);
    //delay(100);//wait 100ms if using a multimeter/serial port
    // Read the output of the DAC:
    int DACout = analogRead(A0); 
    // Code to calculate period in ms
    if (up){
      if (DACout > crossover){
        time_up = millis();
        up = false;
      }
    } else {
      if (DACout < crossover){
        time_down = millis();
        if (Serial.available()){
          unsigned long period = 2 * (time_down - time_up);
          Serial.println(period);
        }
        up = true;
      }
    }
    // Print out the wave:
    if (Serial.available())
    {
      for(int i = 0; i<((DACout / 8)); i++)
      {
        Serial.print(' ');
      }
      Serial.print('*');
      Serial.println(DACout); 
    }
  }
}

/* 
  Use the following code for a simple (and faster)
  version of the sine wave code 
*/
void loop_simple(){
  for (int t=0;t<100;t++){//increment "t"
    PORTD = 127+127*sin(2*3.14*t/100);//send sine wave to DAC, centered around (127/255)*5 = 2.5V
    delayMicroseconds(50);//wait 50us
  }
}

The above code not only lets us see the wave form, but it also calculates the period of the wave using a trigger technique: the timers start and stop when the wave crosses the 512 reading (2.5 V) on the ADC input.

sine_wave_serial_display_1.png

Sample output. The number //472// on the left/bottom is the initial guess at the period of the wave. The first value should be discarded. Observe that the period estimates quickly settle down to a near-constant value. The *s indicate the waveform and the adjacent numbers the ADC input values. The fact that these never get to 1024 is because the DAC output voltage never gets to 5V. By default the ADC converts the range $[0,5]$ Volts into an output range $[0,1024]$. It is a 10 bit ADC.

Things to note:

Note

The serial display is a great way to probe the circuit. Particularly if you don't have an oscilloscope at hand. The jumper attached to analogue pin A0 can be moved around to different parts of the circuit, allowing you to see what's going on in much the same way as you'd probe it using a multimeter, only the A0 probe gives you voltages as a function of time.

DAC Buffer

The R-2R DAC is very sensitive to loads. (I've still not understood why, but this may be related to the limitations of the Arduino.) Under load, the waveform distorts significantly. To protect the DAC output we use a buffer circuit built out of an Op-Amp.

For a good introduction to Op-Amps (they are collections of transistors) have a look at the following:

Parts

The only new parts are the TC922INs. These differ from the standard 081 or 741 op-amps in two ways: they contain two op-amps each and Vcc for these is between 2.7 and 15 Volts (as opposed to +-12 V needed for the 081/741). So we can run them off the Arduino's 5 V supply voltage.

In addition we will use a standard red diode and 220 Ohm resistor to test the circuit.

Warning

The TDA7052 audio amp does not work without buffering. I've tried it. The DAC output gets badly clipped when it is connected to the TDA7052. This could be related to the load put on the DAC. Since the lower part of the sine wave is large un-distorted, I guess that the Arduino or DAC cannot sustain the high loads when the number of Arduino pins that are HIGH increases.

Circuit

Procedure & Code

Basically, these little devices allow us to sample the signal without loading it. We will use the op-amp in what is known as a voltage-follower setup.

AJMPublic/teaching/arduino-pi/projects/arduino/dac-audio/old-versions (last edited 2021-04-14 13:15:13 by apw109)