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) };