nrf52/
acomp.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Analog Comparator Peripheral Driver, for nrf52
6//!
7//! Partially based on sam4l implementation of an analog comparator.
8//!
9//! The comparator (COMP) compares an input voltage (VIN+) against a second input voltage (VIN-). VIN+ can
10//! be derived from an analog input pin (AIN0-AIN7). VIN- can be derived from multiple sources depending on
11//! the operation mode of the comparator.
12//!
13//! Main features of the comparator are:
14//! - Input range from 0 V to VDD
15//! - Single-ended mode
16//!     - Fully flexible hysteresis using a 64-level reference ladder
17//! - Differential mode
18//!     - Configurable 50 mV hysteresis
19//! - Reference inputs (VREF):
20//!     - VDD
21//!     - External reference from AIN0 to AIN7 (between 0 V and VDD)
22//!     - Internal references 1.2 V, 1.8 V and 2.4 V
23//! - Three speed/power consumption modes: low-power, normal and high-speed
24//! - Single-pin capacitive sensor support
25//! - Event generation on output changes
26
27use kernel::hil::analog_comparator;
28use kernel::utilities::cells::OptionalCell;
29use kernel::utilities::registers::interfaces::{Readable, Writeable};
30use kernel::utilities::registers::{
31    register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly,
32};
33use kernel::utilities::StaticRef;
34use kernel::ErrorCode;
35
36/// Only one channel
37#[derive(Copy, Clone, Debug)]
38#[repr(u8)]
39pub enum ChannelNumber {
40    AC0 = 0x00,
41}
42
43/// Analog comparator channels.
44///
45/// The nrf52840 only has one analog comparator, so it does need channels
46/// However, the HIL was designed to support having multiple comparators, each
47/// one with a separate channel. So we create a dummy channel with only
48/// one possible value to represent this.
49/// Code for channels is based on Sam4l implementation
50pub struct Channel {
51    _chan_num: u32,
52}
53
54/// Initialization of an AC channel.
55impl Channel {
56    /// Create a new AC channel.
57    ///
58    /// - `channel`: Channel enum representing the channel number
59    pub const fn new(channel: ChannelNumber) -> Channel {
60        Channel {
61            _chan_num: (channel as u32) & 0x0F,
62        }
63    }
64}
65
66register_structs! {
67    CompRegisters {
68        /// TASK REGISTERS
69        /// Trigger task by writing 1
70        /// start comparator. Needed before comparator does anything.
71        /// After start, triggers events_ready
72        (0x000 => tasks_start: WriteOnly<u32>),
73        /// stop comparator
74        (0x004 => tasks_stop: WriteOnly<u32>),
75        /// sample comparator value
76        /// This triggers an update to RESULT register
77        /// This task doesn't do anything if comparator hasn't
78        /// been started yet.
79        (0x008 => tasks_sample: WriteOnly<u32>),
80        (0x00c => _reserved0),
81
82        /// EVENT REGISTERS
83        /// EVENTS: Can clear by writing 0, occured by 1
84        /// COMP is ready to go after start task
85        (0x100 => events_ready: ReadWrite<u32>),
86        /// Cross from high to low
87        (0x104 => events_down: ReadWrite<u32>),
88        /// cross from low to high
89        (0x108 => events_up: ReadWrite<u32>),
90        /// either events_up or events_down
91        (0x10c => events_cross: ReadWrite<u32>),
92        (0x110 => _reserved1),
93
94        /// Used to link tasks to events in hardware itself
95        /// Pretty much unused.
96        (0x200 => shorts: ReadWrite<u32>),
97        (0x204 => _reserved2),
98
99        /// Used to enable and disable interrupts
100        (0x300 => inten: ReadWrite<u32, InterruptEnable::Register>),
101        /// An alternate way to enable and disable interrupts
102        (0x304 => intenset: ReadWrite<u32>),
103        (0x308 => intenclr: ReadWrite<u32>),
104        (0x30c => _reserved3),
105
106        /// holds result after sampling comparison
107        (0x400 => result: ReadOnly<u32, ComparisonResult::Register>),
108        (0x404 => _reserved4),
109
110        /// Write to enable comparator. Do this after you've set all other settings
111        (0x500 => enable: ReadWrite<u32, Enable::Register>),
112        /// select VIN+
113        (0x504 => psel: ReadWrite<u32, PinSelect::Register>),
114        /// choose where you get VIN- from
115        (0x508 => refsel: ReadWrite<u32, ReferenceSelect::Register>),
116        /// choose which pin to use from VIN-
117        (0x50c => extrefsel: ReadWrite<u32, ExternalRefSelect::Register>),
118        (0x510 => _reserved5),
119
120        /// Hysteresis configuration (single-ended mode)
121        (0x530 => th: ReadWrite<u32>),
122        /// Choose between single ended and differential, and also speed/power
123        (0x534 => mode: ReadWrite<u32, Mode::Register>),
124        /// Hysteresis configuration (differential mode)
125        (0x538 => hyst: ReadWrite<u32, Hysteresis::Register>),
126        /// nrf52832 has one more register that was not included here because it's not used
127        /// and it doesn't exist on nrf52840
128        (0x53c => @END),
129    }
130}
131
132register_bitfields! [
133    u32,
134    InterruptEnable [
135        /// enable / disable interrupts for each event
136        READY OFFSET(0) NUMBITS(1) [],
137        DOWN OFFSET(1) NUMBITS(1) [],
138        UP OFFSET(2) NUMBITS(1) [],
139        CROSS OFFSET(3) NUMBITS(1) []
140    ],
141    ComparisonResult [
142        RESULT OFFSET(1) NUMBITS(1) [
143            /// VIN+ < VIN-
144            Below = 0,
145            /// VIN+ > VIN-
146            Above = 1
147        ]
148    ],
149    Enable [
150        ENABLE OFFSET(0) NUMBITS(2) [
151            Disabled = 0,
152            Enabled = 2
153        ]
154    ],
155    /// Select VIN+ input pin
156    PinSelect [
157        PinSelect OFFSET(0) NUMBITS(3) [
158            AnalogInput0 = 0,
159            AnalogInput1 = 1,
160            AnalogInput2 = 2,
161            AnalogInput3 = 3,
162            AnalogInput4 = 4,
163            AnalogInput5 = 5,
164            AnalogInput6 = 6,
165            AnalogInput7 = 7
166        ]
167    ],
168    /// Select reference source if in Single-Ended mode
169    ReferenceSelect [
170        ReferenceSelect OFFSET(0) NUMBITS(3) [
171            /// Reference voltage = 1.2V VDD >= 1.7
172            Internal1V2 = 0,
173            /// VREF = 1.8V (VDD >= VREF + .2V)
174            Internal1V8 = 1,
175            /// VREF = 2.4V (VDD >= VREF + .2V)
176            Internal2V4 = 2,
177            /// VREF = VDD
178            VDD = 4,
179            /// Select another pin as a reference
180            /// (VDD >= VREF >= AREFMIN)
181            AnalogReference = 5
182        ]
183    ],
184    /// If in diff mode, or single-ended mode with an analog reference,
185    /// use the pin specified here for VIN-
186    ExternalRefSelect[
187        ExternalRefSelect OFFSET(0) NUMBITS(3) [
188            AnalogRef0 = 0,
189            AnalogRef1 = 1,
190            /// The last six values are only valid on nrf52840, not nrf52832
191            AnalogRef2 = 2,
192            AnalogRef3 = 3,
193            AnalogRef4 = 4,
194            AnalogRef5 = 5,
195            AnalogRef6 = 6,
196            AnalogRef7 = 7
197        ]
198    ],
199    Mode[
200        /// Controls power usage and correspondingly speed of comparator
201        SpeedAndPower OFFSET(0) NUMBITS(3) [
202            Low = 0,
203            Normal = 1,
204            High = 2
205        ],
206        OperatingMode OFFSET(8) NUMBITS(1) [
207            SingleEnded = 0,
208            Differential = 1
209        ]
210    ],
211    /// HYST register for hysteresis in diff mode
212    /// Turns on 50mV hysteresis
213    Hysteresis [
214        Hysteresis OFFSET(0) NUMBITS(1)[]
215    ]
216];
217
218pub struct Comparator<'a> {
219    registers: StaticRef<CompRegisters>,
220    client: OptionalCell<&'a dyn analog_comparator::Client>,
221}
222
223impl Comparator<'_> {
224    pub const fn new() -> Self {
225        Comparator {
226            registers: ACOMP_BASE,
227            client: OptionalCell::empty(),
228        }
229    }
230
231    /// Enables comparator
232    /// Uses differential mode, with no hysteresis, and normal speed and power
233    /// VIN+ = AIN5 and VIN- = AIN0
234    fn enable(&self) {
235        // Checks if it's already enabled
236        // Assumes no one else is writing to comp registers directly
237        if self
238            .registers
239            .enable
240            .any_matching_bits_set(Enable::ENABLE::Enabled)
241        {
242            return;
243        }
244
245        // Set mode to Differential
246        // Differential and single ended are pretty much the same,
247        // except single-ended gives more options for input and
248        // uses a ref ladder for hysteresis instead of a set voltage
249        self.registers
250            .mode
251            .write(Mode::OperatingMode::Differential + Mode::SpeedAndPower::Normal);
252        // VIN+ = Pin 0
253        self.registers
254            .psel
255            .write(PinSelect::PinSelect::AnalogInput5);
256        // VIN- = Pin 1
257        self.registers
258            .extrefsel
259            .write(ExternalRefSelect::ExternalRefSelect::AnalogRef0);
260        // Disable hysteresis
261        self.registers.hyst.write(Hysteresis::Hysteresis::CLEAR);
262
263        self.registers.enable.write(Enable::ENABLE::Enabled);
264        // start comparator
265        self.registers.events_ready.set(0);
266        self.registers.tasks_start.set(1);
267        // wait for comparator to be ready
268        // delay is on order of 3 microseconds so spin wait is OK
269        while self.registers.events_ready.get() == 0 {}
270    }
271
272    fn disable(&self) {
273        // stop comparator
274        self.registers.tasks_stop.set(1);
275        // completely turn comparator off
276        self.registers.enable.write(Enable::ENABLE::Disabled);
277    }
278
279    /// Handles upward crossing events (when VIN+ becomes greater than VIN-)
280    pub fn handle_interrupt(&self) {
281        // HIL only cares about upward crossing interrupts
282        // VIN+ crossed VIN-
283        if self.registers.events_up.get() == 1 {
284            // Clear event
285            self.registers.events_up.set(0);
286            self.client.map(|client| {
287                // Only one channel (0)
288                client.fired(0);
289            });
290        }
291    }
292}
293
294impl<'a> analog_comparator::AnalogComparator<'a> for Comparator<'a> {
295    type Channel = Channel;
296
297    /// Starts comparison on only channel
298    /// This enables comparator and interrupts
299    fn start_comparing(&self, _: &Self::Channel) -> Result<(), ErrorCode> {
300        self.enable();
301
302        // Enable only up interrupt (If VIN+ crosses VIN-)
303        self.registers.inten.write(InterruptEnable::UP::SET);
304
305        Ok(())
306    }
307
308    /// Stops comparing and disables comparator
309    fn stop_comparing(&self, _: &Self::Channel) -> Result<(), ErrorCode> {
310        // Disables interrupts
311        self.registers.inten.set(0);
312        // Stops comparison
313        self.registers.tasks_stop.set(1);
314
315        self.disable();
316        Ok(())
317    }
318
319    /// Performs a single comparison between VIN+ and VIN-
320    /// Returns true if vin+ > vin-
321    /// Enables comparator if not enabled, to disable call stop comparing
322    fn comparison(&self, _: &Self::Channel) -> bool {
323        self.enable();
324
325        // Signals to update Result register
326        self.registers.tasks_sample.set(1);
327
328        // Returns 1 (true) if vin+ > vin-
329        self.registers.result.get() == 1
330    }
331
332    fn set_client(&self, client: &'a dyn analog_comparator::Client) {
333        self.client.set(client);
334    }
335}
336
337const ACOMP_BASE: StaticRef<CompRegisters> =
338    unsafe { StaticRef::new(0x40013000 as *const CompRegisters) };