mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-29 05:11:31 +00:00
Merge pull request #4089 from IvanLi-CN/g4-opamp
feat(embassy-stm32/opamp): Add some STM32G4 opamp usage
This commit is contained in:
commit
667400111a
@ -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<u8, _> = 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);
|
||||
|
@ -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<T> + crate::gpio::Pin>,
|
||||
out_pin: Peri<'_, impl OutputPin<T> + 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<T> + crate::gpio::Pin>,
|
||||
out_pin: Peri<'_, impl OutputPin<T> + 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<T> + 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<T> + 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<T> + 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<T> + crate::gpio::Pin>,
|
||||
out_pin: Peri<'_, impl OutputPin<T> + 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<T> + crate::gpio::Pin>,
|
||||
m_pin: Peri<'d, impl InvertingPin<T> + crate::gpio::Pin>,
|
||||
out_pin: Peri<'d, impl OutputPin<T> + 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<T> + crate::gpio::Pin>,
|
||||
m_pin: Peri<'d, impl InvertingPin<T> + 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<peripherals::$inst> for crate::peripherals::$pin {}
|
||||
impl crate::opamp::SealedInvertingPin<peripherals::$inst> for crate::peripherals::$pin {
|
||||
fn channel(&self) -> u8 {
|
||||
$ch
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_opamp_vout_pin {
|
||||
($inst:ident, $pin:ident) => {
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user