diff --git a/ci.sh b/ci.sh
index d2bf4df97..2c46dcc6b 100755
--- a/ci.sh
+++ b/ci.sh
@@ -143,6 +143,7 @@ cargo batch  \
     $BUILD_EXTRA
 
 
+
 function run_elf {
     echo Running target=$1 elf=$2
     STATUSCODE=$(
diff --git a/embassy-lora/src/iv.rs b/embassy-lora/src/iv.rs
index f81134405..d515bc365 100644
--- a/embassy-lora/src/iv.rs
+++ b/embassy-lora/src/iv.rs
@@ -1,7 +1,9 @@
 #[cfg(feature = "stm32wl")]
+use embassy_stm32::interrupt;
+#[cfg(feature = "stm32wl")]
 use embassy_stm32::interrupt::*;
 #[cfg(feature = "stm32wl")]
-use embassy_stm32::{pac, PeripheralRef};
+use embassy_stm32::pac;
 #[cfg(feature = "stm32wl")]
 use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 #[cfg(feature = "stm32wl")]
@@ -13,47 +15,51 @@ use lora_phy::mod_params::RadioError::*;
 use lora_phy::mod_params::{BoardType, RadioError};
 use lora_phy::mod_traits::InterfaceVariant;
 
+/// Interrupt handler.
 #[cfg(feature = "stm32wl")]
-static IRQ_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
+pub struct InterruptHandler {}
 
 #[cfg(feature = "stm32wl")]
-/// Base for the InterfaceVariant implementation for an stm32wl/sx1262 combination
-pub struct Stm32wlInterfaceVariant<'a, CTRL> {
-    board_type: BoardType,
-    irq: PeripheralRef<'a, SUBGHZ_RADIO>,
-    rf_switch_rx: Option<CTRL>,
-    rf_switch_tx: Option<CTRL>,
-}
-
-#[cfg(feature = "stm32wl")]
-impl<'a, CTRL> Stm32wlInterfaceVariant<'a, CTRL>
-where
-    CTRL: OutputPin,
-{
-    /// Create an InterfaceVariant instance for an stm32wl/sx1262 combination
-    pub fn new(
-        irq: PeripheralRef<'a, SUBGHZ_RADIO>,
-        rf_switch_rx: Option<CTRL>,
-        rf_switch_tx: Option<CTRL>,
-    ) -> Result<Self, RadioError> {
-        irq.disable();
-        irq.set_handler(Self::on_interrupt);
-        Ok(Self {
-            board_type: BoardType::Stm32wlSx1262, // updated when associated with a specific LoRa board
-            irq,
-            rf_switch_rx,
-            rf_switch_tx,
-        })
-    }
-
-    fn on_interrupt(_: *mut ()) {
+impl interrupt::Handler<interrupt::SUBGHZ_RADIO> for InterruptHandler {
+    unsafe fn on_interrupt() {
         unsafe { SUBGHZ_RADIO::steal() }.disable();
         IRQ_SIGNAL.signal(());
     }
 }
 
 #[cfg(feature = "stm32wl")]
-impl<CTRL> InterfaceVariant for Stm32wlInterfaceVariant<'_, CTRL>
+static IRQ_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
+
+#[cfg(feature = "stm32wl")]
+/// Base for the InterfaceVariant implementation for an stm32wl/sx1262 combination
+pub struct Stm32wlInterfaceVariant<CTRL> {
+    board_type: BoardType,
+    rf_switch_rx: Option<CTRL>,
+    rf_switch_tx: Option<CTRL>,
+}
+
+#[cfg(feature = "stm32wl")]
+impl<'a, CTRL> Stm32wlInterfaceVariant<CTRL>
+where
+    CTRL: OutputPin,
+{
+    /// Create an InterfaceVariant instance for an stm32wl/sx1262 combination
+    pub fn new(
+        _irq: impl interrupt::Binding<interrupt::SUBGHZ_RADIO, InterruptHandler>,
+        rf_switch_rx: Option<CTRL>,
+        rf_switch_tx: Option<CTRL>,
+    ) -> Result<Self, RadioError> {
+        unsafe { interrupt::SUBGHZ_RADIO::steal() }.disable();
+        Ok(Self {
+            board_type: BoardType::Stm32wlSx1262, // updated when associated with a specific LoRa board
+            rf_switch_rx,
+            rf_switch_tx,
+        })
+    }
+}
+
+#[cfg(feature = "stm32wl")]
+impl<CTRL> InterfaceVariant for Stm32wlInterfaceVariant<CTRL>
 where
     CTRL: OutputPin,
 {
@@ -89,7 +95,7 @@ where
     }
 
     async fn await_irq(&mut self) -> Result<(), RadioError> {
-        self.irq.enable();
+        unsafe { interrupt::SUBGHZ_RADIO::steal() }.enable();
         IRQ_SIGNAL.wait().await;
         Ok(())
     }
diff --git a/embassy-stm32/src/dcmi.rs b/embassy-stm32/src/dcmi.rs
index c19be86c6..5f3fc6a93 100644
--- a/embassy-stm32/src/dcmi.rs
+++ b/embassy-stm32/src/dcmi.rs
@@ -1,4 +1,5 @@
 use core::future::poll_fn;
+use core::marker::PhantomData;
 use core::task::Poll;
 
 use embassy_hal_common::{into_ref, PeripheralRef};
@@ -8,7 +9,31 @@ use crate::dma::Transfer;
 use crate::gpio::sealed::AFType;
 use crate::gpio::Speed;
 use crate::interrupt::{Interrupt, InterruptExt};
-use crate::Peripheral;
+use crate::{interrupt, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        let ris = crate::pac::DCMI.ris().read();
+        if ris.err_ris() {
+            trace!("DCMI IRQ: Error.");
+            crate::pac::DCMI.ier().modify(|ier| ier.set_err_ie(false));
+        }
+        if ris.ovr_ris() {
+            trace!("DCMI IRQ: Overrun.");
+            crate::pac::DCMI.ier().modify(|ier| ier.set_ovr_ie(false));
+        }
+        if ris.frame_ris() {
+            trace!("DCMI IRQ: Frame captured.");
+            crate::pac::DCMI.ier().modify(|ier| ier.set_frame_ie(false));
+        }
+        STATE.waker.wake();
+    }
+}
 
 /// The level on the VSync pin when the data is not valid on the parallel interface.
 #[derive(Clone, Copy, PartialEq)]
@@ -94,7 +119,7 @@ where
     pub fn new_8bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -108,17 +133,17 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7);
         config_pins!(v_sync, h_sync, pixclk);
 
-        Self::new_inner(peri, dma, irq, config, false, 0b00)
+        Self::new_inner(peri, dma, config, false, 0b00)
     }
 
     pub fn new_10bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -134,17 +159,17 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9);
         config_pins!(v_sync, h_sync, pixclk);
 
-        Self::new_inner(peri, dma, irq, config, false, 0b01)
+        Self::new_inner(peri, dma, config, false, 0b01)
     }
 
     pub fn new_12bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -162,17 +187,17 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
         config_pins!(v_sync, h_sync, pixclk);
 
-        Self::new_inner(peri, dma, irq, config, false, 0b10)
+        Self::new_inner(peri, dma, config, false, 0b10)
     }
 
     pub fn new_14bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -192,17 +217,17 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13);
         config_pins!(v_sync, h_sync, pixclk);
 
-        Self::new_inner(peri, dma, irq, config, false, 0b11)
+        Self::new_inner(peri, dma, config, false, 0b11)
     }
 
     pub fn new_es_8bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -214,17 +239,17 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7);
         config_pins!(pixclk);
 
-        Self::new_inner(peri, dma, irq, config, true, 0b00)
+        Self::new_inner(peri, dma, config, true, 0b00)
     }
 
     pub fn new_es_10bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -238,17 +263,17 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9);
         config_pins!(pixclk);
 
-        Self::new_inner(peri, dma, irq, config, true, 0b01)
+        Self::new_inner(peri, dma, config, true, 0b01)
     }
 
     pub fn new_es_12bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -264,17 +289,17 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
         config_pins!(pixclk);
 
-        Self::new_inner(peri, dma, irq, config, true, 0b10)
+        Self::new_inner(peri, dma, config, true, 0b10)
     }
 
     pub fn new_es_14bit(
         peri: impl Peripheral<P = T> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
         d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
         d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
@@ -292,17 +317,16 @@ where
         pixclk: impl Peripheral<P = impl PixClkPin<T>> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, dma, irq);
+        into_ref!(peri, dma);
         config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13);
         config_pins!(pixclk);
 
-        Self::new_inner(peri, dma, irq, config, true, 0b11)
+        Self::new_inner(peri, dma, config, true, 0b11)
     }
 
     fn new_inner(
         peri: PeripheralRef<'d, T>,
         dma: PeripheralRef<'d, Dma>,
-        irq: PeripheralRef<'d, T::Interrupt>,
         config: Config,
         use_embedded_synchronization: bool,
         edm: u8,
@@ -322,30 +346,12 @@ where
             });
         }
 
-        irq.set_handler(Self::on_interrupt);
-        irq.unpend();
-        irq.enable();
+        unsafe { T::Interrupt::steal() }.unpend();
+        unsafe { T::Interrupt::steal() }.enable();
 
         Self { inner: peri, dma }
     }
 
-    unsafe fn on_interrupt(_: *mut ()) {
-        let ris = crate::pac::DCMI.ris().read();
-        if ris.err_ris() {
-            trace!("DCMI IRQ: Error.");
-            crate::pac::DCMI.ier().modify(|ier| ier.set_err_ie(false));
-        }
-        if ris.ovr_ris() {
-            trace!("DCMI IRQ: Overrun.");
-            crate::pac::DCMI.ier().modify(|ier| ier.set_ovr_ie(false));
-        }
-        if ris.frame_ris() {
-            trace!("DCMI IRQ: Frame captured.");
-            crate::pac::DCMI.ier().modify(|ier| ier.set_frame_ie(false));
-        }
-        STATE.waker.wake();
-    }
-
     unsafe fn toggle(enable: bool) {
         crate::pac::DCMI.cr().modify(|r| {
             r.set_enable(enable);
diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs
index e1d7a09b4..4989e17c7 100644
--- a/embassy-stm32/src/eth/mod.rs
+++ b/embassy-stm32/src/eth/mod.rs
@@ -11,7 +11,7 @@ use core::task::Context;
 use embassy_net_driver::{Capabilities, LinkState};
 use embassy_sync::waitqueue::AtomicWaker;
 
-pub use self::_version::*;
+pub use self::_version::{InterruptHandler, *};
 
 #[allow(unused)]
 const MTU: usize = 1514;
diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs
index 9c0f4d66d..8ef2c3584 100644
--- a/embassy-stm32/src/eth/v1/mod.rs
+++ b/embassy-stm32/src/eth/v1/mod.rs
@@ -5,7 +5,7 @@ mod tx_desc;
 
 use core::sync::atomic::{fence, Ordering};
 
-use embassy_cortex_m::interrupt::InterruptExt;
+use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
 use embassy_hal_common::{into_ref, PeripheralRef};
 use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf};
 
@@ -19,7 +19,30 @@ use crate::pac::AFIO;
 #[cfg(any(eth_v1b, eth_v1c))]
 use crate::pac::SYSCFG;
 use crate::pac::{ETH, RCC};
-use crate::Peripheral;
+use crate::{interrupt, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler {}
+
+impl interrupt::Handler<interrupt::ETH> for InterruptHandler {
+    unsafe fn on_interrupt() {
+        WAKER.wake();
+
+        // TODO: Check and clear more flags
+        unsafe {
+            let dma = ETH.ethernet_dma();
+
+            dma.dmasr().modify(|w| {
+                w.set_ts(true);
+                w.set_rs(true);
+                w.set_nis(true);
+            });
+            // Delay two peripheral's clock
+            dma.dmasr().read();
+            dma.dmasr().read();
+        }
+    }
+}
 
 pub struct Ethernet<'d, T: Instance, P: PHY> {
     _peri: PeripheralRef<'d, T>,
@@ -77,7 +100,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
     pub fn new<const TX: usize, const RX: usize>(
         queue: &'d mut PacketQueue<TX, RX>,
         peri: impl Peripheral<P = T> + 'd,
-        interrupt: impl Peripheral<P = crate::interrupt::ETH> + 'd,
+        _irq: impl interrupt::Binding<interrupt::ETH, InterruptHandler> + 'd,
         ref_clk: impl Peripheral<P = impl RefClkPin<T>> + 'd,
         mdio: impl Peripheral<P = impl MDIOPin<T>> + 'd,
         mdc: impl Peripheral<P = impl MDCPin<T>> + 'd,
@@ -91,7 +114,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
         mac_addr: [u8; 6],
         phy_addr: u8,
     ) -> Self {
-        into_ref!(peri, interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en);
+        into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en);
 
         unsafe {
             // Enable the necessary Clocks
@@ -244,30 +267,12 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
             P::phy_reset(&mut this);
             P::phy_init(&mut this);
 
-            interrupt.set_handler(Self::on_interrupt);
-            interrupt.enable();
+            interrupt::ETH::steal().unpend();
+            interrupt::ETH::steal().enable();
 
             this
         }
     }
-
-    fn on_interrupt(_cx: *mut ()) {
-        WAKER.wake();
-
-        // TODO: Check and clear more flags
-        unsafe {
-            let dma = ETH.ethernet_dma();
-
-            dma.dmasr().modify(|w| {
-                w.set_ts(true);
-                w.set_rs(true);
-                w.set_nis(true);
-            });
-            // Delay two peripheral's clock
-            dma.dmasr().read();
-            dma.dmasr().read();
-        }
-    }
 }
 
 unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> {
diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs
index d49b1f767..b56c3e8ab 100644
--- a/embassy-stm32/src/eth/v2/mod.rs
+++ b/embassy-stm32/src/eth/v2/mod.rs
@@ -2,7 +2,7 @@ mod descriptors;
 
 use core::sync::atomic::{fence, Ordering};
 
-use embassy_cortex_m::interrupt::InterruptExt;
+use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
 use embassy_hal_common::{into_ref, PeripheralRef};
 
 pub(crate) use self::descriptors::{RDes, RDesRing, TDes, TDesRing};
@@ -10,7 +10,30 @@ use super::*;
 use crate::gpio::sealed::{AFType, Pin as _};
 use crate::gpio::{AnyPin, Speed};
 use crate::pac::ETH;
-use crate::Peripheral;
+use crate::{interrupt, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler {}
+
+impl interrupt::Handler<interrupt::ETH> for InterruptHandler {
+    unsafe fn on_interrupt() {
+        WAKER.wake();
+
+        // TODO: Check and clear more flags
+        unsafe {
+            let dma = ETH.ethernet_dma();
+
+            dma.dmacsr().modify(|w| {
+                w.set_ti(true);
+                w.set_ri(true);
+                w.set_nis(true);
+            });
+            // Delay two peripheral's clock
+            dma.dmacsr().read();
+            dma.dmacsr().read();
+        }
+    }
+}
 
 const MTU: usize = 1514; // 14 Ethernet header + 1500 IP packet
 
@@ -41,7 +64,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
     pub fn new<const TX: usize, const RX: usize>(
         queue: &'d mut PacketQueue<TX, RX>,
         peri: impl Peripheral<P = T> + 'd,
-        interrupt: impl Peripheral<P = crate::interrupt::ETH> + 'd,
+        _irq: impl interrupt::Binding<interrupt::ETH, InterruptHandler> + 'd,
         ref_clk: impl Peripheral<P = impl RefClkPin<T>> + 'd,
         mdio: impl Peripheral<P = impl MDIOPin<T>> + 'd,
         mdc: impl Peripheral<P = impl MDCPin<T>> + 'd,
@@ -55,7 +78,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
         mac_addr: [u8; 6],
         phy_addr: u8,
     ) -> Self {
-        into_ref!(peri, interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en);
+        into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en);
 
         unsafe {
             // Enable the necessary Clocks
@@ -215,30 +238,12 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
             P::phy_reset(&mut this);
             P::phy_init(&mut this);
 
-            interrupt.set_handler(Self::on_interrupt);
-            interrupt.enable();
+            interrupt::ETH::steal().unpend();
+            interrupt::ETH::steal().enable();
 
             this
         }
     }
-
-    fn on_interrupt(_cx: *mut ()) {
-        WAKER.wake();
-
-        // TODO: Check and clear more flags
-        unsafe {
-            let dma = ETH.ethernet_dma();
-
-            dma.dmacsr().modify(|w| {
-                w.set_ti(true);
-                w.set_ri(true);
-                w.set_nis(true);
-            });
-            // Delay two peripheral's clock
-            dma.dmacsr().read();
-            dma.dmacsr().read();
-        }
-    }
 }
 
 unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> {
diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs
index 4b47f0eb1..b9be2e587 100644
--- a/embassy-stm32/src/i2c/v1.rs
+++ b/embassy-stm32/src/i2c/v1.rs
@@ -9,7 +9,16 @@ use crate::gpio::Pull;
 use crate::i2c::{Error, Instance, SclPin, SdaPin};
 use crate::pac::i2c;
 use crate::time::Hertz;
-use crate::Peripheral;
+use crate::{interrupt, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {}
+}
 
 #[non_exhaustive]
 #[derive(Copy, Clone)]
@@ -48,7 +57,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         _peri: impl Peripheral<P = T> + 'd,
         scl: impl Peripheral<P = impl SclPin<T>> + 'd,
         sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
-        _irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         tx_dma: impl Peripheral<P = TXDMA> + 'd,
         rx_dma: impl Peripheral<P = RXDMA> + 'd,
         freq: Hertz,
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index 853bc128f..642ddc18c 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -1,7 +1,9 @@
 use core::cmp;
 use core::future::poll_fn;
+use core::marker::PhantomData;
 use core::task::Poll;
 
+use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
 use embassy_embedded_hal::SetConfig;
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
@@ -11,10 +13,30 @@ use crate::dma::{NoDma, Transfer};
 use crate::gpio::sealed::AFType;
 use crate::gpio::Pull;
 use crate::i2c::{Error, Instance, SclPin, SdaPin};
-use crate::interrupt::InterruptExt;
 use crate::pac::i2c;
 use crate::time::Hertz;
-use crate::Peripheral;
+use crate::{interrupt, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        let regs = T::regs();
+        let isr = regs.isr().read();
+
+        if isr.tcr() || isr.tc() {
+            T::state().waker.wake();
+        }
+        // The flag can only be cleared by writting to nbytes, we won't do that here, so disable
+        // the interrupt
+        critical_section::with(|_| {
+            regs.cr1().modify(|w| w.set_tcie(false));
+        });
+    }
+}
 
 #[non_exhaustive]
 #[derive(Copy, Clone)]
@@ -56,13 +78,13 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         peri: impl Peripheral<P = T> + 'd,
         scl: impl Peripheral<P = impl SclPin<T>> + 'd,
         sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         tx_dma: impl Peripheral<P = TXDMA> + 'd,
         rx_dma: impl Peripheral<P = RXDMA> + 'd,
         freq: Hertz,
         config: Config,
     ) -> Self {
-        into_ref!(peri, irq, scl, sda, tx_dma, rx_dma);
+        into_ref!(peri, scl, sda, tx_dma, rx_dma);
 
         T::enable();
         T::reset();
@@ -111,9 +133,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             });
         }
 
-        irq.set_handler(Self::on_interrupt);
-        irq.unpend();
-        irq.enable();
+        unsafe { T::Interrupt::steal() }.unpend();
+        unsafe { T::Interrupt::steal() }.enable();
 
         Self {
             _peri: peri,
@@ -122,20 +143,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         }
     }
 
-    unsafe fn on_interrupt(_: *mut ()) {
-        let regs = T::regs();
-        let isr = regs.isr().read();
-
-        if isr.tcr() || isr.tc() {
-            T::state().waker.wake();
-        }
-        // The flag can only be cleared by writting to nbytes, we won't do that here, so disable
-        // the interrupt
-        critical_section::with(|_| {
-            regs.cr1().modify(|w| w.set_tcie(false));
-        });
-    }
-
     fn master_stop(&mut self) {
         unsafe {
             T::regs().cr2().write(|w| w.set_stop(true));
diff --git a/embassy-stm32/src/interrupt.rs b/embassy-stm32/src/interrupt.rs
deleted file mode 100644
index b66e4c7ef..000000000
--- a/embassy-stm32/src/interrupt.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-pub use critical_section::{CriticalSection, Mutex};
-pub use embassy_cortex_m::interrupt::*;
-
-pub use crate::_generated::interrupt::*;
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 1920e2642..6722658c9 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -6,7 +6,6 @@ pub mod fmt;
 include!(concat!(env!("OUT_DIR"), "/_macros.rs"));
 
 // Utilities
-pub mod interrupt;
 pub mod time;
 mod traits;
 
@@ -73,6 +72,41 @@ pub(crate) mod _generated {
     include!(concat!(env!("OUT_DIR"), "/_generated.rs"));
 }
 
+pub mod interrupt {
+    //! Interrupt definitions and macros to bind them.
+    pub use cortex_m::interrupt::{CriticalSection, Mutex};
+    pub use embassy_cortex_m::interrupt::{Binding, Handler, Interrupt, InterruptExt, Priority};
+
+    pub use crate::_generated::interrupt::*;
+
+    /// Macro to bind interrupts to handlers.
+    ///
+    /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`)
+    /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to
+    /// prove at compile-time that the right interrupts have been bound.
+    // developer note: this macro can't be in `embassy-cortex-m` due to the use of `$crate`.
+    #[macro_export]
+    macro_rules! bind_interrupts {
+        ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => {
+            $vis struct $name;
+
+            $(
+                #[allow(non_snake_case)]
+                #[no_mangle]
+                unsafe extern "C" fn $irq() {
+                    $(
+                        <$handler as $crate::interrupt::Handler<$crate::interrupt::$irq>>::on_interrupt();
+                    )*
+                }
+
+                $(
+                    unsafe impl $crate::interrupt::Binding<$crate::interrupt::$irq, $handler> for $name {}
+                )*
+            )*
+        };
+    }
+}
+
 // Reexports
 pub use _generated::{peripherals, Peripherals};
 pub use embassy_cortex_m::executor;
diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs
index be788f1b0..be03a1bac 100644
--- a/embassy-stm32/src/sdmmc/mod.rs
+++ b/embassy-stm32/src/sdmmc/mod.rs
@@ -2,6 +2,7 @@
 
 use core::default::Default;
 use core::future::poll_fn;
+use core::marker::PhantomData;
 use core::ops::{Deref, DerefMut};
 use core::task::Poll;
 
@@ -17,7 +18,36 @@ use crate::interrupt::{Interrupt, InterruptExt};
 use crate::pac::sdmmc::Sdmmc as RegBlock;
 use crate::rcc::RccPeripheral;
 use crate::time::Hertz;
-use crate::{peripherals, Peripheral};
+use crate::{interrupt, peripherals, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> InterruptHandler<T> {
+    fn data_interrupts(enable: bool) {
+        let regs = T::regs();
+        // NOTE(unsafe) Atomic write
+        unsafe {
+            regs.maskr().write(|w| {
+                w.set_dcrcfailie(enable);
+                w.set_dtimeoutie(enable);
+                w.set_dataendie(enable);
+
+                #[cfg(sdmmc_v2)]
+                w.set_dabortie(enable);
+            });
+        }
+    }
+}
+
+impl<T: Instance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        Self::data_interrupts(false);
+        T::state().wake();
+    }
+}
 
 /// Frequency used for SD Card initialization. Must be no higher than 400 kHz.
 const SD_INIT_FREQ: Hertz = Hertz(400_000);
@@ -223,7 +253,6 @@ impl Default for Config {
 /// Sdmmc device
 pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma<T> = NoDma> {
     _peri: PeripheralRef<'d, T>,
-    irq: PeripheralRef<'d, T::Interrupt>,
     #[allow(unused)]
     dma: PeripheralRef<'d, Dma>,
 
@@ -247,7 +276,7 @@ pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma<T> = NoDma> {
 impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
     pub fn new_1bit(
         sdmmc: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
         clk: impl Peripheral<P = impl CkPin<T>> + 'd,
         cmd: impl Peripheral<P = impl CmdPin<T>> + 'd,
@@ -268,7 +297,6 @@ impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
 
         Self::new_inner(
             sdmmc,
-            irq,
             dma,
             clk.map_into(),
             cmd.map_into(),
@@ -282,7 +310,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
 
     pub fn new_4bit(
         sdmmc: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
         clk: impl Peripheral<P = impl CkPin<T>> + 'd,
         cmd: impl Peripheral<P = impl CmdPin<T>> + 'd,
@@ -312,7 +340,6 @@ impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
 
         Self::new_inner(
             sdmmc,
-            irq,
             dma,
             clk.map_into(),
             cmd.map_into(),
@@ -329,7 +356,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
 impl<'d, T: Instance> Sdmmc<'d, T, NoDma> {
     pub fn new_1bit(
         sdmmc: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         clk: impl Peripheral<P = impl CkPin<T>> + 'd,
         cmd: impl Peripheral<P = impl CmdPin<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
@@ -349,7 +376,6 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> {
 
         Self::new_inner(
             sdmmc,
-            irq,
             NoDma.into_ref(),
             clk.map_into(),
             cmd.map_into(),
@@ -363,7 +389,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> {
 
     pub fn new_4bit(
         sdmmc: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         clk: impl Peripheral<P = impl CkPin<T>> + 'd,
         cmd: impl Peripheral<P = impl CmdPin<T>> + 'd,
         d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
@@ -392,7 +418,6 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> {
 
         Self::new_inner(
             sdmmc,
-            irq,
             NoDma.into_ref(),
             clk.map_into(),
             cmd.map_into(),
@@ -408,7 +433,6 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> {
 impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
     fn new_inner(
         sdmmc: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
         dma: impl Peripheral<P = Dma> + 'd,
         clk: PeripheralRef<'d, AnyPin>,
         cmd: PeripheralRef<'d, AnyPin>,
@@ -418,14 +442,13 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
         d3: Option<PeripheralRef<'d, AnyPin>>,
         config: Config,
     ) -> Self {
-        into_ref!(sdmmc, irq, dma);
+        into_ref!(sdmmc, dma);
 
         T::enable();
         T::reset();
 
-        irq.set_handler(Self::on_interrupt);
-        irq.unpend();
-        irq.enable();
+        unsafe { T::Interrupt::steal() }.unpend();
+        unsafe { T::Interrupt::steal() }.enable();
 
         let regs = T::regs();
         unsafe {
@@ -451,7 +474,6 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
 
         Self {
             _peri: sdmmc,
-            irq,
             dma,
 
             clk,
@@ -691,7 +713,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
         let on_drop = OnDrop::new(|| unsafe { Self::on_drop() });
 
         let transfer = self.prepare_datapath_read(&mut status, 64, 6);
-        Self::data_interrupts(true);
+        InterruptHandler::<T>::data_interrupts(true);
         Self::cmd(Cmd::cmd6(set_function), true)?; // CMD6
 
         let res = poll_fn(|cx| {
@@ -767,7 +789,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
         let on_drop = OnDrop::new(|| unsafe { Self::on_drop() });
 
         let transfer = self.prepare_datapath_read(&mut status, 64, 6);
-        Self::data_interrupts(true);
+        InterruptHandler::<T>::data_interrupts(true);
         Self::cmd(Cmd::card_status(0), true)?;
 
         let res = poll_fn(|cx| {
@@ -849,23 +871,6 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
         }
     }
 
-    /// Enables the interrupts for data transfer
-    #[inline(always)]
-    fn data_interrupts(enable: bool) {
-        let regs = T::regs();
-        // NOTE(unsafe) Atomic write
-        unsafe {
-            regs.maskr().write(|w| {
-                w.set_dcrcfailie(enable);
-                w.set_dtimeoutie(enable);
-                w.set_dataendie(enable);
-
-                #[cfg(sdmmc_v2)]
-                w.set_dabortie(enable);
-            });
-        }
-    }
-
     async fn get_scr(&mut self, card: &mut Card) -> Result<(), Error> {
         // Read the the 64-bit SCR register
         Self::cmd(Cmd::set_block_length(8), false)?; // CMD16
@@ -878,7 +883,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
         let on_drop = OnDrop::new(|| unsafe { Self::on_drop() });
 
         let transfer = self.prepare_datapath_read(&mut scr[..], 8, 3);
-        Self::data_interrupts(true);
+        InterruptHandler::<T>::data_interrupts(true);
         Self::cmd(Cmd::cmd51(), true)?;
 
         let res = poll_fn(|cx| {
@@ -996,7 +1001,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
             // Wait for the abort
             while Self::data_active() {}
         }
-        Self::data_interrupts(false);
+        InterruptHandler::<T>::data_interrupts(false);
         Self::clear_interrupt_flags();
         Self::stop_datapath();
     }
@@ -1170,7 +1175,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
         let on_drop = OnDrop::new(|| unsafe { Self::on_drop() });
 
         let transfer = self.prepare_datapath_read(buffer, 512, 9);
-        Self::data_interrupts(true);
+        InterruptHandler::<T>::data_interrupts(true);
         Self::cmd(Cmd::read_single_block(address), true)?;
 
         let res = poll_fn(|cx| {
@@ -1219,7 +1224,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
         Self::cmd(Cmd::write_single_block(address), true)?;
 
         let transfer = self.prepare_datapath_write(buffer, 512, 9);
-        Self::data_interrupts(true);
+        InterruptHandler::<T>::data_interrupts(true);
 
         #[cfg(sdmmc_v2)]
         Self::cmd(Cmd::write_single_block(address), true)?;
@@ -1279,17 +1284,11 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> {
     pub fn clock(&self) -> Hertz {
         self.clock
     }
-
-    #[inline(always)]
-    fn on_interrupt(_: *mut ()) {
-        Self::data_interrupts(false);
-        T::state().wake();
-    }
 }
 
 impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Drop for Sdmmc<'d, T, Dma> {
     fn drop(&mut self) {
-        self.irq.disable();
+        unsafe { T::Interrupt::steal() }.disable();
         unsafe { Self::on_drop() };
 
         critical_section::with(|_| unsafe {
diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs
index d45c90dd8..2236fde28 100644
--- a/embassy-stm32/src/time_driver.rs
+++ b/embassy-stm32/src/time_driver.rs
@@ -4,13 +4,14 @@ use core::sync::atomic::{compiler_fence, Ordering};
 use core::{mem, ptr};
 
 use atomic_polyfill::{AtomicU32, AtomicU8};
+use critical_section::CriticalSection;
 use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 use embassy_sync::blocking_mutex::Mutex;
 use embassy_time::driver::{AlarmHandle, Driver};
 use embassy_time::TICK_HZ;
 use stm32_metapac::timer::regs;
 
-use crate::interrupt::{CriticalSection, InterruptExt};
+use crate::interrupt::InterruptExt;
 use crate::pac::timer::vals;
 use crate::rcc::sealed::RccPeripheral;
 use crate::timer::sealed::{Basic16bitInstance as BasicInstance, GeneralPurpose16bitInstance as Instance};
diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs
index 3651b8ea5..dc6104cc3 100644
--- a/embassy-stm32/src/tl_mbox/mod.rs
+++ b/embassy-stm32/src/tl_mbox/mod.rs
@@ -1,7 +1,6 @@
 use core::mem::MaybeUninit;
 
 use bit_field::BitField;
-use embassy_cortex_m::interrupt::InterruptExt;
 use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 use embassy_sync::channel::Channel;
 
@@ -12,7 +11,7 @@ use self::mm::MemoryManager;
 use self::shci::{shci_ble_init, ShciBleInitCmdParam};
 use self::sys::Sys;
 use self::unsafe_linked_list::LinkedListNode;
-use crate::_generated::interrupt::{IPCC_C1_RX, IPCC_C1_TX};
+use crate::interrupt;
 use crate::ipcc::Ipcc;
 
 mod ble;
@@ -55,6 +54,19 @@ pub struct FusInfoTable {
     fus_info: u32,
 }
 
+/// Interrupt handler.
+pub struct ReceiveInterruptHandler {}
+
+impl interrupt::Handler<interrupt::IPCC_C1_RX> for ReceiveInterruptHandler {
+    unsafe fn on_interrupt() {}
+}
+
+pub struct TransmitInterruptHandler {}
+
+impl interrupt::Handler<interrupt::IPCC_C1_TX> for TransmitInterruptHandler {
+    unsafe fn on_interrupt() {}
+}
+
 /// # Version
 /// - 0 -> 3   = Build - 0: Untracked - 15:Released - x: Tracked version
 /// - 4 -> 7   = branch - 0: Mass Market - x: ...
@@ -285,7 +297,11 @@ pub struct TlMbox {
 
 impl TlMbox {
     /// initializes low-level transport between CPU1 and BLE stack on CPU2
-    pub fn init(ipcc: &mut Ipcc, rx_irq: IPCC_C1_RX, tx_irq: IPCC_C1_TX) -> TlMbox {
+    pub fn init(
+        ipcc: &mut Ipcc,
+        _irqs: impl interrupt::Binding<interrupt::IPCC_C1_RX, ReceiveInterruptHandler>
+            + interrupt::Binding<interrupt::IPCC_C1_TX, TransmitInterruptHandler>,
+    ) -> TlMbox {
         unsafe {
             TL_REF_TABLE = MaybeUninit::new(RefTable {
                 device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(),
@@ -326,23 +342,23 @@ impl TlMbox {
         let _ble = Ble::new(ipcc);
         let _mm = MemoryManager::new();
 
-        rx_irq.disable();
-        tx_irq.disable();
-
-        rx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
-        tx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
-
-        rx_irq.set_handler(|ipcc| {
-            let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
-            Self::interrupt_ipcc_rx_handler(ipcc);
-        });
-        tx_irq.set_handler(|ipcc| {
-            let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
-            Self::interrupt_ipcc_tx_handler(ipcc);
-        });
-
-        rx_irq.enable();
-        tx_irq.enable();
+        //        rx_irq.disable();
+        //        tx_irq.disable();
+        //
+        //        rx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
+        //        tx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
+        //
+        //        rx_irq.set_handler(|ipcc| {
+        //            let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
+        //            Self::interrupt_ipcc_rx_handler(ipcc);
+        //        });
+        //        tx_irq.set_handler(|ipcc| {
+        //            let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
+        //            Self::interrupt_ipcc_tx_handler(ipcc);
+        //        });
+        //
+        //        rx_irq.enable();
+        //        tx_irq.enable();
 
         TlMbox { _sys, _ble, _mm }
     }
@@ -374,6 +390,7 @@ impl TlMbox {
         TL_CHANNEL.recv().await
     }
 
+    #[allow(dead_code)]
     fn interrupt_ipcc_rx_handler(ipcc: &mut Ipcc) {
         if ipcc.is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) {
             sys::Sys::evt_handler(ipcc);
@@ -384,6 +401,7 @@ impl TlMbox {
         }
     }
 
+    #[allow(dead_code)]
     fn interrupt_ipcc_tx_handler(ipcc: &mut Ipcc) {
         if ipcc.is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) {
             // TODO: handle this case
diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs
index 12cf8b0fc..9f1da3583 100644
--- a/embassy-stm32/src/usart/buffered.rs
+++ b/embassy-stm32/src/usart/buffered.rs
@@ -8,6 +8,78 @@ use embassy_sync::waitqueue::AtomicWaker;
 
 use super::*;
 
+/// Interrupt handler.
+pub struct InterruptHandler<T: BasicInstance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: BasicInstance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        let r = T::regs();
+        let state = T::buffered_state();
+
+        // RX
+        unsafe {
+            let sr = sr(r).read();
+            clear_interrupt_flags(r, sr);
+
+            if sr.rxne() {
+                if sr.pe() {
+                    warn!("Parity error");
+                }
+                if sr.fe() {
+                    warn!("Framing error");
+                }
+                if sr.ne() {
+                    warn!("Noise error");
+                }
+                if sr.ore() {
+                    warn!("Overrun error");
+                }
+
+                let mut rx_writer = state.rx_buf.writer();
+                let buf = rx_writer.push_slice();
+                if !buf.is_empty() {
+                    // This read also clears the error and idle interrupt flags on v1.
+                    buf[0] = rdr(r).read_volatile();
+                    rx_writer.push_done(1);
+                } else {
+                    // FIXME: Should we disable any further RX interrupts when the buffer becomes full.
+                }
+
+                if state.rx_buf.is_full() {
+                    state.rx_waker.wake();
+                }
+            }
+
+            if sr.idle() {
+                state.rx_waker.wake();
+            };
+        }
+
+        // TX
+        unsafe {
+            if sr(r).read().txe() {
+                let mut tx_reader = state.tx_buf.reader();
+                let buf = tx_reader.pop_slice();
+                if !buf.is_empty() {
+                    r.cr1().modify(|w| {
+                        w.set_txeie(true);
+                    });
+                    tdr(r).write_volatile(buf[0].into());
+                    tx_reader.pop_done(1);
+                    state.tx_waker.wake();
+                } else {
+                    // Disable interrupt until we have something to transmit again
+                    r.cr1().modify(|w| {
+                        w.set_txeie(false);
+                    });
+                }
+            }
+        }
+    }
+}
+
 pub struct State {
     rx_waker: AtomicWaker,
     rx_buf: RingBuffer,
@@ -43,7 +115,7 @@ pub struct BufferedUartRx<'d, T: BasicInstance> {
 impl<'d, T: BasicInstance> BufferedUart<'d, T> {
     pub fn new(
         peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
         tx_buffer: &'d mut [u8],
@@ -53,12 +125,12 @@ impl<'d, T: BasicInstance> BufferedUart<'d, T> {
         T::enable();
         T::reset();
 
-        Self::new_inner(peri, irq, rx, tx, tx_buffer, rx_buffer, config)
+        Self::new_inner(peri, rx, tx, tx_buffer, rx_buffer, config)
     }
 
     pub fn new_with_rtscts(
         peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
         rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
@@ -81,13 +153,13 @@ impl<'d, T: BasicInstance> BufferedUart<'d, T> {
             });
         }
 
-        Self::new_inner(peri, irq, rx, tx, tx_buffer, rx_buffer, config)
+        Self::new_inner(peri, rx, tx, tx_buffer, rx_buffer, config)
     }
 
     #[cfg(not(any(usart_v1, usart_v2)))]
     pub fn new_with_de(
         peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
         de: impl Peripheral<P = impl DePin<T>> + 'd,
@@ -107,19 +179,18 @@ impl<'d, T: BasicInstance> BufferedUart<'d, T> {
             });
         }
 
-        Self::new_inner(peri, irq, rx, tx, tx_buffer, rx_buffer, config)
+        Self::new_inner(peri, rx, tx, tx_buffer, rx_buffer, config)
     }
 
     fn new_inner(
         _peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
         tx_buffer: &'d mut [u8],
         rx_buffer: &'d mut [u8],
         config: Config,
     ) -> BufferedUart<'d, T> {
-        into_ref!(_peri, rx, tx, irq);
+        into_ref!(_peri, rx, tx);
 
         let state = T::buffered_state();
         let len = tx_buffer.len();
@@ -145,9 +216,8 @@ impl<'d, T: BasicInstance> BufferedUart<'d, T> {
             });
         }
 
-        irq.set_handler(on_interrupt::<T>);
-        irq.unpend();
-        irq.enable();
+        unsafe { T::Interrupt::steal() }.unpend();
+        unsafe { T::Interrupt::steal() }.enable();
 
         Self {
             rx: BufferedUartRx { phantom: PhantomData },
@@ -336,71 +406,6 @@ impl<'d, T: BasicInstance> Drop for BufferedUartTx<'d, T> {
     }
 }
 
-unsafe fn on_interrupt<T: BasicInstance>(_: *mut ()) {
-    let r = T::regs();
-    let state = T::buffered_state();
-
-    // RX
-    unsafe {
-        let sr = sr(r).read();
-        clear_interrupt_flags(r, sr);
-
-        if sr.rxne() {
-            if sr.pe() {
-                warn!("Parity error");
-            }
-            if sr.fe() {
-                warn!("Framing error");
-            }
-            if sr.ne() {
-                warn!("Noise error");
-            }
-            if sr.ore() {
-                warn!("Overrun error");
-            }
-
-            let mut rx_writer = state.rx_buf.writer();
-            let buf = rx_writer.push_slice();
-            if !buf.is_empty() {
-                // This read also clears the error and idle interrupt flags on v1.
-                buf[0] = rdr(r).read_volatile();
-                rx_writer.push_done(1);
-            } else {
-                // FIXME: Should we disable any further RX interrupts when the buffer becomes full.
-            }
-
-            if state.rx_buf.is_full() {
-                state.rx_waker.wake();
-            }
-        }
-
-        if sr.idle() {
-            state.rx_waker.wake();
-        };
-    }
-
-    // TX
-    unsafe {
-        if sr(r).read().txe() {
-            let mut tx_reader = state.tx_buf.reader();
-            let buf = tx_reader.pop_slice();
-            if !buf.is_empty() {
-                r.cr1().modify(|w| {
-                    w.set_txeie(true);
-                });
-                tdr(r).write_volatile(buf[0].into());
-                tx_reader.pop_done(1);
-                state.tx_waker.wake();
-            } else {
-                // Disable interrupt until we have something to transmit again
-                r.cr1().modify(|w| {
-                    w.set_txeie(false);
-                });
-            }
-        }
-    }
-}
-
 impl embedded_io::Error for Error {
     fn kind(&self) -> embedded_io::ErrorKind {
         embedded_io::ErrorKind::Other
diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs
index b4373529c..0f3e9412e 100644
--- a/embassy-stm32/src/usart/mod.rs
+++ b/embassy-stm32/src/usart/mod.rs
@@ -5,7 +5,7 @@ use core::marker::PhantomData;
 use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::Poll;
 
-use embassy_cortex_m::interrupt::InterruptExt;
+use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use futures::future::{select, Either};
@@ -18,7 +18,71 @@ use crate::pac::usart::Lpuart as Regs;
 use crate::pac::usart::Usart as Regs;
 use crate::pac::usart::{regs, vals};
 use crate::time::Hertz;
-use crate::{peripherals, Peripheral};
+use crate::{interrupt, peripherals, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler<T: BasicInstance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: BasicInstance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        let r = T::regs();
+        let s = T::state();
+
+        let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) };
+
+        let mut wake = false;
+        let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie());
+        if has_errors {
+            // clear all interrupts and DMA Rx Request
+            unsafe {
+                r.cr1().modify(|w| {
+                    // disable RXNE interrupt
+                    w.set_rxneie(false);
+                    // disable parity interrupt
+                    w.set_peie(false);
+                    // disable idle line interrupt
+                    w.set_idleie(false);
+                });
+                r.cr3().modify(|w| {
+                    // disable Error Interrupt: (Frame error, Noise error, Overrun error)
+                    w.set_eie(false);
+                    // disable DMA Rx Request
+                    w.set_dmar(false);
+                });
+            }
+
+            wake = true;
+        } else {
+            if cr1.idleie() && sr.idle() {
+                // IDLE detected: no more data will come
+                unsafe {
+                    r.cr1().modify(|w| {
+                        // disable idle line detection
+                        w.set_idleie(false);
+                    });
+                }
+
+                wake = true;
+            }
+
+            if cr1.rxneie() {
+                // We cannot check the RXNE flag as it is auto-cleared by the DMA controller
+
+                // It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection
+
+                wake = true;
+            }
+        }
+
+        if wake {
+            compiler_fence(Ordering::SeqCst);
+
+            s.rx_waker.wake();
+        }
+    }
+}
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
 pub enum DataBits {
@@ -215,7 +279,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
     /// Useful if you only want Uart Rx. It saves 1 pin and consumes a little less power.
     pub fn new(
         peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         rx_dma: impl Peripheral<P = RxDma> + 'd,
         config: Config,
@@ -223,12 +287,12 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
         T::enable();
         T::reset();
 
-        Self::new_inner(peri, irq, rx, rx_dma, config)
+        Self::new_inner(peri, rx, rx_dma, config)
     }
 
     pub fn new_with_rts(
         peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
         rx_dma: impl Peripheral<P = RxDma> + 'd,
@@ -246,17 +310,16 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
             });
         }
 
-        Self::new_inner(peri, irq, rx, rx_dma, config)
+        Self::new_inner(peri, rx, rx_dma, config)
     }
 
     fn new_inner(
         peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         rx_dma: impl Peripheral<P = RxDma> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, irq, rx, rx_dma);
+        into_ref!(peri, rx, rx_dma);
 
         let r = T::regs();
 
@@ -266,9 +329,8 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
 
         configure(r, &config, T::frequency(), T::KIND, true, false);
 
-        irq.set_handler(Self::on_interrupt);
-        irq.unpend();
-        irq.enable();
+        unsafe { T::Interrupt::steal() }.unpend();
+        unsafe { T::Interrupt::steal() }.enable();
 
         // create state once!
         let _s = T::state();
@@ -282,63 +344,6 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
         }
     }
 
-    fn on_interrupt(_: *mut ()) {
-        let r = T::regs();
-        let s = T::state();
-
-        let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) };
-
-        let mut wake = false;
-        let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie());
-        if has_errors {
-            // clear all interrupts and DMA Rx Request
-            unsafe {
-                r.cr1().modify(|w| {
-                    // disable RXNE interrupt
-                    w.set_rxneie(false);
-                    // disable parity interrupt
-                    w.set_peie(false);
-                    // disable idle line interrupt
-                    w.set_idleie(false);
-                });
-                r.cr3().modify(|w| {
-                    // disable Error Interrupt: (Frame error, Noise error, Overrun error)
-                    w.set_eie(false);
-                    // disable DMA Rx Request
-                    w.set_dmar(false);
-                });
-            }
-
-            wake = true;
-        } else {
-            if cr1.idleie() && sr.idle() {
-                // IDLE detected: no more data will come
-                unsafe {
-                    r.cr1().modify(|w| {
-                        // disable idle line detection
-                        w.set_idleie(false);
-                    });
-                }
-
-                wake = true;
-            }
-
-            if cr1.rxneie() {
-                // We cannot check the RXNE flag as it is auto-cleared by the DMA controller
-
-                // It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection
-
-                wake = true;
-            }
-        }
-
-        if wake {
-            compiler_fence(Ordering::SeqCst);
-
-            s.rx_waker.wake();
-        }
-    }
-
     #[cfg(any(usart_v1, usart_v2))]
     unsafe fn check_rx_flags(&mut self) -> Result<bool, Error> {
         let r = T::regs();
@@ -643,7 +648,7 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
         peri: impl Peripheral<P = T> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         tx_dma: impl Peripheral<P = TxDma> + 'd,
         rx_dma: impl Peripheral<P = RxDma> + 'd,
         config: Config,
@@ -651,14 +656,14 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
         T::enable();
         T::reset();
 
-        Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config)
+        Self::new_inner(peri, rx, tx, tx_dma, rx_dma, config)
     }
 
     pub fn new_with_rtscts(
         peri: impl Peripheral<P = T> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
         cts: impl Peripheral<P = impl CtsPin<T>> + 'd,
         tx_dma: impl Peripheral<P = TxDma> + 'd,
@@ -678,7 +683,7 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
                 w.set_ctse(true);
             });
         }
-        Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config)
+        Self::new_inner(peri, rx, tx, tx_dma, rx_dma, config)
     }
 
     #[cfg(not(any(usart_v1, usart_v2)))]
@@ -686,7 +691,7 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
         peri: impl Peripheral<P = T> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         de: impl Peripheral<P = impl DePin<T>> + 'd,
         tx_dma: impl Peripheral<P = TxDma> + 'd,
         rx_dma: impl Peripheral<P = RxDma> + 'd,
@@ -703,19 +708,18 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
                 w.set_dem(true);
             });
         }
-        Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config)
+        Self::new_inner(peri, rx, tx, tx_dma, rx_dma, config)
     }
 
     fn new_inner(
         peri: impl Peripheral<P = T> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
         tx: impl Peripheral<P = impl TxPin<T>> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
         tx_dma: impl Peripheral<P = TxDma> + 'd,
         rx_dma: impl Peripheral<P = RxDma> + 'd,
         config: Config,
     ) -> Self {
-        into_ref!(peri, rx, tx, irq, tx_dma, rx_dma);
+        into_ref!(peri, rx, tx, tx_dma, rx_dma);
 
         let r = T::regs();
 
@@ -726,9 +730,8 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
 
         configure(r, &config, T::frequency(), T::KIND, true, true);
 
-        irq.set_handler(UartRx::<T, RxDma>::on_interrupt);
-        irq.unpend();
-        irq.enable();
+        unsafe { T::Interrupt::steal() }.unpend();
+        unsafe { T::Interrupt::steal() }.enable();
 
         // create state once!
         let _s = T::state();
@@ -1068,6 +1071,9 @@ mod eio {
 
 #[cfg(feature = "nightly")]
 pub use buffered::*;
+
+#[cfg(feature = "nightly")]
+pub use crate::usart::buffered::InterruptHandler as BufferedInterruptHandler;
 #[cfg(feature = "nightly")]
 mod buffered;
 
diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs
index 56c46476c..a9ff284ae 100644
--- a/embassy-stm32/src/usb/usb.rs
+++ b/embassy-stm32/src/usb/usb.rs
@@ -14,12 +14,99 @@ use embassy_usb_driver::{
 
 use super::{DmPin, DpPin, Instance};
 use crate::gpio::sealed::AFType;
-use crate::interrupt::InterruptExt;
+use crate::interrupt::{Interrupt, InterruptExt};
 use crate::pac::usb::regs;
 use crate::pac::usb::vals::{EpType, Stat};
 use crate::pac::USBRAM;
 use crate::rcc::sealed::RccPeripheral;
-use crate::Peripheral;
+use crate::{interrupt, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        unsafe {
+            let regs = T::regs();
+            //let x = regs.istr().read().0;
+            //trace!("USB IRQ: {:08x}", x);
+
+            let istr = regs.istr().read();
+
+            if istr.susp() {
+                //trace!("USB IRQ: susp");
+                IRQ_SUSPEND.store(true, Ordering::Relaxed);
+                regs.cntr().modify(|w| {
+                    w.set_fsusp(true);
+                    w.set_lpmode(true);
+                });
+
+                // Write 0 to clear.
+                let mut clear = regs::Istr(!0);
+                clear.set_susp(false);
+                regs.istr().write_value(clear);
+
+                // Wake main thread.
+                BUS_WAKER.wake();
+            }
+
+            if istr.wkup() {
+                //trace!("USB IRQ: wkup");
+                IRQ_RESUME.store(true, Ordering::Relaxed);
+                regs.cntr().modify(|w| {
+                    w.set_fsusp(false);
+                    w.set_lpmode(false);
+                });
+
+                // Write 0 to clear.
+                let mut clear = regs::Istr(!0);
+                clear.set_wkup(false);
+                regs.istr().write_value(clear);
+
+                // Wake main thread.
+                BUS_WAKER.wake();
+            }
+
+            if istr.reset() {
+                //trace!("USB IRQ: reset");
+                IRQ_RESET.store(true, Ordering::Relaxed);
+
+                // Write 0 to clear.
+                let mut clear = regs::Istr(!0);
+                clear.set_reset(false);
+                regs.istr().write_value(clear);
+
+                // Wake main thread.
+                BUS_WAKER.wake();
+            }
+
+            if istr.ctr() {
+                let index = istr.ep_id() as usize;
+                let mut epr = regs.epr(index).read();
+                if epr.ctr_rx() {
+                    if index == 0 && epr.setup() {
+                        EP0_SETUP.store(true, Ordering::Relaxed);
+                    }
+                    //trace!("EP {} RX, setup={}", index, epr.setup());
+                    EP_OUT_WAKERS[index].wake();
+                }
+                if epr.ctr_tx() {
+                    //trace!("EP {} TX", index);
+                    EP_IN_WAKERS[index].wake();
+                }
+                epr.set_dtog_rx(false);
+                epr.set_dtog_tx(false);
+                epr.set_stat_rx(Stat(0));
+                epr.set_stat_tx(Stat(0));
+                epr.set_ctr_rx(!epr.ctr_rx());
+                epr.set_ctr_tx(!epr.ctr_tx());
+                regs.epr(index).write_value(epr);
+            }
+        }
+    }
+}
 
 const EP_COUNT: usize = 8;
 
@@ -168,14 +255,13 @@ pub struct Driver<'d, T: Instance> {
 impl<'d, T: Instance> Driver<'d, T> {
     pub fn new(
         _usb: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
         dp: impl Peripheral<P = impl DpPin<T>> + 'd,
         dm: impl Peripheral<P = impl DmPin<T>> + 'd,
     ) -> Self {
-        into_ref!(irq, dp, dm);
-        irq.set_handler(Self::on_interrupt);
-        irq.unpend();
-        irq.enable();
+        into_ref!(dp, dm);
+        unsafe { T::Interrupt::steal() }.unpend();
+        unsafe { T::Interrupt::steal() }.enable();
 
         let regs = T::regs();
 
@@ -225,86 +311,6 @@ impl<'d, T: Instance> Driver<'d, T> {
         }
     }
 
-    fn on_interrupt(_: *mut ()) {
-        unsafe {
-            let regs = T::regs();
-            //let x = regs.istr().read().0;
-            //trace!("USB IRQ: {:08x}", x);
-
-            let istr = regs.istr().read();
-
-            if istr.susp() {
-                //trace!("USB IRQ: susp");
-                IRQ_SUSPEND.store(true, Ordering::Relaxed);
-                regs.cntr().modify(|w| {
-                    w.set_fsusp(true);
-                    w.set_lpmode(true);
-                });
-
-                // Write 0 to clear.
-                let mut clear = regs::Istr(!0);
-                clear.set_susp(false);
-                regs.istr().write_value(clear);
-
-                // Wake main thread.
-                BUS_WAKER.wake();
-            }
-
-            if istr.wkup() {
-                //trace!("USB IRQ: wkup");
-                IRQ_RESUME.store(true, Ordering::Relaxed);
-                regs.cntr().modify(|w| {
-                    w.set_fsusp(false);
-                    w.set_lpmode(false);
-                });
-
-                // Write 0 to clear.
-                let mut clear = regs::Istr(!0);
-                clear.set_wkup(false);
-                regs.istr().write_value(clear);
-
-                // Wake main thread.
-                BUS_WAKER.wake();
-            }
-
-            if istr.reset() {
-                //trace!("USB IRQ: reset");
-                IRQ_RESET.store(true, Ordering::Relaxed);
-
-                // Write 0 to clear.
-                let mut clear = regs::Istr(!0);
-                clear.set_reset(false);
-                regs.istr().write_value(clear);
-
-                // Wake main thread.
-                BUS_WAKER.wake();
-            }
-
-            if istr.ctr() {
-                let index = istr.ep_id() as usize;
-                let mut epr = regs.epr(index).read();
-                if epr.ctr_rx() {
-                    if index == 0 && epr.setup() {
-                        EP0_SETUP.store(true, Ordering::Relaxed);
-                    }
-                    //trace!("EP {} RX, setup={}", index, epr.setup());
-                    EP_OUT_WAKERS[index].wake();
-                }
-                if epr.ctr_tx() {
-                    //trace!("EP {} TX", index);
-                    EP_IN_WAKERS[index].wake();
-                }
-                epr.set_dtog_rx(false);
-                epr.set_dtog_tx(false);
-                epr.set_stat_rx(Stat(0));
-                epr.set_stat_tx(Stat(0));
-                epr.set_ctr_rx(!epr.ctr_rx());
-                epr.set_ctr_tx(!epr.ctr_tx());
-                regs.epr(index).write_value(epr);
-            }
-        }
-    }
-
     fn alloc_ep_mem(&mut self, len: u16) -> u16 {
         assert!(len as usize % USBRAM_ALIGN == 0);
         let addr = self.ep_mem_free;
diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs
index 96c574ca8..921a73c8b 100644
--- a/embassy-stm32/src/usb_otg/usb.rs
+++ b/embassy-stm32/src/usb_otg/usb.rs
@@ -4,7 +4,7 @@ use core::task::Poll;
 
 use atomic_polyfill::{AtomicBool, AtomicU16, Ordering};
 use embassy_cortex_m::interrupt::InterruptExt;
-use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
+use embassy_hal_common::{into_ref, Peripheral};
 use embassy_sync::waitqueue::AtomicWaker;
 use embassy_usb_driver::{
     self, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, EndpointOut,
@@ -14,490 +14,18 @@ use futures::future::poll_fn;
 
 use super::*;
 use crate::gpio::sealed::AFType;
+use crate::interrupt;
 use crate::pac::otg::{regs, vals};
 use crate::rcc::sealed::RccPeripheral;
 use crate::time::Hertz;
 
-macro_rules! config_ulpi_pins {
-    ($($pin:ident),*) => {
-        into_ref!($($pin),*);
-        // NOTE(unsafe) Exclusive access to the registers
-        critical_section::with(|_| unsafe {
-            $(
-                $pin.set_as_af($pin.af_num(), AFType::OutputPushPull);
-                #[cfg(gpio_v2)]
-                $pin.set_speed(crate::gpio::Speed::VeryHigh);
-            )*
-        })
-    };
+/// Interrupt handler.
+pub struct InterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
 }
 
-// From `synopsys-usb-otg` crate:
-// This calculation doesn't correspond to one in a Reference Manual.
-// In fact, the required number of words is higher than indicated in RM.
-// The following numbers are pessimistic and were figured out empirically.
-const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30;
-
-/// USB PHY type
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum PhyType {
-    /// Internal Full-Speed PHY
-    ///
-    /// Available on most High-Speed peripherals.
-    InternalFullSpeed,
-    /// Internal High-Speed PHY
-    ///
-    /// Available on a few STM32 chips.
-    InternalHighSpeed,
-    /// External ULPI High-Speed PHY
-    ExternalHighSpeed,
-}
-
-impl PhyType {
-    pub fn internal(&self) -> bool {
-        match self {
-            PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true,
-            PhyType::ExternalHighSpeed => false,
-        }
-    }
-
-    pub fn high_speed(&self) -> bool {
-        match self {
-            PhyType::InternalFullSpeed => false,
-            PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true,
-        }
-    }
-
-    pub fn to_dspd(&self) -> vals::Dspd {
-        match self {
-            PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL,
-            PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED,
-            PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED,
-        }
-    }
-}
-
-/// Indicates that [State::ep_out_buffers] is empty.
-const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX;
-
-pub struct State<const EP_COUNT: usize> {
-    /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true.
-    ep0_setup_data: UnsafeCell<[u8; 8]>,
-    ep0_setup_ready: AtomicBool,
-    ep_in_wakers: [AtomicWaker; EP_COUNT],
-    ep_out_wakers: [AtomicWaker; EP_COUNT],
-    /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint.
-    /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY].
-    ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT],
-    ep_out_size: [AtomicU16; EP_COUNT],
-    bus_waker: AtomicWaker,
-}
-
-unsafe impl<const EP_COUNT: usize> Send for State<EP_COUNT> {}
-unsafe impl<const EP_COUNT: usize> Sync for State<EP_COUNT> {}
-
-impl<const EP_COUNT: usize> State<EP_COUNT> {
-    pub const fn new() -> Self {
-        const NEW_AW: AtomicWaker = AtomicWaker::new();
-        const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _);
-        const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY);
-
-        Self {
-            ep0_setup_data: UnsafeCell::new([0u8; 8]),
-            ep0_setup_ready: AtomicBool::new(false),
-            ep_in_wakers: [NEW_AW; EP_COUNT],
-            ep_out_wakers: [NEW_AW; EP_COUNT],
-            ep_out_buffers: [NEW_BUF; EP_COUNT],
-            ep_out_size: [NEW_SIZE; EP_COUNT],
-            bus_waker: NEW_AW,
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy)]
-struct EndpointData {
-    ep_type: EndpointType,
-    max_packet_size: u16,
-    fifo_size_words: u16,
-}
-
-pub struct Driver<'d, T: Instance> {
-    phantom: PhantomData<&'d mut T>,
-    irq: PeripheralRef<'d, T::Interrupt>,
-    ep_in: [Option<EndpointData>; MAX_EP_COUNT],
-    ep_out: [Option<EndpointData>; MAX_EP_COUNT],
-    ep_out_buffer: &'d mut [u8],
-    ep_out_buffer_offset: usize,
-    phy_type: PhyType,
-}
-
-impl<'d, T: Instance> Driver<'d, T> {
-    /// Initializes USB OTG peripheral with internal Full-Speed PHY.
-    ///
-    /// # Arguments
-    ///
-    /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets.
-    /// Must be large enough to fit all OUT endpoint max packet sizes.
-    /// Endpoint allocation will fail if it is too small.
-    pub fn new_fs(
-        _peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
-        dp: impl Peripheral<P = impl DpPin<T>> + 'd,
-        dm: impl Peripheral<P = impl DmPin<T>> + 'd,
-        ep_out_buffer: &'d mut [u8],
-    ) -> Self {
-        into_ref!(dp, dm, irq);
-
-        unsafe {
-            dp.set_as_af(dp.af_num(), AFType::OutputPushPull);
-            dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
-        }
-
-        Self {
-            phantom: PhantomData,
-            irq,
-            ep_in: [None; MAX_EP_COUNT],
-            ep_out: [None; MAX_EP_COUNT],
-            ep_out_buffer,
-            ep_out_buffer_offset: 0,
-            phy_type: PhyType::InternalFullSpeed,
-        }
-    }
-
-    /// Initializes USB OTG peripheral with external High-Speed PHY.
-    ///
-    /// # Arguments
-    ///
-    /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets.
-    /// Must be large enough to fit all OUT endpoint max packet sizes.
-    /// Endpoint allocation will fail if it is too small.
-    pub fn new_hs_ulpi(
-        _peri: impl Peripheral<P = T> + 'd,
-        irq: impl Peripheral<P = T::Interrupt> + 'd,
-        ulpi_clk: impl Peripheral<P = impl UlpiClkPin<T>> + 'd,
-        ulpi_dir: impl Peripheral<P = impl UlpiDirPin<T>> + 'd,
-        ulpi_nxt: impl Peripheral<P = impl UlpiNxtPin<T>> + 'd,
-        ulpi_stp: impl Peripheral<P = impl UlpiStpPin<T>> + 'd,
-        ulpi_d0: impl Peripheral<P = impl UlpiD0Pin<T>> + 'd,
-        ulpi_d1: impl Peripheral<P = impl UlpiD1Pin<T>> + 'd,
-        ulpi_d2: impl Peripheral<P = impl UlpiD2Pin<T>> + 'd,
-        ulpi_d3: impl Peripheral<P = impl UlpiD3Pin<T>> + 'd,
-        ulpi_d4: impl Peripheral<P = impl UlpiD4Pin<T>> + 'd,
-        ulpi_d5: impl Peripheral<P = impl UlpiD5Pin<T>> + 'd,
-        ulpi_d6: impl Peripheral<P = impl UlpiD6Pin<T>> + 'd,
-        ulpi_d7: impl Peripheral<P = impl UlpiD7Pin<T>> + 'd,
-        ep_out_buffer: &'d mut [u8],
-    ) -> Self {
-        assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB");
-
-        config_ulpi_pins!(
-            ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6,
-            ulpi_d7
-        );
-
-        into_ref!(irq);
-
-        Self {
-            phantom: PhantomData,
-            irq,
-            ep_in: [None; MAX_EP_COUNT],
-            ep_out: [None; MAX_EP_COUNT],
-            ep_out_buffer,
-            ep_out_buffer_offset: 0,
-            phy_type: PhyType::ExternalHighSpeed,
-        }
-    }
-
-    // Returns total amount of words (u32) allocated in dedicated FIFO
-    fn allocated_fifo_words(&self) -> u16 {
-        RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in)
-    }
-
-    fn alloc_endpoint<D: Dir>(
-        &mut self,
-        ep_type: EndpointType,
-        max_packet_size: u16,
-        interval_ms: u8,
-    ) -> Result<Endpoint<'d, T, D>, EndpointAllocError> {
-        trace!(
-            "allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
-            ep_type,
-            max_packet_size,
-            interval_ms,
-            D::dir()
-        );
-
-        if D::dir() == Direction::Out {
-            if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() {
-                error!("Not enough endpoint out buffer capacity");
-                return Err(EndpointAllocError);
-            }
-        };
-
-        let fifo_size_words = match D::dir() {
-            Direction::Out => (max_packet_size + 3) / 4,
-            // INEPTXFD requires minimum size of 16 words
-            Direction::In => u16::max((max_packet_size + 3) / 4, 16),
-        };
-
-        if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS {
-            error!("Not enough FIFO capacity");
-            return Err(EndpointAllocError);
-        }
-
-        let eps = match D::dir() {
-            Direction::Out => &mut self.ep_out,
-            Direction::In => &mut self.ep_in,
-        };
-
-        // Find free endpoint slot
-        let slot = eps.iter_mut().enumerate().find(|(i, ep)| {
-            if *i == 0 && ep_type != EndpointType::Control {
-                // reserved for control pipe
-                false
-            } else {
-                ep.is_none()
-            }
-        });
-
-        let index = match slot {
-            Some((index, ep)) => {
-                *ep = Some(EndpointData {
-                    ep_type,
-                    max_packet_size,
-                    fifo_size_words,
-                });
-                index
-            }
-            None => {
-                error!("No free endpoints available");
-                return Err(EndpointAllocError);
-            }
-        };
-
-        trace!("  index={}", index);
-
-        if D::dir() == Direction::Out {
-            // Buffer capacity check was done above, now allocation cannot fail
-            unsafe {
-                *T::state().ep_out_buffers[index].get() =
-                    self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _);
-            }
-            self.ep_out_buffer_offset += max_packet_size as usize;
-        }
-
-        Ok(Endpoint {
-            _phantom: PhantomData,
-            info: EndpointInfo {
-                addr: EndpointAddress::from_parts(index, D::dir()),
-                ep_type,
-                max_packet_size,
-                interval_ms,
-            },
-        })
-    }
-}
-
-impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> {
-    type EndpointOut = Endpoint<'d, T, Out>;
-    type EndpointIn = Endpoint<'d, T, In>;
-    type ControlPipe = ControlPipe<'d, T>;
-    type Bus = Bus<'d, T>;
-
-    fn alloc_endpoint_in(
-        &mut self,
-        ep_type: EndpointType,
-        max_packet_size: u16,
-        interval_ms: u8,
-    ) -> Result<Self::EndpointIn, EndpointAllocError> {
-        self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
-    }
-
-    fn alloc_endpoint_out(
-        &mut self,
-        ep_type: EndpointType,
-        max_packet_size: u16,
-        interval_ms: u8,
-    ) -> Result<Self::EndpointOut, EndpointAllocError> {
-        self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
-    }
-
-    fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
-        let ep_out = self
-            .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0)
-            .unwrap();
-        let ep_in = self
-            .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0)
-            .unwrap();
-        assert_eq!(ep_out.info.addr.index(), 0);
-        assert_eq!(ep_in.info.addr.index(), 0);
-
-        trace!("start");
-
-        (
-            Bus {
-                phantom: PhantomData,
-                irq: self.irq,
-                ep_in: self.ep_in,
-                ep_out: self.ep_out,
-                phy_type: self.phy_type,
-                enabled: false,
-            },
-            ControlPipe {
-                _phantom: PhantomData,
-                max_packet_size: control_max_packet_size,
-                ep_out,
-                ep_in,
-            },
-        )
-    }
-}
-
-pub struct Bus<'d, T: Instance> {
-    phantom: PhantomData<&'d mut T>,
-    irq: PeripheralRef<'d, T::Interrupt>,
-    ep_in: [Option<EndpointData>; MAX_EP_COUNT],
-    ep_out: [Option<EndpointData>; MAX_EP_COUNT],
-    phy_type: PhyType,
-    enabled: bool,
-}
-
-impl<'d, T: Instance> Bus<'d, T> {
-    fn restore_irqs() {
-        // SAFETY: atomic write
-        unsafe {
-            T::regs().gintmsk().write(|w| {
-                w.set_usbrst(true);
-                w.set_enumdnem(true);
-                w.set_usbsuspm(true);
-                w.set_wuim(true);
-                w.set_iepint(true);
-                w.set_oepint(true);
-                w.set_rxflvlm(true);
-            });
-        }
-    }
-}
-
-impl<'d, T: Instance> Bus<'d, T> {
-    fn init_fifo(&mut self) {
-        trace!("init_fifo");
-
-        let r = T::regs();
-
-        // Configure RX fifo size. All endpoints share the same FIFO area.
-        let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out);
-        trace!("configuring rx fifo size={}", rx_fifo_size_words);
-
-        // SAFETY: register is exclusive to `Bus` with `&mut self`
-        unsafe { r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)) };
-
-        // Configure TX (USB in direction) fifo size for each endpoint
-        let mut fifo_top = rx_fifo_size_words;
-        for i in 0..T::ENDPOINT_COUNT {
-            if let Some(ep) = self.ep_in[i] {
-                trace!(
-                    "configuring tx fifo ep={}, offset={}, size={}",
-                    i,
-                    fifo_top,
-                    ep.fifo_size_words
-                );
-
-                let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) };
-
-                // SAFETY: register is exclusive to `Bus` with `&mut self`
-                unsafe {
-                    dieptxf.write(|w| {
-                        w.set_fd(ep.fifo_size_words);
-                        w.set_sa(fifo_top);
-                    });
-                }
-
-                fifo_top += ep.fifo_size_words;
-            }
-        }
-
-        assert!(
-            fifo_top <= T::FIFO_DEPTH_WORDS,
-            "FIFO allocations exceeded maximum capacity"
-        );
-    }
-
-    fn configure_endpoints(&mut self) {
-        trace!("configure_endpoints");
-
-        let r = T::regs();
-
-        // Configure IN endpoints
-        for (index, ep) in self.ep_in.iter().enumerate() {
-            if let Some(ep) = ep {
-                // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW
-                critical_section::with(|_| unsafe {
-                    r.diepctl(index).write(|w| {
-                        if index == 0 {
-                            w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
-                        } else {
-                            w.set_mpsiz(ep.max_packet_size);
-                            w.set_eptyp(to_eptyp(ep.ep_type));
-                            w.set_sd0pid_sevnfrm(true);
-                        }
-                    });
-                });
-            }
-        }
-
-        // Configure OUT endpoints
-        for (index, ep) in self.ep_out.iter().enumerate() {
-            if let Some(ep) = ep {
-                // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Endpoint` so critical section is needed for RMW
-                critical_section::with(|_| unsafe {
-                    r.doepctl(index).write(|w| {
-                        if index == 0 {
-                            w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
-                        } else {
-                            w.set_mpsiz(ep.max_packet_size);
-                            w.set_eptyp(to_eptyp(ep.ep_type));
-                            w.set_sd0pid_sevnfrm(true);
-                        }
-                    });
-
-                    r.doeptsiz(index).modify(|w| {
-                        w.set_xfrsiz(ep.max_packet_size as _);
-                        if index == 0 {
-                            w.set_rxdpid_stupcnt(1);
-                        } else {
-                            w.set_pktcnt(1);
-                        }
-                    });
-                });
-            }
-        }
-
-        // Enable IRQs for allocated endpoints
-        // SAFETY: register is exclusive to `Bus` with `&mut self`
-        unsafe {
-            r.daintmsk().modify(|w| {
-                w.set_iepm(ep_irq_mask(&self.ep_in));
-                // OUT interrupts not used, handled in RXFLVL
-                // w.set_oepm(ep_irq_mask(&self.ep_out));
-            });
-        }
-    }
-
-    fn disable(&mut self) {
-        self.irq.disable();
-        self.irq.remove_handler();
-
-        <T as RccPeripheral>::disable();
-
-        #[cfg(stm32l4)]
-        unsafe {
-            crate::pac::PWR.cr2().modify(|w| w.set_usv(false));
-            // Cannot disable PWR, because other peripherals might be using it
-        }
-    }
-
-    fn on_interrupt(_: *mut ()) {
+impl<T: Instance> interrupt::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
         trace!("irq");
         let r = T::regs();
         let state = T::state();
@@ -641,6 +169,478 @@ impl<'d, T: Instance> Bus<'d, T> {
     }
 }
 
+macro_rules! config_ulpi_pins {
+    ($($pin:ident),*) => {
+        into_ref!($($pin),*);
+        // NOTE(unsafe) Exclusive access to the registers
+        critical_section::with(|_| unsafe {
+            $(
+                $pin.set_as_af($pin.af_num(), AFType::OutputPushPull);
+                #[cfg(gpio_v2)]
+                $pin.set_speed(crate::gpio::Speed::VeryHigh);
+            )*
+        })
+    };
+}
+
+// From `synopsys-usb-otg` crate:
+// This calculation doesn't correspond to one in a Reference Manual.
+// In fact, the required number of words is higher than indicated in RM.
+// The following numbers are pessimistic and were figured out empirically.
+const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30;
+
+/// USB PHY type
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum PhyType {
+    /// Internal Full-Speed PHY
+    ///
+    /// Available on most High-Speed peripherals.
+    InternalFullSpeed,
+    /// Internal High-Speed PHY
+    ///
+    /// Available on a few STM32 chips.
+    InternalHighSpeed,
+    /// External ULPI High-Speed PHY
+    ExternalHighSpeed,
+}
+
+impl PhyType {
+    pub fn internal(&self) -> bool {
+        match self {
+            PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true,
+            PhyType::ExternalHighSpeed => false,
+        }
+    }
+
+    pub fn high_speed(&self) -> bool {
+        match self {
+            PhyType::InternalFullSpeed => false,
+            PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true,
+        }
+    }
+
+    pub fn to_dspd(&self) -> vals::Dspd {
+        match self {
+            PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL,
+            PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED,
+            PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED,
+        }
+    }
+}
+
+/// Indicates that [State::ep_out_buffers] is empty.
+const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX;
+
+pub struct State<const EP_COUNT: usize> {
+    /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true.
+    ep0_setup_data: UnsafeCell<[u8; 8]>,
+    ep0_setup_ready: AtomicBool,
+    ep_in_wakers: [AtomicWaker; EP_COUNT],
+    ep_out_wakers: [AtomicWaker; EP_COUNT],
+    /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint.
+    /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY].
+    ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT],
+    ep_out_size: [AtomicU16; EP_COUNT],
+    bus_waker: AtomicWaker,
+}
+
+unsafe impl<const EP_COUNT: usize> Send for State<EP_COUNT> {}
+unsafe impl<const EP_COUNT: usize> Sync for State<EP_COUNT> {}
+
+impl<const EP_COUNT: usize> State<EP_COUNT> {
+    pub const fn new() -> Self {
+        const NEW_AW: AtomicWaker = AtomicWaker::new();
+        const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _);
+        const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY);
+
+        Self {
+            ep0_setup_data: UnsafeCell::new([0u8; 8]),
+            ep0_setup_ready: AtomicBool::new(false),
+            ep_in_wakers: [NEW_AW; EP_COUNT],
+            ep_out_wakers: [NEW_AW; EP_COUNT],
+            ep_out_buffers: [NEW_BUF; EP_COUNT],
+            ep_out_size: [NEW_SIZE; EP_COUNT],
+            bus_waker: NEW_AW,
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+struct EndpointData {
+    ep_type: EndpointType,
+    max_packet_size: u16,
+    fifo_size_words: u16,
+}
+
+pub struct Driver<'d, T: Instance> {
+    phantom: PhantomData<&'d mut T>,
+    ep_in: [Option<EndpointData>; MAX_EP_COUNT],
+    ep_out: [Option<EndpointData>; MAX_EP_COUNT],
+    ep_out_buffer: &'d mut [u8],
+    ep_out_buffer_offset: usize,
+    phy_type: PhyType,
+}
+
+impl<'d, T: Instance> Driver<'d, T> {
+    /// Initializes USB OTG peripheral with internal Full-Speed PHY.
+    ///
+    /// # Arguments
+    ///
+    /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets.
+    /// Must be large enough to fit all OUT endpoint max packet sizes.
+    /// Endpoint allocation will fail if it is too small.
+    pub fn new_fs(
+        _peri: impl Peripheral<P = T> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
+        dp: impl Peripheral<P = impl DpPin<T>> + 'd,
+        dm: impl Peripheral<P = impl DmPin<T>> + 'd,
+        ep_out_buffer: &'d mut [u8],
+    ) -> Self {
+        into_ref!(dp, dm);
+
+        unsafe {
+            dp.set_as_af(dp.af_num(), AFType::OutputPushPull);
+            dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
+        }
+
+        Self {
+            phantom: PhantomData,
+            ep_in: [None; MAX_EP_COUNT],
+            ep_out: [None; MAX_EP_COUNT],
+            ep_out_buffer,
+            ep_out_buffer_offset: 0,
+            phy_type: PhyType::InternalFullSpeed,
+        }
+    }
+
+    /// Initializes USB OTG peripheral with external High-Speed PHY.
+    ///
+    /// # Arguments
+    ///
+    /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets.
+    /// Must be large enough to fit all OUT endpoint max packet sizes.
+    /// Endpoint allocation will fail if it is too small.
+    pub fn new_hs_ulpi(
+        _peri: impl Peripheral<P = T> + 'd,
+        _irq: impl interrupt::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
+        ulpi_clk: impl Peripheral<P = impl UlpiClkPin<T>> + 'd,
+        ulpi_dir: impl Peripheral<P = impl UlpiDirPin<T>> + 'd,
+        ulpi_nxt: impl Peripheral<P = impl UlpiNxtPin<T>> + 'd,
+        ulpi_stp: impl Peripheral<P = impl UlpiStpPin<T>> + 'd,
+        ulpi_d0: impl Peripheral<P = impl UlpiD0Pin<T>> + 'd,
+        ulpi_d1: impl Peripheral<P = impl UlpiD1Pin<T>> + 'd,
+        ulpi_d2: impl Peripheral<P = impl UlpiD2Pin<T>> + 'd,
+        ulpi_d3: impl Peripheral<P = impl UlpiD3Pin<T>> + 'd,
+        ulpi_d4: impl Peripheral<P = impl UlpiD4Pin<T>> + 'd,
+        ulpi_d5: impl Peripheral<P = impl UlpiD5Pin<T>> + 'd,
+        ulpi_d6: impl Peripheral<P = impl UlpiD6Pin<T>> + 'd,
+        ulpi_d7: impl Peripheral<P = impl UlpiD7Pin<T>> + 'd,
+        ep_out_buffer: &'d mut [u8],
+    ) -> Self {
+        assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB");
+
+        config_ulpi_pins!(
+            ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6,
+            ulpi_d7
+        );
+
+        Self {
+            phantom: PhantomData,
+            ep_in: [None; MAX_EP_COUNT],
+            ep_out: [None; MAX_EP_COUNT],
+            ep_out_buffer,
+            ep_out_buffer_offset: 0,
+            phy_type: PhyType::ExternalHighSpeed,
+        }
+    }
+
+    // Returns total amount of words (u32) allocated in dedicated FIFO
+    fn allocated_fifo_words(&self) -> u16 {
+        RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in)
+    }
+
+    fn alloc_endpoint<D: Dir>(
+        &mut self,
+        ep_type: EndpointType,
+        max_packet_size: u16,
+        interval_ms: u8,
+    ) -> Result<Endpoint<'d, T, D>, EndpointAllocError> {
+        trace!(
+            "allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
+            ep_type,
+            max_packet_size,
+            interval_ms,
+            D::dir()
+        );
+
+        if D::dir() == Direction::Out {
+            if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() {
+                error!("Not enough endpoint out buffer capacity");
+                return Err(EndpointAllocError);
+            }
+        };
+
+        let fifo_size_words = match D::dir() {
+            Direction::Out => (max_packet_size + 3) / 4,
+            // INEPTXFD requires minimum size of 16 words
+            Direction::In => u16::max((max_packet_size + 3) / 4, 16),
+        };
+
+        if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS {
+            error!("Not enough FIFO capacity");
+            return Err(EndpointAllocError);
+        }
+
+        let eps = match D::dir() {
+            Direction::Out => &mut self.ep_out,
+            Direction::In => &mut self.ep_in,
+        };
+
+        // Find free endpoint slot
+        let slot = eps.iter_mut().enumerate().find(|(i, ep)| {
+            if *i == 0 && ep_type != EndpointType::Control {
+                // reserved for control pipe
+                false
+            } else {
+                ep.is_none()
+            }
+        });
+
+        let index = match slot {
+            Some((index, ep)) => {
+                *ep = Some(EndpointData {
+                    ep_type,
+                    max_packet_size,
+                    fifo_size_words,
+                });
+                index
+            }
+            None => {
+                error!("No free endpoints available");
+                return Err(EndpointAllocError);
+            }
+        };
+
+        trace!("  index={}", index);
+
+        if D::dir() == Direction::Out {
+            // Buffer capacity check was done above, now allocation cannot fail
+            unsafe {
+                *T::state().ep_out_buffers[index].get() =
+                    self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _);
+            }
+            self.ep_out_buffer_offset += max_packet_size as usize;
+        }
+
+        Ok(Endpoint {
+            _phantom: PhantomData,
+            info: EndpointInfo {
+                addr: EndpointAddress::from_parts(index, D::dir()),
+                ep_type,
+                max_packet_size,
+                interval_ms,
+            },
+        })
+    }
+}
+
+impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> {
+    type EndpointOut = Endpoint<'d, T, Out>;
+    type EndpointIn = Endpoint<'d, T, In>;
+    type ControlPipe = ControlPipe<'d, T>;
+    type Bus = Bus<'d, T>;
+
+    fn alloc_endpoint_in(
+        &mut self,
+        ep_type: EndpointType,
+        max_packet_size: u16,
+        interval_ms: u8,
+    ) -> Result<Self::EndpointIn, EndpointAllocError> {
+        self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
+    }
+
+    fn alloc_endpoint_out(
+        &mut self,
+        ep_type: EndpointType,
+        max_packet_size: u16,
+        interval_ms: u8,
+    ) -> Result<Self::EndpointOut, EndpointAllocError> {
+        self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
+    }
+
+    fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
+        let ep_out = self
+            .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0)
+            .unwrap();
+        let ep_in = self
+            .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0)
+            .unwrap();
+        assert_eq!(ep_out.info.addr.index(), 0);
+        assert_eq!(ep_in.info.addr.index(), 0);
+
+        trace!("start");
+
+        (
+            Bus {
+                phantom: PhantomData,
+                ep_in: self.ep_in,
+                ep_out: self.ep_out,
+                phy_type: self.phy_type,
+                enabled: false,
+            },
+            ControlPipe {
+                _phantom: PhantomData,
+                max_packet_size: control_max_packet_size,
+                ep_out,
+                ep_in,
+            },
+        )
+    }
+}
+
+pub struct Bus<'d, T: Instance> {
+    phantom: PhantomData<&'d mut T>,
+    ep_in: [Option<EndpointData>; MAX_EP_COUNT],
+    ep_out: [Option<EndpointData>; MAX_EP_COUNT],
+    phy_type: PhyType,
+    enabled: bool,
+}
+
+impl<'d, T: Instance> Bus<'d, T> {
+    fn restore_irqs() {
+        // SAFETY: atomic write
+        unsafe {
+            T::regs().gintmsk().write(|w| {
+                w.set_usbrst(true);
+                w.set_enumdnem(true);
+                w.set_usbsuspm(true);
+                w.set_wuim(true);
+                w.set_iepint(true);
+                w.set_oepint(true);
+                w.set_rxflvlm(true);
+            });
+        }
+    }
+}
+
+impl<'d, T: Instance> Bus<'d, T> {
+    fn init_fifo(&mut self) {
+        trace!("init_fifo");
+
+        let r = T::regs();
+
+        // Configure RX fifo size. All endpoints share the same FIFO area.
+        let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out);
+        trace!("configuring rx fifo size={}", rx_fifo_size_words);
+
+        // SAFETY: register is exclusive to `Bus` with `&mut self`
+        unsafe { r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)) };
+
+        // Configure TX (USB in direction) fifo size for each endpoint
+        let mut fifo_top = rx_fifo_size_words;
+        for i in 0..T::ENDPOINT_COUNT {
+            if let Some(ep) = self.ep_in[i] {
+                trace!(
+                    "configuring tx fifo ep={}, offset={}, size={}",
+                    i,
+                    fifo_top,
+                    ep.fifo_size_words
+                );
+
+                let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) };
+
+                // SAFETY: register is exclusive to `Bus` with `&mut self`
+                unsafe {
+                    dieptxf.write(|w| {
+                        w.set_fd(ep.fifo_size_words);
+                        w.set_sa(fifo_top);
+                    });
+                }
+
+                fifo_top += ep.fifo_size_words;
+            }
+        }
+
+        assert!(
+            fifo_top <= T::FIFO_DEPTH_WORDS,
+            "FIFO allocations exceeded maximum capacity"
+        );
+    }
+
+    fn configure_endpoints(&mut self) {
+        trace!("configure_endpoints");
+
+        let r = T::regs();
+
+        // Configure IN endpoints
+        for (index, ep) in self.ep_in.iter().enumerate() {
+            if let Some(ep) = ep {
+                // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW
+                critical_section::with(|_| unsafe {
+                    r.diepctl(index).write(|w| {
+                        if index == 0 {
+                            w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
+                        } else {
+                            w.set_mpsiz(ep.max_packet_size);
+                            w.set_eptyp(to_eptyp(ep.ep_type));
+                            w.set_sd0pid_sevnfrm(true);
+                        }
+                    });
+                });
+            }
+        }
+
+        // Configure OUT endpoints
+        for (index, ep) in self.ep_out.iter().enumerate() {
+            if let Some(ep) = ep {
+                // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Endpoint` so critical section is needed for RMW
+                critical_section::with(|_| unsafe {
+                    r.doepctl(index).write(|w| {
+                        if index == 0 {
+                            w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
+                        } else {
+                            w.set_mpsiz(ep.max_packet_size);
+                            w.set_eptyp(to_eptyp(ep.ep_type));
+                            w.set_sd0pid_sevnfrm(true);
+                        }
+                    });
+
+                    r.doeptsiz(index).modify(|w| {
+                        w.set_xfrsiz(ep.max_packet_size as _);
+                        if index == 0 {
+                            w.set_rxdpid_stupcnt(1);
+                        } else {
+                            w.set_pktcnt(1);
+                        }
+                    });
+                });
+            }
+        }
+
+        // Enable IRQs for allocated endpoints
+        // SAFETY: register is exclusive to `Bus` with `&mut self`
+        unsafe {
+            r.daintmsk().modify(|w| {
+                w.set_iepm(ep_irq_mask(&self.ep_in));
+                // OUT interrupts not used, handled in RXFLVL
+                // w.set_oepm(ep_irq_mask(&self.ep_out));
+            });
+        }
+    }
+
+    fn disable(&mut self) {
+        unsafe { T::Interrupt::steal() }.disable();
+
+        <T as RccPeripheral>::disable();
+
+        #[cfg(stm32l4)]
+        unsafe {
+            crate::pac::PWR.cr2().modify(|w| w.set_usv(false));
+            // Cannot disable PWR, because other peripherals might be using it
+        }
+    }
+}
+
 impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
     async fn poll(&mut self) -> Event {
         poll_fn(move |cx| {
@@ -902,9 +902,8 @@ impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
             <T as RccPeripheral>::enable();
             <T as RccPeripheral>::reset();
 
-            self.irq.set_handler(Self::on_interrupt);
-            self.irq.unpend();
-            self.irq.enable();
+            T::Interrupt::steal().unpend();
+            T::Interrupt::steal().enable();
 
             let r = T::regs();
             let core_id = r.cid().read().0;
diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs
index 07cad84ef..663099ff7 100644
--- a/examples/stm32f1/src/bin/usb_serial.rs
+++ b/examples/stm32f1/src/bin/usb_serial.rs
@@ -8,13 +8,17 @@ use embassy_futures::join::join;
 use embassy_stm32::gpio::{Level, Output, Speed};
 use embassy_stm32::time::Hertz;
 use embassy_stm32::usb::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb, Config};
 use embassy_time::{Duration, Timer};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USB_LP_CAN1_RX0 => usb::InterruptHandler<peripherals::USB>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -35,8 +39,7 @@ async fn main(_spawner: Spawner) {
     }
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(USB_LP_CAN1_RX0);
-    let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+    let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
 
     // Create embassy-usb Config
     let config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32f3/src/bin/usart_dma.rs b/examples/stm32f3/src/bin/usart_dma.rs
index 47121acf1..85f01a69e 100644
--- a/examples/stm32f3/src/bin/usart_dma.rs
+++ b/examples/stm32f3/src/bin/usart_dma.rs
@@ -7,19 +7,22 @@ use core::fmt::Write;
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use heapless::String;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USART1 => usart::InterruptHandler<peripherals::USART1>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
     info!("Hello World!");
 
     let config = Config::default();
-    let irq = interrupt::take!(USART1);
-    let mut usart = Uart::new(p.USART1, p.PE1, p.PE0, irq, p.DMA1_CH4, NoDma, config);
+    let mut usart = Uart::new(p.USART1, p.PE1, p.PE0, Irqs, p.DMA1_CH4, NoDma, config);
 
     for n in 0u32.. {
         let mut s: String<128> = String::new();
diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs
index 5b4e0a91a..f15f333b7 100644
--- a/examples/stm32f3/src/bin/usb_serial.rs
+++ b/examples/stm32f3/src/bin/usb_serial.rs
@@ -8,13 +8,17 @@ use embassy_futures::join::join;
 use embassy_stm32::gpio::{Level, Output, Speed};
 use embassy_stm32::time::mhz;
 use embassy_stm32::usb::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb, Config};
 use embassy_time::{Duration, Timer};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USB_LP_CAN_RX0 => usb::InterruptHandler<peripherals::USB>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -33,8 +37,7 @@ async fn main(_spawner: Spawner) {
     dp_pullup.set_high();
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(USB_LP_CAN_RX0);
-    let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+    let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
 
     // Create embassy-usb Config
     let config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32f4/src/bin/i2c.rs b/examples/stm32f4/src/bin/i2c.rs
index f8ae0890c..a92957325 100644
--- a/examples/stm32f4/src/bin/i2c.rs
+++ b/examples/stm32f4/src/bin/i2c.rs
@@ -6,25 +6,28 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
 use embassy_stm32::i2c::{Error, I2c, TimeoutI2c};
-use embassy_stm32::interrupt;
 use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use embassy_time::Duration;
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
+bind_interrupts!(struct Irqs {
+    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello world!");
     let p = embassy_stm32::init(Default::default());
 
-    let irq = interrupt::take!(I2C2_EV);
     let mut i2c = I2c::new(
         p.I2C2,
         p.PB10,
         p.PB11,
-        irq,
+        Irqs,
         NoDma,
         NoDma,
         Hertz(100_000),
diff --git a/examples/stm32f4/src/bin/sdmmc.rs b/examples/stm32f4/src/bin/sdmmc.rs
index eeecbd321..6ec7d0fec 100644
--- a/examples/stm32f4/src/bin/sdmmc.rs
+++ b/examples/stm32f4/src/bin/sdmmc.rs
@@ -6,13 +6,17 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::sdmmc::{DataBlock, Sdmmc};
 use embassy_stm32::time::mhz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config};
 use {defmt_rtt as _, panic_probe as _};
 
 /// This is a safeguard to not overwrite any data on the SD card.
 /// If you don't care about SD card contents, set this to `true` to test writes.
 const ALLOW_WRITES: bool = false;
 
+bind_interrupts!(struct Irqs {
+    SDIO => sdmmc::InterruptHandler<peripherals::SDIO>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -21,11 +25,9 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
     info!("Hello World!");
 
-    let irq = interrupt::take!(SDIO);
-
     let mut sdmmc = Sdmmc::new_4bit(
         p.SDIO,
-        irq,
+        Irqs,
         p.DMA2_CH3,
         p.PC12,
         p.PD2,
diff --git a/examples/stm32f4/src/bin/usart.rs b/examples/stm32f4/src/bin/usart.rs
index 8f41bb6c4..7680fe845 100644
--- a/examples/stm32f4/src/bin/usart.rs
+++ b/examples/stm32f4/src/bin/usart.rs
@@ -5,10 +5,14 @@
 use cortex_m_rt::entry;
 use defmt::*;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USART3 => usart::InterruptHandler<peripherals::USART3>;
+});
+
 #[entry]
 fn main() -> ! {
     info!("Hello World!");
@@ -16,8 +20,7 @@ fn main() -> ! {
     let p = embassy_stm32::init(Default::default());
 
     let config = Config::default();
-    let irq = interrupt::take!(USART3);
-    let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, irq, NoDma, NoDma, config);
+    let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, Irqs, NoDma, NoDma, config);
 
     unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n"));
     info!("wrote Hello, starting echo");
diff --git a/examples/stm32f4/src/bin/usart_buffered.rs b/examples/stm32f4/src/bin/usart_buffered.rs
index a93f8baeb..c573dc3a3 100644
--- a/examples/stm32f4/src/bin/usart_buffered.rs
+++ b/examples/stm32f4/src/bin/usart_buffered.rs
@@ -4,11 +4,15 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{BufferedUart, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use embedded_io::asynch::BufRead;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USART3 => usart::BufferedInterruptHandler<peripherals::USART3>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
@@ -16,10 +20,9 @@ async fn main(_spawner: Spawner) {
 
     let config = Config::default();
 
-    let irq = interrupt::take!(USART3);
     let mut tx_buf = [0u8; 32];
     let mut rx_buf = [0u8; 32];
-    let mut buf_usart = BufferedUart::new(p.USART3, irq, p.PD9, p.PD8, &mut tx_buf, &mut rx_buf, config);
+    let mut buf_usart = BufferedUart::new(p.USART3, Irqs, p.PD9, p.PD8, &mut tx_buf, &mut rx_buf, config);
 
     loop {
         let buf = buf_usart.fill_buf().await.unwrap();
diff --git a/examples/stm32f4/src/bin/usart_dma.rs b/examples/stm32f4/src/bin/usart_dma.rs
index 78baeaa0d..3408ec370 100644
--- a/examples/stm32f4/src/bin/usart_dma.rs
+++ b/examples/stm32f4/src/bin/usart_dma.rs
@@ -7,19 +7,22 @@ use core::fmt::Write;
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use heapless::String;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USART3 => usart::InterruptHandler<peripherals::USART3>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
     info!("Hello World!");
 
     let config = Config::default();
-    let irq = interrupt::take!(USART3);
-    let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, irq, p.DMA1_CH3, NoDma, config);
+    let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, Irqs, p.DMA1_CH3, NoDma, config);
 
     for n in 0u32.. {
         let mut s: String<128> = String::new();
diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs
index 9131e5896..c4e395f0f 100644
--- a/examples/stm32f4/src/bin/usb_ethernet.rs
+++ b/examples/stm32f4/src/bin/usb_ethernet.rs
@@ -9,7 +9,7 @@ use embassy_net::{Stack, StackResources};
 use embassy_stm32::rng::Rng;
 use embassy_stm32::time::mhz;
 use embassy_stm32::usb_otg::Driver;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
 use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState};
 use embassy_usb::class::cdc_ncm::{CdcNcmClass, State};
 use embassy_usb::{Builder, UsbDevice};
@@ -45,6 +45,10 @@ async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! {
     stack.run().await
 }
 
+bind_interrupts!(struct Irqs {
+    OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
+});
+
 #[embassy_executor::main]
 async fn main(spawner: Spawner) {
     info!("Hello World!");
@@ -56,9 +60,8 @@ async fn main(spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(OTG_FS);
     let ep_out_buffer = &mut singleton!([0; 256])[..];
-    let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, ep_out_buffer);
+    let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs
index d2b1dca43..f8f5940a7 100644
--- a/examples/stm32f4/src/bin/usb_serial.rs
+++ b/examples/stm32f4/src/bin/usb_serial.rs
@@ -6,13 +6,17 @@ use defmt::{panic, *};
 use embassy_executor::Spawner;
 use embassy_stm32::time::mhz;
 use embassy_stm32::usb_otg::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello World!");
@@ -24,9 +28,8 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(OTG_FS);
     let mut ep_out_buffer = [0u8; 256];
-    let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+    let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs
index b947361ac..6d286c368 100644
--- a/examples/stm32f7/src/bin/eth.rs
+++ b/examples/stm32f7/src/bin/eth.rs
@@ -11,7 +11,7 @@ use embassy_stm32::eth::{Ethernet, PacketQueue};
 use embassy_stm32::peripherals::ETH;
 use embassy_stm32::rng::Rng;
 use embassy_stm32::time::mhz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, eth, Config};
 use embassy_time::{Duration, Timer};
 use embedded_io::asynch::Write;
 use rand_core::RngCore;
@@ -27,6 +27,10 @@ macro_rules! singleton {
     }};
 }
 
+bind_interrupts!(struct Irqs {
+    ETH => eth::InterruptHandler;
+});
+
 type Device = Ethernet<'static, ETH, GenericSMI>;
 
 #[embassy_executor::task]
@@ -48,13 +52,12 @@ async fn main(spawner: Spawner) -> ! {
     rng.fill_bytes(&mut seed);
     let seed = u64::from_le_bytes(seed);
 
-    let eth_int = interrupt::take!(ETH);
     let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
 
     let device = Ethernet::new(
         singleton!(PacketQueue::<16, 16>::new()),
         p.ETH,
-        eth_int,
+        Irqs,
         p.PA1,
         p.PA2,
         p.PC1,
diff --git a/examples/stm32f7/src/bin/sdmmc.rs b/examples/stm32f7/src/bin/sdmmc.rs
index c050a4002..9d43892a0 100644
--- a/examples/stm32f7/src/bin/sdmmc.rs
+++ b/examples/stm32f7/src/bin/sdmmc.rs
@@ -6,9 +6,13 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::sdmmc::Sdmmc;
 use embassy_stm32::time::mhz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    SDMMC1 => sdmmc::InterruptHandler<peripherals::SDMMC1>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -18,11 +22,9 @@ async fn main(_spawner: Spawner) {
 
     info!("Hello World!");
 
-    let irq = interrupt::take!(SDMMC1);
-
     let mut sdmmc = Sdmmc::new_4bit(
         p.SDMMC1,
-        irq,
+        Irqs,
         p.DMA2_CH3,
         p.PC12,
         p.PD2,
diff --git a/examples/stm32f7/src/bin/usart_dma.rs b/examples/stm32f7/src/bin/usart_dma.rs
index 4827c52ae..4700287a7 100644
--- a/examples/stm32f7/src/bin/usart_dma.rs
+++ b/examples/stm32f7/src/bin/usart_dma.rs
@@ -7,17 +7,20 @@ use core::fmt::Write;
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use heapless::String;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART7 => usart::InterruptHandler<peripherals::UART7>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
     let config = Config::default();
-    let irq = interrupt::take!(UART7);
-    let mut usart = Uart::new(p.UART7, p.PA8, p.PA15, irq, p.DMA1_CH1, NoDma, config);
+    let mut usart = Uart::new(p.UART7, p.PA8, p.PA15, Irqs, p.DMA1_CH1, NoDma, config);
 
     for n in 0u32.. {
         let mut s: String<128> = String::new();
diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs
index dca90d9cb..763309ce2 100644
--- a/examples/stm32f7/src/bin/usb_serial.rs
+++ b/examples/stm32f7/src/bin/usb_serial.rs
@@ -6,13 +6,17 @@ use defmt::{panic, *};
 use embassy_executor::Spawner;
 use embassy_stm32::time::mhz;
 use embassy_stm32::usb_otg::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello World!");
@@ -25,9 +29,8 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(OTG_FS);
     let mut ep_out_buffer = [0u8; 256];
-    let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+    let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32h5/src/bin/eth.rs b/examples/stm32h5/src/bin/eth.rs
index b2e252fc7..fa1f225fe 100644
--- a/examples/stm32h5/src/bin/eth.rs
+++ b/examples/stm32h5/src/bin/eth.rs
@@ -12,7 +12,7 @@ use embassy_stm32::peripherals::ETH;
 use embassy_stm32::rcc::{AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllSource, Sysclk, VoltageScale};
 use embassy_stm32::rng::Rng;
 use embassy_stm32::time::Hertz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, eth, Config};
 use embassy_time::{Duration, Timer};
 use embedded_io::asynch::Write;
 use rand_core::RngCore;
@@ -28,6 +28,10 @@ macro_rules! singleton {
     }};
 }
 
+bind_interrupts!(struct Irqs {
+    ETH => eth::InterruptHandler;
+});
+
 type Device = Ethernet<'static, ETH, GenericSMI>;
 
 #[embassy_executor::task]
@@ -67,13 +71,12 @@ async fn main(spawner: Spawner) -> ! {
     rng.fill_bytes(&mut seed);
     let seed = u64::from_le_bytes(seed);
 
-    let eth_int = interrupt::take!(ETH);
     let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
 
     let device = Ethernet::new(
         singleton!(PacketQueue::<4, 4>::new()),
         p.ETH,
-        eth_int,
+        Irqs,
         p.PA1,
         p.PA2,
         p.PC1,
diff --git a/examples/stm32h5/src/bin/i2c.rs b/examples/stm32h5/src/bin/i2c.rs
index 6cbf58bbc..8b6fe71ae 100644
--- a/examples/stm32h5/src/bin/i2c.rs
+++ b/examples/stm32h5/src/bin/i2c.rs
@@ -5,25 +5,28 @@
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::i2c::{Error, I2c, TimeoutI2c};
-use embassy_stm32::interrupt;
 use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use embassy_time::Duration;
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
+bind_interrupts!(struct Irqs {
+    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello world!");
     let p = embassy_stm32::init(Default::default());
 
-    let irq = interrupt::take!(I2C2_EV);
     let mut i2c = I2c::new(
         p.I2C2,
         p.PB10,
         p.PB11,
-        irq,
+        Irqs,
         p.GPDMA1_CH4,
         p.GPDMA1_CH5,
         Hertz(100_000),
diff --git a/examples/stm32h5/src/bin/usart.rs b/examples/stm32h5/src/bin/usart.rs
index 405f18ec7..0abb94abb 100644
--- a/examples/stm32h5/src/bin/usart.rs
+++ b/examples/stm32h5/src/bin/usart.rs
@@ -6,18 +6,21 @@ use cortex_m_rt::entry;
 use defmt::*;
 use embassy_executor::Executor;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use static_cell::StaticCell;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART7 => usart::InterruptHandler<peripherals::UART7>;
+});
+
 #[embassy_executor::task]
 async fn main_task() {
     let p = embassy_stm32::init(Default::default());
 
     let config = Config::default();
-    let irq = interrupt::take!(UART7);
-    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, NoDma, NoDma, config);
+    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, NoDma, NoDma, config);
 
     unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n"));
     info!("wrote Hello, starting echo");
diff --git a/examples/stm32h5/src/bin/usart_dma.rs b/examples/stm32h5/src/bin/usart_dma.rs
index 43d791aae..48264f884 100644
--- a/examples/stm32h5/src/bin/usart_dma.rs
+++ b/examples/stm32h5/src/bin/usart_dma.rs
@@ -8,19 +8,22 @@ use cortex_m_rt::entry;
 use defmt::*;
 use embassy_executor::Executor;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use heapless::String;
 use static_cell::StaticCell;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART7 => usart::InterruptHandler<peripherals::UART7>;
+});
+
 #[embassy_executor::task]
 async fn main_task() {
     let p = embassy_stm32::init(Default::default());
 
     let config = Config::default();
-    let irq = interrupt::take!(UART7);
-    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, p.GPDMA1_CH0, NoDma, config);
+    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.GPDMA1_CH0, NoDma, config);
 
     for n in 0u32.. {
         let mut s: String<128> = String::new();
diff --git a/examples/stm32h5/src/bin/usart_split.rs b/examples/stm32h5/src/bin/usart_split.rs
index 16a499582..debd6f454 100644
--- a/examples/stm32h5/src/bin/usart_split.rs
+++ b/examples/stm32h5/src/bin/usart_split.rs
@@ -5,13 +5,17 @@
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::peripherals::{GPDMA1_CH1, UART7};
 use embassy_stm32::usart::{Config, Uart, UartRx};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
 use embassy_sync::channel::Channel;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART7 => usart::InterruptHandler<peripherals::UART7>;
+});
+
 #[embassy_executor::task]
 async fn writer(mut usart: Uart<'static, UART7, NoDma, NoDma>) {
     unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n"));
@@ -32,8 +36,7 @@ async fn main(spawner: Spawner) -> ! {
     info!("Hello World!");
 
     let config = Config::default();
-    let irq = interrupt::take!(UART7);
-    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, p.GPDMA1_CH0, p.GPDMA1_CH1, config);
+    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1, config);
     unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n"));
 
     let (mut tx, rx) = usart.split();
diff --git a/examples/stm32h5/src/bin/usb_serial.rs b/examples/stm32h5/src/bin/usb_serial.rs
index 4f987cbd1..3912327e2 100644
--- a/examples/stm32h5/src/bin/usb_serial.rs
+++ b/examples/stm32h5/src/bin/usb_serial.rs
@@ -7,13 +7,17 @@ use embassy_executor::Spawner;
 use embassy_stm32::rcc::{AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllSource, Sysclk, VoltageScale};
 use embassy_stm32::time::Hertz;
 use embassy_stm32::usb::{Driver, Instance};
-use embassy_stm32::{interrupt, pac, Config};
+use embassy_stm32::{bind_interrupts, pac, peripherals, usb, Config};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USB_DRD_FS => usb::InterruptHandler<peripherals::USB>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -48,8 +52,7 @@ async fn main(_spawner: Spawner) {
     }
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(USB_DRD_FS);
-    let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+    let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32h7/src/bin/camera.rs b/examples/stm32h7/src/bin/camera.rs
index 9c443b83a..6f75a0630 100644
--- a/examples/stm32h7/src/bin/camera.rs
+++ b/examples/stm32h7/src/bin/camera.rs
@@ -8,7 +8,7 @@ use embassy_stm32::gpio::{Level, Output, Speed};
 use embassy_stm32::i2c::I2c;
 use embassy_stm32::rcc::{Mco, Mco1Source, McoClock};
 use embassy_stm32::time::{khz, mhz};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, i2c, peripherals, Config};
 use embassy_time::{Duration, Timer};
 use ov7725::*;
 use {defmt_rtt as _, panic_probe as _};
@@ -18,6 +18,11 @@ const HEIGHT: usize = 100;
 
 static mut FRAME: [u32; WIDTH * HEIGHT / 2] = [0u32; WIDTH * HEIGHT / 2];
 
+bind_interrupts!(struct Irqs {
+    I2C1_EV => i2c::InterruptHandler<peripherals::I2C1>;
+    DCMI => dcmi::InterruptHandler<peripherals::DCMI>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -34,12 +39,11 @@ async fn main(_spawner: Spawner) {
     let mco = Mco::new(p.MCO1, p.PA8, Mco1Source::Hsi, McoClock::Divided(3));
 
     let mut led = Output::new(p.PE3, Level::High, Speed::Low);
-    let i2c_irq = interrupt::take!(I2C1_EV);
     let cam_i2c = I2c::new(
         p.I2C1,
         p.PB8,
         p.PB9,
-        i2c_irq,
+        Irqs,
         p.DMA1_CH1,
         p.DMA1_CH2,
         khz(100),
@@ -55,11 +59,9 @@ async fn main(_spawner: Spawner) {
 
     defmt::info!("manufacturer: 0x{:x}, pid: 0x{:x}", manufacturer_id, camera_id);
 
-    let dcmi_irq = interrupt::take!(DCMI);
     let config = dcmi::Config::default();
     let mut dcmi = Dcmi::new_8bit(
-        p.DCMI, p.DMA1_CH0, dcmi_irq, p.PC6, p.PC7, p.PE0, p.PE1, p.PE4, p.PD3, p.PE5, p.PE6, p.PB7, p.PA4, p.PA6,
-        config,
+        p.DCMI, p.DMA1_CH0, Irqs, p.PC6, p.PC7, p.PE0, p.PE1, p.PE4, p.PD3, p.PE5, p.PE6, p.PB7, p.PA4, p.PA6, config,
     );
 
     defmt::info!("attempting capture");
diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs
index 61bb7e37b..dbfc90cf4 100644
--- a/examples/stm32h7/src/bin/eth.rs
+++ b/examples/stm32h7/src/bin/eth.rs
@@ -11,7 +11,7 @@ use embassy_stm32::eth::{Ethernet, PacketQueue};
 use embassy_stm32::peripherals::ETH;
 use embassy_stm32::rng::Rng;
 use embassy_stm32::time::mhz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, eth, Config};
 use embassy_time::{Duration, Timer};
 use embedded_io::asynch::Write;
 use rand_core::RngCore;
@@ -27,6 +27,10 @@ macro_rules! singleton {
     }};
 }
 
+bind_interrupts!(struct Irqs {
+    ETH => eth::InterruptHandler;
+});
+
 type Device = Ethernet<'static, ETH, GenericSMI>;
 
 #[embassy_executor::task]
@@ -49,13 +53,12 @@ async fn main(spawner: Spawner) -> ! {
     rng.fill_bytes(&mut seed);
     let seed = u64::from_le_bytes(seed);
 
-    let eth_int = interrupt::take!(ETH);
     let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
 
     let device = Ethernet::new(
         singleton!(PacketQueue::<16, 16>::new()),
         p.ETH,
-        eth_int,
+        Irqs,
         p.PA1,
         p.PA2,
         p.PC1,
diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs
index b609fa5df..14e6b7914 100644
--- a/examples/stm32h7/src/bin/eth_client.rs
+++ b/examples/stm32h7/src/bin/eth_client.rs
@@ -11,7 +11,7 @@ use embassy_stm32::eth::{Ethernet, PacketQueue};
 use embassy_stm32::peripherals::ETH;
 use embassy_stm32::rng::Rng;
 use embassy_stm32::time::mhz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, eth, Config};
 use embassy_time::{Duration, Timer};
 use embedded_io::asynch::Write;
 use embedded_nal_async::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpConnect};
@@ -28,6 +28,10 @@ macro_rules! singleton {
     }};
 }
 
+bind_interrupts!(struct Irqs {
+    ETH => eth::InterruptHandler;
+});
+
 type Device = Ethernet<'static, ETH, GenericSMI>;
 
 #[embassy_executor::task]
@@ -50,13 +54,12 @@ async fn main(spawner: Spawner) -> ! {
     rng.fill_bytes(&mut seed);
     let seed = u64::from_le_bytes(seed);
 
-    let eth_int = interrupt::take!(ETH);
     let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
 
     let device = Ethernet::new(
         singleton!(PacketQueue::<16, 16>::new()),
         p.ETH,
-        eth_int,
+        Irqs,
         p.PA1,
         p.PA2,
         p.PC1,
diff --git a/examples/stm32h7/src/bin/i2c.rs b/examples/stm32h7/src/bin/i2c.rs
index 78e03f014..c2979c59b 100644
--- a/examples/stm32h7/src/bin/i2c.rs
+++ b/examples/stm32h7/src/bin/i2c.rs
@@ -5,25 +5,28 @@
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::i2c::{Error, I2c, TimeoutI2c};
-use embassy_stm32::interrupt;
 use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use embassy_time::Duration;
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
+bind_interrupts!(struct Irqs {
+    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello world!");
     let p = embassy_stm32::init(Default::default());
 
-    let irq = interrupt::take!(I2C2_EV);
     let mut i2c = I2c::new(
         p.I2C2,
         p.PB10,
         p.PB11,
-        irq,
+        Irqs,
         p.DMA1_CH4,
         p.DMA1_CH5,
         Hertz(100_000),
diff --git a/examples/stm32h7/src/bin/sdmmc.rs b/examples/stm32h7/src/bin/sdmmc.rs
index 26d1db01e..ce91b6b1c 100644
--- a/examples/stm32h7/src/bin/sdmmc.rs
+++ b/examples/stm32h7/src/bin/sdmmc.rs
@@ -6,9 +6,13 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::sdmmc::Sdmmc;
 use embassy_stm32::time::mhz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    SDMMC1 => sdmmc::InterruptHandler<peripherals::SDMMC1>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) -> ! {
     let mut config = Config::default();
@@ -16,11 +20,9 @@ async fn main(_spawner: Spawner) -> ! {
     let p = embassy_stm32::init(config);
     info!("Hello World!");
 
-    let irq = interrupt::take!(SDMMC1);
-
     let mut sdmmc = Sdmmc::new_4bit(
         p.SDMMC1,
-        irq,
+        Irqs,
         p.PC12,
         p.PD2,
         p.PC8,
diff --git a/examples/stm32h7/src/bin/usart.rs b/examples/stm32h7/src/bin/usart.rs
index 405f18ec7..0abb94abb 100644
--- a/examples/stm32h7/src/bin/usart.rs
+++ b/examples/stm32h7/src/bin/usart.rs
@@ -6,18 +6,21 @@ use cortex_m_rt::entry;
 use defmt::*;
 use embassy_executor::Executor;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use static_cell::StaticCell;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART7 => usart::InterruptHandler<peripherals::UART7>;
+});
+
 #[embassy_executor::task]
 async fn main_task() {
     let p = embassy_stm32::init(Default::default());
 
     let config = Config::default();
-    let irq = interrupt::take!(UART7);
-    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, NoDma, NoDma, config);
+    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, NoDma, NoDma, config);
 
     unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n"));
     info!("wrote Hello, starting echo");
diff --git a/examples/stm32h7/src/bin/usart_dma.rs b/examples/stm32h7/src/bin/usart_dma.rs
index 6e3491e55..f1fe7fce6 100644
--- a/examples/stm32h7/src/bin/usart_dma.rs
+++ b/examples/stm32h7/src/bin/usart_dma.rs
@@ -8,19 +8,22 @@ use cortex_m_rt::entry;
 use defmt::*;
 use embassy_executor::Executor;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use heapless::String;
 use static_cell::StaticCell;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART7 => usart::InterruptHandler<peripherals::UART7>;
+});
+
 #[embassy_executor::task]
 async fn main_task() {
     let p = embassy_stm32::init(Default::default());
 
     let config = Config::default();
-    let irq = interrupt::take!(UART7);
-    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, p.DMA1_CH0, NoDma, config);
+    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.DMA1_CH0, NoDma, config);
 
     for n in 0u32.. {
         let mut s: String<128> = String::new();
diff --git a/examples/stm32h7/src/bin/usart_split.rs b/examples/stm32h7/src/bin/usart_split.rs
index f97176ecb..330d1ce09 100644
--- a/examples/stm32h7/src/bin/usart_split.rs
+++ b/examples/stm32h7/src/bin/usart_split.rs
@@ -5,13 +5,17 @@
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::peripherals::{DMA1_CH1, UART7};
 use embassy_stm32::usart::{Config, Uart, UartRx};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
 use embassy_sync::channel::Channel;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART7 => usart::InterruptHandler<peripherals::UART7>;
+});
+
 #[embassy_executor::task]
 async fn writer(mut usart: Uart<'static, UART7, NoDma, NoDma>) {
     unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n"));
@@ -32,8 +36,7 @@ async fn main(spawner: Spawner) -> ! {
     info!("Hello World!");
 
     let config = Config::default();
-    let irq = interrupt::take!(UART7);
-    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, p.DMA1_CH0, p.DMA1_CH1, config);
+    let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.DMA1_CH0, p.DMA1_CH1, config);
     unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n"));
 
     let (mut tx, rx) = usart.split();
diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs
index 475af116d..c622f19f7 100644
--- a/examples/stm32h7/src/bin/usb_serial.rs
+++ b/examples/stm32h7/src/bin/usb_serial.rs
@@ -6,13 +6,17 @@ use defmt::{panic, *};
 use embassy_executor::Spawner;
 use embassy_stm32::time::mhz;
 use embassy_stm32::usb_otg::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello World!");
@@ -24,9 +28,8 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(OTG_FS);
     let mut ep_out_buffer = [0u8; 256];
-    let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+    let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32l0/src/bin/usart_dma.rs b/examples/stm32l0/src/bin/usart_dma.rs
index c307f857a..eae8f3452 100644
--- a/examples/stm32l0/src/bin/usart_dma.rs
+++ b/examples/stm32l0/src/bin/usart_dma.rs
@@ -4,15 +4,18 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USART1 => usart::InterruptHandler<peripherals::USART1>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
-    let irq = interrupt::take!(USART1);
-    let mut usart = Uart::new(p.USART1, p.PB7, p.PB6, irq, p.DMA1_CH2, p.DMA1_CH3, Config::default());
+    let mut usart = Uart::new(p.USART1, p.PB7, p.PB6, Irqs, p.DMA1_CH2, p.DMA1_CH3, Config::default());
 
     usart.write(b"Hello Embassy World!\r\n").await.unwrap();
     info!("wrote Hello, starting echo");
diff --git a/examples/stm32l0/src/bin/usart_irq.rs b/examples/stm32l0/src/bin/usart_irq.rs
index 465347004..f2c72a107 100644
--- a/examples/stm32l0/src/bin/usart_irq.rs
+++ b/examples/stm32l0/src/bin/usart_irq.rs
@@ -4,11 +4,15 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{BufferedUart, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use embedded_io::asynch::{Read, Write};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USART2 => usart::BufferedInterruptHandler<peripherals::USART2>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
@@ -20,8 +24,7 @@ async fn main(_spawner: Spawner) {
     let mut config = Config::default();
     config.baudrate = 9600;
 
-    let irq = interrupt::take!(USART2);
-    let mut usart = unsafe { BufferedUart::new(p.USART2, irq, p.PA3, p.PA2, &mut TX_BUFFER, &mut RX_BUFFER, config) };
+    let mut usart = unsafe { BufferedUart::new(p.USART2, Irqs, p.PA3, p.PA2, &mut TX_BUFFER, &mut RX_BUFFER, config) };
 
     usart.write_all(b"Hello Embassy World!\r\n").await.unwrap();
     info!("wrote Hello, starting echo");
diff --git a/examples/stm32l4/src/bin/i2c.rs b/examples/stm32l4/src/bin/i2c.rs
index d40d6803d..d0060d20c 100644
--- a/examples/stm32l4/src/bin/i2c.rs
+++ b/examples/stm32l4/src/bin/i2c.rs
@@ -6,22 +6,25 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
 use embassy_stm32::i2c::I2c;
-use embassy_stm32::interrupt;
 use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
+bind_interrupts!(struct Irqs {
+    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
-    let irq = interrupt::take!(I2C2_EV);
     let mut i2c = I2c::new(
         p.I2C2,
         p.PB10,
         p.PB11,
-        irq,
+        Irqs,
         NoDma,
         NoDma,
         Hertz(100_000),
diff --git a/examples/stm32l4/src/bin/i2c_blocking_async.rs b/examples/stm32l4/src/bin/i2c_blocking_async.rs
index d868cac01..eca59087b 100644
--- a/examples/stm32l4/src/bin/i2c_blocking_async.rs
+++ b/examples/stm32l4/src/bin/i2c_blocking_async.rs
@@ -7,23 +7,26 @@ use embassy_embedded_hal::adapter::BlockingAsync;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
 use embassy_stm32::i2c::I2c;
-use embassy_stm32::interrupt;
 use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use embedded_hal_async::i2c::I2c as I2cTrait;
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
+bind_interrupts!(struct Irqs {
+    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
-    let irq = interrupt::take!(I2C2_EV);
     let i2c = I2c::new(
         p.I2C2,
         p.PB10,
         p.PB11,
-        irq,
+        Irqs,
         NoDma,
         NoDma,
         Hertz(100_000),
diff --git a/examples/stm32l4/src/bin/i2c_dma.rs b/examples/stm32l4/src/bin/i2c_dma.rs
index 7e62ee637..cf6f3da67 100644
--- a/examples/stm32l4/src/bin/i2c_dma.rs
+++ b/examples/stm32l4/src/bin/i2c_dma.rs
@@ -5,22 +5,25 @@
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::i2c::I2c;
-use embassy_stm32::interrupt;
 use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
+bind_interrupts!(struct Irqs {
+    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
-    let irq = interrupt::take!(I2C2_EV);
     let mut i2c = I2c::new(
         p.I2C2,
         p.PB10,
         p.PB11,
-        irq,
+        Irqs,
         p.DMA1_CH4,
         p.DMA1_CH5,
         Hertz(100_000),
diff --git a/examples/stm32l4/src/bin/usart.rs b/examples/stm32l4/src/bin/usart.rs
index 7d874d9d7..beb5ec558 100644
--- a/examples/stm32l4/src/bin/usart.rs
+++ b/examples/stm32l4/src/bin/usart.rs
@@ -4,10 +4,14 @@
 
 use defmt::*;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART4 => usart::InterruptHandler<peripherals::UART4>;
+});
+
 #[cortex_m_rt::entry]
 fn main() -> ! {
     info!("Hello World!");
@@ -15,8 +19,7 @@ fn main() -> ! {
     let p = embassy_stm32::init(Default::default());
 
     let config = Config::default();
-    let irq = interrupt::take!(UART4);
-    let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, irq, NoDma, NoDma, config);
+    let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, Irqs, NoDma, NoDma, config);
 
     unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n"));
     info!("wrote Hello, starting echo");
diff --git a/examples/stm32l4/src/bin/usart_dma.rs b/examples/stm32l4/src/bin/usart_dma.rs
index 452bede30..b7d4cb01e 100644
--- a/examples/stm32l4/src/bin/usart_dma.rs
+++ b/examples/stm32l4/src/bin/usart_dma.rs
@@ -7,19 +7,22 @@ use core::fmt::Write;
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use heapless::String;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    UART4 => usart::InterruptHandler<peripherals::UART4>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
     info!("Hello World!");
 
     let config = Config::default();
-    let irq = interrupt::take!(UART4);
-    let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, irq, p.DMA1_CH3, NoDma, config);
+    let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, Irqs, p.DMA1_CH3, NoDma, config);
 
     for n in 0u32.. {
         let mut s: String<128> = String::new();
diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs
index bdb290e63..80811a43e 100644
--- a/examples/stm32l4/src/bin/usb_serial.rs
+++ b/examples/stm32l4/src/bin/usb_serial.rs
@@ -7,13 +7,17 @@ use defmt_rtt as _; // global logger
 use embassy_executor::Spawner;
 use embassy_stm32::rcc::*;
 use embassy_stm32::usb_otg::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use futures::future::join;
 use panic_probe as _;
 
+bind_interrupts!(struct Irqs {
+    OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello World!");
@@ -25,9 +29,8 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(OTG_FS);
     let mut ep_out_buffer = [0u8; 256];
-    let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+    let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs
index 6c5645a41..b84e53d3a 100644
--- a/examples/stm32l5/src/bin/usb_ethernet.rs
+++ b/examples/stm32l5/src/bin/usb_ethernet.rs
@@ -9,7 +9,7 @@ use embassy_net::{Stack, StackResources};
 use embassy_stm32::rcc::*;
 use embassy_stm32::rng::Rng;
 use embassy_stm32::usb::Driver;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb, Config};
 use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState};
 use embassy_usb::class::cdc_ncm::{CdcNcmClass, State};
 use embassy_usb::{Builder, UsbDevice};
@@ -31,6 +31,10 @@ macro_rules! singleton {
 
 const MTU: usize = 1514;
 
+bind_interrupts!(struct Irqs {
+    USB_FS => usb::InterruptHandler<peripherals::USB>;
+});
+
 #[embassy_executor::task]
 async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! {
     device.run().await
@@ -54,8 +58,7 @@ async fn main(spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(USB_FS);
-    let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+    let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs
index e3bbe9d09..7e894e407 100644
--- a/examples/stm32l5/src/bin/usb_hid_mouse.rs
+++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs
@@ -7,7 +7,7 @@ use embassy_executor::Spawner;
 use embassy_futures::join::join;
 use embassy_stm32::rcc::*;
 use embassy_stm32::usb::Driver;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb, Config};
 use embassy_time::{Duration, Timer};
 use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State};
 use embassy_usb::control::OutResponse;
@@ -15,6 +15,10 @@ use embassy_usb::Builder;
 use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USB_FS => usb::InterruptHandler<peripherals::USB>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -23,8 +27,7 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(USB_FS);
-    let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+    let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs
index 66ccacb73..0c719560f 100644
--- a/examples/stm32l5/src/bin/usb_serial.rs
+++ b/examples/stm32l5/src/bin/usb_serial.rs
@@ -7,12 +7,16 @@ use embassy_executor::Spawner;
 use embassy_futures::join::join;
 use embassy_stm32::rcc::*;
 use embassy_stm32::usb::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb, Config};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    USB_FS => usb::InterruptHandler<peripherals::USB>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = Config::default();
@@ -23,8 +27,7 @@ async fn main(_spawner: Spawner) {
     info!("Hello World!");
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(USB_FS);
-    let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+    let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
 
     // Create embassy-usb Config
     let config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs
index 4882cd2e0..f36daf91b 100644
--- a/examples/stm32u5/src/bin/usb_serial.rs
+++ b/examples/stm32u5/src/bin/usb_serial.rs
@@ -7,13 +7,17 @@ use defmt_rtt as _; // global logger
 use embassy_executor::Spawner;
 use embassy_stm32::rcc::*;
 use embassy_stm32::usb_otg::{Driver, Instance};
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
 use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
 use futures::future::join;
 use panic_probe as _;
 
+bind_interrupts!(struct Irqs {
+    OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello World!");
@@ -26,9 +30,8 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     // Create the driver, from the HAL.
-    let irq = interrupt::take!(OTG_FS);
     let mut ep_out_buffer = [0u8; 256];
-    let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+    let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer);
 
     // Create embassy-usb Config
     let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
diff --git a/examples/stm32wb/src/bin/tl_mbox.rs b/examples/stm32wb/src/bin/tl_mbox.rs
index acbc60c87..326e4be85 100644
--- a/examples/stm32wb/src/bin/tl_mbox.rs
+++ b/examples/stm32wb/src/bin/tl_mbox.rs
@@ -4,12 +4,17 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
 use embassy_stm32::ipcc::{Config, Ipcc};
 use embassy_stm32::tl_mbox::TlMbox;
+use embassy_stm32::{bind_interrupts, tl_mbox};
 use embassy_time::{Duration, Timer};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => tl_mbox::ReceiveInterruptHandler;
+    IPCC_C1_TX => tl_mbox::TransmitInterruptHandler;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     /*
@@ -42,10 +47,7 @@ async fn main(_spawner: Spawner) {
     let config = Config::default();
     let mut ipcc = Ipcc::new(p.IPCC, config);
 
-    let rx_irq = interrupt::take!(IPCC_C1_RX);
-    let tx_irq = interrupt::take!(IPCC_C1_TX);
-
-    let mbox = TlMbox::init(&mut ipcc, rx_irq, tx_irq);
+    let mbox = TlMbox::init(&mut ipcc, Irqs);
 
     loop {
         let wireless_fw_info = mbox.wireless_fw_info();
diff --git a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
index 1008e1e41..7a69f26b7 100644
--- a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
+++ b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
@@ -4,11 +4,16 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
 use embassy_stm32::ipcc::{Config, Ipcc};
 use embassy_stm32::tl_mbox::TlMbox;
+use embassy_stm32::{bind_interrupts, tl_mbox};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => tl_mbox::ReceiveInterruptHandler;
+    IPCC_C1_TX => tl_mbox::TransmitInterruptHandler;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     /*
@@ -41,10 +46,7 @@ async fn main(_spawner: Spawner) {
     let config = Config::default();
     let mut ipcc = Ipcc::new(p.IPCC, config);
 
-    let rx_irq = interrupt::take!(IPCC_C1_RX);
-    let tx_irq = interrupt::take!(IPCC_C1_TX);
-
-    let mbox = TlMbox::init(&mut ipcc, rx_irq, tx_irq);
+    let mbox = TlMbox::init(&mut ipcc, Irqs);
 
     // initialize ble stack, does not return a response
     mbox.shci_ble_init(&mut ipcc, Default::default());
diff --git a/examples/stm32wl/src/bin/lora_lorawan.rs b/examples/stm32wl/src/bin/lora_lorawan.rs
index 1a271b2f2..e179c5ca1 100644
--- a/examples/stm32wl/src/bin/lora_lorawan.rs
+++ b/examples/stm32wl/src/bin/lora_lorawan.rs
@@ -7,12 +7,12 @@
 
 use defmt::info;
 use embassy_executor::Spawner;
-use embassy_lora::iv::Stm32wlInterfaceVariant;
+use embassy_lora::iv::{InterruptHandler, Stm32wlInterfaceVariant};
 use embassy_lora::LoraTimer;
 use embassy_stm32::gpio::{Level, Output, Pin, Speed};
 use embassy_stm32::rng::Rng;
 use embassy_stm32::spi::Spi;
-use embassy_stm32::{interrupt, into_ref, pac, Peripheral};
+use embassy_stm32::{bind_interrupts, pac};
 use embassy_time::Delay;
 use lora_phy::mod_params::*;
 use lora_phy::sx1261_2::SX1261_2;
@@ -24,6 +24,10 @@ use {defmt_rtt as _, panic_probe as _};
 
 const LORAWAN_REGION: region::Region = region::Region::EU868; // warning: set this appropriately for the region
 
+bind_interrupts!(struct Irqs{
+    SUBGHZ_RADIO => InterruptHandler;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = embassy_stm32::Config::default();
@@ -35,13 +39,11 @@ async fn main(_spawner: Spawner) {
 
     let spi = Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2);
 
-    let irq = interrupt::take!(SUBGHZ_RADIO);
-    into_ref!(irq);
     // Set CTRL1 and CTRL3 for high-power transmission, while CTRL2 acts as an RF switch between tx and rx
     let _ctrl1 = Output::new(p.PC4.degrade(), Level::Low, Speed::High);
     let ctrl2 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
     let _ctrl3 = Output::new(p.PC3.degrade(), Level::High, Speed::High);
-    let iv = Stm32wlInterfaceVariant::new(irq, None, Some(ctrl2)).unwrap();
+    let iv = Stm32wlInterfaceVariant::new(Irqs, None, Some(ctrl2)).unwrap();
 
     let mut delay = Delay;
 
diff --git a/examples/stm32wl/src/bin/lora_p2p_receive.rs b/examples/stm32wl/src/bin/lora_p2p_receive.rs
index 5e80e8f6a..d3f051b1c 100644
--- a/examples/stm32wl/src/bin/lora_p2p_receive.rs
+++ b/examples/stm32wl/src/bin/lora_p2p_receive.rs
@@ -7,10 +7,10 @@
 
 use defmt::info;
 use embassy_executor::Spawner;
-use embassy_lora::iv::Stm32wlInterfaceVariant;
+use embassy_lora::iv::{InterruptHandler, Stm32wlInterfaceVariant};
+use embassy_stm32::bind_interrupts;
 use embassy_stm32::gpio::{Level, Output, Pin, Speed};
 use embassy_stm32::spi::Spi;
-use embassy_stm32::{interrupt, into_ref, Peripheral};
 use embassy_time::{Delay, Duration, Timer};
 use lora_phy::mod_params::*;
 use lora_phy::sx1261_2::SX1261_2;
@@ -19,6 +19,10 @@ use {defmt_rtt as _, panic_probe as _};
 
 const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region
 
+bind_interrupts!(struct Irqs{
+    SUBGHZ_RADIO => InterruptHandler;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = embassy_stm32::Config::default();
@@ -27,13 +31,11 @@ async fn main(_spawner: Spawner) {
 
     let spi = Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2);
 
-    let irq = interrupt::take!(SUBGHZ_RADIO);
-    into_ref!(irq);
     // Set CTRL1 and CTRL3 for high-power transmission, while CTRL2 acts as an RF switch between tx and rx
     let _ctrl1 = Output::new(p.PC4.degrade(), Level::Low, Speed::High);
     let ctrl2 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
     let _ctrl3 = Output::new(p.PC3.degrade(), Level::High, Speed::High);
-    let iv = Stm32wlInterfaceVariant::new(irq, None, Some(ctrl2)).unwrap();
+    let iv = Stm32wlInterfaceVariant::new(Irqs, None, Some(ctrl2)).unwrap();
 
     let mut delay = Delay;
 
diff --git a/examples/stm32wl/src/bin/lora_p2p_send.rs b/examples/stm32wl/src/bin/lora_p2p_send.rs
index e22c714bd..fc5205c85 100644
--- a/examples/stm32wl/src/bin/lora_p2p_send.rs
+++ b/examples/stm32wl/src/bin/lora_p2p_send.rs
@@ -7,10 +7,10 @@
 
 use defmt::info;
 use embassy_executor::Spawner;
-use embassy_lora::iv::Stm32wlInterfaceVariant;
+use embassy_lora::iv::{InterruptHandler, Stm32wlInterfaceVariant};
+use embassy_stm32::bind_interrupts;
 use embassy_stm32::gpio::{Level, Output, Pin, Speed};
 use embassy_stm32::spi::Spi;
-use embassy_stm32::{interrupt, into_ref, Peripheral};
 use embassy_time::Delay;
 use lora_phy::mod_params::*;
 use lora_phy::sx1261_2::SX1261_2;
@@ -19,6 +19,10 @@ use {defmt_rtt as _, panic_probe as _};
 
 const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region
 
+bind_interrupts!(struct Irqs{
+    SUBGHZ_RADIO => InterruptHandler;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let mut config = embassy_stm32::Config::default();
@@ -27,13 +31,11 @@ async fn main(_spawner: Spawner) {
 
     let spi = Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2);
 
-    let irq = interrupt::take!(SUBGHZ_RADIO);
-    into_ref!(irq);
     // Set CTRL1 and CTRL3 for high-power transmission, while CTRL2 acts as an RF switch between tx and rx
     let _ctrl1 = Output::new(p.PC4.degrade(), Level::Low, Speed::High);
     let ctrl2 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
     let _ctrl3 = Output::new(p.PC3.degrade(), Level::High, Speed::High);
-    let iv = Stm32wlInterfaceVariant::new(irq, None, Some(ctrl2)).unwrap();
+    let iv = Stm32wlInterfaceVariant::new(Irqs, None, Some(ctrl2)).unwrap();
 
     let mut delay = Delay;
 
diff --git a/examples/stm32wl/src/bin/uart_async.rs b/examples/stm32wl/src/bin/uart_async.rs
index ac8766af6..07b0f9d2c 100644
--- a/examples/stm32wl/src/bin/uart_async.rs
+++ b/examples/stm32wl/src/bin/uart_async.rs
@@ -4,10 +4,15 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
-use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::usart::{Config, InterruptHandler, Uart};
+use embassy_stm32::{bind_interrupts, peripherals};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs{
+    USART1 => InterruptHandler<peripherals::USART1>;
+    LPUART1 => InterruptHandler<peripherals::LPUART1>;
+});
+
 /*
 Pass Incoming data from LPUART1 to USART1
 Example is written for the LoRa-E5 mini v1.0,
@@ -28,12 +33,10 @@ async fn main(_spawner: Spawner) {
     config2.baudrate = 9600;
 
     //RX/TX connected to USB/UART Bridge on LoRa-E5 mini v1.0
-    let irq = interrupt::take!(USART1);
-    let mut usart1 = Uart::new(p.USART1, p.PB7, p.PB6, irq, p.DMA1_CH3, p.DMA1_CH4, config1);
+    let mut usart1 = Uart::new(p.USART1, p.PB7, p.PB6, Irqs, p.DMA1_CH3, p.DMA1_CH4, config1);
 
     //RX1/TX1 (LPUART) on LoRa-E5 mini v1.0
-    let irq = interrupt::take!(LPUART1);
-    let mut usart2 = Uart::new(p.LPUART1, p.PC0, p.PC1, irq, p.DMA1_CH5, p.DMA1_CH6, config2);
+    let mut usart2 = Uart::new(p.LPUART1, p.PC0, p.PC1, Irqs, p.DMA1_CH5, p.DMA1_CH6, config2);
 
     unwrap!(usart1.write(b"Hello Embassy World!\r\n").await);
     unwrap!(usart2.write(b"Hello Embassy World!\r\n").await);
diff --git a/tests/stm32/src/bin/sdmmc.rs b/tests/stm32/src/bin/sdmmc.rs
index c4e50cb4a..7d96cf41e 100644
--- a/tests/stm32/src/bin/sdmmc.rs
+++ b/tests/stm32/src/bin/sdmmc.rs
@@ -7,9 +7,13 @@ use defmt::{assert_eq, *};
 use embassy_executor::Spawner;
 use embassy_stm32::sdmmc::{DataBlock, Sdmmc};
 use embassy_stm32::time::mhz;
-use embassy_stm32::{interrupt, Config};
+use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    SDIO => sdmmc::InterruptHandler<peripherals::SDIO>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello World!");
@@ -20,17 +24,8 @@ async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config);
 
     #[cfg(feature = "stm32f429zi")]
-    let (mut sdmmc, mut irq, mut dma, mut clk, mut cmd, mut d0, mut d1, mut d2, mut d3) = (
-        p.SDIO,
-        interrupt::take!(SDIO),
-        p.DMA2_CH3,
-        p.PC12,
-        p.PD2,
-        p.PC8,
-        p.PC9,
-        p.PC10,
-        p.PC11,
-    );
+    let (mut sdmmc, mut dma, mut clk, mut cmd, mut d0, mut d1, mut d2, mut d3) =
+        (p.SDIO, p.DMA2_CH3, p.PC12, p.PD2, p.PC8, p.PC9, p.PC10, p.PC11);
 
     // Arbitrary block index
     let block_idx = 16;
@@ -48,7 +43,7 @@ async fn main(_spawner: Spawner) {
     info!("initializing in 4-bit mode...");
     let mut s = Sdmmc::new_4bit(
         &mut sdmmc,
-        &mut irq,
+        Irqs,
         &mut dma,
         &mut clk,
         &mut cmd,
@@ -97,7 +92,7 @@ async fn main(_spawner: Spawner) {
     info!("initializing in 1-bit mode...");
     let mut s = Sdmmc::new_1bit(
         &mut sdmmc,
-        &mut irq,
+        Irqs,
         &mut dma,
         &mut clk,
         &mut cmd,
diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs
index 0749f8406..415c7afa9 100644
--- a/tests/stm32/src/bin/usart.rs
+++ b/tests/stm32/src/bin/usart.rs
@@ -7,11 +7,37 @@ mod example_common;
 use defmt::assert_eq;
 use embassy_executor::Spawner;
 use embassy_stm32::dma::NoDma;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Error, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use embassy_time::{Duration, Instant};
 use example_common::*;
 
+#[cfg(any(
+    feature = "stm32f103c8",
+    feature = "stm32g491re",
+    feature = "stm32g071rb",
+    feature = "stm32h755zi",
+    feature = "stm32c031c6",
+))]
+bind_interrupts!(struct Irqs {
+    USART1 => usart::InterruptHandler<peripherals::USART1>;
+});
+
+#[cfg(feature = "stm32u585ai")]
+bind_interrupts!(struct Irqs {
+    USART3 => usart::InterruptHandler<peripherals::USART3>;
+});
+
+#[cfg(feature = "stm32f429zi")]
+bind_interrupts!(struct Irqs {
+    USART6 => usart::InterruptHandler<peripherals::USART6>;
+});
+
+#[cfg(any(feature = "stm32wb55rg", feature = "stm32h563zi"))]
+bind_interrupts!(struct Irqs {
+    LPUART1 => usart::InterruptHandler<peripherals::LPUART1>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config());
@@ -20,27 +46,27 @@ async fn main(_spawner: Spawner) {
     // Arduino pins D0 and D1
     // They're connected together with a 1K resistor.
     #[cfg(feature = "stm32f103c8")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PA9, p.PA10, p.USART1, interrupt::take!(USART1));
+    let (mut tx, mut rx, mut usart) = (p.PA9, p.PA10, p.USART1);
     #[cfg(feature = "stm32g491re")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1));
+    let (mut tx, mut rx, mut usart) = (p.PC4, p.PC5, p.USART1);
     #[cfg(feature = "stm32g071rb")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1));
+    let (mut tx, mut rx, mut usart) = (p.PC4, p.PC5, p.USART1);
     #[cfg(feature = "stm32f429zi")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PG14, p.PG9, p.USART6, interrupt::take!(USART6));
+    let (mut tx, mut rx, mut usart) = (p.PG14, p.PG9, p.USART6);
     #[cfg(feature = "stm32wb55rg")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PA2, p.PA3, p.LPUART1, interrupt::take!(LPUART1));
+    let (mut tx, mut rx, mut usart) = (p.PA2, p.PA3, p.LPUART1);
     #[cfg(feature = "stm32h755zi")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1));
+    let (mut tx, mut rx, mut usart) = (p.PB6, p.PB7, p.USART1);
     #[cfg(feature = "stm32u585ai")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PD8, p.PD9, p.USART3, interrupt::take!(USART3));
+    let (mut tx, mut rx, mut usart) = (p.PD8, p.PD9, p.USART3);
     #[cfg(feature = "stm32h563zi")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PB6, p.PB7, p.LPUART1, interrupt::take!(LPUART1));
+    let (mut tx, mut rx, mut usart) = (p.PB6, p.PB7, p.LPUART1);
     #[cfg(feature = "stm32c031c6")]
-    let (mut tx, mut rx, mut usart, mut irq) = (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1));
+    let (mut tx, mut rx, mut usart) = (p.PB6, p.PB7, p.USART1);
 
     {
         let config = Config::default();
-        let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, &mut irq, NoDma, NoDma, config);
+        let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, Irqs, NoDma, NoDma, config);
 
         // We can't send too many bytes, they have to fit in the FIFO.
         // This is because we aren't sending+receiving at the same time.
@@ -56,7 +82,7 @@ async fn main(_spawner: Spawner) {
     // Test error handling with with an overflow error
     {
         let config = Config::default();
-        let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, &mut irq, NoDma, NoDma, config);
+        let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, Irqs, NoDma, NoDma, config);
 
         // Send enough bytes to fill the RX FIFOs off all USART versions.
         let data = [0xC0, 0xDE, 0x12, 0x23, 0x34];
@@ -90,7 +116,7 @@ async fn main(_spawner: Spawner) {
 
         let mut config = Config::default();
         config.baudrate = baudrate;
-        let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, &mut irq, NoDma, NoDma, config);
+        let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, Irqs, NoDma, NoDma, config);
 
         let n = (baudrate as usize / 100).max(64);
 
diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs
index 62444f0a8..7f002b97e 100644
--- a/tests/stm32/src/bin/usart_dma.rs
+++ b/tests/stm32/src/bin/usart_dma.rs
@@ -7,10 +7,36 @@ mod example_common;
 use defmt::assert_eq;
 use embassy_executor::Spawner;
 use embassy_futures::join::join;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, Uart};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use example_common::*;
 
+#[cfg(any(
+    feature = "stm32f103c8",
+    feature = "stm32g491re",
+    feature = "stm32g071rb",
+    feature = "stm32h755zi",
+    feature = "stm32c031c6",
+))]
+bind_interrupts!(struct Irqs {
+    USART1 => usart::InterruptHandler<peripherals::USART1>;
+});
+
+#[cfg(feature = "stm32u585ai")]
+bind_interrupts!(struct Irqs {
+    USART3 => usart::InterruptHandler<peripherals::USART3>;
+});
+
+#[cfg(feature = "stm32f429zi")]
+bind_interrupts!(struct Irqs {
+    USART6 => usart::InterruptHandler<peripherals::USART6>;
+});
+
+#[cfg(any(feature = "stm32wb55rg", feature = "stm32h563zi"))]
+bind_interrupts!(struct Irqs {
+    LPUART1 => usart::InterruptHandler<peripherals::LPUART1>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config());
@@ -19,62 +45,23 @@ async fn main(_spawner: Spawner) {
     // Arduino pins D0 and D1
     // They're connected together with a 1K resistor.
     #[cfg(feature = "stm32f103c8")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PA9,
-        p.PA10,
-        p.USART1,
-        interrupt::take!(USART1),
-        p.DMA1_CH4,
-        p.DMA1_CH5,
-    );
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PA9, p.PA10, p.USART1, Irqs, p.DMA1_CH4, p.DMA1_CH5);
     #[cfg(feature = "stm32g491re")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, Irqs, p.DMA1_CH1, p.DMA1_CH2);
     #[cfg(feature = "stm32g071rb")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, Irqs, p.DMA1_CH1, p.DMA1_CH2);
     #[cfg(feature = "stm32f429zi")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PG14,
-        p.PG9,
-        p.USART6,
-        interrupt::take!(USART6),
-        p.DMA2_CH6,
-        p.DMA2_CH1,
-    );
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PG14, p.PG9, p.USART6, Irqs, p.DMA2_CH6, p.DMA2_CH1);
     #[cfg(feature = "stm32wb55rg")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PA2,
-        p.PA3,
-        p.LPUART1,
-        interrupt::take!(LPUART1),
-        p.DMA1_CH1,
-        p.DMA1_CH2,
-    );
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PA2, p.PA3, p.LPUART1, Irqs, p.DMA1_CH1, p.DMA1_CH2);
     #[cfg(feature = "stm32h755zi")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH0, p.DMA1_CH1);
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, Irqs, p.DMA1_CH0, p.DMA1_CH1);
     #[cfg(feature = "stm32u585ai")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PD8,
-        p.PD9,
-        p.USART3,
-        interrupt::take!(USART3),
-        p.GPDMA1_CH0,
-        p.GPDMA1_CH1,
-    );
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PD8, p.PD9, p.USART3, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1);
     #[cfg(feature = "stm32h563zi")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PB6,
-        p.PB7,
-        p.LPUART1,
-        interrupt::take!(LPUART1),
-        p.GPDMA1_CH0,
-        p.GPDMA1_CH1,
-    );
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PB6, p.PB7, p.LPUART1, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1);
     #[cfg(feature = "stm32c031c6")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
+    let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, Irqs, p.DMA1_CH1, p.DMA1_CH2);
 
     let config = Config::default();
     let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config);
diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs
index 9d75dbe55..3a34773f7 100644
--- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs
+++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs
@@ -8,13 +8,40 @@
 mod example_common;
 use defmt::{assert_eq, panic};
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
 use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx};
+use embassy_stm32::{bind_interrupts, peripherals, usart};
 use embassy_time::{Duration, Timer};
 use example_common::*;
 use rand_chacha::ChaCha8Rng;
 use rand_core::{RngCore, SeedableRng};
 
+#[cfg(any(
+    feature = "stm32f103c8",
+    feature = "stm32g491re",
+    feature = "stm32g071rb",
+    feature = "stm32h755zi",
+    feature = "stm32c031c6",
+))]
+bind_interrupts!(struct Irqs {
+    USART1 => usart::InterruptHandler<peripherals::USART1>;
+});
+
+#[cfg(feature = "stm32u585ai")]
+bind_interrupts!(struct Irqs {
+    USART3 => usart::InterruptHandler<peripherals::USART3>;
+});
+
+#[cfg(feature = "stm32f429zi")]
+bind_interrupts!(struct Irqs {
+    USART1 => usart::InterruptHandler<peripherals::USART1>;
+    USART6 => usart::InterruptHandler<peripherals::USART6>;
+});
+
+#[cfg(any(feature = "stm32wb55rg", feature = "stm32h563zi"))]
+bind_interrupts!(struct Irqs {
+    LPUART1 => usart::InterruptHandler<peripherals::LPUART1>;
+});
+
 #[cfg(feature = "stm32f103c8")]
 mod board {
     pub type Uart = embassy_stm32::peripherals::USART1;
@@ -74,53 +101,21 @@ async fn main(spawner: Spawner) {
     // Arduino pins D0 and D1
     // They're connected together with a 1K resistor.
     #[cfg(feature = "stm32f103c8")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PA9,
-        p.PA10,
-        p.USART1,
-        interrupt::take!(USART1),
-        p.DMA1_CH4,
-        p.DMA1_CH5,
-    );
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PA9, p.PA10, p.USART1, p.DMA1_CH4, p.DMA1_CH5);
     #[cfg(feature = "stm32g491re")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2);
     #[cfg(feature = "stm32g071rb")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2);
     #[cfg(feature = "stm32f429zi")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PG14,
-        p.PG9,
-        p.USART6,
-        interrupt::take!(USART6),
-        p.DMA2_CH6,
-        p.DMA2_CH1,
-    );
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PG14, p.PG9, p.USART6, p.DMA2_CH6, p.DMA2_CH1);
     #[cfg(feature = "stm32wb55rg")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PA2,
-        p.PA3,
-        p.LPUART1,
-        interrupt::take!(LPUART1),
-        p.DMA1_CH1,
-        p.DMA1_CH2,
-    );
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PA2, p.PA3, p.LPUART1, p.DMA1_CH1, p.DMA1_CH2);
     #[cfg(feature = "stm32h755zi")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH0, p.DMA1_CH1);
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, p.DMA1_CH0, p.DMA1_CH1);
     #[cfg(feature = "stm32u585ai")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) = (
-        p.PD8,
-        p.PD9,
-        p.USART3,
-        interrupt::take!(USART3),
-        p.GPDMA1_CH0,
-        p.GPDMA1_CH1,
-    );
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PD8, p.PD9, p.USART3, p.GPDMA1_CH0, p.GPDMA1_CH1);
     #[cfg(feature = "stm32c031c6")]
-    let (tx, rx, usart, irq, tx_dma, rx_dma) =
-        (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
+    let (tx, rx, usart, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, p.DMA1_CH1, p.DMA1_CH2);
 
     // To run this test, use the saturating_serial test utility to saturate the serial port
 
@@ -132,7 +127,7 @@ async fn main(spawner: Spawner) {
     config.stop_bits = StopBits::STOP1;
     config.parity = Parity::ParityNone;
 
-    let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config);
+    let usart = Uart::new(usart, rx, tx, Irqs, tx_dma, rx_dma, config);
     let (tx, rx) = usart.split();
     static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE];
     let dma_buf = unsafe { DMA_BUF.as_mut() };