Merge #1004
1004: Fix internal channels for adc v2 r=lulf a=chemicstry Internal channel reading was broken on adc_v2, because `Adc::read()` requires gpio pin trait, which was not implemented by `VrefInt`, `Temperature`, `Vbat`. The required configuration bits `tsvrefe`, `vbate` were not enabled either. This PR makes it a bit closer to how adc_v4 works. While at it, I also changed adc_v2 to use `RccPeripheral` instead of permanently enabling all ADCs. Co-authored-by: chemicstry <chemicstry@gmail.com>
This commit is contained in:
commit
ef533e6df4
4 changed files with 143 additions and 78 deletions
|
@ -28,15 +28,20 @@ pub(crate) mod sealed {
|
||||||
pub trait AdcPin<T: Instance> {
|
pub trait AdcPin<T: Instance> {
|
||||||
fn channel(&self) -> u8;
|
fn channel(&self) -> u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait InternalChannel<T> {
|
||||||
|
fn channel(&self) -> u8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(adc_f1))]
|
#[cfg(not(any(adc_f1, adc_v2)))]
|
||||||
pub trait Instance: sealed::Instance + 'static {}
|
pub trait Instance: sealed::Instance + 'static {}
|
||||||
#[cfg(adc_f1)]
|
#[cfg(any(adc_f1, adc_v2))]
|
||||||
pub trait Instance: sealed::Instance + crate::rcc::RccPeripheral + 'static {}
|
pub trait Instance: sealed::Instance + crate::rcc::RccPeripheral + 'static {}
|
||||||
#[cfg(all(not(adc_f1), not(adc_v1)))]
|
#[cfg(all(not(adc_f1), not(adc_v1)))]
|
||||||
pub trait Common: sealed::Common + 'static {}
|
pub trait Common: sealed::Common + 'static {}
|
||||||
pub trait AdcPin<T: Instance>: sealed::AdcPin<T> {}
|
pub trait AdcPin<T: Instance>: sealed::AdcPin<T> {}
|
||||||
|
pub trait InternalChannel<T>: sealed::InternalChannel<T> {}
|
||||||
|
|
||||||
#[cfg(not(stm32h7))]
|
#[cfg(not(stm32h7))]
|
||||||
foreach_peripheral!(
|
foreach_peripheral!(
|
||||||
|
|
|
@ -3,7 +3,9 @@ use core::marker::PhantomData;
|
||||||
use embassy_hal_common::into_ref;
|
use embassy_hal_common::into_ref;
|
||||||
use embedded_hal_02::blocking::delay::DelayUs;
|
use embedded_hal_02::blocking::delay::DelayUs;
|
||||||
|
|
||||||
|
use super::InternalChannel;
|
||||||
use crate::adc::{AdcPin, Instance};
|
use crate::adc::{AdcPin, Instance};
|
||||||
|
use crate::peripherals::ADC1;
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::Peripheral;
|
use crate::Peripheral;
|
||||||
|
|
||||||
|
@ -12,20 +14,8 @@ pub const VREF_DEFAULT_MV: u32 = 3300;
|
||||||
/// VREF voltage used for factory calibration of VREFINTCAL register.
|
/// VREF voltage used for factory calibration of VREFINTCAL register.
|
||||||
pub const VREF_CALIB_MV: u32 = 3300;
|
pub const VREF_CALIB_MV: u32 = 3300;
|
||||||
|
|
||||||
#[cfg(not(any(rcc_f4, rcc_f7)))]
|
/// ADC turn-on time
|
||||||
fn enable() {
|
pub const ADC_POWERUP_TIME_US: u32 = 3;
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(rcc_f4, rcc_f7))]
|
|
||||||
fn enable() {
|
|
||||||
critical_section::with(|_| unsafe {
|
|
||||||
// TODO do not enable all adc clocks if not needed
|
|
||||||
crate::pac::RCC.apb2enr().modify(|w| w.set_adc1en(true));
|
|
||||||
crate::pac::RCC.apb2enr().modify(|w| w.set_adc2en(true));
|
|
||||||
crate::pac::RCC.apb2enr().modify(|w| w.set_adc3en(true));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Resolution {
|
pub enum Resolution {
|
||||||
TwelveBit,
|
TwelveBit,
|
||||||
|
@ -61,24 +51,53 @@ impl Resolution {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VrefInt;
|
pub struct VrefInt;
|
||||||
impl<T: Instance> AdcPin<T> for VrefInt {}
|
impl InternalChannel<ADC1> for VrefInt {}
|
||||||
impl<T: Instance> super::sealed::AdcPin<T> for VrefInt {
|
impl super::sealed::InternalChannel<ADC1> for VrefInt {
|
||||||
fn channel(&self) -> u8 {
|
fn channel(&self) -> u8 {
|
||||||
17
|
17
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VrefInt {
|
||||||
|
/// Time needed for internal voltage reference to stabilize
|
||||||
|
pub fn start_time_us() -> u32 {
|
||||||
|
10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Temperature;
|
pub struct Temperature;
|
||||||
impl<T: Instance> AdcPin<T> for Temperature {}
|
impl InternalChannel<ADC1> for Temperature {}
|
||||||
impl<T: Instance> super::sealed::AdcPin<T> for Temperature {
|
impl super::sealed::InternalChannel<ADC1> for Temperature {
|
||||||
fn channel(&self) -> u8 {
|
fn channel(&self) -> u8 {
|
||||||
16
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(any(stm32f40, stm32f41))] {
|
||||||
|
16
|
||||||
|
} else {
|
||||||
|
18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Temperature {
|
||||||
|
/// Converts temperature sensor reading in millivolts to degrees celcius
|
||||||
|
pub fn to_celcius(sample_mv: u16) -> f32 {
|
||||||
|
// From 6.3.22 Temperature sensor characteristics
|
||||||
|
const V25: i32 = 760; // mV
|
||||||
|
const AVG_SLOPE: f32 = 2.5; // mV/C
|
||||||
|
|
||||||
|
(sample_mv as i32 - V25) as f32 / AVG_SLOPE + 25.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Time needed for temperature sensor readings to stabilize
|
||||||
|
pub fn start_time_us() -> u32 {
|
||||||
|
10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Vbat;
|
pub struct Vbat;
|
||||||
impl<T: Instance> AdcPin<T> for Vbat {}
|
impl InternalChannel<ADC1> for Vbat {}
|
||||||
impl<T: Instance> super::sealed::AdcPin<T> for Vbat {
|
impl super::sealed::InternalChannel<ADC1> for Vbat {
|
||||||
fn channel(&self) -> u8 {
|
fn channel(&self) -> u8 {
|
||||||
18
|
18
|
||||||
}
|
}
|
||||||
|
@ -164,21 +183,19 @@ where
|
||||||
{
|
{
|
||||||
pub fn new(_peri: impl Peripheral<P = T> + 'd, delay: &mut impl DelayUs<u32>) -> Self {
|
pub fn new(_peri: impl Peripheral<P = T> + 'd, delay: &mut impl DelayUs<u32>) -> Self {
|
||||||
into_ref!(_peri);
|
into_ref!(_peri);
|
||||||
enable();
|
T::enable();
|
||||||
|
T::reset();
|
||||||
|
|
||||||
let presc = unsafe { Prescaler::from_pclk2(crate::rcc::get_freqs().apb2) };
|
let presc = Prescaler::from_pclk2(T::frequency());
|
||||||
unsafe {
|
unsafe {
|
||||||
T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre()));
|
T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre()));
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
// disable before config is set
|
|
||||||
T::regs().cr2().modify(|reg| {
|
T::regs().cr2().modify(|reg| {
|
||||||
reg.set_adon(crate::pac::adc::vals::Adon::DISABLED);
|
reg.set_adon(crate::pac::adc::vals::Adon::ENABLED);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
delay.delay_us(20); // TODO?
|
delay.delay_us(ADC_POWERUP_TIME_US);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
sample_time: Default::default(),
|
sample_time: Default::default(),
|
||||||
|
@ -208,6 +225,45 @@ where
|
||||||
((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16
|
((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enables internal voltage reference and returns [VrefInt], which can be used in
|
||||||
|
/// [Adc::read_internal()] to perform conversion.
|
||||||
|
pub fn enable_vrefint(&self) -> VrefInt {
|
||||||
|
unsafe {
|
||||||
|
T::common_regs().ccr().modify(|reg| {
|
||||||
|
reg.set_tsvrefe(crate::pac::adccommon::vals::Tsvrefe::ENABLED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
VrefInt {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables internal temperature sensor and returns [Temperature], which can be used in
|
||||||
|
/// [Adc::read_internal()] to perform conversion.
|
||||||
|
///
|
||||||
|
/// On STM32F42 and STM32F43 this can not be used together with [Vbat]. If both are enabled,
|
||||||
|
/// temperature sensor will return vbat value.
|
||||||
|
pub fn enable_temperature(&self) -> Temperature {
|
||||||
|
unsafe {
|
||||||
|
T::common_regs().ccr().modify(|reg| {
|
||||||
|
reg.set_tsvrefe(crate::pac::adccommon::vals::Tsvrefe::ENABLED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Temperature {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables vbat input and returns [Vbat], which can be used in
|
||||||
|
/// [Adc::read_internal()] to perform conversion.
|
||||||
|
pub fn enable_vbat(&self) -> Vbat {
|
||||||
|
unsafe {
|
||||||
|
T::common_regs().ccr().modify(|reg| {
|
||||||
|
reg.set_vbate(crate::pac::adccommon::vals::Vbate::ENABLED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Vbat {}
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform a single conversion.
|
/// Perform a single conversion.
|
||||||
fn convert(&mut self) -> u16 {
|
fn convert(&mut self) -> u16 {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -238,44 +294,31 @@ where
|
||||||
P: crate::gpio::sealed::Pin,
|
P: crate::gpio::sealed::Pin,
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
// dissable ADC
|
|
||||||
T::regs().cr2().modify(|reg| {
|
|
||||||
reg.set_swstart(false);
|
|
||||||
});
|
|
||||||
T::regs().cr2().modify(|reg| {
|
|
||||||
reg.set_adon(crate::pac::adc::vals::Adon::DISABLED);
|
|
||||||
});
|
|
||||||
|
|
||||||
pin.set_as_analog();
|
pin.set_as_analog();
|
||||||
|
|
||||||
// Configure ADC
|
self.read_channel(pin.channel())
|
||||||
T::regs().cr1().modify(|reg| reg.set_res(self.resolution.res()));
|
|
||||||
|
|
||||||
// Select channel
|
|
||||||
T::regs().sqr3().write(|reg| reg.set_sq(0, pin.channel()));
|
|
||||||
|
|
||||||
// Configure channel
|
|
||||||
Self::set_channel_sample_time(pin.channel(), self.sample_time);
|
|
||||||
|
|
||||||
// enable adc
|
|
||||||
T::regs().cr2().modify(|reg| {
|
|
||||||
reg.set_adon(crate::pac::adc::vals::Adon::ENABLED);
|
|
||||||
});
|
|
||||||
|
|
||||||
let val = self.convert();
|
|
||||||
|
|
||||||
// dissable ADC
|
|
||||||
T::regs().cr2().modify(|reg| {
|
|
||||||
reg.set_swstart(false);
|
|
||||||
});
|
|
||||||
T::regs().cr2().modify(|reg| {
|
|
||||||
reg.set_adon(crate::pac::adc::vals::Adon::DISABLED);
|
|
||||||
});
|
|
||||||
|
|
||||||
val
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_internal(&mut self, channel: &mut impl InternalChannel<T>) -> u16 {
|
||||||
|
unsafe { self.read_channel(channel.channel()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_channel(&mut self, channel: u8) -> u16 {
|
||||||
|
// Configure ADC
|
||||||
|
T::regs().cr1().modify(|reg| reg.set_res(self.resolution.res()));
|
||||||
|
|
||||||
|
// Select channel
|
||||||
|
T::regs().sqr3().write(|reg| reg.set_sq(0, channel));
|
||||||
|
|
||||||
|
// Configure channel
|
||||||
|
Self::set_channel_sample_time(channel, self.sample_time);
|
||||||
|
|
||||||
|
let val = self.convert();
|
||||||
|
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) {
|
unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) {
|
||||||
if ch <= 9 {
|
if ch <= 9 {
|
||||||
T::regs()
|
T::regs()
|
||||||
|
@ -288,3 +331,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance> Drop for Adc<'d, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
T::disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use embedded_hal_02::blocking::delay::DelayUs;
|
||||||
use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel};
|
use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel};
|
||||||
use pac::adccommon::vals::Presc;
|
use pac::adccommon::vals::Presc;
|
||||||
|
|
||||||
use super::{AdcPin, Instance};
|
use super::{AdcPin, Instance, InternalChannel};
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::{pac, Peripheral};
|
use crate::{pac, Peripheral};
|
||||||
|
|
||||||
|
@ -50,18 +50,10 @@ impl Resolution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InternalChannel<T>: sealed::InternalChannel<T> {}
|
|
||||||
|
|
||||||
mod sealed {
|
|
||||||
pub trait InternalChannel<T> {
|
|
||||||
fn channel(&self) -> u8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Vrefint/Temperature/Vbat are only available on ADC3 on H7, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs
|
// NOTE: Vrefint/Temperature/Vbat are only available on ADC3 on H7, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs
|
||||||
pub struct VrefInt;
|
pub struct VrefInt;
|
||||||
impl<T: Instance> InternalChannel<T> for VrefInt {}
|
impl<T: Instance> InternalChannel<T> for VrefInt {}
|
||||||
impl<T: Instance> sealed::InternalChannel<T> for VrefInt {
|
impl<T: Instance> super::sealed::InternalChannel<T> for VrefInt {
|
||||||
fn channel(&self) -> u8 {
|
fn channel(&self) -> u8 {
|
||||||
19
|
19
|
||||||
}
|
}
|
||||||
|
@ -69,7 +61,7 @@ impl<T: Instance> sealed::InternalChannel<T> for VrefInt {
|
||||||
|
|
||||||
pub struct Temperature;
|
pub struct Temperature;
|
||||||
impl<T: Instance> InternalChannel<T> for Temperature {}
|
impl<T: Instance> InternalChannel<T> for Temperature {}
|
||||||
impl<T: Instance> sealed::InternalChannel<T> for Temperature {
|
impl<T: Instance> super::sealed::InternalChannel<T> for Temperature {
|
||||||
fn channel(&self) -> u8 {
|
fn channel(&self) -> u8 {
|
||||||
18
|
18
|
||||||
}
|
}
|
||||||
|
@ -77,7 +69,7 @@ impl<T: Instance> sealed::InternalChannel<T> for Temperature {
|
||||||
|
|
||||||
pub struct Vbat;
|
pub struct Vbat;
|
||||||
impl<T: Instance> InternalChannel<T> for Vbat {}
|
impl<T: Instance> InternalChannel<T> for Vbat {}
|
||||||
impl<T: Instance> sealed::InternalChannel<T> for Vbat {
|
impl<T: Instance> super::sealed::InternalChannel<T> for Vbat {
|
||||||
fn channel(&self) -> u8 {
|
fn channel(&self) -> u8 {
|
||||||
// TODO this should be 14 for H7a/b/35
|
// TODO this should be 14 for H7a/b/35
|
||||||
17
|
17
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
|
use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs;
|
||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_stm32::adc::Adc;
|
use embassy_stm32::adc::{Adc, Temperature, VrefInt};
|
||||||
use embassy_time::{Delay, Duration, Timer};
|
use embassy_time::{Delay, Duration, Timer};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
@ -13,12 +14,30 @@ async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_stm32::init(Default::default());
|
let p = embassy_stm32::init(Default::default());
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
let mut adc = Adc::new(p.ADC1, &mut Delay);
|
let mut delay = Delay;
|
||||||
|
let mut adc = Adc::new(p.ADC1, &mut delay);
|
||||||
let mut pin = p.PC1;
|
let mut pin = p.PC1;
|
||||||
|
|
||||||
|
let mut vrefint = adc.enable_vrefint();
|
||||||
|
let mut temp = adc.enable_temperature();
|
||||||
|
|
||||||
|
// Startup delay can be combined to the maximum of either
|
||||||
|
delay.delay_us(Temperature::start_time_us().max(VrefInt::start_time_us()));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
// Read pin
|
||||||
let v = adc.read(&mut pin);
|
let v = adc.read(&mut pin);
|
||||||
info!("--> {} - {} mV", v, adc.to_millivolts(v));
|
info!("PC1: {} ({} mV)", v, adc.to_millivolts(v));
|
||||||
|
|
||||||
|
// Read internal temperature
|
||||||
|
let v = adc.read_internal(&mut temp);
|
||||||
|
let celcius = Temperature::to_celcius(adc.to_millivolts(v));
|
||||||
|
info!("Internal temp: {} ({} C)", v, celcius);
|
||||||
|
|
||||||
|
// Read internal voltage reference
|
||||||
|
let v = adc.read_internal(&mut vrefint);
|
||||||
|
info!("VrefInt: {} ({} mV)", v, adc.to_millivolts(v));
|
||||||
|
|
||||||
Timer::after(Duration::from_millis(100)).await;
|
Timer::after(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue