diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 9b3d3b29e..19499632d 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -52,6 +52,9 @@ jobs:
           - package: embassy-stm32f4
             target: thumbv7em-none-eabi
             features: stm32f405
+          - package: embassy-stm32f4
+            target: thumbv7em-none-eabi
+            features: stm32f446
           - package: embassy-stm32f4
             target: thumbv7em-none-eabi
             features: stm32f405,defmt
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8a292c0be..58ef9d8af 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,7 @@
 {
   "rust-analyzer.assist.importMergeBehavior": "last",
   "editor.formatOnSave": true,
+  "rust.target": "thumbv7em-none-eabihf",
   "rust-analyzer.cargo.allFeatures": false,
   "rust-analyzer.checkOnSave.allFeatures": false,
   "rust-analyzer.checkOnSave.allTargets": false,
diff --git a/README.md b/README.md
index 29c4a2215..03732306e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
 # Embassy
 
-Embassy is a project to make async/await a first-class option for embedded development.
+Embassy is a project to make async/await a first-class option for embedded development. For more information and instructions to
+get started, click [here](https://github.com/embassy-rs/embassy/wiki).
 
 ## Traits and types
 
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index 136bd7fc7..66221ed59 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -43,5 +43,7 @@ cortex-m-rt = "0.6.13"
 cortex-m = "0.7.1"
 embedded-hal    = { version = "0.2.4" }
 embedded-dma    = { version = "0.1.2" }
+bxcan = "0.5.0"
+nb = "*"
 stm32f4xx-hal  = { version = "0.8.3", features = ["rt", "can"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git", optional = true }
-stm32l0xx-hal = { version = "0.7.0", features = ["rt"], git = "https://github.com/stm32-rs/stm32l0xx-hal.git", optional = true }
\ No newline at end of file
+stm32l0xx-hal = { version = "0.7.0", features = ["rt"], optional = true }
\ No newline at end of file
diff --git a/embassy-stm32f4/src/can.rs b/embassy-stm32/src/can.rs
similarity index 85%
rename from embassy-stm32f4/src/can.rs
rename to embassy-stm32/src/can.rs
index 526d002c0..f97e900ea 100644
--- a/embassy-stm32f4/src/can.rs
+++ b/embassy-stm32/src/can.rs
@@ -95,6 +95,25 @@ macro_rules! can {
     }
 }
 
+#[cfg(any(
+    feature = "stm32f401",
+    feature = "stm32f405",
+    feature = "stm32f407",
+    feature = "stm32f410",
+    feature = "stm32f411",
+    feature = "stm32f412",
+    feature = "stm32f413",
+    feature = "stm32f415",
+    feature = "stm32f417",
+    feature = "stm32f423",
+    feature = "stm32f427",
+    feature = "stm32f429",
+    feature = "stm32f437",
+    feature = "stm32f439",
+    feature = "stm32f446",
+    feature = "stm32f469",
+    feature = "stm32f479",
+))]
 can! {
     CAN1 => (CAN1_TX, CAN1_RX0),
     CAN2 => (CAN2_TX, CAN2_RX0),
diff --git a/embassy-stm32f4/src/exti.rs b/embassy-stm32/src/exti.rs
similarity index 54%
rename from embassy-stm32f4/src/exti.rs
rename to embassy-stm32/src/exti.rs
index 154ca45c1..8d70defe6 100644
--- a/embassy-stm32f4/src/exti.rs
+++ b/embassy-stm32/src/exti.rs
@@ -3,35 +3,57 @@ use core::mem;
 use core::pin::Pin;
 use cortex_m;
 
-use embassy::traits::gpio::{WaitForFallingEdge, WaitForRisingEdge};
+use crate::hal::gpio;
+
+#[cfg(any(
+    feature = "stm32f401",
+    feature = "stm32f405",
+    feature = "stm32f407",
+    feature = "stm32f410",
+    feature = "stm32f411",
+    feature = "stm32f412",
+    feature = "stm32f413",
+    feature = "stm32f415",
+    feature = "stm32f417",
+    feature = "stm32f423",
+    feature = "stm32f427",
+    feature = "stm32f429",
+    feature = "stm32f437",
+    feature = "stm32f439",
+    feature = "stm32f446",
+    feature = "stm32f469",
+    feature = "stm32f479",
+))]
+use crate::hal::syscfg::SysCfg;
+
+#[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+use crate::hal::syscfg::SYSCFG as SysCfg;
+
+use embassy::traits::gpio::{
+    WaitForAnyEdge, WaitForFallingEdge, WaitForHigh, WaitForLow, WaitForRisingEdge,
+};
 use embassy::util::InterruptFuture;
 
-use crate::hal::gpio;
-use crate::hal::gpio::Edge;
-use crate::hal::syscfg::SysCfg;
-use crate::pac::EXTI;
 use embedded_hal::digital::v2 as digital;
 
 use crate::interrupt;
 
-pub struct ExtiPin<T: gpio::ExtiPin + WithInterrupt> {
+pub struct ExtiPin<T: Instance> {
     pin: T,
     interrupt: T::Interrupt,
 }
 
-impl<T: gpio::ExtiPin + WithInterrupt> ExtiPin<T> {
-    pub fn new(mut pin: T, interrupt: T::Interrupt) -> Self {
-        let mut syscfg: SysCfg = unsafe { mem::transmute(()) };
-
+impl<T: Instance> ExtiPin<T> {
+    pub fn new(mut pin: T, interrupt: T::Interrupt, syscfg: &mut SysCfg) -> Self {
         cortex_m::interrupt::free(|_| {
-            pin.make_interrupt_source(&mut syscfg);
+            pin.make_source(syscfg);
         });
 
         Self { pin, interrupt }
     }
 }
 
-impl<T: gpio::ExtiPin + WithInterrupt + digital::OutputPin> digital::OutputPin for ExtiPin<T> {
+impl<T: Instance + digital::OutputPin> digital::OutputPin for ExtiPin<T> {
     type Error = T::Error;
 
     fn set_low(&mut self) -> Result<(), Self::Error> {
@@ -43,9 +65,7 @@ impl<T: gpio::ExtiPin + WithInterrupt + digital::OutputPin> digital::OutputPin f
     }
 }
 
-impl<T: gpio::ExtiPin + WithInterrupt + digital::StatefulOutputPin> digital::StatefulOutputPin
-    for ExtiPin<T>
-{
+impl<T: Instance + digital::StatefulOutputPin> digital::StatefulOutputPin for ExtiPin<T> {
     fn is_set_low(&self) -> Result<bool, Self::Error> {
         self.pin.is_set_low()
     }
@@ -55,9 +75,7 @@ impl<T: gpio::ExtiPin + WithInterrupt + digital::StatefulOutputPin> digital::Sta
     }
 }
 
-impl<T: gpio::ExtiPin + WithInterrupt + digital::ToggleableOutputPin> digital::ToggleableOutputPin
-    for ExtiPin<T>
-{
+impl<T: Instance + digital::ToggleableOutputPin> digital::ToggleableOutputPin for ExtiPin<T> {
     type Error = T::Error;
 
     fn toggle(&mut self) -> Result<(), Self::Error> {
@@ -65,7 +83,7 @@ impl<T: gpio::ExtiPin + WithInterrupt + digital::ToggleableOutputPin> digital::T
     }
 }
 
-impl<T: gpio::ExtiPin + WithInterrupt + digital::InputPin> digital::InputPin for ExtiPin<T> {
+impl<T: Instance + digital::InputPin> digital::InputPin for ExtiPin<T> {
     type Error = T::Error;
 
     fn is_high(&self) -> Result<bool, Self::Error> {
@@ -77,6 +95,73 @@ impl<T: gpio::ExtiPin + WithInterrupt + digital::InputPin> digital::InputPin for
     }
 }
 
+impl<T: Instance + digital::InputPin + 'static> ExtiPin<T> {
+    fn wait_for_state<'a>(self: Pin<&'a mut Self>, state: bool) -> impl Future<Output = ()> + 'a {
+        let s = unsafe { self.get_unchecked_mut() };
+
+        s.pin.clear_pending_bit();
+        async move {
+            let fut = InterruptFuture::new(&mut s.interrupt);
+            let pin = &mut s.pin;
+            cortex_m::interrupt::free(|_| {
+                pin.trigger_edge(if state {
+                    EdgeOption::Rising
+                } else {
+                    EdgeOption::Falling
+                });
+            });
+
+            if (state && s.pin.is_high().unwrap_or(false))
+                || (!state && s.pin.is_low().unwrap_or(false))
+            {
+                return;
+            }
+
+            fut.await;
+
+            s.pin.clear_pending_bit();
+        }
+    }
+}
+
+impl<T: Instance + 'static> ExtiPin<T> {
+    fn wait_for_edge<'a>(
+        self: Pin<&'a mut Self>,
+        state: EdgeOption,
+    ) -> impl Future<Output = ()> + 'a {
+        let s = unsafe { self.get_unchecked_mut() };
+
+        s.pin.clear_pending_bit();
+        async move {
+            let fut = InterruptFuture::new(&mut s.interrupt);
+            let pin = &mut s.pin;
+            cortex_m::interrupt::free(|_| {
+                pin.trigger_edge(state);
+            });
+
+            fut.await;
+
+            s.pin.clear_pending_bit();
+        }
+    }
+}
+
+impl<T: Instance + digital::InputPin + 'static> WaitForHigh for ExtiPin<T> {
+    type Future<'a> = impl Future<Output = ()> + 'a;
+
+    fn wait_for_high<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
+        self.wait_for_state(true)
+    }
+}
+
+impl<T: Instance + digital::InputPin + 'static> WaitForLow for ExtiPin<T> {
+    type Future<'a> = impl Future<Output = ()> + 'a;
+
+    fn wait_for_low<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
+        self.wait_for_state(false)
+    }
+}
+
 /*
     Irq	Handler	Description
     EXTI0_IRQn	EXTI0_IRQHandler	Handler for pins connected to line 0
@@ -88,49 +173,27 @@ impl<T: gpio::ExtiPin + WithInterrupt + digital::InputPin> digital::InputPin for
     EXTI15_10_IRQn	EXTI15_10_IRQHandler	Handler for pins connected to line 10 to 15
 */
 
-impl<T: gpio::ExtiPin + WithInterrupt + 'static> WaitForRisingEdge for ExtiPin<T> {
+impl<T: Instance + 'static> WaitForRisingEdge for ExtiPin<T> {
     type Future<'a> = impl Future<Output = ()> + 'a;
 
     fn wait_for_rising_edge<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
-        let s = unsafe { self.get_unchecked_mut() };
-
-        s.pin.clear_interrupt_pending_bit();
-        async move {
-            let fut = InterruptFuture::new(&mut s.interrupt);
-            let pin = &mut s.pin;
-            cortex_m::interrupt::free(|_| {
-                let mut exti: EXTI = unsafe { mem::transmute(()) };
-
-                pin.trigger_on_edge(&mut exti, Edge::RISING);
-                pin.enable_interrupt(&mut exti);
-            });
-            fut.await;
-
-            s.pin.clear_interrupt_pending_bit();
-        }
+        self.wait_for_edge(EdgeOption::Rising)
     }
 }
 
-impl<T: gpio::ExtiPin + WithInterrupt + 'static> WaitForFallingEdge for ExtiPin<T> {
+impl<T: Instance + 'static> WaitForFallingEdge for ExtiPin<T> {
     type Future<'a> = impl Future<Output = ()> + 'a;
 
     fn wait_for_falling_edge<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
-        let s = unsafe { self.get_unchecked_mut() };
+        self.wait_for_edge(EdgeOption::Falling)
+    }
+}
 
-        s.pin.clear_interrupt_pending_bit();
-        async move {
-            let fut = InterruptFuture::new(&mut s.interrupt);
-            let pin = &mut s.pin;
-            cortex_m::interrupt::free(|_| {
-                let mut exti: EXTI = unsafe { mem::transmute(()) };
+impl<T: Instance + 'static> WaitForAnyEdge for ExtiPin<T> {
+    type Future<'a> = impl Future<Output = ()> + 'a;
 
-                pin.trigger_on_edge(&mut exti, Edge::FALLING);
-                pin.enable_interrupt(&mut exti);
-            });
-            fut.await;
-
-            s.pin.clear_interrupt_pending_bit();
-        }
+    fn wait_for_any_edge<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
+        self.wait_for_edge(EdgeOption::RisingFalling)
     }
 }
 
@@ -138,10 +201,23 @@ mod private {
     pub trait Sealed {}
 }
 
+#[derive(Copy, Clone)]
+pub enum EdgeOption {
+    Rising,
+    Falling,
+    RisingFalling,
+}
+
 pub trait WithInterrupt: private::Sealed {
     type Interrupt: interrupt::Interrupt;
 }
 
+pub trait Instance: WithInterrupt {
+    fn make_source(&mut self, syscfg: &mut SysCfg);
+    fn clear_pending_bit(&mut self);
+    fn trigger_edge(&mut self, edge: EdgeOption);
+}
+
 macro_rules! exti {
     ($set:ident, [
         $($INT:ident => $pin:ident,)+
@@ -151,8 +227,88 @@ macro_rules! exti {
             impl<T> WithInterrupt for gpio::$set::$pin<T> {
                 type Interrupt = interrupt::$INT;
             }
-        )+
 
+            #[cfg(any(
+                feature = "stm32f401",
+                feature = "stm32f405",
+                feature = "stm32f407",
+                feature = "stm32f410",
+                feature = "stm32f411",
+                feature = "stm32f412",
+                feature = "stm32f413",
+                feature = "stm32f415",
+                feature = "stm32f417",
+                feature = "stm32f423",
+                feature = "stm32f427",
+                feature = "stm32f429",
+                feature = "stm32f437",
+                feature = "stm32f439",
+                feature = "stm32f446",
+                feature = "stm32f469",
+                feature = "stm32f479",
+            ))]
+            impl<T> Instance for gpio::$set::$pin<gpio::Input<T>> {
+                fn make_source(&mut self, syscfg: &mut SysCfg) {
+                    use crate::hal::gpio::ExtiPin;
+                    self.make_interrupt_source(syscfg);
+                }
+
+                fn clear_pending_bit(&mut self) {
+                    use crate::hal::{gpio::Edge, gpio::ExtiPin, syscfg::SysCfg};
+
+                    self.clear_interrupt_pending_bit();
+                }
+
+                fn trigger_edge(&mut self, edge: EdgeOption) {
+                    use crate::hal::{gpio::Edge, gpio::ExtiPin, syscfg::SysCfg};
+                    use crate::pac::EXTI;
+                    let mut exti: EXTI = unsafe { mem::transmute(()) };
+                    let edge = match edge {
+                        EdgeOption::Falling => Edge::FALLING,
+                        EdgeOption::Rising => Edge::RISING,
+                        EdgeOption::RisingFalling => Edge::RISING_FALLING,
+                    };
+                    self.trigger_on_edge(&mut exti, edge);
+                    self.enable_interrupt(&mut exti);
+                }
+            }
+
+            #[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+            impl<T> Instance for gpio::$set::$pin<T> {
+                fn make_source(&mut self, syscfg: &mut SysCfg) {}
+
+                fn clear_pending_bit(&mut self) {
+                    use crate::hal::{
+                        exti::{Exti, ExtiLine, GpioLine, TriggerEdge},
+                        syscfg::SYSCFG,
+                    };
+
+                    Exti::unpend(GpioLine::from_raw_line(self.pin_number()).unwrap());
+                }
+
+                fn trigger_edge(&mut self, edge: EdgeOption) {
+                    use crate::hal::{
+                        exti::{Exti, ExtiLine, GpioLine, TriggerEdge},
+                        syscfg::SYSCFG,
+                    };
+
+                    use crate::pac::EXTI;
+
+                    let edge = match edge {
+                        EdgeOption::Falling => TriggerEdge::Falling,
+                        EdgeOption::Rising => TriggerEdge::Rising,
+                        EdgeOption::RisingFalling => TriggerEdge::Both,
+                    };
+
+                    let exti: EXTI = unsafe { mem::transmute(()) };
+                    let mut exti = Exti::new(exti);
+                    let port = self.port();
+                    let mut syscfg: SYSCFG = unsafe { mem::transmute(()) };
+                    let line = GpioLine::from_raw_line(self.pin_number()).unwrap();
+                    exti.listen_gpio(&mut syscfg, port, line, edge);
+                }
+            }
+        )+
     };
 }
 
@@ -533,3 +689,111 @@ exti!(gpiok, [
     EXTI9_5 => PK6,
     EXTI9_5 => PK7,
 ]);
+
+#[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+exti!(gpioa, [
+    EXTI0_1 => PA0,
+    EXTI0_1 => PA1,
+    EXTI2_3 => PA2,
+    EXTI2_3 => PA3,
+    EXTI4_15 => PA4,
+    EXTI4_15 => PA5,
+    EXTI4_15 => PA6,
+    EXTI4_15 => PA7,
+    EXTI4_15 => PA8,
+    EXTI4_15 => PA9,
+    EXTI4_15 => PA10,
+    EXTI4_15 => PA11,
+    EXTI4_15 => PA12,
+    EXTI4_15 => PA13,
+    EXTI4_15 => PA14,
+    EXTI4_15 => PA15,
+]);
+
+#[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+exti!(gpiob, [
+    EXTI0_1 => PB0,
+    EXTI0_1 => PB1,
+    EXTI2_3 => PB2,
+    EXTI2_3 => PB3,
+    EXTI4_15 => PB4,
+    EXTI4_15 => PB5,
+    EXTI4_15 => PB6,
+    EXTI4_15 => PB7,
+    EXTI4_15 => PB8,
+    EXTI4_15 => PB9,
+    EXTI4_15 => PB10,
+    EXTI4_15 => PB11,
+    EXTI4_15 => PB12,
+    EXTI4_15 => PB13,
+    EXTI4_15 => PB14,
+    EXTI4_15 => PB15,
+]);
+
+#[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+exti!(gpioc, [
+    EXTI0_1 => PC0,
+    EXTI0_1 => PC1,
+    EXTI2_3 => PC2,
+    EXTI2_3 => PC3,
+    EXTI4_15 => PC4,
+    EXTI4_15 => PC5,
+    EXTI4_15 => PC6,
+    EXTI4_15 => PC7,
+    EXTI4_15 => PC8,
+    EXTI4_15 => PC9,
+    EXTI4_15 => PC10,
+    EXTI4_15 => PC11,
+    EXTI4_15 => PC12,
+    EXTI4_15 => PC13,
+    EXTI4_15 => PC14,
+    EXTI4_15 => PC15,
+]);
+
+#[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+exti!(gpiod, [
+    EXTI0_1 => PD0,
+    EXTI0_1 => PD1,
+    EXTI2_3 => PD2,
+    EXTI2_3 => PD3,
+    EXTI4_15 => PD4,
+    EXTI4_15 => PD5,
+    EXTI4_15 => PD6,
+    EXTI4_15 => PD7,
+    EXTI4_15 => PD8,
+    EXTI4_15 => PD9,
+    EXTI4_15 => PD10,
+    EXTI4_15 => PD11,
+    EXTI4_15 => PD12,
+    EXTI4_15 => PD13,
+    EXTI4_15 => PD14,
+    EXTI4_15 => PD15,
+]);
+
+#[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+exti!(gpioe, [
+    EXTI0_1 => PE0,
+    EXTI0_1 => PE1,
+    EXTI2_3 => PE2,
+    EXTI2_3 => PE3,
+    EXTI4_15 => PE4,
+    EXTI4_15 => PE5,
+    EXTI4_15 => PE6,
+    EXTI4_15 => PE7,
+    EXTI4_15 => PE8,
+    EXTI4_15 => PE9,
+    EXTI4_15 => PE10,
+    EXTI4_15 => PE11,
+    EXTI4_15 => PE12,
+    EXTI4_15 => PE13,
+    EXTI4_15 => PE14,
+    EXTI4_15 => PE15,
+]);
+
+#[cfg(any(feature = "stm32l0x1", feature = "stm32l0x2", feature = "stm32l0x3",))]
+exti!(gpioh, [
+    EXTI0_1 => PH0,
+    EXTI0_1 => PH1,
+    EXTI4_15 => PH9,
+    EXTI4_15 => PH10,
+]);
diff --git a/embassy-stm32/src/interrupt.rs b/embassy-stm32/src/interrupt.rs
index 5ad7ef8ef..262bc3d98 100644
--- a/embassy-stm32/src/interrupt.rs
+++ b/embassy-stm32/src/interrupt.rs
@@ -889,7 +889,7 @@ mod irqs {
     declare!(TIM8_CC);
     declare!(DMA1_STREAM7);
     declare!(FMC);
-    declare!(SDIO);
+    //    declare!(SDIO);
     declare!(TIM5);
     declare!(SPI3);
     declare!(UART4);
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index d15d4130e..d098d6995 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -32,12 +32,33 @@ pub use {stm32l0xx_hal as hal, stm32l0xx_hal::pac};
 
 pub mod fmt;
 
+pub mod exti;
 pub mod interrupt;
 
 #[cfg(any(
     feature = "stm32f401",
     feature = "stm32f405",
     feature = "stm32f407",
+    feature = "stm32f412",
+    feature = "stm32f413",
+    feature = "stm32f415",
+    feature = "stm32f417",
+    feature = "stm32f423",
+    feature = "stm32f427",
+    feature = "stm32f429",
+    feature = "stm32f437",
+    feature = "stm32f439",
+    feature = "stm32f446",
+    feature = "stm32f469",
+    feature = "stm32f479",
+))]
+pub mod can;
+
+#[cfg(any(
+    feature = "stm32f401",
+    feature = "stm32f405",
+    feature = "stm32f407",
+    feature = "stm32f410",
     feature = "stm32f411",
     feature = "stm32f412",
     feature = "stm32f413",
@@ -52,4 +73,6 @@ pub mod interrupt;
     feature = "stm32f469",
     feature = "stm32f479",
 ))]
+pub mod rtc;
+
 unsafe impl embassy_extras::usb::USBInterrupt for interrupt::OTG_FS {}
diff --git a/embassy-stm32f4/src/rtc.rs b/embassy-stm32/src/rtc.rs
similarity index 100%
rename from embassy-stm32f4/src/rtc.rs
rename to embassy-stm32/src/rtc.rs
diff --git a/embassy-stm32f4-examples/src/bin/exti.rs b/embassy-stm32f4-examples/src/bin/exti.rs
index 2201189eb..9b2535b1c 100644
--- a/embassy-stm32f4-examples/src/bin/exti.rs
+++ b/embassy-stm32f4-examples/src/bin/exti.rs
@@ -24,8 +24,9 @@ async fn run(dp: stm32::Peripherals, _cp: cortex_m::Peripherals) {
     let gpioa = dp.GPIOA.split();
 
     let button = gpioa.pa0.into_pull_up_input();
+    let mut syscfg = dp.SYSCFG.constrain();
 
-    let pin = ExtiPin::new(button, interrupt::take!(EXTI0));
+    let pin = ExtiPin::new(button, interrupt::take!(EXTI0), &mut syscfg);
     pin_mut!(pin);
 
     info!("Starting loop");
diff --git a/embassy-stm32f4/Cargo.toml b/embassy-stm32f4/Cargo.toml
index 5375261c2..8e9b14f03 100644
--- a/embassy-stm32f4/Cargo.toml
+++ b/embassy-stm32f4/Cargo.toml
@@ -37,6 +37,7 @@ defmt = { version = "0.2.0", optional = true }
 log = { version = "0.4.11", optional = true }
 cortex-m-rt = "0.6.13"
 cortex-m = "0.7.1"
+futures = { version = "0.3.5", default-features = false, features = ["async-await"] }
 embedded-hal    = { version = "0.2.4" }
 embedded-dma    = { version = "0.1.2" }
 stm32f4xx-hal  = { version = "0.8.3", features = ["rt", "can"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"}
diff --git a/embassy-stm32f4/src/lib.rs b/embassy-stm32f4/src/lib.rs
index 0d490525c..eb6f04f88 100644
--- a/embassy-stm32f4/src/lib.rs
+++ b/embassy-stm32f4/src/lib.rs
@@ -307,12 +307,11 @@ compile_error!(
     "Multile chip features activated. You must activate exactly one of the following features: "
 );
 
-pub use embassy_stm32::{fmt, hal, interrupt, pac};
+pub use embassy_stm32::{exti, fmt, hal, interrupt, pac, rtc};
 
 #[cfg(not(any(feature = "stm32f401", feature = "stm32f410", feature = "stm32f411",)))]
-pub mod can;
-pub mod exti;
+pub use embassy_stm32::can;
+
 #[cfg(not(feature = "stm32f410"))]
 pub mod qei;
-pub mod rtc;
 pub mod serial;
diff --git a/embassy-stm32f4/src/serial.rs b/embassy-stm32f4/src/serial.rs
index 87d0caaaf..c8dc0598a 100644
--- a/embassy-stm32f4/src/serial.rs
+++ b/embassy-stm32f4/src/serial.rs
@@ -7,8 +7,10 @@
 use core::future::Future;
 use core::marker::PhantomData;
 
+use futures::{select_biased, FutureExt};
+
 use embassy::interrupt::Interrupt;
-use embassy::traits::uart::{Error, Uart};
+use embassy::traits::uart::{Error, IdleUart, Uart};
 use embassy::util::InterruptFuture;
 
 use crate::hal::{
@@ -19,7 +21,7 @@ use crate::hal::{
     rcc::Clocks,
     serial,
     serial::config::{Config as SerialConfig, DmaConfig as SerialDmaConfig},
-    serial::{Event as SerialEvent, Pins, Serial as HalSerial},
+    serial::{Event as SerialEvent, Pins},
 };
 use crate::interrupt;
 use crate::pac;
@@ -29,14 +31,14 @@ pub struct Serial<
     USART: PeriAddress<MemSize = u8> + WithInterrupt,
     TSTREAM: Stream + WithInterrupt,
     RSTREAM: Stream + WithInterrupt,
-    CHANNEL: dma::traits::Channel,
+    CHANNEL: Channel,
 > {
     tx_stream: Option<TSTREAM>,
     rx_stream: Option<RSTREAM>,
     usart: Option<USART>,
     tx_int: TSTREAM::Interrupt,
     rx_int: RSTREAM::Interrupt,
-    _usart_int: USART::Interrupt,
+    usart_int: USART::Interrupt,
     channel: PhantomData<CHANNEL>,
 }
 
@@ -68,12 +70,10 @@ where
         PINS: Pins<USART>,
     {
         config.dma = SerialDmaConfig::TxRx;
-        let mut serial = HalSerial::new(usart, pins, config, clocks).unwrap();
 
-        serial.listen(SerialEvent::Idle);
-        //        serial.listen(SerialEvent::Txe);
-
-        let (usart, _) = serial.release();
+        let (usart, _) = serial::Serial::new(usart, pins, config, clocks)
+            .unwrap()
+            .release();
 
         let (tx_stream, rx_stream) = streams;
 
@@ -83,8 +83,8 @@ where
             usart: Some(usart),
             tx_int: tx_int,
             rx_int: rx_int,
-            _usart_int: usart_int,
-            channel: core::marker::PhantomData,
+            usart_int: usart_int,
+            channel: PhantomData,
         }
     }
 }
@@ -127,10 +127,10 @@ where
             let fut = InterruptFuture::new(&mut self.tx_int);
 
             tx_transfer.start(|_usart| {});
-
             fut.await;
 
             let (tx_stream, usart, _buf, _) = tx_transfer.free();
+
             self.tx_stream.replace(tx_stream);
             self.usart.replace(usart);
 
@@ -163,7 +163,6 @@ where
             );
 
             let fut = InterruptFuture::new(&mut self.rx_int);
-
             rx_transfer.start(|_usart| {});
             fut.await;
 
@@ -176,6 +175,79 @@ where
     }
 }
 
+impl<USART, TSTREAM, RSTREAM, CHANNEL> IdleUart for Serial<USART, TSTREAM, RSTREAM, CHANNEL>
+where
+    USART: serial::Instance
+        + PeriAddress<MemSize = u8>
+        + DMASet<TSTREAM, CHANNEL, MemoryToPeripheral>
+        + DMASet<RSTREAM, CHANNEL, PeripheralToMemory>
+        + WithInterrupt
+        + 'static,
+    TSTREAM: Stream + WithInterrupt + 'static,
+    RSTREAM: Stream + WithInterrupt + 'static,
+    CHANNEL: Channel + 'static,
+{
+    type ReceiveFuture<'a> = impl Future<Output = Result<usize, Error>> + 'a;
+
+    /// Receives serial data.
+    ///
+    /// The future is pending until either the buffer is completely full, or the RX line falls idle after receiving some data.
+    ///
+    /// Returns the number of bytes read.
+    fn receive_until_idle<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReceiveFuture<'a> {
+        let static_buf = unsafe { core::mem::transmute::<&'a mut [u8], &'static mut [u8]>(buf) };
+
+        let rx_stream = self.rx_stream.take().unwrap();
+        let usart = self.usart.take().unwrap();
+
+        async move {
+            unsafe {
+                /*  __HAL_UART_ENABLE_IT(&uart->UartHandle, UART_IT_IDLE); */
+                (*USART::ptr()).cr1.modify(|_, w| w.idleie().set_bit());
+
+                /* __HAL_UART_CLEAR_IDLEFLAG(&uart->UartHandle); */
+                (*USART::ptr()).sr.read();
+                (*USART::ptr()).dr.read();
+            };
+
+            let mut rx_transfer = Transfer::init(
+                rx_stream,
+                usart,
+                static_buf,
+                None,
+                DmaConfig::default()
+                    .transfer_complete_interrupt(true)
+                    .memory_increment(true)
+                    .double_buffer(false),
+            );
+
+            let total_bytes = RSTREAM::get_number_of_transfers() as usize;
+
+            let fut = InterruptFuture::new(&mut self.rx_int);
+            let fut_idle = InterruptFuture::new(&mut self.usart_int);
+
+            rx_transfer.start(|_usart| {});
+
+            select_biased! {
+                () = fut.fuse() => {},
+                () = fut_idle.fuse() => {},
+            }
+
+            let (rx_stream, usart, _, _) = rx_transfer.free();
+
+            let remaining_bytes = RSTREAM::get_number_of_transfers() as usize;
+
+            unsafe {
+                (*USART::ptr()).cr1.modify(|_, w| w.idleie().clear_bit());
+            }
+            self.rx_stream.replace(rx_stream);
+            self.usart.replace(usart);
+
+            Ok(total_bytes - remaining_bytes)
+        }
+    }
+}
+
 mod private {
     pub trait Sealed {}
 }
@@ -278,6 +350,6 @@ usart! {
 
     UART4 => (UART4),
     UART5 => (UART5),
-    UART7 => (UART7),
-    UART8 => (UART8),
+//    UART7 => (UART7),
+//    UART8 => (UART8),
 }
diff --git a/embassy-stm32l0/src/exti.rs b/embassy-stm32l0/src/exti.rs
deleted file mode 100644
index f50c3ae8e..000000000
--- a/embassy-stm32l0/src/exti.rs
+++ /dev/null
@@ -1,268 +0,0 @@
-use core::future::Future;
-use core::mem;
-use core::pin::Pin;
-
-use embassy::traits::gpio::{
-    WaitForAnyEdge, WaitForFallingEdge, WaitForHigh, WaitForLow, WaitForRisingEdge,
-};
-use embassy::util::InterruptFuture;
-
-use crate::hal::{
-    exti::{Exti, ExtiLine, GpioLine, TriggerEdge},
-    gpio,
-    syscfg::SYSCFG,
-};
-use crate::interrupt;
-use crate::pac::EXTI;
-use embedded_hal::digital::v2::InputPin;
-
-pub struct ExtiPin<T: PinWithInterrupt> {
-    pin: T,
-    interrupt: T::Interrupt,
-}
-
-impl<T: PinWithInterrupt + 'static> ExtiPin<T> {
-    pub fn new(pin: T, interrupt: T::Interrupt) -> ExtiPin<T> {
-        ExtiPin { pin, interrupt }
-    }
-
-    fn wait_for_edge<'a>(
-        self: Pin<&'a mut Self>,
-        edge: TriggerEdge,
-    ) -> impl Future<Output = ()> + 'a {
-        let line = self.pin.line();
-        let s = unsafe { self.get_unchecked_mut() };
-
-        Exti::unpend(line);
-
-        async move {
-            let exti: EXTI = unsafe { mem::transmute(()) };
-            let mut exti = Exti::new(exti);
-
-            let fut = InterruptFuture::new(&mut s.interrupt);
-
-            let port = s.pin.port();
-            cortex_m::interrupt::free(|_| {
-                let mut syscfg: SYSCFG = unsafe { mem::transmute(()) };
-                exti.listen_gpio(&mut syscfg, port, line, edge);
-            });
-
-            fut.await;
-
-            Exti::unpend(line);
-        }
-    }
-}
-
-impl<T: InputPin + PinWithInterrupt + 'static> ExtiPin<T> {
-    fn wait_for_state<'a>(self: Pin<&'a mut Self>, state: bool) -> impl Future<Output = ()> + 'a {
-        let line = self.pin.line();
-        let s = unsafe { self.get_unchecked_mut() };
-
-        Exti::unpend(line);
-
-        async move {
-            let exti: EXTI = unsafe { mem::transmute(()) };
-            let mut exti = Exti::new(exti);
-
-            let fut = InterruptFuture::new(&mut s.interrupt);
-
-            let port = s.pin.port();
-            cortex_m::interrupt::free(|_| {
-                let mut syscfg: SYSCFG = unsafe { mem::transmute(()) };
-                let edge = if state {
-                    TriggerEdge::Rising
-                } else {
-                    TriggerEdge::Falling
-                };
-                exti.listen_gpio(&mut syscfg, port, line, edge);
-            });
-
-            let pin_has_state = if state {
-                s.pin.is_high()
-            } else {
-                s.pin.is_low()
-            }
-            .unwrap_or(false);
-            if pin_has_state {
-                return ();
-            }
-
-            fut.await;
-
-            Exti::unpend(line);
-        }
-    }
-}
-
-impl<T: PinWithInterrupt + 'static> WaitForRisingEdge for ExtiPin<T> {
-    type Future<'a> = impl Future<Output = ()> + 'a;
-
-    fn wait_for_rising_edge<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
-        self.wait_for_edge(TriggerEdge::Rising)
-    }
-}
-
-impl<T: PinWithInterrupt + 'static> WaitForFallingEdge for ExtiPin<T> {
-    type Future<'a> = impl Future<Output = ()> + 'a;
-
-    fn wait_for_falling_edge<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
-        self.wait_for_edge(TriggerEdge::Falling)
-    }
-}
-
-impl<T: PinWithInterrupt + 'static> WaitForAnyEdge for ExtiPin<T> {
-    type Future<'a> = impl Future<Output = ()> + 'a;
-
-    fn wait_for_any_edge<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
-        self.wait_for_edge(TriggerEdge::Both)
-    }
-}
-
-impl<T: InputPin + PinWithInterrupt + 'static> WaitForHigh for ExtiPin<T> {
-    type Future<'a> = impl Future<Output = ()> + 'a;
-
-    fn wait_for_high<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
-        self.wait_for_state(true)
-    }
-}
-
-impl<T: InputPin + PinWithInterrupt + 'static> WaitForLow for ExtiPin<T> {
-    type Future<'a> = impl Future<Output = ()> + 'a;
-
-    fn wait_for_low<'a>(self: Pin<&'a mut Self>) -> Self::Future<'a> {
-        self.wait_for_state(false)
-    }
-}
-
-mod private {
-    pub trait Sealed {}
-}
-
-pub trait PinWithInterrupt: private::Sealed {
-    type Interrupt: interrupt::Interrupt;
-    fn port(&self) -> gpio::Port;
-    fn line(&self) -> GpioLine;
-}
-
-macro_rules! exti {
-    ($set:ident, [
-        $($INT:ident => $pin:ident,)+
-    ]) => {
-        $(
-            impl<T> private::Sealed for gpio::$set::$pin<T> {}
-            impl<T> PinWithInterrupt for gpio::$set::$pin<T> {
-                type Interrupt = interrupt::$INT;
-                fn port(&self) -> gpio::Port {
-                    self.port()
-                }
-                fn line(&self) -> GpioLine {
-                    GpioLine::from_raw_line(self.pin_number()).unwrap()
-                }
-            }
-        )+
-
-    };
-}
-
-exti!(gpioa, [
-    EXTI0_1 => PA0,
-    EXTI0_1 => PA1,
-    EXTI2_3 => PA2,
-    EXTI2_3 => PA3,
-    EXTI4_15 => PA4,
-    EXTI4_15 => PA5,
-    EXTI4_15 => PA6,
-    EXTI4_15 => PA7,
-    EXTI4_15 => PA8,
-    EXTI4_15 => PA9,
-    EXTI4_15 => PA10,
-    EXTI4_15 => PA11,
-    EXTI4_15 => PA12,
-    EXTI4_15 => PA13,
-    EXTI4_15 => PA14,
-    EXTI4_15 => PA15,
-]);
-
-exti!(gpiob, [
-    EXTI0_1 => PB0,
-    EXTI0_1 => PB1,
-    EXTI2_3 => PB2,
-    EXTI2_3 => PB3,
-    EXTI4_15 => PB4,
-    EXTI4_15 => PB5,
-    EXTI4_15 => PB6,
-    EXTI4_15 => PB7,
-    EXTI4_15 => PB8,
-    EXTI4_15 => PB9,
-    EXTI4_15 => PB10,
-    EXTI4_15 => PB11,
-    EXTI4_15 => PB12,
-    EXTI4_15 => PB13,
-    EXTI4_15 => PB14,
-    EXTI4_15 => PB15,
-]);
-
-exti!(gpioc, [
-    EXTI0_1 => PC0,
-    EXTI0_1 => PC1,
-    EXTI2_3 => PC2,
-    EXTI2_3 => PC3,
-    EXTI4_15 => PC4,
-    EXTI4_15 => PC5,
-    EXTI4_15 => PC6,
-    EXTI4_15 => PC7,
-    EXTI4_15 => PC8,
-    EXTI4_15 => PC9,
-    EXTI4_15 => PC10,
-    EXTI4_15 => PC11,
-    EXTI4_15 => PC12,
-    EXTI4_15 => PC13,
-    EXTI4_15 => PC14,
-    EXTI4_15 => PC15,
-]);
-
-exti!(gpiod, [
-    EXTI0_1 => PD0,
-    EXTI0_1 => PD1,
-    EXTI2_3 => PD2,
-    EXTI2_3 => PD3,
-    EXTI4_15 => PD4,
-    EXTI4_15 => PD5,
-    EXTI4_15 => PD6,
-    EXTI4_15 => PD7,
-    EXTI4_15 => PD8,
-    EXTI4_15 => PD9,
-    EXTI4_15 => PD10,
-    EXTI4_15 => PD11,
-    EXTI4_15 => PD12,
-    EXTI4_15 => PD13,
-    EXTI4_15 => PD14,
-    EXTI4_15 => PD15,
-]);
-
-exti!(gpioe, [
-    EXTI0_1 => PE0,
-    EXTI0_1 => PE1,
-    EXTI2_3 => PE2,
-    EXTI2_3 => PE3,
-    EXTI4_15 => PE4,
-    EXTI4_15 => PE5,
-    EXTI4_15 => PE6,
-    EXTI4_15 => PE7,
-    EXTI4_15 => PE8,
-    EXTI4_15 => PE9,
-    EXTI4_15 => PE10,
-    EXTI4_15 => PE11,
-    EXTI4_15 => PE12,
-    EXTI4_15 => PE13,
-    EXTI4_15 => PE14,
-    EXTI4_15 => PE15,
-]);
-
-exti!(gpioh, [
-    EXTI0_1 => PH0,
-    EXTI0_1 => PH1,
-    EXTI4_15 => PH9,
-    EXTI4_15 => PH10,
-]);
diff --git a/embassy-stm32l0/src/lib.rs b/embassy-stm32l0/src/lib.rs
index e80fe6cb4..a684c4e3e 100644
--- a/embassy-stm32l0/src/lib.rs
+++ b/embassy-stm32l0/src/lib.rs
@@ -19,6 +19,4 @@ compile_error!(
     "Multile chip features activated. You must activate exactly one of the following features: "
 );
 
-pub use embassy_stm32::{fmt, hal, interrupt, pac};
-
-pub mod exti;
+pub use embassy_stm32::{exti, fmt, hal, interrupt, pac};
diff --git a/embassy-traits/src/delay.rs b/embassy-traits/src/delay.rs
index b893ee4a3..1e763350b 100644
--- a/embassy-traits/src/delay.rs
+++ b/embassy-traits/src/delay.rs
@@ -4,6 +4,9 @@ use core::pin::Pin;
 pub trait Delay {
     type DelayFuture<'a>: Future<Output = ()> + 'a;
 
+    /// Future that completes after now + millis
     fn delay_ms<'a>(self: Pin<&'a mut Self>, millis: u64) -> Self::DelayFuture<'a>;
+
+    /// Future that completes after now + micros
     fn delay_us<'a>(self: Pin<&'a mut Self>, micros: u64) -> Self::DelayFuture<'a>;
 }
diff --git a/embassy-traits/src/uart.rs b/embassy-traits/src/uart.rs
index b40b9e9bd..441747181 100644
--- a/embassy-traits/src/uart.rs
+++ b/embassy-traits/src/uart.rs
@@ -10,6 +10,15 @@ pub enum Error {
 pub trait Uart {
     type ReceiveFuture<'a>: Future<Output = Result<(), Error>>;
     type SendFuture<'a>: Future<Output = Result<(), Error>>;
+    /// Receive into the buffer until the buffer is full
     fn receive<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReceiveFuture<'a>;
+    /// Send the specified buffer, and return when the transmission has completed
     fn send<'a>(&'a mut self, buf: &'a [u8]) -> Self::SendFuture<'a>;
 }
+
+pub trait IdleUart {
+    type ReceiveFuture<'a>: Future<Output = Result<usize, Error>>;
+    /// Receive into the buffer until the buffer is full or the line is idle after some bytes are received
+    /// Return the number of bytes received
+    fn receive_until_idle<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReceiveFuture<'a>;
+}
diff --git a/embassy/src/executor/timer.rs b/embassy/src/executor/timer.rs
index 256ecf665..541f96f59 100644
--- a/embassy/src/executor/timer.rs
+++ b/embassy/src/executor/timer.rs
@@ -7,6 +7,7 @@ use futures::Stream;
 use super::raw;
 use crate::time::{Duration, Instant};
 
+/// Delay abstraction using embassy's clock.
 pub struct Delay {
     _data: PhantomData<bool>,
 }
@@ -30,12 +31,14 @@ impl crate::traits::delay::Delay for Delay {
     }
 }
 
+/// A future that completes at a specified [Instant](struct.Instant.html).
 pub struct Timer {
     expires_at: Instant,
     yielded_once: bool,
 }
 
 impl Timer {
+    /// Expire at specified [Instant](struct.Instant.html)
     pub fn at(expires_at: Instant) -> Self {
         Self {
             expires_at,
@@ -43,6 +46,26 @@ impl Timer {
         }
     }
 
+    /// Expire after specified [Duration](struct.Duration.html).
+    /// This can be used as a `sleep` abstraction.
+    ///
+    /// Example:
+    /// ``` no_run
+    /// # #![feature(trait_alias)]
+    /// # #![feature(min_type_alias_impl_trait)]
+    /// # #![feature(impl_trait_in_bindings)]
+    /// # #![feature(type_alias_impl_trait)]
+    /// #
+    /// # fn foo() {}
+    /// use embassy::executor::task;
+    /// use embassy::time::{Duration, Timer};
+    ///
+    /// #[task]
+    /// async fn demo_sleep_seconds() {
+    ///     // suspend this task for one second.
+    ///     Timer::after(Duration::from_secs(1)).await;
+    /// }
+    /// ```
     pub fn after(duration: Duration) -> Self {
         Self {
             expires_at: Instant::now() + duration,
@@ -66,12 +89,62 @@ impl Future for Timer {
     }
 }
 
+/// Asynchronous stream that yields every Duration, indefinitely.
+///
+/// This stream will tick at uniform intervals, even if blocking work is performed between ticks.
+///
+/// For instance, consider the following code fragment.
+/// ``` no_run
+/// # #![feature(trait_alias)]
+/// # #![feature(min_type_alias_impl_trait)]
+/// # #![feature(impl_trait_in_bindings)]
+/// # #![feature(type_alias_impl_trait)]
+/// #
+/// use embassy::executor::task;
+/// use embassy::time::{Duration, Timer};
+/// # fn foo() {}
+///
+/// #[task]
+/// async fn ticker_example_0() {
+///     loop {
+///         foo();
+///         Timer::after(Duration::from_secs(1)).await;
+///     }
+/// }
+/// ```
+///
+/// This fragment will not call `foo` every second.
+/// Instead, it will call it every second + the time it took to previously call `foo`.
+///
+/// Example using ticker, which will consistently call `foo` once a second.
+///
+/// ``` no_run
+/// # #![feature(trait_alias)]
+/// # #![feature(min_type_alias_impl_trait)]
+/// # #![feature(impl_trait_in_bindings)]
+/// # #![feature(type_alias_impl_trait)]
+/// #
+/// use embassy::executor::task;
+/// use embassy::time::{Duration, Ticker};
+/// use futures::StreamExt;
+/// # fn foo(){}
+///
+/// #[task]
+/// async fn ticker_example_1() {
+///     let mut ticker = Ticker::every(Duration::from_secs(1));
+///     loop {
+///         foo();
+///         ticker.next().await;
+///     }
+/// }
+/// ```
 pub struct Ticker {
     expires_at: Instant,
     duration: Duration,
 }
 
 impl Ticker {
+    /// Creates a new ticker that ticks at the specified duration interval.
     pub fn every(duration: Duration) -> Self {
         let expires_at = Instant::now() + duration;
         Self {
diff --git a/embassy/src/time/duration.rs b/embassy/src/time/duration.rs
index e04afa184..5157450ad 100644
--- a/embassy/src/time/duration.rs
+++ b/embassy/src/time/duration.rs
@@ -5,6 +5,7 @@ use super::TICKS_PER_SECOND;
 
 #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+/// Represents the difference between two [Instant](struct.Instant.html)s
 pub struct Duration {
     pub(crate) ticks: u64,
 }
@@ -26,49 +27,55 @@ impl Duration {
         self.ticks * 1_000_000 / TICKS_PER_SECOND
     }
 
+    /// Creates a duration from the specified number of clock ticks
     pub const fn from_ticks(ticks: u64) -> Duration {
         Duration { ticks }
     }
 
+    /// Creates a duration from the specified number of seconds
     pub const fn from_secs(secs: u64) -> Duration {
         Duration {
             ticks: secs * TICKS_PER_SECOND,
         }
     }
 
+    /// Creates a duration from the specified number of milliseconds
     pub const fn from_millis(millis: u64) -> Duration {
         Duration {
             ticks: millis * TICKS_PER_SECOND / 1000,
         }
     }
 
-    /*
-        NOTE: us delays may not be as accurate
-    */
+    /// Creates a duration from the specified number of microseconds
+    /// NOTE: Delays this small may be inaccurate.
     pub const fn from_micros(micros: u64) -> Duration {
         Duration {
             ticks: micros * TICKS_PER_SECOND / 1_000_000,
         }
     }
 
+    /// Adds one Duration to another, returning a new Duration or None in the event of an overflow.
     pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
         self.ticks
             .checked_add(rhs.ticks)
             .map(|ticks| Duration { ticks })
     }
 
+    /// Subtracts one Duration to another, returning a new Duration or None in the event of an overflow.
     pub fn checked_sub(self, rhs: Duration) -> Option<Duration> {
         self.ticks
             .checked_sub(rhs.ticks)
             .map(|ticks| Duration { ticks })
     }
 
+    /// Multiplies one Duration by a scalar u32, returning a new Duration or None in the event of an overflow.
     pub fn checked_mul(self, rhs: u32) -> Option<Duration> {
         self.ticks
             .checked_mul(rhs as _)
             .map(|ticks| Duration { ticks })
     }
 
+    /// Divides one Duration a scalar u32, returning a new Duration or None in the event of an overflow.
     pub fn checked_div(self, rhs: u32) -> Option<Duration> {
         self.ticks
             .checked_div(rhs as _)
diff --git a/embassy/src/time/instant.rs b/embassy/src/time/instant.rs
index 06ab84c75..61a61defe 100644
--- a/embassy/src/time/instant.rs
+++ b/embassy/src/time/instant.rs
@@ -6,6 +6,7 @@ use super::{now, Duration};
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+/// An Instant in time, based on the MCU's clock ticks since startup.
 pub struct Instant {
     ticks: u64,
 }
@@ -14,44 +15,55 @@ impl Instant {
     pub const MIN: Instant = Instant { ticks: u64::MIN };
     pub const MAX: Instant = Instant { ticks: u64::MAX };
 
+    /// Returns an Instant representing the current time.
     pub fn now() -> Instant {
         Instant { ticks: now() }
     }
 
+    /// Instant as clock ticks since MCU start.
     pub const fn from_ticks(ticks: u64) -> Self {
         Self { ticks }
     }
 
+    /// Instant as milliseconds since MCU start.
     pub const fn from_millis(millis: u64) -> Self {
         Self {
             ticks: millis * TICKS_PER_SECOND as u64 / 1000,
         }
     }
 
+    /// Instant representing seconds since MCU start.
     pub const fn from_secs(seconds: u64) -> Self {
         Self {
             ticks: seconds * TICKS_PER_SECOND as u64,
         }
     }
 
+    /// Instant as ticks since MCU start.
+
     pub const fn as_ticks(&self) -> u64 {
         self.ticks
     }
+    /// Instant as seconds since MCU start.
 
     pub const fn as_secs(&self) -> u64 {
         self.ticks / TICKS_PER_SECOND as u64
     }
+    /// Instant as miliseconds since MCU start.
 
     pub const fn as_millis(&self) -> u64 {
         self.ticks * 1000 / TICKS_PER_SECOND as u64
     }
 
+    /// Duration between this Instant and another Instant
+    /// Panics on over/underflow.
     pub fn duration_since(&self, earlier: Instant) -> Duration {
         Duration {
             ticks: self.ticks.checked_sub(earlier.ticks).unwrap(),
         }
     }
 
+    /// Duration between this Instant and another Instant
     pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
         if self.ticks < earlier.ticks {
             None
@@ -62,6 +74,8 @@ impl Instant {
         }
     }
 
+    /// Returns the duration since the "earlier" Instant.
+    /// If the "earlier" instant is in the future, the duration is set to zero.
     pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
         Duration {
             ticks: if self.ticks < earlier.ticks {
@@ -72,6 +86,7 @@ impl Instant {
         }
     }
 
+    /// Duration elapsed since this Instant.
     pub fn elapsed(&self) -> Duration {
         Instant::now() - *self
     }
diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs
index b45a608dd..4e9b5f592 100644
--- a/embassy/src/time/mod.rs
+++ b/embassy/src/time/mod.rs
@@ -1,3 +1,6 @@
+//! Time abstractions
+//! To use these abstractions, first call `set_clock` with an instance of an [Clock](trait.Clock.html).
+//!
 mod duration;
 mod instant;
 mod traits;
@@ -14,10 +17,16 @@ pub const TICKS_PER_SECOND: u64 = 32768;
 
 static mut CLOCK: Option<&'static dyn Clock> = None;
 
+/// Sets the clock used for the timing abstractions
+///
+/// Safety: Sets a mutable global.
 pub unsafe fn set_clock(clock: &'static dyn Clock) {
     CLOCK = Some(clock);
 }
 
+/// Return the current timestamp in ticks.
+/// This is guaranteed to be monotonic, i.e. a call to now() will always return
+/// a greater or equal value than earler calls.
 pub(crate) fn now() -> u64 {
     unsafe { unwrap!(CLOCK, "No clock set").now() }
 }
diff --git a/embassy/src/util/drop_bomb.rs b/embassy/src/util/drop_bomb.rs
index b2b0684e5..388209a23 100644
--- a/embassy/src/util/drop_bomb.rs
+++ b/embassy/src/util/drop_bomb.rs
@@ -1,6 +1,12 @@
 use crate::fmt::panic;
 use core::mem;
 
+/// An explosive ordinance that panics if it is improperly disposed of.
+///
+/// This is to forbid dropping futures, when there is absolutely no other choice.
+///
+/// To correctly dispose of this device, call the [defuse](struct.DropBomb.html#method.defuse)
+/// method before this object is dropped.
 pub struct DropBomb {
     _private: (),
 }
@@ -10,6 +16,7 @@ impl DropBomb {
         Self { _private: () }
     }
 
+    /// Diffuses the bomb, rendering it safe to drop.
     pub fn defuse(self) {
         mem::forget(self)
     }
diff --git a/embassy/src/util/forever.rs b/embassy/src/util/forever.rs
index ac23a3ce3..efa96f30e 100644
--- a/embassy/src/util/forever.rs
+++ b/embassy/src/util/forever.rs
@@ -3,6 +3,25 @@ use core::mem::MaybeUninit;
 
 use atomic_polyfill::{AtomicBool, Ordering};
 
+/// Type with static lifetime that may be written to once at runtime.
+///
+/// This may be used to initialize static objects at runtime, typically in the init routine.
+/// This is useful for objects such as Embassy's RTC, which cannot be initialized in a const
+/// context.
+///
+/// Note: IF a global mutable variable is desired, use a CriticalSectionMutex or ThreadModeMutex instead.
+///
+/// ```
+/// use embassy::util::Forever;
+/// // Using an integer for the sake of keeping this example self-contained,
+/// // see https://github.com/embassy-rs/embassy/wiki/Getting-Started for a more "proper" example.
+/// static SOME_INT: Forever<u32> =Forever::new();
+///
+/// // put returns a mutable pointer to the object stored in the forever, which may then be passed
+/// // around.
+/// let mut x = SOME_INT.put(42);
+/// assert_eq!(*x, 42);
+/// ```
 pub struct Forever<T> {
     used: AtomicBool,
     t: UnsafeCell<MaybeUninit<T>>,
@@ -19,6 +38,11 @@ impl<T> Forever<T> {
         }
     }
 
+    /// Gives this `Forever` a value.
+    ///
+    /// Panics if this `Forever` already has a value.
+    ///
+    /// Returns a mutable reference to the stored value.
     pub fn put(&'static self, val: T) -> &'static mut T {
         if self
             .used
diff --git a/embassy/src/util/mod.rs b/embassy/src/util/mod.rs
index e64e7f1f1..6917e9993 100644
--- a/embassy/src/util/mod.rs
+++ b/embassy/src/util/mod.rs
@@ -1,3 +1,4 @@
+//! Async utilities
 mod drop_bomb;
 mod forever;
 mod mutex;
diff --git a/embassy/src/util/signal.rs b/embassy/src/util/signal.rs
index 41e27d4ca..0fd5c9275 100644
--- a/embassy/src/util/signal.rs
+++ b/embassy/src/util/signal.rs
@@ -10,6 +10,9 @@ use crate::executor;
 use crate::fmt::panic;
 use crate::interrupt::{Interrupt, InterruptExt};
 
+/// Synchronization primitive. Allows creating awaitable signals that may be passed between tasks.
+///
+/// For more advanced use cases, please consider [futures-intrusive](https://crates.io/crates/futures-intrusive) channels or mutexes.
 pub struct Signal<T> {
     state: UnsafeCell<State<T>>,
 }
@@ -30,6 +33,7 @@ impl<T: Send> Signal<T> {
         }
     }
 
+    /// Mark this Signal as completed.
     pub fn signal(&self, val: T) {
         cortex_m::interrupt::free(|_| unsafe {
             let state = &mut *self.state.get();
@@ -64,10 +68,12 @@ impl<T: Send> Signal<T> {
         })
     }
 
+    /// Future that completes when this Signal has been signaled.
     pub fn wait(&self) -> impl Future<Output = T> + '_ {
         futures::future::poll_fn(move |cx| self.poll_wait(cx))
     }
 
+    /// non-blocking method to check whether this signal has been signaled.
     pub fn signaled(&self) -> bool {
         cortex_m::interrupt::free(|_| matches!(unsafe { &*self.state.get() }, State::Signaled(_)))
     }
@@ -80,6 +86,25 @@ unsafe impl cortex_m::interrupt::Nr for NrWrap {
     }
 }
 
+/// Creates a future that completes when the specified Interrupt is triggered.
+///
+/// The input handler is unregistered when this Future is dropped.
+///
+/// Example:
+/// ``` no_compile
+/// use embassy::traits::*;
+/// use embassy::util::InterruptFuture;
+/// use embassy::executor::task;
+/// use embassy_stm32f4::interrupt; // Adjust this to your MCU's embassy HAL.
+/// #[task]
+/// async fn demo_interrupt_future() {
+///     // Using STM32f446 interrupt names, adjust this to your application as necessary.
+///     // Wait for TIM2 to tick.
+///     let mut tim2_interrupt = interrupt::take!(TIM2);
+///     InterruptFuture::new(&mut tim2_interrupt).await;
+///     // TIM2 interrupt went off, do something...
+/// }
+/// ```
 pub struct InterruptFuture<'a, I: Interrupt> {
     interrupt: &'a mut I,
 }