Merge pull request #417 from huntc/extend-saadc

Extend SAADC one shot support
This commit is contained in:
Dario Nieuwenhuis 2021-10-11 00:45:49 +02:00 committed by GitHub
commit 902f566b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 105 additions and 56 deletions

View File

@ -35,6 +35,8 @@ The `embassy-nrf` crate contains implementations for nRF 52 series SoCs.
- `uarte`: UARTE driver implementing `AsyncBufRead` and `AsyncWrite`. - `uarte`: UARTE driver implementing `AsyncBufRead` and `AsyncWrite`.
- `qspi`: QSPI driver implementing `Flash`. - `qspi`: QSPI driver implementing `Flash`.
- `gpiote`: GPIOTE driver. Allows `await`ing GPIO pin changes. Great for reading buttons or receiving interrupts from external chips. - `gpiote`: GPIOTE driver. Allows `await`ing GPIO pin changes. Great for reading buttons or receiving interrupts from external chips.
- `saadc`: SAADC driver. Provides a full implementation of the one-shot sampling for analog channels.
- `rtc`: RTC driver implementing `Clock` and `Alarm`, for use with `embassy::executor`. - `rtc`: RTC driver implementing `Clock` and `Alarm`, for use with `embassy::executor`.
## Examples ## Examples

View File

@ -15,7 +15,7 @@ use pac::{saadc, SAADC};
pub use saadc::{ pub use saadc::{
ch::{ ch::{
config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time}, config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time},
pselp::PSELP_A as PositiveChannel, pselp::PSELP_A as InputChannel, // We treat the positive and negative channels with the same enum values to keep our type tidy and given they are the same
}, },
oversample::OVERSAMPLE_A as Oversample, oversample::OVERSAMPLE_A as Oversample,
resolution::VAL_A as Resolution, resolution::VAL_A as Resolution,
@ -27,7 +27,7 @@ pub use saadc::{
pub enum Error {} pub enum Error {}
/// One-shot saadc. Continuous sample mode TODO. /// One-shot saadc. Continuous sample mode TODO.
pub struct OneShot<'d> { pub struct OneShot<'d, const N: usize> {
phantom: PhantomData<&'d mut peripherals::SAADC>, phantom: PhantomData<&'d mut peripherals::SAADC>,
} }
@ -36,11 +36,29 @@ static WAKER: AtomicWaker = AtomicWaker::new();
/// Used to configure the SAADC peripheral. /// Used to configure the SAADC peripheral.
/// ///
/// See the `Default` impl for suitable default values. /// See the `Default` impl for suitable default values.
#[non_exhaustive]
pub struct Config { pub struct Config {
/// Output resolution in bits. /// Output resolution in bits.
pub resolution: Resolution, pub resolution: Resolution,
/// Average 2^`oversample` input samples before transferring the result into memory. /// Average 2^`oversample` input samples before transferring the result into memory.
pub oversample: Oversample, pub oversample: Oversample,
}
impl Default for Config {
/// Default configuration for single channel sampling.
fn default() -> Self {
Self {
resolution: Resolution::_14BIT,
oversample: Oversample::BYPASS,
}
}
}
/// Used to configure an individual SAADC peripheral channel.
///
/// See the `Default` impl for suitable default values.
#[non_exhaustive]
pub struct ChannelConfig<'d> {
/// Reference voltage of the SAADC input. /// Reference voltage of the SAADC input.
pub reference: Reference, pub reference: Reference,
/// Gain used to control the effective input range of the SAADC. /// Gain used to control the effective input range of the SAADC.
@ -49,26 +67,52 @@ pub struct Config {
pub resistor: Resistor, pub resistor: Resistor,
/// Acquisition time in microseconds. /// Acquisition time in microseconds.
pub time: Time, pub time: Time,
/// Positive channel to sample
p_channel: InputChannel,
/// An optional negative channel to sample
n_channel: Option<InputChannel>,
phantom: PhantomData<&'d ()>,
} }
impl Default for Config { impl<'d> ChannelConfig<'d> {
fn default() -> Self { /// Default configuration for single ended channel sampling.
pub fn single_ended(input: impl Unborrow<Target = impl Input> + 'd) -> Self {
unborrow!(input);
Self { Self {
resolution: Resolution::_14BIT, reference: Reference::INTERNAL,
oversample: Oversample::OVER8X, gain: Gain::GAIN1_6,
reference: Reference::VDD1_4,
gain: Gain::GAIN1_4,
resistor: Resistor::BYPASS, resistor: Resistor::BYPASS,
time: Time::_20US, time: Time::_10US,
p_channel: input.channel(),
n_channel: None,
phantom: PhantomData,
}
}
/// Default configuration for differential channel sampling.
pub fn differential(
p_input: impl Unborrow<Target = impl Input> + 'd,
n_input: impl Unborrow<Target = impl Input> + 'd,
) -> Self {
unborrow!(p_input, n_input);
Self {
reference: Reference::VDD1_4,
gain: Gain::GAIN1_6,
resistor: Resistor::BYPASS,
time: Time::_10US,
p_channel: p_input.channel(),
n_channel: Some(n_input.channel()),
phantom: PhantomData,
} }
} }
} }
impl<'d> OneShot<'d> { impl<'d, const N: usize> OneShot<'d, N> {
pub fn new( pub fn new(
_saadc: impl Unborrow<Target = peripherals::SAADC> + 'd, _saadc: impl Unborrow<Target = peripherals::SAADC> + 'd,
irq: impl Unborrow<Target = interrupt::SAADC> + 'd, irq: impl Unborrow<Target = interrupt::SAADC> + 'd,
config: Config, config: Config,
channel_configs: [ChannelConfig; N],
) -> Self { ) -> Self {
unborrow!(irq); unborrow!(irq);
@ -77,23 +121,30 @@ impl<'d> OneShot<'d> {
let Config { let Config {
resolution, resolution,
oversample, oversample,
reference,
gain,
resistor,
time,
} = config; } = config;
// Configure pins // Configure channels
r.enable.write(|w| w.enable().enabled()); r.enable.write(|w| w.enable().enabled());
r.resolution.write(|w| w.val().variant(resolution)); r.resolution.write(|w| w.val().variant(resolution));
r.oversample.write(|w| w.oversample().variant(oversample)); r.oversample.write(|w| w.oversample().variant(oversample));
r.ch[0].config.write(|w| { for (i, cc) in channel_configs.iter().enumerate() {
w.refsel().variant(reference); r.ch[i].pselp.write(|w| w.pselp().variant(cc.p_channel));
w.gain().variant(gain); if let Some(n_channel) = cc.n_channel {
w.tacq().variant(time); r.ch[i]
.pseln
.write(|w| unsafe { w.pseln().bits(n_channel as u8) });
}
r.ch[i].config.write(|w| {
w.refsel().variant(cc.reference);
w.gain().variant(cc.gain);
w.tacq().variant(cc.time);
if cc.n_channel.is_none() {
w.mode().se(); w.mode().se();
w.resp().variant(resistor); } else {
w.mode().diff();
}
w.resp().variant(cc.resistor);
w.resn().bypass(); w.resn().bypass();
if !matches!(oversample, Oversample::BYPASS) { if !matches!(oversample, Oversample::BYPASS) {
w.burst().enabled(); w.burst().enabled();
@ -102,6 +153,7 @@ impl<'d> OneShot<'d> {
} }
w w
}); });
}
// Disable all events interrupts // Disable all events interrupts
r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) });
@ -128,18 +180,16 @@ impl<'d> OneShot<'d> {
unsafe { &*SAADC::ptr() } unsafe { &*SAADC::ptr() }
} }
pub async fn sample(&mut self, pin: &mut impl PositivePin) -> i16 { pub async fn sample(&mut self, buf: &mut [i16; N]) {
let r = Self::regs(); let r = Self::regs();
// Set positive channel
r.ch[0].pselp.write(|w| w.pselp().variant(pin.channel()));
// Set up the DMA // Set up the DMA
let mut val: i16 = 0;
r.result r.result
.ptr .ptr
.write(|w| unsafe { w.ptr().bits(((&mut val) as *mut _) as u32) }); .write(|w| unsafe { w.ptr().bits(buf.as_mut_ptr() as u32) });
r.result.maxcnt.write(|w| unsafe { w.maxcnt().bits(1) }); r.result
.maxcnt
.write(|w| unsafe { w.maxcnt().bits(N as _) });
// Reset and enable the end event // Reset and enable the end event
r.events_end.reset(); r.events_end.reset();
@ -166,32 +216,27 @@ impl<'d> OneShot<'d> {
Poll::Pending Poll::Pending
}) })
.await; .await;
// The DMA wrote the sampled value to `val`.
val
} }
} }
impl<'d> Drop for OneShot<'d> { impl<'d, const N: usize> Drop for OneShot<'d, N> {
fn drop(&mut self) { fn drop(&mut self) {
let r = Self::regs(); let r = Self::regs();
r.enable.write(|w| w.enable().disabled()); r.enable.write(|w| w.enable().disabled());
} }
} }
/// A pin that can be used as the positive end of a ADC differential in the SAADC periperhal. /// An input that can be used as either or negative end of a ADC differential in the SAADC periperhal.
/// pub trait Input {
/// Currently negative is always shorted to ground (0V). fn channel(&self) -> InputChannel;
pub trait PositivePin {
fn channel(&self) -> PositiveChannel;
} }
macro_rules! positive_pin_mappings { macro_rules! input_mappings {
( $($ch:ident => $pin:ident,)*) => { ( $($ch:ident => $input:ident,)*) => {
$( $(
impl PositivePin for crate::peripherals::$pin { impl Input for crate::peripherals::$input {
fn channel(&self) -> PositiveChannel { fn channel(&self) -> InputChannel {
PositiveChannel::$ch InputChannel::$ch
} }
} }
)* )*
@ -199,9 +244,9 @@ macro_rules! positive_pin_mappings {
} }
// TODO the variant names are unchecked // TODO the variant names are unchecked
// the pins are copied from nrf hal // the inputs are copied from nrf hal
#[cfg(feature = "9160")] #[cfg(feature = "9160")]
positive_pin_mappings! { input_mappings! {
ANALOGINPUT0 => P0_13, ANALOGINPUT0 => P0_13,
ANALOGINPUT1 => P0_14, ANALOGINPUT1 => P0_14,
ANALOGINPUT2 => P0_15, ANALOGINPUT2 => P0_15,
@ -213,7 +258,7 @@ positive_pin_mappings! {
} }
#[cfg(not(feature = "9160"))] #[cfg(not(feature = "9160"))]
positive_pin_mappings! { input_mappings! {
ANALOGINPUT0 => P0_02, ANALOGINPUT0 => P0_02,
ANALOGINPUT1 => P0_03, ANALOGINPUT1 => P0_03,
ANALOGINPUT2 => P0_04, ANALOGINPUT2 => P0_04,

View File

@ -7,18 +7,20 @@ mod example_common;
use defmt::panic; use defmt::panic;
use embassy::executor::Spawner; use embassy::executor::Spawner;
use embassy::time::{Duration, Timer}; use embassy::time::{Duration, Timer};
use embassy_nrf::saadc::{Config, OneShot}; use embassy_nrf::saadc::{ChannelConfig, Config, OneShot};
use embassy_nrf::{interrupt, Peripherals}; use embassy_nrf::{interrupt, Peripherals};
use example_common::*; use example_common::*;
#[embassy::main] #[embassy::main]
async fn main(_spawner: Spawner, mut p: Peripherals) { async fn main(_spawner: Spawner, mut p: Peripherals) {
let config = Config::default(); let config = Config::default();
let mut saadc = OneShot::new(p.SAADC, interrupt::take!(SAADC), config); let channel_config = ChannelConfig::single_ended(&mut p.P0_02);
let mut saadc = OneShot::new(p.SAADC, interrupt::take!(SAADC), config, [channel_config]);
loop { loop {
let sample = saadc.sample(&mut p.P0_02).await; let mut buf = [0; 1];
info!("sample: {=i16}", sample); saadc.sample(&mut buf).await;
info!("sample: {=i16}", &buf[0]);
Timer::after(Duration::from_millis(100)).await; Timer::after(Duration::from_millis(100)).await;
} }
} }