2021-12-05 17:28:30 +01:00
|
|
|
//! API for Pulse-Width Modulation (PWM)
|
|
|
|
//!
|
|
|
|
//! The Vorago VA108xx devices use the TIM peripherals to perform PWM related tasks
|
|
|
|
//!
|
|
|
|
//! ## Examples
|
|
|
|
//!
|
2021-12-06 16:02:51 +01:00
|
|
|
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/pwm.rs)
|
2021-12-05 17:28:30 +01:00
|
|
|
use core::marker::PhantomData;
|
|
|
|
|
|
|
|
use crate::{clock::enable_peripheral_clock, gpio::DynPinId};
|
|
|
|
pub use crate::{gpio::PinId, prelude::*, time::Hertz, timer::*};
|
|
|
|
|
|
|
|
use va108xx::SYSCONFIG;
|
|
|
|
|
|
|
|
const DUTY_MAX: u16 = u16::MAX;
|
|
|
|
|
|
|
|
pub struct PwmBase {
|
|
|
|
sys_clk: Hertz,
|
|
|
|
/// For PWMB, this is the upper limit
|
|
|
|
current_duty: u16,
|
|
|
|
/// For PWMA, this value will not be used
|
|
|
|
current_lower_limit: u16,
|
|
|
|
current_period: Hertz,
|
|
|
|
current_rst_val: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
enum StatusSelPwm {
|
|
|
|
PwmA = 3,
|
|
|
|
PwmB = 4,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct PWMA {}
|
|
|
|
pub struct PWMB {}
|
|
|
|
|
|
|
|
//==================================================================================================
|
|
|
|
// Common
|
|
|
|
//==================================================================================================
|
|
|
|
|
|
|
|
macro_rules! pwm_common_func {
|
|
|
|
() => {
|
|
|
|
#[inline]
|
|
|
|
fn enable_pwm_a(&mut self) {
|
|
|
|
self.reg
|
2021-12-05 22:57:54 +01:00
|
|
|
.reg()
|
2021-12-05 17:28:30 +01:00
|
|
|
.ctrl
|
|
|
|
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn enable_pwm_b(&mut self) {
|
|
|
|
self.reg
|
2021-12-05 22:57:54 +01:00
|
|
|
.reg()
|
2021-12-05 17:28:30 +01:00
|
|
|
.ctrl
|
|
|
|
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn get_period(&self) -> Hertz {
|
|
|
|
self.pwm_base.current_period
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn set_period(&mut self, period: impl Into<Hertz>) {
|
|
|
|
self.pwm_base.current_period = period.into();
|
|
|
|
// Avoid division by 0
|
|
|
|
if self.pwm_base.current_period.0 == 0 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self.pwm_base.current_rst_val =
|
|
|
|
self.pwm_base.sys_clk.0 / self.pwm_base.current_period.0;
|
|
|
|
self.reg
|
2021-12-05 22:57:54 +01:00
|
|
|
.reg()
|
2021-12-05 17:28:30 +01:00
|
|
|
.rst_value
|
|
|
|
.write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! pwmb_func {
|
|
|
|
() => {
|
|
|
|
pub fn pwmb_lower_limit(&self) -> u16 {
|
|
|
|
self.pwm_base.current_lower_limit
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pwmb_upper_limit(&self) -> u16 {
|
|
|
|
self.pwm_base.current_duty
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the lower limit for PWMB
|
|
|
|
///
|
|
|
|
/// The PWM signal will be 1 as long as the current RST counter is larger than
|
|
|
|
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
|
|
|
|
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
|
|
|
/// state
|
|
|
|
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
|
|
|
|
self.pwm_base.current_lower_limit = duty;
|
|
|
|
let pwmb_val: u64 = (self.pwm_base.current_rst_val as u64
|
|
|
|
* self.pwm_base.current_lower_limit as u64)
|
|
|
|
/ DUTY_MAX as u64;
|
|
|
|
self.reg
|
2021-12-05 22:57:54 +01:00
|
|
|
.reg()
|
2021-12-05 17:28:30 +01:00
|
|
|
.pwmb_value
|
|
|
|
.write(|w| unsafe { w.bits(pwmb_val as u32) });
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the higher limit for PWMB
|
|
|
|
///
|
|
|
|
/// The PWM signal will be 1 as long as the current RST counter is smaller than
|
|
|
|
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
|
|
|
|
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
|
|
|
/// state
|
|
|
|
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
|
|
|
|
self.pwm_base.current_duty = duty;
|
|
|
|
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
|
|
|
|
* self.pwm_base.current_duty as u64)
|
|
|
|
/ DUTY_MAX as u64;
|
|
|
|
self.reg
|
2021-12-05 22:57:54 +01:00
|
|
|
.reg()
|
2021-12-05 17:28:30 +01:00
|
|
|
.pwma_value()
|
|
|
|
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
//==================================================================================================
|
|
|
|
// Strongly typed PWM pin
|
|
|
|
//==================================================================================================
|
|
|
|
|
|
|
|
pub struct PwmPin<PIN: TimPin, TIM: ValidTim, MODE = PWMA> {
|
2021-12-05 22:57:54 +01:00
|
|
|
reg: TimAndPinRegister<PIN, TIM>,
|
2021-12-05 17:28:30 +01:00
|
|
|
pwm_base: PwmBase,
|
|
|
|
_mode: PhantomData<MODE>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim, MODE> PwmPin<PIN, TIM, MODE>
|
|
|
|
where
|
|
|
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
|
|
|
{
|
|
|
|
/// Create a new stronlgy typed PWM pin
|
|
|
|
pub fn new(
|
|
|
|
vtp: (PIN, TIM),
|
|
|
|
sys_clk: impl Into<Hertz> + Copy,
|
|
|
|
sys_cfg: &mut SYSCONFIG,
|
|
|
|
initial_period: impl Into<Hertz> + Copy,
|
|
|
|
) -> Self {
|
|
|
|
let mut pin = PwmPin {
|
|
|
|
pwm_base: PwmBase {
|
|
|
|
current_duty: 0,
|
|
|
|
current_lower_limit: 0,
|
|
|
|
current_period: initial_period.into(),
|
|
|
|
current_rst_val: 0,
|
|
|
|
sys_clk: sys_clk.into(),
|
|
|
|
},
|
2021-12-05 22:57:54 +01:00
|
|
|
reg: unsafe { TimAndPinRegister::new(vtp.0, vtp.1) },
|
2021-12-05 17:28:30 +01:00
|
|
|
_mode: PhantomData,
|
|
|
|
};
|
|
|
|
enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Gpio);
|
|
|
|
enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Ioconfig);
|
|
|
|
sys_cfg
|
|
|
|
.tim_clk_enable
|
|
|
|
.modify(|r, w| unsafe { w.bits(r.bits() | pin.reg.mask_32()) });
|
|
|
|
pin.enable_pwm_a();
|
|
|
|
pin.set_period(initial_period);
|
|
|
|
pin
|
|
|
|
}
|
|
|
|
pub fn release(self) -> (PIN, TIM) {
|
|
|
|
self.reg.release()
|
|
|
|
}
|
|
|
|
|
|
|
|
pwm_common_func!();
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM, PWMA>> for PwmPin<PIN, TIM, PWMB>
|
|
|
|
where
|
|
|
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
|
|
|
{
|
|
|
|
fn from(other: PwmPin<PIN, TIM, PWMA>) -> Self {
|
|
|
|
let mut pwmb = Self {
|
|
|
|
reg: other.reg,
|
|
|
|
pwm_base: other.pwm_base,
|
|
|
|
_mode: PhantomData,
|
|
|
|
};
|
|
|
|
pwmb.enable_pwm_b();
|
|
|
|
pwmb
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM, PWMB>> for PwmPin<PIN, TIM, PWMA>
|
|
|
|
where
|
|
|
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
|
|
|
{
|
|
|
|
fn from(other: PwmPin<PIN, TIM, PWMB>) -> Self {
|
|
|
|
let mut pwmb = Self {
|
|
|
|
reg: other.reg,
|
|
|
|
pwm_base: other.pwm_base,
|
|
|
|
_mode: PhantomData,
|
|
|
|
};
|
|
|
|
pwmb.enable_pwm_a();
|
|
|
|
pwmb
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> PwmPin<PIN, TIM, PWMA>
|
|
|
|
where
|
|
|
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
|
|
|
{
|
|
|
|
pub fn pwma(
|
|
|
|
vtp: (PIN, TIM),
|
|
|
|
sys_clk: impl Into<Hertz> + Copy,
|
|
|
|
sys_cfg: &mut SYSCONFIG,
|
|
|
|
initial_period: impl Into<Hertz> + Copy,
|
|
|
|
) -> Self {
|
|
|
|
let mut pin: PwmPin<PIN, TIM, PWMA> = Self::new(vtp, sys_clk, sys_cfg, initial_period);
|
|
|
|
pin.enable_pwm_a();
|
|
|
|
pin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> PwmPin<PIN, TIM, PWMB>
|
|
|
|
where
|
|
|
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
|
|
|
{
|
|
|
|
pub fn pwmb(
|
|
|
|
vtp: (PIN, TIM),
|
|
|
|
sys_clk: impl Into<Hertz> + Copy,
|
|
|
|
sys_cfg: &mut SYSCONFIG,
|
|
|
|
initial_period: impl Into<Hertz> + Copy,
|
|
|
|
) -> Self {
|
|
|
|
let mut pin: PwmPin<PIN, TIM, PWMB> = Self::new(vtp, sys_clk, sys_cfg, initial_period);
|
|
|
|
pin.enable_pwm_b();
|
|
|
|
pin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==================================================================================================
|
|
|
|
// Reduced PWM pin
|
|
|
|
//==================================================================================================
|
|
|
|
|
|
|
|
/// Reduced version where type information is deleted
|
|
|
|
pub struct ReducedPwmPin<MODE = PWMA> {
|
|
|
|
reg: TimDynRegister,
|
|
|
|
pwm_base: PwmBase,
|
|
|
|
_pin_id: DynPinId,
|
|
|
|
_mode: PhantomData<MODE>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM>> for ReducedPwmPin<PWMA> {
|
|
|
|
fn from(pwm_pin: PwmPin<PIN, TIM>) -> Self {
|
|
|
|
ReducedPwmPin {
|
|
|
|
reg: TimDynRegister::from(pwm_pin.reg),
|
|
|
|
pwm_base: pwm_pin.pwm_base,
|
|
|
|
_pin_id: PIN::DYN,
|
|
|
|
_mode: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<MODE> ReducedPwmPin<MODE> {
|
|
|
|
pwm_common_func!();
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ReducedPwmPin<PWMA>> for ReducedPwmPin<PWMB> {
|
|
|
|
fn from(other: ReducedPwmPin<PWMA>) -> Self {
|
|
|
|
let mut pwmb = Self {
|
|
|
|
reg: other.reg,
|
|
|
|
pwm_base: other.pwm_base,
|
|
|
|
_pin_id: other._pin_id,
|
|
|
|
_mode: PhantomData,
|
|
|
|
};
|
|
|
|
pwmb.enable_pwm_b();
|
|
|
|
pwmb
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ReducedPwmPin<PWMB>> for ReducedPwmPin<PWMA> {
|
|
|
|
fn from(other: ReducedPwmPin<PWMB>) -> Self {
|
|
|
|
let mut pwmb = Self {
|
|
|
|
reg: other.reg,
|
|
|
|
pwm_base: other.pwm_base,
|
|
|
|
_pin_id: other._pin_id,
|
|
|
|
_mode: PhantomData,
|
|
|
|
};
|
|
|
|
pwmb.enable_pwm_a();
|
|
|
|
pwmb
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==================================================================================================
|
|
|
|
// PWMB implementations
|
|
|
|
//==================================================================================================
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> PwmPin<PIN, TIM, PWMB>
|
|
|
|
where
|
|
|
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
|
|
|
{
|
|
|
|
pwmb_func!();
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ReducedPwmPin<PWMB> {
|
|
|
|
pwmb_func!();
|
|
|
|
}
|
|
|
|
|
|
|
|
//==================================================================================================
|
|
|
|
// Embedded HAL implementation: PWMA only
|
|
|
|
//==================================================================================================
|
|
|
|
|
|
|
|
macro_rules! pwm_pin_impl {
|
|
|
|
() => {
|
|
|
|
#[inline]
|
|
|
|
fn disable(&mut self) {
|
2021-12-05 22:57:54 +01:00
|
|
|
self.reg.reg().ctrl.modify(|_, w| w.enable().clear_bit());
|
2021-12-05 17:28:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn enable(&mut self) {
|
2021-12-05 22:57:54 +01:00
|
|
|
self.reg.reg().ctrl.modify(|_, w| w.enable().set_bit());
|
2021-12-05 17:28:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn set_duty(&mut self, duty: Self::Duty) {
|
|
|
|
self.pwm_base.current_duty = duty;
|
|
|
|
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
|
|
|
|
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
|
|
|
|
/ DUTY_MAX as u64;
|
|
|
|
self.reg
|
2021-12-05 22:57:54 +01:00
|
|
|
.reg()
|
2021-12-05 17:28:30 +01:00
|
|
|
.pwma_value()
|
|
|
|
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_duty(&self) -> Self::Duty {
|
|
|
|
self.pwm_base.current_duty
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_max_duty(&self) -> Self::Duty {
|
|
|
|
DUTY_MAX
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! pwm_impl {
|
|
|
|
() => {
|
|
|
|
#[inline]
|
|
|
|
fn disable(&mut self, _channel: Self::Channel) {
|
2021-12-05 22:57:54 +01:00
|
|
|
self.reg.reg().ctrl.modify(|_, w| w.enable().clear_bit());
|
2021-12-05 17:28:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn enable(&mut self, _channel: Self::Channel) {
|
2021-12-05 22:57:54 +01:00
|
|
|
self.reg.reg().ctrl.modify(|_, w| w.enable().set_bit());
|
2021-12-05 17:28:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_period(&self) -> Self::Time {
|
|
|
|
self.pwm_base.current_period
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn set_duty(&mut self, _channel: Self::Channel, duty: Self::Duty) {
|
|
|
|
self.pwm_base.current_duty = duty;
|
|
|
|
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
|
|
|
|
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
|
|
|
|
/ DUTY_MAX as u64;
|
|
|
|
self.reg
|
2021-12-05 22:57:54 +01:00
|
|
|
.reg()
|
2021-12-05 17:28:30 +01:00
|
|
|
.pwma_value()
|
|
|
|
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn set_period<P>(&mut self, period: P)
|
|
|
|
where
|
|
|
|
P: Into<Self::Time>,
|
|
|
|
{
|
|
|
|
self.pwm_base.current_period = period.into();
|
|
|
|
// Avoid division by 0
|
|
|
|
if self.pwm_base.current_period.0 == 0 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self.pwm_base.current_rst_val =
|
|
|
|
self.pwm_base.sys_clk.0 / self.pwm_base.current_period.0;
|
2021-12-05 22:57:54 +01:00
|
|
|
let reg_block = self.reg.reg();
|
2021-12-05 17:28:30 +01:00
|
|
|
reg_block
|
|
|
|
.rst_value
|
|
|
|
.write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) });
|
|
|
|
reg_block
|
|
|
|
.cnt_value
|
|
|
|
.write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) });
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn get_duty(&self, _channel: Self::Channel) -> Self::Duty {
|
|
|
|
self.pwm_base.current_duty
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn get_max_duty(&self) -> Self::Duty {
|
|
|
|
DUTY_MAX
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> embedded_hal::Pwm for PwmPin<PIN, TIM> {
|
|
|
|
type Channel = ();
|
|
|
|
type Duty = u16;
|
|
|
|
type Time = Hertz;
|
|
|
|
|
|
|
|
pwm_impl!();
|
|
|
|
}
|
|
|
|
|
|
|
|
impl embedded_hal::Pwm for ReducedPwmPin<PWMA> {
|
|
|
|
type Channel = ();
|
|
|
|
type Duty = u16;
|
|
|
|
type Time = Hertz;
|
|
|
|
|
|
|
|
pwm_impl!();
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<PIN: TimPin, TIM: ValidTim> embedded_hal::PwmPin for PwmPin<PIN, TIM> {
|
|
|
|
type Duty = u16;
|
|
|
|
|
|
|
|
pwm_pin_impl!();
|
|
|
|
}
|
|
|
|
|
|
|
|
impl embedded_hal::PwmPin for ReducedPwmPin<PWMA> {
|
|
|
|
type Duty = u16;
|
|
|
|
|
|
|
|
pwm_pin_impl!();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
|
|
|
|
///
|
|
|
|
/// Please note that this might load a lot of floating point code because this processor does not
|
|
|
|
/// have a FPU
|
|
|
|
pub fn get_duty_from_percent(percent: f32) -> u16 {
|
|
|
|
if percent > 1.0 {
|
|
|
|
DUTY_MAX
|
|
|
|
} else if percent <= 0.0 {
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
(percent * DUTY_MAX as f32) as u16
|
|
|
|
}
|
|
|
|
}
|