diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs
index fba016a77..0eb4eba73 100644
--- a/embassy-stm32/src/adc/mod.rs
+++ b/embassy-stm32/src/adc/mod.rs
@@ -28,6 +28,10 @@ pub(crate) mod sealed {
     pub trait AdcPin<T: Instance> {
         fn channel(&self) -> u8;
     }
+
+    pub trait InternalChannel<T> {
+        fn channel(&self) -> u8;
+    }
 }
 
 #[cfg(not(any(adc_f1, adc_v2)))]
@@ -37,6 +41,7 @@ pub trait Instance: sealed::Instance + crate::rcc::RccPeripheral + 'static {}
 #[cfg(all(not(adc_f1), not(adc_v1)))]
 pub trait Common: sealed::Common + 'static {}
 pub trait AdcPin<T: Instance>: sealed::AdcPin<T> {}
+pub trait InternalChannel<T>: sealed::InternalChannel<T> {}
 
 #[cfg(not(stm32h7))]
 foreach_peripheral!(
diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs
index 70e3b73b3..4fe4ad1f0 100644
--- a/embassy-stm32/src/adc/v2.rs
+++ b/embassy-stm32/src/adc/v2.rs
@@ -3,7 +3,9 @@ use core::marker::PhantomData;
 use embassy_hal_common::into_ref;
 use embedded_hal_02::blocking::delay::DelayUs;
 
+use super::InternalChannel;
 use crate::adc::{AdcPin, Instance};
+use crate::peripherals::ADC1;
 use crate::time::Hertz;
 use crate::Peripheral;
 
@@ -12,6 +14,9 @@ pub const VREF_DEFAULT_MV: u32 = 3300;
 /// VREF voltage used for factory calibration of VREFINTCAL register.
 pub const VREF_CALIB_MV: u32 = 3300;
 
+/// ADC turn-on time
+pub const ADC_POWERUP_TIME_US: u32 = 3;
+
 pub enum Resolution {
     TwelveBit,
     TenBit,
@@ -46,24 +51,53 @@ impl Resolution {
 }
 
 pub struct VrefInt;
-impl<T: Instance> AdcPin<T> for VrefInt {}
-impl<T: Instance> super::sealed::AdcPin<T> for VrefInt {
+impl InternalChannel<ADC1> for VrefInt {}
+impl super::sealed::InternalChannel<ADC1> for VrefInt {
     fn channel(&self) -> u8 {
         17
     }
 }
 
+impl VrefInt {
+    /// Time needed for internal voltage reference to stabilize
+    pub fn start_time_us() -> u32 {
+        10
+    }
+}
+
 pub struct Temperature;
-impl<T: Instance> AdcPin<T> for Temperature {}
-impl<T: Instance> super::sealed::AdcPin<T> for Temperature {
+impl InternalChannel<ADC1> for Temperature {}
+impl super::sealed::InternalChannel<ADC1> for Temperature {
     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;
-impl<T: Instance> AdcPin<T> for Vbat {}
-impl<T: Instance> super::sealed::AdcPin<T> for Vbat {
+impl InternalChannel<ADC1> for Vbat {}
+impl super::sealed::InternalChannel<ADC1> for Vbat {
     fn channel(&self) -> u8 {
         18
     }
@@ -152,19 +186,16 @@ where
         T::enable();
         T::reset();
 
-        let presc = unsafe { Prescaler::from_pclk2(T::frequency()) };
+        let presc = Prescaler::from_pclk2(T::frequency());
         unsafe {
             T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre()));
-        }
 
-        unsafe {
-            // disable before config is set
             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 {
             sample_time: Default::default(),
@@ -194,6 +225,45 @@ where
         ((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.
     fn convert(&mut self) -> u16 {
         unsafe {
@@ -224,44 +294,31 @@ where
         P: crate::gpio::sealed::Pin,
     {
         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();
 
-            // Configure ADC
-            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
+            self.read_channel(pin.channel())
         }
     }
 
+    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) {
         if ch <= 9 {
             T::regs()
diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs
index d356d7b66..7e6a219e1 100644
--- a/embassy-stm32/src/adc/v4.rs
+++ b/embassy-stm32/src/adc/v4.rs
@@ -50,14 +50,6 @@ 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
 pub struct VrefInt;
 impl<T: Instance> InternalChannel<T> for VrefInt {}
diff --git a/examples/stm32f4/src/bin/adc.rs b/examples/stm32f4/src/bin/adc.rs
index 871185074..6f80c1ef1 100644
--- a/examples/stm32f4/src/bin/adc.rs
+++ b/examples/stm32f4/src/bin/adc.rs
@@ -2,9 +2,10 @@
 #![no_main]
 #![feature(type_alias_impl_trait)]
 
+use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs;
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::adc::Adc;
+use embassy_stm32::adc::{Adc, SampleTime, Temperature, VrefInt};
 use embassy_time::{Delay, Duration, Timer};
 use {defmt_rtt as _, panic_probe as _};
 
@@ -13,12 +14,30 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
     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 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 {
+        // Read 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;
     }
 }