Skip to main content

PWM

Pulse Width Modulation (PWM) generates a square wave with a fixed frequency and a variable duty cycle. It's commonly used to dim LEDs, control motor speed, drive servos, or output digital pulses for protocols like infrared. The duty cycle is the ratio of the pulse width to its period — both frequency and duty cycle are software-controllable.

Two APIs

TinyCLR offers two PWM APIs that coexist on the same hardware. Both namespaces ship in a single NuGet package — install one and you have both.

  • .NET IoT API — Microsoft's standard System.Device.Pwm namespace. Use this for new code and for portability with desktop .NET IoT applications.
  • TinyCLR API — the original GHIElectronics.TinyCLR.Devices.Pwm namespace. Keeps v2 code working without changes.

NuGet packages:

  • GHIElectronics.TinyCLR.Devices.Pwm — contains both the System.Device.Pwm (.NET IoT) and GHIElectronics.TinyCLR.Devices.Pwm (TinyCLR) namespaces.
  • GHIElectronics.TinyCLR.Pins — pin name constants for SITCore boards. Shared across GPIO, PWM, SPI, I²C, UART, and other peripherals.
tip

Pick the right controller and channel — there is no Default. Most peripherals have a single controller, but PWM has multiple. Always select a specific channel on a specific controller.

tip

Notation: "PWM2.3" means channel 3 on controller 2.

tip

.NET IoT chip numbering: the chip argument in PwmChannel.Create(chip, channel, ...) is the controller number minus one — Controller1 → chip: 0, Controller2 → chip: 1, Controller3 → chip: 2. The pin constant tells you which controller (and channel) to use — for example, FEZBit.Timer.Pwm.Controller1.Led is on chip 0.

Energy level

PWM is perfect for dimming an LED or controlling the speed of a motor. When the duty cycle is 50%, half the energy is transferred to the attached load.

using System.Device.Pwm;
using GHIElectronics.TinyCLR.Pins;

using PwmChannel led = PwmChannel.Create(FEZBit.Timer.Pwm.Controller1.Chip, FEZBit.Timer.Pwm.Controller1.Led, 10000, 0.5);

double duty = 0.5, speed = 0.01;

led.Start();

while (true){
if (duty <= 0 || duty >= 1.0){
speed *= -1; // Reverse direction.
duty += speed;
}

led.DutyCycle = duty;
duty += speed;

Thread.Sleep(10); // Always give the system time to think!
}

Musical tones

Musical notes have specific frequencies — C is about 261 Hz. Putting frequencies and durations in arrays lets you sequence notes to play simple melodies. When playing notes by changing the frequency, keep the duty cycle at 0.5.

The example below targets the SC20100S Dev Board.

using System.Device.Pwm;
using GHIElectronics.TinyCLR.Pins;

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 = 2000;
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 arrays match in length — each duration corresponds to one note.
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 };

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 };

using PwmChannel toneOut = PwmChannel.Create(FEZBit.Timer.Pwm.Controller3.Chip, FEZBit.Timer.Pwm.Controller3.Buzzer, NOTE_C, 0.5);

while (true){
toneOut.Start();

for (int i = 0; i < note.Length; i++){
toneOut.Frequency = note[i];
Thread.Sleep(duration[i]);
}

toneOut.Stop();
Thread.Sleep(1000);
}

Software PWM

Hardware PWM generates signals accurately with zero CPU overhead but is limited to specific pins. TinyCLR also supports software PWM on any GPIO pin — less accurate, costs CPU time, but works well at lower frequencies suitable for dimming or servos.

tip

On SITCore, the PWM frequency range is 15.4 Hz to 10 kHz.

note

Software PWM is currently exposed through the TinyCLR API only.

using System.Threading;
using GHIElectronics.TinyCLR.Devices.Pwm;
using GHIElectronics.TinyCLR.Pins;

var softwarePwmController = PwmController.FromName(FEZBit.Timer.Pwm.Software.Id);
var buzzer = softwarePwmController.OpenChannel(FEZBit.GpioPin.Buzzer);

softwarePwmController.SetDesiredFrequency(1000); // 1 kHz
buzzer.SetActiveDutyCyclePercentage(0.5);

buzzer.Start();

Thread.Sleep(10 * 1000); // 10 seconds

buzzer.Stop();

Servo motors

Servos use PWM signals to position their shaft. A driver NuGet package is provided to simplify common servo operations. The example below uses software PWM so any GPIO pin works.

tip

Servos typically have three wires:

  • Center — connect to 5V
  • Lighter color (one side) — signal, connect to a PWM pin
  • Darker color (third) — ground

Typical pulse width is 1–2 ms, but check your servo's datasheet.

NuGet packages needed: GHIElectronics.TinyCLR.Devices.Pwm, GHIElectronics.TinyCLR.Pins, GHIElectronics.TinyCLR.Drivers.Motor.Servo.

using System.Threading;
using GHIElectronics.TinyCLR.Devices.Pwm;
using GHIElectronics.TinyCLR.Drivers.Motor.Servo;
using GHIElectronics.TinyCLR.Pins;

var softwarePwmController = PwmController.FromName(FEZBit.Timer.Pwm.Software.Id);
var pad0 = softwarePwmController.OpenChannel(FEZBit.GpioPin.P0);

var servo = new ServoController(softwarePwmController, pad0);

while (true){
servo.Set(0); // 0 degrees
Thread.Sleep(2000);

servo.Set(45.0); // 45 degrees
Thread.Sleep(2000);

servo.Set(90.0); // 90 degrees
Thread.Sleep(2000);

servo.Set(180.0); // 180 degrees
Thread.Sleep(4000);
}

API reference

NamespaceDescription
GHIElectronics.TinyCLR.Devices.PwmTinyCLR PWM controller and channel classes
System.Device.Pwm.NET IoT standard PWM API (included in the same NuGet)