Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PWM

Pico de Gallo provides 4 PWM output channels through the RP2350’s hardware PWM slices. Each channel maps to a specific GPIO pin and slice/channel combination.

Pin Mapping

PWM ChannelGPIOSliceSlice Channel
0126A
1136B
2147A
3157B

The total number of available channels is defined by the constant NUM_PWM_CHANNELS = 4. Channel indices are 0–3 in all APIs.

Operations

OperationDescription
Set DutySets the duty cycle for a channel (0–65535)
Get DutyReturns current and maximum duty cycle
EnableEnables PWM output on a channel
DisableDisables PWM output on a channel
Set ConfigConfigures frequency and phase-correct mode
Get ConfigReturns current configuration

LED Brightness Example

A common use case is driving an LED at variable brightness. Connect an LED (with appropriate current-limiting resistor) to one of the PWM pins and control its brightness through the duty cycle.

CLI

# 1. Configure channel 0 for 1 kHz, normal (not phase-correct) mode
gallo pwm set-config --channel 0 --frequency 1000

# 2. Enable PWM output on channel 0
gallo pwm enable --channel 0

# 3. Set duty cycle to 50% (32768 out of 65535)
gallo pwm set-duty --channel 0 --duty 32768

# 4. Read back the current duty cycle
gallo pwm get-duty --channel 0

# 5. Dim the LED to ~25%
gallo pwm set-duty --channel 0 --duty 16384

# 6. Read back the current configuration
gallo pwm get-config --channel 0

# 7. Switch to phase-correct mode at 500 Hz
gallo pwm set-config --channel 0 --frequency 500 --phase-correct

# 8. Disable the channel when done
gallo pwm disable --channel 0

Rust Library

#![allow(unused)]
fn main() {
use pico_de_gallo_lib::PicoDeGallo;

async fn led_brightness(gallo: &PicoDeGallo) {
    // Configure channel 0: 1 kHz, no phase-correct
    gallo.pwm_set_config(0, 1_000, false).await.unwrap();

    // Enable PWM output
    gallo.pwm_enable(0).await.unwrap();

    // Set 50% duty cycle
    gallo.pwm_set_duty_cycle(0, 32_768).await.unwrap();

    // Read back duty info
    let duty_info = gallo.pwm_get_duty_cycle(0).await.unwrap();
    println!(
        "Duty: {}/{} ({:.1}%)",
        duty_info.current_duty,
        duty_info.max_duty,
        duty_info.current_duty as f32 / duty_info.max_duty as f32 * 100.0
    );

    // Read back configuration
    let config = gallo.pwm_get_config(0).await.unwrap();
    println!(
        "Frequency: {} Hz, Phase-correct: {}, Enabled: {}",
        config.frequency_hz, config.phase_correct, config.enabled
    );

    // Disable when done
    gallo.pwm_disable(0).await.unwrap();
}
}

C (FFI)

#include "pico_de_gallo.h"
#include <stdio.h>

void led_brightness(PicoDeGallo *gallo) {
    /* Configure channel 0: 1 kHz, no phase-correct */
    gallo_pwm_set_config(gallo, 0, 1000, false);

    /* Enable PWM output */
    gallo_pwm_enable(gallo, 0);

    /* Set 50% duty cycle */
    gallo_pwm_set_duty_cycle(gallo, 0, 32768);

    /* Read back duty info */
    GalloPwmDutyCycleInfo duty_info;
    gallo_pwm_get_duty_cycle(gallo, 0, &duty_info);
    printf("Duty: %u/%u\n", duty_info.current_duty, duty_info.max_duty);

    /* Read back configuration */
    GalloPwmConfigurationInfo config_info;
    gallo_pwm_get_config(gallo, 0, &config_info);
    printf("Frequency: %u Hz, Phase-correct: %d, Enabled: %d\n",
           config_info.frequency_hz, config_info.phase_correct,
           config_info.enabled);

    /* Disable when done */
    gallo_pwm_disable(gallo, 0);
}

HAL

The HAL crate exposes individual PWM channels as embedded_hal::pwm::SetDutyCycle implementors, allowing use with any driver that accepts the standard trait.

#![allow(unused)]
fn main() {
use pico_de_gallo_hal::Hal;
use embedded_hal::pwm::SetDutyCycle;

fn servo_control(hal: &Hal) {
    let mut pwm = hal.pwm_channel(0);

    // SetDutyCycle trait methods
    pwm.set_duty_cycle(32_768).unwrap();

    let max = pwm.max_duty_cycle();
    println!("Max duty: {max}");

    // 75% duty cycle
    pwm.set_duty_cycle(max * 3 / 4).unwrap();

    // Fully on / fully off
    pwm.set_duty_cycle_fully_on().unwrap();
    pwm.set_duty_cycle_fully_off().unwrap();

    // Percentage-based (if available via trait extension)
    pwm.set_duty_cycle_percent(50).unwrap();
}
}

Lib API Reference

All library methods are async and return Result types. The PicoDeGallo instance is created with PicoDeGallo::new() (which is not async).

MethodReturn Type
pwm_set_duty_cycle(channel: u8, duty: u16)Result<(), PicoDeGalloError<PwmError>>
pwm_get_duty_cycle(channel: u8)Result<PwmDutyCycleInfo, PicoDeGalloError<PwmError>>
pwm_enable(channel: u8)Result<(), PicoDeGalloError<PwmError>>
pwm_disable(channel: u8)Result<(), PicoDeGalloError<PwmError>>
pwm_set_config(channel: u8, frequency_hz: u32, phase_correct: bool)Result<(), PicoDeGalloError<PwmError>>
pwm_get_config(channel: u8)Result<PwmConfigurationInfo, PicoDeGalloError<PwmError>>

Response Types

PwmDutyCycleInfo

FieldTypeDescription
max_dutyu16Maximum duty cycle value (65535)
current_dutyu16Currently configured duty cycle

PwmConfigurationInfo

FieldTypeDescription
frequency_hzu32Configured PWM frequency in Hz
phase_correctboolWhether phase-correct mode is enabled
enabledboolWhether the channel is currently enabled

FFI Reference

All FFI functions follow the gallo_pwm_* naming convention and return a Status code.

Status gallo_pwm_set_duty_cycle(PicoDeGallo *gallo, uint8_t channel, uint16_t duty);
Status gallo_pwm_get_duty_cycle(PicoDeGallo *gallo, uint8_t channel, GalloPwmDutyCycleInfo *out_info);
Status gallo_pwm_enable(PicoDeGallo *gallo, uint8_t channel);
Status gallo_pwm_disable(PicoDeGallo *gallo, uint8_t channel);
Status gallo_pwm_set_config(PicoDeGallo *gallo, uint8_t channel, uint32_t frequency, bool phase_correct);
Status gallo_pwm_get_config(PicoDeGallo *gallo, uint8_t channel, GalloPwmConfigurationInfo *out_info);

Hardware Setup

Connect an LED (or other PWM-compatible load) to one of the PWM pins with a current-limiting resistor:

GPIO 12 (PWM 0) ── 330Ω ──┬── LED ── GND
                           │
GPIO 13 (PWM 1) ── 330Ω ──┘  (or separate LEDs)

For servo motors, connect the signal wire directly to a PWM pin and configure for the servo’s expected frequency (typically 50 Hz). Adjust the duty cycle to control the servo position.