JeremyBlum.com

v3.1

Driving 5 Speakers Simultaneously with an Arduino

| 55 Comments


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

speaker

For the ReacXion Audio/Visual Project I’ve been working on, it’s necessary 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 squarewave 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. For this project, I’m simultaneously controlling a large LED matrix. If the matrix isn’t refreshed often enough, the display looks choppy. Unfortunately, cutting away to an interrupt routine every 50μs was too often, and the display started to look funny. So, I had no choice but 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


 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//** ReacXion Source Code **//
//** www.jeremyblum.com **//

/* 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!
}

55 Comments

  1. Pingback: 5 Tones, 1 Arduino - Hack a Day

  2. You might want to low-pass filter the square-wave to get something more useful… ;-)

    – michael

  3. On the idea of outputting multiple tones at once, I had been messing with something similar, but using a s2p chip and dac (just a resistor ladder in my case) to get multiple analog waves out at different forms and frequencies. If you’re interested, I’ve got the code in SVN at http://code.google.com/p/obriencj/source/browse/sequencer/lfo_1/lfo_1.pde

  4. 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!

    • 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.

    • 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…

  5. Pingback: Connecting Bloggers » Blog Archive » 5 Tones, 1 Arduino

  6. 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?

    • 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.

    • 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.

  7. 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…

    • Also, if I never reset the counters, they may overflow eventually.

    • 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.)

  8. 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.

  9. You might be able to go back to 50us if you used a faster method of writing the output pins. This blog post http://www.billporter.info/?p=308 made me aware of just how slow the standard digitalWrite is and that http://code.google.com/p/digitalwritefast/downloads/list is much quicker.

  10. 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
    }
    }

  11. 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!

  12. 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!

    • 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.

  13. 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!!

  14. Pingback: Arduino | Embeddedpub - Embedded Projects

  15. Pingback: Zarathustra | Mimi Yin

  16. Nice project!

    Time slicing worked for me. Just share the loop time between multiple channels and you only need a single output and no math in your mixer. I have 6 channels working on an AVR324 this way…

    http://www.lucidscience.com/pro-lazarus-64%20prototype-1.aspx

    Videos near the end show the sound system working.
    Brad

  17. hello sir can you please upload video of this design of 5 speakers

  18. Pingback: [Sciguy14] Arduino Tutorial 10: Interrupts and Hardware Debouncing | Ayarafun

  19. 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.

  20. 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?

    • Yes, it sounds like using timer interrupts might be the way to go, to ensure you send commands at regular intervals.

    • 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

  21. 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.

    • 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?

  22. 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?

  23. Can I play multiple tones on ONE Speaker at one time using this speaker?

  24. 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 ;)

  25. I meant “never exceeds”, not “net”… (Stupid spellchecking …)

    • 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.

  26. Pingback: Tutorial 10 for Arduino: Interrupts + Debouncing | JeremyBlum.com

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

    • 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.

  28. Pingback: Arduino: Orchester aus alten PC Lüftern | Technik - Blogbasis.net

  29. Pingback: Arduino – Noções Básicas | CryptoPost: Criptografia e Certificação Digital

  30. PRAISE YOU!!!
    Busted my head for days on this. XD

    Making an instrument for my gf :P

  31. 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!

  32. 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

Leave a Reply

Required fields are marked *.