Skip to content

Commit

Permalink
Allow free-running ADC mode without FIFO
Browse files Browse the repository at this point in the history
  • Loading branch information
jannic committed Dec 22, 2023
1 parent 7c9117c commit 66e93e5
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 13 deletions.
2 changes: 1 addition & 1 deletion rp2040-hal/examples/adc_fifo_dma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ fn main() -> ! {
// Enable DMA transfers for the FIFO
.enable_dma()
// Create the FIFO, but don't start it just yet
.prepare();
.start_paused();

// Start a DMA transfer (must happen before resuming the ADC FIFO)
let dma_transfer =
Expand Down
99 changes: 87 additions & 12 deletions rp2040-hal/src/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
//! See [examples/adc.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal/examples/adc.rs) and
//! [pimoroni_pico_explorer_showcase.rs](https://github.com/rp-rs/rp-hal-boards/tree/main/boards/pimoroni-pico-explorer/examples/pimoroni_pico_explorer_showcase.rs) for more complete examples
//!
//! ### Free running mode
//! ### Free running mode with FIFO
//!
//! In free-running mode the ADC automatically captures samples in regular intervals.
//! The samples are written to a FIFO, from which they can be retrieved.
Expand Down Expand Up @@ -115,6 +115,37 @@
//! ```
//! //! See [examples/adc_fifo_dma.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal/examples/adc_fifo_dma.rs) for a more complete example.
//!
//! ### Free running mode without FIFO
//!
//! While free-running mode is usually used in combination with a FIFO, there are
//! use cases where it can be used without. For example, if you want to be able to
//! get the latest available sample at any point in time, and without waiting 96 ADC clock
//! cycles (2µs).
//!
//! In this case, you can just enable free-running mode on it's own. The ADC will
//! continuously do ADC conversions. The ones not read will just be discarded, but it's
//! always possible to read the latest value, without additional delay:
//!
//! ```no_run
//! use rp2040_hal::{adc::Adc, adc::AdcPin, gpio::Pins, pac, Sio};
//! use embedded_hal::adc::OneShot;
//! let mut peripherals = pac::Peripherals::take().unwrap();
//! let sio = Sio::new(peripherals.SIO);
//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS);
//! // Enable adc
//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS);
//! // Configure one of the pins as an ADC input
//! let mut adc_pin_0 = AdcPin::new(pins.gpio26.into_floating_input());
//! // Read at least once to configure ADC channel and trigger first conversion
//! let _: u16 = adc.read(&mut adc_pin_0).unwrap();
//! // Enable free-running mode
//! adc.free_running(true);
//! // Read the ADC counts from the ADC channel whenever necessary
//! loop {
//! let pin_adc_counts: u16 = adc.read_single();
//! // Do time critical stuff
//! }
//! ```
use core::convert::Infallible;
use core::marker::PhantomData;
Expand Down Expand Up @@ -248,7 +279,13 @@ impl Adc {
self.device
}

/// Read single
/// Read the most recently sampled ADC value
///
/// This function does not wait for the current conversion to finish.
/// If a conversion is still in progress, it returns the result of the
/// previous one.
///
/// It also doesn't trigger a new conversion.
pub fn read_single(&self) -> u16 {
self.device.result.read().result().bits()
}
Expand Down Expand Up @@ -299,20 +336,39 @@ impl Adc {
}
}

/// Enable free-running mode by setting the start_many flag.
pub fn free_running(&mut self, enable: bool) {
self.wait_ready();
self.device.cs.modify(|_, w| w.start_many().bit(enable));
}

fn inner_read(&mut self, chan: u8) -> u16 {
while !self.device.cs.read().ready().bit_is_set() {
cortex_m::asm::nop();
}
self.wait_ready();

self.device
.cs
.modify(|_, w| unsafe { w.ainsel().bits(chan).start_once().set_bit() });

while !self.device.cs.read().ready().bit_is_set() {
self.wait_ready();

self.read_single()
}

/// Wait for the ADC to become ready.
///
/// Also returns immediately if start_many is set, to avoid indefinite blocking.
pub fn wait_ready(&self) {
let cs = self.device.cs.read();
while !cs.ready().bit_is_set() && !cs.start_many().bit_is_set() {
cortex_m::asm::nop();
}
}

self.device.result.read().result().bits()
/// Returns true if the ADC is ready for the next conversion.
///
/// This implies that any previous converison has finished.
pub fn is_ready(&self) -> bool {
self.device.cs.read().ready().bit_is_set()
}
}

Expand Down Expand Up @@ -500,16 +556,23 @@ impl<'a, Word> AdcFifoBuilder<'a, Word> {
/// Same as [`AdcFifoBuilder::start`], except the FIFO is initially paused.
///
/// Use [`AdcFifo::resume`] to start conversion.
pub fn prepare(self) -> AdcFifo<'a, Word> {
pub fn start_paused(self) -> AdcFifo<'a, Word> {
self.adc.device.fcs.modify(|_, w| w.en().set_bit());
self.adc.device.cs.modify(|_, w| w.start_many().clear_bit());
AdcFifo {
adc: self.adc,
marker: PhantomData,
}
}

/// Alias for [`start_paused`].
#[deprecated(note = "Use `start_paused()` instead.", since = "0.10.0")]
pub fn prepare(self) -> AdcFifo<'a, Word> {
self.start_paused()
}
}

/// Represents the ADC fifo, when used in free running mode
/// Represents the ADC fifo
///
/// Constructed by [`AdcFifoBuilder::start`], which is accessible through [`Adc::build_fifo`].
///
Expand Down Expand Up @@ -654,9 +717,7 @@ impl<'a, Word> AdcFifo<'a, Word> {
// The only way to clear `INTS.FIFO` is for `FCS.LEVEL` to go
// below `FCS.THRESH`, which requires `FCS.THRESH` not to be 0.
while self.adc.device.cs.read().ready().bit_is_clear() {}
while self.len() > 0 {
self.read_from_fifo();
}
self.clear();
// disable fifo, reset threshold to 0 and disable DMA
self.adc
.device
Expand Down Expand Up @@ -688,6 +749,20 @@ impl<'a, Word> AdcFifo<'a, Word> {
pub fn dma_read_target(&self) -> DmaReadTarget<Word> {
DmaReadTarget(&self.adc.device.fifo as *const _ as u32, PhantomData)
}

/// Trigger a single conversion
///
/// Ignored unless in [`AdcFifoBuilder::manual_trigger`] mode.
pub fn trigger(&mut self) {
self.adc.device.cs.modify(|_, w| w.start_once().set_bit());
}

/// Check if ADC is ready for the next conversion trigger
///
/// Only useful in [`AdcFifoBuilder::manual_trigger`] mode.
pub fn is_ready(&self) -> bool {
self.adc.device.cs.read().ready().bit_is_set()
}
}

impl<'a> AdcFifo<'a, u16> {
Expand Down

0 comments on commit 66e93e5

Please sign in to comment.