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 adjusments to /boot/config.txt
allows the Pi to boot without error and allows me run the following Rust code without any run time errors, but the fan (connected to the PWM) won’t spin.
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 objectr");
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 Quitsr");
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 Stoppedr"),
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)