BLOG > Driving 5 Speakers Simultaneously with an Arduino


This post was featured on the Hack-a-Day Blog on 9/14/10
Mimi Yin has a neat implementation of this code for an interactive sculpture.

speaker

I’m working on a project where I want to drive 5 speakers independently – each with a variable volume and set frequency (though the frequency of each speaker is different).  I examined several methods for generating the 5 square waves in hardware – using a 555 timer, oscillating RC with schmitt trigger, and a bunch of opamp circuits.  Unfortunately, none of these generated tones as nice as the ones I got when simply using the arduino tone library.  The built-in tone() function allows you to generate a square wave with 50% duty cycle of your selected frequency on any pin on the arduino.  It relies on one of the arduino’s 3 timers to work in the background.  Specifically, it uses timer2, which is also responsible for controlling PWM on pins 3 and 11.  So you naturally loose that ability when using the tone() function.  But I wanted to make 5 tones simultaneously!  How to do it?

While the tone() function can only be run one instance at a time, I discovered (after much research), that I could hijack one of the timers (again, I chose timer2), and use some clever math and software interrupts to generate 5 tones simultaneously! I wanted to generate 5 tones of the following frequencies:

  1. C = 131 Hz
  2. D = 147 Hz
  3. E = 165 Hz
  4. G = 196 Hz
  5. A = 220 Hz

Any music lovers out there will surely recognize this as the pentatonic scale (5 notes that nearly always sound good together). Now, think back to physics, and recall that period = 1/frequency.  In other words, each frequency pin will have to cycle from 0V to 5V and back again in the time dictated by the period for that note.  See the following picture:

Square Wave

The red portion of the square wave represents one cycle.  From the formula above, we can determine the following periods required to generate each note:

  1. C = 1/131 Hz = 7.6ms
  2. D = 1/147 Hz = 6.8ms
  3. E = 1/165 Hz = 6.0ms
  4. G = 1/196 Hz = 5.1ms
  5. A = 1/220 Hz = 4.5ms

But, remember that we are dealing with a microcontroller, so we need to flip the value twice every period.  If you take a look at the green portions on the image above you’ll see what I’m talking about.  In one red portion of the wave, the value needs to change twice.  So in reality, we actually need to be able to flip bits twice as fast as the times outlined above.  Dividing those values by two, we get the following:

  1. C = 7.6ms/2 = 3.80ms
  2. D = 6.8ms/2 = 3.40ms
  3. E = 6.0ms/2 = 3.00ms
  4. G = 5.1ms/2 = 2.55ms
  5. A = 4.5ms/2 = 2.25ms

So, if we want to generate all these tones at the same time, we’re going to need to have an interrupt service that can activate at a Greatest Common Factor for all these values.  This interrupt request will toggle the appropriate pin every prescribed number of milliseconds, and we’ll get the tones we need! So, the least common multiple for the variables above would be: .05ms, or 50μs.  If we use this ideal value, we can construct an interrupt service request that will initiate every 50μs during our program.  Using some counters, we can flip the bits after the appropriate intervals!  Let’s calculate those counters now:

  1. C = 3.80ms/50μs = 76 counts
  2. D = 3.40ms/50μs = 68 counts
  3. E = 3.00ms/50μs = 60 counts
  4. G = 2.55ms/50μs = 51 counts
  5. A = 2.25ms/50μs = 45 counts

In an ideal world, we would simply have a software interrupt trigger every 50μs, increment 5 separate counters each time, and toggle the associated pin when we reached the right value.  For example, every time counter1 reached 76, we would reset it to 0, and toggle the value of that pin.  If the only thing you were doing with your CPU was making noises, then this would work quite well.  But the whole reason for doing this in an interrupt routine is so that we can do other things simultaneously.  I had to trade off some frequency accuracy for a reduction in how often I activated the interrupt.  After some trial and error, I deemed the golden value to be 64μs.  It’s a little more than the ideal lowest common denominator, but still close enough that the notes sound good.  Using this new value, I calculated new counts, and implemented them in my interrupt routine:

  1. C = 3.80ms/64μs ≈ 60 counts
  2. D = 3.40ms/64μs ≈ 53 counts
  3. E = 3.00ms/64μs ≈ 47 counts
  4. G = 2.55ms/64μs ≈ 40 counts
  5. A = 2.25ms/64μs ≈ 35 counts

And that’s it!  Using these values, I can simultaneously generate all five notes, while maintaining enough time to quickly update my LED Matrix.  Below is the final arduino code for making this work. Thanks to Sebastian Wallin for some awesome information on interfacing with arduino timer interrupts.

Creative Commons License Licensed via Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License


//** Arduino – Drive Multiple Speakers with Interrupts **//
//** http://www.jeremyblum.com/2010/09/05/driving-5-speakers-simultaneously-with-an-arduino/ **//
/* Timer reload value, globally available */
unsigned int tcnt2;
/* Toggle HIGH or LOW digital write */
int toggle1 = 0;
int toggle2 = 0;
int toggle3 = 0;
int toggle4 = 0;
int toggle5 = 0;
/* Keep track of when each note needs to be switched */
int count1 = 0;
int count2 = 0;
int count3 = 0;
int count4 = 0;
int count5 = 0;
/* Frequency Output Pins */
#define FREQ1 9
#define FREQ2 10
#define FREQ3 11
#define FREQ4 12
#define FREQ5 13
//Setup Function will run once at initialization
void setup()
{
/* First disable the timer overflow interrupt*/
TIMSK2 &= ~(1<<TOIE2);
/* Configure timer2 in normal mode (no PWM) */
TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
TCCR2B &= ~(1<<WGM22);
/* Select clock source: internal I/O clock */
ASSR &= ~(1<<AS2);
/* Disable Compare Match A interrupt (only overflow) */
TIMSK2 &= ~(1<<OCIE2A);
/* Configure the prescaler to CPU clock divided by 128 */
TCCR2B |= (1<<CS22) | (1<<CS20); // Set bits
TCCR2B &= ~(1<<CS21); // Clear bit
/* We need to calculate a proper value to load the counter.
* The following loads the value 248 into the Timer 2 counter
* The math behind this is:
* (Desired period) = 64us.
* (CPU frequency) / (prescaler value) = 125000 Hz -> 8us.
* (desired period) / 8us = 8.
* MAX(uint8) – 8 = 248;
*/
/* Save value globally for later reload in ISR */
tcnt2 = 248;
/* Finally load end enable the timer */
TCNT2 = tcnt2;
TIMSK2 |= (1<<TOIE2);
//Configure I/O Pin Directions
pinMode(FREQ1, OUTPUT);
pinMode(FREQ2, OUTPUT);
pinMode(FREQ3, OUTPUT);
pinMode(FREQ4, OUTPUT);
pinMode(FREQ5, OUTPUT);
}
/* Install the Interrupt Service Routine (ISR) for Timer2. */
ISR(TIMER2_OVF_vect)
{
/* Reload the timer */
TCNT2 = tcnt2;
count1++; count2++; count3++; count4++; count5++;
if (count1 == 60)
{
digitalWrite(FREQ1, toggle1 == 0 ? HIGH : LOW);
toggle1 = ~toggle1;
count1 = 0;
}
if (count2 == 53)
{
digitalWrite(FREQ2, toggle2 == 0 ? HIGH : LOW);
toggle2 = ~toggle2;
count2 = 0;
}
if (count3 == 47)
{
digitalWrite(FREQ3, toggle3 == 0 ? HIGH : LOW);
toggle3 = ~toggle3;
count3 = 0;
}
if (count4 == 40)
{
digitalWrite(FREQ4, toggle4 == 0 ? HIGH : LOW);
toggle4 = ~toggle4;
count4 = 0;
}
if (count5 == 35)
{
digitalWrite(FREQ5, toggle5 == 0 ? HIGH : LOW);
toggle5 = ~toggle5;
count5 = 0;
}
}
void loop()
{
//Do whatever else you want to do with your arduino!
}

66 comments

  1. Hi Jermemy,
    nice code, but i noticed one thing: in your isr routine u could speed up things a bit by using else if instead of if in the following ifs. also try to avoid digitalWrite it uses a lot of time,
    use direkt pin access instead and save lot s of time!

    nice hack!

    1. Jan> in your isr routine u could speed up things a bit by using else if instead of if in the following ifs
      I don’t think so – each if block is dealing with a different counter.

    2. Steve is right, they do need to be if statements to account for instances when more than one counter has to be reset in the same cycle. You do make a good point about using digitalWrite though…

  2. it is just me, or is he counting from 0 and not taking that into account, so he’s actually 1 rep off of his calculated values?

    1. No, I think I should be starting at zero. If I want a cycle to be 3 ticks long for example… 0->1->2->3. I’m actually thinking about the space “between” the numbers. So in this case, I’d need to start at 0 and reset at 3 to get a pulse 3 ticks long.

    2. Hi Mark,Yeah, I saw the >250 pages of documentation of the uIP TCP/IP stack and thhogut to myself: lets not immediately start with too complex stuff It would be very nice if I could get a messaging client running on the simplecortex though; but that will probably take some time to accomplish.

  3. Aleks,

    No, I don’t think so, because he is incrementing BEFORE the tests.

    Also, it might make it faster to use modulo to test the counters, that way you don’t have to set them to 0 each time. Saving an instruction. But the modulo might cost more than the assignment, so…

    1. Mod, just for the record, is monstrously slow.

      Fun tip: if you want to cycle count, you can use AVR Studio in debug mode and actually step through your code to see how many cycles each operation takes. (Though this means you can only use the bare metal ATMega functions, not the nice Arduino library.) That’s what I did, and I found lots of neat stuff about the speed of various math operations.

      (Summary: add/sub fast, 8 bit ops fast; 32 bit ops slower, mul slower; division and float ops very slow at between 700 ~ 1600 cycles.)

  4. Also, HIGH and LOW (I believe) just renaming of true and false (or 1 and 0).

    So you might be able to change:

    digitalWrite(FREQ5, toggle5 == 0 ? HIGH : LOW);

    to

    digitalWrite(FREQ5, toggle5);

    and save yourself the comparison step.

    I may be wrong. YMMV.

  5. Nicely done. A few possibly helpful tips:
    1. It’s easier to test for 0 or negative than other values – so counting down helps.
    2. You can toggle an AVR pin with a single instruction, by trying to set it in the PIN register (not PORT). Setting it to a value based on a variable requires a lot more, particularly with wrappers like digitalWrite.
    3. The timer will run with your intended period in CTC mode (resetting TCNT from the handler will vary with interrupt latency).

    The central tests could thus look like:

    ISR(TIMER2_OVF_vect) {
    static int8_t count1=max1-1;

    if (0>–count1) {
    count1=max1-1;
    PIND|=_BV(1); // Toggle the output pin
    }
    }

  6. Not to sound like a jerk or anything, but you used the term “Least Common Multiple” wrong… I think you might’ve meant Greatest Common Factor. Still, brilliant!

  7. Interrupting every 50 or 64μSec is barely better than polling, no wonder your LED display gets messed up. You may cause less interruption to your LED display if you calculate how many intervals you need to wait until the next pin toggle, and set the timer to that exact amount. Then you only get timer interrupts when you really need them.

    Second, for whatever reason, your blog post is treating the code as HTML, and so everything that has a < (such as <<) or an & followed by a ; is getting eaten. So I can't read your code properly.

    But otherwise, nice work!

    1. I’ve found it to be much better than polling. I tested both ways. Using an ISR produced far better results, whereas polling was too often “off-the-mark”. The sweet spot I found works quite and allows me to drive my LED array with no noticeable lag while still playing notes.

      However, your idea is a good one, and it would reduce the number of ISRs I’d have to jump into, so I’ll definitely give it at try. Thanks!

      I’ve fixed the code issue too.

  8. AWesome work here man! i was wondering, say im working with five buttons. how can i use this to make a polyphonic keyboard? i guess what im saying is that would i just have to make the counters go up and the if statements run only if a button is pressed otherwise reset the counter to zero? and what about volume. i looked into your video for ReacXion, and the sound gradually ramped up. how is this done? i am decent at programming and get the jist of it, i just have not worked with any interupts. thanks!!

    1. i know im double posting. sorry! i was wondering what IC you used for the digital potentiometer, and how the transistor circuitry works as well. after watching the other videos i realized that was how you controlled audio volume. pretty clever!

        1. The problem is that I have a controller that changes the nits I play based on some sensors. I’m guessing that this code isn’t ideal for that sort of thing then huh?

  9. Hia. I am new to interrupts with the Arduino, and I had a question or two. I feel like there are probably obvious solutions, so excuse me if these questions are pretty dumb. >.< I was wondering how exactly I could use the interrupts to generate these notes for a specified period of time, like if I was to play an eighth note, half, whole, etc? I tried just setting a boolean value on and off with some delays in the loop function, the value effecting the interrupt itself, but it stopped working after going down low enough in milliseconds. And I was hoping to be able to do this with timer interrupts instead of using the loop function, so that I could do it with multiple pins playing notes at different speeds while doing something else at the same time.

    1. You might be able to do that by using the other internal time to trigger transistors that switches the signals on and off before they reach the speaker.

  10. I read your article on driving 5 speakers with one arduino. Nice work.
    I was wondering if this would be a good starting point for a project I am working on. I need to drive 4 stepper motors independently with ramp-up and ramp-down. The driver board for the steppers is a Toshiba based board in which I only need to send it a pulse to step the motor, so I will need to send 4 independent pulses to drive each motor at different speeds. Your work looks like the way to go, but the ramping has me in a quandary. Any suggestions?

    1. I have been playing with my Mod-vga, I made a cable which moved some of the pins about so that I could talk to it (the Arduino pins coinlfct with the memory card reader). I was reading that there are some solder jumper settings so that I can use the UEXT port, anyone know where the jumpers are on the board?It would be great to have a board with a fully integrated mod-vga chip on it, imagine the power! It would be like the good ole C64 days but quicker and smaller

  11. Very nice. Is there any way I can change the code so that the frequencies are variable, i.e by using a potentiometer to adjust them?

    I tried changing
    if (count1 == 60)
    to
    if (count1 == knob)

    where ‘knob’ is defined by an analogRead value but this had some weird effects.

    1. Actually, just realised that that does work (I’d got something in the wrong place before).

      However – is it possible to make it so that the period of the “HIGH” output is different from the period of the “LOW” output?

  12. Hey Jeremy,
    I need to make a loop which works in a way such that piezo buzzer works for a minute. Can you please help me in that?

  13. Nice tutorial !

    Anyway, I noticed you always use 16-bit integer data type (int) for pin numbers. That’s a rather bad habit. No Arduino has more than 54 “ready to use” pins (54 being the Mega 2560 pin count).
    So, using int to store pin numbers, there’s a huge memory waste. Using 16-bit data type when the max value net exceeds 255 is just nonsense.
    When coding, a good practice is to use the data type that will hold the value with the smallest memory footprint possible. So, to store pin numbers, the 8-bit integer “byte” is the best choice ;)

    1. When I’m writing actual “production code” I’m obviously much more careful with data types and memory allocation. Most of the tutorials I post on this site are aimed at getting people introduced to Arduino, so I try to keep the solutions as simple as possible. In this case, always using “int” is simpler than explaining how to choose the integer size.

  14. I had a doubt in your code – why have you set prescaler to 128?
    Rest of the things are nicely explained!

    1. That’s just a divider to get it down to a more reasonable speed. You could conceivable use different dividers, but the clock would be going faster than you need, and you’d have to do a lot more interrupts.

  15. Hi Jeremy. This is a fantastic solution. When I bought my arduino pack I thought of starting by making some poliphonic noises… and you can already guess my frustration… I hadn’t even made the blink demo at that moment!
    Despite all that, some weeks later, based on this tutorial and other stuff, I was able to build this:
    https://github.com/mexomagno/arduitunes
    This makes your arduino play poliphonic music. You can play pre made tunes like Mario Bross, Pokemon Battle… (I made them :D ) or make your own with an ultra simple web app that I built from scratch.
    I hope someone likes it!

  16. I’m using this code to play the same frequency in 2 speakers and I need to control the interval in which the sound is played. What do you suggest to change in the main code ?

    Thanks for the post

  17. Hi !

    I’m doing his school project were I need to be able to control four speakers with arduino individually. The speakers would play same tone but with different volume and it would be important to control the volume easily.. do you have any tips for this ?

    Thanks !

  18. We wanted to use it but its impossible to get that part as quickly as we need it… so we’re wondering is there any other solutions ? :)

    1. If you’re using an arduino with a DAC, like the Due, you could use that to set the reference voltage of an amplifier circuit for the speaker. That would be able to adjust the volume.

  19. Hey Jeremy,

    Thanks for a very nice code share :) I love using Arduino, but I’m still a beginner.

    I want to use your code to play 5 simultaneous tones to make a nice pleasant chord (on a high frequency tweeter).
    For my project I am training mice to discriminate between two tones.
    I want to use a chord to tell “Good job!” after they’ve done something correctly. I don’t want to use pure tones because I’m using them in the discrimination task. I want something that is unique.
    My problem is that mice hear best between 8-16 kHz.
    This leads to times that are 62.5 us to 35.2 us (8 – 14.2 kHz).
    I see in your code that you set the interrupts to 64 us.
    How low can I go on this?
    I am using a Mega with a clock speed of 16 MHz, meaning each tick is 62.5 ns.
    Even if I use an interrupt of 8 us, that leaves the value of tcnt2 = 255. Is this too high?
    Also dividing these frequencies by 8 gives me interrupt values of 8,7,6,5, which corresponds to frequencies of 7.8, 8.9, 10.4, 12.5 and 15.6 kHz, which to me sounds more like a penalty than a pleasant “Good work mouse.”

    Is this problem solvable with the Arduino Mega or is it just not possible to play frequencies this high with the code you shared?

    I look forward to your reply.
    Sincerely,
    Mic

  20. Hi jeramy. Great code.it got me thinking so i changed the code a bit a i can now play tones on 8 pins at once. I combined a second chip ani have created a top octave generator. Only problem is 2 of the note are just to high or to low of the exact note required.guess thats the ardurino limit. Will see just how many pins i ca have playing at once

  21. Can I use also two external interrupts (attachinterrupt) for sync this sound from external controller?

    I wanna put some math in main loop to change count values to get sine or saw change.
    But I want to trigger period of modulation(before changing to another value of count) by external interrupt from some pin(time between edges changing). Will it work?

  22. Hi Jeremy, thanks for the article! I was wondering if it’s possible to incorporate this code inside the loop function? So I’m building a simple application for a classroom and when they interact with certain objects different tones will be played. Currently I’m using the tone() function and I was wondering if there’s a way to do poliphony and having 2 piezo speakers together. But I’d need t obe in the loop since I’d trigger the sounds based on the pins input signal. Thank you!

  23. Pingback: Connecting Bloggers » Blog Archive » 5 Tones, 1 Arduino
  24. Pingback: Arduino | Embeddedpub - Embedded Projects
  25. Pingback: [Sciguy14] Arduino Tutorial 10: Interrupts and Hardware Debouncing | Ayarafun

Leave a Reply to Maximiliano Castro Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Advertisement