Skip to content

Commit f6155f9

Browse files
HarkonenBadetherealprof
authored andcommitted
Initial test implementation of ADC helper functions (#22)
Added ADC helper functions to read more intuitive values Co-Authored-By: HarkonenBade <github@harkonen.net>
1 parent 11ff76c commit f6155f9

File tree

3 files changed

+277
-5
lines changed

3 files changed

+277
-5
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
### Added
11+
- Added ADC helper functions to read more intuitive values (#22) - @HarkonenBade
12+
1013
## [v0.10.1] - 2018-12-25
1114

1215
### Added

examples/adc_values.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#![no_main]
2+
#![no_std]
3+
4+
#[allow(unused)]
5+
use panic_halt;
6+
7+
use stm32f0xx_hal as hal;
8+
9+
use crate::hal::prelude::*;
10+
use crate::hal::stm32;
11+
12+
use cortex_m::{interrupt::Mutex, peripheral::syst::SystClkSource::Core};
13+
use cortex_m_rt::{entry, exception};
14+
15+
use core::fmt::Write;
16+
17+
use core::cell::RefCell;
18+
19+
struct Shared {
20+
adc: hal::adc::Adc,
21+
tx: hal::serial::Tx<stm32::USART1>,
22+
}
23+
24+
static SHARED: Mutex<RefCell<Option<Shared>>> = Mutex::new(RefCell::new(None));
25+
26+
#[entry]
27+
fn main() -> ! {
28+
if let (Some(p), Some(cp)) = (
29+
hal::stm32::Peripherals::take(),
30+
cortex_m::peripheral::Peripherals::take(),
31+
) {
32+
let gpioa = p.GPIOA.split();
33+
let rcc = p.RCC.constrain();
34+
let clocks = rcc.cfgr.sysclk(8.mhz()).freeze();
35+
36+
let mut syst = cp.SYST;
37+
38+
// Set source for SysTick counter, here full operating frequency (== 8MHz)
39+
syst.set_clock_source(Core);
40+
41+
// Set reload value, i.e. timer delay 8 MHz/counts
42+
syst.set_reload(8_000_000 - 1);
43+
44+
// Start SysTick counter
45+
syst.enable_counter();
46+
47+
// Start SysTick interrupt generation
48+
syst.enable_interrupt();
49+
50+
// USART1 at PA9 (TX) and PA10(RX)
51+
let tx = gpioa.pa9.into_alternate_af1();
52+
let rx = gpioa.pa10.into_alternate_af1();
53+
54+
// Initialiase UART
55+
let (mut tx, _) =
56+
hal::serial::Serial::usart1(p.USART1, (tx, rx), 115_200.bps(), clocks).split();
57+
58+
// Initialise ADC
59+
let adc = hal::adc::Adc::new(p.ADC);
60+
61+
// Output a friendly greeting
62+
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();
63+
64+
// Move all components under Mutex supervision
65+
cortex_m::interrupt::free(move |cs| {
66+
*SHARED.borrow(cs).borrow_mut() = Some(Shared {
67+
adc,
68+
tx,
69+
});
70+
});
71+
}
72+
73+
loop {
74+
continue;
75+
}
76+
}
77+
78+
#[exception]
79+
fn SysTick() -> ! {
80+
use core::ops::DerefMut;
81+
82+
// Enter critical section
83+
cortex_m::interrupt::free(|cs| {
84+
// Get access to the Mutex protected shared data
85+
if let Some(ref mut shared) = SHARED.borrow(cs).borrow_mut().deref_mut() {
86+
// Read temperature data from internal sensor using ADC
87+
let t = hal::adc::VTemp::read(&mut shared.adc, None);
88+
writeln!(shared.tx, "Temperature {}.{}C\r", t/100, t%100).ok();
89+
90+
// Read volatage reference data from internal sensor using ADC
91+
let t = hal::adc::VRef::read_vdda(&mut shared.adc);
92+
writeln!(shared.tx, "Vdda {}mV\r", t).ok();
93+
}
94+
});
95+
}

src/adc.rs

Lines changed: 179 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,28 @@
3434
//! ```
3535
3636
#[cfg(feature = "device-selected")]
37-
use embedded_hal::adc::{Channel, OneShot};
37+
const VREFCAL: *const u16 = 0x1FFF_F7BA as *const u16;
3838

3939
#[cfg(feature = "device-selected")]
40-
use crate::{stm32, gpio::*};
40+
const VTEMPCAL30: *const u16 = 0x1FFF_F7B8 as *const u16;
41+
42+
#[cfg(feature = "device-selected")]
43+
const VTEMPCAL110: *const u16 = 0x1FFF_F7C2 as *const u16;
44+
45+
#[cfg(feature = "device-selected")]
46+
const VDD_CALIB: u16 = 3300;
47+
48+
#[cfg(feature = "device-selected")]
49+
use core::ptr;
50+
51+
#[cfg(feature = "device-selected")]
52+
use embedded_hal::{
53+
adc::{Channel, OneShot},
54+
blocking::delay::DelayUs,
55+
};
56+
57+
#[cfg(feature = "device-selected")]
58+
use crate::{delay::Delay, gpio::*, stm32};
4159

4260
#[cfg(feature = "device-selected")]
4361
/// Analog to Digital converter interface
@@ -48,7 +66,7 @@ pub struct Adc {
4866
precision: AdcPrecision,
4967
}
5068

51-
#[derive(Debug, PartialEq)]
69+
#[derive(Clone, Copy, Debug, PartialEq)]
5270
/// ADC Sampling time
5371
///
5472
/// Options for the sampling time, each is T + 0.5 ADC clock cycles.
@@ -96,7 +114,7 @@ impl AdcSampleTime {
96114
}
97115
}
98116

99-
#[derive(Debug, PartialEq)]
117+
#[derive(Clone, Copy, Debug, PartialEq)]
100118
/// ADC Result Alignment
101119
pub enum AdcAlign {
102120
/// Left aligned results (most significant bits)
@@ -137,7 +155,7 @@ impl AdcAlign {
137155
}
138156
}
139157

140-
#[derive(Debug, PartialEq)]
158+
#[derive(Clone, Copy, Debug, PartialEq)]
141159
/// ADC Sampling Precision
142160
pub enum AdcPrecision {
143161
/// 12 bit precision
@@ -241,6 +259,59 @@ impl VTemp {
241259
pub fn disable(&mut self, adc: &mut Adc) {
242260
adc.rb.ccr.modify(|_, w| w.tsen().clear_bit());
243261
}
262+
263+
/// Checks if the temperature sensor is enabled, does not account for the
264+
/// t<sub>START</sub> time however.
265+
pub fn is_enabled(&self, adc: &Adc) -> bool {
266+
adc.rb.ccr.read().tsen().bit_is_set()
267+
}
268+
269+
fn convert_temp(vtemp: u16, vdda: u16) -> i16 {
270+
let vtemp30_cal = i32::from(unsafe { ptr::read(VTEMPCAL30) }) * 100;
271+
let vtemp110_cal = i32::from(unsafe { ptr::read(VTEMPCAL110) }) * 100;
272+
273+
let mut temperature: i32 = (vtemp as i32) * 100;
274+
temperature = (temperature * (vdda as i32) / (VDD_CALIB as i32)) - vtemp30_cal;
275+
temperature *= (110 - 30) * 100;
276+
temperature /= vtemp110_cal - vtemp30_cal;
277+
temperature += 3000;
278+
temperature as i16
279+
}
280+
281+
/// Read the value of the internal temperature sensor and return the
282+
/// result in 100ths of a degree centigrade.
283+
///
284+
/// Given a delay reference it will attempt to restrict to the
285+
/// minimum delay needed to ensure a 10 us t<sub>START</sub> value.
286+
/// Otherwise it will approximate the required delay using ADC reads.
287+
pub fn read(adc: &mut Adc, delay: Option<&mut Delay>) -> i16 {
288+
let mut vtemp = Self::new();
289+
let vtemp_preenable = vtemp.is_enabled(&adc);
290+
291+
if !vtemp_preenable {
292+
vtemp.enable(adc);
293+
294+
if let Some(dref) = delay {
295+
dref.delay_us(2_u16);
296+
} else {
297+
// Double read of vdda to allow sufficient startup time for the temp sensor
298+
VRef::read_vdda(adc);
299+
}
300+
}
301+
let vdda = VRef::read_vdda(adc);
302+
303+
let prev_cfg = adc.default_cfg();
304+
305+
let vtemp_val = adc.read(&mut vtemp).unwrap();
306+
307+
if !vtemp_preenable {
308+
vtemp.disable(adc);
309+
}
310+
311+
adc.restore_cfg(prev_cfg);
312+
313+
Self::convert_temp(vtemp_val, vdda)
314+
}
244315
}
245316

246317
#[cfg(feature = "device-selected")]
@@ -259,6 +330,34 @@ impl VRef {
259330
pub fn disable(&mut self, adc: &mut Adc) {
260331
adc.rb.ccr.modify(|_, w| w.vrefen().clear_bit());
261332
}
333+
334+
/// Returns if the internal voltage reference is enabled.
335+
pub fn is_enabled(&self, adc: &Adc) -> bool {
336+
adc.rb.ccr.read().vrefen().bit_is_set()
337+
}
338+
339+
/// Reads the value of VDDA in milli-volts
340+
pub fn read_vdda(adc: &mut Adc) -> u16 {
341+
let vrefint_cal = u32::from(unsafe { ptr::read(VREFCAL) });
342+
let mut vref = Self::new();
343+
344+
let prev_cfg = adc.default_cfg();
345+
346+
let vref_val: u32 = if vref.is_enabled(&adc) {
347+
adc.read(&mut vref).unwrap()
348+
} else {
349+
vref.enable(adc);
350+
351+
let ret = adc.read(&mut vref).unwrap();
352+
353+
vref.disable(adc);
354+
ret
355+
};
356+
357+
adc.restore_cfg(prev_cfg);
358+
359+
((VDD_CALIB as u32) * vrefint_cal / vref_val) as u16
360+
}
262361
}
263362

264363
#[cfg(feature = "stm32f042")]
@@ -288,8 +387,35 @@ impl VBat {
288387
pub fn disable(&mut self, adc: &mut Adc) {
289388
adc.rb.ccr.modify(|_, w| w.vbaten().clear_bit());
290389
}
390+
391+
/// Returns if the internal VBat sense is enabled
392+
pub fn is_enabled(&self, adc: &Adc) -> bool {
393+
adc.rb.ccr.read().vbaten().bit_is_set()
394+
}
395+
396+
/// Reads the value of VBat in milli-volts
397+
pub fn read(adc: &mut Adc) -> u16 {
398+
let mut vbat = Self::new();
399+
400+
let vbat_val: u16 = if vbat.is_enabled(&adc) {
401+
adc.read_abs_mv(&mut vbat)
402+
} else {
403+
vbat.enable(adc);
404+
405+
let ret = adc.read_abs_mv(&mut vbat);
406+
407+
vbat.disable(adc);
408+
ret
409+
};
410+
411+
vbat_val * 2
412+
}
291413
}
292414

415+
/// A stored ADC config, can be restored by using the `Adc::restore_cfg` method
416+
#[derive(Copy, Clone, Debug, PartialEq)]
417+
pub struct StoredConfig(AdcSampleTime, AdcAlign, AdcPrecision);
418+
293419
#[cfg(feature = "device-selected")]
294420
impl Adc {
295421
/// Init a new Adc
@@ -309,6 +435,28 @@ impl Adc {
309435
s
310436
}
311437

438+
/// Saves a copy of the current ADC config
439+
pub fn save_cfg(&mut self) -> StoredConfig {
440+
StoredConfig(self.sample_time, self.align, self.precision)
441+
}
442+
443+
/// Restores a stored config
444+
pub fn restore_cfg(&mut self, cfg: StoredConfig) {
445+
self.sample_time = cfg.0;
446+
self.align = cfg.1;
447+
self.precision = cfg.2;
448+
}
449+
450+
/// Resets the ADC config to default, returning the existing config as
451+
/// a stored config.
452+
pub fn default_cfg(&mut self) -> StoredConfig {
453+
let cfg = self.save_cfg();
454+
self.sample_time = AdcSampleTime::default();
455+
self.align = AdcAlign::default();
456+
self.precision = AdcPrecision::default();
457+
cfg
458+
}
459+
312460
/// Set the Adc sampling time
313461
///
314462
/// Options can be found in [AdcSampleTime](crate::adc::AdcSampleTime).
@@ -330,6 +478,32 @@ impl Adc {
330478
self.precision = precision;
331479
}
332480

481+
/// Returns the largest possible sample value for the current settings
482+
pub fn max_sample(&self) -> u16 {
483+
match self.align {
484+
AdcAlign::Left => u16::max_value(),
485+
AdcAlign::LeftAsRM => match self.precision {
486+
AdcPrecision::B_6 => u8::max_value() as u16,
487+
_ => u16::max_value(),
488+
},
489+
AdcAlign::Right => match self.precision {
490+
AdcPrecision::B_12 => (1 << 12) - 1,
491+
AdcPrecision::B_10 => (1 << 10) - 1,
492+
AdcPrecision::B_8 => (1 << 8) - 1,
493+
AdcPrecision::B_6 => (1 << 6) - 1,
494+
},
495+
}
496+
}
497+
498+
/// Read the value of a channel and converts the result to milli-volts
499+
pub fn read_abs_mv<PIN: Channel<Adc, ID = u8>>(&mut self, pin: &mut PIN) -> u16 {
500+
let vdda = VRef::read_vdda(self) as u32;
501+
let v: u32 = self.read(pin).unwrap();
502+
let max_samp = self.max_sample() as u32;
503+
504+
(v * vdda / max_samp) as u16
505+
}
506+
333507
fn calibrate(&mut self) {
334508
/* Ensure that ADEN = 0 */
335509
if self.rb.cr.read().aden().bit_is_set() {

0 commit comments

Comments
 (0)