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