In this tutorial, we will see how to use PWM signals in MicroPython to generate pseudo-analog outputs to control, for example, the intensity of LEDs or the speed of motors.
PWM (pulse width modulation) is a technique that we have often seen on the blog, which allows us to generate “more or less analog” signals cheaply with a microcontroller.
Basically, since it is expensive to have hardware to make a real analog signal, what we do is a cycle where we turn a digital signal on and off every certain amount of time.
- Duty Cycle: It is the percentage of time the signal is in a high state. For example, a duty cycle of 50% means that the signal is on half the time and off the other half.
- Frequency: It is the number of times the signal repeats per second, measured in Hertz (Hz).
The PWM frequency must be adjusted according to the device being controlled. In general, a frequency between 500 Hz and 1 kHz is usually adequate in many cases.
If the device is “slow” (has a lot of inertia) compared to the PWM frequency, it may behave to our signal more or less like an analog signal.
But not all devices will accept your pseudo-analog signal. Some of them may behave erratically and we could even damage them if they are not prepared for the voltage levels.
If you want to learn more, check out,
PWM Configuration in MicroPython
To use PWM in MicroPython, we first need to import the machine
module, which provides the necessary functions to interact with the hardware (as we did with digital outputs).
Then, we configure a GPIO pin as a PWM output like this,
pwm = PWM(led_pin)
Now we can use some of the PWM methods to configure our pseudo-analog output. For example:
Command | Description |
---|---|
pwm.freq(freq) | Sets or returns the frequency of the PWM signal in Hertz (Hz). |
pwm.duty(duty_cycle) | Adjusts the duty cycle, varying the brightness of the LED (value between 0 and 1023). |
pwm.deinit() | Deactivates the PWM signal on the specified pin. |
pwm.duty_u16(duty_cycle) | Adjusts the duty cycle with 16-bit resolution (value between 0 and 65535). |
pwm.init(freq, duty) | Initializes the PWM with specific frequency and duty cycle. |
Practical Example
Let’s see it better with a simple example. We will control the intensity of an LED using PWM.
from machine import Pin, PWM
import time
# GPIO pin configuration where the LED is connected
led_pin = Pin(2, Pin.OUT) # Using GPIO pin 2 (common in ESP32/ESP8266)
pwm = PWM(led_pin) # Configure the pin as a PWM output
# PWM frequency configuration (for example, 1000 Hz)
pwm.freq(1000)
# Function to vary the brightness of the LED
def vary_brightness():
for duty_cycle in range(0, 1024): # Range from 0 to 1023 (10 bits)
pwm.duty(duty_cycle) # Adjust the duty cycle
time.sleep_ms(10) # Small pause to observe the change
# Execute the function
vary_brightness()
In this example,
- We import
Pin
andPWM
from themachine
module, andtime
to handle pauses. - We define GPIO pin 2 as output and configure it for PWM.
- We set the frequency of the PWM signal to 1000 Hz.
- We use a
for
loop to vary the duty cycle from 0 to 1023 (10 bits), which allows us to control the brightness of the LED. - We add a small pause (
time.sleep_ms(10)
) to observe the gradual change in brightness.
Practical Examples
Control of RGB LED Brightness
Just as we can control an LED with a PWM signal, we can use three to control an RGB LED, allowing us to vary the color through the intensity of each channel (red, green, and blue).
from machine import Pin, PWM
import time
# Configuration of the pins for the RGB LEDs
red = PWM(Pin(2, Pin.OUT))
green = PWM(Pin(4, Pin.OUT))
blue = PWM(Pin(5, Pin.OUT))
# PWM frequency configuration
red.freq(1000)
green.freq(1000)
blue.freq(1000)
# Function to vary the color of the RGB LED
def vary_color():
for i in range(0, 1024):
red.duty(i)
green.duty(1023 - i)
blue.duty(i // 2)
time.sleep_ms(10)
# Execute the function
vary_color()
Control of a Motor with PWM
Now, we will apply PWM to control the speed of a motor. This example is useful in applications such as robots or automation systems.
from machine import Pin, PWM
import time
# GPIO pin configuration where the motor is connected
motor_pin = Pin(4, Pin.OUT) # Using GPIO pin 4
pwm = PWM(motor_pin) # Configure the pin as a PWM output
# PWM frequency configuration (for example, 500 Hz)
pwm.freq(500)
# Function to vary the speed of the motor
def vary_speed():
for duty_cycle in range(0, 1024): # Range from 0 to 1023 (10 bits)
pwm.duty(duty_cycle) # Adjust the duty cycle
time.sleep_ms(20) # Small pause to observe the change
# Execute the function
vary_speed()
In this case, the motor will vary its speed according to the applied duty cycle. A duty cycle of 50% will make the motor rotate at half of its maximum speed.
Be careful with motors, as depending on the size, they usually do not like PWM signals at all. They can fry a digital pin due to the induced voltages.
Tone Generation with a Buzzer
Passive buzzers require a PWM signal to generate audible tones. Let’s assume a buzzer is connected to GPIO15 (through a resistor).
from machine import Pin, PWM
import time
# PWM configuration
buzzer = PWM(Pin(15))
def beep(frequency, duration):
"""
Generates a tone in the buzzer.
:param frequency: Frequency in Hz.
:param duration: Duration in seconds.
"""
buzzer.freq(frequency)
buzzer.duty_u16(32768) # Duty cycle at 50%
time.sleep(duration)
buzzer.duty_u16(0) # Turn off the buzzer
# Generate tones
try:
beep(440, 1) # A4 - 440 Hz for 1 second
beep(880, 0.5) # A5 - 880 Hz for 0.5 seconds
except KeyboardInterrupt:
buzzer.deinit()