From 72e96ffb357bdfe4d862efba95783888e2dae11c Mon Sep 17 00:00:00 2001 From: Jihyun Yu Date: Mon, 4 May 2020 20:03:08 +0900 Subject: [PATCH 1/2] adc: multichannel continuous conversion --- examples/adc-dma-rx.rs | 71 +++++++++++++++++++++++++++++++++--------- src/adc.rs | 10 +++--- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/examples/adc-dma-rx.rs b/examples/adc-dma-rx.rs index be62425e..f64e1eef 100644 --- a/examples/adc-dma-rx.rs +++ b/examples/adc-dma-rx.rs @@ -5,10 +5,23 @@ use panic_halt as _; -use cortex_m::{asm, singleton}; +use cortex_m::singleton; +use cortex_m_semihosting::hprintln; use cortex_m_rt::entry; -use stm32f1xx_hal::{adc, pac, prelude::*}; +use stm32f1xx_hal::gpio::gpioa::{PA0, PA2}; +use stm32f1xx_hal::{adc, gpio::Analog, pac, prelude::*}; + +pub struct AdcPins(PA0, PA2); +impl adc::SetChannels for adc::Adc { + fn set_samples(&mut self) { + self.set_channel_sample_time(0, adc::SampleTime::T_28); + self.set_channel_sample_time(2, adc::SampleTime::T_28); + } + fn set_sequence(&mut self) { + self.set_regular_sequence(&[0, 2, 0, 2]); + } +} #[entry] fn main() -> ! { @@ -34,21 +47,51 @@ fn main() -> ! { // Configure pa0 as an analog input let adc_ch0 = gpioa.pa0.into_analog(&mut gpioa.crl); + let adc_ch2 = gpioa.pa2.into_analog(&mut gpioa.crl); + + // single-channel continuous conversion + let (adc1, adc_ch0, dma_ch1) = { + let adc_dma = adc1.with_dma(adc_ch0, dma_ch1); + let buf = singleton!(: [u16; 8] = [0; 8]).unwrap(); + + // The read method consumes the buf and self, starts the adc and dma transfer and returns a + // RxDma struct. The wait method consumes the RxDma struct, waits for the whole transfer to + // be completed and then returns the updated buf and underlying adc_dma struct. For non + // blocking, one can call the is_done method of RxDma and only call wait after that method + // returns true. + let (_buf, adc_dma) = adc_dma.read(buf).wait(); + hprintln!("single-channel continuous conversion={:?}", _buf).ok(); + + // Consumes the AdcDma struct, restores adc configuration to previous state and returns the + // Adc struct in normal mode. + adc_dma.split() + }; + + // multi-channel single-shot conversion + let (adc1, AdcPins(adc_ch0, adc_ch2), dma_ch1) = { + let pins = AdcPins(adc_ch0, adc_ch2); + let adc_dma = adc1.with_scan_dma::<_, adc::Single>(pins, dma_ch1); + + // TODO: should match with # of conversions specified by AdcPins + let buf = singleton!(: [u16; 4] = [0; 4]).unwrap(); + + let (_buf, adc_dma) = adc_dma.read(buf).wait(); + hprintln!("multi-channel single-shot conversion={:?}", _buf).ok(); + + adc_dma.split() + }; - let adc_dma = adc1.with_dma(adc_ch0, dma_ch1); - let buf = singleton!(: [u16; 8] = [0; 8]).unwrap(); + // multi-channel continuous conversion + let (_adc1, AdcPins(_adc_ch0, _adc_ch2), _dma_ch1) = { + let pins = AdcPins(adc_ch0, adc_ch2); + let adc_dma = adc1.with_scan_dma::<_, adc::Continuous>(pins, dma_ch1); + let buf = singleton!(: [u16; 8] = [0; 8]).unwrap(); - // The read method consumes the buf and self, starts the adc and dma transfer and returns a - // RxDma struct. The wait method consumes the RxDma struct, waits for the whole transfer to be - // completed and then returns the updated buf and underlying adc_dma struct. For non blocking, - // one can call the is_done method of RxDma and only call wait after that method returns true. - let (_buf, adc_dma) = adc_dma.read(buf).wait(); - asm::bkpt(); + let (_buf, adc_dma) = adc_dma.read(buf).wait(); + hprintln!("multi-channel continuous conversion={:?}", _buf).ok(); - // Consumes the AdcDma struct, restores adc configuration to previous state and returns the - // Adc struct in normal mode. - let (_adc1, _adc_ch0, _dma_ch1) = adc_dma.split(); - asm::bkpt(); + adc_dma.split() + }; loop {} } diff --git a/src/adc.rs b/src/adc.rs index f6cd520b..30f6020e 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -18,8 +18,8 @@ use crate::pac::ADC3; /// Continuous mode pub struct Continuous; -/// Scan mode -pub struct Scan; +/// Single conversion mode +pub struct Single; /// ADC configuration pub struct Adc { @@ -567,7 +567,7 @@ impl TransferPayload for AdcDma { } } -impl TransferPayload for AdcDma { +impl TransferPayload for AdcDma { fn start(&mut self) { self.channel.start(); self.payload.adc.rb.cr2.modify(|_, w| w.adon().set_bit()); @@ -601,7 +601,7 @@ impl Adc { } } - pub fn with_scan_dma(mut self, pins: PINS, dma_ch: C1) -> AdcDma + pub fn with_scan_dma(mut self, pins: PINS, dma_ch: C1) -> AdcDma where Self: SetChannels, { @@ -651,7 +651,7 @@ where } } -impl AdcDma +impl AdcDma where Self: TransferPayload, { From 88a45af594c0655c693488d19daab5b96b42f281 Mon Sep 17 00:00:00 2001 From: Jihyun Yu Date: Sun, 24 May 2020 13:07:24 +0900 Subject: [PATCH 2/2] adc: check buffer length on DMA --- CHANGELOG.md | 1 + examples/adc-dma-rx.rs | 16 +++++++++++++--- src/adc.rs | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e51d3645..726cdb54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `QeiOptions` struct to configure slave mode and auto reload value of QEI interface - Implement multiplication and division for frequency wrappers (#193) - Add support for CRC +- ADC multichannel continuous conversion ### Changed diff --git a/examples/adc-dma-rx.rs b/examples/adc-dma-rx.rs index f64e1eef..d397b09f 100644 --- a/examples/adc-dma-rx.rs +++ b/examples/adc-dma-rx.rs @@ -3,7 +3,7 @@ #![no_main] #![no_std] -use panic_halt as _; +use panic_semihosting as _; use cortex_m::singleton; use cortex_m_semihosting::hprintln; @@ -72,11 +72,21 @@ fn main() -> ! { let pins = AdcPins(adc_ch0, adc_ch2); let adc_dma = adc1.with_scan_dma::<_, adc::Single>(pins, dma_ch1); - // TODO: should match with # of conversions specified by AdcPins let buf = singleton!(: [u16; 4] = [0; 4]).unwrap(); + let (_buf, adc_dma) = adc_dma.read(buf).wait(); + hprintln!( + "multi-channel single-shot conversion, exact buffer={:?}", + _buf + ) + .ok(); + let buf = singleton!(: [u16; 8] = [0; 8]).unwrap(); let (_buf, adc_dma) = adc_dma.read(buf).wait(); - hprintln!("multi-channel single-shot conversion={:?}", _buf).ok(); + hprintln!( + "multi-channel single-shot conversion, larger buffer={:?}", + _buf + ) + .ok(); adc_dma.split() }; diff --git a/src/adc.rs b/src/adc.rs index 30f6020e..b51d12bf 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -21,6 +21,22 @@ pub struct Continuous; /// Single conversion mode pub struct Single; +pub trait ConversionMode { + fn is_continuous() -> bool; +} + +impl ConversionMode for Continuous { + fn is_continuous() -> bool { + true + } +} + +impl ConversionMode for Single { + fn is_continuous() -> bool { + false + } +} + /// ADC configuration pub struct Adc { rb: ADC, @@ -344,6 +360,11 @@ macro_rules! adc_hal { self.rb.sqr1.modify(|_, w| w.l().bits((len-1) as u8)); } + #[allow(unused)] + fn sequence_len(&self) -> usize { + self.rb.sqr1.read().l().bits() as usize + 1 + } + /** Performs an ADC conversion @@ -709,15 +730,29 @@ impl crate::dma::ReadDma for AdcDma where Self: TransferPayload, B: as_slice::AsMutSlice, + MODE: ConversionMode, { fn read(mut self, buffer: &'static mut B) -> Transfer { { let buffer = buffer.as_mut_slice(); + + let conversion_len = if MODE::is_continuous() { + buffer.len() + } else { + // for non-continous conversion, conversion sequence length should match with DMA + // transfer length, to prevent DMA from waiting for a conversion indefinitely + let sequence_len = self.payload.adc.sequence_len(); + if buffer.len() < sequence_len { + panic!("short buffer on ADC conversion: buffer.len()={:?}, adc.seqeuence_len()={:?}", buffer.len(), sequence_len); + } + sequence_len + }; + self.channel .set_peripheral_address(unsafe { &(*ADC1::ptr()).dr as *const _ as u32 }, false); self.channel .set_memory_address(buffer.as_ptr() as u32, true); - self.channel.set_transfer_length(buffer.len()); + self.channel.set_transfer_length(conversion_len); } atomic::compiler_fence(Ordering::Release); self.channel.ch().cr.modify(|_, w| {