Pulse Width Modulation (PWM) is a wonderful technology found in many modern microcontroller boards and can be easily added to any others. In my most recent project, PWM technology helped me to achieve peace and quiet. Read on and I’ll tell you all about that below. I also used PWM for many parts of my human-sized robot project shown above.
So what does PWM do?
PWM is a technique typically used in digital systems to vary the average amount of power being emitted on a wire, creating an effect not unlike a variable resistor being used to adjust the power emitted from an analog circuit. Digital circuits are able to emit only zero (i.e., “off“, near zero volts) or one (i.e. “on“, nearly full voltage). To approximate something between those extremes, PWM software or circuitry rapidly cycles the output wire between zero and one. For example, if this cycling is done such that the wire is on only about half of the time, then the average voltage at this pin will be about half of the full voltage. The PWM technique is particularly well suited to operating motors, since the motor has spinning inertia which will tend to smooth out the effects of the PWM power cycling. There’s an excellent Wikipedia article about this if you want to read up in more detail.
What is PWM useful for?
PWM is useful whenever you want to vary the amount of power on an output wire. This is useful, for example, if you want to change the brightness of an LED or you want to change the speed of an analog electric motor. It is also useful for setting a precise angle on a servo motor. I have a (never ending?) project to build a human-sized robot, pieces of which are shown at the top of this article. Each of those forearms contains six servo motors (one for the wrist rotation, and one for each of the five fingers). PWM is used to send the signals to each of these servo motors that will position them at a particular rotation angle. One PWM channel is required for each servo motor.
What components will you need?
Your microcontroller board may be all that you need. Most modern microcontrollers have at least one PWM output. Boards like the Arduino Uno have about half a dozen of them. Newer Raspberry Pi boards have two of them (in addition to one for the audio output volume). If your microcontroller does not have any PWM ports, or if it does not have as many as you need for your project, for just a few dollars you can buy an add-on board with 16 PWM channels, like ones shown below (available from many stores). I use several of these boards in my robot. Adafruit now sells one with 24 channels that is about the same size as these.
Note that although the average voltage can be varied on these PWM ports, the amperage you can draw is still very limited (e.g., a typical microcontroller GPIO pin should only be expected to provide about 20mA of power). If you are just varying the brightness of an LED, that may be sufficient. Servo motors also draw very little power from their control lines, so they are fine too. However, if you wish to control an analog electric motor, you will need some additional circuitry because they draw much more amperage. The next section will cover how to handle more demanding loads.
How Can You Control Analog Motors With PWM?
My latest project has 5 small computers, and miscellaneous other components in a box, so I really needed to provide some ventilation. PWM is useful for this, and it’s how I achieved “peace” (and quiet) for that project. Note that I only needed one PWM port for this, so the multi-port PWM boards shown above were not needed for this project.
Initially I just purchased a small 5V case fan, thinking I could connect it to one of the USB power ports inside this project and that would be it. So I began with that. The result was a very noisy case fan! So I decided to instead setup some code to monitor the CPU temperature (all modern CPUs report their temperature) and build a small circuit so I can use PWM to speed up or slow down the fan according to the CPU temperature.
The circuit I used to control the fan is shown below. The fan was connected at the place in the circuit marked “Load”. Doing this, and writing a small amount of code, enabled me to run the fan at a much lower (and much quieter and more peaceful!) speed while still keeping the CPU temperature at an appropriate level.
Here’s the circuit I used:
If you previously read the article on this site about transistors, then this circuit may look familiar. It is similar to the one that I use in that article to drive a relay from a GPIO. However, in this case the GPIO will be configured for PWM to be able to drive the fan at different speeds. Just as in that other circuit, the 2N2222 (standard NPN) transistor here provides a path to ground from the collector (“c”) pin to the emitter (“e”) pin, controlled by the power applied to the base (“b”) pin. The resistor (R1) prevents the transistor from pulling too much power from the GPIO, and the diode (D1) helps to protect the circuit from spikes cause by the motor. Note also that the 2N2222 is rated for 40V, collector-to-emitter, so if you have a 12V fan instead of a 5V fan, it will work here too (and if you do, then where it shows 5V above, just use 12V instead). Also note that electric motors tend to briefly consume a larger spike of power when they are first turned on before settling down. Make sure your load stays well clear of the rated maximum amperage across this transistor, which is 800mA.
What Software Do I Need To Control PWM?
Let’s take a look at some simple code to see the steps that are required to use PWM. Below that I also provide a complete solution that I actually use to control the fan in my current project.
This example uses the RPi.GPIO python library to interact with the GPIO pins on the Raspberry Pi. Other PWM libraries have similar interfaces. The Arduino libraries seem to prefer a range of 1..255, instead of the more typical range of 0..100 when setting the output value, but the other aspects are the same.
First you need to install the library on your host (or inside your Docker container). Usually that is done with
pip, like this:
pip install RPi.GPIO
Then you can start coding in python. The first step is to import the library and configure Broadcom pin numbering. Here I also .
import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False)
The next steps are to configure the pin for output, and setup the frequency in cycles per second (Hz) that will be used for the power cycling. Here I assume you will be using GPIO pin #18, which on the Raspberry Pi is on physical pin #12. I chose a frequency of 110Hz which works well for this application. For my robot servos I use 1000Hz. Much has been written about how to select a PWM frequency but for my personal projects I have just randomly experimented to select a frequency and that has been good enough. In general I think the greater the frequency, the more the output looks like an analog curve instead of a coarse step function. So when in doubt, a higher frequency number is probably better. Anyway, the steps below are needed to set output mode and configure the PWM frequency:
MY_FAN_CONTROL_PWM = 18 PWM_FREQUENCY_HZ = 110 GPIO.setup(MY_FAN_CONTROL_PWM, GPIO.OUT) fan_pwm = GPIO.PWM(MY_FAN_CONTROL_PWM, PWM_FREQUENCY_HZ)
Then finally you need to turn on the PWM function, specifying an initial “duty cycle”. The duty cycle refers to what percentage of the time the PWM pin should be set high (i.e. “on”, or full output voltage). So a duty cycle of
100 indicates that full power should be continuously supplied to the GPIO pin, while. duty cycle of
25 indicates that full power should only be provided 25% of the time. As noted above, the Arduino libraries use 255 to indicate full power. In the Arduino libraries a duty cycle of
64 (approximately one quarter of 255) would indicate that full power should be provided about 25% of the time.
After setting the initial duty cycle with the
start() function, you can use the
ChangeDutyCycle() function repeatedly to alter the duty cycle value as needed. If you wish to stop the PWM function altogether, you can use the
fan_pwm.start(100) fan_pwm.ChangeDutyCycle(50) fan_pwm.stop()
That’s everything you need to know about PWM. If you are interested in looking at a more complete usage example, I have provided the code I use for fan control on my current project. Just wire the transistor circuit above to GPIO#18 (board pin #12), and connect your fan. Then install docker on your Raspberry Pi, clone this repository, run
make and this container will build and run and then manage your fan for you based upon your CPU temperature!