Audio out using PCM (synth trial runs)

Hello everyone!

In advance, sorry for all this text. I want to make a tutorial out of this...

I got an Arduino for Christmas with the hopes of making a subtractive synth out of it. In a perfect world I would like to have at least 2 oscillators (perhaps using PWM on outputs 9 and 10 on the board?) and be able to select each oscillator's wave form individually from a choice of a square, a triangle, a sawtooth or a sine wave.

I was able to make some progress using the piezo speaker example but that method is really only capable of producing square waves. I could of course convert the outputted square wave to whatever form I wanted externally (along with the phasing and stereo effect I was able to get working) but that's not really not clean enough for me. Along with requiring external signal processing, I couldn't get the square wave frequency range wide enough for the application.

So I want to move onto using Pulse Width Modulation to create the oscillators. I tried starting with a triangle wave. (it's hard to program a sine wave without a sin function.... anyone know how math.h is ever supposed to fit in ram?? or maybe even create a useable sine table??? ... anyways). I looked around and couldn't find any clear tutorials of how to do this so I figured I'd put what I figured out here... sorry if this info's repeated or if it's not exactly how it should be done.

The REAL problem is that I need to filter out the PWM's carrier signal to leave me with a clean analog waveform. This means I need a low-pass filter with a cutoff frequency above (hopefully) 20k Hz but below the PWM's carrier frequency. As stated on the analogWrite reference page, "The frequency of the PWM signal is approximately 30769 Hz".

After a few google searches I found this page that discusses how to make a low-pass filter that will be sufficient:

It mentions that a good cut off frequency for the filter should be at least 2 times greater then the highest frequency that you want to output. In (hi-fi?) audio applications, 20k Hz is usually the highest you ever need to pass.

20k Hz * 2 = 40k Hz > 30.769k Hz

By calculating this out, it seems, at first glance, that we are out of luck. The cut off frequency will still leave the carrier signal in the line. Not good. Maybe we won't have the Fundamental harmonic in there (higher then 30.769... calculate it out if you want...) but the carrier signal (though out of the audible range) is still going to be there.

But we can get around this. We are able to change the carrier frequencies of Arduino's PWM outputs. Looking around on the forums I found a post hinting at how we change it. (tell me if the link doesn't work...):
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1152547089/2

I've copied the relevant code snippet into this post to put it all in one place:

#define TIMER_CLK_STOP            0x00      ///< Timer Stopped
#define TIMER_CLK_DIV1            0x01      ///< Timer clocked at F_CPU
#define TIMER_CLK_DIV8            0x02      ///< Timer clocked at F_CPU/8
#define TIMER_CLK_DIV64            0x03      ///< Timer clocked at F_CPU/64
#define TIMER_CLK_DIV256            0x04      ///< Timer clocked at F_CPU/256
#define TIMER_CLK_DIV1024            0x05      ///< Timer clocked at F_CPU/1024

void Setup()
{
[...]

TCCR1B = (TCCR1B & ~TIMER_PRESCALE_MASK) | TIMER_CLK_DIV8);

[...]
}

Really we can set the frequency by using:

void Setup()
{
[...]

TCCR1B = 0x03;  //or whatever setting we want to use...

[...]
}

We want the PWM carrier frequency to be higher then the default. The default I found (after trying all the options listed) seems to be 0x03.

This is where I start to get a bit confused. If 0x03 is the default value of TCCR1B and this value corresponds to "Timer clocked at F_CPU/64" AND if this means the PWM's carrier signal is at around 30.769 kHz then, (unless I'm reading it wrong... and doing my math wrong... which I probably did...):

F_CPU = 30.769 kHz *64 = 1969.216 kHz = 1.969 MHz != 16 mHz ATMega8 clock

What is going on here? Shouldn't it equal out? What does F_CPU really stand for?....

And thus my final question is this: What value of TCCR1B should I use to get the PWM's carrier frequency above 40 kHz? Or is it there already and I'm just not calculating this right?

I'd love to see a table in the reference page about the corresponding frequencies...

Thanks a lot for the help,
Sorry for all the text, I really couldn't find any real audio tutorials for Arduino and would love to get some synth tutorials out there.

~jamis

The smaller the prescale factor, the higher the frequency, so to achieve maximum frequency, you want to set the low three bit of TCCR1B to 0x01. You shouldn't simply assign a value to TCCR1B, as it will write to the other bits (they might happen to already be 0, but it's not good to rely on that).

F_CPU is the speed of the clock running the chip: 16 MHz. The frequency of the PWM wave, however, is determined by the timer overflows which happen every 256 increments of the timer. And the current frequency of the PWMs is around 1 KHz. So: 1 KHz * 64 * 256 = ~16,000 KHz = 16 MHz.

The reason this sort of thing isn't in the reference is because it's more advanced than we think is appropriate for the Arduino (like anything that involves messing around with registers and bit manipulation). A method for setting the PWM frequency, however, has been a frequent request, and I hope to find a simple way to provide that functionality. Suggestions welcome. Also, consider writing these issues up for the Arduino playground so that more advanced users can take advantage of the information.

hey jamisnemo. i'm interested in your code to generate square and triangular signals. i don't care too much about the higher freqs so i'd be fine with the ~31k freq of the pwm and a low pass at 30k. if you don't mind sendin your code, you can email me at tateu@yaoo.com.
i would like to generate audio from the arduino, eventually, my goal is to do some ADC on some audio on analog pins, screw with it and output it back with pwm. i've looked a bit into generating sq signals but got nowhere so far.
thanks in advance for your help.
tateu.

Well I've run into some issues with this project that will probably require an external chip to solve.

The reason that most computers have a dedicated sound processing card/chip in addition to the cpu is because the logic code needed to create different wave forms can't be run quick enough directly from the cpu itself.

I ran into the problem with the Arduino and cant really find an easy fix. Because the processor only runs at 16 or 20 MHz, it's hard to have accurate frequency control over any oscillator code you write.

For example, if you setup the "PlayMelody" tutorial (http://www.arduino.cc/en/Tutorial/PlayMelody) and use a musical tuner to check the frequencies (such as the 'a' at 440 hertz) you'll find that the frequencies are lower then they should be. The A given in the code doesn't actually give a frequency of 440. This is due to the time delay given while the rest of the code runs each loop. This could be fixed in the PlayMelody tutorial because it's playing a tune that's coded in.

However, the method shown in the PlayMelody tutorial can't be used for realtime frequency generation. The amount of time spent by the Arduino's CPU when executing the analogRead() function puts a HUGE kink in how high the frequency can be.

I set up some code that would switch one of the analog outputs from high to low on every alternating loop. The highest frequency I got (using a software oscilloscope) was rather high. I believe it was in the range of 34k hertz (above the human hearing).

As soon as I added the code to read in a value from an analog input, a delay was added into each loop that made the highest frequency square wave peak out at about 148 hertz (not anywhere near high enough).

I'll redo the experiment and get the exact numbers after I sign up for classes this afternoon but I know for a fact that the numbers aren't high enough for any real time signal generation using the equipment the Arduino comes with alone.

However, for those of us that would like to make synthesizers using the Arduino, there do exist frequency generator chips! A friend sent me this link last night (http://www.makezine.com/blog/archive/2007/01/how_to_make_a_f_2.html) which hints at two such chips (the Maxim MAX038 and the Exar XR2206). I'm currently looking for some samples of either chip to test in conjunction with the Arduino.

Another possible option is to find a "broken" game system from the NES generation. The synth sounds used in many video games is quite tasty for some styles of music and it may be feasible to use a chip from one of those systems.

I'll post again when I find a chip that'll do the job I need.

Tateu: sorry but the code I have doesn't really produce reliable waveforms. I can, however, try to draw up a circuit for a low pass filter along with the formulas needed for the component values.

Anyone know of a good circuit design software...?

~jamis

hey jamis. thanks for your reply and info. keep posting what you find. i think this subject is very interesting.
for the design soft, everybody seems to favor eagle's pcb. it's free:
http://www.cadsoft.de/freeware.htm

I looked into using the chip from a nes, but i was unable to find any datasheets at all... I don't know if that is me not looking very well... there seems to be a few synths made from the C64 SID chip tho... http://www.ucapps.de/

What about http://www.atmel.com/dyn/products/devices.asp?family_id=618? I found it while looking (still looking) for a single-chip midi synthesis. Tell me what you think.

Superware

I looked into using the chip from a nes, but i was unable to find any datasheets at all... I don't know if that is me not looking very well... there seems to be a few synths made from the C64 SID chip tho... http://www.ucapps.de/

the NES chip is a custom CPU with a built in synthesis section... I believe it is based on the 6502 and probably won't work as a stand alone sound generator.

Ahh, that explains it... I've seen quite a few nes cartridge midi hacks, but thats not really what i'm after- i think i'll have a go at building a sid synth, but i need to get my hands on an old commodore 64 somehow, or at least the sid chip from one (if anyone has an old one they don't want... ;))

Silly question: why do you need to use analogRead() in your frequency generation code?

The reason that the analogRead() function is so slow is because of a call to delay(1) in it. Without this call, I was getting inconsistent results (i.e. sometimes the function would return the value of the wrong pin). At the time, we had no delayMicroseconds() function, so I used the smallest delay I could. If there's a need for the function to run faster, I can work on it, but until now, no one has complained.

Actually, I would like it if we could figure out how to get a faster version of analogRead also... it was bugging me because I was thinking of doing some faster sampling than 1kHz. It would be really neat if we could get the sampling rate up above 10kHz, because I would like to experiment with recognizing sounds on a microphone hooked up to an analog pin. For example: something that could decode the touch tone frequencies on a telephone back into the numbers that were pressed. These frequencies are (all in Hz): 697, 770, 852, 941, 1209, 1336, 1477. Since the highest is 1477, the sampling rate would have to be at least 2*1477, or about 3kHz.

hey mellis, i don't know if your question about the use of analogin() was for me but my idea was to sample music or voice coming into an analog port, tweaking it in software (echo, reverb, adding a sine, etc.) and outputing it back either through pwm or an external DAC. it would obviously be low-fi (8bit/11khz?) so the 10bit resolution of analogin() would be perfect but it seems the freq would be too low... covering a range of 0-15khz (half of the pwm's max of 30khz) would be ok for me on the output side but i see other people having difficulty producing a decent sound...

...

The reason that the analogRead() function is so slow is because of a call to delay(1) in it. Without this call, I was getting inconsistent results (i.e. sometimes the function would return the value of the wrong pin). ..

I would be interested in learning more about this problem. How did you test the
analogRead function? (I do not have a 'scope BTW). Page 200 of the datasheet
might have some clues.

Thanks,

It would be really neat if we could get the sampling rate up above 10kHz,

According to the datasheet, the best the arduino is going to do is less that 9khz.
Clock is 16Mhz, ADC prescalar is 128, so the ADC cycle rate is 125Khz. It takes
a minimum of 13 cycles to do one ADC conversion, which gives a total sample rate
of under 9Khz.

The datasheet implies that if you go for lower resolution, you can push the sample
rate, but I have no idea how that would work.

HTH,

ckiick: basically, I connected one of the pins to ground, one of them to +5V and read from them alternately. Sometimes reading from a pin connected to ground would return 1023, and vice-versa. Adding the delay() fixed the problem.