diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index ba118f338..a84af258e 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -1030,6 +1030,38 @@ fn main() {
         (("octospi", "NCS"), quote!(crate::ospi::NSSPin)),
         (("octospi", "CLK"), quote!(crate::ospi::SckPin)),
         (("octospi", "NCLK"), quote!(crate::ospi::NckPin)),
+        (("tsc", "G1_IO1"), quote!(crate::ospi::G1IO1Pin)),
+        (("tsc", "G1_IO2"), quote!(crate::ospi::G1IO2Pin)),
+        (("tsc", "G1_IO3"), quote!(crate::ospi::G1IO3Pin)),
+        (("tsc", "G1_IO4"), quote!(crate::ospi::G1IO4Pin)),
+        (("tsc", "G2_IO1"), quote!(crate::ospi::G2IO1Pin)),
+        (("tsc", "G2_IO2"), quote!(crate::ospi::G2IO2Pin)),
+        (("tsc", "G2_IO3"), quote!(crate::ospi::G2IO3Pin)),
+        (("tsc", "G2_IO4"), quote!(crate::ospi::G2IO4Pin)),
+        (("tsc", "G3_IO1"), quote!(crate::ospi::G3IO1Pin)),
+        (("tsc", "G3_IO2"), quote!(crate::ospi::G3IO2Pin)),
+        (("tsc", "G3_IO3"), quote!(crate::ospi::G3IO3Pin)),
+        (("tsc", "G3_IO4"), quote!(crate::ospi::G3IO4Pin)),
+        (("tsc", "G4_IO1"), quote!(crate::ospi::G4IO1Pin)),
+        (("tsc", "G4_IO2"), quote!(crate::ospi::G4IO2Pin)),
+        (("tsc", "G4_IO3"), quote!(crate::ospi::G4IO3Pin)),
+        (("tsc", "G4_IO4"), quote!(crate::ospi::G4IO4Pin)),
+        (("tsc", "G5_IO1"), quote!(crate::ospi::G5IO1Pin)),
+        (("tsc", "G5_IO2"), quote!(crate::ospi::G5IO2Pin)),
+        (("tsc", "G5_IO3"), quote!(crate::ospi::G5IO3Pin)),
+        (("tsc", "G5_IO4"), quote!(crate::ospi::G5IO4Pin)),
+        (("tsc", "G6_IO1"), quote!(crate::ospi::G6IO1Pin)),
+        (("tsc", "G6_IO2"), quote!(crate::ospi::G6IO2Pin)),
+        (("tsc", "G6_IO3"), quote!(crate::ospi::G6IO3Pin)),
+        (("tsc", "G6_IO4"), quote!(crate::ospi::G6IO4Pin)),
+        (("tsc", "G7_IO1"), quote!(crate::ospi::G7IO1Pin)),
+        (("tsc", "G7_IO2"), quote!(crate::ospi::G7IO2Pin)),
+        (("tsc", "G7_IO3"), quote!(crate::ospi::G7IO3Pin)),
+        (("tsc", "G7_IO4"), quote!(crate::ospi::G7IO4Pin)),
+        (("tsc", "G8_IO1"), quote!(crate::ospi::G8IO1Pin)),
+        (("tsc", "G8_IO2"), quote!(crate::ospi::G8IO2Pin)),
+        (("tsc", "G8_IO3"), quote!(crate::ospi::G8IO3Pin)),
+        (("tsc", "G8_IO4"), quote!(crate::ospi::G8IO4Pin)),
     ].into();
 
     for p in METADATA.peripherals {
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 1f4e9ab1e..dd89618ef 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -103,6 +103,8 @@ pub mod sdmmc;
 pub mod spi;
 #[cfg(ucpd)]
 pub mod ucpd;
+#[cfg(tsc)]
+pub mod tsc;
 #[cfg(uid)]
 pub mod uid;
 #[cfg(usart)]
diff --git a/embassy-stm32/src/tsc/enums.rs b/embassy-stm32/src/tsc/enums.rs
new file mode 100644
index 000000000..6dfc8709c
--- /dev/null
+++ b/embassy-stm32/src/tsc/enums.rs
@@ -0,0 +1,100 @@
+/// Charge transfer pulse cycles
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub enum ChargeTransferPulseCycle {
+    _1,
+    _2,
+    _3,
+    _4,
+    _5,
+    _6,
+    _7,
+    _8,
+    _9,
+    _10,
+    _11,
+    _12,
+    _13,
+    _14,
+    _15,
+    _16,
+}
+
+impl Into<u8> for ChargeTransferPulseCycle {
+    fn into(self) -> u8 {
+        match self {
+            ChargeTransferPulseCycle::_1 => 0,
+            ChargeTransferPulseCycle::_2 => 1,
+            ChargeTransferPulseCycle::_3 => 2,
+            ChargeTransferPulseCycle::_4 => 3,
+            ChargeTransferPulseCycle::_5 => 4,
+            ChargeTransferPulseCycle::_6 => 5,
+            ChargeTransferPulseCycle::_7 => 6,
+            ChargeTransferPulseCycle::_8 => 7,
+            ChargeTransferPulseCycle::_9 => 8,
+            ChargeTransferPulseCycle::_10 => 9,
+            ChargeTransferPulseCycle::_11 => 10,
+            ChargeTransferPulseCycle::_12 => 11,
+            ChargeTransferPulseCycle::_13 => 12,
+            ChargeTransferPulseCycle::_14 => 13,
+            ChargeTransferPulseCycle::_15 => 14,
+            ChargeTransferPulseCycle::_16 => 15,
+        }
+    }
+}
+
+/// Prescaler divider
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub enum PGPrescalerDivider {
+    _1,
+    _2,
+    _4,
+    _8,
+    _16,
+    _32,
+    _64,
+    _128,
+}
+
+impl Into<u8> for PGPrescalerDivider {
+    fn into(self) -> u8 {
+        match self {
+            PGPrescalerDivider::_1 => 0,
+            PGPrescalerDivider::_2 => 1,
+            PGPrescalerDivider::_4 => 2,
+            PGPrescalerDivider::_8 => 3,
+            PGPrescalerDivider::_16 => 4,
+            PGPrescalerDivider::_32 => 5,
+            PGPrescalerDivider::_64 => 6,
+            PGPrescalerDivider::_128 => 7,
+        }
+    }
+}
+
+/// Max count
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub enum MaxCount {
+    _255,
+    _511,
+    _1023,
+    _2047,
+    _4095,
+    _8191,
+    _16383,
+}
+
+impl Into<u8> for MaxCount {
+    fn into(self) -> u8 {
+        match self {
+            MaxCount::_255 => 0,
+            MaxCount::_511 => 1,
+            MaxCount::_1023 => 2,
+            MaxCount::_2047 => 3,
+            MaxCount::_4095 => 4,
+            MaxCount::_8191 => 5,
+            MaxCount::_16383 => 6,
+        }
+    }
+}
diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs
new file mode 100644
index 000000000..6bff642fa
--- /dev/null
+++ b/embassy-stm32/src/tsc/mod.rs
@@ -0,0 +1,357 @@
+//! TSC Peripheral Interface
+
+#![macro_use]
+
+pub mod enums;
+
+use crate::gpio::AnyPin;
+use crate::{pac::tsc::Tsc as Regs, rcc::RccPeripheral};
+use crate::{peripherals, Peripheral};
+use embassy_hal_internal::{into_ref, PeripheralRef};
+
+pub use enums::*;
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Error {
+    /// Test error for TSC
+    Test,
+}
+
+pub enum PinType {
+    Channel,
+    Sample,
+    Shield,
+}
+
+pub struct TscGroup {}
+
+pub enum State {
+    Reset,
+    Ready,
+    Busy,
+    Error,
+}
+
+pub enum GroupStatus {
+    Ongoing,
+    Complete,
+}
+
+pub enum Group {
+    One,
+    Two,
+    Three,
+    Four,
+    Five,
+    Six,
+    Seven,
+    Eight,
+}
+
+impl Into<usize> for Group {
+    fn into(self) -> usize {
+        match self {
+            Group::One => 0,
+            Group::Two => 1,
+            Group::Three => 2,
+            Group::Four => 3,
+            Group::Five => 4,
+            Group::Six => 5,
+            Group::Seven => 6,
+            Group::Eight => 7,
+        }
+    }
+}
+
+pub struct Config {
+    pub ct_pulse_high_length: ChargeTransferPulseCycle,
+    pub ct_pulse_low_length: ChargeTransferPulseCycle,
+    pub spread_spectrum: bool,
+    pub spread_spectrum_deviation: u8,
+    pub spread_spectrum_prescaler: bool,
+    pub pulse_generator_prescaler: PGPrescalerDivider,
+    pub max_count_value: u8,
+    pub io_default_mode: bool,
+    pub synchro_pin_polarity: bool,
+    pub acquisition_mode: bool,
+    pub max_count_interrupt: bool,
+    pub channel_ios: u32,
+    pub shield_ios: u32,
+    pub sampling_ios: u32,
+}
+
+pub struct TSC<'d, T: Instance> {
+    _peri: PeripheralRef<'d, T>,
+    state: State,
+    config: Config,
+}
+
+impl<'d, T: Instance> TSC<'d, T> {
+    pub fn new(peri: impl Peripheral<P = T> + 'd, config: Config) -> Self {
+        into_ref!(peri);
+
+        // Need to check valid pin configuration input
+        // Need to configure pin
+        Self::new_inner(peri, config)
+    }
+
+    fn new_inner(peri: impl Peripheral<P = T> + 'd, config: Config) -> Self {
+        into_ref!(peri);
+
+        T::enable_and_reset();
+
+        T::REGS.cr().modify(|w| {
+            w.set_tsce(true);
+            w.set_ctph(config.ct_pulse_high_length.into());
+            w.set_ctpl(config.ct_pulse_low_length.into());
+            w.set_sse(config.spread_spectrum);
+            w.set_ssd(config.spread_spectrum_deviation);
+            w.set_sspsc(config.spread_spectrum_prescaler);
+            w.set_pgpsc(config.pulse_generator_prescaler.into());
+            w.set_mcv(config.max_count_value);
+            w.set_syncpol(config.synchro_pin_polarity);
+            w.set_am(config.acquisition_mode)
+        });
+
+        // Set IO configuration
+        // Disable Schmitt trigger hysteresis on all used TSC IOs
+        // T::REGS.iohcr().modify(|w| {
+        //     w.
+        // });
+
+        // Set channel and shield IOs
+        // T::REGS.ioccr().modify(|w| {});
+
+        // Set sampling IOs
+        // T::REGS.ioscr().modify(|w| {
+        //     w.set_g1_io1(val)
+        // });
+
+        // Set the groups to be acquired
+        // T::REGS.iogcsr().modify(|w| {
+        //     w.set_g1e(val);
+        // });
+
+        // Disable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(false);
+            w.set_mceie(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        Self {
+            _peri: peri,
+            state: State::Ready,
+            config,
+        }
+    }
+
+    pub fn start(&mut self) {
+        self.state = State::Busy;
+
+        // Disable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(false);
+            w.set_mceie(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        // Set the touch sensing IOs not acquired to the default mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(self.config.io_default_mode);
+        });
+
+        // Start the acquisition
+        T::REGS.cr().modify(|w| {
+            w.set_start(true);
+        });
+    }
+
+    pub fn start_it(&mut self) {
+        self.state = State::Busy;
+
+        // Enable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(true);
+            w.set_mceie(self.config.max_count_interrupt);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        // Set the touch sensing IOs not acquired to the default mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(self.config.io_default_mode);
+        });
+
+        // Start the acquisition
+        T::REGS.cr().modify(|w| {
+            w.set_start(true);
+        });
+    }
+
+    pub fn stop(&mut self) {
+        T::REGS.cr().modify(|w| {
+            w.set_start(false);
+        });
+
+        // Set the touch sensing IOs in low power mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        self.state = State::Ready;
+    }
+
+    pub fn stop_it(&mut self) {
+        T::REGS.cr().modify(|w| {
+            w.set_start(false);
+        });
+
+        // Set the touch sensing IOs in low power mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(false);
+        });
+
+        // Disable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(false);
+            w.set_mceie(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        self.state = State::Ready;
+    }
+
+    pub fn poll_for_acquisition(&mut self) {
+        while self.get_state() == State::Busy {}
+    }
+
+    pub fn get_state(&mut self) -> State {
+        if self.state == State::Busy {
+            if T::REGS.isr().read().eoaf() {
+                if T::REGS.isr().read().mcef() {
+                    self.state = State::Error
+                } else {
+                    self.state = State::Ready
+                }
+            }
+        }
+        self.state
+    }
+
+    pub fn group_get_status(&mut self, index: Group) -> GroupStatus {
+        // Status bits are set by hardware when the acquisition on the corresponding
+        // enabled analog IO group is complete, cleared when new acquisition is started
+        let status = match index {
+            Group::One => T::REGS.iogcsr().read().g1s(),
+            Group::Two => T::REGS.iogcsr().read().g2s(),
+            Group::Three => T::REGS.iogcsr().read().g3s(),
+            Group::Four => T::REGS.iogcsr().read().g4s(),
+            Group::Five => T::REGS.iogcsr().read().g5s(),
+            Group::Six => T::REGS.iogcsr().read().g6s(),
+            Group::Seven => T::REGS.iogcsr().read().g7s(),
+            Group::Eight => T::REGS.iogcsr().read().g8s(),
+        };
+        match status {
+            true => GroupStatus::Complete,
+            false => GroupStatus::Ongoing,
+        }
+    }
+
+    pub fn group_get_value(&mut self, index: Group) -> u16 {
+        T::REGS.iogcr(index.into()).read().cnt()
+    }
+
+    // pub fn configure_io()
+
+    pub fn discharge_io(&mut self, status: bool) {
+        // Set the touch sensing IOs in low power mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(!status);
+        });
+    }
+}
+
+impl<'d, T: Instance> Drop for TSC<'d, T> {
+    fn drop(&mut self) {
+        //  Need to figure out what to do with the IOs
+        T::disable();
+    }
+}
+
+pub(crate) trait SealedInstance {
+    const REGS: Regs;
+}
+
+/// TSC instance trait
+#[allow(private_bounds)]
+pub trait Instance: Peripheral<P = Self> + SealedInstance + RccPeripheral {}
+
+foreach_peripheral!(
+    (tsc, $inst:ident) => {
+        impl SealedInstance for peripherals::$inst {
+            const REGS: Regs = crate::pac::$inst;
+        }
+
+        impl Instance for peripherals::$inst {}
+    };
+);
+
+pin_trait!(G1IO1Pin, Instance);
+pin_trait!(G1IO2Pin, Instance);
+pin_trait!(G1IO3Pin, Instance);
+pin_trait!(G1IO4Pin, Instance);
+pin_trait!(G2IO1Pin, Instance);
+pin_trait!(G2IO2Pin, Instance);
+pin_trait!(G2IO3Pin, Instance);
+pin_trait!(G2IO4Pin, Instance);
+pin_trait!(G3IO1Pin, Instance);
+pin_trait!(G3IO2Pin, Instance);
+pin_trait!(G3IO3Pin, Instance);
+pin_trait!(G3IO4Pin, Instance);
+pin_trait!(G4IO1Pin, Instance);
+pin_trait!(G4IO2Pin, Instance);
+pin_trait!(G4IO3Pin, Instance);
+pin_trait!(G4IO4Pin, Instance);
+pin_trait!(G5IO1Pin, Instance);
+pin_trait!(G5IO2Pin, Instance);
+pin_trait!(G5IO3Pin, Instance);
+pin_trait!(G5IO4Pin, Instance);
+pin_trait!(G6IO1Pin, Instance);
+pin_trait!(G6IO2Pin, Instance);
+pin_trait!(G6IO3Pin, Instance);
+pin_trait!(G6IO4Pin, Instance);
+pin_trait!(G7IO1Pin, Instance);
+pin_trait!(G7IO2Pin, Instance);
+pin_trait!(G7IO3Pin, Instance);
+pin_trait!(G7IO4Pin, Instance);
+pin_trait!(G8IO1Pin, Instance);
+pin_trait!(G8IO2Pin, Instance);
+pin_trait!(G8IO3Pin, Instance);
+pin_trait!(G8IO4Pin, Instance);