diff --git a/Cargo.toml b/Cargo.toml
index 3f05cd245a76a3775517771851218e64a122b3e5..a3beb7788358341d2f20dbb122474c61a8cd7ee1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,9 +9,19 @@ name = "f3"
 repository = "https://github.com/japaric/f3"
 version = "0.5.0"
 
+[dependencies]
+static-ref = "0.2.0"
+
 [dependencies.cast]
 default-features = false
-version = "0.2.0"
+version = "0.2.2"
+
+[dependencies.embedded-hal]
+git = "https://github.com/japaric/embedded-hal"
+rev = "7d904f515d15fd5fe7ea34e18820ea83e2651fa2"
+
+[dependencies.nb]
+git = "https://github.com/japaric/nb"
 
 [dependencies.stm32f30x]
 features = ["rt"]
diff --git a/examples/loopback.rs b/examples/loopback.rs
index a9417b0efdb3066ee7e596165a993cf6c2fe4d79..bb345e03d26b4e505090d7fcb082431c87f13970 100644
--- a/examples/loopback.rs
+++ b/examples/loopback.rs
@@ -7,11 +7,14 @@
 extern crate cortex_m_rtfm as rtfm;
 extern crate f3;
 
-use f3::serial::Serial;
+use f3::prelude::*;
+use f3::Serial;
+use f3::serial::Event;
+use f3::time::Hertz;
 use rtfm::{app, Threshold};
 
 // CONFIGURATION
-const BAUD_RATE: u32 = 115_200; // bits per second
+const BAUD_RATE: Hertz = Hertz(115_200);
 
 // TASKS & RESOURCES
 app! {
@@ -27,9 +30,10 @@ app! {
 
 // INITIALIZATION PHASE
 fn init(p: init::Peripherals) {
-    let serial = Serial(&p.USART1);
+    let serial = Serial(p.USART1);
 
-    serial.init(&p.GPIOA, &p.RCC, BAUD_RATE);
+    serial.init(BAUD_RATE.invert(), Some(p.DMA1), p.GPIOA, p.RCC);
+    serial.listen(Event::Rxne);
 }
 
 // IDLE LOOP
@@ -43,7 +47,7 @@ fn idle() -> ! {
 // TASKS
 // Send back the received byte
 fn loopback(_t: &mut Threshold, r: USART1_EXTI25::Resources) {
-    let serial = Serial(&r.USART1);
+    let serial = Serial(&**r.USART1);
 
     let byte = serial.read().unwrap();
     serial.write(byte).unwrap();
diff --git a/src/dma.rs b/src/dma.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a207f03c00b5b919ea1eed5602058cb5173084a8
--- /dev/null
+++ b/src/dma.rs
@@ -0,0 +1,259 @@
+//! Direct Memory Access (DMA)
+
+use core::cell::{Cell, UnsafeCell};
+use core::marker::PhantomData;
+use core::ops;
+
+use nb;
+use stm32f30x::DMA1;
+
+/// DMA error
+#[derive(Debug)]
+pub enum Error {
+    /// DMA channel in use
+    InUse,
+    /// Previous data got overwritten before it could be read because it was
+    /// not accessed in a timely fashion
+    Overrun,
+    /// Transfer error
+    Transfer,
+}
+
+/// Channel 2 of DMA1
+pub struct Dma1Channel2 {
+    _0: (),
+}
+
+/// Channel 4 of DMA1
+pub struct Dma1Channel4 {
+    _0: (),
+}
+
+/// Channel 5 of DMA1
+pub struct Dma1Channel5 {
+    _0: (),
+}
+
+/// Buffer to be used with a certain DMA `CHANNEL`
+// NOTE(packed) workaround for rust-lang/rust#41315
+#[repr(packed)]
+pub struct Buffer<T, CHANNEL> {
+    data: UnsafeCell<T>,
+    flag: Cell<BorrowFlag>,
+    state: Cell<State>,
+    _marker: PhantomData<CHANNEL>,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum State {
+    // A new `Buffer` starts in this state. We set it to zero to place this
+    // buffer in the .bss section
+    Unlocked = 0,
+
+    Locked,
+    MutLocked,
+}
+
+type BorrowFlag = usize;
+
+const UNUSED: BorrowFlag = 0;
+const WRITING: BorrowFlag = !0;
+
+/// Wraps a borrowed reference to a value in a `Buffer`
+pub struct Ref<'a, T>
+where
+    T: 'a,
+{
+    data: &'a T,
+    flag: &'a Cell<BorrowFlag>,
+}
+
+impl<'a, T> ops::Deref for Ref<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        self.data
+    }
+}
+
+impl<'a, T> Drop for Ref<'a, T> {
+    fn drop(&mut self) {
+        self.flag.set(self.flag.get() - 1);
+    }
+}
+
+/// A wrapper type for a mutably borrowed value from a `Buffer``
+pub struct RefMut<'a, T>
+where
+    T: 'a,
+{
+    data: &'a mut T,
+    flag: &'a Cell<BorrowFlag>,
+}
+
+impl<'a, T> ops::Deref for RefMut<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        self.data
+    }
+}
+
+impl<'a, T> ops::DerefMut for RefMut<'a, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        self.data
+    }
+}
+
+impl<'a, T> Drop for RefMut<'a, T> {
+    fn drop(&mut self) {
+        self.flag.set(UNUSED);
+    }
+}
+
+impl<T, CHANNEL> Buffer<T, CHANNEL> {
+    /// Creates a new buffer
+    pub const fn new(data: T) -> Self {
+        Buffer {
+            _marker: PhantomData,
+            data: UnsafeCell::new(data),
+            flag: Cell::new(0),
+            state: Cell::new(State::Unlocked),
+        }
+    }
+
+    /// Immutably borrows the wrapped value.
+    ///
+    /// The borrow lasts until the returned `Ref` exits scope. Multiple
+    /// immutable borrows can be taken out at the same time.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the value is currently mutably borrowed.
+    pub fn borrow(&self) -> Ref<T> {
+        assert_ne!(self.flag.get(), WRITING);
+
+        self.flag.set(self.flag.get() + 1);
+
+        Ref {
+            data: unsafe { &*self.data.get() },
+            flag: &self.flag,
+        }
+    }
+
+    /// Mutably borrows the wrapped value.
+    ///
+    /// The borrow lasts until the returned `RefMut` exits scope. The value
+    /// cannot be borrowed while this borrow is active.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the value is currently borrowed.
+    pub fn borrow_mut(&self) -> RefMut<T> {
+        assert_eq!(self.flag.get(), UNUSED);
+
+        self.flag.set(WRITING);
+
+        RefMut {
+            data: unsafe { &mut *self.data.get() },
+            flag: &self.flag,
+        }
+    }
+
+    pub(crate) fn lock(&self) -> &T {
+        assert_eq!(self.state.get(), State::Unlocked);
+        assert_ne!(self.flag.get(), WRITING);
+
+        self.flag.set(self.flag.get() + 1);
+        self.state.set(State::Locked);
+
+        unsafe { &*self.data.get() }
+    }
+
+    pub(crate) fn lock_mut(&self) -> &mut T {
+        assert_eq!(self.state.get(), State::Unlocked);
+        assert_eq!(self.flag.get(), UNUSED);
+
+        self.flag.set(WRITING);
+        self.state.set(State::MutLocked);
+
+        unsafe { &mut *self.data.get() }
+    }
+
+    unsafe fn unlock(&self, state: State) {
+        match state {
+            State::Locked => self.flag.set(self.flag.get() - 1),
+            State::MutLocked => self.flag.set(UNUSED),
+            _ => { /* unreachable!() */ }
+        }
+
+        self.state.set(State::Unlocked);
+    }
+}
+
+// FIXME these `release` methods probably want some of sort of barrier
+impl<T> Buffer<T, Dma1Channel2> {
+    /// Waits until the DMA releases this buffer
+    pub fn release(&self, dma1: &DMA1) -> nb::Result<(), Error> {
+        let state = self.state.get();
+
+        if state == State::Unlocked {
+            return Ok(());
+        }
+
+        if dma1.isr.read().teif2().bit_is_set() {
+            Err(nb::Error::Other(Error::Transfer))
+        } else if dma1.isr.read().tcif2().bit_is_set() {
+            unsafe { self.unlock(state) }
+            dma1.ifcr.write(|w| w.ctcif2().set_bit());
+            dma1.ccr2.modify(|_, w| w.en().clear_bit());
+            Ok(())
+        } else {
+            Err(nb::Error::WouldBlock)
+        }
+    }
+}
+
+impl<T> Buffer<T, Dma1Channel4> {
+    /// Waits until the DMA releases this buffer
+    pub fn release(&self, dma1: &DMA1) -> nb::Result<(), Error> {
+        let state = self.state.get();
+
+        if state == State::Unlocked {
+            return Ok(());
+        }
+
+        if dma1.isr.read().teif4().bit_is_set() {
+            Err(nb::Error::Other(Error::Transfer))
+        } else if dma1.isr.read().tcif4().bit_is_set() {
+            unsafe { self.unlock(state) }
+            dma1.ifcr.write(|w| w.ctcif4().set_bit());
+            dma1.ccr4.modify(|_, w| w.en().clear_bit());
+            Ok(())
+        } else {
+            Err(nb::Error::WouldBlock)
+        }
+    }
+}
+
+impl<T> Buffer<T, Dma1Channel5> {
+    /// Waits until the DMA releases this buffer
+    pub fn release(&self, dma1: &DMA1) -> nb::Result<(), Error> {
+        let state = self.state.get();
+
+        if state == State::Unlocked {
+            return Ok(());
+        }
+
+        if dma1.isr.read().teif5().bit_is_set() {
+            Err(nb::Error::Other(Error::Transfer))
+        } else if dma1.isr.read().tcif5().bit_is_set() {
+            unsafe { self.unlock(state) }
+            dma1.ifcr.write(|w| w.ctcif5().set_bit());
+            dma1.ccr5.modify(|_, w| w.en().clear_bit());
+            Ok(())
+        } else {
+            Err(nb::Error::WouldBlock)
+        }
+    }
+}
diff --git a/src/frequency.rs b/src/frequency.rs
index ebb4f9b240e9e2b0aa3816dc8b981746e4bf544b..ce5eb546e826c89aafce968d20b0a56649d7eb42 100644
--- a/src/frequency.rs
+++ b/src/frequency.rs
@@ -1,3 +1,86 @@
-// pub const AHB: u32 = 8_000_000;
-pub const APB1: u32 = 8_000_000;
-pub const APB2: u32 = 8_000_000;
+//! Definition of bus frequency details for f3.
+
+macro_rules! frequency {
+    ($FREQUENCY:expr) => {
+        use time::*;
+
+        /// Frequency
+        pub const FREQUENCY: u32 = $FREQUENCY;
+
+        /// Unit of time
+        #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+        pub struct Ticks(pub u32);
+
+        impl Ticks {
+            /// Applies the function `f` to the inner value
+            pub fn map<F>(self, f: F) -> Self
+                where F: FnOnce(u32) -> u32,
+            {
+                Ticks(f(self.0))
+            }
+        }
+
+        impl From<Ticks> for Microseconds {
+            fn from(ticks: Ticks) -> Self {
+                Microseconds(ticks.0 / (FREQUENCY / 1_000_000))
+            }
+        }
+
+        impl From<Ticks> for Milliseconds {
+            fn from(ticks: Ticks) -> Self {
+                Milliseconds(ticks.0 / (FREQUENCY / 1_000))
+            }
+        }
+
+        impl From<Ticks> for Seconds {
+            fn from(ticks: Ticks) -> Self {
+                Seconds(ticks.0 / FREQUENCY)
+            }
+        }
+
+        impl From<IHertz> for Ticks {
+            fn from(ihz: IHertz) -> Ticks {
+                Ticks(FREQUENCY / ihz.0)
+            }
+        }
+
+        impl From<Microseconds> for Ticks {
+            fn from(us: Microseconds) -> Ticks {
+                Ticks(us.0 * (FREQUENCY / 1_000_000))
+            }
+        }
+
+        impl From<Milliseconds> for Ticks {
+            fn from(ms: Milliseconds) -> Ticks {
+                Ticks(ms.0 * (FREQUENCY / 1_000))
+            }
+        }
+
+        impl From<Seconds> for Ticks {
+            fn from(s: Seconds) -> Ticks {
+                Ticks(s.0 * FREQUENCY)
+            }
+        }
+
+        impl Into<u32> for Ticks {
+            fn into(self) -> u32 {
+                self.0
+            }
+        }
+    }
+}
+
+/// Advance High-performance Bus (AHB)
+pub mod ahb {
+    frequency!(8_000_000);
+}
+
+/// Advance Peripheral Bus 1 (APB1)
+pub mod apb1 {
+    frequency!(8_000_000);
+}
+
+/// Advance Peripheral Bus 2 (APB2)
+pub mod apb2 {
+    frequency!(8_000_000);
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9b0c42c77fb836bec681a025747fdfed5065aceb..eda4eeea5bb5bbf598e987c7f7d261c0c5d4ebda 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,16 +17,31 @@
 
 #![deny(missing_docs)]
 #![deny(warnings)]
+#![feature(const_fn)]
+#![feature(get_type_id)]
+#![feature(never_type)]
+#![feature(unsize)]
 #![no_std]
 
 extern crate cast;
+extern crate embedded_hal as hal;
+extern crate nb;
+extern crate static_ref;
+
 pub extern crate stm32f30x;
 
 // For documentation only
 pub mod examples;
 
+pub mod dma;
 pub mod led;
 pub mod serial;
 pub mod timer;
+pub mod time;
+
+pub mod frequency;
+use frequency::*;
+
+pub use hal::prelude;
+pub use serial::Serial;
 
-mod frequency;
diff --git a/src/serial.rs b/src/serial.rs
index 62dd09d8c33bd504735600413288937c3c94b5d3..2f292af6b3306046303c22ad9711b4a07f887c9a 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -1,117 +1,398 @@
 //! Serial interface
 //!
-//! - TX - PA9
-//! - RX - PA10
+//! You can use the `Serial` interface with these USART instances
+//!
+//! # USART1
+//!
+//! - TX = PA9
+//! - RX = PA10
+//! - Interrupt = USART1
 
+use core::any::{Any, TypeId};
+use core::marker::Unsize;
+use core::ops::Deref;
 use core::ptr;
 
-use cast::{u16, u8};
-use stm32f30x::{USART1, GPIOA, RCC};
+use cast::u16;
+use hal;
+use nb;
+use static_ref::Static;
+use stm32f30x::{gpioa, DMA1, USART1, usart1, GPIOA,
+                  RCC};
 
-use frequency;
+use dma::{self, Buffer, Dma1Channel4, Dma1Channel5};
 
 /// Specialized `Result` type
-pub type Result<T> = ::core::result::Result<T, Error>;
+pub type Result<T> = ::core::result::Result<T, nb::Error<Error>>;
+
+/// IMPLEMENTATION DETAIL
+pub unsafe trait Usart: Deref<Target = usart1::RegisterBlock> {
+    /// IMPLEMENTATION DETAIL
+    type GPIO: Deref<Target = gpioa::RegisterBlock>;
+    /// IMPLEMENTATION DETAIL
+    type Ticks: Into<u32>;
+}
+
+unsafe impl Usart for USART1 {
+    type GPIO = GPIOA;
+    type Ticks = ::apb2::Ticks;
+}
 
 /// An error
 #[derive(Debug)]
 pub enum Error {
+    /// De-synchronization, excessive noise or a break character detected
+    Framing,
+    /// Noise detected in the received frame
+    Noise,
+    /// RX buffer overrun
+    Overrun,
     #[doc(hidden)]
     _Extensible,
 }
 
+/// Interrupt event
+pub enum Event {
+    /// RX buffer Not Empty (new data available)
+    Rxne,
+    /// Transmission Complete
+    Tc,
+    /// TX buffer Empty (more data can be send)
+    Txe,
+}
+
 /// Serial interface
 ///
 /// # Interrupts
 ///
-/// - `Usart1Exti25` - RXNE (RX buffer not empty)
-pub struct Serial<'a>(pub &'a USART1);
+/// - RXNE
+pub struct Serial<'a, U>(pub &'a U)
+where
+    U: Any + Usart;
+
+impl<'a, U> Clone for Serial<'a, U>
+where
+    U: Any + Usart,
+{
+    fn clone(&self) -> Self {
+        *self
+    }
+}
 
-impl<'a> Serial<'a> {
+impl<'a, U> Copy for Serial<'a, U>
+where
+    U: Any + Usart,
+{
+}
+
+impl<'a, U> Serial<'a, U>
+where
+    U: Any + Usart,
+{
     /// Initializes the serial interface with a baud rate of `baut_rate` bits
     /// per second
-    pub fn init(&self, gpioa: &GPIOA, rcc: &RCC, baud_rate: u32) {
-        let usart1 = self.0;
+    ///
+    /// The serial interface will be configured to use 8 bits of data, 1 stop
+    /// bit, no hardware control and to omit parity checking
+    pub fn init<B>(
+        &self,
+        baud_rate: B,
+        dma1: Option<&DMA1>,
+        gpio: &U::GPIO,
+        rcc: &RCC,
+    ) where
+        B: Into<U::Ticks>,
+    {
+        self._init(baud_rate.into(), dma1, gpio, rcc)
+    }
+
+    fn _init(
+        &self,
+        baud_rate: U::Ticks,
+        dma1: Option<&DMA1>,
+        gpio: &U::GPIO,
+        rcc: &RCC,
+    ) {
+        let usart = self.0;
+
+        // power up peripherals
+        if dma1.is_some() {
+            rcc.ahbenr.modify(|_, w| w.dmaen().enabled());
+        }
+        if usart.get_type_id() == TypeId::of::<USART1>() {
+            rcc.apb2enr.modify(|_, w| {
+                w.usart1en().enabled()
+            });
+        }
+
+        rcc.ahbenr.modify(|_, w| w.iopaen().set_bit());
+
+        if usart.get_type_id() == TypeId::of::<USART1>() {
+            // PA9. = TX, PA10 = RX
+            gpio.afrh.modify(|_, w| {
+                unsafe {
+                    w.afrh9().bits(7).afrh10().bits(7)
+                }
+            });
+            gpio.moder.modify(|_, w| {
+                w.moder9().alternate()
+                    .moder10().alternate()
+            });
+        }
+
+        if let Some(dma1) = dma1 {
+            if usart.get_type_id() == TypeId::of::<USART1>() {
+                // TX DMA transfer
+                // mem2mem: Memory to memory mode disabled
+                // pl: Medium priority
+                // msize: Memory size = 8 bits
+                // psize: Peripheral size = 8 bits
+                // minc: Memory increment mode enabled
+                // pinc: Peripheral increment mode disabled
+                // circ: Circular mode disabled
+                // dir: Transfer from memory to peripheral
+                // tceie: Transfer complete interrupt enabled
+                // en: Disabled
+                dma1.ccr4.write(|w| unsafe {
+                    w.mem2mem()
+                        .clear_bit()
+                        .pl()
+                        .bits(0b01)
+                        .msize()
+                        .bits(0b00)
+                        .psize()
+                        .bits(0b00)
+                        .minc()
+                        .set_bit()
+                        .circ()
+                        .clear_bit()
+                        .pinc()
+                        .clear_bit()
+                        .dir()
+                        .set_bit()
+                        .tcie()
+                        .set_bit()
+                        .en()
+                        .clear_bit()
+                });
+
+                // RX DMA transfer
+                // mem2mem: Memory to memory mode disabled
+                // pl: Medium priority
+                // msize: Memory size = 8 bits
+                // psize: Peripheral size = 8 bits
+                // minc: Memory increment mode enabled
+                // pinc: Peripheral increment mode disabled
+                // circ: Circular mode disabled
+                // dir: Transfer from peripheral to memory
+                // tceie: Transfer complete interrupt enabled
+                // en: Disabled
+                dma1.ccr5.write(|w| unsafe {
+                    w.mem2mem()
+                        .clear_bit()
+                        .pl()
+                        .bits(0b01)
+                        .msize()
+                        .bits(0b00)
+                        .psize()
+                        .bits(0b00)
+                        .minc()
+                        .set_bit()
+                        .circ()
+                        .clear_bit()
+                        .pinc()
+                        .clear_bit()
+                        .dir()
+                        .clear_bit()
+                        .tcie()
+                        .set_bit()
+                        .en()
+                        .clear_bit()
+                });
+            }
+        }
 
-        // Power up the peripherals
-        rcc.apb2enr.modify(|_, w| w.usart1en().enabled());
-        rcc.ahbenr.modify(|_, w| w.iopaen().enabled());
-
-        // Configure PA9 as TX and PA10 as RX
-        gpioa
-            .afrh
-            .modify(|_, w| unsafe { w.afrh9().bits(7).afrh10().bits(7) });
-        gpioa
-            .moder
-            .modify(|_, w| w.moder9().alternate().moder10().alternate());
-
-        // 8 data bits, 0 stop bits
-        usart1.cr2.write(|w| unsafe { w.stop().bits(0b00) });
-
-        // Disable hardware flow control
-        usart1
-            .cr3
-            .write(|w| w.rtse().clear_bit().ctse().clear_bit());
-
-        // set baud rate
-        let brr = u16(frequency::APB2 / baud_rate).unwrap();
-        let fraction = u8(brr & 0b1111).unwrap();
-        let mantissa = brr >> 4;
-        usart1.brr.write(|w| unsafe {
-            w.div_fraction()
-                .bits(fraction)
-                .div_mantissa()
-                .bits(mantissa)
+        // 8N1
+        usart.cr2.write(|w| unsafe { w.stop().bits(0b00) });
+
+        // baud rate
+        let brr = baud_rate.into();
+        assert!(brr >= 16, "impossible baud rate");
+        usart.brr.write(|w| unsafe { w.bits(brr) });
+
+        // disable hardware flow control
+        // enable DMA TX and RX transfers
+        usart.cr3.write(|w| {
+            w.rtse()
+                .clear_bit()
+                .ctse()
+                .clear_bit()
+                .dmat()
+                .set_bit()
+                .dmar()
+                .set_bit()
         });
 
-        // enable peripheral, transmitter, receiver
-        // enable RXNE event
-        usart1.cr1.write(|w| {
+        // enable TX, RX; disable parity checking
+        usart.cr1.write(|w| {
             w.ue()
                 .set_bit()
                 .re()
                 .set_bit()
                 .te()
                 .set_bit()
-                .pce()
+                .m()
                 .clear_bit()
                 .over8()
                 .clear_bit()
+                .pce()
+                .clear_bit()
                 .rxneie()
-                .set_bit()
+                .clear_bit()
         });
     }
 
-    /// Reads a byte from the RX buffer
-    ///
-    /// Returns `None` if the buffer is empty
-    pub fn read(&self) -> Result<u8> {
+    /// Starts listening for an interrupt `event`
+    pub fn listen(&self, event: Event) {
+        let usart = self.0;
+
+        match event {
+            Event::Rxne => usart.cr1.modify(|_, w| w.rxneie().set_bit()),
+            Event::Tc => usart.cr1.modify(|_, w| w.tcie().set_bit()),
+            Event::Txe => usart.cr1.modify(|_, w| w.txeie().set_bit()),
+        }
+    }
+
+    /// Stops listening for an interrupt `event`
+    pub fn unlisten(&self, event: Event) {
+        let usart = self.0;
+
+        match event {
+            Event::Rxne => usart.cr1.modify(|_, w| w.rxneie().clear_bit()),
+            Event::Tc => usart.cr1.modify(|_, w| w.tcie().clear_bit()),
+            Event::Txe => usart.cr1.modify(|_, w| w.txeie().clear_bit()),
+        }
+    }
+}
+
+impl<'a, U> hal::serial::Read<u8> for Serial<'a, U>
+where
+    U: Any + Usart,
+{
+    type Error = Error;
+
+    fn read(&self) -> Result<u8> {
         let usart1 = self.0;
+        let sr = usart1.isr.read();
 
-        if usart1.isr.read().rxne().bit_is_set() {
+        if sr.ore().bit_is_set() {
+            Err(nb::Error::Other(Error::Overrun))
+        } else if sr.nf().bit_is_set() {
+            Err(nb::Error::Other(Error::Noise))
+        } else if sr.fe().bit_is_set() {
+            Err(nb::Error::Other(Error::Framing))
+        } else if sr.rxne().bit_is_set() {
             // NOTE(read_volatile) the register is 9 bits big but we'll only
             // work with the first 8 bits
             Ok(unsafe {
                 ptr::read_volatile(&usart1.rdr as *const _ as *const u8)
             })
         } else {
-            Err(Error::_Extensible)
+            Err(nb::Error::WouldBlock)
         }
     }
+}
 
-    /// Writes byte into the TX buffer
-    ///
-    /// Returns `Err` if the buffer is already full
-    pub fn write(&self, byte: u8) -> Result<()> {
+impl<'a, U> hal::serial::Write<u8> for Serial<'a, U>
+where
+    U: Any + Usart,
+{
+    type Error = Error;
+
+    fn write(&self, byte: u8) -> Result<()> {
         let usart1 = self.0;
+        let sr = usart1.isr.read();
 
-        if usart1.isr.read().txe().bit_is_set() {
+        if sr.ore().bit_is_set() {
+            Err(nb::Error::Other(Error::Overrun))
+        } else if sr.nf().bit_is_set() {
+            Err(nb::Error::Other(Error::Noise))
+        } else if sr.fe().bit_is_set() {
+            Err(nb::Error::Other(Error::Framing))
+        } else if sr.txe().bit_is_set() {
+            // NOTE(write_volatile) see NOTE in the `read` method
             unsafe {
                 ptr::write_volatile(&usart1.tdr as *const _ as *mut u8, byte)
             }
             Ok(())
         } else {
-            Err(Error::_Extensible)
+            Err(nb::Error::WouldBlock)
         }
     }
 }
+
+impl<'a> Serial<'a, USART1> {
+    /// Starts a DMA transfer to receive serial data into a `buffer`
+    ///
+    /// This will mutably lock the `buffer` preventing borrowing its contents
+    /// The `buffer` can be `release`d after the DMA transfer finishes
+    // TODO support circular mode + half transfer interrupt as a double
+    // buffering mode
+    pub fn read_exact<B>(
+        &self,
+        dma1: &DMA1,
+        buffer: &Static<Buffer<B, Dma1Channel5>>,
+    ) -> ::core::result::Result<(), dma::Error>
+    where
+        B: Unsize<[u8]>,
+    {
+        let usart1 = self.0;
+
+        if dma1.ccr5.read().en().bit_is_set() {
+            return Err(dma::Error::InUse);
+        }
+
+        let buffer: &mut [u8] = buffer.lock_mut();
+
+        dma1.cndtr5
+            .write(|w| unsafe { w.ndt().bits(u16(buffer.len()).unwrap()) });
+        dma1.cpar5
+            .write(|w| unsafe { w.bits(&usart1.rdr as *const _ as u32) });
+        dma1.cmar5
+            .write(|w| unsafe { w.bits(buffer.as_ptr() as u32) });
+        dma1.ccr5.modify(|_, w| w.en().set_bit());
+
+        Ok(())
+    }
+
+    /// Starts a DMA transfer to send `buffer` through this serial port
+    ///
+    /// This will immutably lock the `buffer` preventing mutably borrowing its
+    /// contents. The `buffer` can be `release`d after the DMA transfer finishes
+    pub fn write_all<B>(
+        &self,
+        dma1: &DMA1,
+        buffer: &Static<Buffer<B, Dma1Channel4>>,
+    ) -> ::core::result::Result<(), dma::Error>
+    where
+        B: Unsize<[u8]>,
+    {
+        let usart1 = self.0;
+
+        if dma1.ccr4.read().en().bit_is_set() {
+            return Err(dma::Error::InUse);
+        }
+
+        let buffer: &[u8] = buffer.lock();
+
+        dma1.cndtr4
+            .write(|w| unsafe { w.ndt().bits(u16(buffer.len()).unwrap()) });
+        dma1.cpar4
+            .write(|w| unsafe { w.bits(&usart1.tdr as *const _ as u32) });
+        dma1.cmar4
+            .write(|w| unsafe { w.bits(buffer.as_ptr() as u32) });
+        dma1.ccr4.modify(|_, w| w.en().set_bit());
+
+        Ok(())
+    }
+}
diff --git a/src/time.rs b/src/time.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3922a718896a763b4f82205ba985939198affac5
--- /dev/null
+++ b/src/time.rs
@@ -0,0 +1,93 @@
+
+//! Units of time
+
+macro_rules! map {
+    ($Self:ident) => {
+        impl $Self {
+            /// Applies the function `f` to inner value
+            pub fn map<F>(self, f: F) -> $Self
+            where
+                F: FnOnce(u32) -> u32
+            {
+                $Self(f(self.0))
+            }
+        }
+    }
+}
+
+/// `Hz^-1`
+#[derive(Clone, Copy, Debug)]
+pub struct IHertz(pub u32);
+
+impl IHertz {
+    /// Invert this quantity
+    pub fn invert(self) -> Hertz {
+        Hertz(self.0)
+    }
+}
+
+map!(IHertz);
+
+/// `Hz`
+#[derive(Clone, Copy, Debug)]
+pub struct Hertz(pub u32);
+
+impl Hertz {
+    /// Invert this quantity
+    pub fn invert(self) -> IHertz {
+        IHertz(self.0)
+    }
+}
+
+map!(Hertz);
+
+/// `us`
+#[derive(Clone, Copy, Debug)]
+pub struct Microseconds(pub u32);
+
+map!(Microseconds);
+
+/// `ms`
+#[derive(Clone, Copy, Debug)]
+pub struct Milliseconds(pub u32);
+
+map!(Milliseconds);
+
+/// `s`
+#[derive(Clone, Copy, Debug)]
+pub struct Seconds(pub u32);
+
+map!(Seconds);
+
+/// `u32` extension trait
+pub trait U32Ext {
+    /// Wrap in `Hz`
+    fn hz(self) -> Hertz;
+
+    /// Wrap in `Milliseconds`
+    fn ms(self) -> Milliseconds;
+
+    /// Wrap in `Seconds`
+    fn s(self) -> Seconds;
+
+    /// Wrap in `Microseconds`
+    fn us(self) -> Microseconds;
+}
+
+impl U32Ext for u32 {
+    fn hz(self) -> Hertz {
+        Hertz(self)
+    }
+
+    fn ms(self) -> Milliseconds {
+        Milliseconds(self)
+    }
+
+    fn s(self) -> Seconds {
+        Seconds(self)
+    }
+
+    fn us(self) -> Microseconds {
+        Microseconds(self)
+    }
+}
diff --git a/src/timer.rs b/src/timer.rs
index bffc2f5e26bc18790a2c18e1ed832e87edc0eac6..41d41300e65266a2ba9ae33e85703cdc1bcdd5c1 100644
--- a/src/timer.rs
+++ b/src/timer.rs
@@ -5,8 +5,6 @@ use core::u16;
 use cast::{u16, u32};
 use stm32f30x::{RCC, TIM7};
 
-use frequency;
-
 /// Specialized `Result` type
 pub type Result<T> = ::core::result::Result<T, Error>;
 
@@ -32,7 +30,7 @@ impl<'a> Timer<'a> {
         // Power up peripherals
         rcc.apb1enr.modify(|_, w| w.tim7en().enabled());
 
-        let ratio = frequency::APB1 / frequency;
+        let ratio = ::apb1::FREQUENCY / frequency;
         let psc = u16((ratio - 1) / u32(u16::MAX)).unwrap();
         tim7.psc.write(|w| w.psc().bits(psc));
         let arr = u16(ratio / u32(psc + 1)).unwrap();