Hello,
I'm currently working on project to get a PWM signal running in software through the sysfs_gpio
library (not using a hardware timer, like in the sysfs_pwm
library does). I know it will be a little more jittery than a hardware timer, but I'm curious to see if it will be possible to do something like run a couple of ESCs with it.
I generally have the PWM itself working (via the code below), but have run into the issue putting the PWM signal itself (the loop turning the GPIO high and low) onto a different thread that only has read-access to the PWM struct's duty_cycle
and period
structures. I think this comes down to lifetimes, since the PWM struct could be dropped while the thread is still running.
I've tried several different methods, like making all of the PWM's internal fields Arc<Mutex<T>>
types, using crossbeam
's scoped threads, adding a new JoinHandle()
field to the struct, and throwing in a bunch of <'a>
lifetimes to see if that would help. Unfortunately, I usually end up with an error about unnamed lifetimes <'_>
that shows up, and my experience writing multi-threaded code outside of rayon's .iter() -> .par_iter()
methods doesn't seem to be sufficient. I was also considering somehow separating an mpsc (tx, rx)
channel and putting them in different parts of the struct, but was unsure if that was good practice.
I don't, of course, expect a complete solution, but any suggestions about avenues to explore (hopefully in a way that doesn't require reading/writing raw pointers across threads, although I guess that could work) or links to projects doing something similar would be really appreciated!
// using sysfs_gpio = "0.5" in my Cargo.toml file
use std::error::Error;
use std::thread::sleep;
use std::time::Duration;
#[derive(Debug)]
struct Pwm {
pin: sysfs_gpio::Pin,
period: std::time::Duration,
duty_cycle: f32,
direction: sysfs_gpio::Direction,
}
impl Pwm {
fn new(pin: u64, period: u64, duty_cycle: f32) -> Self {
let pwm = Pwm {
pin: sysfs_gpio::Pin::new(pin),
period: std::time::Duration::from_micros(period),
duty_cycle: duty_cycle,
direction: sysfs_gpio::Direction::Out,
};
pwm
}
fn direction(&mut self, direction: sysfs_gpio::Direction) -> Result<(), Box<dyn Error>> {
self.direction = direction;
self.pin.set_direction(self.direction)?;
Ok(())
}
fn export(&mut self) -> Result<(), Box<dyn Error>> {
self.pin.export()?;
Ok(())
}
// I saw a post somewhere about interior/exterior mutability, but I think I need &mut in order
// to change the Pin state
fn enable(&mut self) -> Result<(), Box<dyn Error>> {
// It would be great to put this loop in a new thread that handles writing to the Pin, while
// adjusting the loop based on reading values from the pwm.period and pwm.duty_cycle fields
loop {
let duty =
Duration::from_micros((self.period.as_micros() as f32 * self.duty_cycle) as u64);
self.pin.set_value(1)?;
sleep(duty);
self.pin.set_value(0)?;
sleep(self.period - duty);
}
Ok(())
}
fn duty_cycle(&mut self, duty_cycle: f32) {
self.duty_cycle = duty_cycle;
}
}
fn main() -> Result<(), Box<dyn Error>> {
let mut pwm = Pwm::new(23, 100_000, 0.75);
pwm.export()?; // Raspberry Pi BCM Pin 23, GPIO w/o hardware timer
pwm.enable()?; // The internal loop {} here is currently blocking
// sleep(Duration::from_millis(1000));
// pwm.set_duty_cycle(0.25)?; // We never get here
// Ideally, I'd like to be able to set up the PWM signal on a separate, non-blocking
// thread, and modify the signal's parameters via a .duty_cycle() or .period() method
// and have those characteristics change in real time
sleep(Duration::from_millis(1000));
Ok(())
}