diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 94744c44..8bf4f727 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -10,6 +10,7 @@ use embassy_hal_common::unborrow; use futures::future::poll_fn; use crate::interrupt; +use crate::ppi::Task; use crate::{pac, peripherals}; use pac::{saadc, SAADC}; @@ -29,7 +30,7 @@ pub use saadc::{ pub enum Error {} /// One-shot saadc. Continuous sample mode TODO. -pub struct OneShot<'d, const N: usize> { +pub struct Saadc<'d, const N: usize> { phantom: PhantomData<&'d mut peripherals::SAADC>, } @@ -50,7 +51,7 @@ impl Default for Config { /// Default configuration for single channel sampling. fn default() -> Self { Self { - resolution: Resolution::_14BIT, + resolution: Resolution::_12BIT, oversample: Oversample::BYPASS, } } @@ -98,7 +99,7 @@ impl<'d> ChannelConfig<'d> { ) -> Self { unborrow!(p_input, n_input); Self { - reference: Reference::VDD1_4, + reference: Reference::INTERNAL, gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, time: Time::_10US, @@ -109,7 +110,17 @@ impl<'d> ChannelConfig<'d> { } } -impl<'d, const N: usize> OneShot<'d, N> { +/// The state of a continuously running sampler. While it reflects +/// the progress of a sampler, it also signals what should be done +/// next. For example, if the sampler has stopped then the Saadc implementation +/// can then tear down its infrastructure. +#[derive(PartialEq)] +pub enum SamplerState { + Sampled, + Stopped, +} + +impl<'d, const N: usize> Saadc<'d, N> { pub fn new( _saadc: impl Unborrow + 'd, irq: impl Unborrow + 'd, @@ -176,12 +187,18 @@ impl<'d, const N: usize> OneShot<'d, N> { r.intenclr.write(|w| w.end().clear()); WAKER.wake(); } + + if r.events_started.read().bits() != 0 { + r.intenclr.write(|w| w.started().clear()); + WAKER.wake(); + } } fn regs() -> &'static saadc::RegisterBlock { unsafe { &*SAADC::ptr() } } + /// One shot sampling. The buffer must be the same size as the number of channels configured. pub async fn sample(&mut self, buf: &mut [i16; N]) { let r = Self::regs(); @@ -219,9 +236,148 @@ impl<'d, const N: usize> OneShot<'d, N> { }) .await; } + + /// Continuous sampling with double buffers. + /// + /// A task-driven approach to driving TASK_SAMPLE is expected. With a task + /// driven approach, multiple channels can be used. + /// + /// A sampler closure is provided that receives the buffer of samples, noting + /// that the size of this buffer can be less than the original buffer's size. + /// A command is return from the closure that indicates whether the sampling + /// should continue or stop. + pub async fn run_task_sampler( + &mut self, + bufs: &mut [[[i16; N]; N0]; 2], + sampler: S, + ) where + S: FnMut(&[[i16; N]]) -> SamplerState, + { + self.run_sampler(bufs, None, sampler).await; + } + + async fn run_sampler( + &mut self, + bufs: &mut [[[i16; N]; N0]; 2], + sample_rate_divisor: Option, + mut sampler: S, + ) where + S: FnMut(&[[i16; N]]) -> SamplerState, + { + let r = Self::regs(); + + // Establish mode and sample rate + match sample_rate_divisor { + Some(sr) => { + r.samplerate.write(|w| unsafe { + w.cc().bits(sr); + w.mode().timers(); + w + }); + r.tasks_sample.write(|w| unsafe { w.bits(1) }); // Need to kick-start the internal timer + } + None => r.samplerate.write(|w| unsafe { + w.cc().bits(0); + w.mode().task(); + w + }), + } + + // Set up the initial DMA + r.result + .ptr + .write(|w| unsafe { w.ptr().bits(bufs[0].as_mut_ptr() as u32) }); + r.result + .maxcnt + .write(|w| unsafe { w.maxcnt().bits((N0 * N) as _) }); + + // Reset and enable the events + r.events_end.reset(); + r.events_started.reset(); + r.intenset.write(|w| { + w.end().set(); + w.started().set(); + w + }); + + // Don't reorder the ADC start event before the previous writes. Hopefully self + // wouldn't happen anyway. + compiler_fence(Ordering::SeqCst); + + r.tasks_start.write(|w| unsafe { w.bits(1) }); + + let mut current_buffer = 0; + + // Wait for events and complete when the sampler indicates it has had enough. + poll_fn(|cx| { + let r = Self::regs(); + + WAKER.register(cx.waker()); + + if r.events_end.read().bits() != 0 { + compiler_fence(Ordering::SeqCst); + + r.events_end.reset(); + r.intenset.write(|w| w.end().set()); + + if sampler(&bufs[current_buffer][0..r.result.amount.read().bits() as usize / N]) + == SamplerState::Sampled + { + let next_buffer = 1 - current_buffer; + current_buffer = next_buffer; + r.tasks_start.write(|w| unsafe { w.bits(1) }); + } else { + return Poll::Ready(()); + }; + } + + if r.events_started.read().bits() != 0 { + r.events_started.reset(); + r.intenset.write(|w| w.started().set()); + + let next_buffer = 1 - current_buffer; + r.result + .ptr + .write(|w| unsafe { w.ptr().bits(bufs[next_buffer].as_mut_ptr() as u32) }); + } + + Poll::Pending + }) + .await; + } + + /// Return the sample task for use with PPI + pub fn task_sample(&self) -> Task { + let r = Self::regs(); + Task::from_reg(&r.tasks_sample) + } } -impl<'d, const N: usize> Drop for OneShot<'d, N> { +impl<'d> Saadc<'d, 1> { + /// Continuous sampling on a single channel with double buffers. + /// + /// The internal clock is to be used with a sample rate expressed as a divisor of + /// 16MHz, ranging from 80..2047. For example, 1600 represnts a sample rate of 10KHz + /// given 16_000_000 / 10_000_000 = 1600. + /// + /// A sampler closure is provided that receives the buffer of samples, noting + /// that the size of this buffer can be less than the original buffer's size. + /// A command is return from the closure that indicates whether the sampling + /// should continue or stop. + pub async fn run_timer_sampler( + &mut self, + bufs: &mut [[[i16; 1]; N0]; 2], + sample_rate_divisor: u16, + sampler: S, + ) where + S: FnMut(&[[i16; 1]]) -> SamplerState, + { + self.run_sampler(bufs, Some(sample_rate_divisor), sampler) + .await; + } +} + +impl<'d, const N: usize> Drop for Saadc<'d, N> { fn drop(&mut self) { let r = Self::regs(); r.enable.write(|w| w.enable().disabled()); diff --git a/examples/nrf/src/bin/saadc.rs b/examples/nrf/src/bin/saadc.rs index d12717c0..c6eac555 100644 --- a/examples/nrf/src/bin/saadc.rs +++ b/examples/nrf/src/bin/saadc.rs @@ -7,7 +7,7 @@ mod example_common; use defmt::panic; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; -use embassy_nrf::saadc::{ChannelConfig, Config, OneShot}; +use embassy_nrf::saadc::{ChannelConfig, Config, Saadc}; use embassy_nrf::{interrupt, Peripherals}; use example_common::*; @@ -15,7 +15,7 @@ use example_common::*; async fn main(_spawner: Spawner, mut p: Peripherals) { let config = Config::default(); let channel_config = ChannelConfig::single_ended(&mut p.P0_02); - let mut saadc = OneShot::new(p.SAADC, interrupt::take!(SAADC), config, [channel_config]); + let mut saadc = Saadc::new(p.SAADC, interrupt::take!(SAADC), config, [channel_config]); loop { let mut buf = [0; 1]; diff --git a/examples/nrf/src/bin/saadc_continuous.rs b/examples/nrf/src/bin/saadc_continuous.rs new file mode 100644 index 00000000..149b9c60 --- /dev/null +++ b/examples/nrf/src/bin/saadc_continuous.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; +use defmt::panic; +use embassy::executor::Spawner; +use embassy_nrf::ppi::Ppi; +use embassy_nrf::saadc::{ChannelConfig, Config, Saadc, SamplerState}; +use embassy_nrf::timer::{Frequency, Timer}; +use embassy_nrf::{interrupt, Peripherals}; +use example_common::*; + +// Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer + +#[embassy::main] +async fn main(_spawner: Spawner, mut p: Peripherals) { + let config = Config::default(); + let channel_1_config = ChannelConfig::single_ended(&mut p.P0_02); + let channel_2_config = ChannelConfig::single_ended(&mut p.P0_03); + let channel_3_config = ChannelConfig::single_ended(&mut p.P0_04); + let mut saadc = Saadc::new( + p.SAADC, + interrupt::take!(SAADC), + config, + [channel_1_config, channel_2_config, channel_3_config], + ); + + let mut timer = Timer::new(p.TIMER0); + timer.set_frequency(Frequency::F1MHz); + timer.cc(0).write(100); // We want to sample at 10KHz + timer.cc(0).short_compare_clear(); + + let mut ppi = Ppi::new(p.PPI_CH0); + ppi.set_event(timer.cc(0).event_compare()); + ppi.set_task(saadc.task_sample()); + ppi.enable(); + + timer.start(); + + let mut bufs = [[[0; 3]; 50]; 2]; + + let mut c = 0; + let mut a: i32 = 0; + + saadc + .run_task_sampler(&mut bufs, move |buf| { + for b in buf { + a += b[0] as i32; + } + c += buf.len(); + if c > 10000 { + a = a / c as i32; + info!("channel 1: {=i32}", a); + c = 0; + a = 0; + } + SamplerState::Sampled + }) + .await; +}