nrf52_hal_common/
saadc.rs

1use crate::{
2    gpio::{Floating, Input},
3    target::{saadc, SAADC},
4};
5use core::{
6    hint::unreachable_unchecked,
7    ops::Deref,
8    sync::atomic::{compiler_fence, Ordering::SeqCst},
9};
10use embedded_hal::adc::{Channel, OneShot};
11
12pub use crate::target::saadc::{
13    ch::config::{GAINW as Gain, REFSELW as Reference, RESPW as Resistor, TACQW as Time},
14    oversample::OVERSAMPLEW as Oversample,
15    resolution::VALW as Resolution,
16};
17
18// Only 1 channel is allowed right now, a discussion needs to be had as to how
19// multiple channels should work (See "scan mode" in the datasheet)
20// Issue: https://github.com/nrf-rs/nrf52-hal/issues/82
21
22pub trait SaadcExt: Deref<Target = saadc::RegisterBlock> + Sized {
23    fn constrain(self) -> Saadc;
24}
25
26impl SaadcExt for SAADC {
27    fn constrain(self) -> Saadc {
28        Saadc::new(self, SaadcConfig::default())
29    }
30}
31
32pub struct Saadc(SAADC);
33
34impl Saadc {
35    pub fn new(saadc: SAADC, config: SaadcConfig) -> Self {
36        // The write enums do not implement clone/copy/debug, only the
37        // read ones, hence the need to pull out and move the values
38        let SaadcConfig {
39            resolution,
40            oversample,
41            reference,
42            gain,
43            resistor,
44            time
45        } = config;
46
47        saadc.enable.write(|w| w.enable().enabled());
48        saadc
49            .resolution
50            .write(|w| w.val().variant(resolution));
51        saadc
52            .oversample
53            .write(|w| w.oversample().variant(oversample));
54        saadc.samplerate.write(|w| w.mode().task());
55
56        saadc.ch[0].config.write(|w| {
57            w.refsel()
58                .variant(reference)
59                .gain()
60                .variant(gain)
61                .tacq()
62                .variant(time)
63                .mode()
64                .se()
65                .resp()
66                .variant(resistor)
67                .resn()
68                .bypass()
69                .burst()
70                .enabled()
71        });
72        saadc.ch[0].pseln.write(|w| w.pseln().nc());
73
74        // Calibrate
75        saadc.tasks_calibrateoffset.write(|w| unsafe { w.bits(1) });
76        while saadc.events_calibratedone.read().bits() == 0 {}
77
78        Saadc(saadc)
79    }
80}
81
82pub struct SaadcConfig {
83    resolution: Resolution,
84    oversample: Oversample,
85    reference: Reference,
86    gain: Gain,
87    resistor: Resistor,
88    time: Time,
89}
90
91// 0 volts reads as 0, VDD volts reads as u16::MAX
92impl Default for SaadcConfig {
93    fn default() -> Self {
94        SaadcConfig {
95            resolution: Resolution::_14BIT,
96            oversample: Oversample::OVER8X,
97            reference: Reference::VDD1_4,
98            gain: Gain::GAIN1_4,
99            resistor: Resistor::BYPASS,
100            time: Time::_20US,
101        }
102    }
103}
104
105impl<PIN> OneShot<Saadc, u16, PIN> for Saadc
106where
107    PIN: Channel<Saadc, ID = u8>,
108{
109    type Error = ();
110    fn read(&mut self, _pin: &mut PIN) -> nb::Result<u16, Self::Error> {
111        match PIN::channel() {
112            0 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input0()),
113            1 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input1()),
114            2 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input2()),
115            3 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input3()),
116            4 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input4()),
117            5 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input5()),
118            6 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input6()),
119            7 => self.0.ch[0].pselp.write(|w| w.pselp().analog_input7()),
120            // This can never happen the only analog pins have already been defined
121            // PAY CLOSE ATTENTION TO ANY CHANGES TO THIS IMPL OR THE `channel_mappings!` MACRO
122            _ => unsafe { unreachable_unchecked() },
123        }
124
125        let mut val: u16 = 0;
126        self.0
127            .result
128            .ptr
129            .write(|w| unsafe { w.ptr().bits(((&mut val) as *mut _) as u32) });
130        self.0
131            .result
132            .maxcnt
133            .write(|w| unsafe { w.maxcnt().bits(1) });
134
135        // Conservative compiler fence to prevent starting the ADC before the
136        // pointer and maxcount have been set
137        compiler_fence(SeqCst);
138
139        self.0.tasks_start.write(|w| unsafe { w.bits(1) });
140        self.0.tasks_sample.write(|w| unsafe { w.bits(1) });
141
142        while self.0.events_end.read().bits() == 0 {}
143        self.0.events_end.reset();
144
145        // Will only occur if more than one channel has been enabled
146        if self.0.result.amount.read().bits() != 1 {
147            return Err(nb::Error::Other(()));
148        }
149
150        // Second fence to prevent optimizations creating issues with the EasyDMA-modified `val`
151        compiler_fence(SeqCst);
152
153        Ok(val)
154    }
155}
156
157macro_rules! channel_mappings {
158    ($($n:expr => $pin:path),*) => {
159        $(
160            impl Channel<Saadc> for $pin {
161                type ID = u8;
162
163                fn channel() -> <Self as embedded_hal::adc::Channel<Saadc>>::ID {
164                    $n
165                }
166            }
167        )*
168    };
169}
170
171channel_mappings! {
172    0 => crate::gpio::p0::P0_02<Input<Floating>>,
173    1 => crate::gpio::p0::P0_03<Input<Floating>>,
174    2 => crate::gpio::p0::P0_04<Input<Floating>>,
175    3 => crate::gpio::p0::P0_05<Input<Floating>>,
176    4 => crate::gpio::p0::P0_28<Input<Floating>>,
177    5 => crate::gpio::p0::P0_29<Input<Floating>>,
178    6 => crate::gpio::p0::P0_30<Input<Floating>>,
179    7 => crate::gpio::p0::P0_31<Input<Floating>>
180}