//! This example shows how to create a pwm using the PIO module in the RP2040 chip. #![no_std] #![no_main] use core::time::Duration; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; use embassy_rp::pio::{Instance, InterruptHandler, Pio}; use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical const REFRESH_INTERVAL: u64 = 20000; // The period of each cycle bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); pub struct ServoBuilder<'d, T: Instance, const SM: usize> { pwm: PioPwm<'d, T, SM>, period: Duration, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, } impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { Self { pwm, period: Duration::from_micros(REFRESH_INTERVAL), min_pulse_width: Duration::from_micros(DEFAULT_MIN_PULSE_WIDTH), max_pulse_width: Duration::from_micros(DEFAULT_MAX_PULSE_WIDTH), max_degree_rotation: DEFAULT_MAX_DEGREE_ROTATION, } } pub fn set_period(mut self, duration: Duration) -> Self { self.period = duration; self } pub fn set_min_pulse_width(mut self, duration: Duration) -> Self { self.min_pulse_width = duration; self } pub fn set_max_pulse_width(mut self, duration: Duration) -> Self { self.max_pulse_width = duration; self } pub fn set_max_degree_rotation(mut self, degree: u64) -> Self { self.max_degree_rotation = degree; self } pub fn build(mut self) -> Servo<'d, T, SM> { self.pwm.set_period(self.period); Servo { pwm: self.pwm, min_pulse_width: self.min_pulse_width, max_pulse_width: self.max_pulse_width, max_degree_rotation: self.max_degree_rotation, } } } pub struct Servo<'d, T: Instance, const SM: usize> { pwm: PioPwm<'d, T, SM>, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, } impl<'d, T: Instance, const SM: usize> Servo<'d, T, SM> { pub fn start(&mut self) { self.pwm.start(); } pub fn stop(&mut self) { self.pwm.stop(); } pub fn write_time(&mut self, duration: Duration) { self.pwm.write(duration); } pub fn rotate(&mut self, degree: u64) { let degree_per_nano_second = (self.max_pulse_width.as_nanos() as u64 - self.min_pulse_width.as_nanos() as u64) / self.max_degree_rotation; let mut duration = Duration::from_nanos(degree * degree_per_nano_second + self.min_pulse_width.as_nanos() as u64); if self.max_pulse_width < duration { duration = self.max_pulse_width; } self.pwm.write(duration); } } #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); let prg = PioPwmProgram::new(&mut common); let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); let mut servo = ServoBuilder::new(pwm_pio) .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. .set_max_pulse_width(Duration::from_micros(2600)) // Along with this value. .build(); servo.start(); let mut degree = 0; loop { degree = (degree + 1) % 120; servo.rotate(degree); Timer::after_millis(50).await; } }