Implementation of ADC interface

Base implementation of ADC functionality according to the embedded hal
traits for `Channel` and `OneShot`. Also provides references for
converting the internal temperature sensor and voltage reference values,
also the VBat reference where available.

Closes #11.
This commit is contained in:
HarkonenBade 2018-12-20 17:38:50 +00:00
parent 0daa4df1c9
commit 0fa7a2012a
6 changed files with 478 additions and 1 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added Sync & Send ability to Pin
- Added overflow guards to delay
- Added initial implementation of an ADC interface (#13) - @HarkonenBade
## [v0.10.0] - 2018-12-23

56
examples/blinky_adc.rs Normal file
View File

@ -0,0 +1,56 @@
#![no_main]
#![no_std]
#[allow(unused_imports)]
use panic_halt;
use stm32f0xx_hal as hal;
use crate::hal::delay::Delay;
use crate::hal::prelude::*;
use crate::hal::stm32;
use crate::hal::adc::Adc;
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
if let (Some(p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) {
let gpioa = p.GPIOA.split();
/* (Re-)configure PA1 as output */
let mut led = gpioa.pa1.into_push_pull_output();
/* (Re-)configure PA0 as analog in */
let mut an_in = gpioa.pa0.into_analog();
/* Constrain clocking registers */
let rcc = p.RCC.constrain();
/* Configure clock to 8 MHz (i.e. the default) and freeze it */
let clocks = rcc.cfgr.sysclk(8.mhz()).freeze();
/* Get delay provider */
let mut delay = Delay::new(cp.SYST, clocks);
let mut adc = Adc::new(p.ADC);
loop {
led.toggle();
let val: u16 = adc.read(&mut an_in).unwrap();
/* shift the value right by 3, same as divide by 8, reduces
the 0-4095 range into something approximating 1-512 */
let time: u16 = (val >> 3) + 1;
delay.delay_ms(time);
}
}
loop {
continue;
}
}

397
src/adc.rs Normal file
View File

@ -0,0 +1,397 @@
//! # API for the Analog to Digital converter
//!
//! Currently implements oneshot conversion with variable sampling times.
//! Also references for the internal temperature sense, voltage
//! reference and battery sense are provided.
//!
//! ## Example
//! ``` no_run
//! use stm32f0xx_hal as hal;
//!
//! use crate::hal::stm32;
//! use crate::hal::prelude::*;
//! use crate::hal::adc::Adc;
//!
//! let mut p = stm32::Peripherals::take().unwrap();
//!
//! let mut led = gpioa.pa1.into_push_pull_pull_output();
//! let mut an_in = gpioa.pa0.into_analog();
//!
//! let rcc = p.RCC.constrain().cfgr.freeze();
//! let mut delay = Delay::new(cp.SYST, clocks);
//!
//! let mut adc = Adc::new(p.ADC);
//!
//! loop {
//! let val: u16 = adc.read(&mut an_in).unwrap();
//! if val < ((1 << 8) - 1) {
//! led.set_low();
//! } else {
//! led.set_high();
//! }
//! delay.delay_ms(50_u16);
//! }
//! ```
use embedded_hal::adc::{Channel, OneShot};
use crate::stm32;
use crate::gpio::*;
/// Analog to Digital converter interface
pub struct Adc {
rb: stm32::ADC,
sample_time: AdcSampleTime,
align: AdcAlign,
precision: AdcPrecision,
}
#[derive(Debug, PartialEq)]
/// ADC Sampling time
///
/// Options for the sampling time, each is T + 0.5 ADC clock cycles.
pub enum AdcSampleTime {
/// 1.5 cycles sampling time
T_1,
/// 7.5 cycles sampling time
T_7,
/// 13.5 cycles sampling time
T_13,
/// 28.5 cycles sampling time
T_28,
/// 41.5 cycles sampling time
T_41,
/// 55.5 cycles sampling time
T_55,
/// 71.5 cycles sampling time
T_71,
/// 239.5 cycles sampling time
T_239,
}
impl AdcSampleTime {
fn write_bits(&self, adc: &mut stm32::ADC) {
unsafe {
adc.smpr.write(|w| {
w.smpr().bits(match self {
AdcSampleTime::T_1 => 0b000_u8,
AdcSampleTime::T_7 => 0b001_u8,
AdcSampleTime::T_13 => 0b010_u8,
AdcSampleTime::T_28 => 0b011_u8,
AdcSampleTime::T_41 => 0b100_u8,
AdcSampleTime::T_55 => 0b101_u8,
AdcSampleTime::T_71 => 0b110_u8,
AdcSampleTime::T_239 => 0b111_u8,
})
});
}
}
/// Get the default sample time (currently 239.5 cycles)
pub fn default() -> Self {
AdcSampleTime::T_239
}
}
#[derive(Debug, PartialEq)]
/// ADC Result Alignment
pub enum AdcAlign {
/// Left aligned results (most significant bits)
///
/// Results in all precisions returning a value in the range 0-65535.
/// Depending on the precision the result will step by larger or smaller
/// amounts.
Left,
/// Right aligned results (least significant bits)
///
/// Results in all precisions returning values from 0-(2^bits-1) in
/// steps of 1.
Right,
/// Left aligned results without correction of 6bit values.
///
/// Returns left aligned results exactly as shown in RM0091 Fig.37.
/// Where the values are left aligned within the u16, with the exception
/// of 6 bit mode where the value is left aligned within the first byte of
/// the u16.
LeftAsRM,
}
impl AdcAlign {
fn write_bits(&self, adc: &mut stm32::ADC) {
adc.cfgr1.write(|w| {
w.align().bit(match self {
AdcAlign::Left => true,
AdcAlign::Right => false,
AdcAlign::LeftAsRM => true,
})
});
}
/// Get the default alignment (currently right aligned)
pub fn default() -> Self {
AdcAlign::Right
}
}
#[derive(Debug, PartialEq)]
/// ADC Sampling Precision
pub enum AdcPrecision {
/// 12 bit precision
B_12,
/// 10 bit precision
B_10,
/// 8 bit precision
B_8,
/// 6 bit precision
B_6,
}
impl AdcPrecision {
fn write_bits(&self, adc: &mut stm32::ADC) {
unsafe {
adc.cfgr1.write(|w| {
w.res().bits(match self {
AdcPrecision::B_12 => 0b00_u8,
AdcPrecision::B_10 => 0b01_u8,
AdcPrecision::B_8 => 0b10_u8,
AdcPrecision::B_6 => 0b11_u8,
})
});
}
}
/// Get the default precision (currently 12 bit precision)
pub fn default() -> Self {
AdcPrecision::B_12
}
}
macro_rules! adc_pins {
($($pin:ty => $chan:expr),+ $(,)*) => {
$(
impl Channel<Adc> for $pin {
type ID = u8;
fn channel() -> u8 { $chan }
}
)+
};
}
#[cfg(any(feature = "stm32f042", feature = "stm32f030", feature = "stm32f070",))]
adc_pins!(
gpioa::PA0<Analog> => 0_u8,
gpioa::PA1<Analog> => 1_u8,
gpioa::PA2<Analog> => 2_u8,
gpioa::PA3<Analog> => 3_u8,
gpioa::PA4<Analog> => 4_u8,
gpioa::PA5<Analog> => 5_u8,
gpioa::PA6<Analog> => 6_u8,
gpioa::PA7<Analog> => 7_u8,
gpiob::PB0<Analog> => 8_u8,
gpiob::PB1<Analog> => 9_u8,
);
#[cfg(any(feature = "stm32f030", feature = "stm32f070",))]
adc_pins!(
gpioc::PC0<Analog> => 10_u8,
gpioc::PC1<Analog> => 11_u8,
gpioc::PC2<Analog> => 12_u8,
gpioc::PC3<Analog> => 13_u8,
gpioc::PC4<Analog> => 14_u8,
gpioc::PC5<Analog> => 15_u8,
);
#[derive(Debug)]
/// Internal temperature sensor (ADC Channel 16)
pub struct VTemp;
#[derive(Debug)]
/// Internal voltage reference (ADC Channel 17)
pub struct VRef;
adc_pins!(
VTemp => 16_u8,
VRef => 17_u8,
);
impl VTemp {
/// Init a new VTemp
pub fn new() -> Self {
VTemp {}
}
/// Enable the internal temperature sense, this has a wake up time
/// t<sub>START</sub> which can be found in your micro's datasheet, you
/// must wait at least that long after enabling before taking a reading.
/// Remember to disable when not in use.
pub fn enable(&mut self, adc: &mut Adc) {
adc.rb.ccr.modify(|_, w| w.tsen().set_bit());
}
/// Disable the internal temperature sense.
pub fn disable(&mut self, adc: &mut Adc) {
adc.rb.ccr.modify(|_, w| w.tsen().clear_bit());
}
}
impl VRef {
/// Init a new VRef
pub fn new() -> Self {
VRef {}
}
/// Enable the internal voltage reference, remember to disable when not in use.
pub fn enable(&mut self, adc: &mut Adc) {
adc.rb.ccr.modify(|_, w| w.vrefen().set_bit());
}
/// Disable the internal reference voltage.
pub fn disable(&mut self, adc: &mut Adc) {
adc.rb.ccr.modify(|_, w| w.vrefen().clear_bit());
}
}
#[cfg(any(feature = "stm32f042",))]
#[derive(Debug)]
/// Battery reference voltage (ADC Channel 18)
pub struct VBat;
#[cfg(any(feature = "stm32f042",))]
adc_pins!(
VBat => 18_u8,
);
#[cfg(any(feature = "stm32f042",))]
impl VBat {
/// Init a new VBat
pub fn new() -> Self {
VBat {}
}
/// Enable the internal VBat sense, remember to disable when not in use
/// as otherwise it will sap current from the VBat source.
pub fn enable(&mut self, adc: &mut Adc) {
adc.rb.ccr.modify(|_, w| w.vbaten().set_bit());
}
/// Disable the internal VBat sense.
pub fn disable(&mut self, adc: &mut Adc) {
adc.rb.ccr.modify(|_, w| w.vbaten().clear_bit());
}
}
impl Adc {
/// Init a new Adc
///
/// Sets all configurable parameters to defaults, enables the HSI14 clock
/// for the ADC if it is not already enabled and performs a boot time
/// calibration. As such this method may take an appreciable time to run.
pub fn new(adc: stm32::ADC) -> Self {
let mut s = Self {
rb: adc,
sample_time: AdcSampleTime::default(),
align: AdcAlign::default(),
precision: AdcPrecision::default(),
};
s.select_clock();
s.calibrate();
s
}
/// Set the Adc sampling time
///
/// Options can be found in [AdcSampleTime](crate::adc::AdcSampleTime).
pub fn set_sample_time(&mut self, t_samp: AdcSampleTime) {
self.sample_time = t_samp;
}
/// Set the Adc result alignment
///
/// Options can be found in [AdcAlign](crate::adc::AdcAlign).
pub fn set_align(&mut self, align: AdcAlign) {
self.align = align;
}
/// Set the Adc precision
///
/// Options can be found in [AdcPrecision](crate::adc::AdcPrecision).
pub fn set_precision(&mut self, precision: AdcPrecision) {
self.precision = precision;
}
fn calibrate(&mut self) {
/* Ensure that ADEN = 0 */
if self.rb.cr.read().aden().bit_is_set() {
/* Clear ADEN by setting ADDIS */
self.rb.cr.modify(|_, w| w.addis().set_bit());
}
while self.rb.cr.read().aden().bit_is_set() {}
/* Clear DMAEN */
self.rb.cfgr1.modify(|_, w| w.dmaen().clear_bit());
/* Start calibration by setting ADCAL */
self.rb.cr.modify(|_, w| w.adcal().set_bit());
/* Wait until calibration is finished and ADCAL = 0 */
while self.rb.cr.read().adcal().bit_is_set() {}
}
fn select_clock(&mut self) {
let rcc = unsafe { &*stm32::RCC::ptr() };
rcc.apb2enr.modify(|_, w| w.adcen().set_bit());
rcc.cr2.write(|w| w.hsi14on().set_bit());
while rcc.cr2.read().hsi14rdy().bit_is_clear() {}
}
fn power_up(&mut self) {
if self.rb.isr.read().adrdy().bit_is_set() {
self.rb.isr.modify(|_, w| w.adrdy().clear_bit());
}
self.rb.cr.modify(|_, w| w.aden().set_bit());
while self.rb.isr.read().adrdy().bit_is_clear() {}
}
fn power_down(&mut self) {
self.rb.cr.modify(|_, w| w.adstp().set_bit());
while self.rb.cr.read().adstp().bit_is_set() {}
self.rb.cr.modify(|_, w| w.addis().set_bit());
while self.rb.cr.read().aden().bit_is_set() {}
}
fn convert(&mut self, chan: u8) -> u16 {
self.rb.chselr.write(|w| unsafe { w.bits(1_u32 << chan) });
self.sample_time.write_bits(&mut self.rb);
self.align.write_bits(&mut self.rb);
self.precision.write_bits(&mut self.rb);
self.rb.cr.modify(|_, w| w.adstart().set_bit());
while self.rb.isr.read().eoc().bit_is_clear() {}
let res = self.rb.dr.read().bits() as u16;
if self.align == AdcAlign::Left && self.precision == AdcPrecision::B_6 {
res << 8
} else {
res
}
}
}
impl<WORD, PIN> OneShot<Adc, WORD, PIN> for Adc
where
WORD: From<u16>,
PIN: Channel<Adc, ID = u8>,
{
type Error = ();
fn read(&mut self, _pin: &mut PIN) -> nb::Result<WORD, Self::Error> {
self.power_up();
let res = self.convert(PIN::channel());
self.power_down();
Ok(res.into())
}
}

View File

@ -48,6 +48,9 @@ pub struct PullUp;
/// Open drain input or output (type state)
pub struct OpenDrain;
/// Analog mode (type state)
pub struct Analog;
/// Output mode (type state)
pub struct Output<MODE> {
_mode: PhantomData<MODE>,
@ -168,7 +171,7 @@ macro_rules! gpio {
use crate::stm32::RCC;
use super::{
Alternate, Floating, GpioExt, Input, OpenDrain, Output,
Alternate, Analog, Floating, GpioExt, Input, OpenDrain, Output,
PullDown, PullUp, PushPull, AF0, AF1, AF2, AF3, AF4, AF5, AF6, AF7,
Pin, GpioRegExt,
};
@ -338,6 +341,22 @@ macro_rules! gpio {
$PXi { _mode: PhantomData }
}
/// Configures the pin to operate as an analog pin
pub fn into_analog(
self,
) -> $PXi<Analog> {
let offset = 2 * $i;
unsafe {
&(*$GPIOX::ptr()).pupdr.modify(|r, w| {
w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset))
});
&(*$GPIOX::ptr()).moder.modify(|r, w| {
w.bits((r.bits() & !(0b11 << offset)) | (0b11 << offset))
});
}
$PXi { _mode: PhantomData }
}
/// Configures the pin to operate as an open drain output pin
pub fn into_open_drain_output(
self,

View File

@ -9,6 +9,7 @@ pub use stm32f0::stm32f0x2 as stm32;
#[cfg(any(feature = "stm32f030", feature = "stm32f070"))]
pub use stm32f0::stm32f0x0 as stm32;
#[cfg(not(any(
feature = "stm32f030",
feature = "stm32f042",
@ -16,6 +17,7 @@ pub use stm32f0::stm32f0x0 as stm32;
)))]
pub mod stm32 {}
pub mod adc;
pub mod delay;
pub mod gpio;
pub mod i2c;

View File

@ -3,6 +3,8 @@ pub use embedded_hal::prelude::*;
pub use embedded_hal::watchdog::Watchdog as _stm32f0xx_hal_embedded_hal_watchdog_Watchdog;
pub use embedded_hal::watchdog::WatchdogEnable as _stm32f0xx_hal_embedded_hal_watchdog_WatchdogEnable;
pub use embedded_hal::adc::OneShot as _embedded_hal_adc_OneShot;
pub use crate::gpio::GpioExt as _stm32f0xx_hal_gpio_GpioExt;
pub use crate::rcc::RccExt as _stm32f0xx_hal_rcc_RccExt;
pub use crate::time::U32Ext as _stm32f0xx_hal_time_U32Ext;