On Fri, Jun 3, 2011 at 11:22 AM, sdaau sd@imi.aau.dk wrote:
On 2011-05-30 23:51, Caleb Crome wrote:
FYI, if you can raise the PWM rate on the arduino output, you can use
modulation tricks to increase the resolution.
On the Arduino Duemillanove , there is 16 MHz clock and ATmega328 - as far as I could gather, the highest possible PWM frequency there is 62.5 kHz (in Fast PWM mode - achievable by setting the correct prescalers).
Ah, so that's not going to let you get too far in terms of oversampling. You could set your audio bandwidth to 62.5 / 2 / 2 ~= 15kHz and try for ~9-bit resolution. Here's the basic procedure:
On your host, resample the audio file to 62.5 kHz, lowpass filter it, and convert it to 8-bits. You can do this all as preprocessing steps until you get it into your application. It looks something like this:
sox input.wav -r 31250 i1.wav # This resamples to 31250 Hz. This
includes a built-in antialiasing filter, so will limit bandwidth to somewhere below 15kHz
sox i1.wav -r 62500 i2.wav # This resamples to 62500 Hz. It
creates samples between all the 31250Hz samples that are interpolated. This is the important bit!
sox i2.wav --bits 8 i3.wav # This converts to 8-bit samples.
(these sox command not tested ;-)
Now, you have a band-limited signal, with 8-bit samples. However, the sample rate is double what it would need to be for the limited bandwidth (<15kHz).
Now, just play this i3.wav file out to your arduino at 62.5 kHz. This *should* give you somewhat improved SNR on your signal. Theoretical best would be .... hmmm I don't recall, either 3 dB or 6 dB, or maybe 4.2 dB :-) Somewhere between 3 and 6 for sure.
If you can transmit, say, 10 bits and run your sample rate at ~4x your
intended rate, you can probably get about 10-bits of resolution on the output.
Interesting - I'm not sure if I understand that statement correctly: in this case, an output compare (OC) unit of an 8-bit timer/counter of the Arduino's ATMega328 is used to produce PWM output; and since I by default can only write 8-bit values there in the microcontroller code, I have a hard time seeing how I could squeeze out 10 bits on output in that context; but maybe you had a different context in mind?
Sorry, your PWM will still be 8-bits. The point is that *somebody* needs to upsample and lowpass filter the data. Forget this idea, and look to the sox example above. You could try different varients of that to get better resoltuion in the band of interest.
It requires a lowpass (antialiasing) filter though -- which gets
really nasty at sample rates below 40khz -- because then they need to be sharp so you don't hear the aliasing.
Heh, this is where I got surprised :)
I too expected that at least I would hear some high-frequency "buzz" due to the 'hard edges' of the PWM signal - which would assume that I don't have a low pass filter which is sharp enough, as you state. But the surprising bit for me was that I could just stick this 62.5 kHz (carrying a 44.1 kHz data) PWM signal raw in a loudspeaker - and I can *not* hear a high-frequency "buzz" that I expected :) Obviously, the loudspeaker itself performs low pass filtering - I just thought originally it would not be sharp enough :)
Yep, the loudspeaker does a descent job of lowpass filtering. Also, if your sample rate is above ~40kHz, any aliasing is inaudible (because you only hear to 20kHz at best). Unfortunately, if you take the output and drive an amplifier with it, the amplifier will be slew-rate limited on the hard PWM edges, and it'll start distorting. That's why you need an antialiasing filter if running into an opamp, but don't necessarily need one if going directly to a speaker or headphone.
Modern codecs manage this by shoving
all the aliasing noise up at a few MHz, so a simple RC filter is enough to eliminate aliasing issues. And not even necessary if driving speakers/headphones directly.
Thanks for mentioning this too - I have often heard about the technique of "pushing aliasing noise up to MHz", however, still do not understand it practically yet. And interesting that you state it is "not necessary" for speakers/headphones - I'd agree with that (based on my above experience), but I wander if that is mainly due to the mechanical inertia of the loudspeaker membrane - or because of presence of coil/inductor?
As long as the aliased signals are above the audible range, it's okay even if your loudspeaker/headphone *does* reproduce them. Who cares if your speaker also produces ultrasonic noise at 1 MHz? Reality is that speakers do act as lowpass filters (even good tweeters ;-), so it's really not something that you need to worry about.
But, you do need to worry about it if going to another amplifier because of the slew rate limitations above.
Anyway, 9-bits is going to be much better than 8, so if you can run the
arduino at 88.2k, you could probably get roughly 9 bits of resolution. You'd probably do the upsampling on the linux side -- since the AVR is not exactly bursting with MIPS.
I could send a data stream at 88.2 kHz, and there would be available data bandwidth too - however, since I have only an 8-bit timer/counter OC as a PWM generator, I still cannot see how I could get 9-bit resolution out of that.
It's the oversampling technique I demonstrated above. By running at a faster rate, the lowpass filter (i.e. speaker) smooths the 8-bit samples out to get better-than-8-bit resoltuion. It's a type of averaging.
Think of it this way: at 88.2kHz, you have 2 samples for every 1 time slice that you care about. (you care about only 20kHz audio bandwidth or 1/40kHz slices) If sample 1 is 34 and sample 2 is 35 (both 8-bit values), the AVERAGE sample, for that 1/40kHz time slice is (34+35) /2 = 34.5. This is how oversampling helps you. Lowpass filters *are* averagers.
Hmmm, or another idea: what if you use 2 PWM channels and mix the outputs
together with an op-amp? You could potentially get much improved resolution that way. Basically, use a summing node on your op-amp with one signal at unity, and the other signal at 1/256*unity.
Heh, that's exactly what I was thinking too :) (I think its mentioned very briefly in the end of the paper) - basically, one 8-bit PWM reproduces the MSB of the 16-bit sample; and the other PWM reproduces the LSB of this 16-bit simple, and they are appropriately mixed in the analog domain. However, as you say:
BTW, another limitation is the cleanlieness of your 5V power supply. It's probably extremely dirty, which will translate directly into noise on the output because your HIGH levels can only be as clean as the VCC rail. Low levels will be pretty clean (limited by the Ron of the AVR's output stage).
For instance, before this, I've always had the misconception that when, say, Audacity plays a file, it sends the value for each sample (say, 16-bit) one-by-one to the soundcard, and so the soundcard plays back the samples one-by-one. And that is not what happens: ALSA does its own chunking, then USB does its own chunking - which means that, at least for playback, as a code for the device (soundcard) you better be storing those incoming bursts of data in an array somewhere, and *then* reproduce sample-by-sample (from the stored data) at the requested reproduction rate.
:-) Yep. It would be really inefficient to have a 1 sample block size :-)
I believe that USB is fixed on 1mS block size, so the block size depends on your sample rate. I could be mistaken here.
Of course, in such a system, one needs to think about the analog domain as well - I tried to document a simple board, that would take the AudioArduino as is, and try to provide an analog line in/out interface to it with the simplest approaches (e.g. a discrete ramp-and-hold circuit, to convert the PWM signal to analog voltage levels); however, all I could conclude was that simple approaches actually produce slightly worse results (than just having the AudioArduino PWM out going directly into a speaker)
I think that the project is pretty neat, and a good demonstration project.
Take care,
-Caleb