diff --git a/embassy-extras/src/ring_buffer.rs b/embassy-extras/src/ring_buffer.rs
index f2b9f7359..0ef66f00a 100644
--- a/embassy-extras/src/ring_buffer.rs
+++ b/embassy-extras/src/ring_buffer.rs
@@ -69,6 +69,12 @@ impl<'a> RingBuffer<'a> {
         self.empty = self.start == self.end;
     }
 
+    pub fn clear(&mut self) {
+        self.start = 0;
+        self.end = 0;
+        self.empty = true;
+    }
+
     fn wrap(&self, n: usize) -> usize {
         assert!(n <= self.buf.len());
         if n == self.buf.len() {
diff --git a/embassy-stm32/src/interrupt.rs b/embassy-stm32/src/interrupt.rs
index 5ad7ef8ef..7def7be58 100644
--- a/embassy-stm32/src/interrupt.rs
+++ b/embassy-stm32/src/interrupt.rs
@@ -9,7 +9,7 @@ use crate::pac::NVIC_PRIO_BITS;
 
 // Re-exports
 pub use cortex_m::interrupt::{CriticalSection, Mutex};
-pub use embassy::interrupt::{declare, take, Interrupt};
+pub use embassy::interrupt::{declare, take, Interrupt, InterruptExt};
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
diff --git a/embassy-stm32f4-examples/src/bin/usb_serial.rs b/embassy-stm32f4-examples/src/bin/usb_serial.rs
index 90139c5ac..bf8ca4252 100644
--- a/embassy-stm32f4-examples/src/bin/usb_serial.rs
+++ b/embassy-stm32f4-examples/src/bin/usb_serial.rs
@@ -12,7 +12,7 @@ use embassy::executor::{task, Executor};
 use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
 use embassy::time::{Duration, Timer};
 use embassy::util::Forever;
-use embassy_stm32f4::interrupt::OwnedInterrupt;
+use embassy_stm32f4::interrupt::InterruptExt;
 use embassy_stm32f4::usb::Usb;
 use embassy_stm32f4::usb_serial::UsbSerial;
 use embassy_stm32f4::{interrupt, pac, rtc};
diff --git a/embassy-stm32f4-examples/src/bin/usb_serial2.rs b/embassy-stm32f4-examples/src/bin/usb_serial2.rs
index 79e323ca6..dd2618759 100644
--- a/embassy-stm32f4-examples/src/bin/usb_serial2.rs
+++ b/embassy-stm32f4-examples/src/bin/usb_serial2.rs
@@ -11,7 +11,7 @@ use defmt::panic;
 use embassy::executor::{task, Executor};
 use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
 use embassy::util::Forever;
-use embassy_stm32f4::interrupt::OwnedInterrupt;
+use embassy_stm32f4::interrupt::InterruptExt;
 use embassy_stm32f4::usb::Usb;
 use embassy_stm32f4::usb_serial::UsbSerial;
 use embassy_stm32f4::{interrupt, pac};
diff --git a/embassy-stm32f4/Cargo.toml b/embassy-stm32f4/Cargo.toml
index b39a141b4..55e6b84dd 100644
--- a/embassy-stm32f4/Cargo.toml
+++ b/embassy-stm32f4/Cargo.toml
@@ -32,6 +32,8 @@ stm32f479 = ["stm32f4xx-hal/stm32f469", "embassy-stm32/stm32f479"]
 [dependencies]
 embassy = { version = "0.1.0", path = "../embassy" }
 embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" }
+embassy-extras = {version = "0.1.0", path = "../embassy-extras" }
+
 defmt = { version = "0.2.0", optional = true }
 log = { version = "0.4.11", optional = true }
 cortex-m-rt = "0.6.13"
diff --git a/embassy-stm32f4/src/lib.rs b/embassy-stm32f4/src/lib.rs
index 1788f5e77..1d44e379f 100644
--- a/embassy-stm32f4/src/lib.rs
+++ b/embassy-stm32f4/src/lib.rs
@@ -318,7 +318,6 @@ pub mod rtc;
 pub mod serial;
 pub mod usb;
 pub mod usb_serial;
-pub mod util;
 
 pub(crate) mod cdc_acm;
 
diff --git a/embassy-stm32f4/src/usb.rs b/embassy-stm32f4/src/usb.rs
index 9e7411562..1d43c4f51 100644
--- a/embassy-stm32f4/src/usb.rs
+++ b/embassy-stm32f4/src/usb.rs
@@ -8,7 +8,7 @@ use usb_device::device::UsbDevice;
 
 use crate::interrupt;
 use crate::usb_serial::{ReadInterface, UsbSerial, WriteInterface};
-use crate::util::peripheral::{PeripheralMutex, PeripheralState};
+use embassy_extras::peripheral::{PeripheralMutex, PeripheralState};
 
 pub struct State<'bus, B, T>
 where
@@ -36,7 +36,7 @@ where
     pub fn new<S: IntoClassSet<B, T>>(
         device: UsbDevice<'bus, B>,
         class_set: S,
-        irq: interrupt::OTG_FSInterrupt,
+        irq: interrupt::OTG_FS,
     ) -> Self {
         let state = State {
             device,
@@ -54,7 +54,7 @@ where
         let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
 
         // Use inner to register the irq
-        mutex.with(|_, _| {});
+        mutex.register_interrupt();
     }
 }
 
@@ -119,7 +119,7 @@ where
     B: UsbBus,
     T: ClassSet<B>,
 {
-    type Interrupt = interrupt::OTG_FSInterrupt;
+    type Interrupt = interrupt::OTG_FS;
     fn on_interrupt(&mut self) {
         self.classes.poll_all(&mut self.device);
     }
diff --git a/embassy-stm32f4/src/usb_serial.rs b/embassy-stm32f4/src/usb_serial.rs
index bacc886d8..00d92c9c5 100644
--- a/embassy-stm32f4/src/usb_serial.rs
+++ b/embassy-stm32f4/src/usb_serial.rs
@@ -11,8 +11,8 @@ use usb_device::UsbError;
 
 use crate::cdc_acm::CdcAcmClass;
 use crate::usb::{ClassSet, SerialState, State};
-use crate::util::peripheral::PeripheralMutex;
-use crate::util::ring_buffer::RingBuffer;
+use embassy_extras::peripheral::PeripheralMutex;
+use embassy_extras::ring_buffer::RingBuffer;
 
 pub struct ReadInterface<'a, 'bus, 'c, I, B, T>
 where
diff --git a/embassy-stm32f4/src/util/mod.rs b/embassy-stm32f4/src/util/mod.rs
deleted file mode 100644
index cf3306545..000000000
--- a/embassy-stm32f4/src/util/mod.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-pub mod peripheral;
-pub mod ring_buffer;
-
-/// Low power blocking wait loop using WFE/SEV.
-pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) {
-    while !condition() {
-        // WFE might "eat" an event that would have otherwise woken the executor.
-        cortex_m::asm::wfe();
-    }
-    // Retrigger an event to be transparent to the executor.
-    cortex_m::asm::sev();
-}
diff --git a/embassy-stm32f4/src/util/peripheral.rs b/embassy-stm32f4/src/util/peripheral.rs
deleted file mode 100644
index f2c7912ff..000000000
--- a/embassy-stm32f4/src/util/peripheral.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use core::cell::UnsafeCell;
-use core::marker::{PhantomData, PhantomPinned};
-use core::pin::Pin;
-use core::sync::atomic::{compiler_fence, Ordering};
-
-use crate::interrupt::OwnedInterrupt;
-
-pub trait PeripheralState {
-    type Interrupt: OwnedInterrupt;
-    fn on_interrupt(&mut self);
-}
-
-pub struct PeripheralMutex<S: PeripheralState> {
-    inner: Option<(UnsafeCell<S>, S::Interrupt)>,
-    _not_send: PhantomData<*mut ()>,
-    _pinned: PhantomPinned,
-}
-
-impl<S: PeripheralState> PeripheralMutex<S> {
-    pub fn new(state: S, irq: S::Interrupt) -> Self {
-        Self {
-            inner: Some((UnsafeCell::new(state), irq)),
-            _not_send: PhantomData,
-            _pinned: PhantomPinned,
-        }
-    }
-
-    pub fn with<R>(self: Pin<&mut Self>, f: impl FnOnce(&mut S, &mut S::Interrupt) -> R) -> R {
-        let this = unsafe { self.get_unchecked_mut() };
-        let (state, irq) = unwrap!(this.inner.as_mut());
-
-        irq.disable();
-        compiler_fence(Ordering::SeqCst);
-
-        irq.set_handler(
-            |p| {
-                // Safety: it's OK to get a &mut to the state, since
-                // - We're in the IRQ, no one else can't preempt us
-                // - We can't have preempted a with() call because the irq is disabled during it.
-                let state = unsafe { &mut *(p as *mut S) };
-                state.on_interrupt();
-            },
-            state.get() as *mut (),
-        );
-
-        // Safety: it's OK to get a &mut to the state, since the irq is disabled.
-        let state = unsafe { &mut *state.get() };
-
-        let r = f(state, irq);
-
-        compiler_fence(Ordering::SeqCst);
-        irq.enable();
-
-        r
-    }
-
-    pub fn try_free(self: Pin<&mut Self>) -> Option<(S, S::Interrupt)> {
-        let this = unsafe { self.get_unchecked_mut() };
-        this.inner.take().map(|(state, irq)| {
-            irq.disable();
-            irq.remove_handler();
-            (state.into_inner(), irq)
-        })
-    }
-
-    pub fn free(self: Pin<&mut Self>) -> (S, S::Interrupt) {
-        unwrap!(self.try_free())
-    }
-}
-
-impl<S: PeripheralState> Drop for PeripheralMutex<S> {
-    fn drop(&mut self) {
-        if let Some((_state, irq)) = &mut self.inner {
-            irq.disable();
-            irq.remove_handler();
-        }
-    }
-}
diff --git a/embassy-stm32f4/src/util/ring_buffer.rs b/embassy-stm32f4/src/util/ring_buffer.rs
deleted file mode 100644
index 0ef66f00a..000000000
--- a/embassy-stm32f4/src/util/ring_buffer.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-use crate::fmt::{assert, *};
-
-pub struct RingBuffer<'a> {
-    buf: &'a mut [u8],
-    start: usize,
-    end: usize,
-    empty: bool,
-}
-
-impl<'a> RingBuffer<'a> {
-    pub fn new(buf: &'a mut [u8]) -> Self {
-        Self {
-            buf,
-            start: 0,
-            end: 0,
-            empty: true,
-        }
-    }
-
-    pub fn push_buf(&mut self) -> &mut [u8] {
-        if self.start == self.end && !self.empty {
-            trace!("  ringbuf: push_buf empty");
-            return &mut self.buf[..0];
-        }
-
-        let n = if self.start <= self.end {
-            self.buf.len() - self.end
-        } else {
-            self.start - self.end
-        };
-
-        trace!("  ringbuf: push_buf {:?}..{:?}", self.end, self.end + n);
-        &mut self.buf[self.end..self.end + n]
-    }
-
-    pub fn push(&mut self, n: usize) {
-        trace!("  ringbuf: push {:?}", n);
-        if n == 0 {
-            return;
-        }
-
-        self.end = self.wrap(self.end + n);
-        self.empty = false;
-    }
-
-    pub fn pop_buf(&mut self) -> &mut [u8] {
-        if self.empty {
-            trace!("  ringbuf: pop_buf empty");
-            return &mut self.buf[..0];
-        }
-
-        let n = if self.end <= self.start {
-            self.buf.len() - self.start
-        } else {
-            self.end - self.start
-        };
-
-        trace!("  ringbuf: pop_buf {:?}..{:?}", self.start, self.start + n);
-        &mut self.buf[self.start..self.start + n]
-    }
-
-    pub fn pop(&mut self, n: usize) {
-        trace!("  ringbuf: pop {:?}", n);
-        if n == 0 {
-            return;
-        }
-
-        self.start = self.wrap(self.start + n);
-        self.empty = self.start == self.end;
-    }
-
-    pub fn clear(&mut self) {
-        self.start = 0;
-        self.end = 0;
-        self.empty = true;
-    }
-
-    fn wrap(&self, n: usize) -> usize {
-        assert!(n <= self.buf.len());
-        if n == self.buf.len() {
-            0
-        } else {
-            n
-        }
-    }
-}