diff --git a/examples/usart2-dma.rs b/examples/usart2-dma.rs
index 7d1e2c89131aa7d6e7f4da056c05401ddd186357..305a595809220d31af1cc8bb350658a3f1c035bf 100644
--- a/examples/usart2-dma.rs
+++ b/examples/usart2-dma.rs
@@ -7,13 +7,14 @@
 #![no_std]
 
 extern crate cortex_m_rtfm as rtfm;
+#[macro_use]
 extern crate f4;
 extern crate heapless;
 
 use core::fmt::Write;
 use core::ops::Deref;
 use f4::Serial;
-use f4::Writer as w;
+use f4::U8Writer;
 use f4::prelude::*;
 use f4::dma::{Buffer, Dma1Channel5, Dma1Channel6};
 use f4::time::Hertz;
@@ -30,6 +31,12 @@ const MAX_RX_LEN: usize = 1;
 
 const BAUD_RATE: Hertz = Hertz(115_200);
 
+enum CmdType {
+    Greeting,
+    Unknown,
+    None,
+}
+
 app! {
     device: f4::stm32f40x,
 
@@ -37,7 +44,7 @@ app! {
         static CMD_BUFFER: Vec<u8, [u8; MAX_CMD_LEN]> = Vec::new([0; MAX_CMD_LEN]);
         static RX_BUFFER: Buffer<[u8; MAX_RX_LEN], Dma1Channel5> = Buffer::new([0; MAX_RX_LEN]);
         static TX_BUFFER: Buffer<[u8; MAX_TX_LEN], Dma1Channel6> = Buffer::new([0; MAX_TX_LEN]);
-        static CNT: u8 = 1;
+        static CNT: u8 = 0;
     },
 
     tasks: {
@@ -54,40 +61,12 @@ app! {
     },
 }
 
-fn init(p: init::Peripherals, r: init::Resources) {
-    // Set clock to higher than default in order to test it works
-    clock::set_84_mhz(&p.RCC, &p.FLASH);
-
-    // There is no need to claim() resources in the init.
-    // Start the serial port
-    let serial = Serial(p.USART2);
-    serial.init(BAUD_RATE.invert(), Some(p.DMA1), p.GPIOA, p.RCC);
-
-    // Send a welcome message by borrowing transmit buffer and writing a formatted string into it
-    let x = 1.0;
-    write!(
-        w::out(&mut r.TX_BUFFER.borrow_mut()[..MAX_TX_LEN]),
-        "Hello, world! {}\r\n",
-        x
-    ).unwrap();
-    serial.write_all(p.DMA1, r.TX_BUFFER).unwrap();
-
-    // Listen to serial input on the receive DMA
-    serial.read_exact(p.DMA1, r.RX_BUFFER).unwrap();
-}
-
-fn idle() -> ! {
-    loop {
-        rtfm::wfi();
-    }
-}
-
 // Interrupt for serial receive DMA
 fn rx_done(t: &mut Threshold, mut r: DMA1_STREAM5::Resources) {
     use rtfm::Resource;
 
     let mut byte: u8 = 0;
-    let mut say_hello = false;
+    let mut cmd_type: CmdType = CmdType::None;
 
     r.RX_BUFFER.claim(t, |rx, t| {
         // We need to unlock the DMA to use it again in the future
@@ -117,45 +96,38 @@ fn rx_done(t: &mut Threshold, mut r: DMA1_STREAM5::Resources) {
     r.CMD_BUFFER.claim_mut(t, |cmd, _| {
         if byte == b'\r' {
             // End of command
-            match &***cmd {
-                b"hi" | b"Hi" => {
-                    say_hello = true;
+            cmd_type = match &***cmd {
+                b"hi" | b"Hi" => CmdType::Greeting,
+                _ => {
+                    if cmd.len() == 0 {
+                        CmdType::None // Empty string
+                    } else {
+                        CmdType::Unknown // Unknown string
+                    }
                 }
-                _ => {}
-            }
+            };
             cmd.clear();
         } else {
             if cmd.push(byte).is_err() {
-                // Error: buffer full
-                // KISS: we just clear the buffer when it gets full
+                // Error: command buffer is full
                 cmd.clear();
             }
         }
     });
     // If user wrote 'hi' and pressed enter, respond appropriately
-    if say_hello {
-        // Increment 'hi' counter
-        **r.CNT = **r.CNT + 1;
-        let cnt: u8 = **r.CNT;
-        // Claim the transmit buffer and write a formatted string into it
-        r.TX_BUFFER.claim_mut(t, |tx, _| {
-            write!(
-                w::out(&mut (*tx).deref().borrow_mut()[..MAX_TX_LEN]),
-                "Hello, there! {}\r\n",
-                cnt
-            ).unwrap();
-        });
-
-        // Transmit the response
-        r.TX_BUFFER.claim(t, |tx, t| {
-            r.DMA1.claim(t, |dma, t| {
-                r.USART2.claim(t, |usart, _| {
-                    let serial = Serial(&**usart);
-                    serial.write_all(dma, tx).unwrap();
-                });
-            });
-        });
-        // r.CNT.claim_mut(t, |cnt,_| cnt.wrapping_add(1));
+    match cmd_type {
+        CmdType::Greeting => {
+            // Increment 'hi' counter
+            **r.CNT = (**r.CNT).wrapping_add(1);
+            let cnt: u8 = **r.CNT;
+            // Print a response using DMA
+            uprint!(t, r.USART2, r.DMA1, r.TX_BUFFER, "Hi counter {}!\r\n", cnt);
+        }
+        CmdType::Unknown => {
+            // Unknown command
+            uprint!(t, r.USART2, r.DMA1, r.TX_BUFFER, "That's no greeting.\r\n");
+        }
+        _ => {}
     }
 }
 
@@ -172,3 +144,31 @@ fn tx_done(t: &mut Threshold, r: DMA1_STREAM6::Resources) {
         array.borrow_mut()[..MAX_TX_LEN].clone_from_slice(&[0; MAX_TX_LEN]);
     });
 }
+
+fn init(p: init::Peripherals, r: init::Resources) {
+    // Set clock to higher than default in order to test that it works
+    clock::set_84_mhz(&p.RCC, &p.FLASH);
+
+    // Start the serial port
+    let serial = Serial(p.USART2);
+    serial.init(BAUD_RATE.invert(), Some(p.DMA1), p.GPIOA, p.RCC);
+
+    // FIXME: We cannot use the uprint macro in the init since it needs Resources
+    // and Threshold...
+    // Send a welcome message by borrowing a slice of the transmit buffer and
+    // writing a formatted string into it.
+    write!(
+        U8Writer::new(&mut r.TX_BUFFER.borrow_mut()[..MAX_TX_LEN]),
+        "Hello, world! Say hi to me!\r\n",
+    ).unwrap();
+    serial.write_all(p.DMA1, r.TX_BUFFER).unwrap();
+
+    // Listen to serial input on the receive DMA
+    serial.read_exact(p.DMA1, r.RX_BUFFER).unwrap();
+}
+
+fn idle() -> ! {
+    loop {
+        rtfm::wfi();
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 25060109bf0bf1dd19f2fb9bdc4f2a8b8a549281..96938c9b75293e3f4bc896c279534ebf1093063e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -50,7 +50,7 @@ use frequency::*;
 
 pub use hal::prelude;
 pub use serial::Serial;
-pub use serial::Writer;
+pub use serial::U8Writer;
 pub use timer::{Channel, Timer};
 pub use pwm::Pwm;
 pub use capture::Capture;
diff --git a/src/serial.rs b/src/serial.rs
index 35f336b69343f472a1a1913812683ed2c83d3638..be071b51465c94f49e03ef9266872294bb0235d3 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -23,37 +23,6 @@ use dma::{self, Buffer, Dma1Channel5, Dma1Channel6};
 
 use core::fmt;
 
-///
-pub struct Writer<'a> {
-    buf: &'a mut [u8],
-    offset: usize,
-}
-
-impl<'a> Writer<'a> {
-    ///
-    pub fn out(buf: &'a mut [u8]) -> Self {
-        Writer {
-            buf: buf,
-            offset: 0,
-        }
-    }
-}
-
-impl<'a> fmt::Write for Writer<'a> {
-    fn write_str(&mut self, s: &str) -> fmt::Result {
-        let bytes = s.as_bytes();
-        // Skip over already-copied data
-        let remainder = &mut self.buf[self.offset..];
-        // Make the two slices the same length
-        let remainder = &mut remainder[..bytes.len()];
-        // Copy
-        remainder.copy_from_slice(bytes);
-        // Increment offset by number of copied bytes
-        self.offset += bytes.len();
-        Ok(())
-    }
-}
-
 /// Specialized `Result` type
 pub type Result<T> = ::core::result::Result<T, nb::Error<Error>>;
 
@@ -153,9 +122,13 @@ where
         if usart.get_type_id() == TypeId::of::<USART2>() {
             // we don't care about the speed register atm
             // DM00102166
-            // AF7, Table 9
             // PA2 and PA3 is connected to USART2 TX and RX respectively
+            // Alternate function AF7, Table 9
             gpio.afrl.modify(|_, w| w.afrl2().bits(7).afrl3().bits(7));
+            // Highest output speed
+            gpio.ospeedr
+                .modify(|_, w| w.ospeedr2().bits(0b11).ospeedr3().bits(0b11));
+            // RM0368 8.3 Table 23
             gpio.moder
                 .modify(|_, w| w.moder2().bits(2).moder3().bits(2));
         }
@@ -322,7 +295,9 @@ where
         } 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(&usart2.dr as *const _ as *const u8) })
+            Ok(unsafe {
+                ptr::read_volatile(&usart2.dr as *const _ as *const u8)
+            })
         } else {
             Err(nb::Error::WouldBlock)
         }
@@ -421,3 +396,76 @@ impl<'a> Serial<'a, USART2> {
         Ok(())
     }
 }
+///
+pub struct U8Writer<'a> {
+    buf: &'a mut [u8],
+    offset: usize,
+}
+
+impl<'a> U8Writer<'a> {
+    ///
+    pub fn new(buf: &'a mut [u8]) -> Self {
+       U8Writer {
+            buf: buf,
+            offset: 0,
+        }
+    }
+}
+
+impl<'a> fmt::Write for U8Writer<'a> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        let bytes = s.as_bytes();
+        // Skip over already-copied data
+        let remainder = &mut self.buf[self.offset..];
+        // Make the two slices the same length
+        let remainder = &mut remainder[..bytes.len()];
+        // Copy
+        remainder.copy_from_slice(bytes);
+        // Increment offset by number of copied bytes
+        self.offset += bytes.len();
+        Ok(())
+    }
+}
+
+/// Macro for printing formatted strings over serial through DMA.
+/// Uses the corted-m-rtfm resource model and can thus not be used
+/// outside rtfm tasks.
+#[macro_export]
+macro_rules! uprint {
+    ($T:ident, $USART:expr, $DMA:expr, $TX_BUFFER:expr, $s:expr) => {
+        use core::fmt::Write;
+        // Claim the transmit buffer and write a string literal into it
+        $TX_BUFFER.claim_mut($T, |tx, _| {
+            let len = (*tx).deref().borrow().len();
+            let buf = &mut (*tx).deref().borrow_mut();
+            write!(U8Writer::new(&mut buf[..len]), $s).unwrap();
+        });
+        // Transmit the contents of the buffer using DMA
+        $TX_BUFFER.claim($T, |tx, t| {
+            $DMA.claim(t, |dma, t| {
+                $USART.claim(t, |usart, _| {
+                    let serial = Serial(&**usart);
+                    serial.write_all(dma, tx).unwrap();
+                });
+            });
+        });
+    };
+    ($T:ident, $USART:expr, $DMA:expr, $TX_BUFFER:expr, $($arg:tt)* ) => {
+        use core::fmt::Write;
+        // Claim the transmit buffer and write a formatted string into it
+        $TX_BUFFER.claim_mut($T, |tx, _| {
+            let len = (*tx).deref().borrow().len();
+            let buf = &mut (*tx).deref().borrow_mut();
+            write!(U8Writer::new(&mut buf[..len]), $($arg)*).unwrap();
+        });
+         // Transmit the contents of the buffer using DMA
+        $TX_BUFFER.claim($T, |tx, t| {
+            $DMA.claim(t, |dma, t| {
+                $USART.claim(t, |usart, _| {
+                    let serial = Serial(&**usart);
+                    serial.write_all(dma, tx).unwrap();
+                });
+            });
+        });
+    }
+}