Some ideas that come to mind:
sample based instrument- store samples on the Arduino or on an SD card and trigger playback with buttons or other types of controls. Check out my Arduino drum sampler for an idea of how to get started.
digital synthesizer- make saw, sine, triangle, pulse, or arbitrary waveshapes- check out my waveform generator to get started
MIDI to control voltage module/ MIDI synthesizer- receive MIDI messages and translate them into a voltage so you can control an analog synthesizer with MIDI, or use the MIDI data to output audio of a certain frequency
analog output- you may find yourself needing to generate analog voltages from your Arduino at some point, maybe to communicate with an analog device
effects box/digital signal processing- in combination with a microphone/audio input you can perform all kinds of digital signal manipulations and send the processed audio out to speakers. Check out my vocal effects box for an example.
audio playback device- make your own ipod. With the addition of an SD shield you could create your own Arduino mp3 player (check out the wave shield documentation for an idea of how to get started with the code). The circuits and code provided here are compatible with SD shields that communicate via SPI.
Feel free to use any of the info here to put together an amazing project for the DIY Audio Contest! We're giving away an HDTV, some DSLR cameras, and tons of other great stuff! The contest closes Nov 26.
Parts List:
(x9) 1/4 Watt 20kOhm Resistors Digikey 0KQBK-ND
(x7) 1/4 Watt 10kOhm Resistors Digiikey CF14JT10K0CT-ND
(x2) TS922IN Digikey 497-3049-5-ND I like these because they can be powered off the Arduino's 5V supply (one 924 works too, but they don't seem to be available on digikey at the moment)
(x1) 10kOhm potentiometer linear Digikey 987-1308-ND
(x1) 0.01uF capacitor Digikey 445-5252-ND
(x1) 220uF capacitor Digikey P5183-ND
(x1) 0.1uF capacitor Digikey 445-5303-ND
(x1) 1/4 Watt 3kOhm Resistor Digikey CF14JT3K00CT-ND
(x1) 1/4 Watt 10Ohm Resistor Digikey CF14JT10R0CT-ND
(x1) Arduino Uno Sparkfun DEV-09950
Additional Materials:
22 Gauge Wire
solder
Step 1: Digital to Analog Converter
The resistor ladder I'll be demonstrating in this tutorial is an 8-bit DAC, this means it can produce 256 (2^8) different voltage levels between 0 and 5v. I connected each of digital pins 0-7 to each of the 8 junctions in my 8 bit DAC (shown in figs 1 and 3).
You can also use an external DAC chip to perform digital to analog conversion. These chips receive digital serial data from the Arduino and output a voltage. If you are short on digital I/O pins or if you want higher fidelity digital to analog conversion, a DAC chip may be a good solution for you. I like to use the R2R ladder because it's easily sourced (you probably already have all the resistors), cheap, easy to address in the Arduino code, fast, and it does a surprisingly good job for what it is. There seems to be kind of a misconception abut 8 bit audio- that it always has to sound like the sounds effects from a Mario game- but 8bit audio with this really basic DAC can actually replicate the sounds of people's voices and instruments really well, I'm always amazed at the quality of sound that can come from a bunch of resistors.
Step 2: Set up DAC and Test
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 I've described above. In the main loop() function I've written:
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 shown on an oscilloscope in fig 4. The center horizontal line across the oscilloscope represents 0V and each horizontal line represents a voltage increase/decrease of 2V. The image notes on fig 4 show the output of each of the lines of code above, click on the image to view the image notes.
The code below outputs a ramp from 0 to 5V. In the loop() function, the variable "a" is incremented from 0 to 255. Each time it is incremented, the value of "a" is sent to PORTD. This value is held for 50us before a new value of "a" is sent. Once "a" reaches 255, it gets reset back to 0. The time for each cycle of this ramp (also called the period) takes:
period = (duration of each step) * (number of steps)
period = 50us * 256 = 12800us = 0.0128s
so the frequency is:
frequency of ramp = 1/0.0128s = 78Hz
The output from the DAC on an oscilloscope can be seen in fig 5.
The code below outputs a sine wave centered around 2.5V, oscillating up to a max of 5V and a min of 0V. In the loop() function, the variable "t" is incremented from 0 to 100. Each time it is incremented, the expression:
127+127*sin(2*3.14*t/100)
is sent to PORTD. This value is held for 50us before "t" is incremented again and a new value is sent out to PORTD. Once "t" reaches 100, it gets reset back to 0. The period of this sine wave should be:
period = (duration of each step) * (number of steps)
period = 50us * 100 = 5000us = 0.005s
so the frequency should be:
frequency of ramp = 1/0.005s = 200Hz
But this is not the case, the output from the DAC is shown in fig 6. As indicated in the image notes, it does not have a frequency of 200hz, its frequency is more like 45hz. This is because the line:
PORTD = 127+127*sin(2*3.14*t/100);
takes a very long time to calculate. In general multiplication/division with decimal numbers and the sin() function take the Arduino a lot of time to perform.
One solution is to calculate the values of sine ahead of time and store them in the Arduino's memory. Then when the Arduino sketch is running all the Arduino will have to do is recall these values from memory (a very easy and quick task for the Arduino). 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 values in an array called "sine" in the Arduino sketch below. Then in my loop, for each value of "t" I sent an element of sine[] to PORTD:
PORTD = sine[t];
The output from this DAC for this sketch is shown in fig 7. You can see that it outputs a sine wave of 200hz, as expected.
Step 3: DAC Buffer
Once this was set up I wired an LED and 220ohm resistor in series between the output of the op amp and ground. The sketch below outputs a slow ramp out the DAC so you can actually see the LED get brighter as the ramp increases in voltage. The period of the ramp is:
period = (duration of each step) * (number of steps)
period = 5ms * 256 = 1280ms = 1.28s
so the LED takes 1.28 seconds to ramp up from off to full brightness.
Step 4: Low Pass Filter
You can calculate the values of the capacitor and resistor you need for a low pass filter according to the following equation:
cutoff frequency = 1/ (2*pi*R*C)
Nyquist's Theroum states that for a signal with a sampling rate of x Hz, the highest frequency that can be produced is x/2 Hz. You should set your cutoff frequency to x/2Hz (or maybe slightly lower depending on what you like). So if you have a sampling rate of 40kHz (standard for most audio), then the maximum frequency you can reproduce is 20kHz (the upper limit of the audible spectrum), and the cutoff frequency of your low pass filter should be around 20kHz.
For a cutoff frequency of 20,000Hz and 1kOhm resistor:
20000=1/(2*3.14*1000*C)
C =~ 8nF
since 8nF capacitors are hard to come by I rounded up to 0.01uF. This gives a cutoff frequency of about 16kHz. You can mess around with different values and see what you like best, I tend to like heavier filtering because it removes more unwanted noise.
Step 5: Signal Amplitude
Step 6: Amplifier
Step 7: DC Offset
Step 8: Output
Step 9: 40kHz Sampling Rate
To set up the interrupt you need to copy the following lines into your setup() function:
cli();//disable interrupts
//set timer0 interrupt at 40kHz
TCCR0A = 0;// set entire TCCR0A register to 0
TCCR0B = 0;// same for TCCR0B
TCNT0 = 0;//initialize counter value to 0
// set compare match register for 40khz increments
OCR0A = 49;// = (16*10^6) / (2000*8) - 1 (must be <256)
// turn on CTC mode
TCCR0A |= (1 << WGM01);
// Set CS11 bit for 8 prescaler
TCCR0B |= (1 << CS11);
// enable timer compare interrupt
TIMSK0 |= (1 << OCIE0A);
sei();//enable interrupts
the contents of the interrupt routine are encapsulated in the following function:
ISR(TIMER0_COMPA_vect){ //40kHz interrupt routine
}
You want to keep the interrupt routine as short as possible, only the necessities. You can do all of your other tasks (checking on buttons, turning on leds, etc) in the loop(). Also keep in mind that setting up interrupts may affect other Arduino functions such as analogWrite and delay.
In the code below, I use the interrupt function to send a new value of sine[] to PORTD at a rate of 40kHz and increment the variable "t." Figs 1 and 2 show the (unfiltered) output of the code on an oscilloscope. We can calculate the expected frequency as follows:
frequency = (sampling frequency) / (steps per cycle)
frequency = 40,000 / 100 = 400hz
at a sampling frequency of 40kHz we expect the duration of each step to be:
duration of each sample step = 1/(sampling frequency)
duration of each sample step = 1/40,000 = 25us
Step 10: Extra Tips
If you want to do serial communication: Software Serial is an Arduino library that allows you to turn any of the Arduino's pins into serial pins. Usually when you are doing an Arduino project that requires serial communication, you avoid using digital pins 0 and 1 because they need to be free to send serial data. I like to use them for the 8 bit DAC because pins 0-7 are all part of PORTD on the Arduino's Atmel328 chip, this allows me to address all of them in a single line of code. PORTB only has 6 pins (digital pins 8-13) and PORTC only has 6 pins (analog pins 0-5), so you cannot construct an 8 bit DAC with these ports alone.
If you need to use the PWM pins, or otherwise need to use different pins as the DAC: If you must use the PWM pins you can use bit manipulation to free up pins 3, 5, and 6 and replace them with pins 8, 12, and 13. Say you want to send the number 36 to PORTD. You can use the following lines:
//define variables:
boolean bit3state;
boolean bit5state;
boolean bit6state;
//in your main loop():
bit3state = (36 & B00001000)>>3;//get the third bit of 36
bit5state = (36 & B00100000)>>5;//get the fifth bit of 36
bit6state = (36 & B01000000)>>6;//get the sixth bit of 36
//send data to portd w/o disrupting pins 3, 5, and 6
PORTD |= (36&B10010111);//set high pins high using the number 36 with zeros replacing bits 3, 5, and 6
PORTD &= (36|B01101000);//set low pins low using the number 36 with ones replacing bits 3, 5, and 6
//send data to portb w/o disrupting pins 9, 10, and 11
PORTB |= 0 | (bit3state) | (bit5state<<4) | (bit6state<<5);//set high pins
PORTB &= 255 & ~(1-bit3state) & ~((1-bit5state)<<4) & ~((1-bit6state)<<5);//set low pins
be sure to keep these PORTD and PORTB lines right next to each other in your code, you want the pins on port d and port b to switch at as close to the same time as possible.
Here is the code from the previous step, edited so that it does not use any PWM pins. As you see in fig 1, the unfiltered output from the DAC has many discontinuities caused by the lag between sending data to port d and port b, as well as splitting up the commands for setting pins high and low. You can get rid of most of these discontinuities with the low pass filter (fig 2). If you wanted to use this technique you might consider increasing the cutoff frequency of your low pass filter. If you wanted to make this really good, you could send your 5 most significant bits to port d and your 3 least significant bits to port b. This would decrease the amplitude of some of the discontinuities, reducing the magnitude of the noise. I'll let you figure that one out on your own.
If you run out of digital pins and need more: Remember you can always use your analog pins as Digital I/O. Try out the following functions, they work just like you are dealing with a regular digital pin.
digitalWrite(A0,HIGH);//set pin A0 high
digitalWrite(A0,LOW);//set pin A0 low
digitalRead(A0);//read digital data from pin A0
Otherwise, try using a multiplexer. If you need more digital outputs, the 74HC595 allows you to turn three of the Arduino's digital pins into 8 outputs. You can even daisy chain multiple 595's together to create many more outputs pins. You could set up your whole DAC on one of these chips if you wanted (though it would take a few lines of code to address it and might slow you down too much for higher sampling rates). The Arduino website is a good place to start learning about how to use the 595.
If you need more digital inputs, the 74HC165 or CD4021B let you turn three of the Arduino's digital pins into 8 inputs. Again, the Arduino website is a good place to start learning how to use these chips.
If you want to use the info in this Instructable with the Mega or other boards: In this Instructable I talked exclusively about the Arduino Uno with Atmel328. The same code will run fine on any board with an Atmel328 or Atmel168 chip on it. You can also use the same ideas with a Mega. You should try to attach your DAC to any port that has 8 available pins, that way you can address your DAC with one line of code ("PORTD =" ) On the Uno, the only port that has 8 available pins is port d. This picture indicates that the Mega has several ports with 8 pins: ports a, b, c, and l are the obvious choices. If you don't care about wasting analog pins you could also use ports f or k.
























































Visit Our Store »
Go Pro Today »




TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 49;
TCCR1A |= (1 << WGM11);
TCCR1B |= (1 << CS11);
TIMSK1 |= (1 << OCIE1A);
and changing the ISR to ISR(TIMER1_COMPA_vect) ?
which code are you talking about? which step?
I want to use your project as a basis for my midi guitar. Where do i plug my oscilloscope probe to see if the signal is now digital?
How can i do to play in the output (speaker) my voice with a delay?
Like a 3sec ou 5sec delay...
I don't know how to do it in arduino.
I want to do a digital low pass filtering before putting the signal in the DAC.
I am not quite sur how to handle it in arduino.
Since the most simple form of a low pass filter is
T*ds/dt+s(t)=K*e(t) e : signal to process
s : output signal
T : Time constant
K : Gain filter
I was thinking about doing something like :
int incoming;
void setup(){}
void loop()
{ e = analogRead(A0);
for (byte j=0; j { s[j+Δt] = K*e[j]*Δt/T + s[j](1-Δt/T) }
}
Δt is the sampling rate
But how can I define the upper limit ( N ), since the arduino is constantly reading value from A0 ?
Thanks
this is what I'm thinking:
lastOutput = output;//record the last thing you output
incoming = analogRead(A0);//get new value
output = lastOutput + (1-c*(abs(incoming-lastOutput))*(incoming-lastOutput);//where 0<=c*(abs(incoming-lastOutput))<1
PORTD = output;//send to DAC
not sure if this is quite it, just an idea off the top of my head, but some really interesting ideas here, I may have to experiments with it a little myself... let me know how it works out!
I don't understand the line :
output = lastOutput + (1-c*(abs(incoming-lastOutput))*(incoming-lastOutput) ;
How did you come up with it ?
And how is this changing the slew rate ?
http://en.wikipedia.org/wiki/Low-pass_filter#Algorithmic_implementation
basically you would have:
lastOutput = output;//record the last thing you output
incoming = analogRead(A0);//get new value
output = lastOutput + c*(incoming-lastOutput);//where 0PORTD = output;//send to DAC
does it make sense how I got this from the wiki article? specifically from this part:
"for i from 1 to n
y[i] := y[i-1] + α * (x[i] - y[i-1])"
as for the slew rate stuff, the slew rate is the maximum change in voltage over time of a component. So if you put a pulse signal into a component, you can measure the slew by looking at how long it took the component to output the change in voltage. low pass filtering relates to slew rate because the effect of a low pass filter is to slow down the rate of change of a signal (dv/dt). it;s a little different than slew bc slew often only effects the max dv/dt, anything under this max is unaffected, but filter effects all dV/dt, but dampens higher dv/dt more. does that make sense?
this pic might help:
http://en.wikipedia.org/w/index.php?title=File:Slew-rate.svg&page=1
this is the effect of low slew rate on a pulse, with lp filtering the output would follow the same general shape, but will have exponential curves in its transitions:
http://en.wikipedia.org/wiki/File:Raised-cosine-filter.png
I'll try the code within the next couple of weeks. I will keep you in the loop !
Thank you SO much again.
-frequency response- they cut out the higher frequency components of a signal- this is a much more analytical approach, but it is very hard to implement in code, as it requires an fft of the signal
-waveshaping- they smooth out large changes in dv/dt. This way of thinking about low pass filtering makes them much simpler to implement in your code because you only need to store two data points and do some simple math to get the slope
yes I'm really interested to hear the result, definitely keep me posted!
good luck!
lastOutput = output;//record the last thing you output
incoming = analogRead(A0);//get new value
output = lastOutput + c*(incoming-lastOutput);//where c is between 1 and 0
PORTD = output;//send to DAC
PS: I'm trying to do the Input and output Instructables togheter, in other words: Talk on the mic->ADC->DAC->Speakers.
Can someone help-me with this?
Thanks
22k will be ok for testing purposes, but when you solder this together I'd recommend getting the 20k. If you read my Arduino Audio Input tutorial you can get some info about how to set up the mic input circuit. The code for mic->ADC->DAC->speakers is at the bottom of step 6 of my audio input tutorial: here.
hope that helps you get started!
Personally, I never really bother with this, I guess the 8-bit R2R DAC is fun for me because it's such a lo-fi solution for generating audio, so I don't worry too much about making it perfect. I kind of accept that each one just ends up with its own unique idiosyncrasies. If I wanted to make a better 8 bit DAC I'd probably opt for a dedicated IC rather than bother with matching resistors. There are plenty of 8 bit parallel input DACs available that would work great with this tutorial and code.
thanks for the comment, that was really helpful!
http://www.taydaelectronics.com/catalogsearch/result/?q=operational+amplifier+quad+pdip
The aim is to replace the TS924IN, which I'm getting quoted at about £1.59
I'm running a project to 'standardise' a breadboard and stripboard layout for use in an educational context, and audio output (especially with a resistor ladder) would be a brilliant project. However, the one Op-Amp IC you've chosen almost doubles the cost of this as a kit!
Finding something which would be adequate from Tayda's stock would be really handy. Hope someone knows more about reading data sheets than I do.
http://www.taydaelectronics.com/tlc274-quad-operational-amplifier-precision-pdip-14-tlc274cn.html
with datasheet...
http://pdf1.alldatasheet.com/datasheet-pdf/view/28886/TI/TLC274CN.html
...might be able to cope, although the need for a negative rail I'm not totally clear on. I appreciate it's more limited at 45mA total (30mA per channel) but more in the ballpark.
Do you think it's possible to set up a circuit with a TLC274CN across 6V (4xAA) giving -3V and +3V which seems a good enough voltage to drive an ATMEGA328-PU as well. Would certainly be quiet, but maybe good enough for a short sample to be played as part of an electronic game, and could add a battery-boosted speaker.
Does this make any sense?
Higher sample rates allow more resolution in regards to amplitude which directly translates into more dynamic range. Let's not confuse things here.
If Amanda is synth'ing/decoding 8 bit samples, it is 8 bit audio period. 8 bit gave digital audio a bad name in the early days account its grainy sound. 16 bit/44.1kHz is red book CD quality. Most modern audio interfaces allow 24 bit with 44.1-96kHz sample rates.
Most DSP is done internally at 32 bit allowing additional headroom and reducing the artifacts caused by floating point rounding errors