From 23d83bac3e26b5301bc54227a4bc2249150e43a8 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge@japaric.io> Date: Thu, 22 Jun 2017 15:56:14 -0500 Subject: [PATCH] ADC + DMA API --- Cargo.toml | 1 + src/adc.rs | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/dma.rs | 126 +++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 2 + src/pwm.rs | 2 +- 5 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 src/adc.rs diff --git a/Cargo.toml b/Cargo.toml index 9f5555b..b959bb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ version = "0.1.0" [dependencies] static-ref = "0.1.0" stm32f103xx = "0.6.1" +volatile-register = "0.2.0" [dependencies.cast] default-features = false diff --git a/src/adc.rs b/src/adc.rs new file mode 100644 index 0000000..0e16b65 --- /dev/null +++ b/src/adc.rs @@ -0,0 +1,149 @@ +//! Analog to Digital Converter + +use core::marker::Unsize; + +use cast::u16; +use hal::prelude::*; +use static_ref::Ref; + +use dma::{self, CircBuffer, Dma1Channel1}; +use stm32f103xx::{ADC1, DMA1, GPIOA, RCC, TIM2}; +use {Channel, Pwm}; + +/// ADC Channel 1 (PA1) +pub struct Adc1<'a>(pub &'a ADC1); + +impl<'a> Adc1<'a> { + /// Initializes the ADC + /// + /// NOTE `Pwm<TIM2>.init` must be called before this method because both + /// methods configure the PA1 pin (one as input and the other as output :-/) + pub fn init(&self, dma1: &DMA1, gpioa: &GPIOA, rcc: &RCC) { + let adc1 = self.0; + + // enable ADC1, DMA1, GPIOA, TIM2 + rcc.ahbenr.modify(|_, w| w.dma1en().enabled()); + rcc.apb1enr.modify(|_, w| w.tim2en().enabled()); + rcc.apb2enr + .modify(|_, w| w.adc1en().enabled().iopaen().enabled()); + + // Set PA1 as analog input + gpioa.crl.modify(|_, w| w.cnf1().bits(0b00).mode1().input()); + + // Sample only the channel 1 + adc1.sqr1.modify(|_, w| unsafe { w.l().bits(1) }); + adc1.sqr3.modify(|_, w| unsafe { w.sq1().bits(1) }); + + // Sample time: 55.5 + 12.5 = 68 cycles + adc1.smpr2.modify(|_, w| unsafe { w.smp1().bits(0b101) }); + + // ADC1 + // mem2mem: Memory to memory mode disabled + // pl: Medium priority + // msize: Memory size = 16 bits + // psize: Peripheral size = 16 bits + // minc: Memory increment mode enabled + // pinc: Peripheral increment mode disabled + // circ: Circular mode enabled + // dir: Transfer from peripheral to memory + // htie: Half transfer interrupt enabled + // tceie: Transfer complete interrupt enabled + // en: Disabled + dma1.ccr1.write(|w| unsafe { + w.mem2mem() + .clear() + .pl() + .bits(0b01) + .msize() + .bits(0b01) + .psize() + .bits(0b01) + .minc() + .set() + .pinc() + .clear() + .circ() + .set() + .dir() + .clear() + .htie() + .set() + .tcie() + .set() + .en() + .clear() + }); + + // exttrig: Conversion on external event enabled + // extsel: Timer 2 CC2 event + // align: Right alignment + // dma: DMA mode enabled + // cont: Single conversion mode + // adon: Disable ADC conversion + adc1.cr2.write(|w| unsafe { + w.exttrig() + .set() + .extsel() + .bits(0b011) // T2C2 + // .bits(0b111) // swstart + .align() + .clear() + .dma() + .set() + .cont() + .clear() + .adon() + .clear() + }); + } + + /// Disables the ADC + pub fn disable(&self) { + self.0.cr2.modify(|_, w| w.adon().clear()); + } + + /// Enables the ADC + pub fn enable(&self) { + self.0.cr2.modify(|_, w| w.adon().set()); + } + + /// Starts an analog to digital conversion that will be periodically + /// triggered by the channel 2 of TIM2 + /// + /// The conversions will be stored in the circular `buffer` + pub fn start<B>( + &self, + buffer: Ref<CircBuffer<u16, B, Dma1Channel1>>, + dma1: &DMA1, + pwm: Pwm<TIM2>, + ) -> Result<(), dma::Error> + where + B: Unsize<[u16]>, + { + let adc1 = self.0; + + + if dma1.ccr1.read().en().is_set() { + return Err(dma::Error::InUse); + } + + pwm.disable(Channel::_2); + pwm.set_duty(Channel::_2, 1); + + let buffer: &[u16] = &buffer.lock()[0]; + + dma1.cndtr1 + .write(|w| unsafe { w.ndt().bits(u16(buffer.len() * 2).unwrap()) }); + + dma1.cpar1 + .write(|w| unsafe { w.bits(&adc1.dr as *const _ as u32) }); + + dma1.cmar1 + .write(|w| unsafe { w.bits(buffer.as_ptr() as u32) }); + + dma1.ccr1.modify(|_, w| w.en().set()); + pwm.enable(Channel::_2); + + Ok(()) + } +} diff --git a/src/dma.rs b/src/dma.rs index c47fbcf..d4f6f31 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -1,21 +1,30 @@ //! Direct Memory Access (DMA) use core::cell::{Cell, UnsafeCell}; -use core::marker::PhantomData; -use core::ops; +use core::marker::{PhantomData, Unsize}; +use core::{ops, slice}; use nb; use stm32f103xx::DMA1; +use volatile_register::RO; /// DMA error #[derive(Debug)] pub enum Error { /// DMA channel in use InUse, + /// Previous data got overwritten before it could be read because it was + /// not accessed in a timely fashion + Overrun, /// Transfer error Transfer, } +/// Channel 1 of DMA1 +pub struct Dma1Channel1 { + _0: (), +} + /// Channel 2 of DMA1 pub struct Dma1Channel2 { _0: (), @@ -249,3 +258,116 @@ impl<T> Buffer<T, Dma1Channel5> { } } } + +/// A circular buffer associated to a DMA `CHANNEL` +pub struct CircBuffer<T, B, CHANNEL> +where + B: Unsize<[T]>, +{ + _marker: PhantomData<CHANNEL>, + _t: PhantomData<[T]>, + buffer: UnsafeCell<[B; 2]>, + status: Cell<CircStatus>, +} + +impl<T, B, CHANNEL> CircBuffer<T, B, CHANNEL> +where + B: Unsize<[T]>, +{ + pub(crate) fn lock(&self) -> &[B; 2] { + assert_eq!(self.status.get(), CircStatus::Free); + + self.status.set(CircStatus::MutatingFirstHalf); + + unsafe { &*self.buffer.get() } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum CircStatus { + /// Not in use by the DMA + Free, + /// The DMA is mutating the first half of the buffer + MutatingFirstHalf, + /// The DMA is mutating the second half of the buffer + MutatingSecondHalf, +} + +impl<T, B> CircBuffer<T, B, Dma1Channel1> +where + B: Unsize<[T]>, + T: Atomic, +{ + /// Constructs a circular buffer from two halves + pub const fn new(buffer: [B; 2]) -> Self { + CircBuffer { + _t: PhantomData, + _marker: PhantomData, + buffer: UnsafeCell::new(buffer), + status: Cell::new(CircStatus::Free), + } + } + + /// Yields read access to the half of the circular buffer that's not + /// currently being mutated by the DMA + pub fn read(&self, dma1: &DMA1) -> nb::Result<&[RO<T>], Error> { + let status = self.status.get(); + + assert_ne!(status, CircStatus::Free); + + let isr = dma1.isr.read(); + + if isr.teif1().is_set() { + Err(nb::Error::Other(Error::Transfer)) + } else { + match status { + CircStatus::MutatingFirstHalf => { + if isr.tcif1().is_set() { + Err(nb::Error::Other(Error::Overrun)) + } else if isr.htif1().is_set() { + dma1.ifcr.write(|w| w.chtif1().set()); + + self.status.set(CircStatus::MutatingSecondHalf); + + unsafe { + let half: &[T] = &(*self.buffer.get())[0]; + Ok(slice::from_raw_parts( + half.as_ptr() as *const _, + half.len(), + )) + } + } else { + Err(nb::Error::WouldBlock) + } + } + CircStatus::MutatingSecondHalf => { + if isr.htif1().is_set() { + Err(nb::Error::Other(Error::Overrun)) + } else if isr.tcif1().is_set() { + dma1.ifcr.write(|w| w.ctcif1().set()); + + self.status.set(CircStatus::MutatingFirstHalf); + + unsafe { + let half: &[T] = &(*self.buffer.get())[1]; + Ok(slice::from_raw_parts( + half.as_ptr() as *const _, + half.len(), + )) + } + } else { + Err(nb::Error::WouldBlock) + } + } + _ => unreachable!(), + } + } + } +} + +/// Values that can be atomically read +pub trait Atomic: Copy {} + +impl Atomic for u8 {} +impl Atomic for u16 {} +impl Atomic for u32 {} diff --git a/src/lib.rs b/src/lib.rs index ccf869a..fd8c0a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,9 +23,11 @@ extern crate either; extern crate embedded_hal as hal; extern crate nb; extern crate static_ref; +extern crate volatile_register; pub extern crate stm32f103xx; +pub mod adc; pub mod capture; pub mod dma; pub mod gpio; diff --git a/src/pwm.rs b/src/pwm.rs index e9cdcc0..4d48d7e 100644 --- a/src/pwm.rs +++ b/src/pwm.rs @@ -392,7 +392,7 @@ where // pinc: Peripheral increment mode disabled // circ: Circular mode disabled // dir: Transfer from memory to peripheral - // tceie: Transfer complete interrupt disabled + // tceie: Transfer complete interrupt enabled // en: Disabled dma1.ccr2.write(|w| unsafe { w.mem2mem() -- GitLab