aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2025-04-18 11:09:00 +0000
committerGitHub <[email protected]>2025-04-18 11:09:00 +0000
commit667400111a4d4e0118135ef314ae3e4443b24202 (patch)
tree647fbe99fe907c92fcad985de9fc9356578e7d33
parent7a620661da85c0a4b550abbde7df7e248fe0f155 (diff)
parentbbfebf968e31815a78ba9f19e7a6b2e26bad7456 (diff)
Merge pull request #4089 from IvanLi-CN/g4-opamp
feat(embassy-stm32/opamp): Add some STM32G4 opamp usage
-rw-r--r--embassy-stm32/build.rs12
-rw-r--r--embassy-stm32/src/opamp.rs347
-rw-r--r--examples/stm32f334/src/bin/opamp.rs4
3 files changed, 341 insertions, 22 deletions
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() {
1338 g.extend(quote! { 1338 g.extend(quote! {
1339 impl_opamp_vp_pin!( #peri, #pin_name, #ch); 1339 impl_opamp_vp_pin!( #peri, #pin_name, #ch);
1340 }) 1340 })
1341 } else if pin.signal.starts_with("VINM") {
1342 // Impl NonInvertingPin for the VINM* signals ( VINM0, VINM1, etc)
1343 // STM32G4
1344 let peri = format_ident!("{}", p.name);
1345 let pin_name = format_ident!("{}", pin.pin);
1346 let ch: Result<u8, _> = pin.signal.strip_prefix("VINM").unwrap().parse();
1347
1348 if let Ok(ch) = ch {
1349 g.extend(quote! {
1350 impl_opamp_vn_pin!( #peri, #pin_name, #ch);
1351 })
1352 }
1341 } else if pin.signal == "VOUT" { 1353 } else if pin.signal == "VOUT" {
1342 // Impl OutputPin for the VOUT pin 1354 // Impl OutputPin for the VOUT pin
1343 let peri = format_ident!("{}", p.name); 1355 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;
6use crate::pac::opamp::vals::*; 6use crate::pac::opamp::vals::*;
7use crate::Peri; 7use crate::Peri;
8 8
9/// Performs a busy-wait delay for a specified number of microseconds.
10#[cfg(opamp_g4)]
11fn blocking_delay_ms(ms: u32) {
12 #[cfg(feature = "time")]
13 embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64));
14 #[cfg(not(feature = "time"))]
15 cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms);
16}
17
9/// Gain 18/// Gain
10#[allow(missing_docs)] 19#[allow(missing_docs)]
11#[derive(Clone, Copy)] 20#[derive(Clone, Copy)]
12pub enum OpAmpGain { 21pub enum OpAmpGain {
13 Mul1,
14 Mul2, 22 Mul2,
15 Mul4, 23 Mul4,
16 Mul8, 24 Mul8,
17 Mul16, 25 Mul16,
26 #[cfg(opamp_g4)]
27 Mul32,
28 #[cfg(opamp_g4)]
29 Mul64,
30}
31
32#[cfg(opamp_g4)]
33enum OpAmpDifferentialPair {
34 P,
35 N,
18} 36}
19 37
20/// Speed 38/// Speed
@@ -82,24 +100,71 @@ impl<'d, T: Instance> OpAmp<'d, T> {
82 &mut self, 100 &mut self,
83 in_pin: Peri<'_, impl NonInvertingPin<T> + crate::gpio::Pin>, 101 in_pin: Peri<'_, impl NonInvertingPin<T> + crate::gpio::Pin>,
84 out_pin: Peri<'_, impl OutputPin<T> + crate::gpio::Pin>, 102 out_pin: Peri<'_, impl OutputPin<T> + crate::gpio::Pin>,
103 ) -> OpAmpOutput<'_, T> {
104 in_pin.set_as_analog();
105 out_pin.set_as_analog();
106
107 #[cfg(opamp_g4)]
108 let vm_sel = VmSel::OUTPUT;
109 #[cfg(not(opamp_g4))]
110 let vm_sel = VmSel::from_bits(0b11);
111
112 T::regs().csr().modify(|w| {
113 w.set_vp_sel(VpSel::from_bits(in_pin.channel()));
114 w.set_vm_sel(vm_sel);
115 #[cfg(opamp_g4)]
116 w.set_opaintoen(Opaintoen::OUTPUT_PIN);
117 w.set_opampen(true);
118 });
119
120 OpAmpOutput { _inner: self }
121 }
122
123 /// Configure the OpAmp as a PGA for the provided input pin,
124 /// outputting to the provided output pin, and enable the opamp.
125 ///
126 /// The input pin is configured for analogue mode but not consumed,
127 /// so it may subsequently be used for ADC or comparator inputs.
128 ///
129 /// The output pin is held within the returned [`OpAmpOutput`] struct,
130 /// preventing it being used elsewhere. The `OpAmpOutput` can then be
131 /// directly used as an ADC input. The opamp will be disabled when the
132 /// [`OpAmpOutput`] is dropped.
133 pub fn pga_ext(
134 &mut self,
135 in_pin: Peri<'_, impl NonInvertingPin<T> + crate::gpio::Pin>,
136 out_pin: Peri<'_, impl OutputPin<T> + crate::gpio::Pin>,
85 gain: OpAmpGain, 137 gain: OpAmpGain,
86 ) -> OpAmpOutput<'_, T> { 138 ) -> OpAmpOutput<'_, T> {
87 in_pin.set_as_analog(); 139 in_pin.set_as_analog();
88 out_pin.set_as_analog(); 140 out_pin.set_as_analog();
89 141
90 // PGA_GAIN value may have different meaning in different MCU serials, use with caution. 142 #[cfg(opamp_g4)]
91 let (vm_sel, pga_gain) = match gain { 143 let vm_sel = VmSel::PGA;
92 OpAmpGain::Mul1 => (0b11, 0b00), 144 #[cfg(not(opamp_g4))]
93 OpAmpGain::Mul2 => (0b10, 0b00), 145 let vm_sel = VmSel::from_bits(0b10);
94 OpAmpGain::Mul4 => (0b10, 0b01), 146
95 OpAmpGain::Mul8 => (0b10, 0b10), 147 #[cfg(opamp_g4)]
96 OpAmpGain::Mul16 => (0b10, 0b11), 148 let pga_gain = match gain {
149 OpAmpGain::Mul2 => PgaGain::GAIN2,
150 OpAmpGain::Mul4 => PgaGain::GAIN4,
151 OpAmpGain::Mul8 => PgaGain::GAIN8,
152 OpAmpGain::Mul16 => PgaGain::GAIN16,
153 OpAmpGain::Mul32 => PgaGain::GAIN32,
154 OpAmpGain::Mul64 => PgaGain::GAIN64,
97 }; 155 };
156 #[cfg(not(opamp_g4))]
157 let pga_gain = PgaGain::from_bits(match gain {
158 OpAmpGain::Mul2 => 0b00,
159 OpAmpGain::Mul4 => 0b01,
160 OpAmpGain::Mul8 => 0b10,
161 OpAmpGain::Mul16 => 0b11,
162 });
98 163
99 T::regs().csr().modify(|w| { 164 T::regs().csr().modify(|w| {
100 w.set_vp_sel(VpSel::from_bits(in_pin.channel())); 165 w.set_vp_sel(VpSel::from_bits(in_pin.channel()));
101 w.set_vm_sel(VmSel::from_bits(vm_sel)); 166 w.set_vm_sel(vm_sel);
102 w.set_pga_gain(PgaGain::from_bits(pga_gain)); 167 w.set_pga_gain(pga_gain);
103 #[cfg(opamp_g4)] 168 #[cfg(opamp_g4)]
104 w.set_opaintoen(Opaintoen::OUTPUT_PIN); 169 w.set_opaintoen(Opaintoen::OUTPUT_PIN);
105 w.set_opampen(true); 170 w.set_opampen(true);
@@ -107,6 +172,7 @@ impl<'d, T: Instance> OpAmp<'d, T> {
107 172
108 OpAmpOutput { _inner: self } 173 OpAmpOutput { _inner: self }
109 } 174 }
175
110 /// Configure the OpAmp as a buffer for the DAC it is connected to, 176 /// Configure the OpAmp as a buffer for the DAC it is connected to,
111 /// outputting to the provided output pin, and enable the opamp. 177 /// outputting to the provided output pin, and enable the opamp.
112 /// 178 ///
@@ -142,30 +208,259 @@ impl<'d, T: Instance> OpAmp<'d, T> {
142 pub fn buffer_int( 208 pub fn buffer_int(
143 &mut self, 209 &mut self,
144 pin: Peri<'_, impl NonInvertingPin<T> + crate::gpio::Pin>, 210 pin: Peri<'_, impl NonInvertingPin<T> + crate::gpio::Pin>,
211 ) -> OpAmpInternalOutput<'_, T> {
212 pin.set_as_analog();
213
214 T::regs().csr().modify(|w| {
215 w.set_vp_sel(VpSel::from_bits(pin.channel()));
216 w.set_vm_sel(VmSel::OUTPUT);
217 #[cfg(opamp_g4)]
218 w.set_opaintoen(Opaintoen::ADCCHANNEL);
219 w.set_opampen(true);
220 });
221
222 OpAmpInternalOutput { _inner: self }
223 }
224
225 /// Configure the OpAmp as a PGA for the provided input pin,
226 /// with the output only used internally, and enable the opamp.
227 ///
228 /// The input pin is configured for analogue mode but not consumed,
229 /// so it may be subsequently used for ADC or comparator inputs.
230 ///
231 /// The returned `OpAmpInternalOutput` struct may be used as an ADC input.
232 /// The opamp output will be disabled when it is dropped.
233 #[cfg(opamp_g4)]
234 pub fn pga_int(
235 &mut self,
236 pin: Peri<'_, impl NonInvertingPin<T> + crate::gpio::Pin>,
145 gain: OpAmpGain, 237 gain: OpAmpGain,
146 ) -> OpAmpInternalOutput<'_, T> { 238 ) -> OpAmpInternalOutput<'_, T> {
147 pin.set_as_analog(); 239 pin.set_as_analog();
148 240
149 // PGA_GAIN value may have different meaning in different MCU serials, use with caution. 241 let pga_gain = match gain {
150 let (vm_sel, pga_gain) = match gain { 242 OpAmpGain::Mul2 => PgaGain::GAIN2,
151 OpAmpGain::Mul1 => (0b11, 0b00), 243 OpAmpGain::Mul4 => PgaGain::GAIN4,
152 OpAmpGain::Mul2 => (0b10, 0b00), 244 OpAmpGain::Mul8 => PgaGain::GAIN8,
153 OpAmpGain::Mul4 => (0b10, 0b01), 245 OpAmpGain::Mul16 => PgaGain::GAIN16,
154 OpAmpGain::Mul8 => (0b10, 0b10), 246 OpAmpGain::Mul32 => PgaGain::GAIN32,
155 OpAmpGain::Mul16 => (0b10, 0b11), 247 OpAmpGain::Mul64 => PgaGain::GAIN64,
156 }; 248 };
157 249
158 T::regs().csr().modify(|w| { 250 T::regs().csr().modify(|w| {
159 use crate::pac::opamp::vals::*;
160 w.set_vp_sel(VpSel::from_bits(pin.channel())); 251 w.set_vp_sel(VpSel::from_bits(pin.channel()));
161 w.set_vm_sel(VmSel::from_bits(vm_sel)); 252 w.set_vm_sel(VmSel::OUTPUT);
162 w.set_pga_gain(PgaGain::from_bits(pga_gain)); 253 w.set_pga_gain(pga_gain);
163 w.set_opaintoen(Opaintoen::ADCCHANNEL); 254 w.set_opaintoen(Opaintoen::ADCCHANNEL);
164 w.set_opampen(true); 255 w.set_opampen(true);
165 }); 256 });
166 257
167 OpAmpInternalOutput { _inner: self } 258 OpAmpInternalOutput { _inner: self }
168 } 259 }
260
261 /// Configure the OpAmp as a standalone DAC with the inverting input
262 /// connected to the provided pin, and the output is connected
263 /// internally to an ADC channel.
264 ///
265 /// The input pin is configured for analogue mode but not consumed,
266 /// so it may be subsequently used for ADC or comparator inputs.
267 ///
268 /// The returned `OpAmpInternalOutput` struct may be used as an ADC
269 /// input. The opamp output will be disabled when it is dropped.
270 #[cfg(opamp_g4)]
271 pub fn standalone_dac_int(
272 &mut self,
273 m_pin: Peri<'_, impl InvertingPin<T> + crate::gpio::Pin>,
274 ) -> OpAmpInternalOutput<'_, T> {
275 m_pin.set_as_analog();
276
277 T::regs().csr().modify(|w| {
278 use crate::pac::opamp::vals::*;
279 w.set_vp_sel(VpSel::DAC3_CH1); // Actually DAC3_CHx
280 w.set_vm_sel(VmSel::from_bits(m_pin.channel()));
281 w.set_opaintoen(Opaintoen::ADCCHANNEL);
282 w.set_opampen(true);
283 });
284
285 OpAmpInternalOutput { _inner: self }
286 }
287
288 /// Configure the OpAmp as a standalone DAC with the inverting input
289 /// connected to the provided pin, and the output connected to the
290 /// provided pin.
291 ///
292 /// The input pin is configured for analogue mode but not consumed,
293 /// so it may be subsequently used for ADC or comparator inputs.
294 ///
295 /// The output pin is held within the returned [`OpAmpOutput`] struct,
296 /// preventing it being used elsewhere. The opamp will be disabled when
297 /// the [`OpAmpOutput`] is dropped.
298 #[cfg(opamp_g4)]
299 pub fn standalone_dac_ext(
300 &mut self,
301 m_pin: Peri<'_, impl InvertingPin<T> + crate::gpio::Pin>,
302 out_pin: Peri<'_, impl OutputPin<T> + crate::gpio::Pin>,
303 ) -> OpAmpOutput<'_, T> {
304 m_pin.set_as_analog();
305 out_pin.set_as_analog();
306
307 T::regs().csr().modify(|w| {
308 use crate::pac::opamp::vals::*;
309 w.set_vp_sel(VpSel::DAC3_CH1); // Actually DAC3_CHx
310 w.set_vm_sel(VmSel::from_bits(m_pin.channel()));
311 w.set_opaintoen(Opaintoen::OUTPUT_PIN);
312 w.set_opampen(true);
313 });
314
315 OpAmpOutput { _inner: self }
316 }
317
318 /// Configure the OpAmp in standalone mode with the non-inverting input
319 /// connected to the provided `p_pin`, the inverting input connected to
320 /// the `m_pin`, and output to the provided `out_pin`.
321 ///
322 /// The input pins are configured for analogue mode but not consumed,
323 /// allowing their subsequent use for ADC or comparator inputs.
324 ///
325 /// The output pin is held within the returned [`OpAmpOutput`] struct,
326 /// preventing it being used elsewhere. The opamp will be disabled when
327 /// the [`OpAmpOutput`] is dropped.
328 #[cfg(opamp_g4)]
329 pub fn standalone_ext(
330 &mut self,
331 p_pin: Peri<'d, impl NonInvertingPin<T> + crate::gpio::Pin>,
332 m_pin: Peri<'d, impl InvertingPin<T> + crate::gpio::Pin>,
333 out_pin: Peri<'d, impl OutputPin<T> + crate::gpio::Pin>,
334 ) -> OpAmpOutput<'_, T> {
335 p_pin.set_as_analog();
336 m_pin.set_as_analog();
337 out_pin.set_as_analog();
338
339 T::regs().csr().modify(|w| {
340 use crate::pac::opamp::vals::*;
341 w.set_vp_sel(VpSel::from_bits(p_pin.channel()));
342 w.set_vm_sel(VmSel::from_bits(m_pin.channel()));
343 w.set_opaintoen(Opaintoen::OUTPUT_PIN);
344 w.set_opampen(true);
345 });
346
347 OpAmpOutput { _inner: self }
348 }
349
350 /// Configure the OpAmp in standalone mode with the non-inverting input
351 /// connected to the provided `p_pin`, the inverting input connected to
352 /// the `m_pin`, and output is connected to the DAC.
353 ///
354 /// The input pins are configured for analogue mode but not consumed,
355 /// allowing their subsequent use for ADC or comparator inputs.
356 ///
357 /// The returned `OpAmpOutput` struct may be used as an ADC
358 /// input. The opamp output will be disabled when it is dropped.
359 #[cfg(opamp_g4)]
360 pub fn standalone_int(
361 &mut self,
362 p_pin: Peri<'d, impl NonInvertingPin<T> + crate::gpio::Pin>,
363 m_pin: Peri<'d, impl InvertingPin<T> + crate::gpio::Pin>,
364 ) -> OpAmpOutput<'_, T> {
365 p_pin.set_as_analog();
366 m_pin.set_as_analog();
367
368 T::regs().csr().modify(|w| {
369 use crate::pac::opamp::vals::*;
370 w.set_vp_sel(VpSel::from_bits(p_pin.channel()));
371 w.set_vm_sel(VmSel::from_bits(m_pin.channel()));
372 w.set_opaintoen(Opaintoen::ADCCHANNEL);
373 w.set_opampen(true);
374 });
375
376 OpAmpOutput { _inner: self }
377 }
378
379 /// Calibrates the operational amplifier.
380 ///
381 /// This function enables the opamp and sets the user trim mode for calibration.
382 /// Depending on the speed mode of the opamp, it calibrates the differential pair inputs.
383 /// For normal speed, both the P and N differential pairs are calibrated,
384 /// while for high-speed mode, only the P differential pair is calibrated.
385 ///
386 /// Calibrating a differential pair requires waiting 12ms in the worst case (binary method).
387 #[cfg(opamp_g4)]
388 pub fn calibrate(&mut self) {
389 T::regs().csr().modify(|w| {
390 w.set_opampen(true);
391 w.set_calon(true);
392 w.set_usertrim(Usertrim::USER);
393 });
394
395 match T::regs().csr().read().opahsm() {
396 Opahsm::NORMAL => {
397 self.calibrate_differential_pair(OpAmpDifferentialPair::P);
398 self.calibrate_differential_pair(OpAmpDifferentialPair::N);
399 }
400 Opahsm::HIGH_SPEED => {
401 self.calibrate_differential_pair(OpAmpDifferentialPair::P);
402 }
403 }
404
405 T::regs().csr().modify(|w| {
406 w.set_calon(false);
407 w.set_opampen(false);
408 });
409 }
410
411 /// Calibrate differential pair.
412 ///
413 /// The calibration is done by trying different offset values and
414 /// measuring the outcal bit.
415 ///
416 /// The calibration range is from 0 to 31.
417 ///
418 /// The result is stored in the OPAMP_CSR register.
419 #[cfg(opamp_g4)]
420 fn calibrate_differential_pair(&mut self, pair: OpAmpDifferentialPair) {
421 let mut low = 0;
422 let mut high = 31;
423
424 let calsel = match pair {
425 OpAmpDifferentialPair::P => Calsel::PERCENT10,
426 OpAmpDifferentialPair::N => Calsel::PERCENT90,
427 };
428
429 T::regs().csr().modify(|w| {
430 w.set_calsel(calsel);
431 });
432
433 while low <= high {
434 let mid = (low + high) / 2;
435
436 T::regs().csr().modify(|w| match pair {
437 OpAmpDifferentialPair::P => {
438 defmt::info!("p calibration. offset: {}", mid);
439 w.set_trimoffsetp(mid);
440 }
441 OpAmpDifferentialPair::N => {
442 defmt::info!("n calibration. offset: {}", mid);
443 w.set_trimoffsetn(mid);
444 }
445 });
446
447 // The closer the trimming value is to the optimum trimming value, the longer it takes to stabilize
448 // (with a maximum stabilization time remaining below 2 ms in any case) -- RM0440 25.3.7
449 blocking_delay_ms(2);
450
451 if T::regs().csr().read().outcal() == Outcal::LOW {
452 if mid == 0 {
453 break;
454 }
455 high = mid - 1;
456 } else {
457 if mid == 31 {
458 break;
459 }
460 low = mid + 1;
461 }
462 }
463 }
169} 464}
170 465
171impl<'d, T: Instance> Drop for OpAmpOutput<'d, T> { 466impl<'d, T: Instance> Drop for OpAmpOutput<'d, T> {
@@ -339,6 +634,18 @@ macro_rules! impl_opamp_vp_pin {
339} 634}
340 635
341#[allow(unused_macros)] 636#[allow(unused_macros)]
637macro_rules! impl_opamp_vn_pin {
638 ($inst:ident, $pin:ident, $ch:expr) => {
639 impl crate::opamp::InvertingPin<peripherals::$inst> for crate::peripherals::$pin {}
640 impl crate::opamp::SealedInvertingPin<peripherals::$inst> for crate::peripherals::$pin {
641 fn channel(&self) -> u8 {
642 $ch
643 }
644 }
645 };
646}
647
648#[allow(unused_macros)]
342macro_rules! impl_opamp_vout_pin { 649macro_rules! impl_opamp_vout_pin {
343 ($inst:ident, $pin:ident) => { 650 ($inst:ident, $pin:ident) => {
344 impl crate::opamp::OutputPin<peripherals::$inst> for crate::peripherals::$pin {} 651 impl crate::opamp::OutputPin<peripherals::$inst> for crate::peripherals::$pin {}
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 @@
4use defmt::info; 4use defmt::info;
5use embassy_executor::Spawner; 5use embassy_executor::Spawner;
6use embassy_stm32::adc::{Adc, SampleTime}; 6use embassy_stm32::adc::{Adc, SampleTime};
7use embassy_stm32::opamp::{OpAmp, OpAmpGain}; 7use embassy_stm32::opamp::OpAmp;
8use embassy_stm32::peripherals::ADC2; 8use embassy_stm32::peripherals::ADC2;
9use embassy_stm32::time::mhz; 9use embassy_stm32::time::mhz;
10use embassy_stm32::{adc, bind_interrupts, Config}; 10use embassy_stm32::{adc, bind_interrupts, Config};
@@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) -> ! {
48 48
49 let mut vrefint = adc.enable_vref(); 49 let mut vrefint = adc.enable_vref();
50 let mut temperature = adc.enable_temperature(); 50 let mut temperature = adc.enable_temperature();
51 let mut buffer = opamp.buffer_ext(p.PA7.reborrow(), p.PA6.reborrow(), OpAmpGain::Mul1); 51 let mut buffer = opamp.buffer_ext(p.PA7.reborrow(), p.PA6.reborrow());
52 52
53 loop { 53 loop {
54 let vref = adc.read(&mut vrefint).await; 54 let vref = adc.read(&mut vrefint).await;