aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhuntc <[email protected]>2022-02-26 18:15:37 +1100
committerhuntc <[email protected]>2022-03-07 14:51:17 +1100
commit98bdac51fe24c48ba097fcba3ec705f9da7df783 (patch)
treec259432d7cf8ba49ef5c53bf62c25fd4e6f7a2c2
parent9735c38592ab6749d832f027fdc3070f97dc57cf (diff)
Improve nRF Saadc sampling
Starting the sampling task prior to starting the SAADC peripheral can lead to unexpected buffer behaviour with multiple channels. We now provide an init callback at the point where the SAADC has started for the first time. This callback can be used to kick off sampling via PPI. We also need to trigger the SAADC to start sampling the next buffer when the previous one is ended so that we do not drop samples - the major benefit of double buffering. As a bonus we provide a calibrate method as it is recommended to use before starting up the sampling. The example has been updated to illustrate these new features.
-rw-r--r--embassy-nrf/src/saadc.rs83
-rw-r--r--examples/nrf/src/bin/saadc_continuous.rs57
2 files changed, 116 insertions, 24 deletions
diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs
index 617c9e041..dcc7e86a2 100644
--- a/embassy-nrf/src/saadc.rs
+++ b/embassy-nrf/src/saadc.rs
@@ -10,7 +10,7 @@ use embassy_hal_common::unborrow;
10use futures::future::poll_fn; 10use futures::future::poll_fn;
11 11
12use crate::interrupt; 12use crate::interrupt;
13use crate::ppi::Task; 13use crate::ppi::{Event, Task};
14use crate::{pac, peripherals}; 14use crate::{pac, peripherals};
15 15
16use pac::{saadc, SAADC}; 16use pac::{saadc, SAADC};
@@ -207,6 +207,11 @@ impl<'d, const N: usize> Saadc<'d, N> {
207 fn on_interrupt(_ctx: *mut ()) { 207 fn on_interrupt(_ctx: *mut ()) {
208 let r = Self::regs(); 208 let r = Self::regs();
209 209
210 if r.events_calibratedone.read().bits() != 0 {
211 r.intenclr.write(|w| w.calibratedone().clear());
212 WAKER.wake();
213 }
214
210 if r.events_end.read().bits() != 0 { 215 if r.events_end.read().bits() != 0 {
211 r.intenclr.write(|w| w.end().clear()); 216 r.intenclr.write(|w| w.end().clear());
212 WAKER.wake(); 217 WAKER.wake();
@@ -222,6 +227,35 @@ impl<'d, const N: usize> Saadc<'d, N> {
222 unsafe { &*SAADC::ptr() } 227 unsafe { &*SAADC::ptr() }
223 } 228 }
224 229
230 /// Perform SAADC calibration. Completes when done.
231 pub async fn calibrate(&self) {
232 let r = Self::regs();
233
234 // Reset and enable the end event
235 r.events_calibratedone.reset();
236 r.intenset.write(|w| w.calibratedone().set());
237
238 // Order is important
239 compiler_fence(Ordering::SeqCst);
240
241 r.tasks_calibrateoffset.write(|w| unsafe { w.bits(1) });
242
243 // Wait for 'calibratedone' event.
244 poll_fn(|cx| {
245 let r = Self::regs();
246
247 WAKER.register(cx.waker());
248
249 if r.events_calibratedone.read().bits() != 0 {
250 r.events_calibratedone.reset();
251 return Poll::Ready(());
252 }
253
254 Poll::Pending
255 })
256 .await;
257 }
258
225 /// One shot sampling. The buffer must be the same size as the number of channels configured. 259 /// One shot sampling. The buffer must be the same size as the number of channels configured.
226 pub async fn sample(&mut self, buf: &mut [i16; N]) { 260 pub async fn sample(&mut self, buf: &mut [i16; N]) {
227 let r = Self::regs(); 261 let r = Self::regs();
@@ -263,29 +297,46 @@ impl<'d, const N: usize> Saadc<'d, N> {
263 297
264 /// Continuous sampling with double buffers. 298 /// Continuous sampling with double buffers.
265 /// 299 ///
300 /// NOTE: It is important that the time spent within the callback supplied
301 /// does not exceed the time taken to acquire the samples into a single buffer.
302 /// You should measure the time taken by the callback and set the sample buffer
303 /// size accordingly. Exceeding this time can lead to the peripheral re-writing
304 /// the other buffer.
305 ///
266 /// A task-driven approach to driving TASK_SAMPLE is expected. With a task 306 /// A task-driven approach to driving TASK_SAMPLE is expected. With a task
267 /// driven approach, multiple channels can be used. 307 /// driven approach, multiple channels can be used.
268 /// 308 ///
309 /// In addition, the caller is responsible for triggering TASK_START in
310 /// relation to the previous one having ended (EVENTS_END). The the initial
311 /// TASKS_START is triggered by this method.
312 ///
313 /// A closure is provided so that any required initialization such as starting
314 /// the sampling task can occur once the peripheral has been started.
315 ///
269 /// A sampler closure is provided that receives the buffer of samples, noting 316 /// A sampler closure is provided that receives the buffer of samples, noting
270 /// that the size of this buffer can be less than the original buffer's size. 317 /// that the size of this buffer can be less than the original buffer's size.
271 /// A command is return from the closure that indicates whether the sampling 318 /// A command is return from the closure that indicates whether the sampling
272 /// should continue or stop. 319 /// should continue or stop.
273 pub async fn run_task_sampler<S, const N0: usize>( 320 pub async fn run_task_sampler<I, S, const N0: usize>(
274 &mut self, 321 &mut self,
275 bufs: &mut [[[i16; N]; N0]; 2], 322 bufs: &mut [[[i16; N]; N0]; 2],
323 init: I,
276 sampler: S, 324 sampler: S,
277 ) where 325 ) where
326 I: FnMut(),
278 S: FnMut(&[[i16; N]]) -> SamplerState, 327 S: FnMut(&[[i16; N]]) -> SamplerState,
279 { 328 {
280 self.run_sampler(bufs, None, sampler).await; 329 self.run_sampler(bufs, None, init, sampler).await;
281 } 330 }
282 331
283 async fn run_sampler<S, const N0: usize>( 332 async fn run_sampler<I, S, const N0: usize>(
284 &mut self, 333 &mut self,
285 bufs: &mut [[[i16; N]; N0]; 2], 334 bufs: &mut [[[i16; N]; N0]; 2],
286 sample_rate_divisor: Option<u16>, 335 sample_rate_divisor: Option<u16>,
336 mut init: I,
287 mut sampler: S, 337 mut sampler: S,
288 ) where 338 ) where
339 I: FnMut(),
289 S: FnMut(&[[i16; N]]) -> SamplerState, 340 S: FnMut(&[[i16; N]]) -> SamplerState,
290 { 341 {
291 let r = Self::regs(); 342 let r = Self::regs();
@@ -330,6 +381,8 @@ impl<'d, const N: usize> Saadc<'d, N> {
330 381
331 r.tasks_start.write(|w| unsafe { w.bits(1) }); 382 r.tasks_start.write(|w| unsafe { w.bits(1) });
332 383
384 let mut inited = false;
385
333 let mut current_buffer = 0; 386 let mut current_buffer = 0;
334 387
335 // Wait for events and complete when the sampler indicates it has had enough. 388 // Wait for events and complete when the sampler indicates it has had enough.
@@ -347,7 +400,6 @@ impl<'d, const N: usize> Saadc<'d, N> {
347 if sampler(&bufs[current_buffer]) == SamplerState::Sampled { 400 if sampler(&bufs[current_buffer]) == SamplerState::Sampled {
348 let next_buffer = 1 - current_buffer; 401 let next_buffer = 1 - current_buffer;
349 current_buffer = next_buffer; 402 current_buffer = next_buffer;
350 r.tasks_start.write(|w| unsafe { w.bits(1) });
351 } else { 403 } else {
352 return Poll::Ready(()); 404 return Poll::Ready(());
353 }; 405 };
@@ -357,6 +409,11 @@ impl<'d, const N: usize> Saadc<'d, N> {
357 r.events_started.reset(); 409 r.events_started.reset();
358 r.intenset.write(|w| w.started().set()); 410 r.intenset.write(|w| w.started().set());
359 411
412 if !inited {
413 init();
414 inited = true;
415 }
416
360 let next_buffer = 1 - current_buffer; 417 let next_buffer = 1 - current_buffer;
361 r.result 418 r.result
362 .ptr 419 .ptr
@@ -368,11 +425,23 @@ impl<'d, const N: usize> Saadc<'d, N> {
368 .await; 425 .await;
369 } 426 }
370 427
428 /// Return the end event for use with PPI
429 pub fn event_end(&self) -> Event {
430 let r = Self::regs();
431 Event::from_reg(&r.events_end)
432 }
433
371 /// Return the sample task for use with PPI 434 /// Return the sample task for use with PPI
372 pub fn task_sample(&self) -> Task { 435 pub fn task_sample(&self) -> Task {
373 let r = Self::regs(); 436 let r = Self::regs();
374 Task::from_reg(&r.tasks_sample) 437 Task::from_reg(&r.tasks_sample)
375 } 438 }
439
440 /// Return the start task for use with PPI
441 pub fn task_start(&self) -> Task {
442 let r = Self::regs();
443 Task::from_reg(&r.tasks_start)
444 }
376} 445}
377 446
378impl<'d> Saadc<'d, 1> { 447impl<'d> Saadc<'d, 1> {
@@ -386,7 +455,7 @@ impl<'d> Saadc<'d, 1> {
386 /// that the size of this buffer can be less than the original buffer's size. 455 /// that the size of this buffer can be less than the original buffer's size.
387 /// A command is return from the closure that indicates whether the sampling 456 /// A command is return from the closure that indicates whether the sampling
388 /// should continue or stop. 457 /// should continue or stop.
389 pub async fn run_timer_sampler<S, const N0: usize>( 458 pub async fn run_timer_sampler<I, S, const N0: usize>(
390 &mut self, 459 &mut self,
391 bufs: &mut [[[i16; 1]; N0]; 2], 460 bufs: &mut [[[i16; 1]; N0]; 2],
392 sample_rate_divisor: u16, 461 sample_rate_divisor: u16,
@@ -394,7 +463,7 @@ impl<'d> Saadc<'d, 1> {
394 ) where 463 ) where
395 S: FnMut(&[[i16; 1]]) -> SamplerState, 464 S: FnMut(&[[i16; 1]]) -> SamplerState,
396 { 465 {
397 self.run_sampler(bufs, Some(sample_rate_divisor), sampler) 466 self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler)
398 .await; 467 .await;
399 } 468 }
400} 469}
diff --git a/examples/nrf/src/bin/saadc_continuous.rs b/examples/nrf/src/bin/saadc_continuous.rs
index 81559237b..991adabad 100644
--- a/examples/nrf/src/bin/saadc_continuous.rs
+++ b/examples/nrf/src/bin/saadc_continuous.rs
@@ -5,6 +5,7 @@
5#[path = "../example_common.rs"] 5#[path = "../example_common.rs"]
6mod example_common; 6mod example_common;
7use embassy::executor::Spawner; 7use embassy::executor::Spawner;
8use embassy::time::Duration;
8use embassy_nrf::ppi::Ppi; 9use embassy_nrf::ppi::Ppi;
9use embassy_nrf::saadc::{ChannelConfig, Config, Saadc, SamplerState}; 10use embassy_nrf::saadc::{ChannelConfig, Config, Saadc, SamplerState};
10use embassy_nrf::timer::{Frequency, Timer}; 11use embassy_nrf::timer::{Frequency, Timer};
@@ -26,34 +27,56 @@ async fn main(_spawner: Spawner, mut p: Peripherals) {
26 [channel_1_config, channel_2_config, channel_3_config], 27 [channel_1_config, channel_2_config, channel_3_config],
27 ); 28 );
28 29
30 // We want the task start to effectively short with the last one ending so
31 // we don't miss any samples. The Saadc will trigger the initial TASKS_START.
32 let mut start_ppi = Ppi::new_one_to_one(p.PPI_CH0, saadc.event_end(), saadc.task_start());
33 start_ppi.enable();
34
29 let mut timer = Timer::new(p.TIMER0); 35 let mut timer = Timer::new(p.TIMER0);
30 timer.set_frequency(Frequency::F1MHz); 36 timer.set_frequency(Frequency::F1MHz);
31 timer.cc(0).write(100); // We want to sample at 10KHz 37 timer.cc(0).write(1000); // We want to sample at 1KHz
32 timer.cc(0).short_compare_clear(); 38 timer.cc(0).short_compare_clear();
33 39
34 let mut ppi = Ppi::new_one_to_one(p.PPI_CH0, timer.cc(0).event_compare(), saadc.task_sample()); 40 let mut sample_ppi =
35 ppi.enable(); 41 Ppi::new_one_to_one(p.PPI_CH1, timer.cc(0).event_compare(), saadc.task_sample());
36 42
37 timer.start(); 43 timer.start();
38 44
39 let mut bufs = [[[0; 3]; 50]; 2]; 45 // This delay demonstrates that starting the timer prior to running
46 // the task sampler is benign given the calibration that follows.
47 embassy::time::Timer::after(Duration::from_millis(500)).await;
48 saadc.calibrate().await;
49
50 let mut bufs = [[[0; 3]; 500]; 2];
40 51
41 let mut c = 0; 52 let mut c = 0;
42 let mut a: i32 = 0; 53 let mut a: i32 = 0;
43 54
44 saadc 55 saadc
45 .run_task_sampler(&mut bufs, move |buf| { 56 .run_task_sampler(
46 for b in buf { 57 &mut bufs,
47 a += b[0] as i32; 58 || {
48 } 59 sample_ppi.enable();
49 c += buf.len(); 60 },
50 if c > 10000 { 61 move |buf| {
51 a = a / c as i32; 62 // NOTE: It is important that the time spent within this callback
52 info!("channel 1: {=i32}", a); 63 // does not exceed the time taken to acquire the 1500 samples we
53 c = 0; 64 // have in this example, which would be 10us + 2us per
54 a = 0; 65 // sample * 1500 = 18ms. You need to measure the time taken here
55 } 66 // and set the sample buffer size accordingly. Exceeding this
56 SamplerState::Sampled 67 // time can lead to the peripheral re-writing the other buffer.
57 }) 68 for b in buf {
69 a += b[0] as i32;
70 }
71 c += buf.len();
72 if c > 1000 {
73 a = a / c as i32;
74 info!("channel 1: {=i32}", a);
75 c = 0;
76 a = 0;
77 }
78 SamplerState::Sampled
79 },
80 )
58 .await; 81 .await;
59} 82}