Recently I've been posting a lot of projects that use an 8 bit resistor ladder digital to analog converter (DAC) and an Arduino to make sound.  (see the Arduino vocal effects box, the Arduino drum sampler, and my audio output tutorial).  The technique I've been using to make these DACs is very simple, it requires only a handful of 10k and 20k resistors wired together into a network.  But the convenience comes with a price, as these DACs end up a little noisier than I would like at times.  So I decided to buy a specialized IC that will be compatible with all the code I've already written for the resistor ladder DACs, but uses highly matched resistors to reduce noise.  When I looked on Digikey for such a DAC, I found the TLC7528, a dual output 8 bit DAC IC.  The dual output capability of the chip interested me a lot; while it is easy to set this chip up with one permanent output, it also gives you the option of  toggling between two isolated output pins, making it fairly straightforward to set up a 2 channel audio output with a relatively small amount of additional effort/hardware setup/Arduino data pins.

In this instructable I'll show you how to use the TLC7528 with the Arduino to output stereo audio.  Stereo audio means 2 independent channels of audio.  Stereo audio is especially fun when sent to headphones because you can achieve some interesting auditory effects since each ear is hearing its own independent channel of sound, some ideas include:

"3D audio" spatial effects- by adjusting the filtering, amplitude, and phase of two channels of audio you can simulate the experience of sound directionality, making a sound source seem to originate from a precise location in the space around you, here's a great example
binaural beats- by sending two sine waves of similar -but unequal- frequencies to headphones (one to each ear), you will hear a pulsating beatnote that is thought to induce relaxation and other meditative effects.  Here's an example.
panning- change the relative amplitude of a sound source in each channel of the stereo mix.  This effect is simple, but can be really cool sounding, a great example is in the bridge of Led Zeppelin's Whole Lotta Love (listen to it with headphones!)

Parts List:

(x1) TLC7528 Digikey 296-1871-5-ND
(x1) Arduino Uno Sparkfun DEV-11021

Other Materials:

22 gauge jumper wire

Step 1: 8 bit DACs and Serial vs Parallel

The TLC7528 is a type digital to analog converter (DAC).  It takes digital data (numbers between 0 and 255) and outputs a voltage between 0 and whatever voltage you supply the chip with.  The output voltage of the DAC can be calculated according to the following equation:

output voltage from 8 bit DAC = (supply voltage) * (digital input data) / 255

In this Instructable, I'll be powering the DAC from the Arduino's built in 5V supply, so the equation above can be simplified to:

output voltage from 8 bit DAC = 5V * (digital input data) / 255

From this equation, we can see that the TLC7528 would output 5V if it receives a value of 255, 0V if it receives a value of 0, 2.5V if it receives a value of 127, and so on.  You may be wondering where the 255 came from, this is a result of the TLC7528 being an 8 bit DAC.  8 bit means that the binary numbers we can send to the DAC must have no more than 8 digits in them.  In binary, numbers that are represented with 8 digits (or less) range in value from 0 to 255 (as opposed to the regular decimal numeral system where and 8 digit numbers range from 0 to 99999999).  So there are 256 possible values (0-255) that the 8 bit DAC can receive.  This can be calculated quickly from the equation below:

2^8 = 256 possible values

If we were using a 10 bit DAC, then it could receive 2^10 = 1024 different values, ranging from 0-1023.  This means a 10 bit DAC has a higher resolution than an 8 bit DAC.  Despite this, I've found that 8 bit DAC's are generally much more useful than 10 bit DACs because data is easier to store and output from the Arduino in 8 bit form than in 10 bit form.  For example, the data type byte in the Arduino language is for storing 8 bit numbers.  If you wanted to store a 10 bit number, you would have to use an int data type, but int data types can store up to 16 bit numbers, so you would be wasting 6 bits of memory.  Additionally, the pins of the Arduino are grouped together in clusters of 8 or less.  On the Uno, the only full group of 8 are digital pins 0-7, this group is called PORTD.  When writing Arduino code you can easily output 8 bits of data by setting the states of digital pins 0-7 all at once.  In the code this is done by sending an 8 bit number to PORTD.  For example:

PORTD = 255;

sets digital pins 0-7 HIGH, it is equivalent to the following:


but using the PORTD command sets all the pins simultaneously and is much faster.  The following command would set digital pins 0-7 LOW:

PORTD = 0;

you can also use the PORTD command to set some of the pins high and others low.  For example:

PORTD = 137;
137 in binary is 10001001, so sending 137 to PORTD will set pin 7 HIGH (because the first digit of the binary number is a "1"), pins 6-4 LOW (because the next 3 digits are "0"), pin 3 HIGH, pins 2 and 1 LOW, and pin 0 HIGH.   You can read more about how this works on the Arduino website.  You can even send binary numbers to PORTD, for example:

PORTD = B10001001;
is equivalent to PORTD = 137;

Finally, I'll talk about how we get this data into the TLC7528.  The TLC7528 is called a parallel DAC.  This means that all the data we send to the DAC is sent in parallel.  8 bit parallel DACs have eight data connections between the Arduino and DAC that send all 8 bits of data at the same time.  The opposite of parallel is serial, in serial setups you use fewer data connections (usually three), but send only one bit over at a time.  So, in order to transmit an 8 bit number via a serial connection you have to send eight 1 bit packages, one after the other, while in parallel setups you can send all 8 bits at the same time.  This means that serial connections require faster data transfer than parallel connections.  If you are not worried about using 8 digital pins of the Arduino, a parallel 8 bit DAC is a good option because it requires less clock speed and is simpler to code.
<p>Amanda, I breadboarded the connection 4 times thinking that i did something wrong. I was getting 4 volts for 11111111. It's not the reference voltage. If I use the MDAC in current mode and a I to V converter, then would I get a 5v output?</p>
Some other people said that too, there might be something in the circuit that prevents it from behaving ideally, if you need it to get up to 5v (or higher) you can use an op amp to amplify it.
​It was because RFBA and RFBB were connected to VCC. Those are internal resistances built into the MDAC for the Transresistance amplifier when used in current mode. According to forums, current mode is more reliable and accurate but gives output in the range of 0 to -Vref so voltage mode is more convenient.
<p>Multiplying DAC require a I to V converters at output. For Voltage operation mode is it necessary that we should connect RFBA and RFBB to Vcc? It's not specified in the datasheet. Isnt RFBA the feedback resistor for the opamp built into the chip? Sorry for the noob question. Thanks for the tutorial!</p>
Hey Amanda <br> <br>I am a big fan of your work and, although new to electronics, have been slowly gathering parts to make up one of your 'Glitch Box' projects. I've just stumbled on this instructable and I'm wondering whether I should use one of these TLC7528 chips to improve the sound output? Would this be simple to adapt? Would I need to change or add any other parts? Appreciate any help you can give!
the rest of the circuit should stay the same
yes! you should definitely do that. I think it will sound better and it will be less stuff to solder.
It's already experimented or we have to experiment with that project?
this is meant to be a resource for people to create new projects from. There is tons of experimentation that can be done from these concepts.
Sorry this is a weird way to do it, but I couldn't get the replying working (something about the CAPTCHA I couldn't figure out) but here's what I would've replied! <br> <br>Wow thanks! That still sounds a little bit fuzzier than I'd like for using samples though. Is there a way I could increase the quality some more? I'd like to use my instrument for recording, and possibly preforming. Also, could I just downgrade the quality to 8-bit using the one-bit audio? Might be cool
I'm a little confused by the last thing you said? For lower frequencies 8 bit can be ok if you apply some low pass filtering. I made that last recording with a simple resistor dac and I didn't do any resistor matching to make sure that I was getting a more high fidelity output, but I think something like the chip I used in this instructable would be much less noisy. not sure exactly how much though.
So this says that you're using an 8-bit DAC, so does that mean you can only output 8-bit chipet-style sound? If not, what could you do to play higher quality sounds, like a piano sample?
chiptunes is actually 1 bit audio, 8 bit sounds much more organic (though a bit noisy). here's a sample: <br />https://soundcloud.com/amanda-ghassaei/over-the-rainbow
I'm using the output to lie to an ecu about throttle position, typical output 1.2 to 4.8v) Is there a way to configure the DAC correctly? It doesn't seem to be doing what is asked of it. I've put a post up on the ti forum hoping for an answer. <br>If possible can you please suggest a circuit to convert 0 to 4v back to a true 0 - 5v?
have you used op amps before? you can set up a non-inverting amplifier like this: <br /> <br />http://en.wikipedia.org/wiki/Operational_amplifier#Non-inverting_amplifier <br /> <br />if you choose your resistors correctly, you can amplify the signal back up to 0-5V. I think it is something like R1 = 4*R2, but you should double check. does that make sense?
Using the DAC in this configuration the output is scaled to 4/5 the input (i.e. 255 = 4v). Feeding OUTA / OUTB from a 6.25v regulated supply gives a true 0-5v output. Why is this necessary? Anyone?
good point, since the arduino only outputs 5V, I'd recommend using an amplifier in a voltage follower configuration to get the full 0-5V. what are you using it for?
Hi. Hopefully someone can help: I've built the circuit as shown in this build and found that rather than a full 5v output I only get 4v given a 5v reference. The diagram from the 'scope above also shows 4v (at 2v per division). I've pm'd Amanda but had no response as yet
First, I want to thank you for all of these wonderful instructables covering audio with the Arduino and also for your earlier help with my project. I've managed to set up a TLC7528 and use your sine wave code to get a nice clean output. I'm currently trying to understand your code in order to modify it to my own purposes. Essentially, I need to read a signal through the A0 pin, process it, and then output it through the DAC. It would also help if I could use the value of the PORTB register to determine how I process the signal. It seems to me that the actual audio output is done inside the ISR macro which looks to me like some kind of loop but I'm not sure. Should I just add a switch statement controlled by PORTB, read A0 and do the calculations in the individual switches, and output the final value to something like PORTD = output;? Would using this switch statement reduce the quality of the signal too much? If so, is there a better way? Thanks for all the help again.
Here's the code I'm using: I'm getting a sound out of it but it's very fuzzy. What might be wrong? <br> <br> <br> <br>int output; <br> <br>void setup() <br>{ <br> <br> for (byte i=0;i&lt;8;i++){ <br> pinMode(i, OUTPUT);//set digital pins 0-7 as outputs <br> } <br> <br> //set up continuous sampling of analog pin 0 <br> <br> //clear ADCSRA and ADCSRB registers <br> ADCSRA = 0; <br> ADCSRB = 0; <br> <br> ADMUX |= (1 &lt;&lt; REFS0); //set reference voltage <br> ADMUX |= (1 &lt;&lt; ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only <br> <br> ADCSRA |= (1 &lt;&lt; ADPS2) | (1 &lt;&lt; ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz <br> ADCSRA |= (1 &lt;&lt; ADATE); //enabble auto trigger <br> ADCSRA |= (1 &lt;&lt; ADEN); //enable ADC <br> ADCSRA |= (1 &lt;&lt; ADSC); //start ADC measurements <br> <br> cli();//stop interrupts <br> <br> //set timer1 interrupt at ~44.1kHz <br> TCCR1A = 0;// set entire TCCR1A register to 0 <br> TCCR1B = 0;// same for TCCR1B <br> TCNT1 = 0;//initialize counter value to 0 <br> // set compare match register for 1hz increments <br> OCR1A = 361;// = (16*10^6) / (44100*1) - 1 (must be &lt;65536) <br> // turn on CTC mode <br> TCCR1B |= (1 &lt;&lt; WGM12); <br> // Set CS10 bit for 1 prescaler <br> TCCR1B |= (1 &lt;&lt; CS10); <br> // enable timer compare interrupt <br> TIMSK1 |= (1 &lt;&lt; OCIE1A); <br> <br> sei();//enable interrupts <br> <br> <br>} <br> <br>ISR(TIMER1_COMPA_vect) //timer1 interrupt ~44.1kHz to send audio data (it is really 44.199kHz) <br>{ <br> output = ADCH; //read the value from A0 <br> PORTD = output; //send the output value to the DAC through digital pins 0-7 <br>} <br> <br>void loop(){}
this looks good, it would be better to set the audio output frequency at the same frequency that you are reading data from a0 (~38.5khz). try this:<br /> <br /> int incomingAudio;//store incoming audio data<br /> <br /> void setup(){<br /> <br /> &nbsp; cli();//disable interrupts<br /> &nbsp;<br /> &nbsp; //set up continuous sampling of analog pin 0<br /> &nbsp;<br /> &nbsp; //clear ADCSRA and ADCSRB registers<br /> &nbsp; ADCSRA = 0;<br /> &nbsp; ADCSRB = 0;<br /> &nbsp;<br /> &nbsp; ADMUX |= (1 &lt;&lt; REFS0); //set reference voltage<br /> &nbsp; ADMUX |= (1 &lt;&lt; ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only<br /> &nbsp;<br /> &nbsp; ADCSRA |= (1 &lt;&lt; ADPS2) | (1 &lt;&lt; ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz<br /> &nbsp; ADCSRA |= (1 &lt;&lt; ADATE); //enabble auto trigger<br /> &nbsp; ADCSRA |= (1 &lt;&lt; ADIE); //enable interrupts when measurement complete<br /> &nbsp; ADCSRA |= (1 &lt;&lt; ADEN); //enable ADC<br /> &nbsp; ADCSRA |= (1 &lt;&lt; ADSC); //start ADC measurements<br /> &nbsp;<br /> &nbsp; sei();//enable interrupts<br /> <br /> &nbsp; //if you want to add other things to setup(), do it here<br /> <br /> }<br /> <br /> ISR(ADC_vect) {//when new ADC value ready<br /> &nbsp; incomingAudio = ADCH;//update the variable incomingAudio with new value from A0 (between 0 and 255)<br /> &nbsp; <strong>PORTD = incomingAudio;//send out audio</strong><br /> }<br /> <br /> void loop(){<br /> //do other stuff here<br /> }
No, that's even worse, now it just makes popping sounds.
I set up 8 LEDs which tell me what bits of the DAC are HIGH and which are LOW. According to them there's barely any output with the new code.
sorry, I forgot to set pins 0-7 as outputs, try adding that piece back in.
Still all fuzzy, should I post my circuit? Might the problem be in the electronics?
Granted, the fuzz is a neat effect but I want to put the effects in myself instead of having them caused by a problem in the system.
the isr function is a timer interrupt: <br />http://www.instructables.com/id/Arduino-Timer-Interrupts/ <br />it gets called at a constant frequency to send out audio data- basically it takes care of sending out audio at a certain sampling rate. <br />reading A0 and sending out data should both be handled with interrupts. all the code for reading from portb should happen in your main loop. check out my vocal effects box for an example of this. <br />let me know if you have more questions! <br />amanda
i have the same headphones :P
Nice project. <br>For those who need a 10-bit solution, please have a look at http://sourceforge.net/projects/lxardoscope/files/accuracyInvestigation/ where the Arduino Uno drives a MAX503.
Great Project, I'm hearing Binaurals right now, but on my PC. A link went to my Gizmo blog: <br>http://faz-voce-mesmo.blogspot.pt/2012/11/instructables-sofa-de-paletes-stereo.html
cool, thanks!
Yes, I try to promote empowering stuff.
Cool project

About This Instructable


182 favorites


Bio: I'm a grad student at the Center for Bits and Atoms at MIT Media Lab. Before that I worked at Instructables, writing code for ... More »
More by amandaghassaei: OTCA Metapixel - Conway's Game of Life "9 Degrees of Freedom" IMU Twitter Controlled Pet Feeder
Add instructable to: