diff --git a/esp-hal/src/lib.rs b/esp-hal/src/lib.rs index df92a6d6f4d..2a9bd2b428a 100644 --- a/esp-hal/src/lib.rs +++ b/esp-hal/src/lib.rs @@ -216,6 +216,8 @@ pub mod rom; pub mod rsa; #[cfg(any(lp_clkrst, rtc_cntl))] pub mod rtc_cntl; +#[cfg(sdm)] +pub mod sdm; #[cfg(sha)] pub mod sha; #[cfg(any(spi0, spi1, spi2, spi3))] diff --git a/esp-hal/src/sdm.rs b/esp-hal/src/sdm.rs new file mode 100644 index 00000000000..5b12b55db2a --- /dev/null +++ b/esp-hal/src/sdm.rs @@ -0,0 +1,264 @@ +//! Sigma-Delta modulation peripheral driver. +//! + +use crate::{ + clock::Clocks, + gpio::{OutputSignal, PeripheralOutput}, + peripheral::{Peripheral, PeripheralRef}, + peripherals, + private, +}; +use fugit::HertzU32; + +/// Sigma-Delta modulation peripheral driver. +pub struct Sdm<'d, SD> { + _sd: PeripheralRef<'d, SD>, + channels_usage: u8, +} + +/// Channel errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Prescale out of range + PrescaleRange, + /// No free channels to use + NoChannels, +} + +impl<'d, SD> Sdm<'d, SD> +where + SD: RegisterAccess, +{ + /// Initialize driver using a given SD instance. + pub fn new(sd_instance: impl crate::peripheral::Peripheral

+ 'd) -> Self { + Self { + _sd: sd_instance.into_ref(), + channels_usage: 0, + } + } + + /// Configure and acquire channel. + pub fn enable_pin( + &mut self, + pin: impl Peripheral

+ 'd, + frequency: HertzU32, + ) -> Result, Error> { + crate::into_ref!(pin); + + let chs = self.channels_usage; + let chidx = self.alloc_channel()?; + let signal = CHANNELS[chidx as usize]; + + if chs == 0 { + SD::enable_clock(true); + } + + pin.connect_peripheral_to_output(signal, private::Internal); + + let mut channel = Channel { + _sdm: self, + pin, + chidx, + }; + + channel.set_frequency(frequency)?; + + Ok(channel) + } + + /// Deconfigure and release channel. + pub fn disable_pin(&mut self, channel: Channel<'d, SD, PIN>) { + let Channel { mut pin, chidx, .. } = channel; + + let signal = CHANNELS[chidx as usize]; + + pin.disconnect_from_peripheral_output(signal, private::Internal); + + self.dealloc_channel(chidx); + + if self.channels_usage == 0 { + SD::enable_clock(false); + } + } + + fn alloc_channel(&mut self) -> Result { + let mut usage = self.channels_usage; + let mut chidx: u8 = 0; + while usage & 1 != 0 && chidx < CHANNELS.len() as u8 { + usage >>= 1; + chidx += 1; + } + if chidx < CHANNELS.len() as u8 { + self.channels_usage |= 1 << chidx; + Ok(chidx) + } else { + Err(Error::NoChannels) + } + } + + fn dealloc_channel(&mut self, chidx: u8) { + self.channels_usage &= !(1 << chidx); + } +} + +/// Sigma-Delta modulation channel handle. +pub struct Channel<'d, SD, PIN> { + _sdm: &'d Sdm<'d, SD>, + pin: PeripheralRef<'d, PIN>, + chidx: u8, +} + +impl<'d, SD, PIN> Channel<'d, SD, PIN> +where + SD: RegisterAccess, +{ + /// Set raw pulse density + /// + /// Sigma-delta quantized density of one channel, the value ranges from -128 + /// to 127, recommended range is -90 ~ 90. The waveform is more like a + /// random one in this range. + pub fn set_pulse_density(&mut self, density: i8) { + SD::set_pulse_density(self.chidx, density); + } + + /// Set duty cycle + pub fn set_duty(&mut self, duty: u8) { + let density = duty as i16 - 128; + self.set_pulse_density(density as i8) + } + + /// Set raw prescale + /// + /// The divider of source clock, ranges from 1 to 256 + pub fn set_prescale(&mut self, prescale: u16) -> Result<(), Error> { + if (1..=256).contains(&prescale) { + SD::set_prescale(self.chidx, prescale); + Ok(()) + } else { + Err(Error::PrescaleRange) + } + } + + /// Set prescale using frequency + pub fn set_frequency(&mut self, frequency: HertzU32) -> Result<(), Error> { + let clocks = Clocks::get(); + let clock_frequency = clocks.apb_clock.to_Hz(); + let frequency = frequency.to_Hz(); + + let prescale = prescale_from_frequency(clock_frequency, frequency); + + self.set_prescale(prescale) + } +} + +mod ehal1 { + use embedded_hal::pwm::{Error as PwmError, ErrorKind, ErrorType, SetDutyCycle}; + use crate::gpio::OutputPin; + use super::{Channel, RegisterAccess, Error}; + + impl PwmError for Error { + fn kind(&self) -> ErrorKind { + ErrorKind::Other + } + } + + impl<'d, SD: RegisterAccess, PIN: OutputPin> ErrorType for Channel<'d, SD, PIN> { + type Error = Error; + } + + impl<'d, SD: RegisterAccess, PIN: OutputPin> SetDutyCycle for Channel<'d, SD, PIN> { + fn max_duty_cycle(&self) -> u16 { + 255 as u16 + } + + fn set_duty_cycle(&mut self, mut duty: u16) -> Result<(), Self::Error> { + let max = self.max_duty_cycle(); + duty = if duty > max { max } else { duty }; + self.set_duty(duty as u8); + Ok(()) + } + } +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +const CHANNELS: [OutputSignal; 8] = [ + OutputSignal::GPIO_SD0, + OutputSignal::GPIO_SD1, + OutputSignal::GPIO_SD2, + OutputSignal::GPIO_SD3, + OutputSignal::GPIO_SD4, + OutputSignal::GPIO_SD5, + OutputSignal::GPIO_SD6, + OutputSignal::GPIO_SD7, +]; + +#[cfg(any(esp32c3, esp32c6, esp32h2))] +const CHANNELS: [OutputSignal; 4] = [ + OutputSignal::GPIO_SD0, + OutputSignal::GPIO_SD1, + OutputSignal::GPIO_SD2, + OutputSignal::GPIO_SD3, +]; + +#[doc(hidden)] +pub trait RegisterAccess { + /// Enable/disable sigma/delta clock + fn enable_clock(en: bool); + + /// Set channel pulse density + fn set_pulse_density(ch: u8, density: i8); + + /// Set channel clock pre-scale + fn set_prescale(ch: u8, prescale: u16); +} + +impl RegisterAccess for peripherals::GPIO_SD { + fn enable_clock(en: bool) { + // The clk enable register does not exist on ESP32. + #[cfg(not(esp32))] + { + let sd = unsafe { &*Self::PTR }; + + sd.sigmadelta_misc() + .modify(|_, w| w.function_clk_en().bit(en)); + } + } + + fn set_pulse_density(ch: u8, density: i8) { + let sd = unsafe { &*Self::PTR }; + + sd.sigmadelta(ch as _) + .modify(|_, w| unsafe { w.in_().bits(density as _) }); + } + + fn set_prescale(ch: u8, prescale: u16) { + let sd = unsafe { &*Self::PTR }; + + sd.sigmadelta(ch as _) + .modify(|_, w| unsafe { w.prescale().bits((prescale - 1) as _) }); + } +} + +fn prescale_from_frequency(clk_freq: u32, req_freq: u32) -> u16 { + let pre = clk_freq / req_freq; + let err = clk_freq % req_freq; + + // Do the normal rounding and error >= (src/n + src/(n+1)) / 2, + // then carry the bit + let pre = if err >= clk_freq / (2 * pre * (pre + 1)) { + pre + 1 + } else { + pre + }; + + //pre.max(1).min(256) as _ + + /*if pre < 1 || pre > 256 { + Err(Error::PrescaleRange) + } else { + pre as _ + }*/ + + pre as _ +} diff --git a/esp-metadata/devices/esp32.toml b/esp-metadata/devices/esp32.toml index e5a70a666f4..6b885acf57a 100644 --- a/esp-metadata/devices/esp32.toml +++ b/esp-metadata/devices/esp32.toml @@ -33,6 +33,7 @@ peripherals = [ "rtc_io", "sdhost", "sens", + "sdm", "sha", "slc", "slchost", diff --git a/esp-metadata/devices/esp32c3.toml b/esp-metadata/devices/esp32c3.toml index 08a62907cb3..f385b633119 100644 --- a/esp-metadata/devices/esp32c3.toml +++ b/esp-metadata/devices/esp32c3.toml @@ -26,6 +26,7 @@ peripherals = [ "rsa", "rtc_cntl", "sensitive", + "sdm", "sha", "spi0", "spi1", diff --git a/esp-metadata/devices/esp32c6.toml b/esp-metadata/devices/esp32c6.toml index 128e8a06c24..4c57a6a917a 100644 --- a/esp-metadata/devices/esp32c6.toml +++ b/esp-metadata/devices/esp32c6.toml @@ -50,6 +50,7 @@ peripherals = [ "rmt", "rng", "rsa", + "sdm", "sha", "slchost", "soc_etm", diff --git a/esp-metadata/devices/esp32h2.toml b/esp-metadata/devices/esp32h2.toml index 901f4bd8ef9..803a1ad4bf7 100644 --- a/esp-metadata/devices/esp32h2.toml +++ b/esp-metadata/devices/esp32h2.toml @@ -44,6 +44,7 @@ peripherals = [ "rmt", "rng", "rsa", + "sdm", "sha", "soc_etm", "spi0", diff --git a/esp-metadata/devices/esp32p4.toml b/esp-metadata/devices/esp32p4.toml index 9d745becd85..4766b8efb7c 100644 --- a/esp-metadata/devices/esp32p4.toml +++ b/esp-metadata/devices/esp32p4.toml @@ -71,6 +71,7 @@ peripherals = [ # "rmt", # "rsa", # "sdhost", + # "sdm", # "sha", # "soc_etm", # "spi0", diff --git a/esp-metadata/devices/esp32s2.toml b/esp-metadata/devices/esp32s2.toml index 2b556cd00cf..0505462981f 100644 --- a/esp-metadata/devices/esp32s2.toml +++ b/esp-metadata/devices/esp32s2.toml @@ -29,6 +29,7 @@ peripherals = [ "rtc_i2c", "rtc_io", "sens", + "sdm", "sha", "spi0", "spi1", diff --git a/esp-metadata/devices/esp32s3.toml b/esp-metadata/devices/esp32s3.toml index 96fbe57278a..b9bd0d29658 100644 --- a/esp-metadata/devices/esp32s3.toml +++ b/esp-metadata/devices/esp32s3.toml @@ -37,6 +37,7 @@ peripherals = [ "rtc_io", "sens", "sensitive", + "sdm", "sha", "spi0", "spi1",