From b7cffdb209f40124740fa872027b01263b141949 Mon Sep 17 00:00:00 2001 From: Niklas Cathor Date: Sat, 24 Jun 2023 16:30:14 +0200 Subject: [PATCH 1/7] Add `start_dma` method to `AdcFifoBuilder` The method first enables the FIFO, sets FCS.THRESH = 1 and FCS.DREQ_EN = 1. Afterwards the given closure is called with an appropriate `dma::ReadTarget`, yielding either 8-bit or 16-bit samples (depending on `FCS.SHIFT`). The closure must start a DMA transfer and return it. Finally CS.START_MANY is set, and a `AdcFifo` together with the DMA transfer is returned. --- rp2040-hal/Cargo.toml | 5 + rp2040-hal/examples/adc_fifo_dma.rs | 174 ++++++++++++++++++++++++++++ rp2040-hal/examples/adc_fifo_irq.rs | 2 +- rp2040-hal/src/adc.rs | 94 ++++++++++++--- 4 files changed, 261 insertions(+), 14 deletions(-) create mode 100644 rp2040-hal/examples/adc_fifo_dma.rs diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index 409198a4c..e4c4be1ea 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -97,3 +97,8 @@ required-features = ["rt", "critical-section-impl"] # adc_fifo_irq example uses cortex-m-rt::interrupt, need rt feature for that name = "adc_fifo_irq" required-features = ["rt", "critical-section-impl"] + +[[example]] +# adc_fifo_dma example uses cortex-m-rt::interrupt, need rt feature for that +name = "adc_fifo_dma" +required-features = ["rt", "critical-section-impl"] diff --git a/rp2040-hal/examples/adc_fifo_dma.rs b/rp2040-hal/examples/adc_fifo_dma.rs new file mode 100644 index 000000000..964e1e29c --- /dev/null +++ b/rp2040-hal/examples/adc_fifo_dma.rs @@ -0,0 +1,174 @@ +//! # ADC FIFO Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! and reading them from the FIFO by polling the fifo's `len()`. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use core::fmt::Write; +use fugit::RateExtU32; +use rp2040_hal::Clock; +use hal::dma::{single_buffer, DMAExt}; +use cortex_m::singleton; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then prints the temperature +/// in an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC FIFO poll example\r\n"); + + // Initialize DMA + let dma = pac.DMA.split(&mut pac.RESETS); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()); + + // NOTE: when calling `shift_8bit` below, the type here must be changed from `u16` to `u8` + let buf_for_samples = singleton!(: [u16; 1000] = [0; 1000]).unwrap(); + + // Configure free-running mode: + let (dma_transfer, adc_fifo) = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + // sample the temperature sensor first + .set_channel(&mut temperature_sensor) + // then alternate between GPIO26 and the temperature sensor + .round_robin((&mut adc_pin_0, &mut temperature_sensor)) + // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) + //.shift_8bit() + // Start DMA channel & start sampling + .start_dma(|read_target| single_buffer::Config::new(dma.ch0, read_target, buf_for_samples).start()); + + // we'll capture 1000 samples in total (500 per channel) + + // initialize a timer, to measure the total sampling time (printed below) + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // the DMA unit takes care of shuffling data from the FIFO into the buffer. + // We just sit here and wait... 😴 + let (_ch, _adc_read_target, buf_for_samples) = dma_transfer.wait(); + + // ^^^ the three results here (channel, AdcReadTarget, write target) can be reused + // right away to start another transfer. + + let time_taken = timer.get_counter(); + + uart.write_full_blocking(b"Done sampling, printing results:\r\n"); + + // Stop free-running mode (the returned `adc` can be reused for future captures) + let _adc = adc_fifo.stop(); + + // Print the measured values + for i in 0..500 { + writeln!( + uart, + "Temp:\t{}\tPin\t{}\r", + buf_for_samples[i*2], buf_for_samples[i*2+1] + ) + .unwrap(); + } + + writeln!(uart, "Sampling took: {}\r", time_taken).unwrap(); + + loop { + delay.delay_ms(1000); + } +} + +// End of file diff --git a/rp2040-hal/examples/adc_fifo_irq.rs b/rp2040-hal/examples/adc_fifo_irq.rs index a7cae2912..96b3b7c87 100644 --- a/rp2040-hal/examples/adc_fifo_irq.rs +++ b/rp2040-hal/examples/adc_fifo_irq.rs @@ -58,7 +58,7 @@ mod app { #[local] struct Local { - adc_fifo: Option>, + adc_fifo: Option>, } #[init(local = [adc: Option = None])] diff --git a/rp2040-hal/src/adc.rs b/rp2040-hal/src/adc.rs index 93ab354ab..2e8f92c93 100644 --- a/rp2040-hal/src/adc.rs +++ b/rp2040-hal/src/adc.rs @@ -69,9 +69,12 @@ //! use core::convert::Infallible; +use core::marker::PhantomData; use hal::adc::{Channel, OneShot}; use pac::{ADC, RESETS}; +use crate::dma; +use pac::dma::ch::ch_ctrl_trig::TREQ_SEL_A; use crate::{ gpio::{ @@ -243,8 +246,8 @@ impl Adc { /// /// Capturing is started by calling [`AdcFifoBuilder::start`], which /// returns an [`AdcFifo`] to read from. - pub fn build_fifo(&mut self) -> AdcFifoBuilder<'_, false> { - AdcFifoBuilder { adc: self } + pub fn build_fifo(&mut self) -> AdcFifoBuilder<'_, u16> { + AdcFifoBuilder { adc: self, marker: PhantomData } } fn read(&mut self, chan: u8) -> u16 { @@ -306,11 +309,12 @@ where /// Used to configure & build an [`AdcFifo`] /// /// See [`Adc::build_fifo`] for details, as well as the `adc_fifo_*` [examples](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal/examples). -pub struct AdcFifoBuilder<'a, const SHIFTED: bool> { +pub struct AdcFifoBuilder<'a, Word> { adc: &'a mut Adc, + marker: PhantomData, } -impl<'a, const SHIFTED: bool> AdcFifoBuilder<'a, SHIFTED> { +impl<'a, Word> AdcFifoBuilder<'a, Word> { /// Manually set clock divider to control sample rate /// /// The ADC is tied to the USB clock, normally running at 48MHz. @@ -388,7 +392,6 @@ impl<'a, const SHIFTED: bool> AdcFifoBuilder<'a, SHIFTED> { /// Enable the FIFO interrupt ([`ADC_IRQ_FIFO`](pac::Interrupt::ADC_IRQ_FIFO)) /// /// It will be triggered whenever there are at least `threshold` samples waiting in the FIFO. - /// pub fn enable_interrupt(self, threshold: u8) -> Self { self.adc.device.inte.modify(|_, w| w.fifo().set_bit()); self.adc @@ -404,9 +407,9 @@ impl<'a, const SHIFTED: bool> AdcFifoBuilder<'a, SHIFTED> { /// Shifting the values loses some precision, but produces smaller samples. /// /// When this method has been called, the resulting fifo's `read` method returns u8. - pub fn shift_8bit(self) -> AdcFifoBuilder<'a, true> { + pub fn shift_8bit(self) -> AdcFifoBuilder<'a, u8> { self.adc.device.fcs.modify(|_, w| w.shift().set_bit()); - AdcFifoBuilder { adc: self.adc } + AdcFifoBuilder { adc: self.adc, marker: PhantomData } } /// Enable ADC FIFO and start free-running conversion @@ -414,10 +417,55 @@ impl<'a, const SHIFTED: bool> AdcFifoBuilder<'a, SHIFTED> { /// Use the returned [`AdcFifo`] instance to access the captured data. /// /// To stop capturing, call [`AdcFifo::stop`]. - pub fn start(self) -> AdcFifo<'a, SHIFTED> { + pub fn start(self) -> AdcFifo<'a, Word> { self.adc.device.fcs.modify(|_, w| w.en().set_bit()); self.adc.device.cs.modify(|_, w| w.start_many().set_bit()); - AdcFifo { adc: self.adc } + AdcFifo { adc: self.adc, marker: PhantomData } + } + + /// Set up and start a DMA transfer, then start FIFO capture + /// + /// Note that this will force the FIFO threshold to `1`, overwriting any other value passed to [`AdcFifoBuilder::enable_interrupt`]. + /// + /// Example: + /// ```ignore + /// + /// let write_target = ...; // set up a destination (e.g. buffer) accepting u16 words + /// + /// let (transfer, fifo) = adc + /// .build_fifo() + /// .start_dma(|read_target| { + /// dma::single_buffer::Config::new(dma.ch0, read_target, write_target).start() + /// }); + /// + /// // wait for transfer to complete + /// let (channel, read_target, write_target) = transfer.wait(); + /// + /// // another transfer could be started now (the ADC conversion is still running) + /// + /// // stop capturing: + /// let adc = fifo.stop(); + /// ``` + pub fn start_dma(self, start_transfer: F) -> (dma::single_buffer::Transfer, TO>, AdcFifo<'a, Word>) + where + CH: dma::SingleChannel, + TO: dma::WriteTarget, + F: FnOnce(AdcReadTarget) -> dma::single_buffer::Transfer, TO> + { + // Enable DREQ, set threshold to 1, enable FIFO + self.adc + .device + .fcs + .modify(|_, w| unsafe { + w.dreq_en().set_bit() + .thresh().bits(1) + .en().set_bit() + }); + // Start DMA transfer + let transfer = start_transfer(AdcReadTarget(&self.adc.device.fifo as *const _ as u32, PhantomData)); + // Start capturing + self.adc.device.cs.modify(|_, w| w.start_many().set_bit()); + (transfer, AdcFifo { adc: self.adc, marker: PhantomData }) } } @@ -425,11 +473,12 @@ impl<'a, const SHIFTED: bool> AdcFifoBuilder<'a, SHIFTED> { /// /// Constructed by [`AdcFifoBuilder::start`], which is accessible through [`Adc::build_fifo`]. /// -pub struct AdcFifo<'a, const SHIFTED: bool> { +pub struct AdcFifo<'a, Word> { adc: &'a mut Adc, + marker: PhantomData, } -impl<'a, const SHIFTED: bool> AdcFifo<'a, SHIFTED> { +impl<'a, Word> AdcFifo<'a, Word> { #[allow(clippy::len_without_is_empty)] /// Returns the number of elements currently in the fifo pub fn len(&mut self) -> u8 { @@ -544,14 +593,14 @@ impl<'a, const SHIFTED: bool> AdcFifo<'a, SHIFTED> { } } -impl<'a> AdcFifo<'a, false> { +impl<'a> AdcFifo<'a, u16> { /// Read a single value from the fifo (u16 version, not shifted) pub fn read(&mut self) -> u16 { self.read_from_fifo() } } -impl<'a> AdcFifo<'a, true> { +impl<'a> AdcFifo<'a, u8> { /// Read a single value from the fifo (u8 version, shifted) /// /// Also see [`AdcFifoBuilder::shift_8bit`]. @@ -560,6 +609,25 @@ impl<'a> AdcFifo<'a, true> { } } +/// Represents a ReadTarget for DMA transfers +pub struct AdcReadTarget(u32, PhantomData); + +impl dma::ReadTarget for AdcReadTarget { + type ReceivedWord = Word; + + fn rx_treq() -> Option { + Some(TREQ_SEL_A::ADC.into()) + } + + fn rx_address_count(&self) -> (u32, u32) { + (self.0, u32::MAX) + } + + fn rx_increment(&self) -> bool { + false + } +} + /// Internal struct representing values for the `CS.RROBIN` register. /// /// See [`AdcFifoBuilder::round_robin`], for usage example. From f79d6ddfe30019e11aa7ccfda59c44a6221b2480 Mon Sep 17 00:00:00 2001 From: Niklas Cathor Date: Thu, 6 Jul 2023 09:16:12 +0200 Subject: [PATCH 2/7] Remove `start_dma` in favor of `enable_dma` / `prepare` Also adds methods to `pause` and `resume` FIFO capturing. --- rp2040-hal/src/adc.rs | 178 +++++++++++++++++++++++++++++++----------- 1 file changed, 131 insertions(+), 47 deletions(-) diff --git a/rp2040-hal/src/adc.rs b/rp2040-hal/src/adc.rs index 2e8f92c93..55aa4fe36 100644 --- a/rp2040-hal/src/adc.rs +++ b/rp2040-hal/src/adc.rs @@ -67,6 +67,52 @@ //! ``` //! See [examples/adc_fifo_poll.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal/examples/adc_fifo_poll.rs) for a more complete example. //! +//! ### Using DMA +//! +//! When the ADC is in free-running mode, it's possible to use DMA to transfer data from the FIFO elsewhere, without having to read the FIFO manually. +//! +//! This requires a number of steps: +//! 1. Build and `AdcFifo`, with DMA enabled ([`AdcFifoBuilder::enable_dma`]) +//! 2. Use [`AdcFifoBuilder::prepare`] instead of [`AdcFifoBuilder::start`], so that the FIFO is created in `paused` state +//! 3. Start a DMA transfer ([`dma::single_buffer::Transfer`], [`dma::double_buffer::Transfer`], ...), using the [`AdcFifo::dma_read_target`] as the source +//! 4. Finally unpause the FIFO by calling [`AdcFifo::resume`], to start capturing +//! +//! Example: +//! ```no_run +//! use rp2040_hal::{adc::Adc, gpio::Pins, pac, Sio, dma::single_buffer}; +//! 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); +//! let dma = peripherals.DMA.split(&mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! +//! // Configure & start capturing to the fifo: +//! let mut fifo = adc.build_fifo() +//! .clock_divider(0, 0) // sample as fast as possible (500ksps. This is the default) +//! .set_channel(&mut temperature_sensor) +//! .enable_dma() +//! .prepare(); +//! +//! // Set up a buffer, where the samples should be written: +//! let buf = singleton!(: [u16; 500] = [0; 500]); +//! +//! // Start DMA transfer +//! let transfer = single_buffer::Config::new(dma.ch0, fifo.dma_read_target(), buf).start(); +//! +//! // Resume the FIFO to start capturing +//! adc_fifo.resume(); +//! +//! // Wait for the transfer to complete: +//! let (ch, adc_read_target, buf) = transfer.wait(); +//! +//! // do something with `buf` (it now contains 500 samples read from the ADC) +//! //... +//! +//! ``` +//! use core::convert::Infallible; use core::marker::PhantomData; @@ -412,60 +458,41 @@ impl<'a, Word> AdcFifoBuilder<'a, Word> { AdcFifoBuilder { adc: self.adc, marker: PhantomData } } + /// Enable DMA for the FIFO. + /// + /// This must be called to be able to transfer data from the ADC using a DMA transfer. + /// + /// **NOTE:** *this method sets the FIFO interrupt threshold to `1`, which is required for DMA transfers to work. + /// The threshold is the same one as set by [`AdcFifoBuilder::enable_interrupt`]. If you want to enable FIFO + /// interrupts, but also use DMA, the `threshold` parameter passed to `enable_interrupt` *must* be set to `1` as well.* + pub fn enable_dma(self) -> Self { + self.adc.device.fcs.modify(|_, w| unsafe { + w.dreq_en().set_bit().thresh().bits(1) + }); + self + } + /// Enable ADC FIFO and start free-running conversion /// /// Use the returned [`AdcFifo`] instance to access the captured data. /// /// To stop capturing, call [`AdcFifo::stop`]. + /// + /// Note: if you plan to use the FIFO for DMA transfers, [`AdcFifoBuilder::prepare`] instead. pub fn start(self) -> AdcFifo<'a, Word> { self.adc.device.fcs.modify(|_, w| w.en().set_bit()); self.adc.device.cs.modify(|_, w| w.start_many().set_bit()); AdcFifo { adc: self.adc, marker: PhantomData } } - /// Set up and start a DMA transfer, then start FIFO capture - /// - /// Note that this will force the FIFO threshold to `1`, overwriting any other value passed to [`AdcFifoBuilder::enable_interrupt`]. - /// - /// Example: - /// ```ignore - /// - /// let write_target = ...; // set up a destination (e.g. buffer) accepting u16 words + /// Enable ADC FIFO, but do not start conversion yet /// - /// let (transfer, fifo) = adc - /// .build_fifo() - /// .start_dma(|read_target| { - /// dma::single_buffer::Config::new(dma.ch0, read_target, write_target).start() - /// }); + /// Same as [`AdcFifoBuilder::start`], except the FIFO is initially paused. /// - /// // wait for transfer to complete - /// let (channel, read_target, write_target) = transfer.wait(); - /// - /// // another transfer could be started now (the ADC conversion is still running) - /// - /// // stop capturing: - /// let adc = fifo.stop(); - /// ``` - pub fn start_dma(self, start_transfer: F) -> (dma::single_buffer::Transfer, TO>, AdcFifo<'a, Word>) - where - CH: dma::SingleChannel, - TO: dma::WriteTarget, - F: FnOnce(AdcReadTarget) -> dma::single_buffer::Transfer, TO> - { - // Enable DREQ, set threshold to 1, enable FIFO - self.adc - .device - .fcs - .modify(|_, w| unsafe { - w.dreq_en().set_bit() - .thresh().bits(1) - .en().set_bit() - }); - // Start DMA transfer - let transfer = start_transfer(AdcReadTarget(&self.adc.device.fifo as *const _ as u32, PhantomData)); - // Start capturing - self.adc.device.cs.modify(|_, w| w.start_many().set_bit()); - (transfer, AdcFifo { adc: self.adc, marker: PhantomData }) + /// Use [`AdcFifo::resume`] to start conversion. + pub fn prepare(self) -> AdcFifo<'a, Word> { + self.adc.device.fcs.modify(|_, w| w.en().set_bit()); + AdcFifo { adc: self.adc, marker: PhantomData } } } @@ -520,7 +547,7 @@ impl<'a, Word> AdcFifo<'a, Word> { /// /// Example: /// ```ignore - /// // start continously sampling values: + /// // start continuously sampling values: /// let mut fifo = adc.build_fifo().set_channel(&mut adc_pin).start(); /// /// loop { @@ -544,6 +571,49 @@ impl<'a, Word> AdcFifo<'a, Word> { self.adc.read_single() } + /// Returns `true` if conversion is currently paused. + /// + /// While paused, no samples will be added to the FIFO. + /// + /// There may be existing samples in the FIFO though, or a conversion may still be in progress. + pub fn is_paused(&mut self) -> bool { + self.adc.device.cs.read().start_many().bit_is_clear() + } + + /// Temporarily pause conversion + /// + /// This method stops ADC conversion, but leaves everything else configured. + /// + /// No new samples are captured until [`AdcFifo::resume`] is called. + /// + /// Note that existing samples can still be read from the FIFO, and can possibly + /// cause interrupts and DMA transfer progress until the FIFO is emptied. + pub fn pause(&mut self) { + self.adc.device.cs.modify(|_, w| w.start_many().clear_bit()); + } + + /// Resume conversion after it was paused + /// + /// There are two situations when it makes sense to use this method: + /// - After having called [`AdcFifo::pause`] on an AdcFifo + /// - If the FIFO was initialized using [`AdcFifoBuilder::prepare`]. + /// + /// Calling this method when conversion is already running has no effect. + pub fn resume(&mut self) { + self.adc.device.cs.modify(|_, w| w.start_many().set_bit()); + } + + /// Clears the FIFO, removing all values + /// + /// Reads and discards values from the FIFO until it is empty. + /// + /// This only makes sense to use while the FIFO is paused (see [`AdcFifo::pause`]). + pub fn clear(&mut self) { + while self.len() > 0 { + self.read_from_fifo(); + } + } + /// Stop capturing in free running mode. /// /// Resets all capture options that can be set via [`AdcFifoBuilder`] to @@ -568,11 +638,11 @@ impl<'a, Word> AdcFifo<'a, Word> { while self.len() > 0 { self.read_from_fifo(); } - // disable fifo and reset threshold to 0 + // disable fifo, reset threshold to 0 and disable DMA self.adc .device .fcs - .modify(|_, w| unsafe { w.en().clear_bit().thresh().bits(0) }); + .modify(|_, w| unsafe { w.en().clear_bit().thresh().bits(0).dreq_en().clear_bit() }); // reset clock divider self.adc .device @@ -591,6 +661,14 @@ impl<'a, Word> AdcFifo<'a, Word> { fn read_from_fifo(&mut self) -> u16 { self.adc.device.fifo.read().val().bits() } + + /// Returns a read-target for initiating DMA transfers + /// + /// The [`DmaReadTarget`] returned by this function can be used to initiate DMA transfers + /// reading from the ADC. + pub fn dma_read_target(&self) -> DmaReadTarget { + DmaReadTarget(&self.adc.device.fifo as *const _ as u32, PhantomData) + } } impl<'a> AdcFifo<'a, u16> { @@ -609,10 +687,14 @@ impl<'a> AdcFifo<'a, u8> { } } -/// Represents a ReadTarget for DMA transfers -pub struct AdcReadTarget(u32, PhantomData); +/// Represents a [`dma::ReadTarget`] for the [`AdcFifo`] +/// +/// If [`AdcFifoBuilder::shift_8bit`] was called when constructing the FIFO, +/// `Word` will be `u8`, otherwise it will be `u16`. +/// +pub struct DmaReadTarget(u32, PhantomData); -impl dma::ReadTarget for AdcReadTarget { +impl dma::ReadTarget for DmaReadTarget { type ReceivedWord = Word; fn rx_treq() -> Option { @@ -628,6 +710,8 @@ impl dma::ReadTarget for AdcReadTarget { } } +impl dma::EndlessReadTarget for DmaReadTarget {} + /// Internal struct representing values for the `CS.RROBIN` register. /// /// See [`AdcFifoBuilder::round_robin`], for usage example. From b06fe1a39c4f41a9f25dea3a4ff6f6d19a7e34a6 Mon Sep 17 00:00:00 2001 From: Niklas Cathor Date: Thu, 6 Jul 2023 09:29:18 +0200 Subject: [PATCH 3/7] fmt, clippy --- rp2040-hal/examples/adc_fifo_dma.rs | 25 +++++++++++++++-------- rp2040-hal/src/adc.rs | 31 ++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/rp2040-hal/examples/adc_fifo_dma.rs b/rp2040-hal/examples/adc_fifo_dma.rs index 964e1e29c..1afd3dff6 100644 --- a/rp2040-hal/examples/adc_fifo_dma.rs +++ b/rp2040-hal/examples/adc_fifo_dma.rs @@ -19,10 +19,10 @@ use rp2040_hal as hal; // Some traits we need use core::fmt::Write; +use cortex_m::singleton; use fugit::RateExtU32; -use rp2040_hal::Clock; use hal::dma::{single_buffer, DMAExt}; -use cortex_m::singleton; +use rp2040_hal::Clock; // UART related types use hal::uart::{DataBits, StopBits, UartConfig}; @@ -116,11 +116,12 @@ fn main() -> ! { // Configure GPIO26 as an ADC input let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()); + // we'll capture 1000 samples in total (500 per channel) // NOTE: when calling `shift_8bit` below, the type here must be changed from `u16` to `u8` let buf_for_samples = singleton!(: [u16; 1000] = [0; 1000]).unwrap(); // Configure free-running mode: - let (dma_transfer, adc_fifo) = adc + let mut adc_fifo = adc .build_fifo() // Set clock divider to target a sample rate of 1000 samples per second (1ksps). // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. @@ -132,10 +133,17 @@ fn main() -> ! { .round_robin((&mut adc_pin_0, &mut temperature_sensor)) // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) //.shift_8bit() - // Start DMA channel & start sampling - .start_dma(|read_target| single_buffer::Config::new(dma.ch0, read_target, buf_for_samples).start()); + // Enable DMA transfers for the FIFO + .enable_dma() + // Create the FIFO, but don't start it just yet + .prepare(); - // we'll capture 1000 samples in total (500 per channel) + // Start a DMA transfer (must happen before resuming the ADC FIFO) + let dma_transfer = + single_buffer::Config::new(dma.ch0, adc_fifo.dma_read_target(), buf_for_samples).start(); + + // Resume the FIFO to start capturing + adc_fifo.resume(); // initialize a timer, to measure the total sampling time (printed below) let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); @@ -144,7 +152,7 @@ fn main() -> ! { // We just sit here and wait... 😴 let (_ch, _adc_read_target, buf_for_samples) = dma_transfer.wait(); - // ^^^ the three results here (channel, AdcReadTarget, write target) can be reused + // ^^^ the three results here (channel, adc::DmaReadTarget, write target) can be reused // right away to start another transfer. let time_taken = timer.get_counter(); @@ -159,7 +167,8 @@ fn main() -> ! { writeln!( uart, "Temp:\t{}\tPin\t{}\r", - buf_for_samples[i*2], buf_for_samples[i*2+1] + buf_for_samples[i * 2], + buf_for_samples[i * 2 + 1] ) .unwrap(); } diff --git a/rp2040-hal/src/adc.rs b/rp2040-hal/src/adc.rs index 55aa4fe36..bdccc8066 100644 --- a/rp2040-hal/src/adc.rs +++ b/rp2040-hal/src/adc.rs @@ -117,10 +117,10 @@ use core::convert::Infallible; use core::marker::PhantomData; -use hal::adc::{Channel, OneShot}; -use pac::{ADC, RESETS}; use crate::dma; +use hal::adc::{Channel, OneShot}; use pac::dma::ch::ch_ctrl_trig::TREQ_SEL_A; +use pac::{ADC, RESETS}; use crate::{ gpio::{ @@ -293,7 +293,10 @@ impl Adc { /// Capturing is started by calling [`AdcFifoBuilder::start`], which /// returns an [`AdcFifo`] to read from. pub fn build_fifo(&mut self) -> AdcFifoBuilder<'_, u16> { - AdcFifoBuilder { adc: self, marker: PhantomData } + AdcFifoBuilder { + adc: self, + marker: PhantomData, + } } fn read(&mut self, chan: u8) -> u16 { @@ -455,7 +458,10 @@ impl<'a, Word> AdcFifoBuilder<'a, Word> { /// When this method has been called, the resulting fifo's `read` method returns u8. pub fn shift_8bit(self) -> AdcFifoBuilder<'a, u8> { self.adc.device.fcs.modify(|_, w| w.shift().set_bit()); - AdcFifoBuilder { adc: self.adc, marker: PhantomData } + AdcFifoBuilder { + adc: self.adc, + marker: PhantomData, + } } /// Enable DMA for the FIFO. @@ -466,9 +472,10 @@ impl<'a, Word> AdcFifoBuilder<'a, Word> { /// The threshold is the same one as set by [`AdcFifoBuilder::enable_interrupt`]. If you want to enable FIFO /// interrupts, but also use DMA, the `threshold` parameter passed to `enable_interrupt` *must* be set to `1` as well.* pub fn enable_dma(self) -> Self { - self.adc.device.fcs.modify(|_, w| unsafe { - w.dreq_en().set_bit().thresh().bits(1) - }); + self.adc + .device + .fcs + .modify(|_, w| unsafe { w.dreq_en().set_bit().thresh().bits(1) }); self } @@ -482,7 +489,10 @@ impl<'a, Word> AdcFifoBuilder<'a, Word> { pub fn start(self) -> AdcFifo<'a, Word> { self.adc.device.fcs.modify(|_, w| w.en().set_bit()); self.adc.device.cs.modify(|_, w| w.start_many().set_bit()); - AdcFifo { adc: self.adc, marker: PhantomData } + AdcFifo { + adc: self.adc, + marker: PhantomData, + } } /// Enable ADC FIFO, but do not start conversion yet @@ -492,7 +502,10 @@ impl<'a, Word> AdcFifoBuilder<'a, Word> { /// Use [`AdcFifo::resume`] to start conversion. pub fn prepare(self) -> AdcFifo<'a, Word> { self.adc.device.fcs.modify(|_, w| w.en().set_bit()); - AdcFifo { adc: self.adc, marker: PhantomData } + AdcFifo { + adc: self.adc, + marker: PhantomData, + } } } From 2a896d5f327d483e75311102043f2d0fdf6f661f Mon Sep 17 00:00:00 2001 From: Niklas Cathor Date: Thu, 6 Jul 2023 09:41:37 +0200 Subject: [PATCH 4/7] tweak example --- rp2040-hal/examples/adc_fifo_dma.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rp2040-hal/examples/adc_fifo_dma.rs b/rp2040-hal/examples/adc_fifo_dma.rs index 1afd3dff6..306f302b3 100644 --- a/rp2040-hal/examples/adc_fifo_dma.rs +++ b/rp2040-hal/examples/adc_fifo_dma.rs @@ -1,7 +1,7 @@ -//! # ADC FIFO Example +//! # ADC FIFO DMA Example //! //! This application demonstrates how to read ADC samples in free-running mode, -//! and reading them from the FIFO by polling the fifo's `len()`. +//! and reading them from the FIFO by using a DMA transfer. //! //! It may need to be adapted to your particular board layout and/or pin assignment. //! @@ -102,7 +102,7 @@ fn main() -> ! { .unwrap(); // Write to the UART - uart.write_full_blocking(b"ADC FIFO poll example\r\n"); + uart.write_full_blocking(b"ADC FIFO DMA example\r\n"); // Initialize DMA let dma = pac.DMA.split(&mut pac.RESETS); @@ -148,6 +148,11 @@ fn main() -> ! { // initialize a timer, to measure the total sampling time (printed below) let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + // NOTE: in a real-world program, instead of calling `wait` now, you would probably: + // 1. Enable one of the DMA interrupts for the channel (e.g. `dma.ch0.listen_irq0()`) + // 2. Set up a handler for the respective `DMA_IRQ_*` interrupt + // 3. Call `wait` only within that interrupt, which will be fired once the transfer is complete. + // the DMA unit takes care of shuffling data from the FIFO into the buffer. // We just sit here and wait... 😴 let (_ch, _adc_read_target, buf_for_samples) = dma_transfer.wait(); From e0649d092a0ab4e1331fe13cc88e469c336622c2 Mon Sep 17 00:00:00 2001 From: Niklas Cathor Date: Thu, 6 Jul 2023 09:42:10 +0200 Subject: [PATCH 5/7] Mention DMA example in docs --- rp2040-hal/src/adc.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rp2040-hal/src/adc.rs b/rp2040-hal/src/adc.rs index bdccc8066..f3b58ccef 100644 --- a/rp2040-hal/src/adc.rs +++ b/rp2040-hal/src/adc.rs @@ -112,6 +112,7 @@ //! //... //! //! ``` +//! //! 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. //! use core::convert::Infallible; From 852e0902aeb1ec3dfb9f5ebf199ddb28728c0edd Mon Sep 17 00:00:00 2001 From: Niklas Cathor Date: Thu, 6 Jul 2023 09:52:09 +0200 Subject: [PATCH 6/7] Fix doc example, so it compiles --- rp2040-hal/src/adc.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rp2040-hal/src/adc.rs b/rp2040-hal/src/adc.rs index f3b58ccef..babc90fda 100644 --- a/rp2040-hal/src/adc.rs +++ b/rp2040-hal/src/adc.rs @@ -79,7 +79,8 @@ //! //! Example: //! ```no_run -//! use rp2040_hal::{adc::Adc, gpio::Pins, pac, Sio, dma::single_buffer}; +//! use rp2040_hal::{adc::Adc, gpio::Pins, pac, Sio, dma::{single_buffer, DMAExt}}; +//! use cortex_m::singleton; //! 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); @@ -97,13 +98,13 @@ //! .prepare(); //! //! // Set up a buffer, where the samples should be written: -//! let buf = singleton!(: [u16; 500] = [0; 500]); +//! let buf = singleton!(: [u16; 500] = [0; 500]).unwrap(); //! //! // Start DMA transfer //! let transfer = single_buffer::Config::new(dma.ch0, fifo.dma_read_target(), buf).start(); //! //! // Resume the FIFO to start capturing -//! adc_fifo.resume(); +//! fifo.resume(); //! //! // Wait for the transfer to complete: //! let (ch, adc_read_target, buf) = transfer.wait(); From 0f9f891a5126eb2c9ffde78a1dc0982460ad23f1 Mon Sep 17 00:00:00 2001 From: Niklas Cathor Date: Thu, 6 Jul 2023 10:01:01 +0200 Subject: [PATCH 7/7] typo, clarification --- rp2040-hal/src/adc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rp2040-hal/src/adc.rs b/rp2040-hal/src/adc.rs index babc90fda..4ed4c20b1 100644 --- a/rp2040-hal/src/adc.rs +++ b/rp2040-hal/src/adc.rs @@ -72,9 +72,9 @@ //! When the ADC is in free-running mode, it's possible to use DMA to transfer data from the FIFO elsewhere, without having to read the FIFO manually. //! //! This requires a number of steps: -//! 1. Build and `AdcFifo`, with DMA enabled ([`AdcFifoBuilder::enable_dma`]) +//! 1. Build an `AdcFifo`, with DMA enabled ([`AdcFifoBuilder::enable_dma`]) //! 2. Use [`AdcFifoBuilder::prepare`] instead of [`AdcFifoBuilder::start`], so that the FIFO is created in `paused` state -//! 3. Start a DMA transfer ([`dma::single_buffer::Transfer`], [`dma::double_buffer::Transfer`], ...), using the [`AdcFifo::dma_read_target`] as the source +//! 3. Start a DMA transfer ([`dma::single_buffer::Transfer`], [`dma::double_buffer::Transfer`], ...), using the [`AdcFifo::dma_read_target`] as the source (`from` parameter) //! 4. Finally unpause the FIFO by calling [`AdcFifo::resume`], to start capturing //! //! Example: