From bbcaab13bc074d8223b43d8e05682b708c192d78 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Mon, 8 Sep 2025 09:10:16 +0200 Subject: [PATCH 1/6] mspm0-adc: add adc with examples --- embassy-mspm0/build.rs | 59 ++++ embassy-mspm0/src/adc.rs | 483 +++++++++++++++++++++++++++++ embassy-mspm0/src/lib.rs | 1 + examples/mspm0g3507/src/bin/adc.rs | 48 +++ examples/mspm0l1306/src/bin/adc.rs | 48 +++ 5 files changed, 639 insertions(+) create mode 100644 embassy-mspm0/src/adc.rs create mode 100644 examples/mspm0g3507/src/bin/adc.rs create mode 100644 examples/mspm0l1306/src/bin/adc.rs diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index e8364e31a..ad90b5223 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs @@ -68,6 +68,7 @@ fn generate_code() { g.extend(generate_pin_trait_impls()); g.extend(generate_groups()); g.extend(generate_dma_channel_count()); + g.extend(generate_adc_constants()); let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); @@ -220,6 +221,59 @@ fn generate_dma_channel_count() -> TokenStream { quote! { pub const DMA_CHANNELS: usize = #count; } } +fn generate_adc_constants() -> TokenStream { + let vrsel = METADATA.adc_vrsel; + let memctl = METADATA.adc_memctl; + + if vrsel == 3 { + quote! { + pub const ADC_VRSEL: u8 = #vrsel; + pub const ADC_MEMCTL: u8 = #memctl; + + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + /// Reference voltage (Vref) selection for the ADC channels. + pub enum Vrsel { + /// VDDA reference + VddaVssa = 0, + + /// External reference from pin + ExtrefVrefm = 1, + + /// Internal reference + IntrefVssa = 2, + } + } + } else if vrsel == 5 { + quote! { + pub const ADC_VRSEL: u8 = #vrsel; + pub const ADC_MEMCTL: u8 = #memctl; + + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + /// Reference voltage (Vref) selection for the ADC channels. + pub enum Vrsel { + /// VDDA reference + VddaVssa = 0, + + /// External reference from pin + ExtrefVrefm = 1, + + /// Internal reference + IntrefVssa = 2, + + /// VDDA and VREFM connected to VREF+ and VREF- of ADC + VddaVrefm = 3, + + /// INTREF and VREFM connected to VREF+ and VREF- of ADC + IntrefVrefm = 4, + } + } + } else { + panic!("Unsupported ADC VRSEL value: {vrsel}"); + } +} + #[derive(Debug, Clone)] struct Singleton { name: String, @@ -561,6 +615,7 @@ fn generate_peripheral_instances() -> TokenStream { "uart" => Some(quote! { impl_uart_instance!(#peri); }), "i2c" => Some(quote! { impl_i2c_instance!(#peri, #fifo_size); }), "wwdt" => Some(quote! { impl_wwdt_instance!(#peri); }), + "adc" => Some(quote! { impl_adc_instance!(#peri); }), _ => None, }; @@ -609,6 +664,10 @@ fn generate_pin_trait_impls() -> TokenStream { ("uart", "RTS") => Some(quote! { impl_uart_rts_pin!(#peri, #pin_name, #pf); }), ("i2c", "SDA") => Some(quote! { impl_i2c_sda_pin!(#peri, #pin_name, #pf); }), ("i2c", "SCL") => Some(quote! { impl_i2c_scl_pin!(#peri, #pin_name, #pf); }), + ("adc", s) => { + let signal = s.parse::().unwrap(); + Some(quote! { impl_adc_pin!(#peri, #pin_name, #signal); }) + } _ => None, }; 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; diff --git a/examples/mspm0g3507/src/bin/adc.rs b/examples/mspm0g3507/src/bin/adc.rs new file mode 100644 index 000000000..fed8b9dd3 --- /dev/null +++ b/examples/mspm0g3507/src/bin/adc.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::adc::{self, Adc, AdcChannel, AdcConfig, Resolution, Vrsel}; +use embassy_mspm0::{bind_interrupts, peripherals, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +bind_interrupts!(struct Irqs { + ADC0 => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Config::default()); + + let adc_config = AdcConfig { + resolution: Resolution::BIT12, + vr_select: Vrsel::VddaVssa, + sample_time: 50, + }; + + // Configure adc with sequence 0 to 1 + let mut adc = Adc::new_async(p.ADC0, adc_config, Irqs); + let pin1 = p.PA22.degrade_adc(); + let pin2 = p.PB20.degrade_adc(); + let sequence = [(&pin1, Vrsel::VddaVssa), (&pin2, Vrsel::VddaVssa)]; + let mut readings = [0u16; 2]; + let mut pin3 = p.PA27; + + loop { + let r = adc.read_channel(&mut pin3).await; + info!("Raw adc PA27: {}", r); + // With a voltage range of 0-3.3V and a resolution of 12 bits, the raw value can be + // approximated to voltage (~0.0008 per step). + let mut x = r as u32; + x = x * 8; + info!("Adc voltage PA27: {},{:#04}", x / 10_000, x % 10_000); + // Read a sequence of channels + adc.read_sequence(sequence.into_iter(), &mut readings).await; + info!("Raw adc sequence: {}", readings); + + Timer::after_millis(400).await; + } +} diff --git a/examples/mspm0l1306/src/bin/adc.rs b/examples/mspm0l1306/src/bin/adc.rs new file mode 100644 index 000000000..9ede31fed --- /dev/null +++ b/examples/mspm0l1306/src/bin/adc.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::adc::{self, Adc, AdcChannel, AdcConfig, Resolution, Vrsel}; +use embassy_mspm0::{bind_interrupts, peripherals, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +bind_interrupts!(struct Irqs { + ADC0 => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Config::default()); + + let adc_config = AdcConfig { + resolution: Resolution::BIT12, + vr_select: Vrsel::VddaVssa, + sample_time: 50, + }; + + // Configure adc with sequence 0 to 1 + let mut adc = Adc::new_async(p.ADC0, adc_config, Irqs); + let pin1 = p.PA22.degrade_adc(); + let pin2 = p.PA20.degrade_adc(); + let sequence = [(&pin1, Vrsel::VddaVssa), (&pin2, Vrsel::VddaVssa)]; + let mut readings = [0u16; 2]; + let mut pin3 = p.PA27; + + loop { + let r = adc.read_channel(&mut pin3).await; + info!("Raw adc PA27: {}", r); + // With a voltage range of 0-3.3V and a resolution of 12 bits, the raw value can be + // approximated to voltage (~0.0008 per step). + let mut x = r as u32; + x = x * 8; + info!("Adc voltage PA27: {},{:#04}", x / 10_000, x % 10_000); + // Read a sequence of channels + adc.read_sequence(sequence.into_iter(), &mut readings).await; + info!("Raw adc sequence: {}", readings); + + Timer::after_millis(400).await; + } +} From e21f5e62c4ecfaaeb9f58d7e526fc19ef58b85fb Mon Sep 17 00:00:00 2001 From: crispaudio Date: Mon, 8 Sep 2025 09:24:11 +0200 Subject: [PATCH 2/6] mspm0-adc: add to changelog --- embassy-mspm0/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-mspm0/CHANGELOG.md b/embassy-mspm0/CHANGELOG.md index c7da4eb33..b846f21b1 100644 --- a/embassy-mspm0/CHANGELOG.md +++ b/embassy-mspm0/CHANGELOG.md @@ -12,3 +12,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix gpio interrupt not being set for mspm0l110x - feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574) - feat: Add MSPM0C1105/C1106 support +- feat: Add adc implementation (#4646) From a1e387e6a5729e2ca6486a9a0d44d4af13d0b423 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Mon, 8 Sep 2025 09:58:35 +0200 Subject: [PATCH 3/6] mspm0-adc: fix rustfmt imports --- embassy-mspm0/src/adc.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/embassy-mspm0/src/adc.rs b/embassy-mspm0/src/adc.rs index 32fea4453..e4ba81139 100644 --- a/embassy-mspm0/src/adc.rs +++ b/embassy-mspm0/src/adc.rs @@ -1,17 +1,17 @@ #![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; +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::mode::{Async, Blocking, Mode}; +use crate::pac::adc::{vals, Adc as Regs}; +use crate::{interrupt, Peri}; + /// Interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, From 7b9fe7e6398a8bce236da904b251b4cb424150fb Mon Sep 17 00:00:00 2001 From: crispaudio Date: Tue, 9 Sep 2025 22:21:10 +0200 Subject: [PATCH 4/6] mspm0-adc: remove dynamic vrsel and cleanup --- embassy-mspm0/build.rs | 55 +++++------------------------- embassy-mspm0/src/adc.rs | 53 ++++++++++++++++++++++------ examples/mspm0g3507/src/bin/adc.rs | 10 ++---- examples/mspm0l1306/src/bin/adc.rs | 10 ++---- 4 files changed, 56 insertions(+), 72 deletions(-) diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index ad90b5223..4bfeb5626 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs @@ -225,52 +225,15 @@ fn generate_adc_constants() -> TokenStream { let vrsel = METADATA.adc_vrsel; let memctl = METADATA.adc_memctl; - if vrsel == 3 { - quote! { - pub const ADC_VRSEL: u8 = #vrsel; - pub const ADC_MEMCTL: u8 = #memctl; - - #[derive(Clone, Copy, PartialEq, Eq, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - /// Reference voltage (Vref) selection for the ADC channels. - pub enum Vrsel { - /// VDDA reference - VddaVssa = 0, - - /// External reference from pin - ExtrefVrefm = 1, - - /// Internal reference - IntrefVssa = 2, - } - } - } else if vrsel == 5 { - quote! { - pub const ADC_VRSEL: u8 = #vrsel; - pub const ADC_MEMCTL: u8 = #memctl; - - #[derive(Clone, Copy, PartialEq, Eq, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - /// Reference voltage (Vref) selection for the ADC channels. - pub enum Vrsel { - /// VDDA reference - VddaVssa = 0, - - /// External reference from pin - ExtrefVrefm = 1, - - /// Internal reference - IntrefVssa = 2, - - /// VDDA and VREFM connected to VREF+ and VREF- of ADC - VddaVrefm = 3, - - /// INTREF and VREFM connected to VREF+ and VREF- of ADC - IntrefVrefm = 4, - } - } - } else { - panic!("Unsupported ADC VRSEL value: {vrsel}"); + println!("cargo::rustc-check-cfg=cfg(adc_neg_vref)"); + match vrsel { + 3 => (), + 5 => println!("cargo:rustc-cfg=adc_neg_vref"), + _ => panic!("Unsupported ADC VRSEL value: {vrsel}"), + } + quote! { + pub const ADC_VRSEL: u8 = #vrsel; + pub const ADC_MEMCTL: u8 = #memctl; } } diff --git a/embassy-mspm0/src/adc.rs b/embassy-mspm0/src/adc.rs index e4ba81139..71569aef0 100644 --- a/embassy-mspm0/src/adc.rs +++ b/embassy-mspm0/src/adc.rs @@ -58,10 +58,32 @@ impl Resolution { } } -pub use crate::_generated::Vrsel; +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Reference voltage (Vref) selection for the ADC channels. +pub enum Vrsel { + /// VDDA reference + VddaVssa = 0, + + /// External reference from pin + ExtrefVrefm = 1, + + /// Internal reference + IntrefVssa = 2, + + /// VDDA and VREFM connected to VREF+ and VREF- of ADC + #[cfg(adc_neg_vref)] + VddaVrefm = 3, + + /// INTREF and VREFM connected to VREF+ and VREF- of ADC + #[cfg(adc_neg_vref)] + IntrefVrefm = 4, +} /// ADC configuration. -pub struct AdcConfig { +#[derive(Copy, Clone)] +#[non_exhaustive] +pub struct Config { /// Resolution of the ADC conversion. The number of bits used to represent an ADC measurement. pub resolution: Resolution, /// ADC voltage reference selection. @@ -73,19 +95,29 @@ pub struct AdcConfig { pub sample_time: u16, } +impl Default for Config { + fn default() -> Self { + Self { + resolution: Resolution::BIT12, + vr_select: Vrsel::VddaVssa, + sample_time: 50, + } + } +} + /// 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, + config: Config, _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 { + pub fn new_blocking(peri: Peri<'d, T>, config: Config) -> Self { let mut this = Self { adc: peri, info: T::info(), @@ -102,10 +134,8 @@ 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, + config: Config, + _irqs: impl interrupt::typelevel::Binding> + 'd, ) -> Self { let mut this = Self { adc: peri, @@ -244,6 +274,9 @@ impl<'d, T: Instance, M: Mode> Adc<'d, T, M> { } impl<'d, T: Instance> Adc<'d, T, Async> { + /// Maximum length allowed for [`Self::read_sequence`]. + pub const MAX_SEQUENCE_LEN: usize = ADC_MEMCTL as usize; + async fn wait_for_conversion(&self) { let info = self.info; let state = self.state; @@ -337,9 +370,9 @@ impl<'d, T: Instance> Adc<'d, T, Async> { "Sequence length must be equal to readings length" ); assert!( - sequence.len() <= ADC_MEMCTL as usize, + sequence.len() <= Self::MAX_SEQUENCE_LEN, "Asynchronous read sequence cannot be more than {} in length", - ADC_MEMCTL + Self::MAX_SEQUENCE_LEN ); // CTL0.ENC must be 0 to write the MEMCTL register diff --git a/examples/mspm0g3507/src/bin/adc.rs b/examples/mspm0g3507/src/bin/adc.rs index fed8b9dd3..73711c75c 100644 --- a/examples/mspm0g3507/src/bin/adc.rs +++ b/examples/mspm0g3507/src/bin/adc.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_mspm0::adc::{self, Adc, AdcChannel, AdcConfig, Resolution, Vrsel}; +use embassy_mspm0::adc::{self, Adc, AdcChannel, Vrsel}; use embassy_mspm0::{bind_interrupts, peripherals, Config}; use embassy_time::Timer; use {defmt_rtt as _, panic_halt as _}; @@ -17,14 +17,8 @@ async fn main(_spawner: Spawner) -> ! { info!("Hello world!"); let p = embassy_mspm0::init(Config::default()); - let adc_config = AdcConfig { - resolution: Resolution::BIT12, - vr_select: Vrsel::VddaVssa, - sample_time: 50, - }; - // Configure adc with sequence 0 to 1 - let mut adc = Adc::new_async(p.ADC0, adc_config, Irqs); + let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); let pin1 = p.PA22.degrade_adc(); let pin2 = p.PB20.degrade_adc(); let sequence = [(&pin1, Vrsel::VddaVssa), (&pin2, Vrsel::VddaVssa)]; diff --git a/examples/mspm0l1306/src/bin/adc.rs b/examples/mspm0l1306/src/bin/adc.rs index 9ede31fed..a0c2c0cff 100644 --- a/examples/mspm0l1306/src/bin/adc.rs +++ b/examples/mspm0l1306/src/bin/adc.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_mspm0::adc::{self, Adc, AdcChannel, AdcConfig, Resolution, Vrsel}; +use embassy_mspm0::adc::{self, Adc, AdcChannel, Vrsel}; use embassy_mspm0::{bind_interrupts, peripherals, Config}; use embassy_time::Timer; use {defmt_rtt as _, panic_halt as _}; @@ -17,14 +17,8 @@ async fn main(_spawner: Spawner) -> ! { info!("Hello world!"); let p = embassy_mspm0::init(Config::default()); - let adc_config = AdcConfig { - resolution: Resolution::BIT12, - vr_select: Vrsel::VddaVssa, - sample_time: 50, - }; - // Configure adc with sequence 0 to 1 - let mut adc = Adc::new_async(p.ADC0, adc_config, Irqs); + let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); let pin1 = p.PA22.degrade_adc(); let pin2 = p.PA20.degrade_adc(); let sequence = [(&pin1, Vrsel::VddaVssa), (&pin2, Vrsel::VddaVssa)]; From b2fa01cec7164980333a17355af215feb3cb33c2 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Wed, 10 Sep 2025 09:22:50 +0200 Subject: [PATCH 5/6] mspm0-adc: use CfgSet to enable vrsel cfg option --- embassy-mspm0/build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index 4bfeb5626..d294bc422 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs @@ -68,7 +68,7 @@ fn generate_code() { g.extend(generate_pin_trait_impls()); g.extend(generate_groups()); g.extend(generate_dma_channel_count()); - g.extend(generate_adc_constants()); + g.extend(generate_adc_constants(&mut cfgs)); let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); @@ -221,14 +221,14 @@ fn generate_dma_channel_count() -> TokenStream { quote! { pub const DMA_CHANNELS: usize = #count; } } -fn generate_adc_constants() -> TokenStream { +fn generate_adc_constants(cfgs: &mut CfgSet) -> TokenStream { let vrsel = METADATA.adc_vrsel; let memctl = METADATA.adc_memctl; - println!("cargo::rustc-check-cfg=cfg(adc_neg_vref)"); + cfgs.declare("adc_neg_vref"); match vrsel { 3 => (), - 5 => println!("cargo:rustc-cfg=adc_neg_vref"), + 5 => cfgs.enable("adc_neg_vref"), _ => panic!("Unsupported ADC VRSEL value: {vrsel}"), } quote! { From 31b5a3f0a4fafd425aef34b9d6fb93ead851b4c6 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Sun, 14 Sep 2025 01:34:49 +0200 Subject: [PATCH 6/6] mspm0-adc: implement From for AnyAdcChannel --- embassy-mspm0/src/adc.rs | 64 ++++++++++++++---------------- examples/mspm0g3507/src/bin/adc.rs | 9 ++--- examples/mspm0l1306/src/bin/adc.rs | 9 ++--- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/embassy-mspm0/src/adc.rs b/embassy-mspm0/src/adc.rs index 71569aef0..5b93e9a6e 100644 --- a/embassy-mspm0/src/adc.rs +++ b/embassy-mspm0/src/adc.rs @@ -217,7 +217,7 @@ impl<'d, T: Instance, M: Mode> Adc<'d, T, M> { }); } - fn setup_blocking_channel(&mut self, channel: &mut impl AdcChannel) { + fn setup_blocking_channel(&mut self, channel: &Peri<'d, impl AdcChannel>) { channel.setup(); // CTL0.ENC must be 0 to write the MEMCTL register @@ -262,7 +262,7 @@ impl<'d, T: Instance, M: Mode> Adc<'d, T, M> { } /// Read one ADC channel in blocking mode using the config provided at initialization. - pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + pub fn blocking_read(&mut self, channel: &Peri<'d, impl AdcChannel>) -> u16 { self.setup_blocking_channel(channel); self.enable_conversion(); self.start_conversion(); @@ -292,7 +292,7 @@ impl<'d, T: Instance> Adc<'d, T, Async> { .await; } - fn setup_async_channel(&self, id: usize, channel: &impl AdcChannel, vrsel: Vrsel) { + fn setup_async_channel(&self, id: usize, channel: &Peri<'d, impl AdcChannel>, vrsel: Vrsel) { let vrsel = vals::Vrsel::from_bits(vrsel as u8); // Conversion mem config self.info.regs.memctl(id).modify(|reg| { @@ -316,7 +316,7 @@ impl<'d, T: Instance> Adc<'d, T, Async> { } /// Read one ADC channel asynchronously using the config provided at initialization. - pub async fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + pub async fn read_channel(&mut self, channel: &Peri<'d, impl AdcChannel>) -> u16 { channel.setup(); // CTL0.ENC must be 0 to write the MEMCTL register @@ -345,24 +345,18 @@ impl<'d, T: Instance> Adc<'d, T, Async> { /// 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 sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PA20.into(), Vrsel::VddaVssa)]; /// let mut readings = [0u16; 2]; /// - /// adc.read_sequence( - /// sequence.into_iter(), - /// &mut readings, - /// ) - /// .await; + /// 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)>, + sequence: impl ExactSizeIterator>, Vrsel)>, readings: &mut [u16], ) where - T: 'a, + 'd: 'a, { assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); assert!( @@ -398,7 +392,7 @@ impl<'d, T: Instance> Adc<'d, T, Async> { /// Peripheral instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + PeripheralType { +pub trait Instance: PeripheralType + SealedInstance { type Interrupt: crate::interrupt::typelevel::Interrupt; } @@ -460,8 +454,8 @@ macro_rules! impl_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, + pub(crate) channel: u8, + pub(crate) _phantom: PhantomData, } impl_peripheral!(AnyAdcChannel); @@ -481,36 +475,36 @@ impl AnyAdcChannel { /// 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 trait AdcChannel: PeripheralType + Into> + SealedAdcChannel + Sized {} pub(crate) trait SealedAdcChannel { - fn setup(&mut self) {} + fn setup(&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); + ($inst: ident, $pin: ident, $ch: expr) => { + impl crate::adc::AdcChannel for crate::peripherals::$pin {} + impl crate::adc::SealedAdcChannel for crate::peripherals::$pin { + fn setup(&self) { + crate::gpio::SealedPin::set_as_analog(self); } fn channel(&self) -> u8 { $ch } } + + impl From for crate::adc::AnyAdcChannel { + fn from(val: crate::peripherals::$pin) -> Self { + crate::adc::SealedAdcChannel::::setup(&val); + + Self { + channel: crate::adc::SealedAdcChannel::::channel(&val), + _phantom: core::marker::PhantomData, + } + } + } }; } diff --git a/examples/mspm0g3507/src/bin/adc.rs b/examples/mspm0g3507/src/bin/adc.rs index 73711c75c..ceccc7c02 100644 --- a/examples/mspm0g3507/src/bin/adc.rs +++ b/examples/mspm0g3507/src/bin/adc.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_mspm0::adc::{self, Adc, AdcChannel, Vrsel}; +use embassy_mspm0::adc::{self, Adc, Vrsel}; use embassy_mspm0::{bind_interrupts, peripherals, Config}; use embassy_time::Timer; use {defmt_rtt as _, panic_halt as _}; @@ -19,14 +19,11 @@ async fn main(_spawner: Spawner) -> ! { // Configure adc with sequence 0 to 1 let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); - let pin1 = p.PA22.degrade_adc(); - let pin2 = p.PB20.degrade_adc(); - let sequence = [(&pin1, Vrsel::VddaVssa), (&pin2, Vrsel::VddaVssa)]; + let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PB20.into(), Vrsel::VddaVssa)]; let mut readings = [0u16; 2]; - let mut pin3 = p.PA27; loop { - let r = adc.read_channel(&mut pin3).await; + let r = adc.read_channel(&p.PA27).await; info!("Raw adc PA27: {}", r); // With a voltage range of 0-3.3V and a resolution of 12 bits, the raw value can be // approximated to voltage (~0.0008 per step). diff --git a/examples/mspm0l1306/src/bin/adc.rs b/examples/mspm0l1306/src/bin/adc.rs index a0c2c0cff..2806b98cc 100644 --- a/examples/mspm0l1306/src/bin/adc.rs +++ b/examples/mspm0l1306/src/bin/adc.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_mspm0::adc::{self, Adc, AdcChannel, Vrsel}; +use embassy_mspm0::adc::{self, Adc, Vrsel}; use embassy_mspm0::{bind_interrupts, peripherals, Config}; use embassy_time::Timer; use {defmt_rtt as _, panic_halt as _}; @@ -19,14 +19,11 @@ async fn main(_spawner: Spawner) -> ! { // Configure adc with sequence 0 to 1 let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); - let pin1 = p.PA22.degrade_adc(); - let pin2 = p.PA20.degrade_adc(); - let sequence = [(&pin1, Vrsel::VddaVssa), (&pin2, Vrsel::VddaVssa)]; + let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PA20.into(), Vrsel::VddaVssa)]; let mut readings = [0u16; 2]; - let mut pin3 = p.PA27; loop { - let r = adc.read_channel(&mut pin3).await; + let r = adc.read_channel(&p.PA27).await; info!("Raw adc PA27: {}", r); // With a voltage range of 0-3.3V and a resolution of 12 bits, the raw value can be // approximated to voltage (~0.0008 per step).