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

Commit

Permalink
ADC + DMA API
Browse files Browse the repository at this point in the history
  • Loading branch information
japaric committed Jun 22, 2017
1 parent c5252ee commit 23d83ba
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 3 deletions.
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

0 comments on commit 23d83ba

Please sign in to comment.