use core::marker::PhantomData;
use super::PeripheralGuard;
use crate::{
gpio::interconnect::{OutputSignal, PeripheralOutput},
mcpwm::{PwmClockGuard, PwmPeripheral, timer::Timer},
pac,
};
#[derive(Copy, Clone)]
#[allow(clippy::upper_case_acronyms, reason = "peripheral is unstable")]
pub enum PWMStream {
PWMA,
PWMB,
}
#[derive(Copy, Clone)]
pub struct DeadTimeCfg {
cfg_reg: u32,
}
#[allow(clippy::unusual_byte_groupings)]
impl DeadTimeCfg {
const S0: u32 = 0b01_0000_0000_0000_0000;
const S1: u32 = 0b00_1000_0000_0000_0000;
const S2: u32 = 0b00_0010_0000_0000_0000;
const S3: u32 = 0b00_0100_0000_0000_0000;
const S4: u32 = 0b00_0000_1000_0000_0000;
const S5: u32 = 0b00_0001_0000_0000_0000;
const S6: u32 = 0b00_0000_0010_0000_0000;
const S7: u32 = 0b00_0000_0100_0000_0000;
const _S8: u32 = 0b00_0000_0001_0000_0000;
const CLK_SEL: u32 = 0b10_0000_0000_0000_0000;
pub const fn new_bypass() -> DeadTimeCfg {
DeadTimeCfg {
cfg_reg: Self::S0 | Self::S1,
}
}
pub const fn new_ahc() -> DeadTimeCfg {
DeadTimeCfg { cfg_reg: Self::S3 }
}
#[must_use]
const fn set_flag(mut self, flag: u32, val: bool) -> Self {
if val {
self.cfg_reg |= flag;
} else {
self.cfg_reg &= !flag;
}
self
}
#[must_use]
pub const fn invert_output(self, fed: bool, red: bool) -> Self {
self.set_flag(Self::S3, fed).set_flag(Self::S2, red)
}
#[must_use]
pub const fn set_output_swap(self, stream: PWMStream, swap: bool) -> Self {
self.set_flag(
match stream {
PWMStream::PWMA => Self::S6,
PWMStream::PWMB => Self::S7,
},
swap,
)
}
#[must_use]
pub const fn set_bypass(self, stream: PWMStream, enable: bool) -> Self {
self.set_flag(
match stream {
PWMStream::PWMA => Self::S1,
PWMStream::PWMB => Self::S0,
},
enable,
)
}
#[must_use]
pub const fn select_clock(self, pwm_clock: bool) -> Self {
self.set_flag(Self::CLK_SEL, pwm_clock)
}
#[must_use]
pub const fn select_input(self, fed: PWMStream, red: PWMStream) -> Self {
self.set_flag(
Self::S5,
match fed {
PWMStream::PWMA => false,
PWMStream::PWMB => true,
},
)
.set_flag(
Self::S4,
match red {
PWMStream::PWMA => false,
PWMStream::PWMB => true,
},
)
}
}
pub struct Operator<'d, const OP: u8, PWM> {
phantom: PhantomData<&'d PWM>,
_guard: PeripheralGuard,
_pwm_clock_guard: PwmClockGuard,
}
impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> {
pub(super) fn new(guard: PeripheralGuard) -> Self {
Operator {
phantom: PhantomData,
_guard: guard,
_pwm_clock_guard: PwmClockGuard::new::<PWM>(),
}
}
pub fn set_timer<const TIM: u8>(&mut self, timer: &Timer<TIM, PWM>) {
let _ = timer;
let block = unsafe { &*PWM::block() };
block.operator_timersel().modify(|_, w| match OP {
0 => unsafe { w.operator0_timersel().bits(TIM) },
1 => unsafe { w.operator1_timersel().bits(TIM) },
2 => unsafe { w.operator2_timersel().bits(TIM) },
_ => {
unreachable!()
}
});
}
pub fn with_pin_a(
self,
pin: impl PeripheralOutput<'d>,
config: PwmPinConfig<true>,
) -> PwmPin<'d, PWM, OP, true> {
PwmPin::new(pin, config)
}
pub fn with_pin_b(
self,
pin: impl PeripheralOutput<'d>,
config: PwmPinConfig<false>,
) -> PwmPin<'d, PWM, OP, false> {
PwmPin::new(pin, config)
}
pub fn with_pins(
self,
pin_a: impl PeripheralOutput<'d>,
config_a: PwmPinConfig<true>,
pin_b: impl PeripheralOutput<'d>,
config_b: PwmPinConfig<false>,
) -> (PwmPin<'d, PWM, OP, true>, PwmPin<'d, PWM, OP, false>) {
(PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b))
}
pub fn with_linked_pins(
self,
pin_a: impl PeripheralOutput<'d>,
config_a: PwmPinConfig<true>,
pin_b: impl PeripheralOutput<'d>,
config_b: PwmPinConfig<false>,
config_dt: DeadTimeCfg,
) -> LinkedPins<'d, PWM, OP> {
LinkedPins::new(pin_a, config_a, pin_b, config_b, config_dt)
}
}
pub struct PwmPinConfig<const IS_A: bool> {
actions: PwmActions<IS_A>,
update_method: PwmUpdateMethod,
}
impl<const IS_A: bool> PwmPinConfig<IS_A> {
pub const UP_ACTIVE_HIGH: Self =
Self::new(PwmActions::UP_ACTIVE_HIGH, PwmUpdateMethod::SYNC_ON_ZERO);
pub const UP_DOWN_ACTIVE_HIGH: Self = Self::new(
PwmActions::UP_DOWN_ACTIVE_HIGH,
PwmUpdateMethod::SYNC_ON_ZERO,
);
pub const EMPTY: Self = Self::new(PwmActions::empty(), PwmUpdateMethod::empty());
pub const fn new(actions: PwmActions<IS_A>, update_method: PwmUpdateMethod) -> Self {
PwmPinConfig {
actions,
update_method,
}
}
}
pub struct PwmPin<'d, PWM, const OP: u8, const IS_A: bool> {
pin: OutputSignal<'d>,
phantom: PhantomData<PWM>,
_guard: PeripheralGuard,
}
impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, IS_A> {
fn new(pin: impl PeripheralOutput<'d>, config: PwmPinConfig<IS_A>) -> Self {
let pin = pin.into();
let guard = PeripheralGuard::new(PWM::peripheral());
let mut pin = PwmPin {
pin,
phantom: PhantomData,
_guard: guard,
};
pin.set_actions(config.actions);
pin.set_update_method(config.update_method);
PWM::output_signal::<OP, IS_A>().connect_to(&pin.pin);
pin.pin.set_output_enable(true);
pin
}
pub fn set_actions(&mut self, value: PwmActions<IS_A>) {
let ch = unsafe { Self::ch() };
let bits = value.0;
ch.gen_((!IS_A) as usize).write(|w| unsafe { w.bits(bits) });
}
pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) {
let ch = unsafe { Self::ch() };
let bits = update_method.0;
#[cfg(esp32s3)]
let cfg = ch.cmpr_cfg();
#[cfg(any(esp32, esp32c6, esp32h2))]
let cfg = ch.gen_stmp_cfg();
cfg.modify(|_, w| unsafe {
if IS_A {
w.a_upmethod().bits(bits)
} else {
w.b_upmethod().bits(bits)
}
});
}
pub fn set_timestamp(&mut self, value: u16) {
let ch = unsafe { Self::ch() };
#[cfg(esp32s3)]
if IS_A {
ch.cmpr_value0().write(|w| unsafe { w.a().bits(value) });
} else {
ch.cmpr_value1().write(|w| unsafe { w.b().bits(value) });
}
#[cfg(any(esp32, esp32c6, esp32h2))]
if IS_A {
ch.gen_tstmp_a().write(|w| unsafe { w.a().bits(value) });
} else {
ch.gen_tstmp_b().write(|w| unsafe { w.b().bits(value) });
}
}
pub fn timestamp(&self) -> u16 {
let ch = unsafe { Self::ch() };
#[cfg(esp32s3)]
if IS_A {
ch.cmpr_value0().read().a().bits()
} else {
ch.cmpr_value1().read().b().bits()
}
#[cfg(any(esp32, esp32c6, esp32h2))]
if IS_A {
ch.gen_tstmp_a().read().a().bits()
} else {
ch.gen_tstmp_b().read().b().bits()
}
}
pub fn period(&self) -> u16 {
let block = unsafe { &*PWM::block() };
let tim_select = block.operator_timersel().read();
let tim = match OP {
0 => tim_select.operator0_timersel().bits(),
1 => tim_select.operator1_timersel().bits(),
2 => tim_select.operator2_timersel().bits(),
_ => {
unreachable!()
}
};
block.timer(tim as usize).cfg0().read().period().bits()
}
unsafe fn ch() -> &'static pac::mcpwm0::CH {
let block = unsafe { &*PWM::block() };
block.ch(OP as usize)
}
}
impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::ErrorType
for PwmPin<'_, PWM, OP, IS_A>
{
type Error = core::convert::Infallible;
}
impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::SetDutyCycle
for PwmPin<'_, PWM, OP, IS_A>
{
fn max_duty_cycle(&self) -> u16 {
self.period()
}
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), core::convert::Infallible> {
self.set_timestamp(duty);
Ok(())
}
}
#[procmacros::doc_replace(
"mcpwm_clk" => {
cfg(not(esp32h2)) => "40",
cfg(esp32h2) => "32"
}
)]
pub struct LinkedPins<'d, PWM, const OP: u8> {
pin_a: PwmPin<'d, PWM, OP, true>,
pin_b: PwmPin<'d, PWM, OP, false>,
}
impl<'d, PWM: PwmPeripheral, const OP: u8> LinkedPins<'d, PWM, OP> {
fn new(
pin_a: impl PeripheralOutput<'d>,
config_a: PwmPinConfig<true>,
pin_b: impl PeripheralOutput<'d>,
config_b: PwmPinConfig<false>,
config_dt: DeadTimeCfg,
) -> Self {
#[cfg(esp32s3)]
let dt_cfg = unsafe { Self::ch() }.db_cfg();
#[cfg(not(esp32s3))]
let dt_cfg = unsafe { Self::ch() }.dt_cfg();
dt_cfg.write(|w| unsafe { w.bits(config_dt.cfg_reg) });
let pin_a = PwmPin::new(pin_a, config_a);
let pin_b = PwmPin::new(pin_b, config_b);
LinkedPins { pin_a, pin_b }
}
pub fn set_actions_a(&mut self, value: PwmActions<true>) {
self.pin_a.set_actions(value)
}
pub fn set_actions_b(&mut self, value: PwmActions<false>) {
self.pin_b.set_actions(value)
}
pub fn set_update_method_a(&mut self, update_method: PwmUpdateMethod) {
self.pin_a.set_update_method(update_method)
}
pub fn set_update_method_b(&mut self, update_method: PwmUpdateMethod) {
self.pin_b.set_update_method(update_method)
}
pub fn set_timestamp_a(&mut self, value: u16) {
self.pin_a.set_timestamp(value)
}
pub fn set_timestamp_b(&mut self, value: u16) {
self.pin_a.set_timestamp(value)
}
pub fn set_deadtime_cfg(&mut self, config: DeadTimeCfg) {
#[cfg(esp32s3)]
let dt_cfg = unsafe { Self::ch() }.db_cfg();
#[cfg(not(esp32s3))]
let dt_cfg = unsafe { Self::ch() }.dt_cfg();
dt_cfg.write(|w| unsafe { w.bits(config.cfg_reg) });
}
pub fn set_rising_edge_deadtime(&mut self, dead_time: u16) {
#[cfg(esp32s3)]
let dt_red = unsafe { Self::ch() }.db_red_cfg();
#[cfg(not(esp32s3))]
let dt_red = unsafe { Self::ch() }.dt_red_cfg();
dt_red.write(|w| unsafe { w.red().bits(dead_time) });
}
pub fn set_falling_edge_deadtime(&mut self, dead_time: u16) {
#[cfg(esp32s3)]
let dt_fed = unsafe { Self::ch() }.db_fed_cfg();
#[cfg(not(esp32s3))]
let dt_fed = unsafe { Self::ch() }.dt_fed_cfg();
dt_fed.write(|w| unsafe { w.fed().bits(dead_time) });
}
unsafe fn ch() -> &'static pac::mcpwm0::CH {
let block = unsafe { &*PWM::block() };
block.ch(OP as usize)
}
}
#[non_exhaustive]
#[repr(u32)]
pub enum UpdateAction {
SetLow = 1,
SetHigh = 2,
Toggle = 3,
}
pub struct PwmActions<const IS_A: bool>(u32);
impl<const IS_A: bool> PwmActions<IS_A> {
pub const UP_ACTIVE_HIGH: Self = Self::empty()
.on_up_counting_timer_equals_zero(UpdateAction::SetHigh)
.on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
pub const UP_DOWN_ACTIVE_HIGH: Self = Self::empty()
.on_down_counting_timer_equals_timestamp(UpdateAction::SetHigh)
.on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
pub const fn empty() -> Self {
PwmActions(0)
}
pub const fn on_up_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 0)
}
pub const fn on_up_counting_timer_equals_period(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 2)
}
pub const fn on_up_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
match IS_A {
true => self.with_value_at_offset(action as u32, 4),
false => self.with_value_at_offset(action as u32, 6),
}
}
pub const fn on_up_counting_timer_equals_ch_timestamp<const CH_A: bool>(
self,
action: UpdateAction,
) -> Self {
match CH_A {
true => self.with_value_at_offset(action as u32, 4),
false => self.with_value_at_offset(action as u32, 6),
}
}
pub const fn on_down_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 12)
}
pub const fn on_down_counting_timer_equals_period(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 14)
}
pub const fn on_down_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
match IS_A {
true => self.with_value_at_offset(action as u32, 16),
false => self.with_value_at_offset(action as u32, 18),
}
}
pub const fn on_down_counting_timer_equals_ch_timestamp<const CH_A: bool>(
self,
action: UpdateAction,
) -> Self {
match CH_A {
true => self.with_value_at_offset(action as u32, 16),
false => self.with_value_at_offset(action as u32, 18),
}
}
const fn with_value_at_offset(self, value: u32, offset: u32) -> Self {
let mask = !(0b11 << offset);
let value = (self.0 & mask) | (value << offset);
PwmActions(value)
}
}
pub struct PwmUpdateMethod(u8);
impl PwmUpdateMethod {
pub const SYNC_IMMEDIATLY: Self = Self::empty();
pub const SYNC_ON_ZERO: Self = Self::empty().sync_on_timer_equals_zero();
pub const SYNC_ON_PERIOD: Self = Self::empty().sync_on_timer_equals_period();
pub const fn empty() -> Self {
PwmUpdateMethod(0)
}
pub const fn sync_on_timer_equals_zero(mut self) -> Self {
self.0 |= 0b0001;
self
}
pub const fn sync_on_timer_equals_period(mut self) -> Self {
self.0 |= 0b0010;
self
}
}