Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PWM #39

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ pub struct Output<MODE> {
_mode: PhantomData<MODE>,
}

/// PWM mode (type state)
pub struct Pwm<MODE> {
_mode: PhantomData<MODE>,
}

/// UART pin mode (type state)
pub struct Uart;

Expand Down Expand Up @@ -346,6 +351,24 @@ macro_rules! impl_glb {
self.into_pin_with_mode(11, false, true, true)
}

// Do pull-up/pull-down/hi-z actually do anything for PWM?

/// Configures the pin to operate as a pull-down PWM pin.
pub fn into_pull_down_pwm(self) -> $Pini<Pwm<PullDown>> {
// `bl602_pac::glb::gpio_cfgctln::REG_GPIO_n_FUNC_SEL_A::PWM_CHn`
self.into_pin_with_mode(8, false, true, true)
}

/// Configures the pin to operate as a pull-up PWM pin.
pub fn into_pull_up_pwm(self) -> $Pini<Pwm<PullUp>> {
self.into_pin_with_mode(8, false, true, true)
}

/// Configures the pin to operate as a Hi-Z floating PWM pin.
pub fn into_floating_pwm(self) -> $Pini<Pwm<Floating>> {
self.into_pin_with_mode(8, false, false, true)
}

paste::paste! {
#[inline]
fn into_pin_with_mode<T>(self, mode: u8, pu: bool, pd: bool, ie: bool) -> $Pini<T> {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub mod serial;
pub mod spi;
pub mod timer;
pub mod watchdog;
pub mod pwm;

/// HAL crate prelude
pub mod prelude {
Expand Down
204 changes: 204 additions & 0 deletions src/pwm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//! Pulse Width Modulation
//!
//! # Example
//!
//! ```no_run
//! # use crate::pwm;
//! # let dp = crate::pac::Peripherals::take().unwrap();
//! # let clocks = crate::clock::Clocks::new();
//! use embedded_time::duration::Milliseconds;
//!
//! let mut channels = pwm::Channels::from((dp.PWM, clocks));
//!
//! pwm.channel2.enable(&()).unwrap();
//!
//! pwm.channel2.set_period(Milleseconds::new(20)).unwrap();
//!
//! // 5% duty cycle
//! let duty = 5 * (pwm.channel2.get_max_duty().unwrap() / 100);
//! pwm.channel2.set_duty(duty).unwrap();
//!
//! // Use pin 17 as PWM channel 2's output
//! parts.pin17.into_pull_down_pwm();
//!
//! // Control PWM and its settings via the `pwm` object
//! ```

use core::convert::{Infallible, TryInto};
use embedded_hal::pwm::blocking::Pwm as PwmTrait;
use embedded_time::{
duration::{Duration, Milliseconds, Seconds},
fixed_point::FixedPoint,
rate::Hertz,
};

use crate::{clock::Clocks, pac};

macro_rules! per_channel {
( $($channel:literal),* ) => { paste::paste!{
/// PWM entry point
pub struct Channels {
$(pub [<channel $channel>]: [<Channel $channel>]),+
}

$(
#[doc = concat!("PWM channel ", stringify!($channel)) ]
pub struct [<Channel $channel>] {
pwm: &'static pac::pwm::RegisterBlock,
clocks: Clocks,
}
)+

impl From<(pac::PWM, Clocks)> for Channels {
fn from(other: (pac::PWM, Clocks)) -> Self {
Self {
$([<channel $channel>]: [<Channel $channel>] {
pwm: unsafe { &*pac::PWM::ptr() },
clocks: other.1,
}),+
}
}
}

$(impl PwmTrait for [<Channel $channel>] {
type Error = Infallible;
type Channel = ();
type Time = Milliseconds<u64>;
type Duty = u16;

fn disable(
&mut self,
channel: &Self::Channel,
) -> Result<(), Self::Error> {
let _ = channel;

self.pwm.[<pwm $channel _config>].write(|w|
w.pwm_stop_en().set_bit()
);

Ok(())
}

fn enable(
&mut self,
channel: &Self::Channel,
) -> Result<(), Self::Error> {
let _ = channel;

self.pwm.[<pwm $channel _config>].write(|w|
w.pwm_stop_en().clear_bit()
);
Ok(())
}

fn get_period(&self) -> Result<Self::Time, Self::Error> {
todo!()
}

fn get_duty(
&self,
channel: &Self::Channel,
) -> Result<Self::Duty, Self::Error> {
let _ = channel;

Ok(self.pwm.[<pwm $channel _thre2>].read().pwm_thre2().bits())
}

fn get_max_duty(&self) -> Result<Self::Duty, Self::Error> {
Ok(
self
.pwm
.[<pwm $channel _period>]
.read()
.pwm_period()
.bits()
)
}

fn set_duty(
&mut self,
channel: &Self::Channel,
duty: Self::Duty,
) -> Result<(), Self::Error> {
let (_, duty) = (channel, duty);

// Zero out threshold 1
self.pwm.[<pwm $channel _thre1>].write(|w| unsafe {
w.pwm_thre1().bits(0)
});

// Set threshold 2
self.pwm. [<pwm $channel _thre2>] .write(|w| unsafe {
w.pwm_thre2().bits(duty)
});

Ok(())
}

fn set_period<P>(
&mut self,
period: P,
) -> Result<(), Self::Error>
where
P: Into<Self::Time>
{
let period_time = period.into();

// The non-whole-second portion of the desired period length
let period_time_subsec = period_time % Seconds(1_u32);

// The rate required by subsecond period time
let period_rate: Hertz<u32> =
match period_time_subsec.to_rate() {
Ok(x) => x,
Err(_) => todo!(
"handle periods where period_time_subsec == 0?"
),
};

let clk_hz = self.clocks.sysclk();

// Make clk_div as small as possible so Self::Duty can have a
// usefully large range of values
let clk_div_rem = clk_hz.integer()
% (u16::MAX as u32 * period_rate.integer());
let clk_div = clk_hz.integer()
/ (u16::MAX as u32 * period_rate.integer())
+ if clk_div_rem == 0 { 0 } else { 1 };

let clk_divided = clk_hz / clk_div as u32;

// Divided clocks per period
let period_val: u16 = (clk_divided / period_rate.integer())
.integer()
.try_into()
.unwrap_or(u16::max_value());

// Use the system clock
//
// TODO: make this configurable
self.pwm.[<pwm $channel _config>].write(|w| unsafe {
w.reg_clk_sel().bits(0b11)
});

// Clock divider
self.pwm.[<pwm $channel _clkdiv>].write(|w| unsafe {
w.pwm_clk_div().bits(
clk_div
.try_into()
.unwrap_or(u16::max_value())
)
});

// Set how many divided clocks are in a period
self.pwm.[<pwm $channel _period>].write(|w| unsafe {
w.pwm_period().bits(period_val)
});

Ok(())
}
})+
}}
}

per_channel!(0, 1, 2, 3, 4);