From bbcaab13bc074d8223b43d8e05682b708c192d78 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Mon, 8 Sep 2025 09:10:16 +0200 Subject: mspm0-adc: add adc with examples --- embassy-mspm0/src/adc.rs | 483 +++++++++++++++++++++++++++++++++++++++++++++++ embassy-mspm0/src/lib.rs | 1 + 2 files changed, 484 insertions(+) create mode 100644 embassy-mspm0/src/adc.rs (limited to 'embassy-mspm0/src') diff --git a/embassy-mspm0/src/adc.rs b/embassy-mspm0/src/adc.rs new file mode 100644 index 000000000..32fea4453 --- /dev/null +++ b/embassy-mspm0/src/adc.rs @@ -0,0 +1,483 @@ +#![macro_use] + +use crate::interrupt; +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::mode::{Async, Blocking, Mode}; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use crate::pac::adc::{vals, Adc as Regs}; +use crate::Peri; +use embassy_hal_internal::{impl_peripheral, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + // Mis is cleared upon reading iidx + let iidx = T::info().regs.cpu_int(0).iidx().read().stat(); + // TODO: Running in sequence mode, we get an interrupt per finished result. It would be + // nice to wake up only after all results are finished. + if vals::CpuIntIidxStat::MEMRESIFG0 <= iidx && iidx <= vals::CpuIntIidxStat::MEMRESIFG23 { + T::state().waker.wake(); + } + } +} + +// Constants from the metapac crate +const ADC_VRSEL: u8 = crate::_generated::ADC_VRSEL; +const ADC_MEMCTL: u8 = crate::_generated::ADC_MEMCTL; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Conversion resolution of the ADC results. +pub enum Resolution { + /// 12-bits resolution + BIT12, + + /// 10-bits resolution + BIT10, + + /// 8-bits resolution + BIT8, +} + +impl Resolution { + /// Number of bits of the resolution. + pub fn bits(&self) -> u8 { + match self { + Resolution::BIT12 => 12, + Resolution::BIT10 => 10, + Resolution::BIT8 => 8, + } + } +} + +pub use crate::_generated::Vrsel; + +/// ADC configuration. +pub struct AdcConfig { + /// Resolution of the ADC conversion. The number of bits used to represent an ADC measurement. + pub resolution: Resolution, + /// ADC voltage reference selection. + /// + /// This value is used when reading a single channel. When reading a sequence + /// the vr_select is provided per channel. + pub vr_select: Vrsel, + /// The sample time in number of ADC sample clock cycles. + pub sample_time: u16, +} + +/// ADC (Analog to Digial Converter) Driver. +pub struct Adc<'d, T: Instance, M: Mode> { + #[allow(unused)] + adc: crate::Peri<'d, T>, + info: &'static Info, + state: &'static State, + config: AdcConfig, + _phantom: PhantomData, +} + +impl<'d, T: Instance> Adc<'d, T, Blocking> { + /// A new blocking ADC driver instance. + pub fn new_blocking(peri: Peri<'d, T>, config: AdcConfig) -> Self { + let mut this = Self { + adc: peri, + info: T::info(), + state: T::state(), + config, + _phantom: PhantomData, + }; + this.setup(); + this + } +} + +impl<'d, T: Instance> Adc<'d, T, Async> { + /// A new asynchronous ADC driver instance. + pub fn new_async( + peri: Peri<'d, T>, + config: AdcConfig, + _irqs: impl interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + 'd, + ) -> Self { + let mut this = Self { + adc: peri, + info: T::info(), + state: T::state(), + config, + _phantom: PhantomData, + }; + this.setup(); + unsafe { + this.info.interrupt.enable(); + } + this + } +} + +impl<'d, T: Instance, M: Mode> Adc<'d, T, M> { + const SINGLE_CHANNEL: u8 = 0; + + fn setup(&mut self) { + let config = &self.config; + assert!( + (config.vr_select as u8) < ADC_VRSEL, + "Reference voltage selection out of bounds" + ); + // Reset adc + self.info.regs.gprcm(0).rstctl().write(|reg| { + reg.set_resetstkyclr(true); + reg.set_resetassert(true); + reg.set_key(vals::ResetKey::KEY); + }); + + // Power up adc + self.info.regs.gprcm(0).pwren().modify(|reg| { + reg.set_enable(true); + reg.set_key(vals::PwrenKey::KEY); + }); + + // Wait for cycles similar to TI power setup + cortex_m::asm::delay(16); + + // Set clock config + self.info.regs.gprcm(0).clkcfg().modify(|reg| { + reg.set_key(vals::ClkcfgKey::KEY); + reg.set_sampclk(vals::Sampclk::SYSOSC); + }); + self.info.regs.ctl0().modify(|reg| { + reg.set_sclkdiv(vals::Sclkdiv::DIV_BY_4); + }); + self.info.regs.clkfreq().modify(|reg| { + reg.set_frange(vals::Frange::RANGE24TO32); + }); + + // Init single conversion with software trigger and auto sampling + // + // We use sequence to support sequence operation in the future, but only set up a single + // channel + self.info.regs.ctl1().modify(|reg| { + reg.set_conseq(vals::Conseq::SEQUENCE); + reg.set_sampmode(vals::Sampmode::AUTO); + reg.set_trigsrc(vals::Trigsrc::SOFTWARE); + }); + let res = match config.resolution { + Resolution::BIT12 => vals::Res::BIT_12, + Resolution::BIT10 => vals::Res::BIT_10, + Resolution::BIT8 => vals::Res::BIT_8, + }; + self.info.regs.ctl2().modify(|reg| { + // Startadd detemines the channel used in single mode. + reg.set_startadd(Self::SINGLE_CHANNEL); + reg.set_endadd(Self::SINGLE_CHANNEL); + reg.set_res(res); + reg.set_df(false); + }); + + // Set the sample time used by all channels for now + self.info.regs.scomp0().modify(|reg| { + reg.set_val(config.sample_time); + }); + } + + fn setup_blocking_channel(&mut self, channel: &mut impl AdcChannel) { + channel.setup(); + + // CTL0.ENC must be 0 to write the MEMCTL register + while self.info.regs.ctl0().read().enc() { + // Wait until the ADC is not in conversion mode + } + + // Conversion mem config + let vrsel = vals::Vrsel::from_bits(self.config.vr_select as u8); + self.info.regs.memctl(Self::SINGLE_CHANNEL as usize).modify(|reg| { + reg.set_chansel(channel.channel()); + reg.set_vrsel(vrsel); + reg.set_stime(vals::Stime::SEL_SCOMP0); + reg.set_avgen(false); + reg.set_bcsen(false); + reg.set_trig(vals::Trig::AUTO_NEXT); + reg.set_wincomp(false); + }); + self.info.regs.ctl2().modify(|reg| { + // Set end address to the number of used channels + reg.set_endadd(Self::SINGLE_CHANNEL); + }); + } + + fn enable_conversion(&mut self) { + // Enable conversion + self.info.regs.ctl0().modify(|reg| { + reg.set_enc(true); + }); + } + + fn start_conversion(&mut self) { + // Start conversion + self.info.regs.ctl1().modify(|reg| { + reg.set_sc(vals::Sc::START); + }); + } + + fn conversion_result(&mut self, channel_id: usize) -> u16 { + // Read result + self.info.regs.memres(channel_id).read().data() + } + + /// Read one ADC channel in blocking mode using the config provided at initialization. + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + self.setup_blocking_channel(channel); + self.enable_conversion(); + self.start_conversion(); + + while self.info.regs.ctl0().read().enc() {} + + self.conversion_result(Self::SINGLE_CHANNEL as usize) + } +} + +impl<'d, T: Instance> Adc<'d, T, Async> { + async fn wait_for_conversion(&self) { + let info = self.info; + let state = self.state; + poll_fn(move |cx| { + state.waker.register(cx.waker()); + + if !info.regs.ctl0().read().enc() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + } + + fn setup_async_channel(&self, id: usize, channel: &impl AdcChannel, vrsel: Vrsel) { + let vrsel = vals::Vrsel::from_bits(vrsel as u8); + // Conversion mem config + self.info.regs.memctl(id).modify(|reg| { + reg.set_chansel(channel.channel()); + reg.set_vrsel(vrsel); + reg.set_stime(vals::Stime::SEL_SCOMP0); + reg.set_avgen(false); + reg.set_bcsen(false); + reg.set_trig(vals::Trig::AUTO_NEXT); + reg.set_wincomp(false); + }); + + // Clear interrupt status + self.info.regs.cpu_int(0).iclr().write(|reg| { + reg.set_memresifg(id, true); + }); + // Enable interrupt + self.info.regs.cpu_int(0).imask().modify(|reg| { + reg.set_memresifg(id, true); + }); + } + + /// Read one ADC channel asynchronously using the config provided at initialization. + pub async fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + channel.setup(); + + // CTL0.ENC must be 0 to write the MEMCTL register + self.wait_for_conversion().await; + + self.info.regs.ctl2().modify(|reg| { + // Set end address to the number of used channels + reg.set_endadd(Self::SINGLE_CHANNEL); + }); + + self.setup_async_channel(Self::SINGLE_CHANNEL as usize, channel, self.config.vr_select); + + self.enable_conversion(); + self.start_conversion(); + self.wait_for_conversion().await; + + self.conversion_result(Self::SINGLE_CHANNEL as usize) + } + + /// Read one or multiple ADC channels using the Vrsel provided per channel. + /// + /// `sequence` iterator and `readings` must have the same length. + /// + /// Example + /// ```rust,ignore + /// use embassy_mspm0::adc::{Adc, AdcChannel, Vrsel}; + /// + /// let mut adc = Adc::new_async(p.ADC0, adc_config, Irqs); + /// let pin1 = p.PA14.degrade_adc(); + /// let pin2 = p.PA25.degrade_adc(); + /// let sequence = [(&pin1, Vrsel::VddaVssa), (&pin2, Vrsel::VddaVssa)]; + /// let mut readings = [0u16; 2]; + /// + /// adc.read_sequence( + /// sequence.into_iter(), + /// &mut readings, + /// ) + /// .await; + /// defmt::info!("Measurements: {}", readings); + /// ``` + pub async fn read_sequence<'a>( + &mut self, + sequence: impl ExactSizeIterator, Vrsel)>, + readings: &mut [u16], + ) where + T: 'a, + { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); + assert!( + sequence.len() <= ADC_MEMCTL as usize, + "Asynchronous read sequence cannot be more than {} in length", + ADC_MEMCTL + ); + + // CTL0.ENC must be 0 to write the MEMCTL register + self.wait_for_conversion().await; + + self.info.regs.ctl2().modify(|reg| { + // Set end address to the number of used channels + reg.set_endadd((sequence.len() - 1) as u8); + }); + + for (i, (channel, vrsel)) in sequence.enumerate() { + self.setup_async_channel(i, channel, vrsel); + } + self.enable_conversion(); + self.start_conversion(); + self.wait_for_conversion().await; + + for (i, r) in readings.iter_mut().enumerate() { + *r = self.conversion_result(i); + } + } +} + +/// Peripheral instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType { + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +/// Peripheral state. +pub(crate) struct State { + waker: AtomicWaker, +} + +impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +/// Peripheral information. +pub(crate) struct Info { + pub(crate) regs: Regs, + pub(crate) interrupt: Interrupt, +} + +/// Peripheral instance trait. +pub(crate) trait SealedInstance { + fn info() -> &'static Info; + fn state() -> &'static State; +} + +macro_rules! impl_adc_instance { + ($instance: ident) => { + impl crate::adc::SealedInstance for crate::peripherals::$instance { + fn info() -> &'static crate::adc::Info { + use crate::adc::Info; + use crate::interrupt::typelevel::Interrupt; + + static INFO: Info = Info { + regs: crate::pac::$instance, + interrupt: crate::interrupt::typelevel::$instance::IRQ, + }; + &INFO + } + + fn state() -> &'static crate::adc::State { + use crate::adc::State; + + static STATE: State = State::new(); + &STATE + } + } + + impl crate::adc::Instance for crate::peripherals::$instance { + type Interrupt = crate::interrupt::typelevel::$instance; + } + }; +} + +/// A type-erased channel for a given ADC instance. +/// +/// This is useful in scenarios where you need the ADC channels to have the same type, such as +/// storing them in an array. +pub struct AnyAdcChannel { + channel: u8, + _phantom: PhantomData, +} + +impl_peripheral!(AnyAdcChannel); +impl AdcChannel for AnyAdcChannel {} +impl SealedAdcChannel for AnyAdcChannel { + fn channel(&self) -> u8 { + self.channel + } +} + +impl AnyAdcChannel { + #[allow(unused)] + pub(crate) fn get_hw_channel(&self) -> u8 { + self.channel + } +} + +/// ADC channel. +#[allow(private_bounds)] +pub trait AdcChannel: SealedAdcChannel + Sized { + /// Allows an ADC channel to be converted into a type-erased [`AnyAdcChannel`]. + #[allow(unused_mut)] + fn degrade_adc(mut self) -> AnyAdcChannel { + self.setup(); + + AnyAdcChannel { + channel: self.channel(), + _phantom: PhantomData, + } + } +} + +pub(crate) trait SealedAdcChannel { + fn setup(&mut self) {} + + fn channel(&self) -> u8; +} + +macro_rules! impl_adc_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl crate::adc::AdcChannel for crate::Peri<'_, crate::peripherals::$pin> {} + impl crate::adc::SealedAdcChannel for crate::Peri<'_, crate::peripherals::$pin> { + fn setup(&mut self) { + ::set_as_analog(self); + } + + fn channel(&self) -> u8 { + $ch + } + } + }; +} diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs index 0cb19a379..13f0ce662 100644 --- a/embassy-mspm0/src/lib.rs +++ b/embassy-mspm0/src/lib.rs @@ -13,6 +13,7 @@ pub(crate) mod fmt; // This must be declared early as well for mod macros; +pub mod adc; pub mod dma; pub mod gpio; pub mod i2c; -- cgit