diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | embassy-nrf/src/saadc.rs | 175 | ||||
| -rw-r--r-- | examples/nrf/src/bin/saadc.rs | 10 |
3 files changed, 144 insertions, 43 deletions
| @@ -35,6 +35,8 @@ The `embassy-nrf` crate contains implementations for nRF 52 series SoCs. | |||
| 35 | - `uarte`: UARTE driver implementing `AsyncBufRead` and `AsyncWrite`. | 35 | - `uarte`: UARTE driver implementing `AsyncBufRead` and `AsyncWrite`. |
| 36 | - `qspi`: QSPI driver implementing `Flash`. | 36 | - `qspi`: QSPI driver implementing `Flash`. |
| 37 | - `gpiote`: GPIOTE driver. Allows `await`ing GPIO pin changes. Great for reading buttons or receiving interrupts from external chips. | 37 | - `gpiote`: GPIOTE driver. Allows `await`ing GPIO pin changes. Great for reading buttons or receiving interrupts from external chips. |
| 38 | - `saadc`: SAADC driver. Provides a full implementation of the one-shot sampling for analog channels. | ||
| 39 | |||
| 38 | - `rtc`: RTC driver implementing `Clock` and `Alarm`, for use with `embassy::executor`. | 40 | - `rtc`: RTC driver implementing `Clock` and `Alarm`, for use with `embassy::executor`. |
| 39 | 41 | ||
| 40 | ## Examples | 42 | ## Examples |
diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index b6e8f4e44..12c302d58 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | use core::convert::TryInto; | ||
| 1 | use core::marker::PhantomData; | 2 | use core::marker::PhantomData; |
| 2 | use core::sync::atomic::{compiler_fence, Ordering}; | 3 | use core::sync::atomic::{compiler_fence, Ordering}; |
| 3 | use core::task::Poll; | 4 | use core::task::Poll; |
| @@ -15,6 +16,7 @@ use pac::{saadc, SAADC}; | |||
| 15 | pub use saadc::{ | 16 | pub use saadc::{ |
| 16 | ch::{ | 17 | ch::{ |
| 17 | config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time}, | 18 | config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time}, |
| 19 | pseln::PSELN_A as NegativeChannel, | ||
| 18 | pselp::PSELP_A as PositiveChannel, | 20 | pselp::PSELP_A as PositiveChannel, |
| 19 | }, | 21 | }, |
| 20 | oversample::OVERSAMPLE_A as Oversample, | 22 | oversample::OVERSAMPLE_A as Oversample, |
| @@ -27,7 +29,7 @@ pub use saadc::{ | |||
| 27 | pub enum Error {} | 29 | pub enum Error {} |
| 28 | 30 | ||
| 29 | /// One-shot saadc. Continuous sample mode TODO. | 31 | /// One-shot saadc. Continuous sample mode TODO. |
| 30 | pub struct OneShot<'d> { | 32 | pub struct OneShot<'d, const N: usize> { |
| 31 | phantom: PhantomData<&'d mut peripherals::SAADC>, | 33 | phantom: PhantomData<&'d mut peripherals::SAADC>, |
| 32 | } | 34 | } |
| 33 | 35 | ||
| @@ -36,11 +38,29 @@ static WAKER: AtomicWaker = AtomicWaker::new(); | |||
| 36 | /// Used to configure the SAADC peripheral. | 38 | /// Used to configure the SAADC peripheral. |
| 37 | /// | 39 | /// |
| 38 | /// See the `Default` impl for suitable default values. | 40 | /// See the `Default` impl for suitable default values. |
| 41 | #[non_exhaustive] | ||
| 39 | pub struct Config { | 42 | pub struct Config { |
| 40 | /// Output resolution in bits. | 43 | /// Output resolution in bits. |
| 41 | pub resolution: Resolution, | 44 | pub resolution: Resolution, |
| 42 | /// Average 2^`oversample` input samples before transferring the result into memory. | 45 | /// Average 2^`oversample` input samples before transferring the result into memory. |
| 43 | pub oversample: Oversample, | 46 | pub oversample: Oversample, |
| 47 | } | ||
| 48 | |||
| 49 | impl Default for Config { | ||
| 50 | /// Default configuration for single channel sampling. | ||
| 51 | fn default() -> Self { | ||
| 52 | Self { | ||
| 53 | resolution: Resolution::_14BIT, | ||
| 54 | oversample: Oversample::BYPASS, | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Used to configure an individual SAADC peripheral channel. | ||
| 60 | /// | ||
| 61 | /// See the `Default` impl for suitable default values. | ||
| 62 | #[non_exhaustive] | ||
| 63 | pub struct ChannelConfig { | ||
| 44 | /// Reference voltage of the SAADC input. | 64 | /// Reference voltage of the SAADC input. |
| 45 | pub reference: Reference, | 65 | pub reference: Reference, |
| 46 | /// Gain used to control the effective input range of the SAADC. | 66 | /// Gain used to control the effective input range of the SAADC. |
| @@ -49,26 +69,48 @@ pub struct Config { | |||
| 49 | pub resistor: Resistor, | 69 | pub resistor: Resistor, |
| 50 | /// Acquisition time in microseconds. | 70 | /// Acquisition time in microseconds. |
| 51 | pub time: Time, | 71 | pub time: Time, |
| 72 | /// Positive channel to sample | ||
| 73 | p_channel: PositiveChannel, | ||
| 74 | /// An optional negative channel to sample | ||
| 75 | n_channel: Option<NegativeChannel>, | ||
| 52 | } | 76 | } |
| 53 | 77 | ||
| 54 | impl Default for Config { | 78 | impl ChannelConfig { |
| 55 | fn default() -> Self { | 79 | /// Default configuration for single ended channel sampling. |
| 80 | pub fn single_ended(pin: impl Unborrow<Target = impl PositivePin>) -> Self { | ||
| 81 | unborrow!(pin); | ||
| 82 | Self { | ||
| 83 | reference: Reference::INTERNAL, | ||
| 84 | gain: Gain::GAIN1_6, | ||
| 85 | resistor: Resistor::BYPASS, | ||
| 86 | time: Time::_10US, | ||
| 87 | p_channel: pin.channel(), | ||
| 88 | n_channel: None, | ||
| 89 | } | ||
| 90 | } | ||
| 91 | /// Default configuration for differential channel sampling. | ||
| 92 | pub fn differential( | ||
| 93 | ppin: impl Unborrow<Target = impl PositivePin>, | ||
| 94 | npin: impl Unborrow<Target = impl NegativePin>, | ||
| 95 | ) -> Self { | ||
| 96 | unborrow!(ppin, npin); | ||
| 56 | Self { | 97 | Self { |
| 57 | resolution: Resolution::_14BIT, | ||
| 58 | oversample: Oversample::OVER8X, | ||
| 59 | reference: Reference::VDD1_4, | 98 | reference: Reference::VDD1_4, |
| 60 | gain: Gain::GAIN1_4, | 99 | gain: Gain::GAIN1_6, |
| 61 | resistor: Resistor::BYPASS, | 100 | resistor: Resistor::BYPASS, |
| 62 | time: Time::_20US, | 101 | time: Time::_10US, |
| 102 | p_channel: ppin.channel(), | ||
| 103 | n_channel: Some(npin.channel()), | ||
| 63 | } | 104 | } |
| 64 | } | 105 | } |
| 65 | } | 106 | } |
| 66 | 107 | ||
| 67 | impl<'d> OneShot<'d> { | 108 | impl<'d, const N: usize> OneShot<'d, N> { |
| 68 | pub fn new( | 109 | pub fn new( |
| 69 | _saadc: impl Unborrow<Target = peripherals::SAADC> + 'd, | 110 | _saadc: impl Unborrow<Target = peripherals::SAADC> + 'd, |
| 70 | irq: impl Unborrow<Target = interrupt::SAADC> + 'd, | 111 | irq: impl Unborrow<Target = interrupt::SAADC> + 'd, |
| 71 | config: Config, | 112 | config: Config, |
| 113 | channel_configs: [ChannelConfig; N], | ||
| 72 | ) -> Self { | 114 | ) -> Self { |
| 73 | unborrow!(irq); | 115 | unborrow!(irq); |
| 74 | 116 | ||
| @@ -77,31 +119,37 @@ impl<'d> OneShot<'d> { | |||
| 77 | let Config { | 119 | let Config { |
| 78 | resolution, | 120 | resolution, |
| 79 | oversample, | 121 | oversample, |
| 80 | reference, | ||
| 81 | gain, | ||
| 82 | resistor, | ||
| 83 | time, | ||
| 84 | } = config; | 122 | } = config; |
| 85 | 123 | ||
| 86 | // Configure pins | 124 | // Configure channels |
| 87 | r.enable.write(|w| w.enable().enabled()); | 125 | r.enable.write(|w| w.enable().enabled()); |
| 88 | r.resolution.write(|w| w.val().variant(resolution)); | 126 | r.resolution.write(|w| w.val().variant(resolution)); |
| 89 | r.oversample.write(|w| w.oversample().variant(oversample)); | 127 | r.oversample.write(|w| w.oversample().variant(oversample)); |
| 90 | 128 | ||
| 91 | r.ch[0].config.write(|w| { | 129 | for (i, cc) in channel_configs.iter().enumerate() { |
| 92 | w.refsel().variant(reference); | 130 | r.ch[i].pselp.write(|w| w.pselp().variant(cc.p_channel)); |
| 93 | w.gain().variant(gain); | 131 | if let Some(npin) = cc.n_channel.as_ref() { |
| 94 | w.tacq().variant(time); | 132 | r.ch[i].pseln.write(|w| w.pseln().variant(*npin)); |
| 95 | w.mode().se(); | ||
| 96 | w.resp().variant(resistor); | ||
| 97 | w.resn().bypass(); | ||
| 98 | if !matches!(oversample, Oversample::BYPASS) { | ||
| 99 | w.burst().enabled(); | ||
| 100 | } else { | ||
| 101 | w.burst().disabled(); | ||
| 102 | } | 133 | } |
| 103 | w | 134 | r.ch[i].config.write(|w| { |
| 104 | }); | 135 | w.refsel().variant(cc.reference); |
| 136 | w.gain().variant(cc.gain); | ||
| 137 | w.tacq().variant(cc.time); | ||
| 138 | if cc.n_channel.is_none() { | ||
| 139 | w.mode().se(); | ||
| 140 | } else { | ||
| 141 | w.mode().diff(); | ||
| 142 | } | ||
| 143 | w.resp().variant(cc.resistor); | ||
| 144 | w.resn().bypass(); | ||
| 145 | if !matches!(oversample, Oversample::BYPASS) { | ||
| 146 | w.burst().enabled(); | ||
| 147 | } else { | ||
| 148 | w.burst().disabled(); | ||
| 149 | } | ||
| 150 | w | ||
| 151 | }); | ||
| 152 | } | ||
| 105 | 153 | ||
| 106 | // Disable all events interrupts | 154 | // Disable all events interrupts |
| 107 | r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); | 155 | r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); |
| @@ -128,18 +176,16 @@ impl<'d> OneShot<'d> { | |||
| 128 | unsafe { &*SAADC::ptr() } | 176 | unsafe { &*SAADC::ptr() } |
| 129 | } | 177 | } |
| 130 | 178 | ||
| 131 | pub async fn sample(&mut self, pin: &mut impl PositivePin) -> i16 { | 179 | pub async fn sample(&mut self, buf: &mut [i16; N]) { |
| 132 | let r = Self::regs(); | 180 | let r = Self::regs(); |
| 133 | 181 | ||
| 134 | // Set positive channel | ||
| 135 | r.ch[0].pselp.write(|w| w.pselp().variant(pin.channel())); | ||
| 136 | |||
| 137 | // Set up the DMA | 182 | // Set up the DMA |
| 138 | let mut val: i16 = 0; | ||
| 139 | r.result | 183 | r.result |
| 140 | .ptr | 184 | .ptr |
| 141 | .write(|w| unsafe { w.ptr().bits(((&mut val) as *mut _) as u32) }); | 185 | .write(|w| unsafe { w.ptr().bits(buf.as_mut_ptr() as u32) }); |
| 142 | r.result.maxcnt.write(|w| unsafe { w.maxcnt().bits(1) }); | 186 | r.result |
| 187 | .maxcnt | ||
| 188 | .write(|w| unsafe { w.maxcnt().bits(N.try_into().unwrap()) }); | ||
| 143 | 189 | ||
| 144 | // Reset and enable the end event | 190 | // Reset and enable the end event |
| 145 | r.events_end.reset(); | 191 | r.events_end.reset(); |
| @@ -166,13 +212,10 @@ impl<'d> OneShot<'d> { | |||
| 166 | Poll::Pending | 212 | Poll::Pending |
| 167 | }) | 213 | }) |
| 168 | .await; | 214 | .await; |
| 169 | |||
| 170 | // The DMA wrote the sampled value to `val`. | ||
| 171 | val | ||
| 172 | } | 215 | } |
| 173 | } | 216 | } |
| 174 | 217 | ||
| 175 | impl<'d> Drop for OneShot<'d> { | 218 | impl<'d, const N: usize> Drop for OneShot<'d, N> { |
| 176 | fn drop(&mut self) { | 219 | fn drop(&mut self) { |
| 177 | let r = Self::regs(); | 220 | let r = Self::regs(); |
| 178 | r.enable.write(|w| w.enable().disabled()); | 221 | r.enable.write(|w| w.enable().disabled()); |
| @@ -180,8 +223,6 @@ impl<'d> Drop for OneShot<'d> { | |||
| 180 | } | 223 | } |
| 181 | 224 | ||
| 182 | /// A pin that can be used as the positive end of a ADC differential in the SAADC periperhal. | 225 | /// A pin that can be used as the positive end of a ADC differential in the SAADC periperhal. |
| 183 | /// | ||
| 184 | /// Currently negative is always shorted to ground (0V). | ||
| 185 | pub trait PositivePin { | 226 | pub trait PositivePin { |
| 186 | fn channel(&self) -> PositiveChannel; | 227 | fn channel(&self) -> PositiveChannel; |
| 187 | } | 228 | } |
| @@ -198,6 +239,38 @@ macro_rules! positive_pin_mappings { | |||
| 198 | }; | 239 | }; |
| 199 | } | 240 | } |
| 200 | 241 | ||
| 242 | /// A pin that can be used as the negative end of a ADC differential in the SAADC periperhal. | ||
| 243 | pub trait NegativePin { | ||
| 244 | fn channel(&self) -> NegativeChannel; | ||
| 245 | } | ||
| 246 | |||
| 247 | macro_rules! negative_pin_mappings { | ||
| 248 | ( $($ch:ident => $pin:ident,)*) => { | ||
| 249 | $( | ||
| 250 | impl NegativePin for crate::peripherals::$pin { | ||
| 251 | fn channel(&self) -> NegativeChannel { | ||
| 252 | NegativeChannel::$ch | ||
| 253 | } | ||
| 254 | } | ||
| 255 | )* | ||
| 256 | }; | ||
| 257 | } | ||
| 258 | |||
| 259 | /// Represents an unconnected pin | ||
| 260 | pub struct NotConnected {} | ||
| 261 | |||
| 262 | impl PositivePin for NotConnected { | ||
| 263 | fn channel(&self) -> PositiveChannel { | ||
| 264 | PositiveChannel::NC | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | impl NegativePin for NotConnected { | ||
| 269 | fn channel(&self) -> NegativeChannel { | ||
| 270 | NegativeChannel::NC | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 201 | // TODO the variant names are unchecked | 274 | // TODO the variant names are unchecked |
| 202 | // the pins are copied from nrf hal | 275 | // the pins are copied from nrf hal |
| 203 | #[cfg(feature = "9160")] | 276 | #[cfg(feature = "9160")] |
| @@ -223,3 +296,27 @@ positive_pin_mappings! { | |||
| 223 | ANALOGINPUT6 => P0_30, | 296 | ANALOGINPUT6 => P0_30, |
| 224 | ANALOGINPUT7 => P0_31, | 297 | ANALOGINPUT7 => P0_31, |
| 225 | } | 298 | } |
| 299 | |||
| 300 | #[cfg(feature = "9160")] | ||
| 301 | negative_pin_mappings! { | ||
| 302 | ANALOGINPUT0 => P0_13, | ||
| 303 | ANALOGINPUT1 => P0_14, | ||
| 304 | ANALOGINPUT2 => P0_15, | ||
| 305 | ANALOGINPUT3 => P0_16, | ||
| 306 | ANALOGINPUT4 => P0_17, | ||
| 307 | ANALOGINPUT5 => P0_18, | ||
| 308 | ANALOGINPUT6 => P0_19, | ||
| 309 | ANALOGINPUT7 => P0_20, | ||
| 310 | } | ||
| 311 | |||
| 312 | #[cfg(not(feature = "9160"))] | ||
| 313 | negative_pin_mappings! { | ||
| 314 | ANALOGINPUT0 => P0_02, | ||
| 315 | ANALOGINPUT1 => P0_03, | ||
| 316 | ANALOGINPUT2 => P0_04, | ||
| 317 | ANALOGINPUT3 => P0_05, | ||
| 318 | ANALOGINPUT4 => P0_28, | ||
| 319 | ANALOGINPUT5 => P0_29, | ||
| 320 | ANALOGINPUT6 => P0_30, | ||
| 321 | ANALOGINPUT7 => P0_31, | ||
| 322 | } | ||
diff --git a/examples/nrf/src/bin/saadc.rs b/examples/nrf/src/bin/saadc.rs index c4d23360e..d12717c04 100644 --- a/examples/nrf/src/bin/saadc.rs +++ b/examples/nrf/src/bin/saadc.rs | |||
| @@ -7,18 +7,20 @@ mod example_common; | |||
| 7 | use defmt::panic; | 7 | use defmt::panic; |
| 8 | use embassy::executor::Spawner; | 8 | use embassy::executor::Spawner; |
| 9 | use embassy::time::{Duration, Timer}; | 9 | use embassy::time::{Duration, Timer}; |
| 10 | use embassy_nrf::saadc::{Config, OneShot}; | 10 | use embassy_nrf::saadc::{ChannelConfig, Config, OneShot}; |
| 11 | use embassy_nrf::{interrupt, Peripherals}; | 11 | use embassy_nrf::{interrupt, Peripherals}; |
| 12 | use example_common::*; | 12 | use example_common::*; |
| 13 | 13 | ||
| 14 | #[embassy::main] | 14 | #[embassy::main] |
| 15 | async fn main(_spawner: Spawner, mut p: Peripherals) { | 15 | async fn main(_spawner: Spawner, mut p: Peripherals) { |
| 16 | let config = Config::default(); | 16 | let config = Config::default(); |
| 17 | let mut saadc = OneShot::new(p.SAADC, interrupt::take!(SAADC), config); | 17 | let channel_config = ChannelConfig::single_ended(&mut p.P0_02); |
| 18 | let mut saadc = OneShot::new(p.SAADC, interrupt::take!(SAADC), config, [channel_config]); | ||
| 18 | 19 | ||
| 19 | loop { | 20 | loop { |
| 20 | let sample = saadc.sample(&mut p.P0_02).await; | 21 | let mut buf = [0; 1]; |
| 21 | info!("sample: {=i16}", sample); | 22 | saadc.sample(&mut buf).await; |
| 23 | info!("sample: {=i16}", &buf[0]); | ||
| 22 | Timer::after(Duration::from_millis(100)).await; | 24 | Timer::after(Duration::from_millis(100)).await; |
| 23 | } | 25 | } |
| 24 | } | 26 | } |
