Merge pull request #417 from huntc/extend-saadc
Extend SAADC one shot support
This commit is contained in:
commit
902f566b9a
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user