12 Bit PWM Sound Output and Math all in one Thread

Technical questions regarding the XTC tools and programming with XMOS.
User avatar
williamk
Experienced Member
Posts: 114
Joined: Fri Oct 01, 2010 7:47 pm

Post by williamk »

Well, in any event, this code seems to work better, but still have flaws, as the sound has a weird random-noise to it. (very loud noise actually) The code does an up and down pitch envelope to a Sawtooth waveform, 6 times, then it stops. To adjust the speed just change "soundRateCounter > 20" to something like "soundRateCounter > 200" to make it slower, then you can hear the noise very easily.

Code: Select all

#include <xs1.h>
#include <platform.h>
out buffered port:1 speaker = PORT_SPEAKER;

int main (void)
{
	unsigned short count;
	unsigned short soundOut = 0;
	unsigned int soundRate = 0;
	unsigned int soundRateCounter = 0;
	short goingUp = 1;
	short pitchEnvTimes = 0;

	speaker <: 0 @ count;  					// Find out the current port time

	while (1) // 12 bits = 0 to 4000 * 25 Khz = 100 Mhz
	{
		speaker @ count <: 1;
		count += soundOut;
		speaker @ count <: 0;
		count += 4000 - soundOut;

		// Calculate the next soundOutTime //
		soundOut += soundRate;
		if (soundOut > 4000) soundOut -= 4000;

		soundRateCounter++;
		if (soundRateCounter > 20)
		{
			soundRateCounter = 0;
			if (goingUp) soundRate++; else soundRate--;
			if (soundRate > 400 || soundRate < 0)
			{
				goingUp = !goingUp;
				pitchEnvTimes++;
				if (pitchEnvTimes > 6) break;
			}
		}
	}

	return 0;
}


Wusik Dot Com (http://www.Wusik.com)
William-K.com (http://www.William-K.com)
User avatar
williamk
Experienced Member
Posts: 114
Joined: Fri Oct 01, 2010 7:47 pm

Post by williamk »

So, here's a picture of what happens, it likes something is not timing correctly somehow. :-( You can see the Sawtooth then a big gap which produces a loud noise = the problem.

Image

Here's the code used for this test:

Code: Select all

#include <xs1.h>
#include <platform.h>
out buffered port:1 speaker = PORT_SPEAKER;

int main (void)
{
	unsigned short count;
	unsigned short soundOut = 0;
	unsigned int soundRate = 0;
	unsigned int soundRateCounter = 0;
	short goingUp = 1;
	short pitchEnvTimes = 0;

	speaker <: 0 @ count;  					// Find out the current port time

	while (1) // 12 bits = 0 to 4000 * 25 Khz = 100 Mhz
	{
		speaker @ count <: 1;
		count += soundOut; speaker @ count <: 0;
		count += 4000 - soundOut;

		// Calculate the next soundOutTime //
		soundOut += soundRate;
		if (soundOut > 4000) soundOut -= 4000;

		soundRateCounter++;
		if (soundRateCounter > 100)
		{
			soundRateCounter = 0;
			if (goingUp) soundRate++; else soundRate--;
			if (soundRate > 400 || soundRate < 0)
			{
				goingUp = !goingUp;
				pitchEnvTimes++;
				if (pitchEnvTimes > 1) break;
			}
		}
	}

	return 0;
}
Wusik Dot Com (http://www.Wusik.com)
William-K.com (http://www.William-K.com)
User avatar
williamk
Experienced Member
Posts: 114
Joined: Fri Oct 01, 2010 7:47 pm

Post by williamk »

I also tested with just a slow and steady pitch envelope, just to remove the idea that the envelope could be the problem. I get a steady good quality tone, then a bad tone, then a good tone, then a bad tone, when going from current rate to rate += 2.

Here's an audio example:

http://www.wusik.com/temp/PWM_Problem.wav
Wusik Dot Com (http://www.Wusik.com)
William-K.com (http://www.William-K.com)
User avatar
williamk
Experienced Member
Posts: 114
Joined: Fri Oct 01, 2010 7:47 pm

Post by williamk »

Hummm, now, thinking of the whole thing again, I'm pretty sure I'm doing this all wrong... :oops:

I will return to this some other time, once I figured out the whole PWM math to do this 12 bit audio output.

I will also check the SID PWM code, but that's only 8 bits.

Wk
Wusik Dot Com (http://www.Wusik.com)
William-K.com (http://www.William-K.com)
User avatar
lilltroll
XCore Expert
Posts: 956
Joined: Fri Dec 11, 2009 3:53 am
Location: Sweden, Eskilstuna

Post by lilltroll »

If it is classic PWM you are trying, you cannot use

REMOVED AN INCORRECT STATEMENT

When you have succeed in that you might realize that PWM is very stupid unless you want to create a constant DC value out.
For an example, using 12 bit PWM creating 50% of maximum amplitude would look like.

000..(2^11 of zeros)..000111..(2^11 of ones)..111, but the series 101010101010 would create the same mean value.

There is a much more effective way to use the bandwith called Pulse Density Modulation, which is the fundamental in modern DACs. Instead of writing data at 100 MHz to produce a signal with 25 kHz bandwith, you might use 384 kHz to create a fs of 48 kHz.

Image
Above is not PWM, it is SDM Sigma Delta Modulation that uses very few zeros and ones to represent the sine-wave.
Start with understanding Delta-Sigma
http://en.wikipedia.org/wiki/Delta-sigma_modulation

You might also check out this, where I have a low bitrate and do not want a large buffer on the port.
We need to calculate the accumulated error and adjust next bit "on the fly"
https://www.xcore.com/forum/viewtopic.php?p=5867#p5867
Probably not the most confused programmer anymore on the XCORE forum.
User avatar
williamk
Experienced Member
Posts: 114
Joined: Fri Oct 01, 2010 7:47 pm

Post by williamk »

Thanks for all that input, but that doesn't help me at all, sorry.

I know all that, but I need to fix the code so it works, simple as that. ;-)

The other methods are just too complicated for such a silly thing.

I did some great PWM audio on the BeatVox project with a small AVR, so I'm sure I can come up with something usable on the XMOS chip...

I don't want to complicate things, I want to simplify.

I know that Port timers are 16 bits, but I was hoping the overflow would work itself out, at least on the AVR side I never had to worry about that. If overflow happens, the numbers adjust itself up, unless I'm seeing things. In any way, it just works, and I don't have to worry about much stuff.

Wk
Wusik Dot Com (http://www.Wusik.com)
William-K.com (http://www.William-K.com)
User avatar
lilltroll
XCore Expert
Posts: 956
Joined: Fri Dec 11, 2009 3:53 am
Location: Sweden, Eskilstuna

Post by lilltroll »

I believe that you have timing issues in all your code with your unbuffered ports.

Take a look at this part for an example.

Code: Select all

      if (soundOut > 0)
      {
         speaker <: 1 @ count;                 // Find out the current port time
         count += soundOut;
         speaker @ count <: 0;
      }
This part works when soundOut is 100 for an example, but is probably starved out of time when soundOut==1. Instead it will wrap around and make a match about 2^16-n cycles later.

If the counter is running @ 100 MHz you must take the instruction time into account.

Using the other timer with
t when timerafter(time) :> time;
also needs time to complete.

An easy way around this to start with is to not use the whole amplitude span from 0-100%
Use the span [1% 99%] of the amplitude.
This way you will always have at least 40 ones or zeros in the beginning and in the end of each PWM cycle with a 12 bit PWM.
Probably not the most confused programmer anymore on the XCORE forum.
mgarbett
Newbie
Posts: 1
Joined: Wed Sep 28, 2011 10:34 pm

Post by mgarbett »

Hi I'm a newbie to XMOS so this might not be so helpful.
I was wondering if you could generate Pulse Density Mod (PDM) using a 32 bit buffered S-R
end-point running at 100Mhz.
Referring to the XS1-Architecture doc [Page35] "Buffered Transfer" the Shift Reg. appears to be
buffered by a "Transfer Register" that reloads the shift register when empty. This gives you ~3M
4-bit + Sign samples/Sec at 100M shift rate and no dead time between samples. Residual error
left over from each 5-bit sample is carried over to the next sample so error averages to zero.
The 32 Bit patterns can be predefined and accessed using a look-up table. Downside is you may
need to use interrupts to keep the Transfer Register Filled.
I hope this was useful.
Matt
Post Reply