diff --git a/CHANGELOG.md b/CHANGELOG.md index cff7ef96..f7eb97a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Implement ADC example +- Implement ADC embedded_hal traits +- Implement ADC clock configuration - Add feature for using STM32F101 chip - Add gpio pins corresponding to LQFP-100 package - Implement `core::fmt::Write` for `serial::Tx` diff --git a/examples/adc.rs b/examples/adc.rs new file mode 100644 index 00000000..6101af64 --- /dev/null +++ b/examples/adc.rs @@ -0,0 +1,76 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +#[macro_use] +extern crate cortex_m_rt as rt; +extern crate cortex_m; +extern crate cortex_m_semihosting; +extern crate embedded_hal; +extern crate panic_semihosting; +extern crate stm32f1xx_hal; + +use core::fmt::Write; +use cortex_m_semihosting::hio; +use stm32f1xx_hal::prelude::*; + +use rt::ExceptionFrame; +use stm32f1xx_hal::adc; + +#[entry] +fn main() -> ! { + // Aquire peripherals + let p = stm32f1xx_hal::stm32::Peripherals::take().unwrap(); + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + + // Configure ADC clocks + // Default value is the slowest possible ADC clock: PCLK2 / 8. Meanwhile ADC + // clock is configurable. So its frequency may be tweaked to meet certain + // practical needs. User specified value is be approximated using supported + // prescaler values 2/4/6/8. + let clocks = rcc.cfgr.adcclk(2.mhz()).freeze(&mut flash.acr); + hio::hstdout() + .map(|mut hio| writeln!(hio, "adc freq: {}", clocks.adcclk().0).unwrap()) + .unwrap(); + + // Setup ADC + let mut adc1 = adc::Adc::adc1(p.ADC1, &mut rcc.apb2); + + #[cfg(feature = "stm32f103")] + let mut adc2 = adc::Adc::adc2(p.ADC2, &mut rcc.apb2); + + // Setup GPIOB + let mut gpiob = p.GPIOB.split(&mut rcc.apb2); + + // Configure pb0, pb1 as an analog input + let mut ch0 = gpiob.pb0.into_analog(&mut gpiob.crl); + + #[cfg(feature = "stm32f103")] + let mut ch1 = gpiob.pb1.into_analog(&mut gpiob.crl); + + loop { + let data: u16 = adc1.read(&mut ch0).unwrap(); + hio::hstdout() + .map(|mut hio| writeln!(hio, "adc1: {}", data).unwrap()) + .unwrap(); + + #[cfg(feature = "stm32f103")] + { + let data1: u16 = adc2.read(&mut ch1).unwrap(); + hio::hstdout() + .map(|mut hio| writeln!(hio, "adc2: {}", data1).unwrap()) + .unwrap(); + } + } +} + +#[exception] +fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} + +#[exception] +fn DefaultHandler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} diff --git a/src/adc.rs b/src/adc.rs new file mode 100644 index 00000000..7827bba4 --- /dev/null +++ b/src/adc.rs @@ -0,0 +1,403 @@ +//! # API for the Analog to Digital converter + +use embedded_hal::adc::{Channel, OneShot}; + +use crate::gpio::Analog; +use crate::gpio::{gpioa, gpiob, gpioc}; +use crate::rcc::APB2; + +use crate::stm32::ADC1; +#[cfg(any( + feature = "stm32f103", +))] +use crate::stm32::ADC2; + +/// ADC configuration +pub struct Adc { + rb: ADC, + sample_time: AdcSampleTime, + align: AdcAlign, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[allow(non_camel_case_types)] +/// 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 { + /// Get the default sample time (currently 28.5 cycles) + pub fn default() -> Self { + AdcSampleTime::T_28 + } +} + +impl From for u8 { + fn from(val: AdcSampleTime) -> Self { + match val { + AdcSampleTime::T_1 => 0, + AdcSampleTime::T_7 => 1, + AdcSampleTime::T_13 => 2, + AdcSampleTime::T_28 => 3, + AdcSampleTime::T_41 => 4, + AdcSampleTime::T_55 => 5, + AdcSampleTime::T_71 => 6, + AdcSampleTime::T_239 => 7, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +/// ADC data register alignment +pub enum AdcAlign { + /// Right alignment of output data + Right, + /// Left alignment of output data + Left, +} + +impl AdcAlign { + /// Default: right alignment + pub fn default() -> Self { + AdcAlign::Right + } +} + +impl From for u8 { + fn from(val: AdcAlign) -> Self { + match val { + AdcAlign::Right => 0, + AdcAlign::Left => 1, + } + } +} + +impl From for bool { + fn from(val: AdcAlign) -> Self { + match val { + AdcAlign::Right => false, + AdcAlign::Left => true, + } + } +} + +macro_rules! adc_pins { + ($ADC:ident, $($pin:ty => $chan:expr),+ $(,)*) => { + $( + impl Channel<$ADC> for $pin { + type ID = u8; + + fn channel() -> u8 { $chan } + } + )+ + }; +} + +adc_pins!(ADC1, + gpioa::PA0 => 0_u8, + gpioa::PA1 => 1_u8, + gpioa::PA2 => 2_u8, + gpioa::PA3 => 3_u8, + gpioa::PA4 => 4_u8, + gpioa::PA5 => 5_u8, + gpioa::PA6 => 6_u8, + gpioa::PA7 => 7_u8, + gpiob::PB0 => 8_u8, + gpiob::PB1 => 9_u8, + gpioc::PC0 => 10_u8, + gpioc::PC1 => 11_u8, + gpioc::PC2 => 12_u8, + gpioc::PC3 => 13_u8, + gpioc::PC4 => 14_u8, + gpioc::PC5 => 15_u8, +); + +#[cfg(any( + feature = "stm32f103", +))] +adc_pins!(ADC2, + gpioa::PA0 => 0_u8, + gpioa::PA1 => 1_u8, + gpioa::PA2 => 2_u8, + gpioa::PA3 => 3_u8, + gpioa::PA4 => 4_u8, + gpioa::PA5 => 5_u8, + gpioa::PA6 => 6_u8, + gpioa::PA7 => 7_u8, + gpiob::PB0 => 8_u8, + gpiob::PB1 => 9_u8, + gpioc::PC0 => 10_u8, + gpioc::PC1 => 11_u8, + gpioc::PC2 => 12_u8, + gpioc::PC3 => 13_u8, + gpioc::PC4 => 14_u8, + gpioc::PC5 => 15_u8, +); + +/// Stored ADC config can be restored using the `Adc::restore_cfg` method +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct StoredConfig(AdcSampleTime, AdcAlign); + +macro_rules! adc_hal { + ($( + $ADC:ident: ( + $init:ident, + $adcxen:ident, + $adcxrst:ident + ), + )+) => { + $( + + impl Adc<$ADC> { + /// Init a new Adc + /// + /// Sets all configurable parameters to one-shot defaults, + /// performs a boot-time calibration. + pub fn $init(adc: $ADC, apb2: &mut APB2) -> Self { + let mut s = Self { + rb: adc, + sample_time: AdcSampleTime::default(), + align: AdcAlign::default(), + }; + s.enable_clock(apb2); + s.power_down(); + s.reset(apb2); + s.setup_oneshot(); + s.power_up(); + s.calibrate(); + s + } + + /// Save current ADC config + pub fn save_cfg(&mut self) -> StoredConfig { + StoredConfig(self.sample_time, self.align) + } + + /// Restore saved ADC config + pub fn restore_cfg(&mut self, cfg: StoredConfig) { + self.sample_time = cfg.0; + self.align = cfg.1; + } + + /// Reset the ADC config to default, return existing config + pub fn default_cfg(&mut self) -> StoredConfig { + let cfg = self.save_cfg(); + self.sample_time = AdcSampleTime::default(); + self.align = AdcAlign::default(); + cfg + } + + /// Set 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; + } + + /// 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::Right => (1 << 12) - 1, + } + } + + fn power_up(&mut self) { + self.rb.cr2.modify(|_, w| w.adon().set_bit()); + } + + fn power_down(&mut self) { + self.rb.cr2.modify(|_, w| w.adon().clear_bit()); + } + + fn reset(&mut self, apb2: &mut APB2) { + apb2.rstr().modify(|_, w| w.$adcxrst().set_bit()); + apb2.rstr().modify(|_, w| w.$adcxrst().clear_bit()); + } + + fn enable_clock(&mut self, apb2: &mut APB2) { + apb2.enr().modify(|_, w| w.$adcxen().set_bit()); + } + + fn calibrate(&mut self) { + /* reset calibration */ + self.rb.cr2.modify(|_, w| w.rstcal().set_bit()); + while self.rb.cr2.read().rstcal().bit_is_set() {} + + /* calibrate */ + self.rb.cr2.modify(|_, w| w.cal().set_bit()); + while self.rb.cr2.read().cal().bit_is_set() {} + } + + fn setup_oneshot(&mut self) { + self.rb.cr2.modify(|_, w| w.cont().clear_bit()); + self.rb.cr2.modify(|_, w| w.exttrig().set_bit()); + self.rb.cr2.modify(|_, w| unsafe { w.extsel().bits(0b111) }); + + self.rb.cr1.modify(|_, w| w.scan().clear_bit()); + self.rb.cr1.modify(|_, w| w.discen().set_bit()); + + self.rb.sqr1.modify(|_, w| unsafe { w.l().bits(0b0) }); + } + + fn set_chan_smps(&mut self, chan: u8) { + match chan { + 0 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp0().bits(self.sample_time.into()) }), + 1 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp1().bits(self.sample_time.into()) }), + 2 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp2().bits(self.sample_time.into()) }), + 3 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp3().bits(self.sample_time.into()) }), + 4 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp4().bits(self.sample_time.into()) }), + 5 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp5().bits(self.sample_time.into()) }), + 6 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp6().bits(self.sample_time.into()) }), + 7 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp7().bits(self.sample_time.into()) }), + 8 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp8().bits(self.sample_time.into()) }), + 9 => self + .rb + .smpr2 + .modify(|_, w| unsafe { w.smp9().bits(self.sample_time.into()) }), + + 10 => self + .rb + .smpr1 + .modify(|_, w| unsafe { w.smp10().bits(self.sample_time.into()) }), + 11 => self + .rb + .smpr1 + .modify(|_, w| unsafe { w.smp11().bits(self.sample_time.into()) }), + 12 => self + .rb + .smpr1 + .modify(|_, w| unsafe { w.smp12().bits(self.sample_time.into()) }), + 13 => self + .rb + .smpr1 + .modify(|_, w| unsafe { w.smp13().bits(self.sample_time.into()) }), + 14 => self + .rb + .smpr1 + .modify(|_, w| unsafe { w.smp14().bits(self.sample_time.into()) }), + 15 => self + .rb + .smpr1 + .modify(|_, w| unsafe { w.smp15().bits(self.sample_time.into()) }), + + _ => unreachable!(), + } + + return; + } + + fn convert(&mut self, chan: u8) -> u16 { + self.rb.cr2.modify(|_, w| w.align().bit(self.align.into())); + self.set_chan_smps(chan); + self.rb.sqr3.modify(|_, w| unsafe { w.sq1().bits(chan) }); + + // ADC start conversion of regular sequence + self.rb.cr2.modify(|_, w| w.swstart().set_bit()); + while self.rb.cr2.read().swstart().bit_is_set() {} + // ADC wait for conversion results + while self.rb.sr.read().eoc().bit_is_clear() {} + + let res = self.rb.dr.read().data().bits(); + res + } + } + + impl OneShot<$ADC, WORD, PIN> for Adc<$ADC> + where + WORD: From, + PIN: Channel<$ADC, ID = u8>, + { + type Error = (); + + fn read(&mut self, _pin: &mut PIN) -> nb::Result { + self.power_up(); + let res = self.convert(PIN::channel()); + self.power_down(); + Ok(res.into()) + } + } + + )+ + } +} + +#[cfg(any( + feature = "stm32f100", + feature = "stm32f101", +))] +adc_hal! { + ADC1: ( + adc1, + adc1en, + adc1rst + ), +} + +#[cfg(any( + feature = "stm32f103", +))] +adc_hal! { + ADC1: ( + adc1, + adc1en, + adc1rst + ), + ADC2: ( + adc2, + adc2en, + adc2rst + ), +} diff --git a/src/lib.rs b/src/lib.rs index 6c896300..48e561c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,6 +107,7 @@ pub use stm32f1::stm32f103 as pac; pub use crate::pac as device; pub use crate::pac as stm32; +pub mod adc; pub mod afio; pub mod backup_domain; pub mod bb; diff --git a/src/prelude.rs b/src/prelude.rs index 05808608..565a518c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,6 +3,7 @@ pub use crate::dma::DmaChannel as _stm32_hal_dma_DmaChannel; pub use crate::dma::DmaExt as _stm32_hal_dma_DmaExt; pub use crate::flash::FlashExt as _stm32_hal_flash_FlashExt; pub use crate::gpio::GpioExt as _stm32_hal_gpio_GpioExt; +pub use crate::hal::adc::OneShot as _embedded_hal_adc_OneShot; pub use crate::hal::digital::StatefulOutputPin as _embedded_hal_digital_StatefulOutputPin; pub use crate::hal::digital::ToggleableOutputPin as _embedded_hal_digital_ToggleableOutputPin; pub use crate::hal::prelude::*; diff --git a/src/rcc.rs b/src/rcc.rs index 348e3bb1..21804f2a 100644 --- a/src/rcc.rs +++ b/src/rcc.rs @@ -29,6 +29,7 @@ impl RccExt for RCC { pclk1: None, pclk2: None, sysclk: None, + adcclk: None, }, bkp: BKP { _0: () }, } @@ -104,6 +105,7 @@ pub struct CFGR { pclk1: Option, pclk2: Option, sysclk: Option, + adcclk: Option, } impl CFGR { @@ -153,10 +155,16 @@ impl CFGR { self } + /// Sets the desired frequency for the SYSCLK clock + pub fn adcclk(mut self, freq: F) -> Self + where + F: Into, + { + self.adcclk = Some(freq.into().0); + self + } pub fn freeze(self, acr: &mut ACR) -> Clocks { - // TODO ADC clock - let pllsrcclk = self.hse.unwrap_or(HSI / 2); let pllmul = self.sysclk.unwrap_or(pllsrcclk) / pllsrcclk; @@ -247,6 +255,21 @@ impl CFGR { _ => (true, false), }; + let apre_bits = self + .adcclk + .map(|adcclk| match pclk2 / adcclk { + 0...2 => 0b00, + 3...4 => 0b01, + 5...7 => 0b10, + _ => 0b11, + }) + .unwrap_or(0b11); + + let apre = (apre_bits + 1) << 1; + let adcclk = pclk2 / u32(apre); + + assert!(adcclk <= 14_000_000); + let rcc = unsafe { &*RCC::ptr() }; if self.hse.is_some() { @@ -275,6 +298,7 @@ impl CFGR { // set prescalers and clock source #[cfg(feature = "stm32f103")] rcc.cfgr.modify(|_, w| unsafe { + w.adcpre().bits(apre_bits); w.ppre2() .bits(ppre2_bits) .ppre1() @@ -301,6 +325,7 @@ impl CFGR { feature = "stm32f101" ))] rcc.cfgr.modify(|_, w| unsafe { + w.adcpre().bits(apre_bits); w.ppre2() .bits(ppre2_bits) .ppre1() @@ -327,6 +352,7 @@ impl CFGR { ppre1, ppre2, sysclk: Hertz(sysclk), + adcclk: Hertz(adcclk), usbclk_valid, } } @@ -370,6 +396,7 @@ pub struct Clocks { ppre1: u8, ppre2: u8, sysclk: Hertz, + adcclk: Hertz, usbclk_valid: bool, } @@ -414,6 +441,11 @@ impl Clocks { self.sysclk } + /// Returns the adc clock frequency + pub fn adcclk(&self) -> Hertz { + self.adcclk + } + /// Returns whether the USBCLK clock frequency is valid for the USB peripheral pub fn usbclk_valid(&self) -> bool { self.usbclk_valid