diff options
| -rw-r--r-- | embassy-nrf/src/lib.rs | 4 | ||||
| -rw-r--r-- | embassy-nrf/src/saadc.rs | 242 |
2 files changed, 246 insertions, 0 deletions
diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 22c71e7f5..009cb99be 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs | |||
| @@ -100,6 +100,7 @@ pub mod interrupt; | |||
| 100 | #[cfg(feature = "52840")] | 100 | #[cfg(feature = "52840")] |
| 101 | pub mod qspi; | 101 | pub mod qspi; |
| 102 | pub mod rtc; | 102 | pub mod rtc; |
| 103 | pub mod saadc; | ||
| 103 | pub mod spim; | 104 | pub mod spim; |
| 104 | pub mod uarte; | 105 | pub mod uarte; |
| 105 | 106 | ||
| @@ -130,6 +131,9 @@ embassy_extras::peripherals! { | |||
| 130 | #[cfg(any(feature = "52833", feature = "52840"))] | 131 | #[cfg(any(feature = "52833", feature = "52840"))] |
| 131 | spim3: SPIM3, | 132 | spim3: SPIM3, |
| 132 | 133 | ||
| 134 | // SAADC | ||
| 135 | saadc: SAADC, | ||
| 136 | |||
| 133 | // GPIOTE | 137 | // GPIOTE |
| 134 | gpiote: GPIOTE, | 138 | gpiote: GPIOTE, |
| 135 | gpiote_ch_0: GPIOTE_CH0, | 139 | gpiote_ch_0: GPIOTE_CH0, |
diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs new file mode 100644 index 000000000..b854ce780 --- /dev/null +++ b/embassy-nrf/src/saadc.rs | |||
| @@ -0,0 +1,242 @@ | |||
| 1 | use core::future::Future; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use core::pin::Pin; | ||
| 4 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 5 | use core::task::Poll; | ||
| 6 | use embassy::traits; | ||
| 7 | use embassy::util::{wake_on_interrupt, PeripheralBorrow}; | ||
| 8 | use embassy_extras::unborrow; | ||
| 9 | use futures::future::poll_fn; | ||
| 10 | use traits::spi::FullDuplex; | ||
| 11 | |||
| 12 | use crate::gpio::Pin as GpioPin; | ||
| 13 | use crate::interrupt::{self, Interrupt}; | ||
| 14 | use crate::{pac, peripherals, slice_in_ram_or}; | ||
| 15 | |||
| 16 | #[cfg(feature = "9160")] | ||
| 17 | use pac::{saadc_ns as saadc, SAADC_NS as SAADC}; | ||
| 18 | |||
| 19 | #[cfg(not(feature = "9160"))] | ||
| 20 | use pac::{saadc, SAADC}; | ||
| 21 | |||
| 22 | pub use saadc::{ | ||
| 23 | ch::{ | ||
| 24 | config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time}, | ||
| 25 | pselp::PSELP_A as PositiveChannel, | ||
| 26 | }, | ||
| 27 | oversample::OVERSAMPLE_A as Oversample, | ||
| 28 | resolution::VAL_A as Resolution, | ||
| 29 | }; | ||
| 30 | |||
| 31 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 32 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 33 | #[non_exhaustive] | ||
| 34 | pub enum Error {} | ||
| 35 | |||
| 36 | /// One-shot saadc. Continuous sample mode TODO. | ||
| 37 | pub struct OneShot<'d, T: PositivePin> { | ||
| 38 | peri: peripherals::SAADC, | ||
| 39 | positive_pin: T, | ||
| 40 | irq: interrupt::SAADC, | ||
| 41 | phantom: PhantomData<(&'d mut peripherals::SAADC, &'d mut T)>, | ||
| 42 | } | ||
| 43 | |||
| 44 | /// Used to configure the SAADC peripheral. | ||
| 45 | /// | ||
| 46 | /// See the `Default` impl for suitable default values. | ||
| 47 | pub struct Config { | ||
| 48 | /// Output resolution in bits. | ||
| 49 | pub resolution: Resolution, | ||
| 50 | /// Average 2^`oversample` input samples before transferring the result into memory. | ||
| 51 | pub oversample: Oversample, | ||
| 52 | /// Reference voltage of the SAADC input. | ||
| 53 | pub reference: Reference, | ||
| 54 | /// Gain used to control the effective input range of the SAADC. | ||
| 55 | pub gain: Gain, | ||
| 56 | /// Positive channel resistor control. | ||
| 57 | pub resistor: Resistor, | ||
| 58 | /// Acquisition time in microseconds. | ||
| 59 | pub time: Time, | ||
| 60 | } | ||
| 61 | |||
| 62 | impl Default for Config { | ||
| 63 | fn default() -> Self { | ||
| 64 | Self { | ||
| 65 | resolution: Resolution::_14BIT, | ||
| 66 | oversample: Oversample::OVER8X, | ||
| 67 | reference: Reference::VDD1_4, | ||
| 68 | gain: Gain::GAIN1_4, | ||
| 69 | resistor: Resistor::BYPASS, | ||
| 70 | time: Time::_20US, | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | impl<'d, T: PositivePin> OneShot<'d, T> { | ||
| 76 | pub fn new( | ||
| 77 | saadc: impl PeripheralBorrow<Target = peripherals::SAADC> + 'd, | ||
| 78 | irq: impl PeripheralBorrow<Target = interrupt::SAADC> + 'd, | ||
| 79 | positive_pin: impl PeripheralBorrow<Target = T> + 'd, | ||
| 80 | config: Config, | ||
| 81 | ) -> Self { | ||
| 82 | unborrow!(saadc, irq, positive_pin); | ||
| 83 | |||
| 84 | let r = unsafe { &*SAADC::ptr() }; | ||
| 85 | |||
| 86 | let Config { | ||
| 87 | resolution, | ||
| 88 | oversample, | ||
| 89 | reference, | ||
| 90 | gain, | ||
| 91 | resistor, | ||
| 92 | time, | ||
| 93 | } = config; | ||
| 94 | |||
| 95 | // Configure pins | ||
| 96 | r.enable.write(|w| w.enable().enabled()); | ||
| 97 | r.resolution.write(|w| w.val().variant(resolution)); | ||
| 98 | r.oversample.write(|w| w.oversample().variant(oversample)); | ||
| 99 | |||
| 100 | r.ch[0].config.write(|w| { | ||
| 101 | w.refsel().variant(reference); | ||
| 102 | w.gain().variant(gain); | ||
| 103 | w.tacq().variant(time); | ||
| 104 | w.mode().se(); | ||
| 105 | w.resp().variant(resistor); | ||
| 106 | w.resn().bypass(); | ||
| 107 | if !matches!(oversample, Oversample::BYPASS) { | ||
| 108 | w.burst().enabled(); | ||
| 109 | } else { | ||
| 110 | w.burst().disabled(); | ||
| 111 | } | ||
| 112 | w | ||
| 113 | }); | ||
| 114 | |||
| 115 | // Set positive channel | ||
| 116 | r.ch[0] | ||
| 117 | .pselp | ||
| 118 | .write(|w| w.pselp().variant(positive_pin.channel())); | ||
| 119 | |||
| 120 | // Disable all events interrupts | ||
| 121 | r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); | ||
| 122 | |||
| 123 | Self { | ||
| 124 | peri: saadc, | ||
| 125 | positive_pin, | ||
| 126 | irq, | ||
| 127 | phantom: PhantomData, | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | fn regs(&self) -> &saadc::RegisterBlock { | ||
| 132 | unsafe { &*SAADC::ptr() } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | impl<'d, T: PositivePin> Drop for OneShot<'d, T> { | ||
| 137 | fn drop(&mut self) { | ||
| 138 | let r = self.regs(); | ||
| 139 | r.enable.write(|w| w.enable().disabled()); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | pub trait Sample { | ||
| 144 | type SampleFuture<'a>: Future<Output = i16> + 'a | ||
| 145 | where | ||
| 146 | Self: 'a; | ||
| 147 | |||
| 148 | fn sample<'a>(self: Pin<&'a mut Self>) -> Self::SampleFuture<'a>; | ||
| 149 | } | ||
| 150 | |||
| 151 | impl<'d, T: PositivePin> Sample for OneShot<'d, T> { | ||
| 152 | #[rustfmt::skip] | ||
| 153 | type SampleFuture<'a> where Self: 'a = impl Future<Output = i16> + 'a; | ||
| 154 | |||
| 155 | fn sample<'a>(self: Pin<&'a mut Self>) -> Self::SampleFuture<'a> { | ||
| 156 | async move { | ||
| 157 | let this = unsafe { self.get_unchecked_mut() }; | ||
| 158 | let r = this.regs(); | ||
| 159 | |||
| 160 | // Set up the DMA | ||
| 161 | let mut val: i16 = 0; | ||
| 162 | r.result | ||
| 163 | .ptr | ||
| 164 | .write(|w| unsafe { w.ptr().bits(((&mut val) as *mut _) as u32) }); | ||
| 165 | r.result.maxcnt.write(|w| unsafe { w.maxcnt().bits(1) }); | ||
| 166 | |||
| 167 | // Reset and enable the end event | ||
| 168 | r.events_end.reset(); | ||
| 169 | r.intenset.write(|w| w.end().set()); | ||
| 170 | |||
| 171 | // Don't reorder the ADC start event before the previous writes. Hopefully this | ||
| 172 | // wouldn't happen anyway. | ||
| 173 | compiler_fence(Ordering::SeqCst); | ||
| 174 | |||
| 175 | r.tasks_start.write(|w| unsafe { w.bits(1) }); | ||
| 176 | r.tasks_sample.write(|w| unsafe { w.bits(1) }); | ||
| 177 | |||
| 178 | // Wait for 'end' event. | ||
| 179 | poll_fn(|cx| { | ||
| 180 | let r = this.regs(); | ||
| 181 | |||
| 182 | if r.events_end.read().bits() != 0 { | ||
| 183 | r.events_end.reset(); | ||
| 184 | return Poll::Ready(()); | ||
| 185 | } | ||
| 186 | |||
| 187 | wake_on_interrupt(&mut this.irq, cx.waker()); | ||
| 188 | |||
| 189 | Poll::Pending | ||
| 190 | }) | ||
| 191 | .await; | ||
| 192 | |||
| 193 | // The DMA wrote the sampled value to `val`. | ||
| 194 | val | ||
| 195 | } | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | /// A pin that can be used as the positive end of a ADC differential in the SAADC periperhal. | ||
| 200 | /// | ||
| 201 | /// Currently negative is always shorted to ground (0V). | ||
| 202 | pub trait PositivePin { | ||
| 203 | fn channel(&self) -> PositiveChannel; | ||
| 204 | } | ||
| 205 | |||
| 206 | macro_rules! positive_pin_mappings { | ||
| 207 | ( $($ch:ident => $pin:ident,)*) => { | ||
| 208 | $( | ||
| 209 | impl PositivePin for crate::peripherals::$pin { | ||
| 210 | fn channel(&self) -> PositiveChannel { | ||
| 211 | PositiveChannel::$ch | ||
| 212 | } | ||
| 213 | } | ||
| 214 | )* | ||
| 215 | }; | ||
| 216 | } | ||
| 217 | |||
| 218 | // TODO the variant names are unchecked | ||
| 219 | // the pins are copied from nrf hal | ||
| 220 | #[cfg(feature = "9160")] | ||
| 221 | positive_pin_mappings! { | ||
| 222 | ANALOGINPUT0 => P0_13, | ||
| 223 | ANALOGINPUT1 => P0_14, | ||
| 224 | ANALOGINPUT2 => P0_15, | ||
| 225 | ANALOGINPUT3 => P0_16, | ||
| 226 | ANALOGINPUT4 => P0_17, | ||
| 227 | ANALOGINPUT5 => P0_18, | ||
| 228 | ANALOGINPUT6 => P0_19, | ||
| 229 | ANALOGINPUT7 => P0_20, | ||
| 230 | } | ||
| 231 | |||
| 232 | #[cfg(not(feature = "9160"))] | ||
| 233 | positive_pin_mappings! { | ||
| 234 | ANALOGINPUT0 => P0_02, | ||
| 235 | ANALOGINPUT1 => P0_03, | ||
| 236 | ANALOGINPUT2 => P0_04, | ||
| 237 | ANALOGINPUT3 => P0_05, | ||
| 238 | ANALOGINPUT4 => P0_28, | ||
| 239 | ANALOGINPUT5 => P0_29, | ||
| 240 | ANALOGINPUT6 => P0_30, | ||
| 241 | ANALOGINPUT7 => P0_31, | ||
| 242 | } | ||
