Skip to main content

gd32e2_hal/
cmp.rs

1//! Analog comparator.
2//!
3//! ```ignore
4//! let parts = dp.gpioa.split(&mut rcu);
5//! let pos = parts.pa1.into_analog();
6//! let config = CmpConfig::new(Speed::High);
7//! let cmp = Cmp::new(&mut rcu, dp.cmp, pos, VrefintHalf, config).enable();
8//! cortex_m::asm::delay(4_800);
9//! let above = cmp.output();
10//! ```
11
12use core::marker::PhantomData;
13
14use crate::gpio::{Analog, Pin};
15use crate::pac;
16use crate::rcu::Rcu;
17
18/// Hysteresis on the comparator output, suppressing chatter near the threshold.
19#[allow(missing_docs)]
20#[derive(Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22pub enum Hysteresis {
23    None = 0b00,
24    Low = 0b01,
25    Medium = 0b10,
26    High = 0b11,
27}
28
29/// A source the inverting input can be tied to.
30///
31/// Implemented by the four internal reference taps and by the four pins the
32/// multiplexer reaches, each only in [`Analog`] — the manual requires that mode
33/// before a pin is selected as an input.
34///
35/// `VREFINT` is 1.2 V, so the taps sit at 0.3, 0.6, 0.9 and 1.2 V.
36pub trait InvertingInput {
37    /// The `CMPxMSEL` encoding of this source.
38    const MSEL: u8;
39}
40
41/// A quarter of the internal reference.
42pub struct VrefintQuarter;
43/// Half of the internal reference.
44pub struct VrefintHalf;
45/// Three quarters of the internal reference.
46pub struct VrefintThreeQuarters;
47/// The internal reference itself.
48pub struct Vrefint;
49
50impl InvertingInput for VrefintQuarter {
51    const MSEL: u8 = 0b000;
52}
53impl InvertingInput for VrefintHalf {
54    const MSEL: u8 = 0b001;
55}
56impl InvertingInput for VrefintThreeQuarters {
57    const MSEL: u8 = 0b010;
58}
59impl InvertingInput for Vrefint {
60    const MSEL: u8 = 0b011;
61}
62impl InvertingInput for Pin<'A', 4, Analog> {
63    const MSEL: u8 = 0b100;
64}
65impl InvertingInput for Pin<'A', 5, Analog> {
66    const MSEL: u8 = 0b101;
67}
68impl InvertingInput for Pin<'A', 0, Analog> {
69    const MSEL: u8 = 0b110;
70}
71impl InvertingInput for Pin<'A', 2, Analog> {
72    const MSEL: u8 = 0b111;
73}
74
75/// What the non-inverting input consists of.
76///
77/// `SW` is the `CMPxSW` state. `PA1` is the only pin wired to this input;
78/// handing over `PA4` as well closes the switch and ties the two together,
79/// which is why the pair is a separate implementor. Owning `PA4` here is what
80/// keeps it from also serving as the [`InvertingInput`] and shorting both
81/// inputs together.
82pub trait NonInvertingInput {
83    /// Whether `CMPxSW` is closed for this source.
84    const SW: bool;
85}
86
87impl NonInvertingInput for Pin<'A', 1, Analog> {
88    const SW: bool = false;
89}
90impl NonInvertingInput for (Pin<'A', 1, Analog>, Pin<'A', 4, Analog>) {
91    const SW: bool = true;
92}
93
94/// Where the comparator output is routed on top of the pin.
95///
96/// Enable the comparator before configuring the timer channel that captures it.
97#[derive(Clone, Copy, PartialEq, Eq)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99pub enum OutputSel {
100    /// Nowhere.
101    None = 0b000,
102    /// TIMER0 break input.
103    Timer0Break = 0b001,
104    /// TIMER0 CH0 input capture.
105    Timer0Ch0 = 0b010,
106    /// TIMER0 `OCPRE_CLR` input.
107    Timer0OcpreClr = 0b011,
108    /// TIMER2 CH0 input capture.
109    Timer2Ch0 = 0b110,
110    /// TIMER2 `OCPRE_CLR` input.
111    Timer2OcpreClr = 0b111,
112}
113
114/// Propagation delay traded against current draw.
115#[allow(missing_docs)]
116#[derive(Clone, Copy, PartialEq, Eq)]
117#[cfg_attr(feature = "defmt", derive(defmt::Format))]
118pub enum Speed {
119    High = 0b00,
120    Medium = 0b01,
121    Low = 0b10,
122    VeryLow = 0b11,
123}
124
125/// Polarity of the comparator output.
126///
127/// Affects the pin, EXTI and the timer, but not [`CmpRunning::output`], which
128/// is read off the raw comparison.
129#[derive(Clone, Copy, PartialEq, Eq)]
130#[cfg_attr(feature = "defmt", derive(defmt::Format))]
131pub enum Polarity {
132    /// The output is high while the non-inverting input is the higher one.
133    NotInverted = 0,
134    /// The output is inverted.
135    Inverted = 1,
136}
137
138/// Everything the comparator is set up with, apart from its inputs.
139///
140/// ```ignore
141/// CmpConfig::new(Speed::High)
142///     .hysteresis(Hysteresis::Medium)
143///     .output_sel(OutputSel::Timer0Break)
144/// ```
145#[derive(Clone, Copy, PartialEq, Eq)]
146#[cfg_attr(feature = "defmt", derive(defmt::Format))]
147#[must_use]
148pub struct CmpConfig {
149    speed: Speed,
150    hysteresis: Hysteresis,
151    output_sel: OutputSel,
152    polarity: Polarity,
153}
154
155impl CmpConfig {
156    /// Creates a configuration with the given speed, no hysteresis, no output
157    /// routing and a non-inverted output.
158    ///
159    /// Speed is a required argument: it trades propagation delay against current
160    /// draw, and neither choice is universal.
161    pub const fn new(speed: Speed) -> Self {
162        Self {
163            speed,
164            hysteresis: Hysteresis::None,
165            output_sel: OutputSel::None,
166            polarity: Polarity::NotInverted,
167        }
168    }
169    /// Sets the hysteresis. Defaults to [`Hysteresis::None`].
170    pub const fn hysteresis(mut self, hysteresis: Hysteresis) -> Self {
171        self.hysteresis = hysteresis;
172        self
173    }
174    /// Routes the output to a timer as well as the pin. Defaults to
175    /// [`OutputSel::None`].
176    pub const fn output_sel(mut self, output_sel: OutputSel) -> Self {
177        self.output_sel = output_sel;
178        self
179    }
180    /// Sets the output polarity. Defaults to [`Polarity::NotInverted`].
181    pub const fn polarity(mut self, polarity: Polarity) -> Self {
182        self.polarity = polarity;
183        self
184    }
185}
186
187/// Typestate: the control register still accepts writes.
188pub struct Unlocked;
189/// Typestate: the control register is frozen until the next system reset.
190pub struct Locked;
191
192/// The comparator, configured and stopped.
193pub struct Cmp<POS, INV> {
194    cmp: pac::Cmp,
195    pos: POS,
196    inv: INV,
197}
198
199/// The comparator, running.
200///
201/// Once `LOCKED` is [`Locked`] the control register is frozen for good: the
202/// peripheral cannot be stopped and the pins cannot be taken back.
203pub struct CmpRunning<POS, INV, LOCKED = Unlocked> {
204    cmp: pac::Cmp,
205    pos: POS,
206    inv: INV,
207    _locked: PhantomData<LOCKED>,
208}
209
210impl<POS: NonInvertingInput, INV: InvertingInput> Cmp<POS, INV> {
211    /// Clocks the peripheral and applies `config`, leaving the comparator stopped.
212    pub fn new(rcu: &mut Rcu, cmp: pac::Cmp, pos: POS, inv: INV, config: CmpConfig) -> Self {
213        rcu.enable_cfgcmp();
214        let mut cmp = Self { cmp, pos, inv };
215        cmp.apply_config(config);
216        cmp
217    }
218    /// Hands back the peripheral and both inputs. The clock stays on, since
219    /// `CFGCMPEN` also feeds SYSCFG.
220    pub fn release(self) -> (pac::Cmp, POS, INV) {
221        (self.cmp, self.pos, self.inv)
222    }
223
224    /// Writes the whole control register, leaving `CMPxEN` clear.
225    fn apply_config(&mut self, config: CmpConfig) {
226        self.cmp.cs().modify(|_, w| {
227            let w = w.cmphst().bits(config.hysteresis as u8);
228            let w = w.cmposel().bits(config.output_sel as u8);
229            let w = w.cmpm().bits(config.speed as u8);
230            let w = w.cmppl().bit(config.polarity == Polarity::Inverted);
231            // SAFETY: `MSEL` comes from an `InvertingInput` impl, each one a
232            // code of this field.
233            let w = unsafe { w.cmpmsel().bits(INV::MSEL) };
234            w.cmpsw().bit(POS::SW)
235        });
236    }
237
238    /// Starts the comparator.
239    pub fn enable(self) -> CmpRunning<POS, INV, Unlocked> {
240        self.cmp.cs().modify(|_, w| w.cmpen().set_bit());
241        CmpRunning {
242            cmp: self.cmp,
243            pos: self.pos,
244            inv: self.inv,
245            _locked: PhantomData,
246        }
247    }
248}
249
250impl<POS, INV> CmpRunning<POS, INV, Unlocked> {
251    /// Stops the comparator, handing back the configured but idle peripheral.
252    pub fn disable(self) -> Cmp<POS, INV> {
253        self.cmp.cs().modify(|_, w| w.cmpen().clear_bit());
254        Cmp {
255            cmp: self.cmp,
256            pos: self.pos,
257            inv: self.inv,
258        }
259    }
260
261    /// Freezes the control register until the next system reset.
262    ///
263    /// Nothing clears `CMPxLK`, so the comparator keeps running for good and the
264    /// pins stay with it: the locked type has neither `disable` nor a way out.
265    pub fn lock(self) -> CmpRunning<POS, INV, Locked> {
266        self.cmp.cs().modify(|_, w| w.cmplk().set_bit());
267        CmpRunning {
268            cmp: self.cmp,
269            pos: self.pos,
270            inv: self.inv,
271            _locked: PhantomData,
272        }
273    }
274}
275
276impl<POS, INV, LOCKED> CmpRunning<POS, INV, LOCKED> {
277    /// Whether the non-inverting input is above the inverting one.
278    ///
279    /// `CMPxO` is taken before the polarity multiplexer, so [`Polarity`] does
280    /// not show up here — only on the pin, EXTI and the timer.
281    pub fn output(&self) -> bool {
282        self.cmp.cs().read().cmpo().bit_is_set()
283    }
284}