PWM -> Audio Output

I am using a PWM output on my micro to produce audio from headerless WAV (RAW) files. The audio output works but the quality is not quite there. By that I mean that there are 'artifacts' which appear in the audio output from my device which do not appear in the RAW file.... mainly a popping noise which seems to follow the amplitude.

I've discovered that I can remove these artifacts by changing the PWM frequency. As I lower the frequency the pops and clicks go away but the audio does not reproduce as nicely.

My audio playback source code has two parameters which effect the playback. First, the sample rate which I currently have fixed at 8kHz for FLASH space savings. Considering that all audio played back will be voice prompts I thought this was sufficient. Second, the PWM frequency which I have set to be a multiple of the sample rate. So, if the sample rate is

8kHz and my PWM frequency is 32 times that... I have 32 PWM periods per sample. I get really nice reproduction at 32 and 64 times the sample rate but the popping noise is present. I eliminate the popping noise with a setting of 8 but then the audio is marginal.

My PWM output feeds through a first order RC filter which is set to cut off at around 8kHz. In practice it's currently around 12kHz because I didn't have the correct resistor value in my bin at the time I populated the board. The output of the filter is then fed to a amplifier (capacitively coupled).

Any suggestions?

Reply to
eeboy
Loading thread data ...

You need a better anti-aliasing filter. Look at an MF4 or MF10 active filter. Here's some reading material...

formatting link

Reply to
Jim Stewart

popping ? - have you scoped this,

  • to see just how large a disturbance it really is ?
  • if it matches any processor interrupts ?
  • or if it is a wrap-around error .. ?

What resolution if the PWM ?

-jg

Reply to
-jg

What kind of audio quality do you expect? The approach depends on that.

Popping noise = look for bugs, missed interrupts, race conditions or something like that.

Looks like the CPU doesn't have enough of speed for the higher sample rates.

Reasonable.

Way too much. The optimal choice would be PWM rate of x4 = 32kHz. Interpolate the samples by the simplest linear interpolation.

Apparently you don't have enough of CPU. No wonder: 32x8 = 256kHz.

That should be enough. Also, apply a preemphasis to the input waveform and apply deemphasis in the hardware.

I've done the PWM like that many times; you can get quite reasonable voice quality.

Vladimir Vassilevsky DSP and Mixed Signal Design Consultant

formatting link

Reply to
Vladimir Vassilevsky

Coincidentally, I just finished an experiment nearly identical to yours. I know exactly what popping you're talking about. It was an MSP430 running at

4 MHz. The poppiness is an absolutely sonically repeatable sound that can also be described as a scratchiness. In my situation, I didn't have any interrupt problems. I chose to forego the output filter relying on the low pass characteristics of my stereo instead.

First I picked my PWM rate as 15 KHz and 8 bits. The poppiness was evident. Then I went to 60 KHz and 6 bits and the poppiness was reduced significantly but not eliminated. Then I recalled a PWM audio experiment I did with an STM32 part running at 72 MHz. The STM32 was running PWM at several hundred KHz with a single pole output filter. There was no poppiness.

Taking all my observations together, I think one needs to consider and carefully balance desired sound quality, sample rate, sample size, PWM resolution, PWM rate, and most importantly have a well designed output filter. I doubt you'll ever be able to completely eliminate the poppiness because PWM in practice is an imperfect system but you can certainly improve it to the point of acceptability.

JJS

Reply to
John Speth

Personally I think your popping issue is MOST likely caused by a software loading problem, race condition, or similar issue - probably while you're fetching data from your storage medium. I would suggest you put a GPIO strobe in the ISR that reloads the PWM register and see if a gap corresponds with a pop.

Another thing to try is simply to have a sinewave lookup table in main memory and loop through it continuously. See if you observe the same problems as you tinker with the sample rate.

Also, at 8kHz sample rate your filter should be cutting off at 4kHz.

Reply to
larwe

WAV

there.

output

popping

Using a logic analyzer, I did manage to find a bug which caused an additional PWM period to be output. This explains the change in pitch as the PWM multiplier was lowered (sample rate effectively lowered). A periodic popping noise is still present. Given that it is periodic I'll have to agree that this is another bug. I'll continue digging into it.

is

Since I found the issue described above the 4x multiplier works great. Linear interpolation should be easy enough. I'll have to apply that to see the effect it has.

cut

By that do you mean apply a high pass using DSP and low pass in hardware? Can you point me to an example of applying preemphasis to a waveform?

Reply to
eeboy

As I don't see the OP in this thread, I will reply to this one, it is however not meant as a reply to Jim.

Could it be that You (so once more, You != Jim) do not fully understand PWM (just a question, not to be unkind, no problem if it would be the case, can't be an expert at everything)? You don't mention which PWM-resolution You are using which is rather important. When You say You are able to do 32x and even 64x oversampling, You must be working with quite a micro if You are using 8 bit.

First there is the resolution of Your PWM. Let's say this is 8-bit. That means that every period Your counter has to count from 0 to 255 (assuming You are not using the phase correct modes of e.g. Atmel, then it doubles). I'll presume that Your prescaler is at 1 and You are doing

64x oversampling. Your sampling rate is supposed to be 8 kHz (and as larwe also mentions, Your filter should cutt off at 4 kHz and not the 12 Khz You have made)(allthough, if we're talking output filter and You are actually oversampling that much, 12 kHz will do more than suffice - I doubt however that You are oversampling). That means Your clock frequency is 256x8000x64 = 131 MHz. I doubt this is the case.

You wrote "So, if the sample rate is 8kHz and my PWM frequency is 32 times that...", I am wondering what You mean with PWM frequency. Do You mean the frequency of the clock that drives the PWM counter or the frequency of the timerinterrupt, i.e. the number of times a sample is being output, so full count cycles of the timer? Could it be that You are in fact talking about the resolution of the pwm?

In another message You are talking about "the 4x multiplier works great", do You mean here oversampling? What are You multiplying?

So first get straight what the parameters You are talking about, really are:

  1. Clock frequency of the micro: ?
  2. Prescaler setting in pwm-timer: ?
  3. Resolution of timer in it's current pwm-configuration: ?
  4. Oversampling number: ?
  5. Resolution of Your samples in the RAW file: ?
  6. Sampling frequency at which the samples in the raw file have been taken: ? (I presume this is 8 kHz.)

How have the samples been taken? Did You, if You took them Yourself, use a better input filter as the output filter You are using now?

Your story is very unclear, if You fill in the values above things might get a bit clearer. Perhaps also mention what micro You are using, posting some sourcecode of the program You are using wouldn't be a bad idea as well.

The suggestion of Larwe to visualize the isr that updates the overflow value on the scope is a very good piece of advice. Tell us what frequency You see there and if the spaces between the isr's are constant.

Yours sincerely, Rene

Reply to
Rene

The most important thing is to understand enough about dsp so that one sees what one can do to make it unnecessary to have a well designed output filter (hint: with decent oversampling a single order filter can be more than sufficient) and why that is so.

And as far as it comes to PWM being in practice an imperfect system, You may come and listen to my cd-player, You won't hear any popping there. PWM is a very good way of da conversion and is used in relatively high-end stuff as well.

Rene

Reply to
Rene

To start, I have solved the popping issues using the sine look up table suggestion above. Turns out it's got nothing to do with the audio specific source code. I had a corrupted page at the start of each FLASH sector (4k). With an 8kHz sampling rate that produced a nice periodic pop at a rate of

2Hz. Feeling quite foolish on that...

Audio is now quite nice but I think it could still use some refining. There is a faint high pitch whine (which will hopefully disappear when I lower the cutoff point) and a bit of static throughout (although I could certainly live with it given the end use).

As Rene has pointed out my post lacks some important information (sorry) and so here it is:

are:

50 MHz (Micro = LMI PN:LM3S1439)

Clock driving the PWM module is scaled by 2

16 bit

4x currently

RAW file contains unsigned 8bit

8kHz

All samples have been taken on the PC via a mic or are converted from CD's. I am trying all different types (with respect to the frequency band) at the moment. In the end it will mainly be voice prompts. I am playing back the files on the PC (in RAW form) to create a benchmark of quality.

The most relevant piece of source code is below. It is the PWM int where everything happens. There are a few things you'll need to know to make sense of it. First, I have implemented two buffers for obvious reasons. When one buffer is emptied its service is scheduled (well within the bounds of the other exhausting). Next, g_ulSampleCount stores a count which compares against the oversampling multiplier. So, the PWM width is changed every AUDIO_PWM_FREQ_MULT times (currently 4). I have not yet made an attempt at smoothing (interpolating) between the current sample and the next. Trying to think of an elegant way to do that.

void audioInt(){ char cWidth; //Clear int PWMGenIntClear(PWM_BASE,PWM_GEN_0,PWM_INT_CNT_ZERO); //Is it time for a PWM update? (based on sampling rate of audio and PWM freq)

if(g_ulSampleCount++>=AUDIO_PWM_FREQ_MULT){ //Reset count g_ulSampleCount=1; //If both buffers exhausted, stop PWM if(0==audioBuffer_A.uiBufferCount + audioBuffer_B.uiBufferCount){

//Disable generator and shutdown amplifier ampPowerDown(); PWMGenDisable(PWM_BASE,PWM_GEN_0); PWMOutputState(PWM_BASE,PWM_OUT_0_BIT,false); } //If buffer A exhausted, switch to B else if(0==pBuffer->uiBufferCount && pBuffer==&audioBuffer_A){ //Point to next buffer pBuffer=&audioBuffer_B; //Schedule buffer service deltaqInsert(audioBuffer,AUDIO_BUF_SVC_INTERVAL); } //If buffer B exhausted, switch to A else if(0==pBuffer->uiBufferCount && pBuffer==&audioBuffer_B){ //Point to next buffer pBuffer=&audioBuffer_A;

//Schedule buffer service deltaqInsert(audioBuffer,AUDIO_BUF_SVC_INTERVAL); } //Scale width cWidth=*(pBuffer->pcBuffer++); PWMPulseWidthSet(PWM_BASE,PWM_OUT_0,cWidth*PWMGenPeriodGet(PWM_BASE,PWM_GEN_0)/256); pBuffer->uiBufferCount--; } }

Considering all of this... any suggestions for improvement? Anything stand out? Can anyone elaborate on preemphasis and how to apply it in my case?

Thanks, Jason

Reply to
eeboy

You may have to post (on a website, and give the URL here, don't post attachements to Usenet text groups) a recording of this so we can hear and analyze it. Your description of "poppiness" appears to be quite different from what I and apparently the rest of us interpret the word to mean. I think of pops as in LP playback where random ticks and pops are caused by dust and scratches. Looking at further posts by the OP, it appears his pops were indeed caused by interruptions of the PWM waveform due to a software bug.

I did 9600Hz PWM going into an analog filter (a twin-T notch at

9600 and two or three poles of low-pass around 3k) to generate DTMF and 300 baud modem tones, and it worked just fine, the signals met specs and all. With PWM you really need a "strong" filter, as the level of the PWM rectangular wave fundamental is as high as any of the desired signals. And yes, with sufficient digital horsepower (which I didn't have in my POTS-powered design from over a decade ago) you CAN upsample, use a higher PWM frequency and a less aggressive filter on the output which will more strongly attentuate the much higher frequency PWM fundamental.
Reply to
Ben Bradley

Reply to
Jim Stewart

Don't ;-)!

Do You hear this static as well as You play the sinus or maybe just 0 all the time? Do You hear it as well when You connect the input of Your output amplifier to ground? Just trying to check where this static originates.

So:

Clock speed -> 50 MHz Timer speed -> 50 / 2 = 25 MHz Timer resolution -> 16 bit

What is important in using a timer for pwm is the interrupt/reset value. For an 8 bit sample resolution, this should be 0xFF. That is the pwm resolution we are talking about. In this case the timer resolution may be 16 bit, the pwm resolution is 8 bit. This means it counts from 0 to FF, generates an int and resets itself to 0 to start counting again. Somewhere during counting the pwm pin level is toggled, depending on how You configured it. This cycle takes 256 clock ticks.

Should You have chosen phase correct pwm, the timer will count down after having reached the top and generate the int when it is at 0 again (toggling the pin once when counting up and toggling it again when counting down). That way every cycly takes 512 clock ticks.

But now some math with these numbers. The clock tick for the timer comes

25.000.000 times per second. If You are using an 8 bit pwm resolution (which can be in a 16 bit timer, no problem there, just configure the TOP value to be 0xFF), one cycle will take 256 ticks. So that means You can have almost 100.000 cycles per second. You need at least 8000 cycles per second for the sampling frequency, that leaves You with a maximum of 12x oversampling (and 6x times when using phase correct pwm). Therefore, the numbers 32 and 64 You mentioned, are not possible and will cause strange things to happen. Which fits in with Your remarks about the sound getting better when decreasing number of oversampling.

I asked You the wrong question, I asked the timer resolution but I meant the pwm resolution, the bit width of this TOP value I mentioned. If You are actually outputting at 16 bit pwm, it will give a very low output volume. In fact so low that I presume You are actually using the correct pwm resolution (and this is off course just as good in a 16 bit timer as it is in an 8 bit timer).

If You are using 12x oversampling You need not worry about Your output filter (with Your 4x I can imagine the high pitched sound may be caused by it, just increase the oversampling and see what happens), the thing You have now with the resistor You had lying around is more than sufficient.

I am however still curious whether all these assumptions are actually true. I would be a good idea to put a 1 on some I/O pin when You enter the ISR and turn it of the next time You enter it. That way You can use Your scope to see what output sampling frequency is actually being used and whether it is truly regular. When You hear a high pitched sound, it might be 8 Khz, and in that case I suspect the oversampling not be taking actually taking place, because then the sound You want to have filtered out at the output, the tone that is added because of the mere fact that You are playing back discrete samples, would have to be a multitude of this frequency being more difficult to hear for Your ears, not in the least because 16 kHz (I am not even going to mention 24 and up) will be filtered out quite efficiently.

You can also download spectrum analyzers on the web, just google for them, that way You might easily see what frequency this high pitched tone has. I mean software analyzers, You connect the output of Your digital sound processing system (sound impressive! ;-)) to the input of the soundcard, can be quite handy sometimes!

I apologize for underestimating Your knowledge previously, it was the way You put Your initial question, a little bit chaotic (no offense meant!), that made me suspect this, however, this followup is showing quite the opposite. And the fact that Your flash was broken, well, who would have thought of that. When experimenting with some technique, usually one suspects one's own experiments. Allthough... Very often I blame the compiler for generating errors in my programs! The times I actually managed to pin the error down on an actual compiler error (always optimizations that work "too well") are quite rare however, usually it turns out to be my own mistake after all.

Actually I wanted to see how You configured the timer but after having read the above and knowing that the sound is OK now AND knowing now that I don't have any experience with the micro You are using, it would not have added much after all.

It is the PWM int where

It is not that obvious to me. Why use a buffer when the audio You want to play is sitting around in memory? Or do You load them from something or so? I am not saying the buffers are not necessary, I just don't understand why they are necessary and am curious. If I want a micro to play samples I just insert them in my source as tables and then just retrieve the samples from this table at runtime.

PWMPulseWidthSet(PWM_BASE,PWM_OUT_0,cWidth*PWMGenPeriodGet(PWM_BASE,PWM_GEN_0)/256);

Your indentation is not perfect when it comes to if-else, I really think this is important because it tells one much about the program without having read a single word.

You're in the position that You can do quite some oversampling. That way the cut-off frequency from Your output filter can be quite high (like it is because You did not have the correct part at hand) and the influence of the filter at the top of the spectrum You are using will be small. I don't think preemphasis will pay off in this case and I would not bother.

I wish You good luck and fun with Your project, it looks both interesting and promising. Let us know how things progress, I for one am very interested.

Yours sincerely, Rene

Reply to
Rene

And I should add that it toggles again when it is 0 or TOP, this also depends on how You configured it. So it toggles twich in every cycle, that is important, the way I described it was incomplete, sorry.

Rene

Reply to
Rene

Check if the static is quantize noise. You have only +/- 7 bits on your PWM output data, but if you are oversampling, you could extend the effective number of bits, by LSB dither. ie you use a Rate-Multiplier algorithm, and weight the LSB in each sample, by the 'fraction' value A 8x over sample, would give 3 more bits, and you would start with 11 bit Audio.

-jg

Reply to
-jg

Thanks to all for the help so far!

I created a RAW file filled with 0's and played it back. There really is nothing audible when doing so which tells me that the source of the noise is in the signal processing or the file its self. I played back a voice prompt on the PC side and found that it does have the static I speak of as well... it's just not as noticeable on the PC. I have to turn the volume WAY up to hear it. So, when it's reproduced on my device it just seems as if the static is more prevalent.

I chose my PWM to align left... it counts down then reloads. Is there any reason why I should consider otherwise?

Strange things do in fact happen at 32...

I tried adjusting the oversampling up (4->6->8->12) and I get no noticeable improvement. Would this not have more of an effect if I was interpolating between samples?

I have viewed the PWM on my logic analyzer (sampling at 8MHz) and I can confirm that the stream is regular in that the periods are consistent at

32kHz. I'll find a GPIO to toggle and further confirm it's regular.

I googled this and found nothing free. :) I browsed around in the Linux forums and found that Audacity can do this... just need to make a cable and hope Audacity plays well with my sound card.

No need to apologize. I realize I left quite a bit of information out and may not have articulated my question to the best of my ability. This is one of those problems that I hesitate posting in the first place because the scope is so broad. As you see the problem turned out to be nothing related to audio output. However, I am learning a great deal from the discussion.

Sorry! One more piece of information that should have been provided in the beginning. I am pulling from a serial FLASH IC. I double buffered because I didn't want to hold the SPI bus while outputting audio. So, I created two

1kB buffers that I could pull data into... then I only have to occupy the SPI bus every 1/8th of a second. I can freely communicate with other peripherals when not fetching.

That's quite likely... I am a bit of a hack with respect to programming. Always looking for suggestions to make it more efficient and easier to read. Can you give me an example of what a properly indented if-else should look like?

Thanks again, Jason

Reply to
eeboy

At one point I also did a number of experiments with PWM output and various filtering techniques. I found that with low resolution and low sample rates, you really do need a strong filter. In the end I settled on 8-bit samples with a sampling rate of 15.625 kHz and the PWM output at 31.250 kHz with linear interpolation. For the filter, a 4th order lowpass at 7.2 kHz gave the best results based on a number of subjective listening tests. With this configuration I had no noticeable pops, whistles, or aliasing and the only artefact was a faint background hiss that could be heard during the quiet parts of the audio.

Reply to
Tom

... snip ...

It looked fine to me, except you were using excessive indentation (i.e. tabs in place of 3 or 4 spaces) and the commentary could be clearer. For C:

if (whatever) { do_nonsense1(); do_morenonsense1(); } else if (something2) { do_nonsense2(); do_morenonsense2(); } else /* anything else */ { do_nonsense3(); do_morenonsense3(); }

Also please don't snip attribution lines for material you quote. Those are the "joe wrote:" header lines.

--
 [mail]: Chuck F (cbfalconer at maineline dot net) 
 [page]: 
 Click to see the full signature
Reply to
CBFalconer

You are welcome, programming embedded stuff is great fun and audio is maybe the best part of it!

I read that You make the voice samples with Your microphone, this gives a very weak signal which is off course very sensitive to interference from the outside. Maybe that is the source of it? Does it happen when You use a sample that is e.g. taken from a CD?

No, there isn't, the way I told it is what I usually do, there are many ways to configure the timers for this purpose.

I think there You are going to miss interrupts. Well no, I am quite sure of it ;-). Is not usefull anyway, but it confirms the "math" ;-).

Yes, I do think it will. Just try!

I think it will be regular. I am quite sure it is OK now, I do not expect irregularities anymore (the way You describe the sound quality). It could be interesting, just as an experiment, to look at it while You increase the number of oversampling.

That is interesting, did not know that! It is quite a while ago, I know there were freeware versions, maybe the makers found them so succesfull that they started asking money for them. Next time I need one, I'll take audacity.

I know exactly what You mean, the questions that are left over when one really has done all one could think of, often require to type in such a huge amount of info about everything that has already come up during one's own searches...

I find it very interesting as well.

Ah I see, in that case it is indeed a very good choice to do it this way.

There are off course different tastes when it comes to that and off course everyone finds his/her own style the best, but to be honest, usually that is off course not true. Only the way I do it is the best (just kidding!). What I would do with Your example is the following:

begin //If both buffers exhausted, stop PWM if(0==audioBuffer_A.uiBufferCount + audioBuffer_B.uiBufferCount) { //Disable generator and shutdown amplifier ampPowerDown(); PWMGenDisable(PWM_BASE,PWM_GEN_0); PWMOutputState(PWM_BASE,PWM_OUT_0_BIT,false); } //If buffer A exhausted, switch to B else if(0==pBuffer->uiBufferCount && pBuffer==&audioBuffer_A) { //Point to next buffer pBuffer=&audioBuffer_B; //Schedule buffer service deltaqInsert(audioBuffer,AUDIO_BUF_SVC_INTERVAL); } //If buffer B exhausted, switch to A else if(0==pBuffer->uiBufferCount && pBuffer==&audioBuffer_B) { //Point to next buffer pBuffer=&audioBuffer_A; //Schedule buffer service deltaqInsert(audioBuffer,AUDIO_BUF_SVC_INTERVAL); }

See how every if-else has it's own indentation? That way it is much easier to see which parts belong together.

Good luck with Your project! Sincerely, Rene

Reply to
Rene

I got my new scope and was able to take a snapshot of the audio output and perform FFT on it.

formatting link

There is the obvious spike at 32kHz (8kHz * 4 oversample). There's also another spike at 29kHz which I don't quite know how to explain. To my untrained eye it looks as if I could use a filter with a bit steeper roll off to attenuate the components beyond 8kHz-9kHz (8kHz to 17kHz looks like it could use some more attenuation). Any thoughts from this image?

Reply to
eeboy

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.