diff --git a/CHANGELOG.md b/CHANGELOG.md index 438b9e4..7281571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/examples/adc_values.rs b/examples/adc_values.rs new file mode 100644 index 0000000..12ed5f9 --- /dev/null +++ b/examples/adc_values.rs @@ -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, +} + +static SHARED: Mutex>> = 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(); + } + }); +} diff --git a/src/adc.rs b/src/adc.rs index ea8dac9..e2ae5e2 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -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 + /// tSTART 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 tSTART 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>(&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() {