diff --git a/examples/rtfm-serial-echo.rs b/examples/rtfm-serial-echo.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7196c81045ebd3ceeaa5c185e76c6c8b031a210f
--- /dev/null
+++ b/examples/rtfm-serial-echo.rs
@@ -0,0 +1,69 @@
+//! Serial interface echo server (reactive version)
+//!
+//! This is a reactive version of the `serial-echo` example. Here the processor sleeps most of the
+//! time and only wakes up to sent back received bytes.
+//!
+//! This example uses the [Real Time For the Masses framework](https://docs.rs/cortex-m-rtfm/~0.3)
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![feature(proc_macro)]
+#![no_std]
+
+extern crate cortex_m_rtfm as rtfm;
+extern crate stm32f4x_hal as hal;
+
+use hal::prelude::*;
+use hal::serial::{Event, Rx, Serial, Tx};
+use hal::stm32f4x;
+use rtfm::{app, Threshold};
+
+app! {
+    device: stm32f4x,
+
+    resources: {
+        static TX: Tx<stm32f4x::USART2>;
+        static RX: Rx<stm32f4x::USART2>;
+    },
+
+    tasks: {
+        USART2: {
+            path: echo,
+            resources: [TX, RX],
+        }
+    },
+}
+
+fn init(p: init::Peripherals) -> init::LateResources {
+    let mut flash = p.device.FLASH.constrain();
+    let mut rcc = p.device.RCC.constrain();
+    let mut gpioa = p.device.GPIOA.split(&mut rcc.ahb1);
+
+    let clocks = rcc.cfgr.freeze(&mut flash.acr);
+
+    let tx = gpioa.pa2.into_af7(&mut gpioa.moder, &mut gpioa.afrl);
+    let rx = gpioa.pa3.into_af7(&mut gpioa.moder, &mut gpioa.afrl);
+
+    let mut serial = Serial::usart2(
+        p.device.USART2,
+        (tx, rx),
+        115_200.bps(),
+        clocks,
+        &mut rcc.apb1,
+    );
+    serial.listen(Event::Rxne);
+    let (tx, rx) = serial.split();
+
+    init::LateResources { TX: tx, RX: rx }
+}
+
+fn idle() -> ! {
+    // sleep
+    loop {
+        // rtfm::wfi();
+    }
+}
+
+fn echo(_: &mut Threshold, mut r: USART2::Resources) {
+    let byte = r.RX.read().unwrap();
+    r.TX.write(byte).unwrap();
+}