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