diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index bb1f1c047..d982d9010 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1338,6 +1338,18 @@ fn main() { g.extend(quote! { impl_opamp_vp_pin!( #peri, #pin_name, #ch); }) + } else if pin.signal.starts_with("VINM") { + // Impl NonInvertingPin for the VINM* signals ( VINM0, VINM1, etc) + // STM32G4 + let peri = format_ident!("{}", p.name); + let pin_name = format_ident!("{}", pin.pin); + let ch: Result = pin.signal.strip_prefix("VINM").unwrap().parse(); + + if let Ok(ch) = ch { + g.extend(quote! { + impl_opamp_vn_pin!( #peri, #pin_name, #ch); + }) + } } else if pin.signal == "VOUT" { // Impl OutputPin for the VOUT pin let peri = format_ident!("{}", p.name); diff --git a/embassy-stm32/src/opamp.rs b/embassy-stm32/src/opamp.rs index a81493c1b..82de4a89b 100644 --- a/embassy-stm32/src/opamp.rs +++ b/embassy-stm32/src/opamp.rs @@ -6,15 +6,33 @@ use embassy_hal_internal::PeripheralType; use crate::pac::opamp::vals::*; use crate::Peri; +/// Performs a busy-wait delay for a specified number of microseconds. +#[cfg(opamp_g4)] +fn blocking_delay_ms(ms: u32) { + #[cfg(feature = "time")] + embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64)); + #[cfg(not(feature = "time"))] + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms); +} + /// Gain #[allow(missing_docs)] #[derive(Clone, Copy)] pub enum OpAmpGain { - Mul1, Mul2, Mul4, Mul8, Mul16, + #[cfg(opamp_g4)] + Mul32, + #[cfg(opamp_g4)] + Mul64, +} + +#[cfg(opamp_g4)] +enum OpAmpDifferentialPair { + P, + N, } /// Speed @@ -82,24 +100,18 @@ impl<'d, T: Instance> OpAmp<'d, T> { &mut self, in_pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, out_pin: Peri<'_, impl OutputPin + crate::gpio::Pin>, - gain: OpAmpGain, ) -> OpAmpOutput<'_, T> { in_pin.set_as_analog(); out_pin.set_as_analog(); - // PGA_GAIN value may have different meaning in different MCU serials, use with caution. - let (vm_sel, pga_gain) = match gain { - OpAmpGain::Mul1 => (0b11, 0b00), - OpAmpGain::Mul2 => (0b10, 0b00), - OpAmpGain::Mul4 => (0b10, 0b01), - OpAmpGain::Mul8 => (0b10, 0b10), - OpAmpGain::Mul16 => (0b10, 0b11), - }; + #[cfg(opamp_g4)] + let vm_sel = VmSel::OUTPUT; + #[cfg(not(opamp_g4))] + let vm_sel = VmSel::from_bits(0b11); T::regs().csr().modify(|w| { w.set_vp_sel(VpSel::from_bits(in_pin.channel())); - w.set_vm_sel(VmSel::from_bits(vm_sel)); - w.set_pga_gain(PgaGain::from_bits(pga_gain)); + w.set_vm_sel(vm_sel); #[cfg(opamp_g4)] w.set_opaintoen(Opaintoen::OUTPUT_PIN); w.set_opampen(true); @@ -107,6 +119,60 @@ impl<'d, T: Instance> OpAmp<'d, T> { OpAmpOutput { _inner: self } } + + /// Configure the OpAmp as a PGA for the provided input pin, + /// outputting to the provided output pin, and enable the opamp. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may subsequently be used for ADC or comparator inputs. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The `OpAmpOutput` can then be + /// directly used as an ADC input. The opamp will be disabled when the + /// [`OpAmpOutput`] is dropped. + pub fn pga_ext( + &mut self, + in_pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, + out_pin: Peri<'_, impl OutputPin + crate::gpio::Pin>, + gain: OpAmpGain, + ) -> OpAmpOutput<'_, T> { + in_pin.set_as_analog(); + out_pin.set_as_analog(); + + #[cfg(opamp_g4)] + let vm_sel = VmSel::PGA; + #[cfg(not(opamp_g4))] + let vm_sel = VmSel::from_bits(0b10); + + #[cfg(opamp_g4)] + let pga_gain = match gain { + OpAmpGain::Mul2 => PgaGain::GAIN2, + OpAmpGain::Mul4 => PgaGain::GAIN4, + OpAmpGain::Mul8 => PgaGain::GAIN8, + OpAmpGain::Mul16 => PgaGain::GAIN16, + OpAmpGain::Mul32 => PgaGain::GAIN32, + OpAmpGain::Mul64 => PgaGain::GAIN64, + }; + #[cfg(not(opamp_g4))] + let pga_gain = PgaGain::from_bits(match gain { + OpAmpGain::Mul2 => 0b00, + OpAmpGain::Mul4 => 0b01, + OpAmpGain::Mul8 => 0b10, + OpAmpGain::Mul16 => 0b11, + }); + + T::regs().csr().modify(|w| { + w.set_vp_sel(VpSel::from_bits(in_pin.channel())); + w.set_vm_sel(vm_sel); + w.set_pga_gain(pga_gain); + #[cfg(opamp_g4)] + w.set_opaintoen(Opaintoen::OUTPUT_PIN); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + /// Configure the OpAmp as a buffer for the DAC it is connected to, /// outputting to the provided output pin, and enable the opamp. /// @@ -142,30 +208,259 @@ impl<'d, T: Instance> OpAmp<'d, T> { pub fn buffer_int( &mut self, pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, - gain: OpAmpGain, ) -> OpAmpInternalOutput<'_, T> { pin.set_as_analog(); - // PGA_GAIN value may have different meaning in different MCU serials, use with caution. - let (vm_sel, pga_gain) = match gain { - OpAmpGain::Mul1 => (0b11, 0b00), - OpAmpGain::Mul2 => (0b10, 0b00), - OpAmpGain::Mul4 => (0b10, 0b01), - OpAmpGain::Mul8 => (0b10, 0b10), - OpAmpGain::Mul16 => (0b10, 0b11), - }; - T::regs().csr().modify(|w| { - use crate::pac::opamp::vals::*; w.set_vp_sel(VpSel::from_bits(pin.channel())); - w.set_vm_sel(VmSel::from_bits(vm_sel)); - w.set_pga_gain(PgaGain::from_bits(pga_gain)); + w.set_vm_sel(VmSel::OUTPUT); + #[cfg(opamp_g4)] w.set_opaintoen(Opaintoen::ADCCHANNEL); w.set_opampen(true); }); OpAmpInternalOutput { _inner: self } } + + /// Configure the OpAmp as a PGA for the provided input pin, + /// with the output only used internally, and enable the opamp. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may be subsequently used for ADC or comparator inputs. + /// + /// The returned `OpAmpInternalOutput` struct may be used as an ADC input. + /// The opamp output will be disabled when it is dropped. + #[cfg(opamp_g4)] + pub fn pga_int( + &mut self, + pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, + gain: OpAmpGain, + ) -> OpAmpInternalOutput<'_, T> { + pin.set_as_analog(); + + let pga_gain = match gain { + OpAmpGain::Mul2 => PgaGain::GAIN2, + OpAmpGain::Mul4 => PgaGain::GAIN4, + OpAmpGain::Mul8 => PgaGain::GAIN8, + OpAmpGain::Mul16 => PgaGain::GAIN16, + OpAmpGain::Mul32 => PgaGain::GAIN32, + OpAmpGain::Mul64 => PgaGain::GAIN64, + }; + + T::regs().csr().modify(|w| { + w.set_vp_sel(VpSel::from_bits(pin.channel())); + w.set_vm_sel(VmSel::OUTPUT); + w.set_pga_gain(pga_gain); + w.set_opaintoen(Opaintoen::ADCCHANNEL); + w.set_opampen(true); + }); + + OpAmpInternalOutput { _inner: self } + } + + /// Configure the OpAmp as a standalone DAC with the inverting input + /// connected to the provided pin, and the output is connected + /// internally to an ADC channel. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may be subsequently used for ADC or comparator inputs. + /// + /// The returned `OpAmpInternalOutput` struct may be used as an ADC + /// input. The opamp output will be disabled when it is dropped. + #[cfg(opamp_g4)] + pub fn standalone_dac_int( + &mut self, + m_pin: Peri<'_, impl InvertingPin + crate::gpio::Pin>, + ) -> OpAmpInternalOutput<'_, T> { + m_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::DAC3_CH1); // Actually DAC3_CHx + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(Opaintoen::ADCCHANNEL); + w.set_opampen(true); + }); + + OpAmpInternalOutput { _inner: self } + } + + /// Configure the OpAmp as a standalone DAC with the inverting input + /// connected to the provided pin, and the output connected to the + /// provided pin. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may be subsequently used for ADC or comparator inputs. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The opamp will be disabled when + /// the [`OpAmpOutput`] is dropped. + #[cfg(opamp_g4)] + pub fn standalone_dac_ext( + &mut self, + m_pin: Peri<'_, impl InvertingPin + crate::gpio::Pin>, + out_pin: Peri<'_, impl OutputPin + crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { + m_pin.set_as_analog(); + out_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::DAC3_CH1); // Actually DAC3_CHx + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(Opaintoen::OUTPUT_PIN); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + + /// Configure the OpAmp in standalone mode with the non-inverting input + /// connected to the provided `p_pin`, the inverting input connected to + /// the `m_pin`, and output to the provided `out_pin`. + /// + /// The input pins are configured for analogue mode but not consumed, + /// allowing their subsequent use for ADC or comparator inputs. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The opamp will be disabled when + /// the [`OpAmpOutput`] is dropped. + #[cfg(opamp_g4)] + pub fn standalone_ext( + &mut self, + p_pin: Peri<'d, impl NonInvertingPin + crate::gpio::Pin>, + m_pin: Peri<'d, impl InvertingPin + crate::gpio::Pin>, + out_pin: Peri<'d, impl OutputPin + crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { + p_pin.set_as_analog(); + m_pin.set_as_analog(); + out_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::from_bits(p_pin.channel())); + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(Opaintoen::OUTPUT_PIN); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + + /// Configure the OpAmp in standalone mode with the non-inverting input + /// connected to the provided `p_pin`, the inverting input connected to + /// the `m_pin`, and output is connected to the DAC. + /// + /// The input pins are configured for analogue mode but not consumed, + /// allowing their subsequent use for ADC or comparator inputs. + /// + /// The returned `OpAmpOutput` struct may be used as an ADC + /// input. The opamp output will be disabled when it is dropped. + #[cfg(opamp_g4)] + pub fn standalone_int( + &mut self, + p_pin: Peri<'d, impl NonInvertingPin + crate::gpio::Pin>, + m_pin: Peri<'d, impl InvertingPin + crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { + p_pin.set_as_analog(); + m_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::from_bits(p_pin.channel())); + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(Opaintoen::ADCCHANNEL); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + + /// Calibrates the operational amplifier. + /// + /// This function enables the opamp and sets the user trim mode for calibration. + /// Depending on the speed mode of the opamp, it calibrates the differential pair inputs. + /// For normal speed, both the P and N differential pairs are calibrated, + /// while for high-speed mode, only the P differential pair is calibrated. + /// + /// Calibrating a differential pair requires waiting 12ms in the worst case (binary method). + #[cfg(opamp_g4)] + pub fn calibrate(&mut self) { + T::regs().csr().modify(|w| { + w.set_opampen(true); + w.set_calon(true); + w.set_usertrim(Usertrim::USER); + }); + + match T::regs().csr().read().opahsm() { + Opahsm::NORMAL => { + self.calibrate_differential_pair(OpAmpDifferentialPair::P); + self.calibrate_differential_pair(OpAmpDifferentialPair::N); + } + Opahsm::HIGH_SPEED => { + self.calibrate_differential_pair(OpAmpDifferentialPair::P); + } + } + + T::regs().csr().modify(|w| { + w.set_calon(false); + w.set_opampen(false); + }); + } + + /// Calibrate differential pair. + /// + /// The calibration is done by trying different offset values and + /// measuring the outcal bit. + /// + /// The calibration range is from 0 to 31. + /// + /// The result is stored in the OPAMP_CSR register. + #[cfg(opamp_g4)] + fn calibrate_differential_pair(&mut self, pair: OpAmpDifferentialPair) { + let mut low = 0; + let mut high = 31; + + let calsel = match pair { + OpAmpDifferentialPair::P => Calsel::PERCENT10, + OpAmpDifferentialPair::N => Calsel::PERCENT90, + }; + + T::regs().csr().modify(|w| { + w.set_calsel(calsel); + }); + + while low <= high { + let mid = (low + high) / 2; + + T::regs().csr().modify(|w| match pair { + OpAmpDifferentialPair::P => { + defmt::info!("p calibration. offset: {}", mid); + w.set_trimoffsetp(mid); + } + OpAmpDifferentialPair::N => { + defmt::info!("n calibration. offset: {}", mid); + w.set_trimoffsetn(mid); + } + }); + + // The closer the trimming value is to the optimum trimming value, the longer it takes to stabilize + // (with a maximum stabilization time remaining below 2 ms in any case) -- RM0440 25.3.7 + blocking_delay_ms(2); + + if T::regs().csr().read().outcal() == Outcal::LOW { + if mid == 0 { + break; + } + high = mid - 1; + } else { + if mid == 31 { + break; + } + low = mid + 1; + } + } + } } impl<'d, T: Instance> Drop for OpAmpOutput<'d, T> { @@ -338,6 +633,18 @@ macro_rules! impl_opamp_vp_pin { }; } +#[allow(unused_macros)] +macro_rules! impl_opamp_vn_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl crate::opamp::InvertingPin for crate::peripherals::$pin {} + impl crate::opamp::SealedInvertingPin for crate::peripherals::$pin { + fn channel(&self) -> u8 { + $ch + } + } + }; +} + #[allow(unused_macros)] macro_rules! impl_opamp_vout_pin { ($inst:ident, $pin:ident) => { diff --git a/examples/stm32f334/src/bin/opamp.rs b/examples/stm32f334/src/bin/opamp.rs index b30445ead..c344935d7 100644 --- a/examples/stm32f334/src/bin/opamp.rs +++ b/examples/stm32f334/src/bin/opamp.rs @@ -4,7 +4,7 @@ use defmt::info; use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, SampleTime}; -use embassy_stm32::opamp::{OpAmp, OpAmpGain}; +use embassy_stm32::opamp::OpAmp; use embassy_stm32::peripherals::ADC2; use embassy_stm32::time::mhz; use embassy_stm32::{adc, bind_interrupts, Config}; @@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) -> ! { let mut vrefint = adc.enable_vref(); let mut temperature = adc.enable_temperature(); - let mut buffer = opamp.buffer_ext(p.PA7.reborrow(), p.PA6.reborrow(), OpAmpGain::Mul1); + let mut buffer = opamp.buffer_ext(p.PA7.reborrow(), p.PA6.reborrow()); loop { let vref = adc.read(&mut vrefint).await;