Skip to content
This repository has been archived by the owner on Feb 13, 2019. It is now read-only.

ADC + DMA API #15

Merged
merged 1 commit into from
Jun 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
149 changes: 149 additions & 0 deletions src/adc.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
126 changes: 124 additions & 2 deletions src/dma.rs
Original file line number Diff line number Diff line change
@@ -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: (),
Expand Down Expand Up @@ -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 {}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/pwm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down