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}