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) diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index e8364e31a..d294bc422 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(&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(); @@ -220,6 +221,22 @@ fn generate_dma_channel_count() -> TokenStream { quote! { pub const DMA_CHANNELS: usize = #count; } } +fn generate_adc_constants(cfgs: &mut CfgSet) -> TokenStream { + let vrsel = METADATA.adc_vrsel; + let memctl = METADATA.adc_memctl; + + cfgs.declare("adc_neg_vref"); + match vrsel { + 3 => (), + 5 => cfgs.enable("adc_neg_vref"), + _ => panic!("Unsupported ADC VRSEL value: {vrsel}"), + } + quote! { + pub const ADC_VRSEL: u8 = #vrsel; + pub const ADC_MEMCTL: u8 = #memctl; + } +} + #[derive(Debug, Clone)] struct Singleton { name: String, @@ -561,6 +578,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 +627,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..5b93e9a6e --- /dev/null +++ b/embassy-mspm0/src/adc.rs @@ -0,0 +1,510 @@ +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +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, +} + +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, + } + } +} + +#[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. +#[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. + /// + /// 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, +} + +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: 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: Config) -> 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: Config, + _irqs: impl 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: &Peri<'d, 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: &Peri<'d, 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> { + /// 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; + 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: &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| { + 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: &Peri<'d, 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 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; + /// defmt::info!("Measurements: {}", readings); + /// ``` + pub async fn read_sequence<'a>( + &mut self, + sequence: impl ExactSizeIterator>, Vrsel)>, + readings: &mut [u16], + ) where + 'd: '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() <= Self::MAX_SEQUENCE_LEN, + "Asynchronous read sequence cannot be more than {} in length", + Self::MAX_SEQUENCE_LEN + ); + + // 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: PeripheralType + SealedInstance { + 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 { + pub(crate) channel: u8, + pub(crate) _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: PeripheralType + Into> + SealedAdcChannel + Sized {} + +pub(crate) trait SealedAdcChannel { + fn setup(&self) {} + + fn channel(&self) -> u8; +} + +macro_rules! impl_adc_pin { + ($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/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..ceccc7c02 --- /dev/null +++ b/examples/mspm0g3507/src/bin/adc.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +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 _}; + +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()); + + // Configure adc with sequence 0 to 1 + let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); + let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PB20.into(), Vrsel::VddaVssa)]; + let mut readings = [0u16; 2]; + + loop { + 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). + 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..2806b98cc --- /dev/null +++ b/examples/mspm0l1306/src/bin/adc.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +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 _}; + +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()); + + // Configure adc with sequence 0 to 1 + let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); + let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PA20.into(), Vrsel::VddaVssa)]; + let mut readings = [0u16; 2]; + + loop { + 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). + 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; + } +}