You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
17 KiB
Rust
587 lines
17 KiB
Rust
//! # 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);
|
|
//! }
|
|
//! ```
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
const VREFCAL: *const u16 = 0x1FFF_F7BA as *const u16;
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
const VTEMPCAL30: *const u16 = 0x1FFF_F7B8 as *const u16;
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
const VTEMPCAL110: *const u16 = 0x1FFF_F7C2 as *const u16;
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
const VDD_CALIB: u16 = 3300;
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
use core::ptr;
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
use embedded_hal::{
|
|
adc::{Channel, OneShot},
|
|
blocking::delay::DelayUs,
|
|
};
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
use crate::{delay::Delay, gpio::*, stm32};
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
/// Analog to Digital converter interface
|
|
pub struct Adc {
|
|
rb: stm32::ADC,
|
|
sample_time: AdcSampleTime,
|
|
align: AdcAlign,
|
|
precision: AdcPrecision,
|
|
}
|
|
|
|
#[derive(Clone, Copy, 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,
|
|
}
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
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(Clone, Copy, 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,
|
|
}
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
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(Clone, Copy, 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,
|
|
}
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
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
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
macro_rules! adc_pins {
|
|
($($pin:ty => $chan:expr),+ $(,)*) => {
|
|
$(
|
|
impl Channel<Adc> for $pin {
|
|
type ID = u8;
|
|
|
|
fn channel() -> u8 { $chan }
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
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",
|
|
feature = "stm32f072",
|
|
feature = "stm32f091"
|
|
))]
|
|
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, Default)]
|
|
/// Internal temperature sensor (ADC Channel 16)
|
|
pub struct VTemp;
|
|
|
|
#[derive(Debug, Default)]
|
|
/// Internal voltage reference (ADC Channel 17)
|
|
pub struct VRef;
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
adc_pins!(
|
|
VTemp => 16_u8,
|
|
VRef => 17_u8,
|
|
);
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
impl VTemp {
|
|
/// Init a new VTemp
|
|
pub fn new() -> Self {
|
|
VTemp::default()
|
|
}
|
|
|
|
/// 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());
|
|
}
|
|
|
|
/// Checks if the temperature sensor is enabled, does not account for the
|
|
/// t<sub>START</sub> time however.
|
|
pub fn is_enabled(&self, adc: &Adc) -> bool {
|
|
adc.rb.ccr.read().tsen().bit_is_set()
|
|
}
|
|
|
|
fn convert_temp(vtemp: u16, vdda: u16) -> i16 {
|
|
let vtemp30_cal = i32::from(unsafe { ptr::read(VTEMPCAL30) }) * 100;
|
|
let vtemp110_cal = i32::from(unsafe { ptr::read(VTEMPCAL110) }) * 100;
|
|
|
|
let mut temperature = i32::from(vtemp) * 100;
|
|
temperature = (temperature * (i32::from(vdda) / i32::from(VDD_CALIB))) - vtemp30_cal;
|
|
temperature *= (110 - 30) * 100;
|
|
temperature /= vtemp110_cal - vtemp30_cal;
|
|
temperature += 3000;
|
|
temperature as i16
|
|
}
|
|
|
|
/// Read the value of the internal temperature sensor and return the
|
|
/// result in 100ths of a degree centigrade.
|
|
///
|
|
/// Given a delay reference it will attempt to restrict to the
|
|
/// minimum delay needed to ensure a 10 us t<sub>START</sub> value.
|
|
/// Otherwise it will approximate the required delay using ADC reads.
|
|
pub fn read(adc: &mut Adc, delay: Option<&mut Delay>) -> i16 {
|
|
let mut vtemp = Self::new();
|
|
let vtemp_preenable = vtemp.is_enabled(&adc);
|
|
|
|
if !vtemp_preenable {
|
|
vtemp.enable(adc);
|
|
|
|
if let Some(dref) = delay {
|
|
dref.delay_us(2_u16);
|
|
} else {
|
|
// Double read of vdda to allow sufficient startup time for the temp sensor
|
|
VRef::read_vdda(adc);
|
|
}
|
|
}
|
|
let vdda = VRef::read_vdda(adc);
|
|
|
|
let prev_cfg = adc.default_cfg();
|
|
|
|
let vtemp_val = adc.read(&mut vtemp).unwrap();
|
|
|
|
if !vtemp_preenable {
|
|
vtemp.disable(adc);
|
|
}
|
|
|
|
adc.restore_cfg(prev_cfg);
|
|
|
|
Self::convert_temp(vtemp_val, vdda)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
impl VRef {
|
|
/// Init a new VRef
|
|
pub fn new() -> Self {
|
|
VRef::default()
|
|
}
|
|
|
|
/// 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());
|
|
}
|
|
|
|
/// Returns if the internal voltage reference is enabled.
|
|
pub fn is_enabled(&self, adc: &Adc) -> bool {
|
|
adc.rb.ccr.read().vrefen().bit_is_set()
|
|
}
|
|
|
|
/// Reads the value of VDDA in milli-volts
|
|
pub fn read_vdda(adc: &mut Adc) -> u16 {
|
|
let vrefint_cal = u32::from(unsafe { ptr::read(VREFCAL) });
|
|
let mut vref = Self::new();
|
|
|
|
let prev_cfg = adc.default_cfg();
|
|
|
|
let vref_val: u32 = if vref.is_enabled(&adc) {
|
|
adc.read(&mut vref).unwrap()
|
|
} else {
|
|
vref.enable(adc);
|
|
|
|
let ret = adc.read(&mut vref).unwrap();
|
|
|
|
vref.disable(adc);
|
|
ret
|
|
};
|
|
|
|
adc.restore_cfg(prev_cfg);
|
|
|
|
(u32::from(VDD_CALIB) * vrefint_cal / vref_val) as u16
|
|
}
|
|
}
|
|
|
|
#[cfg(any(feature = "stm32f042", feature = "stm32f072", feature = "stm32f091"))]
|
|
#[derive(Debug, Default)]
|
|
/// Battery reference voltage (ADC Channel 18)
|
|
pub struct VBat;
|
|
|
|
#[cfg(any(feature = "stm32f042", feature = "stm32f072", feature = "stm32f091"))]
|
|
adc_pins!(
|
|
VBat => 18_u8,
|
|
);
|
|
|
|
#[cfg(any(feature = "stm32f042", feature = "stm32f072", feature = "stm32f091"))]
|
|
impl VBat {
|
|
/// Init a new VBat
|
|
pub fn new() -> Self {
|
|
VBat::default()
|
|
}
|
|
|
|
/// 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());
|
|
}
|
|
|
|
/// Returns if the internal VBat sense is enabled
|
|
pub fn is_enabled(&self, adc: &Adc) -> bool {
|
|
adc.rb.ccr.read().vbaten().bit_is_set()
|
|
}
|
|
|
|
/// Reads the value of VBat in milli-volts
|
|
pub fn read(adc: &mut Adc) -> u16 {
|
|
let mut vbat = Self::new();
|
|
|
|
let vbat_val: u16 = if vbat.is_enabled(&adc) {
|
|
adc.read_abs_mv(&mut vbat)
|
|
} else {
|
|
vbat.enable(adc);
|
|
|
|
let ret = adc.read_abs_mv(&mut vbat);
|
|
|
|
vbat.disable(adc);
|
|
ret
|
|
};
|
|
|
|
vbat_val * 2
|
|
}
|
|
}
|
|
|
|
/// A stored ADC config, can be restored by using the `Adc::restore_cfg` method
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub struct StoredConfig(AdcSampleTime, AdcAlign, AdcPrecision);
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
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
|
|
}
|
|
|
|
/// Saves a copy of the current ADC config
|
|
pub fn save_cfg(&mut self) -> StoredConfig {
|
|
StoredConfig(self.sample_time, self.align, self.precision)
|
|
}
|
|
|
|
/// Restores a stored config
|
|
pub fn restore_cfg(&mut self, cfg: StoredConfig) {
|
|
self.sample_time = cfg.0;
|
|
self.align = cfg.1;
|
|
self.precision = cfg.2;
|
|
}
|
|
|
|
/// Resets the ADC config to default, returning the existing config as
|
|
/// a stored config.
|
|
pub fn default_cfg(&mut self) -> StoredConfig {
|
|
let cfg = self.save_cfg();
|
|
self.sample_time = AdcSampleTime::default();
|
|
self.align = AdcAlign::default();
|
|
self.precision = AdcPrecision::default();
|
|
cfg
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
/// Returns the largest possible sample value for the current settings
|
|
pub fn max_sample(&self) -> u16 {
|
|
match self.align {
|
|
AdcAlign::Left => u16::max_value(),
|
|
AdcAlign::LeftAsRM => match self.precision {
|
|
AdcPrecision::B_6 => u16::from(u8::max_value()),
|
|
_ => u16::max_value(),
|
|
},
|
|
AdcAlign::Right => match self.precision {
|
|
AdcPrecision::B_12 => (1 << 12) - 1,
|
|
AdcPrecision::B_10 => (1 << 10) - 1,
|
|
AdcPrecision::B_8 => (1 << 8) - 1,
|
|
AdcPrecision::B_6 => (1 << 6) - 1,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Read the value of a channel and converts the result to milli-volts
|
|
pub fn read_abs_mv<PIN: Channel<Adc, ID = u8>>(&mut self, pin: &mut PIN) -> u16 {
|
|
let vdda = u32::from(VRef::read_vdda(self));
|
|
let v: u32 = self.read(pin).unwrap();
|
|
let max_samp = u32::from(self.max_sample());
|
|
|
|
(v * vdda / max_samp) as u16
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "device-selected")]
|
|
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())
|
|
}
|
|
}
|