From d58fb11b2e020a6a0b846e3437440ff688b2fe94 Mon Sep 17 00:00:00 2001 From: Bob McWhirter Date: Thu, 10 Jun 2021 15:33:43 -0400 Subject: [PATCH] ADCv3 and example. --- embassy-stm32/src/adc/mod.rs | 125 ++++++++++ embassy-stm32/src/adc/v3.rs | 317 ++++++++++++++++++++++++ embassy-stm32/src/lib.rs | 2 + examples/stm32l4/src/bin/adc.rs | 100 ++++++++ examples/stm32l4/src/bin/button.rs | 1 - examples/stm32l4/src/bin/button_exti.rs | 1 - examples/stm32l4/src/bin/dac.rs | 20 +- stm32-data | 2 +- 8 files changed, 555 insertions(+), 13 deletions(-) create mode 100644 embassy-stm32/src/adc/mod.rs create mode 100644 embassy-stm32/src/adc/v3.rs create mode 100644 examples/stm32l4/src/bin/adc.rs diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs new file mode 100644 index 00000000..8cae5f1d --- /dev/null +++ b/embassy-stm32/src/adc/mod.rs @@ -0,0 +1,125 @@ +#![macro_use] + +#[cfg_attr(adc_v3, path = "v3.rs")] +mod _version; + +#[allow(unused)] +pub use _version::*; + +use crate::gpio::NoPin; +use crate::peripherals; + +pub(crate) mod sealed { + use crate::gpio::Pin; + + pub trait Instance { + fn regs() -> &'static crate::pac::adc::Adc; + fn common_regs() -> &'static crate::pac::adccommon::AdcCommon; + } + + pub trait Common { + fn regs() -> &'static crate::pac::adccommon::AdcCommon; + } + + pub trait AdcPin { + fn channel(&self) -> u8; + } +} + +pub trait Instance: sealed::Instance + 'static {} +pub trait Common: sealed::Common + 'static {} +pub trait AdcPin: sealed::AdcPin {} + +crate::pac::peripherals!( + (adc, $inst:ident) => { + impl crate::adc::sealed::Instance for peripherals::$inst { + fn regs() -> &'static crate::pac::adc::Adc { + &crate::pac::$inst + } + fn common_regs() -> &'static crate::pac::adccommon::AdcCommon { + crate::pac::peripherals!{ + (adccommon, $common_inst:ident) => { + return &crate::pac::$common_inst + }; + } + } + } + + impl crate::adc::Instance for peripherals::$inst {} + }; +); + +crate::pac::peripherals!( + (adccommon, $inst:ident) => { + impl sealed::Common for peripherals::$inst { + fn regs() -> &'static crate::pac::adccommon::AdcCommon { + &crate::pac::$inst + } + } + + impl crate::adc::Common for peripherals::$inst {} + }; +); + +macro_rules! impl_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl AdcPin for peripherals::$pin {} + + impl sealed::AdcPin for peripherals::$pin { + fn channel(&self) -> u8 { + $ch + } + } + }; +} + +crate::pac::peripheral_pins!( + ($inst:ident, adc, ADC, $pin:ident, IN1) => { + impl_pin!($inst, $pin, 1); + }; + ($inst:ident, adc, ADC, $pin:ident, IN2) => { + impl_pin!($inst, $pin, 2); + }; + ($inst:ident, adc, ADC, $pin:ident, IN3) => { + impl_pin!($inst, $pin, 3); + }; + ($inst:ident, adc, ADC, $pin:ident, IN4) => { + impl_pin!($inst, $pin, 4); + }; + ($inst:ident, adc, ADC, $pin:ident, IN5) => { + impl_pin!($inst, $pin, 5); + }; + ($inst:ident, adc, ADC, $pin:ident, IN6) => { + impl_pin!($inst, $pin, 6); + }; + ($inst:ident, adc, ADC, $pin:ident, IN7) => { + impl_pin!($inst, $pin, 7); + }; + ($inst:ident, adc, ADC, $pin:ident, IN8) => { + impl_pin!($inst, $pin, 8); + }; + ($inst:ident, adc, ADC, $pin:ident, IN9) => { + impl_pin!($inst, $pin, 9); + }; + ($inst:ident, adc, ADC, $pin:ident, IN10) => { + impl_pin!($inst, $pin, 10); + }; + ($inst:ident, adc, ADC, $pin:ident, IN11) => { + impl_pin!($inst, $pin, 11); + }; + ($inst:ident, adc, ADC, $pin:ident, IN12) => { + impl_pin!($inst, $pin, 12); + }; + ($inst:ident, adc, ADC, $pin:ident, IN13) => { + impl_pin!($inst, $pin, 13); + }; + ($inst:ident, adc, ADC, $pin:ident, IN14) => { + impl_pin!($inst, $pin, 14); + }; + ($inst:ident, adc, ADC, $pin:ident, IN15) => { + impl_pin!($inst, $pin, 15); + }; + ($inst:ident, adc, ADC, $pin:ident, IN16) => { + impl_pin!($inst, $pin, 16); + }; +); diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs new file mode 100644 index 00000000..14c705b8 --- /dev/null +++ b/embassy-stm32/src/adc/v3.rs @@ -0,0 +1,317 @@ +use crate::adc::{AdcPin, Instance}; +use core::convert::Infallible; +use core::marker::PhantomData; +use cortex_m::delay::Delay; +use embassy::util::Unborrow; +use embassy_extras::unborrow; +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; + +pub const VDDA_CALIB_MV: u32 = 3000; + +pub enum Resolution { + TwelveBit, + TenBit, + EightBit, + SixBit, +} + +impl Default for Resolution { + fn default() -> Self { + Self::TwelveBit + } +} + +impl Resolution { + fn res(&self) -> crate::pac::adc::vals::Res { + match self { + Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, + Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, + Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, + Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, + } + } + + fn to_max_count(&self) -> u32 { + match self { + Resolution::TwelveBit => (1 << 12) - 1, + Resolution::TenBit => (1 << 10) - 1, + Resolution::EightBit => (1 << 8) - 1, + Resolution::SixBit => (1 << 6) - 1, + } + } +} + +pub struct Vref; +impl AdcPin for Vref {} +impl super::sealed::AdcPin for Vref { + fn channel(&self) -> u8 { + 0 + } +} + +pub struct Temperature; +impl AdcPin for Temperature {} +impl super::sealed::AdcPin for Temperature { + fn channel(&self) -> u8 { + 17 + } +} + +pub struct Vbat; +impl AdcPin for Vbat {} +impl super::sealed::AdcPin for Vbat { + fn channel(&self) -> u8 { + 18 + } +} + +/// ADC sample time +/// +/// The default setting is 2.5 ADC clock cycles. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum SampleTime { + /// 2.5 ADC clock cycles + Cycles2_5 = 0b000, + + /// 6.5 ADC clock cycles + Cycles6_5 = 0b001, + + /// 12.5 ADC clock cycles + Cycles12_5 = 0b010, + + /// 24.5 ADC clock cycles + Cycles24_5 = 0b011, + + /// 47.5 ADC clock cycles + Cycles47_5 = 0b100, + + /// 92.5 ADC clock cycles + Cycles92_5 = 0b101, + + /// 247.5 ADC clock cycles + Cycles247_5 = 0b110, + + /// 640.5 ADC clock cycles + Cycles640_5 = 0b111, +} + +impl SampleTime { + fn sample_time(&self) -> crate::pac::adc::vals::SampleTime { + match self { + SampleTime::Cycles2_5 => crate::pac::adc::vals::SampleTime::CYCLES2_5, + SampleTime::Cycles6_5 => crate::pac::adc::vals::SampleTime::CYCLES6_5, + SampleTime::Cycles12_5 => crate::pac::adc::vals::SampleTime::CYCLES12_5, + SampleTime::Cycles24_5 => crate::pac::adc::vals::SampleTime::CYCLES24_5, + SampleTime::Cycles47_5 => crate::pac::adc::vals::SampleTime::CYCLES47_5, + SampleTime::Cycles92_5 => crate::pac::adc::vals::SampleTime::CYCLES92_5, + SampleTime::Cycles247_5 => crate::pac::adc::vals::SampleTime::CYCLES247_5, + SampleTime::Cycles640_5 => crate::pac::adc::vals::SampleTime::CYCLES640_5, + } + } +} + +impl Default for SampleTime { + fn default() -> Self { + Self::Cycles2_5 + } +} + +pub struct Adc<'d, T: Instance> { + sample_time: SampleTime, + calibrated_vdda: u32, + resolution: Resolution, + phantom: PhantomData<&'d mut T>, +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn new(_peri: impl Unborrow + 'd, mut delay: Delay) -> (Self, Delay) { + unborrow!(_peri); + unsafe { + T::regs().cr().modify(|reg| { + reg.set_deeppwd(false); + reg.set_advregen(true); + }); + } + + delay.delay_us(20); + + unsafe { + while T::regs().cr().read().adcal() { + // spin + } + } + + delay.delay_us(1); + + ( + Self { + sample_time: Default::default(), + resolution: Resolution::default(), + calibrated_vdda: VDDA_CALIB_MV, + phantom: PhantomData, + }, + delay, + ) + } + + pub fn enable_vref(&self, mut delay: Delay) -> (Vref, Delay) { + unsafe { + T::common_regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); + } + + // "Table 24. Embedded internal voltage reference" states that it takes a maximum of 12 us + // to stabilize the internal voltage reference, we wait a little more. + // TODO: delay 15us + //cortex_m::asm::delay(20_000_000); + delay.delay_us(15); + + (Vref {}, delay) + } + + pub fn enable_temperature(&self) -> Temperature { + unsafe { + T::common_regs().ccr().modify(|reg| { + reg.set_ch17sel(true); + }); + } + + Temperature {} + } + + pub fn enable_vbat(&self) -> Vbat { + unsafe { + T::common_regs().ccr().modify(|reg| { + reg.set_ch18sel(true); + }); + } + + Vbat {} + } + + /// Calculates the system VDDA by sampling the internal VREF channel and comparing + /// the result with the value stored at the factory. If the chip's VDDA is not stable, run + /// this before each ADC conversion. + fn calibrate(&mut self, vref: &mut Vref) { + let vref_cal = unsafe { crate::pac::VREFINTCAL.data().read().value() }; + let old_sample_time = self.sample_time; + + // "Table 24. Embedded internal voltage reference" states that the sample time needs to be + // at a minimum 4 us. With 640.5 ADC cycles we have a minimum of 8 us at 80 MHz, leaving + // some headroom. + self.sample_time = SampleTime::Cycles640_5; + + // This can't actually fail, it's just in a result to satisfy hal trait + let vref_samp = self.read(vref); + + self.sample_time = old_sample_time; + + self.calibrated_vdda = (VDDA_CALIB_MV * u32::from(vref_cal)) / u32::from(vref_samp); + } + + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + pub fn set_resolution(&mut self, resolution: Resolution) { + self.resolution = resolution; + } + + /// Convert a measurement to millivolts + pub fn to_millivolts(&self, sample: u16) -> u16 { + ((u32::from(sample) * self.calibrated_vdda) / self.resolution.to_max_count()) as u16 + } + + /* + /// Convert a raw sample from the `Temperature` to deg C + pub fn to_degrees_centigrade(sample: u16) -> f32 { + (130.0 - 30.0) / (VtempCal130::get().read() as f32 - VtempCal30::get().read() as f32) + * (sample as f32 - VtempCal30::get().read() as f32) + + 30.0 + } + */ + + pub fn read(&mut self, pin: &mut impl AdcPin) -> u16 { + let v = pin.channel(); + + unsafe { + // Make sure bits are off + while T::regs().cr().read().addis() { + // spin + } + + // Enable ADC + T::regs().isr().modify(|reg| { + reg.set_adrdy(true); + }); + T::regs().cr().modify(|reg| { + reg.set_aden(true); + }); + + while !T::regs().isr().read().adrdy() { + // spin + } + + // Configure ADC + T::regs() + .cfgr() + .modify(|reg| reg.set_res(self.resolution.res())); + + // Configure channel + Self::set_channel_sample_time(pin.channel(), self.sample_time); + + // Select channel + T::regs().sqr1().write(|reg| reg.set_sq(0, pin.channel())); + + // Start conversion + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + while !T::regs().isr().read().eos() { + // spin + } + + // Read ADC value first time and discard it, as per errata sheet. + // The errata states that if we do conversions slower than 1 kHz, the + // first read ADC value can be corrupted, so we discard it and measure again. + + let _ = T::regs().dr().read(); + + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + while !T::regs().isr().read().eos() { + // spin + } + + let val = T::regs().dr().read().0 as u16; + + T::regs().cr().modify(|reg| reg.set_addis(true)); + + val + } + } + + unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + if ch >= 0 && ch <= 9 { + T::regs() + .smpr1() + .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + } else { + T::regs() + .smpr2() + .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + } + } +} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index c205bc49..6a08fc58 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -21,6 +21,8 @@ pub mod gpio; pub mod rcc; // Sometimes-present hardware +#[cfg(adc)] +pub mod adc; #[cfg(timer)] pub mod clock; #[cfg(dac)] diff --git a/examples/stm32l4/src/bin/adc.rs b/examples/stm32l4/src/bin/adc.rs new file mode 100644 index 00000000..fe97fb0b --- /dev/null +++ b/examples/stm32l4/src/bin/adc.rs @@ -0,0 +1,100 @@ +#![no_std] +#![no_main] +#![feature(trait_alias)] +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use embassy_stm32::gpio::{Input, Level, NoPin, Output, Pull}; +use embedded_hal::digital::v2::{InputPin, OutputPin}; +use example_common::*; + +use cortex_m_rt::entry; +//use stm32f4::stm32f429 as pac; +use cortex_m::delay::Delay; +use embassy_stm32::adc::{Adc, Resolution}; +use embassy_stm32::dac::{Channel, Dac, Value}; +use embassy_stm32::spi::{ByteOrder, Config, Spi, MODE_0}; +use embassy_stm32::time::Hertz; +use embedded_hal::blocking::spi::Transfer; +use micromath::F32Ext; +use stm32l4::stm32l4x5 as pac; +use stm32l4xx_hal::gpio::PA4; +use stm32l4xx_hal::rcc::PllSource; +use stm32l4xx_hal::{prelude::*, rcc}; + +#[entry] +fn main() -> ! { + info!("Hello World, dude!"); + //let pp = pac::Peripherals::take().unwrap(); + let cp = cortex_m::Peripherals::take().unwrap(); + let pp = stm32l4xx_hal::stm32::Peripherals::take().unwrap(); + let mut flash = pp.FLASH.constrain(); + let mut rcc = pp.RCC.constrain(); + let mut pwr = pp.PWR.constrain(&mut rcc.apb1r1); + + let mut delay = Delay::new(cp.SYST, 80_000_000); + + // TRY the other clock configuration + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + let clocks = rcc + .cfgr + .sysclk(80.mhz()) + .pclk1(80.mhz()) + .pclk2(80.mhz()) + .pll_source(PllSource::HSI16) + .freeze(&mut flash.acr, &mut pwr); + + let pp = unsafe { pac::Peripherals::steal() }; + + pp.RCC.ccipr.modify(|_, w| { + unsafe { + w.adcsel().bits(0b11); + } + w + }); + + pp.DBGMCU.cr.modify(|_, w| { + w.dbg_sleep().set_bit(); + w.dbg_standby().set_bit(); + w.dbg_stop().set_bit() + }); + + pp.RCC.ahb2enr.modify(|_, w| { + w.adcen().set_bit(); + w.gpioaen().set_bit(); + w.gpioben().set_bit(); + w.gpiocen().set_bit(); + w.gpioden().set_bit(); + w.gpioeen().set_bit(); + w.gpiofen().set_bit(); + w + }); + + let p = embassy_stm32::init(Default::default()); + + let (mut adc, mut delay) = Adc::new(p.ADC1, delay); + //adc.enable_vref(); + adc.set_resolution(Resolution::EightBit); + let mut channel = p.PC0; + + loop { + let v = adc.read(&mut channel); + info!("--> {}", v); + } +} + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} diff --git a/examples/stm32l4/src/bin/button.rs b/examples/stm32l4/src/bin/button.rs index 43d81715..962d5aa7 100644 --- a/examples/stm32l4/src/bin/button.rs +++ b/examples/stm32l4/src/bin/button.rs @@ -15,7 +15,6 @@ use example_common::*; use cortex_m_rt::entry; use stm32l4::stm32l4x5 as pac; - #[entry] fn main() -> ! { info!("Hello World!"); diff --git a/examples/stm32l4/src/bin/button_exti.rs b/examples/stm32l4/src/bin/button_exti.rs index caace835..c6b7c83e 100644 --- a/examples/stm32l4/src/bin/button_exti.rs +++ b/examples/stm32l4/src/bin/button_exti.rs @@ -80,5 +80,4 @@ fn main() -> ! { executor.run(|spawner| { unwrap!(spawner.spawn(main_task())); }) - } diff --git a/examples/stm32l4/src/bin/dac.rs b/examples/stm32l4/src/bin/dac.rs index 5bd5dafb..0ca40fbd 100644 --- a/examples/stm32l4/src/bin/dac.rs +++ b/examples/stm32l4/src/bin/dac.rs @@ -8,20 +8,20 @@ #[path = "../example_common.rs"] mod example_common; -use embassy_stm32::gpio::{Level, Output, Input, Pull, NoPin}; -use embedded_hal::digital::v2::{OutputPin, InputPin}; +use embassy_stm32::gpio::{Input, Level, NoPin, Output, Pull}; +use embedded_hal::digital::v2::{InputPin, OutputPin}; use example_common::*; use cortex_m_rt::entry; //use stm32f4::stm32f429 as pac; -use stm32l4::stm32l4x5 as pac; -use embassy_stm32::spi::{Spi, MODE_0, ByteOrder, Config}; +use embassy_stm32::dac::{Channel, Dac, Value}; +use embassy_stm32::spi::{ByteOrder, Config, Spi, MODE_0}; use embassy_stm32::time::Hertz; use embedded_hal::blocking::spi::Transfer; -use stm32l4xx_hal::{rcc, prelude::*}; -use stm32l4xx_hal::rcc::PllSource; -use embassy_stm32::dac::{Dac, Value, Channel}; +use stm32l4::stm32l4x5 as pac; use stm32l4xx_hal::gpio::PA4; +use stm32l4xx_hal::rcc::PllSource; +use stm32l4xx_hal::{prelude::*, rcc}; #[entry] fn main() -> ! { @@ -72,7 +72,7 @@ fn main() -> ! { loop { for v in 0..=255 { dac.set(Channel::Ch1, Value::Bit8(to_sine_wave(v))); - dac.trigger( Channel::Ch1 ); + dac.trigger(Channel::Ch1); } } } @@ -82,11 +82,11 @@ use micromath::F32Ext; fn to_sine_wave(v: u8) -> u8 { if v >= 128 { // top half - let r = 3.14 * ( (v-128) as f32/ 128.0) ; + let r = 3.14 * ((v - 128) as f32 / 128.0); (r.sin() * 128.0 + 127.0) as u8 } else { // bottom half - let r = 3.14 + 3.14 * (v as f32/ 128.0); + let r = 3.14 + 3.14 * (v as f32 / 128.0); (r.sin() * 128.0 + 127.0) as u8 } } diff --git a/stm32-data b/stm32-data index 8f32d8e4..67b2029a 160000 --- a/stm32-data +++ b/stm32-data @@ -1 +1 @@ -Subproject commit 8f32d8e4f25276f6f2155e8dd5bbed1acefd657b +Subproject commit 67b2029a7f203e52f6e1f68c71c7d0aacfef5795