diff --git a/macros/src/check.rs b/macros/src/check.rs
index 1dac363f215b0bb18a1b41c2cd3961b6eadf03fc..cedf9336ff6dd7f90de777f634127024d06b6d9d 100644
--- a/macros/src/check.rs
+++ b/macros/src/check.rs
@@ -18,6 +18,7 @@ pub type Tasks = HashMap<Ident, Task>;
 
 pub struct Task {
     pub enabled: Option<bool>,
+    pub path: Option<Path>,
     pub priority: u8,
     pub resources: Idents,
 }
@@ -71,6 +72,7 @@ fn task(task: syntax::check::Task) -> Result<Task> {
     if let Some(priority) = task.priority {
         Ok(Task {
             enabled: task.enabled,
+            path: task.path,
             priority,
             resources: task.resources,
         })
diff --git a/macros/src/trans.rs b/macros/src/trans.rs
index 0a02d3e291624fcb03958ea78b5207ebf8d6e6bb..36ddb6a561368f668738d9bc181dccce49fe1434 100644
--- a/macros/src/trans.rs
+++ b/macros/src/trans.rs
@@ -1,4 +1,5 @@
 use quote::{Ident, Tokens};
+use syn::{Lit, StrStyle};
 
 use analyze::{Ownership, Ownerships};
 use check::App;
@@ -497,89 +498,148 @@ fn tasks(app: &App, ownerships: &Ownerships, root: &mut Vec<Tokens>) {
 
         let mut lifetime = None;
         let mut needs_reexport = false;
-        for name in &task.resources {
-            match ownerships[name] {
-                Ownership::Shared { ceiling } if ceiling > task.priority => {
-                    fields.push(quote! {
-                        pub #name: super::_resource::#name,
-                    });
+        let mut needs_threshold = false;
+        let has_resources = !task.resources.is_empty();
 
-                    exprs.push(quote! {
-                        #name: {
-                            super::_resource::#name::new()
-                        },
-                    });
-                }
-                _ => {
-                    lifetime = Some(quote!('a));
-                    if let Some(resource) = app.resources.get(name) {
-                        needs_reexport = true;
-                        let ty = &resource.ty;
+        if has_resources {
+            for name in &task.resources {
+                match ownerships[name] {
+                    Ownership::Shared { ceiling }
+                        if ceiling > task.priority =>
+                    {
+                        needs_threshold = true;
 
                         fields.push(quote! {
-                            pub #name: &'a mut ::#krate::Static<#ty>,
+                            pub #name: super::_resource::#name,
                         });
 
                         exprs.push(quote! {
-                            #name: ::#krate::Static::ref_mut(&mut super::#name),
+                            #name: {
+                                super::_resource::#name::new()
+                            },
                         });
-                    } else {
-                        fields.push(quote! {
-                            pub #name:
+                    }
+                    _ => {
+                        lifetime = Some(quote!('a));
+                        if let Some(resource) = app.resources.get(name) {
+                            needs_reexport = true;
+                            let ty = &resource.ty;
+
+                            fields.push(quote! {
+                                pub #name: &'a mut ::#krate::Static<#ty>,
+                            });
+
+                            exprs.push(quote! {
+                                #name: ::#krate::Static::ref_mut(&mut super::#name),
+                            });
+                        } else {
+                            fields.push(quote! {
+                                pub #name:
                                 &'a mut ::#krate::Static<::#device::#name>,
-                        });
+                            });
 
-                        exprs.push(quote! {
-                            #name: ::#krate::Static::ref_mut(
-                                &mut *::#device::#name.get(),
-                            ),
-                        });
+                            exprs.push(quote! {
+                                #name: ::#krate::Static::ref_mut(
+                                    &mut *::#device::#name.get(),
+                                ),
+                            });
+                        }
                     }
                 }
             }
+
+            if needs_reexport {
+                let rname = Ident::new(format!("_{}Resources", name));
+                root.push(quote! {
+                    #[allow(non_camel_case_types)]
+                    #[allow(non_snake_case)]
+                    pub struct #rname<#lifetime> {
+                        #(#fields)*
+                    }
+                });
+
+                items.push(quote! {
+                    pub use ::#rname as Resources;
+                });
+            } else {
+                items.push(quote! {
+                    #[allow(non_snake_case)]
+                    pub struct Resources<#lifetime> {
+                        #(#fields)*
+                    }
+                });
+            }
+
+            items.push(quote! {
+                #[allow(unsafe_code)]
+                impl<#lifetime> Resources<#lifetime> {
+                    pub unsafe fn new() -> Self {
+                        Resources {
+                            #(#exprs)*
+                        }
+                    }
+                }
+            });
         }
 
-        if needs_reexport {
-            let rname = Ident::new(format!("_{}Resources", name));
+        if let Some(path) = task.path.as_ref() {
+            let mut tys = vec![];
+            let mut exprs = vec![];
+
+            let priority = task.priority;
+            if needs_threshold {
+                tys.push(quote!(&mut Threshold));
+                exprs.push(quote!(&mut Threshold::new(#priority)));
+            }
+
+            if has_resources {
+                tys.push(quote!(#name::Resources));
+                exprs.push(quote!(#name::Resources::new()));
+            }
+
+            let _name = Ident::new(format!("_{}", name));
+            let export_name = Lit::Str(name.as_ref().to_owned(), StrStyle::Cooked);
             root.push(quote! {
-                #[allow(non_camel_case_types)]
                 #[allow(non_snake_case)]
-                pub struct #rname<#lifetime> {
-                    #(#fields)*
+                #[allow(unsafe_code)]
+                #[export_name = #export_name]
+                pub unsafe extern "C" fn #_name() {
+                    let f: fn(#(#tys,)*) = #path;
+
+                    f(#(#exprs,)*)
                 }
             });
-
+        } else if !has_resources {
             items.push(quote! {
-                pub use ::#rname as Resources;
-            });
-        } else {
-            items.push(quote! {
-                #[allow(non_snake_case)]
-                pub struct Resources<#lifetime> {
-                    #(#fields)*
+                pub struct Resources {
+                    _0: (),
                 }
-            });
-        }
 
-        items.push(quote! {
-            #[allow(unsafe_code)]
-            impl<#lifetime> Resources<#lifetime> {
-                pub unsafe fn new() -> Self {
-                    Resources {
-                        #(#exprs)*
+                impl Resources {
+                    pub unsafe fn new() -> Self {
+                        Resources { _0: () }
                     }
                 }
-            }
-        });
+            });
+            // the `task!` macro will be used so the `#NAME::Resources` type
+            // must exist
+        }
 
         let priority = task.priority;
+        if task.path.is_none() {
+            // This `const`ant is mainly used to make sure the user doesn't
+            // forget to set a task handler using the `task!` macro. They'll get
+            // an error if they do.
+            items.push(quote! {
+                #[deny(dead_code)]
+                pub const #name: u8 = #priority;
+            });
+        }
+
         root.push(quote!{
             #[allow(non_snake_case)]
             #[allow(unsafe_code)]
             mod #name {
-                #[deny(dead_code)]
-                pub const #name: u8 = #priority;
-
                 #[allow(dead_code)]
                 #[deny(const_err)]
                 const CHECK_PRIORITY: (u8, u8) = (
diff --git a/src/lib.rs b/src/lib.rs
index 1b50b3d0c2acd60a6fac022a9ba4b37e0add45f2..ba42d1debf9840982919c99ce4adb7f20a1e1f45 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -191,6 +191,10 @@ impl Threshold {
 impl !Send for Threshold {}
 
 /// Sets an interrupt as pending
+///
+/// If the interrupt priority is high enough the interrupt will be serviced
+/// immediately, otherwise it will be serviced at some point after the current
+/// task ends.
 pub fn set_pending<I>(interrupt: I)
 where
     I: Nr,