Stopping pwm clean

Is this your feature request William?

[url]http://netmf.codeplex.com/workitem/787[/url]

yes

I haven’t digested the arm7 hardware PWM but, I think this may be a problem. Also looked at the implementation of some old servo chips based on pic 16’s. They were written in assembler and with a 20 ms refresh rate there is plenty of time to change the next pulse after the previous pulse. I have a pololu usb modern version of these chips and with out source code I can’t be sure but they do not seem to have this problem. May be this is a case were a coproc is a solution. Let the fez do the high level and a little coproc handle this low level task. Of course with master-slave distributed computing latency can be an issue.

In thinking about this more I suspect it is an unavoidable artifact of the servo itself. If the servo is active low and is triggered on the high->low edge than it will look at the low period following the state change as its position. Therefore it does not matter if you force the servo low or you keep it low after a ‘normal’ state change the servo will see the exact same thing.

Perhaps the trick is to force it high? But then you have the same problem unless the FEZ keeps the pin high after you dispose of the PWM object. The only way to truly avoid the problem is to switch power to the servo only after the PWM is in effect and remove power before the PWM is disposed.

I wonder if William ever caught this issue on an oscilloscope.

I’m sure that setting the duty to 0% will always be in sync! I have the datasheet of the NXP LPC24XX in front of me, and they clearly state: (page 635)

[quote]A synchronizing register (shadow register) is added to each match register to allow
changes to take effect only when requested by software, and only at the transition
between PWM cycles.[/quote]

Of course you need to call Thread.Sleep between setting the duty to 0% and calling the Dispose method.

So, something else is going on as what Jeff_Birt suggests.

  1. It happens regardless of what overload your using Set(false), Set(0,0), etc. Setting the wave to low at the wrong time chops the high wave down before it goes low itself. So they are not syncd based on observation. I have ordered a Saleae Logic unit and will post better data when I get it.

  2. IMO, servos is not the issue. I see same on two different servos. Servos happen to show the issue visually. But it would be happening to other devices or data trans.

  3. Remember, this is just not a stop issue. It can happen anytime you change commands. I have seen a servo go backward a few clicks before going forward because of this issue.

You folks with the scopes should be able to find same thing. Its about 1 out of 1000 chance to hit a high spot wrong. On computer system, that is many times a day.

I will test it on my cobra. What frequency are you using?

Note with Set(0, 0), you also change the frequency. Can you try not to? Keep the frq constant, only change duty.

int delay = 1000;
ss.SetDegree(0);
Debug.Print("Set 0");
Thread.Sleep(delay);

ss.SetDegree(90);
Debug.Print("Set 90");
Thread.Sleep(delay);

ss.SetDegree(180);
Debug.Print("Set 180");
Thread.Sleep(delay);

Debug.Print("Set 90");
ss.SetDegree(90);
Thread.Sleep(890); //889 move. 890 for 20ms freq. 1000 no move.

ss.pwm.SetPulse(20*1000*1000, 0); //freq 20ms, duty 0 - same error.
//ss.pwm.Set(false); // Not work. Same effect as SetPulse(freq, 0).
Thread.Sleep(2000);
ss.Dispose();

Keeping same freq with SetPulse(freq, 0) exhibits same High-Chop behavior.

@ William: good news, you are correct
@ GHI: bad news, there is a bug in the firmware :slight_smile:

So, I did measure PWM signal on the oscilloscope and there is a problem with the SetPulse method. The problem even occurs more then often.

Here is the test code:


PWM pwm = new PWM(PWM.Pin.PWM2);

for (int i = 0; i < 600; i++)
{
    pwm.SetPulse(20 * 1000 * 1000, (uint)((5 + (i % 10)) * 1000 * 1000));
    Thread.Sleep(100);
}
                
// Sleep for 100 milliseconds
Thread.Sleep(100);

pwm.Dispose();

In short: the code sets a fixed PWM signal with a period of 20ms on PWM2. The duty cycle ramps up from 5ms to 14ms (increasing with 1ms each 100ms). The code runs for 1 minute.

Note that I didn’t even had to look for a problem at the dispose, the problem already occurs more then often while changing the duty with the SetPulse method. I have attached an image where you can see a high pulse for longer then 20ms (the pulse is 25ms), which should never happen.

So, I think the SetPulse method always resets the PWM and does not only change the duty. Since the latter should be in sync as noted in the datasheet. A simple solution may be to create seperate methods, SetPeriod and SetDuty. Or the SetPulse method can only update the frequency when it needs to change, and only enable the PWM when it was not enabled.

I’ll see what happens when I change the duty cycle through RLP.

As I recall there is a difference in how SetPulse and Set work. AFAIK one of them is synchronized and the other is not.

Sorry, it isn’t a GHI firmware bug either. It seems to be a bug in the CPU…

I’ve created a SetPulse function in RLP, and ran the same test code as in my previous post, but then, with the RLP function. In the attached image you’ll see a totally wrong pulse of 31ms instead of 20ms.
Such wrong pulses occur once each one or two seconds.

RLP code:


 #define SUCCESS			0
 #define ERR_ARG_COUNT		-1
 #define ERR_UNKNOWN_CHANNEL	-2
 #define ERR_UNKNOWN_INDEX	-3

int SetPulse(unsigned int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
	if (argsCount != 4)
		return ERR_ARG_COUNT;

	BYTE latch = 0;
	DWORD channel = *(DWORD *)args[0];
	DWORD index = *(DWORD *)args[1];
	DWORD period = *(DWORD *)args[2];
	DWORD duty = *(DWORD *)args[3];	

	if (channel > 1)
		return ERR_UNKNOWN_CHANNEL;

	if (index > 5)
		return ERR_UNKNOWN_INDEX;

	if (duty > period)
		duty = period;

	// Adjust for 72 Mhz / 4 clock
	period *= 9;
	period /= 500;	
	duty *= 9;
	duty /= 500;

	if (channel == 0)
	{
		if (PWM0MR0 != period)
		{
			PWM0MR0 = period;
			latch |= LER0_EN;
		}

		switch (index)
		{
		case 0:
			PWM0MR1 = duty;
			latch |= LER1_EN;
			break;
		case 1:
			PWM0MR2 = duty;
			latch |= LER2_EN;
			break;
		case 2:
			PWM0MR3 = duty;
			latch |= LER3_EN;
			break;
		case 3:
			PWM0MR4 = duty;
			latch |= LER4_EN;
			break;
		case 4:
			PWM0MR5 = duty;
			latch |= LER5_EN;
			break;
		case 5:
			PWM0MR6 = duty;
			latch |= LER6_EN;
			break;	
		}
		
		PWM0LER = latch;
	}
	else if (channel == 1)
	{
		if (PWM1MR0 != period)
		{
			PWM1MR0 = period;
			latch |= LER0_EN;
		}
		
		switch (index)
		{
		case 0:
			PWM1MR1 = duty;
			latch |= LER1_EN;
			break;
		case 1:
			PWM1MR2 = duty;
			latch |= LER2_EN;
			break;
		case 2:
			PWM1MR3 = duty;
			latch |= LER3_EN;
			break;
		case 3:
			PWM1MR4 = duty;
			latch |= LER4_EN;
			break;
		case 4:
			PWM1MR5 = duty;
			latch |= LER5_EN;
			break;
		case 5:
			PWM1MR6 = duty;
			latch |= LER6_EN;
			break;	
		}
				
		PWM1LER = latch;
	}

	return SUCCESS;
}

Call in managed code:


rlpSetPulse.Invoke(PWM_CHANNEL, PWM_PIN, (uint)(20 * 1000 * 1000), (uint)((5 + (i % 10)) * 1000 * 1000));

Where PWM_CHANNEL is 0 and PWM_PIN is 1 (seems to be PWM.Pin.PWM2 as defined by GHI)

I see you are becoming an RLP expert :slight_smile: love seeing that

Glad I am not crazy (or my servos) :slight_smile:
Nice work. Scope shows clearly what I expected. And the fact that it can/does happen each cmd is something of an issue. In this case, sw OutputCompare may have some advantages. Maybe ghi can comp us a cobra for finding and verifying this :wink:

@ Jeff “As I recall there is a difference in how SetPulse and Set work.”

I have said here a few times it makes no difference.

I remember Gus explaining that there is a difference for high frequency uses only (but that does not apply to servos driving :wink: )

http://tinyclr.com/forum/13/2415/#/1/

Or a bug in the docs. The pwm does not respect atomic changes per the docs?

Small adition, the erroneous pulse seems only to occur when my example goes from SetPulse(20000000, 14000000) back to SetPulse(20000000, 5000000).

So on overflow of the (i % 10).

That leaves two options open:

  1. it only occurs when decreasing the duty
  2. it only occurs when increasing/descreasing the duty with a large value.

To be tested… :slight_smile:

Here is what I think but I could completely wrong!

  1. the period is set to 1000 for example
  2. the high pulse is set to 500 for example (that is 50% duty)
  3. the timer is continuously running generating PWM from numbers above…signal goes low when counter is 500 then back high when it reaches 1000 which also resets the counter to 0
  4. if you now change 500 to 800 then you will not see a problem. if counter was at 450 when you changed the high pulse then it will keep going till it is at 800 then signal goes low
  5. if you now change 800 to 500 and current counter is below 500 then you will not see the problem either. Once the counter reaches 500 then the pulse will change as expected
    6.but if you now change the counter from 500 to 300 and the current counter is at 400 then what will happen? This is when the signal will be chopped at 400 as 400 is larger than 300. Now counter goes back to 0 and everything will work fine

@ Gus. But you have already launched the missles.
Love your optimism Gus :slight_smile:

But the CPU should prevent this kind of situations.

When you write a new duty value to the register, it isn’t updated directly. You need to set a ‘register changed flag’. Then the CPU will copy the new duty value on timeroverflow and clear the ‘register changed flag’ to ensure the changes are in sync with the PWM signal. This is what the doc says, but it seems that it is not how the CPU works.

Unless there is a configuration bit that is set to the wrong value.