mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-27 20:30:29 +00:00
Merge pull request #4646 from Iooon/mspm0-adc
MSPM0: add adc implementation
This commit is contained in:
commit
bbc93851fb
@ -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
|
- fix gpio interrupt not being set for mspm0l110x
|
||||||
- feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574)
|
- feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574)
|
||||||
- feat: Add MSPM0C1105/C1106 support
|
- feat: Add MSPM0C1105/C1106 support
|
||||||
|
- feat: Add adc implementation (#4646)
|
||||||
|
@ -68,6 +68,7 @@ fn generate_code() {
|
|||||||
g.extend(generate_pin_trait_impls());
|
g.extend(generate_pin_trait_impls());
|
||||||
g.extend(generate_groups());
|
g.extend(generate_groups());
|
||||||
g.extend(generate_dma_channel_count());
|
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_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||||
let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string();
|
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; }
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
struct Singleton {
|
struct Singleton {
|
||||||
name: String,
|
name: String,
|
||||||
@ -561,6 +578,7 @@ fn generate_peripheral_instances() -> TokenStream {
|
|||||||
"uart" => Some(quote! { impl_uart_instance!(#peri); }),
|
"uart" => Some(quote! { impl_uart_instance!(#peri); }),
|
||||||
"i2c" => Some(quote! { impl_i2c_instance!(#peri, #fifo_size); }),
|
"i2c" => Some(quote! { impl_i2c_instance!(#peri, #fifo_size); }),
|
||||||
"wwdt" => Some(quote! { impl_wwdt_instance!(#peri); }),
|
"wwdt" => Some(quote! { impl_wwdt_instance!(#peri); }),
|
||||||
|
"adc" => Some(quote! { impl_adc_instance!(#peri); }),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -609,6 +627,10 @@ fn generate_pin_trait_impls() -> TokenStream {
|
|||||||
("uart", "RTS") => Some(quote! { impl_uart_rts_pin!(#peri, #pin_name, #pf); }),
|
("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", "SDA") => Some(quote! { impl_i2c_sda_pin!(#peri, #pin_name, #pf); }),
|
||||||
("i2c", "SCL") => Some(quote! { impl_i2c_scl_pin!(#peri, #pin_name, #pf); }),
|
("i2c", "SCL") => Some(quote! { impl_i2c_scl_pin!(#peri, #pin_name, #pf); }),
|
||||||
|
("adc", s) => {
|
||||||
|
let signal = s.parse::<u8>().unwrap();
|
||||||
|
Some(quote! { impl_adc_pin!(#peri, #pin_name, #signal); })
|
||||||
|
}
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
510
embassy-mspm0/src/adc.rs
Normal file
510
embassy-mspm0/src/adc.rs
Normal file
@ -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<T: Instance> {
|
||||||
|
_phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||||
|
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<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T::Interrupt, InterruptHandler<T>> + '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<T>>) {
|
||||||
|
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<T>>) -> 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<T>>, 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<T>>) -> 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<Item = (&'a Peri<'d, AnyAdcChannel<T>>, 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<T> {
|
||||||
|
pub(crate) channel: u8,
|
||||||
|
pub(crate) _phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_peripheral!(AnyAdcChannel<T: Instance>);
|
||||||
|
impl<T: Instance> AdcChannel<T> for AnyAdcChannel<T> {}
|
||||||
|
impl<T: Instance> SealedAdcChannel<T> for AnyAdcChannel<T> {
|
||||||
|
fn channel(&self) -> u8 {
|
||||||
|
self.channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AnyAdcChannel<T> {
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn get_hw_channel(&self) -> u8 {
|
||||||
|
self.channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ADC channel.
|
||||||
|
#[allow(private_bounds)]
|
||||||
|
pub trait AdcChannel<T>: PeripheralType + Into<AnyAdcChannel<T>> + SealedAdcChannel<T> + Sized {}
|
||||||
|
|
||||||
|
pub(crate) trait SealedAdcChannel<T> {
|
||||||
|
fn setup(&self) {}
|
||||||
|
|
||||||
|
fn channel(&self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_adc_pin {
|
||||||
|
($inst: ident, $pin: ident, $ch: expr) => {
|
||||||
|
impl crate::adc::AdcChannel<peripherals::$inst> for crate::peripherals::$pin {}
|
||||||
|
impl crate::adc::SealedAdcChannel<peripherals::$inst> for crate::peripherals::$pin {
|
||||||
|
fn setup(&self) {
|
||||||
|
crate::gpio::SealedPin::set_as_analog(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn channel(&self) -> u8 {
|
||||||
|
$ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::peripherals::$pin> for crate::adc::AnyAdcChannel<peripherals::$inst> {
|
||||||
|
fn from(val: crate::peripherals::$pin) -> Self {
|
||||||
|
crate::adc::SealedAdcChannel::<peripherals::$inst>::setup(&val);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
channel: crate::adc::SealedAdcChannel::<peripherals::$inst>::channel(&val),
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -13,6 +13,7 @@ pub(crate) mod fmt;
|
|||||||
// This must be declared early as well for
|
// This must be declared early as well for
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
pub mod adc;
|
||||||
pub mod dma;
|
pub mod dma;
|
||||||
pub mod gpio;
|
pub mod gpio;
|
||||||
pub mod i2c;
|
pub mod i2c;
|
||||||
|
39
examples/mspm0g3507/src/bin/adc.rs
Normal file
39
examples/mspm0g3507/src/bin/adc.rs
Normal file
@ -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<peripherals::ADC0>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
}
|
39
examples/mspm0l1306/src/bin/adc.rs
Normal file
39
examples/mspm0l1306/src/bin/adc.rs
Normal file
@ -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<peripherals::ADC0>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user