diff --git a/.github/workflows/ci_opamp.yml b/.github/workflows/ci_opamp.yml new file mode 100644 index 00000000..81513d28 --- /dev/null +++ b/.github/workflows/ci_opamp.yml @@ -0,0 +1,84 @@ +on: + push: + branches: [opamp] + pull_request: + +name: Continuous integration + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: # All permutations of {rust, mcu} + rust: + - stable + mcu: + - stm32l412 + - stm32l422 + - stm32l431 + - stm32l432 + - stm32l433 + - stm32l442 + - stm32l443 + - stm32l451 + - stm32l452 + - stm32l462 + - stm32l471 + - stm32l475 + - stm32l476 + - stm32l486 + - stm32l496 + - stm32l4a6 + #- stm32l4r9 + #- stm32l4s9 + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: thumbv7em-none-eabihf + override: true + - name: build + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --verbose --release --examples --target thumbv7em-none-eabihf --features rt,unproven,${{ matrix.mcu }} + - name: test + uses: actions-rs/cargo@v1 + with: + command: test + args: --lib --target x86_64-unknown-linux-gnu --features rt,unproven,${{ matrix.mcu }} + + ci-r9: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + mcu: + - stm32l4r9 + - stm32l4s9 + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: thumbv7em-none-eabihf + override: true + - name: build + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --verbose --release --target thumbv7em-none-eabihf --features rt,unproven,${{ matrix.mcu }} + # note that examples were not built + - name: test + uses: actions-rs/cargo@v1 + with: + command: test + args: --lib --target x86_64-unknown-linux-gnu --features rt,unproven,${{ matrix.mcu }} diff --git a/.github/workflows/clippy_opamp.yml b/.github/workflows/clippy_opamp.yml new file mode 100644 index 00000000..4a087de2 --- /dev/null +++ b/.github/workflows/clippy_opamp.yml @@ -0,0 +1,22 @@ +on: + push: + branches: [ opamp ] + pull_request: + +name: Clippy check +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabihf + override: true + components: clippy + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --examples --target thumbv7em-none-eabihf --features=stm32l432,rt,unproven diff --git a/.github/workflows/rustfmt_opamp.yml b/.github/workflows/rustfmt_opamp.yml new file mode 100644 index 00000000..7b035804 --- /dev/null +++ b/.github/workflows/rustfmt_opamp.yml @@ -0,0 +1,23 @@ +on: + push: + branches: [ opamp ] + pull_request: + +name: Code formatting check + +jobs: + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/Cargo.toml b/Cargo.toml index 769f5b15..6ae4c951 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,3 +197,4 @@ required-features = ["rt"] [[example]] name = "adc_dma" required-features = ["rt"] + diff --git a/examples/adc_dma_seq.rs b/examples/adc_dma_seq.rs new file mode 100644 index 00000000..dc222bfd --- /dev/null +++ b/examples/adc_dma_seq.rs @@ -0,0 +1,109 @@ +#![no_main] +#![no_std] + +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use stm32l4xx_hal::{ + adc::{DmaMode, SampleTime, Sequence, ADC}, + delay::DelayCM, + dma::{dma1, RxDma, Transfer, W}, + prelude::*, + time::Hertz, +}; + +use rtic::app; + +const SEQUENCE_LEN: usize = 4; + +#[app(device = stm32l4xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +const APP: () = { + // RTIC app is written in here! + + struct Resources { + transfer: Option>>, + } + + #[init] + fn init(cx: init::Context) -> init::LateResources { + let MEMORY = { + static mut MEMORY: [u16; SEQUENCE_LEN] = [0u16; SEQUENCE_LEN]; + unsafe { &mut MEMORY } + }; + + rtt_init_print!(); + + rprintln!("Hello from init!"); + + let cp = cx.core; + let mut dcb = cp.DCB; + let mut dwt = cp.DWT; + + dcb.enable_trace(); + dwt.enable_cycle_counter(); + + let pac = cx.device; + + let mut rcc = pac.RCC.constrain(); + let mut flash = pac.FLASH.constrain(); + let mut pwr = pac.PWR.constrain(&mut rcc.apb1r1); + let dma_channels = pac.DMA1.split(&mut rcc.ahb1); + + // + // Initialize the clocks + // + let clocks = rcc + .cfgr + .sysclk(Hertz(80_000_000)) + .freeze(&mut flash.acr, &mut pwr); + + let mut delay = DelayCM::new(clocks); + + let mut adc = ADC::new( + pac.ADC1, + pac.ADC_COMMON, + &mut rcc.ahb2, + &mut rcc.ccipr, + &mut delay, + ); + + let mut temp_pin = adc.enable_temperature(&mut delay); + + let dma1_channel = dma_channels.1; + let mut gpioc = pac.GPIOC.split(&mut rcc.ahb2); + let mut pc0 = gpioc.pc0.into_analog(&mut gpioc.moder, &mut gpioc.pupdr); + + adc.configure_sequence(&mut temp_pin, Sequence::One, SampleTime::Cycles12_5); + adc.configure_sequence(&mut temp_pin, Sequence::Two, SampleTime::Cycles247_5); + adc.configure_sequence(&mut temp_pin, Sequence::Three, SampleTime::Cycles640_5); + adc.configure_sequence(&mut pc0, Sequence::Four, SampleTime::Cycles640_5); + + // Heapless boxes also work very well as buffers for DMA transfers + let transfer = Transfer::from_adc(adc, dma1_channel, MEMORY, DmaMode::Oneshot, true); + + init::LateResources { + transfer: Some(transfer), + } + } + + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + cortex_m::asm::nop(); + } + } + + #[task(binds = DMA1_CH1, resources = [transfer])] + fn dma1_interrupt(cx: dma1_interrupt::Context) { + let transfer = cx.resources.transfer; + if let Some(transfer_val) = transfer.take() { + let (buffer, rx_dma) = transfer_val.wait(); + rprintln!("DMA measurements: {:?}", buffer); + *transfer = Some(Transfer::from_adc_dma( + rx_dma, + buffer, + DmaMode::Oneshot, + true, + )); + } + } +}; diff --git a/examples/adc_opamp1.rs b/examples/adc_opamp1.rs new file mode 100644 index 00000000..e456436b --- /dev/null +++ b/examples/adc_opamp1.rs @@ -0,0 +1,66 @@ +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_rtt_target as _; + +use cortex_m_rt::entry; +use rtt_target::{rprint, rprintln}; +use stm32l4xx_hal::{adc::ADC, delay::Delay, opamp::*, pac, prelude::*}; + +use stm32l4xx_hal::traits::opamp::*; + +#[entry] +fn main() -> ! { + rtt_target::rtt_init_print!(); + rprint!("Initializing..."); + + let cp = pac::CorePeripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut flash = dp.FLASH.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + // set IOs to analgo mode, which are used by the Opamp + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + // OPAMP1_VINP + let mut pa0 = gpioa.pa0.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + + let mut delay = Delay::new(cp.SYST, clocks); + let mut adc = ADC::new( + dp.ADC1, + dp.ADC_COMMON, + &mut rcc.ahb2, + &mut rcc.ccipr, + &mut delay, + ); + + let mut opamp1_out = adc.enable_opamp1_out(); + + let ops = dp.OPAMP; + let op1: OP1 = OP1::new( + &ops.opamp1_csr, + &ops.opamp1_otr, + &ops.opamp1_lpotr, + &ops.opamp1_csr, + &rcc.apb1r1, + ); + + op1.set_opamp_oper_mode(OperationMode::Pga).unwrap(); + // set pga gain + op1.set_pga_gain(2).unwrap(); + op1.enable(true); + + rprintln!(" done."); + + loop { + // let value = adc.read(&mut opamp2_out).unwrap(); + let value_opamp1 = adc.read(&mut opamp1_out).unwrap(); + let value_a0 = adc.read(&mut pa0).unwrap(); + rprintln!("Value: pa0 {} opam1 {}", value_a0, value_opamp1); + } +} diff --git a/examples/opamp_ext.rs b/examples/opamp_ext.rs new file mode 100644 index 00000000..f8123fbc --- /dev/null +++ b/examples/opamp_ext.rs @@ -0,0 +1,98 @@ +#![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +// use cortex_m::asm; +use cortex_m_rt::entry; +use rtt_target::{rprint, rprintln}; +use stm32l4xx_hal; +use stm32l4xx_hal::{opamp::*, pac, prelude::*}; + +use stm32l4xx_hal::traits::opamp::*; + +// use embedded_hal::*; +// use embedded_hal::blocking::delay::DelayUs; +use stm32l4xx_hal::*; + +#[entry] +fn main() -> ! { + rtt_target::rtt_init_print!(); + rprint!("Initializing..."); + + let _cp = pac::CorePeripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut flash = dp.FLASH.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + let mut _delay = delay::DelayCM::new(clocks); + + // // set IOs to analgo mode, which are used by the Opamp + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + // OPAMP1_VINP + let mut _pa0 = gpioa.pa0.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + // OPAMP1_VINM + let mut _pa1 = gpioa.pa1.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + // OPAMP1_VOUT + let mut _pa3 = gpioa.pa3.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + + // // set IOs to analgo mode, which are used by the Opamp + // let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); + // // OPAMP1_VINP + // let mut _pa6 = gpioa.pa6.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + // // OPAMP1_VINM + // let mut _pa7 = gpioa.pa7.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + // // OPAMP1_VOUT + // let mut _pb0 = gpiob.pb0.into_analog(&mut gpiob.moder, &mut gpiob.pupdr); + + let ops = dp.OPAMP; + + //ops.opamp1_csr.opaen; + let op1: OP1 = OP1::new( + &ops.opamp1_csr, + &ops.opamp1_otr, + &ops.opamp1_lpotr, + &ops.opamp1_csr, + &rcc.apb1r1, + ); + // let op2: OP2 = OP2::new( + // &ops.opamp2_csr, + // &ops.opamp2_otr, + // &ops.opamp2_lpotr, + // &ops.opamp1_csr, + // &rcc.apb1r1, + // ); + + rprintln!("op1 object created..."); + // set operation models + let _ret = op1.set_opamp_oper_mode(OperationMode::External).unwrap(); + rprintln!("op1 operation mode set..."); + + op1.enable(true); + + // =================================================================================== + // Debug Info below + + rprintln!( + "opamode OP1: {}\n", + ops.opamp1_csr.read().opamode().bits() as u8 + ); + // rprintln!("opaen: {}\n", ops.opamp1_csr.read().opaen().bits() as u8); + // rprintln!("opa_range: {}\n", ops.opamp1_csr.read().opa_range().bits() as u8); + // rprintln!("vp_sel: {}\n", ops.opamp1_csr.read().vp_sel().bits() as u8); + // rprintln!("vm_sel: {}\n", ops.opamp1_csr.read().vm_sel().bits() as u8); + // rprintln!("opalpm: {}\n", ops.opamp1_csr.read().opalpm().bits() as u8); + // rprintln!("calout: {}\n", ops.opamp1_csr.read().calout().bits() as u8); + // rprintln!("calon: {}\n", ops.opamp1_csr.read().calon().bits() as u8); + // rprintln!("calsel: {}\n", ops.opamp1_csr.read().calsel().bits() as u8); + // rprintln!("usertrim: {}\n", ops.opamp1_csr.read().usertrim().bits() as u8); + // rprintln!("pga_gain: {}\n", ops.opamp1_csr.read().pga_gain().bits() as u8); + + loop {} +} diff --git a/examples/opamp_pga.rs b/examples/opamp_pga.rs new file mode 100644 index 00000000..0b157100 --- /dev/null +++ b/examples/opamp_pga.rs @@ -0,0 +1,75 @@ +#![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +// use cortex_m::asm; +use cortex_m_rt::entry; +use rtt_target::{rprint, rprintln}; +use stm32l4xx_hal::{opamp::*, pac, prelude::*}; + +use stm32l4xx_hal::traits::opamp::*; + +#[entry] +fn main() -> ! { + rtt_target::rtt_init_print!(); + rprint!("Initializing..."); + + let _cp = pac::CorePeripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + + // set IOs to analgo mode, which are used by the Opamp + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + // OPAMP1_VINP + let mut _pa0 = gpioa.pa0.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + + let ops = dp.OPAMP; + //ops.opamp1_csr.opaen; + let op1: OP1 = OP1::new( + &ops.opamp1_csr, + &ops.opamp1_otr, + &ops.opamp1_lpotr, + &ops.opamp1_csr, + &rcc.apb1r1, + ); + + // set operation models + op1.set_opamp_oper_mode(OperationMode::Pga).unwrap(); + // set pga gain to 8 + op1.set_pga_gain(2).unwrap(); + op1.enable(true); + + // =================================================================================== + // Debug Info below + rprintln!(" done."); + + rprintln!( + "opamode: {}\n", + ops.opamp1_csr.read().opamode().bits() as u8 + ); + rprintln!("opaen: {}\n", ops.opamp1_csr.read().opaen().bits() as u8); + rprintln!( + "opa_range: {}\n", + ops.opamp1_csr.read().opa_range().bits() as u8 + ); + rprintln!("vp_sel: {}\n", ops.opamp1_csr.read().vp_sel().bits() as u8); + rprintln!("vm_sel: {}\n", ops.opamp1_csr.read().vm_sel().bits() as u8); + rprintln!("opalpm: {}\n", ops.opamp1_csr.read().opalpm().bits() as u8); + rprintln!("calout: {}\n", ops.opamp1_csr.read().calout().bits() as u8); + rprintln!("calon: {}\n", ops.opamp1_csr.read().calon().bits() as u8); + rprintln!("calsel: {}\n", ops.opamp1_csr.read().calsel().bits() as u8); + rprintln!( + "usertrim: {}\n", + ops.opamp1_csr.read().usertrim().bits() as u8 + ); + rprintln!( + "pga_gain: {}\n", + ops.opamp1_csr.read().pga_gain().bits() as u8 + ); + + loop {} +} diff --git a/src/adc.rs b/src/adc.rs index df3ced0a..a69691a6 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -30,6 +30,12 @@ pub struct Vbat; /// Core temperature internal signal pub struct Temperature; +/// Opamp1 output connected to ADC1 +pub struct Opamp1Out; + +/// Opamp1 output connected to ADC1 +pub struct Opamp2Out; + /// Analog to Digital converter interface pub struct ADC { pub(crate) adc: ADC1, @@ -222,6 +228,16 @@ impl ADC { Vbat {} } + /// Get the `Opamp1Out` + pub fn enable_opamp1_out(&mut self) -> Opamp1Out { + Opamp1Out {} + } + + /// Get the `Opamp2Out` + pub fn enable_opamp2_out(&mut self) -> Opamp2Out { + Opamp2Out {} + } + /// Calculates the system VDDA by sampling the internal VREF channel and comparing /// the result with the value stored at the factory. If the chip's VDDA is not stable, run /// this before each ADC conversion. @@ -698,4 +714,6 @@ adc_pins!( 16, gpio::PB1, smpr2, smp16; 17, Temperature, smpr2, smp17; 18, Vbat, smpr2, smp18; + 8, Opamp1Out, smpr1, smp8; + 15, Opamp2Out, smpr2, smp15; ); diff --git a/src/lib.rs b/src/lib.rs index 87ea2daf..92255019 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,7 @@ pub mod adc; pub mod can; #[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] pub mod crc; + pub mod datetime; #[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] pub mod delay; @@ -141,6 +142,8 @@ pub mod gpio; pub mod i2c; #[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] pub mod lptimer; +#[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] +pub mod opamp; #[cfg(all( feature = "otg_fs", any( diff --git a/src/opamp.rs b/src/opamp.rs new file mode 100644 index 00000000..da239fb2 --- /dev/null +++ b/src/opamp.rs @@ -0,0 +1,339 @@ +use crate::hal::blocking::delay::DelayUs; +use crate::rcc::APB1R1; //{Enable, Reset, APB1R1, CCIPR}; +use crate::stm32::opamp::*; +use crate::traits::opamp as opamp_trait; + +use core::sync::atomic::{compiler_fence, Ordering}; + +// // s.common.ccr.modify(|_, w| w.vrefen().clear_bit()); +// // self.csr.modify(|_, w| w.opaen().clear_bit()); +// // .clear_bit() +// // .set_bit() +// // .bit_is_set() +// // .bit_is_clear() +// // let en = self.csr.read().opaen().bit_is_set(); +// // let mode = self.csr.read().opamode().bits() as u8; + +// // let en = self.csr.read().opaen().bit_is_set(); +// // self.csr.modify(|_, w| w.opaen().clear_bit()); +// // let mode = self.csr.read().opamode().bits() as u8; + +macro_rules! opamps { + ( + $( + $op:ident, + $csr_ty:ty, + $otr_ty:ty, + $lpotr_ty:ty, + $range_ty:ty; + )* + ) => { + $( + + pub struct $op<'a> { + csr: &'a $csr_ty, + otr: &'a $otr_ty, + lpotr: &'a $lpotr_ty, + range: &'a $range_ty, + } + + impl<'a> $op<'a> { + + /// Create a new OPAMP instance. the default range is always set to >2.4V so that the device is + /// not damaged at first call on typical eval-boards from ST + pub fn new( + csr: &'a $csr_ty, + otr: &'a $otr_ty, + lpotr: &'a $lpotr_ty, + // to set default range to > 2.4V + range: &'a $range_ty, + // rcc pointer and enable the clock for the configuration registers + apb1r1: &'a APB1R1, + ) -> Self { + // enable clock for configuration registers of OPAMPS + apb1r1.enr().modify(|_, w| w.opampen().set_bit()); + + // set OPA_RANGE = 1 (VDDA > 2.4V) by default, if ADC1 is not enabled + // if a lower voltage is applied, use an instance of ADC1 to clear the opa_range bit + if range.read().opaen().bit_is_clear() == true { + range.modify(|_, w| w.opa_range().set_bit()); // else expect bit is set correct + } + compiler_fence(Ordering::SeqCst); + $op { + csr: csr, + otr: otr, + lpotr: lpotr, + range: range, + } + + } + + /// opa range specifice the VDDA voltage applied to the device. + /// Is valied for both OP, if there are two in the device + /// it is by default set to >2.4V (high) + /// do not use this function, if you do not know, what you are decoding + /// you might damage the devices + /// since the setting applies to all opamps in the device and before + /// changeing this value, all opamps must be disabled; up to this pointe + /// only one opamp is supportd in this file, for that only that opamp is disable + /// before the value is set. + /// you need to enable the opamp after calling this function separately + pub fn set_opa_range(&self, high: bool) { + self.csr.modify(|_, w| w.opaen().clear_bit()); + if high { + self.range.modify(|_, w| w.opa_range().set_bit()); + } else { + self.range.modify(|_, w| w.opa_range().clear_bit()); + } + compiler_fence(Ordering::SeqCst); + } + } + + impl<'a> opamp_trait::ConfigOpamp for $op<'a> { + + /// Set operation mode, External is the defalult mode, where all signals are routed to pins + /// and must be configured connected to define the function of the opamp + fn set_opamp_oper_mode(&self, opmode: opamp_trait::OperationMode) -> opamp_trait::Result { + + match opmode { + opamp_trait::OperationMode::External => { + // disable OPEAN (before changeing anything else) + self.csr.modify(|_, w| w.opaen().clear_bit()); + // set OPAMODE = 0 + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b00)}); + // VP_SEL = 0 (GPIO) + self.csr.modify(|_, w| w.vp_sel().clear_bit()); + // VM_SEL = 0 (GPIO) + self.csr.modify(|_, w| unsafe {w.vm_sel().bits(0b00)}); + Ok(()) + }, + opamp_trait::OperationMode::Pga => { + // disable OPEAN (before changeing anything else) + self.csr.modify(|_, w| w.opaen().clear_bit()); + // set OPAMODE = 3 // follower mode = pga gain = 1 + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b11)}); + // VP_SEL = 0 (GPIO) + self.csr.modify(|_, w| w.vp_sel().clear_bit()); + // VM_SEL = 0 (GPIO) + self.csr.modify(|_, w| unsafe {w.vm_sel().bits(0b10)}); + Ok(()) + }, + opamp_trait::OperationMode::PgaExternalFiltering => { + // disable OPEAN (before changeing anything else) + self.csr.modify(|_, w| w.opaen().clear_bit()); + // set OPAMODE = 2 // pga mode = pga, gain = 2..16 (no filtering in follower mode) + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b10)}); + // VP_SEL = 0 (GPIO) + self.csr.modify(|_, w| w.vp_sel().clear_bit()); + // VM_SEL = 0 (GPIO) for external filtering + self.csr.modify(|_, w| unsafe {w.vm_sel().bits(0b00)}); + Ok(()) + }, + _ => Err(opamp_trait::Error::NotImplemented), + } + } + + /// Connect the input pins of the opamp to DAC or internnaly to follower or leakage-input + fn conect_inputs(&self, vinp: opamp_trait::VINP, vinm: opamp_trait::VINM) -> opamp_trait::Result { + match vinp { + opamp_trait::VINP::ExternalPin1 => { + // VP_SEL = 0 (GPIO), PA0 + self.csr.modify(|_, w| w.vp_sel().clear_bit()); + }, + opamp_trait::VINP::DAC1 => { + // VP_SEL = 1 (DAC1) + self.csr.modify(|_, w| w.vp_sel().set_bit()); + }, + _ => return Err(opamp_trait::Error::NotImplemented), + }; + match vinm { + opamp_trait::VINM::ExternalPin1 => { + // VM_SEL = 0 (GPIO), PA1 for external filtering + self.csr.modify(|_, w| unsafe {w.vm_sel().bits(0b00)}); + }, + opamp_trait::VINM::LeakageInputPin => { + // VM_SEL = 01 + self.csr.modify(|_, w| unsafe {w.vm_sel().bits(0b01)}); + }, + opamp_trait::VINM::PGA=> { + // VM_SEL = 10 + self.csr.modify(|_, w| unsafe {w.vm_sel().bits(0b10)}); + }, + _ => return Err(opamp_trait::Error::NotImplemented), + }; + Ok(()) + } + + /// Set the gain in pga mode, the device supports + /// the values 1, 2, 4, 8, 16 all other values are ignored + fn set_pga_gain(&self, gain: u16) -> opamp_trait::Result { + let opaen_state: bool = self.csr.read().opaen().bit_is_set(); + // disable OPEAN (before changeing anything else) + self.csr.modify(|_, w| w.opaen().clear_bit()); + + match gain { + 1 => { + // set OPAMODE = 3 // follower mode = pga gain = 1 + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b11)}); + }, + 2 => { + // set OPAMODE = 3 + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b10)}); + // set PGA_GAIN = 2 // follower mode = pga gain = 1 + self.csr.modify(|_, w| unsafe {w.pga_gain().bits(0b00)}); + }, + 4 => { + // set OPAMODE = 3 + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b10)}); + // set PGA_GAIN = 2 // follower mode = pga gain = 1 + self.csr.modify(|_, w| unsafe {w.pga_gain().bits(0b01)}); + }, + 8 => { + // set OPAMODE = 3 + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b10)}); + // set PGA_GAIN = 2 // follower mode = pga gain = 1 + self.csr.modify(|_, w| unsafe {w.pga_gain().bits(0b10)}); + }, + 16 => { + // set OPAMODE = 3 + self.csr.modify(|_, w| unsafe {w.opamode().bits(0b10)}); + // set PGA_GAIN = 2 // follower mode = pga gain = 1 + self.csr.modify(|_, w| unsafe {w.pga_gain().bits(0b11)}); + }, + _ => return Err(opamp_trait::Error::NotImplemented), + }; + + if opaen_state == true { + // if it has been enabled before, enable it again + self.csr.modify(|_, w| w.opaen().set_bit()); + } + Ok(()) + } + + /// the function preserves the enable state + /// when the function is called and the opamp is enabled, it is disabled + /// and reenabled after switch the power mode. + /// short incontinous signals may occrue + fn set_power_mode(&self, power_mode: opamp_trait::PowerMode) -> opamp_trait::Result { + let ena_state = self.csr.read().opaen().bit_is_set(); + self.enable(false); + match power_mode { + opamp_trait::PowerMode::Normal => { + // set normal mode + self.csr.modify(|_, w| w.opalpm().clear_bit()); + }, + opamp_trait::PowerMode::LowPower => { + // set normal mode + self.csr.modify(|_, w| w.opalpm().set_bit()); + }, + _ => return Err(opamp_trait::Error::NotImplemented), + }; + if ena_state { + self.enable(true); + } + + Ok(()) + } + + /// For the calibration to work, the opamp must be enabled and in + /// Calibration must be called for low power and normal mode separately if both + /// are needed + /// USERTRIM is preserved as it is before calling calibrate() + /// the driven load must be below 500uA during calibration + /// and must not be in external filtering mode and PGA-GAIN=1 + fn calibrate(&self, delay: &mut impl DelayUs) -> opamp_trait::Result { + // get USERTRIM bit value + let usertrim = self.csr.read().usertrim().bit(); + // set usertrim bit, so that calibration is possible + self.csr.modify(|_, w| w.usertrim().bit(true)); + // set opamp into callibration mode + self.csr.modify(|_, w| w.calon().set_bit()); + // select PMOS calibration first + self.csr.modify(|_, w| w.calsel().set_bit()); + // read if in LowPower Mode or in normal mode + let low_poer_mode = self.csr.read().opalpm().bit_is_set(); + + // set N and P calibration registers to 0 + if low_poer_mode == true { + self.lpotr.modify(|_, w| unsafe {w.trimlpoffsetn().bits(0b00000)}); + self.lpotr.modify(|_, w| unsafe {w.trimlpoffsetp().bits(0b00000)}); + } else { + self.otr.modify(|_, w| unsafe {w.trimoffsetn().bits(0b00000)}); + self.otr.modify(|_, w| unsafe {w.trimoffsetp().bits(0b00000)}); + } + + // increase calibration reg P till it toggles + for i in 0..32 { + if low_poer_mode == true { + self.lpotr.modify(|_, w| unsafe {w.trimlpoffsetp().bits(i)}); + } else { + self.otr.modify(|_, w| unsafe {w.trimoffsetp().bits(i)}); + } + + compiler_fence(Ordering::SeqCst); + // wait at least 1ms to new config to settle + delay.delay_us(1200); + if self.csr.read().calout().bit_is_set() == false { + break; + } + } + // if this point is reached, an the flag didn't change over the whole range + if self.csr.read().calout().bit_is_set() == true { + return Err(opamp_trait::Error::CalibrationError); + } + + // select NMOS calibration first + self.csr.modify(|_, w| w.calsel().clear_bit()); + // increase calibration reg N till it toggles + for i in 0..32 { + if low_poer_mode == true { + self.lpotr.modify(|_, w| unsafe {w.trimlpoffsetn().bits(i)}); + } else { + self.otr.modify(|_, w| unsafe {w.trimoffsetn().bits(i)}); + } + compiler_fence(Ordering::SeqCst); + // wait for at least 1ms to settle of Configuration + delay.delay_us(1200); + if self.csr.read().calout().bit_is_set() == true { + break + } + } + if self.csr.read().calout().bit_is_set() == false { + return Err(opamp_trait::Error::CalibrationError); + } + // set opamp into normal mode + self.csr.modify(|_, w| w.calon().clear_bit()); + // restore usertrim bit as it was before caling calibrate() + self.csr.modify(|_, w| w.usertrim().bit(usertrim)); + + Ok(()) + } + /// in calibration mode the calibrated values are used, which were set with calibrate() + /// default is using factory trimmed values which should be good for room temperatures + fn set_calibration_mode(&self, usertrim: bool){ + if usertrim { + self.csr.modify(|_, w| w.usertrim().set_bit()); + } else { + self.csr.modify(|_, w| w.usertrim().clear_bit()); + } + } + + /// enable the opamp + fn enable(&self, en: bool) { + compiler_fence(Ordering::SeqCst); + if en { + self.csr.modify(|_, w| w.opaen().set_bit()); + } else { + self.csr.modify(|_, w| w.opaen().clear_bit()); + } + } + } + )* + } + +} + +opamps!( + OP1, OPAMP1_CSR, OPAMP1_OTR, OPAMP1_LPOTR, OPAMP1_CSR; + OP2, OPAMP2_CSR, OPAMP2_OTR, OPAMP2_LPOTR, OPAMP1_CSR; +); diff --git a/src/timer.rs b/src/timer.rs index 34f1996b..e5081f12 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -248,11 +248,18 @@ macro_rules! hal { } /// Get the count of the timer. - pub fn count() -> $width { + pub fn count(&mut self) -> $width { let cnt = unsafe { (*$TIM::ptr()).cnt.read() }; cnt.cnt().bits() } + // not able to select compilation for timers which support this feature + // pub fn set_mms(&mut self, mms_val: u8) { + // // see chapter 27.4.2 in reference manual RM0394 + // // unsafe { (*$TIM::ptr()).mms().bits(mms_val) }; + // self.tim.cr2.modify(|_, w| unsafe{w.mms().bits(mms_val)} ); + // } + /// Releases the TIM peripheral pub fn free(self) -> $TIM { // pause counter diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 2e50f82b..036ab33e 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -1 +1,2 @@ pub mod flash; +pub mod opamp; diff --git a/src/traits/opamp.rs b/src/traits/opamp.rs new file mode 100644 index 00000000..46cc09d8 --- /dev/null +++ b/src/traits/opamp.rs @@ -0,0 +1,121 @@ +use crate::hal::blocking::delay::DelayUs; + +#[derive(Copy, Clone, Debug)] +pub enum OPAMPS { + OP1, + OP2, +} + +/// Opamp Operation Modes +#[derive(Copy, Clone, Debug)] +pub enum OperationMode { + // wrong values provided + External, + // PGA mode, (configurable gain) connected to an ADC + Pga, + // pga mode with external filter connected + PgaExternalFiltering, + // added, that warnings for global match pattern are suppressed + SuppressMachtWarnings, +} + +/// Opamp Power Mode configuration +#[derive(Copy, Clone, Debug)] +pub enum PowerMode { + // Normal operational power (full bandwith) + Normal, + // low power mode with resticted bandwith + LowPower, + // added, that warnings for global match pattern are suppressed + SuppressMachtWarnings, +} + +/// Opamp postive input configuration. External pins are numerated, to which they finaly match is +/// device specific +#[derive(Copy, Clone, Debug)] +pub enum VINP { + // output conected to external pin + ExternalPin1, + // output conected to DAC1 + DAC1, + // output of opamp1 + OPAMP1, + // added, that warnings for global match pattern are suppressed + SuppressMachtWarnings, +} + +/// Opamp negativ input configuration. External pins are numerated, to which they finaly match is +/// device specific +#[derive(Copy, Clone, Debug)] +pub enum VINM { + // input connected to Opamp output of other Opamp + LeakageInputPin, + // internally connected to PGA gain settings + PGA, + // input conected to external pin (also pga mode with filtering) + ExternalPin1, + // input conected to external pin (also pga mode with filtering) + ExternalPin2, + // added, that warnings for global match pattern are suppressed + SuppressMachtWarnings, +} + +/// Opamp gain configuration in PGA mode +#[derive(Copy, Clone, Debug)] +pub enum PgaGain { + PgaG1, + PgaG2, + PgaG4, + PgaG8, + PgaG16, + PgaG32, // this values and below may be used for stm32g4 devices + PgaG64, + PgaG128, + PgaG256, + // added, that warnings for global match pattern are suppressed + SuppressMachtWarnings, +} + +/// Opamp configuration error +#[derive(Copy, Clone, Debug)] +pub enum Error { + // wrong values provided + ConfigFailure, + // operaation mode not supportet for this Opamp + UnsuportedMode, + // gain out of bound + PDAGainOutOfBound, + // not Implemented + NotImplemented, + // calibration Error + CalibrationError, + // wrong pin assignment for selected operation mode + WrongPinAssinment, + // added, that warnings for global match pattern are suppressed + SuppressMachtWarnings, +} + +/// A type alias for the result of opamp configruation error. +pub type Result = core::result::Result<(), Error>; + +// impl From<> for Error { +// fn from +// } + +pub trait ConfigOpamp { + fn set_opamp_oper_mode(&self, opmode: OperationMode) -> Result; + + fn conect_inputs(&self, vinp: VINP, vinm: VINM) -> Result; + + // fn set_pga_gain_enum(&self, gain: PgaGain) -> Result; + + fn set_pga_gain(&self, gain: u16) -> Result; + + fn set_power_mode(&self, power_mode: PowerMode) -> Result; + + fn calibrate(&self, delay: &mut impl DelayUs) -> Result; + + fn set_calibration_mode(&self, usertrim: bool); + + fn enable(&self, en: bool); +}