From a4866ad2782b5f66ed1ea67620d4117b0d474ab5 Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 21 Apr 2023 00:57:28 +0200 Subject: [PATCH] rp: add PWM api --- embassy-rp/Cargo.toml | 3 +- embassy-rp/src/clocks.rs | 4 +- embassy-rp/src/lib.rs | 10 ++ embassy-rp/src/pwm.rs | 338 +++++++++++++++++++++++++++++++++++++ embassy-rp/src/usb.rs | 2 +- examples/rp/src/bin/pwm.rs | 27 +++ tests/rp/src/bin/pwm.rs | 142 ++++++++++++++++ 7 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 embassy-rp/src/pwm.rs create mode 100644 examples/rp/src/bin/pwm.rs create mode 100644 tests/rp/src/bin/pwm.rs diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index b7ed6ccb..f784ab33 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -60,8 +60,9 @@ chrono = { version = "0.4", default-features = false, optional = true } embedded-io = { version = "0.4.0", features = ["async"], optional = true } embedded-storage = { version = "0.3" } rand_core = "0.6.4" +fixed = "1.23.1" -rp-pac = { version = "1", features = ["rt"] } +rp-pac = { version = "2", features = ["rt"] } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true} diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 85c9bbb7..8a34b293 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -155,8 +155,8 @@ unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1: let cs = p.cs().read(); let prim = p.prim().read(); if cs.lock() - && cs.refdiv() == refdiv as _ - && p.fbdiv_int().read().fbdiv_int() == fbdiv as _ + && cs.refdiv() == refdiv as u8 + && p.fbdiv_int().read().fbdiv_int() == fbdiv as u16 && prim.postdiv1() == post_div1 && prim.postdiv2() == post_div2 { diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 3841bb83..aa866032 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -21,6 +21,7 @@ pub mod interrupt; pub mod pio; #[cfg(feature = "pio")] pub mod pio_instr_util; +pub mod pwm; #[cfg(feature = "pio")] pub mod relocate; @@ -109,6 +110,15 @@ embassy_hal_common::peripherals! { DMA_CH10, DMA_CH11, + PWM_CH0, + PWM_CH1, + PWM_CH2, + PWM_CH3, + PWM_CH4, + PWM_CH5, + PWM_CH6, + PWM_CH7, + USB, RTC, diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs new file mode 100644 index 00000000..d2bf7958 --- /dev/null +++ b/embassy-rp/src/pwm.rs @@ -0,0 +1,338 @@ +//! Pulse Width Modulation (PWM) + +use embassy_embedded_hal::SetConfig; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; +use fixed::traits::ToFixed; +use fixed::FixedU16; +use pac::pwm::regs::{ChDiv, Intr}; +use pac::pwm::vals::Divmode; + +use crate::gpio::sealed::Pin as _; +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::{pac, peripherals, RegExt}; + +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + pub invert_a: bool, + pub invert_b: bool, + pub phase_correct: bool, + pub enable: bool, + pub divider: fixed::FixedU16, + pub compare_a: u16, + pub compare_b: u16, + pub top: u16, +} + +impl Default for Config { + fn default() -> Self { + Self { + invert_a: false, + invert_b: false, + phase_correct: false, + enable: true, // differs from reset value + divider: 1.to_fixed(), + compare_a: 0, + compare_b: 0, + top: 0xffff, + } + } +} + +pub enum InputMode { + Level, + RisingEdge, + FallingEdge, +} + +impl From for Divmode { + fn from(value: InputMode) -> Self { + match value { + InputMode::Level => Divmode::LEVEL, + InputMode::RisingEdge => Divmode::RISE, + InputMode::FallingEdge => Divmode::FALL, + } + } +} + +pub struct Pwm<'d, T: Channel> { + inner: PeripheralRef<'d, T>, + pin_a: Option>, + pin_b: Option>, +} + +impl<'d, T: Channel> Pwm<'d, T> { + fn new_inner( + inner: impl Peripheral

+ 'd, + a: Option>, + b: Option>, + config: Config, + divmode: Divmode, + ) -> Self { + into_ref!(inner); + + let p = inner.regs(); + unsafe { + p.csr().modify(|w| { + w.set_divmode(divmode); + w.set_en(false); + }); + p.ctr().write(|w| w.0 = 0); + Self::configure(p, &config); + + if let Some(pin) = &a { + pin.io().ctrl().write(|w| w.set_funcsel(4)); + } + if let Some(pin) = &b { + pin.io().ctrl().write(|w| w.set_funcsel(4)); + } + } + Self { + inner, + pin_a: a.into(), + pin_b: b.into(), + } + } + + #[inline] + pub fn new_free(inner: impl Peripheral

+ 'd, config: Config) -> Self { + Self::new_inner(inner, None, None, config, Divmode::DIV) + } + + #[inline] + pub fn new_output_a( + inner: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(a); + Self::new_inner(inner, Some(a.map_into()), None, config, Divmode::DIV) + } + + #[inline] + pub fn new_output_b( + inner: impl Peripheral

+ 'd, + b: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(b); + Self::new_inner(inner, None, Some(b.map_into()), config, Divmode::DIV) + } + + #[inline] + pub fn new_output_ab( + inner: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(a, b); + Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, Divmode::DIV) + } + + #[inline] + pub fn new_input( + inner: impl Peripheral

+ 'd, + b: impl Peripheral

> + 'd, + mode: InputMode, + config: Config, + ) -> Self { + into_ref!(b); + Self::new_inner(inner, None, Some(b.map_into()), config, mode.into()) + } + + #[inline] + pub fn new_output_input( + inner: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

> + 'd, + mode: InputMode, + config: Config, + ) -> Self { + into_ref!(a, b); + Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, mode.into()) + } + + fn configure(p: pac::pwm::Channel, config: &Config) { + if config.divider > FixedU16::::from_bits(0xFF_F) { + panic!("Requested divider is too large"); + } + + unsafe { + p.div().write_value(ChDiv(config.divider.to_bits() as u32)); + p.cc().write(|w| { + w.set_a(config.compare_a); + w.set_b(config.compare_b); + }); + p.top().write(|w| w.set_top(config.top)); + p.csr().modify(|w| { + w.set_a_inv(config.invert_a); + w.set_b_inv(config.invert_b); + w.set_ph_correct(config.phase_correct); + w.set_en(config.enable); + }); + } + } + + #[inline] + pub unsafe fn phase_advance(&mut self) { + let p = self.inner.regs(); + p.csr().write_set(|w| w.set_ph_adv(true)); + while p.csr().read().ph_adv() {} + } + + #[inline] + pub unsafe fn phase_retard(&mut self) { + let p = self.inner.regs(); + p.csr().write_set(|w| w.set_ph_ret(true)); + while p.csr().read().ph_ret() {} + } + + #[inline] + pub fn counter(&self) -> u16 { + unsafe { self.inner.regs().ctr().read().ctr() } + } + + #[inline] + pub fn set_counter(&self, ctr: u16) { + unsafe { self.inner.regs().ctr().write(|w| w.set_ctr(ctr)) } + } + + #[inline] + pub fn wait_for_wrap(&mut self) { + while !self.wrapped() {} + self.clear_wrapped(); + } + + #[inline] + pub fn wrapped(&mut self) -> bool { + unsafe { pac::PWM.intr().read().0 & self.bit() != 0 } + } + + #[inline] + pub fn clear_wrapped(&mut self) { + unsafe { + pac::PWM.intr().write_value(Intr(self.bit() as _)); + } + } + + #[inline] + fn bit(&self) -> u32 { + 1 << self.inner.number() as usize + } +} + +pub struct PwmBatch(u32); + +impl PwmBatch { + #[inline] + pub fn enable(&mut self, pwm: &Pwm<'_, impl Channel>) { + self.0 |= pwm.bit(); + } + + #[inline] + pub fn set_enabled(enabled: bool, batch: impl FnOnce(&mut PwmBatch)) { + let mut en = PwmBatch(0); + batch(&mut en); + unsafe { + if enabled { + pac::PWM.en().write_set(|w| w.0 = en.0); + } else { + pac::PWM.en().write_clear(|w| w.0 = en.0); + } + } + } +} + +impl<'d, T: Channel> Drop for Pwm<'d, T> { + fn drop(&mut self) { + unsafe { + self.inner.regs().csr().write_clear(|w| w.set_en(false)); + if let Some(pin) = &self.pin_a { + pin.io().ctrl().write(|w| w.set_funcsel(31)); + } + if let Some(pin) = &self.pin_b { + pin.io().ctrl().write(|w| w.set_funcsel(31)); + } + } + } +} + +mod sealed { + pub trait Channel {} +} + +pub trait Channel: Peripheral

+ sealed::Channel + Sized + 'static { + fn number(&self) -> u8; + + fn regs(&self) -> pac::pwm::Channel { + pac::PWM.ch(self.number() as _) + } +} + +macro_rules! channel { + ($name:ident, $num:expr) => { + impl sealed::Channel for peripherals::$name {} + impl Channel for peripherals::$name { + fn number(&self) -> u8 { + $num + } + } + }; +} + +channel!(PWM_CH0, 0); +channel!(PWM_CH1, 1); +channel!(PWM_CH2, 2); +channel!(PWM_CH3, 3); +channel!(PWM_CH4, 4); +channel!(PWM_CH5, 5); +channel!(PWM_CH6, 6); +channel!(PWM_CH7, 7); + +pub trait PwmPinA: GpioPin {} +pub trait PwmPinB: GpioPin {} + +macro_rules! impl_pin { + ($pin:ident, $channel:ident, $kind:ident) => { + impl $kind for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, PWM_CH0, PwmPinA); +impl_pin!(PIN_1, PWM_CH0, PwmPinB); +impl_pin!(PIN_2, PWM_CH1, PwmPinA); +impl_pin!(PIN_3, PWM_CH1, PwmPinB); +impl_pin!(PIN_4, PWM_CH2, PwmPinA); +impl_pin!(PIN_5, PWM_CH2, PwmPinB); +impl_pin!(PIN_6, PWM_CH3, PwmPinA); +impl_pin!(PIN_7, PWM_CH3, PwmPinB); +impl_pin!(PIN_8, PWM_CH4, PwmPinA); +impl_pin!(PIN_9, PWM_CH4, PwmPinB); +impl_pin!(PIN_10, PWM_CH5, PwmPinA); +impl_pin!(PIN_11, PWM_CH5, PwmPinB); +impl_pin!(PIN_12, PWM_CH6, PwmPinA); +impl_pin!(PIN_13, PWM_CH6, PwmPinB); +impl_pin!(PIN_14, PWM_CH7, PwmPinA); +impl_pin!(PIN_15, PWM_CH7, PwmPinB); +impl_pin!(PIN_16, PWM_CH0, PwmPinA); +impl_pin!(PIN_17, PWM_CH0, PwmPinB); +impl_pin!(PIN_18, PWM_CH1, PwmPinA); +impl_pin!(PIN_19, PWM_CH1, PwmPinB); +impl_pin!(PIN_20, PWM_CH2, PwmPinA); +impl_pin!(PIN_21, PWM_CH2, PwmPinB); +impl_pin!(PIN_22, PWM_CH3, PwmPinA); +impl_pin!(PIN_23, PWM_CH3, PwmPinB); +impl_pin!(PIN_24, PWM_CH4, PwmPinA); +impl_pin!(PIN_25, PWM_CH4, PwmPinB); +impl_pin!(PIN_26, PWM_CH5, PwmPinA); +impl_pin!(PIN_27, PWM_CH5, PwmPinB); +impl_pin!(PIN_28, PWM_CH6, PwmPinA); +impl_pin!(PIN_29, PWM_CH6, PwmPinB); + +impl<'d, T: Channel> SetConfig for Pwm<'d, T> { + type Config = Config; + fn set_config(&mut self, config: &Self::Config) { + Self::configure(self.inner.regs(), config); + } +} diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 2e3708ef..a049e476 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -231,7 +231,7 @@ impl<'d, T: Instance> Driver<'d, T> { let len = (max_packet_size + 63) / 64 * 64; let addr = self.ep_mem_free; - if addr + len > EP_MEMORY_SIZE as _ { + if addr + len > EP_MEMORY_SIZE as u16 { warn!("Endpoint memory full"); return Err(EndpointAllocError); } diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs new file mode 100644 index 00000000..69d31555 --- /dev/null +++ b/examples/rp/src/bin/pwm.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_rp::pwm::{Config, Pwm}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut c: Config = Default::default(); + c.top = 0x8000; + c.compare_b = 8; + let mut pwm = Pwm::new_output_b(p.PWM_CH4, p.PIN_25, c.clone()); + + loop { + info!("current LED duty cycle: {}/32768", c.compare_b); + Timer::after(Duration::from_secs(1)).await; + c.compare_b = c.compare_b.rotate_left(4); + pwm.set_config(&c); + } +} diff --git a/tests/rp/src/bin/pwm.rs b/tests/rp/src/bin/pwm.rs new file mode 100644 index 00000000..b8cbe74c --- /dev/null +++ b/tests/rp/src/bin/pwm.rs @@ -0,0 +1,142 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{assert, assert_eq, assert_ne, *}; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::pwm::{Config, InputMode, Pwm}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // Connections on CI device: 6 -> 9, 7 -> 11 + let (mut p6, mut p7, mut p9, mut p11) = (p.PIN_6, p.PIN_7, p.PIN_9, p.PIN_11); + + let cfg = { + let mut c = Config::default(); + c.divider = 125.into(); + c.top = 10000; + c.compare_a = 5000; + c.compare_b = 5000; + c + }; + + // Test free-running clock + { + let pwm = Pwm::new_free(&mut p.PWM_CH3, cfg.clone()); + cortex_m::asm::delay(125); + let ctr = pwm.counter(); + assert!(ctr > 0); + assert!(ctr < 100); + cortex_m::asm::delay(125); + assert!(ctr < pwm.counter()); + } + + for invert_a in [false, true] { + info!("free-running, invert A: {}", invert_a); + let mut cfg = cfg.clone(); + cfg.invert_a = invert_a; + cfg.invert_b = !invert_a; + + // Test output from A + { + let pin1 = Input::new(&mut p9, Pull::None); + let _pwm = Pwm::new_output_a(&mut p.PWM_CH3, &mut p6, cfg.clone()); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pin1.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + } + + // Test output from B + { + let pin2 = Input::new(&mut p11, Pull::None); + let _pwm = Pwm::new_output_b(&mut p.PWM_CH3, &mut p7, cfg.clone()); + Timer::after(Duration::from_millis(1)).await; + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_ne!(pin2.is_high(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_ne!(pin2.is_high(), invert_a); + } + + // Test output from A+B + { + let pin1 = Input::new(&mut p9, Pull::None); + let pin2 = Input::new(&mut p11, Pull::None); + let _pwm = Pwm::new_output_ab(&mut p.PWM_CH3, &mut p6, &mut p7, cfg.clone()); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pin1.is_low(), invert_a); + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + assert_ne!(pin2.is_high(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_low(), invert_a); + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + assert_ne!(pin2.is_high(), invert_a); + } + } + + // Test level-gated + { + let mut pin2 = Output::new(&mut p11, Level::Low); + let pwm = Pwm::new_input(&mut p.PWM_CH3, &mut p7, InputMode::Level, cfg.clone()); + assert_eq!(pwm.counter(), 0); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pwm.counter(), 0); + pin2.set_high(); + Timer::after(Duration::from_millis(1)).await; + pin2.set_low(); + let ctr = pwm.counter(); + assert!(ctr >= 1000); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pwm.counter(), ctr); + } + + // Test rising-gated + { + let mut pin2 = Output::new(&mut p11, Level::Low); + let pwm = Pwm::new_input(&mut p.PWM_CH3, &mut p7, InputMode::RisingEdge, cfg.clone()); + assert_eq!(pwm.counter(), 0); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pwm.counter(), 0); + pin2.set_high(); + Timer::after(Duration::from_millis(1)).await; + pin2.set_low(); + assert_eq!(pwm.counter(), 1); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pwm.counter(), 1); + } + + // Test falling-gated + { + let mut pin2 = Output::new(&mut p11, Level::High); + let pwm = Pwm::new_input(&mut p.PWM_CH3, &mut p7, InputMode::FallingEdge, cfg.clone()); + assert_eq!(pwm.counter(), 0); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pwm.counter(), 0); + pin2.set_low(); + Timer::after(Duration::from_millis(1)).await; + pin2.set_high(); + assert_eq!(pwm.counter(), 1); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pwm.counter(), 1); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +}