diff --git a/examples/loopback.rs b/examples/loopback.rs
index 48872eec184a19e51a3cdab2e1c5743cd7d73412..e820129a7e3c772568aaa8a36f5e3fda35deed90 100644
--- a/examples/loopback.rs
+++ b/examples/loopback.rs
@@ -115,7 +115,7 @@ fn usart2_handler(_t: &mut Threshold, r: USART2::Resources) {
         match r.SEND.dequeue() {
             Some(b) => {
                 // we (still) have something in the queue to send
-                let _ = serial.write(b);
+                let _ = serial.write(b); // will clear Txe bit
             }
             _ => {
                 // the que was empty so the last item was already sent
@@ -152,12 +152,12 @@ fn command(
         _ => {}
     }
 
-    // here we assume that
+    // re-enable the USART2 Txe to trigger the USART2 handler
     USART2.claim(t, |usart, _| {
         Serial(&**usart).listen(Event::Txe);
     });
     // trigger the usart to send queued data
-    rtfm::set_pending(f4::stm32f40x::Interrupt::USART2);
+    // rtfm::set_pending(f4::stm32f40x::Interrupt::USART2);
 }
 
 fn error_receive_buffer_full() {
@@ -172,30 +172,251 @@ fn rxne_not_set() {
     ip!("-");
 }
 
-#[derive(Debug)]
-enum Command {
-    Start,
-    Stop,
-    Freq(u32),
-}
+// loopback example
+// NOTICE, this example is made to show that a seemingly trivial
+// function, like an interrutp driven loopback faces you with
+// non-trivial concurrency/real-time challenges
+//
+// the setup is the following
+// SEND/RECEIVE buffers are circular, each holding both
+// producer index and a consumer index
+//
+// only when a full frame (ending with a Carrige return or Newline)
+// has been received, the command handler is notified (from the USART)
+// interrupt handler.
+//
+// to stress the problems the RECEIVE buffer is larger than the
+// SEND buffer, hence we can run in the problem that
+// receiving (at full speed), and trying to loop that back
+// will overflow the SEND buffer
+//
+// initially we enqueque "abc", and trigger the USART2 interrupt
+//
+//    let _ = r.SEND.enqueue('a' as u8);
+//    let _ = r.SEND.enqueue('b' as u8);
+//    let _ = r.SEND.enqueue('c' as u8);
+//    rtfm::set_pending(f4::stm32f40x::Interrupt::USART2); // trigger USART2 task
+//
+// the hardware provides only a single point entry for a USART2 interrupt
+// initially enabled by
+//
+//    serial.listen(Event::Rxne); // data received interrupt
+//    serial.listen(Event::Txe);  // data transmitted interrupt
+//
+// in the USART2 handler we have to determine by reading the RXNE if data has arrived
+//
+//    if sr.rxne().bit_is_set()
+//
+// in this case not, so we check if we have data to send
+//
+//     match r.SEND.dequeue()
+//
+// in this case we have (the 'a' of "abc", so a write will occur)
+//
+//     serial.write(b); // will clear Txe bit
+//
+//
+// the interrupt handler will exit (with the Txe bit cleared),
+// and the processor will be put to sleep (in idle)
+//
+// when the 'a' has been transmitted, we get another USART2 interrupt (Txe bit set)
+// the above procedure is repeated, and we will write a 'b'
+//
+// again we get an interrupt and this time write the 'c'
+//
+// and when 'c' has been written we get a new USART2 interrupt (Txe bit set).
+//
+// here we have the case of the SEND buffer being empty and match
+//
+//     serial.unlisten(Event::Txe);
+//
+// this is needed in order to avoid that the USART2 interrupt handler is
+// re-entered. (Still the Txe bit will be Set at this point)
+// the only way to clear the Txe is by writing and we have nothing to write
+// so the only option here is to "unlisten" to it....
+// already here we see that we need to fully understand the HARDWARE in order
+// to implement correct software, so Read the Manual
+//
+// you should now see "abc" in the moserial console
+// in the swo trace you should have something like
+//
+// [2018-02-23T10:28:30.735Z]   init
+// [2018-02-23T10:28:30.735Z]   --
+//
+// the two (2) dashes `-` are due to the triggering of the EXTI4
+// why not three (3), we have triggered it three (3), times when writing
+// `a`, `b` and `c`. Well the hardware has a single buffer for each interrupt
+// so if the interrupt was already pended (but not yet executed)
+// the events are *coalesced* (if we need to handle an event without coalescing
+// we need to implement this by a sofware overlay/buffer)
+//
+// and this was the easy part....
+//
+// now lets give an input from `moserial`. Set it to generate CR end.
+// enter `1234567<return>`
+//
+// in the trace output you should get something like...
+//
+// [2018-02-23T10:41:54.644Z]   command
+// [2018-02-23T10:41:54.645Z]   SEND BufferFullError
+// [2018-02-23T10:41:54.646Z]   error_receive_buffer_full
+// [2018-02-23T10:41:54.652Z]   error_receive_buffer_full
+//
+// and in `moserial` you should get
+//
+// abc123
+//
+// there is much in play, let's try to break it down
+// a USART2 Rxne interrupt was initally generated, and we enter the
+// interrupt handler, and detect this
+//
+// if sr.rxne().bit_is_set() {
+//     // interrupt due to data received
+//     match serial.read() {
+//         Ok(b) => {
+//             if b == '\n' as u8 || b == '\r' as u8 {
+//                 // we have received a `return`, trigger command to process it
+//                 rtfm::set_pending(f4::stm32f40x::Interrupt::EXTI1);
+//             } else {
+//                 match r.RECEIVE.enqueue(b) {
+//                     Err(_) => {
+//                         // trigger error_handler on buffer overflow
+//                         rtfm::set_pending(f4::stm32f40x::Interrupt::EXTI2);
+//                     }
+//                     _ => {}
+//                 }
+//             }
+//         }
+//
+// here the serial read was successful (Some('1')), its not a Carrige return so we enque on the BUFFER
+// as a side effect of serial.read(), the Rxne bit was cleared and we exit the interrupt handler
+// and sleep until the next byte arrives (`2`).
+//
+// this is repeated (wake, enque, sleep) until the buffer is full (that happens before we
+// receive the carrige return)
+//
+// when the buffer is full we trigger EXTI2 (that makes a trace output of the error)
+// the buffer is of lenght 6, but the implementation allows only N-1 elements
+// so both the `6` and `7` are dropped, and we produce two error events.
+// in this case they were not coalesced (both printed)
+//
+// we will recieve the carrige return, and and pend the `command` (EXTI1), i.e., our listener.
+//
+// the `command` interrupt handler
+// {
+//     ipln!("command");
+//     match SEND.claim_mut(t, |send, t1| {
+//         RECEIVE.claim_mut(t1, |receive, _| {
+//             let mut err = Ok(());
+//             while let Some(e) = receive.dequeue() {
+//                 err = send.enqueue(e);
+//             }
+//             err
+//         })
+//     }) {
+//         Err(err) => {
+//             // error handling outside the critical section
+//             ipln!("SEND {:?}", err);
+//         }
+//         _ => {}
+//     }
+//
+//     // re-enable Txe
+//     USART2.claim(t, |usart, _| {
+//         Serial(&**usart).listen(Event::Txe);
+//     });
+//     // trigger the usart to send queued data
+//     rtfm::set_pending(f4::stm32f40x::Interrupt::USART2);
+// }
+// we trace `command` and claim both SEND and RECEIVE
+// in the critical section we copy the RECIEVE buffer elements to the SEND buffer
+// (this is actually the *loopback* behavior, we could also copy to another local buffer
+// to do further parsing or simiar)
+//
+// here its important that we keep the crical section as short as possible
+// so we just copy nothing else... since we hold a shared resource we will block
+// the USART2 from executing, and may loose incoming data
+//
+// we also do error handling here if the send buffer is full we will return from
+// the claim(s) withe an error (else with an Ok(())),
+// result is matched
+// {
+//         Err(err) => {
+//             // error handling outside the critical section
+//             ipln!("SEND {:?}", err);
+//         }
+//         _ => {}
+// rendering a `SEND BufferFullError` (or nonthig)
+//
+// finally we need to re-enable the USART2 Txe, so that interrupts due to USART2 writes
+// will cause USART2 interrupts
+//
+//     // re-enable Txe
+//     USART2.claim(t, |usart, _| {
+//         Serial(&**usart).listen(Event::Txe);
+//     });
+//
+// recall that at an earlier point, we left a Txe interrupt condition hanging,
+// and filtered it out by *unlisten*
+//
+// alternative we could add a triggering event
+// rtfm::set_pending(f4::stm32f40x::Interrupt::USART2);
+// 
+// but it is not needed for this use case
+//
+// so what DO this implementatin promise
+//
+// 1. graceful handling of RECIEVE buffer overflow
+// 2. graceful handling of SEND buffer overflow
+// 3. no data loss, (e.g., we never miss detecting the carrige return)
+//    (you can stress this by entering a long input)
+// oahuesthueoasnthueonthueoantueoaueoasnthueoasnthueoasnueoasnthueoasnthueoa
+//  the output should be some errors, in the trace log but the `moserial` should receive
+//  `oah`, i.e. the firs three bytes
+//
+// what it DOES NOT promise
+// handling of hardware level errors (framing, etc.), this could be added if needed
+// 
+// of course you need to adjust the SEND/RECEIVE buffer lenghts to the requirements
+// of the application
+//
+// so lessons learned
+// 1. seemingly trivial problems may be harder than expected
+// 2. Reading the Manual(s) are required to write correct low level code
+// 3. RTFM provides a means to implment correct low level code
+//
+// On a side note, it took me quite some time to implment, debug and comment
+// the example, so be prepared, writing correct low level code is HARD
+// 
+// On the possitive side, the safety enforced by Rust RTFM
+// makes our lifes much easier, as we don't need to worry about
+// - race conditions, on the shared data structures
+// - deadlocks, for the reasource managenent
+//
+// As a comparison, in RTFM we write the low level code INSIDE the model of computation
+// hence we get the luxyry of letting RTFM manage the resource management
+// In a threaded environment (e.g. FreeRTOS), the driver (interrupt handling etc.) would 
+// be largely OUTSIDE the threaded model of computation, so correctness would
+// be up to you....
+//
+// In the arduino world there is no protection mechanisms at all, what you write is what you get:
+// hence rubustness can never be argued else than for the specic application at hand.
+//
+// The outsets are very different, in the FreeRTOS/arduino word we are typically happy to see things 
+// working, without concerns of roubustness (simly put robustness can never be argued, unless you
+// somehow make a proof over the complete code base).
+//
+// Using RTFM, we can establish critera for correctness, and from there argue robustness etc. 
+// (The theorecical task/resource underpinning, gives us a solid fundament for reasoning) 
+//
+// If one would want to prove correctness for the above implementation the way forward is
+// 1 Derive the possible sates of the system. *regarding both HW and software*
+// 2 Formulate invariants for each state.
+// 3 Show that each possible state transition upholds the invariants.
+// 
+// We have not gone the length of doing so for this application.. but its indeed possible but
+// takes a lot of effort.
+
+
 
-fn parse(s: &str) -> Result<Command, &str> {
-    let mut iter = s.split(' ').filter(|c| !(c == &""));
 
-    match iter.next() {
-        Some("Stop") => Ok(Command::Stop),
-        Some("Start") => Ok(Command::Start),
-        Some("Freq") => match iter.next() {
-            Some(fs) => {
-                if let Ok(f) = fs.parse::<u32>() {
-                    Ok(Command::Freq(f))
-                } else {
-                    Err("Invalid frequency")
-                }
-            }
-            None => Err("No frequency"),
-        },
-        Some(_) => Err("Invalid command"),
-        None => Err("No input"),
-    }
-}