How to generate PWM signals on Raspberry Pi?

How to generate PWM signals on Raspberry Pi?

Pulse Width Modulation (PWM) is a technique that involves modifying the width of a pulse while keeping the frequency constant. By adjusting the width of the pulse, we can regulate the power delivered to a load. PWM finds application in various scenarios, including controlling the speed of a DC motor, managing the rotation of a servo motor, adjusting the intensity of an LED, and others.

The frequency of a PWM signal represents the number of pulse cycles occurring within a specific time period, typically one second. A pulse cycle starts from the rising edge of a pulse and extends to the rising edge of the next pulse. The duration of a pulse cycle is referred to as the period and is measured in seconds.

The pulse width, also known as the duty cycle, signifies the duration during which the digital signal maintains a high voltage value relative to the entire period. For instance, in Figure 1, the period measures 10 milliseconds (or a frequency of 100Hz), while the active pulse duration is only 3 milliseconds. In this particular case, we refer to the duty cycle or pulse width as 30%.

PWM Signal

Figure 1. PWM Signal.

On the Raspberry Pi, there are two methods to generate PWM signals: software and hardware. With software PWM, you have the flexibility to generate PWM signals on any GPIO pin of your choice. For information on the available GPIO pins on the Raspberry Pi, you can refer to this article.

In addition to software PWM, the Raspberry Pi also provides two hardware PWM modules: PWM0 and PWM1. Each module is connected to two GPIO pins. PWM0 is connected to GPIO18 (pin 12) and GPIO12 (pin 32 ), while PWM1 is connected to pin GPIO13 (33) and pin GPIO19 (34). It’s important to note that these pins share the same frequency and duty cycle as the module they are associated with.

Below are code examples that demonstrate how to generate a PWM signal on pin GPIO18 (physical pin 12) of the Raspberry Pi. I have connected an LED to the pin, so I can adjust its brightness by modifying the duty cycle of the PWM signal, see Figure 2.

PWM experiment schematics

Figure 2. PWM experiment schematics.

If you wish to control the intensity of an LED, you can directly connect it to the PWM pin on the Raspberry Pi (of course, don’t forget the current limiting resistor). However, for devices that require higher power consumption, such as DC motors, you will need to employ a dedicated driver or use a BJT or MOSFET transistor.

Python Code Example

The Python program below gradually increases the brightness of an LED to its maximum and then gradually fades it away.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import RPi.GPIO as GPIO
from time import sleep

pwm_pin = 18
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(pwm_pin, GPIO.OUT)

pwm = GPIO.PWM(pwm_pin, 100)
pwm.start(0)

for dc in range(0, 101, 1):
    pwm.ChangeDutyCycle(dc)
    sleep(.1)

for dc in range(100, -1, -1):
    pwm.ChangeDutyCycle(dc)
    sleep(.1)

pwm.stop()
GPIO.cleanup()
  • Line 4: Specifies the GPIO pin for generating the PWM signal.
  • Line 5: Sets the GPIO numbering reference mode.
  • Line 6: Disables any warnings related to GPIO pin usage.
  • Line 7: Sets the specified GPIO pin as an output.
  • Line 9: Creates a PWM object with the specified GPIO pin and a frequency of 100 Hz. The frequency can be modified later using the PWM.ChangeFrequency(frequency) method.
  • Line 10: Starts the PWM with an initial duty cycle of 0.
  • Line 12: A for loop that gradually increases the PWM duty cycle in increments of 1 every 0.1 seconds. The PWM.ChangeDutyCycle(dutyCycle) method is used to update the duty cycle.
  • Line 16: A for loop that gradually decreases the PWM duty cycle in decrements of 1 every 0.1 seconds.
  • Line 20 and 21: Stops the PWM operation and cleans up the GPIO input.

The RPi.GPIO Python library provides software-based PWM functionality.

During my experiments, I found that there is an offset between the frequency I set in the code and the actual frequency on the output pin. This offset becomes more prominent as the frequency increases.
For instance, if I set the frequency to 300 Hz in the code, I measure a frequency of around 287 Hz on the output pin. When I set the frequency to 1 KHz, the measured frequency is approximately 870 Hz. Similarly, with a set frequency of 10 KHz, I observe a frequency of around 4.2 KHz on the pin. The maximum frequency I was able to obtain was 7 KHz, despite setting it to 80 KHz in the code. Also, for frequencies higher than 4 kHz, I wasn’t able to vary the duty cycle across the entire range of 1% to 100%.

C Code Software PWM Example

The C code below uses the pigpio library and does the same as the Python code above. This code generates a stable PWM signal with a maximum frequency of 40 KHz and varies the duty cycle from 1% to 100%. Please note that in this example the library also generates a software based PWM.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <pigpio.h>

int main(){
	
	int pwm_pin = 18;
	gpioCfgClock(1, 1, 0);
	
	if(gpioInitialise() < 0) {
		fprintf(stderr, "Failed to initialize pigpio\n");
		return 1;
	}

	gpioSetPWMrange(pwm_pin, 100);
	gpioSetPWMfrequency(pwm_pin, 40000);
	
	for(int i = 0; i < 100; i++) {
		gpioPWM(pwm_pin, i);
		time_sleep(.1);
	}
	
	for(int i = 100; i > -1; i--){
		gpioPWM(pwm_pin, i);
		time_sleep(.1);
	}
	
	gpioTerminate();
	return 0;
}
  • Line 6: Defines the GPIO pin that will generate the PWM signal.
  • Line 7: Calls gpioCfgClock() to configure pigpio with a specific sample rate timed by the PWM peripheral. This step is optional if you intend to use lower PWM frequencies. However, in this case, a maximum frequency test was conducted, and the sample rate was set to 1. Please note that only certain values are valid for the sample rate, as specified here. Additionally, you must call gpioCfgClock() before gpioInitialise() to take effect.
  • Line 9: Initializes the pigpio library and exits the program if initialization fails.
  • Line 14: Sets the duty cycle range. You can vary the duty cycle from 0 to the specified range (default value is 250).
  • Line 15: Sets the PWM frequency for the specified pin to 40 KHz. Please note that you cannot set any arbitrary frequency. The frequency is dependent on the sample rate set on line 7. Valid frequency values for a specific sample rate are specified here.
    If an unsupported frequency is provided, the library will select the next available higher frequency. For example, with the default sample rate of 5, if you attempt to set the frequency to 3 KHz, which is not a supported value, the library will automatically choose the next available higher frequency, which is 4 KHz.
  • Line 17: A for loop that gradually increases the PWM duty cycle every tenth of a second.
  • Line 22: A for loop that gradually decreases the PWM duty cycle every tenth of a second.
  • Line 27: Calls gpioTerminate() to properly terminate the pigpio library.

To compile the code, use the following command:

gcc -Wall -pthread -o pwm pwm.c -lpigpio -lrt

To run the compiled program, it needs to be executed with sudo privileges:

sudo ./pwm

C Code Hardware PWM Example

The pigpio library offers support for hardware PWM. The code snippet below demonstrates how to use this feature.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <pigpio.h>

int main() {
	int pwm_pin = 18;
	if(gpioInitialise() < 0) {
		fprintf(stderr, "Failed to initialize pigpio\n");
		return 1;
	}
	
	for(int i = 0; i < 100; i++) {
		gpioHardwarePWM(pwm_pin, 1000000, i*10000);
		time_sleep(.1);
	}
	
	for(int i = 100; i > -1; i--){
		gpioHardwarePWM(pwm_pin, 1000000, i*10000);
		time_sleep(.1);
	}
	
	gpioTerminate();
	return 0;
}

The code is similar to the previous C program, with the main difference being the absence of function calls to set the PWM range and frequency. Instead, everything is accomplished by calling the gpioHardwarePWM(gpio, frequency, duty_cycle) function on lines 12 and 17.
This function takes three arguments: the GPIO pin number, the frequency in Hz, and the duty cycle, which ranges from 0 to 1000000. A duty cycle of 0 represents 0% duty cycle, while a duty cycle of 1000000 represents 100% duty cycle.

Setting up hardware PWM on the Raspberry Pi is a complex task, and the gpioHardwarePWM() function handles many operations behind the scenes.

The code example generates a PWM signal with a frequency of 1 MHz, but you can set higher frequencies. However, I observed that for frequencies around 4 MHz and higher, the PWM pulses start to resemble trapezoids instead of triangles. For frequencies greater than 10 MHz, the PWM pulse takes on a triangular shape.

Unfortunately, the pigpio library does not support selecting between Balanced and Mark/Space PWM modes, as the WiringPi library does, which I won’t cover in this article.

In this article, I have utilized the libraries that come pre-installed with Raspberry Pi OS and have used the Geany and Thonny IDEs available within the distribution. The intention was to work with a PWM peripheral without installing additional libraries.

Please note that the code samples provided were tested on a Raspberry Pi 3 B board. If you run the code on different board models, you may obtain different results.

There are numerous other libraries available that support software and hardware PWM, including libraries for various programming languages. I encourage you to explore these options on your own.

When deciding whether to use software or hardware PWM, it depends on your specific application. It may be beneficial to test different libraries and determine which one best suits your requirements. As demonstrated, software PWM can generate PWM signals up to a certain frequency within the kilohertz (KHz) range. If you require higher frequencies, hardware PWM is a better option as it allows for PWM signals to be generated in the megahertz (MHz) range. Additionally, bear in mind that software PWM may slightly increase CPU usage.