PWM

Required

Tutorials: Digital Inputs

Assemblies: Microsoft.SPOT.Hardware.PWM

Introduction

In previous tutorials we have been able to blink an LED. Using PWM, we can fade an LED in and out. PWM control the level of energy transferred by switching a pin high and low very quickly where the ratio of ON to OFF state determines the level. For example: a 0.5 duty cycle means that the pin is ON or HIGH 50% of the time, so in effect half the amount of energy is outputted. The PWM feature has been implemented by Microsoft.SPOT.Hardware.PWM assembly. Only some pins on a processor are capable of hardware PWM. This means that the pin can be toggled rapidly without CPU or user inventino beyond starting the process. Use the pin enumeration that corresponds to your device to determine which pins are capable.

Software PWM

PWM pins are controlled internally by the processor using special PWM hardware.  The processor only needs to set some registers and then no processor interaction is required. The specialized hardware does the rest.

GHI's NETMF devices provide an SignalGenerator class. Through this class a user can generate nearly any signal, including PWM signals. This class emulates PWM in software just like I2C. This will eat up a lot of processor time however as it will be constantly toggling a pin on and off in a loop sleeping for only fractions of a second in between. This method is not always precise as the CPU may have to service other interrupts before returning to your PWM thread.

Example 1: Getting Started

This example starts an LED at 0% intensity then slowly steps up to full intensity, after which it reverts to 0% and starts over.

using System.Threading;
using Microsoft.SPOT.Hardware;

namespace change_this_to_your_namespace
{
   public class Program
   {
      static PWM MyFader = new PWM(Cpu.PWMChannel.PWM_3, 10000, 0.1, false);

      public static void Main()
      {
         double i = 0.0;
         while (true)
         {
            MyFader.DutyCycle = i;
            /* DutyCycle is not dynamic so we must make a call to
             * Start() to refresh the object */

            MyFader.Start();

            if ((i += 0.1) >= 1.0)
            {
               i = 0.0;
            }

            Thread.Sleep(10);
         }
      }
   }
}

Example 2: Smoother Transitions

This example fades an LED in like before, but it also fades the LED out once it is on and then repeats.

using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace change_this_to_your_namespace
{
   public class Program
   {
      static PWM MyFader = new PWM(Cpu.PWMChannel.PWM_3, 10000, 0.1, false);

      public static void Main()
      {
         double i = 0.0;
         double dirr = 0.1;
         while (true)
         {
            MyFader.DutyCycle = i;
            MyFader.Start();

            i = (double)(i + dirr);

            if (i >= 0.9)
               dirr = -0.1;
            if (i <= 0.1)
               dirr = 0.1;

            Debug.Print(i.ToString());

            Thread.Sleep(10);
         }
      }
   }
}

Example 3: Using a Potentiometer

This example sets the brightness of the LED with an analog potentiometer knob. The knob is hooked up to an analog input so a varying signal can be read.

AnalogInput POTknob = new AnalogInput(Cpu.AnalogChannel.ANALOG_7, 1.0, 0.0, 10); constructs the object for our Potentiometer and sets the scale to 1.0. This means that the output from POTknob.Read() will be between 0.0 and 1.0 to match the requirements of setting MyFader.DutyCycle

using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace change_this_to_your_namespace
{
   public class Program
   {
      public static void Main()
      {
         PWM MyFader = new PWM(Cpu.PWMChannel.PWM_3, 10000, 0.1, false);
         AnalogInput POTknob = new AnalogInput(Cpu.AnalogChannel.ANALOG_7, 1.0, 0.0, 10);
         double POTknobReadout = 0.0;

         while (true)
         {
            POTknobReadout = POTknob.Read();
            Debug.Print(POTknobReadout.ToString());

            MyFader.DutyCycle = POTknobReadout;
            MyFader.Start();

            Thread.Sleep(10);
         }
      }
   }
}

Example 4: Music

PWM is usually used to control the intensity of lights or speed of motors. This is done by changing the duty-cycle. Another way of using PWM is by keeping the duty-cycle at 50% then changing the frequency to generate some tone. Music notes have specific frequencies; C for example is about 261Hz. Plugging these numbers into an array and knowing the length of each tone is all we need to play some simple music.

Code Language: C#
using System.Threading;
using Microsoft.SPOT.Hardware;

namespace change_this_to_your_namespace
{
   public class Program
   {
      const int NOTE_C = 261;
      const int NOTE_D = 294;
      const int NOTE_E = 330;
      const int NOTE_F = 349;
      const int NOTE_G = 392;

      const int WHOLE_DURATION = 1000;
      const int EIGHTH = WHOLE_DURATION / 8;
      const int QUARTER = WHOLE_DURATION / 4;
      const int QUARTERDOT = WHOLE_DURATION / 3;
      const int HALF = WHOLE_DURATION / 2;
      const int WHOLE = WHOLE_DURATION;

      //make sure the two below arrays match in length. each duration element corresponds to
      //one note element.
      static int[] note = { NOTE_E, NOTE_E, NOTE_F, NOTE_G, NOTE_G, NOTE_F, NOTE_E, NOTE_D,
                NOTE_C, NOTE_C, NOTE_D, NOTE_E, NOTE_E, NOTE_D, NOTE_D, NOTE_E, NOTE_E, NOTE_F, NOTE_G,
                NOTE_G, NOTE_F, NOTE_E, NOTE_D, NOTE_C, NOTE_C, NOTE_D, NOTE_E, NOTE_D, NOTE_C, NOTE_C};

      static int[] duration = { QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER,
            QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTERDOT, EIGHTH, HALF, QUARTER, QUARTER,
            QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER,
            QUARTERDOT, EIGHTH, WHOLE};

      public static void Main()
      {
         PWM MyPWM = new PWM((Cpu.PWMChannel)Cpu.PWMChannel.PWM_3, 261, 0.50, false);
         while (true)
         {
            for (int i = 0; i < note.Length; i++)
            {
               MyPWM.Stop();
               MyPWM.Frequency = note[i];
               MyPWM.Start();
               Thread.Sleep(duration[i]);
            }
            Thread.Sleep(100);
         }
      }
   }
}

Example 5: Servos

PWM can also be used to control servos. You can control the position of a servo by providing it with a pulse of a specific duration or width. If the pulse width is about 1.25ms then the servo is at 0 degrees. Increasing the pulse width to 1.50ms will move the servo to 90 degrees (neutral). A wider pulse of 1.75ms will move the servo to 180 degrees. Servos expect a pulse every 20ms to 30ms but the exact time is not very critical to servos outside of high performance applications.

When constructing a PWM object, it needs to know the period and duration of the pulse. The duration is how long the pin is high or active while the period is the time the pin is high (pulsing) and the time spent waiting to send the next pulse (20 - 30ms). What is most important is that the high pulse is between 1.25ms and 1.75ms so that the servo can set its position properly. You can also set the scale of the numbers you provide since servos and PWM can provide high resolution output.

In this example, we will move the position of the servo to 180 degrees so it expects a pulse of 1.75ms. We will also use a pause time between pulses of 20ms. So the period is 21.75ms and a duration of 1.75ms. Instead of passing doubles and setting the scale to miliseconds, we can pass in 2175 and 175 and set the scale to microseconds.

using System.Threading;
using Microsoft.SPOT.Hardware;

namespace change_this_to_your_namespace
{
   public class Program
   {
      public static void Main()
      {
         PWM MyServo = new PWM(Cpu.PWMChannel.PWM_3, 2175, 175, PWM.ScaleFactor.Microseconds, false);

         while (true)
         {
            // 0 degrees. 20ms period and 1.25ms high pulse
            MyServo.Duration = 125;
            MyServo.Period = 2125;
            MyServo.Start();
            Thread.Sleep(1000);

            // 90 degrees. 20ms period and 1.50ms high pulse
            MyServo.Duration = 150;
            MyServo.Period = 2150;
            MyServo.Start();
            Thread.Sleep(1000);

            // 180 degrees. 20ms period and 1.75ms high pulse
            MyServo.Duration = 175;
            MyServo.Period = 2175;
            MyServo.Start();
            Thread.Sleep(1000);
         }
      }
   }
}

Example 6: Using SignalGenerator

There are a lot of PWM pins on devices like the FEZ Domino, FEZ Mini, and FEZ Cobra but you may need more pins than your hardware supports.

One easy option is to sit in a loop setting the pin high and low according to specific timings. Simple devices like basic stamp and Arduino do that. This is really bad since your program stops just to handle PWM and can't really do much else. Even using a thread will eat up a lot of resources since you constantly loop.

The right option here would be to use the SignalGenerator class. This class lets you generate all kind of signals, such as TV remote signals. Generating PWM signals is also an easy option. Below is a class that simulates the PWM class (same methods) but internally it uses OC so it can generate PWM on any pin.

Remember that using PWM in hardware is always the preferred choice.

using GHI.Premium.Hardware;
using Microsoft.SPOT.Hardware;

class PWM_UsingSG
{
   SignalGenerator _sg;
   uint[] timings = new uint[2];

   public PWM_UsingSG(Cpu.Pin dp)
   {
      _sg = new SignalGenerator(dp, false, timings.Length);
   }

   public void SetPulse(uint period_nanosecond, uint highTime_nanosecond)
   {
      uint period = period_nanosecond / 1000;
      uint highTime = highTime_nanosecond / 1000;

      timings[0] = highTime;
      timings[1] = period - highTime;
      _sg.Set(true, timings, 0, 2, true);
   }

   public void Set(int frequency, byte dutyCycle)
   {
      // in nano seconds
      uint period = 0;
      uint highTime = 0;

      period = (uint)(1000000000 / frequency);

      highTime = (uint)((ulong)(period) * dutyCycle / 100);

      SetPulse(period, highTime);
   }
}

Arduino brand, Arduino logo, design of their boards are copyright of Arduino SA. For informations about the right way to use them, please write to trademark@arduino.cc



Leave feedback about this document.
Let us know if the information presented here was accurate, helpful and if you have any suggestions.
Leave feedback about this document.
Let us know if the information presented here was accurate, helpful and if you have any suggestions.

* Indicates required fields.
This form is only for feedback not support.
Review our how to find information guide on locating helpful resources.