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:
parent
11ff76c315
commit
f6155f99a5
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
184
src/adc.rs
184
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
|
||||
/// 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() {
|
||||
|
|
Loading…
Reference in New Issue