Jamie
Jamie

Reputation: 7431

How can I utilize a Raspberry Pi's PWM peripheral hardware in rust?

I'm struggling to utilize the PWM peripheral hardware on a Raspberry Pi 4 (Model B Rev 1.5) with Rust code. I can bit bash the pin using python code (see below), but I can't get the rppal crate to work in Rust.

For the Rust/PWM peripheral, I've configured the device tree overlay with a line in /boot/config.txt:

dtoverlay=pwm,pin=12,func=4

And I've also commented out the audio in the same file with:

#dtparam=audio=on

These adjustments to /boot/config.txt allows the Pi to boot without error and allows me run both code snippets below without run time errors. The Python code is able to control the fan speed (connected to the GPIO/PWM pin) but the Rust implementation is unable to make fan spin at all.

What should I be looking at to figure this out?

Rust implementation:

use crossterm::{
    cursor,
    event::{self, Event, KeyCode, KeyEvent},
    execute, terminal,
};
use rppal::pwm::{Channel, Error, Pwm};
use std::io::{stdout, Write};

// Function to decode PWM errors
fn decode_pwm_error(err: Error, prefix: &str) {
    match err {
        Error::Io(e) => {
            println!("'{}': OS IO Error: '{}'\r", prefix, e)
        }
        Error::UnknownModel => {
            println!("'{}': Unknown model? Programming error?\r", prefix)
        }
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Configure the PWM channel (Channel 0 corresponds to GPIO12)
    let pwm = match Pwm::new(Channel::Pwm0) {
        Ok(p) => {
            println!("Instantiated object\r");
            p
        }
        Err(err) => {
            decode_pwm_error(err, "Instantiating");
            return Ok(());
        }
    };

    // Initialize the duty cycle to 50%
    let mut duty_cycle = 0.5;
    let frequency = 100.0;

    // Set the PWM frequency to 100 Hz and the duty cycle to 0% initially
    match pwm.set_frequency(frequency, duty_cycle) {
        Ok(_) => println!("Initial frequency set.\r"),
        Err(err) => {
            decode_pwm_error(err, "Setting frequency");
            return Ok(());
        }
    };

    // Set up the terminal
    let mut stdout = stdout();
    terminal::enable_raw_mode()?;
    execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?;

    println!("Use arrow keys to change the duty cycle.  ESC Quits\r");
    loop {
        // Display the current duty cycle
        println!("Current duty cycle: {:.1}%\r", duty_cycle * 100.0);

        // Wait for an event
        if let Event::Key(KeyEvent { code, .. }) = event::read()? {
            match code {
                KeyCode::Up => {
                    if duty_cycle < 1.0 {
                        duty_cycle += 0.01;
                    }
                    if duty_cycle > 1.0 {
                        duty_cycle = 1.0;
                    }
                }
                KeyCode::Down => {
                    if duty_cycle > 0.0 {
                        duty_cycle -= 0.01;
                    }
                    if duty_cycle < 0.0 {
                        duty_cycle = 0.0;
                    }
                }
                KeyCode::Esc => break,
                _ => {}
            }
            // Set the PWM duty cycle
            match pwm.set_duty_cycle(duty_cycle) {
                Ok(_) => {}
                Err(err) => {
                    decode_pwm_error(
                        err,
                        format!("Setting duty cycle to '{:.1}%'", duty_cycle * 100.0).as_str(),
                    );
                    return Ok(());
                }
            };
        }

        // Flush the output
        stdout.flush()?;
    }

    // Stop the PWM
    match pwm.disable() {
        Ok(_) => println!("PWM Stopped\r"),
        Err(err) => {
            decode_pwm_error(err, "Stopping PWM");
            return Ok(());
        }
    };

    // Restore the terminal
    execute!(stdout, terminal::LeaveAlternateScreen, cursor::Show)?;
    terminal::disable_raw_mode()?;

    Ok(())
}

In python, I can bit bash the output with the following code:

import RPi.GPIO as GPIO
import time
import curses

# Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(12, GPIO.OUT)
GPIO.setwarnings(False)

# Initialize PWM on GPIO 12 at 100 Hz
pwm = GPIO.PWM(12, 100)
pwm.start(0)  # Start with 0% duty cycle

def main(stdscr):
    # Clear screen and initialize curses
    stdscr.clear()
    curses.curs_set(0)
    stdscr.nodelay(1)  # Don't block on getch()
    stdscr.timeout(100)  # Refresh every 100 milliseconds

    duty_cycle = 0.0

    while True:
        stdscr.clear()
        stdscr.addstr(0, 0, "Use arrow keys to change the duty cycle.")
        stdscr.addstr(1, 0, "Current duty cycle: {:.1f}%".format(duty_cycle * 100))
        stdscr.addstr(2, 0, "Press ESC to exit.")
        stdscr.refresh()

        key = stdscr.getch()

        if key == curses.KEY_UP:
            if duty_cycle < 1.0:
                duty_cycle += 0.01
                if duty_cycle > 1.0:
                    duty_cycle = 1.0
                pwm.ChangeDutyCycle(duty_cycle * 100)
        elif key == curses.KEY_DOWN:
            if duty_cycle > 0.0:
                duty_cycle -= 0.01
                if duty_cycle < 0.0:
                    duty_cycle = 0.0
                pwm.ChangeDutyCycle(duty_cycle * 100)
        elif key == 27:  # ESC key
            break

        time.sleep(0.1)  # Small delay to prevent high CPU usage

    pwm.stop()
    GPIO.cleanup()

if __name__ == "__main__":
    curses.wrapper(main)

Upvotes: 0

Views: 110

Answers (0)

Related Questions