Browse Source
Base implementation of ADC functionality according to the embedded hal traits for `Channel` and `OneShot`. Also provides references for converting the internal temperature sensor and voltage reference values, also the VBat reference where available. Closes #11.features/pwm

6 changed files with 478 additions and 1 deletions
@ -0,0 +1,56 @@
|
||||
#![no_main] |
||||
#![no_std] |
||||
|
||||
#[allow(unused_imports)] |
||||
use panic_halt; |
||||
|
||||
use stm32f0xx_hal as hal; |
||||
|
||||
use crate::hal::delay::Delay; |
||||
use crate::hal::prelude::*; |
||||
use crate::hal::stm32; |
||||
|
||||
use crate::hal::adc::Adc; |
||||
|
||||
use cortex_m::peripheral::Peripherals; |
||||
use cortex_m_rt::entry; |
||||
|
||||
#[entry] |
||||
fn main() -> ! { |
||||
if let (Some(p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) { |
||||
let gpioa = p.GPIOA.split(); |
||||
|
||||
/* (Re-)configure PA1 as output */ |
||||
let mut led = gpioa.pa1.into_push_pull_output(); |
||||
|
||||
/* (Re-)configure PA0 as analog in */ |
||||
let mut an_in = gpioa.pa0.into_analog(); |
||||
|
||||
/* Constrain clocking registers */ |
||||
let rcc = p.RCC.constrain(); |
||||
|
||||
/* Configure clock to 8 MHz (i.e. the default) and freeze it */ |
||||
let clocks = rcc.cfgr.sysclk(8.mhz()).freeze(); |
||||
|
||||
/* Get delay provider */ |
||||
let mut delay = Delay::new(cp.SYST, clocks); |
||||
|
||||
let mut adc = Adc::new(p.ADC); |
||||
|
||||
loop { |
||||
led.toggle(); |
||||
|
||||
let val: u16 = adc.read(&mut an_in).unwrap(); |
||||
|
||||
/* shift the value right by 3, same as divide by 8, reduces
|
||||
the 0-4095 range into something approximating 1-512 */ |
||||
let time: u16 = (val >> 3) + 1; |
||||
|
||||
delay.delay_ms(time); |
||||
} |
||||
} |
||||
|
||||
loop { |
||||
continue; |
||||
} |
||||
} |
@ -0,0 +1,397 @@
|
||||
//! # 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);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use embedded_hal::adc::{Channel, OneShot}; |
||||
|
||||
use crate::stm32; |
||||
|
||||
use crate::gpio::*; |
||||
|
||||
/// Analog to Digital converter interface
|
||||
pub struct Adc { |
||||
rb: stm32::ADC, |
||||
sample_time: AdcSampleTime, |
||||
align: AdcAlign, |
||||
precision: AdcPrecision, |
||||
} |
||||
|
||||
#[derive(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, |
||||
} |
||||
|
||||
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(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, |
||||
} |
||||
|
||||
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(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, |
||||
} |
||||
|
||||
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 |
||||
} |
||||
} |
||||
|
||||
macro_rules! adc_pins { |
||||
($($pin:ty => $chan:expr),+ $(,)*) => { |
||||
$( |
||||
impl Channel<Adc> for $pin { |
||||
type ID = u8; |
||||
|
||||
fn channel() -> u8 { $chan } |
||||
} |
||||
)+ |
||||
}; |
||||
} |
||||
|
||||
#[cfg(any(feature = "stm32f042", feature = "stm32f030", feature = "stm32f070",))] |
||||
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",))] |
||||
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)] |
||||
/// Internal temperature sensor (ADC Channel 16)
|
||||
pub struct VTemp; |
||||
|
||||
#[derive(Debug)] |
||||
/// Internal voltage reference (ADC Channel 17)
|
||||
pub struct VRef; |
||||
|
||||
adc_pins!( |
||||
VTemp => 16_u8, |
||||
VRef => 17_u8, |
||||
); |
||||
|
||||
impl VTemp { |
||||
/// Init a new VTemp
|
||||
pub fn new() -> Self { |
||||
VTemp {} |
||||
} |
||||
|
||||
/// 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()); |
||||
} |
||||
} |
||||
|
||||
impl VRef { |
||||
/// Init a new VRef
|
||||
pub fn new() -> Self { |
||||
VRef {} |
||||
} |
||||
|
||||
/// 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()); |
||||
} |
||||
} |
||||
|
||||
#[cfg(any(feature = "stm32f042",))] |
||||
#[derive(Debug)] |
||||
/// Battery reference voltage (ADC Channel 18)
|
||||
pub struct VBat; |
||||
|
||||
#[cfg(any(feature = "stm32f042",))] |
||||
adc_pins!( |
||||
VBat => 18_u8, |
||||
); |
||||
|
||||
#[cfg(any(feature = "stm32f042",))] |
||||
impl VBat { |
||||
/// Init a new VBat
|
||||
pub fn new() -> Self { |
||||
VBat {} |
||||
} |
||||
|
||||
/// 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()); |
||||
} |
||||
} |
||||
|
||||
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 |
||||
} |
||||
|
||||
/// 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; |
||||
} |
||||
|
||||
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 |
||||
} |
||||
} |
||||
} |
||||
|
||||
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()) |
||||
} |
||||
} |
Loading…
Reference in new issue