diff --git a/Cargo.toml b/Cargo.toml index 692c72bf..8d887b1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -203,3 +203,7 @@ required-features = ["rt"] [[example]] name = "adc_dma" required-features = ["rt"] + +[[example]] +name = "comparator" +required-features = ["rt"] diff --git a/examples/comparator.rs b/examples/comparator.rs new file mode 100644 index 00000000..d88e08f0 --- /dev/null +++ b/examples/comparator.rs @@ -0,0 +1,71 @@ +//! Testing comparator. +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m_rt::entry; +use rtt_target::rprintln; +use stm32l4xx_hal::{ + comp::{self, Comp, CompConfig, CompDevice}, + delay::Delay, + pac, + prelude::*, +}; + +#[entry] +fn main() -> ! { + // Set Up RTT + rtt_target::rtt_init_print!(); + + // Set up ARM Cortex-M peripherals. These are common to many MCUs, including all STM32 ones. + let cp = cortex_m::Peripherals::take().unwrap(); + // Set up peripherals specific to the microcontroller you're using. + let dp = pac::Peripherals::take().unwrap(); + + // Setting Up Clock + 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); + + // Setting Up GPIO (Not really needed) + let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); + gpiob.pb2.into_analog(&mut gpiob.moder, &mut gpiob.pupdr); + + // Setting Up Delay + let mut delay = Delay::new(cp.SYST, clocks); + + // Setting Up Comparator + // Comparator Configuration + let cfg = CompConfig { + // No Hysterysis + hyst: comp::Hysterisis::NoHysterisis, + // Using internal Vref as negative input + // e.g. (1.22V) in STM32L47xx, STM32L48xx, STM32L49xx and STM32L4Axx. + // Consult Reference Manual for all negative input. + inmsel: comp::InvertingInput::Vref, + // Using Io2 as positive input + // e.g. (PB2) for COMP1 in STM32L47xx, STM32L48xx, STM32L49xx and STM32L4Axx. + // Consult Reference Manual for all positive input. + inpsel: comp::NonInvertingInput::Io2, + // Don't invert output high when inverting input < noninverting and etc. + polarity: comp::OutputPolarity::NotInverted, + // High Power Consumption (lowest propagation delay) + pwrmode: comp::PowerMode::HighSpeed, + }; + // Creating Comparator device using COMP1 + let mut comparator = Comp::new(CompDevice::One, cfg, &mut rcc.apb2); + // Starting Comparator + comparator.start().unwrap(); + + loop { + // Reading and Printing Output + let output = comparator.get_output_level(); + rprintln!("{}", output); + delay.delay_ms(1000u32); + } +} diff --git a/src/comp.rs b/src/comp.rs new file mode 100644 index 00000000..bd603e89 --- /dev/null +++ b/src/comp.rs @@ -0,0 +1,305 @@ +//! Comparator +//! +//! TODO: +//! - Window Mode Configuration (COMP1 and COMP2 have different configs) +//! - Blanking Source Configuration (COMP1 and COMP2 have different configs) +//! - More Inputs For Inverting Input (STM32L41xxx/42xxx/43xxx/44xxx/45xxx/46xxx) +//! - Moving Peripheral into Struct (pac needs to change) +//! - Add Configuration Defaults +//! - Interrupts? +use crate::{ + pac, + rcc::{Enable, Reset, APB2}, +}; + +// Config enums +/// Comparator power mode +pub enum PowerMode { + /// High speed/full power (Lowest propagation delay). + HighSpeed = 0x00000000, + /// Medium speed/medium power (Medium propagation delay). + MediumSpeed = 0x00000004, + /// Low speed/ultra-low power (Highest propagation delay). + LowSpeed = 0x0000000c, +} + +/// Comparator input plus (Non-inverting Input) +pub enum NonInvertingInput { + /// From the first GPIO pin connected to the comparator. + /// + /// The GPIO pin used depends on the MCU and comparator used. + Io1 = 0x00000000, + /// From the second GPIO pin connected to the comparator. + /// + /// The GPIO pin used depends on the MCU and comparator used. + Io2 = 0x00000080, + // PA1/PA3 for STM32L41xxx/42xxx/43xxx/44xxx/45xxx/46xxx + #[cfg(any( + feature = "stm32l431", + feature = "stm32l451", + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l433", + feature = "stm32l443", + ))] + /// From the third GPIO pin connected to the comparator. + /// + /// The GPIO pin used depends on the MCU and comparator used. + Io3 = 0x00000100, +} + +// TODO Values are based on SCALEN (0x800000) and BRGEN (0x400000) check for other MCU. +/// Comparator input minus (Inverted Input) +pub enum InvertingInput { + /// 1/4 of Vref + OneQuarterVref = 0x00c00000, + /// 1/2 of Vref + OneHalfVref = 0x00c00010, + /// 3/4 of Vref + ThreeQuarterVref = 0x00c00020, + /// Vref + Vref = 0x00800030, + /// From DAC channel 1 + DacCh1 = 0x00000040, + /// From DAC channel 2 + DacCh2 = 0x00000050, + /// From the first GPIO pin connected to the comparator. + /// + /// The GPIO pin used depends on the MCU and comparator used. + Io1 = 0x00000060, + /// From the second GPIO pin connected to the comparator. + /// + /// The GPIO pin used depends on the MCU and comparator used. + Io2 = 0x00000070, +} + +/// Comparator hysterisis +pub enum Hysterisis { + /// No Hysterisis. + NoHysterisis = 0x00000000, + /// Low Hysterisis. + LowHysteresis = 0x00010000, + /// Medium Hysterisis. + MediumHysteresis = 0x00020000, + /// High Hysterisis. + HighHysteresis = 0x00030000, +} + +/// Comparator output polarity +/// +/// When [OutputPolarity::NotInverted] is used. +/// The comparator output will be high (1) when [NonInvertingInput] has higher +/// voltage than [InvertingInput]. The comparator output will be low (0) when +/// [NonInvertingInput] has lower voltage than [InvertingInput]. +/// +/// When [OutputPolarity::Inverted] is used. +/// The comparator output will be high (1) when [NonInvertingInput] has lower +/// voltage than [InvertingInput]. The comparator output will be low (0) when +/// [NonInvertingInput] has higher voltage than [InvertingInput]. +pub enum OutputPolarity { + /// Comparator output will not be inverted. + NotInverted = 0x00000000, + /// Comparator output will be inverted. + Inverted = 0x00008000, +} + +/// Comparator blanking source +pub enum BlankingSource { + /// No Blanking. + None = 0x00000000, + /// TIM1 OC5 as the blanking source. + Timloc5 = 0x400000, +} + +/// Comparator devices avaiable. +pub enum CompDevice { + /// Comparator number 1 (COMP1). + One, + /// Comparator number 2 (COMP2). + #[cfg(not(any(feature = "stm32l412", feature = "stm32l422")))] + Two, +} + +// Structs +/// Initial configuration data for the comparator peripheral. +pub struct CompConfig { + /// Comparator power mode. + pub pwrmode: PowerMode, + /// Comparator non-inverting input. + pub inpsel: NonInvertingInput, + /// Comparator inverting input. + pub inmsel: InvertingInput, + /// Comparator hysterisis. + pub hyst: Hysterisis, + /// Comparator output polarity. + pub polarity: OutputPolarity, + // Comparator blanking source. + // pub blanking: BlankingSource, +} + +/// Macro to write bits to the register +macro_rules! set_bit { + ($comp:ident, $value:expr) => { + unsafe { + let regs = &(*pac::COMP::ptr()).$comp; + regs.modify(|r, w| { + let current_bits = r.bits(); + let output_bits = current_bits | $value; + w.bits(output_bits) + }) + } + }; +} + +/// Macro to clear bits in the register +macro_rules! clear_bit { + ($comp:ident, $value:expr) => { + unsafe { + let regs = &(*pac::COMP::ptr()).$comp; + let current_bits = regs.read().bits(); + let output_bits = current_bits & !$value; + regs.modify(|_, w| w.bits(output_bits)) + } + }; +} + +/// Macro to read bits in the register +macro_rules! read_bit { + ($comp:ident, $value:expr) => { + unsafe { + let regs = &(*pac::COMP::ptr()).$comp; + regs.read().bits() & $value + } + }; +} + +/// Macro to modify the register +macro_rules! modify_bit { + ($comp:ident, $value:expr) => { + unsafe { + let regs = &(*pac::COMP::ptr()).$comp; + regs.write(|w| w.bits($value)) + } + }; +} + +/// Represents an Analog Comparator peripheral. +pub struct Comp { + /// The comparator device. + device: CompDevice, + /// The lock status of the comparator. + is_locked: bool, +} + +impl Comp { + /// Initialize the comparator peripheral. This will writes the configuration + /// according to `cfg`. + pub fn new(device: CompDevice, cfg: CompConfig, apb: &mut APB2) -> Self { + let result = Self { + device, + is_locked: false, + }; + + let config = cfg.hyst as u32 + | cfg.inmsel as u32 + | cfg.inpsel as u32 + | cfg.polarity as u32 + | cfg.pwrmode as u32; + + ::enable(apb); + ::reset(apb); + + match result.device { + CompDevice::One => modify_bit!(comp1_csr, config), + CompDevice::Two => modify_bit!(comp2_csr, config), + } + result + } + + /// Writes bit/bits to the regiter. + fn set_bit(&mut self, value: u32) -> Result<(), ()> { + if self.is_locked { + return Err(()); + } + + match self.device { + CompDevice::One => set_bit!(comp1_csr, value), + CompDevice::Two => set_bit!(comp2_csr, value), + } + + Ok(()) + } + + /// Clears bit/bits in the register. + /// + /// This function will return an Error when the comparator is locked. + fn clear_bit(&mut self, value: u32) -> Result<(), ()> { + if self.is_locked { + return Err(()); + } + + match self.device { + CompDevice::One => clear_bit!(comp1_csr, value), + CompDevice::Two => clear_bit!(comp2_csr, value), + } + + Ok(()) + } + + /// Read bit/bits in the register. + /// + /// This function will return an Error when the comparator is locked. + fn read_bit(&self, value: u32) -> u32 { + match self.device { + CompDevice::One => read_bit!(comp1_csr, value), + CompDevice::Two => read_bit!(comp2_csr, value), + } + } + + /// Gets the output level of the comparator + /// + /// The output level depends on the configuration of the comparator. + /// If the [polarity](CompConfig::polarity) is [NotInverted](OutputPolarity::NotInverted) + /// - It will output high (1) if the non-inverting input is higher than + /// the output of inverting input. + /// - It will output low (0) if the non-inverting input is lower than + /// the output of the inverting input. + /// + /// The oposite will be out inverted if [polarity](CompConfig::polarity) is + /// [Inverted](OutputPolarity::NotInverted). + pub fn get_output_level(&self) -> u32 { + self.read_bit(0b1 << 30) >> 30 + } + + /// Starts the comparator. + /// + /// This function will return an Error when the comparator is locked. + pub fn start(&mut self) -> Result<(), ()> { + self.set_bit(0b1) + } + + /// Stops the comparator. + /// + /// This function will return an Error when the comparator is locked. + pub fn stop(&mut self) -> Result<(), ()> { + self.clear_bit(0b1) + } + + /// Locks the comparator. + /// + /// This locks the comparator registers making it only read-only. + /// + /// **Note:** The lock also applies to the lock bit itself. Therefore, + /// the comparator register/configuration **cannot** be changed until + /// a hardware reset. + /// + /// This function will return an Error when the comparator is locked. + pub fn lock(&mut self) -> Result<(), ()> { + self.is_locked = true; + self.set_bit(0x80000000) + } +} diff --git a/src/lib.rs b/src/lib.rs index e0788a66..d9fff178 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,3 +184,5 @@ mod sealed { pub trait Sealed {} } pub(crate) use sealed::Sealed; + +pub mod comp; diff --git a/src/rcc/enable.rs b/src/rcc/enable.rs index 67b9ac9f..7f5697d5 100644 --- a/src/rcc/enable.rs +++ b/src/rcc/enable.rs @@ -137,6 +137,8 @@ bus! { TIM15 => (APB2, tim15en, tim15smen, tim15rst), // 16 TIM16 => (APB2, tim16en, tim16smen, tim16rst), // 17 SAI1 => (APB2, sai1en, sai1smen, sai1rst), // 21 + + COMP => (APB2, syscfgen, syscfgsmen, syscfgrst), } // L4x1, L4x2, L4x3, L4x5 or L4x6