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:
parent
0daa4df1c9
commit
0fa7a2012a
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
21
src/gpio.rs
21
src/gpio.rs
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue