diff --git a/embassy-mspm0/Cargo.toml b/embassy-mspm0/Cargo.toml index 2e76eb5653..7683b6ce11 100644 --- a/embassy-mspm0/Cargo.toml +++ b/embassy-mspm0/Cargo.toml @@ -70,7 +70,7 @@ critical-section = "1.2.0" micromath = "2.0.0" # mspm0-metapac = { version = "" } -mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-d9f54366c65cec47957065f42fe04542b852a8cd" } +mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-cccbd8ec574f5f850067d9058a034959ae6d36be" } rand_core = "0.9" # Force no cache padding or the types are too large. maitake-sync = { version = "0.3.0", default-features = false, features = ["critical-section", "no-cache-pad"]} @@ -84,7 +84,7 @@ quote = "1.0.40" cfg_aliases = "0.2.1" # mspm0-metapac = { version = "", default-features = false, features = ["metadata"] } -mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-d9f54366c65cec47957065f42fe04542b852a8cd", default-features = false, features = ["metadata"] } +mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-cccbd8ec574f5f850067d9058a034959ae6d36be", default-features = false, features = ["metadata"] } [features] default = ["rt"] diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index c94894eda7..34afad4c1b 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs @@ -31,7 +31,11 @@ fn generate_code(cfgs: &mut CfgSet) { PathBuf::from(env::var_os("OUT_DIR").unwrap()).display(), ); - cfgs.declare_all(&["gpio_pb", "gpio_pc", "int_group1", "unicomm"]); + cfgs.declare_all(&["gpio_pb", "gpio_pc", "int_group1", "unicomm", "opa"]); + + if METADATA.peripherals.iter().any(|p| p.kind == "opa") { + cfgs.enable("opa"); + } let chip_name = match env::vars() .map(|(a, _)| a) @@ -68,6 +72,7 @@ fn generate_code(cfgs: &mut CfgSet) { g.extend(generate_interrupts()); g.extend(generate_peripheral_instances()); g.extend(generate_pin_trait_impls()); + g.extend(generate_opa_adc_channels()); g.extend(generate_groups()); g.extend(generate_dma_channel_count()); g.extend(generate_adc_constants(cfgs)); @@ -678,6 +683,7 @@ fn generate_peripheral_instances() -> TokenStream { "wwdt" => Some(quote! { impl_wwdt_instance!(#peri); }), "adc" => Some(quote! { impl_adc_instance!(#peri); }), "mathacl" => Some(quote! { impl_mathacl_instance!(#peri); }), + "opa" => Some(quote! { impl_opa_instance!(#peri); }), _ => None, }; @@ -703,6 +709,32 @@ fn generate_peripheral_instances() -> TokenStream { } } +fn generate_opa_adc_channels() -> TokenStream { + // OPA outputs are internally routed to fixed ADC channels. This mapping + // is not present in mspm0-data, so it is reproduced here from the TI + // SDK's `ADC12_internalConnections.js` (SysConfig metadata). + let mapping: &[(&str, &str, u8)] = match METADATA.family { + "mspm0g150x" | "mspm0g350x" => &[("OPA0", "ADC0", 13), ("OPA1", "ADC1", 13)], + "mspm0l130x" | "mspm0l134x" => &[("OPA0", "ADC0", 12), ("OPA1", "ADC0", 13)], + _ => &[], + }; + + let mut impls = Vec::::new(); + + for (opa, adc, channel) in mapping { + let exists = |name| METADATA.peripherals.iter().any(|p| p.name == name); + if exists(*opa) && exists(*adc) { + let opa = format_ident!("{}", opa); + let adc = format_ident!("{}", adc); + impls.push(quote! { impl_opa_adc_channel!(#opa, #adc, #channel); }); + } + } + + quote! { + #(#impls)* + } +} + fn generate_pin_trait_impls() -> TokenStream { let mut impls = Vec::::new(); @@ -729,6 +761,13 @@ fn generate_pin_trait_impls() -> TokenStream { ("tim", "CCP1_CMPL") => Some(quote! { impl_tim_pin!(#peri, #pin_name, #pf, CompCh1); }), ("tim", "CCP2_CMPL") => Some(quote! { impl_tim_pin!(#peri, #pin_name, #pf, CompCh2); }), ("tim", "CCP3_CMPL") => Some(quote! { impl_tim_pin!(#peri, #pin_name, #pf, CompCh3); }), + // OPA pin channels are the CFG.PSEL mux values from the + // MSPM0 TRM. IN2+ shares the DAC_OUT pad and is selected + // through the DAC12 PSEL channel. + ("opa", "IN0+") => Some(quote! { impl_opa_non_inverting_pin!(#peri, #pin_name, 1u8); }), + ("opa", "IN1+") => Some(quote! { impl_opa_non_inverting_pin!(#peri, #pin_name, 2u8); }), + ("opa", "IN2+") => Some(quote! { impl_opa_non_inverting_pin!(#peri, #pin_name, 3u8); }), + ("opa", "OUT") => Some(quote! { impl_opa_output_pin!(#peri, #pin_name); }), ("uart", "TX") => Some(quote! { impl_uart_tx_pin!(#peri, #pin_name, #pf); }), ("uart", "RX") => Some(quote! { impl_uart_rx_pin!(#peri, #pin_name, #pf); }), ("uart", "CTS") => Some(quote! { impl_uart_cts_pin!(#peri, #pin_name, #pf); }), diff --git a/embassy-mspm0/src/adc.rs b/embassy-mspm0/src/adc.rs index 9488016798..57fa4f8aa7 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: &Peri<'d, impl AdcChannel>) { + fn setup_blocking_channel(&mut self, channel: &Peri<'_, 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: &Peri<'d, impl AdcChannel>) -> u16 { + pub fn blocking_read(&mut self, channel: &Peri<'_, 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: &Peri<'d, impl AdcChannel>, vrsel: Vrsel) { + fn setup_async_channel(&self, id: usize, channel: &Peri<'_, 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: &Peri<'d, impl AdcChannel>) -> u16 { + pub async fn read_channel(&mut self, channel: &Peri<'_, impl AdcChannel>) -> u16 { channel.setup(); // CTL0.ENC must be 0 to write the MEMCTL register @@ -351,12 +351,12 @@ impl<'d, T: Instance> Adc<'d, T, Async> { /// adc.read_sequence(sequence.into_iter(), &mut readings).await; /// defmt::info!("Measurements: {}", readings); /// ``` - pub async fn read_sequence<'a>( + pub async fn read_sequence<'a, 'b: 'a>( &mut self, - sequence: impl ExactSizeIterator>, Vrsel)>, + sequence: impl ExactSizeIterator>, Vrsel)>, readings: &mut [u16], ) where - 'd: 'a, + T: 'b, { assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); assert!( diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs index 8004bd15f0..2a4e7c3996 100644 --- a/embassy-mspm0/src/lib.rs +++ b/embassy-mspm0/src/lib.rs @@ -22,6 +22,8 @@ pub mod i2c; pub mod i2c_target; #[cfg(any(mspm0g150x, mspm0g151x, mspm0g350x, mspm0g351x))] pub mod mathacl; +#[cfg(opa)] +pub mod opa; pub mod tim; #[cfg(any(mspm0g150x, mspm0g151x, mspm0g350x, mspm0g351x, mspm0l122x, mspm0l222x))] pub mod trng; diff --git a/embassy-mspm0/src/opa.rs b/embassy-mspm0/src/opa.rs new file mode 100644 index 0000000000..8abcca374b --- /dev/null +++ b/embassy-mspm0/src/opa.rs @@ -0,0 +1,424 @@ +//! Operational Amplifier (OPA) +//! +//! The MSPM0 OPA is a zero-drift, chopper-stabilized operational amplifier +//! with an internal programmable gain ladder. This driver currently exposes +//! the buffer and non-inverting PGA topologies (TRM "OPA Amplifier Modes"): +//! +//! - **Buffer** (unity gain follower): [`Opa::buffer_ext`] / [`Opa::buffer_int`] +//! - **Non-inverting PGA** (x2..x32): [`Opa::pga_ext`] / [`Opa::pga_int`] +//! +//! `_ext` variants drive the OPAx_OUT pin; `_int` variants keep the output +//! off the pins and only route it to the ADC. Both can be sampled by the ADC +//! through the internal channel returned by +//! [`OpaOutput::adc_channel`]/[`OpaInternalOutput::adc_channel`]. +//! +//! The non-inverting input can come from a pin or from an internal source +//! (DAC12, the COMP reference DAC8, VREF, ground, or the previous OPA for +//! cascading) — see [`NonInvertingInput`]. +//! +//! Chopping ([`Chopping`]) removes the amplifier's input offset, but the +//! standard mode modulates ripple onto the output; it is intended to be used +//! with the ADC's hardware averaging. When sampling the output without +//! averaging, leave chopping disabled and expect a few millivolts of offset. + +#![macro_use] + +use core::marker::PhantomData; + +use embassy_hal_internal::PeripheralType; + +use crate::Peri; +use crate::pac::opa::{regs, vals}; + +/// Gain-bandwidth selection (CFGBASE.GBW). +/// +/// The high setting increases both bandwidth and current consumption; see the +/// device datasheet for values. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum GainBandwidth { + /// Low gain bandwidth, lower current. + Low, + /// High gain bandwidth, higher current. + High, +} + +/// Chopping mode (CFG.CHOP). +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Chopping { + /// No chopping. The raw input offset voltage (up to a few millivolts, + /// multiplied by the gain) appears at the output. + Disabled, + /// Standard chopping. Removes the input offset but modulates ripple at + /// the chop frequency onto the output. + Standard, + /// Chop with post-averaging. Requires the output to be sampled by the + /// ADC in hardware averaging mode. + AdcAveraging, +} + +/// Configuration common to all OPA topologies. +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + /// Gain-bandwidth selection. Defaults to [`GainBandwidth::High`]. + pub gain_bandwidth: GainBandwidth, + /// Rail-to-rail input. Defaults to `true`. + pub rail_to_rail_input: bool, + /// Chopping mode. Defaults to [`Chopping::Disabled`]. + pub chopping: Chopping, +} + +impl Default for Config { + fn default() -> Self { + Self { + gain_bandwidth: GainBandwidth::High, + rail_to_rail_input: true, + chopping: Chopping::Disabled, + } + } +} + +/// Gain for the buffer and non-inverting PGA topologies. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Gain { + X1 = 0, + X2 = 1, + X4 = 2, + X8 = 3, + X16 = 4, + X32 = 5, +} + +/// A source for the non-inverting (+) input (CFG.PSEL). +/// +/// Created from an `OPAx_INy+` pin (which is configured for analog mode and +/// consumed — use [`Peri::reborrow`] to keep it), or from one of the +/// internal-source constructors. +pub struct NonInvertingInput<'d, T: Instance> { + channel: vals::Psel, + _phantom: PhantomData<(&'d (), T)>, +} + +impl<'d, T: Instance> NonInvertingInput<'d, T> { + const fn internal(channel: vals::Psel) -> Self { + Self { + channel, + _phantom: PhantomData, + } + } + + /// The DAC12 output, routed internally (only on devices with a DAC). + /// + /// This is the same channel as the `OPAx_IN2+` pad shared with DAC_OUT: + /// with the DAC disabled, an external voltage on that pad drives it. + pub const fn dac12() -> Self { + Self::internal(vals::Psel::DAC12OUT) + } + + /// The 8-bit reference DAC of the paired COMP peripheral. + pub const fn dac8() -> Self { + Self::internal(vals::Psel::DAC8OUT) + } + + /// The internal voltage reference (VREF). + pub const fn vref() -> Self { + Self::internal(vals::Psel::VREF) + } + + /// Analog ground. + pub const fn ground() -> Self { + Self::internal(vals::Psel::VSS) + } +} + +impl<'d, T: Instance, P: NonInvertingPin> From> for NonInvertingInput<'d, T> { + fn from(pin: Peri<'d, P>) -> Self { + SealedNonInvertingPin::setup(&*pin); + Self { + channel: vals::Psel::from_bits(SealedNonInvertingPin::channel(&*pin)), + _phantom: PhantomData, + } + } +} + +/// OPA driver. +/// +/// Power to the peripheral is enabled on construction and removed on drop. +/// Use the topology methods to configure and enable the amplifier. +pub struct Opa<'d, T: Instance> { + _peri: Peri<'d, T>, + chop: vals::Chop, +} + +/// An enabled OPA whose output drives the OPAx_OUT pin. +/// +/// Can also be sampled by the ADC via [`OpaOutput::adc_channel`]. The +/// amplifier is disabled when this is dropped. +pub struct OpaOutput<'d, T: Instance> { + _inner: &'d Opa<'d, T>, +} + +/// An enabled OPA whose output is only routed internally (to the ADC). +/// +/// Sample it via [`OpaInternalOutput::adc_channel`]. The amplifier is +/// disabled when this is dropped. +pub struct OpaInternalOutput<'d, T: Instance> { + _inner: &'d Opa<'d, T>, +} + +impl<'d, T: Instance> Opa<'d, T> { + /// Create a new OPA driver. + /// + /// Resets and powers up the peripheral and applies `config`. The + /// amplifier itself stays disabled until a topology method is called. + pub fn new(peri: Peri<'d, T>, config: Config) -> Self { + let r = T::regs(); + + r.gprcm().rstctl().write(|w| { + w.set_key(vals::ResetKey::KEY); + w.set_resetassert(true); + w.set_resetstkyclr(true); + }); + r.gprcm().pwren().write(|w| { + w.set_key(vals::PwrenKey::KEY); + w.set_enable(true); + }); + // A few bus cycles are required after the power switch before + // touching peripheral registers. + cortex_m::asm::delay(16); + + r.cfgbase().write(|w| { + w.set_gbw(match config.gain_bandwidth { + GainBandwidth::Low => vals::Gbw::LOWGAIN, + GainBandwidth::High => vals::Gbw::HIGHGAIN, + }); + w.set_rri(config.rail_to_rail_input); + }); + + let chop = match config.chopping { + Chopping::Disabled => vals::Chop::OFF, + Chopping::Standard => vals::Chop::ON, + Chopping::AdcAveraging => vals::Chop::AVGON, + }; + + Self { _peri: peri, chop } + } + + fn enable(&self, mut cfg: regs::Cfg) { + let r = T::regs(); + cfg.set_chop(self.chop); + r.cfg().write_value(cfg); + r.ctl().write(|w| w.set_enable(true)); + while !r.stat().read().rdy() {} + } + + /// Unity-gain buffer of `input`, driving the output pin. + pub fn buffer_ext<'a>( + &'a mut self, + input: impl Into>, + output: Peri<'a, impl OutputPin>, + ) -> OpaOutput<'a, T> { + SealedOutputPin::setup(&*output); + let mut cfg = Self::buffer_cfg(input.into()); + cfg.set_outpin(true); + self.enable(cfg); + OpaOutput { _inner: self } + } + + /// Unity-gain buffer of `input`, output routed only to the ADC. + pub fn buffer_int<'a>(&'a mut self, input: impl Into>) -> OpaInternalOutput<'a, T> { + self.enable(Self::buffer_cfg(input.into())); + OpaInternalOutput { _inner: self } + } + + fn buffer_cfg(input: NonInvertingInput<'_, T>) -> regs::Cfg { + // Feedback from the ladder top; the ladder bottom is left open so no + // current flows and the ladder acts as a plain wire. + let mut cfg = regs::Cfg(0); + cfg.set_psel(input.channel); + cfg.set_nsel(vals::Nsel::OANRTOP); + cfg + } + + /// Non-inverting PGA: output is `gain * input`, driving the output pin. + pub fn pga_ext<'a>( + &'a mut self, + input: impl Into>, + output: Peri<'a, impl OutputPin>, + gain: Gain, + ) -> OpaOutput<'a, T> { + SealedOutputPin::setup(&*output); + let mut cfg = Self::pga_cfg(input.into(), gain); + cfg.set_outpin(true); + self.enable(cfg); + OpaOutput { _inner: self } + } + + /// Non-inverting PGA: output is `gain * input`, routed only to the ADC. + pub fn pga_int<'a>( + &'a mut self, + input: impl Into>, + gain: Gain, + ) -> OpaInternalOutput<'a, T> { + self.enable(Self::pga_cfg(input.into(), gain)); + OpaInternalOutput { _inner: self } + } + + fn pga_cfg(input: NonInvertingInput<'_, T>, gain: Gain) -> regs::Cfg { + // Ladder bottom to ground, feedback from the tap. + let mut cfg = regs::Cfg(0); + cfg.set_psel(input.channel); + cfg.set_nsel(vals::Nsel::OANRTAP); + cfg.set_msel(vals::Msel::VSS); + cfg.set_gain(gain as u8); + cfg + } +} + +impl<'d, T: Instance> Drop for Opa<'d, T> { + fn drop(&mut self) { + let r = T::regs(); + r.ctl().write(|w| w.set_enable(false)); + r.gprcm().pwren().write(|w| { + w.set_key(vals::PwrenKey::KEY); + w.set_enable(false); + }); + } +} + +impl<'d, T: Instance> OpaOutput<'d, T> { + /// Change the PGA gain while the amplifier is running. + /// + /// Only meaningful for outputs created by [`Opa::pga_ext`]; useful for + /// auto-ranging. The output settles within a couple of milliseconds. + pub fn set_gain(&mut self, gain: Gain) { + T::regs().cfg().modify(|w| w.set_gain(gain as u8)); + } +} + +impl<'d, T: Instance> OpaInternalOutput<'d, T> { + /// Change the PGA gain while the amplifier is running. + /// + /// Only meaningful for outputs created by [`Opa::pga_int`]; useful for + /// auto-ranging. The output settles within a couple of milliseconds. + pub fn set_gain(&mut self, gain: Gain) { + T::regs().cfg().modify(|w| w.set_gain(gain as u8)); + } +} + +impl<'d, T: Instance> Drop for OpaOutput<'d, T> { + fn drop(&mut self) { + T::regs().ctl().write(|w| w.set_enable(false)); + } +} + +impl<'d, T: Instance> Drop for OpaInternalOutput<'d, T> { + fn drop(&mut self) { + T::regs().ctl().write(|w| w.set_enable(false)); + } +} + +pub(crate) trait SealedInstance { + fn regs() -> crate::pac::opa::Opa; +} + +/// OPA instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static {} + +pub(crate) trait SealedNonInvertingPin { + fn setup(&self); + fn channel(&self) -> u8; +} + +pub(crate) trait SealedOutputPin { + fn setup(&self); +} + +/// A pin that can be used as the OPA non-inverting (+) input. +#[allow(private_bounds)] +pub trait NonInvertingPin: PeripheralType + SealedNonInvertingPin + Sized {} + +/// The OPAx_OUT pin. +#[allow(private_bounds)] +pub trait OutputPin: PeripheralType + SealedOutputPin + Sized {} + +macro_rules! impl_opa_instance { + ($inst:ident) => { + impl crate::opa::SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::opa::Opa { + crate::pac::$inst + } + } + impl crate::opa::Instance for crate::peripherals::$inst {} + }; +} + +macro_rules! impl_opa_pin { + ($trait:ident, $sealed:ident, $inst:ident, $pin:ident, $ch:expr) => { + impl crate::opa::$trait for crate::peripherals::$pin {} + impl crate::opa::$sealed for crate::peripherals::$pin { + fn setup(&self) { + crate::gpio::SealedPin::set_as_analog(self); + } + + fn channel(&self) -> u8 { + $ch + } + } + }; +} + +macro_rules! impl_opa_non_inverting_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl_opa_pin!(NonInvertingPin, SealedNonInvertingPin, $inst, $pin, $ch); + }; +} + +macro_rules! impl_opa_output_pin { + ($inst:ident, $pin:ident) => { + impl crate::opa::OutputPin for crate::peripherals::$pin {} + impl crate::opa::SealedOutputPin for crate::peripherals::$pin { + fn setup(&self) { + crate::gpio::SealedPin::set_as_analog(self); + } + } + }; +} + +macro_rules! impl_opa_adc_channel { + ($inst:ident, $adc:ident, $ch:expr) => { + impl<'d> crate::opa::OpaOutput<'d, crate::peripherals::$inst> { + /// The internal ADC channel for this OPA's output. + /// + /// The returned channel borrows the output, so the amplifier + /// stays enabled while the channel is in use. + pub fn adc_channel(&self) -> crate::Peri<'_, crate::adc::AnyAdcChannel> { + unsafe { + crate::Peri::new_unchecked(crate::adc::AnyAdcChannel { + channel: $ch, + _phantom: core::marker::PhantomData, + }) + } + } + } + + impl<'d> crate::opa::OpaInternalOutput<'d, crate::peripherals::$inst> { + /// The internal ADC channel for this OPA's output. + /// + /// The returned channel borrows the output, so the amplifier + /// stays enabled while the channel is in use. + pub fn adc_channel(&self) -> crate::Peri<'_, crate::adc::AnyAdcChannel> { + unsafe { + crate::Peri::new_unchecked(crate::adc::AnyAdcChannel { + channel: $ch, + _phantom: core::marker::PhantomData, + }) + } + } + } + }; +} diff --git a/examples/mspm0g3507/src/bin/opa.rs b/examples/mspm0g3507/src/bin/opa.rs new file mode 100644 index 0000000000..904c1426da --- /dev/null +++ b/examples/mspm0g3507/src/bin/opa.rs @@ -0,0 +1,40 @@ +//! Example of using the OPA as a non-inverting PGA. +//! +//! An external voltage on PB19 (OPA1_IN0+, J3.23 on the LP-MSPM0G3507) is +//! amplified 4x onto PA16 (OPA1_OUT, J3.29) and sampled through the OPA's +//! internal ADC channel. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::adc::{self, Adc}; +use embassy_mspm0::opa::{Gain, Opa}; +use embassy_mspm0::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +bind_interrupts!(struct Irqs { + ADC1 => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Default::default()); + + let mut adc = Adc::new_async(p.ADC1, Default::default(), Irqs); + let mut opa = Opa::new(p.OPA1, Default::default()); + + // 4x gain from PB19, driving PA16. Use `opa.pga_int(...)` instead if the + // output is only needed by the ADC. + let out = opa.pga_ext(p.PB19, p.PA16, Gain::X4); + + loop { + let raw = adc.read_channel(&out.adc_channel()).await; + info!("OPA1 output: {}", raw); + + Timer::after_millis(500).await; + } +}