Beginner PWM

PWM Control

High-precision PWM control with 25MHz clock for servo positioning and motor control

Overview

PoKeys devices feature a built-in PWM module operating at 25MHz clock frequency, providing high-precision timing control for servo motors, LED dimming, and motor speed control.

Hardware Specifications

  • • 6 PWM channels (pins 17-22)
  • • 25MHz clock frequency
  • • 40ns timing resolution
  • • Shared period, individual duty cycles

Common Applications

  • • Servo motor positioning
  • • LED brightness control
  • • Motor speed control
  • • Signal generation

Pin Mapping

Pin Channel Name Description
22 0 PWM1 Primary PWM output
21 1 PWM2 Secondary PWM output
20 2 PWM3 Third PWM output
19 3 PWM4 Fourth PWM output
18 4 PWM5 Fifth PWM output
17 5 PWM6 Sixth PWM output

Timing Calculations

All PWM timing values must be expressed as clock cycles at 25MHz frequency:

Clock Cycles = Time (seconds) × 25,000,000

Common Servo Timing

  • • 20ms period = 500,000 cycles
  • • 1ms pulse = 25,000 cycles
  • • 1.5ms pulse = 37,500 cycles
  • • 2ms pulse = 50,000 cycles

Speed Servo Timing

  • • Counterclockwise: 1-1.5ms
  • • Stop: 1.5ms (37,500 cycles)
  • • Clockwise: 1.5-2ms
  • • Full range: 25,000-50,000 cycles

Basic PWM Control

use pokeys_lib::*;

fn main() -> Result<()> {
    let mut device = connect_to_device(0)?;
    
    // Configure PWM with 25MHz clock cycles
    // 20ms period = 0.020 × 25,000,000 = 500,000 cycles
    device.set_pwm_period(500000)?;
    
    // Enable PWM on pin 22 (PWM1)
    device.enable_pwm_for_pin(22, true)?;
    
    // Set duty cycle (1.5ms = 37,500 cycles for servo center)
    device.set_pwm_duty_cycle_for_pin(22, 37500)?;
    
    println!("PWM active on pin 22");
    
    // Disable PWM
    device.enable_pwm_for_pin(22, false)?;
    
    Ok(())
}

Servo Control Example

Complete servo positioning example with calibrated positions:

use pokeys_lib::*;
use std::thread;
use std::time::Duration;

fn main() -> Result<()> {
    let mut device = connect_to_device(0)?;
    
    // Configure for servo control (20ms period)
    device.set_pwm_period(500000)?;
    device.enable_pwm_for_pin(22, true)?;
    
    // Servo positions (calibrated for specific servo)
    let positions = [
        (60000, "0°"),   // Custom calibrated 0° position
        (36000, "90°"),  // Custom calibrated 90° position  
        (12000, "180°"), // Custom calibrated 180° position
    ];
    
    for (duty_cycles, angle) in positions.iter() {
        println!(\"Moving to {}\", angle);
        device.set_pwm_duty_cycle_for_pin(22, *duty_cycles)?;
        thread::sleep(Duration::from_secs(1));
    }
    
    // Return to center and disable
    device.set_pwm_duty_cycle_for_pin(22, 36000)?;
    thread::sleep(Duration::from_millis(500));
    device.enable_pwm_for_pin(22, false)?;
    
    Ok(())
}

Advanced Servo Control

High-level servo control with different servo types:

use pokeys_lib::*;

fn main() -> Result<()> {
    let mut device = connect_to_device(0)?;
    
    // 180-degree position servo
    let servo_180 = ServoConfig::one_eighty(22, 60000, 12000);
    device.configure_servo(servo_180.clone())?;
    device.set_servo_angle(&servo_180, 90.0)?; // Move to 90°
    
    // 360-degree position servo (multi-turn)
    let servo_360_pos = ServoConfig::three_sixty_position(20, 25000, 50000);
    device.configure_servo(servo_360_pos.clone())?;
    device.set_servo_angle(&servo_360_pos, 270.0)?; // Move to 270°
    
    // 360-degree speed servo (continuous rotation)
    // Datasheet: Counterclockwise 1-1.5ms, Stop 1.5ms, Clockwise 1.5-2ms
    let servo_speed = ServoConfig::three_sixty_speed(21, 37500, 50000, 25000);
    device.configure_servo(servo_speed.clone())?;
    device.set_servo_speed(&servo_speed, 50.0)?; // 50% clockwise
    device.stop_servo(&servo_speed)?; // Stop rotation
    
    Ok(())
}

Servo Types

  • 180° Position: Standard servo with 0-180° range
  • 360° Position: Multi-turn servo with 0-360° range
  • 360° Speed: Continuous rotation with speed control

Percentage-Based Control

For easier control, use percentage-based duty cycle functions:

use pokeys_lib::*;

fn main() -> Result<()> {
    let mut device = connect_to_device(0)?;
    
    // Set up PWM
    device.set_pwm_period(25000)?; // 1kHz for LED dimming
    device.enable_pwm_for_pin(22, true)?;
    
    // Fade LED from 0% to 100%
    for brightness in (0..=100).step_by(10) {
        device.set_pwm_duty_cycle_percent_for_pin(22, brightness as f32)?;
        println!(\"Brightness: {}%\", brightness);
        std::thread::sleep(std::time::Duration::from_millis(200));
    }
    
    // Turn off
    device.enable_pwm_for_pin(22, false)?;
    
    Ok(())
}

Multi-Channel Control

Control multiple PWM channels simultaneously:

use pokeys_lib::*;

fn main() -> Result<()> {
    let mut device = connect_to_device(0)?;
    
    // Configure PWM period (shared by all channels)
    device.set_pwm_period(500000)?; // 20ms for servo control
    
    // Enable multiple PWM channels
    let servo_pins = [22, 21, 20]; // PWM1, PWM2, PWM3
    for pin in servo_pins.iter() {
        device.enable_pwm_for_pin(*pin, true)?;
    }
    
    // Set different positions for each servo
    device.set_pwm_duty_cycle_for_pin(22, 60000)?; // Servo 1 to 0°
    device.set_pwm_duty_cycle_for_pin(21, 36000)?; // Servo 2 to 90°
    device.set_pwm_duty_cycle_for_pin(20, 12000)?; // Servo 3 to 180°
    
    println!("All servos positioned");
    
    // Disable all channels
    for pin in servo_pins.iter() {
        device.enable_pwm_for_pin(*pin, false)?;
    }
    
    Ok(())
}

Best Practices

✅ Do

  • • Calibrate servo positions for your hardware
  • • Use 20ms period (500,000 cycles) for servos
  • • Disable PWM when not in use
  • • Validate duty cycle values
  • • Use percentage functions for simple dimming

❌ Don't

  • • Set duty cycle higher than period
  • • Use zero period values
  • • Assume standard servo timing works for all servos
  • • Change period frequently during operation
  • • Forget to enable PWM before setting duty cycle

Troubleshooting

Servo Not Moving

Check that PWM is enabled and period is set to 500,000 cycles (20ms). Verify duty cycle values are appropriate for your servo (typically 25,000-50,000 cycles).

Incorrect Servo Positions

Servo timing varies by manufacturer. Use the calibration tool to find correct duty cycle values for 0°, 90°, and 180° positions. Speed servos require datasheet timing specifications.

Speed Servo Not Responding

Verify timing matches datasheet: counterclockwise (1-1.5ms), stop (1.5ms), clockwise (1.5-2ms). Use ServoConfig::three_sixty_speed with correct timing values.

PWM Not Working

Ensure you're using pins 17-22 only. Other pins don't support PWM. Check that the pin isn't configured for another function.