loading
Picture of Stereo Audio with Arduino
main.jpg
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
oscillosope
 
Remove these adsRemove these ads by Signing Up

Step 1: 8 bit DACs and Serial vs Parallel

Picture of 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:

digitalWrite(0,HIGH);
digitalWrite(1,HIGH);
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,HIGH);
digitalWrite(5,HIGH);
digitalWrite(6,HIGH);
digitalWrite(7,HIGH);

 
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.

Step 2: TLC7528 Overview

Picture of TLC7528 Overview
Screen shot 2012-10-30 at 7.28.56 PM.png
voltage mode.png
The pin diagram shown above comes directly from the datasheet of the TLC7528 DAC.  As I described in the last step, the TLC7528 has 8 parallel data inputs, labelled DB0-DB7; these pins will connect directly to digital pins 0-7 of the Arduino.  The TLC7528 is an interesting chip because it actually has two outputs on it (called DACA and DACB), which are both connected to the same 8 digital input pins.  You can select which output to want to use by setting pin 6 HIGH or LOW (LOW outputs to DACA and high outputs to DACB).  Pins 15 and 16 are used to control the outputs of the DAC as well, I'll explain more in later steps.

Fig 2 shows a functional block diagram of the chip.  Again, notice the data inputs, logic controls, and two separate outputs of the DAC.  There are many ways to set up this DAC depending on what you are trying to do with it, you can read more about these on the TLC7528 datasheet.  Fig 3 shows how to set up the DAC in voltage-mode operation.  In this setup, the analog output of the DAC will be pins REFA and REFB, and the pins labeled OUTA and OUTB will be connected to a fixed input voltage (I used the 5V supply voltage).  In the next step I'll show you how I set this up on a breadboard.

Step 3: Mono Audio Output with 8 Bit DAC and 44.1kHz Sampling Rate

The schematic for the DAC setup is shown in fig 2.  AGND and DGND (pins 1 and 5) connect to Arduino ground.  VDD (pin 17), OUTA (pin 2), OUTB (pin 20). RFBA (pin 3), and RFBB (pin 19) connect to Arduino 5V.  WR (pin 16) connects to digital pin 10, CS (pin 15) connects to digital pin 9, and DACA/DACB (pin 6) connects to digital pin 8.  DB0-DB7 (pins 14-7) connect to digital pins 0-7.  The outputs from the DAC are pins 4 (for DACA) and 18 (for DACB).

In the following piece code I use a timer interrupt to send data to the DAC at a rate of about 44.1kHz (standard audio sampling rate).  Interrupts are routines that are executed at specifically timed intervals.  While the Arduino is running commands in the main loop() function it pauses briefly to execute the contents of ISR(TIMER1_COMPA_vect){} at a rate of 44.1kHz.  Once the commands inside the function are executed, the Arduino resumes what it was doing in the loop() function.  This way we can easily add code to the loop function (check sensors, turn on leds, etc) and not have to worry about the timing of the audio output.  The following lines set up the interrupt:

  cli();//stop interrupts

  //set timer1 interrupt at ~44.1kHz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = 361;// = (16*10^6) / (44100*1) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 bit for 1 prescaler
  TCCR1B |= (1 << CS10); 
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
 
  sei();//enable interrupts


A full explanation of these lines is given in my Arduino Timer interrupt tutorial.  Inside the interrupt routine we find the following lines:

  PORTD = saw;//send saw out to the DAC through digital pins 0-7
  saw++;//increment saw value by one
  if (saw==256){//reset saw if it reaches 256 (keeps output within 0-255 always)
    saw=0;
  }


So each time the interrupt routine executes it sends the value of the variable "saw" to the DAC.  Then the variable saw is increased by one for the next time the interrupt routine executes.  If saw is >255 it is reset to zero.  Essentially the Arduino is sending the numbers 0-255 to the DAC and then resetting back to 0 once it reaches 255.  This will output a saw wave from the DAC (fig 7).

I noted in the comments of the code that the interrupt routine is not executing at exactly 44.1kHz, but as close as I could get.  The actual sampling rate is 44.199kHz (which is actually slightly better than 44.1). This was due to some limitations in the setup of timer interrupts.  We'll use this number to calculate some info about the DAC output:

duration of each sample = 1/sampling rate
duration of each sample = 1/44199Hz = 22.6us


Fig 8 shows a zoomed in view of the DAC saw output on an oscilloscope.  You can see the individual 22.6us steps of the wave, just as calculated.  The period of the wave (the length of one complete saw cycle) is:

period = duration of each sample * samples per cycle
period = 22.6us * 256 = 5.8ms


and the frequency:

frequency = 1/period
frequency = 1/0.0058s = 172Hz


these can be seen in fig 6.

The code below outputs a sine wave using the same interrupt I set up above.  Arduino has a built in sine function, but it is too slow to execute at 44.1kHz, so I stored an array of sine values to pull from during each interrupt.  I ran a simple Python script (below) to generate 100 values of 127+127*sin(2*3.14*t/100):

import math
for x in range(0, 100):
print str(int(127+127*math.sin(2*math.pi*x*0.01)),)+str(",")
,

I stored these numbers in an array called "sine" and then put the following line in the interrupt routine:

  PORTD = sine[index];//send a value stored in the array sine out to the DAC through digital pins 0-7
  index++;//increment index by one
  if (index==100){//reset index if it reaches 100
    index=0;
  }


these lines send a value from the array "sine", specified by the value of the variable "index", to PORTD.  Then "index" is increased by one for the next cycle.  If index > 99 it gets reset to zero.  The output from this code is shown in fig 9.

Since I used the same interrupt setup as the saw code, the duration of each sample is the same: 22.6us (fig 10).  Since there are only 100 samples per cycle for this sine (vs 255 samples per cycle for the saw) the period is:

period = duration of each sample * samples per cycle
period = 22.6us * 100 = 2.3ms


frequency = 1/period
frequency = 1/0.0023s = 442Hz


these can be seen in fig 9

Step 4: Stereo Audio Output with 8 Bit DAC and 44.1kHz Sampling Rate

Picture of Stereo Audio Output with 8 Bit DAC and 44.1kHz Sampling Rate
IMG_0838 copy.jpg
IMG_0840 copy.jpg
IMG_0843 copy.jpg
IMG_0841 copy.jpg
In this code I am sending a sine wave out DACA and a saw wave out DACB at the same time and at a rate of 44.1kHz.  This is stereo audio, two separate channels of audio.  To get this to work, I combined elements sine and saw mono code from the last step and used the WR and DACA/DACB pins to toggle between the two DAC outputs.

I set up an interrupt like in the last step, but this time I set it up at a rate of 2*44.1 = 88.2kHz.  Then each time the interrupt executed, I alternated between sending out something to DACA and DACB, so each received a sample during every other interrupt.  This makes the sampling rate on both of the outputs 44.1kHz.  The contents of the interrupt routine are copied below:

  digitalWrite(WR,HIGH);//hold outputs- so new DAC data does not get sent out until we are ready
  if (channel){
    PORTD = sine[index];//send sine to digital pins 0-7
    digitalWrite(outputSelector,LOW);//select DACA
    index++;//increment index value by one
    if (index==100){//reset index if it reaches 100
      index=0;
    }
  }
  else{
    PORTD = saw;//send saw to digital pins 0-7
    digitalWrite(outputSelector,HIGH);//select DACB
    saw++;//increment saw value by one
    if (saw==255){//reset saw if it reaches 256 (keeps output within 0-255 always)
      saw=0;
    }
  }
  digitalWrite(WR,LOW);//enable output again
  channel ^=1;//toggle channel


When the interrupts starts, the Arduino sets the WR pin HIGH, this temporarily holds the DAC outputs at their current voltages and allows us to send data into the DAC without changing the current selected DAC output.  The variable "channel" toggles between values of 0 and 1 each time the interrupt executes, alternating the sine and saw output.  When "channel" = 1, a value from the array "sine" is set to the DAC via PORTD.  The next line sets the outputSelector pin (DACA/DACB pin) LOW, which causes DACA to be selected.  Then WR is set LOW, causing the new sine value to output via DACA.  In the next interrupt routine, a similar series of events causes a saw value to output from DACB.
As in the last step, my sampling rate was not exactly 88.2kHz, it was actually 88.398kHz (slightly better than 88.2), so I'll use that number in the following calculations:

duration of each sample = 2 * 1/sampling rate
duration of each sample = 2 * 1/88398Hz = 22.6us


as in the last step the period of the sine and saw are as follows:

saw period = 22.6us * 256 = 5.8ms
sine period = 22.6us * 100 = 2.3ms


but if you look at figs 2 and 3 you will see that the sample duration and period of the output waves is much longer.  This is because the code in the interrupt routine is inefficient and is taking longer than 22.6us to execute.  To fix this I had to replace the Arduino library command "digitalWrite" with much more efficient direct pin manipulation commands in the code below.  You can read more about how they work here, you can also read the comments I've put in the code below.  Figs 4 and 5 show the outputs from this optimized code, you can see that the period and sample durations are what we expect from the calculations.


I'll also note here, that since the CS pin is held LOW for the duration of this code (setting it HIGH will disable writing new data to either output), you could free up an extra Arduino pin by attaching CS to ground permanently and deleting the instances of CS in the Arduino code.

Step 5: Amplifier, Additional Circuitry, and Tips

Picture of Amplifier, Additional Circuitry, and Tips
If you want to send the output of this dual channel DAC to speakers you will need use some additional circuitry.  Steps 3-8 of my Arduino Audio Output tutorial describe how to buffer, low pass filter, amplify, and DC offset an audio signal to send it to speakers.  You will need to build a separate copy of these circuits for each channel of audio.

I've also written a comprehensive list of potential issues with using 8 bit parallel DACs and their solutions in Step 10 of my Arduino Audio Output tutorial.  The problems I cover include:

-using serial communication (this is usually handled by digital pins 0 and 1)
-changing the pin connections to the DAC (if you need to use PWM pins or make the DAC compatible with a shield/some of the code)
-running out of digital inputs/outputs
-adapting this code to Mega or other boards

Step 6: Binaural Beats with Arduino

Picture of Binaural Beats with Arduino
Binaural beats are an interesting effect of sending two sine waves of slightly different frequencies to headphones (one sine wave to each ear).  Listen to this for an example (you must listen with headphones).  When listening to this example, try listening to just one headphone by itself and then the other.  You will find that the sounds coming out of each channel are pure sine waves of slightly different frequencies, and when you listen to them together you perceive a pulsating effect.  Many people believe that certain combinations of frequencies help with focus, meditation, sleep, and other brain activities.  I don't know enough about binaural beats to comment on this, but I am interested in the fact that this pulsating effect exists in the first place.

If you've ever tried to tune an instrument, you may be familiar with the concept of beatnotes (also called dissonance notes).  When you hear two frequencies that are very close to each other you start to hear a pulsating tremolo effect (called a "beat").  This effect is easily explained by interference between the two similar waves.  In this picture you can see two waves of very similar frequencies on the bottom, and their sum on the top, notice how the top signal varies in amplitude over time, this is the beat note.  The frequency of the beat is equal to the difference between the two frequencies.  For example if you play a 300hz and 305hz signal at the same time, you will hear a 5hz beat.  As you tune the 305hz signal closer to the 300hz signal, you will hear the beat slow down and eventually disappear when the two frequencies are equal, here is an example.   The interesting thing about binaural beats is that the two signals are never physically mixed together like they are for the beat notes I've just described, in binaural beats each frequency is sent separately to one ear.  All the signal mixing to produce something like a beatnote happens inside our brains, seemingly by the interference of the electrical/chemical signals coming from each ear.

To set this up I increased the resolution of the stored sine function to 1000 samples by running the following Python script and saving the array of values in my Arduino code:

import math
for x in range(0, 1000):
print str(int(127+127*math.sin(2*math.pi*x*0.001)),)+str(",")
,

I sent the sine waves out each channel similarly to step 4, but instead I incremented the index variables for each sine wave by different amounts for each interrupt cycle.  For instance:

index1 += 10;
will increment the index1 variable by ten each time a new value of sine is sent to DACA.  The frequency of the resulting wave is calculated as follows:

frequency = [ (interrupt frequency)*(index incrementation) ] / [ (samples in sine array)*(number of channels) ]
frequency = [ 88398*10) ] / [ 1000*2 ] = 442Hz

by incrementing inex1 and index2 at slightly different rate, you can send different frequency sine waves (440 and 480hz in this example) out each channel do the DAC.

Step 7: Simple Panning Circuit with Arduino

Picture of Simple Panning Circuit with Arduino
IMG_0873 copy.jpg
IMG_0874 copy.jpg
IMG_0875 copy.jpg
IMG_0876 copy.jpg
IMG_0879 copy.jpg
In this piece of code I used a potentiometer to control the pan between two channels of audio.  First I wired up the middle lead of a potentiometer to A0 and the outside leads to 5V and ground.  (I used a 100kOhm potentiometer, but anything between 1k and 1M will work fine too).  Then I uploaded the following code:

This code outputs 440hz sine waves out both stereo outputs.  When the potentiometer is turned all the way to one side, one of the outputs will be at maximum amplitude and the other will flat-line at 0 (fig 2).  As the potentiometer is turned in the other direction, the amplitude of one channel will go down and the other will go up until the channel which was previously flat-lining is at full amplitude and the other channel is at 0 (fig 3-6).

This code is fairly straightforward, but it uses a few tricks to maximize efficiency that I'll explain a little further here.  As in step 4, I've replaced all the digitalWrite() commands with PORTB &= and PORTB |= commands.  I did this purely for speed reasons (when you're working with 88kHz interrupts, you need to work quickly), and the comments of each of these lines gives their equivalent Arduino library command.  for example the line:

PORTB &= B11111011;

is equivalent to:

digitalWrite(WR,LOW);
where WR = digital pin 10

more info about how these commands work can be found on the Arduino website.  The function checkPan() checks the state of the pan potentiometer.  Since it is not critical to measure this pot at a high frequency, I only called the function in the main loop() instead of the interrupt.  The contents of checkPan()  are repeated below:

  pan = analogRead(A0);
  pan = pan >> 3;//convert from 10 bit to 7 bit (0-127)
  panA = pan;
  panB = 127-pan;


First the variable pan is set to the output from analogRead(A0).  The function analogRead() will always return a number between 0 and 1023- a 10 bit number.  I didn't think such high resolution was necessary for what I was doing, so I reduced the resolution of this number down to 7 bit in the second line to make it a bit more manageable.  Now instead of being a number between 0 and 1023, pan has been scaled down to fit between 0 and 127.  I did this with a simple bit shift, you could also do this with some division, or the map function, they all have the same end result (but the bit shift is the fastest way to do this).  Now that I have pan, a value between 0 and 127, I did some simple subtraction to calculate the amplitudes to send to DACA and DACB and assigned these values to panA and panB. 

In the interrupt routine I replaced the simple sine output from step 4:

PORTD = sine[index2];

with an output that has been scaled:

PORTD = (sine[index2]*panB)>>7;

this line is functionally equivalent to the following:

PORTD = (sine[index2]*panB)/127;

but since it uses a bit shift instead of division, it is much more efficient.  This is very important in high frequency interrupts, in fact, the line above will slow down the interrupt so much that it will not be able to execute fully before it is time for a new interrupt to start.  Using this bit shift trick is the only way to get the code to work correctly.
achand81 year ago

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?

amandaghassaei (author)  achand81 year ago
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.
achand81 year ago

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!

wizer1 year ago
Hey Amanda

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!
amandaghassaei (author)  wizer1 year ago
the rest of the circuit should stay the same
amandaghassaei (author)  wizer1 year ago
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?
amandaghassaei (author)  denimjohnson2 years ago
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.
qazxvy2 years ago
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!

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
amandaghassaei (author)  qazxvy2 years ago
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.
qazxvy2 years ago
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?
amandaghassaei (author)  qazxvy2 years ago
chiptunes is actually 1 bit audio, 8 bit sounds much more organic (though a bit noisy). here's a sample:
https://soundcloud.com/amanda-ghassaei/over-the-rainbow
locost332 years ago
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.
If possible can you please suggest a circuit to convert 0 to 4v back to a true 0 - 5v?
amandaghassaei (author)  locost332 years ago
have you used op amps before? you can set up a non-inverting amplifier like this:

http://en.wikipedia.org/wiki/Operational_amplifier#Non-inverting_amplifier

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?
locost332 years ago
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?
amandaghassaei (author)  locost332 years ago
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?
locost332 years ago
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
manuel1232 years ago
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?



int output;

void setup()
{

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

//set up continuous sampling of analog pin 0

//clear ADCSRA and ADCSRB registers
ADCSRA = 0;
ADCSRB = 0;

ADMUX |= (1 << REFS0); //set reference voltage
ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only

ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz
ADCSRA |= (1 << ADATE); //enabble auto trigger
ADCSRA |= (1 << ADEN); //enable ADC
ADCSRA |= (1 << ADSC); //start ADC measurements

cli();//stop interrupts

//set timer1 interrupt at ~44.1kHz
TCCR1A = 0;// set entire TCCR1A register to 0
TCCR1B = 0;// same for TCCR1B
TCNT1 = 0;//initialize counter value to 0
// set compare match register for 1hz increments
OCR1A = 361;// = (16*10^6) / (44100*1) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS10 bit for 1 prescaler
TCCR1B |= (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);

sei();//enable interrupts


}

ISR(TIMER1_COMPA_vect) //timer1 interrupt ~44.1kHz to send audio data (it is really 44.199kHz)
{
output = ADCH; //read the value from A0
PORTD = output; //send the output value to the DAC through digital pins 0-7
}

void loop(){}
amandaghassaei (author)  manuel1232 years ago
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:

int incomingAudio;//store incoming audio data

void setup(){

  cli();//disable interrupts
 
  //set up continuous sampling of analog pin 0
 
  //clear ADCSRA and ADCSRB registers
  ADCSRA = 0;
  ADCSRB = 0;
 
  ADMUX |= (1 << REFS0); //set reference voltage
  ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only
 
  ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz
  ADCSRA |= (1 << ADATE); //enabble auto trigger
  ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete
  ADCSRA |= (1 << ADEN); //enable ADC
  ADCSRA |= (1 << ADSC); //start ADC measurements
 
  sei();//enable interrupts

  //if you want to add other things to setup(), do it here

}

ISR(ADC_vect) {//when new ADC value ready
  incomingAudio = ADCH;//update the variable incomingAudio with new value from A0 (between 0 and 255)
  PORTD = incomingAudio;//send out audio
}

void loop(){
//do other stuff here
}
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.
amandaghassaei (author)  manuel1232 years ago
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.
amandaghassaei (author)  manuel1232 years ago
the isr function is a timer interrupt:
http://www.instructables.com/id/Arduino-Timer-Interrupts/
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.
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.
let me know if you have more questions!
amanda
mielke112 years ago
i have the same headphones :P
privatier2 years ago
Nice project.
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.
Edgar2 years ago
Great Project, I'm hearing Binaurals right now, but on my PC. A link went to my Gizmo blog:
http://faz-voce-mesmo.blogspot.pt/2012/11/instructables-sofa-de-paletes-stereo.html
amandaghassaei (author)  Edgar2 years ago
cool, thanks!
Yes, I try to promote empowering stuff.
diy_bloke2 years ago
Cool project