diff --git a/card10-l0dable/src/bhi160.rs b/card10-l0dable/src/bhi160.rs
index 964ef5bd6e01e7e90bccc268c1d5779362c0320e..e98de6980e49d53d4b40fda16daea4b54aa7ead6 100644
--- a/card10-l0dable/src/bhi160.rs
+++ b/card10-l0dable/src/bhi160.rs
@@ -1,3 +1,5 @@
+/// Accelerometer, Gyroscope, Orientation
+
 use core::{
     fmt::{self, Display, Write},
     marker::PhantomData,
@@ -53,6 +55,10 @@ pub struct Sensor<S: SensorType> {
 }
 
 impl<S: SensorType> Sensor<S> {
+    /// Use one of:
+    /// - `BHI160::<Accelerometer>::start()`
+    /// - `BHI160::<Gyroscope>::start()`
+    /// - `BHI160::<Orientation>::start()`
     fn new(stream_id: i32) -> Self {
         Self {
             stream_id,
diff --git a/card10-l0dable/src/bme680.rs b/card10-l0dable/src/bme680.rs
index 0c7524feb4333cc0dc4e084e0f91b5acc0de866d..ac30ff780625f1461197c37e7dd9bb1e15515e44 100644
--- a/card10-l0dable/src/bme680.rs
+++ b/card10-l0dable/src/bme680.rs
@@ -1,3 +1,5 @@
+/// Enviromental sensor
+
 use card10_sys::*;
 use core::mem::uninitialized;
 
diff --git a/card10-l0dable/src/buttons.rs b/card10-l0dable/src/buttons.rs
index dc033618c1ec0c727655572ca0811ee83a9fff83..d13d91abd670dda52a7c08e78370d2f6d7473f1e 100644
--- a/card10-l0dable/src/buttons.rs
+++ b/card10-l0dable/src/buttons.rs
@@ -1,10 +1,12 @@
 use card10_sys::*;
 
+/// Button inputs
 pub struct Buttons {
     state: u32,
 }
 
 impl Buttons {
+    /// Read the current button state
     pub fn read() -> Self {
         let mask = epic_button_BUTTON_LEFT_BOTTOM
             | epic_button_BUTTON_RIGHT_BOTTOM
diff --git a/card10-l0dable/src/display.rs b/card10-l0dable/src/display.rs
index 83cdc26fe6d07641ae3f29c5f444cfd4b5bd5df7..e99cae24fc41c5f0d8603f3d447c553f471357fc 100644
--- a/card10-l0dable/src/display.rs
+++ b/card10-l0dable/src/display.rs
@@ -84,12 +84,14 @@ pub enum Font {
     Font24 = disp_font_name_DISP_FONT24 as u8,
 }
 
+/// Immediate mode routines
 impl Display {
     pub const W: u16 = 160;
     pub const H: u16 = 80;
     pub const FONT_W: u16 = 14;
     pub const FONT_H: u16 = 20;
 
+    /// Open the display, return an instance
     pub fn open() -> Self {
         unsafe {
             epic_disp_open();
@@ -97,18 +99,22 @@ impl Display {
         Display
     }
 
+    /// Write Epicardium's framebuffer to the display
     pub fn update(&self) {
         unsafe {
             epic_disp_update();
         }
     }
 
+    /// Clear everything with a solid `color`
     pub fn clear(&self, color: Color) {
         unsafe {
             epic_disp_clear(color.0);
         }
     }
 
+    /// Print text
+    ///
     /// s must be 0-terminated
     pub fn print(&self, x: u16, y: u16, s: &[u8], fg: Color, bg: Color) {
         unsafe {
@@ -116,6 +122,8 @@ impl Display {
         }
     }
 
+    /// Print text with a selected font
+    ///
     /// s must be 0-terminated
     pub fn print_adv(&self, font: Font, x: u16, y: u16, s: &[u8], fg: Color, bg: Color) {
         unsafe {
@@ -123,12 +131,14 @@ impl Display {
         }
     }
 
+    /// Set a pixel
     pub fn pixel(&self, x: u16, y: u16, color: Color) {
         unsafe {
             epic_disp_pixel(x, y, color.0);
         }
     }
 
+    /// Draw a line
     pub fn line(
         &self,
         x1: u16,
@@ -159,6 +169,7 @@ impl Display {
         }
     }
 
+    /// Draw a circle
     pub fn circ(
         &self,
         x: u16,
@@ -173,6 +184,10 @@ impl Display {
         }
     }
 
+    /// Obtain a handle for a framebuffer.
+    ///
+    /// Don't use `display.send()` but `framebuffer.send()` as long as
+    /// it is in use.
     pub fn framebuffer<'d>(&'d self) -> FrameBuffer<'d> {
         FrameBuffer::uninitialized(self)
     }
@@ -186,6 +201,8 @@ impl Drop for Display {
     }
 }
 
+/// Convenience text display
+///
 /// Requires `card10_alloc::init()` and `extern crate alloc;`
 #[macro_export]
 macro_rules! display {
@@ -203,6 +220,8 @@ macro_rules! display {
     });
 }
 
+/// Convenience text display with selected font
+///
 /// Requires `card10_alloc::init()` and `extern crate alloc;`
 #[macro_export]
 macro_rules! display_adv {
diff --git a/card10-l0dable/src/fs.rs b/card10-l0dable/src/fs.rs
index 4148023db079a1f54e9d17314c1fcf34c9189077..ea61cf23034254f73314d7de180e90391975e584 100644
--- a/card10-l0dable/src/fs.rs
+++ b/card10-l0dable/src/fs.rs
@@ -1,3 +1,5 @@
+//! File System support (unfinished)
+
 use core::mem::uninitialized;
 use core::str::from_utf8_unchecked;
 
diff --git a/card10-l0dable/src/leds.rs b/card10-l0dable/src/leds.rs
index a54da84ed3ef3d05d3091e0f983275993d797b3e..b6912c067c9c1bf6a5b8d0a585e785c59c97420d 100644
--- a/card10-l0dable/src/leds.rs
+++ b/card10-l0dable/src/leds.rs
@@ -1,3 +1,5 @@
+//! Lights
+
 use card10_sys::*;
 
 #[derive(Clone, Copy)]
@@ -6,6 +8,9 @@ pub enum LEDColor {
     HSV(f32, f32, f32),
 }
 
+/// Update all RGB LEDs
+///
+/// `f` must supply a `LEDColor` for `0..=10`.
 pub fn update_rgb_leds<F>(f: F)
 where
     F: Fn(i32) -> LEDColor,
diff --git a/card10-l0dable/src/lib.rs b/card10-l0dable/src/lib.rs
index acf2a07d06f31fc1fef746253c22ccdf2f01ef2e..011f370c1c4983ab49e128e77db3eba33b6e1f23 100644
--- a/card10-l0dable/src/lib.rs
+++ b/card10-l0dable/src/lib.rs
@@ -1,3 +1,4 @@
+//! [Repository](https://git.card10.badge.events.ccc.de/astro/rust-card10)
 #![no_std]
 
 mod os;
diff --git a/card10-l0dable/src/light_sensor.rs b/card10-l0dable/src/light_sensor.rs
index f68e0351f12e38cbbf3133fb592f549d0cffb1ee..633fa2dc2a053c23a48cc4dc033326dcbba6004b 100644
--- a/card10-l0dable/src/light_sensor.rs
+++ b/card10-l0dable/src/light_sensor.rs
@@ -1,11 +1,13 @@
 use card10_sys::*;
 
+/// Light sensor
 pub struct LightSensor {
     // Prevent creation of this struct by all but this module.
     _private: (),
 }
 
 impl LightSensor {
+    /// Start sensing light
     pub fn start() -> Self {
         if unsafe { epic_light_sensor_run() } != 0 {
             panic!("Cannot start light sensor");
@@ -13,6 +15,7 @@ impl LightSensor {
         LightSensor { _private: () }
     }
 
+    /// Obtain current light sensor reading
     pub fn get(&self) -> Option<u16> {
         let mut result = 0;
         if unsafe { epic_light_sensor_get(&mut result) } == 0 {
diff --git a/card10-l0dable/src/os.rs b/card10-l0dable/src/os.rs
index e209671614d31cae2d746c3f1e733470d1f57693..05a23b0d748ceeb9cea03f9aef221c456e6b91c2 100644
--- a/card10-l0dable/src/os.rs
+++ b/card10-l0dable/src/os.rs
@@ -1,5 +1,6 @@
 use card10_sys::*;
 
+/// Execute Python script or ELF file
 pub fn exec(path: &str) -> ! {
     let mut pathbuf = super::str_to_cstr(path);
     unsafe {
@@ -8,6 +9,7 @@ pub fn exec(path: &str) -> ! {
     unreachable!()
 }
 
+/// Exit current l0dable
 pub fn exit(ret: i32) -> ! {
     unsafe {
         epic_exit(ret);
@@ -15,6 +17,7 @@ pub fn exit(ret: i32) -> ! {
     unreachable!()
 }
 
+/// Cause a reboot
 pub fn system_reset() -> ! {
     unsafe {
         epic_system_reset();
diff --git a/card10-l0dable/src/rtc.rs b/card10-l0dable/src/rtc.rs
index cdf25a58c8bfc9369e2d483fc84c51a9ac2053bf..dcc78b8624619b4d08c6bc73a9206a6474bfd221 100644
--- a/card10-l0dable/src/rtc.rs
+++ b/card10-l0dable/src/rtc.rs
@@ -1,8 +1,13 @@
+//! Real-time clock functionality
+
 use card10_sys::*;
 use core::ops::Sub;
 
+/// Implemented for `Seconds` and `Milliseconds`
 pub trait Time {
+    /// Get current time
     fn time() -> Self;
+    /// Set the time (TODO)
     fn set_time(&self);
 }
 
@@ -20,8 +25,8 @@ impl Time for Seconds {
         let s = unsafe { epic_rtc_get_seconds() };
         Seconds(s)
     }
+    /// TODO
     fn set_time(&self) {
-        // TODO
     }
 }
 
@@ -46,8 +51,8 @@ impl Time for MilliSeconds {
         let ms = unsafe { epic_rtc_get_milliseconds() };
         MilliSeconds(ms)
     }
+    /// TODO
     fn set_time(&self) {
-        // TODO
     }
 }
 
diff --git a/card10-l0dable/src/trng.rs b/card10-l0dable/src/trng.rs
index 7bceca95a893ee76481ca1f791309619f423bef9..5ba0e6776d0ac0c59609d12ed483e85fe9799887 100644
--- a/card10-l0dable/src/trng.rs
+++ b/card10-l0dable/src/trng.rs
@@ -1,5 +1,8 @@
+//! True Random Number Generator
+
 use card10_sys::*;
 
+/// Read bytes from the True Random Number Generated
 pub fn read(dest: &mut [u8]) -> bool {
     unsafe { epic_trng_read(dest.as_mut_ptr(), dest.len()) != 0 }
 }
diff --git a/card10-l0dable/src/uart.rs b/card10-l0dable/src/uart.rs
index 7dd860e9614086f5ced9c1d16e09104c03306637..ba87d93b9d6cac888ec16018ac3cf9ac9b637fff 100644
--- a/card10-l0dable/src/uart.rs
+++ b/card10-l0dable/src/uart.rs
@@ -2,6 +2,10 @@ use core::fmt::Write;
 
 use card10_sys::*;
 
+/// USB UART, 115200 baud
+///
+/// Supports use of `write!(Uart, "{}", ...);` Use `println!(...);`
+/// for convenience.
 pub struct Uart;
 
 impl Write for Uart {
diff --git a/card10-l0dable/src/vibra.rs b/card10-l0dable/src/vibra.rs
index d2e4d53cd2c4af864dff5334d31bb292b670d814..b32b79d386df04b636e547cc31d4c24d4666f27b 100644
--- a/card10-l0dable/src/vibra.rs
+++ b/card10-l0dable/src/vibra.rs
@@ -1,11 +1,15 @@
+//! Vibration motor
+
 use card10_sys::*;
 
+/// Set vibration motor to off/on
 pub fn set(status: bool) {
     unsafe {
         epic_vibra_set(status.into());
     }
 }
 
+/// Vibrate for a duration
 pub fn vibrate(millis: i32) {
     unsafe {
         epic_vibra_vibrate(millis);