Stopping pwm clean

I went ahead and implemented the PWM functionality using the Register class from C# as a couple of you had suggested and it totally addresses the PWM glitch issue! It did take quite a while to get all of the register calls set up just right (8-9 hours) but in the end it’s not too bad. In addition to the user manual, I found that the sample code bundle from NXP ([url]http://ics.nxp.com/support/documents/microcontrollers/zip/code.bundle.lpc23xx.lpc24xx.uvision.zip[/url]) was really useful.

Here is my test code so far. If I have time after getting it cleaned up and generalized I’ll post that as well.


using System;
using System.Threading;

using GHIElectronics.NETMF.Hardware.LowLevel;

// This class demonstrates using direct access to the registers to implement PWM on the Fez Panda.  The code
// ramps PWM2 from 0% duty cycle up to 100% duty cycle and then back down to 0% over the course of about 2 seconds.
public class RegisterPWMDemo
{
	public static void Main()
	{

		// Create register objects for the registers we need to manipulate to implement the PWM.
		Register PCONP =    new Register(0xE01FC0C4); // Power Control for Peripherals is needed to make sure power is supplied to the PWM module.
		Register PCLKSEL0 = new Register(0xE01FC1A8); // Peripheral Clock Selection register 0 is needed to specify the input clock rate to the PWM module.
		Register PINSEL3 =  new Register(0xE002C00C); // Pin function select register 3 is needed to connect the PWM2 to arduino pin 9.

		Register PWM1TCR =  new Register(0xE0018004); // Timer Control Register is needed to reset the timers and enable the timers and PWM mode.
		Register PWM1PR =   new Register(0xE001800C); // Pre-Scale Register could be used to slow down the PWM clock though we leave it at zero here.
		Register PWM1MCR =  new Register(0xE0018014); // Match Control Register is used to specify that the PWM repeats instead of being a one-shot.
		Register PWM1MR0 =  new Register(0xE0018018); // Match Register 0 is used to specify the total waveform duration.
		Register PWM1MR2 =  new Register(0xE0018020); // Match Register 2 is used to specify the duration of the PWM high output.
		Register PWM1PCR =  new Register(0xE001804C); // PWM Control Register enables each of the PWM lines and sets them to be single edge PWMs.
		Register PWM1LER =  new Register(0xE0018050); // Latch Enable Register bits must be enabled every time after the match values are updated.
		Register PWM1CTCR = new Register(0xE0018070); // Count Control Register is used to select timer mode (instead of triggering off some other input).


		PCONP.SetBits(1 << 6); // Turn on power to the PWM module.
		PCLKSEL0.SetBits(1 << 12); // Set the PWM module to use the clock at its original frequency.
		PINSEL3.ClearBits(1 << 8); // Set output pin 20 to act as a PWM (Pin 9 on the arduino shield is P1.20 on the arm processor (i.e. pin 20 on port one))
		PINSEL3.SetBits(1 << 9);

		const uint WAVEFORM_DURATION = 1000000; // Duration in clock counts of the waveform duration (high plus low).  All PWM's will use this value.

		PWM1TCR.Write(0x00000002); // Reset the counters.
		PWM1PR.Write(0x00000000); // Set the counter pre-scale to zero so that the PWM runs at the full clock frequency.
		PWM1MCR.Write(0x00000002); // Specify that the counter should reset when it equals MR0 (instead of stopping or triggering an interrupt).
		PWM1MR0.Write(WAVEFORM_DURATION); // Set the waveform duration.  This MUST be done before enabling PWM mode in the TCR.
		PWM1MR2.Write(0); // Initialize the duration of the high output to zero so the output is 100% low until it is set differently later on.
		PWM1LER.Write(0x00000005); // Enable the PWM latches for MR0 and MR2 so the total and high duration values get copied into the match registers.
		PWM1PCR.Write(0x00000400); // Set all of the PWM channels to be single edge mode and only enable the output for PWM 2.
		PWM1TCR.Write(0x00000009); // Enable the counter and the PWM feature.

		// Loop continuously, ramping the PWM2 up and down between 0% and 100% duty cycle
		while (true)
		{
			for (uint i = 0; i < 100; ++i) // Ramp up the PWM by changing i (the percent high duty cycle).
			{
				PWM1MR2.Write(i * (WAVEFORM_DURATION/100)); // Set the MR2 to be a value between zero and the totally waveform duration.
				PWM1LER.SetBits(1 << 2); // Make sure to set the latch enable register so the new data will be copied into the match registers when the cycle completes.
				Thread.Sleep(10); // Sleep for 100th of a second.
			}
			for (uint i = 100; i > 0; --i) // Now ramp the PWM back down to zero.
			{
				PWM1MR2.Write(i * (WAVEFORM_DURATION/100));
				PWM1LER.SetBits(1 << 2);
				Thread.Sleep(10);
			}
		}
	}
}

Thanks for everyone’s help,
David

Nice job.
Did you try just setting the PWM1TCR register as mentioned in another post?
Then you would only need one line instead of a complete procedure.

[quote]In short: PWM0TCR must be set to 0x09 instead of 0x01 (which the GHI firmware does now), bit3 must be set to enable the shadow register stuff. (See page 640 of the NXP LPC24xx datasheet)
[/quote]

If I remember right, the TCR register is overwritten by each call to SetPulse. But you can try what it does to set the TCR before or after the SetPulse. One of them might work.

I thought about trying to augment the built-in SetPulse() functionality with additional register calls but didn’t actually try it. In addition to setting the TCR I think you’d also need to set the LER after every SetPulse() call.

So, I see that you guys are still trying to solve this problem, because GHI seems not to care that much.

You can do it without RLP with a small hack.


public class PwmCleaner
    {
        const uint PWM0_BASE_ADDR = 0xE0014000;
        const uint PWM0TCR = PWM0_BASE_ADDR + 0x04;
        const uint PWM0LER = PWM0_BASE_ADDR + 0x50;
        const uint PWM1_BASE_ADDR = 0xE0018000;
        const uint PWM1TCR = PWM1_BASE_ADDR + 0x04;
        const uint PWM1LER = PWM1_BASE_ADDR + 0x50;

        Register pwm0tcr = new Register(PWM0TCR);
        Register pwm0ler = new Register(PWM0LER);
        Register pwm1tcr = new Register(PWM1TCR);
        Register pwm1ler = new Register(PWM1LER);

        public void SetPulse(PWM pwm, uint period_nanosecond, uint highTime_nanosecond)
        {
            pwm0tcr.Write(0x09);
            pwm1tcr.Write(0x09);
            pwm.SetPulse(period_nanosecond, highTime_nanosecond);
            pwm0ler.Write(0x7F);
            pwm1ler.Write(0x7F);
        }
    }

Usage:


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

cleaner.SetPulse(pwm, 20000000, 5000000); // last two parameters are same as pwm.SetPulse parameters

Note that I’m writing the LER bits for all channels. You could figure out which LER bit equals the PWM.Pin.PWMx enum and enable only the correct LER bit for the correct channel. You will have to do this when you use different PWM channels from different threads.

@ Mike:

I rather think this is a annoying problem. Let’s say you want to fade a backlight or a RGB colour led? You don’t want it to flash now and then, this is unacceptable.

Can anyone give me a map of all the PWM enum values in the GHI library, and how they relate to the hardware pwm modulator / channel? Then it would be possible to write a clean C# class for that.

What enums? There isn’t much you need from GHI. PWM is very simple, especially to you, who have done some nice work already.

Make your own class with your own enum and configure it anyway you like.

Yeah, I was working on tracking down which PWM channels get connected to which output pins on the Fez Panda last night but then I got busy with other things. I was looking at the circuit diagram to figure out which pin from the processor is connected to which pin on the arduino shield. Not sure if there’s an easier way. Resulting in something like:


		// Set PWM1 as an output on port P1.18 (Pin 10 on the arduino shield).
		m_PINSEL3.ClearBits(1 << 4); 
		m_PINSEL3.SetBits(1 << 5);

		// Set PWM2 as an output on port P1.20 (Pin 9 on the arduino shield).
		m_PINSEL3.ClearBits(1 << 8);
		m_PINSEL3.SetBits(1 << 9);

		// Set PWM3 as an output on port P1.21 (Pin 8 on the arduino shield).
		m_PINSEL3.ClearBits(1 << 10);
		m_PINSEL3.SetBits(1 << 11);

[quote]Make your own class with your own enum and configure it anyway you like.
[/quote]

And so I did :slight_smile:

Find the code here: http://code.tinyclr.com/project/348/pwm-helper-class-with-clean-transitions/

I did not have a single doubt about your capabilities. Very nice job.

Just as an FYI to anyone else having PWM glitches, I ran into another unrelated glitch which happens even when using the shadow registers. It sounds like this is a known hardware bug with the NXP LPC23XX ([url]http://mbed.org/forum/mbed/post/1672/[/url]) but apparently you can get a glitch on PWM channel 1 by setting MR1 = MR0 and then by setting it to a slightly smaller value. Apparently with the current NXP hardware you should never set MR1 == MR0.

I found this because I was ramping LEDs up and down in brightness using the shadow register-based PWM but was still seeing a glitch on PWM channel 1, though not any of the other channels. Setting MR1 = MR0 + 1 works fine as a substitute when the PWM needs to be at 100% duty cycle.