Go back to the examples list

# Generating PWM

In the context of a microcontroller, a PWM (Pulse-Width Modulation) signal is a square-wave signal where the frequency is fixed and the duty cycle is modulated. Since the average value of the signal is directly proportional to the duty cycle, this is a simple way to modulate the amount of power that goes into a peripheral, and it therefore widely used in applications such as motor speed control and LED dimming.

PWM signals can be generated using a GPIO controlled manually and timing functions, but this would be neither efficient nor reliable because the processor would spend most of its time waiting for the correct time to toggle the GPIO. Instead, a TC counter can be configured to control a pin and toggle it whenever its current value reaches a specified value, resulting in a configurable PWM signal entirely generated in hardware without the need of the processor, which can even be put into sleep mode if necessary. See the paragraph about PWM Mode in the TC module documentation for more information.

In order to generate a PWM signal, start by calling TC::enablePWM() with the counter channel you want to use and the characteristics of the signal (period and high time) :

TC::enablePWM(TC::TC0_0A, 20000, 5000);

The output pin may be specified by calling TC::setPin() before enablePWM() (see Pin muxing for more details). The configuration can then be changed at any time by using the PWM-related functions, such as TC::setPeriod() or TC::setDutyCycle().

Below is an example that illustrates how multiple channels can be configured and used independently :

tc_generate_pwm.cpp [download]
wget https://libtungsten.io/static/code/examples/tc_generate_pwm/tc_generate_pwm.cpp

#include <carbide.h>
#include <core.h>
#include <tc.h>

int main() {
// Init the microcontroller
Carbide::init();

// Start a PWM output on the channel TC0_0A, which is pin PB07 by default,
// with a 20ms period and a 5ms high-time (25% duty cycle)
TC::Channel channel1 = TC::TC0_0A;
TC::enablePWM(channel1, 20000, 5000);

// Start another PWM output on the channel TC0_1A and on pin PA10,
// with a 500ms period and output disabled
TC::Channel channel2 = TC::TC0_1A;
const GPIO::Pin PIN_CHANNEL_2 = {GPIO::Port::A, 10, GPIO::Periph::B};
TC::setPin(channel2, TC::PinFunction::OUT, PIN_CHANNEL_2);
TC::enablePWM(channel2, 500000, 0, false, TC::SourceClock::PBA_OVER_128);

// Wait for half a second
// Note : the PWM will still be generated while the CPU sleeps
Core::sleep(500);

// Change the period on the first channel to 10ms
// The high-time will not change, the duty cycle will therefore
// be 50%
TC::setDutyCycle(channel1, 75);

// Enable the output of the second channel with a duty cycle of 75%
TC::setDutyCycle(channel2, 75);
TC::enableOutput(channel2);

while (true) {
// Do something else...
}
}

Makefile [download]
wget https://libtungsten.io/static/code/examples/tc_generate_pwm/Makefile

## libtungsten project compilation config
# Remember to do 'make clean' whenever you change anything in this Makefile

NAME=tc_generate_pwm

CARBIDE=true
include libtungsten/Makefile