6 bit Digital-to-Analog Converter


It is well known that the Arduino analogWrite command does not perform a digital-to-analog conversion but programs the ATmega's timers to give a PWM signal at 490 Hz which is fine for controlling light and motors.
To obtain an audible signal you better take another option. If you want to convert a digital signal to an analog one it is best if you can set all the digital pins at the same time rather than setting them one after the other with digitalWrite(pin,value) commands. Therefore, in this example we were looking for one of the Arduino's connectors where you have many pins of one port and the ground next to each other. Obviously, this is the connector where you find pin-8 to pin-13.
(Unfortunately, when you upgrade to the ATmega-2560, this advantage gets lost.)
Mostly, you find a so-called R-2R ladder used for DACs. For a six bit resolution you need 19 resistors of the same value. (The standard E12 series resistors come with 10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82. So you never get one which is twice the resistance of another one which you need for 2R.)
So we decided to have an earphone of 32 Ohms as a load and determine the values of the six weighted resistors using trimmers and an analog oscilloscope. Unfortunalely, the resistors you need are not on stock. So check this:

what you need what you take
32 use 33
88 330 || 120
210 4700 || 220
446 390 + 56
914 820 + 100
1662 1200 + 470
|| means use both resistors in parallel
+ means use both resistors in serial.

The simple circuit shown above does the job. The 32 Ohms resistor connected to the ground is the earphone.
To suppress the noise which is generated by switching the digital outputs just add a simple capacitor of some 22 μF and connect it to the earphone, that will do.

As the ATmegas were not built to perform real-time audio you cannot expect very high fidelity. With the CD you have 44,100 samples per second of 16-bit left and right channel, giving you 176,400 bytes per second. Not to overburden the little chip, please be satisfied with 8,000 samples per second, one channel of 8 bits. So, every 125 μs the controller has to output a new value to be converted.

The code shown below works for both the Arduino UNO and the Arduino MEGA.

// generates either sawtooth or sine
// in sawtooth mode you can check the linearity of the ADC
// to watch the sawtooth properly remove the capacitor over the load

#define SIN

int   dt = 187;  // give it enough time to calc sin()
float f  = 100;  // Hz
float k = 2 * PI * f * 0.000001; // speed up calc
long t;
const byte LEDpin = A0;

void setup() {
  Serial.begin(9600);
/*    
data  d7 d6 d5 d4 d3 d2 d1 d0
Ports 13 12 11 10  9  8  -  -
UNO   B5 B4 B3 B2 B1 B0  -  -
MEGA  B7 B6 B5 B4 H6 H5  -  -
*/  
#ifdef __AVR_ATmega2560__
  DDRB = B11110000;
  DDRH = B01100000;
  Serial.println("6 bit-DAC (ATmega 2560)");
#else  
  DDRB = B00111111;;
  Serial.println("6 bit-DAC (Arduino UNO)");
#endif    pinMode(LEDpin, OUTPUT);
  t = micros() + dt;
}

void loop() {
  static boolean x;
  static byte b;
  // flash LED if spare time
  while (micros() < t) PORTC =  (x = !x); // PORTC, bit 0 = A0 
  t = t + dt; 
#ifdef SIN  
  t = t + 0.000001 * dt;
//b = 31 * (sin(2 * PI * f * t * 0.000001) + 1);
  b = 31 * (sin(k * t) + 1);
#else
  if (b++ > 63) b = 0;
#endif
#ifdef __AVR_ATmega2560__
  PORTH = ((b << 2) & B00001100) << 3;
  PORTB =  (b << 2) & B11110000;
#else
  PORTB = b;  // Arduino UNO
#endif  
}

If you want the Arduino to play pre-recorded sounds, just store them in *.h-files.
How can you do this? Just follow these 10 instructions:

  1. Record your sound(s) using any software that offers to export in TXT format.
  2. Set the wave format to 8000 samples per second, mono, 8-bit
  3. Edit the sound. It has to fit in the flash memory! With the Arduino UNO you have about 3.7 seconds of sound.
  4. Save it as *.TXT
  5. In your favorite text editor it should look like this:
    133
    145
    149
    153
    159
    165
    170
    168
    167
    168
    
    If the file contains a header like this:
    SAMPLES:	4559
    BITSPERSAMPLE:	8
    CHANNELS:	1
    SAMPLERATE:	8000
    NORMALIZED:	FALSE
    
    just delete it, but keep in mind the number of samples.
  6. The C language needs commas after each value. If you are using Microsoft WORD, select EDIT -> REPLACE -> EXTENDED -> SPECIAL and select "Search for: Paragraph (^p)" and replace with comma (,).
  7. Insert these lines at the beginning:
    const int sounddata_length = xxxxx;
    const unsigned char sounddata_data[] PROGMEM = {  
    
    For xxxxx type the number of samples that was given in the header.
  8. Add this line at the end:
    };
    
  9. Select "SAVE AS" and select "Text only". The filename should end with ".h". Select the directory where your sketch is in.
  10. At the beginning of your sketch insert an include directive which contains this filename:
    #include "welcome.h"
    

#include <avr/pgmspace.h>
#include "welcome.h"

const int dt = 125;  // = 8000 samples per second
long t = micros() + dt;
int sample = 0;

void setup() {
  //Serial.begin(9600);
/*    
data  d7 d6 d5 d4 d3 d2 d1 d0
Ports 13 12 11 10  9  8  -  -
UNO   B5 B4 B3 B2 B1 B0  -  -
MEGA  B7 B6 B5 B4 H6 H5  -  -
*/  
#ifdef __AVR_ATmega2560__
  DDRB = B11110000;
  DDRH = B01100000;
  //Serial.println("6 bit-DAC (ATmega 2560)");
#else  
  DDRB = B00111111;;
  //Serial.println("6 bit-DAC (Arduino UNO)");
#endif  
}

void loop() {
  if (sample > sounddata_length) return;
  while (micros() < t);
  t = t + dt; 
  byte b = pgm_read_byte(&sounddata_data[sample++]); // raw data  
// data: d7 d6 d5 d4 d3 d2 d1 d0
// d1 and d0 will be ignored
#ifdef __AVR_ATmega2560__
  byte h = (b & B00001100) << 3;
  b = b & B11110000;
  PORTH = h;
  PORTB = b;
#else
  PORTB = b >> 2;  // Arduino UNO
#endif  
}

Sample data welcome.h

Beware: even if you are using the ATmega-2560, the size of arrays is limited to 32 kByte. You can split your data in two arrays but the space for your sound samples is still limited to 64 kByte. If the data exceed this limit the code will compile and upload but nothing will happen.


contact: nji(at)gmx.de