CircuitPython APIs for non-CircuitPython versions of Python such as CPython on Linux and MicroPython.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pulse Width Modulation output and pulse measurement capabilities for motor control, servo positioning, and signal generation. Provides CircuitPython-compatible PWM and pulse measurement operations with automatic platform detection and driver loading.
The PWMOut class provides pulse width modulation output for controlling motors, servos, LEDs, and other devices that respond to duty cycle control. Automatically detects and uses appropriate platform-specific drivers.
class PWMOut(ContextManaged):
def __init__(self, pin, *, duty_cycle: int = 0, frequency: int = 500, variable_frequency: bool = False):
"""
Initialize PWM output pin.
Args:
pin: Board PWM-capable pin object (from board module)
duty_cycle: Initial PWM duty cycle (0-65535, default 0)
frequency: PWM frequency in Hz (default 500)
variable_frequency: Enable variable frequency support (not implemented)
Raises:
RuntimeError: If no PWM channel found for the pin
ValueError: If PWM channel does not exist or modules not loaded
"""
@property
def duty_cycle(self) -> int:
"""
Get or set PWM duty cycle.
Returns:
int: Current duty cycle (0-65535, where 65535 = 100%)
"""
@duty_cycle.setter
def duty_cycle(self, value: int) -> None:
"""
Set PWM duty cycle.
Args:
value: Duty cycle value (0-65535)
Raises:
TypeError: If value is not int or float
ValueError: If value is outside 0.0-1.0 range (internally converted)
"""
@property
def frequency(self) -> float:
"""
Get or set PWM frequency in Hz.
Returns:
float: Current PWM frequency
"""
@frequency.setter
def frequency(self, value: float) -> None:
"""
Set PWM frequency.
Args:
value: Frequency in Hz
Raises:
TypeError: If value is not int or float
"""
@property
def period(self) -> float:
"""
Get or set PWM period in seconds.
Returns:
float: Current PWM period (1/frequency)
"""
@period.setter
def period(self, value: float) -> None:
"""
Set PWM period.
Args:
value: Period in seconds
Raises:
TypeError: If value is not int or float
"""
def deinit(self) -> None:
"""Release PWM resources and disable output"""The PulseIn class provides pulse width measurement for reading PWM signals, IR remote controls, and other pulse-based communications. Limited platform support.
class PulseIn(ContextManaged):
def __init__(self, pin, maxlen: int = 2, idle_state: bool = False):
"""
Initialize pulse input measurement.
Args:
pin: Board pin object for pulse measurement
maxlen: Maximum number of pulses to store (default 2)
idle_state: Expected idle state - False for low, True for high
Raises:
RuntimeError: If platform not supported or setup failed
"""
@property
def maxlen(self) -> int:
"""
Maximum number of pulses stored.
Returns:
int: Maximum pulse buffer length
"""
@property
def paused(self) -> bool:
"""
True if pulse capture is paused.
Returns:
bool: Paused state
"""
def __len__(self) -> int:
"""
Number of pulses currently captured.
Returns:
int: Current number of stored pulses
"""
def __getitem__(self, index: int) -> int:
"""
Get pulse duration by index.
Args:
index: Pulse index (0 = oldest)
Returns:
int: Pulse duration in microseconds
Raises:
IndexError: If index is out of range
"""
def clear(self) -> None:
"""Clear all captured pulses"""
def popleft(self) -> int:
"""
Remove and return oldest pulse.
Returns:
int: Duration of oldest pulse in microseconds
Raises:
IndexError: If no pulses available
"""
def pause(self) -> None:
"""Pause pulse capture"""
def resume(self, trigger_duration: int = 0) -> None:
"""
Resume pulse capture.
Args:
trigger_duration: Optional trigger pulse duration in microseconds
"""
def deinit(self) -> None:
"""Release pulse measurement resources"""import board
import pwmio
import time
# Create PWM output
led = pwmio.PWMOut(board.D18, frequency=1000, duty_cycle=0)
# Fade in
for i in range(100):
led.duty_cycle = int(i * 655.35) # 0 to 65535
time.sleep(0.01)
# Fade out
for i in range(100, 0, -1):
led.duty_cycle = int(i * 655.35)
time.sleep(0.01)
# Cleanup
led.deinit()import board
import pwmio
import time
# Standard servo control (50Hz, 1-2ms pulse width)
servo = pwmio.PWMOut(board.D18, frequency=50)
def set_servo_angle(servo, angle):
"""Set servo angle (0-180 degrees)"""
# Convert angle to duty cycle
# 1ms = 5% duty cycle, 2ms = 10% duty cycle at 50Hz
min_duty = int(0.05 * 65535) # 1ms pulse
max_duty = int(0.10 * 65535) # 2ms pulse
duty_range = max_duty - min_duty
duty_cycle = min_duty + int((angle / 180.0) * duty_range)
servo.duty_cycle = duty_cycle
# Sweep servo back and forth
for _ in range(3):
# Move to 0 degrees
set_servo_angle(servo, 0)
time.sleep(1)
# Move to 90 degrees
set_servo_angle(servo, 90)
time.sleep(1)
# Move to 180 degrees
set_servo_angle(servo, 180)
time.sleep(1)
servo.deinit()import board
import pwmio
import digitalio
import time
# Motor control with PWM speed and direction
motor_pwm = pwmio.PWMOut(board.D18, frequency=1000)
motor_dir = digitalio.DigitalInOut(board.D19)
motor_dir.direction = digitalio.Direction.OUTPUT
def set_motor_speed(pwm, direction, speed_percent):
"""Set motor speed (-100 to +100 percent)"""
if speed_percent < 0:
direction.value = False # Reverse
speed_percent = -speed_percent
else:
direction.value = True # Forward
# Convert percentage to duty cycle
duty_cycle = int((speed_percent / 100.0) * 65535)
pwm.duty_cycle = duty_cycle
# Motor control demo
speeds = [25, 50, 75, 100, 0, -25, -50, -75, -100, 0]
for speed in speeds:
set_motor_speed(motor_pwm, motor_dir, speed)
print(f"Motor speed: {speed}%")
time.sleep(2)
# Cleanup
motor_pwm.deinit()
motor_dir.deinit()import board
import pwmio
import time
import math
# Using context manager for automatic cleanup
with pwmio.PWMOut(board.D18, frequency=500) as pwm:
# Generate sine wave pattern
for i in range(360):
# Convert angle to sine wave (0-1 range)
sine_val = (math.sin(math.radians(i)) + 1) / 2
# Convert to duty cycle
pwm.duty_cycle = int(sine_val * 65535)
time.sleep(0.01) # ~10Hz update rate# Note: PulseIn has limited platform support (Raspberry Pi, some Odroid boards)
import board
import pulseio
import time
try:
# Initialize pulse measurement
pulse_in = pulseio.PulseIn(board.D2, maxlen=10, idle_state=False)
print("Measuring pulses... Press Ctrl+C to stop")
while True:
# Wait for pulses
while len(pulse_in) == 0:
time.sleep(0.01)
# Read and display pulses
while len(pulse_in) > 0:
pulse_duration = pulse_in.popleft()
print(f"Pulse: {pulse_duration} microseconds")
time.sleep(0.1)
except KeyboardInterrupt:
print("Stopping pulse measurement")
except RuntimeError as e:
print(f"PulseIn not supported on this platform: {e}")
finally:
try:
pulse_in.deinit()
except:
passimport board
import pwmio
import time
# Create PWM with variable frequency
pwm = pwmio.PWMOut(board.D18, duty_cycle=32768) # 50% duty cycle
# Sweep frequency from 100Hz to 2000Hz
frequencies = [100, 200, 500, 1000, 1500, 2000]
for freq in frequencies:
pwm.frequency = freq
print(f"PWM frequency: {freq} Hz")
time.sleep(2)
# Reset to default
pwm.frequency = 500
pwm.duty_cycle = 0
pwm.deinit()Widely Supported Platforms:
Platform-Specific Notes:
Supported Platforms:
Requirements:
import board
import pwmio
try:
pwm = pwmio.PWMOut(board.D18, frequency=1000)
pwm.duty_cycle = 32768 # 50%
# Use PWM...
pwm.deinit()
except RuntimeError as e:
if "PWM channel" in str(e):
print(f"PWM not available on this pin: {e}")
else:
print(f"PWM error: {e}")
except ValueError as e:
print(f"PWM configuration error: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-adafruit-blinka