From ffa39f65f5005d6f44f743cded53aa679a7f5ac3 Mon Sep 17 00:00:00 2001 From: Hmvp Date: Thu, 2 Sep 2021 01:37:17 +0200 Subject: [PATCH] Pwm improvements (#61) * Reimplement PWM driver using typestates * Improve docs --- boards/pico/examples/pico_pwm_blink.rs | 36 +- rp2040-hal/examples/pwm_blink.rs | 30 +- rp2040-hal/src/pwm.rs | 271 ---------- rp2040-hal/src/pwm/dyn_slice.rs | 30 ++ rp2040-hal/src/pwm/mod.rs | 661 +++++++++++++++++++++++++ rp2040-hal/src/pwm/reg.rs | 98 ++++ 6 files changed, 827 insertions(+), 299 deletions(-) delete mode 100644 rp2040-hal/src/pwm.rs create mode 100644 rp2040-hal/src/pwm/dyn_slice.rs create mode 100644 rp2040-hal/src/pwm/mod.rs create mode 100644 rp2040-hal/src/pwm/reg.rs diff --git a/boards/pico/examples/pico_pwm_blink.rs b/boards/pico/examples/pico_pwm_blink.rs index 37848e6..1a2c655 100644 --- a/boards/pico/examples/pico_pwm_blink.rs +++ b/boards/pico/examples/pico_pwm_blink.rs @@ -15,8 +15,9 @@ use pico::{ pwm::*, watchdog::Watchdog, }, - XOSC_CRYSTAL_FREQ, + Pins, XOSC_CRYSTAL_FREQ, }; +use rp2040_hal::sio::Sio; #[link_section = ".boot2"] #[used] @@ -44,32 +45,37 @@ fn main() -> ! { .ok() .unwrap(); - let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); - - let mut pwm_pin = Pwm4::new(25); - - //Instead of having it take references to all of these pac objects, eventually this should just - //take ownership of a GPIO pin. - pwm_pin.default_config( - &mut pac.PWM, - &mut pac.PADS_BANK0, - &mut pac.IO_BANK0, + let sio = Sio::new(pac.SIO); + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, &mut pac.RESETS, ); - pwm_pin.set_ph_correct(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); - pwm_pin.enable(); + // Init PWMs + let mut pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Use B channel (which outputs to GPIO 25) + let channel = &mut pwm.channel_b; + channel.output_to(pins.led); loop { for i in (LOW..=HIGH).skip(100) { delay.delay_us(8); - pwm_pin.set_duty(i); + channel.set_duty(i); } for i in (LOW..=HIGH).rev().skip(100) { delay.delay_us(8); - pwm_pin.set_duty(i); + channel.set_duty(i); } delay.delay_ms(500); diff --git a/rp2040-hal/examples/pwm_blink.rs b/rp2040-hal/examples/pwm_blink.rs index 585e48f..afd194a 100644 --- a/rp2040-hal/examples/pwm_blink.rs +++ b/rp2040-hal/examples/pwm_blink.rs @@ -4,7 +4,7 @@ use cortex_m_rt::entry; use embedded_hal::PwmPin; use panic_halt as _; -use rp2040_hal::pwm::*; +use rp2040_hal::{gpio::Pins, pwm::*, sio::Sio}; #[link_section = ".boot2"] #[used] @@ -14,26 +14,30 @@ pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; fn main() -> ! { let mut pac = rp2040_pac::Peripherals::take().unwrap(); - let mut pwm_pin = Pwm4::new(25); - - //Instead of having it take references to all of these pac objects, eventually this should just - //take ownership of a GPIO pin. - pwm_pin.default_config( - &mut pac.PWM, - &mut pac.PADS_BANK0, - &mut pac.IO_BANK0, + let sio = Sio::new(pac.SIO); + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, &mut pac.RESETS, ); - pwm_pin.set_ph_correct(); + // Init PWMs + let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); - pwm_pin.enable(); + // Configure PWM4 + let mut pwm = pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + // Use B channel (which outputs to GPIO 25) + let mut channel = pwm.channel_b; + channel.output_to(pins.gpio25); loop { - pwm_pin.set_duty(15000); + channel.set_duty(15000); // TODO: Replace with proper delays once we have clocks working cortex_m::asm::delay(5_000_000); - pwm_pin.set_duty(30000); + channel.set_duty(30000); cortex_m::asm::delay(5_000_000); } } diff --git a/rp2040-hal/src/pwm.rs b/rp2040-hal/src/pwm.rs deleted file mode 100644 index b6bfb30..0000000 --- a/rp2040-hal/src/pwm.rs +++ /dev/null @@ -1,271 +0,0 @@ -//! Pulse Width Modulation (PWM) -//! -//! To access the PWM pins you must call the 'split' method on the PWM. This will return a -//! `_____` struct with access to each PWM pin: -//! -//! ```no_run -//! use embedded_hal::PwmPin; -//! use rp2040_hal::{pac, pwm::Pwm0}; -//! let mut peripherals = pac::Peripherals::take().unwrap(); -//! let pin_num = 0; -//! let mut pwm_pin = Pwm0::new(pin_num); -//! ``` -//! -//! Once you have the PWM pins struct, you can take individual pins and configure them: -//! -//! ```no_run -//! # use embedded_hal::PwmPin; -//! # use rp2040_hal::{pac, pwm::Pwm0}; -//! # let mut peripherals = pac::Peripherals::take().unwrap(); -//! # let pin_num = 0; -//! # let mut pwm_pin = Pwm0::new(pin_num); -//! pwm_pin.default_config(&mut peripherals.PWM, &mut peripherals.PADS_BANK0, &mut peripherals.IO_BANK0, &mut peripherals.RESETS); -//! pwm_pin.set_duty(32767); -//! pwm_pin.enable(); -//! ``` -//! -//! The following configuration options are also available: -//! -//! ```no_run -//! # use embedded_hal::PwmPin; -//! # use rp2040_hal::{pac, pwm::Pwm0}; -//! # let mut peripherals = pac::Peripherals::take().unwrap(); -//! # let pin_num = 0; -//! # let mut pwm_pin = Pwm0::new(pin_num); -//! pwm_pin.min_config(&mut peripherals.PWM, &mut peripherals.PADS_BANK0, &mut peripherals.IO_BANK0, &mut peripherals.RESETS); -//! -//! pwm_pin.get_duty(); -//! pwm_pin.get_max_duty(); -//! -//! pwm_pin.set_ph_correct(); // Run in phase correct mode -//! pwm_pin.clr_ph_correct(); // Don't run in phase correct mode -//! -//! pwm_pin.set_div_int(1u8); // To set integer part of clock divider -//! pwm_pin.set_div_frac(0u8); // To set fractional part of clock divider -//! -//! pwm_pin.set_inv(); // Invert the output -//! pwm_pin.clr_inv(); // Don't invert the output -//! -//! pwm_pin.set_top(u16::MAX); // To set the TOP register -//! -//! pwm_pin.divmode_div(); // Default divmode. Counts up at a rate dictated by div. -//! pwm_pin.divmode_level(); // These 3 divmodes can be used with a PWM B pin to read PWM inputs. -//! pwm_pin.divmode_rise(); -//! pwm_pin.divmode_fall(); -//! ``` -//! -//! default_config() sets ph_correct to false, the clock divider to 1, does not invert the output, sets top to 65535, and resets the counter. -//! min_config() leaves those registers in the state they were before it was called (Careful, this can lead to unexpected behavior) -//! It's recommended to only call min_config() after calling default_config() on a pin that shares a PWM block. - -use super::*; -use crate::resets::SubsystemReset; -use embedded_hal::PwmPin; - -macro_rules! pwm { - ($PWMX:ident, $pwmx:ident, [ - $($PXi:ident: ($pxi:ident, $pwms:expr, $pins:expr, $i:expr),)+]) => { - $( - -#[doc = "Struct for any of the "] -#[doc = $pwms] -#[doc = " pins"] -pub struct $PXi { - pin: usize -} - -impl $PXi { - #[doc = "Constructor for a PWM pin struct"] - pub fn new (pin: usize) -> Self { - let mut pin_num : usize = 255; - for i in 0..$pins.len() { - if (pin == $pins[i]) { - pin_num = pin; - } - } - - if (pin_num == 255) { - pin_num = $pins[0]; - } - - Self { - pin: pin_num, - } - } - - // TODO: This function contains all the methods that required the PWM to have an instance of PADS_BANK0, RESETS, or IO_BANK0. - // Since the GPIO pins take ownership of these, after the GPIO refactor, this method should be moved into gpio.rs, and the PWM - // will instead receive a single gpio pin. - fn init_io(&self, pwm: &mut pac::$PWMX, pad : &mut pac::PADS_BANK0, io : &mut pac::IO_BANK0, resets: &mut pac::RESETS) -> () { - //TODO: Merge these into gpio.rs split function after GPIO refactor. At the moment, this is here because these need to be reset for - // the PWM to work. However, because they're here, they'll be reset every time a new PWM pin is created (BAD). - pwm.reset_bring_up(resets); - io.reset_bring_up(resets); - - pad.gpio[self.pin].modify(|_, w| w.ie().set_bit()); - pad.gpio[self.pin].modify(|_, w| w.od().clear_bit()); - unsafe { - io.gpio[self.pin].gpio_ctrl.write_with_zero(|w| w.funcsel().pwm_a_0()); - } - } - - fn cc(&self) -> &pac::$pwmx::ch::CC { - unsafe { - &(*pac::$PWMX::ptr()).ch[$i].cc - } - } - - fn csr(&self) -> &pac::$pwmx::ch::CSR { - unsafe { - &(*pac::$PWMX::ptr()).ch[$i].csr - } - } - - fn ctr(&self) -> &pac::$pwmx::ch::CTR { - unsafe { - &(*pac::$PWMX::ptr()).ch[$i].ctr - } - } - - fn div(&self) -> &pac::$pwmx::ch::DIV { - unsafe { - &(*pac::$PWMX::ptr()).ch[$i].div - } - } - - fn top(&self) -> &pac::$pwmx::ch::TOP { - unsafe { - &(*pac::$PWMX::ptr()).ch[$i].top - } - } - - #[doc = "Sets up a pin with the default configurations"] - pub fn default_config(&mut self, pwm: &mut pac::$PWMX, pad: &mut pac::PADS_BANK0, io: &mut pac::IO_BANK0, resets: &mut pac::RESETS) -> () { - self.init_io(pwm, pad, io, resets); - - self.clr_ph_correct(); - self.set_div_int(1); - self.set_div_frac(0); - self.divmode_div(); - self.set_top(0xffffu16); - self.ctr().write(|w| unsafe { w.ctr().bits(0x0000u16) }); //Reset the counter - - self.set_duty(0); //Default duty cycle of 0% - self.clr_inv(); //Don't invert the channel - } - - #[doc = "Sets up a pin with minimum configurations"] - pub fn min_config(&mut self, pwm: &mut pac::$PWMX, pad: &mut pac::PADS_BANK0, io: &mut pac::IO_BANK0, resets: &mut pac::RESETS) -> () { - self.init_io(pwm, pad, io, resets); - } - - #[doc = "Enables phase correct mode"] - pub fn set_ph_correct(&self) { - self.csr().modify(|_, w| w.ph_correct().set_bit()); - } - - #[doc = "Disales phase correct mode"] - pub fn clr_ph_correct(&self) { - self.csr().modify(|_, w| w.ph_correct().clear_bit()); - } - - #[doc = "Sets the integer part of the clock divider"] - pub fn set_div_int(&self, value: u8) { - self.div().modify(|_, w| unsafe { w.int().bits(value) }); - } - - #[doc = "Sets the fractional part of the clock divider"] - pub fn set_div_frac(&self, value: u8) { - self.div().modify(|_, w| unsafe { w.frac().bits(value) }); - } - - #[doc = "Enables output inversion"] - pub fn set_inv(&self) { - if (self.pin % 2 == 0) { - self.csr().modify(|_, w| w.a_inv().set_bit()); - } else { - self.csr().modify(|_, w| w.b_inv().set_bit()); - } - } - - #[doc = "Disables output inversion"] - pub fn clr_inv(&self) { - if (self.pin % 2 == 0) { - self.csr().modify(|_, w| w.a_inv().clear_bit()); - } else { - self.csr().modify(|_, w| w.b_inv().clear_bit()); - } - } - - #[doc = "Sets the top register value"] - pub fn set_top(&self, value: u16) { - self.top().write(|w| unsafe { w.top().bits(value) }); - } - - #[doc = "Sets the divmode to div. Use this if you aren't reading a PWM input."] - pub fn divmode_div(&self) { - self.csr().modify(|_, w| w.divmode().div()); - } - - #[doc = "Sets the divmode to level."] - pub fn divmode_level(&self) { - self.csr().modify(|_, w| w.divmode().level()); - } - - #[doc = "Sets the divmode to rise."] - pub fn divmode_rise(&self) { - self.csr().modify(|_, w| w.divmode().rise()); - } - - #[doc = "Sets the divmode to fall."] - pub fn divmode_fall(&self) { - self.csr().modify(|_, w| w.divmode().div()); - } -} - -impl PwmPin for $PXi { - type Duty = u16; - - fn disable(&mut self) -> () { - self.csr().modify(|_, w| w.en().clear_bit()); - } - - fn enable(&mut self) -> () { - self.csr().modify(|_, w| w.en().set_bit()); - } - - fn get_duty(&self) -> Self::Duty { - if (self.pin % 2 == 0) { - self.cc().read().a().bits() - } else { - self.cc().read().b().bits() - } - } - - fn get_max_duty(&self) -> Self::Duty { - self.top().read().top().bits() - } - - fn set_duty(&mut self, duty: Self::Duty) { - if (self.pin % 2 == 0) { - self.cc().modify(|_, w| unsafe { w.a().bits(duty) }); - } else { - self.cc().modify(|_, w| unsafe { w.b().bits(duty) }); - } - } -} - -)+}} - -pwm! { - PWM, pwm, [ - Pwm0: (pwm0, "pwm0", [0, 1, 16, 18], 0), - Pwm1: (pwm1, "pwm1", [2, 3, 18, 19], 1), - Pwm2: (pwm2, "pwm2", [4, 5, 20, 21], 2), - Pwm3: (pwm3, "pwm3", [6, 7, 22, 23], 3), - Pwm4: (pwm4, "pwm4", [8, 9, 24, 25], 4), - Pwm5: (pwm5, "pwm5", [10, 11, 26, 27], 5), - Pwm6: (pwm6, "pwm6", [12, 13, 28, 29], 6), - Pwm7: (pwm7, "pwm7", [14, 15], 7), - ] -} diff --git a/rp2040-hal/src/pwm/dyn_slice.rs b/rp2040-hal/src/pwm/dyn_slice.rs new file mode 100644 index 0000000..dec3aff --- /dev/null +++ b/rp2040-hal/src/pwm/dyn_slice.rs @@ -0,0 +1,30 @@ +//! Semi-internal enums mostly used in typelevel magic + +/// Value-level `struct` representing slice IDs +#[derive(PartialEq, Clone, Copy)] +pub struct DynSliceId { + /// Slice id + pub num: u8, +} + +/// Slice modes +#[derive(PartialEq, Clone, Copy)] +pub enum DynSliceMode { + /// Count continuously whenever the slice is enabled + FreeRunning, + /// Count continuously when a high level is detected on the B pin + InputHighRunning, + /// Count once with each rising edge detected on the B pin + CountRisingEdge, + /// Count once with each falling edge detected on the B pin + CountFallingEdge, +} + +/// Channel ids +#[derive(PartialEq, Clone, Copy)] +pub enum DynChannelId { + /// Channel A + A, + /// Channel B + B, +} diff --git a/rp2040-hal/src/pwm/mod.rs b/rp2040-hal/src/pwm/mod.rs new file mode 100644 index 0000000..2282bc8 --- /dev/null +++ b/rp2040-hal/src/pwm/mod.rs @@ -0,0 +1,661 @@ +//! Pulse Width Modulation (PWM) +//! +//! First you must create a Slices struct which contains all the pwm slices. +//! +//! ```no_run +//! use rp2040_hal::{prelude::*, pwm::{InputHighRunning, Slices}}; +//! +//! +//! let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! +//! // Init PWMs +//! let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! +//! // Configure PWM4 +//! let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); +//! pwm.enable(); +//! +//! // Set to run when b channel is high +//! let pwm = pwm.into_mode::(); +//! ``` +//! +//! Once you have the PWM slice struct, you can add individual pins: +//! +//! ```no_run +//! # use rp2040_hal::{prelude::*, gpio::Pins, sio::Sio, pwm::{InputHighRunning, Slices}}; +//! # let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4.into_mode::(); +//! # let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! # +//! # let sio = Sio::new(pac.SIO); +//! # let pins = Pins::new( +//! # pac.IO_BANK0, +//! # pac.PADS_BANK0, +//! # sio.gpio_bank0, +//! # &mut pac.RESETS, +//! # ); +//! # +//! use embedded_hal::PwmPin; +//! +//! // Use B channel (which inputs from GPIO 25) +//! let mut channel_b = pwm.channel_b; +//! let channel_pin_b = channel_b.input_from(pins.gpio25); +//! +//! // Use A channel (which outputs to GPIO 24) +//! let mut channel_a = pwm.channel_a; +//! let channel_pin_a = channel_a.output_to(pins.gpio24); +//! +//! // Set duty cycle +//! channel_a.set_duty(0x00ff); +//! channel_a.get_duty(); +//! channel_a.set_inverted(); // Invert the output +//! channel_a.clr_inverted(); // Don't invert the output +//! ``` +//! +//! The following configuration options are also available: +//! +//! ```no_run +//! # use rp2040_hal::{prelude::*, pwm::Slices}; +//! # let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); // Run in phase correct mode +//! pwm.clr_ph_correct(); // Don't run in phase correct mode +//! +//! pwm.set_div_int(1u8); // To set integer part of clock divider +//! pwm.set_div_frac(0u8); // To set fractional part of clock divider +//! +//! pwm.get_top(); // To set the TOP register +//! pwm.set_top(u16::MAX); // To set the TOP register +//! +//! ``` +//! +//! default_config() sets ph_correct to false, the clock divider to 1, does not invert the output, sets top to 65535, and resets the counter. +//! min_config() leaves those registers in the state they were before it was called (Careful, this can lead to unexpected behavior) +//! It's recommended to only call min_config() after calling default_config() on a pin that shares a PWM block. + +use core::marker::PhantomData; + +use crate::{ + gpio::{ + bank0::*, FunctionClock, FunctionI2C, FunctionPio0, FunctionPio1, FunctionPwm, FunctionSpi, + FunctionUart, FunctionUsbAux, FunctionXip, Input, InputConfig, Output, OutputConfig, Pin, + PinId, PinMode, ValidPinMode, + }, + resets::SubsystemReset, + typelevel::Sealed, +}; +use embedded_hal::PwmPin; +use pac::PWM; + +pub mod dyn_slice; +pub use dyn_slice::*; + +mod reg; + +use reg::RegisterInterface; + +/// Used to pin traits to a specific channel (A or B) +pub trait ChannelId: Sealed { + /// Corresponding [`DynChannelId`](dyn_slice::DynChannelId) + const DYN: DynChannelId; +} + +/// Channel A +/// +/// These are attached to the even gpio pins and can only do PWM output +pub enum A {} + +/// Channel B +/// +/// These are attached to the odd gpio pins and can do PWM output and edge counting for input +pub enum B {} + +impl ChannelId for A { + const DYN: DynChannelId = DynChannelId::A; +} +impl ChannelId for B { + const DYN: DynChannelId = DynChannelId::B; +} +impl Sealed for A {} +impl Sealed for B {} + +/// Counter is free-running, and will count continuously whenever the slice is enabled +pub struct FreeRunning; +/// Count continuously when a high level is detected on the B pin +pub struct InputHighRunning; +/// Count once with each rising edge detected on the B pin +pub struct CountRisingEdge; +/// Count once with each falling edge detected on the B pin +pub struct CountFallingEdge; + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceMode: Sealed {} + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceInputMode: Sealed + ValidSliceMode {} + +/// Mode for slice +pub trait SliceMode: Sealed + Sized { + /// Corresponding [`DynSliceMode`](dyn_slice::DynSliceMode) + const DYN: DynSliceMode; +} + +impl Sealed for FreeRunning {} +impl SliceMode for FreeRunning { + const DYN: DynSliceMode = DynSliceMode::FreeRunning; +} +impl Sealed for InputHighRunning {} +impl SliceMode for InputHighRunning { + const DYN: DynSliceMode = DynSliceMode::InputHighRunning; +} +impl Sealed for CountRisingEdge {} +impl SliceMode for CountRisingEdge { + const DYN: DynSliceMode = DynSliceMode::CountRisingEdge; +} +impl Sealed for CountFallingEdge {} +impl SliceMode for CountFallingEdge { + const DYN: DynSliceMode = DynSliceMode::CountFallingEdge; +} + +impl ValidSliceMode for FreeRunning {} +impl ValidSliceMode for InputHighRunning {} +impl ValidSliceMode for CountRisingEdge {} +impl ValidSliceMode for CountFallingEdge {} +impl ValidSliceInputMode for InputHighRunning {} +impl ValidSliceInputMode for CountRisingEdge {} +impl ValidSliceInputMode for CountFallingEdge {} + +//============================================================================== +// Slice IDs +//============================================================================== + +/// Type-level `enum` for slice IDs +pub trait SliceId: Sealed { + /// Corresponding [`DynSliceId`](dyn_slice::DynSliceId) + const DYN: DynSliceId; + /// [`SliceMode`] at reset + type Reset; +} + +macro_rules! slice_id { + ($Id:ident, $NUM:literal, $reset : ident) => { + $crate::paste::paste! { + #[doc = "Slice ID representing slice " $NUM] + pub enum $Id {} + impl Sealed for $Id {} + impl SliceId for $Id { + type Reset = $reset; + const DYN: DynSliceId = DynSliceId { num: $NUM }; + } + } + }; +} + +//============================================================================== +// Registers +//============================================================================== + +/// Provide a safe register interface for [`Slice`]s +/// +/// This `struct` takes ownership of a [`SliceId`] and provides an API to +/// access the corresponding registers. +struct Registers { + id: PhantomData, +} + +// [`Registers`] takes ownership of the [`SliceId`], and [`Slice`] guarantees that +// each slice is a singleton, so this implementation is safe. +unsafe impl RegisterInterface for Registers { + #[inline] + fn id(&self) -> DynSliceId { + I::DYN + } +} + +impl Registers { + /// Create a new instance of [`Registers`] + /// + /// # Safety + /// + /// Users must never create two simultaneous instances of this `struct` with + /// the same [`SliceId`] + #[inline] + unsafe fn new() -> Self { + Registers { id: PhantomData } + } + + /// Provide a type-level equivalent for the + /// [`RegisterInterface::change_mode`] method. + #[inline] + fn change_mode>(&mut self) { + RegisterInterface::do_change_mode(self, M::DYN); + } +} + +/// Pwm slice +pub struct Slice +where + I: SliceId, + M: SliceMode + ValidSliceMode, +{ + regs: Registers, + mode: PhantomData, + /// Channel A (always output) + pub channel_a: Channel, + /// Channel B (input or output) + pub channel_b: Channel, +} + +impl Slice +where + I: SliceId, + M: SliceMode + ValidSliceMode, +{ + /// Create a new [`Slice`] + /// + /// # Safety + /// + /// Each [`Slice`] must be a singleton. For a given [`SliceId`], there must be + /// at most one corresponding [`Slice`] in existence at any given time. + /// Violating this requirement is `unsafe`. + #[inline] + pub(crate) unsafe fn new() -> Slice { + Slice { + regs: Registers::new(), + mode: PhantomData, + channel_a: Channel::new(0), + channel_b: Channel::new(0), + } + } + + /// Convert the slice to the requested [`SliceMode`] + #[inline] + pub fn into_mode>(mut self) -> Slice { + if N::DYN != M::DYN { + self.regs.change_mode::(); + } + // Safe because we drop the existing slice + unsafe { Slice::new() } + } + + /// Set a default config for the slice + pub fn default_config(&mut self) { + self.regs.write_ph_correct(false); + self.regs.write_div_int(1); // No divisor + self.regs.write_div_frac(0); // No divisor + self.regs.write_inv_a(false); //Don't invert the channel + self.regs.write_inv_b(false); //Don't invert the channel + self.regs.write_top(0xffff); // Wrap at max + self.regs.write_ctr(0x0000); //Reset the counter + self.regs.write_cc_a(0); //Default duty cycle of 0% + self.regs.write_cc_b(0); //Default duty cycle of 0% + } + + /// Advance the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn advance_phase(&mut self) { + self.regs.advance_phase() + } + + /// Retard the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn retard_phase(&mut self) { + self.regs.retard_phase() + } + + /// Enable phase correct mode + #[inline] + pub fn set_ph_correct(&mut self) { + self.regs.write_ph_correct(true) + } + + /// Disables phase correct mode + #[inline] + pub fn clr_ph_correct(&mut self) { + self.regs.write_ph_correct(false) + } + + /// Enable slice + #[inline] + pub fn enable(&mut self) { + self.regs.write_enable(true); + } + + /// Disable slice + #[inline] + pub fn disable(&mut self) { + self.regs.write_enable(false) + } + + /// Sets the integer part of the clock divider + #[inline] + pub fn set_div_int(&mut self, value: u8) { + self.regs.write_div_int(value) + } + + /// Sets the fractional part of the clock divider + #[inline] + pub fn set_div_frac(&mut self, value: u8) { + self.regs.write_div_frac(value) + } + + /// Get the counter register value + #[inline] + pub fn get_counter(&self) -> u16 { + self.regs.read_ctr() + } + + /// Set the counter register value + #[inline] + pub fn set_counter(&mut self, value: u16) { + self.regs.write_ctr(value) + } + + /// Get the top register value + #[inline] + pub fn get_top(&self) -> u16 { + self.regs.read_top() + } + + /// Sets the top register value + #[inline] + pub fn set_top(&mut self, value: u16) { + self.regs.write_top(value) + } +} + +macro_rules! pwm { + ($PWMX:ident, [ + $($SXi:ident: ($slice:literal, [$($pin_a:ident, $pin_b:ident),*], $i:expr)),+ + ]) => { + $( + slice_id!($SXi, $slice, FreeRunning); + + $( + impl ValidPwmOutputPin<$SXi, A> for $pin_a {} + impl ValidPwmOutputPin<$SXi, B> for $pin_b {} + impl ValidPwmInputPin<$SXi> for $pin_b {} + )* + )+ + + $crate::paste::paste!{ + + /// Collection of all the individual [`Slices`]s + pub struct Slices { + _pwm: $PWMX, + $( + #[doc = "Slice " $SXi] + pub [<$SXi:lower>] : Slice<$SXi,<$SXi as SliceId>::Reset>, + )+ + } + + impl Slices { + /// Take ownership of the PAC peripheral and split it into discrete [`Slice`]s + pub fn new(pwm: $PWMX, reset : &mut pac::RESETS) -> Self { + pwm.reset_bring_up(reset); + unsafe { + Self { + _pwm: pwm, + $( + [<$SXi:lower>]: Slice::new(), + )+ + } + } + } + } + } + } +} + +pwm! { + PWM, [ + Pwm0: (0, [Gpio0, Gpio1, Gpio16, Gpio17], 0), + Pwm1: (1, [Gpio2, Gpio3, Gpio18, Gpio19], 1), + Pwm2: (2, [Gpio4, Gpio5, Gpio20, Gpio21], 2), + Pwm3: (3, [Gpio6, Gpio7, Gpio22, Gpio23], 3), + Pwm4: (4, [Gpio8, Gpio9, Gpio24, Gpio25], 4), + Pwm5: (5, [Gpio10, Gpio11, Gpio26, Gpio27], 5), + Pwm6: (6, [Gpio12, Gpio13, Gpio28, Gpio29], 6), + Pwm7: (7, [Gpio14, Gpio15], 7) + ] +} + +/// Marker trait for valid output pins +pub trait ValidPwmInputPin: Sealed {} +/// Marker trait for valid input pins (Channel B only) +pub trait ValidPwmOutputPin: Sealed {} + +/// Make sure we can't free an GPIO pin while still keeping it attached to pwm +/// TODO: Maybe FunctionPWM should be private? +pub trait NonPwmPinMode: Sealed {} + +impl NonPwmPinMode for FunctionClock {} +impl NonPwmPinMode for FunctionI2C {} +impl NonPwmPinMode for FunctionPio0 {} +impl NonPwmPinMode for FunctionPio1 {} +impl NonPwmPinMode for FunctionSpi {} +impl NonPwmPinMode for FunctionUart {} +impl NonPwmPinMode for FunctionUsbAux {} +impl NonPwmPinMode for FunctionXip {} +impl NonPwmPinMode for Input {} +impl NonPwmPinMode for Output {} + +/// Stores the attached gpio pin. +/// +/// This value can be ignored/dropped or stored to retrieve the original pin struct +pub struct PwmPinToken { + pin: Pin, +} + +impl PwmPinToken { + /// Retrieve the original pin while disconnecting it from the pwm + pub fn into_mode + NonPwmPinMode>(self) -> Pin { + self.pin.into_mode::() + } +} + +impl Slices { + /// Free the pwm registers from the pwm hal struct while consuming it. + pub fn free(self) -> PWM { + self._pwm + } + + // /// Enable multiple slices at the same time to make their counters sync up. + // /// + // /// You still need to call `slice` to get an actual slice + // pub fn enable_simultaneous(&mut self, bits: u8) { + // // Enable all slices at the same time + // unsafe { + // &(*pac::PWM::ptr()) + // .en + // .modify(|r, w| w.bits(((r.bits() as u8) | bits) as u32)); + // } + // } + + // /// Get pwm slice based on gpio pin + // pub fn borrow_mut_from_pin< + // S: SliceId, + // C: ChannelId, + // G: PinId + BankPinId + ValidPwmOutputPin, + // PM: PinMode + ValidPinMode, + // SM: SliceMode + ValidSliceMode, + // >(&mut self, _: &Pin) -> &mut Slice{ + // match S::DYN { + // DynSliceId{num} if num == 0 => &mut self.pwm0, + // DynSliceId{num} if num == 1 => &mut self.pwm1, + // DynSliceId{num} if num == 2 => &mut self.pwm2, + // DynSliceId{num} if num == 3 => &mut self.pwm3, + // DynSliceId{num} if num == 4 => &mut self.pwm4, + // DynSliceId{num} if num == 5 => &mut self.pwm5, + // DynSliceId{num} if num == 6 => &mut self.pwm6, + // DynSliceId{num} if num == 7 => &mut self.pwm7, + // _ => unreachable!() + // } + // } +} + +/// A Channel from the Pwm subsystem. +/// +/// Its attached to one of the eight slices and can be an A or B side channel +pub struct Channel { + regs: Registers, + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle: u16, +} + +impl Channel { + pub(super) unsafe fn new(duty_cycle: u16) -> Self { + Channel { + regs: Registers::new(), + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle, + } + } + + /// Invert channel output + #[inline] + pub fn set_inverted(&mut self) { + self.regs.write_inv_b(true) + } + + /// Invert channel output or not + #[inline] + pub fn clr_inverted(&mut self) { + self.regs.write_inv_b(false) + } +} + +impl Sealed for Channel {} + +impl PwmPin for Channel { + type Duty = u16; + + /// We cant disable the channel without disturbing the other channel. + /// So this just sets the duty cycle to zero + fn disable(&mut self) { + self.duty_cycle = self.regs.read_cc_a(); + self.regs.write_cc_a(0) + } + + fn enable(&mut self) { + self.regs.write_cc_a(self.duty_cycle) + } + + fn get_duty(&self) -> Self::Duty { + self.regs.read_cc_a() + } + + fn get_max_duty(&self) -> Self::Duty { + self.regs.read_top() + } + + fn set_duty(&mut self, duty: Self::Duty) { + self.regs.write_cc_a(duty) + } +} + +impl PwmPin for Channel { + type Duty = u16; + + /// We cant disable the channel without disturbing the other channel. + /// So this just sets the duty cycle to zero + fn disable(&mut self) { + self.duty_cycle = self.regs.read_cc_b(); + self.regs.write_cc_b(0) + } + + fn enable(&mut self) { + self.regs.write_cc_b(self.duty_cycle) + } + + fn get_duty(&self) -> Self::Duty { + self.regs.read_cc_b() + } + + fn get_max_duty(&self) -> Self::Duty { + self.regs.read_top() + } + + fn set_duty(&mut self, duty: Self::Duty) { + self.regs.write_cc_b(duty) + } +} + +impl> Channel { + /// Capture a gpio pin and use it as pwm output for channel A + pub fn output_to< + G: PinId + BankPinId + ValidPwmOutputPin, + PM: PinMode + ValidPinMode, + >( + &mut self, + pin: Pin, + ) -> PwmPinToken { + PwmPinToken { + pin: pin.into_mode(), + } + } +} + +impl> Channel { + /// Capture a gpio pin and use it as pwm output for channel B + pub fn output_to< + G: PinId + BankPinId + ValidPwmOutputPin, + PM: PinMode + ValidPinMode, + >( + &mut self, + pin: Pin, + ) -> PwmPinToken { + PwmPinToken { + pin: pin.into_mode(), + } + } +} + +impl> Channel { + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from, PM: PinMode + ValidPinMode>( + &mut self, + pin: Pin, + ) -> PwmPinToken { + PwmPinToken { + pin: pin.into_mode(), + } + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm output + pub fn output_to< + G: PinId + BankPinId + ValidPwmOutputPin, + PM: PinMode + ValidPinMode, + C: ChannelId, + >( + &mut self, + pin: Pin, + ) -> PwmPinToken { + PwmPinToken { + pin: pin.into_mode(), + } + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from, PM: PinMode + ValidPinMode>( + &mut self, + pin: Pin, + ) -> PwmPinToken { + PwmPinToken { + pin: pin.into_mode(), + } + } +} diff --git a/rp2040-hal/src/pwm/reg.rs b/rp2040-hal/src/pwm/reg.rs new file mode 100644 index 0000000..a288195 --- /dev/null +++ b/rp2040-hal/src/pwm/reg.rs @@ -0,0 +1,98 @@ +use super::dyn_slice::{DynSliceId, DynSliceMode}; +use pac::pwm::CH; +pub(super) unsafe trait RegisterInterface { + /// Provide a [`DynSliceId`] identifying the set of registers controlled by + /// this type. + fn id(&self) -> DynSliceId; + + #[inline] + fn ch(&self) -> &CH { + let num = self.id().num as usize; + unsafe { &(*pac::PWM::ptr()).ch[num] } + } + + #[inline] + fn advance_phase(&mut self) { + self.ch().csr.modify(|_, w| w.ph_adv().set_bit()) + } + + #[inline] + fn retard_phase(&mut self) { + self.ch().csr.modify(|_, w| w.ph_ret().set_bit()) + } + + #[inline] + fn do_change_mode(&mut self, mode: DynSliceMode) { + self.ch().csr.modify(|_, w| match mode { + DynSliceMode::FreeRunning => w.divmode().div(), + DynSliceMode::InputHighRunning => w.divmode().level(), + DynSliceMode::CountRisingEdge => w.divmode().rise(), + DynSliceMode::CountFallingEdge => w.divmode().fall(), + }) + } + + #[inline] + fn write_inv_a(&mut self, value: bool) { + self.ch().csr.modify(|_, w| w.a_inv().bit(value)); + } + + #[inline] + fn write_inv_b(&mut self, value: bool) { + self.ch().csr.modify(|_, w| w.b_inv().bit(value)); + } + + #[inline] + fn write_ph_correct(&mut self, value: bool) { + self.ch().csr.modify(|_, w| w.ph_correct().bit(value)); + } + + #[inline] + fn write_enable(&mut self, value: bool) { + self.ch().csr.modify(|_, w| w.en().bit(value)); + } + + #[inline] + fn write_div_int(&mut self, value: u8) { + self.ch().div.modify(|_, w| unsafe { w.int().bits(value) }); + } + #[inline] + fn write_div_frac(&mut self, value: u8) { + self.ch().div.modify(|_, w| unsafe { w.frac().bits(value) }); + } + + #[inline] + fn write_ctr(&mut self, value: u16) { + self.ch().ctr.write(|w| unsafe { w.ctr().bits(value) }); + } + + #[inline] + fn read_ctr(&self) -> u16 { + self.ch().ctr.read().ctr().bits() + } + #[inline] + fn write_cc_a(&mut self, value: u16) { + self.ch().cc.modify(|_, w| unsafe { w.a().bits(value) }); + } + #[inline] + fn read_cc_a(&self) -> u16 { + self.ch().cc.read().a().bits() + } + + #[inline] + fn write_cc_b(&mut self, value: u16) { + self.ch().cc.modify(|_, w| unsafe { w.b().bits(value) }); + } + + #[inline] + fn read_cc_b(&self) -> u16 { + self.ch().cc.read().b().bits() + } + #[inline] + fn write_top(&mut self, value: u16) { + self.ch().top.write(|w| unsafe { w.top().bits(value) }); + } + #[inline] + fn read_top(&self) -> u16 { + self.ch().top.read().top().bits() + } +}