spwm 0.1.6

Software PWM for microcontrollers
Documentation

GitHub Actions Workflow Status docs.rs codecov

SPWM - Software PWM for Embedded Systems


A no_std Rust library for generating software-based Pulse Width Modulation (PWM) signals on microcontrollers and embedded systems. This crate provides a flexible, interrupt-driven PWM implementation that doesn't require dedicated hardware PWM peripherals.

Features

  • no_std compatible - Works in embedded environments without the standard library
  • Multiple independent channels - Configure up to N channels (compile-time constant)
  • Thread-safe - Uses atomic operations for safe access from interrupt contexts
  • Type-safe builder pattern - Compile-time guarantees for proper channel configuration
  • Flexible callbacks - Register callbacks for state changes and period completion
  • Dynamic updates - Change frequency and duty cycle at runtime

Basic Usage

Add this to your Cargo.toml:

[dependencies]
soft-pwm = "0.1"

Creating a Simple PWM Channel

use spwm::{Spwm, SpwmState};
// Create SPWM manager with hardware timer frequency of 100 kHz
// and space for 4 channels
let mut spwm = Spwm::<4>::new(100_000);
// Create a channel with 1 kHz frequency and 50% duty cycle
let channel = spwm
    .create_channel()
    .freq_hz(1_000)
    .duty_cycle(50)
    .on_off_callback(|state: &SpwmState| {
        match state {
            SpwmState::On => {
                // Turn your output pin HIGH
            }
            SpwmState::Off => {
                // Turn your output pin LOW
            }
        }
    })
    .period_callback(|| {
        // Called at the end of each PWM period
    })
    .build()?;
let channel_id = spwm.register_channel(channel)?;

// Enable the channel to start PWM generation
spwm.get_channel(channel_id).unwrap().enable()?;

In Your Timer Interrupt Handler

#[interrupt]
fn TIMER_IRQ() {
    spwm.irq_handler();
}

Requirements

  • Hardware timer that can interrupt at a consistent frequency
  • Timer frequency must be at least 100x the desired PWM channel frequency to achieve 1% duty cycle resolution capabilities
  • Callbacks must be short and non-blocking (they run in interrupt context)

Example

That example configures 4 channels:

  • PC9: 10 Hz, 50% duty cycle
  • PC8: 50 Hz, 10% duty cycle
  • PC6: 500 Hz, 50% duty cycle
  • PC5: 250 Hz, 64% duty cycle

Oscillogram that shows PC9 and PC8 output waveforms:

pwm_oscillogram

Simple LED PWM control example:

use spwm::{Spwm, SpwmState};

static mut LED_STATE: bool = false;

fn led_callback(state: &SpwmState) {
    LED_STATE = matches!(state, SpwmState::On);
    // Update your LED pin based on LED_STATE
}

let mut pwm = Spwm::<1>::new(100_000);
let channel = pwm
    .create_channel()
    .freq_hz(100) // 100 Hz PWM frequency
    .duty_cycle(25) // 25% brightness
    .on_off_callback(led_callback)
    .period_callback(|| {})
    .build()?;

let id = pwm.register_channel(channel)?;
pwm.get_channel(id).unwrap().enable()?;

License