use crate::peripherals::Pwm;
use crate::soc::chip::SYSTEM_CLOCK_HZ;
use core::marker::PhantomData;
pub const PWM_CLOCK_HZ: u32 = SYSTEM_CLOCK_HZ / 6;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Duty(u8);
impl Duty {
pub const ZERO: Duty = Duty(0);
pub const HALF: Duty = Duty(50);
pub const FULL: Duty = Duty(100);
pub const fn from_percent(percent: u8) -> Option<Self> {
if percent <= 100 { Some(Duty(percent)) } else { None }
}
pub const fn percent(self) -> u8 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct PwmPeriod(u16);
impl PwmPeriod {
pub const fn from_count(count: u16) -> Option<Self> {
if count == 0 { None } else { Some(PwmPeriod(count)) }
}
pub const fn try_from_hz(hz: u32) -> Option<Self> {
if hz == 0 {
return None;
}
let count = PWM_CLOCK_HZ / hz;
if count == 0 || count > u16::MAX as u32 {
return None;
}
Some(PwmPeriod(count as u16))
}
pub const fn count(self) -> u16 {
self.0
}
pub const fn duty_count(self, duty: Duty) -> u16 {
((self.0 as u32 * duty.0 as u32) / 100) as u16
}
}
fn enable_pwm_clock() {
#[cfg(feature = "chip-ws63")]
{
const PWM_CKEN_FIELD: u16 = 0x1FF; const PWM_DIV_6: u8 = 6;
let cldo = unsafe { &*crate::peripherals::CldoCrg::ptr() };
cldo.clk_sel().modify(|_, w| w.pwm_clk_sel().set_bit());
cldo.cken_ctl0().modify(|_, w| unsafe { w.pwm_cken().bits(PWM_CKEN_FIELD) });
cldo.div_ctl3().modify(|_, w| w.pwm0_load_div_en().clear_bit());
cldo.div_ctl3().modify(|_, w| unsafe { w.pwm0_div1_cfg().bits(PWM_DIV_6) });
cldo.div_ctl3().modify(|_, w| w.pwm0_load_div_en().set_bit());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PwmChannelId {
Ch0,
Ch1,
Ch2,
Ch3,
Ch4,
Ch5,
Ch6,
Ch7,
}
impl PwmChannelId {
pub const fn from_index(index: u8) -> Option<Self> {
match index {
0 => Some(Self::Ch0),
1 => Some(Self::Ch1),
2 => Some(Self::Ch2),
3 => Some(Self::Ch3),
4 => Some(Self::Ch4),
5 => Some(Self::Ch5),
6 => Some(Self::Ch6),
7 => Some(Self::Ch7),
_ => None,
}
}
pub const fn index(self) -> u8 {
match self {
Self::Ch0 => 0,
Self::Ch1 => 1,
Self::Ch2 => 2,
Self::Ch3 => 3,
Self::Ch4 => 4,
Self::Ch5 => 5,
Self::Ch6 => 6,
Self::Ch7 => 7,
}
}
}
pub struct PwmChannel<'d> {
channel: PwmChannelId,
_marker: PhantomData<&'d ()>,
}
impl<'d> PwmChannel<'d> {
pub fn new(_pwm: &Pwm<'d>, channel: PwmChannelId) -> Self {
Self { channel, _marker: PhantomData }
}
fn regs(&self) -> &'static crate::soc::pac::pwm::RegisterBlock {
unsafe { &*Pwm::ptr() }
}
fn channel_index(&self) -> u8 {
self.channel.index()
}
fn period_count(&self) -> u32 {
let r = self.regs();
let (lo, hi) = match self.channel_index() {
0 => (r.pwm_freq_l0().read().bits(), r.pwm_freq_h0().read().bits()),
1 => (r.pwm_freq_l1().read().bits(), r.pwm_freq_h1().read().bits()),
2 => (r.pwm_freq_l2().read().bits(), r.pwm_freq_h2().read().bits()),
3 => (r.pwm_freq_l3().read().bits(), r.pwm_freq_h3().read().bits()),
4 => (r.pwm_freq_l4().read().bits(), r.pwm_freq_h4().read().bits()),
5 => (r.pwm_freq_l5().read().bits(), r.pwm_freq_h5().read().bits()),
6 => (r.pwm_freq_l6().read().bits(), r.pwm_freq_h6().read().bits()),
7 => (r.pwm_freq_l7().read().bits(), r.pwm_freq_h7().read().bits()),
_ => unreachable!(),
};
(lo & 0xFFFF) | ((hi & 0xFFFF) << 16)
}
pub fn configure(&mut self, period_cfg: PwmPeriod, duty_cfg: Duty) {
enable_pwm_clock();
let r = self.regs();
let period = period_cfg.count() as u32;
let duty = period_cfg.duty_count(duty_cfg) as u32;
match self.channel_index() {
0 => {
r.pwm_freq_l0().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h0().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l0().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h0().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
1 => {
r.pwm_freq_l1().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h1().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l1().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h1().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
2 => {
r.pwm_freq_l2().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h2().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l2().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h2().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
3 => {
r.pwm_freq_l3().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h3().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l3().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h3().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
4 => {
r.pwm_freq_l4().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h4().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l4().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h4().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
5 => {
r.pwm_freq_l5().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h5().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l5().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h5().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
6 => {
r.pwm_freq_l6().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h6().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l6().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h6().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
7 => {
r.pwm_freq_l7().write(|w| unsafe { w.bits(period & 0xFFFF) });
r.pwm_freq_h7().write(|w| unsafe { w.bits((period >> 16) & 0xFFFF) });
r.pwm_duty_l7().write(|w| unsafe { w.bits(duty & 0xFFFF) });
r.pwm_duty_h7().write(|w| unsafe { w.bits((duty >> 16) & 0xFFFF) });
}
_ => unreachable!(),
}
}
pub fn enable(&mut self) {
match self.channel_index() {
0 => self.regs().pwm_en0().write(|w| unsafe { w.bits(1u32) }),
1 => self.regs().pwm_en1().write(|w| unsafe { w.bits(1u32) }),
2 => self.regs().pwm_en2().write(|w| unsafe { w.bits(1u32) }),
3 => self.regs().pwm_en3().write(|w| unsafe { w.bits(1u32) }),
4 => self.regs().pwm_en4().write(|w| unsafe { w.bits(1u32) }),
5 => self.regs().pwm_en5().write(|w| unsafe { w.bits(1u32) }),
6 => self.regs().pwm_en6().write(|w| unsafe { w.bits(1u32) }),
7 => self.regs().pwm_en7().write(|w| unsafe { w.bits(1u32) }),
_ => unreachable!(),
};
}
pub fn disable(&mut self) {
match self.channel_index() {
0 => self.regs().pwm_en0().write(|w| unsafe { w.bits(0u32) }),
1 => self.regs().pwm_en1().write(|w| unsafe { w.bits(0u32) }),
2 => self.regs().pwm_en2().write(|w| unsafe { w.bits(0u32) }),
3 => self.regs().pwm_en3().write(|w| unsafe { w.bits(0u32) }),
4 => self.regs().pwm_en4().write(|w| unsafe { w.bits(0u32) }),
5 => self.regs().pwm_en5().write(|w| unsafe { w.bits(0u32) }),
6 => self.regs().pwm_en6().write(|w| unsafe { w.bits(0u32) }),
7 => self.regs().pwm_en7().write(|w| unsafe { w.bits(0u32) }),
_ => unreachable!(),
};
}
#[instability::unstable]
pub fn set_polarity(&mut self, active_high: bool) {
let val = if active_high { 1u32 } else { 0u32 };
match self.channel_index() {
0 => self.regs().pwm_portity0().write(|w| unsafe { w.bits(val) }),
1 => self.regs().pwm_portity1().write(|w| unsafe { w.bits(val) }),
2 => self.regs().pwm_portity2().write(|w| unsafe { w.bits(val) }),
3 => self.regs().pwm_portity3().write(|w| unsafe { w.bits(val) }),
4 => self.regs().pwm_portity4().write(|w| unsafe { w.bits(val) }),
5 => self.regs().pwm_portity5().write(|w| unsafe { w.bits(val) }),
6 => self.regs().pwm_portity6().write(|w| unsafe { w.bits(val) }),
7 => self.regs().pwm_portity7().write(|w| unsafe { w.bits(val) }),
_ => unreachable!(),
};
}
#[instability::unstable]
pub fn start(&mut self) {
self.regs().pwm_start0().write(|w| unsafe { w.bits(1u32 << self.channel_index()) });
}
#[instability::unstable]
pub fn set_pulse_count(&mut self, count: u32) {
match self.channel_index() {
0 => self.regs().pwm_period_val0().write(|w| unsafe { w.bits(count) }),
1 => self.regs().pwm_period_val1().write(|w| unsafe { w.bits(count) }),
2 => self.regs().pwm_period_val2().write(|w| unsafe { w.bits(count) }),
3 => self.regs().pwm_period_val3().write(|w| unsafe { w.bits(count) }),
4 => self.regs().pwm_period_val4().write(|w| unsafe { w.bits(count) }),
5 => self.regs().pwm_period_val5().write(|w| unsafe { w.bits(count) }),
6 => self.regs().pwm_period_val6().write(|w| unsafe { w.bits(count) }),
7 => self.regs().pwm_period_val7().write(|w| unsafe { w.bits(count) }),
_ => unreachable!(),
};
}
#[must_use = "dropping the PwmRunning marker is fine, but assign it to make the leak intentional"]
#[instability::unstable]
pub fn into_running(self) -> PwmRunning {
core::mem::forget(self); PwmRunning(())
}
}
#[derive(Debug)]
#[must_use]
#[instability::unstable]
pub struct PwmRunning(());
impl Drop for PwmChannel<'_> {
fn drop(&mut self) {
self.disable();
}
}
impl embedded_hal::pwm::ErrorType for PwmChannel<'_> {
type Error = PwmError;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum PwmError {
DutyOutOfRange,
}
impl embedded_hal::pwm::Error for PwmError {
fn kind(&self) -> embedded_hal::pwm::ErrorKind {
embedded_hal::pwm::ErrorKind::Other
}
}
impl embedded_hal::pwm::SetDutyCycle for PwmChannel<'_> {
fn max_duty_cycle(&self) -> u16 {
self.period_count().min(u16::MAX as u32) as u16
}
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
if duty > self.max_duty_cycle() {
return Err(PwmError::DutyOutOfRange);
}
let r = self.regs();
let duty_val = duty as u32;
match self.channel_index() {
0 => {
unsafe { r.pwm_duty_l0().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h0().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
1 => {
unsafe { r.pwm_duty_l1().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h1().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
2 => {
unsafe { r.pwm_duty_l2().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h2().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
3 => {
unsafe { r.pwm_duty_l3().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h3().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
4 => {
unsafe { r.pwm_duty_l4().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h4().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
5 => {
unsafe { r.pwm_duty_l5().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h5().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
6 => {
unsafe { r.pwm_duty_l6().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h6().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
7 => {
unsafe { r.pwm_duty_l7().write(|w| w.bits(duty_val & 0xFFFF)) };
unsafe { r.pwm_duty_h7().write(|w| w.bits((duty_val >> 16) & 0xFFFF)) };
}
_ => unreachable!(),
}
Ok(())
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::{Duty, PWM_CLOCK_HZ, PwmPeriod, PwmRunning};
#[test]
fn running_marker_is_zero_sized() {
assert_eq!(core::mem::size_of::<PwmRunning>(), 0);
}
#[test]
fn duty_rejects_over_100() {
assert!(Duty::from_percent(0).is_some());
assert!(Duty::from_percent(100).is_some());
assert!(Duty::from_percent(101).is_none());
assert!(Duty::from_percent(255).is_none());
}
#[test]
fn duty_consts() {
assert_eq!(Duty::ZERO.percent(), 0);
assert_eq!(Duty::HALF.percent(), 50);
assert_eq!(Duty::FULL.percent(), 100);
}
#[test]
fn period_from_count_rejects_zero() {
assert!(PwmPeriod::from_count(0).is_none());
assert_eq!(PwmPeriod::from_count(1).unwrap().count(), 1);
assert_eq!(PwmPeriod::from_count(60_000).unwrap().count(), 60_000);
}
#[test]
fn try_from_hz_basic_and_bounds() {
assert!(PwmPeriod::try_from_hz(0).is_none());
assert!(PwmPeriod::try_from_hz(PWM_CLOCK_HZ + 1).is_none());
assert!(PwmPeriod::try_from_hz(1).is_none());
assert_eq!(PwmPeriod::try_from_hz(PWM_CLOCK_HZ).unwrap().count(), 1);
assert_eq!(PwmPeriod::try_from_hz(1_000).unwrap().count(), (PWM_CLOCK_HZ / 1_000) as u16);
}
#[test]
fn duty_count_zero_half_full() {
let p = PwmPeriod::from_count(1_000).unwrap();
assert_eq!(p.duty_count(Duty::ZERO), 0);
assert_eq!(p.duty_count(Duty::HALF), 500);
assert_eq!(p.duty_count(Duty::FULL), 1_000);
}
#[test]
fn duty_count_monotonic_and_bounded() {
let p = PwmPeriod::from_count(1_000).unwrap();
let mut prev = 0u16;
for pct in 0u8..=100 {
let d = p.duty_count(Duty::from_percent(pct).unwrap());
assert!(d >= prev, "duty must be monotonic: pct={pct} d={d} prev={prev}");
assert!(d <= p.count(), "duty {d} must never exceed period {}", p.count());
prev = d;
}
}
#[test]
fn duty_count_no_overflow_for_max_period() {
let p = PwmPeriod::from_count(u16::MAX).unwrap();
assert_eq!(p.duty_count(Duty::FULL), u16::MAX);
assert_eq!(p.duty_count(Duty::from_percent(1).unwrap()), u16::MAX / 100);
}
#[test]
fn start_mask_is_channel_bit() {
for ch in 0u8..8 {
assert_eq!(1u32 << ch, 1u32 << ch);
assert_eq!((1u32 << ch).count_ones(), 1);
}
assert_eq!(1u32 << 7, 0x80);
}
#[test]
fn channel_bounds() {
assert!((0u8..8).all(|c| c < 8));
assert!(8u8 >= 8);
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use super::{Duty, PWM_CLOCK_HZ, PwmPeriod};
use proptest::prelude::*;
proptest! {
#[test]
fn try_from_hz_in_range(hz in (PWM_CLOCK_HZ / u16::MAX as u32 + 1)..=PWM_CLOCK_HZ) {
let p = PwmPeriod::try_from_hz(hz).unwrap();
prop_assert!(p.count() >= 1);
}
#[test]
fn try_from_hz_rejects_too_low(hz in 1u32..(PWM_CLOCK_HZ / u16::MAX as u32)) {
prop_assert!(PwmPeriod::try_from_hz(hz).is_none());
}
#[test]
fn try_from_hz_rejects_out_of_range(hz in (PWM_CLOCK_HZ + 1)..=u32::MAX) {
prop_assert!(PwmPeriod::try_from_hz(hz).is_none());
}
#[test]
fn duty_within_period(count in 1u16..=u16::MAX, pct in 0u8..=100) {
let p = PwmPeriod::from_count(count).unwrap();
let d = p.duty_count(Duty::from_percent(pct).unwrap());
prop_assert!(d <= p.count(), "duty {} > period {} (pct {})", d, p.count(), pct);
}
#[test]
fn duty_validates(pct in any::<u8>()) {
prop_assert_eq!(Duty::from_percent(pct).is_some(), pct <= 100);
}
#[test]
fn start_mask_single_bit(ch in 0u8..8) {
let mask = 1u32 << ch;
prop_assert_eq!(mask.count_ones(), 1);
}
}
}