From ac6558d19abcac0e5fabbef4cfc99b801683c941 Mon Sep 17 00:00:00 2001 From: Joel Aschmann Date: Tue, 21 Mar 2023 08:42:42 +0100 Subject: [PATCH 1/2] PWM: Add wrapper around RIOTS PWM interface --- Cargo.toml | 2 + src/lib.rs | 3 + src/pwm.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 src/pwm.rs diff --git a/Cargo.toml b/Cargo.toml index df8de3fb..9a3edbbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,8 @@ riot-sys = "0.7.8" num-traits = { version = "0.2", default-features = false } mutex-trait = "0.2" +fugit = "0.3.6" + bare-metal = "1" cstr = "^0.2.11" diff --git a/src/lib.rs b/src/lib.rs index 8c976e6a..c283f9de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,6 +121,9 @@ pub mod adc; #[cfg(riot_module_periph_dac)] pub mod dac; +#[cfg(riot_module_periph_pwm)] +pub mod pwm; + #[cfg(riot_module_ztimer)] pub mod ztimer; diff --git a/src/pwm.rs b/src/pwm.rs new file mode 100644 index 00000000..19d169a8 --- /dev/null +++ b/src/pwm.rs @@ -0,0 +1,235 @@ +use embedded_hal::Pwm; +pub use fugit::{HertzU32, Rate}; +use riot_sys::pwm_t; + +/// PWM modes +/// +/// [`PWMMode::CENTER`] exists in RIOT but is marked as "not supported" +#[derive(Debug)] +pub enum PWMMode { + Left, + Right, + Center, +} + +impl PWMMode { + /// Converts the rust enum to a c type + fn to_c(self) -> riot_sys::pwm_mode_t { + match self { + Self::Left => riot_sys::pwm_mode_t_PWM_LEFT, + Self::Right => riot_sys::pwm_mode_t_PWM_RIGHT, + Self::Center => riot_sys::pwm_mode_t_PWM_CENTER, + } + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum PwmInitError { + /// No information available on the error details + /// + /// This is the catch-all for unknown types, and should not be handled any different than the `_` case. + Other, +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum PWMChannelError { + /// The specified `PWMChannel` is not available + Unavailable, +} + +/// A descriptor for a PWM channel +#[derive(Debug)] +pub struct PWMChannel { + channel: u8, +} + +/// A device for generating PWM signals on multiple channels. +/// +/// **Note** that the `CHANNELS` Variable is only used to cache the duty values for each of the `CHANNELS` number of +/// PWM channels. This is used by [`embedded_hal::Pwm::get_duty`]. If this is not needed setting `CHANNELS` to `0` saves memory. +#[derive(Debug)] +pub struct PWMDevice { + dev: pwm_t, + channel_duty_values: [u16; CHANNELS], //Needed because of embedded_hal implementation + frequency: HertzU32, //Needed because of embedded_hal implementation + resolution: u16, +} + +impl PWMDevice { + /// Creates and initializes a [`PWMDevice`] with the given frequency in Hz and the resolution of a period/duty cycle + /// + /// The `index` specifies the index of a boards PWM device. To see how many devices a board supports + /// please refer to its RIOT documentation. + /// + /// Note that duty-values set in [`embedded_hal::Pwm::set_duty`] have to be in [0..resolution] + /// with `value==resolution` representing `100%` duty-cycle. + /// + /// If the given frequency and resolution values can not be acchieved on the current device, the + /// resolution will be kept the same while the frequency will be lowered until the device can handle the combination. + /// The actual set frequency can the be obtained with [`PWMDevice::get_frequency`]. + /// + /// Returns the initialized [`PWMDevice`] on success + /// + pub fn new( + index: usize, + mode: PWMMode, + frequency: HertzU32, + resolution: u16, + ) -> Result { + let mut pwm_dev = PWMDevice { + dev: unsafe { riot_sys::macro_PWM_DEV(index as u32) }, + channel_duty_values: [0; CHANNELS], + frequency, + resolution, + }; + + pwm_dev.init(mode, frequency, resolution)?; + Ok(pwm_dev) + } + + /// Creates a PWM device from an already initialized c type. + /// + /// **Note** that the `CHANNELS` Variable is only used to cache the duty values for each of the `CHANNELS` number of + /// PWM channels. This is used by [`embedded_hal::Pwm::get_duty`]. If this is not needed setting `CHANNELS` to `0` saves memory. + /// + /// ## Important: + /// It is **important** to make sure that the provided **device is already initialized** by using [`riot_sys::pwm_init`](https://rustdoc.etonomy.org/riot_sys/fn.pwm_init.html). + /// Using the returned device otherwise results in **undefined behavior**! + /// + /// Also note, that the given frequency is only to be used by [`embedded_hal::Pwm::get_period`] or [`PWMDevice::get_frequency`]. + /// Just setting this to `x` will only result in those two functions returning `x` but will have no other impact, if + /// for example the given [`pwm_t`] is initialized by a board and the actually set frequency is unknown. + /// + /// The same goes for `resolution` which is only used in [`embedded_hal::Pwm::get_max_duty`]. + pub unsafe fn new_without_init(dev: pwm_t, frequency: HertzU32, resolution: u16) -> Self { + PWMDevice { + dev, + channel_duty_values: [0; CHANNELS], + frequency, + resolution, + } + } + + /// Initializes the [`PWMDevice`] with the given frequency in Hz and resolution of a period/duty cycle. + /// + /// If the given frequency and resolution values can not be acchieved on the current device, the + /// resolution will be kept the same while the frequency will be lowered until the device can handle the combination. + /// The resulting frequency is written into the [`PWMDevice`]. + /// + /// Uses a [`Result`] in anticipation of a more usefull error handling in the future. + fn init( + &mut self, + mode: PWMMode, + frequency: HertzU32, + resolution: u16, + ) -> Result<(), PwmInitError> { + let err = unsafe { riot_sys::pwm_init(self.dev, mode.to_c(), frequency.raw(), resolution) }; + + match err { + 0 => Err(PwmInitError::Other), + freq => { + // Set frequency in the device + self.frequency = Rate::::from_raw(freq); + Ok(()) + } + } + } + + /// Provides a channel-descriptor for the given channel number. + /// + /// Returns an error if the channel number has no corresponding PWM channel in + /// RIOT + pub fn get_channel(&self, channel_num: u8) -> Result { + if channel_num >= self.channels() { + Err(PWMChannelError::Unavailable) + } else { + Ok(PWMChannel { + channel: channel_num, + }) + } + } + + /// Returns the number of available channels for this device. + /// + /// This is completely independent from `CHANNELS` and only returns + /// the count of channels available in RIOT + pub fn channels(&self) -> u8 { + unsafe { riot_sys::pwm_channels(self.dev) } + } + + /// Stops PWM generation on this device + pub fn power_off(&mut self) { + unsafe { riot_sys::pwm_poweroff(self.dev) } + } + + /// Resumes PWM generation after power_off on this device + pub fn power_on(&mut self) { + unsafe { + riot_sys::pwm_poweron(self.dev); + } + } + + /// Sets the duty-cycle for the given channel + /// + /// value: `0: 0%, resolution: 100%` duty_cycle + fn set(&mut self, channel: PWMChannel, value: u16) { + unsafe { + riot_sys::pwm_set(self.dev, channel.channel, value); + } + + let channel = channel.channel as usize; + // Ignore if entry does not exists because + // only embedded_hal interface cares about those values + // and the implementation already checks for this + if channel < CHANNELS { + self.channel_duty_values[channel] = value; + } + } +} + +impl Pwm for PWMDevice { + type Channel = PWMChannel; + type Duty = u16; + type Time = HertzU32; + + fn disable(&mut self, _channel: Self::Channel) { + panic!("RIOT does not support enabling/disabling single channels") + } + + fn enable(&mut self, _channel: Self::Channel) { + panic!("RIOT does not support enabling/disabling single channels") + } + + fn get_period(&self) -> Self::Time { + self.frequency + } + + fn get_duty(&self, channel: Self::Channel) -> Self::Duty { + let channel = channel.channel as usize; + + if channel >= CHANNELS { + panic!( + "Tried to get duty for not cached channel: {} >= CHANNELS", + channel + ) + } + self.channel_duty_values[channel] + } + + fn get_max_duty(&self) -> Self::Duty { + self.resolution + } + + fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { + self.set(channel, duty); + } + + fn set_period

(&mut self, _period: P) + where + P: Into, + { + panic!("RIOT does not support setting the period after initialisation") + } +} From 5536eafeb638b0e3e010a685481dcf3415b4785d Mon Sep 17 00:00:00 2001 From: Joel Aschmann Date: Tue, 21 Mar 2023 08:44:08 +0100 Subject: [PATCH 2/2] PWM: Add test --- tests/pwm/Cargo.toml | 16 ++++++++++++++++ tests/pwm/Makefile | 7 +++++++ tests/pwm/src/lib.rs | 13 +++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 tests/pwm/Cargo.toml create mode 100644 tests/pwm/Makefile create mode 100644 tests/pwm/src/lib.rs diff --git a/tests/pwm/Cargo.toml b/tests/pwm/Cargo.toml new file mode 100644 index 00000000..6b80f4b8 --- /dev/null +++ b/tests/pwm/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "riot-wrappers-test-pwm" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["staticlib"] + +[profile.release] +panic = "abort" + +[dependencies] +riot-wrappers = { version = "*", features = [ "set_panic_handler" ] } +riot-sys = "*" +embedded-hal = { version = "0.2.4", features = ["unproven"] } diff --git a/tests/pwm/Makefile b/tests/pwm/Makefile new file mode 100644 index 00000000..ea5ac4fe --- /dev/null +++ b/tests/pwm/Makefile @@ -0,0 +1,7 @@ +APPLICATION = riot-wrappers-test-pwm +APPLICATION_RUST_MODULE = riot_wrappers_test_pwm +BASELIBS += $(APPLICATION_RUST_MODULE).module +FEATURES_REQUIRED += rust_target +FEATURES_REQUIRED += periph_pwm + +include $(RIOTBASE)/Makefile.include diff --git a/tests/pwm/src/lib.rs b/tests/pwm/src/lib.rs new file mode 100644 index 00000000..c8177f3f --- /dev/null +++ b/tests/pwm/src/lib.rs @@ -0,0 +1,13 @@ +#![no_std] + +use embedded_hal::Pwm; +use riot_wrappers::pwm::{HertzU32, PWMDevice, PWMMode}; +use riot_wrappers::riot_main; + +riot_main!(main); + +fn main() { + let mut pwm = PWMDevice::<0>::new(0, PWMMode::Left, HertzU32::Hz(10), 100).unwrap(); + let channel_0 = pwm.get_channel(0).unwrap(); + pwm.set_duty(channel_0, 50); // 50% duty_cycle +}