Initial test implementation of ADC helper functions (#22)

Added ADC helper functions to read more intuitive values

Co-Authored-By: HarkonenBade <github@harkonen.net>
This commit is contained in:
Tom 2018-12-29 20:46:54 +00:00 committed by Daniel Egger
parent 11ff76c315
commit f6155f99a5
3 changed files with 277 additions and 5 deletions

View File

@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- Added ADC helper functions to read more intuitive values (#22) - @HarkonenBade
## [v0.10.1] - 2018-12-25
### Added

95
examples/adc_values.rs Normal file
View File

@ -0,0 +1,95 @@
#![no_main]
#![no_std]
#[allow(unused)]
use panic_halt;
use stm32f0xx_hal as hal;
use crate::hal::prelude::*;
use crate::hal::stm32;
use cortex_m::{interrupt::Mutex, peripheral::syst::SystClkSource::Core};
use cortex_m_rt::{entry, exception};
use core::fmt::Write;
use core::cell::RefCell;
struct Shared {
adc: hal::adc::Adc,
tx: hal::serial::Tx<stm32::USART1>,
}
static SHARED: Mutex<RefCell<Option<Shared>>> = Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
if let (Some(p), Some(cp)) = (
hal::stm32::Peripherals::take(),
cortex_m::peripheral::Peripherals::take(),
) {
let gpioa = p.GPIOA.split();
let rcc = p.RCC.constrain();
let clocks = rcc.cfgr.sysclk(8.mhz()).freeze();
let mut syst = cp.SYST;
// Set source for SysTick counter, here full operating frequency (== 8MHz)
syst.set_clock_source(Core);
// Set reload value, i.e. timer delay 8 MHz/counts
syst.set_reload(8_000_000 - 1);
// Start SysTick counter
syst.enable_counter();
// Start SysTick interrupt generation
syst.enable_interrupt();
// USART1 at PA9 (TX) and PA10(RX)
let tx = gpioa.pa9.into_alternate_af1();
let rx = gpioa.pa10.into_alternate_af1();
// Initialiase UART
let (mut tx, _) =
hal::serial::Serial::usart1(p.USART1, (tx, rx), 115_200.bps(), clocks).split();
// Initialise ADC
let adc = hal::adc::Adc::new(p.ADC);
// Output a friendly greeting
tx.write_str("\n\rThis ADC example will read various values using the ADC and print them out to the serial terminal\r\n").ok();
// Move all components under Mutex supervision
cortex_m::interrupt::free(move |cs| {
*SHARED.borrow(cs).borrow_mut() = Some(Shared {
adc,
tx,
});
});
}
loop {
continue;
}
}
#[exception]
fn SysTick() -> ! {
use core::ops::DerefMut;
// Enter critical section
cortex_m::interrupt::free(|cs| {
// Get access to the Mutex protected shared data
if let Some(ref mut shared) = SHARED.borrow(cs).borrow_mut().deref_mut() {
// Read temperature data from internal sensor using ADC
let t = hal::adc::VTemp::read(&mut shared.adc, None);
writeln!(shared.tx, "Temperature {}.{}C\r", t/100, t%100).ok();
// Read volatage reference data from internal sensor using ADC
let t = hal::adc::VRef::read_vdda(&mut shared.adc);
writeln!(shared.tx, "Vdda {}mV\r", t).ok();
}
});
}

View File

@ -34,10 +34,28 @@
//! ```
#[cfg(feature = "device-selected")]
use embedded_hal::adc::{Channel, OneShot};
const VREFCAL: *const u16 = 0x1FFF_F7BA as *const u16;
#[cfg(feature = "device-selected")]
use crate::{stm32, gpio::*};
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
@ -48,7 +66,7 @@ pub struct Adc {
precision: AdcPrecision,
}
#[derive(Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
/// ADC Sampling time
///
/// Options for the sampling time, each is T + 0.5 ADC clock cycles.
@ -96,7 +114,7 @@ impl AdcSampleTime {
}
}
#[derive(Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
/// ADC Result Alignment
pub enum AdcAlign {
/// Left aligned results (most significant bits)
@ -137,7 +155,7 @@ impl AdcAlign {
}
}
#[derive(Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
/// ADC Sampling Precision
pub enum AdcPrecision {
/// 12 bit precision
@ -241,6 +259,59 @@ impl VTemp {
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 = (vtemp as i32) * 100;
temperature = (temperature * (vdda as i32) / (VDD_CALIB as i32)) - 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")]
@ -259,6 +330,34 @@ impl VRef {
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);
((VDD_CALIB as u32) * vrefint_cal / vref_val) as u16
}
}
#[cfg(feature = "stm32f042")]
@ -288,8 +387,35 @@ impl VBat {
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
@ -309,6 +435,28 @@ impl Adc {
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).
@ -330,6 +478,32 @@ impl Adc {
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 => u8::max_value() as u16,
_ => 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 = VRef::read_vdda(self) as u32;
let v: u32 = self.read(pin).unwrap();
let max_samp = self.max_sample() as u32;
(v * vdda / max_samp) as u16
}
fn calibrate(&mut self) {
/* Ensure that ADEN = 0 */
if self.rb.cr.read().aden().bit_is_set() {